Shared ND-500 MMU design - generic IAddressTranslator for both CPUs (2026-07-21)¶
Goal (Ronny's direction): one generic MMU in the Nuget namespace, plugged into BOTH the
functional macrocode CPU (CpuND500, migrating to Nuget) and the microcode CPU (CpuND5000,
already in Nuget), following the existing Nuget machine-bus MMU-mapping pattern. First of several
shared classes; the octobus interfaces / structures / bus follow later.
Evidence grades: [V] verified in code this session, [D] derived from the pattern, [OPEN] decision needed.
0. Why now - the diagnostic that motivates it [V]¶
Running the REAL swapper (SWAPPER-K01.PSEG/DSEG) through the REAL B30 microcode via a MICFU-23
3START (SwapperStartDiagnosticTests, green): the microcode dispatched the start, loaded the PCB,
read the DSEG build-tag correctly (0x08012818 = 'REV.'), executed the swapper's FIRST instruction
(init), and then stopped:
Stop: InvalidOperationException: Data type 7 memory access not implemented yet
CORRECTION (2026-07-22, Ronny): data type 7 was NOT the MMU. It was a missing memory-op
data-type case in the microword CpuND5000 itself (microcode session's lane), root-caused and
FIXED there. The "= virtual write = the only gap is the MMU" reading here was a wrong inference from
the error string - do not build on it. The shared MMU is still the eventual path for real virtual
translation, but it did not cause the data-type-7 stop. Re-run the diagnostic for the real next stop.
This does NOT change the design below (a generic shared MMU is Ronny's directive regardless); it only
corrects the motivation.
1. The pattern to follow - IAddressTranslator [V]¶
HackerCorpLabs.Emulation.Abstractions.Cpu.IAddressTranslator is the canonical Nuget MMU seam
(skill retrocore-cpu-mmu). Contract highlights:
bool Translate(uint va, AccessKind kind, int sideChannel, out uint pa, out int space, out TranslationFault fault)- hot path, NEVER throws, sealed impl,
AggressiveInlining. Fault viaout, CPU raises the trap.
- hot path, NEVER throws, sealed impl,
bool TranslateAtContext(..., int contextOverride, ...)- cold/DAP path with explicit context.bool TestAddress(...)- PTEST/dry-run, no ref/mod mutation.void Flush(FlushScope, int context, uint va),void Reset().AccessKind= Read / Write / InstructionFetch;TranslationFault= None / PageFault / ProtectionError / WriteToReadOnly / SupervisorViolation / BusError.
Plug-in model: the CPU holds CpuState.Translator (IAddressTranslator?, null = identity) and
CpuState.PhysicalBus (SystemBus?). Page-table walks read via PhysicalBus.ReadPhysical*, never
the virtual bus (else infinite recursion).
ND-100 is the precedent for a custom-paging ND CPU: split instruction/data tables, per-context
(PIL) via TranslateAtContext. ND-500 is the same shape with context = domain (CED) and
I-space/D-space distinguished by AccessKind.InstructionFetch (program capability) vs data
(data capability).
2. Nd500Mmu : IAddressTranslator - the generic unit [D]¶
The walk already exists twice and agrees (both from the ND-500 3-level model):
- CpuND5000.MmsUnit [V present] - pure Translate(IMicroMemory, va, domain, isWrite, isInstruction) -> MmsResult;
capability -> PST -> PTE; PFN==0 = not-present; PSTP/PUWP latches. Enabled=false, NOT wired into fetch,
validated vs spec + nd500x only.
- CpuND500 MMU (CpuND500.MMU.cs) [V present] - TranslateVirtualAddress(va, isWrite, isInstruction)
+ TryMapVirtualToPhysical; the PROVEN one (runs the swapper on the 3022 track); its own code already
cross-references MmsUnit.Translate.
Nd500Mmu = ONE sealed IAddressTranslator that supersedes both, carrying the validated walk:
| IAddressTranslator | ND-500 meaning |
|---|---|
AccessKind.InstructionFetch |
use program-capability array (pcb_pc[32]) |
AccessKind.Read/Write |
use data-capability array (pcb_dc[32]); Write checks DC_WRP |
sideChannel |
unused (0) - ND-500 has no FC/ASI |
contextOverride / current context |
domain (CED) - selects the PCB (PcbBase + domain*256) |
out space |
I-space vs D-space (and/or MPM-vs-local routing, bit 31) |
TranslationFault.PageFault |
PGF (octal 46) - the diagnostic's data-type-7 target |
TranslationFault.WriteToReadOnly/ProtectionError |
PV (octal 44) |
Geometry (carve ground truth, already in MmsUnit): page = 2 KB; VA = Seg(5) L1(7) L2(9) Off(11);
PSTE 4 B (bits1-0 mode AZI/ASI/ADI, 31-2 PFN); PTE bit0 = protection, no present bit (PFN==0 =
not-present); PCB = 256 B/domain, pcb_pc @0, pcb_dc @64; roots MM,PSTP=2 / MM,PUWP=3 [OPEN
byte-base mapping].
3. THE one real decision - the memory seam [OPEN]¶
The pattern says walk via PhysicalBus.ReadPhysical* (a concrete SystemBus). But the two CPUs
reach memory differently TODAY:
- CpuND5000 uses IMicroMemory (flat, big-endian) - no SystemBus.
- CpuND500 uses ISystemBus + RouteToMpm (MPM vs local).
Two ways to give Nd500Mmu its page-walk reads:
Option A - abstract the walk seam. Nd500Mmu walks via a tiny IPhysicalMemory (Read/Write
big-endian by physical byte addr). Provide two thin adapters: one over SystemBus.ReadPhysical*
(CpuND500), one over IMicroMemory (CpuND5000). Smallest change to the microcode CPU; slight
divergence from the skill's "walk via PhysicalBus" letter.
Option B - both CPUs adopt SystemBus/PhysicalBus. Fully matches the pattern and the
CpuState.Translator/PhysicalBus plug-in. Bigger lift: the microcode CPU would move from
IMicroMemory to a SystemBus seam. Natural to do WHEN CpuND5000 + CpuND500 consolidate in
Nuget, but not a small step.
Recommendation: A first (unblocks both CPUs behind the shared IAddressTranslator now, proven by
a differential test), converge to B during the Nuget consolidation. The public seam is
IAddressTranslator either way, so the Option-A adapter is an implementation detail, not a contract.
3b. Reconciliation findings from the PROVEN walk - decisions needed [V/OPEN]¶
Reading CpuND500.MMU.cs (the proven walk) vs MmsUnit surfaced real ND-500-specific points the
generic 68K/SPARC-shaped IAddressTranslator does not cover. These are build-time decisions, not
guesses to bury:
- Walk source diverges [V].
CpuND500keeps PCB and PST in HOST C# arrays (PCBTable[domain],PST[psn]) and reads only PTEs from memory.MmsUnitreads all three from guest memory (microcode-accurate). The pattern (walk viaPhysicalBus.ReadPhysical*) and the Option-AIPhysicalMemoryseam both imply guest-memory for all three. Decision:Nd500Mmureads PCB/PST/PTE fromIPhysicalMemory(guest); the differential test builds equivalent tables in BOTH forms to prove algorithm parity vsCpuND500. WiringCpuND500to actually source PCB/PST from guest memory is the convergence step (SINTRAN's SPLAC/ENDPL build them there on real HW). - Indirect segments have no
TranslationFaultvalue [OPEN]. A PROGRAM capability withPC_TYP(bit 15) set = inter-domain CALL segment.MmsUnitadded its ownMmsFaultKind.IndirectSegment; the shared enum (None/PageFault/ProtectionError/WriteToReadOnly/SupervisorViolation/BusError) can't express it. Decision needed: extend the shared enum (touchesAbstractions, affects all CPUs) vs surface indirect via a separate translator output.CpuND500today falls back to identity for indirect (a stub). - Address-Zero (AZ) is an ignorable trap [V]. A null data read (VA 0, no capability) is AZ
(bit 24), which real ND software relies on (continues, reads 0).
CpuND500handles it by raising an ignorable trap and returning physical 0. In the translator model this is "success, pa=0", not a fault. Decision:Nd500Mmureturns success/pa=0 for the AZ case and lets the CPU's trap policy decide (keeps the translator pure). - Privileged / user access (
DC_PAC) [V].CpuND500checksDC_PAC+ PCB privilege ->SupervisorViolation.MmsUnitomits this. Keep it (maps cleanly toSupervisorViolation).
None of these block building+validating Nd500Mmu standalone (differential test hand-builds tables);
they DO shape its output contract, so they are settled here before coding.
4. Where it lives + lane split [D]¶
- Interface: already in
HackerCorpLabs.Emulation.Abstractions.Cpu- no new interface. Nd500Mmu(sealed): a shared ND-500 Nuget package (e.g.HackerCorpLabs.Emulation.CPU.ND500.Common, or the ND-500 CPU Nuget onceCpuND500migrates). Shared by both CPUs.CpuND500(my lane, Emulated.HW): adoptCpuState.Translator+PhysicalBus; route its memory ops throughNd500Mmu; delete/retire the bespokeTranslateVirtualAddressbody once parity holds.CpuND5000+MmsUnit(microcode session's lane): wireNd500Mmuinto the fetch/data path and implement the virtual MEMORY ops (data-type 7/15) that currently throw. I must not edit their files - coordinate; supply the shared unit + the differential oracle.
Validation (mandatory per skill): the standard translator fixtures (identity, page hit/miss,
protection, ref/mod, boundary, flush, TestAddress-no-mutate, context-switch, per-space) + a
differential test asserting Nd500Mmu == the proven CpuND500 MMU == MmsUnit on a table
corpus + a translator perf gate (<100 ns/translate).
5. Migration sequence [D]¶
- Build
Nd500Mmu : IAddressTranslator(Option-A seam) from the validatedMmsUnit/CpuND500walk. - Differential test:
Nd500MmuvsCpuND500MMU (proven) vsMmsUnit- lock parity. CpuND500adoptsTranslator+PhysicalBus; route memory ops through it (my lane).- Microcode session wires
Nd500MmuintoCpuND5000fetch + virtual MEMORY ops (their lane). - Re-run
SwapperStartDiagnosticTests- the swapper should now pass its first virtual write and run on. - Later: consolidate both CPUs + octobus (interfaces/structures/bus) into shared Nuget (Option-B seam).
Status of record¶
- BUILT (increment 1, green): shared Nuget package
RetroCore\Nuget\HackerCorpLabs.Emulation.CPU.ND500.Common\(added toRetroCore.sln):src\IPhysicalMemory.cs(Option-A walk seam),src\Nd500Mmu.cs(Nd500Mmu : IAddressTranslator, the AZI/ASI/ADI walk),tests\Nd500MmuTests.cs(12/12 mandatory fixtures pass). Decisions from sec 3b baked in; indirect + DC_PAC deferred. - NEXT increments: (2) differential test
Nd500Mmuvs the provenCpuND500MMU vsMmsUnit; (3)CpuND500adapter (IPhysicalMemoryoverSystemBus) + route memory ops through the shared MMU (my lane); (4) microcode session wiresNd500MmuintoCpuND5000fetch + the virtual MEMORY ops (data-type 7/15) that currently throw; (5) re-runSwapperStartDiagnosticTestspast the first write. - Diagnostic test:
RetroCore\Nuget\HackerCorpLabs.Emulation.CPU.ND5000\tests\SwapperStartDiagnosticTests.cs(green, observational). - Pattern: skill
retrocore-cpu-mmu; interfaceAbstractions\src\Cpu\IAddressTranslator.cs. - Existing walks:
CpuND5000\src\MmsUnit.cs;Emulated.HW\ND\CPU\ND500\CpuND500.MMU.cs. - Cross-link: swapper start = message-driven MICFU-23 (this session's agent traces); see
SWAPPER-START-MECHANISM-CARVE-2026-07-19.md(retraction: swapper is macrocode), and the octobus bring-up (LCS0/STAMIC0 = real B30 microcode, version 0x2E9A).