ND-500 Emulator Discrepancy Audit - NDBusND500IF.cs vs the verified spec¶
Target: E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\NDBUS\NDBusND500IF.cs
(1851 lines as of 2026-07-08) plus SimulatedND500.cs, NDSharedMemory.cs,
IND500Cpu.cs in the same repo.
Spec: ND500-BUS-INTERFACE-REFERENCE.md
("REF" below); evidence in
ND500-EVIDENCE-AND-CONTRADICTIONS.md
("dossier").
Rule for this document: NO code is changed by the documentation task that produced it. This is the burn-down list for a separate RetroCore fix task. C# line numbers reference the 2026-07-08 state of the file and will drift as fixes land - anchor on member names.
Severity: CRITICAL = SINTRAN cannot work correctly against this behavior / protocol is fabricated. HIGH = wrong values or decode visible to the driver. MEDIUM = wrong in corner cases or against hardware (not exercised by SINTRAN's normal path). LOW = documentation/cleanup.
Recommended fix order¶
- D01/D02 - delete the fabricated TAG message protocol; introduce the message engine (REF 5, 6, 7).
- D03 - interrupt gating (CONTROL bit 0).
- D04-D08 - four-mode IOX decode corrections (REF 3.2).
- D09/D10 - DMA word stride + shared-memory endianness unification.
- D11-D14 - stop/clear semantics (RETG5, MCLR5, TERM5, LSTA5 masking).
- D15+ - cleanups.
Each fix should quote its D-number in the commit and flip the Status field here.
CRITICAL¶
D01 - The TAG-IN/TAG-OUT message protocol is fabricated¶
- Where:
OnND500WritesTag(:1167-1200),ProcessTagIn(:1495-1552),TagInHighLevelCodes/TagOutHighLevelCodes(:719-755),TagRegisterFormat(:760-779); consumed bySimulatedND500via theTagWrittenevent. - Current: ND-500 -> ND-100 signalling is modeled as TAG-IN codes 8 = MonitorCallRequest, 9 = PageFaultRequest, 16 = OperationComplete (with a process number in bits 8-11), which set ND500Finished and raise the interrupt.
- Correct: No such codes exist. TAG-IN codes are 4-bit register strobes (8 = DIEN, 9 = DUEN; 16 unrepresentable) - REF 10.1, dossier C6. ND-500 -> ND-100 signalling is: microcode writes the answer into the MESSAGE (N5STA status word, STOPR stop reason), sets STATUS "finished"/stop bits, and raises level 12; the driver walks the message queue (REF 7). Monitor calls arrive as messages with MICFU in {3MONCO,3TRACO,3START,3WMONCO} and STOPR in {MOCALL,5FMOCALL} (REF 7.4).
- Fix: remove the high-level TAG code path entirely; replace with the message engine (D02). Keep the TAG registers only as the hardware strobe channel (REF 10.1-10.2) if microcode-level tests/loader support is wanted.
- Status: OPEN
D02 - No message engine; ExecuteND500Operation reports instant completion¶
- Where:
ExecuteND500Operation(:1824-1848), TODO at :1826. - Current: on CONTROL activate, extracts bits 8-14, does nothing with the attached CPU, immediately clears Busy, sets Finished and interrupts. The CPU is never stepped; the MAR-addressed message is never fetched.
- Correct: activate (CONTROL bit 2) locks the interface (STATUS bit 5) and starts the microcode; the operation = fetch the message at MAR from ND-100 memory, execute MICFU, write answer status/fields back, THEN finish + interrupt (REF 5.4, 14 item 5). Completion must also clear the lock or respond to TERM5 (D12) or SINTRAN's timeout logic will master-clear the interface (REF 14 item 7).
- Fix: implement the message lifecycle against the attached IND500Cpu (or the simulated one); message layout in REF 6.2; status-word semantics REF 6.3.
- Status: OPEN
D03 - Interrupt gate reads the wrong enable bit¶
- Where:
CheckTriggerInterrupt(:1206-1220). - Current: raises level 12 iff STATUS bit0 (InterruptEnabled) AND STATUS bit3 (ND500Finished). Nothing ever copies CONTROL bit0 into STATUS bit0, so enabling interrupts via the CONTROL register (the only thing SINTRAN does) never arms the gate.
- Correct: the software enable is CONTROL bit 0 "Enable interrupt from ND-500" (REF 4.1); STATUS bit 0 "Interrupt enabled" REFLECTS the enable state and is not settable via LSTA5 (TST26, REF 4.2). Gate on CONTROL bit 0; mirror it into STATUS bit 0 on read.
- Fix: gate on controlRegister bit0; derive statusRegister bit0 from it.
- Status: OPEN
HIGH - four-mode IOX decode (REF 3.2 is the normative table)¶
D04 - RMAR5/LMAR5 unguarded¶
- Where:
ReadMarX2(:1564-1579),LoadMarX2(:1664-1678). - Current: MAR reads/writes work in every mode.
- Correct: MAR access exists only while UNLOCKED (both test and not-test); locked modes expose no MAR (REF 3.2; TMP table 1 lists no RMAR/LMAR).
- Status: OPEN
D05 - Read CONTROL guard wrong¶
- Where:
ReadControlRegister(:1592-1595). - Current: returns CONTROL when
!isLocked || isTestMode- i.e. also in unlocked+not-test. - Correct: RCON decodes ONLY in test mode (both unlocked+test and locked+test); it is absent from both not-test columns (REF 3.2).
- Status: OPEN
D06 - Master Clear and Terminate gating wrong; unlocked master clear is a no-op¶
- Where:
Writecases MasterClear (:1721-1737) and Terminate (:1739-1754);Reset(:1643). - Current: Master Clear and Terminate only act when
isLocked && !isTestMode; issued while unlocked+not-test they do nothing. - Correct: MCLR (offset 6) and TERM (offset 7) are available in BOTH not-test columns - locked and unlocked (REF 3.2). MCLR is a strobe that restarts the microprogram at control-store address 0 (REF 3.2 notes, dossier 4.2); it should work regardless of lock state when not in test mode. In unlocked+test both offsets become "Load DATA register" (already partially modeled).
- Status: OPEN
D07 - Offsets 14 and 16 modeled as "Read LOCKED" - they are lock COMMANDS¶
- Where:
Register.ReadLockedMaybe/Register.ReadLockedand the shared read case (:1625-1629); enum docs :337, :352-355. - Current: offsets 14 (074) and 16 (076) READ back
isLocked ? 1 : 0in every mode; no write behavior. - Correct: 074 = SLOC "Set locked" (write/command - SINTRAN's activate sequence ends with it, REF 5.3); 076 = UNLC "Release locked" (write/command - first step of 5MCST, REF 9.1). Both exist in both not-test columns; neither is documented as a read of the lock state (REF 3.2). The "ReadLockedMaybe/ReadLocked" model came from the retired old/ND-500-INTERFACE.md guesswork.
- Fix: implement SLOC5/UNLC5 as lock set/clear on WRITE (and IOX-read variants should not return lock state unless new evidence appears).
- Status: OPEN
D08 - Offset 13 and offset 15 decode¶
- Where:
WriteDataXcase (:1769-1780),WriteDatacase (:1782-1790). - Current: offset 13 writes DATAX only when locked+not-test (misses unlocked+not-test); offset 15 ("WriteData") writes DATA-low whenever locked (including locked+test, where TMP exposes nothing).
- Correct: offset 13 (WDAT/LLOW5) = Write DATAX in BOTH not-test columns; load lower limit in unlocked+test. Offset 15 (CLKD5) = "Clock DATA" only in locked+not-test (REF 3.2).
- Status: OPEN
D09 - 32-bit ND-100 DMA uses a +2 WORD stride (skips a word)¶
- Where:
ReadND100Memory(:1395-1405),WriteND100Memory(:1410-1415);DMARead/DMAWriteinNDBusDeviceBase.cs(:515-548) treat the argument as a word address and shift left by 1. - Current: the second half of a 32-bit value is accessed at
addr+2(word address), i.e. 4 bytes above the first half, leaving the word at addr+1 untouched. - Correct: messages are contiguous 16-bit words in ND-100 memory (REF 6.2); consecutive words are at addr and addr+1. The +2 stride corrupts every multi-word transfer.
- Status: OPEN
D10 - Two shared-memory models with conflicting 32-bit endianness¶
- Where:
NDSharedMemory.ReadWord32/WriteWord32(NDSharedMemory.cs:110-131, little-endian, commented "little-endian for ND-500") vs the MPM Port B pathReadMpmWord32/WriteMpmWord32andSimulatedND500local memory (SimulatedND500.cs:257-529, big-endian, commented "ND-500 is BIG-ENDIAN"). - Current: the same CPU model uses both conventions on different memories.
- Correct: ND-500 is big-endian (project memory + REF sources); one shared memory model. Note also the emulator-invented constants: MPM at 0x00420000 with 8MB and the bit-31 "MPM address" convention are EMULATOR conventions, not hardware (the hardware uses per-port BASE/window translation, REF 8.4) - keep them if convenient but document them as emulator-internal.
- Status: OPEN
MEDIUM¶
D11 - RETG5 semantics incomplete¶
- Where:
ReturnGatecase (:1791-1815). - Current: bit1 sets ND500MicroClockStopped, clears Busy, and UNLOCKS the interface.
- Correct: RETG5 bit1 = stop bit (micro stop -> STATUS bit 9 5CLOST), bit0 = reverse tag bus (REF 3.2 notes, dossier 4.5). No evidence that RETG5 clears the lock - SINTRAN's 5MCST issues UNLC5 separately BEFORE RETG5 (REF 9.1). Remove the unlock side effect; model bit0 if TAG-level fidelity is wanted.
- Status: OPEN
D12 - TERM5 completion semantics¶
- Where: Terminate case (:1739-1754).
- Current: immediately unlocks, clears Busy, sets Finished, interrupts.
- Correct: TERM5 REQUESTS a stop; the microcode acknowledges by releasing the lock, and SINTRAN polls RSTA5 until 5ILOCK clears (bounded spin), falling back to 5MCST on timeout (REF 9.2). Instant-unlock is acceptable only as a simplification if the attached CPU model is synchronous - but Finished/interrupt on TERM5 is not evidenced; the driver expects the LOCK BIT to clear, not an interrupt.
- Status: OPEN
D13 - LSTA5 must not set bits 0, 5, 9, 15¶
- Where:
LoadStatusRegistercase (:1680-1686). - Current:
statusRegister = valuewholesale (guard!isLocked && isTestModeis correct). - Correct: bits 0 (int enabled), 5 (locked), 9 (clock stopped) and 15 (CONTROL bit 15 mirror) are not settable via LSTA5 (TST26; REF 4.2). Mask them.
- Status: OPEN
D14 - CONTROL bit 4 side effect missing¶
- Where:
LoadControlRegister(:1688-1719) -ND500ProgrammedCleartriggers onlynd500Cpu.Reset(). - Current: no effect on STATUS.
- Correct: programmed clear (bit 4 = 1) also CLEARS the DMA-error status bit 6 (TST25; REF 4.1).
- Status: OPEN
D15 - ProcessTagOut is a real hardware feature attached to the wrong port¶
- Where:
ProcessTagOut(:1434-1488) - invoked when the ND-100 writes offset 071 (WriteTagOut). - Current: the ND-100's TAG-OUT write executes the TAG-OUT command table (read/write MAR/STATUS/CONTROL/DATA vs ND-100 memory) on the spot.
- Correct: offset 071 writes the TAG-IN register on the 5015 (strobe codes, REF 10.1). The 8-code table the method implements is the TAG-OUT register that the ND-500 MICROCODE drives toward the 3022 (REF 10.2) - i.e. it belongs on the CPU/microcode side, not on the IOX write path. (The code values 0-7 themselves match TMP section 3.13 and can be reused on the correct side.)
- Status: OPEN
LOW / cleanups¶
D16 - Enum XML octal typo¶
Register.ReadLocked doc says "IOX +16 (070)"; offset 14 decimal is octal 076
(:352-355). Overtaken by D07 anyway.
D17 - Dead field¶
ioxRegister (:850) declared, never used, commented //32 bit IOX register ??.
D18 - Direction wording¶
ProcessTagOut comment says "Process TAG-OUT command from ND-500" while the
method runs on ND-100 writes (:1431); contradicts the file's own TAG naming
diagram. Overtaken by D15.
D19 - Header decode table incomplete¶
The four-column table in the file header (:60-79) omits RETG5 at 077 and contains the "Read LOCKED??" guesses; replace it with the REF 3.2 table (cite TMP section 3.14).
D20 - IDENT does not clear a device interrupt-enable¶
NDBusND500IF does not override IDENT (base note at NDBusDeviceBase.cs:217-219).
Verify against the ND-100 interrupt model used by other devices in the repo; align
with however the 3022 should behave after ident (no direct evidence either way -
UNVERIFIED, flag for hardware-doc follow-up).
What the current implementation gets RIGHT (keep)¶
- IOX bases and ident codes per thumbwheel (:876-901) match ND-06.015.02 exactly: 60/1060/660/760/560 with idents 16/116/36/114/76 (REF 3.1).
- Interrupt level 12 (:905) matches the SINTRAN driver (REF 7.1).
- ControlWordBits enum (:375-426) matches TMP section 3.1 bit-for-bit (bit0 enable, bit2 activate, bit3 test, bit4 programmed clear, bit5 disable TAG-IN decode, bit6 DMA error, bit7 chaining, bits8-14 operation).
- StatusRegisterBits enum (:429-493) matches TMP section 3.2 including bits 10-14 stop reason and bit15 CONTROL-15 mirror.
- LoadStatusRegister mode guard (
!isLocked && isTestMode) matches the four-mode table (needs only the D13 bit mask). - MAR two-step access flip-flops (:1564-1579, :1664-1678) - the half-order matches TMP section 3.3 (load MS first, read LS first); only the mode gating (D04) is wrong.
- Big-endian MPM word access on Port A/B helpers - consistent with ND-500 byte order (keep as the survivor of D10).
Version history
| Date | Change |
|---|---|
| 2026-07-08 | Initial audit from the Phase 1-3 exploration + evidence dossier |