rusty_emu/tests/virtual_time_tests.rs
Jan-Peter von Hunnius 08f53d68fe feat: implement virtual clock timing architecture (Phases 5-8)
Introduces a VirtualClock-based timing system replacing wall-clock
Instant-based timing for hardware-accurate simulation.

New modules:
- virtual_clock.rs: Shared virtual time source with condvar sync
- real_time_governor.rs: Thread that advances virtual time
- component_timing.rs: Per-component timing constraints

Key changes:
- Component trait: added update_at_time(), timing(), set_virtual_clock_optional()
- All Intel 400x components migrated to virtual-time-aware operation
- ConfigurableSystem: start() returns Result, spawns governor thread
- GUI: timing controls (speed slider, mode selector, step button)
- Console: speed/mode/step/time commands
- Backward compatible: legacy fallback for components without VirtualClock

Reviewed-by: code review found 2 warnings (clock resume() dead code,
reset() safety docs) — both fixed.
2026-05-14 21:00:22 +02:00

888 lines
32 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Virtual Time Integration Tests
//!
//! Tests for the virtual time architecture (Phase 9):
//! - MaxSpeed mode for fast deterministic execution
//! - SingleStep mode for precise state verification
//! - Overclocking tests verifying component timing violations
//! - Virtual clock integration with ConfigurableSystem
use std::sync::Arc;
use std::time::Duration;
use rusty_emu::component::Component;
use rusty_emu::component_timing::ComponentTiming;
use rusty_emu::components::common::intel_400x::*;
use rusty_emu::components::memory::intel_4001::Intel4001;
use rusty_emu::components::memory::intel_4002::Intel4002;
use rusty_emu::components::memory::intel_4003::Intel4003;
use rusty_emu::real_time_governor::RealTimeGovernor;
use rusty_emu::system_config::SystemFactory;
use rusty_emu::virtual_clock::{SimulationMode, VirtualClock};
// =============================================================================
// MaxSpeed Mode Tests
// =============================================================================
#[cfg(test)]
mod maxspeed_tests {
use super::*;
/// Verify that a VirtualClock in MaxSpeed mode advances time
/// when driven by a RealTimeGovernor.
#[test]
fn test_maxspeed_clock_advances() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Let the governor run for a bit (it calls clock.start() internally)
std::thread::sleep(Duration::from_millis(50));
// Virtual time should have advanced significantly
let vt = clock.now_ns();
assert!(vt > 0, "Virtual time should have advanced past 0");
// Verify we can read the time
assert!(clock.now() > Duration::ZERO, "Virtual time should be > 0");
clock.stop();
let _ = handle.join();
}
/// Verify that multiple time targets are reached in MaxSpeed mode.
#[test]
fn test_maxspeed_multiple_targets() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(100)));
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Let the governor advance time
std::thread::sleep(Duration::from_millis(50));
// Virtual time should have advanced well past multiple targets
let vt = clock.now_ns();
assert!(vt >= 1000, "Should have passed 1000ns, got {}", vt);
assert!(vt >= 5000, "Should have passed 5000ns, got {}", vt);
assert!(vt >= 10000, "Should have passed 10000ns, got {}", vt);
clock.stop();
let _ = handle.join();
}
/// Verify that component update_at_time works with virtual time in MaxSpeed mode.
/// Tests that a ROM component can be updated with specific virtual times.
#[test]
fn test_maxspeed_component_update_at_time() {
let mut rom = Intel4001::new_with_access_time("TestROM".to_string(), 500);
// Load test data
rom.load_rom_data(vec![0xAB, 0xCD], 0).unwrap();
// Simulate address latching at virtual time T=0
rom.set_timing_state(TimingState::Idle);
rom.set_address_high_nibble(Some(0x0));
rom.set_address_low_nibble(Some(0x0));
rom.set_full_address_ready(true);
rom.set_address_latch_time(Some(Duration::ZERO));
// At T=0 (before access_time), data should not be ready
let timing = ComponentTiming::intel_4001();
let elapsed = Duration::ZERO;
assert!(
timing.check_access_time(elapsed).is_err(),
"Access time should not have elapsed at T=0"
);
// At T=500ns (exactly access_time), data should be ready
let elapsed_500 = Duration::from_nanos(500);
assert!(
timing.check_access_time(elapsed_500).is_ok(),
"Access time should have elapsed at T=500ns"
);
// At T=600ns (after access_time), data should definitely be ready
let elapsed_600 = Duration::from_nanos(600);
assert!(
timing.check_access_time(elapsed_600).is_ok(),
"Access time should have elapsed at T=600ns"
);
}
/// Verify that a ROM with 1ns access time works correctly through
/// the full state machine in MaxSpeed mode.
#[test]
fn test_maxspeed_rom_fast_access() {
// Create ROM with 1ns access time (fast for testing)
let mut rom = Intel4001::new_with_access_time("FastROM".to_string(), 1);
rom.load_rom_data(vec![0x42], 0).unwrap();
// Drive through state machine with virtual time
// T=0: Idle state
assert_eq!(rom.get_timing_state(), TimingState::Idle);
// T=100ns: Set address (simulating PHI1 rising edge)
let t = Duration::from_nanos(100);
rom.set_address_high_nibble(Some(0x0));
rom.set_address_low_nibble(Some(0x0));
rom.set_full_address_ready(true);
rom.set_address_latch_time(Some(t));
rom.set_timing_state(TimingState::AddressPhase);
assert_eq!(rom.get_timing_state(), TimingState::AddressPhase);
// T=200ns: Wait for access time (1ns already elapsed)
rom.set_timing_state(TimingState::WaitLatency);
// T=300ns: Drive data
rom.set_timing_state(TimingState::DriveData);
assert_eq!(rom.get_timing_state(), TimingState::DriveData);
// Verify data is accessible
assert_eq!(rom.read_rom(0x00).unwrap(), 0x42);
}
}
// =============================================================================
// SingleStep Mode Tests
// =============================================================================
#[cfg(test)]
mod singlestep_tests {
use super::*;
/// Verify that each trigger_step() advances virtual time by exactly one tick.
#[test]
fn test_singlestep_advances_one_tick() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
clock.set_mode(SimulationMode::SingleStep);
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Wait for governor to start
std::thread::sleep(Duration::from_millis(50));
// Initial time should be 0
assert_eq!(clock.now_ns(), 0, "Initial time should be 0");
// Trigger one step
clock.trigger_step();
std::thread::sleep(Duration::from_millis(50));
assert_eq!(clock.now_ns(), 333, "After 1 step, time should be 333ns");
// Trigger another step
clock.trigger_step();
std::thread::sleep(Duration::from_millis(50));
assert_eq!(clock.now_ns(), 666, "After 2 steps, time should be 666ns");
// Trigger a third step
clock.trigger_step();
std::thread::sleep(Duration::from_millis(50));
assert_eq!(clock.now_ns(), 999, "After 3 steps, time should be 999ns");
clock.stop();
let _ = handle.join();
}
/// Verify that ROM state can be inspected at specific virtual times
/// using SingleStep mode.
#[test]
fn test_singlestep_rom_state_at_specific_time() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(100)));
clock.set_mode(SimulationMode::SingleStep);
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Wait for governor to start
std::thread::sleep(Duration::from_millis(50));
// Create ROM with 500ns access time (standard Intel 4001)
let mut rom = Intel4001::new_with_access_time("StepROM".to_string(), 500);
rom.load_rom_data(vec![0xAB], 0).unwrap();
// Trigger 1 step → T=100ns — latch address
clock.trigger_step();
std::thread::sleep(Duration::from_millis(20));
let t_latch = clock.now();
assert!(t_latch >= Duration::from_nanos(100));
rom.set_address_latch_time(Some(t_latch));
rom.set_address_high_nibble(Some(0x0));
rom.set_address_low_nibble(Some(0x0));
rom.set_full_address_ready(true);
// At T=100ns, 0ns have elapsed since latch — access time NOT met
let timing = ComponentTiming::intel_4001();
assert!(
timing.check_access_time(Duration::ZERO).is_err(),
"At T=100ns (0ns since latch), access time (500ns) should NOT be met"
);
// Trigger 4 more steps → T=500ns (4 × 100ns = 400ns more)
for _ in 0..4 {
clock.trigger_step();
std::thread::sleep(Duration::from_millis(20));
}
// At T=500ns, 400ns have elapsed since latch at T=100ns — still NOT enough
let elapsed_400 = Duration::from_nanos(400);
assert!(
timing.check_access_time(elapsed_400).is_err(),
"At T=500ns (400ns since latch at T=100ns), access time (500ns) should NOT be met"
);
// Trigger 1 more step → T=600ns
clock.trigger_step();
std::thread::sleep(Duration::from_millis(20));
// At T=600ns, 500ns have elapsed since latch at T=100ns — exactly access time
let elapsed_500 = Duration::from_nanos(500);
assert!(
timing.check_access_time(elapsed_500).is_ok(),
"At T=600ns (500ns since latch at T=100ns), access time (500ns) SHOULD be met"
);
clock.stop();
let _ = handle.join();
}
/// Verify that trigger_step returns false when not in SingleStep mode.
#[test]
fn test_trigger_step_only_works_in_singlestep() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
// Default mode is RealTime — trigger_step should fail
assert!(
!clock.trigger_step(),
"trigger_step should return false in RealTime mode"
);
// Switch to MaxSpeed — trigger_step should still fail
clock.set_mode(SimulationMode::MaxSpeed);
assert!(
!clock.trigger_step(),
"trigger_step should return false in MaxSpeed mode"
);
// Switch to SingleStep — now it should work
clock.set_mode(SimulationMode::SingleStep);
assert!(
clock.trigger_step(),
"trigger_step should return true in SingleStep mode"
);
}
/// Verify precise tick count tracking in SingleStep mode.
#[test]
fn test_singlestep_tick_count() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(100)));
clock.set_mode(SimulationMode::SingleStep);
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Wait for governor to start
std::thread::sleep(Duration::from_millis(50));
assert_eq!(clock.tick_count(), 0, "Initial tick count should be 0");
// Trigger 5 steps
for _ in 0..5 {
clock.trigger_step();
std::thread::sleep(Duration::from_millis(20));
}
// After 5 ticks of 100ns each
assert_eq!(clock.now_ns(), 500, "After 5 steps, time should be 500ns");
assert_eq!(clock.tick_count(), 5, "After 5 steps, tick_count should be 5");
clock.stop();
let _ = handle.join();
}
}
// =============================================================================
// Overclocking Tests
// =============================================================================
#[cfg(test)]
mod overclocking_tests {
use super::*;
/// Verify that ROM (500ns access time) fails timing check at 2 MHz.
///
/// At 2 MHz, the clock phase is 250ns. The ROM needs 500ns from address
/// valid to data ready, but only gets 250ns — a timing violation.
#[test]
fn test_overclocking_rom_fails_at_2mhz() {
let rom_timing = ComponentTiming::intel_4001();
// 2 MHz clock → 500ns cycle → 250ns per phase
let phase_time = Duration::from_nanos(250);
// ROM has 500ns access time, but phase is only 250ns
assert!(
rom_timing.check_access_time(phase_time).is_err(),
"ROM (500ns access) should FAIL at 2 MHz (250ns phase)"
);
// Also verify: ROM does NOT support 2 MHz
assert!(
!rom_timing.supports_frequency(2_000_000.0),
"ROM should NOT support 2 MHz (max is 750 kHz)"
);
}
/// Verify that RAM (500ns access time) fails timing check at 2 MHz.
#[test]
fn test_overclocking_ram_fails_at_2mhz() {
let ram_timing = ComponentTiming::intel_4002();
// 2 MHz clock → 250ns per phase
let phase_time = Duration::from_nanos(250);
// RAM has 500ns access time, but phase is only 250ns
assert!(
ram_timing.check_access_time(phase_time).is_err(),
"RAM (500ns access) should FAIL at 2 MHz (250ns phase)"
);
assert!(
!ram_timing.supports_frequency(2_000_000.0),
"RAM should NOT support 2 MHz (max is 750 kHz)"
);
}
/// Verify that the shift register (200ns access time) STILL WORKS at 2 MHz.
///
/// The Intel 4003 has a faster 200ns access time, so at 2 MHz (250ns phase),
/// there's still enough time for data to become valid. This models real
/// hardware behavior where faster chips continue working when slower ones fail.
#[test]
fn test_shift_register_works_at_higher_frequency() {
let sr_timing = ComponentTiming::intel_4003();
// 2 MHz clock → 250ns per phase
let phase_time = Duration::from_nanos(250);
// Shift register has 200ns access time, phase is 250ns — PLENTY of time
assert!(
sr_timing.check_access_time(phase_time).is_ok(),
"Shift register (200ns access) should WORK at 2 MHz (250ns phase)"
);
// Shift register supports 1 MHz
assert!(
sr_timing.supports_frequency(1_000_000.0),
"Shift register should support 1 MHz"
);
// But NOT 2 MHz (max is 1 MHz)
assert!(
!sr_timing.supports_frequency(2_000_000.0),
"Shift register should NOT support 2 MHz (max is 1 MHz)"
);
// However, the access time check passes because 250ns > 200ns.
// This is the key insight: access_time and max_clock_frequency are
// independent constraints. The access time is met (250ns >= 200ns)
// even though the frequency exceeds the spec.
}
/// Verify the overclocking scenario from the timing architecture plan:
/// At 2 MHz, ROM and RAM fail while the shift register keeps working.
#[test]
fn test_overclocking_scenario_comprehensive() {
let rom_timing = ComponentTiming::intel_4001();
let ram_timing = ComponentTiming::intel_4002();
let sr_timing = ComponentTiming::intel_4003();
let cpu_timing = ComponentTiming::intel_4004();
// 2 MHz clock: phase = 250ns
let phase_time_2mhz = Duration::from_nanos(250);
// ROM: 500ns access, 250ns available → FAIL
assert!(rom_timing.check_access_time(phase_time_2mhz).is_err());
// RAM: 500ns access, 250ns available → FAIL
assert!(ram_timing.check_access_time(phase_time_2mhz).is_err());
// Shift register: 200ns access, 250ns available → OK
assert!(sr_timing.check_access_time(phase_time_2mhz).is_ok());
// CPU: 0ns access time → always OK (CPU doesn't have "access time")
assert!(cpu_timing.check_access_time(phase_time_2mhz).is_ok());
// Now at standard 750 kHz: phase = 666ns
let phase_time_750khz = Duration::from_nanos(666);
// All components should work at 750 kHz
assert!(rom_timing.check_access_time(phase_time_750khz).is_ok());
assert!(ram_timing.check_access_time(phase_time_750khz).is_ok());
assert!(sr_timing.check_access_time(phase_time_750khz).is_ok());
assert!(cpu_timing.check_access_time(phase_time_750khz).is_ok());
}
/// Verify that ComponentTiming::min_clock_period() returns correct values.
#[test]
fn test_min_clock_period_per_component() {
let rom_timing = ComponentTiming::intel_4001();
let ram_timing = ComponentTiming::intel_4002();
let sr_timing = ComponentTiming::intel_4003();
// ROM: max 750 kHz → min period ~1333ns
let rom_period = rom_timing.min_clock_period();
assert!(
rom_period > Duration::from_nanos(1300) && rom_period < Duration::from_nanos(1400),
"ROM min clock period should be ~1333ns, got {:?}",
rom_period
);
// RAM: max 750 kHz → min period ~1333ns
let ram_period = ram_timing.min_clock_period();
assert!(
ram_period > Duration::from_nanos(1300) && ram_period < Duration::from_nanos(1400),
"RAM min clock period should be ~1333ns, got {:?}",
ram_period
);
// Shift register: max 1 MHz → min period 1000ns
let sr_period = sr_timing.min_clock_period();
assert!(
sr_period > Duration::from_nanos(900) && sr_period < Duration::from_nanos(1100),
"Shift register min clock period should be ~1000ns, got {:?}",
sr_period
);
}
/// Verify setup time and hold time checks work correctly.
#[test]
fn test_setup_and_hold_time_checks() {
let rom_timing = ComponentTiming::intel_4001();
// Setup time: 100ns
assert!(rom_timing.check_setup_time(Duration::from_nanos(150)).is_ok());
assert!(rom_timing.check_setup_time(Duration::from_nanos(100)).is_ok());
assert!(rom_timing.check_setup_time(Duration::from_nanos(50)).is_err());
// Hold time: 50ns
assert!(rom_timing.check_hold_time(Duration::from_nanos(100)).is_ok());
assert!(rom_timing.check_hold_time(Duration::from_nanos(50)).is_ok());
assert!(rom_timing.check_hold_time(Duration::from_nanos(25)).is_err());
}
/// Verify that timing violations contain meaningful error information.
#[test]
fn test_timing_violation_error_details() {
let rom_timing = ComponentTiming::intel_4001();
let err = rom_timing.check_access_time(Duration::from_nanos(100));
assert!(err.is_err());
let err_msg = format!("{:?}", err.unwrap_err());
assert!(
err_msg.contains("Intel 4001 ROM") || err_msg.contains("4001"),
"Violation should mention component name: {}",
err_msg
);
}
}
// =============================================================================
// Virtual Clock Integration Tests
// =============================================================================
#[cfg(test)]
mod virtual_clock_integration_tests {
use super::*;
/// Verify that ConfigurableSystem creates and exposes a VirtualClock.
#[test]
fn test_configurable_system_creates_virtual_clock() {
let factory = SystemFactory::new();
let system = factory.create_from_json("configs/mcs4_basic.json").unwrap();
// The system should have a virtual clock
let clock = system.get_virtual_clock();
assert!(Arc::strong_count(clock) >= 1, "VirtualClock Arc should exist");
// Clock should have correct tick size for 750 kHz (333ns per quarter-cycle)
let tick_size = clock.tick_size();
assert!(
tick_size > Duration::ZERO,
"Tick size should be non-zero"
);
// For 750 kHz, cycle time is ~1333ns, quarter-cycle is ~333ns
assert_eq!(
tick_size,
Duration::from_nanos(333),
"Tick size should be 333ns for 750 kHz"
);
}
/// Verify that ConfigurableSystem's VirtualClock can be put in MaxSpeed mode.
#[test]
fn test_configurable_system_maxspeed_mode() {
let factory = SystemFactory::new();
let system = factory.create_from_json("configs/mcs4_basic.json").unwrap();
let clock = system.get_virtual_clock();
clock.set_mode(SimulationMode::MaxSpeed);
assert_eq!(clock.mode(), SimulationMode::MaxSpeed);
}
/// Verify that clock.stop() causes the governor thread to exit cleanly.
#[test]
fn test_virtual_clock_stop_exits_governor() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Let the governor start and advance time
std::thread::sleep(Duration::from_millis(50));
assert!(clock.now_ns() > 0, "Clock should have advanced");
// Stop the clock
clock.stop();
// Governor thread should exit cleanly
let join_result = handle.join();
assert!(
join_result.is_ok(),
"Governor thread should exit cleanly after stop"
);
}
/// Verify that clock.stop() causes wait_until to return None.
#[test]
fn test_stop_unblocks_wait_until() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
let governor = RealTimeGovernor::with_defaults(clock.clone());
let clock_clone = Arc::clone(&clock);
let handle = std::thread::spawn(move || {
governor.run();
});
// Let the governor start
std::thread::sleep(Duration::from_millis(50));
// Now stop the clock
clock.stop();
// wait_until for a far-future time should return None immediately
let result = clock_clone.wait_until(Duration::from_secs(100));
assert!(
result.is_none(),
"wait_until should return None after clock is stopped"
);
let _ = handle.join();
}
/// Verify pause/resume behavior: time stops advancing when paused.
#[test]
fn test_pause_resume_behavior() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Let it run
std::thread::sleep(Duration::from_millis(50));
let time_before_pause = clock.now_ns();
assert!(time_before_pause > 0);
// Pause the clock
clock.pause();
assert!(clock.is_paused());
// Wait a short wall-clock time, then check virtual time hasn't advanced
std::thread::sleep(Duration::from_millis(50));
let time_after_pause = clock.now_ns();
// Virtual time should not have advanced significantly while paused
// (may have advanced by 1-2 ticks due to race, but not more)
let diff = time_after_pause.saturating_sub(time_before_pause);
assert!(
diff < 1000,
"Virtual time should not advance significantly while paused. \
Before: {}, After: {}, Diff: {}",
time_before_pause, time_after_pause, diff
);
// Resume the clock
clock.resume();
assert!(!clock.is_paused());
// Wait for time to advance again
std::thread::sleep(Duration::from_millis(50));
let time_after_resume = clock.now_ns();
assert!(
time_after_resume > time_after_pause,
"Virtual time should advance after resume"
);
clock.stop();
let _ = handle.join();
}
/// Verify that the VirtualClock reset works correctly.
#[test]
fn test_virtual_clock_reset() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Advance time
std::thread::sleep(Duration::from_millis(50));
assert!(clock.now_ns() > 0);
// Stop and reset
clock.stop();
let _ = handle.join();
// Reset time to zero
clock.reset();
// Verify time is back to zero
assert_eq!(clock.now(), Duration::ZERO);
assert_eq!(clock.tick_count(), 0);
}
/// Verify speed multiplier can be changed dynamically.
#[test]
fn test_speed_multiplier_change() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
// Default speed is 1.0x
assert!((clock.speed_multiplier() - 1.0).abs() < 0.001);
// Set to 0.5x
clock.set_speed_multiplier(0.5);
assert!((clock.speed_multiplier() - 0.5).abs() < 0.001);
// Set to 10.0x
clock.set_speed_multiplier(10.0);
assert!((clock.speed_multiplier() - 10.0).abs() < 0.01);
// Set to 0.001x (very slow)
clock.set_speed_multiplier(0.001);
assert!((clock.speed_multiplier() - 0.001).abs() < 0.0001);
}
/// Verify that has_reached works correctly for non-blocking time checks.
#[test]
fn test_has_reached_nonblocking() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(100)));
let governor = RealTimeGovernor::with_defaults(clock.clone());
let handle = std::thread::spawn(move || {
governor.run();
});
// Initially, time is 0
assert!(clock.has_reached(Duration::ZERO));
assert!(!clock.has_reached(Duration::from_nanos(1)));
// Wait for time to advance
std::thread::sleep(Duration::from_millis(50));
// Now it should have reached various targets
assert!(clock.has_reached(Duration::from_nanos(100)));
assert!(clock.has_reached(Duration::from_nanos(1000)));
assert!(clock.has_reached(Duration::from_micros(5)));
clock.stop();
let _ = handle.join();
}
/// Verify tick_size can be changed dynamically.
#[test]
fn test_tick_size_change() {
let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333)));
assert_eq!(clock.tick_size(), Duration::from_nanos(333));
clock.set_tick_size(Duration::from_nanos(100));
assert_eq!(clock.tick_size(), Duration::from_nanos(100));
clock.set_tick_size(Duration::from_nanos(500));
assert_eq!(clock.tick_size(), Duration::from_nanos(500));
}
}
// =============================================================================
// Component Timing with Virtual Time Tests
// =============================================================================
#[cfg(test)]
mod component_virtual_time_tests {
use super::*;
/// Verify that Intel4001 implements the timing() method correctly.
#[test]
fn test_intel4001_timing_profile() {
let rom = Intel4001::new("TestROM".to_string());
let timing = rom.timing();
assert_eq!(timing.name, "Intel 4001 ROM");
assert_eq!(timing.access_time, Duration::from_nanos(500));
assert_eq!(timing.propagation_delay, Duration::from_nanos(200));
assert_eq!(timing.setup_time, Duration::from_nanos(100));
assert_eq!(timing.hold_time, Duration::from_nanos(50));
assert!((timing.max_clock_frequency - 750_000.0).abs() < 1.0);
}
/// Verify that Intel4002 implements the timing() method correctly.
#[test]
fn test_intel4002_timing_profile() {
let ram = Intel4002::new("TestRAM".to_string());
let timing = ram.timing();
assert_eq!(timing.name, "Intel 4002 RAM");
assert_eq!(timing.access_time, Duration::from_nanos(500));
assert_eq!(timing.propagation_delay, Duration::from_nanos(200));
}
/// Verify that Intel4003 implements the timing() method correctly.
#[test]
fn test_intel4003_timing_profile() {
let sr = Intel4003::new("TestSR".to_string());
let timing = sr.timing();
assert_eq!(timing.name, "Intel 4003 Shift Register");
assert_eq!(timing.access_time, Duration::from_nanos(200));
assert_eq!(timing.propagation_delay, Duration::from_nanos(100));
assert!((timing.max_clock_frequency - 1_000_000.0).abs() < 1.0);
}
/// Verify that update_at_time() works correctly for ROM component.
/// This tests the virtual-time-aware update path.
#[test]
fn test_rom_update_at_time() {
let mut rom = Intel4001::new_with_access_time("TestROM".to_string(), 1);
rom.load_rom_data(vec![0x42], 0).unwrap();
rom.set_running(true);
// Call update_at_time at various virtual times
// This should work without panicking
rom.update_at_time(Duration::from_nanos(0));
rom.update_at_time(Duration::from_nanos(333));
rom.update_at_time(Duration::from_nanos(666));
rom.update_at_time(Duration::from_nanos(999));
// Component should still function correctly
assert_eq!(rom.read_rom(0x00).unwrap(), 0x42);
}
/// Verify that update_at_time() works correctly for RAM component.
#[test]
fn test_ram_update_at_time() {
let mut ram = Intel4002::new("TestRAM".to_string());
ram.set_running(true);
// Call update_at_time at various virtual times
rom_update_at_time_helper(&mut ram);
}
fn rom_update_at_time_helper(ram: &mut Intel4002) {
ram.update_at_time(Duration::from_nanos(0));
ram.update_at_time(Duration::from_nanos(333));
ram.update_at_time(Duration::from_nanos(666));
}
/// Verify that update_at_time() works correctly for shift register component.
#[test]
fn test_sr_update_at_time() {
let mut sr = Intel4003::new("TestSR".to_string());
sr.set_running(true);
// Call update_at_time at various virtual times
sr.update_at_time(Duration::from_nanos(0));
sr.update_at_time(Duration::from_nanos(333));
sr.update_at_time(Duration::from_nanos(666));
// Should not panic
}
/// Test that address latch time tracking works with virtual time.
#[test]
fn test_address_latch_time_virtual_time() {
let mut rom = Intel4001::new_with_access_time("TestROM".to_string(), 500);
// Set address latch time to a specific virtual time
let latch_time = Duration::from_nanos(1000);
rom.set_address_latch_time(Some(latch_time));
assert_eq!(rom.get_address_latch_time(), Some(latch_time));
// Clear it
rom.set_address_latch_time(None);
assert_eq!(rom.get_address_latch_time(), None);
}
/// Verify that the timing state machine works correctly with virtual time.
#[test]
fn test_timing_state_machine_with_virtual_time() {
let mut rom = Intel4001::new_with_access_time("TestROM".to_string(), 500);
// Start in Idle
assert_eq!(rom.get_timing_state(), TimingState::Idle);
// Simulate a complete memory access cycle with virtual time
let t0 = Duration::ZERO; // Start
let _t1 = Duration::from_nanos(333); // Address phase
let _t2 = Duration::from_nanos(666); // Wait latency
let t3 = Duration::from_nanos(999); // Drive data
// T0: Idle
rom.update_at_time(t0);
assert_eq!(rom.get_timing_state(), TimingState::Idle);
// T1: Address latched
let t1 = Duration::from_nanos(333);
rom.set_address_latch_time(Some(t1));
rom.set_timing_state(TimingState::AddressPhase);
assert_eq!(rom.get_timing_state(), TimingState::AddressPhase);
// T2: Waiting for access time
rom.set_timing_state(TimingState::WaitLatency);
assert_eq!(rom.get_timing_state(), TimingState::WaitLatency);
// T3: Access time (666ns) > 500ns → data ready
let elapsed = t3.saturating_sub(t1);
let timing = ComponentTiming::intel_4001();
assert!(
timing.check_access_time(elapsed).is_ok(),
"After 666ns, access time (500ns) should be met"
);
rom.set_timing_state(TimingState::DriveData);
assert_eq!(rom.get_timing_state(), TimingState::DriveData);
// Return to idle
rom.set_timing_state(TimingState::Idle);
assert_eq!(rom.get_timing_state(), TimingState::Idle);
}
}