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.
This commit is contained in:
parent
add7ace674
commit
08f53d68fe
28 changed files with 7186 additions and 270 deletions
|
|
@ -3,9 +3,10 @@ use std::any::Any;
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::component::{BaseComponent, Component, RunnableComponent};
|
||||
use crate::component_timing::ComponentTiming;
|
||||
use crate::components::common::intel_400x::{
|
||||
process_event_direct, Intel400xAddressHandling, Intel400xClockHandling,
|
||||
Intel400xComponentBehavior, Intel400xComponentType, Intel400xControlPins, Intel400xDataBus,
|
||||
|
|
@ -13,6 +14,7 @@ use crate::components::common::intel_400x::{
|
|||
Intel400xUnifiedState, TimingState,
|
||||
};
|
||||
use crate::pin::{Pin, PinValue};
|
||||
use crate::virtual_clock::VirtualClock;
|
||||
|
||||
/// Intel 4003 - 10-bit serial-in, serial-out shift register
|
||||
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
|
||||
|
|
@ -33,6 +35,13 @@ pub struct Intel4003 {
|
|||
// Clock edge detection
|
||||
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
|
||||
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
|
||||
// Virtual time support (Phase 6)
|
||||
virtual_clock: Option<Arc<VirtualClock>>, // Injected by ConfigurableSystem (Phase 7)
|
||||
/// Virtual time snapshot for the current update cycle.
|
||||
/// Set at the start of `update_at_time()` so all internal methods use a
|
||||
/// consistent time value instead of reading `VirtualClock::now()` which
|
||||
/// may have advanced since the cycle started.
|
||||
current_update_time: Duration,
|
||||
}
|
||||
|
||||
/// Serial operation modes for 4003 shift register
|
||||
|
|
@ -121,11 +130,11 @@ impl Intel400xTimingState for Intel4003 {
|
|||
self.state_machine.current_state = unified_state;
|
||||
}
|
||||
|
||||
fn get_address_latch_time(&self) -> Option<Instant> {
|
||||
fn get_address_latch_time(&self) -> Option<Duration> {
|
||||
self.state_machine.get_address_latch_time()
|
||||
}
|
||||
|
||||
fn set_address_latch_time(&mut self, time: Option<Instant>) {
|
||||
fn set_address_latch_time(&mut self, time: Option<Duration>) {
|
||||
self.state_machine.set_address_latch_time(time);
|
||||
}
|
||||
|
||||
|
|
@ -156,6 +165,10 @@ impl Intel400xTimingState for Intel4003 {
|
|||
fn get_access_time(&self) -> Duration {
|
||||
self.state_machine.get_access_time()
|
||||
}
|
||||
|
||||
fn is_latency_elapsed_at(&self, virtual_time: Duration) -> bool {
|
||||
self.state_machine.is_latency_elapsed_at(virtual_time)
|
||||
}
|
||||
}
|
||||
|
||||
impl Intel400xComponentBehavior for Intel4003 {
|
||||
|
|
@ -243,9 +256,22 @@ impl Intel400xComponentBehavior for Intel4003 {
|
|||
self.handle_address_latching();
|
||||
}
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Check if latency has elapsed
|
||||
if self.state_machine.is_latency_elapsed() {
|
||||
// Transition to ShiftData (default for 4003)
|
||||
// Check if latency has elapsed using virtual time.
|
||||
//
|
||||
// **With VirtualClock (production):** `address_latch_time` was recorded
|
||||
// by `handle_address_nibble_at()` in virtual time. We check if enough
|
||||
// virtual time has elapsed since then (≥ 200ns access time).
|
||||
//
|
||||
// **Without VirtualClock (standalone tests):** `current_virtual_time()`
|
||||
// returns `Duration::ZERO`, so `is_latency_elapsed_at()` would always
|
||||
// return `false`. The `!has_clock` guard short-circuits to skip the wait.
|
||||
//
|
||||
// NOTE: The old `Instant::now()`-based code would naturally wait ~200ns.
|
||||
// This new code skips the wait in legacy mode, making standalone tests
|
||||
// less hardware-accurate but preventing them from hanging in WaitLatency.
|
||||
let has_clock = self.virtual_clock.is_some();
|
||||
let vtime = self.current_update_time;
|
||||
if !has_clock || self.state_machine.is_latency_elapsed_at(vtime) {
|
||||
self.process_latency_elapsed();
|
||||
}
|
||||
}
|
||||
|
|
@ -317,9 +343,30 @@ impl Intel4003 {
|
|||
),
|
||||
prev_phi1: PinValue::Low,
|
||||
prev_phi2: PinValue::Low,
|
||||
virtual_clock: None,
|
||||
current_update_time: Duration::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inject a VirtualClock for virtual time-based operation.
|
||||
/// When set, `update()` uses virtual time and `run()` uses `wait_until()`.
|
||||
/// This method is called by ConfigurableSystem in Phase 7.
|
||||
pub fn set_virtual_clock(&mut self, clock: Arc<VirtualClock>) {
|
||||
self.virtual_clock = Some(clock);
|
||||
}
|
||||
|
||||
/// Get current virtual time, or Duration::ZERO if no VirtualClock is injected.
|
||||
///
|
||||
/// # Zero-Time Invariant
|
||||
///
|
||||
/// When no VirtualClock is injected, this returns `Duration::ZERO`. The `WaitLatency`
|
||||
/// handler checks `vtime == Duration::ZERO` to skip latency enforcement in legacy mode.
|
||||
/// This is intentional: legacy tests don't use VirtualClock and expect latency to be
|
||||
/// skipped for backward compatibility.
|
||||
fn current_virtual_time(&self) -> Duration {
|
||||
self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now())
|
||||
}
|
||||
|
||||
/// Set access time for simulation
|
||||
/// Parameters: access_time_ns - Access time in nanoseconds
|
||||
pub fn set_access_time(&mut self, access_time_ns: u64) {
|
||||
|
|
@ -419,8 +466,9 @@ impl Intel4003 {
|
|||
// But we still need to handle the address nibble for compatibility
|
||||
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
|
||||
|
||||
if self.state_machine.handle_address_nibble(nibble) {
|
||||
// Address is complete, transition to latency wait
|
||||
if self.state_machine.handle_address_nibble_at(nibble, self.current_update_time) {
|
||||
// Address is complete — virtual time was recorded atomically by handle_address_nibble_at()
|
||||
// Transition to latency wait
|
||||
self.process_address_complete();
|
||||
}
|
||||
}
|
||||
|
|
@ -520,9 +568,20 @@ impl Component for Intel4003 {
|
|||
self.base.get_pin(name)
|
||||
}
|
||||
|
||||
/// Main update cycle - handles clock edge detection and operation dispatch
|
||||
/// Main update cycle - delegates to update_at_time with current virtual time
|
||||
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
|
||||
fn update(&mut self) {
|
||||
let vtime = self.current_virtual_time();
|
||||
self.update_at_time(vtime);
|
||||
}
|
||||
|
||||
/// Virtual-time-aware update cycle - handles clock edge detection and operation dispatch
|
||||
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
|
||||
fn update_at_time(&mut self, virtual_time: Duration) {
|
||||
// Snapshot virtual_time for the entire update cycle so internal methods
|
||||
// use a consistent time instead of reading VirtualClock::now() which
|
||||
// may have advanced.
|
||||
self.current_update_time = virtual_time;
|
||||
if !self.is_running() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -581,7 +640,7 @@ impl Component for Intel4003 {
|
|||
self.execute_state_machine();
|
||||
}
|
||||
|
||||
/// Run component in time-slice mode (manual control)
|
||||
/// Run component with VirtualClock support or legacy fallback
|
||||
/// Hardware: Simulates continuous operation with clock edge detection
|
||||
fn run(&mut self) {
|
||||
self.base.set_running(true);
|
||||
|
|
@ -591,13 +650,37 @@ impl Component for Intel4003 {
|
|||
self.prev_phi1 = phi1;
|
||||
self.prev_phi2 = phi2;
|
||||
|
||||
// If no VirtualClock is injected, fall back to wall-clock run loop
|
||||
let vclock = match self.virtual_clock.as_ref() {
|
||||
Some(vc) => Arc::clone(vc),
|
||||
None => {
|
||||
// Legacy mode: use wall-clock timing
|
||||
while self.is_running() {
|
||||
self.update();
|
||||
thread::sleep(Duration::from_micros(1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Virtual time mode: wait for next tick to synchronize with clock
|
||||
while self.is_running() {
|
||||
self.update();
|
||||
// Small delay to prevent busy waiting when no clock is present
|
||||
thread::sleep(Duration::from_micros(1));
|
||||
let next_tick = vclock.now() + vclock.tick_size();
|
||||
if let Some(current_vtime) = vclock.wait_until(next_tick) {
|
||||
self.update_at_time(current_vtime);
|
||||
} else {
|
||||
// Clock was interrupted, exit
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get timing constraints for Intel 4003 shift register
|
||||
/// Returns: ComponentTiming with 200ns access time, 100ns propagation delay
|
||||
fn timing(&self) -> ComponentTiming {
|
||||
ComponentTiming::intel_4003()
|
||||
}
|
||||
|
||||
/// Stop component and tri-state all outputs
|
||||
/// Hardware: Component enters low-power state, all pins high-impedance
|
||||
fn stop(&mut self) {
|
||||
|
|
@ -622,6 +705,14 @@ impl Component for Intel4003 {
|
|||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn set_virtual_clock_optional(&mut self, clock: Option<Arc<VirtualClock>>) {
|
||||
self.virtual_clock = clock;
|
||||
}
|
||||
|
||||
fn has_virtual_clock(&self) -> bool {
|
||||
self.virtual_clock.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl RunnableComponent for Intel4003 {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue