The instruction set, all 174 of them

/ DINO / updated 2026-09-10 / from dino-homebrew isa microcode reference

I’ve never written the instruction set down in one place. It lives in microcode_gen.py as a table of control-word rows, and the assembler and the oracle both read it from there, so the ISA has been whatever that file said. This entry is the readable version. The opcodes, byte lengths and T-state counts below are dumped from that table, so if this page and the machine disagree the page is stale.

The shape of it

8-bit data, 16-bit addresses. Three registers: A is the accumulator and the ALU’s first operand, B is the ALU’s second operand, C is scratch. B:C doubles as an index pair with C as the low half. There’s a 16-bit stack pointer, a program counter, and an 8-LED output latch called OB, which is the only way anything gets out of the machine besides the serial card.

Every instruction is a list of microcode rows in a 24-bit control word across three AT28C64Bs (U9, U15, U23). T0 is the fetch every instruction shares, then the rows run until one carries END. So a 2 T-state instruction is fetch plus one row.

ISA size: 174 opcodes, 82 free. The high nibble of the opcode is its family, which is why I can read a byte off the bus and know roughly what it is.

How to read a row

Flags update on any instruction that uses the ALU and hold across everything else. Loads, moves, stores and stack traffic don’t touch them. A Z set by AND still stands at a JNZ two instructions later.

The settle pads are on 23 instructions, added 2026-08-27 for a fault that turned out to be the ground return. Each one costs a T-state. Pulling them and re-running the soak is phase H work.

Addressing modes

ModeSyntaxCountHow it works
ImpliedCLR85Operands named by the opcode.
ImmediateLDAI n25One operand byte after the opcode.
AbsoluteLDA addr25Two operand bytes, low first, loaded into MAR.
IndexedLDAX20Address is B:C. C is the low half. No carry from C into B.
SP-relativeLDAS13Address is SP. Can’t share the stack with CALLs while doing this.
Memory-indirectLDAM ptr6The pointer lives in memory. Operand names where the pointer is. Clobbers C, B survives.

Memory-indirect instructions are five bytes because the assembler emits the address twice, ptr and ptr+1. MAR loads only from the bus and has no increment, so the microcode reads the low byte through C, re-points MAR at the high byte, parks it in MDR and replays it.

Rules you find out the hard way

0x0x Control

HexMnemonicBTOperationFlagsNotes
00NOP12nothinghold0x00 is what blank RAM reads, so a PC that runs into it slides instead of stopping. First ran 2026-09-08.
01RST12PC <- 0x0000holdunrun. Clears the PC and nothing else.

0x1x Immediate loads

HexMnemonicBTOperationFlagsNotes
11LDAI n22A <- nhold
12LDBI n22B <- nholdThe 0xCx forms do LDBI plus ALU in one instruction.
13LDCI n22C <- nholdDoesn’t survive a CALL.
14LXISP addr33SP <- addrholdRequired before any stack access.
15LXIL n22SP low <- nhold
16LXIH n22SP high <- nhold

0x2x Memory, absolute and SP-relative

A peripheral card is memory. The I/O window is 0x4000-0x7FFF in 2K slots, so LDA 0x4000 reads the DIP switch.

HexMnemonicBTOperationFlagsNotes
21LDA addr34A <- [addr]hold
22STA addr34[addr] <- Ahold
23LDB addr34B <- [addr]hold
24LDC addr34C <- [addr]hold
25STB addr34[addr] <- Bhold
26STC addr34[addr] <- Chold
27LDAS14A <- [SP]holdRead a stack slot without popping.
28STAS14[SP] <- Ahold
29LDBS14B <- [SP]hold
2ASTBS14[SP] <- Bhold
2BLDCS14C <- [SP]hold
2CSTCS14[SP] <- Chold

0x3x Flow

HexMnemonicBTOperationFlagsNotes
31JMP addr34PC <- addrhold
32JNZ addr34jump if Z = 0hold
33CALL addr312push return, PC <- addrholdPushes PC+1, the address of its own operand bytes. RET steps over them.
34RET114pop PC, step over 2 bytesholdclobbers C.
35JMPX14PC <- B:CholdComputed jump.
36JMPSP14PC <- SPhold
37JNC addr34jump if C = 0holdAfter CMP: jump if A < B unsigned.
38JZX14jump to B:C if Z = 0hold
39JCX14jump to B:C if C = 0hold
3AJMPM ptr510PC <- [ptr]holdclobbers C. Vector table in RAM.
3BJZM ptr510jump via [ptr] if Z = 0holdclobbers C.
3CJCM ptr510jump via [ptr] if C = 0holdclobbers C.
3DLDAM ptr510A <- [[ptr]]holdclobbers C.
3ELDBM ptr510B <- [[ptr]]holdclobbers C.
3FSTAM ptr510[[ptr]] <- Aholdclobbers C.

0x4x ALU

Two 74F382s with ripple carry. A and B feed the ALU from shadow latches that reload on every A or B load, so you never load them yourself. SHL, INR, DCR and NOT get to the ALU by staging a constant in B, so all four destroy it.

HexMnemonicBTOperationFlagsNotes
40BIT12A AND B, discardedZNMask test.
41ADD12A <- A + BCZVN
42SUB12A <- A - BCZVN
43AND12A <- A AND BZN
44OR12A <- A OR BZN
45XOR12A <- A XOR BZN
46CLR12A <- 0x00ZNOne byte, shorter than LDAI 0.
47SET12A <- 0xFFZN
48BSUB12A <- B - ACZVN
49CMP12A - B, discardedCZVNFlags update even with no destination.
4ACMPB12B - A, discardedCZVN
4BTST12A OR A, discardedZNZero test on A alone.
4CSHL14A <- A + ACZVNsettle, clobbers B.
4DINR14A <- A + 1CZVNsettle, clobbers B. B staged with 0xFF, A - 0xFF.
4EDCR14A <- A - 1CZVNsettle, clobbers B. A + 0xFF.
4FNOT14A <- NOT AZNsettle, clobbers B. A XOR 0xFF.

0x5x Output

OB latches whatever is on the internal bus. Sourcing it from A was a microcode convention, so anything that can drive the bus can go to the display. Every variant except OUT and OUTI is unrun: the self-test reads OB, so it can’t judge an instruction whose only effect is writing OB.

HexMnemonicBTOperationFlagsNotes
51OUT12OB <- Ahold
52OUTB12OB <- Bholdunrun
53OUTC12OB <- Choldunrun
54OUTSPL12OB <- SP lowholdunrun
55OUTSPH12OB <- SP highholdunrun
56OUTPCL12OB <- PC lowholdunrun
57OUTPCH12OB <- PC highholdunrun
58OUTI n22OB <- nhold
59OUTM addr34OB <- [addr]holdunrun
5AOUTMX14OB <- [B:C]holdunrun
5BOUTMS14OB <- [SP]holdunrun

0x6x Stack and pointers

Empty-descending. SP points at the next free slot. Push stores then decrements, pop increments then loads. Every stack access copies SP into MAR first, which is where the extra two T-states go.

HexMnemonicBTOperationFlagsNotes
60PUSHPCH15push PC highholdunrun
61PUSHA15[SP] <- A, SP–hold
62POPA15SP++, A <- [SP]hold
63PUSHB15[SP] <- B, SP–hold
64POPB15SP++, B <- [SP]hold
65PUSHC15[SP] <- C, SP–hold
66POPC15SP++, C <- [SP]hold
67INXSP12SP <- SP + 1holdThe only 16-bit increment in the machine.
68DCXSP12SP <- SP - 1hold
69SPHL13SP <- B:Chold
6AHLSP13B:C <- SPhold
6BPUSHSPL15push SP lowhold
6CPOPSPL15pop SP lowhold
6DPUSHSPH15push SP highhold
6EPOPSPH15pop SP highhold
6FPUSHPCL15push PC lowhold

0x7x Indexed through B:C

HexMnemonicBTOperationFlagsNotes
71LDAX14A <- [B:C]hold
72STAX14[B:C] <- Ahold
73LDBX14B <- [B:C]holdOverwrites the pointer’s high half.
74STBX14[B:C] <- Bhold
75LDCX14C <- [B:C]holdOverwrites the pointer’s low half. Linked-list walk.
76STCX14[B:C] <- Chold
77MVIX n26[B:C] <- nholdFill through the pointer without touching A.
78STADDX14[B:C] <- A + BCZVN
79STSUBX14[B:C] <- A - BCZVN
7ASTBSUBX14[B:C] <- B - ACZVN
7BSTANDX14[B:C] <- A AND BZN
7CSTORX14[B:C] <- A OR BZN
7DSTXORX14[B:C] <- A XOR BZN

0x8x Register and pointer moves

One row past the fetch. Cheapest instructions in the machine.

HexMnemonicBTOperationFlagsNotes
81MOVAB12A <- Bhold
82MOVAC12A <- CholdC can’t be an ALU operand, so this is how C reaches the ALU.
83MOVBA12B <- Ahold
84MOVBC12B <- Chold
85MOVCA12C <- Ahold
86MOVCB12C <- Bhold
87MOVASPL12A <- SP lowhold
88MOVASPH12A <- SP highhold
89MOVSPLA12SP low <- Ahold
8AMOVSPHA12SP high <- Ahold
8BMOVAPCL12A <- PC lowholdTapped at the counters’ Q outputs.
8CMOVAPCH12A <- PC highholdunrun

0x9x Reserved

Held empty for opcodes that need hardware the machine doesn’t have yet: SHR, MOV A,FLAGS, ADC, SBB, and the branch families that need CW22/CW23 landed.

0xAx Pointer save and restore, immediate stores, computed output

HexMnemonicBTOperationFlagsNotes
A0LDSPL addr34SP low <- [addr]hold
A1LDSPH addr34SP high <- [addr]hold
A2STSPL addr34[addr] <- SP lowhold
A3STSPH addr34[addr] <- SP highhold
A4STPCL addr34[addr] <- PC lowholdunrun
A5STPCH addr34[addr] <- PC highholdunrun
A6MVI addr, n46[addr] <- nholdGot a settle row on 2026-09-05 after PROG_test5 caught it writing a mix of the old byte and the new one.
A7MVIS n26[SP] <- nhold
A8OUTADD12OB <- A + BCZVNunrun
A9OUTSUB12OB <- A - BCZVNunrun
AAOUTBSUB12OB <- B - ACZVNunrun
ABOUTAND12OB <- A AND BZNunrun
ACOUTOR12OB <- A OR BZNunrun
ADOUTXOR12OB <- A XOR BZNunrun

0xBx B and C to and from the pointers

With the ALU these let a routine do arithmetic on SP, or borrow SP as a second index register and hand it back.

HexMnemonicBTOperationFlagsNotes
B0MOVBSPL12B <- SP lowhold
B1MOVBSPH12B <- SP highhold
B2MOVCSPL12C <- SP lowhold
B3MOVCSPH12C <- SP highhold
B4MOVSPLB12SP low <- Bhold
B5MOVSPHB12SP high <- Bhold
B6MOVSPLC12SP low <- Chold
B7MOVSPHC12SP high <- Chold
B8MOVBPCL12B <- PC lowhold
B9MOVBPCH12B <- PC highholdunrun
BAMOVCPCL12C <- PC lowhold
BBMOVCPCH12C <- PC highholdunrun

0xCx ALU with an immediate

Two bytes where LDBI plus the ALU op costs three. The constant comes in through B, so every one of these overwrites B unless B is the destination. Every row here carries a settle pad, which is why they’re 4 T-states and not 3.

HexMnemonicBTOperationFlagsNotes
C0ADI n24A <- A + nCZVNsettle, clobbers B
C1ADI_B n24B <- A + nCZVNsettle
C2ADI_C n24C <- A + nCZVNsettle, clobbers B
C3SUI n24A <- A - nCZVNsettle, clobbers B
C4SUI_B n24B <- A - nCZVNsettle
C5SUI_C n24C <- A - nCZVNsettle, clobbers B
C6BSUI n24A <- n - ACZVNsettle, clobbers B
C7BSUI_B n24B <- n - ACZVNsettle
C8BSUI_C n24C <- n - ACZVNsettle, clobbers B
C9ANI n24A <- A AND nZNsettle, clobbers B
CAANI_B n24B <- A AND nZNsettle
CBANI_C n24C <- A AND nZNsettle, clobbers B
CCORI n24A <- A OR nZNsettle, clobbers B
CDORI_B n24B <- A OR nZNsettle
CEORI_C n24C <- A OR nZNsettle, clobbers B
CFXRI n24A <- A XOR nZNsettle, clobbers B

0xDx Immediate ALU continued, and results into B or C

Where the result lands decides what survives. Into B, it becomes the next op’s second operand. Into C, neither operand is touched.

HexMnemonicBTOperationFlagsNotes
D0XRI_B n24B <- A XOR nZNsettle
D1XRI_C n24C <- A XOR nZNsettle, clobbers B
D2CPI n24A - n, discardedCZVNsettle, clobbers B. CPI n; JNC is compare-and-branch in four bytes.
D3ADD_B12B <- A + BCZVN
D4ADD_C12C <- A + BCZVN
D5SUB_B12B <- A - BCZVN
D6SUB_C12C <- A - BCZVN
D7BSUB_B12B <- B - ACZVN
D8BSUB_C12C <- B - ACZVN
D9AND_B12B <- A AND BZN
DAAND_C12C <- A AND BZN
DBOR_B12B <- A OR BZN
DCOR_C12C <- A OR BZN
DDXOR_B12B <- A XOR BZN
DEXOR_C12C <- A XOR BZN
DFCPX12A XOR B, discardedZNEquality test, both operands intact.

0xEx Compute and store

A MAR load and an ALU row that writes RAM. The result never sits in a register.

HexMnemonicBTOperationFlagsNotes
E0STADD addr34[addr] <- A + BCZVN
E1STSUB addr34[addr] <- A - BCZVN
E2STBSUB addr34[addr] <- B - ACZVN
E3STAND addr34[addr] <- A AND BZN
E4STOR addr34[addr] <- A OR BZN
E5STXOR addr34[addr] <- A XOR BZN
E6STADDS14[SP] <- A + BCZVN
E7STSUBS14[SP] <- A - BCZVN
E8STBSUBS14[SP] <- B - ACZVN
E9STANDS14[SP] <- A AND BZN
EASTORS14[SP] <- A OR BZN
EBSTXORS14[SP] <- A XOR BZN

0xFx Halt

HexMnemonicBTOperationFlagsNotes
FFHALT12T-counter stopshold0xFF so an erased EEPROM halts instead of running. Same byte fills unused microcode slots and parks the bus.

Opcode map

256 slots. 174 assigned, 16 reserved (family 9), 66 free. Free slots are the gaps you can see in the tables above: 0x02-0x10, 0x17-0x20, 0x2D-0x30, 0x50, 0x5C-0x5F, 0x70, 0x7E-0x80, 0x8D-0x9F, 0xAE-0xAF, 0xBC-0xBF, 0xEC-0xFE.

What has actually run

The tables describe what’s burned. What has been witnessed on the hardware is a narrower claim.

Next