4. Boot Mode and Autoload — How Booting From Floppy Works¶
This is the part that was least understood, so it is documented from both the manual and a full reverse-engineering of the on-card bootstrap loader. Where the two agree it is stated plainly; where only the firmware knows, it is marked [FIRMWARE @addr]; open questions are marked [COULD NOT DETERMINE] rather than guessed.
4.1 The two kinds of "load" the manual describes [MANUAL §1]¶
The manual describes two distinct load mechanisms:
-
DMA-load ("first page" load). "When DMA-load is performed, 'the first page' on floppy is dumped to 'first page' on the ND-100." This is the mass-storage / binary-format load used by the ND-100 CPU microcode. Because of the ND-100 microprogram, it can only be invoked from a terminal (the
2156Q$command), not by setting the ALD (Automatic Load Descriptor) selector on the CPU module. [MANUAL §1] -
BPUN LOAD ("press LOAD"). "It is also possible to 'load' BPUN-files (Binary PUNch) of maximum 64 Kwords directly from the floppy by pressing LOAD." This is the operator boot: it loads a bootstrap/monitor image (a BPUN-format file) off the floppy into ND-100 memory and starts it. For this to work the floppy must carry a current FLO-MON (Floppy Monitor 2010F or newer); an out-of-date FLO-MON gives error 51₈. [MANUAL §1]
Both are ultimately driven the same way at the controller: the ND-100 loads the Hardware Control Word with bit 2 ("Activate Autoload") — this is control-word State I in §3.6 Table 1, "Load floppy monitor".
Reconciliation with the firmware. The "LOAD" button lives on the ND-100 CPU, not on the controller card. Pressing it runs ND-100 microcode, which issues the control word to the 3112. The firmware confirms there is no autoload path triggered by controller hardware — see §4.5. So "press LOAD" = "ND-100 microcode asserts the autoload control word to the 3112".
4.2 Firmware entry: how the control word reaches autoload [FIRMWARE]¶
When the ND-100 loads the control word, CTC channel 0 interrupts the Z80 and vectors to
HostCmd_ISR (ram:030c). The ISR stores the 16-bit control word at 2098h and then
decides what to do:
- [FIRMWARE @0325] If the "fetch command & execute" bit (bit 8) is set, dispatch falls
through to the default handler
ram:1ae8(Autoload_BootstrapLoad) — the real bootstrap loader with the signature scan and the oct 50 / oct 51 errors. - [FIRMWARE @0332-0336] There is also an explicit gate
IN (0x52); CP 0x04testing control-word bit 2 alone, which dispatches a different handlerram:1c05— that one reads the floppy and shows a 2-digit code on the front-panel 7-segment LEDs, but does not perform the'!'-signature bootstrap scan.
[COULD NOT DETERMINE] Exactly which control-word bit(s) the ND-100 microcode asserts for an operator LOAD — bit 2, bit 8, or both. The firmware has two loaders; the one that actually parses and loads a BPUN bootstrap is unambiguously
ram:1ae8. The rest of this document tracesram:1ae8, because that is the loader that implements "load the monitor from floppy". An emulator that only needs to boot should implement theram:1ae8behaviour and accept the autoload control word (see §4.6).
4.3 The bootstrap loader state machine (ram:1ae8) [FIRMWARE — verified]¶
Step by step, exactly as the ROM does it:
-
Recalibrate (RESTORE to track 0).
Floppy_ReadToBuffer2200(ram:09f8) issues a WD179x/FD1797 RESTORE (opcode0x00) viaOUT(0x74),0; OUT(0x75),0;thenOUT(0x70), 0x00, and pollsIN(0x70)bit 0 (BUSY) until clear. [FIRMWARE @0a0d-0a20] -
Read the first data into the buffer. It fills the Z80 buffer at
0x2200with0x800= 2048 bytes (LD BC,0x800@1aef) from the floppy. [FIRMWARE @1aef][COULD NOT DETERMINE] the exact track/side/sector/sector-count of that first read;
09f8is parameterised through theIY = 0x2100floppy state block. Confirmed only: RESTORE to track 0, then a 2048-byte fill. -
Scan for the bootstrap signature
'!'. [FIRMWARE @1af2-1af6]The signature byte is ASCIILD A,0x21 ; '!' CPIR ; search HL=0x2200 .. for 0x800 bytes ; not found -> StatusErr_NoBootstrap_oct50'!'(0x21). -
Validate the header behind
'!'. [FIRMWARE @1af9-1b16] Scan backwards up to 128 bytes for a carriage return (0x0D, after maskingAND 0x7F). If no CR → oct 50. The character immediately before the CR is masked (RES 7) and range-checked to be an octal digit'0'..'7'(SUB 0x30borrow → oct 50;SUB 0x08no-borrow → oct 50). -
Parse the header / classify format. [FIRMWARE @1b17-1b62] It ORs 128 even-buffer bytes to classify one of two layouts, reads 16-bit fields out of the ASCII header (offsets relative to
'!':-3,+5,+7,+10, stride 2), and computes:- a 24-bit ND-100 host load address, kept in
(0x2082); - a word/byte count for the transfer.
- a 24-bit ND-100 host load address, kept in
-
Detect a wrong/out-of-date bootstrap. [FIRMWARE @1b2a-1b41] If
header[-3] == '2'andheader[+10] == '@'and a 10-byte stride-2 XOR checksum equals0x52='R', raise oct 51 (WRONG_BOOTSTRAP — "out of date FLO-MON"). -
DMA the image to ND-100 memory. [FIRMWARE @1b85-1bd0] Loop in
0x800-byte chunks: wait for the host-DMA handshake (HaltWaitLoop@1bdf:HALTthenOUT(0x57)NFINI), transfer the chunk to the host viaRST 0x28, advance the running host target pointer(0x2082) += (0x20cd)(byte-swapped, big-endian), and re-fill the buffer with the next floppy chunk (CALL 09f8). Repeat until the count reaches zero. -
Complete and hand control to the ND-100. [FIRMWARE @1bd2-1bde] Re-arm the host-command ISR (
(0x2070) = 0x030c),CALL 0750,RST 0x18,EI,RET. The bootstrap image is now in host memory; the controller signals completion by interrupt. The Z80 does not jump into the bootstrap — the ND-100 CPU (its LOAD microcode) transfers control to the loaded image.
press LOAD (ND-100 CPU) 3112 Z80 firmware
│ ┌──────────────────────────────────────────┐
│ control word (bit2/8) │ HostCmd_ISR @030c │
└───────────────────────►│ dispatch → Autoload_BootstrapLoad @1ae8 │
│ 1. RESTORE track 0 (FD1797 op 0x00) │
│ 2. read 2048 B → buffer 0x2200 │
│ 3. CPIR for '!' (0x21) ──no──► oct 50 │
│ 4. CR + octal digit? ──no──► oct 50 │
│ 5. parse header → host addr + count │
│ 6. wrong-format ('2'…'@'…='R')► oct 51 │
│ 7. DMA image to host in 0x800 chunks │
│ 8. re-arm ISR, raise completion int │
┌───────────────────────◄┤ (Z80 does NOT jump into bootstrap) │
ND-100 LOAD microcode └──────────────────────────────────────────┘
jumps into loaded image
4.4 The autoload error codes [MANUAL §3.9/§3.10 + FIRMWARE]¶
| Octal | Meaning | Firmware trigger |
|---|---|---|
| 50 | No bootstrap found on diskette | '!' not in the first 2048 bytes; or no CR within 128 bytes before '!'; or the char before CR is not an octal digit '0'..'7'. Code byte 0xA8 @ram:1ec8, hard-error class. [FIRMWARE] |
| 51 | Wrong bootstrap (out-of-date FLO-MON version) | '!' valid but header matches the '2'…'@'…checksum='R' pattern. Code byte 0xA9 @ram:1eca, hard-error class. [FIRMWARE] |
| 53 | Error during Autoload | Listed in the manual §3.9; a generic autoload failure. |
How the firmware builds the error image (verified @1f2f-1f6f)¶
On any autoload failure the firmware constructs the ND-100 error image byte-for-byte and DMAs it to the host first page, where the ND-100 runs it from word 0:
- Always copy the 0x3A-byte LOAD-ERROR image from ROM
@1a92to the buffer. It is a self-contained native ND-100 print routine (13 words of code) followed by the textFF SI CR LF " ** LOAD-ERROR: 00 **" CR LF '(the'=0x27is the string terminator). - Read the error code (
status_hi >> 1) and branch:- Wrong-bootstrap (51 oct /
0x29): overlay the 0x1C-byte text"** WRONG BOOTSTTRAP ! **"(ROM@1acc; note the firmware'sBOOTSTTRAPtypo) at byte offset 0x39 — overwriting the LOAD-ERROR terminator, so the printed output is both lines. Total image = 0x56 bytes. The digits are left as literal00. - Any other code: patch the two octal error digits at byte offset 0x32/0x33
(
AND 7; ADD '0'). Total image = 0x3A bytes.
- Wrong-bootstrap (51 oct /
- DMA the byte buffer to the host as big-endian 16-bit words (the card's byte→word PAL), starting at word 0; the ND-100 LOAD microcode runs it.
Implementation status. Both emulators now reproduce this byte-for-byte on boot failure (RetroCore C#
NDBusFloppyDMA.DmaAutoloadErrorImage; nd100x CDmaAutoloadErrorImage). The image bytes are verified against the ROM hexdump and the DMA is unit-tested (the C# test decodes the DMA'd words back to"...LOAD-ERROR:...50..."). The entry point word 0 is inferred from the image layout (code at the start); an end-to-end "boots, message appears on the console" integration test is not part of this change.
The captured ND-100 memory images for these two failure paths are preserved as front-panel
DEPOSIT scripts in the Ghidra project folder — they are the ground truth for the operator-
visible messages:
- E:\Dev\Repos\Ronny\RetroGhidra\N100-FLOPPY-3112\ND Code\Load_error.txt — the
"** LOAD-ERROR: nn **" image (oct 50 path).
- E:\Dev\Repos\Ronny\RetroGhidra\N100-FLOPPY-3112\ND Code\wrong_bootstrap.txt — the
"** WRONG BOOTSTRAP ! **" image (oct 51 path).
- E:\Dev\Repos\Ronny\RetroGhidra\N100-FLOPPY-3112\ND Code\DEPOSIT 0 77400.txt — a working
bootstrap memory image (octal words at octal addresses 0–310).
- E:\Dev\Repos\Ronny\RetroGhidra\N100-FLOPPY-3112\ND Code\convert.cs — the helper that
turns captured Z80 fragments into ND-100 DEPOSIT lines (its embedded strings contain both
the LOAD-ERROR and WRONG-BOOTSTRAP text).
4.5 No hardware LOAD-button path on the card [FIRMWARE — verified]¶
The reset vector reads IN A,(0x61) at ram:0000, but the value is immediately discarded
(LD A,0x1 at ram:0002 overwrites A before it is used, then OUT(0x74),A). Reset then
falls through to FD1797/interface init. There is no power-on or card-side LOAD-button
autoload. Autoload happens only when the ND-100 asserts the autoload control word to the
card. [FIRMWARE @0000-0006]
This matters for an emulator: you do not need to model a card-local boot trigger. Model the control word, and let the ND-100 side (microcode / the emulator's LOAD handling) assert it.
4.6 What an emulator needs for boot [INFERENCE, grounded in the above]¶
Minimum to make "boot from floppy" work at the IOX/functional level (i.e. without running the real Z80 firmware):
- Accept the autoload control word (bit 2, State I — and, given the ambiguity in §4.2, also accept bit 8 "fetch & execute" as the trigger the ND-100 microcode may use).
- Read the diskette's first sectors into a buffer and locate the BPUN bootstrap: find the
'!'(0x21) signature; require a CR (0x0D) within the preceding 128 bytes and an octal digit just before it. On failure, complete with error 50₈. - Reject an out-of-date monitor with error 51₈ if you choose to model the version check
(the
'2'…'@'…='R'pattern); otherwise skip it. - Parse the BPUN header to get the ND-100 load address and word count, DMA the image into ND-100 memory at that address, then complete with an interrupt — do not start execution yourself; the ND-100 LOAD microcode does that.
[COULD NOT DETERMINE] The precise BPUN header field arithmetic (
ram:1b52-1b82) that yields the exact numeric load address and count. If a functional emulator needs bit-exact addresses, either (a) run the real firmware, or (b) parse the BPUN format from its own specification. The workingDEPOSIT 0 77400.txtimage is a concrete example to validate against (its word 0 =77400₈, addresses 0–310₈).