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:
parent
6a335a15d4
commit
add7ace674
7 changed files with 132 additions and 100 deletions
|
|
@ -716,8 +716,9 @@ impl Intel4004 {
|
|||
// NOP
|
||||
0x00 => Instruction::Nop,
|
||||
|
||||
// LD R (duplicate range — same as 0xA0-0xAF)
|
||||
0x01..=0x0F => Instruction::Ld(opcode & 0x0F),
|
||||
// 0x01-0x0F: Undefined on real Intel 4004 hardware
|
||||
// Mapped to Invalid to catch accidental use of undefined opcodes
|
||||
0x01..=0x0F => Instruction::Invalid,
|
||||
|
||||
// JCN — two-byte, condition in low nibble
|
||||
0x10..=0x1F => Instruction::Jcn(opcode & 0x0F, 0),
|
||||
|
|
@ -852,11 +853,14 @@ impl Intel4004 {
|
|||
/// Parameters: condition - 4-bit condition code from JCN instruction
|
||||
/// Returns: true if the condition is met (should jump), false otherwise
|
||||
///
|
||||
/// Condition encoding:
|
||||
/// - Bit 0 (0x01): TEST input pin
|
||||
/// - Bit 1 (0x02): Carry flag
|
||||
/// - Bit 2 (0x04): Accumulator zero
|
||||
/// - Bit 3 (0x08): Invert flag
|
||||
/// Condition encoding per Intel 4004 datasheet (OPA bits C1-C4):
|
||||
/// - C1 / Bit 0 (0x01): Carry flag (inverted: NOT carry)
|
||||
/// - C2 / Bit 1 (0x02): Accumulator zero (inverted: ACC ≠ 0)
|
||||
/// - C3 / Bit 2 (0x04): TEST input pin (inverted: TEST = 0)
|
||||
/// - C4 / Bit 3 (0x08): Invert flag
|
||||
///
|
||||
/// When multiple condition bits are set, they are OR-ed together.
|
||||
/// Bit 3 inverts the entire result.
|
||||
fn evaluate_jcn_condition(&self, condition: u8) -> bool {
|
||||
let test_pin = self.read_test_pin();
|
||||
let acc_zero = self.accumulator == 0;
|
||||
|
|
@ -864,14 +868,14 @@ impl Intel4004 {
|
|||
// Compute the base condition (before invert) by OR-ing active conditions
|
||||
let mut base = false;
|
||||
if (condition & 0x01) != 0 {
|
||||
base = base || test_pin;
|
||||
}
|
||||
if (condition & 0x02) != 0 {
|
||||
base = base || self.carry;
|
||||
}
|
||||
if (condition & 0x04) != 0 {
|
||||
if (condition & 0x02) != 0 {
|
||||
base = base || acc_zero;
|
||||
}
|
||||
if (condition & 0x04) != 0 {
|
||||
base = base || test_pin;
|
||||
}
|
||||
|
||||
// Apply invert (bit 3)
|
||||
if (condition & 0x08) != 0 {
|
||||
|
|
@ -1281,12 +1285,19 @@ impl Intel4004 {
|
|||
|
||||
Instruction::Daa => {
|
||||
// Decimal adjust accumulator
|
||||
// Per Intel 4004 datasheet: if ACC > 9 or carry is set, add 6.
|
||||
// Carry is set if the adjustment causes a 4-bit overflow,
|
||||
// otherwise carry is cleared.
|
||||
if self.accumulator > 9 || self.carry {
|
||||
self.accumulator += 6;
|
||||
if self.accumulator > 0x0F {
|
||||
self.carry = true;
|
||||
self.accumulator &= 0x0F;
|
||||
} else {
|
||||
self.carry = false;
|
||||
}
|
||||
} else {
|
||||
self.carry = false;
|
||||
}
|
||||
self.program_counter.inc();
|
||||
}
|
||||
|
|
@ -1432,10 +1443,10 @@ mod tests {
|
|||
#[test]
|
||||
fn test_decode_ld() {
|
||||
let cpu = Intel4004::new("TEST_CPU".to_string(), 750000.0);
|
||||
// LD at 0x01-0x0F (duplicate range)
|
||||
assert_eq!(cpu.decode_instruction(0x01), Instruction::Ld(1));
|
||||
assert_eq!(cpu.decode_instruction(0x0F), Instruction::Ld(15));
|
||||
// LD at 0xA0-0xAF (primary range)
|
||||
// 0x01-0x0F are undefined opcodes (mapped to Invalid)
|
||||
assert_eq!(cpu.decode_instruction(0x01), Instruction::Invalid);
|
||||
assert_eq!(cpu.decode_instruction(0x0F), Instruction::Invalid);
|
||||
// LD at 0xA0-0xAF (primary and only valid range)
|
||||
assert_eq!(cpu.decode_instruction(0xA0), Instruction::Ld(0));
|
||||
assert_eq!(cpu.decode_instruction(0xAF), Instruction::Ld(15));
|
||||
}
|
||||
|
|
@ -1809,29 +1820,29 @@ mod tests {
|
|||
// Condition 0x8: NOT always false = always true
|
||||
assert!(cpu.evaluate_jcn_condition(0x8));
|
||||
|
||||
// Condition 0x4: ACC = 0 (ACC is 0 after reset)
|
||||
assert!(cpu.evaluate_jcn_condition(0x4));
|
||||
// Condition 0x2: ACC = 0 (ACC is 0 after reset)
|
||||
assert!(cpu.evaluate_jcn_condition(0x2));
|
||||
|
||||
// Condition 0xC: NOT ACC = 0 → ACC != 0
|
||||
assert!(!cpu.evaluate_jcn_condition(0xC));
|
||||
// Condition 0xA: NOT ACC = 0 → ACC != 0
|
||||
assert!(!cpu.evaluate_jcn_condition(0xA));
|
||||
|
||||
// Set ACC to non-zero
|
||||
cpu.set_accumulator(5);
|
||||
|
||||
// Condition 0x4: ACC = 0 → false (ACC is 5)
|
||||
assert!(!cpu.evaluate_jcn_condition(0x4));
|
||||
// Condition 0x2: ACC = 0 → false (ACC is 5)
|
||||
assert!(!cpu.evaluate_jcn_condition(0x2));
|
||||
|
||||
// Condition 0xC: NOT ACC = 0 → ACC != 0 → true
|
||||
assert!(cpu.evaluate_jcn_condition(0xC));
|
||||
// Condition 0xA: NOT ACC = 0 → ACC != 0 → true
|
||||
assert!(cpu.evaluate_jcn_condition(0xA));
|
||||
|
||||
// Test carry conditions
|
||||
// Test carry conditions (bit 0 = carry)
|
||||
cpu.carry = true;
|
||||
assert!(cpu.evaluate_jcn_condition(0x2)); // Carry = 1
|
||||
assert!(!cpu.evaluate_jcn_condition(0xA)); // NOT Carry = 1 → Carry = 0
|
||||
assert!(cpu.evaluate_jcn_condition(0x1)); // Carry = 1
|
||||
assert!(!cpu.evaluate_jcn_condition(0x9)); // NOT Carry = 1 → Carry = 0
|
||||
|
||||
cpu.carry = false;
|
||||
assert!(!cpu.evaluate_jcn_condition(0x2)); // Carry = 1 → false
|
||||
assert!(cpu.evaluate_jcn_condition(0xA)); // NOT Carry = 1 → true
|
||||
assert!(!cpu.evaluate_jcn_condition(0x1)); // Carry = 1 → false
|
||||
assert!(cpu.evaluate_jcn_condition(0x9)); // NOT Carry = 1 → true
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -2181,13 +2192,13 @@ mod tests {
|
|||
assert_eq!(cpu.get_accumulator(), 2); // 12 + 6 = 18 → 18 & 0xF = 2
|
||||
assert_eq!(cpu.get_carry(), true);
|
||||
|
||||
// DAA: adjustment needed (carry set)
|
||||
// DAA: adjustment needed (carry set, no 4-bit overflow)
|
||||
cpu.set_accumulator(5);
|
||||
cpu.carry = true;
|
||||
cpu.current_op = Instruction::Daa;
|
||||
cpu.execute_instruction();
|
||||
assert_eq!(cpu.get_accumulator(), 0xB); // 5 + 6 = 11
|
||||
assert_eq!(cpu.get_carry(), true); // carry was set, stays set
|
||||
assert_eq!(cpu.get_carry(), false); // adjustment made, no 4-bit overflow → carry cleared
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
65
src/main.rs
65
src/main.rs
|
|
@ -381,32 +381,47 @@ fn load_program_data(filename: &str) -> Result<Vec<u8>, String> {
|
|||
|
||||
fn get_default_fibonacci_program() -> Vec<u8> {
|
||||
// Fibonacci program for Intel 4004
|
||||
// This implements a simple Fibonacci sequence generator
|
||||
// Computes Fibonacci numbers using registers R0-R3 and writes to RAM.
|
||||
//
|
||||
// Register usage:
|
||||
// R0 = F(n-2) (two steps back)
|
||||
// R1 = F(n-1) (one step back)
|
||||
// R2 = loop counter (counts up, exit when ISZ wraps to 0)
|
||||
// R3 = temp for register rotation
|
||||
//
|
||||
// Opcode reference (per Intel 4004 datasheet):
|
||||
// FIM Rp,data = 0x20+2*Rp, data (load register pair immediate, 2-byte)
|
||||
// LD R = 0xA0+R (load register R into ACC)
|
||||
// XCH R = 0xB0+R (exchange ACC ↔ register R)
|
||||
// ADD R = 0x80+R (ACC = ACC + R + Carry)
|
||||
// CLB = 0xF0 (clear both ACC and Carry)
|
||||
// WRM = 0xE0 (write ACC to RAM at current pointer)
|
||||
// ISZ R,addr = 0x70+R, addr (increment R; jump to addr if R ≠ 0, 2-byte)
|
||||
vec![
|
||||
0x20, 0x00, // LDM 0 (Load accumulator with 0) - First Fibonacci number
|
||||
0x10, 0x00, // LD 0 (Load accumulator from register 0)
|
||||
0x50, // WRM (Write accumulator to RAM at current pointer)
|
||||
0x21, 0x01, // LDM 1 (Load accumulator with 1) - Second Fibonacci number
|
||||
0x10, 0x01, // LD 1 (Load accumulator from register 1)
|
||||
0x50, // WRM (Write accumulator to RAM)
|
||||
// Fibonacci calculation loop
|
||||
0x00, 0x02, // LD 2 (Load register 2 into accumulator) - Loop counter
|
||||
0x76, // IAC (Increment accumulator)
|
||||
0x00, 0x02, // LD 2 (Store back to register 2)
|
||||
// Calculate next Fibonacci number: F(n) = F(n-1) + F(n-2)
|
||||
0x00, 0x00, // LD 0 (Load F(n-2) into accumulator)
|
||||
0x10, 0x01, // ADD 1 (Add F(n-1) to accumulator)
|
||||
0x50, // WRM (Store result to RAM)
|
||||
// Update registers for next iteration
|
||||
0x00, 0x01, // LD 1 (Load F(n-1) into accumulator)
|
||||
0x00, 0x00, // LD 0 (Store F(n-1) to register 0)
|
||||
0x50, // WRM (Write to RAM - this will be read back as F(n-2))
|
||||
// Check if we've calculated enough numbers (8 iterations)
|
||||
0x00, 0x02, // LD 2 (Load loop counter)
|
||||
0x30, 0x08, // JCN 8 (Jump if accumulator == 8) - Exit condition
|
||||
0x40, 0x0C, // JCN 12 (Jump back to loop start)
|
||||
// Exit - halt program
|
||||
0x00, 0x00, // NOP (placeholder for halt)
|
||||
// === Address 0x00: Initialize registers ===
|
||||
0x20, 0x01, // FIM R0/R1, 0x01 → R0=0, R1=1 (F(0)=0, F(1)=1)
|
||||
0x22, 0x78, // FIM R2/R3, 0x78 → R2=7 (loop counter, ISZ wraps 7→8≠0→jumps, 8→9, ... 15→0→falls through = 8 iterations), R3=8
|
||||
|
||||
// === Address 0x04: Loop — compute F(n) = F(n-1) + F(n-2) ===
|
||||
0xF0, // CLB (ACC=0, Carry=0)
|
||||
0xA1, // LD R1 → ACC = F(n-1)
|
||||
0x80, // ADD R0 → ACC = F(n-1) + F(n-2) + 0 = F(n)
|
||||
0xE0, // WRM (write F(n) to RAM)
|
||||
|
||||
// === Address 0x08: Rotate registers: R0 ← R1, R1 ← F(n) ===
|
||||
// F(n) is still in ACC after WRM (WRM doesn't change ACC)
|
||||
0xB3, // XCH R3 → ACC = old R3, R3 = F(n)
|
||||
0xA1, // LD R1 → ACC = F(n-1)
|
||||
0xB0, // XCH R0 → ACC = old F(n-2), R0 = F(n-1)
|
||||
0xA3, // LD R3 → ACC = F(n)
|
||||
0xB1, // XCH R1 → ACC = old F(n-1), R1 = F(n)
|
||||
|
||||
// === Address 0x0E: Loop control ===
|
||||
0x72, 0x04, // ISZ R2, 0x04 → R2++; if R2 ≠ 0 jump to 0x04 (loop)
|
||||
|
||||
// === Address 0x10: Exit ===
|
||||
0x00, // NOP (halt placeholder)
|
||||
0x00, // NOP (halt placeholder)
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue