SCSI Controller Fix - Handoff for a Windows/PowerShell Claude session¶
Full path: SINTRAN/Devices/SCSI/SCSI-CONTROLLER-PWSH-HANDOFF.md
(WSL: SINTRAN/Devices/SCSI/SCSI-CONTROLLER-PWSH-HANDOFF.md)
Why this handoff exists: the investigation needs to run natively on Windows/PowerShell - to
launch the RetroCore console executable, reach the Ghidra MCP (the ND-3201 ROM project is
open there), and hit the RetroCore DAP server on 127.0.0.1. A WSL session cannot do those
cleanly. This document is self-contained: everything verified so far, the exact open problem, the
two work-tracks, all file paths (Windows form), and the constraints.
All ND-100 addresses are octal unless prefixed 0x. C# values are hex/decimal.
0. THE MISSION (one line)¶
Make @ENTER-DIRECTORY,,DISC-SCSI-1,0 mount a SCSI disk in the RetroCore ND-100 emulator. It boots
fine, but the mount never completes: the disk directory is never entered. SMD disks mount; SCSI does
not. Fails on SINTRAN K, L, and M.
0.5 RESOLUTION (2026-07-14) - READ THIS FIRST; it overrides parts of Sections 2-4¶
The "final WCONT=5 does nothing" thread is CLOSED as correct emulator behavior. Proven
statically from the driver NPL (SINTRAN/NPL-SOURCE/NPL/IP-P2-SCSI-DRIV.NPL):
- Line ~187
5\/SCCCW; T:=HDEV+WCONT; *IOXTthenGO SCWTIis the unconditional common TAIL of the interrupt handler - every path that services an NCR interrupt falls into it on the way out. It is not a request for a transfer; it means "done servicing this interrupt, re-arm so a future NCR event can interrupt me," then park atSCWTI. SCCCWis zeroed on handler entry (line ~1390=:SCCCW) and only set (to the DMA/direction bit) when a data transfer needs it (line ~746). So5\/SCCCWwithSCCCW=0is the0x0005in the trace; the0x0065on real transfers is the identical line with the DMA bit OR'd in. The final GO is therefore a content-free re-arm by construction, not a stalled transfer.- The interrupt it had just serviced was the Disconnect (target hung up because READ_6 finished). No pending work -> no bus event -> no interrupt should fire. The emulator is right.
- Making GO fire an IRQ would be a BUG: a spurious IRQ re-enters
SCINT, which reads RSTAU, finds the NCR-interrupt bit (11) clear, does nothing, hits line 187, re-arms, parks - an interrupt storm that services nothing.ExecuteGocompleting only whenInterruptFromNCR5386is set matches the hardware contract: a GO completes when the NCR has something to report.
Consequences:
- A/B ambiguity (Section 2.2) is settled as B, statically. No CPU trace is needed to prove it.
The blocker is CPU-side, in SINTRAN's connect/verify layer.
- Track 2 (Sections 3 + 4-Track-2: reverse-engineer 45900E.bin) is DEAD - but NOT for the reason
first given. An earlier draft of this section argued the ROM was "a different board's firmware"
because "the ND-100 would only see a mailbox". That reasoning was WRONG and is retracted.
45900E.bin is the ND-3201's ROM (PN 350001, PCB 3201), and RetroCore explicitly emulates the
3201 (see the header comment in NDBusDiscControllerSCSI.cs).
The correct reason (VERIFIED by an exhaustive byte-level I/O sweep of the ROM): the ND-3201 is
two independent controllers on one PCB. The Z80 + AM9517 + FD1797 are the floppy half. The
NCR 5386 is hardware-decoded straight onto the ND-100 IOX bus and is driven by SINTRAN, which
is the SCSI protocol engine register-by-register - it writes WNCOM (NCR command register), reads
RITRG (NCR interrupt register), sets ATN, and programs the transfer counter and MAR itself.
The Z80 never touches the NCR 5386 anywhere in the 8KB ROM. Evidence:
- The supposed "NCR window" at Z80 ports 0x20-0x3D is a complete AM9517/8237 DMA register
file at base 0x20 (0x28 Command/Status, 0x29 Request, 0x2A Single Mask, 0x2B Mode, 0x2C Clear
Byte Pointer, 0x2D Master Clear, channel addr/count at 0x20+2n / 0x21+2n via the byte-pointer
flip-flop). ram:0717 is the giveaway: OUT (0x2b),A; AND 0x3 - extracting the DMA channel
from a mode byte.
- No port anywhere receives an NCR command code (0x00/0x01/0x03/0x04/0x08/0x09/0x0B/0x24/0x54/
0x94/0xA4); no port is read in the "write command -> read interrupt register" NCR pattern.
- The only command-code stream goes to port 0x70 and they are FD179x opcodes (0x02 Restore,
0x12/0x18/0x1C Seek, 0x88/0x8C Read Sector, 0xC4 Read Address, 0xD0/0xD4 Force Interrupt,
0xF0/0xF2 Write Track), with 0x71/0x72/0x73 = Track/Sector/Data and a BIT 0,A Busy poll.
- The DMA is programmed immediately before every FD1797 data-moving command - it exists to serve
the floppy.
- Corroboration: the ND-3106/3112 manual (ND-11.021.1) confirms the Z80-side ports 0x50-0x57, the
command-block format and FDVSEL (port 0x74 = Floppy Drive Select) as identical to those
floppy controllers. The Z80 half of the 3201 is essentially an ND-3112.
So the ROM is not in the SCSI path. Do not RE it for this bug. (It is the reference if floppy support is ever wanted - see revised Section 3.)
Full corrected board reference: SINTRAN/Devices/SCSI/nd-scsi-3201.md.
- The real target (was every prior static pass's conclusion): after the last-block control-record read (function-42 connect)
(READ_6 of LBA 129311 -> blockSize=1024, lastLBA=129311), the connect/verify layer decides not
to queue the block-0 read. On the Disconnect the driver calls DCTHR (disconnect logical thread)
and goes BUSFP -> SELEC (line ~147), which checks the arbitration queue for the next
operation and finds nothing. The question to chase: why was nothing queued - what does the
connect layer do with blockSize=1024 / lastLBA=129311, and which comparison sends it down the
give-up path? That is where the fix is (likely a geometry/capacity-consistency decision, possibly
the ECAPD check), and it is CPU-side, not in the SCSI controller. See revised Section 4.
1. WHAT IS VERIFIED (ground truth - do not re-litigate)¶
1.1 Running system¶
- Running SINTRAN = L-VSX-500 (byte-verified: resident commoncode word
011300₃ matchestools/sintran-segment-carver/versions/L-VSX-500/resident/SINTRAN-DATA_commoncode.bin; hexD1 0A D0 0D CC 77 B5 00 ...). The DAPprogramfield saysBIGDISK0-M.IMG- MISLEADING, it is L. Correct symbol set = L07. - Disk under test:
DISC-SCSI-1, directory PACK-ONE, 129312 blocks (LBA 0..129311), 1024-byte sectors. Block 0 = valid PACK-ONE master. Block 129311 (last) = a SINTRAN disk area/layout table (checksum XOR = 0, valid). Disk = memory byte-identical (no corruption).
1.2 What the SCSI mount actually does on the wire (from the device trace)¶
The full IOX/CDB/DMA/IRQ transaction is documented in
SINTRAN/Devices/SCSI/SCSI-IOX-TRANSACTION-LOG.md (686 lines: register map,
WCONT/RSTAU bit maps, full chronological ledger, interrupt analysis, Mermaid diagrams). Source
trace: C:\Users\ronny\AppData\Local\trace\file-trace.txt (device-side only; NO CPU Opcodes;
lines in the current capture).
Key verified facts:
- CDBs issued, in order: INQUIRY (0x12), READ CAPACITY (0x25, mislabeled SC_GET_WINDOW in
the C# enum but handled correctly; returns blockSize=1024, lastLBA=129311), READ_6 (0x08,
lba=129311). That is the LAST block, read as the control-record (function-42 connect). Block 0 is never read. No WRITE
CDB is ever issued. Only ONE data CDB total transfers real data.
- The 129311 in the READ_6 is copied verbatim from the READ CAPACITY reply (00 01 F9 1F 00 00
04 00), proven earlier: 0xF91F reaches memory only via the capacity DMA, never in a CPU
register/store before the CDB is built.
- The READ_6 completes cleanly: readBlock 129311 returns data, DMA to ND memory succeeds, RSTAU
reads 0x0208/0x3208/0x5208 (bit 4 = error-summary is CLEAR - the old "STATUS 100020" bit-4
theory is a STALE build and no longer applies), interrupts are raised and acked on RITRG. 24 NCR
interrupts = 24 controller completions = 24 RITRG acks, perfectly balanced.
- The mount is NOT a hang. Console reconstruction: @ENTER-DIRECTORY,,DISC-SCSI-1,0 ->
APPROACHING END OF ACCOUNTING FILE -> returns to @. (The later stop-system/WAIT with IONI
off halt was the operator typing @stop-system, not a driver deadlock.)
- APPROACHING END OF ACCOUNTING FILE (error 243) is a non-fatal warning from the accounting
record write, and the SMD-vs-SCSI comparison is CONFOUNDED because the SMD disk (DISC-75-1) is
the already-entered BOOT directory (fast path, no fresh entry). 243 is coincident, not the cause.
1.3 The SINTRAN L SCSI driver expectation (from NPL source, verified)¶
Source: SINTRAN/NPL-SOURCE/NPL/IP-P2-SCSI-DRIV.NPL. Note the NPL is a
DIFFERENT revision than the running L binary, so treat addresses as indicative, logic as reliable.
- The driver services the NCR once per SCSI phase. Interrupt entry SCINT (066651): reads
RSTAU, decodes the phase/status, issues the next NCR command, then re-arms and waits.
- Re-arm pattern (line ~187): 5\/SCCCW; T:=HDEV+WCONT; *IOXT % ACTIVATE+ENABLE INTERRUPT then
GO SCWTI. 5 = WCONT bits 0+2 = Enable-Interrupt + Active. SCWTI = the driver's wait
state. The driver does not poll - after writing Active it relies on the controller raising a
completion interrupt to re-enter at SCINT.
- Symbols: WCONT=05, RSTAU=04 (IOX offsets). SG110=1100₃ is a system/reference device
constant (NOT a directory-format gate - an earlier 126305 "gate" theory was retracted; that PC
is a legit fast-path vs general-connect router).
2. THE OPEN PROBLEM - the final WCONT=5 that does nothing¶
This is the crux the owner wants nailed. Read the tail of the transaction (trace lines ~1795-1906), reproduced as the essential sequence:
(repeat, once per SCSI phase - the WORKING pattern)
IOX R RSTAU (read status)
IOX W WCONT=0x0000 (disarm)
RAUXS (phase)
IOX R RITRG (read NCR interrupt register -> CLEARS intFromNCR)
IOX W WNCOM=0x0054 (NCR command TransferInfo) <-- an NCR COMMAND is loaded
RAUXS / RNDAT
IOX W WCONT=0x0005 [Enable-Interrupt][Active] -> ExecuteGo -> completion IRQ fires
...
IOX W WNCOM=0x0004 (NCR command MessageAccepted) <-- an NCR COMMAND is loaded
IOX W WCONT=0x0005 -> ExecuteGo -> completion IRQ fires (RSTAU 0x0208)
IOX W WCONT=0x0000 (disarm)
RAUXS=0x0000
IOX R RITRG=0x0004 (Disconnected) -> CLEARS intFromNCR to False
IOX W WCONT=0x0005 [Enable-Interrupt][Active] <-- THE FINAL GO
ExecuteGo MAR=$0004C800
(nothing. no completion. no IRQ. console prints the accounting msg, returns to @)
The difference the owner identified is the key: every working GO is preceded by a WNCOM
write (an NCR command: TransferInfo 0x54 or MessageAccepted 0x04). That NCR command is what
causes the NCR chip to raise a fresh interrupt, which the controller converts into the ND-100
completion IRQ. The final GO has NO preceding WNCOM - no NCR command is loaded - and its WCONT
value is 0x0005 (Active + Enable-Interrupt only; no DMA-enable bit 5, no Write-ND-memory bit 6,
unlike a real data transfer which used 0x0065). So the controller is armed with nothing to do.
2.1 Why the emulator produces no IRQ for it (VERIFIED in the C#)¶
File: E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\NDBUS\NDBusDiscControllerSCSI.cs
- WCONT write handler (~line 1095-1176): setting Active (bit 2) calls ExecuteGo().
- ExecuteGo() (~line 1372): is a stub - it logs the banner and return;s at ~line 1394; all
the real command logic below is dead code (#if _GO_, unreachable).
- The ONLY path that raises the ND-100 completion IRQ is StepGoState() (~line 1242), driven each
Clock() while regs.active, and it fires SetInterruptBit(true) only if
regs.InterruptFromNCR5386 == true (~line 1247-1255).
- Ncr5386_OnInterrupt (~line 763) sets regs.InterruptFromNCR5386 = true when the NCR raises an
interrupt. RITRG read (~line 998) clears it.
- Therefore: final GO -> RITRG just cleared InterruptFromNCR5386 -> no NCR command loaded to set
it again -> StepGoState never completes -> regs.active latches true forever, no IRQ. Dead.
2.2 SETTLED (see Section 0.5): reading B - the controller is correct here¶
This was formerly an open A/B ambiguity. It is now resolved statically from the driver NPL: the
final WCONT=5 is the interrupt handler's unconditional re-arm tail (5\/SCCCW, SCCCW=0), issued
after servicing the Disconnect that ended the completed READ_6. No pending work -> no NCR event -> no
interrupt should fire, and forcing one would build an interrupt storm (Section 0.5). The emulator is
correct; the blocker is CPU-side. Section 2.1's mechanism description is accurate but its
"defect" framing is void - that behavior is the hardware contract.
3. THE ND-3201 CONTROLLER - background only (NOT the bug path; see Section 0.5)¶
DEAD END for this bug - and note the reason has been CORRECTED. An earlier banner here said the ROM was "a different board's firmware" whose Z80 "owns its NCR privately". That was wrong on both counts.
45900E.binis the ND-3201's ROM, and RetroCore does emulate the 3201. The real reason it is a dead end: the Z80 half never touches the NCR. See below.
3.1 The verified architecture: two independent halves¶
The ND-3201 board (Norsk Data "PCB 3201 - N-100 SCSI/Floppy Ctrl", PN 350001) chipset, read directly
from E:\Dev\Repos\Ronny\RetroGhidra\ND3201SCSI\CHIP doc\Controller-info.pdf:
- Z80 CPU + 2x Z80 CTC + NCR5386 (SCSI protocol controller) + AM9517 DMA controller +
FD1797 (floppy) + 8 KB SRAM (TMM2063). LEDs include "Z80 DMA". Connectors: (A) floppy,
(B) SCSI bus, (C) ND-100 system bus.
The board is two independent controllers sharing one PCB and one host connector (VERIFIED by an exhaustive byte-level I/O sweep of the ROM - see Section 0.5 for the full evidence):
| Half | Hardware | Driven by |
|---|---|---|
| SCSI | NCR 5386 | The ND-100. NCR register file is hardware-decoded onto the IOX bus; SINTRAN is the SCSI protocol engine |
| Floppy | Z80 + AM9517 + FD1797 | The Z80 firmware (45900E.bin), ND-3112-style command block over ports 0x50-0x57 |
The Z80 never touches the NCR 5386 anywhere in the 8KB ROM. The chipset list above is accurate; the conclusion previously drawn from it ("so the Z80 must drive the NCR") was an inference, and the bytes refute it.
3.2 What this means for ExecuteGo()¶
RetroCore's model is faithful, not a shortcut. Exposing the NCR registers directly on the ND-100 IOX map and not emulating the Z80/AM9517 is exactly what the hardware does for the SCSI half. The Z80 and AM9517 belong to the floppy half, which the emulator simply does not implement (a real gap - but a floppy gap, not a SCSI one).
So the empty ExecuteGo() is not a missing-Z80 stub, and there is no missing completion
behavior for the bare final GO. StepGoState() raising the IRQ only when
regs.InterruptFromNCR5386 is set matches the hardware contract: a GO completes when the NCR has
something to report. Per Section 0.5, the correct response to the final bare WCONT=5 is silence.
3.3 When the Z80 ROM does matter¶
Only if floppy support is ever wanted. Then 45900E.bin is the authoritative spec for the floppy
half, and the corrected map (verified port map, RST/IM2 vectors, FD1797 command values, POST/event
error codes, ND-3112 correspondence) is in:
SINTRAN/Devices/SCSI/nd-scsi-3201.md
4. TWO WORK-TRACKS¶
Track 1 - Unblock live debugging (the A/B ambiguity it was meant to settle is now CLOSED)¶
Scope change per Section 0.5. Track 1's purpose was to get a CPU trace to discriminate A vs B. That is settled statically as B - no trace is needed for it. What remains useful here is only 1a (the DAP thread-safety fix, so live debugging works at all), because you will still want a working debugger to chase the CPU-side connect/verify layer. 1b's specific mission (park-at-SCWTI vs not) is obsolete -
GO SCWTIis the driver's normal exit on every path, so "parked at SCWTI" was never going to discriminate anything. Keep 1b's DAP quirks as reference for whatever breakpoints you do set.
1a. DAP thread-safety fix - ALREADY APPLIED to RetroCore source (build-clean, NOT runtime-tested).
Root cause (verified, documented in
SINTRAN/Emulator/RETROCORE-DAP-THREADSAFETY.md): the crash
NullReferenceException at Instructions.RADD() -> doROP() is NOT an RADD bug. It is a race: two CPU
execution pumps run concurrently under DISJOINT locks. One thread executes
regs.fetchedInstructionRef = null; (CpuND100.cs:298) while the other is mid-instruction reading
regs.fetched.roRAD (Instructions.RegisterOperations.cs:304). The single serialization point is
machine.CpuExecutionGate; machine.DapOwnsCpu signals DAP ownership.
The racing pump was the console BOOT command, BootDevice at
E:\Dev\Repos\Ronny\RetroCore\Emulated.Debugger\DebugCommands.Devices.cs:78. It ran
machine.Run((ushort)startAddress, -1) (run-until-stop) holding no lock and never checking
DapOwnsCpu. The console hosts the DAP TCP server
(Emulated.Debugger\DebugCommands.DebugStepper.cs:115); a DAP continue spawns the gated
DapRunLoop (MachineBaseDAP.cs:270-272) which returns immediately and leaves the ND-100[...]>
prompt live, so a BOOT at that prompt started a second, un-gated Run() -> two pumps -> the NRE.
(doRun already refused in this state; BootDevice was the hole.)
Applied edits (verify with git diff in the RetroCore repo; build-clean, 0 errors; owner has NOT
committed - do not commit without asking):
1. E:\Dev\Repos\Ronny\RetroCore\Emulated.Debugger\DebugCommands.Devices.cs (~78): BootDevice now
refuses when machine.DapOwnsCpu || !Monitor.TryEnter(machine.CpuExecutionGate) (same message as
doRun), else runs inside try/finally { Monitor.Exit(gate) }. This is the fix that matters for
the console host.
2. E:\Dev\Repos\Ronny\RetroCore\Emulated.UI.Avalon\Services\EmulationService.cs (~1567): frame pump
now skips when DapOwnsCpu/gate held, else runs under the gate (was _dapLock only).
3. E:\Dev\Repos\Ronny\RetroCore\Emulated.UI.SDL2\RetroEmulation.cs (~483): frame pump now takes the
gate / respects DapOwnsCpu (was lockless).
All three use non-blocking Monitor.TryEnter and SKIP/refuse when the gate is held, so hosts yield
to the DAP session without blocking (no deadlock; matches lock order _dapLock -> _cpuExecutionGate).
Residual (a reviewer / this session must check):
- Not runtime-verified - confirm a DAP continue on a booted L no longer crashes.
- Two console dev-tool steppers remain un-gated and were DEFERRED (they need an external remote/SIMH
and run step-by-step doing register compares): gRPC StepCpuND100/SyncStepCpuND100
(DebugCommands.DebugStepperND100.cs:79,136) and the SIMH cross-compare loop
(DebugCommands.SimhSync.cs:236,240). Not in the SCSI-mount path; ignore unless you use them.
- Avalon skips its Run() each frame while DAP owns the CPU - confirm existing frame pacing prevents
a busy-spin during long DAP sessions (pacing note, not a deadlock; irrelevant to the console host).
- The transient regs.fetchedInstructionRef = null; at CpuND100.cs:298 was left in place (minimal
change); no null-guard was added inside doROP (that would mask, not fix, the race).
1b. Get the decisive CPU trace. With the emulator surviving a DAP session:
- Restart the console, boot L with the SCSI disk to @.
- Set a breakpoint at SCINT (the SCSI interrupt entry; L07 resident address - find it: NPL name
SCINT, the routine that does T:=HDEV+RSTAU; *IOXT on entry) and observe whether the driver
re-enters SCINT after the final GO (=> it got an IRQ, reading B) or never does / is parked
at SCWTI (=> reading A, controller is the blocker).
- Equivalent: enable a CPU+device trace (WIDE_CPU_TRACE) and read the PC stream after the final GO.
- DAP quirks (learned the hard way) are in
SINTRAN/Devices/SCSI/SCSI-MOUNT-DEBUG-HANDOFF.md sections 3-4: breakpoints
ACCUMULATE (only a restart clears them), continue re-triggers the current PC (budget 2 continues
per bp), no single-step, use HIGH addresses only, console is owned by DAP while paused. From
Windows, DAP is 127.0.0.1:4712 (no WSL IP juggling needed).
This one trace decides whether Track 2 (controller rewrite) is on the critical path or a detour.
Track 2 - CANCELLED. The real target: SINTRAN's connect/verify layer (CPU-side)¶
Per Section 0.5 the controller behaves correctly and the ND-3201 Z80 RE is a dead end. The fix is
CPU-side: after the last-block control-record read (function-42 connect) returns blockSize=1024 / lastLBA=129311, the connect/
verify layer decides not to queue the block-0 (directory-master) read, and on the Disconnect goes
DCTHR -> BUSFP -> SELEC (line ~147), finds the arbitration queue empty, and gives up.
Chase this (needs the DAP fix from Track 1a to debug live):
1. Find where the mass-storage connect/verify layer consumes the probe result. The probe is issued by
the resident mass-storage connect/verify overlay (uncarved in the L carve). READ CAPACITY reports
raw 129311; block-0's PACK-ONE master claims capacity 61036 pages = 122072 usable blocks. The
suspect is a capacity/geometry-consistency comparison (candidate: the ECAPD check - reporting
122071 was already rejected by it, and raw 129311 also does not mount, so the value SINTRAN
wants is neither; pin what it actually compares).
2. Live method (PowerShell): after the DAP fix, boot L, breakpoint the connect/verify path and the
ENDIR/COLDE/GDIRE workers (L07 addresses in SCSI-MOUNT-DEBUG-HANDOFF.md Section 4), run the
mount, and watch which comparison branches to the give-up path instead of queueing the block-0
read. Read the device datafield (dspace:) built from the probe and diff SCSI vs the working SMD
datafield.
3. The fix is then either an emulator-side geometry/capacity value the controller should present so
the consistency check passes, OR (if it is a genuine SINTRAN limitation) a documented
incompatibility - decide with the owner once the exact comparison is pinned. Do NOT blind-guess
capacity numbers (Section 5 note).
The former Ghidra RE instructions are retained below only if someone wants to annotate the ND-3201 firmware for its own sake - it will NOT fix this bug.
(archived - ND-3201 Ghidra annotation, not on the bug path)¶
Do the real Ghidra work (the owner explicitly does NOT want the stale PDF export
Z80ASM_SCSI.pdf - work the live program). Ghidra is open with the ROM.
Ghidra project: E:\Dev\Repos\Ronny\RetroGhidra\ND3201SCSI\ND3201SCSIController.gpr
ROM binary: E:\Dev\Repos\Ronny\RetroGhidra\ND3201SCSI\45900E.bin (8 KB Z80, ram:0000-1fff).
Ghidra MCP tools available in the Windows session (from that project's
.claude\settings.local.json): mcp__ghidra__get_program_info, list_functions,
get_disassembly, get_code, get_function_info, xrefs, get_call_graph, list_strings,
get_hexdump, search_bytes, disassemble, rename_symbol_batch, set_comment, list_programs,
get_task_status. (These are NOT reachable from WSL - that is the whole reason for this handoff.)
Do this in Ghidra:
1. Map the Z80 I/O ports and memory-mapped windows. From the ROM's MAIN (ram:003b): it does
OUT (0x70),A; OUT (0x54),A; OUT (0x55),A; OUT (0x56),A at init, and the main loop reads a
memory-mapped region at 0x2000. Identify which ports are the NCR5386, which are the AM9517 DMA
controller, which are the Z80 CTC, and which are the ND-100 interface (the WCONT/RSTAU side / the
shared SRAM mailbox). Use the chip docs: E:\Dev\Repos\Ronny\RetroGhidra\ND3201SCSI\CHIP
doc\NCR5386_protocol_controller.pdf, ...\Z80-CTC.pdf, and the AM9517 datasheet; the SCSI
protocol references are in ...\ND3201SCSI\Books\.
2. Rename FUN_ram_*, LAB_ram_*, and I/O accesses to describe their role (e.g.
ncr_write_cmd, dma_setup, nd100_irq_raise, wait_ncr_int, handle_reselect), and
set_comment the key logic. Use rename_symbol_batch and set_comment. Follow xrefs and
get_call_graph from the RST/NMI vectors and MAIN.
3. Answer the controlling question: when the ND-100 sets the Active/GO bit (and the NCR is idle /
just disconnected, with no fresh command loaded), what does the firmware do, and when/how does
it raise the completion interrupt to the ND-100? Specifically: does a GO after a target
Disconnect arm a reselection wait, auto-complete, or genuinely do nothing until new work? That is
the direct answer to the Section 2 ambiguity from the hardware's own spec.
4. Write the findings (renamed map + the WCONT/GO/interrupt state machine) to a doc, e.g.
SINTRAN/Devices/SCSI/ND3201-FIRMWARE-ANALYSIS.md.
Then map into the C# (E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\NDBUS\NDBusDiscControllerSCSI.cs
and the NCR model in E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\NCR\SCSI\NCR5386\):
- If the firmware shows a GO must produce a completion/interrupt in the observed state, implement
that in ExecuteGo()/StepGoState() (currently ExecuteGo is an empty stub - Section 2.1).
- Decide with the owner whether it is a tiny targeted fix (make the specific bare-GO-after-disconnect
path complete) or a larger refactor toward modeling the Z80/AM9517 handshake. Do NOT rewrite blind
- Track 1's trace tells you if the controller is even the blocker.
5. C# CONTROLLER CODE MAP (RetroCore, Windows paths)¶
- Controller:
E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\NDBUS\NDBusDiscControllerSCSI.cs- IOX read switch incl.
RSTAUbit assembler (~874-946),RITRGack (~996-1000). - IOX write switch incl.
WCONThandler (~1064-1176),WNCOM/WTC*/WHMAR/WDESI(~1189-1219). Ncr5386_OnInterrupt(~763),Clock->StepGoState(~828-837),StepGoState(~1242),ExecuteGostub (~1372-1394, dead code below).
- IOX read switch incl.
- NCR 5386 chip:
E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\NCR\SCSI\NCR5386\NCR5386SCSI.cs(+.CommandHandling.cs,.StateHandling.cs,Enums.cs). Interrupt-register bits (Enums.cs~847-873):FunctionComplete=0x01,BusService=0x02,Disconnected=0x04. - Disk target:
E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\Common\SCSI\SCSIHDD.cs,...\SCSIHDDMicropolis.cs(READ CAPACITY reply, READ_6). READ CAPACITY reports raw129311; do NOT blind-guess capacity numbers -122071was already rejected by SINTRAN'sECAPDcheck and raw129311also does not mount. The gate is CPU-side / firmware, not a magic capacity value. - CPU (for the DAP race):
E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\ND100\CpuND100.cs(TickCpuUnsafe276,fetchedInstructionRefnull at 298,ExecuteDecodedInstructionguard 315),Instructions.RegisterOperations.cs(doROP302, deref at 304),Registers.cs:252(fetchedproperty). DAP host:E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\Common\Machine\MachineBaseDAP.cs(_cpuExecutionGate191,DapRunLoop262). Console host:RetroCore\Program.cs,RetroCore\FrontendCommands.cs,RetroCore\Menu.cs,Emulated.Debugger\DebugCommands.Machines.cs.
6. REFERENCE DOCS (NDInsight, Windows paths)¶
SINTRAN/Devices/SCSI/SCSI-MOUNT-DEBUG-HANDOFF.md- live-debug state, DAP quirks, L07 addresses, ruled-out theories (read sections 6b, 7, 7b).SINTRAN/Devices/SCSI/SCSI-IOX-TRANSACTION-LOG.md- full IOX ledger + interrupt analysis + diagrams (the device-side reference for Section 2).SINTRAN/Emulator/RETROCORE-DAP-THREADSAFETY.md- the DAP race analysis.SINTRAN/Devices/SCSI/scsi-transfer-status.md,...\scsi-open-last-block-read.md,...\mount-gate-diff.md,SINTRAN/Filesystem/code-logic/scsi-mount-geometry.md- prior RE (note the retractions inside them; the CORRECTED/6b sections supersede earlier claims).- SINTRAN SCSI driver NPL:
SINTRAN/NPL-SOURCE/NPL/IP-P2-SCSI-DRIV.NPL(+IP-P2-SCSI-DISK.NPL,IP-P2-SCSI-OPDI.NPL). - Trace:
C:\Users\ronny\AppData\Local\trace\file-trace.txt(regenerate perSINTRAN/Devices/SCSI/SCSI-TRACE-HOWTO.md;SCSIDevice.ScsiTracedefault true;Logger.EnableLogger(Device, File)).
7. CONSTRAINTS (always)¶
- RetroCore is a separate repo. Editing source to implement a fix is fine, but do NOT commit
there without the owner's explicit OK, and never run destructive git commands (
reset,checkout,restore,clean,stash, branch switch,--no-verify). - Never mention any AI assistant/tool in code, comments, commits, or documents.
- Full absolute paths whenever a file is named (this is a handoff read cold by another session).
- No Unicode in
.cs/.ASM/.NPLor anything fed to period compilers/assemblers. - Bytes/traces/ROM are ground truth; NPL is a different revision; verify, do not claim. Label VERIFIED vs INFERRED.
- Mermaid diagrams follow
MERMAID_COLOR_STANDARDS.md(WCAG 2.1 AA).
8. RECOMMENDED ORDER (revised per Section 0.5)¶
- Verify the DAP thread-safety fix (Track 1a, already applied to source): confirm the console
dotnet buildis clean and a DAPcontinueon a booted L no longer crashes. This is the enabler for live CPU debugging. - Chase the CPU-side blocker (Track 2, revised): with live debugging working, find the connect/
verify comparison that consumes
blockSize=1024 / lastLBA=129311and branches to the give-up path (DCTHR -> BUSFP -> SELEC, empty queue) instead of queueing the block-0 read. Pin the exact comparison (capacity/geometry consistency; candidateECAPD). - Implement the fix where the pinned comparison lives - likely an emulator-side geometry/capacity
value the SCSI target must present, or a documented incompatibility. Do NOT touch the SCSI
controller's GO/interrupt logic and do NOT reverse-engineer
45900E.bin- both are confirmed off the bug path (Section 0.5).