rust_hdl_core/
simulate.rs

1use crossbeam::channel::{bounded, Receiver, Sender};
2use crossbeam::channel::{RecvError, SendError};
3
4use crate::block::Block;
5use crate::check_error::{check_all, CheckError};
6use crate::vcd_probe::{write_vcd_change, write_vcd_dump, write_vcd_header};
7use std::io::Write;
8use std::thread::JoinHandle;
9
10/// Update changes to a circuit until it stabilizes
11///
12/// # Arguments
13///
14/// * `uut` - reference to the circuit - must implement the [Block] trait
15/// * `max_iters` - the maximum number of iterations to try and stabilize the circuit
16///
17/// Returns `true` if the circuit stabilizes, and `false` if not.  Generally you won't
18/// need this function directly.
19pub fn simulate<B: Block>(uut: &mut B, max_iters: usize) -> bool {
20    for _ in 0..max_iters {
21        uut.update_all();
22        if !uut.has_changed() {
23            return true;
24        }
25    }
26    false
27}
28
29#[derive(Clone, Debug, PartialEq)]
30/// The error type returned by a simulation
31pub enum SimError {
32    /// The simulation terminated prematurely (i.e., something went wrong)
33    SimTerminated,
34    /// The simulation reached the maximum allowed time for the simulation
35    MaxTimeReached,
36    /// The simulation halted - usually this means an assertion failed
37    SimHalted,
38    /// The circuit failed to converge.  This means the logic has some issue (like an oscillation).
39    FailedToConverge,
40    /// Something went wrong with the circuit check (either a missing connection or other issue, like a latching write).
41    Check(CheckError),
42    /// The simulation panicked.  This usually means `.unwrap` was called on a result in the testbench.
43    SimPanic,
44}
45
46impl From<CheckError> for SimError {
47    fn from(x: CheckError) -> Self {
48        SimError::Check(x)
49    }
50}
51
52impl From<RecvError> for SimError {
53    fn from(_x: RecvError) -> Self {
54        SimError::SimTerminated
55    }
56}
57
58impl<T> From<SendError<T>> for SimError {
59    fn from(_x: SendError<T>) -> Self {
60        SimError::SimTerminated
61    }
62}
63
64/// Result type used by the simulation routines.
65pub type Result<T> = std::result::Result<T, SimError>;
66
67enum TriggerType<T> {
68    Never,
69    Time(u64),
70    Function(Box<dyn Fn(&T) -> bool + Send>),
71    Clock(u64),
72    Halt,
73}
74
75struct Message<T> {
76    kind: TriggerType<T>,
77    circuit: Box<T>,
78}
79
80enum MessageOrPanic<T> {
81    Message(Message<T>),
82    Panic,
83}
84
85struct Worker<T> {
86    id: usize,
87    channel_to_worker: Sender<Message<T>>,
88    kind: TriggerType<T>,
89}
90
91/// The [CustomLogicFn] is a boxed function that can be used to implement
92/// things (like tri-state buffers or open collector shared busses) that
93/// are otherwise difficult or impossible to model.
94pub type CustomLogicFn<T> = Box<dyn Fn(&mut T) -> ()>;
95
96/// This type represents a simulation over a circuit `T`.   To simulate
97/// a circuit, you will need to construct one of these structs.
98pub struct Simulation<T> {
99    workers: Vec<Worker<T>>,
100    recv: Receiver<MessageOrPanic<T>>,
101    channel_to_sim: Sender<MessageOrPanic<T>>,
102    time: u64,
103    testbenches: Vec<JoinHandle<Result<()>>>,
104    custom_logic: Vec<CustomLogicFn<T>>,
105}
106
107/// The `Sim` struct is used to communicate with a simulation.  Every testbench
108/// will be provided with a copy of this struct, and will use it to communicate
109/// with the core simulation.
110pub struct Sim<T> {
111    time: u64,
112    to_sim: Sender<MessageOrPanic<T>>,
113    from_sim: Receiver<Message<T>>,
114}
115
116struct NextTime {
117    time: u64,
118    idx: usize,
119    clocks_only: bool,
120    halted: bool,
121}
122
123impl<T: Send + 'static + Block> Default for Simulation<T> {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl<T: Send + 'static + Block> Simulation<T> {
130    /// Construct a simulation struct
131    pub fn new() -> Simulation<T> {
132        let (send, recv) = bounded(0);
133        Self {
134            workers: vec![],
135            recv,
136            channel_to_sim: send,
137            time: 0,
138            testbenches: vec![],
139            custom_logic: vec![],
140        }
141    }
142    /// Add a clock function to the simulation
143    ///
144    /// # Arguments
145    ///
146    /// * `interval` - the number of picoseconds between calls to the clock closure
147    /// * `clock_fn` - a closure to change the clock state of the circuit
148    ///
149    /// # Example
150    ///
151    /// ```rust
152    /// # use rust_hdl_core::prelude::*;
153    ///
154    /// #[derive(LogicBlock)]
155    /// struct Foo {
156    ///    pub clock: Signal<In, Clock>
157    /// }
158    ///
159    /// impl Logic for Foo {
160    ///   #[hdl_gen]
161    ///   fn update(&mut self) {
162    ///   }
163    /// }
164    ///
165    /// let mut sim : Simulation<Foo> = Default::default();
166    /// sim.add_clock(5, |x| x.clock.next = !x.clock.val()); // Toggles the clock every 5 picoseconds.
167    /// ```
168    ///
169    pub fn add_clock<F>(&mut self, interval: u64, clock_fn: F)
170    where
171        F: Fn(&mut Box<T>) -> () + Send + 'static + std::panic::RefUnwindSafe,
172    {
173        self.add_testbench(move |mut ep: Sim<T>| {
174            let mut x = ep.init()?;
175            loop {
176                x = ep.clock(interval, x)?;
177                clock_fn(&mut x);
178            }
179        });
180    }
181    /// Add a phased clock to the simulation
182    ///
183    /// Sometimes you will need to control the phasing of a clock so that it starts at some
184    /// non-zero time. This method allows you to add a clock to a simulation and control the
185    /// initial delay.
186    ///
187    /// # Arguments
188    ///
189    /// * `interval` - the delay in picoseconds between the clock function being called
190    /// * `phase_delay` - the number of picoseconds to wait before the clock starts being toggled
191    /// * `clock_fn` - the function that toggles the actual clock.
192    ///
193    /// # Example
194    ///
195    /// ```rust
196    /// # use rust_hdl_core::prelude::*;
197    ///
198    /// #[derive(LogicBlock)]
199    /// struct Foo {
200    ///    pub clock: Signal<In, Clock>
201    /// }
202    ///
203    /// impl Logic for Foo {
204    ///   #[hdl_gen]
205    ///   fn update(&mut self) {
206    ///   }
207    /// }
208    ///
209    /// let mut sim : Simulation<Foo> = Default::default();
210    /// // Toggles every 5 picoseconds, starting after 15 picoseconds
211    /// sim.add_phased_clock(5, 15, |x| x.clock.next = !x.clock.val());
212    /// ```
213    ///
214    pub fn add_phased_clock<F>(&mut self, interval: u64, phase_delay: u64, clock_fn: F)
215    where
216        F: Fn(&mut Box<T>) -> () + Send + 'static + std::panic::RefUnwindSafe,
217    {
218        self.add_testbench(move |mut ep: Sim<T>| {
219            let mut x = ep.init()?;
220            x = ep.wait(phase_delay, x)?;
221            loop {
222                x = ep.clock(interval, x)?;
223                clock_fn(&mut x);
224            }
225        });
226    }
227    /// Add a testbench to the simulation
228    ///
229    /// # Arguments
230    ///
231    /// * `testbench` - a testbench function that will be executed through the
232    /// simulation.  Needs to return a simulation [Result], be [Send] and
233    /// [RefUnwindSafe] (no FFI).
234    ///
235    /// # Example
236    ///
237    pub fn add_testbench<F>(&mut self, testbench: F)
238    where
239        F: Fn(Sim<T>) -> Result<()> + Send + 'static + std::panic::RefUnwindSafe,
240    {
241        let ep = self.endpoint();
242        self.testbenches.push(std::thread::spawn(move || {
243            let ep_panic = ep.to_sim.clone();
244            let result = std::panic::catch_unwind(|| testbench(ep));
245            match result {
246                Ok(x) => x,
247                Err(_e) => {
248                    ep_panic.send(MessageOrPanic::Panic).unwrap();
249                    Err(SimError::SimPanic)
250                }
251            }
252        }));
253    }
254    pub fn add_custom_logic<F>(&mut self, logic: F)
255    where
256        F: Fn(&mut T) -> () + 'static,
257    {
258        self.custom_logic.push(Box::new(logic));
259    }
260    pub fn endpoint(&mut self) -> Sim<T> {
261        let (send_to_worker, recv_from_sim_to_worker) = bounded(0);
262        let id = self.workers.len();
263        let worker = Worker {
264            id,
265            channel_to_worker: send_to_worker,
266            kind: TriggerType::Never,
267        };
268        self.workers.push(worker);
269        Sim {
270            to_sim: self.channel_to_sim.clone(),
271            from_sim: recv_from_sim_to_worker,
272            time: 0,
273        }
274    }
275    fn dispatch(&mut self, idx: usize, x: Box<T>) -> Result<Box<T>> {
276        let worker = &mut self.workers[idx];
277        worker.channel_to_worker.send(Message {
278            kind: TriggerType::Time(self.time),
279            circuit: x,
280        })?;
281        let x = self.recv.recv()?;
282        let mut x = match x {
283            MessageOrPanic::Message(x) => x,
284            MessageOrPanic::Panic => {
285                return Err(SimError::SimPanic);
286            }
287        };
288        worker.kind = x.kind;
289        // Update the circuit
290        let mut converged = false;
291        for _ in 0..100 {
292            for l in &self.custom_logic {
293                l(&mut x.circuit);
294            }
295            x.circuit.update_all();
296            if !x.circuit.has_changed() {
297                converged = true;
298                break;
299            }
300        }
301        if !converged {
302            Err(SimError::FailedToConverge)
303        } else {
304            Ok(x.circuit)
305        }
306    }
307    fn scan_workers(&self, x: &T) -> NextTime {
308        let mut min_time = !0_u64;
309        let mut min_idx = 0;
310        let mut only_clock_waiters = true;
311        for worker in self.workers.iter() {
312            match &worker.kind {
313                TriggerType::Halt => {
314                    return NextTime {
315                        halted: true,
316                        time: !0,
317                        idx: !0,
318                        clocks_only: false,
319                    }
320                }
321                TriggerType::Never => {}
322                TriggerType::Time(t) => {
323                    only_clock_waiters = false;
324                    if *t < min_time {
325                        min_time = *t;
326                        min_idx = worker.id;
327                    }
328                }
329                TriggerType::Function(watch) => {
330                    only_clock_waiters = false;
331                    if watch(&x) {
332                        min_idx = worker.id;
333                        min_time = self.time;
334                        break;
335                    }
336                }
337                TriggerType::Clock(t) => {
338                    if *t < min_time {
339                        min_time = *t;
340                        min_idx = worker.id;
341                    }
342                }
343            }
344        }
345        NextTime {
346            time: min_time,
347            idx: min_idx,
348            clocks_only: only_clock_waiters,
349            halted: false,
350        }
351    }
352    fn terminate(&mut self) {
353        self.workers.clear();
354        for handle in std::mem::take(&mut self.testbenches) {
355            let _ = handle.join().unwrap();
356        }
357    }
358    pub fn run(&mut self, mut x: Box<T>, max_time: u64) -> Result<()> {
359        x.as_mut().connect_all();
360        check_all(x.as_mut())?;
361        // First initialize the workers.
362        for id in 0..self.workers.len() {
363            x = self.dispatch(id, x)?;
364        }
365        // Next run until we have no one else waiting
366        let mut halted = false;
367        while self.time < max_time {
368            let next = self.scan_workers(&x);
369            if next.time == !0 || next.clocks_only || next.halted {
370                halted = next.halted;
371                break;
372            }
373            self.time = next.time;
374            x = self.dispatch(next.idx, x)?;
375        }
376        self.terminate();
377        if self.time >= max_time {
378            return Err(SimError::MaxTimeReached);
379        }
380        if halted {
381            return Err(SimError::SimHalted);
382        }
383        Ok(())
384    }
385    pub fn run_to_file(&mut self, x: Box<T>, max_time: u64, name: &str) -> Result<()> {
386        let mut vcd = vec![];
387        let result = self.run_traced(x, max_time, &mut vcd);
388        std::fs::write(name, vcd).unwrap();
389        result
390    }
391    pub fn run_traced<W: Write>(&mut self, mut x: Box<T>, max_time: u64, trace: W) -> Result<()> {
392        x.as_mut().connect_all();
393        check_all(x.as_mut())?;
394        let mut vcd = write_vcd_header(trace, x.as_ref());
395        // First initialize the workers.
396        for id in 0..self.workers.len() {
397            x = self.dispatch(id, x)?;
398        }
399        vcd = write_vcd_dump(vcd, x.as_ref());
400        let mut halted = false;
401        // Next run until we have no one else waiting
402        while self.time < max_time {
403            let next = self.scan_workers(x.as_ref());
404            if next.time == !0 || next.clocks_only || next.halted {
405                halted = next.halted;
406                break;
407            }
408            self.time = next.time;
409            x = self.dispatch(next.idx, x)?;
410            vcd.timestamp(next.time).unwrap();
411            vcd = write_vcd_change(vcd, x.as_ref());
412        }
413        self.terminate();
414        if self.time >= max_time {
415            return Err(SimError::MaxTimeReached);
416        }
417        if halted {
418            return Err(SimError::SimHalted);
419        }
420        Ok(())
421    }
422}
423
424pub mod sim_time {
425    pub const ONE_PICOSECOND: u64 = 1;
426    pub const ONE_NANOSECOND: u64 = 1000 * ONE_PICOSECOND;
427    pub const ONE_MICROSECOND: u64 = 1000 * ONE_NANOSECOND;
428    pub const ONE_MILLISECOND: u64 = 1000 * ONE_MICROSECOND;
429    pub const ONE_SEC: u64 = 1000 * ONE_MILLISECOND;
430}
431
432impl<T> Sim<T> {
433    pub fn init(&self) -> Result<Box<T>> {
434        Ok(self.from_sim.recv()?.circuit)
435    }
436    pub fn watch<S>(&mut self, check: S, x: Box<T>) -> Result<Box<T>>
437    where
438        S: Fn(&T) -> bool + Send + 'static,
439    {
440        self.to_sim.send(MessageOrPanic::Message(Message {
441            kind: TriggerType::Function(Box::new(check)),
442            circuit: x,
443        }))?;
444        let t = self.from_sim.recv()?;
445        if let TriggerType::Time(t0) = t.kind {
446            self.time = t0;
447        }
448        Ok(t.circuit)
449    }
450    pub fn clock(&mut self, delta: u64, x: Box<T>) -> Result<Box<T>> {
451        self.to_sim.send(MessageOrPanic::Message(Message {
452            kind: TriggerType::Clock(delta + self.time),
453            circuit: x,
454        }))?;
455        let t = self.from_sim.recv()?;
456        if let TriggerType::Time(t0) = t.kind {
457            self.time = t0;
458        }
459        Ok(t.circuit)
460    }
461    pub fn wait(&mut self, delta: u64, x: Box<T>) -> Result<Box<T>> {
462        self.to_sim.send(MessageOrPanic::Message(Message {
463            kind: TriggerType::Time(delta + self.time),
464            circuit: x,
465        }))?;
466        let t = self.from_sim.recv()?;
467        if let TriggerType::Time(t0) = t.kind {
468            self.time = t0;
469        }
470        Ok(t.circuit)
471    }
472    pub fn done(&self, x: Box<T>) -> Result<()> {
473        self.to_sim.send(MessageOrPanic::Message(Message {
474            kind: TriggerType::Never,
475            circuit: x,
476        }))?;
477        Ok(())
478    }
479    pub fn halt(&self, x: Box<T>) -> Result<()> {
480        self.to_sim.send(MessageOrPanic::Message(Message {
481            kind: TriggerType::Halt,
482            circuit: x,
483        }))?;
484        Err(SimError::SimHalted)
485    }
486    pub fn time(&self) -> u64 {
487        self.time
488    }
489}
490
491#[macro_export]
492macro_rules! wait_clock_true {
493    ($sim: ident, $($clock: ident).+, $me: expr) => {
494        $me = $sim.watch(|x| x.$($clock).+.val().clk, $me)?
495    };
496}
497
498#[macro_export]
499macro_rules! wait_clock_false {
500    ($sim: ident, $($clock: ident).+, $me: expr) => {
501        $me = $sim.watch(|x| !x.$($clock).+.val().clk, $me)?
502    };
503}
504
505#[macro_export]
506macro_rules! wait_clock_cycle {
507    ($sim: ident, $($clock: ident).+, $me: expr) => {
508        if $me.$($clock).+.val().clk {
509            wait_clock_false!($sim, $($clock).+, $me);
510            wait_clock_true!($sim, $($clock).+, $me);
511        } else {
512            wait_clock_true!($sim, $($clock).+, $me);
513            wait_clock_false!($sim, $($clock).+, $me);
514        }
515    };
516    ($sim: ident, $clock: ident, $me: expr, $count: expr) => {
517        for _i in 0..$count {
518            wait_clock_cycle!($sim, $clock, $me);
519        }
520    };
521}
522
523#[macro_export]
524macro_rules! wait_clock_cycles {
525    ($sim: ident, $($clock: ident).+, $me: expr, $count: expr) => {
526        for _i in 0..$count {
527            wait_clock_cycle!($sim, $($clock).+, $me);
528        }
529    };
530}
531
532#[macro_export]
533macro_rules! sim_assert {
534    ($sim: ident, $test: expr, $circuit: ident) => {
535        if !($test) {
536            println!("HALT {}", stringify!($test));
537            return $sim.halt($circuit);
538        }
539    };
540}
541
542#[macro_export]
543macro_rules! sim_assert_eq {
544    ($sim: ident, $lhs: expr, $rhs: expr, $circuit: ident) => {
545        if !($lhs == $rhs) {
546            println!(
547                "HALT {} != {},  {:?} != {:?}",
548                stringify!($lhs),
549                stringify!($rhs),
550                $lhs,
551                $rhs
552            );
553            return $sim.halt($circuit);
554        }
555    };
556}
557
558#[macro_export]
559macro_rules! simple_sim {
560    ($kind: ty, $($clock: ident).+, $clock_speed_hz: expr, $fixture: ident, $testbench: expr) => {
561        {
562            let mut sim = Simulation::new();
563            let half_period = 1_000_000_000_000 / (2 * $clock_speed_hz);
564            sim.add_clock(half_period, |x: &mut Box<$kind>| x.$($clock).+.next = !x.$($clock).+.val());
565            sim.add_testbench(move |mut $fixture: Sim<$kind>| {
566                $testbench
567            });
568            sim
569        }
570    }
571}
572
573pub const SIMULATION_TIME_ONE_SECOND: u64 = 1_000_000_000_000;