Fix Intel 4004 CPU emulation bugs: opcode mapping, JCN conditions, DAA carry

- Remap opcodes 0x01-0x0F from duplicate LD to Invalid (undefined on real hardware)
- Fix JCN condition bit mapping per datasheet: C1=Carry, C2=ACC zero, C3=TEST pin
- Fix DAA instruction to clear carry when no 4-bit overflow occurs
- Rewrite fibonacci demo program with correct opcodes (FIM, CLB, LD, ADD, WRM, XCH, ISZ)
- Regenerate fibonacci.bin and fibonacci_output.bin with correct opcodes
- Update all tests to match corrected JCN condition codes
- Update plan document and README to reflect changes
This commit is contained in:
Jan-Peter von Hunnius 2026-05-12 20:55:26 +02:00
parent 6a335a15d4
commit add7ace674
7 changed files with 132 additions and 100 deletions

View file

@ -8,7 +8,7 @@ The current implementation in [`intel_4004.rs`](src/components/cpu/intel_4004.rs
| Opcode Range | Current Mapping | Correct Mapping |
|---|---|---|
| 0x00-0x0F | LD/XCH by register | NOP + LD R (duplicate of 0xA0-0xAF) |
| 0x00-0x0F | NOP + undefined | NOP (0x00) + undefined opcodes (0x01-0x0F) |
| 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 |
@ -366,12 +366,12 @@ Swap `ACC` and `R[opcode & 0x0F]`.
| Opcode Range | Status |
|--------------|--------|
| 0x01-0x0F | **LD R** — duplicate of 0xA0-0xAF. Functionally identical. |
| 0x01-0x0F | **Invalid** — undefined on real Intel 4004 hardware. Mapped to `Instruction::Invalid`. |
| 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`.
> **Note on 0x01-0x0F**: These opcodes are undefined on real Intel 4004 hardware. The only valid LD instruction range is `0xA0-0xAF`. Programs that previously used `0x01-0x0F` as LD should be updated to use `0xA0+R` instead.
---
@ -385,8 +385,8 @@ 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)
// === Register load ===
Ld(u8), // LD R: Load register R into ACC (R = 0..15), opcode 0xA0-0xAF
// === Two-byte instructions ===
Jcn(u8, u8), // 0x10-0x1F: JCN condition, addr_lo
@ -485,8 +485,8 @@ function decode_instruction(opcode: u8) -> Instruction:
// NOP
0x00 => Nop
// LD R (duplicate range — same as 0xA0-0xAF)
0x01..=0x0F => Ld(opcode & 0x0F)
// 0x01-0x0F: Undefined on real Intel 4004 hardware
0x01..=0x0F => Invalid
// JCN — two-byte, condition in low nibble
0x10..=0x1F => Jcn(opcode & 0x0F, 0) // addr filled by byte 2 fetch
@ -837,11 +837,17 @@ function execute_instruction():
Daa:
// Decimal adjust accumulator
// Per Intel 4004 datasheet: carry is set if adjustment causes 4-bit overflow,
// otherwise carry is cleared.
if ACC > 9 or carry:
ACC = ACC + 6
if ACC > 0x0F:
carry = true
ACC = ACC & 0x0F
else:
carry = false
else:
carry = false
PC.inc()
Kbp:
@ -880,7 +886,7 @@ function execute_instruction():
| Opcode Range | Instruction |
|---|---|
| 0x00 | NOP |
| 0x01-0x0F | LD R (duplicate) |
| 0x01-0x0F | Invalid (undefined on real hardware) |
| 0x21, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D, 0x2F | SRC Rpair |
| 0x31, 0x33, 0x35, 0x37, 0x39, 0x3B, 0x3D, 0x3F | JIN Rpair |
| 0x60-0x6F | INC R |
@ -1052,12 +1058,13 @@ 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:
The JCN instruction has a 4-bit condition field (C1-C4 in the OPA nibble).
Per the Intel 4004 datasheet, 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
- **C1 / Bit 0 (0x01)**: Carry flag
- **C2 / Bit 1 (0x02)**: Accumulator zero
- **C3 / Bit 2 (0x04)**: TEST input pin
- **C4 / Bit 3 (0x08)**: Invert flag
### Decoding Algorithm
@ -1070,11 +1077,11 @@ function evaluate_jcn_condition(condition: u8) -> bool:
// 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
if (condition & 0x01) != 0: base = base || carry_flag // C1
if (condition & 0x02) != 0: base = base || acc_zero // C2
if (condition & 0x04) != 0: base = base || test_pin // C3
// Apply invert (bit 3)
// Apply invert (C4)
if (condition & 0x08) != 0:
return !base
else:
@ -1086,27 +1093,27 @@ function evaluate_jcn_condition(condition: u8) -> bool:
| 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 |
| 0x1 | 0001 | Carry = 1 |
| 0x2 | 0010 | ACC = 0 |
| 0x3 | 0011 | Carry=1 OR ACC=0 |
| 0x4 | 0100 | TEST = 1 |
| 0x5 | 0101 | Carry=1 OR TEST=1 |
| 0x6 | 0110 | ACC=0 OR TEST=1 |
| 0x7 | 0111 | Carry=1 OR ACC=0 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 |
| 0x9 | 1001 | Carry = 0 (NOT Carry=1) |
| 0xA | 1010 | ACC ≠ 0 (NOT ACC=0) |
| 0xB | 1011 | Carry=0 AND ACC≠0 (NOT of Carry=1 OR ACC=0) |
| 0xC | 1100 | TEST = 0 (NOT TEST=1) |
| 0xD | 1101 | Carry=0 AND TEST=0 |
| 0xE | 1110 | ACC≠0 AND TEST=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
- So condition 0xB (1011) = NOT (Carry=1 OR ACC=0) = Carry=0 AND ACC≠0
---