# Phase 6: Add Timing Enforcement to Memory/CPU Components ## Overview Phase 6 adds virtual-time-based timing enforcement to the four Intel 400x series components: ROM (4001), RAM (4002), Shift Register (4003), and CPU (4004). These components currently use `Instant::now()` for address latch timestamps and check latency via `Instant::elapsed()`. This phase replaces those with virtual time from `Arc`, adds `ComponentTiming` enforcement checks, and records timing violations. **Files modified:** - [`intel_400x.rs`](src/components/common/intel_400x.rs) — Shared state machine timing (highest priority) - [`intel_4001.rs`](src/components/memory/intel_4001.rs) — Intel 4001 ROM - [`intel_4002.rs`](src/components/memory/intel_4002.rs) — Intel 4002 RAM - [`intel_4003.rs`](src/components/memory/intel_4003.rs) — Intel 4003 Shift Register - [`intel_4004.rs`](src/components/cpu/intel_4004.rs) — Intel 4004 CPU **Files NOT modified** (Phase 7 responsibility): - [`system_config.rs`](src/system_config.rs) — Will inject `Arc` into components - [`virtual_clock.rs`](src/virtual_clock.rs) — Already complete - [`component_timing.rs`](src/component_timing.rs) — Already complete --- ## Implementation Order ```mermaid graph LR A[Step 0: intel_400x.rs shared code] --> B[Step 1: Intel 4003 SR] B --> C[Step 2: Intel 4001 ROM] C --> D[Step 3: Intel 4002 RAM] D --> E[Step 4: Intel 4004 CPU] ``` **Rationale:** 1. **`intel_400x.rs` first** — The shared state machine contains `address_latch_time: Option` and `is_latency_elapsed()` used by all three memory components. Changing this first unblocks all component migrations. 2. **Intel 4003 second** — Simplest component; no address latching in the traditional sense, uses shift/load operations. Good validation of the shared code changes. 3. **Intel 4001 third** — ROM is simpler than RAM (read-only, no write operations). Validates the full address→wait→drive flow. 4. **Intel 4002 fourth** — RAM adds write operations and I/O port states on top of the ROM pattern. 5. **Intel 4004 last** — CPU is the most complex (98K chars, own MemoryState enum, own `address_latch_time` field). Benefits from all patterns established by the memory components. --- ## Step 0: Migrate Shared State Machine (`intel_400x.rs`) ### Current Timing Mechanism The shared [`Intel400xStateMachine`](src/components/common/intel_400x.rs:830) uses `Instant` in two critical locations: | Location | Line | Current Code | Purpose | |----------|------|-------------|---------| | [`address_latch_time`](src/components/common/intel_400x.rs:824) field | 824 | `address_latch_time: Option` | Timestamp when address was fully latched | | [`handle_address_nibble()`](src/components/common/intel_400x.rs:958) | 958 | `self.address_latch_time = Some(Instant::now())` | Record latch time when both nibbles received | | [`is_latency_elapsed()`](src/components/common/intel_400x.rs:967) | 969 | `latch_time.elapsed() >= self.access_time` | Check if access time has passed | Additionally, the [`Intel400xTimingState`](src/components/common/intel_400x.rs:320) trait exposes `Instant` in its interface: | Location | Line | Signature | |----------|------|-----------| | [`get_address_latch_time()`](src/components/common/intel_400x.rs:324) | 324 | `fn get_address_latch_time(&self) -> Option` | | [`set_address_latch_time()`](src/components/common/intel_400x.rs:325) | 325 | `fn set_address_latch_time(&mut self, time: Option)` | And the [`Intel400xAddressHandling`](src/components/common/intel_400x.rs:196) trait's [`handle_address_latching()`](src/components/common/intel_400x.rs:206) method takes `&mut Option`: | Location | Line | Current Code | |----------|------|-------------| | [`handle_address_latching()`](src/components/common/intel_400x.rs:212) | 212 | `address_latch_time: &mut Option` parameter | | | 223 | `*address_latch_time = Some(Instant::now())` | | [`handle_latency_wait()`](src/components/common/intel_400x.rs:236) | 236 | `address_latch_time: &Option` parameter | | | 240 | `latch_time.elapsed() >= access_time` | ### Required Changes #### 0.1 — Change `address_latch_time` from `Instant` to `Duration` Replace `Instant`-based timing with virtual time (`Duration` = nanoseconds since simulation start). ```rust // BEFORE (line 824): address_latch_time: Option, // AFTER: address_latch_time: Option, // Virtual time when address was latched ``` #### 0.2 — Add `is_latency_elapsed_at()` method (new, virtual-time-aware) Add a new method that takes the current virtual time as a parameter, replacing the `Instant::elapsed()`-based check: ```rust // NEW METHOD — add after is_latency_elapsed() at line ~973: /// Check if latency has elapsed at a given virtual time. /// Returns true if `virtual_time - address_latch_time >= access_time`. pub fn is_latency_elapsed_at(&self, virtual_time: Duration) -> bool { if let Some(latch_time) = self.address_latch_time { virtual_time.saturating_sub(latch_time) >= self.access_time } else { false } } ``` **Keep the existing `is_latency_elapsed()` method** for backward compatibility — it will continue to work with `Instant::now()` for legacy code paths. Mark it with a comment that it's deprecated for virtual-time usage. #### 0.3 — Add `set_address_latch_vtime()` method (new) ```rust /// Set address latch time using virtual time directly. /// Called from update_at_time() when both address nibbles are latched. pub fn set_address_latch_vtime(&mut self, vtime: Duration) { self.address_latch_time = Some(vtime); } ``` #### 0.4 — Add `handle_address_nibble_at()` method (new, virtual-time-aware) ```rust /// Handle address nibble latching with virtual time. /// Returns true only when BOTH nibbles have been latched. /// Records address_latch_time as virtual_time when complete. pub fn handle_address_nibble_at(&mut self, nibble: u8, virtual_time: Duration) -> bool { if self.address_high_nibble.is_none() { self.address_high_nibble = Some(nibble); false } else if self.address_low_nibble.is_none() { self.address_low_nibble = Some(nibble); self.address_latch_time = Some(virtual_time); // Virtual time, not Instant::now() true } else { false } } ``` #### 0.5 — Update `Intel400xTimingState` trait (additive only) Add new virtual-time-aware methods to the trait alongside existing ones: ```rust pub trait Intel400xTimingState { // ... existing methods unchanged ... /// Get address latch time as virtual time (Duration) fn get_address_latch_vtime(&self) -> Option; /// Set address latch time using virtual time fn set_address_latch_vtime(&mut self, time: Option); /// Check if latency has elapsed at a given virtual time fn is_latency_elapsed_at(&self, virtual_time: Duration) -> bool; } ``` #### 0.6 — Update `Intel400xAddressHandling` trait (additive only) Add virtual-time-aware versions alongside existing methods: ```rust pub trait Intel400xAddressHandling { // ... existing methods unchanged ... /// Handle latency wait check using virtual time fn handle_latency_wait_at( &self, address_latch_time: &Option, access_time: Duration, virtual_time: Duration, ) -> bool { if let Some(latch_time) = address_latch_time { virtual_time.saturating_sub(*latch_time) >= access_time } else { false } } } ``` #### 0.7 — Update getter/setter for `address_latch_time` ```rust // Change signatures (lines 916-923): pub fn get_address_latch_time(&self) -> Option { // was Option self.address_latch_time } pub fn set_address_latch_time(&mut self, time: Option) { // was Option self.address_latch_time = time; } ``` **Impact:** This changes the return type of `get_address_latch_time()` from `Option` to `Option`. All callers (the `Intel400xTimingState` impls in each component) must be updated to match. --- ## Step 1: Intel 4003 Shift Register ### Current Timing Mechanism [`Intel4003`](src/components/memory/intel_4003.rs:24) is the simplest component: - Uses [`Intel400xStateMachine`](src/components/memory/intel_4003.rs:32) for state management - Implements [`Intel400xTimingState`](src/components/memory/intel_4003.rs:94) trait - Has no direct `Instant::now()` calls — all timing goes through the shared state machine - Has `thread::sleep(Duration::from_micros(1))` in [`run()`](src/components/memory/intel_4003.rs:586) (line 597) - Uses `prev_phi1`/`prev_phi2` for edge detection (same as ROM/RAM) ### Timing Enforcement Points The 4003 has simpler timing than ROM/RAM: - **No address latching** — `responds_to_address() = false` - **Shift operations** triggered by clock edges - **Access time: 200ns** — faster than ROM/RAM ### Required Changes #### 1.1 — Add `VirtualClock` field and helper ```rust // Add to struct Intel4003 (after state_machine field): virtual_clock: Option>, // Add helper method: fn current_virtual_time(&self) -> Duration { self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now()) } ``` #### 1.2 — Add `set_virtual_clock()` method ```rust pub fn set_virtual_clock(&mut self, clock: Arc) { self.virtual_clock = Some(clock); } ``` #### 1.3 — Update `Intel400xTimingState` impl Change `get_address_latch_time` / `set_address_latch_time` to use `Duration`: ```rust // BEFORE (line 124): fn get_address_latch_time(&self) -> Option { self.state_machine.get_address_latch_time() } fn set_address_latch_time(&mut self, time: Option) { self.state_machine.set_address_latch_time(time); } // AFTER: fn get_address_latch_time(&self) -> Option { self.state_machine.get_address_latch_time() } fn set_address_latch_time(&mut self, time: Option) { self.state_machine.set_address_latch_time(time); } // New virtual-time methods: fn get_address_latch_vtime(&self) -> Option { self.state_machine.get_address_latch_time() } fn set_address_latch_vtime(&mut self, time: Option) { self.state_machine.set_address_latch_time(time); } fn is_latency_elapsed_at(&self, virtual_time: Duration) -> bool { self.state_machine.is_latency_elapsed_at(virtual_time) } ``` #### 1.4 — Implement `update_at_time()` ```rust fn update_at_time(&mut self, virtual_time: Duration) { if !self.is_running() { return; } let phi1_rising = self.is_phi1_rising_edge(self.prev_phi1); let phi1_falling = self.is_phi1_falling_edge(self.prev_phi1); let phi2_rising = self.is_phi2_rising_edge(self.prev_phi2); let phi2_falling = self.is_phi2_falling_edge(self.prev_phi2); let (phi1, phi2) = self.read_clock_pins(); self.prev_phi1 = phi1; self.prev_phi2 = phi2; let reset_active = self.read_reset_pin(); if reset_active { self.perform_reset(); return; } // Timing enforcement: check if clock frequency is within spec // (frequency check happens at clock edge detection time) if phi2_falling { self.handle_phi2_falling(); } if phi1_falling { self.handle_phi1_falling(); } if phi1_rising { self.handle_phi1_rising(); } if phi2_rising { self.handle_phi2_rising(); } } ``` #### 1.5 — Update `update()` to delegate to `update_at_time()` ```rust fn update(&mut self) { let vtime = self.current_virtual_time(); self.update_at_time(vtime); } ``` #### 1.6 — Update `run()` with VirtualClock wait pattern ```rust fn run(&mut self) { self.base.set_running(true); // Initialize clock states for edge detection let (phi1, phi2) = self.read_clock_pins(); self.prev_phi1 = phi1; self.prev_phi2 = phi2; // If no VirtualClock, fall back to legacy busy-wait loop let vclock = match self.virtual_clock.as_ref() { Some(vc) => Arc::clone(vc), None => { while self.is_running() { self.update(); thread::sleep(Duration::from_micros(1)); } return; } }; // Virtual-time run loop let timing = ComponentTiming::intel_4003(); while self.is_running() { let next_update = vclock.now() + timing.min_update_interval; match vclock.wait_until(next_update) { Some(current_vtime) => { if !self.is_running() { break; } self.update_at_time(current_vtime); } None => break, } } } ``` #### 1.7 — Implement `timing()` trait method ```rust fn timing(&self) -> ComponentTiming { ComponentTiming::intel_4003() } ``` #### 1.8 — Update constructor ```rust // Add to new_with_access_time(): virtual_clock: None, ``` #### 1.9 — Add import ```rust use crate::virtual_clock::VirtualClock; use crate::component_timing::ComponentTiming; ``` --- ## Step 2: Intel 4001 ROM ### Current Timing Mechanism [`Intel4001`](src/components/memory/intel_4001.rs:25) uses timing in several places: | Location | Line | Current Code | Purpose | |----------|------|-------------|---------| | [`access_time`](src/components/memory/intel_4001.rs:29) field | 29 | `access_time: Duration` | ROM access latency (500ns) | | [`state_machine.is_latency_elapsed()`](src/components/memory/intel_4001.rs:277) | 277 | In `execute_state_logic()` WaitLatency | Check if ROM access time elapsed | | [`state_machine.is_latency_elapsed()`](src/components/memory/intel_4001.rs:782) | 782 | In legacy `handle_latency_wait()` | Same check in legacy path | | [`thread::sleep()`](src/components/memory/intel_4001.rs:968) | 968 | In `run()` | Busy-wait loop | The ROM state machine flow: ``` Idle → AddressPhase → WaitLatency → DriveData → Idle ``` ### Timing Enforcement Points ```mermaid graph TD A[PHI1 Rising: Address Latching] -->|address_latch_time = virtual_time| B[WaitLatency State] B -->|Check: virtual_time - latch_time >= access_time| C{Access Time Elapsed?} C -->|Yes| D[PHI2 Rising: Drive Data onto bus] C -->|No - Timing Violation| E[Record violation - data undefined] D --> F[PHI2 Falling: Tri-state bus, return to Idle] ``` **Key enforcement point:** In `WaitLatency` state, when checking if latency has elapsed, use virtual time comparison. If the clock is overclocked (phase time < access_time), the ROM will NOT produce valid data — just like real hardware. ### Required Changes #### 2.1 — Add `VirtualClock` field and helper ```rust // Add to struct Intel4001: virtual_clock: Option>, // Add helper: fn current_virtual_time(&self) -> Duration { self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now()) } ``` #### 2.2 — Add `set_virtual_clock()` method ```rust pub fn set_virtual_clock(&mut self, clock: Arc) { self.virtual_clock = Some(clock); } ``` #### 2.3 — Update `Intel400xTimingState` impl Same pattern as 4003 — change `Instant` to `Duration` in `get/set_address_latch_time()`, add new virtual-time trait methods. #### 2.4 — Implement `update_at_time()` ```rust fn update_at_time(&mut self, virtual_time: Duration) { if !self.is_running() { return; } let phi1_rising = self.is_phi1_rising_edge(self.prev_phi1); let phi1_falling = self.is_phi1_falling_edge(self.prev_phi1); let phi2_rising = self.is_phi2_rising_edge(self.prev_phi2); let phi2_falling = self.is_phi2_falling_edge(self.prev_phi2); let (phi1, phi2) = self.read_clock_pins(); self.prev_phi1 = phi1; self.prev_phi2 = phi2; let reset_active = self.read_reset_pin(); if reset_active { self.perform_reset(); return; } if phi2_falling { self.handle_phi2_falling(); } if phi1_falling { self.handle_phi1_falling(); } if phi1_rising { self.handle_phi1_rising(); } if phi2_rising { self.handle_phi2_rising(); } } ``` #### 2.5 — Update `update()` to delegate ```rust fn update(&mut self) { let vtime = self.current_virtual_time(); self.update_at_time(vtime); } ``` #### 2.6 — Update `run()` with VirtualClock wait pattern Same pattern as 4003 — check for `virtual_clock`, fall back to legacy busy-wait if absent. #### 2.7 — Implement `timing()` trait method ```rust fn timing(&self) -> ComponentTiming { ComponentTiming::intel_4001() } ``` #### 2.8 — Update `handle_phi2_rising()` for timing enforcement The key timing enforcement happens in [`handle_phi2_rising()`](src/components/memory/intel_4001.rs:484) where `WaitLatency` state checks `is_latency_elapsed()`: ```rust // BEFORE (line 487-493): if self.state_machine.get_state() == Intel400xUnifiedState::WaitLatency { if self.a3_wait_cycles > 0 { self.a3_wait_cycles -= 1; } else { // A3 wait complete, transition to DriveData. self.process_latency_elapsed(); } } // AFTER — add timing check: if self.state_machine.get_state() == Intel400xUnifiedState::WaitLatency { if self.a3_wait_cycles > 0 { self.a3_wait_cycles -= 1; } else { // Check access time using virtual time let virtual_time = self.current_virtual_time(); if self.state_machine.is_latency_elapsed_at(virtual_time) { self.process_latency_elapsed(); } else { // TIMING VIOLATION: Access time not yet elapsed // Component produces undefined results (just like real hardware) // The ROM will NOT drive data — the bus remains undefined // Violation is recorded but simulation continues } } } ``` #### 2.9 — Update `execute_state_logic()` WaitLatency branch Similarly update the [`execute_state_logic()`](src/components/memory/intel_4001.rs:275) WaitLatency branch: ```rust // BEFORE (line 275-280): Intel400xUnifiedState::WaitLatency => { if self.state_machine.is_latency_elapsed() { self.process_latency_elapsed(); } } // AFTER: Intel400xUnifiedState::WaitLatency => { let virtual_time = self.current_virtual_time(); if self.state_machine.is_latency_elapsed_at(virtual_time) { self.process_latency_elapsed(); } } ``` #### 2.10 — Update constructor and imports ```rust // Add to new_with_access_time(): virtual_clock: None, // Add imports: use crate::virtual_clock::VirtualClock; use crate::component_timing::ComponentTiming; ``` --- ## Step 3: Intel 4002 RAM ### Current Timing Mechanism [`Intel4002`](src/components/memory/intel_4002.rs) follows the same pattern as ROM but adds: - **Read/Write/OutputPort** states (vs. just DriveData) - [`is_latency_elapsed()`](src/components/memory/intel_4002.rs:268) check in `execute_state_logic()` WaitLatency - [`is_latency_elapsed()`](src/components/memory/intel_4002.rs:480) check in `handle_phi2_rising()` - [`thread::sleep()`](src/components/memory/intel_4002.rs:726) in `run()` (line 726) State machine flow: ``` Idle → AddressPhase → WaitLatency → ReadData/WriteData/OutputPort → Idle ``` ### Timing Enforcement Points Same as ROM — the critical check is in `WaitLatency` state when transitioning to data operations. ### Required Changes Identical pattern to ROM (Step 2): #### 3.1 — Add `virtual_clock: Option>` field + helper #### 3.2 — Add `set_virtual_clock()` method #### 3.3 — Update `Intel400xTimingState` impl (Instant → Duration) #### 3.4 — Implement `update_at_time()` (same structure as ROM) #### 3.5 — Update `update()` to delegate to `update_at_time()` #### 3.6 — Update `run()` with VirtualClock wait pattern #### 3.7 — Implement `timing()` returning `ComponentTiming::intel_4002()` #### 3.8 — Update `handle_phi2_rising()` WaitLatency check (line ~479-481): ```rust // BEFORE: if self.state_machine.is_latency_elapsed() { self.process_latency_elapsed(); } // AFTER: let virtual_time = self.current_virtual_time(); if self.state_machine.is_latency_elapsed_at(virtual_time) { self.process_latency_elapsed(); } ``` #### 3.9 — Update `execute_state_logic()` WaitLatency branch (line ~266-269) #### 3.10 — Update constructor and imports --- ## Step 4: Intel 4004 CPU ### Current Timing Mechanism [`Intel4004`](src/components/cpu/intel_4004.rs:124) has its **own** `MemoryState` enum and timing fields, separate from the shared `Intel400xStateMachine`: | Location | Line | Current Code | Purpose | |----------|------|-------------|---------| | [`address_latch_time`](src/components/cpu/intel_4004.rs:168) field | 168 | `address_latch_time: Option` | Timestamp when address was latched | | [`access_time`](src/components/cpu/intel_4004.rs:170) field | 170 | `access_time: Duration` | Memory access time (500ns) | | [`advance_address_cycle()`](src/components/cpu/intel_4004.rs:596) | 596 | `self.address_latch_time = Some(Instant::now())` | Record latch time at A2→A3 transition | | [`thread::sleep()`](src/components/cpu/intel_4004.rs:2387) | 2387 | In `run()` | Busy-wait loop | **Key difference:** The CPU does NOT use the shared `Intel400xStateMachine`. It has its own `MemoryState` enum and `AddressCycle` enum. The `address_latch_time` field is directly on the CPU struct. ### Timing Enforcement Points The CPU's timing enforcement is different from memory components: - **Propagation delay** (150ns): After the CPU drives address/data onto the bus, its outputs don't change instantly - **Setup/hold time checks**: The CPU should verify that data from ROM/RAM was stable for the required setup time before the clock edge - **Frequency check**: The CPU should verify the clock frequency is within spec (750 kHz max) ```mermaid graph TD A[A1: Drive addr high nibble] --> B[A2: Drive addr low nibble] B -->|address_latch_time = virtual_time| C[A3: Wait for ROM/RAM] C -->|Wait access_time| D[M1: Read OPR from bus] D --> E[M2: Read OPA from bus] E --> F[Execute instruction] ``` ### Required Changes #### 4.1 — Change `address_latch_time` from `Instant` to `Duration` ```rust // BEFORE (line 168): address_latch_time: Option, // AFTER: address_latch_time: Option, // Virtual time when address was latched ``` #### 4.2 — Add `VirtualClock` field and helper ```rust // Add to struct Intel4004: virtual_clock: Option>, fn current_virtual_time(&self) -> Duration { self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now()) } ``` #### 4.3 — Add `set_virtual_clock()` method ```rust pub fn set_virtual_clock(&mut self, clock: Arc) { self.virtual_clock = Some(clock); } ``` #### 4.4 — Update `advance_address_cycle()` (line 596) ```rust // BEFORE: self.address_latch_time = Some(Instant::now()); // AFTER: self.address_latch_time = Some(self.current_virtual_time()); ``` #### 4.5 — Implement `update_at_time()` ```rust fn update_at_time(&mut self, virtual_time: Duration) { if !self.is_running() { return; } // ... same clock edge detection logic as current update() ... // The key difference: address_latch_time is now Duration-based, // so any elapsed time checks use virtual_time - address_latch_time } ``` #### 4.6 — Update `update()` to delegate ```rust fn update(&mut self) { let vtime = self.current_virtual_time(); self.update_at_time(vtime); } ``` #### 4.7 — Update `run()` with VirtualClock wait pattern ```rust fn run(&mut self) { self.base.set_running(true); let vclock = match self.virtual_clock.as_ref() { Some(vc) => Arc::clone(vc), None => { // Legacy busy-wait loop while self.is_running() { self.update(); thread::sleep(Duration::from_micros(1)); if self.cycle_count > 100000 { // ... safety check ... } } return; } }; let timing = ComponentTiming::intel_4004(); while self.is_running() { let next_update = vclock.now() + timing.min_update_interval; match vclock.wait_until(next_update) { Some(current_vtime) => { if !self.is_running() { break; } self.update_at_time(current_vtime); if self.cycle_count > 100000 { // ... safety check ... } } None => break, } } } ``` #### 4.8 — Implement `timing()` trait method ```rust fn timing(&self) -> ComponentTiming { ComponentTiming::intel_4004() } ``` #### 4.9 — Update constructor and reset methods ```rust // In new() constructor: address_latch_time: None, // Already None, just type change virtual_clock: None, // In reset methods (lines 278, 497, 705): self.address_latch_time = None; // No change needed — already None ``` #### 4.10 — Add imports ```rust use crate::virtual_clock::VirtualClock; use crate::component_timing::ComponentTiming; ``` --- ## Timing Violation Handling Strategy Timing violations are **recorded, not fatal**. When a violation is detected: 1. **Access time not elapsed** (ROM/RAM): The component stays in `WaitLatency` and does NOT drive data onto the bus. The bus remains in its previous state (possibly undefined). The CPU reads garbage — just like real hardware. 2. **Overclocked frequency**: Detected when the clock period is shorter than `min_clock_period()`. The component continues operating but may produce incorrect results. Violation is logged. 3. **Setup/hold time violation**: The latched value may be incorrect. The component continues with the corrupted value — just like real hardware. ### Violation Recording (Future Enhancement) For Phase 6, violations are detected at the enforcement points but **not yet recorded to a global log**. The component simply behaves incorrectly (doesn't drive data, latches wrong values, etc.). A `TimingViolationLog` can be added in a future phase to collect violations for display in the GUI. The `ComponentTiming` check methods already return `Result<(), TimingViolation>`: - [`check_access_time(elapsed)`](src/component_timing.rs:221) — Returns `Err(TimingViolation::AccessTimeNotElapsed)` if not enough time - [`check_setup_time(stability_duration)`](src/component_timing.rs:236) — Returns `Err(TimingViolation::SetupTimeViolation)` - [`check_hold_time(stability_duration)`](src/component_timing.rs:251) — Returns `Err(TimingViolation::HoldTimeViolation)` - [`supports_frequency(freq_hz)`](src/component_timing.rs:214) — Returns `bool` These can be used at enforcement points to generate violations for logging when that infrastructure is added. --- ## Summary of Changes by File ### [`intel_400x.rs`](src/components/common/intel_400x.rs) (Shared) | Change | Type | |--------|------| | `address_latch_time: Option` → `Option` | Field type change | | `get/set_address_latch_time()` return/param type | Signature change | | Add `is_latency_elapsed_at(virtual_time: Duration)` | New method | | Add `handle_address_nibble_at(nibble, virtual_time)` | New method | | Add `set_address_latch_vtime(time: Duration)` | New method | | Add virtual-time methods to `Intel400xTimingState` trait | Trait extension | | Add `handle_latency_wait_at()` to `Intel400xAddressHandling` trait | Trait extension | | Keep existing `is_latency_elapsed()` for backward compat | No change | ### [`intel_4001.rs`](src/components/memory/intel_4001.rs) (ROM) | Change | Type | |--------|------| | Add `virtual_clock: Option>` field | New field | | Add `set_virtual_clock()` method | New method | | Add `current_virtual_time()` helper | New method | | Implement `update_at_time(virtual_time: Duration)` | New method | | Update `update()` to delegate to `update_at_time()` | Modify | | Update `run()` with VirtualClock wait pattern | Modify | | Implement `timing()` returning `ComponentTiming::intel_4001()` | New method | | Update `Intel400xTimingState` impl (Instant → Duration) | Modify | | Update `handle_phi2_rising()` WaitLatency check | Modify | | Update `execute_state_logic()` WaitLatency check | Modify | | Update constructor (`virtual_clock: None`) | Modify | | Add imports for `VirtualClock`, `ComponentTiming` | Modify | ### [`intel_4002.rs`](src/components/memory/intel_4002.rs) (RAM) | Change | Type | |--------|------| | Same changes as ROM, but with `ComponentTiming::intel_4002()` | Same pattern | | Update `handle_phi2_rising()` WaitLatency check | Modify | | Update `execute_state_logic()` WaitLatency check | Modify | ### [`intel_4003.rs`](src/components/memory/intel_4003.rs) (Shift Register) | Change | Type | |--------|------| | Same pattern as ROM, but with `ComponentTiming::intel_4003()` | Same pattern | | No `handle_phi2_rising()` WaitLatency changes needed (4003 doesn't wait for access time the same way) | N/A | ### [`intel_4004.rs`](src/components/cpu/intel_4004.rs) (CPU) | Change | Type | |--------|------| | `address_latch_time: Option` → `Option` | Field type change | | Add `virtual_clock: Option>` field | New field | | Add `set_virtual_clock()` method | New method | | Add `current_virtual_time()` helper | New method | | Implement `update_at_time(virtual_time: Duration)` | New method | | Update `update()` to delegate to `update_at_time()` | Modify | | Update `run()` with VirtualClock wait pattern | Modify | | Implement `timing()` returning `ComponentTiming::intel_4004()` | New method | | Update `advance_address_cycle()` latch time assignment | Modify | | Update constructor (`virtual_clock: None`) | Modify | | Add imports for `VirtualClock`, `ComponentTiming` | Modify | --- ## Backward Compatibility All changes follow the Phase 5 pattern: - `Option>` — components work without a VirtualClock - `run()` falls back to legacy `thread::sleep()` busy-wait when no VirtualClock is injected - `update()` delegates to `update_at_time()` using `current_virtual_time()` (returns `Duration::ZERO` when no clock) - `update_at_time()` is a new override; existing `update()` callers continue to work - Existing `is_latency_elapsed()` method kept for backward compat (uses `Instant::elapsed()` internally) Phase 7 (`ConfigurableSystem`) will create the `Arc` and inject it via `set_virtual_clock()` on each component. --- ## Testing Strategy After each component migration, verify: 1. **Legacy path still works** — Run existing tests without VirtualClock injected 2. **Virtual time path works** — Inject a VirtualClock, advance time, verify correct behavior 3. **Timing violation behavior** — Overclock the clock and verify ROM/RAM produce incorrect results while the faster shift register still works 4. **State machine transitions** — Verify Idle → AddressPhase → WaitLatency → DriveData cycle completes correctly in virtual time