YACC1 architecture — the machine as a whole¶
How the YACC1's cards, buses, registers and control store fit together, and what happens between one opcode fetch
and the next. This is the entry document: the bus itself is in BUS.md, the control store in MICROCODE.md, each
card will get its own theory of operation under docs/cards/.
Written 2026-09-23 from the YACC1-D tree.
Sources: docs/system/MACHINE.md (what is fitted), docs/isa/MICROCODE-REVIEW-NOTES.md section 1 (the hardware model
re-derived from the KiCad netlists), hardware/DESIGN-REVIEW-NOTES-datapath.md and hardware/DESIGN-REVIEW-NOTES-control-io.md
(the 2026-09-21 design review), software/ucemu/y1ucemu.c (the microcode-level emulator: the executable form of that model),
firmware/microcode/ucode-generator2/*.c and firmware/microcode/yaccsignaldata2.h, docs/isa/steps.txt and
docs/isa/LDAI.json / docs/isa/OUTA.json, hardware/cards/*/README.md and eagle/<rev>/Notes.md, the Eagle schematics
(hardware/cards/<card>/eagle/<rev>/*.sch, parsed with Python for part types and nets), hardware/cards/memory/README.md,
firmware/abi/README.md, firmware/monitor/monitor.asm, software/opcodes.h, software/assembler/yacc1.def,
docs/system/OS-PLAN.md, BACKLOG.md, hardware/FABRICATED.md.
Conventions: a signal name with a leading - is active low (the same rule the generator uses to decide which way a bit
is written, main.c setSignal()); "IC7 on the memory card" means the reference designator on that card's schematic;
$ prefixes hexadecimal. Anything the tree does not settle is marked To verify:.
1. What the YACC1 is¶
The YACC1 ("Yet Another Custom CPU", README.md) is an 8-bit accumulator machine built from TTL on plug-in cards
that share a 96-pin DIN 41612 backplane. Its datapath is 8 bits wide (the accumulator, the ALU, memory bytes) but its
address space, its index registers and the data bus are 16 bits wide, so a 16-bit word can travel across the bus in one
step. There is no instruction decoder in the usual sense: every opcode is a 64-step microprogram held in RAM on the
sequencer-memory card, and the sequencer-logic card simply plays those steps onto the bus. The datapath cards (ALU,
index registers, memory, I/O) contain no state machine of their own; each one reacts to the strobes it sees on the bus.
That is why a wrong microcode step can corrupt a stack push (docs/isa/MICROCODE-REVIEW-NOTES.md H-1) and why the
microcode-level emulator can reproduce the machine from the control-store image alone (software/ucemu/README.md).
Register model in one line (firmware/microcode/ucode-generator2/CodeGen.h, docs/isa/MICROCODE-REVIEW-NOTES.md 1.2, L-9):
one 8-bit accumulator ACC, two 16-bit temporaries TMP0/TMP1 on the memory card (the ISA's "TMP" is TMP0), a carry
flip-flop, an 8-bit shift register, and eight 16-bit up/down counters R0..R7 of which R0 is the program counter, R1 the
stack pointer and R2 the operand-address register that LDA/STA/LDT/STT/LDR/STR use as scratch.
2. Block diagram¶
The machine as fitted on 2026-09-23 (docs/system/MACHINE.md; hardware/FABRICATED.md "In the machine"):
SV1/SV2 ribbon (2 x 40 pins: 8 control bytes + CADDR0..13 + READY/SPARE)
+------------------------+ <=====================> +--------------------------+
| SEQUENCER MEMORY V2.1 | | SEQUENCER LOGIC v2.1 | front panel:
| 8 x 62256 = 256 op x | | step counter (2x74LS192) | RESET/EXECUTE, HALT, CONT,
| 64 step x 8 byte | | IR (2x74LS175) | SS/WAIT, STEP-CLK, SS-SEL
| ATmega328P loader, | | operand/branch/INT regs | clock: QG1 oscillator socket
| 24Cxx EEPROM (adaptor) | | pipeline: 8 x 74LS374 |
| not on the bus | | -BUS-EN = NOT READY |
+------------------------+ +-----------+--------------+
| control lines (rows B, C)
=================================================================================================== 96-pin
ADDR0..15 (row A) DATA0..15 (row A/B) -MEM-RD -MEM-WR -IO-RD -IO-WR -VMA ... -RESET DIN 41612
=================================================================================================== backplane
| | | | |
+----+--------+ +----------+----------+ +---------+--------+ +--------+---------+ +--------+--------+
| INDEX REGS | | ALU V3.2 | | MEMORY v1.3 | | I/O V1.1 | | BUS TESTER v1.1 |
| card 0: R0-3| | ACC (74LS374) | | 2 x 62256 RAM | | XR16C550 UART | | ATmega328 + |
| card 1: R4-7| | 2 x 74LS283 adder | | 28C64 EEPROM | | switches/LEDs | | 6 x MCP23017 |
| 74LS192 x16 | | carry FF, 74LS194 | | FORCE-ROM remap | | LCD, TIL311 x2 | | (bring-up only) |
| per card | | 74LS85 compare, | | TMP0/TMP1 | | P0 = control | +-----------------+
| drive ADDR | | 74LS251 BR-COND | | (4 x 74LS374) | | P1 = data |
+-------------+ +---------------------+ +------------------+ +------------------+ +-----------------+
| VIDEO V1.0 |
| IDT7134 + 6845 |
| (no 6845 fitted)|
+-----------------+
Cards that exist but are not on the bus: Mem Switch 1.1 and Mem Register 1.0 (the bring-up ROM/RAM pair, refitted for
the 2026-09-21 CPU bring-up and normally out), the two bus-jumper boards (obsolete), the retired Address+TMP card
(replaced by the index registers in 2020), the protocard (docs/system/MACHINE.md, hardware/FABRICATED.md).
Two things the diagram makes visible that are easy to miss:
- There is no clock on the bus. The only clock in the machine is on the sequencer-logic card (section 11). Every
other card is purely combinational plus latches clocked by the strobes it receives, so the timing rules of the whole
machine are the rules of those strobes (
BUS.mdsection 5). - The sequencer-memory card is not a bus card. It talks only to the logic card over the SV1/SV2 ribbon, and the
logic card's
-BUS-ENoutput is the inverse of the memory card's READY line (docs/isa/MICROCODE-REVIEW-NOTES.md1.1), so the bus is released until the ATmega has copied and verified the control store (about 54 s after reset,embedded/sequencer-card/README.md).
3. The datapath: ACC, TMP, the ALU card¶
The ALU card (ALU V3.2, hardware/cards/alu/eagle/v3.2/ALU V3.2.sch, netlist model in
docs/isa/MICROCODE-REVIEW-NOTES.md 1.4 and hardware/DESIGN-REVIEW-NOTES-datapath.md "ALU v3.2") holds the
accumulator and every arithmetic element. Its internal bus is BDATA0..15; the accumulator's output is ACO;
the selected function's result is INV-IN0..7; the accumulator's input is ACI-DATA.
3.1 The bus side¶
Two 74LS245 transceivers (IC10 for DATA0..7, IC11 for DATA8..15) join BDATA to the backplane. They are enabled by
-ALU-FUNC OR -BUS-EN (IC7) and their direction is set by -AC-RD: with -AC-RD asserted the card drives the bus,
otherwise it receives. The accumulator reaches BDATA0..7 through IC12 (74LS244, enabled by -AC-RD). BDATA8..15 has
nothing on it but the 10 k pull-ups RN2 and the 16-bit zero detector V1. Consequently (this is the fact that decides
several findings):
Whenever
-ALU-FUNCand-AC-RDare both asserted, the ALU card drives DATA0..7 = ACC and DATA8..15 = $FF. Without-AC-RDthe card receives, and the value on the bus (memory, TMP, a register, a port) is what the function blocks see as their B operand. (y1ucemu.ccompute(): "the ALU: AC on DATA0..7, pull-ups on DATA8..15".)
3.2 The function blocks¶
A 74LS138 (IC8) decodes ALU0..2 into one enabled 74LS244 that puts a result onto INV-IN
(docs/isa/MICROCODE-REVIEW-NOTES.md 1.4; the codes are the ALU* constants in CodeGen.h):
| ALU2..0 | Function | Result on INV-IN | Chip |
|---|---|---|---|
| 0 | DATA | BDATA0..7 (pass the operand through) | IC13 |
| 1 | SUB | ACC + NOT(BDATA) + carry-in | IC37 (adder sum), XOR array IC33/IC34 inverts BDATA |
| 2 | AND | ACC AND BDATA | IC20 |
| 3 | OR | ACC OR BDATA | IC23 |
| 4 | XOR | ACC XOR BDATA | IC17 |
| 5 | SHIFT | the 74LS194 shift register's outputs | IC31 |
| 6 | ZERO | 0 (inputs grounded) | IC16 |
| 7 | ADD | ACC + BDATA + carry-in | IC37 |
The adder is a pair of 74LS283 4-bit adders, IC35 (low nibble, carry-in C0 on pin 7) and IC36 (high nibble)
(ALU V3.2.sch parts list; docs/datasheets/7483.pdf is the closest datasheet in the tree). ALU3 is the carry
select: with the ADD or SUB code, ALU3 = 1 feeds the carry flip-flop into C0 ((ALU3 AND C/SHIFT) XOR SUB,
datapath review "Carry-in IC35 C0"). That is how ADDIC/ADDTC (ALUADD | CARRY_SHIFT = code $F in accumulator.c)
differ from ADDI/ADDT (code 7); a plain SUB gets +1 from the XOR (two's complement), a subtract-with-borrow (ALU3 set
with code 1) takes the flag with the right sense. The ALU V3.2 notes record the design change that made this work:
"Flipped AC and BDATA for add/sub circuit: BDATA now goes into xor array, AC directly into adders"
(hardware/cards/alu/eagle/v3.2/Notes.md).
Why an inverting path into the accumulator? ACI-DATA is INV-IN either straight (IC3, 74LS244, enabled when
-AC-LD-INV is inactive) or inverted (IC2, 74LS240, enabled when -AC-LD-INV is asserted). That single bit gives
INVA for free (DATA function with -AC-RD so BDATA = ACC, then load inverted: accumulator.c INVA) and, because the
same bit also inverts BR-COND (section 3.5), it gives every "not" branch (BRNZ, BRNEQ, BRINL, BR16NZ) for free too.
3.3 The accumulator and the carry flip-flop¶
The accumulator is a 74LS374 (IC5) clocked by AC-LD = NOT(-AC-LD) through JP2, so it latches on the leading
(falling) edge of -AC-LD and captures whatever the function blocks produced from the bus of the previous step
(docs/isa/MICROCODE-REVIEW-NOTES.md 1.4, 1.6). The generator honours that by always writing the function code one
step before the strobe (accumulator.c aluOp(): set -ALU-FUNC + code, write a line, set -AC-LD, write, clear, write).
The carry flip-flop is half of a 74LS74 (IC9A). Its D input is CO/BO OR SHIFT-OUT and its clock is
(function is ADD, SUB or SHIFT) AND AC-LD (DESIGN-REVIEW-NOTES-datapath.md "Carry flip-flop IC9A"). Three
consequences for programmers, all reproduced by y1ucemu.c (do_step(), the -AC-LD block):
- Logic operations (AND, OR, XOR, DATA, ZERO) do not touch the carry: the clock is gated off.
CO/BOis the adder's carry-out XOR SUB, gated to add/sub only (IC1 pin 10), so SUB loads the borrow into the same flip-flop (y1ucemu.ccase 1:co_bo = carry_out ^ 1). The instruction-level emulator did not do this (review L-7); the microcode is the truth of the machine.- Every shift (SHL, SHR, RSHL, RSHR, PSHR, CSHL, CSHR) loads the carry with the bit shifted out, because the
SHIFT function's
-AC-LDclocks the flip-flop with SHIFT-OUT as its D input. The ALU notes ask "How to clear carry shift? - ADD INSTRUCTION"; the practical idiom isLDAI 0 / CSHL(BACKLOG.md, C compiler notes).
The flip-flop is cleared by -RESET (IC9 CLR), so the carry is defined only after the reset button (section 8).
3.4 The shift register and SHIFT-OUT¶
Two 74LS194s (IC29, IC30) form an 8-bit shifter clocked by SR-LD = NOT(-SR-LD) (leading edge of -SR-LD). Mode
S1S0 comes from ALU1,ALU0 and the serial input from a 74LS153 (IC28) selected by ALU3,ALU2
(docs/isa/MICROCODE-REVIEW-NOTES.md 1.4; CodeGen.h SHIFT_* constants):
| ALU1..0 | Mode | ALU3..2 | Serial input |
|---|---|---|---|
| 11 | parallel load from ACC (SHIFT_LOAD) |
00 | 0 (SHIFT_ZERO) |
| 01 | shift towards bit 7 (SHIFT_LEFT) |
01 | the opposite end = rotate (SHIFT_RING) |
| 10 | shift towards bit 0 (SHIFT_RIGHT) |
10 | bit 7 = arithmetic right (SHIFT_PROP) |
| 11 | the carry flip-flop (SHIFT_CARRY) |
A shift instruction is therefore three strobes (accumulator.c shiftOp()): -SR-LD with mode 11 (load ACC),
-SR-LD with the shift mode, then -AC-LD with the SHIFT function (code 5) to read the result back. IC9B
(SHIFT-OUT) samples the bit about to leave (SRD7 for left, SRD0 for right, chosen by IC32) at each -SR-LD edge, and
that is what the carry flip-flop takes on the final -AC-LD (y1ucemu.c -SR-LD block, shift_out).
3.5 Compare and the branch condition¶
Two 74LS85 comparators (IC24, IC25, cascaded with the LSB stage seeded pin 2 = GND, pin 3 = VCC, pin 4 = GND) compare
A = BDATA (the operand: TMP0 for BRLT/BREQ/BRGT/BRNEQ) with B = ACC. Two 74HC4078 NORs (V1 on BDATA8..15, V2 on
BDATA0..7) detect zero. A 74LS251 (IC26) selects one condition by ALU2..0 and the result is XORed with AC-LD-INV
(IC27) to make the bus line BR-COND (docs/isa/MICROCODE-REVIEW-NOTES.md 1.4; CodeGen.h compare codes):
| ALU2..0 | Mux input | Condition | Used by |
|---|---|---|---|
| 0 | D0 = VCC | always | BR, BRDEV, JSR/RET/INT loads (ALUBR) |
| 1 | D1 | BDATA < ACC | BRGT (ALUGT) |
| 2 | D2 | BDATA == ACC | BREQ, BRNEQ (inverted) |
| 3 | D3 | BDATA > ACC | BRLT (ALULT) |
| 4 | D4 | BDATA0..7 == 0 | BRZ, BRNZ (with -AC-RD so BDATA = ACC) |
| 5 | D5 | the bus line IN | BRINH, BRINL |
| 6 | D6 | BDATA0..15 == 0 | BR16Z, BR16NZ (cannot work: H-3) |
| 7 | D7 | the carry flip-flop | BRC |
The comparators are unsigned; the compiler notes that a signed compare needs bit 15 flipped first (BACKLOG.md).
The mux's output enable is jumper JP1 (either -ALU-FUNC or GND); with JP1 on -ALU-FUNC the line floats while the ALU
is idle (datapath review A1), harmless only because every record sets up the function one step before BR-TEST.
3.6 TMP0 and TMP1¶
The two 16-bit temporaries do not live on the ALU card but on the memory card (IC26/IC27 = TMP0, IC28/IC29 = TMP1,
74LS374s with D and Q on the same bus lines; docs/isa/MICROCODE-REVIEW-NOTES.md 1.3). They latch on the leading
edge of -TMP-REG-LDn (through inverter IC14) and their reads drive all 16 data lines. TMP0 is the programmer's
TMP (MVAT/MVTA/LDTI/LDT/STT, the T-forms of the ALU ops, the compare branches); TMP1 is microcode scratch (LDIVR, PUSHR).
The memory notes ask "Should tmp registers be moved to ALU - I do not see why" (hardware/cards/memory/eagle/deprecated/v1.3-do-not-use/Notes.md).
When -AC-RD writes ACC into TMP0 (MVAT), TMP0's high byte becomes $FF from the ALU's pull-ups (review section 4).
4. The register file: R0..R7 on two Index Register cards¶
Each Index Register card 1.1 (hardware/cards/register/eagle/v1.1/Index Registers - 1.1.sch; model in
docs/isa/MICROCODE-REVIEW-NOTES.md 1.2 and DESIGN-REVIEW-NOTES-datapath.md "Index Register card 1.1") holds four
16-bit registers. Card 0 is R0..R3, card 1 is R4..R7 (docs/system/MACHINE.md). A register is four cascaded
74LS192 counters (e.g. R0 = IC3..IC6: low byte IC3/IC4, high byte IC5/IC6), so it can be loaded a byte at a time
and counted up or down as a 16-bit whole. The schematic, board and BOM name the counters 74LS192 (BCD decade); the
machine executes binary step and address sequences, so the review concludes 74LS193 (binary) must be what is fitted
(DESIGN-REVIEW-NOTES-control-io.md 1.2). To verify: read the markings on the register-card counters (and the
sequencer's IC33/IC34) and correct the design files.
4.1 What the three register-id fields mean¶
Three 4-bit fields on the bus name registers (BUS.md; main.c setRdId()/setLdId()/setAddrId()):
ADDR-REG-ID0..3(C3–C6): which register drives the address bus. IC38 (74LS139) decodes bits 0–1 into-Rn-ADDRSEL; bits 2–3 select the card through jumper J3. The decode is enabled only while-VMA(and-BUS-EN) are asserted, so the address buffers (74LS244 pairs IC41/42, IC17/18, IC27/28, IC49/50) drive ADDR0..15 only during a-VMAcycle. The generator asserts-VMAin every step (section 8.3), so in practice the selected register is on the address bus all the time.REG-RD-ID0..3(B9–B12): which register is read (or counted). IC33 decodes bits 0–1 into-Rn-RDSEL; bits 2–3 select the card through J1, gated by-REG-FUNC-RDand-BUS-EN(-RDSEL).REG-LD-ID0..3(B13–B16): which register is loaded. Same structure through J2 (-LDSEL).
All three jumper headers on a card must be set to the same card number, because the microcode uses one register
number for all three purposes (datapath review "Both card-select headers"). Only three bits are ever non-zero (eight
registers); the sequencer notes foresaw it: "If design goes down to 8 registers bit 3 of reg rd and ld can be
repurposed" (hardware/cards/sequencer-logic/eagle/v2.1/Notes.md).
4.2 The internal bus ADATA and the three transceivers¶
Reads go through two 74LS244 read buffers per register (R0: IC7 low byte, IC8 high byte) onto the card's 16-bit
internal bus ADATA0..15, which has 10 k pull-ups (RN1/RN2). -REG-RD-LO enables the low-byte buffer onto
ADATA0..7 and -REG-RD-HI the high-byte buffer onto ADATA8..15: the two read strobes select byte lanes of a
16-bit path, not halves of an 8-bit one. Three shared 74LS245s join ADATA to the backplane:
| Transceiver | Path | Enabled when |
|---|---|---|
| IC35 | DATA0..7 ↔ ADATA0..7 | exactly one of RDSEL/LDSEL selects this card, -HL-SWAP off |
| IC36 | DATA8..15 ↔ ADATA8..15 | same |
| IC37 | DATA0..7 ↔ ADATA8..15 (the byte swap) | exactly one of RDSEL/LDSEL selects this card, -HL-SWAP on |
The XNOR that computes "exactly one" is a CD4077 (IC34; the notes say it was chosen as pin-compatible with the 74266,
register/eagle/v1.1/Notes.md), a CMOS part driven by LS levels — review finding R1, MED: its 3.5 V input threshold
is above the guaranteed LS high and it adds ~100 ns to every bus turn-around. Direction is BUS-DIR = NOT(-LDSEL):
bus → card when this card is load-selected, card → bus otherwise.
Two behaviours follow that every microcode author has to know:
-REG-FUNC-RDalone puts the card on the bus. The transceiver enable does not look at the read strobes, so a select with no-REG-RD-LO/HIdrives the pull-up value $FFFF onto DATA0..15 through two LS245s. Every fetch's increment step does this while memory is also driving (review M-1: 327 steps;y1ucemu.ccounts them as "weak pull-up drives", not fights, because the machine demonstrably lives with it).- A same-card move happens on ADATA. When RDSEL and LDSEL both select one card (MOVRR between two registers of the same card) the transceivers close and the copy is internal. The swap path is unavailable then (review R3).
-HL-SWAP is how a byte gets to the other half: MVRHA reads Rn.hi through IC37 onto DATA0..7 for the ALU; MVIW and
MVARH load Rn.hi from DATA0..7; JSR pushes PC.hi by reading it through the swap path (register.c, accumulator.c,
branch.c).
4.3 Loads and counts are trailing-edge; loads of R0 are gated¶
- Load: IC1 pin 8 =
-R0-LDSEL OR -REG-LD-LO→ 74LS192 LOAD (low byte), likewise-REG-LD-HIfor the high byte. The 192's LOAD is asynchronous and level-sensitive: the outputs follow ADATA while LOAD is low and keep the value present at its rising edge. So a register takes the value on ADATA at the end of the strobe step, and the source must stay stable through the whole step (docs/isa/MICROCODE-REVIEW-NOTES.md1.2; the 2020 diagrams indocs/system/waveforms/REG-LD.jsonshow exactly this). - Count: IC1 pin 3 =
-R0-RDSEL OR -REG-UP→ UP; pin 6 with-REG-DN→ DN. A 192 counts on the rising edge of UP (or DN) with the other input high, so the count happens when the strobe ends — or when the register is deselected while the strobe is still low (review R2, MED). The generator'sdecrementReg()writes a select-only step first;incrementReg()does not, and the author's own comment flags it ("might be an issue if current setRdId reg is different",main.c). The review scanned all records and found no case where it bites today. - R0 is special: on the sequencer-logic card the bus lines
-REG-LD-LO/-REG-LD-HIareNOT(N$53 AND LREG-LD-LO/HI)withN$53 = (REG-LD-ID != 0) OR branch-taken(docs/isa/MICROCODE-REVIEW-NOTES.md1.1;y1ucemu.cdo_step(): "N$53: loads of R0 need the branch-taken latch"). A load whose target is register 0 reaches the card only after aBR-TESTthat foundBR-CONDtrue in the same instruction. That is how a not-taken conditional branch leaves the PC alone, and it also means that MVIB R0, MVIW R0, MVARL/MVARH R0, MOVRR ..,R0, POPR R0 and LDR R0 silently do nothing to R0 (review L-6) although the instruction-level emulator performs them.
4.4 The register conventions¶
| Register | Role | Where it is fixed |
|---|---|---|
| R0 | program counter: fetched through, incremented by every fetch, loaded only through the branch path | CodeGen.h #define PC 0; the N$53 gate |
| R1 | stack pointer: JSR/RET/PUSH/POP/INT/IRET address memory through it and count it | CodeGen.h #define SP 1; the monitor sets it to $0EFF (monitor.asm STACK) |
| R2 | the hardware's operand-address register: LDA/STA/LDT/STT load the 16-bit operand into it, then access memory through it; LDR/STR leave it at addr+2 | CodeGen.h #define IR 2; review L-9 |
| R3..R7 | general purpose; the monitor's BIOS passes a pointer in R7 and may clobber R5, R6 (firmware/abi/README.md); y1cc uses R3 as its accumulator (BACKLOG.md) |
Programs must never keep anything in R2: any absolute-addressed load or store overwrites it, and LDR R2 / STR R2
are degenerate (STR R2,addr stores addr at addr; review L-9). With only card 0 fitted, reads of R4..R7 leave the bus
to its pull-ups ($FF) and their loads and counts are lost — the 2026-09-22 bench symptom that y1ucemu -R 1
reproduces (software/ucemu/README.md). Card 1 was fitted that evening (docs/system/MACHINE.md).
Words are big-endian: STR writes the high byte at the address and the low byte at address+1; MVIW's operand is
high byte first; JSR pushes PC.hi then PC.lo at descending addresses (register.c, branch.c; the emulator agrees).
5. The instruction cycle: the two sequencer cards¶
The sequencer is two cards joined by a 2 × 40-pin ribbon (SV1/SV2, MA20-2 headers on both; the control-io review
checked all 80 pins carry the same signal on both cards). Sequencer-memory holds the control store and its loader;
sequencer-logic holds the counters, the instruction register and the pipeline (hardware/cards/sequencer-*/README.md).
The model below is docs/isa/MICROCODE-REVIEW-NOTES.md 1.1 as executed by y1ucemu.c.
5.1 Addressing the control store¶
The control-store address is 14 bits: CADDR6..13 = the opcode in the instruction register (IC8/IC9, two
74LS175, buffered by IC15) and CADDR0..5 = the step (bits 1..6 of the 8-bit step counter IC33→IC34). Eight
62256 RAMs on the memory half, one per control byte, deliver the 64-bit word; a header CADDR14 picks which 16K half
of the RAMs is used (control-io review 2 "CADDR14"). So a record is 256 opcodes × 64 steps × 8 bytes
(CodeGen.h INSTRUCTIONS/LINES_PER_INSTRUCTION/BYTES_PER_LINE) and the word for step k of opcode o sits at
o*512 + k*8 in test.123 (MICROCODE.md).
5.2 One step = two clock periods¶
The gated clock N$4 drives the step counter's UP input. Bit 0 (QA) is CNT-CLK, which clocks the eight pipeline
74LS374s (BUS-LATCH-CLK); bits 1..6 are the step address. The control word for step k is therefore latched when the
counter goes from 2k to 2k+1: each step lasts two clock periods, and the RAMs get one period to deliver the next
word after CADDR changes. Bit 7 is COUNT-FAULT: if a record runs past step 63 the clock stops (IC26→IC31→IC38) and
the machine halts with the last word on the bus (y1ucemu.c reports this as "COUNT-FAULT" and exits 4).
UCODE-COUNT-RESET is ANDed with the clock and ORed with RESET into the counters' asynchronous clear. The step that
carries it therefore lasts one clock period (every strobe in it is half length, review M-7) and step 0 of the next
record lasts three (M-8 explains the ~50 ns race that latches step 0). The emulator counts 2 clocks per step and 1 for
the reset step (y1ucemu.c nclocks).
5.3 The instruction register latches early, so three steps belong to the previous opcode¶
The IR clocks on LD-INS-REG AND RUN at the leading edge of LD-INS-REG, i.e. ~50 ns into the step that asserts
it. The next pipeline latch already reads ROM[new opcode, next step]. So steps 0 and 1 of every record execute
with the previous opcode's record, and the fetched opcode's own record takes over at step 2. It works because the
generator writes the same three-step prologue into every record (main.c startInstruction() +
loadNextInstruction(); since 2026-09-29, docs/system/MICROCODE.md 5.6):
| Step | Signals (docs/isa/steps.txt, any opcode) |
What happens |
|---|---|---|
| 0 | -MEM-RD, ADDR-REG-ID = 0 |
memory at [PC] onto DATA0..7 (three clocks: it follows the one-clock reset step; instruction $00 adds OUT-OFF) |
| 1 | -MEM-RD, LD-INS-REG |
IR ← DATA0..7 (leading edge: the value of step 0); the read holds through the step |
| 2 | -REG-FUNC-RD, -REG-UP |
PC++ (count when -REG-UP rises); memory no longer drives, so no M-1 overlap |
| (3) | release | only in the 55 records whose first body step selects another register or counts |
Until 2026-09-29 the prologue was six steps: 0 idle, 1 -MEM-RD, 2 + LD-INS-REG (the new record took over at step
3), 3 hold, 4 PC++ with -MEM-RD still on (M-1), 5 hold. Steps 3 and 5 held nothing (review L-1).
The IR's D inputs come from IC2 (DATA0..7) while DO-INT is low or from IC1 (all VCC = $FF) while it is high: that is
how an interrupt substitutes opcode $FF (section 9). RESET clears the IR, so record $00 (START) is the reset vector:
its body is just this prologue with OUT-OFF, i.e. "fetch from PC = 0 and go" (main.c startInstruction(0)).
The three-step prologue (review L-1) made every instruction 2-3 steps shorter: 1,434 -> 1,226 steps over the 85
opcodes, ~18 % fewer clocks in compiled code on the microcode emulator; the idle-step pass the same day took it to
1,173 (~3 % more; docs/system/MICROCODE.md 5.7).
5.4 Operand register, two-byte opcodes, branch and interrupt registers¶
- Operand register IC6 (74LS374,
OPERAND-CLK, leading edge) captures the byte after the opcode. With-2-BYTE-OPERAND-SELasserted, IC5 drivesREG-RD-ID0..3 = operand bits 0..3andREG-LD-ID0..3 = operand bits 4..7onto the bus in place of the pipeline's fields (IC4).y1ucemu.ccompute(): "sequencer IC5". That matchesyacc1.def: MOVRR's byte is(dst<<4)|src, PUSHR/JSRUR/BRUR use the low nibble, POPR usesreg<<4. - Branch register IC13 (high byte: D = DATA0..7, Q = DATA8..15, clocked by
BRANCH-LD-HI) and IC21 (low byte,BRANCH-LD-LO), both output-enabled by-BRANCH-RDwhich drives all 16 data lines. Both bytes are loaded from DATA0..7 one at a time, which is why a branch target is fetched as two bytes and why a register can be copied into it through the swap path (BRUR, JSRUR). - Interrupt vector IC3/IC10, loaded by
INT-LD-HI/LO(IADDR), output-enabled by-INT-JMP. - Branch-taken latch
N$71=((BR-TEST AND BR-COND) OR N$71) AND NOT UCODE-COUNT-RESET: level-sensitive, any high on BR-COND while BR-TEST is high sets it, cleared only at the end of the instruction (y1ucemu.ccond_latch). It feeds the N$53 gate of section 4.3. Unconditional loads of the PC (BR, JSR, RET, IRET, INT, BRVR, BRUR) simply assert BR-TEST with ALU code 0 (D0 = VCC).
5.5 Halting and stepping¶
SOFT-HALT (the HALT opcode) clocks IC23B (DO-HALT) at its leading edge and stops the clock in that very step; the
front-panel CONT clears it and the record continues from the same step (docs/isa/MICROCODE-REVIEW-NOTES.md 1.1).
The front panel also has RESET/EXECUTE (an RS latch, section 8), a SS/WAIT switch that selects free-run or single-step
clocking, a STEP-CLK switch and an SS-SEL header that takes the step clock either from the panel or from JP4 pin 2
(EXTERNAL-SINGLESTEP-CLK); JP4 pin 3 exposes UCODE-COUNT-RESET for an external debugger
(Sequencer-Logic-Prod-V2.1l.sch nets; control-io review "Checked, no issue (sequencer logic)").
6. Memory map¶
From hardware/cards/memory/README.md, docs/system/MACHINE.md "Memory card v1.3 settings", firmware/abi/README.md,
firmware/monitor/monitor.asm and docs/system/OS-PLAN.md:
| Range | What | Decided by |
|---|---|---|
| $0000–$7FFF | low RAM (62256 IC1) — always present, cannot be jumpered out (-LO-RAM = NAND(NOT BADDR15, VMA); datapath review M4) |
memory card |
| $0000–$0FFF | the system page: BASIC variables $0100–$02FF; Y1/OS's four handle buffers $0400–$0BFF while the OS runs (2026-09-23); monitor variables $0F00.. (monmode $0F00, continue_addr $0F02, interupt_cnt $0F04, CFLBA0..2 $0F10–$0F12, ARGBUF $0F40–$0F7F, line_buffer $0F80–$0FFF); the hardware stack grows down from $0EFF (R1), informal floor $0C00 | monitor.asm EQUs, firmware/abi/README.md |
| $1000–$1FFF | BASIC's token buffer, also where the O command loads Y1/OS (the two are not used together) |
firmware/abi/README.md |
| $3000.. | where y1cc images load and where G3000 runs them |
docs/system/MACHINE.md |
| $8000–$CFFF | high RAM (62256 IC2), five 4K blocks jumpered "up" | memory card jumper header U$1 (3 × 8) |
| $D000–$DFFF | no jumper: undecoded on the memory card, reserved for the video card, which claims a 2K block there (low 1K display RAM, upper half the 6845; $D400 address register / $D402 data register once RS moves to A1) | video card SV3 = $D000 |
| $E000–$FFFF | ROM (28C64 IC13, 8K), two blocks jumpered "down": BASIC at $E000 (entry points $E000/$E010/$E020..$E060), monitor at $F000, ISR at $FF90, BIOS vectors $FFC0–$FFF8 (4 bytes each, JSR routine / RET) |
monitor.asm, firmware/abi/README.md |
Reading an undecoded block "returns the last value left on the bus" (docs/system/MACHINE.md); the memory card's
DATA pull-down networks RN5/RN6 have no value in the design files (datapath review M8), so To verify: whether RN5/RN6
are fitted and their value (measure A19 to GND with the card out). The 4K-block jumper idea came from the 1.3 notes
("add jumpers so any 4k block can be removed (for memory map IO)", hardware/cards/memory/eagle/deprecated/v1.3-do-not-use/Notes.md).
OS-PLAN.md keeps this map for the disk OS (system page, OS at $1000–$4FFF, TPA above) and lists a variant B that
would move the video block up by a jumper.
7. Reset and the FORCE-ROM boot remap¶
7.1 Reset¶
-RESET (bus C30) is a plain LS04 output on the sequencer-logic card driven by the front-panel latch:
RESET = NOR(FP-RESET, RUN), RUN = NOR(RESET, FP-EXECUTE) (control-io review 1.3, datapath review S1). There is no
RC network and no supervisor anywhere: the machine has no power-on reset, and until the button is pressed the
things -RESET initialises are undefined (S1, MED). Those things are: the IR (IC8/IC9 CLR) and the step counter on
the logic card; all sixteen 74LS192 counters of each register card (CLR via the 4077, RESET active high);
the carry and shift-out flip-flops on the ALU (IC9 CLR); the boot flip-flop on the memory card (IC12A PRE);
the 74LS273 latches and the 16550 on the I/O card (-B-RESET and RESET through the 7406); the 6845's -RES on the
video card. Reset polarity is consistent across the cards (S2). The pipeline itself is not cleared: during reset
the 374s keep whatever word they last held, and only the release of reset produces the pulse (N$32) that latches
address 0 (1.3, MED — with FORCE-ROM active and -MEM-WR a raw -WE on the 28C64 this is a way to write the EEPROM;
the ROM was still byte-identical on 2026-09-18).
7.2 What the first fetch sees¶
After reset R0 = $0000, and the IR = $00 so record $00 (START) runs: -MEM-RD at [PC]. The memory card's boot logic
(IC12A 74LS74 FORCE-ROM, IC11 74LS157) forces BADDR12..15 = 1111 while FORCE-ROM is set, so the block decoder
selects block $F and the EEPROM answers every address: the CPU's first opcode comes from ROM[$F000] although the
address bus says $0000 (hardware/cards/memory/README.md; y1ucemu.c compute(): addr = force_rom ? (a | 0xF000) : a).
The monitor's first instruction is BR eprom to $F003 (monitor.asm "remap eprom from 0x0000 to 0xf000 by initial
access to 0xf003 via BRanch"). That fetch presents an address with ADDR15 high while -VMA and -BUS-EN are asserted,
which clocks D = 0 into IC12A: FORCE-ROM clears and the normal map is in force from then on
(docs/isa/MICROCODE-REVIEW-NOTES.md 1.3 "Boot remap"). A stand-alone image therefore needs the same first branch:
the compiler's --boot stub and tests/assembler/brur/brur.asm (ORG 0F000H / BR 0F003H) do it.
Two review findings live here:
- M1 (HIGH, masked): IC12A's clock is
ADDR15 AND VMA AND BUS-EN. If the address bus floats when-VMAfalls, ADDR15 reads high for the ~40 ns before the register card drives it, and FORCE-ROM clears on the first cycle regardless of the address. The designer met this in 2020 by asserting-VMAin every microcode step (main.cinitCurrentLine():setSignal("-VMA"); // Hack prevent ROM mapping from triggering), so the address bus never floats while the machine runs — at the price of removing the-VMAqualification from every chip select on the card (DESIGN-REVIEW-NOTES-datapath.mdM1). - M2 (MED): the 28C64's
-WEis-MEM-WRre-buffered, with no write-protect. During FORCE-ROM every address selects the ROM block, so a store before the first jump above $8000 would write the EEPROM. The shipped monitor is safe by construction (branch first); nothing in hardware enforces it. Status 2026-09-23: open (DOC-PLAN.mdrule 7).
8. Interrupts¶
What exists (docs/isa/MICROCODE-REVIEW-NOTES.md 1.1 "Interrupts", control-io review 1.6, branch.c):
| Piece | Where | Behaviour |
|---|---|---|
-INT (bus C13) |
driven by the I/O card only: 7406 open collector IC8 pin 12 with RN2 pull-up, from the 16550's INT pin through the INT0 jumper (control-io review "Checked, no issue (IO)"); the logic card can add a 10 k pull-up via its -INT-PULLUP header (R5) |
the one interrupt source in the machine |
| JP3 on the logic card | 1 × 5 header: pin 3 = -INT, pin 2 = INT-EDGE (IC23A CLK), pin 4 = INT-LEVEL (→ IC36 → IC23A PRE), pins 1/5 = GND |
selects edge or level mode; needs two jumpers (the unused input to its GND neighbour) or the LS74 preset/clock floats; in edge mode the 74LS74 clocks on the rising edge, i.e. on the release of -INT (1.6, LOW) |
| pending flip-flop IC23A, enable latch IC22 | logic card | INT-EN sets the enable NOR latch; INT-START or RESET clear it and clear the pending flip-flop; DO-INT = pending AND enabled |
| opcode substitution | IC1/IC2 into the IR | at the next LD-INS-REG edge the IR loads $FF instead of the fetched byte (y1ucemu.c: ir = do_int ? 0xFF : data) |
-INTA (bus C14) |
pipelined from IC17 1Q | never asserted by any record (review L-8) |
| record $FF INT (30 steps) | branch.c "Interrupt handler" |
INT-START (disable + acknowledge), PC−− (undo the prologue's increment), push PC.hi then PC.lo exactly like JSR, PC ← vector via -INT-JMP + BR-TEST/ALU 0 |
| IADDR addr ($FE, 16 steps) | branch.c |
INT vector ← hi (INT-LD-HI), lo (INT-LD-LO), PC += 2 |
| IRET ($FD, 22 steps) | branch.c |
RET, then INT-EN |
| INTE ($FB) / INTD ($FC) | branch.c |
one-step INT-EN / INT-START pulses. INTD therefore also clears a pending interrupt (lost, not deferred: M-6) |
The monitor arms it at boot: iaddr isrcode / INTE before entering the command loop; the ISR at $FF90 pushes ACC and R7,
blinks the OUT LED interupt_cnt (= 5) times, restores and IRETs (firmware/monitor/monitor.asm lines 127–128, 1272–1287).
BASIC uses no interrupt instruction (review section 4). The 16550's interrupt enable is never written by the monitor as
far as the setup at $F003 shows (only LCR/DLL/DLM), so To verify: whether an interrupt has ever been taken on the
hardware, how JP3 and the IO card's INT0 jumper are populated, and whether the 16550 IER is ever set. Review M-6 adds
that the entry path has no synchroniser: DO-INT is asynchronous to the clock and selects the IR's D inputs, so an
interrupt arriving in the set-up window of LD-INS-REG can load a mixed opcode. The microcode emulator models the
enable/pending latches and the records but has no source that raises -INT (software/ucemu/README.md "Not modelled").
9. Input/output¶
9.1 The port mechanism¶
The bus carries a static 4-bit port number IOADDR0..3 (C7–C10) and two strobes -IO-RD (B25) and -IO-WR (B26);
a third line -IO-ADDR-LD (C11) was meant to latch the port number but reaches nothing on the I/O card v1.1
(docs/isa/MICROCODE-REVIEW-NOTES.md 1.5, L-2; the generator still asserts it in OUTA/OUTI/INP). The generator writes
the port number into every step of an I/O record (io.c setIo()), so decode is combinational.
On the I/O card (hardware/cards/io/eagle/v1.1/IO V1.1.sch; control-io review 3):
- IC5 (74LS138) decodes
IO-ADDR0..2into-IO-SEL0..7; theIO-ADDR-HL2 × 3 header decides whetherIO-ADDR3must be low or high, i.e. whether the card answers P0–P7 or P8–PF (it is in the low half,OS-PLAN.md). - Two 2 × 8 jumper blocks,
IO-ADDRandDATA-ADDR, pick which-IO-SELnbecomes-IO-ADDRSEL(the control port) and which becomes-IO-DATASEL(the data port). In the machine they are P0 and P1 (firmware/abi/README.md). To verify: the jumper positions on the board (the schematic only shows the alternatives). - The strobes pass through open-collector 7406 inverters (IC8) with pull-ups RN2 (value blank in the design) to become
IO-RD/IO-WR; every latch clocks on the trailing edge of-IO-WRand every reader drives the bus while-IO-RDis low. The slow OC rising edge is the slowest path in the machine (review M-3 for INP; 3.2 for a possible double LCD write).
9.2 The P0 control latch / P1 data convention¶
P0 is a 74LS273 (IC10) whose bits select the device that P1 talks to (monitor.asm equates; firmware/abi/README.md;
control-io review 3.3):
| P0 bit | Name | Selects on P1 |
|---|---|---|
| $01 | SWITCHLED | the 8 toggle switches on read (IC3, 74LS244) / the 8 LEDs on write (IC4, 74LS273) |
| $02 | LCDENABLE | the HD44780 LCD E line (write only: R/W is grounded) |
| $04 | LCDREGISTER | LCD register select |
| $08, $10, $20 | UARTA0..A2 | the 16550 register number (register n is selected by writing n × 8) |
| $40 | UARTCS | the 16550's chip select (CS1; CS0 is -IO-DATASEL) |
| $80 | TIL311 | the two TIL311 hex displays (IC9 latch) |
So every UART access is two instructions: OUTI P0,(UARTAn!UARTCS) then OUTI/OUTA/INP P1. The monitor's boot
sequence (monitor.asm "SERIAL OUT SETUP") writes LCR = $80 (DLAB), DLL = 3, DLM = 0, LCR = $03. The select bits have
no hardware exclusivity: SWITCHLED and UARTCS set together would make IC3 and the 16550 both drive DATA0..7 on a P1
read (3.3) — a software rule. The emulator implements exactly this latch (y1ucemu.c io_read()/io_write():
ctl & 0x40 → UART register (ctl >> 3) & 7; ctl & 0x01 → switches/LEDs; ctl & 0x80 → TIL311 with -L).
9.3 Port map¶
| Port | Device (firmware/abi/README.md, docs/system/OS-PLAN.md) |
|---|---|
| P0 | I/O card control latch |
| P1 | I/O card data port for the device selected in P0 |
| P2 | nothing on the hardware (-IO-SEL2 reaches only the header); the instruction-level emulator's console, and y1ucemu keeps it as a second console so OUTA P2 programs print |
| P3–P7 | -IO-SEL3..7 on the I/O card's header, unused; reserved to that card |
| P8 | planned: CompactFlash register-select latch (ATA register 0–7 in bits 0–2, bit 3 = CF reset) |
| P9 | planned: CompactFlash data (the ROM driver, O command and both emulators already use P8/P9; the hardware is not built: the CF card v1.0 circuit, planned 2026-09-24 onto the memory card, docs/cards/cf.md) |
| PA, PB | planned: 6845 address/data on a video card v2 |
| PC–PF | free (PS/2, RTC ... in the plan) |
9.4 OUT and IN¶
Two more bus lines belong to I/O but bypass the port mechanism. OUT (C27) is an SR latch on the sequencer-logic card
(IC22) set by the ON opcode's OUT-ON and cleared by OFF's OUT-OFF (io.c); it lights the OUT LED on the I/O card
and on the logic card, and record $00 clears it at reset. IN (C26) is driven only by the IN0 toggle switch on the I/O
card through the INPUT0 jumper and read by the ALU's condition mux (D5), so BRINH/BRINL branch on a front-panel
switch; the monitor uses it at boot ("if INPUT high start the monitor", else run the test code, monitor.asm).
y1ucemu -i 0|1 sets the line and -I N flips it (software/ucemu/README.md).
10. The console¶
The console is the 16550-class UART (XR16C550, IC1 on the I/O card) behind P0/P1, with a MAX232 (IC2) and a DB9 whose TX/RX can be crossed by a jumper (I/O notes: "add null modem logic to route tx/rx"). The monitor programs divisor 3, which is 38400 baud only if the UART clock is 1.8432 MHz; the oscillator Y1's Eagle value "ECS-2100AX-200" is a package name, not a frequency (control-io review 3.1). To verify: read the oscillator can and record the frequency in the BOM.
Console I/O goes through the BIOS vectors CHAROUT ($FFC4), UARTIN ($FFE8, which waits and echoes the byte, turning
CR into LF) and CONST ($FFF8) (firmware/abi/README.md). Inside them BRDEV selects the UART on the machine and port 2
on the instruction-level emulator; on the hardware BRDEV is microcoded as an unconditional branch (branch.c:
branch(PC, ALUBR, ...)), and the microcode emulator takes the same path, so its console is the UART model on
stdin/stdout (y1ucemu.c header). Loading a program over the console uses the monitor's E command (no hex loader
exists yet; BACKLOG.md "Monitor loader").
11. The clock¶
The machine's clock is on the sequencer-logic card. QG1 is a 14-pin oscillator socket (Eagle part "XO- 14", value
blank; Sequencer-Logic-Prod-V2.1l.sch). Its output goes to IC38 gate C, ANDed with FREE-RUN, ORed (IC26 B) with the
single-step path into UCODE-CLK, ANDed with RUN (IC38 B) and with NOT(COUNT-FAULT OR DO-HALT) to make N$4, the
step counter's UP input (docs/isa/MICROCODE-REVIEW-NOTES.md 1.1; control-io review 1.7). The front-panel SS/WAIT switch
chooses between free-run and single step; the single-step clock is the STEP-CLK switch or the external clock on JP4.
What frequency the machine runs at is not in the tree. Bring-up in 2026-09 used a function-generator TTL clock in the
oscillator socket (pin 8 clock, pin 7 GND) (docs/system/MACHINE.md, 2026-09-21), and tests/assembler/romcount
counted overnight on it; that README quotes "about 0.6 s per count at 1 MHz" as an example, not a measurement. The
review's timing budget (M-2) says the one-step memory windows before the leading-edge latches (LDTI, LDIVR, LDT, the
BRANCH-LD and INT-LD steps) are the first thing to fail, at roughly 6 MHz with the 28C64 in the path.
To verify: the oscillator fitted (if any) and the frequency used on the bench.
12. How a program runs: LDAI then OUTA at the signal level¶
Take the two-instruction fragment LDAI $41 / OUTA P1 — put $41 in the accumulator, then write it to port 1
(with P0 already holding UARTCS|UARTA0, that sends 'A' to the UART). Step lists are from docs/isa/steps.txt; the
diagrams docs/isa/LDAI.svg and docs/isa/OUTA.svg draw the same records (with the timing-model caveats in
MICROCODE.md section 7). "Edge" notes use the latch table of docs/isa/MICROCODE-REVIEW-NOTES.md 1.6.
LDAI ($0E, 12 steps, accumulator.c "load accumulator immediate")¶
| Step | Word (besides -VMA, ADDR-REG-ID = 0) |
On the bus / in the latches |
|---|---|---|
| 0 | — | Executed from the previous record. R0 (= PC) is on ADDR0..15 because -VMA is asserted and ADDR-REG-ID = 0. |
| 1 | -MEM-RD |
Memory drives M[PC] = $0E onto DATA0..7 (the memory card's LS245 IC5 turns toward the bus). |
| 2 | -MEM-RD, LD-INS-REG |
Leading edge: IR ← $0E. CADDR6..13 now say "LDAI"; the RAMs start reading its record. |
| 3 | -MEM-RD |
Hold. From here the words come from record $0E. |
| 4 | -REG-FUNC-RD, -REG-UP, -MEM-RD |
Card 0 is read-selected with no read strobe: it drives $FFFF against memory (M-1). At the trailing edge the OR of select and -REG-UP rises: R0 counts to PC+1. The address bus follows within ~40 ns. |
| 5 | -MEM-RD |
Memory now drives M[PC+1] = $41. |
| 6 | -MEM-RD, -ALU-FUNC, ALU = 0 |
The ALU's transceivers open in receive (-AC-RD off). Function DATA: INV-IN = $41, ACI-DATA = $41 (not inverted). |
| 7 | -MEM-RD, -ALU-FUNC, -AC-LD |
Leading edge: ACC ← $41 (the step-6 value). The carry flip-flop's clock is gated off (function 0), so the carry is unchanged. |
| 8 | -MEM-RD, -ALU-FUNC |
Release step (the "set-up, strobe, release" pattern of aluOp()). |
| 9 | -REG-FUNC-RD, -REG-UP, -MEM-RD, -ALU-FUNC |
PC++ again (to PC+2) at the trailing edge; M-1 weak drive again. |
| 10 | -MEM-RD, -ALU-FUNC |
Hold. |
| 11 | -MEM-RD, -ALU-FUNC, UCODE-COUNT-RESET |
One clock long: the counter clears; step 0 of the next record (with -MEM-RD off) is latched from the ~50 ns pulse. |
The emulator's count for this record is 12 steps and 23 clocks (11 × 2 + 1); the hardware's step 0 is three clocks long rather than two (M-8). Note how nothing depends on the ALU "finishing": the function blocks are combinational, and the whole set-up is one step (two clock periods) before the latch.
OUTA P1 ($61, 12 steps, io.c "OUT ACCUM")¶
(As generated with the six-step prologue; since 2026-09-29 the prologue is steps 0-2 and the body below starts at step 3: 9 steps.)
| Step | Word (besides -VMA, ADDR-REG-ID = 0) |
On the bus / in the latches |
|---|---|---|
| 0–5 | the common prologue | Fetch $61 (the IR takes it at step 2), PC++ at step 4. |
| 6 | -ALU-FUNC, -AC-RD, IOADDR = 1 |
The ALU drives DATA0..7 = $41 and DATA8..15 = $FF. The port decoder sees port 1 = -IO-DATASEL. -MEM-RD is off, so nothing else drives. |
| 7 | + -IO-ADDR-LD |
No consumer on the card (L-2); a wasted step. |
| 8 | -ALU-FUNC, -AC-RD |
Hold. |
| 9 | + -IO-WR |
-IO-WR low → 7406 → IO-WR high (the slow OC edge does not matter for a write). The 16550's IOW is high with CS0 (-IO-DATASEL) and CS1 (UARTCS from P0) active. At the trailing edge of -IO-WR (end of step 9) the UART takes THR = $41 (a 74LS273 latch on the same bus, e.g. the LEDs, would clock here too). |
| 10 | -ALU-FUNC, -AC-RD |
Hold: the data source stays valid one step after the strobe. |
| 11 | + UCODE-COUNT-RESET |
End (one clock). |
y1ucemu.c performs the port write exactly once, at the step where -IO-WR is asserted and the next step's word does
not assert it (do_step(): "the I/O card's latches clock on the trailing edge, once per assertion"), and it samples a
port read once at the leading edge of -IO-RD and holds it (io_rd_hold). The review's speed table (section 5 of the
notes) says OUTA could be 7 steps instead of 12; LDAI 8 instead of 12.
13. Findings that shape the machine, and their status on 2026-09-23¶
Per DOC-PLAN.md rule 7 (docs/isa/MICROCODE-REVIEW-NOTES.md, hardware/DESIGN-REVIEW-NOTES-*.md, BACKLOG.md,
docs/system/MACHINE.md):
| ID | One line | Status |
|---|---|---|
| H-1 | PUSHR wrote both stack bytes during a data-bus fight (register card still driving) | fixed in branch.c 2026-09-22, loaded into the EEPROM the same evening; bench check pending |
| H-2 | BRZ/BRNZ/BR16Z/BR16NZ kept -AC-RD on while the PC loaded from the branch register (taken BRZ landed on offset $00) |
fixed in branch.c 2026-09-22, loaded; bench check pending |
| H-3 | BR16Z/BR16NZ cannot work: BDATA8..15 are pull-ups under -AC-RD |
open (nothing uses them) |
| H-4 | 37 opcodes have all-zero records; an all-zero word asserts every active-low line for 61 steps ($80–$8F, $A5, $AE, $C0–$CF, $F8–$FA; $AD is now BRUR) | open |
| H-5 | sequencer IC11 gate B appears to drive ADDR-REG-ID0..3 low permanently in the netlist | To verify on the board (scope C3 during a PUSH) — the 2026-09-22/23 romcount run used only PC-relative addressing plus DECR/BRNZ, so it does not settle it |
| M1 | FORCE-ROM clock races the address drivers; masked by -VMA in every step |
fix designed 2026-09-30: 470 Ω pull-down on ADDR15 (v2.0 design; v1.3 bodge not fitted, docs/cards/memory.md 4.1); masked until then |
| M2 | 28C64 -WE = raw -MEM-WR; a store during FORCE-ROM writes the EEPROM |
open |
| S1 | no power-on reset | open |
| 1.1/4.1 | 17 bus lines driven regardless of -BUS-EN; the bus tester drives everything push-pull at boot |
open (v2.2 "CPU off" switch in BACKLOG) |
| 1.2 | counters drawn as 74LS192 (BCD), machine needs binary | To verify chip markings |
| video | 6845 data register unreachable (RS = A0); 7416 outputs without pull-ups; E-clock one-shot | open, RS-to-A1 fix documented in hardware/cards/video/docs/fix-6845-register-select.md |
| ROM | the 2021 monitor's G was BRVR R7 (indirect); rebuilt as JSRUR R7 in firmware/rom/shipped/rom |
resolved 2026-09-23 (ROM 2026-09-23 burned) |
14. The two emulators, briefly¶
software/emulator interprets instructions (it knows what ADDI does) and is the quick one; it differs from the
hardware where the microcode does something else (BRDEV never branches there, carry semantics of SUB and shifts, R0-load suppression: review L-7). software/ucemu/y1ucemu loads
test.hex and steps the control words through the model described in this document; it is the one that reproduced
H-1 and H-2, runs the monitor from reset and counts bus fights (software/ucemu/README.md). When the two disagree,
the microcode emulator is the closer approximation of the machine, and the bench is the truth.