STOP-SYSTEM: what it actually does, and the CLRKICK gap on the octobus¶
Date: 2026-07-30
Scope: SINTRAN III L stop-system on the ND-100 with an ND-5000 (SAMSON) attached over the
OCTOBUS. Written because the octobus boot harness reported stop-system=STALL on every run
since it was written, and the swapper work (see ../ND500/OCTOBUS-SWAPPER-HANDOFF-2026-07-25.md
section 7.7) left it as the last open item.
Status: the STALL is explained and fixed. One real correctness gap on our side is identified
and NOT yet implemented (section 5).
1. Headline¶
Two separate things were tangled together under one STALL flag:
| # | Finding | Kind |
|---|---|---|
| 1 | The harness waited for three strings SINTRAN never prints. stop-system could never be reported OK. |
Harness bug. FIXED. |
| 2 | Our octobus station never executes the X5CLR clear-functions nor acknowledges them, so ST0PSYS's wait loop always times out into ERRFATAL. |
Real gap. NOT yet implemented - see section 5. |
Finding 1 fully accounts for the STALL flag. Finding 2 does NOT cause a hang (the wait loop is
bounded - section 3), so it was invisible behind finding 1.
1.1 Measured, live, one run - both findings confirmed¶
With the observable corrected, the full octobus ladder is green:
OUTCOME: ENTER=OK login=OK nd-500=OK status=OK start-swapper=OK list=OK stop-system=OK
and the new mailbox dump confirms finding 2 at the same time:
----- STOP-SYSTEM mailbox (before-stop-system) ext=0x428900 runState=RUN
X5CPU=0001 X5ACT=0001 X5PRO=FFFF X5CLR=0000 X5CCL=0000 -----
----- stop-system console output -----
<-- EMPTY. stop-system prints nothing, as designed.
----- STOP-SYSTEM mailbox (after-stop-system) ext=0x428900 runState=OPCOM
X5CPU=0001 X5ACT=0001 X5PRO=FFFF X5CLR=003F X5CCL=0000 -----
Read it line by line - every element of the section 2 analysis is visible:
runStategoes RUN -> OPCOM: the ND-100 really halts.stop-systemworks.- The console window between the two dumps is empty, which is the direct proof that the old text-matching check could never have passed.
X5CPU=0001=MPACTIVE, so the section 2 loop 2 activity test did match and the machine really did enter the wait loop.X5CLRgoes 0x0000 -> 0x003F.0x3F=0o77, exactly what loop 1 wrote, still sitting there afterwards. Nothing consumed it. This is finding 2 measured rather than inferred: the wait loop spun its 1000 iterations and fell intoERRFATAL.
2. What stop-system actually does¶
The monitor-level continuation of the command is ST0PSYS, MP-P2-N500.NPL:3759 (octal
147433). Header comment: "TERMINATE ALL ND-500S (IF ANY) AND SIMULATE POWER FAILURE".
ST0PSYS: *IOF % interrupts off
IF PN500D><0 AND 5MSINIT BIT 5INBUF THEN % ND-500 present AND mailbox initialised
CALL SLOCK; GO BYPASS % take the ND-500 semaphore
"S5CPUDF"=:B
DO WHILE B<<="E5CPUDF" % ---- loop 1: ask every CPU to clear ----
IF CPUAVAILABLE BIT 5ALIVE AND A/\5CPUTYPE=SAMSON THEN
T:=5MBBANK; X:=MAILINK; *AAX X5CPU; LDATX
IF A><0 THEN % CPU active?
77; *AAX X5CLR-X5CPU; STATX % X5CLR := 0o77
CLRKICK; CALL XKICK500 % kick 3
FI
FI
B+5CPUDFSZ
OD
"S5CPUDF"=:B
DO WHILE B<<="E5CPUDF" % ---- loop 2: wait for them ----
IF CPUAVAILABLE BIT 5ALIVE AND A/\5CPUTYPE=SAMSON THEN
T:=5MBBANK; X:=MAILINK; *AAX X5CPU; LDATX
IF A-MPACTIVE=0 THEN % CPU active?
1000-=:L % bounded: -1000 .. 0
*AAX X5CLR-X5CPU
DO *LDATX
WHILE A><0 AND L><0 % Functions executed ?
L+1
OD
IF L=0 THEN CALL ERRFATAL FI % gave up
FI
FI
B+5CPUDFSZ
OD
FOR X:=0 TO 10000 DO D+1; A:=A+C; OD % delay reset-cpu
BYPASS: "RS5CPU"; *IRW LV12B DP % "reset cpu" to the ACCP
LV12; *MST PID
*ION
FOR X:=0 TO 10000 DO D+1; A:=A+C; OD % delay stop-system
CALL SUNLOCK
FI
"PPWFAIL"; *IRW LV14B DP % continue in "power fail" on level 14
LV14; *MST PID
*ION
GO STUPR
So the sequence is, in order:
- Interrupts off, then gate on
PN500D != 0 AND 5MSINIT BIT 5INBUF- an ND-500/5000 is configured AND its mailbox has been initialised. If not, everything below is skipped and it goes straight to the power-fail step. - Take the ND-500 semaphore (
SLOCK). - Loop 1 - request the clear. For each CPU datafield between
S5CPUDFandE5CPUDFwhoseCPUAVAILABLEhas5ALIVEset and whose5CPUTYPEisSAMSON: readX5CPUfrom the per-CPU mailbox extension block; if it is non-zero, write0o77intoX5CLRand send kickCLRKICK(=3) viaXKICK500. The NPL comment spells out what0o77means: "Clear data tsb+cache+dump+forget process". - Loop 2 - wait for the clear. Walk the same table again. This time the activity test is
X5CPU = MPACTIVE(MPACT=000001, so 1), not merely non-zero. If it matches, pollX5CLRup to 1000 times waiting for it to read zero - the NPL comment is "Functions executed ?". If the counter runs out,CALL ERRFATAL. - Reset the CPU. After a spin delay, send
RS5CPUto the ACCP on level 12. - Simulate power failure.
PPWFAILon level 14, thenGO STUPR. The power-fail handler is what actually stops the processor.PH-P2-RESTART.NPL:349carries the matching note: "CAN ALSO BE CALLED BY THE STOP-SYSTEM COMMAND".
Therefore stop-system produces no terminating console message by design. Its successful
outcome is the ND-100 halting. On real hardware that is the front-panel RUN indicator going out.
2.1 Mailbox words involved¶
Per-CPU extension block, word offsets from the L07 symbol tables
(SINTRAN/NPL-SOURCE/SYMBOLS/L07/N500-SYMBOLS.SYMB.TXT); byte offset = word * 2:
| Symbol | Word | Byte | Role in stop-system |
|---|---|---|---|
X5CPU |
4 | 0x08 | activity test; loop 1 wants non-zero, loop 2 wants exactly MPACTIVE=1 |
X5ACT |
5 | 0x0A | not used by stop-system (that is the activation work flag) |
X5PRO |
6 | 0x0C | not used by stop-system |
X5CLR |
0o10 = 8 | 0x10 | the clear-function mask. SINTRAN writes 0o77, then polls for 0 |
X5CCL |
0o11 = 9 | 0x12 | cache-clear counter, read/compared elsewhere |
Constants: MPACT=000001, 5ALIV=000015, STUPR=032034.
3. Why the gap in section 5 does NOT hang stop-system¶
Loop 2's wait is bounded: L starts at -1000 and increments, and the loop condition is
A><0 AND L><0. An ND-500 that never clears X5CLR makes the loop run 1000 iterations and exit
with L=0, which calls ERRFATAL - it does not spin forever.
This matters for interpreting the evidence. It means the historical stop-system=STALL could
never have been caused by a missing X5CLR acknowledgement, and any theory that reached for the
clear-function handshake to explain the STALL was reaching past a bounded loop. The STALL has a
completely different cause (section 4).
4. The harness bug (FIXED)¶
Both boot harnesses detected a completed stop-system like this:
stopped = RunUntilAny(atStop, 30_000,
"RUN indicator is off", "CPU stopped", "WAIT instruction");
RunUntilAny scans ConsoleText(), and _console is appended to only by OnConsoleChar,
the emulated terminal's character callback. Those three strings are not SINTRAN output at all -
they are the wording of a Logger.Dbg line inside our own ND-100 WAIT handler:
Emulated.HW/ND/CPU/ND100/Instructions.SystemControlInstructions.cs:391
Logger.Dbg($"WAIT instruction ran with IONI off. CPU stopped after the WAIT instruction. RUN indicator is off.");
Logger output never reaches _console, so the match could not succeed on any run, ever. On top
of that the check is self-defeating by construction: per section 2 a successful stop-system
halts the ND-100, so the machine deliberately prints nothing more. Waiting for console output to
prove the console went silent cannot work.
4.1 The correct observable¶
Our WAIT handler models the halt as a run-state change:
if (!regs.IONI) { ...; regs.runState = Registers.STATE.OPCOM; return; }
OPCOM is the front-panel state in which RUN is off. So "stopped cleanly" is
runState != STATE.RUN, and the fix is a run-state poll rather than a text match:
private int RunUntilStopped(int timeoutMs) // 0 = stopped, -1 = timeout
{
timeoutMs = Scaled(timeoutMs);
var sw = System.Diagnostics.Stopwatch.StartNew();
while (sw.ElapsedMilliseconds < timeoutMs)
{
Pump();
var st = _machine?.Dbg_Cpu?.regs.runState;
if (st.HasValue && st.Value != Emulated.HW.ND.CPU.ND100.Registers.STATE.RUN) return 0;
Thread.Sleep(1);
}
return -1;
}
Applied in both harnesses:
E:\Dev\Repos\Ronny\RetroCore\Emulated.Tests\ND100\Nd100SintranNd5000OctobusBootHarnessTests.cs(octobus) - plus a newDumpStopSystemMailbox(tag)that printsrunStateandX5CPU/X5ACT/X5PRO/X5CLR/X5CCLbefore and after the command, so a stop that does not complete names its own reason instead of needing a fresh investigation.E:\Dev\Repos\Ronny\RetroCore\Emulated.Tests\ND100\Nd100SintranNd500BootHarnessTests.cs(3022) - same defect, and there it sits behind a hardAssert.That(stopped, Is.GreaterThanOrEqualTo(0), ...). That whole fixture is[Explicit], which is the only reason a permanently-false assert stayed hidden.
4.2 Lesson¶
This is the third false STALL in this one investigation (the first two are recorded in
memory harness-stall-is-wallclock and section 7.7 of the swapper handoff). The first two were
wall-clock timeouts; this one is different and worse - the marker was unmatchable in principle.
Rule: a success marker must be something the machine actually emits on the channel being
scanned. Before treating a harness flag as evidence about the machine, confirm the marker can
ever fire - grep for the string and check which sink produces it. A marker that comes from
Logger, from a comment, or from a manual's prose is not an observable.
5. The real gap: CLRKICK / X5CLR is not implemented (OPEN)¶
Measured: X5CLR reads 0x003F after stop-system returns (section 1.1). SINTRAN wrote 0o77
and nothing ever consumed it.
X5CLR appears in RetroCore only in comments. Nothing reads it, executes the coded clear
functions, or writes it back:
Emulated.HW/ND/CPU/ND500/Servicer/IServicerHost.cs:116 (doc comment)
Emulated.HW/ND/CPU/NDBUS/OctobusND5000Station.cs:628 (doc comment)
And in the station's kick handler, only kick 1 does anything
(OctobusND5000Station.cs:1330-1367): every kick is loaded into the AOB and raises
KickReceived, but only kickNumber == 1 (N100KICK, the preempt doorbell) drives
WalkQueue(). Kick 3 (CLRKICK) is a no-op. Consequently X5CLR keeps whatever SINTRAN
wrote, loop 2 in section 2 spins 1000 times and falls into ERRFATAL.
5.1 What the real ND-5000 microcode does¶
From ND5800-MICROCODE-ACCP-OCTOBUS-CATALOG.md (correction 3, and the OCB_DEC_K table): kick
3 dispatches to OCB_KICK03 at microcode address 025522, which is the CLRKICK cache-clear
protocol:
- read
X5CLR(extension block word0o10, ORCON0x10); MSG_CLEAR_1- cache clear by mask;- write
X5CLRback with bit 15 cleared (acknowledge); - if the original bit 15 was set, spin in
OCB_WAITSEXon global header word0o24(byte 0x28) until it reads zero, callingSCAN_ACCPeach pass; - resume
EXECUTEif a process is loaded, else fall through toKICK06.
5.2 An unresolved contradiction - do not implement past this¶
Step 3 as worded ("write X5CLR back with bit 15 cleared") does not satisfy SINTRAN.
ST0PSYS writes 0o77 = 0x003F, which already has bit 15 clear; clearing bit 15 of 0x003F
leaves 0x003F, and loop 2 polls for zero. Under that literal reading the poll could never
succeed on real hardware either, which cannot be right.
I do not know which side of this is imprecise. The plausible readings are that the microcode
clears the mask bits it has executed (so a fully-executed 0o77 leaves 0), or that the catalog's
wording is a compression of the actual listing. The catalog is a carve summary, and its own
item 3 is already a correction of an earlier wrong description of the same routine. Resolving
it needs the B30 listing for 025522, which is not in either repo. Do not guess this into
code.
What IS unambiguous is the requirement from SINTRAN's side, and it is version-independent:
After
CLRKICK, the ND-5000 must makeX5CLRread zero within 1000 ND-100 poll iterations, or SINTRAN callsERRFATAL.
That is the contract our station has to satisfy, and it can be implemented from the SINTRAN side
alone without depending on the ambiguity above. The bit-15 / OCB_WAITSEX branch is a separate
matter: ST0PSYS's 0o77 never sets bit 15, so that branch is unreachable from stop-system. It
may be reachable from the swapper's path (next section), where the mask is runtime data.
5.3 The other writer of X5CLR - the swapper's clear-function message¶
MP-P2-N500.NPL:1222-1235, label LMPCLR, comment "Clear-function; ND5000 only" - so this
path is octobus-specific by construction:
LMPCLR:
T:=5MBBANK; X:=SWMSG; *AAX 5DP2; LDATX % mask arrives in the swapper message, word 5DP2
A=:SWPCLRMASK
CALL SLOCK; 0/\0
A:="S5CPUDF":=:B=:SWCPU
DO WHILE B<<="E5CPUDF"
IF B><SWCPU AND CPUAVAILABLE BIT 5ALIVE
AND C5STAT NBIT BHPFAIL AND SPREF=0 THEN
T:=5MBBANK; X:=MAILINK; *AAX X5CPU; LDATX; AAX -X5CPU
IF A=MPACTIVE THEN
SWPCLRMASK; T:=5MBBANK; *AAX X5CLR; STATX
CLRKICK; CALL XKICK500
FI
FI
B+5CPUDFSZ
OD
CALL SUNLOCK
Two things worth noting:
- The mask is not a constant here - it is
SWPCLRMASK, taken from the swapper's own message atSWMSG + 5DP2. So bit 15 (theOCB_WAITSEXtrigger) genuinely can be set on this path. Per the no-hardcoding rule, our implementation must read the mask fromX5CLR, never assume0o77. LMPCLRdoes not pollX5CLR. It writes, kicks, and answers OK (OKMONICO/XACTRDY). OnlyST0PSYSwaits. So a missing clear handshake is silent on the swapper path and only becomes visible at shutdown - which is exactly how it stayed hidden.
5.4 Recommended implementation shape (not yet written)¶
In OctobusND5000Station.cs, alongside the existing kickNumber == 1 case:
- on kick 3, read the mask from
ExtBlockBase + 0x10; - perform the modelled clear (our cache/TSB models, per mask bit - the
0o77bits are "data tsb + cache + dump + forget process" per the NPL comment); - write
X5CLR := 0, satisfying the section 5.2 contract; - leave the
OCB_WAITSEXglobal-header-word-0o24spin unimplemented with an explicit comment, since no verified SINTRAN write we have sets bit 15 and the semantics are open.
The station already exposes ExtBlockBase publicly, so no new plumbing is needed.
6. Files touched¶
| File | Change |
|---|---|
E:\Dev\Repos\Ronny\RetroCore\Emulated.Tests\ND100\Nd100SintranNd5000OctobusBootHarnessTests.cs |
RunUntilStopped + DumpStopSystemMailbox; stop-system now uses the run-state observable |
E:\Dev\Repos\Ronny\RetroCore\Emulated.Tests\ND100\Nd100SintranNd500BootHarnessTests.cs |
same RunUntilStopped fix (this fixture is [Explicit]) |
E:\Dev\Ronny\NDInsight\SINTRAN\ND5000\STOP-SYSTEM-ANALYSIS-AND-CLRKICK-GAP-2026-07-30.md |
this document |
7. Sources¶
SINTRAN/NPL-SOURCE/NPL/MP-P2-N500.NPL:3759-ST0PSYSSINTRAN/NPL-SOURCE/NPL/MP-P2-N500.NPL:1222-LMPCLRSINTRAN/NPL-SOURCE/NPL/PH-P2-RESTART.NPL:102,349-X5CLRzeroing on restart; stop-system noteSINTRAN/NPL-SOURCE/SYMBOLS/L07/N500-SYMBOLS.SYMB.TXT-X5CPU/X5ACT/X5PRO/X5CLR/X5CCL,MPACT,5ALIVSINTRAN/ND5000/ND5800-MICROCODE-ACCP-OCTOBUS-CATALOG.md-OCB_DEC_K,OCB_KICK03(025522)SINTRAN/ND5000/CARVE-ANSWER-ND5000-ACTIVATION-WORKFLAG.md- extension-block layout tableSINTRAN/ND500/OCTOBUS-SWAPPER-HANDOFF-2026-07-25.mdsection 7.7 - prior context