//! 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>, phi2: Arc>, 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(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!"); }