ND-110 Microcode Boot Process
Overview
This document describes the complete boot process of the ND-110 minicomputer as implemented in microcode (ND-110-RASK.uc). The boot process starts from power-on/master clear and ends with the CPU executing loaded program code. There are two distinct loader paths depending on the device type selected via the ALD (Automatic Load Description) thumbwheel switch.
Boot Flow — High Level
flowchart TD
POWERON["Power-On / Master Clear<br/>(ucode address 000000)"] --> MACL["MACL Routine<br/>(001752)"]
MACL --> INIT["Initialize CPU State<br/>- COMM,SIOC config (30120)<br/>- Clear interrupts/masks<br/>- Setup microprogram environment"]
INIT --> EAUTO{"EAUTO~ flag<br/>(STS bit 13)"}
EAUTO -->|"TRUE (bit set)"| STOP2["STOP2 — MOPC Stop<br/>(Operator Control)"]
EAUTO -->|"FALSE (bit clear)"| ALDAUTO["Auto-boot via ALD"]
STOP2 --> WAITPANEL["Wait for Panel Interrupt<br/>(trap vector 0x0E)"]
WAITPANEL --> PANEL["PANEL Handler<br/>(000050)"]
PANEL --> PANVC["PANVC Vector Table<br/>(003760)"]
PANVC -->|"Button 0"| STOP["STOP"]
PANVC -->|"Button 1"| MS20["MS20 (20ms timer)"]
PANVC -->|"Button 4"| LOAD["LOAD<br/>(002202)"]
PANVC -->|"Button 5"| CONT["CONTINUE"]
PANVC -->|"Button 6"| RSTRT["RESTART"]
PANVC -->|"Button 7"| MACL
ALDAUTO --> ALDLO
LOAD --> ALDLO["ALDLO<br/>(002140)<br/>Read ALD switch"]
ALDLO --> ALDVC["ALDVC Vector Table<br/>(003560)<br/>Returns device load code in Q"]
ALDVC --> LOAD1["LOAD1<br/>(002144)<br/>Analyze load code"]
LOAD1 --> BIT13{"Bit 13 of<br/>load code?"}
BIT13 -->|"1 — Block transfer"| MASS["MASS Loader<br/>(002147)"]
BIT13 -->|"0 — Serial transfer"| ETLO1["ETLO1 Binary Loader<br/>(005534)"]
MASS --> EXEC["Start execution at P=0"]
ETLO1 --> EXEC
EXEC --> ESCAP["ESCAP<br/>(002530)"]
Panel Vector Dispatch (PANVC)
When the operator presses a panel button, trap vector 0x0E fires. The microcode reads IDBS,PANEL to get the button code, loads it into the loop counter via COMM,LDLC, and dispatches through the PANVC table using T,JMPAOPR.
flowchart LR
TRAP["Trap 0x0E"] --> READ["IDBS,PANEL<br/>Read button code"]
READ --> LDLC["COMM,LDLC<br/>Load into Loop Counter"]
LDLC --> JMPAOPR["T,JMPAOPR PANVC<br/>Jump to PANVC + (LC & 0xF)"]
JMPAOPR --> TABLE
subgraph TABLE ["PANVC Table (003760-003767)"]
direction TB
V0["0: STOP"]
V1["1: MS20"]
V2["2: PRQ"]
V3["3: SING2"]
V4["4: LOAD"]
V5["5: CONT"]
V6["6: RSTRT"]
V7["7: MACL"]
end
ALD Device Selection (ALDVC)
The ALD thumbwheel on the CPU crate selects the boot device. ALDLO reads IDBS,ALD, loads the value into the loop counter, and dispatches through ALDVC. Each entry returns a device load code in the Q register.
flowchart TD
ALDLO["ALDLO (002140)<br/>IDBS,ALD → COMM,LDLC"] --> JMPAOPR["T,JMPAOPR ALDVC<br/>Jump to ALDVC + (LC & 0xF)"]
JMPAOPR --> TABLE
subgraph TABLE ["ALDVC Table (003560-003577) — 16 entries"]
direction TB
E0["0: Q=0 (none)"]
E1["1: Q=1560 (floppy)"]
E2["2: Q=20500 (SMD)"]
E3["3: Q=21540 (SMD unit 1)"]
E4["4: Q=400"]
E5["5: Q=1600 (SCSI/HDLC)"]
E6["6: Q=21560 (SMD/SCSI)"]
E7["7: Q=0 (none)"]
E8["8-15: Extended device codes<br/>(100000, 101560, 120500, ...)"]
end
TABLE --> RETURN["T,RETURN T,POP<br/>Return to LOAD1 with Q = load code"]
ALDVC Load Code Table (all values in octal)
| ALD Position | Load Code | Bit 13 | Loader Path | Device Type |
|---|---|---|---|---|
| 0 | 0 | — | None | No boot |
| 1 | 1560 | 0 | ETLO1 (serial) | Floppy |
| 2 | 20500 | 1 | MASS (block) | SMD disk |
| 3 | 21540 | 1 | MASS (block) | SMD unit 1 |
| 4 | 400 | 0 | ETLO1 (serial) | Serial device |
| 5 | 1600 | 0 | ETLO1 (serial) | SCSI / HDLC |
| 6 | 21560 | 1 | MASS (block) | SMD/SCSI block |
| 7 | 0 | — | None | No boot |
| 8 | 100000 | — | — | Extended |
| 9 | 101560 | 0 | ETLO1 (serial) | Extended floppy |
| 10 | 120500 | 1 | MASS (block) | Extended SMD |
| 11 | 121540 | 1 | MASS (block) | Extended SMD |
| 12 | 100400 | 0 | ETLO1 (serial) | Extended serial |
| 13 | 101600 | 0 | ETLO1 (serial) | Extended SCSI/HDLC |
| 14 | 121560 | 1 | MASS (block) | Extended block |
| 15 | 100000 | — | — | Extended |
LOAD1 — Load Code Decision (002144)
LOAD1 receives the device load code in Q and determines which loader to use.
flowchart TD
ENTRY["LOAD1 (002144)<br/>Q = load code from ALDVC"] --> MASK["A,17 ALUF,MASKDQ<br/>Mask Q to 16 bits"]
MASK --> ZERO{"Q = 0?"}
ZERO -->|"Yes"| CONT["CONT — Continue<br/>(no boot device)"]
ZERO -->|"No"| TESTBIT["A,15 ALUF,ANDDQ<br/>Test bit 13 of Q"]
TESTBIT --> BIT13{"Bit 13?"}
BIT13 -->|"1"| MASS["MASS (002147)<br/>Block transfer loader"]
BIT13 -->|"0"| PREP["A,7 B,STS ALUF,D-1<br/>Prepare device number"]
PREP --> ETLO1["ETLO1 (005534)<br/>Binary serial loader"]
style MASS fill:#009688,stroke:#00695C,stroke-width:2px,color:#fff
style ETLO1 fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
MASS Storage Loader (002147) — Block Transfer
Used for SMD-type disks (load codes with bit 13 = 1). Performs DMA-style block transfer using IOX register offsets relative to device base address N. The sequence is identical for all mass storage devices — only N differs.
flowchart TD
ENTRY["MASS (002147)<br/>Q = load code, D = device base"] --> CALC["D = D + Q + 1<br/>Calculate device register base"]
CALC --> MAS1["MAS1: A = 0<br/>Clear accumulator"]
MAS1 --> IOX1["IOX N+1 — Core Address<br/>R1 = D-1, data = 0<br/>Set memory load address to 0"]
IOX1 --> IOX1B["IOX N+1 — Core Address (repeat)<br/>R1 = D-1, data = 0"]
IOX1B --> IOX3["IOX N+3 — Block Address<br/>R1 = D+1, data = 0<br/>Read from block 0 (boot block)"]
IOX3 --> IOX7["IOX N+7 — Word Counter<br/>R1 = D+5, data = 2000₈<br/>Transfer 1024 words (1 page)"]
IOX7 --> IOX5["IOX N+5 — Activate Device<br/>R1 = D+A+1, data = 2<br/>Start read operation"]
IOX5 --> MAS2["MAS2: IOX N+4 — Read Status<br/>R1 = D+A"]
MAS2 --> POLL{"Status bit 2<br/>(AND with 4)"}
POLL -->|"0 — Busy"| WAIT["Load timeout counter<br/>R7 = 2"]
WAIT --> MAS2
POLL -->|"1 — Done"| CHECK{"Error bits<br/>(A AND R7)"}
CHECK -->|"Errors"| MAS1
CHECK -->|"Clean"| START["P = 0<br/>COMM,START<br/>Clear STOP flipflop"]
START --> ESCAP["JMP ESCAP<br/>Begin execution"]
style IOX1 fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
style IOX1B fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
style IOX3 fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
style IOX7 fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
style IOX5 fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
style MAS2 fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
MASS IOX Register Map
| IOX Offset | Address Calc | Data Sent | Purpose |
|---|---|---|---|
| N+1 | R1 = D - 1 | 0 | Core (memory) load address |
| N+1 | R1 = D - 1 | 0 | Core address (repeated) |
| N+3 | R1 = D + 1 | 0 | Block address on device (boot = block 0) |
| N+7 | R1 = D + 5 | 2000₈ | Word count for transfer (see note below) |
| N+5 | R1 = D + A + 1 | 2 | Activate device (start read) |
| N+4 | R1 = D + A | — | Read device status (poll loop) |
Bootstrap Data Size
The microcode comment at line 5359 says IOX N+7 (2000) WORD COUNTER. The value 2000₈ = 1024 words = 2048 bytes = 1 SINTRAN page.
Source: ND-110-RASK.uc line 5359 (comment), PH-P2-OPPSTART.NPL line 270 (page = 2000₈ words)
Note: The instruction at line 5364 loads
A,12(octal 12 = 10 decimal) into the ALU, but the comment explicitly says the value sent to the device is 2000₈. I cannot fully trace how the ALU datapath transforms 12₈ into 2000₈ — the comment is the primary evidence for the word count value.
This means the MASS bootstrap reads disk page 0 (block 0) — exactly 1 page (1024 words / 2048 bytes) — into memory address 0, then executes from address 0.
Source: ND-30.003.007 EN SINTRAN III System Supervisor.md lines 2923-2965:
- Disk Page 0 contains "BOOTSTRAP AND DIRECTORY ENTRY"
- "The bootstrap is read into physical memory by the ND-100 CPU, and is started"
- "The bootstrap program copies the SINTRAN IMAGE AREA to the MEMORY AREA"
The bootstrap in page 0 is a small standalone program. Once running, it uses the disk driver to load the full SINTRAN image from the SINTRAN IMAGE AREA on disk into memory.
Status Polling
- Bit 2 (mask = 4): Transfer complete flag. Loop until set.
- Error bits: Checked via AND with R7 after completion. On error, retry from MAS1.
Start Address: 0 vs 20₈
The MASS loader always starts at address 0. The comment at line 5404 is explicit: NO ERRORS, START IN 0, AND LEAVE NOPC. Line 5406 sets P = 0 unconditionally.
Source: ND-110-RASK.uc lines 5404-5408
The decision between address 0 and 20₈ happens before the loader, in the ALD/restart path (lines 5449-5465):
flowchart TD
ALDVC["ALDVC returns load code in Q"] --> F15{"Test bit 15 of Q<br/>(COND,F15 at line 5451)"}
F15 -->|"Bit 15 = 1<br/>(Load and run)"| LOAD1["LOAD1 (line 5458)<br/>→ MASS or ETLO1<br/>Load data from device"]
LOAD1 --> START0["MASS: P = 0, COMM,START<br/>ETLO1: P = loaded address, COMM,START"]
F15 -->|"Bit 15 = 0<br/>(Run only, no load)"| ASTR2["ASTR2 (line 5463)<br/>P = 20₈<br/>→ CONTI (continue)"]
style LOAD1 fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
style ASTR2 fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
Source: ND-110-RASK.uc lines 5449-5465. Comment at line 5454: LOAD IF BIT 15=1, START IN ADDRESS 20 IF BIT 15=0
This maps to the ALD positions documented in OPCOM:
| ALD Positions | Bit 15 | Action |
|---|---|---|
| 8-15 | 1 | Load from device, then start at address 0 |
| 2-7 | 0 | No load — start at address 20₈ (SINTRAN power-fail restart entry) |
Source: OPCOM-COMMAND-REFERENCE.md lines 653-658
Address 20₈ is the SINTRAN warm-start entry point. It's used when standby power preserved memory contents during a power failure — no loading is needed, the OS is already in memory. The microcode just jumps to 20₈ where SINTRAN's restart code resides.
Address 0 is the cold-start entry point. The bootstrap loaded into page 0 from disk starts executing here and loads the full SINTRAN image.
Boot Data Size — All Device Types
| Boot Path | Device Types | Data Size | Source Address | Memory Address | Start Address |
|---|---|---|---|---|---|
| MASS | SMD, Winchester, SCSI (bit 13=1) | 1 page (1024 words / 2048 bytes) | Block 0 | 0 | 0 |
| ETLO1 | Floppy PIO, Floppy DMA, HDLC (bit 13=0) | Variable — defined by BPUN stream | Sector 0 (floppy) / remote (HDLC) | Defined in BPUN header | Defined in BPUN header |
MASS path: The microcode reads a fixed amount — 1 page from block 0. The bootstrap in that page is responsible for loading SINTRAN. This is the same for SMD, Winchester, and SCSI — the microcode is device-agnostic.
Source: ND-110-RASK.uc line 5359 comment, ND-30.003.007 EN line 2963
ETLO1 path: The microcode reads a variable amount. The BPUN binary format is self-describing — each block in the stream contains its own core address, word count, and checksum. The microcode reads bytes until the BPUN protocol indicates completion. For floppy boot, the controller's autoload microprogram reads track 0, sector 1 into its internal buffer first (source: NDBusFloppyPIO.cs line 570-585), then the microcode reads bytes from that buffer.
Source: ND-110-RASK.uc lines 5534-5640 (ETLO1 protocol), NDBusFloppyPIO.cs line 570 (floppy autoload reads track 0 sector 1)
After the initial bootstrap is loaded (by either path), the bootstrap program itself takes over and loads the full SINTRAN operating system using the disk swap driver.
Source: ND-30.003.007 EN line 2963-2965
ETLO1 Binary Loader (005534) — Serial/Character Transfer
Used for floppy, SCSI, HDLC devices (load codes with bit 13 = 0). Reads a binary-format program character by character through IOX. The sequence is identical for all serial devices — only the device base address N differs.
flowchart TD
ENTRY["ETLO1 (005534)<br/>D = Q (device number)"] --> DVACT["DVACT (005672)<br/>IOX N+3 with data 4005₈<br/>Activate device"]
DVACT --> SEEK["SEEK: Set initial load address<br/>P = R5"]
SEEK --> SIKI["SIKI: Read octal number<br/>Call ASS8 subroutine"]
SIKI --> PARSE{"Parse input<br/>character"}
PARSE -->|"Octal digit"| SIKI
PARSE -->|"CR (carriage return)"| SEEK
PARSE -->|"! (exclamation)"| EXFOU["EXFOU: Binary load block"]
EXFOU --> READADDR["BIN: Read core address<br/>(16-bit word from 2 bytes)"]
READADDR --> READCOUNT["BIN: Read word count<br/>→ X register"]
READCOUNT --> READDATA["BIN: Read data word<br/>→ T register"]
READDATA --> STLP["STLP: Store word loop"]
STLP --> STORE["Write word to memory<br/>COMM,WRRQ,PT"]
STORE --> DEC{"Word count<br/>T = T - 1"}
DEC -->|"T > 0"| INCADDR["X = X + 1<br/>Increment address"]
INCADDR --> CHECKSUM["L = L + Z<br/>Accumulate checksum"]
CHECKSUM --> STLP
DEC -->|"T = 0"| VERIFY["Read expected checksum<br/>via BIN"]
VERIFY --> CHKTEST{"XOR checksums<br/>match?"}
CHKTEST -->|"Yes"| ACTION{"Action code?"}
CHKTEST -->|"No"| ERROR["Checksum error"]
ACTION -->|"More blocks"| EXFOU
ACTION -->|"Done"| START["P = loaded start address<br/>COMM,START"]
START --> ESCAP["JMP ESCAP"]
style DVACT fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
style STORE fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
ETLO1 Subroutines
INCH — Read One Character (005644)
flowchart LR
INCH["INCH (005644)"] --> IOX2["IOX N+2<br/>R1 = D + A<br/>Read device status"]
IOX2 --> TEST{"Bit 3 set?<br/>(AND with 10₈)"}
TEST -->|"No — not ready"| INCH
TEST -->|"Yes — ready"| IOXN["IOX N<br/>R1 = D<br/>Read data byte"]
IOXN --> RETURN["Return with<br/>character in A"]
DVACT — Activate Device (005672)
flowchart LR
DVACT["DVACT (005672)"] --> LOAD["A = 4005₈"]
LOAD --> IOX3["IOX N+3<br/>R1 = D + A + 1<br/>Data = 4005₈"]
IOX3 --> RETURN["Return"]
BIN — Read 16-bit Word (005731)
Reads two bytes via INCH and combines them into a 16-bit word.
ETLO1 IOX Register Map
| IOX Offset | Used In | Purpose |
|---|---|---|
| N | INCH | Read data byte from device |
| N+2 | INCH | Read device status (poll for ready) |
| N+3 | DVACT | Activate device (data = 4005₈) |
Status Polling (INCH)
- Bit 3 (mask = 10₈): Data ready flag. Loop on IOX N+2 until set, then read byte from IOX N.
IOXG Subroutine — IOX Routing (001363)
All IOX operations go through IOXG, which routes based on device number:
flowchart TD
IOXG["IOXG (001363)<br/>R1 = IOX address<br/>A = data to write"] --> MASK["Q = 3777₈<br/>R1 = R1 AND Q<br/>(mask to 11 bits)"]
MASK --> IOXX1["IOXX1 (001374)<br/>Extract device number"]
IOXX1 --> TEST30X{"Device 30x?<br/>(XOR with 300₈)"}
TEST30X -->|"Yes"| TERMX["TERMX → TRMVC<br/>CPU-board device<br/>(UART, terminal)"]
TEST30X -->|"No"| TEST10{"Device 10-13?<br/>(XOR with 10₈)"}
TEST10 -->|"Yes"| CLKX["CLKX → CLKVC<br/>Clock device"]
TEST10 -->|"No"| GENERAL["General IOX (001404)<br/>External I/O bus"]
subgraph GENERAL_DETAIL ["General IOX Execution"]
direction TB
G1["1. R1 (address) → ALU out"]
G2["2. A (data) → ALU out + COMM,IOX"]
G3["3. Read DBR → A (response data)"]
G1 --> G2 --> G3
end
GENERAL --> GENERAL_DETAIL
GENERAL_DETAIL --> RET["T,RETURN T,POP"]
style GENERAL fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
style TERMX fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
style CLKX fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
For boot devices (floppy N=1560, SMD N=20500, SCSI N=1600): These are NOT in the 30x or 10-13 range, so they always take the General IOX path which issues COMM,IOX on the external I/O bus.
Complete Boot Sequence — Timing Diagram
sequenceDiagram
participant OP as Operator
participant PANEL as Panel
participant UC as Microcode
participant ALD as ALD Switch
participant IOX as I/O Bus
participant DEV as Boot Device
Note over UC: Power-On → MACL (000000)
UC->>UC: Initialize CPU state (001752)
UC->>UC: Check EAUTO~ (STS bit 13)
alt EAUTO~ = TRUE
UC->>UC: STOP2 — Wait at MOPC
OP->>PANEL: Press LOAD button
PANEL->>UC: Trap 0x0E (Panel Interrupt)
UC->>PANEL: IDBS,PANEL — Read button code (4)
UC->>UC: PANVC[4] → LOAD (002202)
else EAUTO~ = FALSE
UC->>UC: Auto-boot via ALD
end
UC->>ALD: IDBS,ALD — Read thumbwheel
ALD-->>UC: Device code (e.g., 2 for SMD)
UC->>UC: ALDVC[2] → Q = 20500
UC->>UC: LOAD1 — Test bit 13 of Q
alt Bit 13 = 1 (MASS — SMD)
UC->>IOX: IOX N+1: Core address = 0
IOX->>DEV: Set memory target
UC->>IOX: IOX N+3: Block address = 0
IOX->>DEV: Set boot block
UC->>IOX: IOX N+7: Word count = 12₈
IOX->>DEV: Set transfer size
UC->>IOX: IOX N+5: Activate (data=2)
IOX->>DEV: Start read
loop Poll status
UC->>IOX: IOX N+4: Read status
IOX-->>UC: Status word
end
DEV-->>IOX: Transfer complete (bit 2 set)
IOX-->>UC: Done
else Bit 13 = 0 (ETLO1 — Floppy/SCSI)
UC->>IOX: IOX N+3: Activate (data=4005₈)
IOX->>DEV: Activate device
loop Read program
UC->>IOX: IOX N+2: Poll status
IOX-->>UC: Ready (bit 3 set)
UC->>IOX: IOX N: Read byte
IOX-->>UC: Data byte
UC->>UC: Store to memory
end
end
UC->>UC: P = start address, COMM,START
UC->>UC: ESCAP — Begin OS execution
Emulator Implementation Status
The microcode boot flow is executed by the emulator in ND110CPU. The microcode itself (loaded into ControlStore) contains all the logic — MACL, PANVC, ALDVC, MASS, ETLO1 are all microcode routines, not C# code.
What works
| Component | Emulator File | How It Works |
|---|---|---|
| MACL vector dispatch | Cpu.cs |
Microcode at address 0 executes normally |
| Panel interrupt (trap 0x0E) | CpuInternals.cs:246 |
Panel_OnPanelInterruptEvent() → SetTrap(0x0E) |
| IDBS,PANEL | Cpu.cs:2928 |
Returns panel.InterruptVector |
| IDBS,ALD | Cpu.cs:2901 |
Returns ALD register with ND-110/ND-120 bit manipulation |
| COMM,LDLC | Cpu.cs:1267 |
Loads 6-bit signed loop counter from IDB |
| T,JMPAOPR | Cpu.cs:792-799 |
(jmpAddress & 0x1FF0) \| (A_Operand & 0x0F) — handles PANVC and ALDVC dispatch |
| MIC stack (PUSH/POP/RETURN) | MIC.cs:290-336 |
4-level hardware stack for subroutine calls |
| Loop Counter as A/B operand | Cpu.cs:2525,2483 |
loopCounter & 0x0F feeds into ALU operands |
| EAUTO~ flag | Uart.cs:59 |
Controls MACL → STOP vs MACL → ALD path |
Where boot breaks
| Component | Emulator File | Issue |
|---|---|---|
| IOX instruction | Cpu.cs:2028 |
Stub — logs "@@IOX" and sets IOX Error interrupt |
| IDENT instruction | Cpu.cs:2017 |
Stub — no device identification |
| I/O Bus | Not implemented | No bus routing IOX addresses to controllers |
| Device controllers | Not implemented | No floppy, SMD, SCSI, or HDLC emulation |
| ALD default value | Registers.cs:158 |
Initialized to 0 — no boot device selected |
Where execution stops
MACL ✓ → PANEL/LOAD ✓ → ALDLO ✓ → ALDVC ✓ → LOAD1 ✓ → MASS/ETLO1 ✓
→ First IOXG call → COMM,IOX ✗ (stub, sets IOX Error) → Boot fails
Device IOX Register Maps — Cross-Reference with Microcode
The microcode MASS and ETLO1 loaders use generic IOX offsets (N+0 through N+7). The actual meaning of each offset depends on the device controller hardware. Here is how the microcode's IOX offsets map to each device type, based on the emulated drivers in RetroCore\Emulated.HW\ND\CPU\NDBUS\.
Floppy PIO (NDBusFloppyPIO) — Device N = octal 1560
Used by ETLO1 binary loader (ALD 1, load code 1560, bit 13 = 0).
| IOX Offset | Address | Register | Used in Boot? | Purpose |
|---|---|---|---|---|
| N+0 | 1560 | ReadDataBuffer | Yes (INCH) | Read one 16-bit word from interface buffer |
| N+1 | 1561 | WriteDataBuffer | — | Write data to buffer |
| N+2 | 1562 | ReadStatusRegister1 (RSR1) | Yes (INCH) | Poll for ready (bit 3 = ReadyForTransfer) |
| N+3 | 1563 | WriteControlWord (WCWD) | Yes (DVACT) | Issue commands. Microcode sends 4005₈ |
| N+4 | 1564 | ReadStatusRegister2 (RSR2) | — | Error status |
| N+5 | 1565 | WriteDriveAddress | — | Drive/track selection |
| N+6 | 1566 | ReadTestData | — | Test mode |
| N+7 | 1567 | WriteSector | — | Sector selection |
PIO Control Word bits (IOX N+3 = WCWD):
| Bit | Name | Boot Value (4005₈) |
|---|---|---|
| 1 | Enable Interrupt | 0 |
| 2 | Autoload | 1 |
| 3 | Test Mode | 0 |
| 4 | Device Clear | 0 |
| 5 | Clear Buffer Address | 0 |
| 6 | Enable Timeout | 0 |
| 8 | Format Track | 0 |
| 9 | Write Data | 0 |
| 10 | Write Deleted Data | 0 |
| 11 | Read ID | 1 |
| 12 | Read Data | 0 |
| 13 | Seek | 0 |
| 14 | Recalibrate | 0 |
| 15 | Control Reset | 0 |
The microcode sends 4005₈ = 0b_100_000_000_101 = Autoload (bit 2) + ReadID (bit 11) via DVACT. This tells the PIO floppy controller to perform an autoload/boot sequence.
PIO Status Register 1 bits (IOX N+2 = RSR1):
| Bit | Name | Boot Check |
|---|---|---|
| 2 | Device Busy | — |
| 3 | Ready For Transfer | Polled by INCH (mask 10₈) |
| 6 | Read/Write Complete | — |
Floppy DMA (NDBusFloppyDMA, card 3112) — Device N = octal 1560
Also at address 1560 but with different register semantics. The DMA controller has a critical difference: Bit 2 of the Control Word is "Activate Autoload" which triggers the controller's own boot microprogram to DMA boot code directly into ND-100 memory.
| IOX Offset | Address | Register | Used in Boot? | Purpose |
|---|---|---|---|---|
| N+0 | 1560 | ReadData | — | Read data |
| N+1 | 1561 | (Not Used) | — | — |
| N+2 | 1562 | ReadStatusRegister1 (RSR1) | Yes | Poll status |
| N+3 | 1563 | LoadControlWord | Yes (DVACT) | Commands + Autoload trigger |
| N+4 | 1564 | ReadStatusRegister2 (RSR2) | — | Format/error info |
| N+5 | 1565 | LoadPointerHigh | — | Memory address bits 16-23 |
| N+6 | 1566 | (Not Used) | — | — |
| N+7 | 1567 | LoadPointerLow / LoadData | — | Memory address bits 0-15 |
DMA Control Word bits (IOX N+3):
| Bit | Name | Boot Value (4005₈) |
|---|---|---|
| 1 | Enable Interrupt | 0 |
| 2 | Activate Autoload | 1 |
| 3 | Test Mode | 0 |
| 4 | Device Clear | 0 |
| 5 | Enable Streamer | 0 |
| 8 | Execute Command | 0 |
With 4005₈ = bit 2 + bit 11 set: The DMA controller sees ActivateAutoload = 1 and calls its internal ExecuteAutoload() microprogram, which DMA-transfers the boot code (BPUN bootstrapper) from the floppy's first sector directly into ND-100 memory — no further IOX interaction needed from the CPU microcode.
DMA Status Register 1 bits (IOX N+2 = RSR1):
| Bit | Name | Note |
|---|---|---|
| 2 | Device Active | Controller busy |
| 3 | Ready For Transfer | Polled by INCH |
| 5 | Deleted Record / EOF | — |
| 7 | Hard Error | — |
| 9-14 | Error Code | From controller microprogram |
| 15 | Dual Density Controller | Always 1 = identifies as DMA (not PIO) |
Note: The DMA controller returns RSR1 | DualDensityController for both IOX N+2 and N+4 reads — they are intentionally duplicated so the microcode MASS loader (which reads N+4 for status) and the ETLO1 loader (which reads N+2) both work.
SMD Disk Controller — Device N = octal 1540
Used by MASS block loader (ALD 2, load code 20500, bit 13 = 1).
| IOX Offset | Address | Register | Used in Boot? | Purpose |
|---|---|---|---|---|
| N+0 | 1540 | ReadMemoryAddress | — | Read back core address |
| N+1 | 1541 | LoadMemoryAddress | Yes | Set DMA target in ND-100 memory |
| N+2 | 1542 | ReadSeekCondition | — | Seek status |
| N+3 | 1543 | LoadBlockAddress | Yes | Set disk block to read |
| N+4 | 1544 | ReadStatusRegister | Yes | Poll for completion (bit 2) |
| N+5 | 1545 | LoadControlWord | Yes | Activate read operation |
| N+6 | 1546 | ReadBlockAddress | — | Read back block address |
| N+7 | 1547 | LoadWordCounter | Yes | Set transfer size |
This maps exactly to the MASS loader's IOX sequence:
| MASS Step | IOX | SMD Register | Data |
|---|---|---|---|
| Core address | N+1 | LoadMemoryAddress | 0 (load to address 0) |
| Block address | N+3 | LoadBlockAddress | 0 (boot block) |
| Word counter | N+7 | LoadWordCounter | 12₈ |
| Activate | N+5 | LoadControlWord | 2 (start read) |
| Poll status | N+4 | ReadStatusRegister | Check bit 2 |
SCSI Controller (NDBusDiscControllerSCSI, card 3201) — Device N = octal 144300
The SCSI controller uses a completely different addressing scheme — 64 IOX registers (not 8). The SCSI controller is controlled by an onboard Z80 CPU with NCR5386 SCSI protocol chip.
| IOX Offset | Register | Purpose |
|---|---|---|
| N+0 | RLMAR | Read Memory Address bits 0-15 |
| N+1 | WLMAR | Write Memory Address bits 0-15 |
| N+2 | REDAT | Read Data (IOX mode only) |
| N+3 | WRDAT | Write Data (IOX mode only) |
| N+4 | RSTAU | Read Status |
| N+5 | WCONT | Write Control |
| N+6 | RHMAR | Read Memory Address bits 16-23 |
| N+7 | WHMAR | Write Memory Address bits 16-23 |
The first 8 registers follow the same general pattern as SMD — the microcode's generic IOX N+1/N+3/N+5/N+7/N+4 sequence maps to equivalent functions.
Why the Same Microcode Works for All Devices
The ND-100 bus device standard requires all mass storage controllers to implement a common IOX register layout for the first 8 offsets:
N+0: Read data / Read memory address
N+1: Write data / Load memory address
N+2: Read status 1
N+3: Write control / Load block address
N+4: Read status 2 / Read status
N+5: Write address / Load control word
N+6: Read test / Read block address
N+7: Write sector / Load word counter
This is why the MASS loader's generic sequence works identically for SMD, SCSI, and DMA floppy controllers — they all follow the same register convention. The PIO floppy uses a slightly different convention (data at N+0/N+1 instead of memory address), which is why it uses the ETLO1 serial path instead.
HDLC Auto Load — Device N = octal 1600
Used by ETLO1 binary loader (ALD 5, load code 1600, bit 13 = 0).
HDLC boot uses a dedicated autoload interface at IOX 1600-1603 on the PCB 3015 (HDLC + Autoload) card. This is separate from the main HDLC DMA/PIO interface (IOX 1640+). The autoload interface has only 4 registers — a simplified bootstrap-only interface.
HDLC Auto Load Register Map (IOX 1600-1603)
| IOX | Address | R/W | Register | Used in Boot? |
|---|---|---|---|---|
| N+0 | 1600 | Read | Read Data | Yes (INCH) |
| N+1 | 1601 | Write | (Write Data / Control) | — |
| N+2 | 1602 | Read | Read Status | Yes (INCH) |
| N+3 | 1603 | Write | Write Control Word | Yes (DVACT) |
IOX 1600 — Read Data (N+0)
Read one byte of the incoming boot program from the HDLC controller's receive buffer.
| Bits | Description |
|---|---|
| 0-7 | Received data byte |
| 8-15 | Unused |
IOX 1602 — Read Status (N+2)
Status register polled by the INCH subroutine during boot.
| Bit | Name | Description |
|---|---|---|
| 0 | — | Not used |
| 1 | Interrupt Enabled | Interrupt flag |
| 2 | Device Busy | Controller is processing |
| 3 | Ready For Transfer | Data byte available — polled by INCH (mask 10₈) |
| 4 | Error indicator | OR of error bits |
| 5-15 | — | Additional status / unused |
The INCH subroutine tests bit 3 (AND with 10₈ = 8 decimal). When set, it means a byte is available to read from IOX 1600.
IOX 1603 — Write Control Word (N+3)
Written by DVACT to activate the autoload process. The microcode sends 4005₈.
4005₈ = 0b_100_000_000_101 = 0x0805
| Bit | Value | Name | Description |
|---|---|---|---|
| 0 | 1 | — | Set |
| 1 | 0 | Enable Interrupt | Not enabled during boot |
| 2 | 1 | Activate Autoload | Triggers the HDLC controller to request boot image from remote machine |
| 3 | 0 | Test Mode | Not set |
| 4 | 0 | Device Clear | Not set |
| 5-7 | 0 | — | Not used |
| 8 | 0 | — | Not set |
| 9 | 0 | — | Not set |
| 10 | 0 | — | Not set |
| 11 | 1 | (Command bit) | Set (same pattern as floppy PIO ReadID) |
| 12-15 | 0 | — | Not set |
The critical bit is bit 2 (Activate Autoload). When the HDLC controller sees this, it initiates a boot request over the HDLC link to the remote ND machine. The remote machine (running SINTRAN with HDLC remote load support) responds by transmitting the binary boot program.
This is the same control word pattern (4005₈) used by the floppy PIO (where bit 2 = Autoload) and floppy DMA (where bit 2 = ActivateAutoload). All three devices interpret 4005₈ as "start the boot/autoload process."
HDLC Remote Load Channels (IOX 1604-1637)
In addition to the auto load interface, the PCB 3015 card provides 7 HDLC Remote Load channels for loading from specific HDLC link numbers:
| Channel | IOX Range | Purpose |
|---|---|---|
| 1 | 1604-1607 | Remote load from HDLC link 1 |
| 2 | 1610-1613 | Remote load from HDLC link 2 |
| 3 | 1614-1617 | Remote load from HDLC link 3 |
| 4 | 1620-1623 | Remote load from HDLC link 4 |
| 5 | 1624-1627 | Remote load from HDLC link 5 |
| 6 | 1630-1633 | Remote load from HDLC link 6 |
| 7 | 1634-1637 | Remote load from HDLC link 7 |
Each channel has the same 4-register layout as the auto load (N+0 through N+3). These are not referenced in the ALDVC table but can be used for directed network boot from a specific link.
HDLC Auto Load — Detailed Flow
flowchart TD
ALD["ALD Switch = 5<br/>Load code = 1600₈<br/>Bit 13 = 0 → ETLO1"]
ALD --> ETLO1["ETLO1 (005534)<br/>D = 1600₈ (device base)"]
ETLO1 --> DVACT["DVACT (005672)<br/>IOX N+3 (1603₈)<br/>Write 4005₈"]
subgraph DVACT_BITS ["Control Word 4005₈ Bit Decode"]
direction TB
B2["Bit 2 = 1: Activate Autoload"]
B11["Bit 11 = 1: Command bit"]
end
DVACT --> DVACT_BITS
DVACT_BITS --> HDLC_REQ["HDLC Controller initiates<br/>boot request over link"]
HDLC_REQ --> REMOTE["Remote ND machine<br/>sends binary boot program<br/>via HDLC frames"]
REMOTE --> INCH_LOOP
subgraph INCH_LOOP ["INCH Character Read Loop"]
direction TB
POLL["IOX 1602₈ (N+2)<br/>Read Status Register"]
CHECK{"Bit 3 set?<br/>(Ready For Transfer)"}
READ["IOX 1600₈ (N+0)<br/>Read Data Byte → A"]
POLL --> CHECK
CHECK -->|"No — not ready"| POLL
CHECK -->|"Yes — byte available"| READ
end
INCH_LOOP --> PROCESS
subgraph PROCESS ["ETLO1 Binary Protocol"]
direction TB
SEEK2["SEEK: Parse start address"]
ASS82["ASS8: Read octal digits"]
EXFOU2["EXFOU: '!' found — binary block"]
BIN_ADDR["BIN: Read core address (2 bytes)"]
BIN_COUNT["BIN: Read word count (2 bytes)"]
STLP2["STLP: Read words, store to memory"]
CHKSUM["Verify checksum (XOR)"]
SEEK2 --> ASS82
ASS82 --> EXFOU2
EXFOU2 --> BIN_ADDR
BIN_ADDR --> BIN_COUNT
BIN_COUNT --> STLP2
STLP2 --> CHKSUM
end
PROCESS --> DONE{"More blocks?"}
DONE -->|"Yes"| INCH_LOOP
DONE -->|"No — all loaded"| START["P = start address<br/>COMM,START<br/>JMP ESCAP"]
sequenceDiagram
participant UC as CPU Microcode<br/>(ETLO1)
participant AL as HDLC Auto Load<br/>(IOX 1600-1603)
participant CTRL as HDLC Controller<br/>(PCB 3015)
participant LINK as HDLC Link
participant REMOTE as Remote ND Machine<br/>(SINTRAN)
Note over UC: ALD=5 → ALDVC → Q=1600₈ → ETLO1
UC->>AL: IOX 1603: Write 4005₈<br/>(Bit 2: Activate Autoload)
AL->>CTRL: Autoload triggered
CTRL->>LINK: HDLC boot request frame
LINK->>REMOTE: Request boot image
REMOTE-->>LINK: Binary boot stream<br/>(octal header + ! + binary blocks)
LINK-->>CTRL: Receive HDLC frames
CTRL-->>AL: Buffer received bytes
loop INCH — read each byte
UC->>AL: IOX 1602: Read status
AL-->>UC: Status (check bit 3)
Note over UC: Loop until bit 3 = 1
UC->>AL: IOX 1600: Read data byte
AL-->>UC: One byte
end
Note over UC: ASS8: Parse octal start address
Note over UC: EXFOU: '!' delimiter found
loop BIN — read 16-bit words
UC->>AL: IOX 1602 → IOX 1600 (high byte)
UC->>AL: IOX 1602 → IOX 1600 (low byte)
UC->>UC: Store word to memory[X]<br/>X++, T-- (word count)
UC->>UC: L = L + Z (accumulate checksum)
end
UC->>AL: IOX 1602 → IOX 1600 (checksum high)
UC->>AL: IOX 1602 → IOX 1600 (checksum low)
UC->>UC: Verify: XOR(L, expected) = 0?
alt Checksum OK
UC->>UC: P = loaded start address
UC->>UC: COMM,START → ESCAP
Note over UC: Begin OS execution
else Checksum Error
UC->>UC: Error handling
end
Comparison: All ETLO1 Devices
All three device types that use the ETLO1 binary serial loader share the same 4-register autoload pattern:
| Floppy PIO | Floppy DMA (3112) | HDLC Auto Load (3015) | |
|---|---|---|---|
| IOX Base | 1560₈ | 1560₈ | 1600₈ |
| N+0 Read | Read data buffer | Read data | Read data byte |
| N+2 Read | RSR1 (bit 3 = ready) | RSR1 (bit 3 = ready) | Status (bit 3 = ready) |
| N+3 Write | Control word | Control word | Control word |
| Boot command | 4005₈ | 4005₈ | 4005₈ |
| Bit 2 meaning | Autoload | Activate Autoload | Activate Autoload |
| Data source | Floppy disk sector | DMA from floppy | HDLC link from remote |
| Transfer | Byte-by-byte PIO | Controller DMA, then PIO | Byte-by-byte PIO |
Main HDLC DMA/PIO Controller — Full Register Map (IOX 1640+)
For reference, the main HDLC controller (used by SINTRAN for normal data communication, NOT for boot) has 16 registers. This is the interface at IOX 1640-1657 (for HDLC unit 1):
| IOX | R/W | Register | Description |
|---|---|---|---|
| +0 | R | ReadRxDR | Receiver Data Register — read assembled character from COM5025 |
| +1 | W | WritePCR | Parameter Control Register — protocol config (CRC, idle mode, etc.) |
| +2 | R | ReadReceiverStatusRegister | Receiver status from COM5025 |
| +3 | W | WriteSAR | Sync/Address Register — HDLC station address or SYNC character |
| +4 | R/W | CharacterLength | Character length config (receiver bits 0-2, transmitter bits 5-7) |
| +5 | W | WriteTransmitterDataRegister | Transmit Data Register — write byte to send |
| +6 | R | ReadTransmitterStatusRegister | Transmitter status from COM5025 |
| +7 | W | WriteTransmitterControlRegister | Transmitter control (same register as +6) |
| +10 | R | ReadReceiverTransferStatus (RRTS) | Receiver transfer status — see bit table below |
| +11 | W | WriteReceiverTransferControl (WRTC) | Receiver enable, DMA, interrupts, DTR |
| +12 | R | ReadTransmitterTransferStatus (RTTS) | Transmitter transfer status |
| +13 | W | WriteTransmitterTransferControl | Transmitter enable, DMA, RQTS |
| +14 | R | ReadDMAAddress | Read back DMA address |
| +15 | W | WriteDMAAddress | Set DMA buffer list base address (bits 0-15) |
| +16 | R | ReadDMACommand | Read DMA command register (0 = ready for new command) |
| +17 | W | WriteDMACommand | Write DMA command + address bits 16-17 |
RRTS — Receiver Transfer Status (IOX +10) Bit Map
| Bit | Name | Description |
|---|---|---|
| 0 | DataAvailable | Character assembled, ready to read from RxDR. IRQ 13 if enabled |
| 1 | StatusAvailable | Status info available in receiver status register. IRQ 13 if enabled |
| 2 | ReceiverActive | Receiver is within a frame (seen start, not end) |
| 3 | SyncFlagReceived | At least one SYNC/FLAG received since last read |
| 4 | DMAModuleRequest | DMA module requesting attention (cleared on read) |
| 5 | SignalDetector (SD) | CCITT 109 — modem signal (ignored by SINTRAN) |
| 6 | DataSetReady (DSR) | CCITT 107 — modem signal (ignored by SINTRAN) |
| 7 | RingIndicator (RI) | CCITT 125 — ring indicator |
| 8 | BlockEnd | DMA block transfer complete (cleared on read) |
| 9 | FrameEnd | DMA frame transfer complete (cleared on read) |
| 10 | ListEnd | End of DMA descriptor list |
| 11 | ListEmpty | No receive buffers — FATAL, shuts down receiver |
| 12 | (Undefined) | — |
| 13 | X21D | X.21 data indication error |
| 14 | X21S | X.21 call setup/clear indication error |
| 15 | ReceiverOverrun | Buffer overrun |
WRTC — Receiver Transfer Control (IOX +11) Bit Map
| Bit | Name | Description |
|---|---|---|
| 0 | DataAvailableIE | Enable IRQ 13 on data available |
| 1 | StatusAvailableIE | Enable IRQ 13 on status available |
| 2 | EnableReceiver (RXE) | Enable serial data into receiver |
| 3 | EnableReceiverDMA | Data available triggers DMA request |
| 4 | DMAModuleIE | Enable IRQ 13 on DMA module request |
| 5 | DeviceClear / SelectMaintenance | Clear + enter maintenance (loopback) mode |
| 6 | DTR | Data Terminal Ready (CCITT 108) to modem |
| 7 | ModemStatusChangeIE | Enable IRQ 13 on modem signal change (SD/DSR/RI) |
| 8 | BlockEndIE | DMA: enable IRQ on block end |
| 9 | FrameEndIE | DMA: enable IRQ on frame end |
| 10 | ListEndIE | DMA: enable IRQ on list end |
| 15 | Bit15 | Always 1 after DUMP command |
RTTS — Transmitter Transfer Status (IOX +12) Bit Map
| Bit | Name | Description |
|---|---|---|
| 0 | TransmitBufferEmpty | Transmit buffer ready for next byte |
| 1 | TransmitUnderrun | Transmitter underrun occurred |
| 2 | TransmitterActive | Transmitter is active (sending) |
| 3 | ReadyForSending (CTS) | CCITT 106 — Clear To Send from modem |
| 4 | DMAModuleRequest | DMA module requesting attention (cleared on read) |
| 8 | BlockEnd | DMA: block transfer complete |
| 9 | FrameEnd | DMA: frame transfer complete |
| 10 | ListEnd | DMA: end of descriptor list |
| 11 | ListEmpty | DMA: no transmit buffers |
TTC — Transmitter Transfer Control (IOX +13) Bit Map
| Bit | Name | Description |
|---|---|---|
| 0 | TransmitBufferEmptyIE | Enable IRQ 12 on buffer empty |
| 1 | TransmitUnderrunIE | Enable IRQ 12 on underrun |
| 2 | TransmitterEnabled (TXE) | Enable transmitter |
| 3 | EnableTransmitterDMA | Buffer empty triggers DMA request |
| 4 | DMAModuleIE | Enable IRQ 12 on DMA module request |
| 5 | HalfDuplex | Half-duplex mode |
| 6 | RQTS | Request To Send (CCITT 105) to modem |
| 7 | CTS_StatusChangeIE | Enable IRQ 12 on CTS change |
| 8 | BlockEndIE | DMA: enable IRQ on block end |
| 9 | FrameEndIE | DMA: enable IRQ on frame end |
| 10 | ListEndIE | DMA: enable IRQ on list end |
DMA Command Register (IOX +17, Write) Bit Map
| Bits | Name | Description |
|---|---|---|
| 0-3 | Bank Bits | Memory address bits 16-19 |
| 4-7 | (Unused) | — |
| 8-10 | Command | DMA command (see below) |
| 11-15 | (Unused) | — |
DMA Commands (bits 8-10):
| Code | Command | Description |
|---|---|---|
| 0 | DEVICE_CLEAR | Clear DMA module |
| 1 | INITIALIZE | Initialize DMA from buffer descriptors |
| 2 | RECEIVER_START | Start DMA receive |
| 3 | RECEIVER_CONTINUE | Continue receive after block end |
| 4 | TRANSMITTER_START | Start DMA transmit |
| 5 | DUMP_DATA_MODULE | Dump data module registers to memory |
| 6 | DUMP_REGISTERS | Dump DMA registers to memory |
| 7 | LOAD_REGISTERS | Load DMA registers from memory |
HDLC Remote Load — Complete Network Boot Architecture
HDLC remote load is a network boot mechanism that allows one ND machine to boot another over an HDLC (High-Level Data Link Control) serial link. This was part of the ND-Net networking system, introduced in SINTRAN III K-version.
Architecture Overview
The HDLC remote load involves two machines with distinct roles:
flowchart LR
subgraph CLIENT ["Machine A — Being Booted"]
direction TB
CPU_A["ND-110 CPU<br/>(microcode)"]
AL["HDLC Auto Load<br/>IOX 1600-1603<br/>(PCB 3015)"]
HDLC_A["HDLC Controller<br/>IOX 1640-1657"]
end
subgraph SERVER ["Machine B — Boot Server"]
direction TB
SINTRAN["SINTRAN III<br/>(K-version or later)"]
REMOTE["@REMOTE-LOAD<br/>service active"]
BSEND["BSEND (TRAWAA#)<br/>Bootstrap sender"]
HDLC_B["HDLC Controller<br/>IOX 1640-1657"]
end
CPU_A --> AL
AL --> HDLC_A
HDLC_A <-->|"HDLC Link<br/>(up to 307.2 kbps)"| HDLC_B
HDLC_B --> BSEND
BSEND --> REMOTE
REMOTE --> SINTRAN
style CLIENT fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
style SERVER fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
Roles
Machine A — Client (Being Booted)
- Has no operating system running (it's being booted)
- The CPU microcode drives the entire process
- Uses the dedicated HDLC Auto Load interface (IOX 1600-1603) — a simplified 4-register bootstrap-only interface on the PCB 3015 card
- Sends a boot request, then receives the binary boot program byte-by-byte via the ETLO1 microcode loader
Machine B — Server (Boot Provider)
- Already running SINTRAN III with ND-Net support
- The
@REMOTE-LOADcommand has been issued to enable remote load service - SINTRAN's HDLC driver contains dedicated routines for serving boot requests:
- BSINIT (routine CBSIN#) — Initializes a remote load session when a request arrives
- BSEND (routine TRAWAA#) — Transmits the bootstrap binary (BPUN format) over HDLC
- The boot image is sent as a standard BPUN binary stream — the same format used for floppy and paper tape boot
SINTRAN Server-Side Data Structures
From ND-60112 SINTRAN III System Documentation, Appendix A — the HDLC line data field contains these remote-load-specific entries:
| Offset | Field | Initial Value | Purpose |
|---|---|---|---|
| 13 | SINIT | CSINI# | Normal line initialization routine |
| 14 | BSINIT | CBSIN# | Remote load initialization — called when a boot request is received |
| 23 | SEND | TSEND# | Normal data send routine |
| 24 | BSEND | TRAWAA# | Bootstrap send routine — transmits BPUN binary to requesting machine |
| 25 | RECEIVE | TRECE# | Normal data receive routine |
| 27 | RMLNR | n | Remote line number for this connection |
When Machine A's HDLC autoload triggers, the HDLC controller on Machine A sends a request frame over the link. Machine B's HDLC interrupt handler recognizes this as a boot request and dispatches to BSINIT (CBSIN#), which sets up the transfer session. Then BSEND (TRAWAA#) reads the boot image and transmits it frame by frame over the HDLC link.
OPCOM Boot Commands for HDLC
From the operator console (OPCOM), HDLC network boot can be initiated:
| Command | Load Code | Action |
|---|---|---|
#1600& |
001600₈ | BPUN load from HDLC, auto-start at 20₈ |
#1600$ |
001600₈ | BPUN load from HDLC, CPU stops after load |
Via ALD thumbwheel:
| ALD Position | I12 Value | Action |
|---|---|---|
| 10 | 001600₈ | BPUN load from HDLC (1600₈) and run from 20₈ |
| 2 | 101600₈ | Binary load from HDLC (1600₈), load only |
I12 Register (ALD) Bit Decoding for HDLC
The I12 register value encodes three fields:
| Bits | Value for HDLC | Meaning |
|---|---|---|
| 17 (MSB) | 0 or 1 | 0 = load on power loss only (positions 8-15), 1 = always load (positions 2-7) |
| 16 | 0 | 0 = BPUN (binary) format. HDLC always uses BPUN, never mass storage bootstrap |
| 15-0 | 001600₈ | Device address = HDLC Auto Load |
HDLC Hardware — Auto Load vs Remote Load vs Data Interfaces
The PCB 3015 (HDLC + Autoload) card exposes three distinct IOX address ranges:
flowchart TD
subgraph CARD ["PCB 3015 — HDLC + Autoload Card"]
direction TB
subgraph AUTO ["HDLC AUTO LOAD<br/>IOX 1600-1603 (4 registers)"]
direction LR
A0["N+0: Read Data"]
A2["N+2: Read Status"]
A3["N+3: Write Control"]
end
subgraph REMOTE_LOAD ["HDLC REMOTE LOAD (7 channels)<br/>IOX 1604-1637 (4 registers each)"]
direction LR
R1["Ch 1: 1604-1607"]
R2["Ch 2: 1610-1613"]
R3["Ch 3: 1614-1617"]
R4["Ch 4-7: ..."]
end
subgraph DATA ["HDLC DMA/PIO Data Interface<br/>IOX 1640-1657 (16 registers)"]
direction LR
D_COM["COM5025 chip<br/>(IOX +0 to +7)"]
D_ND["ND Transfer Ctrl<br/>(IOX +10 to +13)"]
D_DMA["DMA Engine<br/>(IOX +14 to +17)"]
end
end
CPU["CPU Microcode<br/>(ETLO1)"] -->|"Boot: 4005₈ → IOX 1603"| AUTO
AUTO -->|"Internal"| DATA
DATA -->|"HDLC frames"| LINK["HDLC Serial Link"]
LINK --> REMOTE_SERVER["Remote ND Machine"]
style AUTO fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
style REMOTE_LOAD fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
style DATA fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
| Interface | IOX Range | Registers | Purpose |
|---|---|---|---|
| HDLC AUTO LOAD | 1600-1603 | 4 | Bootstrap interface — used by microcode ETLO1 for boot |
| HDLC REMOTE LOAD | 1604-1637 | 4 per channel (7 channels) | Directed boot from specific HDLC link number |
| HDLC DMA/PIO | 1640-1657 | 16 | Normal HDLC data communication (used by SINTRAN) |
The AUTO LOAD and REMOTE LOAD interfaces are simplified bootstrap-only interfaces. They share the same 4-register pattern (N+0 = data, N+2 = status, N+3 = control) but are separate IOX address ranges. The actual HDLC framing and link management is handled by the main DMA/PIO interface internally.
Complete HDLC Boot Sequence — Both Sides
sequenceDiagram
participant OP_A as Operator<br/>(Machine A)
participant UC as CPU Microcode<br/>(Machine A)
participant AL as HDLC Auto Load<br/>(IOX 1600-1603)
participant LINK as HDLC Link<br/>(serial)
participant HDLC_B as HDLC Controller<br/>(Machine B)
participant SIN as SINTRAN<br/>(Machine B)
Note over SIN: Prerequisite: @REMOTE-LOAD<br/>has been issued on Machine B
OP_A->>UC: Press LOAD button<br/>(or type #1600& at OPCOM)
UC->>UC: PANEL → PANVC[4] → LOAD
UC->>UC: ALDLO: Read ALD switch<br/>or use typed device address
UC->>UC: ALDVC → Q = 1600₈
UC->>UC: LOAD1: Bit 13 = 0 → ETLO1
rect rgb(42, 106, 58)
Note over UC,AL: DVACT — Activate Device
UC->>AL: IOX 1603: Write 4005₈<br/>(Bit 2 = Activate Autoload)
AL->>LINK: Boot request frame
LINK->>HDLC_B: Received boot request
HDLC_B->>SIN: Interrupt → BSINIT (CBSIN#)
SIN->>SIN: Initialize remote load session
end
rect rgb(42, 58, 106)
Note over SIN,UC: BSEND (TRAWAA#) — Send Bootstrap
SIN->>SIN: Read BPUN boot image
SIN->>HDLC_B: Start transmitting BPUN data
HDLC_B->>LINK: HDLC data frames
loop ETLO1 Binary Loader
LINK-->>AL: Data available
rect rgb(60, 60, 80)
Note over UC,AL: INCH — Read One Byte
UC->>AL: IOX 1602: Read status
AL-->>UC: Check bit 3
Note over UC: Loop until bit 3 = 1
UC->>AL: IOX 1600: Read data byte
AL-->>UC: One byte of BPUN stream
end
Note over UC: ASS8: Parse octal numbers<br/>until '!' delimiter found
rect rgb(60, 80, 60)
Note over UC: EXFOU — Process Binary Block
UC->>UC: BIN: Read core address (2 bytes via INCH)
UC->>UC: BIN: Read word count (2 bytes via INCH)
loop STLP — Store Words
UC->>UC: BIN: Read 16-bit word (2 bytes via INCH)
UC->>UC: Store to memory[X], X++, T--
UC->>UC: Accumulate checksum: L = L + Z
end
UC->>UC: BIN: Read expected checksum
UC->>UC: Verify: XOR(L, expected) = 0?
end
end
end
rect rgb(106, 58, 42)
Note over UC: Boot Complete
UC->>UC: P = start address (20₈)
UC->>UC: COMM,START (clear STOP)
UC->>UC: JMP ESCAP
Note over UC: OS execution begins at 20₈
end
BPUN Binary Format (What Gets Transmitted)
The BPUN (Binary PUNched) format transmitted over HDLC is a simple binary image format:
<octal-header><CR> ← Optional: start address in octal ASCII
! ← Delimiter: start of binary block
<address-high><address-low> ← 16-bit core load address (2 bytes)
<count-high><count-low> ← 16-bit word count (2 bytes)
<word-high><word-low> ← Data word 1
<word-high><word-low> ← Data word 2
... ← (word-count words total)
<checksum-high><checksum-low> ← 16-bit checksum (XOR of all data words)
Multiple blocks can be concatenated. The microcode ETLO1 loader processes each block, verifies the checksum, then continues to the next block until the entire program is loaded.
Why HDLC Uses BPUN (Not Mass Storage Bootstrap)
Mass storage bootstrap (bit 13 = 1) is designed for devices with block-addressable storage — the microcode issues IOX commands to set core address, block address, word count, and activate a DMA transfer. This requires a device that understands sector/block addressing.
HDLC is a serial stream — there are no blocks or sectors. The data arrives byte-by-byte over the link. The BPUN format is a self-describing serial format that includes addresses and checksums in the data stream itself, making it ideal for serial transfer. This is the same format used for:
- Paper tape reader (IOX 400₈)
- Floppy PIO (IOX 1560₈)
- HDLC auto load (IOX 1600₈)
All three use the ETLO1 binary loader path in the microcode.
HDLC Protocol for Remote Load
NOTE: Significant gaps in documentation. The exact protocol used between the autoload interface (IOX 1600-1603) and the remote SINTRAN machine is not documented in any available source. The sections below separate verified facts from unknowns.
What IS verified (from source code and hardware docs)
HDLC frame format — from 01-HDLC-Hardware-Reference.md and COM5025 datasheet:
┌──────┬─────────┬─────────┬───────────────────┬─────────┬──────┐
│ Flag │ Address │ Control │ Data │ FCS │ Flag │
│ 7E₁₆ │ 1 byte │ 1 byte │ 0-N bytes │ 2 bytes │ 7E₁₆ │
└──────┴─────────┴─────────┴───────────────────┴─────────┘──────┘
- Flag: 0x7E — generated/detected by COM5025 hardware
- FCS: CRC-16-CCITT — computed by COM5025 hardware
- Control field types: I-frames (data), S-frames (flow control), U-frames (link management) — standard HDLC/LAPB
Source: NDInsight\SINTRAN\Devices\HDLC\01-HDLC-Hardware-Reference.md, NDInsight\SINTRAN\TAD\TAD-HDLC-Encapsulation.md
DMA-based HDLC controller uses LAPB for normal data communication — from 05-HDLC-Protocol-Implementation.md and the NPL driver source.
BPUN format is used — from OPCOM reference: #1600& is listed as "BPUN (binary)" load format.
Source: NDInsight\Operations\SINTRAN\OPCOM-COMMAND-REFERENCE.md line 150
What is NOT known
- Whether the autoload interface (IOX 1600-1603) uses LAPB at all. The autoload is a dedicated 4-register interface separate from the full HDLC DMA controller (IOX 1640+). It may use a simpler proprietary protocol, or it may just present raw bytes from the COM5025 receiver.
- How the boot request is signaled at the frame level. We know the CPU writes 4005₈ to IOX 1603, but what the HDLC controller sends on the wire is unknown.
- Whether X.25 is involved. X.25 is used for COSMOS/TAD data, but remote load may bypass X.25 entirely.
- The exact handshake between the two machines. SABM/UA is standard LAPB, but the autoload path might not use it.
- How the server (TRAWAA#) frames the BPUN data. The TRAWAA# routine is not in the available NPL source files.
The missing documentation is likely in ND-12.018 HDLC Interface manual (referenced in NDBusHDLC.cs comments) which is not available in the NDInsight collection.
DMA Descriptor Format (LKEY)
The HDLC DMA hardware uses descriptor lists in ND-100 memory. Each descriptor has this format:
┌──────────────────┐ Offset 0
│ LKEY (16 bits) │ Control key — COM5025 commands + status flags
├──────────────────┤ Offset 1
│ LBYTC (16 bits) │ Byte count for this buffer
├──────────────────┤ Offset 2
│ LMEM1 (16 bits) │ Physical bank / high address bits
├──────────────────┤ Offset 3
│ LMEM2 (16 bits) │ Buffer memory address (low 16 bits)
└──────────────────┘
LKEY Bit Fields
| Bit | Name | Purpose |
|---|---|---|
| 0 | TSOM / RSOM | Transmit/Receive Start Of Message — opening FLAG |
| 1 | TEOM / REOM | Transmit/Receive End Of Message — closing FLAG + CRC |
| 2 | TABORT | Transmit Abort (7+ consecutive 1s) |
| 3 | TGA / XBLDN | Transmit Go Ahead / External Block Done |
| 8-15 | (DMA status) | Block end, frame end, list end flags |
Key LKEY Constants:
| Constant | Value | Meaning |
|---|---|---|
| FSERM | 0x1003 (octal 10003) | Single complete frame: TSOM + TEOM + flag bits |
| ERB | 0x0400 (octal 2000) | Empty Receiver Block — ready for incoming data |
| NLP | (end marker) | New List Pointer — marks end of descriptor chain |
SINTRAN XSSND / XSHDR Interfaces — Programming Guide
The HDLC driver in SINTRAN exposes two privileged interfaces for direct DMA frame transfer. These are the interfaces that the ND-Net remote load service (TRAWAA#/CBSIN#) uses to send and receive boot data.
All information in this section is verified from source code and documentation:
- NPL source:
SINTRAN\NPL-SOURCE\NPL\MP-P2-HDLC-DRIV.NPLin the NDInsight repository - Deep dive analysis:
SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-XSSDATA.mdin the NDInsight repository - Driver setup docs:
SINTRAN\Devices\DEVICE_DRIVER_SETUP_DOCUMENTATION.mdin the NDInsight repository - Interrupt analysis:
SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-Interrupt-Control.mdin the NDInsight repository
XMSG and MON Call Architecture
XSSND and XSHDR are NOT monitor calls. They are internal HDLC driver functions accessed through the XMSG messaging system. The call chain is:
flowchart TD
USER["User / ND-Net code"] --> MON200["MON 200B (XMSG)<br/>Inter-process messaging<br/>Source: NDInsight\Developer\MON\ND MON Calls.md"]
USER --> MON201["MON 201B (MHDLC)<br/>HDLC-specific monitor call<br/>DCB send/receive"]
MON200 --> ROUTING["SINTRAN message routing<br/>Message placed in IQUEU"]
MON201 --> ROUTING
ROUTING --> HDSTA["HDSTA (103636₈)<br/>Main driver entry<br/>Wait for message from queue"]
HDSTA --> DISPATCH["HDENT dispatch table<br/>Lookup XCFUNC field"]
DISPATCH -->|"Func 0,3"| SMCLEAR["SMCLEAR<br/>Master clear"]
DISPATCH -->|"Func 1"| SIDAT["SIDAT → SRDAT<br/>Send data (normal)"]
DISPATCH -->|"Func 4"| HDSIN["HDSIN<br/>Initialize HDLC link"]
DISPATCH -->|"Func 5"| SPSTA["SPSTA<br/>Port status"]
DISPATCH -->|"Func 6"| XSSND["XSSND<br/>Privileged DMA send"]
DISPATCH -->|"Func 7"| XSHDR["XSHDR<br/>Privileged DMA receive"]
style XSSND fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
style XSHDR fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
Source: NPL source line 39: INTEGER ARRAY HDENT:=(SMCLEAR,SIDAT,SIRES,SMCLEAR,HDSIN,SPSTA,XSSND,XSHDR)
MON 200B (XMSG) — the inter-process messaging system. All data communication in SINTRAN goes through XMSG. The COSMOS Programmer Guide (Operations\Cosmos\ND-60164-3-EN COSMOS Programmer Guide.md in the NDInsight repository) documents the full XMSG API:
- XFOPN — Open a port
- XFSND — Send message
- XFRCV — Receive message
- XFGET — Reserve message buffer
- XFREL — Release message buffer
- XFCRD — Create driver (system function)
- XFSTD — Start driver (system function)
Source: Developer\MON\ND MON Calls.md in the NDInsight repository lines 12838-12850
MON 201B (MHDLC) — the HDLC-specific monitor call. Parameters:
- Param1: Function code (0 = send DCB to driver, 1 = receive DCB from driver)
- Param2: Logical device number (e.g., 1360 for HDLC-1)
- Param3: Address of Driver Control Block (DCB)
- Param4: Used DCB size in bytes
- Param5: Maximum DCB size in bytes
Source: Developer\MON\ND MON Calls.md in the NDInsight repository lines 12854-12916
HDLC Message Structure
Every message to the HDLC driver has this structure (from DEVICE_DRIVER_SETUP_DOCUMENTATION.md line 221):
| Field | Offset | Description |
|---|---|---|
| XCFUNC | BHEAD+0 | Function code (0-7, indexes into HDENT table) |
| CRET | BHEAD+1 | Return status (filled by driver on completion) |
| ADSTA | BHEAD+2 | Hardware status (RRTS or RTTS value) |
| BBYTC | BHEAD+3 | Actual byte count |
| BMBYT | BHEAD+4 | Maximum byte count |
| Data | BHEAD+6+ | Transmitted/received data payload |
For privileged XSSND/XSHDR calls, the message must have BBID < 0 (negative = XMSG privileged message).
Source: SINTRAN\Devices\DEVICE_DRIVER_SETUP_DOCUMENTATION.md in the NDInsight repository lines 221-230
Message Flow — Transmit and Receive
Source: SINTRAN\Devices\DEVICE_DRIVER_SETUP_DOCUMENTATION.md in the NDInsight repository lines 709-738
flowchart TD
subgraph TX ["Transmit Flow (XSSDATA)"]
direction TB
TX1["User calls XMSG with XSSDATA function"]
TX2["Message placed in IQUEU (000061₈)"]
TX3["HDSTA (103636₈) gets message from queue"]
TX4["XSSDATA validates and sets up DMA list"]
TX5["XHMST starts hardware transmission"]
TX6["HOINT (104233₈) interrupt when complete"]
TX7["Message returned to user with status"]
TX1 --> TX2 --> TX3 --> TX4 --> TX5 --> TX6 --> TX7
end
subgraph RX ["Receive Flow (HDREC)"]
direction TB
RX1["User calls XMSG with HDREC function"]
RX2["Empty message placed in receiver list"]
RX3["HDENA starts receiver if not active"]
RX4["HIINT (104636₈) interrupt when frame received"]
RX5["Frame copied to message"]
RX6["Message returned to user with data"]
RX1 --> RX2 --> RX3 --> RX4 --> RX5 --> RX6
end
style TX fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff
style RX fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
Driver Dispatch Table
Source: NPL source MP-P2-HDLC-DRIV.NPL line 39
INTEGER ARRAY HDENT:=(SMCLEAR, SIDAT, SIRES, SMCLEAR, HDSIN, SPSTA, XSSND, XSHDR)
% 0 1 2 3 4 5 6 7
| Index | Function | Handler | Purpose | Source Line |
|---|---|---|---|---|
| 0 | SMCLEAR | SMCLEAR | Master clear | 439 |
| 1 | SIDAT | SRDAT | Send data (normal) | 75 |
| 2 | SIRES | SRREST | Response | 76 |
| 3 | SMCLEAR | SMCLEAR | Master clear (duplicate) | 439 |
| 4 | HDSIN | HDSIN | Initialize HDLC link | 487 |
| 5 | SPSTA | SPSTA | Port status query | 602 |
| 6 | XSSND | XSSND | Privileged DMA send | 253 |
| 7 | XSHDR | XSHDR | Privileged DMA receive | 283 |
XSSND — Privileged DMA Transmit
Source: SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-XSSDATA.md in the NDInsight repository (line-by-line analysis)
Source: NPL source MP-P2-HDLC-DRIV.NPL lines 253-264
Purpose: Send an HDLC frame using a pre-built DMA transmitter list. Used by privileged callers (XMSG system, ND-Net) that manage their own DMA descriptor lists. Also serves as the retransmission entry point — called via DRERR when transmission errors occur (DRERR → SRDAT → XSSND).
Source for dual purpose: Deep-Dive-XSSDATA.md lines 280-316
Entry conditions:
- Message ID < 0 (privileged XMSG message — verified by ZXCHK at NPL line 137)
- INTSTA = 2 (interface initialized via HDSIN)
- First two data words of the DCB (Data Control Block) contain a pointer to the DMA transmitter list (XMLIS)
flowchart TD
ENTRY["XSSND Entry<br/>(Function 6)"] --> CHECK["ZXCHK<br/>Verify XMSG DCB (BBID < 0)<br/>Verify INTSTA = 2"]
CHECK -->|"Fail"| ERR["Return EILFNC or ENINIT"]
CHECK -->|"OK"| READLIST["Read DMA list pointer<br/>from DCB words via XMLIS"]
READLIST --> CALCADDR["Calculate physical address<br/>LIINT = list pointer<br/>Add DPITPHYS for physical bank"]
CALCADDR --> READDESC["Read LMEM1 from descriptor<br/>(cache miss workaround for ND-110)"]
READDESC --> READAC["Read AC field (first word<br/>of frame for logging)"]
READAC --> XXHMST["XXHMST: Start transmitter"]
subgraph XXHMST_DETAIL ["XXHMST — Hardware Activation"]
direction TB
S1["IOX HDEV+WDMA (IOX+15):<br/>Write DMA list physical address"]
S2["IOX HDEV+WDCR (IOX+17):<br/>Write 2000₈ | bank bits<br/>(Start Transmitter command)"]
S3["IOX HDEV+RDCR (IOX+16):<br/>Poll until DMA command accepted<br/>(with timeout)"]
S4["IOX HDEV+WTTC (IOX+13):<br/>Write 1134₈ + CMODI<br/>(Enable TX + interrupts + RQTS)"]
S5["ACTSW = 1<br/>DCBX = message pointer"]
S1 --> S2 --> S3 --> S4 --> S5
end
XXHMST --> XXHMST_DETAIL
XXHMST_DETAIL --> WAIT["CONT: Set timeout timer<br/>Call ID12 (wait for IRQ 12)"]
WAIT --> HOINT["HOINT: Interrupt handler"]
subgraph HOINT_DETAIL ["HOINT — Transmit Complete"]
direction TB
H1["Read RTTS (IOX+12):<br/>Transmitter Transfer Status"]
H2["Save in HASTAT"]
H3{"SILFO or TXUND<br/>error bits set?"}
H4["Success: status = 0<br/>Reset RTDYN retries"]
H5["Error: call DRERR<br/>Retry if RTDYN > 0"]
H1 --> H2 --> H3
H3 -->|"No errors"| H4
H3 -->|"Error"| H5
end
HOINT --> HOINT_DETAIL
HOINT_DETAIL --> DONE["DCBX = 0<br/>Return to BACKX<br/>(send status back to caller)"]
style XXHMST_DETAIL fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
style HOINT_DETAIL fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
XSSND IOX Sequence (Exact)
| Step | IOX Register | Value | Purpose |
|---|---|---|---|
| 1 | WDMA (IOX+15) | Physical address of DMA list | Set DMA descriptor base |
| 2 | WDCR (IOX+17) | 0x2000 | bank_bits | Start Transmitter DMA command |
| 3 | RDCR (IOX+16) | (poll read) | Wait for command accepted (loops with timeout) |
| 4 | WTTC (IOX+13) | 0x1134 + CMODI | Enable: TxBufferEmptyIE + TxEnabled + RQTS + DMA IE |
| — | (wait) | IRQ 12 | Wait for transmit complete interrupt |
| 5 | RTTS (IOX+12) | (read) | Check transmit status |
WTTC Value Decode (0x1134 + CMODI)
0x1134 = octal 10464:
| Bit | Value | Name |
|---|---|---|
| 0 | 0 | TransmitBufferEmptyIE — not via this bit |
| 2 | 1 | TransmitterEnabled (TXE) |
| 3 | 0 | EnableTransmitterDMA — set by DMA engine |
| 4 | 1 | DMAModuleIE — enable IRQ on DMA request |
| 5 | 1 | HalfDuplex (if CMODI=40₈) |
| 6 | 0 | RQTS — set separately |
| 8 | 1 | BlockEndIE |
Plus CMODI (0 for full duplex, 40₈ for half duplex) is OR'ed in.
XSHDR — Privileged DMA Receive
Source: NPL source MP-P2-HDLC-DRIV.NPL lines 283-421 (HDREC, HIINT, XSHDR)
Source: SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-Interrupt-Control.md in the NDInsight repository
Purpose: Receive HDLC frames into a pre-built DMA receiver buffer list. Used by privileged callers that manage their own buffer descriptors.
Entry conditions (from NPL source lines 283, 418):
- Message ID < 0 (privileged XMSG message — verified by ZXCHK)
- INTSTA = 2 (interface initialized)
- First two data words of DCB point to receiver buffer (XHBUF)
flowchart TD
ENTRY["XSHDR Entry<br/>(Function 7)"] --> CHECK["ZXCHK<br/>Verify XMSG DCB + INTSTA"]
CHECK -->|"Fail"| ERR["Return error"]
CHECK -->|"OK"| HXST["HXST: Check if receiver<br/>is already active"]
HXST --> READBUF["Read receiver buffer pointer<br/>from DCB via XHBUF"]
READBUF --> HSPEC["HSPEC: Link buffer into<br/>DMA descriptor chain"]
subgraph HSPEC_DETAIL ["HSPEC — Buffer Setup"]
direction TB
SP1["Set LMEM from buffer address"]
SP2["Calculate DCB table index"]
SP3["Save DCB address in DCBX table"]
SP4["Set LKEY = ERB (0x0400)<br/>(Empty Receiver Block)"]
SP5["Advance LISTF pointer"]
SP1 --> SP2 --> SP3 --> SP4 --> SP5
end
HSPEC --> HSPEC_DETAIL
HSPEC_DETAIL --> ACTIVE{"ACTSW = 1?<br/>(Already running)"}
ACTIVE -->|"Yes"| HDSTA["Return to HDSTA<br/>(wait for more messages)"]
ACTIVE -->|"No"| HDENA["HDENA → ZSTARC:<br/>Start receiver hardware"]
subgraph ZSTARC_DETAIL ["ZSTARC — Start Receiver DMA"]
direction TB
Z1["IOX HDEV+WRTC (IOX+11):<br/>Clear old state (MAINT flags)"]
Z2["IOX HDEV+WDMA (IOX+15):<br/>Write DMA list physical address"]
Z3["IOX HDEV+WDCR (IOX+17):<br/>Write 1001₈<br/>(Receiver Start command)"]
Z4["IOX HDEV+RDCR (IOX+16):<br/>Poll until accepted (timeout)"]
Z5["ACTSW = 1"]
Z1 --> Z2 --> Z3 --> Z4 --> Z5
end
HDENA --> ZSTARC_DETAIL
ZSTARC_DETAIL --> ENABLE["IOX HDEV+WRTC (IOX+11):<br/>Write 1734₈ | MAINT | HXDOK<br/>(Enable RX + DMA + all IRQs)"]
ENABLE --> WAIT["Return to HDSTA<br/>(wait for IRQ 13)"]
WAIT --> HIINT["HIINT: Receive interrupt"]
subgraph HIINT_DETAIL ["HIINT — Frame Received"]
direction TB
HI1["Read RRTS (IOX+10)"]
HI2{"X.21 error?<br/>(bits 13-14)"}
HI3{"List Empty?<br/>(bit 11 = EMTY)"}
HI4{"Block Done?<br/>(XBLDN in LKEY)"}
HI5["Extract frame from buffer<br/>Read LBYTC for byte count"]
HI6["XMPAT: Find DCB from<br/>descriptor table"]
HI7["Send message back<br/>to caller via OCHAIN"]
HI8["Advance LIINT pointer<br/>Process MORE frames"]
HI1 --> HI2
HI2 -->|"Yes"| HI9["Save error in LKEY<br/>Check X21S for clear indication"]
HI2 -->|"No"| HI3
HI3 -->|"Yes"| HI10["ACTSW=0, increment STPCNT<br/>Receiver stopped — out of buffers"]
HI3 -->|"No"| HI4
HI4 -->|"No"| HI11["Re-enable receiver (ZSTARC)"]
HI4 -->|"Yes"| HI5
HI5 --> HI6 --> HI7 --> HI8
end
HIINT --> HIINT_DETAIL
style ZSTARC_DETAIL fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
style HIINT_DETAIL fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
XSHDR IOX Sequence (Exact)
| Step | IOX Register | Value | Purpose |
|---|---|---|---|
| 1 | WRTC (IOX+11) | MAINT flags | Clear old receiver state |
| 2 | WDMA (IOX+15) | Physical address of DMA list | Set DMA descriptor base |
| 3 | WDCR (IOX+17) | 0x1001 (octal 1001) | Receiver Start DMA command |
| 4 | RDCR (IOX+16) | (poll read) | Wait for command accepted |
| 5 | WRTC (IOX+11) | 0x1734 | MAINT | HXDOK | Enable receiver + DMA + all IRQ enables |
| — | (wait) | IRQ 13 | Wait for receive complete interrupt |
| 6 | RRTS (IOX+10) | (read) | Check receive status |
WRTC Enable Value Decode (0x1734)
Octal 13464:
| Bit | Value | Name |
|---|---|---|
| 0 | 0 | DataAvailableIE |
| 1 | 0 | StatusAvailableIE |
| 2 | 1 | EnableReceiver (RXE) |
| 3 | 1 | EnableReceiverDMA |
| 4 | 1 | DMAModuleIE |
| 5 | 1 | (part of pattern) |
| 6 | 0 | DTR |
| 7 | 0 | ModemStatusChangeIE |
| 8 | 1 | BlockEndIE |
| 9 | 1 | FrameEndIE |
| 10 | 1 | ListEndIE |
How to Code Against XSSND/XSHDR
Source: COSMOS Programmer Guide Operations\Cosmos\ND-60164-3-EN COSMOS Programmer Guide.md in the NDInsight repository — full XMSG API
Source: MON call reference Developer\MON\ND MON Calls.md in the NDInsight repository — MON 200B (XMSG), MON 201B (MHDLC)
Source: NPL source MP-P2-HDLC-DRIV.NPL — driver implementation
To use the privileged HDLC interfaces, a SINTRAN process sends messages to the HDLC driver port with function codes 6 (XSSND) or 7 (XSHDR). The message must be an XMSG-type message (BBID < 0).
The XMSG API (documented in the COSMOS Programmer Guide, section 1) provides:
- XFOPN — Open a port (section 3.1.1, page 262)
- XFGET — Reserve a message buffer (section 3.2.1, page 266)
- XFWR1 — Write into message buffer (section 3.2.6, page 269)
- XFSND — Send message to a port (section 3.2.11, page 272)
- XFRCV — Receive next message (section 3.2.13, page 275)
- XFREL — Release message buffer (section 3.2.3, page 267)
- XFCRD — Create driver (system function, section 5.3, page 284)
- XFSTD — Start driver (system function, section 5.4, page 285)
Sending a Frame (XSSND)
% Pseudocode for sending a frame via XSSND
% 1. Build DMA transmitter list in memory
% Descriptor at XMLIS:
% LKEY = FSERM (0x1003) — single complete frame
% LBYTC = byte count of data
% LMEM1 = physical bank bits
% LMEM2 = buffer memory address
% 2. Build XMSG DCB (Data Control Block)
% BBID = negative value (marks as privileged XMSG)
% XMLIS = pointer to DMA descriptor
% XHBYT = frame byte count
% 3. Send message to HDLC driver port
% FUNCTION = 6 (XSSND)
% DATA = DCB with DMA list pointer
% 4. Wait for response message
% STATUS = 0 if success
% ADDSTA = hardware status (RTTS value)
Receiving Frames (XSHDR)
% Pseudocode for receiving frames via XSHDR
% 1. Allocate receive buffer in memory
% Buffer at XHBUF:
% Must be large enough for max frame (MAXR bytes)
% 2. Build XMSG DCB
% BBID = negative value (privileged)
% XHBUF = pointer to receive buffer
% BMBYT = max buffer size
% 3. Send EMPTY message to HDLC driver port
% FUNCTION = 7 (XSHDR)
% (no data — buffer will be filled by receiver)
% 4. Wait for response message (blocks until frame arrives)
% STATUS = 0 if success, EINP if error
% ADDSTA = hardware status (RRTS value)
% DATA = received frame data in buffer
% BBYTC = actual byte count received
Error Handling
Source: NPL source MP-P2-HDLC-DRIV.NPL — error codes used throughout the driver. DRERR retry logic at lines 131-135.
| Error Code | Symbol | Meaning |
|---|---|---|
| ENINIT | Not initialized | HDSIN has not been called (INTSTA != 2) |
| EILFNC | Illegal function | Not an XMSG DCB (BBID >= 0) |
| EILFZ | Illegal frame size | Message too small (< 7 bytes for send) |
| EILSIZ | Size error | Frame exceeds MAXR |
| ETOSM | Too small | Receive buffer too small |
| ETOMUCH | Too many | Too many receive buffers queued |
| EUND | Underrun | Transmitter underrun (TXUND) |
| ETOU1 | Timeout | Transmit timeout (no interrupt) |
| ETOU2 | Timeout | Internal timeout |
| EINP | Input error | Receive frame error |
| EX21 | X.21 error | X.21 clear indication received |
| ECLEAR | Cleared | Interface was cleared |
| ELOCK | Locked | Interface locked by X.21 operation |
DMA Command Register Values (IOX+17, WDCR)
Source: NPL source — XHMST (line 186: A:=2000\/D for TX start), ZSTARC (line 310: A:=1001 for RX start), HDSIN (line 509: 401 for init)
| Value (octal) | Command | Used by |
|---|---|---|
| 0001 | Device Clear | HDSIN (initialization) |
| 0201 | Receiver Start (from docs) | — |
| 1001 | Receiver Start (from source) | XSHDR / HDREC |
| 2000 + bank | Transmitter Start | XSSND / XSSDATA |
| 0401 | Initialize | HDSIN |
References
SINTRAN Documentation (the NDInsight repository)
| Document | Full Path | Content |
|---|---|---|
| OPCOM Command Reference | Operations\SINTRAN\OPCOM-COMMAND-REFERENCE.md in the NDInsight repository |
Boot commands (#1600&), ALD table (lines 627-658), I12 register decode (lines 662-681) |
| SINTRAN III System Supervisor | Operations\SINTRAN\ND-30.003.007 EN SINTRAN III System Supervisor.md in the NDInsight repository |
Device list including HDLC 1600₈ (line 17421) |
| SINTRAN III System Documentation | Operations\SINTRAN\ND-60112-01-EN SINTRAN III System Documentation - Appendix A - Data Fields.md in the NDInsight repository |
BSINIT/BSEND data field definitions (lines 1334, 1339) |
| SINTRAN III K-version Release | SINTRAN\Release-Documentation\ND-60230-5-EN SINTRAN III - Release Information - K-version.md in the NDInsight repository |
@REMOTE-LOAD command (line 163) |
| MON Call Reference | Developer\MON\ND MON Calls.md in the NDInsight repository |
MON 200B XMSG (line 12838), MON 201B MHDLC (line 12854) |
| COSMOS Programmer Guide | Operations\Cosmos\ND-60164-3-EN COSMOS Programmer Guide.md in the NDInsight repository |
Full XMSG API: XFOPN, XFSND, XFRCV, XFGET, XFCRD, XFSTD (sections 3.1-5.4) |
HDLC-Specific Documentation (SINTRAN\Devices\HDLC in the NDInsight repository)
| Document | Full Path | Content |
|---|---|---|
| HDLC Hardware Reference | SINTRAN\Devices\HDLC\01-HDLC-Hardware-Reference.md in the NDInsight repository |
COM5025 chip, CRC, X.21, frame format |
| HDLC Protocol Implementation | SINTRAN\Devices\HDLC\archive\original-consolidated\05-HDLC-Protocol-Implementation.md in the NDInsight repository |
LAPB, X.25, XSSDATA/XSSREC pseudocode, protocol variables |
| Deep Dive: XSSDATA | SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-XSSDATA.md in the NDInsight repository |
Line-by-line XSSDATA analysis, all 8 phases, XSSND dual purpose, FSERM decode |
| Deep Dive: Interrupt Control | SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-Interrupt-Control.md in the NDInsight repository |
HIINT complete flow, WRTC 1734₈ decode, LKEY receiver bit fields |
| Deep Dive: COM5025 | SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-COM5025-Interface.md in the NDInsight repository |
RSOM/REOM detection, COM5025 register mapping |
| Deep Dive: PROCPKT | SINTRAN\Devices\HDLC\deep-dives\Deep-Dive-PROCPKT.md in the NDInsight repository |
Packet processing, BBID/BMBYT/BBYTC buffer fields |
| Device Driver Setup | SINTRAN\Devices\DEVICE_DRIVER_SETUP_DOCUMENTATION.md in the NDInsight repository |
HDLC message flow (lines 709-738), message structure (lines 221-230), DMA list structure (lines 741-753) |
| TAD-HDLC Encapsulation | SINTRAN\TAD\TAD-HDLC-Encapsulation.md in the NDInsight repository |
HDLC frame format, X.25 packet structure, encapsulation examples |
| Software Flow Guide | SINTRAN\Devices\HDLC\learning\04-Software-Flow.md in the NDInsight repository |
XSSDATA, HIINT, HOINT, PROCPKT, ZSTARC overview |
NPL Source Code (SINTRAN\NPL-SOURCE\NPL in the NDInsight repository)
| File | Content |
|---|---|
MP-P2-HDLC-DRIV.NPL |
HDLC driver: HDSTA (line 40), XSSDATA (line 168), XSSND (line 253), HDREC (line 284), XSHDR (line 418), HIINT (line 340), HOINT (line 215), HDSIN (line 486), SPSTA (line 602), DRERR (line 131) |
PH-P2-OPPSTART.NPL |
System initialization |
PH-P2-CONFG-TAB.NPL |
Device configuration tables, HDLC LDN setup |
Emulator Code (Emulated.HW\ND\CPU\NDBUS in the RetroCore repository)
| File | Content |
|---|---|
NDBusHDLC.cs |
Full HDLC controller emulation, IOX register Read/Write |
HDLC\Enums.cs |
Register enum (IOX+0 to IOX+17), RTSBits, RTCBits, TTSBits, TTCBits, DMACommands |
HDLC\Registers.cs |
Internal register state, TH1 baud rate selection |
HDLC\DMAEngine.cs |
DMA descriptor processing |
NDBusFloppyDMA.cs |
Floppy DMA controller (3112), ActivateAutoload at line 459 |
NDBusFloppyPIO.cs |
Floppy PIO controller, Autoload at line 568 |
NDBusDiscControllerSMD.cs |
SMD disk controller, Register enum (lines 605-658) |
NDBusDiscControllerSCSI.cs |
SCSI controller (3201), Register enum (lines 403-462) |
Microcode Source (the microcode folder of the NDMicroAsm repository)
| File | Content |
|---|---|
ND-110-RASK.uc |
Full microcode: ALDVC (line 8614), LOAD1 (line 5307), MASS (line 5331), ETLO1 (line 5534), IOXG (line 1363), DVACT (line 5672), INCH (line 5644) |
ND-110-RASK.DETAILS.TXT |
Symbol/address reference table |
ND-110-RASK.LISTING.TXT |
Assembled listing with octal addresses |
Floppy Boot — PIO vs DMA Summary
flowchart TD
ALD["ALD Switch = 1<br/>Load code = 1560₈"]
ALD --> LOAD1["LOAD1: Bit 13 = 0"]
LOAD1 --> ETLO1["ETLO1 Binary Loader"]
ETLO1 --> DVACT["DVACT: IOX N+3<br/>Data = 4005₈"]
DVACT --> PIO_OR_DMA{"Which controller<br/>is at 1560₈?"}
PIO_OR_DMA -->|"PIO Controller"| PIO["PIO: Autoload bit (2) +<br/>ReadID bit (11)<br/>Controller enters boot state"]
PIO --> PIO_LOOP["INCH loop:<br/>IOX N+2 poll → IOX N+0 read<br/>Character by character"]
PIO_LOOP --> PIO_BIN["BIN: Assemble 16-bit words<br/>Store to memory"]
PIO_BIN --> PIO_DONE["CPU loads program<br/>via PIO transfers"]
PIO_OR_DMA -->|"DMA Controller (3112)"| DMA["DMA: ActivateAutoload bit (2)<br/>Controller runs internal<br/>microprogram"]
DMA --> DMA_BOOT["Controller DMA-transfers<br/>BPUN bootstrapper<br/>directly to ND-100 memory"]
DMA_BOOT --> DMA_STATUS["Sets ReadyForTransfer<br/>in RSR1"]
DMA_STATUS --> DMA_DONE["INCH sees ready,<br/>reads status,<br/>boot continues"]
style PIO fill:#3F51B5,stroke:#283593,stroke-width:2px,color:#fff
style DMA fill:#009688,stroke:#00695C,stroke-width:2px,color:#fff
The key difference: both controllers respond to the same IOX addresses and the same 4005₈ control word, but interpret it differently:
- PIO: Bit 2 = "Autoload" flag, bit 11 = "ReadID" command. The controller enters a state where data is available byte-by-byte via IOX N+0, polled through IOX N+2.
- DMA: Bit 2 = "ActivateAutoload" triggers the controller's internal boot microprogram. The controller autonomously DMA-transfers the boot code into ND-100 memory, then signals completion.
After the DMA controller finishes its autoload, the ETLO1 loop in the CPU microcode continues — but by that point, the boot code is already in memory. The controller's InitializeForBoot() sets the state so subsequent IOX reads return appropriate responses.
Key Microcode Addresses (all octal)
| Address | Label | Purpose |
|---|---|---|
| 000000 | — | Master Clear / Power Clear trap vector |
| 000016 | — | Panel interrupt trap vector |
| 000050 | PANEL | Panel interrupt handler |
| 001363 | IOXG | IOX subroutine (11-bit address) |
| 001374 | IOXX1 | IOX device routing |
| 001752 | MACL | Master clear initialization routine |
| 002140 | ALDLO | ALD switch reader |
| 002144 | LOAD1 | Load code analyzer |
| 002147 | MASS | Mass storage block loader |
| 002202 | LOAD | LOAD button handler |
| 002530 | ESCAP | Exit to loaded program |
| 003560 | ALDVC | ALD device vector table (16 entries) |
| 003760 | PANVC | Panel button vector table (8 entries) |
| 005534 | ETLO1 | Binary serial loader |
| 005644 | INCH | Read one character subroutine |
| 005672 | DVACT | Activate device subroutine |
| 005731 | BIN | Read 16-bit word subroutine |
Source Files
| File | Description |
|---|---|
ND110Compile\uCode\ND-110-RASK.uc |
Full microcode source (17,676 lines) |
ND110Compile\uCode\ND-110-RASK.DETAILS.TXT |
Symbol/address reference table |
ND110Compile\uCode\ND-110-RASK.LISTING.TXT |
Assembled listing with addresses |
ND110CPU\Cpu.cs |
CPU emulator — IOX stub at line 2028, JMPAOPR at line 792 |
ND110CPU\MIC.cs |
Microinstruction Controller — loop counter, stack, sequencing |
ND110CPU\Uart.cs |
UART — EAUTO~ flag at line 59 |
ND110CPU\Panel.cs |
Panel buttons and interrupt generation |
ND110CPU\Registers.cs |
CPU registers including ALD at line 96 |