YACC1 I/O ports¶
The sixteen I/O ports: what is on each today, how the I/O card's select-latch scheme works, the CompactFlash ports, what the emulators model, and what the OS plan reserves. Written 2026-09-23 from the YACC1-D tree.
Sources: firmware/abi/README.md (the port table), docs/system/OS-PLAN.md (decision 3, the port map with the
proposed uses), firmware/monitor/monitor.asm (the equates and every port access the ROM makes), hardware/cards/io/README.md,
docs/isa/MICROCODE-REVIEW-NOTES.md 1.5 (how the I/O card decodes), software/assembler/yacc1.def (port names),
software/cfmodel.h (the CF card model), software/emulator/main.c, software/ucemu/README.md,
tests/assembler/romcount/README.md, docs/datasheets/PC16550D.pdf (the UART's register names).
1. How ports work on the bus¶
The port number is a 4-bit field of the microcode word (IOADDR0..3), driven for the duration of an INP/OUTA/
OUTI record; the strobes are -IO-RD and -IO-WR. The I/O card decodes IO-ADDR0..2 with a 74LS138 (IC5) and
takes IO-ADDR3 through the IO-ADDR-HL jumper, so the card answers to P0–P7 or P8–P15; it is strapped to the low
half (OS-PLAN.md). Of its eight selects only two are wired (P0, P1); P2–P7 reach the card's header. A port write
is latched at the trailing edge of -IO-WR; a read drives the bus while -IO-RD is low (review 1.5). The
microcode's -IO-ADDR-LD strobe has no consumer.
Instructions: OUTA Pn (port ← ACC), OUTI Pn,byte (port ← immediate), INP Pn (ACC ← port); port names
P0–P9, PA–PF (yacc1.def). From C: outp(port, v), inp(port) with a constant port.
2. The port map¶
| Port | Today (hardware) | Interpreter (software/emulator) |
ucemu | Proposed (OS-PLAN.md) |
|---|---|---|---|---|
| P0 | I/O card select latch (write): which device P1 talks to — see section 3 | stored; $40 selects the UART path for OUTI P1 |
modelled (control latch) | unchanged |
| P1 | I/O card data port for the device selected in P0 | OUTI P1/OUTA P1 with P0 = $40 prints; INP P1 with P0 = 1 returns $FF once (the switches); with P0 selecting the UART (2026-09-26) RBR and LSR as ucemu models them |
modelled: 16550 (stdin/stdout), switches (-s), LEDs (-L) |
unchanged |
| P2 | -IO-SEL2 on the I/O card's header, nothing wired |
the console: OUTA P2 prints, INP P2 reads a key |
also a console (kept as the old shortcut) | stays the emulator console; reserved to the I/O card |
| P3–P7 | -IO-SEL3..7 on the header, nothing wired |
nothing | nothing | reserved to the I/O card (a second UART, a printer port…); since nothing on the card uses them, probably usable by another card that decodes them itself - verify on the board first (BACKLOG, 2026-09-24) |
| P8 | free | CF register select (cfmodel.h) |
same | CF register select (write-only latch): bits 0–2 = ATA register 0–7, bit 3 = CF reset (1 = held; the ROM never sets it, the model ignores it), 4–7 ignored |
| P9 | free | CF data (cfmodel.h) |
same | CF data: a read/write strobes -IOR/-IOW on the selected register |
| PA, PB | free | nothing | nothing | video card v2: 6845 address register (RS = 0) and data register (RS = 1) |
| PC, PD | free | nothing | nothing | PS/2 keyboard controller data and status/control (or a second CF) |
| PE, PF | free | nothing | nothing | free (RTC, second CF, sound) |
yacc1.def had P8=9 until 2026-09-22 (no firmware used P8 before the CF driver); it is P8=8 now
(tools/patched_files.txt).
3. The I/O card: P0 selects, P1 transfers (monitor.asm equates)¶
The byte written to P0 chooses the device and, for the UART, its register:
| P0 value | Name | Device behind P1 |
|---|---|---|
$01 |
SWITCHLED |
the switch/LED board: INP P1 = the eight switches; OUTA P1 = the eight LEDs (switchin, ledout) |
$02 |
LCDENABLE |
the LCD ($04 LCDREGISTER selects its register); only in commented-out code |
$40 + n |
UARTCS + UARTAn |
the 16550 UART, register n where UARTA0..A7 = $00, $08, $10, $18, $20, $28, $30, $38 |
$80 |
TIL311 |
the TIL311 hex displays (TIL311out) |
The UART registers by their offset (16550 names, docs/datasheets/PC16550D.pdf; the monitor uses exactly these):
| P0 | Register | Used for |
|---|---|---|
$40 (UARTA0) |
RBR / THR (DLL with DLAB) | INP P1 receives, OUTA P1 transmits; at boot DLL ← 3 |
$48 (UARTA1) |
IER (DLM with DLAB) | at boot DLM ← 0 |
$50 (UARTA2) |
IIR / FCR | the ROM never writes FCR (no FIFO: one character of buffering); /BIN/KERMIT writes $07 (FIFOs on, cleared) for a transfer and $00 after (2026-09-26, os/kermit_io.asm) |
$58 (UARTA3) |
LCR | at boot $80 (set DLAB) then $03 (8N1) |
$68 (UARTA5) |
LSR | bit 0 data ready (uartin, const), bit 5 transmit holding register empty (kermit_io's ktx), bit 6 transmitter empty (uartout waits for $40) |
Baud: divisor 3 = 38400 (monitor.asm: OUTI P1,3 ;38400; the commented 12 ;9600 implies a 1.8432 MHz UART
clock: 1.8432 MHz / 16 / 12 = 9600). To verify: the UART crystal value against the I/O card schematic
(hardware/cards/io/eagle/v1.1).
The console idioms (monitor.asm):
uartout: OUTI P0,(UARTCS!UARTA5) / INP P1 / ANDI 40h / BRZ uartout ; wait THRE
OUTI P0,UARTCS / OUTA P1 ; send ACC
uartin: OUTI P0,(UARTCS!UARTA5) / INP P1 / ANDI 01h / BRZ uartin ; wait data ready
OUTI P0,UARTCS / INP P1 ; receive (then CR -> LF, LEDs, echo)
ledout: OUTI P0,SWITCHLED / OUTA P1
Under the BRDEV switch the same routines use P2 on the interpreter (MONITOR.md section 4).
uartinne (vector $FFFC, 2026-09-23) is uartin without the echo and the LED write, for the OS's CONIN.
The input-switch line tested by BRINH/BRINL is not a port: it is the ALU condition mux's IN input, fed from the
I/O card (tests/assembler/romcount/README.md: "the I/O card's input-switch line"). The ON/OFF LED (ON/OFF) is a
latch on the sequencer, not a port either.
4. The CompactFlash ports P8/P9 (monitor.asm driver, cfmodel.h)¶
OUTI P8,nselects ATA task-file register n: 0 data, 1 error (read) / feature (write), 2 sector count, 3 LBA0, 4 LBA1, 5 LBA2, 6 drive/head ($E0= LBA mode, drive 0), 7 status (read) / command (write).INP P9/OUTA P9/OUTI P9,vtransfer one byte with the selected register. A 512-byte sector is one select of register 0 and 512 transfers (cfread:INP P9 / STAVR R7 / INCR R7).- Commands used:
$EFSET FEATURES (feature 1 = 8-bit transfers, at init),$20READ SECTORS,$30WRITE SECTORS; the model also answers$ECIDENTIFY. Status bits: 7 BSY, 6 DRDY, 3 DRQ, 0 ERR. - The model (
software/cfmodel.h): BSY never asserted; DRQ up while a buffer streams; ERR on an unknown command; with no image attached every read returns $FF so a bounded poll times out; a missing image file is created zero-filled (256 sectors). Attach with-c disk.imgon either emulator. - Bit 3 of the select byte holds the CF in reset while 1 (the hardware's addition; the ROM never sets it and the model ignores it).
- The hardware is not built. The circuit is the CF card v1.0's (
docs/cards/cf.md): a 74LS138 decoding P8/P9 (enabled by IO-ADDR3 high), 74LS32 strobe gating from -IO-RD/-IO-WR, a 74LS175 select latch, a 74LS08 (buffer enable, CF reset, ACT LED), a 74LS245 data buffer, status pull-ups and a 40-pin IDE header for a CF-to-IDE adapter. Since 2026-09-24 it is planned onto the memory card, still I/O-mapped (not designed yet). Bench-test with the bus tester before the CPU touches it.
5. Design rules from the plan¶
Every new device with more than one register follows the select+data pattern (one port pair), so the sixteen ports last; the I/O card keeps the low eight. The console stays two BIOS vectors (CHAROUT/UARTIN + CONST) so the video card and a PS/2 keyboard can replace the UART without the OS or the programs knowing.