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
898
plans/phase5_clock_migration.md
Normal file
898
plans/phase5_clock_migration.md
Normal file
|
|
@ -0,0 +1,898 @@
|
|||
# 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`](src/components/clock/generic_clock.rs) — GenericClock
|
||||
- [`two_phase_clock.rs`](src/components/clock/two_phase_clock.rs) — TwoPhaseClock
|
||||
- [`system_config.rs`](src/system_config.rs) — Clock factory closures (minor)
|
||||
|
||||
**Files NOT modified** (Phase 7 responsibility):
|
||||
- [`virtual_clock.rs`](src/virtual_clock.rs) — Already complete
|
||||
- [`real_time_governor.rs`](src/real_time_governor.rs) — Already complete
|
||||
- [`component.rs`](src/component.rs) — Already has `update_at_time()` default
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
**Migrate `GenericClock` first, then `TwoPhaseClock`.**
|
||||
|
||||
Rationale:
|
||||
1. `GenericClock` is simpler — single-phase, single CLK output
|
||||
2. `TwoPhaseClock` has dual outputs (PHI1/PHI2) and inter-phase dependencies
|
||||
3. Both follow the same pattern, so GenericClock establishes the template
|
||||
4. `GenericClock` has existing unit tests to validate the migration
|
||||
|
||||
---
|
||||
|
||||
## Part A: GenericClock Migration
|
||||
|
||||
### A1. Current Timing Mechanism
|
||||
|
||||
[`GenericClock`](src/components/clock/generic_clock.rs:11) uses wall-clock time in 5 locations:
|
||||
|
||||
| Location | Line | Current Code | Purpose |
|
||||
|----------|------|-------------|---------|
|
||||
| [`last_transition`](src/components/clock/generic_clock.rs:16) field | 16 | `last_transition: Instant` | Track when last phase change occurred |
|
||||
| [`pause_start`](src/components/clock/generic_clock.rs:21) field | 21 | `pause_start: Option<Instant>` | Track when pause began |
|
||||
| [`new()`](src/components/clock/generic_clock.rs:34) constructor | 34 | `last_transition: Instant::now()` | Initialize transition timer |
|
||||
| [`enable()`](src/components/clock/generic_clock.rs:68) | 68 | `self.last_transition = Instant::now()` | Reset timer on enable |
|
||||
| [`pause()`](src/components/clock/generic_clock.rs:80) | 80 | `self.pause_start = Some(Instant::now())` | Record pause start |
|
||||
| [`resume()`](src/components/clock/generic_clock.rs:88) | 88 | `pause_start.elapsed()` | Calculate pause duration |
|
||||
| [`should_transition()`](src/components/clock/generic_clock.rs:131) | 131 | `self.last_transition.elapsed()` | Check if phase duration elapsed |
|
||||
| [`perform_transition()`](src/components/clock/generic_clock.rs:149) | 149 | `self.last_transition = Instant::now()` | Record transition time |
|
||||
| [`run()`](src/components/clock/generic_clock.rs:205) | 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.
|
||||
|
||||
```rust
|
||||
// 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
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
/// 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)
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
/// 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)
|
||||
|
||||
```rust
|
||||
// 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)
|
||||
|
||||
```rust
|
||||
// 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)
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
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()`:
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
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
|
||||
|
||||
```rust
|
||||
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
|
||||
|
||||
```rust
|
||||
// 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`](src/components/clock/generic_clock.rs:238) are:
|
||||
|
||||
| Test | What it checks | Impact |
|
||||
|------|---------------|--------|
|
||||
| [`test_clock_creation`](src/components/clock/generic_clock.rs:243) | Name and running state | **No impact** — doesn't use `Instant` |
|
||||
| [`test_clock_timing_calculation`](src/components/clock/generic_clock.rs:250) | `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`](src/components/clock/two_phase_clock.rs:11) uses wall-clock time in 5 locations:
|
||||
|
||||
| Location | Line | Current Code | Purpose |
|
||||
|----------|------|-------------|---------|
|
||||
| [`last_transition`](src/components/clock/two_phase_clock.rs:15) field | 15 | `last_transition: Instant` | Track when last phase swap occurred |
|
||||
| [`pause_start`](src/components/clock/two_phase_clock.rs:19) field | 19 | `pause_start: Option<Instant>` | Track when pause began |
|
||||
| [`new()`](src/components/clock/two_phase_clock.rs:38) constructor | 38 | `last_transition: Instant::now()` | Initialize transition timer |
|
||||
| [`enable()`](src/components/clock/two_phase_clock.rs:64) | 64 | `self.last_transition = Instant::now()` | Reset timer on enable |
|
||||
| [`pause()`](src/components/clock/two_phase_clock.rs:80) | 80 | `self.pause_start = Some(Instant::now())` | Record pause start |
|
||||
| [`resume()`](src/components/clock/two_phase_clock.rs:88) | 88 | `pause_start.elapsed()` | Calculate pause duration |
|
||||
| [`should_transition()`](src/components/clock/two_phase_clock.rs:137) | 137 | `self.last_transition.elapsed()` | Check if phase_time elapsed |
|
||||
| [`perform_transition()`](src/components/clock/two_phase_clock.rs:160) | 160 | `self.last_transition = Instant::now()` | Record transition time |
|
||||
| [`run()`](src/components/clock/two_phase_clock.rs:203) | 203 | `thread::sleep(Duration::from_micros(100))` | Fixed 100µs busy-wait |
|
||||
|
||||
### B2. Required Changes
|
||||
|
||||
#### B2.1 — Add `VirtualClock` field (optional, gradual integration)
|
||||
|
||||
```rust
|
||||
// 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)
|
||||
|
||||
```rust
|
||||
// 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
|
||||
|
||||
```rust
|
||||
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)
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
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)
|
||||
|
||||
```rust
|
||||
// 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)
|
||||
|
||||
```rust
|
||||
// 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()`
|
||||
|
||||
```rust
|
||||
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)
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
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
|
||||
|
||||
```rust
|
||||
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
|
||||
|
||||
```rust
|
||||
// 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`](src/system_config.rs:241-277), 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:
|
||||
1. Create `Arc<VirtualClock>`
|
||||
2. After constructing each clock, call `set_virtual_clock(Arc::clone(&virtual_clock))`
|
||||
3. This requires changing the factory closure signature or adding a post-construction hook
|
||||
|
||||
For Phase 5, no changes to [`system_config.rs`](src/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 `GenericClock` imports — remove `Instant`, add `VirtualClock` + `ComponentTiming`
|
||||
- [ ] **E2.** Replace `GenericClock` struct fields: `last_transition: Instant` → `last_transition_vtime: Duration`, `pause_start: Option<Instant>` → `pause_start_vtime: Option<Duration>`, add `virtual_clock: Option<Arc<VirtualClock>>`
|
||||
- [ ] **E3.** Update `GenericClock::new()` — initialize `last_transition_vtime: Duration::ZERO`, `virtual_clock: None`
|
||||
- [ ] **E4.** Add `GenericClock::set_virtual_clock()` and `GenericClock::current_virtual_time()` helper methods
|
||||
- [ ] **E5.** Update `GenericClock::enable()` — use `current_virtual_time()` instead of `Instant::now()`
|
||||
- [ ] **E6.** Update `GenericClock::pause()` — use `current_virtual_time()` instead of `Instant::now()`
|
||||
- [ ] **E7.** Update `GenericClock::resume()` — compute pause duration from virtual time deltas
|
||||
- [ ] **E8.** Update `GenericClock::should_transition()` — accept `virtual_time: Duration` parameter
|
||||
- [ ] **E9.** Update `GenericClock::perform_transition()` — accept `virtual_time: Duration`, store in `last_transition_vtime`
|
||||
- [ ] **E10.** Implement `GenericClock::update_at_time()` in `Component` impl
|
||||
- [ ] **E11.** Update `GenericClock::update()` to delegate to `update_at_time(current_virtual_time())`
|
||||
- [ ] **E12.** Add `GenericClock::next_transition_time()` and `GenericClock::run_legacy()` helper methods
|
||||
- [ ] **E13.** Rewrite `GenericClock::run()` — use `wait_until()` when VirtualClock present, else `run_legacy()`
|
||||
- [ ] **E14.** Add `GenericClock::timing()` override in `Component` impl
|
||||
- [ ] **E15.** Verify `GenericClock` existing tests still pass
|
||||
- [ ] **E16.** Update `TwoPhaseClock` imports — remove `Instant`, add `VirtualClock` + `ComponentTiming`
|
||||
- [ ] **E17.** Replace `TwoPhaseClock` struct fields: same pattern as GenericClock
|
||||
- [ ] **E18.** Update `TwoPhaseClock::new()` — initialize virtual time fields
|
||||
- [ ] **E19.** Add `TwoPhaseClock::set_virtual_clock()` and `TwoPhaseClock::current_virtual_time()` helpers
|
||||
- [ ] **E20.** Update `TwoPhaseClock::enable()` — use `current_virtual_time()`
|
||||
- [ ] **E21.** Update `TwoPhaseClock::pause()` and `resume()` — virtual time deltas
|
||||
- [ ] **E22.** Update `TwoPhaseClock::should_transition()` — accept `virtual_time: Duration`
|
||||
- [ ] **E23.** Update `TwoPhaseClock::perform_transition()` — accept `virtual_time: Duration`
|
||||
- [ ] **E24.** Implement `TwoPhaseClock::update_at_time()` in `Component` impl
|
||||
- [ ] **E25.** Update `TwoPhaseClock::update()` to delegate to `update_at_time()`
|
||||
- [ ] **E26.** Add `TwoPhaseClock::run_legacy()` helper
|
||||
- [ ] **E27.** Rewrite `TwoPhaseClock::run()` — use `wait_until()` when VirtualClock present
|
||||
- [ ] **E28.** Add `TwoPhaseClock::timing()` override in `Component` impl
|
||||
- [ ] **E29.** Run `cargo build` to verify compilation
|
||||
- [ ] **E30.** Run `cargo test` to verify all existing tests pass
|
||||
|
||||
---
|
||||
|
||||
## Flow Diagram: Clock Thread with Virtual Time
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue