rusty_emu/tests/intel_4001_state_machine_test.rs
Jan-Peter von Hunnius 6a335a15d4 Fix Intel 4004 CPU instruction set and improve MCS-4 timing accuracy
Overhaul the Intel 4004 instruction set to match the real opcode map,
adding missing instructions (NOP, FIM, FIN, JIN, ISZ, KBP, DCL, WR0-3,
RD0-3) and refining two-byte instruction handling with proper M1/M2
data phases.  Rewrite the CPU fetch-execute cycle to correctly process
two-byte opcodes across separate clock cycles.

Improve Intel 4001 ROM timing with M1/M2 nibble phase tracking and an
A3 wait counter so the ROM drives data only when the CPU expects it.
Fix state-machine transition helpers in Intel 4002 and 4003 to capture
the old state before processing events, preventing incorrect exit/entry
callbacks.

Add non-propagating pin driver methods to prevent deadlocks caused by
bidirectional pin connections during signal propagation.

Add Fibonacci integration test and opcode fix plan document.
Update all README files to reflect the new instruction set.
2026-05-12 19:32:01 +02:00

381 lines
13 KiB
Rust

use std::thread;
use std::time::Duration;
use rusty_emu::component::Component;
use rusty_emu::components::common::intel_400x::{Intel400xTimingState, TimingState};
use rusty_emu::components::memory::intel_4001::Intel4001;
use rusty_emu::pin::PinValue;
/// Test to verify that Intel 4001 ROM unified state machine progresses correctly
/// This test isolates unified state machine logic to verify proper state transitions
#[test]
fn test_intel_4001_state_machine_progression() {
let mut rom = Intel4001::new_with_access_time("StateMachineTestROM".to_string(), 1); // 1ns for fast testing
// Load test data: address 0x12 should contain 0xAB
let test_data = vec![0x00; 0x12]; // Initialize with zeros
let mut data_with_test = test_data;
data_with_test.push(0xAB); // Add 0xAB at address 0x12
rom.load_rom_data(data_with_test, 0).unwrap();
// Get pin references for simulation
let sync_pin = rom.get_pin("SYNC").unwrap();
let cm_pin = rom.get_pin("CM").unwrap();
let ci_pin = rom.get_pin("CI").unwrap();
let phi1_pin = rom.get_pin("PHI1").unwrap();
let phi2_pin = rom.get_pin("PHI2").unwrap();
let reset_pin = rom.get_pin("RESET").unwrap();
// Data pins for address and data verification
let d0_pin = rom.get_pin("D0").unwrap();
let d1_pin = rom.get_pin("D1").unwrap();
let d2_pin = rom.get_pin("D2").unwrap();
let d3_pin = rom.get_pin("D3").unwrap();
// Start component
rom.base.set_running(true);
assert!(rom.is_running());
// Initialize clock pins to Low
{
let mut phi1_guard = phi1_pin.lock();
let mut phi2_guard = phi2_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Initialize control pins
// Note: RESET is active LOW, so set it to High to ensure component is not in reset
{
let mut sync_guard = sync_pin.lock();
let mut cm_guard = cm_pin.lock();
let mut ci_guard = ci_pin.lock();
let mut reset_guard = reset_pin.lock();
sync_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
cm_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Not in reset
}
// Initialize data pins to Low
{
let mut d0_guard = d0_pin.lock();
let mut d1_guard = d1_pin.lock();
let mut d2_guard = d2_pin.lock();
let mut d3_guard = d3_pin.lock();
d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Update to initialize clock states
rom.update();
// Verify initial state
assert_eq!(rom.get_timing_state(), TimingState::Idle);
println!("Initial state: {:?}", rom.get_timing_state());
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
{
let mut sync_guard = sync_pin.lock();
let mut cm_guard = cm_pin.lock();
let mut ci_guard = ci_pin.lock();
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
}
// Set up address high nibble (0x1 for address 0x12)
{
let mut d0_guard = d0_pin.lock();
let mut d1_guard = d1_pin.lock();
let mut d2_guard = d2_pin.lock();
let mut d3_guard = d3_pin.lock();
// 0x1 = 0001 (D0=1, D1=0, D2=0, D3=0)
d0_guard.set_driver(Some("TEST".to_string()), PinValue::High);
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Wait for pins to settle
thread::sleep(Duration::from_millis(1));
// Trigger Φ1 rising edge to start memory operation and latch high nibble
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Update to process Φ1 rising edge
rom.update();
// Should have started memory operation and transitioned to AddressPhase
assert_eq!(rom.get_timing_state(), TimingState::AddressPhase);
println!(
"State after Φ1 rising (high nibble): {:?}",
rom.get_timing_state()
);
// Reset Φ1 to Low
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Update to process Φ1 falling edge
rom.update();
// Set up address low nibble (0x2 for address 0x12)
{
let mut d0_guard = d0_pin.lock();
let mut d1_guard = d1_pin.lock();
let mut d2_guard = d2_pin.lock();
let mut d3_guard = d3_pin.lock();
// 0x2 = 0010 (D0=0, D1=1, D2=0, D3=0)
d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Wait for pins to settle
thread::sleep(Duration::from_millis(1));
// Trigger Φ1 rising edge again - ROM latches low nibble on PHI1 rising edge
// (address nibbles are latched on Φ1 RISING edge, same as CPU drives address)
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Update to process Φ1 rising edge
rom.update();
// Should have latched low nibble and transitioned to WaitLatency
// (both nibbles latched on successive PHI1 rising edges)
let state_after_low_nibble = rom.get_timing_state();
assert!(
state_after_low_nibble == TimingState::WaitLatency,
"Expected WaitLatency after latching both nibbles on PHI1 rising, got {:?}",
state_after_low_nibble
);
println!(
"State after second Φ1 rising (both nibbles latched): {:?}",
state_after_low_nibble
);
// Remove test drivers from data pins to allow ROM to drive bus
{
let mut d0_guard = d0_pin.lock();
let mut d1_guard = d1_pin.lock();
let mut d2_guard = d2_pin.lock();
let mut d3_guard = d3_pin.lock();
d0_guard.remove_driver("TEST");
d1_guard.remove_driver("TEST");
d2_guard.remove_driver("TEST");
d3_guard.remove_driver("TEST");
}
// ROM is in WaitLatency with a3_wait_cycles=1.
// Need to cycle the clock to get through the wait period and into DriveData.
// The ROM expects PHI2 rising edges to decrement a3_wait_cycles and transition.
// Step 1: PHI1 falling + PHI2 rising (decrements a3_wait_cycles to 0)
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
{
let mut phi2_guard = phi2_pin.lock();
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
rom.update();
// a3_wait_cycles decremented from 1 to 0
// Step 2: PHI2 falling + PHI1 rising (no transition yet, just clock edge)
{
let mut phi2_guard = phi2_pin.lock();
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
rom.update();
// Step 3: PHI1 falling + PHI2 rising (a3_wait_cycles=0, transitions to DriveData)
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
{
let mut phi2_guard = phi2_pin.lock();
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
rom.update();
// Should now be in DriveData (process_latency_elapsed on this PHI2 rising)
// But return statement means data isn't driven yet
// Step 4: PHI2 falling + PHI1 rising
{
let mut phi2_guard = phi2_pin.lock();
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
rom.update();
// Step 5: PHI1 falling + PHI2 rising - NOW ROM drives data
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
{
let mut phi2_guard = phi2_pin.lock();
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
rom.update();
// Should be in DriveData and driving OPR (high nibble of 0xAB = 0xA)
assert_eq!(rom.get_timing_state(), TimingState::DriveData);
println!("State after PHI2 rising (driving data): {:?}", rom.get_timing_state());
// Wait for pins to settle after ROM drives data
thread::sleep(Duration::from_millis(1));
// Verify OPR is being driven on the bus
// Data 0xAB has high nibble 0xA = 1010 (D0=0, D1=1, D2=0, D3=1)
assert_eq!(d0_pin.lock().read(), PinValue::Low, "D0 should be Low for 0xA");
assert_eq!(d1_pin.lock().read(), PinValue::High, "D1 should be High for 0xA");
assert_eq!(d2_pin.lock().read(), PinValue::Low, "D2 should be Low for 0xA");
assert_eq!(d3_pin.lock().read(), PinValue::High, "D3 should be High for 0xA");
println!("OPR data verified: 0xA");
}
/// Test to verify that unified state machine doesn't progress without proper control signals
#[test]
fn test_intel_4001_state_machine_no_progression_without_signals() {
let mut rom = Intel4001::new_with_access_time("NoProgressionTestROM".to_string(), 1);
// Get pin references for simulation
let phi1_pin = rom.get_pin("PHI1").unwrap();
let phi2_pin = rom.get_pin("PHI2").unwrap();
// Start component
rom.base.set_running(true);
assert!(rom.is_running());
// Initialize clock pins to Low
{
let mut phi1_guard = phi1_pin.lock();
let mut phi2_guard = phi2_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Update to initialize clock states
rom.update();
// Verify initial state
assert_eq!(rom.get_timing_state(), TimingState::Idle);
// Trigger clock edges without setting up control signals
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Update to process Φ1 rising edge
rom.update();
// Should still be in Idle state since control signals are not set
assert_eq!(rom.get_timing_state(), TimingState::Idle);
// Reset Φ1 to Low
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Update to process Φ1 falling edge
rom.update();
// Trigger Φ2 rising edge
{
let mut phi2_guard = phi2_pin.lock();
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Update to process Φ2 rising edge
rom.update();
// Should still be in Idle state
assert_eq!(rom.get_timing_state(), TimingState::Idle);
}
/// Test to verify that reset properly clears unified state machine
#[test]
fn test_intel_4001_state_machine_reset() {
let mut rom = Intel4001::new_with_access_time("ResetTestROM".to_string(), 1);
// Get pin references for simulation
let sync_pin = rom.get_pin("SYNC").unwrap();
let cm_pin = rom.get_pin("CM").unwrap();
let ci_pin = rom.get_pin("CI").unwrap();
let phi1_pin = rom.get_pin("PHI1").unwrap();
let reset_pin = rom.get_pin("RESET").unwrap();
// Start component
rom.base.set_running(true);
assert!(rom.is_running());
// Initialize clock pins to Low
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Update to initialize clock states
rom.update();
// Verify initial state
assert_eq!(rom.get_timing_state(), TimingState::Idle);
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
{
let mut sync_guard = sync_pin.lock();
let mut cm_guard = cm_pin.lock();
let mut ci_guard = ci_pin.lock();
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
}
// Trigger Φ1 rising edge to start memory operation
{
let mut phi1_guard = phi1_pin.lock();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Update to process Φ1 rising edge
rom.update();
// Should have started memory operation and transitioned to AddressPhase
assert_eq!(rom.get_timing_state(), TimingState::AddressPhase);
// Assert RESET (active LOW - set to Low to trigger reset)
{
let mut reset_guard = reset_pin.lock();
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Update to process reset
rom.update();
// Should have returned to Idle state
assert_eq!(rom.get_timing_state(), TimingState::Idle);
}