371 lines
13 KiB
Rust
371 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 should stay in AddressPhase
|
|
// (address nibbles are latched on Φ1 falling edge, not rising)
|
|
{
|
|
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 AddressPhase (latching happens on falling edge)
|
|
assert_eq!(
|
|
rom.get_timing_state(),
|
|
TimingState::AddressPhase,
|
|
"Should still be in AddressPhase after Φ1 rising - latching happens on falling edge"
|
|
);
|
|
println!(
|
|
"State after Φ1 rising (before falling): {:?}",
|
|
rom.get_timing_state()
|
|
);
|
|
|
|
// Reset Φ1 to Low to trigger falling edge - this is when low nibble is latched
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
|
|
// Update to process Φ1 falling edge - low nibble is latched here
|
|
rom.update();
|
|
|
|
// Should have latched low nibble and transitioned to WaitLatency or DriveData
|
|
// With 1ns access time, it might transition directly to DriveData
|
|
let state_after_low_nibble = rom.get_timing_state();
|
|
assert!(
|
|
state_after_low_nibble == TimingState::WaitLatency
|
|
|| state_after_low_nibble == TimingState::DriveData,
|
|
"Expected WaitLatency or DriveData after latching both nibbles, got {:?}",
|
|
state_after_low_nibble
|
|
);
|
|
println!(
|
|
"State after Φ1 falling (low nibble 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");
|
|
}
|
|
|
|
// If still in WaitLatency, trigger Φ2 rising edge to check latency and drive data
|
|
if rom.get_timing_state() == TimingState::WaitLatency {
|
|
// 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 have transitioned to DriveData
|
|
assert_eq!(rom.get_timing_state(), TimingState::DriveData);
|
|
println!("State after Φ2 rising: {:?}", rom.get_timing_state());
|
|
}
|
|
|
|
// Wait for pins to settle after ROM drives data
|
|
thread::sleep(Duration::from_millis(1));
|
|
|
|
// Verify data is being driven on the bus
|
|
// Data 0xAB has low nibble 0xB = 1011 (D0=1, D1=1, D2=0, D3=1)
|
|
// Note: Due to bit ordering, D0 should be High, D1 should be High, D2 should be Low, D3 should be High
|
|
assert_eq!(d0_pin.lock().read(), PinValue::High);
|
|
assert_eq!(d1_pin.lock().read(), PinValue::High);
|
|
assert_eq!(d2_pin.lock().read(), PinValue::Low);
|
|
assert_eq!(d3_pin.lock().read(), PinValue::High);
|
|
|
|
// Reset Φ2 to Low
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
|
|
// Update to process Φ2 falling edge
|
|
rom.update();
|
|
|
|
// Should have returned to Idle
|
|
assert_eq!(rom.get_timing_state(), TimingState::Idle);
|
|
println!("Final state: {:?}", rom.get_timing_state());
|
|
|
|
// Verify data bus is tri-stated
|
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
|
}
|
|
|
|
/// 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);
|
|
}
|