//! Per-component timing constraints for hardware-accurate simulation. //! //! Each `ComponentTiming` instance describes the real timing characteristics of a //! hardware component. These values are used during simulation to enforce that //! components behave realistically — for example, ROM won't drive data onto the bus //! until its access time has elapsed in virtual time. //! //! # Hardware Accuracy //! //! If the clock is overclocked beyond a component's maximum frequency, or if timing //! constraints are violated (setup time, hold time, access time), the component will //! **not** produce correct results — just like real hardware. This is a feature, not //! a bug: it allows users to observe what happens when they push hardware beyond its //! limits. //! //! # Timing Violations //! //! When a timing violation is detected, it is recorded but does not crash the simulation. //! The component simply produces incorrect or undefined results, as real hardware would. use std::fmt; use std::time::Duration; /// Timing constraints for a hardware component. /// /// These values are based on datasheet specifications and define the real-world /// timing behavior that the simulation enforces. #[derive(Debug, Clone, Copy, PartialEq)] pub struct ComponentTiming { /// Human-readable name for this timing profile (e.g., "Intel 4001 ROM"). pub name: &'static str, /// Time from valid address on the bus to data being available. /// /// For ROM/RAM: how long after the address is latched until the data output /// is guaranteed to be valid. /// /// Intel 4001 ROM: ~500ns /// Intel 4002 RAM: ~500ns /// Intel 4003 SR: ~200ns (faster due to simpler design) pub access_time: Duration, /// Time from a clock edge until an output signal changes. /// /// This models the internal gate delay of the component. /// After a clock edge is detected, the component's output doesn't change /// instantly — it takes `propagation_delay` nanoseconds. /// /// Intel 4001 ROM: ~200ns /// Intel 4002 RAM: ~200ns /// Intel 4003 SR: ~100ns pub propagation_delay: Duration, /// Minimum time that address/data inputs must be stable before a clock edge. /// /// If the address changes within `setup_time` before a clock edge, /// the component may latch an incorrect address — just like real hardware. /// /// Intel 4001 ROM: ~100ns /// Intel 4002 RAM: ~100ns pub setup_time: Duration, /// Minimum time that address/data inputs must remain stable after a clock edge. /// /// If the address changes within `hold_time` after a clock edge, /// the component's latched value may be corrupted. /// /// Intel 4001 ROM: ~50ns /// Intel 4002 RAM: ~50ns pub hold_time: Duration, /// Maximum clock frequency this component supports (in Hz). /// /// If the clock exceeds this frequency, the component may not complete /// its internal operations before the next clock edge arrives. /// /// Intel 4001 ROM: 750 kHz /// Intel 4002 RAM: 750 kHz /// Intel 4003 SR: 1 MHz (can handle faster clocks) pub max_clock_frequency: f64, /// How frequently (in virtual time) this component's `update_at_time()` /// should be called. Smaller values = more frequent updates = more accurate /// but more CPU-intensive. /// /// This determines how often the component thread wakes up to check for /// clock edges and timing events. /// /// Intel 4001 ROM: 100ns (check every 100ns of virtual time) /// Intel 4003 SR: 50ns (more frequent due to faster timing) pub min_update_interval: Duration, } impl ComponentTiming { /// Timing profile for the Intel 4001 ROM chip. /// /// The 4001 is a 256-byte mask-programmable ROM with 4-bit I/O ports. /// Access time: 500ns from address valid to data output valid. pub fn intel_4001() -> Self { ComponentTiming { name: "Intel 4001 ROM", access_time: Duration::from_nanos(500), propagation_delay: Duration::from_nanos(200), setup_time: Duration::from_nanos(100), hold_time: Duration::from_nanos(50), max_clock_frequency: 750_000.0, min_update_interval: Duration::from_nanos(100), } } /// Timing profile for the Intel 4002 RAM chip. /// /// The 4002 is a 320-bit dynamic RAM (80 x 4-bit words) with 4-bit output port. /// Access time: 500ns from address valid to data output valid. pub fn intel_4002() -> Self { ComponentTiming { name: "Intel 4002 RAM", access_time: Duration::from_nanos(500), propagation_delay: Duration::from_nanos(200), setup_time: Duration::from_nanos(100), hold_time: Duration::from_nanos(50), max_clock_frequency: 750_000.0, min_update_interval: Duration::from_nanos(100), } } /// Timing profile for the Intel 4003 shift register. /// /// The 4003 is a 10-bit static shift register with serial/parallel I/O. /// Faster access time (200ns) than ROM/RAM due to simpler design. pub fn intel_4003() -> Self { ComponentTiming { name: "Intel 4003 Shift Register", access_time: Duration::from_nanos(200), propagation_delay: Duration::from_nanos(100), setup_time: Duration::from_nanos(50), hold_time: Duration::from_nanos(30), max_clock_frequency: 1_000_000.0, // Can handle 1 MHz min_update_interval: Duration::from_nanos(50), } } /// Timing profile for the Intel 4004 CPU. /// /// The 4004 is the central processing unit that generates control signals. /// Its timing is defined by the clock it receives rather than access times. pub fn intel_4004() -> Self { ComponentTiming { name: "Intel 4004 CPU", access_time: Duration::ZERO, // CPU doesn't have "access time" propagation_delay: Duration::from_nanos(150), setup_time: Duration::from_nanos(100), hold_time: Duration::from_nanos(50), max_clock_frequency: 750_000.0, // 740 kHz nominal, 750 kHz max min_update_interval: Duration::from_nanos(100), } } /// Sentinel value representing "no frequency limit". /// /// Using a large finite value instead of `f64::INFINITY` avoids NaN /// propagation in downstream arithmetic (e.g., `1.0 / frequency`). /// 1 PHz (petahertz) is effectively unlimited for any real-world component. pub const NO_FREQUENCY_LIMIT: f64 = 1_000_000_000_000_000.0; // 1 PHz /// Timing profile for a generic clock generator. /// /// Clocks don't have access times — they just toggle at their configured frequency. pub fn generic_clock() -> Self { ComponentTiming { name: "Generic Clock", access_time: Duration::ZERO, propagation_delay: Duration::from_nanos(10), setup_time: Duration::ZERO, hold_time: Duration::ZERO, max_clock_frequency: Self::NO_FREQUENCY_LIMIT, min_update_interval: Duration::from_nanos(50), } } /// Default timing with zero delays (backward-compatible). /// /// Components that haven't been migrated to the timing model yet /// use this profile, which imposes no timing constraints. pub fn default_no_constraints() -> Self { ComponentTiming { name: "Default (no constraints)", access_time: Duration::ZERO, propagation_delay: Duration::ZERO, setup_time: Duration::ZERO, hold_time: Duration::ZERO, max_clock_frequency: Self::NO_FREQUENCY_LIMIT, min_update_interval: Duration::from_nanos(100), } } /// Calculate the minimum clock period this component supports. /// /// Derived from `max_clock_frequency`: period = 1 / frequency. /// Returns `Duration::ZERO` for components with no practical frequency limit /// (i.e., `max_clock_frequency >= NO_FREQUENCY_LIMIT`). pub fn min_clock_period(&self) -> Duration { if self.max_clock_frequency <= 0.0 || self.max_clock_frequency >= Self::NO_FREQUENCY_LIMIT { Duration::ZERO } else { Duration::from_secs_f64(1.0 / self.max_clock_frequency) } } /// Check if a given clock frequency exceeds this component's maximum. /// /// Returns `true` if the frequency is within spec, `false` if overclocked. /// Non-finite values (infinity, NaN) are always rejected. pub fn supports_frequency(&self, frequency_hz: f64) -> bool { frequency_hz.is_finite() && frequency_hz <= self.max_clock_frequency } /// Check if enough virtual time has elapsed since an event. /// /// Returns `Ok(())` if `elapsed >= access_time`, `Err(TimingViolation)` otherwise. pub fn check_access_time(&self, elapsed: Duration) -> Result<(), TimingViolation> { if elapsed >= self.access_time { Ok(()) } else { Err(TimingViolation::AccessTimeNotElapsed { component: self.name, required: self.access_time, actual: elapsed, }) } } /// Check if a signal was stable for the required setup time before a clock edge. /// /// Returns `Ok(())` if `stability_duration >= setup_time`, `Err(TimingViolation)` otherwise. pub fn check_setup_time(&self, stability_duration: Duration) -> Result<(), TimingViolation> { if stability_duration >= self.setup_time { Ok(()) } else { Err(TimingViolation::SetupTimeViolation { component: self.name, required: self.setup_time, actual: stability_duration, }) } } /// Check if a signal remained stable for the required hold time after a clock edge. /// /// Returns `Ok(())` if `stability_duration >= hold_time`, `Err(TimingViolation)` otherwise. pub fn check_hold_time(&self, stability_duration: Duration) -> Result<(), TimingViolation> { if stability_duration >= self.hold_time { Ok(()) } else { Err(TimingViolation::HoldTimeViolation { component: self.name, required: self.hold_time, actual: stability_duration, }) } } } impl Default for ComponentTiming { fn default() -> Self { Self::default_no_constraints() } } /// A timing violation detected during simulation. /// /// Timing violations are expected when overclocking or when signals change /// too quickly. They don't crash the simulation — instead, the component /// produces incorrect results, just like real hardware. #[derive(Debug, Clone)] pub enum TimingViolation { /// The component's access time has not yet elapsed since the address was latched. /// /// The data output is not yet valid. Reading the bus now returns undefined data. AccessTimeNotElapsed { component: &'static str, required: Duration, actual: Duration, }, /// An input signal did not remain stable for the required setup time /// before a clock edge. /// /// The component may have latched an incorrect value. SetupTimeViolation { component: &'static str, required: Duration, actual: Duration, }, /// An input signal did not remain stable for the required hold time /// after a clock edge. /// /// The component's latched value may be corrupted. HoldTimeViolation { component: &'static str, required: Duration, actual: Duration, }, /// The clock frequency exceeds the component's maximum rated frequency. /// /// The component may not complete its internal operations before the /// next clock edge arrives. Overclocked { component: &'static str, max_frequency_hz: f64, actual_frequency_hz: f64, }, } impl std::error::Error for TimingViolation {} impl fmt::Display for TimingViolation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TimingViolation::AccessTimeNotElapsed { component, required, actual, } => write!( f, "{}: Access time not elapsed (need {:?}, got {:?})", component, required, actual ), TimingViolation::SetupTimeViolation { component, required, actual, } => write!( f, "{}: Setup time violation (need {:?}, got {:?})", component, required, actual ), TimingViolation::HoldTimeViolation { component, required, actual, } => write!( f, "{}: Hold time violation (need {:?}, got {:?})", component, required, actual ), TimingViolation::Overclocked { component, max_frequency_hz, actual_frequency_hz, } => write!( f, "{}: Overclocked (max {:.0} Hz, actual {:.0} Hz)", component, max_frequency_hz, actual_frequency_hz ), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_intel_4001_timing() { let timing = ComponentTiming::intel_4001(); assert_eq!(timing.access_time, Duration::from_nanos(500)); assert_eq!(timing.propagation_delay, Duration::from_nanos(200)); assert_eq!(timing.setup_time, Duration::from_nanos(100)); assert_eq!(timing.hold_time, Duration::from_nanos(50)); assert_eq!(timing.max_clock_frequency, 750_000.0); } #[test] fn test_intel_4003_timing_faster() { let rom = ComponentTiming::intel_4001(); let sr = ComponentTiming::intel_4003(); // Shift register should be faster than ROM assert!(sr.access_time < rom.access_time); assert!(sr.max_clock_frequency > rom.max_clock_frequency); } #[test] fn test_supports_frequency() { let timing = ComponentTiming::intel_4001(); assert!(timing.supports_frequency(500_000.0)); // 500 kHz — within spec assert!(timing.supports_frequency(750_000.0)); // 750 kHz — max rated assert!(!timing.supports_frequency(1_000_000.0)); // 1 MHz — overclocked! } #[test] fn test_check_access_time() { let timing = ComponentTiming::intel_4001(); // Not enough time elapsed let result = timing.check_access_time(Duration::from_nanos(300)); assert!(result.is_err()); // Exactly enough time let result = timing.check_access_time(Duration::from_nanos(500)); assert!(result.is_ok()); // More than enough time let result = timing.check_access_time(Duration::from_nanos(800)); assert!(result.is_ok()); } #[test] fn test_check_setup_time() { let timing = ComponentTiming::intel_4001(); assert!(timing.check_setup_time(Duration::from_nanos(50)).is_err()); assert!(timing.check_setup_time(Duration::from_nanos(100)).is_ok()); assert!(timing.check_setup_time(Duration::from_nanos(200)).is_ok()); } #[test] fn test_overclocking_scenario() { let rom = ComponentTiming::intel_4001(); let sr = ComponentTiming::intel_4003(); // At 2 MHz clock (500ns cycle, 250ns per phase): let clock_freq = 2_000_000.0; let phase_time = Duration::from_nanos(250); // ROM can't handle 2 MHz assert!(!rom.supports_frequency(clock_freq)); // ROM access time is 500ns but only gets 250ns per phase assert!(rom.check_access_time(phase_time).is_err()); // Shift register's access time (200ns) fits within the 250ns phase at 2 MHz, // but it can't actually handle 2 MHz clock frequency (max 1 MHz). // We only verify access time here, not frequency support. assert!(sr.check_access_time(phase_time).is_ok()); } #[test] fn test_min_clock_period() { let timing = ComponentTiming::intel_4001(); let period = timing.min_clock_period(); assert_eq!(period, Duration::from_secs_f64(1.0 / 750_000.0)); } #[test] fn test_default_no_constraints() { let timing = ComponentTiming::default_no_constraints(); assert_eq!(timing.access_time, Duration::ZERO); // Supports any reasonable finite frequency assert!(timing.supports_frequency(1_000_000_000.0)); // 1 GHz — well within limit assert!(timing.supports_frequency(999_999_999_999_999.0)); // Just under sentinel assert!(!timing.supports_frequency(f64::INFINITY)); // Non-finite rejected assert!(!timing.supports_frequency(f64::NAN)); // NaN rejected } #[test] fn test_timing_violation_display() { let violation = TimingViolation::AccessTimeNotElapsed { component: "Intel 4001 ROM", required: Duration::from_nanos(500), actual: Duration::from_nanos(250), }; let display = format!("{}", violation); assert!(display.contains("Access time not elapsed")); assert!(display.contains("Intel 4001 ROM")); } }