Unified the 400x state machine for address latching
Fixed timing issues
This commit is contained in:
parent
e17d6ad177
commit
633463e370
26 changed files with 11904 additions and 2471 deletions
473
tests/intel_4004_state_machine_test.rs
Normal file
473
tests/intel_4004_state_machine_test.rs
Normal 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!");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue