TRACK B - Shared ND-500 CPU Interface Design (octobus attach path)¶
Date: 2026-07-21
Author: TRACK B design session (analysis only, no code edited)
Goal: let the REAL microword CpuND5000 wire into the octobus attach path exactly
where the functional CpuND500 does today, so the boot harness can boot the genuine
128-bit control-store swapper on the microcode CPU.
Grades: [V] verified from source read this session; [I] inferred from the code but not proven; [OPEN] cannot verify from code, needs a decision or a live experiment.
All paths are absolute. This is DESIGN ONLY - nothing here has been implemented.
0. Executive summary¶
- The station's stored CPU field is already an interface (
IND500Cpu), but the three wiring entry points the task cares about -AttachNd5000Cpu,AttachRealCpu, and theNd500CpuProcessBridgeconstructor - are all typed to the CONCRETECpuND500class, not to an interface. [V] CpuND5000lives in the leaf NuGetHackerCorpLabs.Emulation.CPU.ND5000, which references only the HackerCorpLabs.Emulation.* packages and does NOT (and by dependency direction cannot) referenceEmulated.HWwhereIND500CpuandCpuND500live. [V] ThereforeCpuND5000CANNOT implementIND500Cpuor any RetroCore interface without a dependency inversion. That rules out "make CpuND5000 implement the interface" and makes an ADAPTER (living inEmulated.HW, wrappingCpuND5000) the only non-invasive option.- Recommended: EXTRACT a new interface on the RetroCore side (over the concrete-
CpuND500surface the attach path uses) + write aCpuND5000ADAPTER inEmulated.HW.CpuND500already has every member, so it implements the extracted interface trivially. The microcode session'sCpuND5000.csneeds ZERO changes. - The hard part is NOT the interface plumbing - it is that the functional path installs a
C# servicer + bridge that REPLACE the microcode's mailbox/MON/context handling, whereas
CpuND5000does that work itself in real microcode. WiringCpuND5000through the SAME bridge/servicer is semantically wrong. The microcode CPU needs a THINNER attach variant. This is the biggest design decision and the biggest [OPEN] (boot-from-CS model).
1. Task 1 - COMPLETE surface invoked on the CPU object today¶
Two callers touch the CPU: the station (through the IND500Cpu field _cpu, plus the
concrete-typed AttachRealCpu), and the process bridge (through the concrete CpuND500).
Plus the machine attach helper and its attachment handle.
1a. Station - OctobusND5000Station.cs¶
File: E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\NDBUS\OctobusND5000Station.cs
Members reached through the IND500Cpu _cpu field (interface-typed - already abstract):
| Member | Kind | Site |
|---|---|---|
TagWritten (subscribe/unsubscribe) |
event Action<ushort> |
line 483, 490 |
SetMpmMemory(RAM, uint) |
method | line 497, 665 |
Reset() |
method | line 1304 |
CpuRegisters |
property RegistersBase (then .stopMode) |
line 1308-1310 |
Members reached through the CONCRETE CpuND500 cpu parameter of AttachRealCpu (lines
529-552) - these are NOT on IND500Cpu:
| Member | Kind | Site |
|---|---|---|
WakeRunThread() |
method (also used as Action for MailboxDoorbell +=) |
line 539 |
OnRunThreadPark |
settable Action field |
line 540 |
regs.stopMode |
field-of-field (ND500 Registers) |
line 546, 547 |
StartRunThread(string) |
method | line 548 |
1b. Process bridge - Nd500CpuProcessBridge.cs¶
File: E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\ND500\Servicer\Nd500CpuProcessBridge.cs
Constructor param is concrete CpuND500 cpu (line 40, 54). Every call is on the concrete class:
| Member | Kind | Site |
|---|---|---|
MonitorCallSink (set/clear) |
property IMonitorCallSink |
line 59, 87 |
TrapSink (set/clear) |
property ITrapSink |
line 60, 89 |
SetND100PrivateOffset(uint) |
method | line 75 |
regs.stopMode |
field (read/clear WAIT bit) | line 118, 123, 327, 364, 372, 438, 454 |
RunThreadActive |
property bool |
line 122, 156, 338, 373, 455 |
WakeRunThread() |
method | line 123, 157, 331, 339, 374, 456 |
StartProcessFromRegisterImage(ushort, ushort[]) |
method | line 131 |
ReadMpmByte(uint) |
method (on IND500Cpu too) |
line 202, 208 |
regs.PC |
field (read + write) | line 269, 276, 300 |
regs.CAD |
field | line 276 |
MapExistingPhysicalRegion(uint,uint,bool,uint,uint,bool) |
method | line 277, 279 |
EnableProgramMMU() |
method | line 297 |
EnableDataMMU() |
method | line 298 |
StartProcessFromContextBlock(uint) |
method | line 335 |
regs.I1 |
field (write) | line 369, 443 |
regs.ST.K |
field-of-field (write) | line 370, 444 |
WriteVirtualMemory32(uint,uint) |
method | line 420 |
WriteVirtualMemory(uint,byte) |
method | line 450 |
ReadVirtualMemory32(uint) |
method | line 480 |
Bridge also uses the servicer (not the CPU): servicer.ProcessZeroRegisterImage,
ProcessZeroFirstRegister, ResiwrLog, ActiveProcessMessageAddress, Answer*. Those
are servicer-side and out of the CPU contract.
1c. Machine attach + handle - ND100Machine.ND5000.cs¶
File: E:\Dev\Repos\Ronny\RetroCore\Emulated.Machines\ND\ND100\ND100Machine.ND5000.cs
| Member | Kind | Site |
|---|---|---|
new CpuND500(Registers, SystemBusAdapter) |
ctor | line 134 |
passes CpuND500? cpu param into AttachCpu / AttachRealCpu |
- | line 147, 164 |
Nd5000CpuAttachment.Cpu typed CpuND500 |
property | line 237 |
Cpu.OnRunThreadPark = null |
field | line 268 |
Cpu.StopRunThread() |
method | line 269 |
regs type note: on CpuND500, regs is the ND-500 Registers class with .stopMode,
.PC, .CAD, .I1, .ST (with .K). [V] The bridge reaches into these directly.
2. Task 2 - Capability matrix (CpuND500 vs CpuND5000)¶
CpuND5000 public surface (from
E:\Dev\Repos\Ronny\RetroCore\Nuget\HackerCorpLabs.Emulation.CPU.ND5000\src\CpuND5000.cs
and siblings): fields Cs (ControlStore), Cache, Regs (ND5000 Registers: Wrf[24],
Srf[4096], P, Q, Data, micro-status...), State (CpuState: Mpc, Stack1..4,
Stopped), Memory (IMicroMemory?); methods Tick(), Run(int),
StepOneMacroInstruction(int), RaiseTrap(int). No events. No Reset. Transport is the
AccessModule (AIB/AOB, OnAibWritten/OnAobRead) driven by AccpController. [V]
exists = a semantically equivalent member the adapter can forward to directly. adapter = the adapter can synthesize/translate it from CpuND5000 primitives. gap = no equivalent; needs a real decision or new microcode-side capability.
| Contract member (from Task 1) | CpuND500 | CpuND5000 | Verdict |
|---|---|---|---|
TagWritten event / WriteTag |
native event on WriteTag [V] | no event; microcode writes AIB via AccessModule.WriteAib -> OnAibWritten [V] |
adapter: map AccessModule.OnAibWritten -> raise TagWritten (station already treats TagWritten payload as "the AIB word" [V line 55-59, 474-477]) |
SetMpmMemory(RAM,uint) |
Port B backing [V] | Memory is IMicroMemory, byte-addressed big-endian [V] |
adapter: wrap the shared RAM in an IMicroMemory and assign cpu.Memory |
ReadMpmByte/WriteMpmByte/ReadMpmWord32/WriteMpmWord32 |
native [V] | via Memory.Read/Write(addr,width) [V] |
adapter: forward to Memory (bit-31 offset handling in adapter) |
Reset() |
clears regs, stopMode=NONE [V] | NO Reset method [V] | adapter: reset State/Regs, set micro-PC to the init vector; master-clear semantics [OPEN] |
CpuRegisters (RegistersBase) |
native [V] | ND5000 Registers is NOT a RegistersBase and is a different class [V] |
gap/adapter: adapter exposes a RegistersBase facade or the interface drops this member (see section 3) |
WakeRunThread() / StartRunThread() / StopRunThread() / RunThreadActive / OnRunThreadPark |
native run-thread model [V] | NONE - CpuND5000 has no thread; caller calls Tick()/Run() [V] |
adapter: adapter owns a run thread that calls Tick() in a loop; wake/park translate to the loop's gate |
regs.stopMode (WAIT park/un-park) |
native StopMode [V] |
State.Stopped (STOP word) only; no WAIT idle concept [V] |
adapter: the microcode's own IDLE loop IS the park; WAIT has no direct equivalent [OPEN] |
MonitorCallSink / TrapSink (set) |
native sinks; microcode replaced by C# [V] | NONE - the microcode raises traps itself (RaiseTrap) and builds MON stop records in microcode [V] |
gap: for the microcode CPU these sinks should NOT be installed (microcode does the work); see section 5 |
StartProcessFromRegisterImage(ushort,ushort[]) |
applies full 21B block to live regs [V] | NONE - microcode loads context via NEWCNTXT/CNTXTLOAD from the mailbox itself [V] | gap: the microcode does NOT take a 21B register image the C# way; it reads the context block from MPM. Adapter would have to write the block into MPM/SRF and let microcode load it [OPEN] |
StartProcessFromContextBlock(uint) |
native [V] | NONE (same as above) [V] | gap/[OPEN] |
MMU: MapExistingPhysicalRegion, EnableProgramMMU, EnableDataMMU |
native functional MMU [V] | NONE - MMS not built yet (memory ops are identity/POF-approximated) [V CpuND5000.cs:168-176, MmsUnit stub] | gap: the microcode does its own MMS translation once built; the C# swapper-mapping shim does not apply to it |
WriteVirtualMemory32 / WriteVirtualMemory / ReadVirtualMemory32 |
native virtual MMU-translated access [V] | via Memory.Read/Write (currently identity) [V] |
adapter: forward to Memory while MMS is identity; becomes real once MmsUnit lands [OPEN] |
regs.PC/regs.CAD/regs.I1/regs.ST.K (bridge write-backs) |
native ND500 regs [V] | Regs.P exists; CAD/I1/ST.K map onto WRF/SRF/status differently [V/I] |
gap: only relevant if the bridge drives the microcode CPU, which it should not |
SetND100PrivateOffset(uint) |
native [V] | NONE [V] | adapter no-op (only the bridge uses it; bridge not used for microcode CPU) |
Summary: the TRANSPORT + LIFECYCLE members (TagWritten, SetMpmMemory, MPM accessors,
Reset, run-thread) are adaptable. The PROCESS/MON/TRAP/MMU members
(StartProcessFrom*, MonitorCallSink, TrapSink, MapExistingPhysicalRegion,
virtual-memory, register write-backs) are GAPS because they belong to the C# servicer model
that the microcode replaces. They should not be driven against CpuND5000 at all.
3. Task 3 - Recommended interface design¶
Why not "make both implement one interface" (option a in its pure form)¶
CpuND5000 cannot reference IND500Cpu (dependency direction, section 0). So a single
interface implemented by BOTH classes is impossible unless the interface is moved down into
a shared NuGet AND CpuND5000.cs is edited to implement it - which touches the microcode
session's file. Rejected.
Why not "pure adapter satisfying the existing contract unchanged" (option b in its pure form)¶
The attach path (AttachNd5000Cpu, AttachRealCpu, Nd500CpuProcessBridge ctor,
Nd5000CpuAttachment.Cpu) is typed to the CONCRETE CpuND500. An adapter wrapping
CpuND5000 is not a CpuND500, so it cannot be passed to those methods without changing
their signatures. A pure adapter with NO RetroCore-side changes is therefore not possible.
Recommended: HYBRID - interface extraction on the RetroCore side + a CpuND5000 adapter¶
- EXTRACT a new interface in
Emulated.HW(NOT in the microcode NuGet, NOT in any file the microcode session owns) covering the concrete-CpuND500surface the attach path uses. Suggested nameINd500AttachableCpu(or extendIND500Cpuwith a second interfaceINd500ProcessCpu).CpuND500implements it trivially - it already has every member, so this is a pure: IND500Cpu, INd500ProcessCpuaddition with no body changes. - Retype the attach path to the extracted interface:
Nd500CpuProcessBridge(INd500ProcessCpu cpu, Nd500MicrocodeServicer)OctobusND5000Station.AttachRealCpu(INd500ProcessCpu cpu, bool)ND100Machine.AttachNd5000Cpu(..., INd500ProcessCpu? cpu = null)Nd5000CpuAttachment.CputypedINd500ProcessCpu
- Write
CpuND5000Adapter : IND500Cpu, INd500ProcessCpuinEmulated.HW(it can reference both the NuGet andEmulated.HW), wrapping aCpuND5000+ its own run thread + anIMicroMemory-over-RAMshim + anAccpController/AccessModulebound to the station.CpuND5000.csis untouched.
Rationale: the interface extraction is confined to files the RetroCore/functional-CPU session owns; the adapter is new and self-contained; the microcode session signs off on NOTHING because none of their files change.
Sketched interface members (extracted; grouped)¶
Transport (already on IND500Cpu, keep there):
- event Action<ushort> TagWritten; void WriteTag(ushort);
- void SetMpmMemory(RAM, uint); byte ReadMpmByte(uint); void WriteMpmByte(uint,byte);
- uint ReadMpmWord32(uint); void WriteMpmWord32(uint,uint);
- void Reset();
Lifecycle / run-thread (NEW INd500ProcessCpu, the part AttachRealCpu needs):
- void StartRunThread(string name); void StopRunThread(int timeoutMs = 5000);
- void WakeRunThread(); bool RunThreadActive { get; } Action OnRunThreadPark { get; set; }
- a park/idle primitive that both models can honor. CpuND500 uses regs.stopMode WAIT; the
adapter has no WAIT. Recommend replacing the raw cpu.regs.stopMode = WAIT pokes in
AttachRealCpu/ResetCpuToIdle with an interface method ParkOnIdle() / bool IsParked
so the adapter can implement "park" as "hold the tick loop". [I - needs the two poke sites
at station lines 546-547 and 1308-1310 refactored to go through the interface.]
Process / MON / trap / MMU (the servicer-model members): DO NOT put these on the shared
interface. They stay concrete on CpuND500 and are only reachable when the functional
bridge is used. The microcode adapter never exposes them because the microcode does that
work itself (section 5). This keeps INd500ProcessCpu small and honest.
Where the attach path changes (signatures)¶
File E:\Dev\Repos\Ronny\RetroCore\Emulated.Machines\ND\ND100\ND100Machine.ND5000.cs:
- AttachNd5000Cpu(..., CpuND500? cpu = null) -> INd500ProcessCpu? cpu = null (line 108)
- default build new CpuND500(...) stays (line 134); a new overload/param lets the caller
pass a CpuND5000Adapter instead.
- Nd5000CpuAttachment.Cpu -> INd500ProcessCpu (line 237); .Stop() unchanged.
File E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\NDBUS\OctobusND5000Station.cs:
- AttachRealCpu(CpuND500 cpu, bool) -> AttachRealCpu(INd500ProcessCpu cpu, bool)
(line 529). Body already only uses WakeRunThread, OnRunThreadPark, StartRunThread,
and the regs.stopMode poke (route through ParkOnIdle()).
- BUT: AttachRealCpu also new Nd500CpuProcessBridge(cpu, _servicer) (line 535). For the
microcode adapter this bridge must be OMITTED or replaced (section 5). Recommend a separate
AttachMicrocodeCpu(...) entry that skips the bridge/servicer-as-answerer wiring.
File E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\ND500\Servicer\Nd500CpuProcessBridge.cs:
- ctor CpuND500 cpu -> INd500ProcessCpu ONLY IF the microcode path reuses the bridge.
Recommendation is that it does NOT, so the bridge can stay concrete CpuND500.
4. Task 4 - Minimal changes required to CpuND5000.cs¶
NONE. [V-by-construction of the adapter approach]
With the hybrid design, all translation lives in the new CpuND5000Adapter (in
Emulated.HW) and in Emulated.HW-side interface extraction. CpuND5000.cs,
MacroOracleState.cs, MacroStepTests.cs, InstructionFamilyTests.cs, and the ND5000
docs/*PLAN* / WHERE-WE-ARE docs are NOT touched.
The adapter reaches CpuND5000 entirely through members that already exist and are public:
Cs, Cache, Regs, State, Memory, Tick(), Run(), StepOneMacroInstruction(),
RaiseTrap(), and the AccessModule/AccpController transport. [V]
Two things the adapter needs that the microcode session may CHOOSE to provide later (not
required for a first wire-up, listed so they know what would help - see the coordination
request):
- A public master-clear/reset helper on CpuND5000 (there is none today [V]). The adapter
can synthesize reset by reassigning State and clearing Regs, but a
microcode-authentic reset entry (jump micro-PC to the INIT_SAMSON vector) would be more
correct. [OPEN]
- Confirmation of where the ACCP seam (AccessModule/AccpController) is meant to be
owned - inside CpuND5000 or supplied by the host. Today AccpController is constructed
around an AccessModule and is transport-agnostic [V AccpController.cs:26-30], which is
exactly what an adapter wants, so likely no change needed.
5. Task 5 - Boot concern: what "start" means for each CPU¶
CpuND500 (functional) - starts from a 21B register image¶
The functional model: SINTRAN's PLACE/LOAD-SWAPPER chain hands the C# servicer a 21B (3WREG)
register block; a 23B 3START makes the bridge call
StartProcessFromRegisterImage(firstReg, image), which writes the full register block into
the live register file, sets regs.stopMode = NONE, and wakes the run thread; the CPU then
interprets ND-500 MACRO code from P. The C# servicer + bridge REPLACE the microcode: they
answer mailbox activations, MON stops, and trap stops in C#. [V bridge lines 111-160,
ProcessControl.cs:84-121]
CpuND5000 (microcode) - boots from the loaded 128-bit control store¶
The microcode model is fundamentally different [V, from CpuND5000.cs + AccessModule +
AccpController + the octobus-nd5000 skill facts]:
- There is NO 21B-register-image start entry. The genuine boot is: SINTRAN loads the 128-bit
control store through the ACCP (the LCS0/CMWWC path the station currently only MODELS into
its private _controlStore[] array), the microcode master-clears to its INIT_SAMSON
vector, runs its init, and enters the IDLE poll loop that reads X5ACT from 5MPM and walks
the X5BEX chain ITSELF. Context load is the microcode's NEWCNTXT/CNTXTLOAD reading the
context block from shared memory - not a C# StartProcess* call. [V/I]
- "start" for CpuND5000 = (1) the control store is actually loaded into CpuND5000.Cs
(today the CS bytes are captured into the station's own _controlStore, NOT into any CPU),
and (2) a host run thread calls Tick() continuously so the microcode's IDLE loop runs.
A kick/doorbell just needs to make sure the tick loop is running; the microcode reads the
mailbox flags from MPM on its own.
The consequences for wiring (this is the crux)¶
- The C# servicer must NOT answer mailbox messages for the microcode CPU - the microcode
does. So
AttachRealCpu'snew Nd500CpuProcessBridge(...)and theServiceMailboxOnClock/DrainDoorbells-as-IDLE-poll machinery are for the FUNCTIONAL CPU only. A microcode attach variant must skip installing the bridge/servicer as the answerer and instead: assignMemory= IMicroMemory-over-5MPM, loadCsfrom the CS-load path, wire theAccessModule/ACCP to the fabric for kicks/GIVEINT, and runTick(). - The CS-load path is the pivot: today
OctobusND5000Stationintercepts LCS0/CMWWC/DUCS and fills its private_controlStore[]to make SINTRAN's read-back checksum self-consistent (lines 214-352, 703-741). For the microcode CPU those same writes must instead land inCpuND5000.Csso the microengine actually executes them. [OPEN - how/whether to redirect the CS-load writes intoCpuND5000.Cswhile keeping the checksum read-back correct.]
Biggest [OPEN]s¶
- [OPEN] CS-load target: redirect the ACCP CS-load writes into
CpuND5000.Cs(so the microengine runs real microcode) while preserving the read-back checksum the station currently serves from_controlStore. Needs a live experiment in the boot harness. - [OPEN] Mailbox ownership: with the microcode running its own IDLE poll, the station's
servicer/bridge must be disabled for that CPU. Confirm the microcode's POF reads actually
reach 5MPM through the adapter
IMicroMemory(theRD,POF/WR,POFapproximation, CpuND5000.cs:168-193, is an ND-100-seam approximation, not proven end-to-end). [V that it is an approximation; OPEN whether it reaches 5MPM correctly.] - [OPEN] Reset/master-clear model for CpuND5000 (no Reset method today; INIT_SAMSON entry vector unconfirmed in the code I read).
- [OPEN] Park/idle:
CpuND500usesregs.stopMode=WAITas the parked-IDLE state; CpuND5000 has onlyState.Stopped(STOP word) with no WAIT concept. The adapter's "park" = hold the tick loop; needs the two station poke sites (lines 546-547, 1308-1310) routed through an interfaceParkOnIdle()method.
6. Consolidated [OPEN] list¶
- [OPEN] Redirect ACCP control-store load into
CpuND5000.Csvs the station's private_controlStorewhile keeping the DUCS/CMRWC read-back checksum correct. - [OPEN] Disable the C# servicer/bridge-as-answerer for the microcode CPU (separate
AttachMicrocodeCpupath) so the microcode owns the mailbox. - [OPEN] Prove the microcode's POF reads/writes actually reach the 5MPM window through an
IMicroMemory-over-RAMadapter (big-endian byte order, bit-31 offset). - [OPEN] Microcode-authentic Reset/master-clear (INIT_SAMSON vector) - no Reset on CpuND5000.
- [OPEN] Park/idle equivalence: map
stopMode=WAIT(functional) to a tick-loop hold (adapter); refactor the two rawregs.stopModepokes in the station to an interface method. - [OPEN] Whether
CpuND500.regs.CAD/I1/ST.K/PCwrite-back semantics (the bridge's MON restart) have any meaning for the microcode CPU (they should not, since the bridge is not used).
7. Single next step to make task 11 wireable¶
Extract INd500ProcessCpu in Emulated.HW over the run-thread lifecycle members
(StartRunThread/StopRunThread/WakeRunThread/RunThreadActive/OnRunThreadPark + a
ParkOnIdle() primitive), have CpuND500 implement it (no body changes), and retype
AttachRealCpu / AttachNd5000Cpu / Nd5000CpuAttachment.Cpu to it. That alone makes the
attach path accept a non-CpuND500 object. THEN write the CpuND5000Adapter behind a new
AttachMicrocodeCpu entry that skips the functional bridge/servicer. No CpuND5000.cs edit.