YACC1 programming guides¶
Index of the programmer-facing documentation and the one-page programmer's model of the machine. Written 2026-09-23 from the YACC1-D tree.
Sources: software/opcodes.h, software/assembler/yacc1.def, firmware/microcode/ucode-generator2/*.c,
firmware/microcode/ucode-generator2/CodeGen.h, docs/isa/MICROCODE-REVIEW-NOTES.md, firmware/monitor/monitor.asm,
firmware/abi/README.md, docs/system/MACHINE.md, docs/system/OS-PLAN.md, software/emulator/main.c,
software/ucemu/y1ucemu.c, software/compiler/y1cc.py.
Rule of these documents (from docs/DOC-PLAN.md): every fact comes from a file in the tree and says which; anything
the tree does not settle is marked To verify: with what would settle it. The microcode generator is the source of
truth for what an instruction does; the two emulators are executable restatements of it, and where they disagree with
the microcode the ISA reference says so.
The guides¶
| Document | What it covers |
|---|---|
| ISA-REFERENCE.md | every opcode: encoding, bytes, microcode steps, semantics, flags, hazards; the opcode-ordered index $00..$FF; the emulator-vs-hardware differences |
| ASSEMBLER.md | the RC/asm cross assembler: invocation, output formats, directives, expressions, quirks, the four worked examples, the .def table format |
| C-COMPILER.md | y1cc: the C subset, builtins, the code model, CLI flags, the test runner and host oracle, the P8X size comparison, a worked example with its generated code |
| MONITOR.md | the ROM monitor: every command, boot, the BIOS vectors, variables, the CF driver, the O boot, BASIC in ROM, the ROM build, the ISR |
| OS.md | Y1/OS: user guide, writing a /BIN command, the P8XFS v2 on-disk format, memory layout, plan and backlog |
| MEMORY-MAP.md | the definitive address map: RAM, ROM, reserved pages, what the OS plan assigns |
| IO-PORTS.md | the sixteen I/O ports, the I/O card's select latch, the CF card ports, what is reserved |
| EMULATORS.md | the instruction-level emulator and the microcode-level emulator: options, models, what each does not model, how to run the monitor / a program / the OS, how to capture a session for a test |
| TOOLCHAIN.md | end to end: source to .img to emulator to ROM or CF image, loading microcode, the test suites, the tree audits |
Companion documents written the same day under the same plan: docs/system/ARCHITECTURE.md (the machine as a
whole), docs/system/MICROCODE.md (the control store), docs/system/BUS.md (the backplane), docs/procedures/
BRING-UP.md and TESTING.md, the per-card write-ups in docs/cards/. The tree was being edited while these
guides were written (the monitor gained its sixteenth vector, the OS its file API, the compiler sys()/funcaddr()
on the morning of 2026-09-23); each guide states the file dates it read.
The programmer's model in one page¶
Registers¶
| Register | Width | Role | Source |
|---|---|---|---|
| ACC | 8 bits | the accumulator: every ALU operation reads and writes it; the only path to and from memory bytes, I/O ports and the low/high halves of the index registers | firmware/microcode/ucode-generator2/accumulator.c |
| TMP | 8 bits (16 on the card) | the second ALU operand (ADDT, SUBT, ANDT, ORT, XORT, ADDTC) and the comparand of BRLT/BRGT/BREQ/BRNEQ; loaded by LDTI, LDT, MVAT, read back by MVTA, STT. Physically TMP0 on the memory card, a 16-bit 74LS374 pair; only the low byte is meaningful to a program. A second register TMP1 exists but is microcode scratch (PUSHR, LDIVR) and has no instruction of its own |
MICROCODE-REVIEW-NOTES.md 1.3, register.c, memory.c |
| Carry | 1 flip-flop | loaded by every add and subtract and by every shift (see below); tested by BRC; added in by ADDIC/ADDTC; rotated through by CSHL/CSHR |
MICROCODE-REVIEW-NOTES.md 1.4 |
| R0 | 16 bits | the program counter | CodeGen.h #define PC 0 |
| R1 | 16 bits | the stack pointer (JSR, RET, PUSH*, POP*, INT, IRET) |
CodeGen.h #define SP 1 |
| R2 | 16 bits | the hardware's operand-address register: LDA, STA, LDT, STT, LDR, STR load the operand address into it and leave it there (+2 after LDR/STR). A program must never keep anything in R2 |
CodeGen.h #define IR 2, register.c, MICROCODE-REVIEW-NOTES.md L-9 |
| R3..R7 | 16 bits | general index registers: byte access through them (LDAVR/STAVR), 16-bit load/store (LDR/STR), count (INCR/DECR), byte moves to and from ACC (MVRLA/MVRHA/MVARL/MVARH), copy (MOVRR), jump/call targets (BRUR, JSRUR, BRVR). The monitor's convention: R7 = the pointer argument of a BIOS call |
yacc1.def, firmware/abi/README.md |
| IN | 1 line | the I/O card's input-switch line, tested by BRINH/BRINL |
branch.c (ALUIN), tests/assembler/romcount/README.md |
| OUT | 1 latch | the ON/OFF LED, set by ON/OFF |
io.c |
There is no 16-bit ALU and no indexed addressing (Rn+d); 16-bit arithmetic is done a byte at a time through ACC
and TMP with the carry between the halves (the monitor and the C compiler both use the MVRLA/MVAT/.../ADDT/MVARL/
MVRHA/.../ADDTC/MVARH idiom, software/compiler/README.md).
Two register cards hold R0..R3 and R4..R7 (docs/system/MACHINE.md). With only card 0 fitted, R4..R7 read as the
bus pull-ups ($FF) and loads to them vanish; tests/assembler/romdiag stage 9 detects that and y1ucemu -R 1
models it. Both cards are fitted since 2026-09-22 evening.
Words in memory: big-endian¶
Every 16-bit quantity the hardware reads or writes as two bytes is high byte first: instruction address operands
(BR hi lo, JSR hi lo, MVIW Rn,hi lo), LDR/STR ([addr] = high byte, [addr+1] = low byte), the pushed return
address (high byte at the higher address, see the stack below), BRVR vectors, and the assembler's DW
(yacc1.def: DW \W → hi(1) lo(1)). P8XFS directory fields on the CF card are little-endian, which Y1/OS assembles
byte-wise (os/README.md).
The stack¶
R1 is the stack pointer. The monitor sets it to $0EFF at reset (monitor.asm: STACK: EQU 0EFFh, MVIW R1,STACK);
the compiler's --boot stub and the ROM test programs do the same. The stack grows downward and R1 points at the
next free byte (branch.c, software/emulator/main.c):
PUSH:[R1] ← ACC; R1 ← R1−1.POP:R1 ← R1+1; ACC ← [R1].JSR addr/JSRUR Rn/INT:[R1] ← PC.hi; R1−−; [R1] ← PC.lo; R1−−(PC = the address of the next instruction), thenPC ← target.RET/IRET:R1++; PC.lo ← [R1]; R1++; PC.hi ← [R1].PUSHR Rn:[R1] ← Rn.hi; R1−−; [R1] ← Rn.lo; R1−−.POPR Rn: the reverse.
The first byte ever pushed therefore lands at $0EFF; $0C00 is the informal floor (firmware/abi/README.md).
Nothing checks for overflow.
Memory map (details in MEMORY-MAP.md)¶
| Range | What | Source |
|---|---|---|
| $0000–$7FFF | RAM (low 62256) | hardware/cards/memory/README.md |
| $0100–$02FF | BASIC's variables | firmware/basic/basic.asm |
| $0300–$04FF | BASIC's input line and token-line buffers | basic.asm |
| $0C00–$0EFF | the stack (top $0EFF, informal floor $0C00) | monitor.asm, firmware/abi/README.md |
| $0F00–$0FFF | the monitor's variables page: monmode $0F00, continue_addr $0F02, interupt_cnt $0F04; the OS's syscall block SYSARG0..2 $0F06/$08/$0A, SYSRES $0F0C, SYSTAB $0F14–$0F3F (22 words); CFLBA0..2 $0F10; ARGBUF $0F40 (64 bytes to the monitor, 128 to the OS, overlaying the idle line_buffer $0F80) |
monitor.asm, os/lib_abi.c, y1cc.py |
| $1000–$1FFF | BASIC's token buffer, and where the O command loads the OS (the two are not used together) |
basic.asm, monitor.asm |
| $3000 | default load address of a compiled program (G3000) |
software/compiler/y1cc.py |
| $5000–$CFFF | Y1/OS's transient program area | os/lib_abi.c (TPA, TPATOP) |
| $8000–$CFFF | RAM (high 62256, per-4K jumpers) | docs/system/MACHINE.md |
| $D000–$DFFF | undecoded on the memory card, reserved for the video card ($D000–$D7FF) | MACHINE.md |
| $E000–$EFFF | ROM: BASIC | firmware/rom/README.md |
| $F000–$FFFF | ROM: the monitor; ISR at $FF90; BIOS vectors $FFC0..$FFFF (sixteen since 2026-09-23, the table fills the ROM to its last byte) | monitor.asm, monitor.lst |
After reset the ROM appears at every address (FORCE-ROM) until the first access with A15 high; the first instruction
of any ROM-resident program must therefore be a branch to an address ≥ $8000 (monitor.asm: BR eprom at $F000,
hardware/cards/memory/README.md).
The BIOS vectors (details in MONITOR.md)¶
Sixteen 4-byte entries at $FFC0 (JSR routine / RET each; monitor.asm org 0ffc0h): STRINGOUT $FFC0,
CHAROUT $FFC4, UARTOUT $FFC8, SHOWADDR $FFCC, TOUPPER $FFD0, SHOWR7 $FFD4, SHOWBYTE $FFD8, SHOWREGS $FFDC,
SHOWBYTEA $FFE0, SHOWCARRY $FFE4, UARTIN $FFE8, CFINIT $FFEC, CFREAD $FFF0, CFWRITE $FFF4, CONST $FFF8,
UARTINNE $FFFC (console byte without echo, added 2026-09-23 for the OS's CONIN syscall). Convention:
a pointer travels in R7, a byte in ACC; a routine may clobber R5, R6, TMP and, unless stated, R7 (firmware/abi/README.md).
From C: bios(CHAROUT, 0, c) (software/compiler/y1cc.py, os/lib_abi.c).
The console¶
On the machine the console is the I/O card's 16550 UART at 38400 baud, reached through port P0 (select latch,
UARTCS $40 + register offset) and P1 (data) — monitor.asm uartout/uartin. UARTIN echoes every byte it
reads and turns CR into LF; UARTINNE ($FFFC) reads without the echo. Under Y1/OS a program reads its input
through the syscalls SYS_CONIN/SYS_CONST and writes through SYS_CONOUT (y1cc --os makes getchar/putchar
those), which the shell redirects (<, >, >>, |); a key the user answers with is SYS_KEYIN, always the
console (OS.md). CONIN and KEYIN return 65535 at Ctrl-D or end of input. On the instruction-level emulator the same routines take the BRDEV branch and use
port 2 (OUTA P2 / INP P2); the microcode-level emulator models the UART, so it takes the hardware path
(EMULATORS.md).
Instruction timing¶
A microcode step is two clock periods; the step carrying UCODE-COUNT-RESET is one (MICROCODE-REVIEW-NOTES.md 1.1,
software/ucemu/y1ucemu.c header). Instructions take 8 (ON) to 33 (PUSHR) steps: the per-opcode counts are in
ISA-REFERENCE.md. To verify: the oscillator frequency is not recorded in the tree
(MICROCODE-REVIEW-NOTES.md M-2: "BOM: XO-14, unknown"); reading the can on the sequencer-logic card would settle it.