SD-card BPUN loading and ND-100 device emulation - reference¶
Status: BUILT. This started as the 11-JUL-2026 design plan for an SD card +
FAT stack, a Verilog paper tape reader at device 400 and the road to floppy
and SMD. All of that exists now: the SD-FAT library (Verilog/SD-FAT/,
clean-room MIT reader and writer, 4-bit bus proven on the Tang Nano 20K
12-JUL-2026), the device bus interface (ND-BUS-DEVICES/BUS-IF/circuit/ND_BUS_SLAVE.v),
the tape reader (ND-BUS-DEVICES/TAPE-400/circuit/ND_TAPE_400.v), and the
storage facade that serves tape, floppy, SMD and Winchester images from the
card (Verilog/docs/nd-storage-design.md). The plan sections (folder layout,
component specs, milestones, core selection) were removed; they are in git
history. What stays is the verified reference material below.
All paths are relative to the repository root. Anything not confirmed against a primary source is marked UNVERIFIED.
4. ND-100 paper tape reader, device 400 octal - verified spec¶
Primary sources, cross-checked and in agreement:
- "NORD-100 Input/Output System" ND-06.016.01, Appendix A page A-3 (device table) and section I.3.5 (standardized PIO status/control): http://www.bitsavers.org/pdf/norskData/ND-100-IO-ND-06.016.01_NORD-100_Input_Output_System_1980.pdf
- "ND-100 Functional Description" ND-06.015.02, sections 7.2.2 (ALD)
and 7.2.5 (binary format load), and appendix B.2 (paper tape reader
spec, quoted in
Emulated.HW/ND/CPU/NDBUS/NDBusPapertapeReader.csin the RetroCore repository): http://www.bitsavers.org/pdf/norskData/ND-100-FD-ND-06.015.02_ND-100_Functional_Description_1985.pdf - https://www.ndwiki.org/wiki/BPUN_File_Format (bootstrap listing)
Verilog/simDevices/NDDevices.cpp(working sim implementation)
4.1 Identity¶
| Property | Value |
|---|---|
| Register address range | 400-403 octal (reader 2: 404-407) |
| Interrupt level | 12 (input-channel PIO level) |
| Ident code | 2 octal (reader 2: 22 octal) |
| SINTRAN logical device | 2 (nd100x uses 3 - manual says 2; punch is 3) |
4.2 Register map (register = device number + offset)¶
| IOX addr (octal) | Dir | Name | Behavior |
|---|---|---|---|
| 400 | read | Read data register | 8 data bits right-justified in A bits 7-0. Reading clears status bit 3 (ready for transfer). Same character may be read repeatedly until the next activate. |
| 401 | write | Write data buffer | Not used by the reader (punch-style slot). Accept and ignore. |
| 402 | read | Read status register | See bits below. |
| 403 | write | Write control word | See bits below. |
IOX addressing rule: even offset = device-to-A (read), odd = A-to-device (write); address bits 2-0 select the register, higher bits the device (https://ndwiki.org/wiki/IOX).
4.3 Status register bits (IOX 402)¶
| Bit | Meaning |
|---|---|
| 0 | Interrupt-on-ready-for-transfer enabled (echo of control bit 0) |
| 2 | Read active (device busy fetching a character) |
| 3 | READY FOR TRANSFER - data register holds a valid character. This is the bit the boot loader polls (BSKP ONE 30 DA). |
| others | Not used on the reader (return 0) |
Interrupt condition: status bit 0 AND status bit 3 -> raise level 12.
IDENT on level 12 returns ident code 2 and clears the interrupt (and
clears the interrupt-enable bit - both NDDevices.cpp IDENT() and
NDBusPapertapeReader.cs do this).
4.4 Control word bits (IOX 403)¶
| Bit | Meaning |
|---|---|
| 0 | Enable interrupt on ready for transfer |
| 2 | ACTIVATE - fetch the next character from the tape; when it is in the buffer, status bit 3 sets |
| 3 | Test mode (interface self-test; optional - see below) |
| 4 | Device clear - clears control/status, empties the buffer, rewinds the tape (in our case: reopen / seek 0 of the file) |
| others | Not used |
Behavioral contract, distilled from Verilog/simDevices/NDDevices.cpp
PaperTape::Write() (the version the CPU microcode is proven to boot
against in Verilator):
- On control write: copy bit0 -> status bit0, bit2 -> status bit2.
- If bit4 (device clear): clear read-active and ready-for-transfer, zero the character buffer, rewind the tape.
- If read-active: clear ready-for-transfer, fetch one byte from the tape stream; on success put it in the data register and set ready-for-transfer; on EOF leave ready-for-transfer clear. Then clear read-active.
- Update the interrupt line: level-12 request = status bit0 AND bit3.
Note the hardware nuance: in the real interface the fetch takes tape time and status bit 2 stays set meanwhile; in the C++ model the fetch is immediate. The Verilog device should insert a SMALL delay (a few bus-clock cycles, or "data ready when the SD FIFO has a byte") - the polling loop tolerates any latency, and instant-ready is also proven to work in sim, so latency is a free parameter.
Control bit 3 (test mode) lets diagnostics increment the data register without a reader; implement it only if the ND test programs need it - mark: OPTIONAL, not needed for boot.
4.5 The boot flow (why this device is enough to boot)¶
The CPU card ALD strap in Verilog/CPU-BOARD-3202/circuit/IO_REG_41.v
was 0100 binary = ALD switch position 11 = ALD value 400 octal = "BPUN
load from paper tape (400) and run" when this was written. Since
07-AUG-2026 it is 0010 = bootstrap load from the Winchester (500), so a
bare & boots the disc; the tape is reached explicitly with 400$ or
400&. Either makes the MOPC microcode run its built-in
binary loader ("Octal load is not implemented in ND-100" - the binary
loader is microcode, ND-06.015.02 page 7-20). The microcode issues
exactly the polled loop:
rdbyt: SAA 4 A := 4 (control bit 2 = activate)
IOX 403 write control word
wait: IOX 402 read status
BSKP ONE 30 DA skip when status bit 3 set
JMP * -2
IOX 400 read the byte
4.6 BPUN stream format (what comes off the "tape")¶
From ndwiki BPUN page and ND-06.015.02 section 7.2.5.1 (fields A-I),
matching loadfile() in Verilog/runSim/Run120.cpp:
| Field | Content |
|---|---|
| A | Preamble: any bytes except !. Historically an ASCII bootstrap with even parity; the ND-100 microcode just scans past it. |
| B | Start address, ASCII octal digits terminated by CR (LF ignored) |
| C | Bootstrap loader address, ASCII octal terminated by ! |
! |
Delimiter (41 octal) - binary section begins |
| E | Load address: 2 bytes, MSB first |
| F | Word count: 2 bytes, MSB first |
| G | F 16-bit data words, each MSB first |
| H | Checksum: 16-bit arithmetic sum of G, MSB first |
| I | Action code: 0 = start CPU at address B; nonzero = return to OPCOM with P = B |
Important consequence: the device does NOT parse BPUN. It is a dumb byte pipe - the microcode does all parsing and checksumming. The Verilog TapeReader only needs "give me the next byte of the file".
5. How a device-400 IOX reaches a device in THIS design¶
There are two distinct I/O paths in the 3202D board and it matters which one we use:
-
On-board (internal IDB) devices - console UART (SC2661 model,
Verilog/CPU-BOARD-3202/circuit/IO_UART_42.v), RTC, panel. Their chip selects (CEUART_n,RUART_n, ...) are decoded inside the DECODE gate array (instantiated inVerilog/CPU-BOARD-3202/circuit/IO_DCD_38.v) and their data goes straight onto the internal IDB via the source mux inVerilog/CPU-BOARD-3202/circuit/IO_37.v. This decode is fixed by the DGA - we cannot (and should not) add device 400 here. -
External ND-100 bus devices - everything else, including device
- An IOX whose address is not on-board becomes an external bus
cycle through the Bus InterFace (
Verilog/CPU-BOARD-3202/circuit/ BIF_5.vand children), using the active-low multiplexed busBD_23_0_nplus the control strobes. In the Verilator sim these come out ofVerilog/ND120_TOP.vas ports and are serviced byproccess_bif_signal()inVerilog/simDevices/NDBus.cpp(the legacy C devices,VERILOG_TAPE=0) or by the Verilog devices inND120_CORE.v(the default).
- An IOX whose address is not on-board becomes an external bus
cycle through the Bus InterFace (
5.1 The bus protocol to implement (from Verilog/simDevices/NDBus.cpp)¶
All BD data/address bits are ACTIVE LOW on the bus (value = ~BD).
Edge semantics, in order of a typical IOX transfer:
| Event (CPU asserts) | Device action |
|---|---|
BAPR_n falling |
Latch address = ~BD_23_0_n & 0xFFFFFF. Address bit 0 even = READ cycle, odd = WRITE cycle. Deassert BINPUT_n. |
BIOXE_n falling |
WRITE cycle: data = ~BD_23_0_n & 0xFFFF; perform the register write; assert BDRY_n (data accepted). READ cycle: assert BINPUT_n (request to drive the bus) and wait for BINACK_n. |
BINACK_n falling |
READ cycle: drive BD_23_0_n = ~data; assert BDAP_n and BDRY_n. |
BIOXE_n rising |
Release everything: BDRY_n=1, BDAP_n=1, BINPUT_n=1, stop driving BD (drive all-ones = inactive). Cycle done. |
OUTIDENT_n falling |
The address bus holds the IDENT level code: 004 octal -> level 10, 011 -> 11, 022 -> 12, 043 -> 13. If this device has a pending interrupt on that level: assert BINPUT_n, put ~identcode on BD, then complete via BINACK_n/BDAP_n/BDRY_n as a read. Clear the interrupt. |
OUTIDENT_n rising |
Release BD, BINPUT_n, BDRY_n. |
| (continuous) | Drive BINT12_n low while the device requests level-12 interrupt. |
Memory cycles (BMEM_n) are ignored by I/O devices.
Inside the FPGA the "bus" is not a real tri-state bus: each device outputs a 24-bit data word and a drive-enable; a rail module ORs/muxes them (repo tri-state rule). The active-low inversion is kept at the rail so the CPU-side BIF sees exactly the polarity it sees in sim.
5.2 Where it plugs in¶
The Verilog devices hang off ND_BUS_SLAVE inside Verilog/ND120_CORE.v,
which every board top and ND120_TOP.v (Verilator) instantiate. The C++
device path in Verilog/simDevices/ still builds with VERILOG_TAPE=0.
6. SD hardware¶
6.1 Tang Nano 20K microSD slot (primary target)¶
Pin numbers verified against three independent constraint files:
Sipeed's own https://github.com/sipeed/TangNano-20K-example
(nestang/src/nestang.cst), https://github.com/nand2mario/nestang
(src/boards/nano20k.cst) and https://github.com/nand2mario/snestang
(src/boards/nano20k.cst). Board schematic:
https://dl.sipeed.com/shareURL/TANG/Nano_20K/2_Schematic
| Signal | FPGA pin (QN88) | SD-native role | SPI role |
|---|---|---|---|
sd_clk |
83 | CLK | SCLK |
sd_cmd |
82 | CMD (bidir) | MOSI |
sd_dat0 |
84 | DAT0 | MISO |
sd_dat1 |
85 | DAT1 (drive 1) | unused (drive 1) |
sd_dat2 |
80 | DAT2 (drive 1) | unused (drive 1) |
sd_dat3 |
81 | DAT3 (drive 1) | CS |
All IO_TYPE=LVCMOS33 PULL_MODE=NONE. Driving DAT1-3 high keeps the
card in SD-native mode / deselected-for-SPI as appropriate. No card
detect line appears in any constraint file (UNVERIFIED whether the
slot has one at all - assume none; detect the card by init success).
Both modes are proven on this exact slot: Sipeed's NESTang example
uses WangXuan95's SD-native 1-bit reader; nand2mario's iosys uses SPI.
6.2 Basys3 (second target) - Pmod microSD¶
The Basys3 has no SD slot; use a Digilent Pmod MicroSD (or compatible) on Pmod header JA. UNVERIFIED until the adapter is in hand - verify against the Digilent Basys3 master XDC and the Pmod MicroSD reference manual. Expected mapping (Basys3 master XDC pin names for JA):
| Pmod pin | JA site | FPGA pin (UNVERIFIED) | Pmod MicroSD signal |
|---|---|---|---|
| 1 | JA1 | J1 | DAT3 / CS |
| 2 | JA2 | L2 | MOSI (CMD) |
| 3 | JA3 | J2 | MISO (DAT0) |
| 4 | JA4 | G2 | SCK |
| 7 | JA7 | H1 | DAT1 |
| 8 | JA8 | K2 | DAT2 |
| 9 | JA9 | H2 | CD (card detect) |
| 10 | JA10 | G3 | (nc) |
Because the wiring is identical minus the connector, the SD/FAT component must not hard-code pins or vendor primitives - plain Verilog, pins only in each board's constraint file.
12. Still open from the plan¶
- The Tang slot shows no card-detect line in any constraint file (UNVERIFIED whether the slot has one); the stack detects a card by init success or timeout.
- The Basys3 Pmod MicroSD pinout in section 6.2 is unverified; no Basys3 SD build exists.
sd_file_readerscans the root directory only; subdirectories are not searched (card recipe:Verilog/fpga/tang-nano-20k/sd-fat-test/CARD-SETUP.md).
13. Summary of verified facts (quick reference)¶
- Tang Nano 20K SD pins: CLK=83, CMD=82, DAT0=84, DAT1=85, DAT2=80, DAT3=81, all LVCMOS33 (three independent .cst sources).
- SD/FAT core: the clean-room MIT
Verilog/SD-FAT/circuit/sd_file_reader.v(replaced the GPL WangXuan95 reader on 12-JUL-2026) plussd_writer.v. - Device 400 octal: registers 400 data / 402 status / 403 control; status bit 3 = ready-for-transfer (polled by boot loader), control bit 2 = activate, bit 4 = device clear, bit 0 = interrupt enable; interrupt level 12, ident code 2 octal.
- ALD strap in
Verilog/CPU-BOARD-3202/circuit/IO_REG_41.vis the Winchester (500) since 07-AUG-2026;400$/400&at OPCOM boots from the tape reader. - The device does not parse BPUN; the microcode binary loader does.
- Device side of the ND-100 bus protocol =
proccess_bif_signal()inVerilog/simDevices/NDBus.cpp; the Verilog version isND-BUS-DEVICES/BUS-IF/circuit/ND_BUS_SLAVE.v.