SGLOA writes the physical-segment tables - PSPHS / PSLLI / PSULI / PSMOD¶
Date: 2026-08-03
Segment: 030-S3SM5 (ND-100 code, load base 040000B)
Supersedes: section 3 of SGLOA-SEGMENT-PLACE-CARVED-2026-08-03.md, which read these
stores as "fields of a structure reached indirectly through B-176". That was wrong - see
section 1.
0. The short version¶
At PLACE time SGLOA writes four entries in four separate resident tables, all indexed by
the same segment number held in B-176:
| Table | Address | What SGLOA stores |
|---|---|---|
PSPHS |
177401B |
the segment's PHysical Start - M[B-174] OR M[B-75] |
PSLLI |
175341B |
0 (lower limit) |
PSULI |
175441B |
size-1, or -1 when the size word is absent (upper limit) |
PSMOD |
175541B |
1 or 0, taken from bit 9 of a descriptor word (mode flag) |
PSPHS reads as PHysical Start, and RPHS is Read from PHysical Segment - the
instruction the 5SWAP trap fires on (RPHS @1000010525, see TRAP-PRINTER-HUNT-2026-08-03.md).
Do NOT read that as "PSPHS is the table RPHS reads" - it is not established, and the two live
on different sides of the machine. RPHS runs on the ND-500 and resolves its address through
"the physical segment table pointer" (ND-05.009.4 16.31), which is the ND-500's own PSTP /
PST hardware structure. PSPHS is an ND-100-side table inside segment 030-S3SM5, written
by SINTRAN. Whether SINTRAN builds the ND-500 PST from PSPHS, or the two are merely parallel
bookkeeping, was UNPROVEN when this was written. SETTLED 2026-08-11: parallel
bookkeeping — the PST entries are swapper-maintained and the base is a SINTRAN allocation
handed over via control-store cell 0o21; see
CARVE-ANSWER-PST-WRITER-VS-PSPHS-2026-08-11.md. See section 4 for what this doc itself
established.
1. CORRECTION: B-176 is an INDEX, not a pointer [V]¶
The earlier note read STA I ,X 71 as "store through the pointer in B-176". The instruction
word says otherwise.
007071B = STA (004000B) + 1593. In binary 1593 is 110 0011 1001:
| Bit | Value | Meaning |
|---|---|---|
| 10 | 1 | X |
| 9 | 1 | I |
| 8 | 0 | B |
| 7-0 | 71B |
displacement |
Bit assignment cross-checked against two unambiguous lines in the same routine:
046003 = LDA ,X 3 sets only bit 10 (so bit 10 is X), and 044603 = LDA ,B -175 sets only
bit 8 with a low byte of 203B = -125 = -175B (so bit 8 is B and the displacement is 8 bits
signed). Bit 9 is therefore I.
X=1, I=1, B=0 is post-indexed indirect: EA = M[P + disp] + X.
So the displacement names a pointer word in the routine's own literal pool, and X - loaded
from B-176 immediately before each store - is added to it as an array index. Reading it
the other way round turns four table writes into one structure and hides the table names
completely.
2. The four stores, resolved [V]¶
LDX ,B -176 reloads the index before every store, so all four use the same index.
143443 LDA ,B -174 A := the value computed by the open/interrogate chain
143444 ORA ,B -75 A := A | flags
143445 LDX ,B -176 X := segment number
143446 STA I ,X 71 M[143446+71] = M[143537] = 177401 = PSPHS -> PSPHS[X] := A
143447 LDX ,B -6
143450 LDD ,X 13 D := double word at [X+13] (the size)
143451 JAF 3 -> 143454
143452 SKP IF DD UEQ 0
143453 JMP 4 -> 143457
143454 RADD CLD SD DA A := D
143455 AAA -1 A := A - 1
143456 JMP 2 -> 143460
143457 SAA -1 A := -1 (size word absent / zero)
143460 LDX ,B -176
143461 STZ I ,X 57 M[143461+57] = M[143540] = 175341 = PSLLI -> PSLLI[X] := 0
143462 STA I ,X 57 M[143462+57] = M[143541] = 175441 = PSULI -> PSULI[X] := A
143463 LDX ,B -6
143464 LDA ,X 3
143465 BSKP ONE 110 DA test bit 9 (110B / 8 = 9)
143466 JMP 3 -> 143471
143467 SAA 1 A := 1
143470 JMP 2 -> 143472
143471 RADD CLD 0 DA A := 0
143472 LDX ,B -176
143473 STA I ,X 47 M[143473+47] = M[143542] = 175541 = PSMOD -> PSMOD[X] := 0/1
Note STZ and STA at 143461/143462 carry the same displacement 57B but sit at
addresses one apart, so they resolve to two different pool words and therefore two
different tables. Read as one structure they look like a pointless clear-then-store of a single
field; read correctly they are the lower and upper limit of a range.
The literal pool¶
143536 055330 DOOVE (called by JPL I 75 at 143441)
143537 177401 PSPHS
143540 175341 PSLLI
143541 175441 PSULI
143542 175541 PSMOD
143543 076012 PLRES (called by JPL I 42 at 143501)
143544 177441 DSPHS (not used by these four stores)
143545 036633 - (no symbol; called by JPL I 34 at 143511)
The code jumps over this pool - 143533 branches past it - which is the ordinary SINTRAN
shape and the reason a straight-line disassembly prints nonsense mnemonics here.
3. Why the symbol identification is trustworthy [V]¶
All seven named pool words hit SINTRAN\ND500\swapper\N500-SYMBOLS.SYMB on the first try,
with no near-misses, and they turn out to be one family rather than seven unrelated names.
Pulling every symbol in the neighbourhood shows the layout:
175341 PSLLI 175401 DSLLI
175441 PSULI 175501 DSULI
175541 PSMOD 175601 DSMOD
175641 USREG
...
177401 PSPHS 177441 DSPHS
177501 CSYDS
Three properties make this more than a name match:
- Every
PS*table has aDS*twin - Program segment and Data segment, which is exactly the ND-500's two-segment process model. - The spacing is uniform:
40B= 32 words. Each table therefore holds 32 entries, and the P table and its D twin are adjacent. A wrong identification would not produce a regular grid. - All six
PS*/DS*symbols andPSPHS/DSPHSare unique in the symbol table - no aliases from other overlays. (Addresses above177600Bare heavily aliased, which is why this evidence would be worth nothing up there; at175xxxand1774xxxit is clean.)
Contents on disk read as all zeros for all five tables, which is expected and confirms they are
data, not code: resident data cells are runtime-only in a carve
(EXTRACTING-RESIDENT-CODE.md section 8).
4. What this does to the swap-file question [I]¶
The standing puzzle was: why do the swapper's tables say this page lives on the swap file when
nothing appears to have written it there? Three candidates had already been eliminated -
DEFINE-SWAP-FILE/SWFDE only opens and closes, the segment's single WFILE is
caller-directed, and LSWPAGE takes its direction from XABSFUNC=60B = read.
PSPHS[seg] is a fourth answer with a different shape: nothing needs to have written the
page, because PLACE writes the ADDRESS. SGLOA records where the segment lives and the
content is paged in on demand - which is what the measured MON 377B LSWPAGE traffic is.
This is inference, not proof. What is proven is the four writes and their targets. What is NOT proven:
- what
M[B-174]actually holds when it reachesPSPHS(it comes out of the open/interrogate chain at143352-143414and has not been carved); - what
M[B-75]contributes through theORA; - whether the swapper's
RPHSreadsPSPHSdirectly or through a copy; - whether a zero
PSPHS[seg]is what the failingRPHSactually saw.
The last one is the cheap test and it is now well posed: RPHS traps, PSPHS feeds RPHS,
so read PSPHS[seg] at the moment of the trap. If it is zero, PLACE never wrote it for that
segment and the fault is a missing write, not a missing page.
5. The tables are used everywhere - and three routines touch the WHOLE family [V]¶
Scanning the segment for words equal to any of the five table addresses gives 62 pointer-pool references. These are not a corner of the segment; they are core bookkeeping.
Most references are to a single table. Four sites name the whole family in one pool, which is the signature of a routine that reads or writes a complete segment descriptor:
| Pool at | Contents |
|---|---|
054151-054156 |
PSMOD, PSPHS, PSLLI, PSULI (+ DSPHS at 054020) |
072025-072033 |
PSPHS, PSMOD, PSLLI, PSULI, DSPHS |
134514-134522 |
PSPHS, DSPHS, PSLLI, PSULI |
143537-143544 |
PSPHS, PSLLI, PSULI, PSMOD, DSPHS - this is SGLOA, the writer |
So one writer is known and three whole-family consumers are not yet carved. Those three are the highest-value next targets: whatever reads a complete descriptor is what the descriptor is FOR, and one of them is where the "does SINTRAN build the ND-500 PST from this" question gets answered.
A caution against a tempting shortcut: the FUNCS vocabulary contains RPHSG and WPHSG -
read/write physical segment at the ND-100 monitor level. It is tempting to assume PSPHS exists
to serve those, and equally tempting to assume it serves the ND-500's RPHS instruction. Both
are guesses until one of the three pools above is read.
5a. The first consumer read: 054040 is an ADDRESS TRANSLATOR [V for the mechanics]¶
The 054151 pool belongs to a routine running from about 054040. It reads all four tables
through the same post-indexed pattern SGLOA writes them with:
054051 LDA I ,X 100 -> M[054151] = PSMOD -> A := PSMOD[X]
054055 LDA I ,X 76 -> M[054153] = PSPHS -> A := PSPHS[X]
054056 AND 76 -> M[054154] = 007777 -> mask to 12 bits
054057 JAZ 62 -> zero page number: bail to 054141
054060 LDT I ,X 75 -> M[054155] = PSLLI -> T := PSLLI[X]
054061 SKP IF DD MGRE ST bound check against the LOWER limit
054063 LDT I ,X 73 -> M[054156] = PSULI -> T := PSULI[X]
054064 SKP IF DT MGRE SD bound check against the UPPER limit
054065 JMP 54 -> out of range: bail to 054141
054066 RADD CLD SX DT -> T := X
054067 SHT 13 -> T := T << 11 page number -> BYTE address (2048-byte pages)
Three things this settles:
PSPHSentries carry a PAGE NUMBER in the low 12 bits - theAND 007777says so, and theSHT 13(shift left 11) converts a page number to a byte address at the 2048-byte page size used everywhere else in this machine.PSLLI/PSULIreally are limits - they are used for exactly one thing here, a two-sided bound check, which is whatSGLOAwriting 0 and size-1 into them predicted.- A zero
PSPHSentry is a recognised failure case, not an address -JAZjumps out. That is the shape of "this segment is not placed", and it is what the cheap test in section 4 would be looking for.
What this does NOT settle: the routine's name and its callers. The pointer scan for entries
into 054000-054050 returns twenty-plus candidates and most are certainly noise (small
addresses alias with ordinary data). Until a caller is identified, "this is what RPHSG/WPHSG
use" remains a guess - a well-shaped one, since a monitor-level physical-segment read needs
precisely this translation, but a guess.
It does, however, weigh against the ND-500-PST reading: this is the ND-100 translating a physical-segment address in software, for its own use. Nothing here writes a hardware table.
5b. All four whole-family sites are now read - place, unplace, copy, translate [V]¶
| Site | Role | What it does with the tables |
|---|---|---|
143440 |
PLACE (SGLOA) |
writes PSPHS, PSLLI:=0, PSULI:=size-1, PSMOD |
071720 |
PLACE + UNPLACE | writes the same four, and clears PSPHS[X] := 0 at 072007 |
134430 |
COPY | reads PSLLI/PSULI at one index, writes them at another |
054040 |
TRANSLATE | reads all four, bound-checks, page number -> byte address |
071720 is a second writer, and it has the release path¶
Same four tables, same index register idiom, and the PSLLI/PSULI logic is identical to
SGLOA's down to the instruction sequence:
071723 LDX ,B -75
071724 STA I ,X 101 -> M[072025] = PSPHS PSPHS[X] := (X with bit 13 set) | M[B-200]
071732 STA I ,X 74 -> M[072026] = PSMOD PSMOD[X] := 0/1, from bit 15 this time
071742 STZ I ,X 65 -> M[072027] = PSLLI PSLLI[X] := 0
071743 STA I ,X 65 -> M[072030] = PSULI PSULI[X] := size-1
...
072007 STZ I ,X 16 -> M[072025] = PSPHS PSPHS[X] := 0 <- the RELEASE
Note PSMOD is taken from bit 15 here and from bit 9 in SGLOA - different source
words, same 0/1 result.
The three-way agreement that settles what a zero entry means [V]¶
SGLOAand071720writePSPHS[seg]when a segment is placed.072007clearsPSPHS[seg]to zero.054040bails out (JAZ) whenPSPHS[seg]reads zero.
Three independent code paths, one meaning: PSPHS[seg] == 0 is "this segment is not
placed". It is not an address, and it is not a default that happens to work.
That makes the live test in section 4 decisive rather than suggestive: read PSPHS[seg] at the
trap, and zero is a definite answer, not an ambiguous one.
134430 copies a descriptor between two slots¶
134454 LDA I ,X 45 -> PSLLI[X1] X1 from B-200
134456 STA I ,X 43 -> PSLLI[X2] X2 from B-177
134460 LDA I ,X 42 -> PSULI[X1]
134462 STA I ,X 40 -> PSULI[X2]
A descriptor duplicated from one physical segment number to another - the shape of creating a process or domain from an existing one.
And a negative worth stating plainly¶
None of the four sites writes anything toward the ND-500's hardware PST. The harness shows
SINTRAN does set PSTP = 0x0003A000, so a hardware table certainly exists and is populated -
but not from here. Every operation on PSPHS/PSLLI/PSULI/PSMOD in this segment is
ND-100-side bookkeeping.
This is four-for-four and it is the strongest evidence yet against "PSPHS is the table RPHS
reads". It is still an absence rather than a proof: whatever writes the PST at 0x3A000 has not
been found, and until it is, the possibility that it reads PSPHS cannot be fully excluded.
Finding the PSTP writer is the single question that closes this.
5c. THE PSTP WRITER - found, and it closes the question [V]¶
Nothing writes PSTP as a register. It is derived at microprogram start from a
control-store cell:
cell 0o21 (PSTBASE) -> page number -> PSTP = page << 11 (byte address)
cell 0o20 (OFFSET) -> context (register) block base, already a byte address
ND-05.020.01 section 6.6 states the register's own semantics outright:
"PSTP - 0 - Physical Segment Table Pointer - 30 bits - Read/Write. This register, shifted 11 places to the left, points to the start of the Physical Segment Table."
The control store itself is filled by SINTRAN over the octobus with LOCSM / CMWWC (023B),
which DMAs microwords from MPM into the store. So the value in cell 0o21 is whatever SINTRAN
put there.
It is PATCHED by SINTRAN, not shipped [V]¶
The shipped ND-5800 microcode (MICRO-5800-B30.DATA, 262144 bytes = 16384 microwords) carries:
cell 0x10 (0o20, OFFSET) LARG = 0x00000000
cell 0x11 (0o21, PSTBASE) LARG = 0x00000002
The live machine runs PSTP = 0x0003A000, i.e. page 0x74. 0x74 is not 2, so something
overwrote the shipped value between load and start.
That conclusion does not rest on the file alone. A physical segment table base is a runtime allocation - where SINTRAN puts the PST depends on the machine's memory configuration - so it cannot be a constant baked into a shipped microcode image. The file check simply confirms what the reasoning already requires.
Consequence: the PSPHS question is CLOSED¶
The ND-500's physical segment table base comes from a control-store cell that SINTRAN
patches, not from PSPHS. Combined with 5b - four PSPHS sites, none writing toward the
ND-500 - the earlier caution can now be stated positively:
PSPHS/PSLLI/PSULI/PSMOD are the ND-100's own segment bookkeeping. They are not the
source of the ND-500 hardware PST, and PSPHS is not "the table RPHS reads".
Still not carved: the exact ND-100 instructions that patch cell 0o21. That is a smaller
question than it was - the mechanism, the cell, the transport and the proof that a patch happens
are all in hand.
5d. FLAGGED - our PSTP holds BYTES where the hardware register holds PAGES¶
ND-05.020.01 6.6 is explicit that the register is shifted 11 places left to give the table
start, so the hardware PSTP contains a page number. RetroCore stores the byte address:
OctobusND5000Station computes pstpBytes = pstBasePage << 11 and writes that into
regs.PSTP, and the MMU then uses PSTP + index*4 directly as a physical address.
Internally consistent, so the walk is correct. But ReadRegister(PSTP) returns the same byte
value, so a guest reading PSTP back gets 2048x the hardware value - 0x3A000 where real
hardware would report 0x74. Reachable through the ND-5000 monitor's own documented commands
(LOOK-AT-HARDWARE A,IMM,PSTP, and the MEMORY-CONFIGURATION display quoted in the same
manual).
Not fixed here: it is a separate change with its own blast radius, and no guest read of PSTP
has been observed yet. Recorded so it is not rediscovered as a mystery.
6. Next step¶
- ~~Carve the three whole-family consumers.~~ DONE - see 5a and 5b.
- ~~Find what writes
PSTP.~~ DONE - see 5c. It is control-store cell0o21, patched by SINTRAN. ThePSPHS-vs-PST question is closed. - Dump
PSPHS[seg]live at the trap. Now decisive rather than suggestive: three code paths agree that zero means "not placed", so a zero reading is a definite answer. - Optional, small: carve the ND-100 instructions that patch cell
0o21, to name the routine. - Decide what to do about 5d (
PSTPbyte-vs-page), if a guest is ever seen reading it back.