# Intel 4004 Opcode Table Fix — Design Document ## 1. Overview The current implementation in [`intel_4004.rs`](src/components/cpu/intel_4004.rs) has a **completely incorrect opcode mapping**. The decoder assigns wrong instructions to nearly every opcode range. This document provides the authoritative reference for rewriting the opcode decoder, instruction enum, and execution logic. ### Current Issues Summary | Opcode Range | Current Mapping | Correct Mapping | |---|---|---| | 0x00-0x0F | LD/XCH by register | NOP + LD R (duplicate of 0xA0-0xAF) | | 0x10-0x1F | ADD/SUB by register | JCN condition, address (2-byte) | | 0x20-0x2F | ADDC/SBC by register | FIM/SRC by register pair | | 0x30-0x3F | JCN condition | FIN/JIN by register pair | | 0x40-0x4F | LDM immediate | JUN address (2-byte) | | 0x50-0x5F | WRM/WMP | JMS address (2-byte) | | 0x60-0x6F | WRR/WPM | INC R | | 0x70-0x7F | Individual I/O instructions | ISZ R, address (2-byte) | | 0x80-0x8F | JUN high nibble | ADD R with carry | | 0x90-0x9F | JUN low nibble | SUB R with borrow | | 0xA0-0xAF | JMS high nibble | LD R (into accumulator) | | 0xB0-0xBF | JMS low nibble | XCH R (exchange with register) | | 0xC0-0xCF | INC R | BBL data (return from subroutine + load ACC) | | 0xD0-0xDF | INC R | LDM data (load immediate into ACC) | | 0xE0-0xEF | Not mapped | I/O and RAM instructions | | 0xF0-0xFC | Duplicates of accumulator ops | Accumulator group instructions | | 0xFD-0xFF | — | Invalid / unused | --- ## 2. Complete Opcode Table (All 256 Entries) ### Legend - **Size**: 1 = one-byte (8 cycles), 2 = two-byte (16 cycles) - **R** = register number 0-15 - **r** = register pair number 0-7 (even register index) ### 2.1 NOP and Machine Instructions | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x00 | NOP | 1 | No operation | ### 2.2 JCN — Jump Conditional (2-byte) | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x10 | JCN 0, addr | 2 | Jump if condition 0 (always false — never jumps) | | 0x11 | JCN 1, addr | 2 | Jump if TEST = 1 | | 0x12 | JCN 2, addr | 2 | Jump if Carry = 1 | | 0x13 | JCN 3, addr | 2 | Jump if Carry=1 OR TEST=1 | | 0x14 | JCN 4, addr | 2 | Jump if ACC = 0 | | 0x15 | JCN 5, addr | 2 | Jump if ACC=0 OR TEST=1 | | 0x16 | JCN 6, addr | 2 | Jump if ACC=0 OR Carry=1 | | 0x17 | JCN 7, addr | 2 | Jump if ACC=0 OR Carry=1 OR TEST=1 | | 0x18 | JCN 8, addr | 2 | Jump unconditional (invert of always false) | | 0x19 | JCN 9, addr | 2 | Jump if TEST = 0 | | 0x1A | JCN A, addr | 2 | Jump if Carry = 0 | | 0x1B | JCN B, addr | 2 | Jump if Carry=0 AND TEST=0 | | 0x1C | JCN C, addr | 2 | Jump if ACC ≠ 0 | | 0x1D | JCN D, addr | 2 | Jump if ACC≠0 AND TEST=0 | | 0x1E | JCN E, addr | 2 | Jump if Carry=0 AND ACC≠0 | | 0x1F | JCN F, addr | 2 | Jump if Carry=0 AND ACC≠0 AND TEST=0 | **Byte 2**: 8-bit address within current 256-byte page. **Full address**: `(PC & 0xF00) | byte2` ### 2.3 FIM / SRC — Register Pair Operations | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x20 | FIM R0, data | 2 | Fetch immediate to R0/R1 | | 0x21 | SRC R0 | 1 | Send register control from R0/R1 | | 0x22 | FIM R2, data | 2 | Fetch immediate to R2/R3 | | 0x23 | SRC R1 | 1 | Send register control from R2/R3 | | 0x24 | FIM R4, data | 2 | Fetch immediate to R4/R5 | | 0x25 | SRC R2 | 1 | Send register control from R4/R5 | | 0x26 | FIM R6, data | 2 | Fetch immediate to R6/R7 | | 0x27 | SRC R3 | 1 | Send register control from R6/R7 | | 0x28 | FIM R8, data | 2 | Fetch immediate to R8/R9 | | 0x29 | SRC R4 | 1 | Send register control from R8/R9 | | 0x2A | FIM R10, data | 2 | Fetch immediate to R10/R11 | | 0x2B | SRC R5 | 1 | Send register control from R10/R11 | | 0x2C | FIM R12, data | 2 | Fetch immediate to R12/R13 | | 0x2D | SRC R6 | 1 | Send register control from R12/R13 | | 0x2E | FIM R14, data | 2 | Fetch immediate to R14/R15 | | 0x2F | SRC R7 | 1 | Send register control from R14/R15 | **FIM Byte 2**: 8-bit data. High nibble → first register (even), Low nibble → second register (odd). ### 2.4 FIN / JIN — Register Pair Indirect Operations | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x30 | FIN R0 | 2 | Fetch indirect to R0/R1 from ROM | | 0x31 | JIN R0 | 1 | Jump indirect to address in R0/R1 | | 0x32 | FIN R2 | 2 | Fetch indirect to R2/R3 from ROM | | 0x33 | JIN R1 | 1 | Jump indirect to address in R2/R3 | | 0x34 | FIN R4 | 2 | Fetch indirect to R4/R5 from ROM | | 0x35 | JIN R2 | 1 | Jump indirect to address in R4/R5 | | 0x36 | FIN R6 | 2 | Fetch indirect to R6/R7 from ROM | | 0x37 | JIN R3 | 1 | Jump indirect to address in R6/R7 | | 0x38 | FIN R8 | 2 | Fetch indirect to R8/R9 from ROM | | 0x39 | JIN R4 | 1 | Jump indirect to address in R8/R9 | | 0x3A | FIN R10 | 2 | Fetch indirect to R10/R11 from ROM | | 0x3B | JIN R5 | 1 | Jump indirect to address in R10/R11 | | 0x3C | FIN R12 | 2 | Fetch indirect to R12/R13 from ROM | | 0x3D | JIN R6 | 1 | Jump indirect to address in R12/R13 | | 0x3E | FIN R14 | 2 | Fetch indirect to R14/R15 from ROM | | 0x3F | JIN R7 | 1 | Jump indirect to address in R14/R15 | **FIN**: Reads ROM at address pointed to by register pair, stores result in register pair. **JIN**: Sets PC to 12-bit address formed by register pair (even reg = high 6 bits, odd reg = low 6 bits). See Section 8. ### 2.5 JUN — Jump Unconditional (2-byte) | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x40 | JUN addr | 2 | Jump to address (high nibble 0) | | 0x41 | JUN addr | 2 | Jump to address (high nibble 1) | | 0x42 | JUN addr | 2 | Jump to address (high nibble 2) | | 0x43 | JUN addr | 2 | Jump to address (high nibble 3) | | 0x44 | JUN addr | 2 | Jump to address (high nibble 4) | | 0x45 | JUN addr | 2 | Jump to address (high nibble 5) | | 0x46 | JUN addr | 2 | Jump to address (high nibble 6) | | 0x47 | JUN addr | 2 | Jump to address (high nibble 7) | | 0x48 | JUN addr | 2 | Jump to address (high nibble 8) | | 0x49 | JUN addr | 2 | Jump to address (high nibble 9) | | 0x4A | JUN addr | 2 | Jump to address (high nibble A) | | 0x4B | JUN addr | 2 | Jump to address (high nibble B) | | 0x4C | JUN addr | 2 | Jump to address (high nibble C) | | 0x4D | JUN addr | 2 | Jump to address (high nibble D) | | 0x4E | JUN addr | 2 | Jump to address (high nibble E) | | 0x4F | JUN addr | 2 | Jump to address (high nibble F) | **Full address**: `((byte1 & 0x0F) << 8) | byte2` ### 2.6 JMS — Jump to Subroutine (2-byte) | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x50 | JMS addr | 2 | Jump to subroutine (high nibble 0) | | 0x51 | JMS addr | 2 | Jump to subroutine (high nibble 1) | | 0x52 | JMS addr | 2 | Jump to subroutine (high nibble 2) | | 0x53 | JMS addr | 2 | Jump to subroutine (high nibble 3) | | 0x54 | JMS addr | 2 | Jump to subroutine (high nibble 4) | | 0x55 | JMS addr | 2 | Jump to subroutine (high nibble 5) | | 0x56 | JMS addr | 2 | Jump to subroutine (high nibble 6) | | 0x57 | JMS addr | 2 | Jump to subroutine (high nibble 7) | | 0x58 | JMS addr | 2 | Jump to subroutine (high nibble 8) | | 0x59 | JMS addr | 2 | Jump to subroutine (high nibble 9) | | 0x5A | JMS addr | 2 | Jump to subroutine (high nibble A) | | 0x5B | JMS addr | 2 | Jump to subroutine (high nibble B) | | 0x5C | JMS addr | 2 | Jump to subroutine (high nibble C) | | 0x5D | JMS addr | 2 | Jump to subroutine (high nibble D) | | 0x5E | JMS addr | 2 | Jump to subroutine (high nibble E) | | 0x5F | JMS addr | 2 | Jump to subroutine (high nibble F) | **Full address**: `((byte1 & 0x0F) << 8) | byte2` Pushes current PC to stack before jumping. ### 2.7 INC — Increment Register | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x60 | INC R0 | 1 | Increment register 0 | | 0x61 | INC R1 | 1 | Increment register 1 | | 0x62 | INC R2 | 1 | Increment register 2 | | 0x63 | INC R3 | 1 | Increment register 3 | | 0x64 | INC R4 | 1 | Increment register 4 | | 0x65 | INC R5 | 1 | Increment register 5 | | 0x66 | INC R6 | 1 | Increment register 6 | | 0x67 | INC R7 | 1 | Increment register 7 | | 0x68 | INC R8 | 1 | Increment register 8 | | 0x69 | INC R9 | 1 | Increment register 9 | | 0x6A | INC R10 | 1 | Increment register 10 | | 0x6B | INC R11 | 1 | Increment register 11 | | 0x6C | INC R12 | 1 | Increment register 12 | | 0x6D | INC R13 | 1 | Increment register 13 | | 0x6E | INC R14 | 1 | Increment register 14 | | 0x6F | INC R15 | 1 | Increment register 15 | Register = `opcode & 0x0F`. Wraps from 15 to 0 (4-bit). ### 2.8 ISZ — Increment and Skip if Not Zero (2-byte) | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x70 | ISZ R0, addr | 2 | Increment R0, jump if not zero | | 0x71 | ISZ R1, addr | 2 | Increment R1, jump if not zero | | 0x72 | ISZ R2, addr | 2 | Increment R2, jump if not zero | | 0x73 | ISZ R3, addr | 2 | Increment R3, jump if not zero | | 0x74 | ISZ R4, addr | 2 | Increment R4, jump if not zero | | 0x75 | ISZ R5, addr | 2 | Increment R5, jump if not zero | | 0x76 | ISZ R6, addr | 2 | Increment R6, jump if not zero | | 0x77 | ISZ R7, addr | 2 | Increment R7, jump if not zero | | 0x78 | ISZ R8, addr | 2 | Increment R8, jump if not zero | | 0x79 | ISZ R9, addr | 2 | Increment R9, jump if not zero | | 0x7A | ISZ R10, addr | 2 | Increment R10, jump if not zero | | 0x7B | ISZ R11, addr | 2 | Increment R11, jump if not zero | | 0x7C | ISZ R12, addr | 2 | Increment R12, jump if not zero | | 0x7D | ISZ R13, addr | 2 | Increment R13, jump if not zero | | 0x7E | ISZ R14, addr | 2 | Increment R14, jump if not zero | | 0x7F | ISZ R15, addr | 2 | Increment R15, jump if not zero | Register = `opcode & 0x0F`. **Byte 2**: 8-bit address within current page. **Full address**: `(PC & 0xF00) | byte2` ### 2.9 ADD — Add Register to Accumulator with Carry | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x80 | ADD R0 | 1 | Add R0 to ACC with carry | | 0x81 | ADD R1 | 1 | Add R1 to ACC with carry | | 0x82 | ADD R2 | 1 | Add R2 to ACC with carry | | 0x83 | ADD R3 | 1 | Add R3 to ACC with carry | | 0x84 | ADD R4 | 1 | Add R4 to ACC with carry | | 0x85 | ADD R5 | 1 | Add R5 to ACC with carry | | 0x86 | ADD R6 | 1 | Add R6 to ACC with carry | | 0x87 | ADD R7 | 1 | Add R7 to ACC with carry | | 0x88 | ADD R8 | 1 | Add R8 to ACC with carry | | 0x89 | ADD R9 | 1 | Add R9 to ACC with carry | | 0x8A | ADD R10 | 1 | Add R10 to ACC with carry | | 0x8B | ADD R11 | 1 | Add R11 to ACC with carry | | 0x8C | ADD R12 | 1 | Add R12 to ACC with carry | | 0x8D | ADD R13 | 1 | Add R13 to ACC with carry | | 0x8E | ADD R14 | 1 | Add R14 to ACC with carry | | 0x8F | ADD R15 | 1 | Add R15 to ACC with carry | `ACC = (ACC + R + Carry) & 0x0F`, `Carry = (ACC + R + Carry) > 0x0F` ### 2.10 SUB — Subtract Register from Accumulator with Borrow | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0x90 | SUB R0 | 1 | Subtract R0 from ACC with borrow | | 0x91 | SUB R1 | 1 | Subtract R1 from ACC with borrow | | 0x92 | SUB R2 | 1 | Subtract R2 from ACC with borrow | | 0x93 | SUB R3 | 1 | Subtract R3 from ACC with borrow | | 0x94 | SUB R4 | 1 | Subtract R4 from ACC with borrow | | 0x95 | SUB R5 | 1 | Subtract R5 from ACC with borrow | | 0x96 | SUB R6 | 1 | Subtract R6 from ACC with borrow | | 0x97 | SUB R7 | 1 | Subtract R7 from ACC with borrow | | 0x98 | SUB R8 | 1 | Subtract R8 from ACC with borrow | | 0x99 | SUB R9 | 1 | Subtract R9 from ACC with borrow | | 0x9A | SUB R10 | 1 | Subtract R10 from ACC with borrow | | 0x9B | SUB R11 | 1 | Subtract R11 from ACC with borrow | | 0x9C | SUB R12 | 1 | Subtract R12 from ACC with borrow | | 0x9D | SUB R13 | 1 | Subtract R13 from ACC with borrow | | 0x9E | SUB R14 | 1 | Subtract R14 from ACC with borrow | | 0x9F | SUB R15 | 1 | Subtract R15 from ACC with borrow | `ACC = (ACC - R - (1 - Carry)) & 0x0F`, `Carry = (ACC - R - (1 - Carry)) >= 0` Note: Carry = 1 means no borrow, Carry = 0 means borrow occurred. ### 2.11 LD — Load Register into Accumulator | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0xA0 | LD R0 | 1 | Load R0 into ACC | | 0xA1 | LD R1 | 1 | Load R1 into ACC | | 0xA2 | LD R2 | 1 | Load R2 into ACC | | 0xA3 | LD R3 | 1 | Load R3 into ACC | | 0xA4 | LD R4 | 1 | Load R4 into ACC | | 0xA5 | LD R5 | 1 | Load R5 into ACC | | 0xA6 | LD R6 | 1 | Load R6 into ACC | | 0xA7 | LD R7 | 1 | Load R7 into ACC | | 0xA8 | LD R8 | 1 | Load R8 into ACC | | 0xA9 | LD R9 | 1 | Load R9 into ACC | | 0xAA | LD R10 | 1 | Load R10 into ACC | | 0xAB | LD R11 | 1 | Load R11 into ACC | | 0xAC | LD R12 | 1 | Load R12 into ACC | | 0xAD | LD R13 | 1 | Load R13 into ACC | | 0xAE | LD R14 | 1 | Load R14 into ACC | | 0xAF | LD R15 | 1 | Load R15 into ACC | `ACC = R[opcode & 0x0F]` ### 2.12 XCH — Exchange Accumulator with Register | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0xB0 | XCH R0 | 1 | Exchange ACC with R0 | | 0xB1 | XCH R1 | 1 | Exchange ACC with R1 | | 0xB2 | XCH R2 | 1 | Exchange ACC with R2 | | 0xB3 | XCH R3 | 1 | Exchange ACC with R3 | | 0xB4 | XCH R4 | 1 | Exchange ACC with R4 | | 0xB5 | XCH R5 | 1 | Exchange ACC with R5 | | 0xB6 | XCH R6 | 1 | Exchange ACC with R6 | | 0xB7 | XCH R7 | 1 | Exchange ACC with R7 | | 0xB8 | XCH R8 | 1 | Exchange ACC with R8 | | 0xB9 | XCH R9 | 1 | Exchange ACC with R9 | | 0xBA | XCH R10 | 1 | Exchange ACC with R10 | | 0xBB | XCH R11 | 1 | Exchange ACC with R11 | | 0xBC | XCH R12 | 1 | Exchange ACC with R12 | | 0xBD | XCH R13 | 1 | Exchange ACC with R13 | | 0xBE | XCH R14 | 1 | Exchange ACC with R14 | | 0xBF | XCH R15 | 1 | Exchange ACC with R15 | Swap `ACC` and `R[opcode & 0x0F]`. ### 2.13 LDM — Load Immediate into Accumulator | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0xD0 | LDM 0 | 1 | Load 0 into ACC | | 0xD1 | LDM 1 | 1 | Load 1 into ACC | | 0xD2 | LDM 2 | 1 | Load 2 into ACC | | 0xD3 | LDM 3 | 1 | Load 3 into ACC | | 0xD4 | LDM 4 | 1 | Load 4 into ACC | | 0xD5 | LDM 5 | 1 | Load 5 into ACC | | 0xD6 | LDM 6 | 1 | Load 6 into ACC | | 0xD7 | LDM 7 | 1 | Load 7 into ACC | | 0xD8 | LDM 8 | 1 | Load 8 into ACC | | 0xD9 | LDM 9 | 1 | Load 9 into ACC | | 0xDA | LDM A | 1 | Load A into ACC | | 0xDB | LDM B | 1 | Load B into ACC | | 0xDC | LDM C | 1 | Load C into ACC | | 0xDD | LDM D | 1 | Load D into ACC | | 0xDE | LDM E | 1 | Load E into ACC | | 0xDF | LDM F | 1 | Load F into ACC | `ACC = opcode & 0x0F` ### 2.14 I/O and RAM Instructions (0xE0-0xEF) | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0xE0 | WRM | 1 | Write ACC to RAM main memory character | | 0xE1 | WMP | 1 | Write ACC to RAM output port | | 0xE2 | WRR | 1 | Write ACC to ROM output port | | 0xE3 | WPM | 1 | Write ACC to program memory (special, for 4008/4009) | | 0xE4 | WR0 | 1 | Write ACC to RAM status character 0 | | 0xE5 | WR1 | 1 | Write ACC to RAM status character 1 | | 0xE6 | WR2 | 1 | Write ACC to RAM status character 2 | | 0xE7 | WR3 | 1 | Write ACC to RAM status character 3 | | 0xE8 | SBM | 1 | Subtract RAM main memory character from ACC with borrow | | 0xE9 | RDM | 1 | Read RAM main memory character to ACC | | 0xEA | RDR | 1 | Read ROM input port to ACC | | 0xEB | ADM | 1 | Add RAM main memory character to ACC with carry | | 0xEC | RD0 | 1 | Read RAM status character 0 | | 0xED | RD1 | 1 | Read RAM status character 1 | | 0xEE | RD2 | 1 | Read RAM status character 2 | | 0xEF | RD3 | 1 | Read RAM status character 3 | ### 2.15 Accumulator Group Instructions (0xF0-0xFC) | Opcode | Mnemonic | Size | Description | |--------|----------|------|-------------| | 0xF0 | CLB | 1 | Clear both — ACC and Carry | | 0xF1 | CLC | 1 | Clear carry | | 0xF2 | IAC | 1 | Increment accumulator | | 0xF3 | CMC | 1 | Complement carry | | 0xF4 | CMA | 1 | Complement accumulator | | 0xF5 | RAL | 1 | Rotate ACC left through carry | | 0xF6 | RAR | 1 | Rotate ACC right through carry | | 0xF7 | TCC | 1 | Transmit carry to ACC and clear ACC | | 0xF8 | TCS | 1 | Transmit carry set — set ACC to 9 if carry, 10 if not | | 0xF9 | STC | 1 | Set carry | | 0xFA | DAA | 1 | Decimal adjust accumulator | | 0xFB | KBP | 1 | Keyboard process | | 0xFC | DCL | 1 | Designate command line | ### 2.16 Invalid/Unused Opcodes | Opcode Range | Status | |--------------|--------| | 0x01-0x0F | **LD R** — duplicate of 0xA0-0xAF. Functionally identical. | | 0xC0-0xCF | **BBL data** — valid one-byte instruction (return from subroutine + load ACC). See Section 10. | | 0xD0-0xDF | **LDM data** — valid (see Section 2.13) | | 0xFD-0xFF | Invalid / unused | > **Note on 0x01-0x0F**: The Intel 4004 datasheet shows LD at both 0x01-0x0F and 0xA0-0xAF. Both should be decoded identically as `LD R` where `R = opcode & 0x0F`. --- ## 3. Correct Rust Instruction Enum ```rust /// Intel 4004 instruction set enumeration /// Organized by opcode range for clarity #[derive(Debug, Clone, Copy, PartialEq)] enum Instruction { // === No operation === Nop, // 0x00 // === Register load (duplicated at 0x01-0x0F and 0xA0-0xAF) === Ld(u8), // LD R: Load register R into ACC (R = 0..15) // === Two-byte instructions === Jcn(u8, u8), // 0x10-0x1F: JCN condition, addr_lo // condition = byte1 & 0x0F // addr_lo = byte2 // full_addr = (PC & 0xF00) | addr_lo Fim(u8, u8), // 0x20,0x22,...,0x2E: FIM Rpair, data // Rpair = (byte1 >> 1) & 0x07 (register pair 0-7) // data = byte2 // R[Rpair*2] = (data >> 4) & 0x0F // R[Rpair*2+1] = data & 0x0F Src(u8), // 0x21,0x23,...,0x2F: SRC Rpair // Rpair = (byte1 >> 1) & 0x07 Fin(u8, u8), // 0x30,0x32,...,0x3E: FIN Rpair, data (data from ROM) // Rpair = (byte1 >> 1) & 0x07 // data = byte2 (read from ROM at address in register pair) Jin(u8), // 0x31,0x33,...,0x3F: JIN Rpair // Rpair = (byte1 >> 1) & 0x07 Jun(u16), // 0x40-0x4F: JUN addr // addr = ((byte1 & 0x0F) << 8) | byte2 Jms(u16), // 0x50-0x5F: JMS addr // addr = ((byte1 & 0x0F) << 8) | byte2 Isz(u8, u8), // 0x70-0x7F: ISZ R, addr_lo // R = byte1 & 0x0F // addr_lo = byte2 // full_addr = (PC & 0xF00) | addr_lo // === One-byte register operations === Inc(u8), // 0x60-0x6F: INC R (R = 0..15) Add(u8), // 0x80-0x8F: ADD R with carry (R = 0..15) Sub(u8), // 0x90-0x9F: SUB R with borrow (R = 0..15) Xch(u8), // 0xB0-0xBF: XCH R (R = 0..15) // === One-byte immediate === Ldm(u8), // 0xD0-0xDF: LDM data (data = 0..15) // === I/O and RAM instructions === Wrm, // 0xE0: Write ACC to RAM main memory Wmp, // 0xE1: Write ACC to RAM output port Wrr, // 0xE2: Write ACC to ROM output port Wpm, // 0xE3: Write ACC to program memory Wr0, // 0xE4: Write ACC to RAM status character 0 Wr1, // 0xE5: Write ACC to RAM status character 1 Wr2, // 0xE6: Write ACC to RAM status character 2 Wr3, // 0xE7: Write ACC to RAM status character 3 Sbm, // 0xE8: Subtract RAM main memory from ACC with borrow Rdm, // 0xE9: Read RAM main memory to ACC Rdr, // 0xEA: Read ROM input port to ACC Adm, // 0xEB: Add RAM main memory to ACC with carry Rd0, // 0xEC: Read RAM status character 0 Rd1, // 0xED: Read RAM status character 1 Rd2, // 0xEE: Read RAM status character 2 Rd3, // 0xEF: Read RAM status character 3 // === Accumulator group === Clb, // 0xF0: Clear both (ACC and Carry) Clc, // 0xF1: Clear carry Iac, // 0xF2: Increment accumulator Cmc, // 0xF3: Complement carry Cma, // 0xF4: Complement accumulator Ral, // 0xF5: Rotate ACC left through carry Rar, // 0xF6: Rotate ACC right through carry Tcc, // 0xF7: Transmit carry and clear ACC Tcs, // 0xF8: Transmit carry set Stc, // 0xF9: Set carry Daa, // 0xFA: Decimal adjust accumulator Kbp, // 0xFB: Keyboard process Dcl, // 0xFC: Designate command line // === BBL (Branch Back and Load) === Bbl(u8), // 0xC0-0xCF: BBL data (return from subroutine + load ACC) // data = opcode & 0x0F // One-byte instruction. See Section 10. // === Invalid === Invalid, // Unused opcodes (0xFD-0xFF) } ``` > **Note on BBL**: BBL is confirmed at opcode range 0xC0-0xCF. It is a one-byte instruction where the 4-bit immediate value is encoded in the low nibble of the opcode itself (similar to LDM). The instruction pops the return address from the stack and loads the accumulator with the immediate value. --- ## 4. Correct `decode_instruction()` Pseudocode ``` function decode_instruction(opcode: u8) -> Instruction: match opcode: // NOP 0x00 => Nop // LD R (duplicate range — same as 0xA0-0xAF) 0x01..=0x0F => Ld(opcode & 0x0F) // JCN — two-byte, condition in low nibble 0x10..=0x1F => Jcn(opcode & 0x0F, 0) // addr filled by byte 2 fetch // FIM (even) / SRC (odd) 0x20..=0x2F => if opcode & 0x01 == 0: Fim((opcode >> 1) & 0x07, 0) // data filled by byte 2 fetch else: Src((opcode >> 1) & 0x07) // FIN (even) / JIN (odd) 0x30..=0x3F => if opcode & 0x01 == 0: Fin((opcode >> 1) & 0x07) // data from ROM filled by byte 2 else: Jin((opcode >> 1) & 0x07) // JUN — two-byte 0x40..=0x4F => Jun(0) // addr filled by byte 2 fetch // JMS — two-byte 0x50..=0x5F => Jms(0) // addr filled by byte 2 fetch // INC R 0x60..=0x6F => Inc(opcode & 0x0F) // ISZ — two-byte 0x70..=0x7F => Isz(opcode & 0x0F, 0) // addr filled by byte 2 fetch // ADD R (with carry) 0x80..=0x8F => Add(opcode & 0x0F) // SUB R (with borrow) 0x90..=0x9F => Sub(opcode & 0x0F) // LD R (primary range) 0xA0..=0xAF => Ld(opcode & 0x0F) // XCH R 0xB0..=0xBF => Xch(opcode & 0x0F) // BBL (if confirmed) or Invalid 0xC0..=0xCF => Bbl(opcode & 0x0F) // OR Invalid if 0xC0-0xCF is truly unused // Invalid 0xC0..=0xCF => Invalid // (use this if BBL is not at this range) // LDM data 0xD0..=0xDF => Ldm(opcode & 0x0F) // I/O and RAM instructions 0xE0 => Wrm 0xE1 => Wmp 0xE2 => Wrr 0xE3 => Wpm 0xE4 => Wr0 0xE5 => Wr1 0xE6 => Wr2 0xE7 => Wr3 0xE8 => Sbm 0xE9 => Rdm 0xEA => Rdr 0xEB => Adm 0xEC => Rd0 0xED => Rd1 0xEE => Rd2 0xEF => Rd3 // Accumulator group 0xF0 => Clb 0xF1 => Clc 0xF2 => Iac 0xF3 => Cmc 0xF4 => Cma 0xF5 => Ral 0xF6 => Rar 0xF7 => Tcc 0xF8 => Tcs 0xF9 => Stc 0xFA => Daa 0xFB => Kbp 0xFC => Dcl // Invalid/unused 0xFD..=0xFF => Invalid _ => Invalid ``` --- ## 5. Correct `execute_instruction()` Pseudocode ``` function execute_instruction(): match current_op: Nop: // No operation, just advance PC PC.inc() Ld(reg): ACC = index_registers[reg] PC.inc() Xch(reg): temp = ACC ACC = index_registers[reg] index_registers[reg] = temp PC.inc() Add(reg): result = ACC + index_registers[reg] + (if carry then 1 else 0) carry = result > 0x0F ACC = result & 0x0F PC.inc() Sub(reg): // Subtract with borrow: ACC = ACC - R - (1 - Carry) borrow = if not carry then 1 else 0 result = ACC - index_registers[reg] - borrow carry = result >= 0 // carry=1 means no borrow ACC = result & 0x0F PC.inc() Inc(reg): index_registers[reg] = (index_registers[reg] + 1) & 0x0F PC.inc() Ldm(data): ACC = data & 0x0F PC.inc() // === Two-byte instructions (operand already fetched) === Jcn(condition, addr_lo): full_addr = (PC.value() & 0xF00) | addr_lo should_jump = evaluate_jcn_condition(condition) if should_jump: PC.set(full_addr) else: PC.inc() // skip past byte 2 (PC was already inc'd past byte 1) Fim(rpair, data): even_reg = rpair * 2 odd_reg = rpair * 2 + 1 index_registers[even_reg] = (data >> 4) & 0x0F index_registers[odd_reg] = data & 0x0F PC.inc() // past byte 2 Src(rpair): // Send register pair contents to address bus for I/O even_reg = rpair * 2 odd_reg = rpair * 2 + 1 // The 8-bit value from the register pair is sent to RAM/ROM // for addressing: high nibble from even reg, low nibble from odd reg // This selects a specific RAM character or ROM port rom_port = index_registers[even_reg] // high nibble selects chip ram_addr_low = index_registers[odd_reg] // low nibble selects word PC.inc() Fin(rpair): // Read ROM at address in register pair, store in register pair even_reg = rpair * 2 odd_reg = rpair * 2 + 1 rom_addr = (index_registers[even_reg] << 4) | index_registers[odd_reg] // Read 8 bits from ROM at rom_addr // (In practice, this reads from the current ROM page) // data = ROM[PC_page_base + rom_addr] -- simplified // index_registers[even_reg] = (data >> 4) & 0x0F // index_registers[odd_reg] = data & 0x0F PC.inc() Jin(rpair): even_reg = rpair * 2 odd_reg = rpair * 2 + 1 addr = (index_registers[even_reg] << 4) | index_registers[odd_reg] // Note: JIN uses 8 bits from register pair as page offset // Full address = (PC & 0xF00) | addr (stays on current page) // OR: Full address = (index_registers[even_reg] as high 6 bits, // index_registers[odd_reg] as low 6 bits) // See Section 8 for detailed discussion PC.set((PC.value() & 0xF00) | addr) // Note: JIN does NOT advance PC normally — it sets PC Jun(addr): PC.set(addr) // PC already past byte 2 from fetch cycle Jms(addr): // Push return address to stack if stack_pointer < 3: stack[stack_pointer] = PC // PC points past the 2-byte instruction stack_pointer += 1 PC.set(addr) Isz(reg, addr_lo): index_registers[reg] = (index_registers[reg] + 1) & 0x0F if index_registers[reg] != 0: full_addr = (PC.value() & 0xF00) | addr_lo PC.set(full_addr) else: PC.inc() // past byte 2 Bbl(data): // Return from subroutine and load ACC if stack_pointer > 0: stack_pointer -= 1 PC = stack[stack_pointer] ACC = data & 0x0F // === I/O and RAM instructions === Wrm: // Write ACC to RAM main memory at current SRC address // ram_write(current_ram_addr, ACC) PC.inc() Wmp: // Write ACC to RAM output port // ram_output(ACC) PC.inc() Wrr: // Write ACC to ROM output port // rom_output(ACC) PC.inc() Wpm: // Write ACC to program memory (special) // rom_write(ACC) PC.inc() Wr0: // Write ACC to RAM status character 0 PC.inc() Wr1: // Write ACC to RAM status character 1 PC.inc() Wr2: // Write ACC to RAM status character 2 PC.inc() Wr3: // Write ACC to RAM status character 3 PC.inc() Sbm: // Subtract RAM main memory from ACC with borrow // ram_data = ram_read(current_ram_addr) // borrow = if not carry then 1 else 0 // result = ACC - ram_data - borrow // carry = result >= 0 // ACC = result & 0x0F PC.inc() Rdm: // Read RAM main memory to ACC // ACC = ram_read(current_ram_addr) PC.inc() Rdr: // Read ROM input port to ACC // ACC = rom_read_input() PC.inc() Adm: // Add RAM main memory to ACC with carry // ram_data = ram_read(current_ram_addr) // result = ACC + ram_data + (if carry then 1 else 0) // carry = result > 0x0F // ACC = result & 0x0F PC.inc() Rd0: // Read RAM status character 0 to ACC // ACC = ram_read_status(0) PC.inc() Rd1: // Read RAM status character 1 to ACC PC.inc() Rd2: // Read RAM status character 2 to ACC PC.inc() Rd3: // Read RAM status character 3 to ACC PC.inc() // === Accumulator group === Clb: ACC = 0 carry = false PC.inc() Clc: carry = false PC.inc() Iac: result = ACC + 1 carry = result > 0x0F ACC = result & 0x0F PC.inc() Cmc: carry = not carry PC.inc() Cma: ACC = (~ACC) & 0x0F PC.inc() Ral: new_carry = (ACC & 0x08) != 0 ACC = ((ACC << 1) | (if carry then 1 else 0)) & 0x0F carry = new_carry PC.inc() Rar: new_carry = (ACC & 0x01) != 0 ACC = (ACC >> 1) | (if carry then 0x08 else 0) carry = new_carry PC.inc() Tcc: // Transmit carry to ACC and clear ACC = if carry then 1 else 0 carry = false PC.inc() Tcs: // Set ACC to 9 if carry was set, 10 if not // Used for BCD subtraction correction ACC = if carry then 9 else 10 carry = false PC.inc() Stc: carry = true PC.inc() Daa: // Decimal adjust accumulator if ACC > 9 or carry: ACC = ACC + 6 if ACC > 0x0F: carry = true ACC = ACC & 0x0F PC.inc() Kbp: // Keyboard process — converts 1-of-N coded key to binary // If ACC = 0: ACC = 0 // If ACC = 1: ACC = 0 // If ACC = 2: ACC = 1 // If ACC = 4: ACC = 2 // If ACC = 8: ACC = 3 // Otherwise: ACC = ACC (error) match ACC: 0 => ACC = 0 1 => ACC = 0 2 => ACC = 1 4 => ACC = 2 8 => ACC = 3 _ => {} // error, leave as is PC.inc() Dcl: // Designate command line — set RAM bank from ACC ram_bank = ACC & 0x07 // 3 bits for bank selection PC.inc() Invalid: // Treat as NOP — advance PC PC.inc() ``` --- ## 6. One-Byte vs Two-Byte Instruction Classification ### One-Byte Instructions (8 clock cycles) | Opcode Range | Instruction | |---|---| | 0x00 | NOP | | 0x01-0x0F | LD R (duplicate) | | 0x21, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D, 0x2F | SRC Rpair | | 0x31, 0x33, 0x35, 0x37, 0x39, 0x3B, 0x3D, 0x3F | JIN Rpair | | 0x60-0x6F | INC R | | 0x80-0x8F | ADD R | | 0x90-0x9F | SUB R | | 0xA0-0xAF | LD R | | 0xB0-0xBF | XCH R | | 0xD0-0xDF | LDM data | | 0xC0-0xCF | BBL data | | 0xE0-0xEF | I/O and RAM instructions | | 0xF0-0xFC | Accumulator group | ### Two-Byte Instructions (16 clock cycles) | Opcode Range | Instruction | Byte 2 Usage | |---|---|---| | 0x10-0x1F | JCN condition, addr | 8-bit page address | | 0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E | FIM Rpair, data | 8-bit immediate data | | 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E | FIN Rpair | (reads from ROM) | | 0x40-0x4F | JUN addr | Low 8 bits of 12-bit address | | 0x50-0x5F | JMS addr | Low 8 bits of 12-bit address | | 0x70-0x7F | ISZ R, addr | 8-bit page address | ### Quick Detection Function ``` function is_two_byte_instruction(opcode: u8) -> bool: // JCN: 0x10-0x1F if opcode >= 0x10 && opcode <= 0x1F: return true // FIM (even opcodes in 0x20-0x2F) if opcode >= 0x20 && opcode <= 0x2E && (opcode & 0x01) == 0: return true // FIN (even opcodes in 0x30-0x3F) if opcode >= 0x30 && opcode <= 0x3E && (opcode & 0x01) == 0: return true // JUN: 0x40-0x4F if opcode >= 0x40 && opcode <= 0x4F: return true // JMS: 0x50-0x5F if opcode >= 0x50 && opcode <= 0x5F: return true // ISZ: 0x70-0x7F if opcode >= 0x70 && opcode <= 0x7F: return true return false ``` --- ## 7. Two-Byte Instruction Fetch in `update()` Cycle ### Current Problem The current implementation uses a flawed "high nibble / low nibble" approach for JUN and JMS, treating them as two separate 1-byte instructions (`JunHigh`/`JunLow`, `JmsHigh`/`JmsLow`). This is **incorrect** — JUN and JMS are single 2-byte instructions where: - Byte 1 contains the opcode AND the high 4 bits of the address - Byte 2 contains the low 8 bits of the address ### Correct Fetch State Machine The instruction fetch should use a state machine with an additional state for "waiting for byte 2": ```mermaid stateDiagram-v2 [*] --> FetchByte1 FetchByte1 --> Decode: byte1 ready Decode --> Execute: one-byte instruction Decode --> FetchByte2: two-byte instruction FetchByte2 --> Execute: byte2 ready Execute --> FetchByte1: execution complete ``` ### Pseudocode for `update()` Fetch Logic ``` enum InstructionPhase: FetchByte1, // Fetching first byte of instruction FetchByte2, // Fetching second byte of 2-byte instruction Execute, // Executing decoded instruction struct Intel4004: // ... existing fields ... instruction_phase: InstructionPhase byte1: u8, // First byte of instruction byte2: u8, // Second byte (for 2-byte instructions) current_op: Instruction // Decoded instruction function update(): // ... clock edge detection, memory state machine ... match instruction_phase: FetchByte1: if memory_state == DriveData: byte1 = read_data_bus() PC.inc() if is_two_byte_instruction(byte1): // Decode partially, then fetch byte 2 current_op = decode_instruction(byte1) instruction_phase = FetchByte2 else: // One-byte instruction: decode and execute current_op = decode_instruction(byte1) instruction_phase = Execute FetchByte2: if memory_state == DriveData: byte2 = read_data_bus() PC.inc() // Complete the instruction with byte2 data current_op = complete_two_byte(current_op, byte2) instruction_phase = Execute Execute: execute_instruction() instruction_phase = FetchByte1 function complete_two_byte(partial_op: Instruction, byte2: u8) -> Instruction: match partial_op: Jcn(condition, _): return Jcn(condition, byte2) Fim(rpair, _): return Fim(rpair, byte2) Fin(rpair): // FIN reads from ROM at address in register pair // byte2 is already read from ROM by the fetch hardware // Store byte2 into register pair return FinWithData(rpair, byte2) Jun(_): addr = ((byte1 & 0x0F) << 8) | byte2 return Jun(addr) Jms(_): addr = ((byte1 & 0x0F) << 8) | byte2 return Jms(addr) Isz(reg, _): return Isz(reg, byte2) _: return partial_op // Should not happen ``` ### Key Points for Implementation 1. **PC is incremented after each byte fetch** — once after byte1, once after byte2 2. **JUN/JMS address encoding**: Byte 1 low nibble = address bits 11-8, Byte 2 = address bits 7-0 3. **JCN/ISZ address encoding**: Byte 2 = address bits 7-0 within current page. Full address = `(PC_before_fetch & 0xF00) | byte2` 4. **FIM data encoding**: Byte 2 high nibble → even register, low nibble → odd register 5. **FIN**: The second byte is read from ROM at the address formed by the register pair contents. The CPU fetches it from the data bus like any other byte, but the ROM address comes from the register pair, not from PC. ### Important: Save PC Page Before Fetch For JCN and ISZ, the page address must be captured **before** the PC is incremented past byte 1: ``` // In FetchByte1, before incrementing PC: saved_page = PC.value() & 0xF00 // Later, when computing full address: full_addr = saved_page | byte2 ``` --- ## 8. JCN Condition Decoding Logic The JCN instruction has a 4-bit condition field. The conditions use a combination of three tests: - **Bit 0 (0x01)**: TEST input pin - **Bit 1 (0x02)**: Carry flag - **Bit 2 (0x04)**: Accumulator zero - **Bit 3 (0x08)**: Invert flag ### Decoding Algorithm ``` function evaluate_jcn_condition(condition: u8) -> bool: test_pin = read_test_pin() // Current TEST pin state carry_flag = self.carry // Current carry flag acc_zero = (self.accumulator == 0) // ACC zero check // Compute the base condition (before invert) // Conditions are OR-ed together based on which bits are set base = false if (condition & 0x01) != 0: base = base || test_pin if (condition & 0x02) != 0: base = base || carry_flag if (condition & 0x04) != 0: base = base || acc_zero // Apply invert (bit 3) if (condition & 0x08) != 0: return !base else: return base ``` ### Condition Code Table | Condition | Binary | Meaning | |---|---|---| | 0x0 | 0000 | Always false (never jump) | | 0x1 | 0001 | TEST = 1 | | 0x2 | 0010 | Carry = 1 | | 0x3 | 0011 | Carry=1 OR TEST=1 | | 0x4 | 0100 | ACC = 0 | | 0x5 | 0101 | ACC=0 OR TEST=1 | | 0x6 | 0110 | ACC=0 OR Carry=1 | | 0x7 | 0111 | ACC=0 OR Carry=1 OR TEST=1 | | 0x8 | 1000 | Always true (NOT false) | | 0x9 | 1001 | TEST = 0 (NOT TEST=1) | | 0xA | 1010 | Carry = 0 (NOT Carry=1) | | 0xB | 1011 | Carry=0 AND TEST=0 (NOT of Carry=1 OR TEST=1) | | 0xC | 1100 | ACC ≠ 0 (NOT ACC=0) | | 0xD | 1101 | ACC≠0 AND TEST=0 | | 0xE | 1110 | Carry=0 AND ACC≠0 | | 0xF | 1111 | Carry=0 AND ACC≠0 AND TEST=0 | ### Key Insight: De Morgan's Law The invert bit applies De Morgan's law. When bit 3 is set: - `NOT (A OR B OR C)` becomes `NOT A AND NOT B AND NOT C` - So condition 0xB (1011) = NOT (Carry=1 OR TEST=1) = Carry=0 AND TEST=0 --- ## 9. SRC Register Pair Addressing ### Register Pair Concept The 16 index registers (R0-R15) are organized as 8 register pairs: - Pair 0: R0 (high nibble) + R1 (low nibble) - Pair 1: R2 (high nibble) + R3 (low nibble) - Pair 2: R4 (high nibble) + R5 (low nibble) - Pair 3: R6 (high nibble) + R7 (low nibble) - Pair 4: R8 (high nibble) + R9 (low nibble) - Pair 5: R10 (high nibble) + R11 (low nibble) - Pair 6: R12 (high nibble) + R13 (low nibble) - Pair 7: R14 (high nibble) + R15 (low nibble) ### Extracting Register Pair Number from Opcode For FIM/SRC/FIN/JIN, the register pair number is encoded in bits 4-1 of the opcode: ``` pair_number = (opcode >> 1) & 0x07 even_register = pair_number * 2 odd_register = pair_number * 2 + 1 ``` ### SRC Operation The SRC instruction sends the 8-bit value from a register pair to the address bus: - **Even register** → high nibble (chip select / ROM port) - **Odd register** → low nibble (word select within chip) This 8-bit value is used by subsequent I/O instructions (WRM, WMP, WRR, RDM, RDR, etc.) to select the target device and address. ### JIN Operation JIN uses the register pair to form a jump target address: - The 8-bit value from the pair is used as a page offset - Full address = `(current_PC & 0xF00) | (R[even] << 4) | R[odd]` > **Note**: Some sources describe JIN as using all 12 bits from a register pair. However, since each register is only 4 bits wide, a pair provides only 8 bits. The upper 4 bits of the address come from the current PC page. ### FIM Operation FIM loads an 8-bit immediate value into a register pair: ``` data = byte2 R[even] = (data >> 4) & 0x0F // High nibble → even register R[odd] = data & 0x0F // Low nibble → odd register ``` ### FIN Operation FIN reads 8 bits from ROM and stores in a register pair: ``` // The ROM address is formed from the register pair contents rom_addr = (R[even] << 4) | R[odd] // Read 2 nibbles from ROM at current page + rom_addr offset data = ROM[(PC & 0xF00) | rom_addr] // Simplified R[even] = (data >> 4) & 0x0F R[odd] = data & 0x0F ``` --- ## 10. BBL Instruction Clarification The BBL (Branch Back and Load) instruction is a **one-byte instruction** at opcode range 0xC0-0xCF. The 4-bit immediate value is encoded in the low nibble of the opcode (similar to LDM). It: 1. Pops the return address from the stack 2. Loads the accumulator with the 4-bit immediate value from the low nibble of the opcode ``` Bbl(data): if stack_pointer > 0: stack_pointer -= 1 PC = stack[stack_pointer] ACC = data & 0x0F PC.inc() // advance past the return address ``` > **Confirmed**: BBL is at 0xC0-0xCF and is a one-byte instruction. The immediate is in the opcode's low nibble, not a second byte. --- ## 11. Instruction Execution Semantics Notes ### ADD vs. Add-with-Carry The Intel 4004 ADD instruction (0x80-0x8F) **always adds with carry**. There is no separate "add without carry" instruction. The carry flag is both an input and output. ### SUB vs. Subtract-with-Borrow Similarly, SUB (0x90-0x9F) **always subtracts with borrow**. The carry flag acts as the inverse of borrow: - Carry = 1 → no borrow occurred - Carry = 0 → borrow occurred To subtract without borrow, set carry to 1 first (using STC) before executing SUB. ### TCS Semantics TCS (Transmit Carry Set) is used for BCD correction after subtraction: - If carry = 1 (no borrow): ACC = 9 - If carry = 0 (borrow occurred): ACC = 10 This value is then added to correct the BCD result. ### KBP (Keyboard Process) Converts a 1-of-N encoded input to binary: | ACC Input | ACC Output | |-----------|------------| | 0000 (0) | 0000 (0) | | 0001 (1) | 0000 (0) | | 0010 (2) | 0001 (1) | | 0100 (4) | 0010 (2) | | 1000 (8) | 0011 (3) | | Other | Unchanged (error) | --- ## 12. Implementation Checklist When rewriting [`intel_4004.rs`](src/components/cpu/intel_4004.rs), the following changes are needed: 1. **Replace the `Instruction` enum** with the correct one from Section 3 2. **Rewrite `decode_instruction()`** using the correct opcode mapping from Section 4 3. **Rewrite `execute_instruction()`** with correct semantics from Section 5 4. **Add `is_two_byte_instruction()` helper** as described in Section 6 5. **Refactor `update()` fetch logic** to use FetchByte1/FetchByte2/Execute states from Section 7 6. **Remove `JunHigh`/`JunLow`/`JmsHigh`/`JmsLow`** — these are artifacts of the wrong design 7. **Remove `pending_operand` field** — replaced by the FetchByte2 state 8. **Implement correct JCN condition decoding** from Section 8 9. **Implement correct SRC register pair addressing** from Section 9 10. **Add missing instructions**: FIM, FIN, JIN, SRC, BBL, KBP, DCL, RD0-RD3, WR0-WR3 11. **Remove non-existent instructions**: AddC, SubC, Dad, Jnt, JntInvert 12. **Fix ADD/SUB semantics** — they always use carry/borrow 13. **Fix TCC semantics** — should set ACC = carry value, not just clear 14. **Fix TCS semantics** — should set ACC = 9 if carry, 10 if not 15. **Update all tests** to use correct opcodes 16. **Mark 0xFD-0xFF as invalid** (not 0xFA-0xFF — DAA/KBP/DCL are at 0xFA-0xFC)