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.
29 KiB
Phase 5: Migrate Clock Components to Virtual Time
Overview
Phase 5 replaces Instant::now() and thread::sleep() in both clock components with virtual time from Arc<VirtualClock>. This is the first phase that modifies existing components rather than adding new infrastructure.
Files modified:
generic_clock.rs— GenericClocktwo_phase_clock.rs— TwoPhaseClocksystem_config.rs— Clock factory closures (minor)
Files NOT modified (Phase 7 responsibility):
virtual_clock.rs— Already completereal_time_governor.rs— Already completecomponent.rs— Already hasupdate_at_time()default
Implementation Order
Migrate GenericClock first, then TwoPhaseClock.
Rationale:
GenericClockis simpler — single-phase, single CLK outputTwoPhaseClockhas dual outputs (PHI1/PHI2) and inter-phase dependencies- Both follow the same pattern, so GenericClock establishes the template
GenericClockhas existing unit tests to validate the migration
Part A: GenericClock Migration
A1. Current Timing Mechanism
GenericClock uses wall-clock time in 5 locations:
| Location | Line | Current Code | Purpose |
|---|---|---|---|
last_transition field |
16 | last_transition: Instant |
Track when last phase change occurred |
pause_start field |
21 | pause_start: Option<Instant> |
Track when pause began |
new() constructor |
34 | last_transition: Instant::now() |
Initialize transition timer |
enable() |
68 | self.last_transition = Instant::now() |
Reset timer on enable |
pause() |
80 | self.pause_start = Some(Instant::now()) |
Record pause start |
resume() |
88 | pause_start.elapsed() |
Calculate pause duration |
should_transition() |
131 | self.last_transition.elapsed() |
Check if phase duration elapsed |
perform_transition() |
149 | self.last_transition = Instant::now() |
Record transition time |
run() |
205 | thread::sleep(sleep_time) |
Busy-wait loop with sleep |
A2. Required Changes
A2.1 — Add VirtualClock field (optional, gradual integration)
Replace Instant-based fields with virtual-time equivalents. Add Option<Arc<VirtualClock>> for gradual integration — clocks work without a VirtualClock (falling back to wall-clock), but use virtual time when one is injected.
// BEFORE (line 11-22):
pub struct GenericClock {
base: BaseComponent,
frequency: f64,
duty_cycle: f64,
current_state: PinValue,
last_transition: Instant,
high_time: Duration,
low_time: Duration,
enabled: bool,
paused: bool,
pause_start: Option<Instant>,
}
// AFTER:
pub struct GenericClock {
base: BaseComponent,
frequency: f64,
duty_cycle: f64,
current_state: PinValue,
last_transition_vtime: Duration, // Virtual time of last transition
high_time: Duration,
low_time: Duration,
enabled: bool,
paused: bool,
pause_start_vtime: Option<Duration>, // Virtual time when pause started
virtual_clock: Option<Arc<VirtualClock>>, // Injected in Phase 7
}
Key change: last_transition: Instant → last_transition_vtime: Duration (virtual nanoseconds since sim start). pause_start: Option<Instant> → pause_start_vtime: Option<Duration>.
A2.2 — Update constructor
// BEFORE (line 29-43):
let mut clock = GenericClock {
base: BaseComponent::new(name, pins),
frequency,
duty_cycle: 0.5,
current_state: PinValue::Low,
last_transition: Instant::now(),
high_time: Duration::from_secs_f64(0.5 / frequency),
low_time: Duration::from_secs_f64(0.5 / frequency),
enabled: true,
paused: false,
pause_start: None,
};
// AFTER:
let mut clock = GenericClock {
base: BaseComponent::new(name, pins),
frequency,
duty_cycle: 0.5,
current_state: PinValue::Low,
last_transition_vtime: Duration::ZERO, // Virtual time 0
high_time: Duration::from_secs_f64(0.5 / frequency),
low_time: Duration::from_secs_f64(0.5 / frequency),
enabled: true,
paused: false,
pause_start_vtime: None,
virtual_clock: None, // Set later via set_virtual_clock()
};
A2.3 — Add set_virtual_clock() method
New method for Phase 7 injection:
/// Inject a VirtualClock for virtual-time-based operation.
/// If not set, the clock falls back to wall-clock Instant-based timing.
/// This method is called by ConfigurableSystem in Phase 7.
pub fn set_virtual_clock(&mut self, clock: Arc<VirtualClock>) {
self.virtual_clock = Some(clock);
}
A2.4 — Update enable() method (line 64-69)
// BEFORE:
pub fn enable(&mut self) {
self.enabled = true;
self.current_state = PinValue::Low;
self.last_transition = Instant::now();
}
// AFTER:
pub fn enable(&mut self) {
self.enabled = true;
self.current_state = PinValue::Low;
self.last_transition_vtime = self.current_virtual_time();
}
Where current_virtual_time() is a new helper:
/// Get current virtual time, or fall back to Duration::ZERO if no VirtualClock.
fn current_virtual_time(&self) -> Duration {
self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now())
}
A2.5 — Update pause() and resume() (lines 78-94)
// BEFORE:
pub fn pause(&mut self) {
self.paused = true;
self.pause_start = Some(Instant::now());
}
pub fn resume(&mut self) {
if let Some(pause_start) = self.pause_start {
let pause_duration = pause_start.elapsed();
self.last_transition += pause_duration;
}
self.paused = false;
self.pause_start = None;
}
// AFTER:
pub fn pause(&mut self) {
self.paused = true;
self.pause_start_vtime = Some(self.current_virtual_time());
}
pub fn resume(&mut self) {
if let Some(pause_start_vtime) = self.pause_start_vtime {
let pause_duration = self.current_virtual_time().saturating_sub(pause_start_vtime);
self.last_transition_vtime = self.last_transition_vtime.saturating_add(pause_duration);
}
self.paused = false;
self.pause_start_vtime = None;
}
A2.6 — Update should_transition() (line 130-138)
// BEFORE:
fn should_transition(&self) -> bool {
let elapsed = self.last_transition.elapsed();
match self.current_state {
PinValue::High => elapsed >= self.high_time,
PinValue::Low => elapsed >= self.low_time,
PinValue::HighZ => true,
}
}
// AFTER:
fn should_transition(&self, virtual_time: Duration) -> bool {
let elapsed = virtual_time.saturating_sub(self.last_transition_vtime);
match self.current_state {
PinValue::High => elapsed >= self.high_time,
PinValue::Low => elapsed >= self.low_time,
PinValue::HighZ => true,
}
}
Note: should_transition() now takes virtual_time: Duration as a parameter instead of computing elapsed from Instant.
A2.7 — Update perform_transition() (line 140-150)
// BEFORE:
fn perform_transition(&mut self) {
let new_state = match self.current_state {
PinValue::High => PinValue::Low,
PinValue::Low => PinValue::High,
PinValue::HighZ => PinValue::Low,
};
self.current_state = new_state;
self.set_clock_output(new_state);
self.last_transition = Instant::now();
}
// AFTER:
fn perform_transition(&mut self, virtual_time: Duration) {
let new_state = match self.current_state {
PinValue::High => PinValue::Low,
PinValue::Low => PinValue::High,
PinValue::HighZ => PinValue::Low,
};
self.current_state = new_state;
self.set_clock_output(new_state);
self.last_transition_vtime = virtual_time;
}
A2.8 — Implement update_at_time() (new, replaces update() for virtual-time path)
Add the virtual-time-aware update method. This overrides the default from the Component trait:
impl Component for GenericClock {
// ... existing methods unchanged ...
fn update_at_time(&mut self, virtual_time: Duration) {
// Check pause state first - don't update if paused
if self.paused {
return;
}
// Read external enable control
let external_enable = self.read_enable_pin();
let should_be_enabled = self.enabled && external_enable;
if !should_be_enabled {
if self.current_state != PinValue::Low {
self.set_clock_output(PinValue::Low);
self.current_state = PinValue::Low;
}
return;
}
// Check if it's time to transition (using virtual time)
if self.should_transition(virtual_time) {
self.perform_transition(virtual_time);
}
}
// Keep update() for backward compatibility — delegates to update_at_time
// using current virtual time (or Duration::ZERO if no VirtualClock)
fn update(&mut self) {
let vtime = self.current_virtual_time();
self.update_at_time(vtime);
}
}
A2.9 — Rewrite run() to use wait_until() (line 190-210)
This is the most significant change. Replace the thread::sleep() busy-loop with virtual_clock.wait_until():
// BEFORE:
fn run(&mut self) {
self.base.set_running(true);
self.enable();
while self.is_running() {
self.update();
let sleep_time = if self.frequency > 0.0 {
Duration::from_secs_f64(1.0 / self.frequency / 100.0)
.max(Duration::from_micros(1))
} else {
Duration::from_micros(100)
};
thread::sleep(sleep_time);
}
self.disable();
}
// AFTER:
fn run(&mut self) {
self.base.set_running(true);
self.enable();
// 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 path: use old Instant-based timing
return self.run_legacy();
}
};
while self.is_running() {
// Calculate next transition time
let next_transition = self.next_transition_time();
// Block until virtual time reaches next transition
match vclock.wait_until(next_transition) {
Some(current_vtime) => {
if !self.is_running() {
break;
}
self.update_at_time(current_vtime);
}
None => break, // Simulation stopped
}
}
self.disable();
}
Add helper methods:
impl GenericClock {
/// Calculate the virtual time of the next phase transition.
fn next_transition_time(&self) -> Duration {
let phase_duration = match self.current_state {
PinValue::High => self.high_time,
PinValue::Low => self.low_time,
PinValue::HighZ => Duration::from_nanos(1),
};
self.last_transition_vtime.saturating_add(phase_duration)
}
/// Legacy run loop using wall-clock time (backward compatibility).
fn run_legacy(&mut self) {
while self.is_running() {
self.update();
let sleep_time = if self.frequency > 0.0 {
Duration::from_secs_f64(1.0 / self.frequency / 100.0)
.max(Duration::from_micros(1))
} else {
Duration::from_micros(100)
};
thread::sleep(sleep_time);
}
self.disable();
}
}
A2.10 — Add timing() override
impl Component for GenericClock {
// ... existing methods ...
fn timing(&self) -> ComponentTiming {
ComponentTiming {
name: "Generic Clock",
access_time: Duration::ZERO,
propagation_delay: Duration::ZERO,
setup_time: Duration::ZERO,
hold_time: Duration::ZERO,
max_clock_frequency: f64::INFINITY,
min_update_interval: self.high_time.min(self.low_time),
}
}
}
A2.11 — Update imports
// BEFORE (line 1-9):
use parking_lot::Mutex;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
// AFTER:
use parking_lot::Mutex;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crate::component::{BaseComponent, Component};
use crate::component_timing::ComponentTiming;
use crate::pin::{Pin, PinValue};
use crate::virtual_clock::VirtualClock;
Key change: Remove Instant from imports, add VirtualClock and ComponentTiming.
A3. GenericClock Test Impact
The existing tests in generic_clock.rs:238-258 are:
| Test | What it checks | Impact |
|---|---|---|
test_clock_creation |
Name and running state | No impact — doesn't use Instant |
test_clock_timing_calculation |
frequency_to_duration() |
No impact — static calculation |
These tests will pass unchanged. No new tests needed for the migration itself — the existing tests validate construction and static calculations. Virtual-time behavior tests should be added in Phase 9.
Part B: TwoPhaseClock Migration
B1. Current Timing Mechanism
TwoPhaseClock uses wall-clock time in 5 locations:
| Location | Line | Current Code | Purpose |
|---|---|---|---|
last_transition field |
15 | last_transition: Instant |
Track when last phase swap occurred |
pause_start field |
19 | pause_start: Option<Instant> |
Track when pause began |
new() constructor |
38 | last_transition: Instant::now() |
Initialize transition timer |
enable() |
64 | self.last_transition = Instant::now() |
Reset timer on enable |
pause() |
80 | self.pause_start = Some(Instant::now()) |
Record pause start |
resume() |
88 | pause_start.elapsed() |
Calculate pause duration |
should_transition() |
137 | self.last_transition.elapsed() |
Check if phase_time elapsed |
perform_transition() |
160 | self.last_transition = Instant::now() |
Record transition time |
run() |
203 | thread::sleep(Duration::from_micros(100)) |
Fixed 100µs busy-wait |
B2. Required Changes
B2.1 — Add VirtualClock field (optional, gradual integration)
// BEFORE (line 11-21):
pub struct TwoPhaseClock {
base: BaseComponent,
phi1_state: PinValue,
phi2_state: PinValue,
last_transition: Instant,
phase_time: Duration,
enabled: bool,
paused: bool,
pause_start: Option<Instant>,
frequency: f64,
}
// AFTER:
pub struct TwoPhaseClock {
base: BaseComponent,
phi1_state: PinValue,
phi2_state: PinValue,
last_transition_vtime: Duration, // Virtual time of last phase swap
phase_time: Duration,
enabled: bool,
paused: bool,
pause_start_vtime: Option<Duration>, // Virtual time when pause started
frequency: f64,
virtual_clock: Option<Arc<VirtualClock>>, // Injected in Phase 7
}
B2.2 — Update constructor (line 24-45)
// BEFORE:
TwoPhaseClock {
base: BaseComponent::new(name, pins),
phi1_state: PinValue::High,
phi2_state: PinValue::Low,
last_transition: Instant::now(),
phase_time,
enabled: true,
paused: false,
pause_start: None,
frequency,
}
// AFTER:
TwoPhaseClock {
base: BaseComponent::new(name, pins),
phi1_state: PinValue::High,
phi2_state: PinValue::Low,
last_transition_vtime: Duration::ZERO,
phase_time,
enabled: true,
paused: false,
pause_start_vtime: None,
frequency,
virtual_clock: None,
}
B2.3 — Add set_virtual_clock() and current_virtual_time() helpers
impl TwoPhaseClock {
/// Inject a VirtualClock for virtual-time-based operation.
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.
fn current_virtual_time(&self) -> Duration {
self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now())
}
}
B2.4 — Update enable() (line 60-69)
// BEFORE:
pub fn enable(&mut self) {
self.enabled = true;
self.phi1_state = PinValue::High;
self.phi2_state = PinValue::Low;
self.last_transition = Instant::now();
self.update_outputs();
self.update_outputs();
}
// AFTER:
pub fn enable(&mut self) {
self.enabled = true;
self.phi1_state = PinValue::High;
self.phi2_state = PinValue::Low;
self.last_transition_vtime = self.current_virtual_time();
self.update_outputs();
self.update_outputs();
}
B2.5 — Update pause() and resume() (lines 79-95)
Same pattern as GenericClock:
pub fn pause(&mut self) {
self.paused = true;
self.pause_start_vtime = Some(self.current_virtual_time());
}
pub fn resume(&mut self) {
if let Some(pause_start_vtime) = self.pause_start_vtime {
let pause_duration = self.current_virtual_time().saturating_sub(pause_start_vtime);
self.last_transition_vtime = self.last_transition_vtime.saturating_add(pause_duration);
}
self.paused = false;
self.pause_start_vtime = None;
}
B2.6 — Update should_transition() (line 136-138)
// BEFORE:
fn should_transition(&self) -> bool {
self.last_transition.elapsed() >= self.phase_time
}
// AFTER:
fn should_transition(&self, virtual_time: Duration) -> bool {
let elapsed = virtual_time.saturating_sub(self.last_transition_vtime);
elapsed >= self.phase_time
}
B2.7 — Update perform_transition() (line 140-161)
// BEFORE:
fn perform_transition(&mut self) {
match (self.phi1_state, self.phi2_state) {
(PinValue::High, PinValue::Low) => {
self.phi1_state = PinValue::Low;
self.phi2_state = PinValue::High;
}
(PinValue::Low, PinValue::High) => {
self.phi1_state = PinValue::High;
self.phi2_state = PinValue::Low;
}
_ => {
self.phi1_state = PinValue::High;
self.phi2_state = PinValue::Low;
}
}
self.update_outputs();
self.last_transition = Instant::now();
}
// AFTER:
fn perform_transition(&mut self, virtual_time: Duration) {
match (self.phi1_state, self.phi2_state) {
(PinValue::High, PinValue::Low) => {
self.phi1_state = PinValue::Low;
self.phi2_state = PinValue::High;
}
(PinValue::Low, PinValue::High) => {
self.phi1_state = PinValue::High;
self.phi2_state = PinValue::Low;
}
_ => {
self.phi1_state = PinValue::High;
self.phi2_state = PinValue::Low;
}
}
self.update_outputs();
self.last_transition_vtime = virtual_time;
}
B2.8 — Implement update_at_time() and update update()
impl Component for TwoPhaseClock {
// ... existing name/pins/get_pin unchanged ...
fn update_at_time(&mut self, virtual_time: Duration) {
if self.paused {
return;
}
if !self.enabled {
return;
}
self.update_outputs();
if self.should_transition(virtual_time) {
self.perform_transition(virtual_time);
}
}
fn update(&mut self) {
let vtime = self.current_virtual_time();
self.update_at_time(vtime);
}
// ... rest unchanged ...
}
B2.9 — Rewrite run() to use wait_until() (line 195-212)
// BEFORE:
fn run(&mut self) {
self.base.set_running(true);
self.enable();
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(100));
if !self.is_running() {
break;
}
}
self.disable();
}
// AFTER:
fn run(&mut self) {
self.base.set_running(true);
self.enable();
let vclock = match self.virtual_clock.as_ref() {
Some(vc) => Arc::clone(vc),
None => return self.run_legacy(),
};
while self.is_running() {
let next_transition = self.last_transition_vtime + self.phase_time;
match vclock.wait_until(next_transition) {
Some(current_vtime) => {
if !self.is_running() {
break;
}
self.update_at_time(current_vtime);
}
None => break,
}
}
self.disable();
}
Add legacy helper:
impl TwoPhaseClock {
/// Legacy run loop using wall-clock time (backward compatibility).
fn run_legacy(&mut self) {
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(100));
if !self.is_running() {
break;
}
}
self.disable();
}
}
B2.10 — Add timing() override
fn timing(&self) -> ComponentTiming {
ComponentTiming {
name: "Two-Phase Clock",
access_time: Duration::ZERO,
propagation_delay: Duration::ZERO,
setup_time: Duration::ZERO,
hold_time: Duration::ZERO,
max_clock_frequency: f64::INFINITY,
min_update_interval: self.phase_time,
}
}
B2.11 — Update imports
// BEFORE (line 1-9):
use parking_lot::Mutex;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
// AFTER:
use parking_lot::Mutex;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crate::component::{BaseComponent, Component};
use crate::component_timing::ComponentTiming;
use crate::pin::{Pin, PinValue};
use crate::virtual_clock::VirtualClock;
B3. TwoPhaseClock Test Impact
No tests exist for TwoPhaseClock (no #[cfg(test)] module in the file). No test breakage risk.
Part C: System Config Changes
C1. Factory Closure Updates
In system_config.rs, the factory closures create clocks without a VirtualClock. In Phase 5, these remain unchanged — the virtual_clock field is None by default, and clocks use the legacy run_legacy() path.
In Phase 7, ConfigurableSystem will:
- Create
Arc<VirtualClock> - After constructing each clock, call
set_virtual_clock(Arc::clone(&virtual_clock)) - This requires changing the factory closure signature or adding a post-construction hook
For Phase 5, no changes to system_config.rs are needed.
Part D: Backward Compatibility Analysis
D1. What stays the same
| Aspect | Compatibility |
|---|---|
new(name, frequency) constructor signature |
Unchanged — no new required parameters |
Component trait impl |
Unchanged — update() still works via delegation |
enable() / disable() / pause() / resume() |
Unchanged — public API identical |
set_frequency() / get_frequency() |
Unchanged |
| Pin names (CLK, PHI1, PHI2, ENABLE) | Unchanged |
run() without VirtualClock |
Works — falls back to run_legacy() with thread::sleep() |
| Existing tests | Pass — no Instant dependency in test assertions |
D2. What changes (internal only)
| Change | Visible externally? |
|---|---|
last_transition: Instant → last_transition_vtime: Duration |
No — private field |
pause_start: Option<Instant> → pause_start_vtime: Option<Duration> |
No — private field |
should_transition() takes Duration param |
No — private method |
perform_transition() takes Duration param |
No — private method |
New virtual_clock: Option<Arc<VirtualClock>> field |
No — private, None by default |
New set_virtual_clock() method |
Yes — but opt-in, not required |
D3. Risk assessment
Risk: LOW. All changes are internal field replacements. The public API is unchanged. The Option<Arc<VirtualClock>> pattern ensures clocks work identically without a VirtualClock injected.
Part E: Execution Checklist
These are the implementation steps in order:
- E1. Update
GenericClockimports — removeInstant, addVirtualClock+ComponentTiming - E2. Replace
GenericClockstruct fields:last_transition: Instant→last_transition_vtime: Duration,pause_start: Option<Instant>→pause_start_vtime: Option<Duration>, addvirtual_clock: Option<Arc<VirtualClock>> - E3. Update
GenericClock::new()— initializelast_transition_vtime: Duration::ZERO,virtual_clock: None - E4. Add
GenericClock::set_virtual_clock()andGenericClock::current_virtual_time()helper methods - E5. Update
GenericClock::enable()— usecurrent_virtual_time()instead ofInstant::now() - E6. Update
GenericClock::pause()— usecurrent_virtual_time()instead ofInstant::now() - E7. Update
GenericClock::resume()— compute pause duration from virtual time deltas - E8. Update
GenericClock::should_transition()— acceptvirtual_time: Durationparameter - E9. Update
GenericClock::perform_transition()— acceptvirtual_time: Duration, store inlast_transition_vtime - E10. Implement
GenericClock::update_at_time()inComponentimpl - E11. Update
GenericClock::update()to delegate toupdate_at_time(current_virtual_time()) - E12. Add
GenericClock::next_transition_time()andGenericClock::run_legacy()helper methods - E13. Rewrite
GenericClock::run()— usewait_until()when VirtualClock present, elserun_legacy() - E14. Add
GenericClock::timing()override inComponentimpl - E15. Verify
GenericClockexisting tests still pass - E16. Update
TwoPhaseClockimports — removeInstant, addVirtualClock+ComponentTiming - E17. Replace
TwoPhaseClockstruct fields: same pattern as GenericClock - E18. Update
TwoPhaseClock::new()— initialize virtual time fields - E19. Add
TwoPhaseClock::set_virtual_clock()andTwoPhaseClock::current_virtual_time()helpers - E20. Update
TwoPhaseClock::enable()— usecurrent_virtual_time() - E21. Update
TwoPhaseClock::pause()andresume()— virtual time deltas - E22. Update
TwoPhaseClock::should_transition()— acceptvirtual_time: Duration - E23. Update
TwoPhaseClock::perform_transition()— acceptvirtual_time: Duration - E24. Implement
TwoPhaseClock::update_at_time()inComponentimpl - E25. Update
TwoPhaseClock::update()to delegate toupdate_at_time() - E26. Add
TwoPhaseClock::run_legacy()helper - E27. Rewrite
TwoPhaseClock::run()— usewait_until()when VirtualClock present - E28. Add
TwoPhaseClock::timing()override inComponentimpl - E29. Run
cargo buildto verify compilation - E30. Run
cargo testto verify all existing tests pass
Flow Diagram: Clock Thread with Virtual Time
flowchart TD
A[run called] --> B{virtual_clock set?}
B -->|No| C[run_legacy - thread::sleep loop]
B -->|Yes| D[enable - reset phase state]
D --> E[Calculate next_transition_time]
E --> F[wait_until next_transition]
F --> G{wait_until returned}
G -->|None - stopped| H[disable and exit]
G -->|Some vtime| I{is_running?}
I -->|No| H
I -->|Yes| J[update_at_time with vtime]
J --> K[Transition phase if needed]
K --> E
Flow Diagram: Phase Transition Logic in Virtual Time
flowchart LR
subgraph GenericClock
G1[last_transition_vtime = 0ns] --> G2{vtime >= high_time?}
G2 -->|Yes| G3[High to Low transition]
G3 --> G4[last_transition_vtime = vtime]
G4 --> G5{vtime >= low_time?}
G5 -->|Yes| G6[Low to High transition]
G6 --> G4
end
subgraph TwoPhaseClock
T1[last_transition_vtime = 0ns] --> T2{elapsed >= phase_time?}
T2 -->|Yes| T3[PHI1 High+PHI2 Low to PHI1 Low+PHI2 High]
T3 --> T4[last_transition_vtime = vtime]
T4 --> T5{elapsed >= phase_time?}
T5 -->|Yes| T6[PHI1 Low+PHI2 High to PHI1 High+PHI2 Low]
T6 --> T4
end