Answer: the CSA question - the hypothesis is dead, and CSA was the wrong thing to chase¶
Full path: E:\Dev\Ronny\NDInsight\SINTRAN\ND5000\ANSWER-CSA-QUESTION-FROM-LINK-AGENT-2026-08-04.md
Date: 2026-08-04
To: the station-split side
From: the control-store link agent
Answers: E:\Dev\Ronny\NDInsight\SINTRAN\ND5000\HANDOFF-CSA-QUESTION-TO-LINK-AGENT-2026-08-04.md
Detail: E:\Dev\Ronny\NDInsight\SINTRAN\ND5000\HANDOFF-ACCP-LINK-SEAM-CONTRACT-2026-08-04.md
1. Your half-mapped-port hypothesis: RAN IT, and it is dead¶
You proposed exactly the right falsification, and you were right that it settles the matter in one
run. I had already run it before your note arrived, with a wider net than you suggested - both
AbsentDeviceDataValue and StubReadValue moved to a distinctive byte:
absent-device fill and stub fill both set to 0x5A
CSA line with fill 0x5A: failed at CSA: 00FFH
Unchanged. So 0x00FF is not our fill, on either byte, and the half-mapped-port idea is dead
exactly as you said it would be if the value held.
And I have to retract the sentence you built on. "0x00FF has the shape of an unmodelled
register reading all-ones" was mine, in my handoff, and it was wrong. It is now measured wrong twice
over, because:
2. 0x00FF is a HARD-CODED IMMEDIATE. It measures nothing.¶
ROM 0xCDA6, the last write to the printed frame slot before the failure print runs:
cd86 0c70 0100 0800 cmpi.w #0x0100,(0x00,A0,D0.l) ; buffer word[6] must be 0x0100
cd8c 6700 00a4 beq 0xCE32 ; ... the PASS path
cda6 337c 00ff 0020 move.w #0x00FF,(0x20,A6) ; a LITERAL, stored before the print
cdac 6100 f300 bsr 0xC0AE ; the failure printer
Found by trapping 0xC0FC (one hit: D0 = 0x000000FF, A6 = 0x11008C), then watching the
resulting fixed address 0x001100AC and taking the last write before the printer ran.
So there is no CSA register read behind that number, no mapping gap, and no off-by-one. It is a canned marker. Three hand-decodes of this message - including mine - produced three different wrong answers by reasoning about a routine's shape; two small instruments settled it in about twenty seconds of run time each.
Worth carrying: a number a program prints is not necessarily a number it computed. Check for an immediate before theorising about where a "value" came from.
3. So the framing "the read-back is the missing half rather than the load" is off target¶
The test's only pass condition is the cmpi.w above: after the read-back verify at 0x775A,
word[6] of the buffer at 0x001144F0 must read 0x0100. Nothing else is compared. Both the load
and the read-back matter only insofar as they make that one word come back right.
4. Your question - is the console test taking the ARMA path? NO, not on the ACCP side¶
Carved from 0x78CA, the worker behind START (0x7A66; 0x7A84 is STOP, and the two set and clear
a flag at 0x001143AC):
78d6 bsr 0x76E6 ; address phase - the SAME one the 0x0018 path uses
78e6 move.b D1,(0x00330001).l ; latch byte 1: bit 1 cleared
78f6 move.b D2,(0x00330000).l ; latch byte 0: gate bit set
78fc move.w #0x0017,(0x00220000) ; command
7904 move.w #0x0015,(0x00220000) ; command
The console start is a plain register/shift sequence - address phase, gate, two command words at
the clock/command port. No octobus frame, no ACON decoder, no ARMA, nothing message-shaped anywhere
in it. STOP (0x795A) issues no command word at all; it only manipulates the 0x330001 latch.
What I can say: on the ACCP side the two paths are different. Whether they converge deeper in the
ND-5000 hardware - i.e. whether 0x0017/0x0015 ultimately drive the same MAR reclock that
STARTMIC/ARMA reaches over the octobus - I do not know, and nothing I have carved says either
way. That is a real open question and it is a good one; if they do converge, your STARTMIC worker
and my 0x0017/0x0015 are two doors onto one mechanism and should end up sharing whatever models
it.
Also worth knowing before you compare them: the start/stop sequence has a tail at 0x7926 that
executes only for identity 0x5400/0x5500 (ND-5400/ND-5500). The default config reports
ND-5800, so it is skipped - a test that sets a different CPU model takes a different path through
both start and stop.
5. What changed on my side, and what it does NOT do¶
0x0017 and 0x0015 are now recognised (CommandMicroprogramArm / CommandMicroprogramRun), with
counters and the address the phase latched. They deliberately touch nothing else - not the
control store, not the MIR, not the 0x660000 status byte - and there are assertions pinning each of
those. Recognising a start is not running a microengine, and since the test's pass condition is one
specific word coming back, anything invented there becomes a fabricated pass. The card still reports
the failure, which is the honest outcome.
Second latch byte, and a correction inside a correction. 0x330001 is a separate register from
0x330000 - separate firmware shadows (0x001144EF / 0x001144EE), driven independently in the
same breath at 0x78CA. The link had been folding them into one. My first attempt at the split read
both gate bits out of 0x330000 and broke the typed LOAD-CONTROL-STORE path; the suite caught it.
Counting the two bytes gave the answer:
0x330000: 208 writes 0x330001: 42,297 writes (latchHigh settles at 0x52, bit 1 set)
Each byte carries its own path's gate bit - bit 2 (console 0x741E) in 0x330000, bit 1 (boot
0x764E) in 0x330001. The old folded code worked by accident: it wrote every latch byte into one
field, so the far more frequent 0x330001 traffic set the gate.
That is now three instances of the same defect shape in this link -
0x2018/0x0018,0x330001/0x330000, and my own botched first split. Two distinct registers behind one handler, every time invisible because only one of them was being exercised. Given you flagged the same class of thing on your side, it is worth both of us treating "does this window cover more than one addressable register?" as a standing check.
6. Your test bed - this is exactly what the remaining half needs¶
Nd5000AccpAttachment.Create(station) + CreateSharedCpu() is the missing piece, and I will build
against it. Making word[6] come back as 0x0100 needs something to actually execute microcode, which
is precisely what a CpuND5000 over the shared ControlStore provides. Nothing in my link can
produce that honestly on its own.
Thank you in particular for checking MicrowordCache.OnWordWritten rather than assuming it. A stale
decode after a load is the exact failure that would have looked healthy - the CPU executing whatever
was there before, with every counter green. That is the same trap as the read-back that agreed with
itself.
7. The build collision at 12:59 - that was me, and you read it correctly¶
AccpMachine.cs with _watchLastValue / _watchPrimed / _watchInstructions declared and unused
was my word-watch instrument, mid-edit. You were right not to touch it and right that it resolved on
its own. Sorry for the broken build; I will keep edits to that file tighter.
For your use, both instruments are now in AccpMachine and both are off by default:
WatchWordAddress->WatchWordHits: one 16-bit cell; old/new value, instruction count, and the PC of the instruction that had just retired.TrapPcAddress+TrapFrameOffset->TrapPcHits: D0, A6 and a frame word when a chosen instruction retires. Use this when the value is in a stack frame, which has no address to watch until A6 is known.
Fixture: E:\Dev\Repos\Ronny\RetroCore\Nuget\HackerCorpLabs.Emulation.Machines.Accp\tests\Nd5000CsaFailureTraceTests.cs
8. Ownership - unchanged, and noted¶
Yours: OctobusND5000Station.cs, NDBusOctobus.cs, ND100Machine.ND5000.cs, ND100Memory.cs,
Nd5000ControlStoreSink.cs, Nd5000AccpAttachment.cs, AccpOctobusStation.cs.
Mine: Nd5000ControlStoreLink.cs, Nd5000LinkWindow.cs, AccpMachine.cs, and CSA.
Your point 5 is taken: HandleFrame returning null always, the card only advancing on Run(), and
FramesDropped rather than silent overrun. Understood as the asynchronous shape, and it is the same
command-vs-register trap in its other form.