Unified the 400x state machine for address latching

Fixed timing issues
This commit is contained in:
Jan-Peter von Hunnius 2025-11-14 18:51:10 +01:00
parent e17d6ad177
commit 633463e370
26 changed files with 11904 additions and 2471 deletions

View file

@ -0,0 +1,473 @@
//! Intel 4004 CPU State Machine Diagnostic Test
//!
//! This test verifies that the Intel 4004 CPU state machine progresses correctly
//! through all phases: Idle → AddressPhase → WaitLatency → DriveData → Idle
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use rusty_emu::component::Component;
use rusty_emu::components::cpu::intel_4004::Intel4004;
use rusty_emu::pin::{Pin, PinValue};
/// Test configuration for controlling test behavior
#[derive(Debug, Clone)]
struct TestConfig {
timeout_ms: u64,
max_cycles: u64,
_debug_level: u8,
}
impl Default for TestConfig {
fn default() -> Self {
TestConfig {
timeout_ms: 5000,
max_cycles: 10000,
_debug_level: 0,
}
}
}
/// Helper struct to manage CPU lifecycle in tests
struct CpuTestHelper {
cpu: Intel4004,
phi1: Arc<Mutex<Pin>>,
phi2: Arc<Mutex<Pin>>,
config: TestConfig,
cycle_count: u64,
start_time: Instant,
}
impl CpuTestHelper {
fn new(config: TestConfig) -> Self {
let cpu = Intel4004::new("TEST_CPU".to_string(), 750000.0);
let phi1 = Arc::new(Mutex::new(Pin::new("PHI1".to_string())));
let phi2 = Arc::new(Mutex::new(Pin::new("PHI2".to_string())));
// Connect CPU to test clock
cpu.get_pin("PHI1")
.unwrap()
.lock()
.unwrap()
.connect_to(phi1.clone());
cpu.get_pin("PHI2")
.unwrap()
.lock()
.unwrap()
.connect_to(phi2.clone());
CpuTestHelper {
cpu,
phi1,
phi2,
config,
cycle_count: 0,
start_time: Instant::now(),
}
}
fn simulate_clock_cycle(&mut self) -> Result<(), String> {
// Check timeout
if self.start_time.elapsed() >= Duration::from_millis(self.config.timeout_ms) {
return Err(format!("Test timeout after {}ms", self.config.timeout_ms));
}
// Check cycle limit
self.cycle_count += 1;
if self.cycle_count >= self.config.max_cycles {
return Err(format!(
"Test exceeded maximum cycle count of {}",
self.config.max_cycles
));
}
// Check if CPU is still in a valid state
if self.cpu.get_cycle_count() > self.config.max_cycles * 2 {
return Err("CPU internal cycle count exceeded safe limit".to_string());
}
// Phase 1: Φ1 high, Φ2 low
self.phi1
.lock()
.unwrap()
.set_driver(Some("clock".to_string()), PinValue::High);
self.phi2
.lock()
.unwrap()
.set_driver(Some("clock".to_string()), PinValue::Low);
thread::sleep(Duration::from_millis(1));
// Use update() instead of run() to prevent infinite loops
self.cpu.update();
// Phase 2: Φ1 low, Φ2 high
self.phi1
.lock()
.unwrap()
.set_driver(Some("clock".to_string()), PinValue::Low);
self.phi2
.lock()
.unwrap()
.set_driver(Some("clock".to_string()), PinValue::High);
thread::sleep(Duration::from_millis(1));
self.cpu.update();
// Phase 3: Both low
self.phi1
.lock()
.unwrap()
.set_driver(Some("clock".to_string()), PinValue::Low);
self.phi2
.lock()
.unwrap()
.set_driver(Some("clock".to_string()), PinValue::Low);
thread::sleep(Duration::from_millis(1));
self.cpu.update();
Ok(())
}
fn simulate_clock_cycle_unchecked(&mut self) {
if let Err(e) = self.simulate_clock_cycle() {
panic!("Clock cycle simulation failed: {}", e);
}
}
fn set_control_pins(&self, sync: bool, cm: bool, reset: bool) {
self.cpu
.get_pin("SYNC")
.unwrap()
.lock()
.unwrap()
.set_driver(
Some("test".to_string()),
if sync { PinValue::High } else { PinValue::Low },
);
self.cpu.get_pin("CM").unwrap().lock().unwrap().set_driver(
Some("test".to_string()),
if cm { PinValue::High } else { PinValue::Low },
);
self.cpu
.get_pin("RESET")
.unwrap()
.lock()
.unwrap()
.set_driver(
Some("test".to_string()),
if reset { PinValue::High } else { PinValue::Low },
);
}
fn drive_data_bus(&self, data: u8) {
for i in 0..4 {
let bit_value = (data >> i) & 1;
self.cpu
.get_pin(&format!("D{}", i))
.unwrap()
.lock()
.unwrap()
.set_driver(
Some("test".to_string()),
if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
},
);
}
}
fn read_data_bus(&self) -> u8 {
let mut data = 0;
for i in 0..4 {
if let Ok(pin) = self.cpu.get_pin(&format!("D{}", i)) {
if let Ok(pin_guard) = pin.lock() {
if pin_guard.read() == PinValue::High {
data |= 1 << i;
}
}
}
}
data & 0x0F
}
fn tri_state_data_bus(&self) {
for i in 0..4 {
if let Ok(pin) = self.cpu.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(Some("test".to_string()), PinValue::HighZ);
}
}
}
}
}
/// Wrapper function to run tests with timeout
#[allow(dead_code)] // This function is kept for future use but not currently used
fn with_timeout<F>(test_fn: F, timeout_ms: u64)
where
F: FnOnce() + Send + 'static,
{
let handle = thread::spawn(test_fn);
// Wait for completion or timeout
let _completed = thread::spawn(move || {
thread::sleep(Duration::from_millis(timeout_ms));
false
});
// Check if test completed
if handle.join().is_err() {
panic!("Test timed out after {}ms", timeout_ms);
}
}
#[test]
fn test_intel_4004_state_machine_progression() {
println!("=== Intel 4004 CPU State Machine Test ===");
// Create test helper with timeout protection
let config = TestConfig {
timeout_ms: 5000,
max_cycles: 1000,
_debug_level: 1,
};
let mut helper = CpuTestHelper::new(config);
// Reset CPU
helper.cpu.reset();
println!("Initial state after reset:");
println!(
" PC: 0x{:03X}, ACC: 0x{:X}, Phase: {:?}",
helper.cpu.get_program_counter(),
helper.cpu.get_accumulator(),
helper.cpu.get_cycle_count()
);
// Set initial control pins
helper.set_control_pins(false, false, false);
// Start CPU
helper.cpu.update();
// Test 1: Verify initial state is Idle
println!("\n--- Test 1: Initial State ---");
helper.simulate_clock_cycle_unchecked();
println!("CPU should be in Idle state initially");
// Test 2: Start memory operation with SYNC and CM high
println!("\n--- Test 2: Start Memory Operation ---");
helper.set_control_pins(true, true, false);
// Simulate clock cycle to trigger address phase
helper.simulate_clock_cycle_unchecked();
println!("Set SYNC=HIGH, CM=HIGH to start memory operation");
// Test 3: Address Phase - Drive address on data bus
println!("\n--- Test 3: Address Phase ---");
// Drive high nibble of address (0xA) on data bus
helper.drive_data_bus(0xA);
helper.simulate_clock_cycle_unchecked();
println!("Drove high nibble 0xA on data bus");
// Drive low nibble of address (0x5) on data bus
helper.drive_data_bus(0x5);
helper.simulate_clock_cycle_unchecked();
println!("Drove low nibble 0x5 on data bus");
// Test 4: Wait Latency Phase
println!("\n--- Test 4: Wait Latency Phase ---");
helper.tri_state_data_bus();
// Wait for latency (500ns default, but we'll simulate with multiple cycles)
for i in 0..3 {
helper.simulate_clock_cycle_unchecked();
println!("Wait latency cycle {}", i + 1);
}
// Test 5: Data Phase - CPU should drive data
println!("\n--- Test 5: Data Phase ---");
// Set up data latch with test value
// Set up test data on the data bus through pins
for i in 0..4 {
let pin_name = format!("D{}", i);
if let Ok(pin) = helper.cpu.get_pin(&pin_name) {
let mut pin_guard = pin.lock().unwrap();
pin_guard.set_driver(Some("test".to_string()), PinValue::High);
}
}
helper.simulate_clock_cycle_unchecked();
let data_value = helper.read_data_bus();
println!("CPU driving data on bus: 0x{:X}", data_value);
// Test 6: Return to Idle
println!("\n--- Test 6: Return to Idle ---");
helper.set_control_pins(false, false, false);
helper.simulate_clock_cycle_unchecked();
println!("Cleared control signals, should return to Idle");
println!("\n=== Intel 4004 CPU State Machine Test Complete ===");
}
#[test]
fn test_intel_4004_reset_functionality() {
println!("=== Intel 4004 CPU Reset Test ===");
// Create test helper with timeout protection
let config = TestConfig {
timeout_ms: 3000,
max_cycles: 500,
_debug_level: 1,
};
let mut helper = CpuTestHelper::new(config);
// Initialize CPU - use update() instead of run() to avoid infinite loop
helper.cpu.update();
// Set some initial state
helper.cpu.set_accumulator(0xF);
helper.cpu.set_register(0, 0xA).unwrap();
helper.cpu.set_program_counter(0x123);
// Start the CPU to ensure reset is processed
// Start the CPU to ensure reset is processed
// Note: We can't directly set running since base is private
// Instead, we'll call update() which should handle reset even when not running
println!("Before reset:");
println!(
" PC: 0x{:03X}, ACC: 0x{:X}, R0: 0x{:X}",
helper.cpu.get_program_counter(),
helper.cpu.get_accumulator(),
helper.cpu.get_register(0).unwrap()
);
// Apply reset signal (active low)
// First, set RESET pin to High (inactive)
helper
.cpu
.get_pin("RESET")
.unwrap()
.lock()
.unwrap()
.set_driver(Some("test".to_string()), PinValue::High);
helper.cpu.update();
// Now activate reset by setting RESET pin to Low (active low)
helper
.cpu
.get_pin("RESET")
.unwrap()
.lock()
.unwrap()
.set_driver(Some("test".to_string()), PinValue::Low);
// Process the reset signal for multiple cycles to ensure it's applied
for i in 0..5 {
helper.cpu.update();
thread::sleep(Duration::from_millis(1));
println!("Reset processing cycle {}", i + 1);
}
// Release reset signal
helper
.cpu
.get_pin("RESET")
.unwrap()
.lock()
.unwrap()
.set_driver(Some("test".to_string()), PinValue::High);
helper.cpu.update();
thread::sleep(Duration::from_millis(1));
println!("After reset:");
println!(
" PC: 0x{:03X}, ACC: 0x{:X}, R0: 0x{:X}",
helper.cpu.get_program_counter(),
helper.cpu.get_accumulator(),
helper.cpu.get_register(0).unwrap()
);
// Verify reset worked correctly
assert_eq!(
helper.cpu.get_program_counter(),
0,
"Program counter should be reset to 0"
);
assert_eq!(
helper.cpu.get_accumulator(),
0,
"Accumulator should be reset to 0"
);
assert_eq!(
helper.cpu.get_register(0).unwrap(),
0,
"Register 0 should be reset to 0"
);
println!("Reset test passed!");
}
#[test]
fn test_intel_4004_instruction_fetch() {
println!("=== Intel 4004 CPU Instruction Fetch Test ===");
// Create test helper with timeout protection
let config = TestConfig {
timeout_ms: 5000,
max_cycles: 1000,
_debug_level: 1,
};
let mut helper = CpuTestHelper::new(config);
// Reset and start CPU
helper.cpu.reset();
helper.cpu.update();
// Set up control pins for instruction fetch
helper.set_control_pins(true, true, false);
// Simulate instruction fetch cycle
println!("Starting instruction fetch sequence...");
// Phase 1: Address phase - drive PC on data bus
helper.drive_data_bus(0x0); // Low nibble of PC
helper.simulate_clock_cycle_unchecked();
println!("Address phase - drove PC low nibble");
helper.drive_data_bus(0x0); // High nibble of PC
helper.simulate_clock_cycle_unchecked();
println!("Address phase - drove PC high nibble");
// Phase 2: Wait latency
helper.tri_state_data_bus();
for i in 0..3 {
helper.simulate_clock_cycle_unchecked();
println!("Wait latency cycle {}", i + 1);
}
// Phase 3: Data phase - drive instruction on data bus
helper.drive_data_bus(0xF0); // CLB instruction
helper.simulate_clock_cycle_unchecked();
println!("Data phase - drove instruction 0xF0 (CLB)");
// Check if instruction was fetched
println!("After fetch:");
println!(" PC: 0x{:03X}", helper.cpu.get_program_counter());
// Execute the instruction
helper.set_control_pins(false, false, false);
helper.simulate_clock_cycle_unchecked();
println!("After execution:");
println!(
" PC: 0x{:03X}, ACC: 0x{:X}",
helper.cpu.get_program_counter(),
helper.cpu.get_accumulator()
);
println!("Instruction fetch test completed!");
}