Skip to main content

rhdl_ed25519_scalable_core/
simulation.rs

1//! Host-only, constant-memory cycle simulation without waveform recording.
2//!
3//! RHDL's `run_fn` records trace pages, which is useful for waveforms but costly
4//! for throughput experiments. This driver uses the **same** `Synchronous::sim`
5//! hardware transitions with tracing disabled. It is not a cryptographic model
6//! or a replacement for RTL simulation. One [`CycleSim::tick`] is one rising edge.
7//!
8//! # Measuring throughput
9//!
10//! Throughput tests (`sustained_*` in the crate's tests) offer a request
11//! whenever `ready` is high and time the completions in the middle of the
12//! run. The run must be several times longer than the credit pool (2,048):
13//! a shorter run admits everything at once and measures a start-up transient
14//! (15/11/3/1: 32.6 cycles/signature over 700 signatures, 26.43 over 6,000).
15//! The large profiles simulate at roughly 80 cycles per second, so a steady
16//! state run takes about 40 minutes on one core.
17
18use rhdl::prelude::*;
19
20/// A native RHDL simulator with no per-cycle history allocation.
21pub struct CycleSim<C: Synchronous> {
22    /// The circuit description; its `sim` method computes one evaluation.
23    circuit: C,
24    /// The circuit's register state, updated in place on every edge.
25    state: C::S,
26    /// Non-reset rising edges so far (the reset edge in `new` is not
27    /// counted).
28    cycles: u64,
29}
30
31/// Construction, stepping and cycle count.
32impl<C: Synchronous> CycleSim<C> {
33    /// Construct a simulator and apply one synchronous reset edge.
34    pub fn new(circuit: C) -> Self {
35        let state = circuit.init();
36        let mut sim = Self {
37            circuit,
38            state,
39            cycles: 0,
40        };
41        // Inputs are don't-care while reset is asserted.
42        sim.edge(C::I::dont_care(), true);
43        sim
44    }
45
46    /// Evaluates one full clock period with `input` held and reset at
47    /// `reset_high`: clock low, then clock high. Evaluating both levels
48    /// gives the registers in `state` the low-to-high transition they latch
49    /// on. Returns the output seen with the clock high, that is, just after
50    /// the rising edge.
51    fn edge(&mut self, input: C::I, reset_high: bool) -> C::O {
52        for level in [false, true] {
53            let cr = rhdl::core::clock_reset(
54                rhdl::core::clock::clock(level),
55                rhdl::core::types::reset::reset(reset_high),
56            );
57            let output = self.circuit.sim(cr, input, &mut self.state);
58            if level {
59                return output;
60            }
61        }
62        // The loop always returns on the high level.
63        unreachable!()
64    }
65
66    /// Advance one clock cycle; inputs must obey the circuit's ready/valid rules.
67    pub fn tick(&mut self, input: C::I) -> C::O {
68        self.cycles += 1;
69        self.edge(input, false)
70    }
71
72    /// This cycle's output for `input`, without clocking (clock low, so no
73    /// register changes). A testbench uses it when its own input depends on
74    /// an output that itself depends on another input (a `ready` that
75    /// includes a downstream `ready`).
76    pub fn peek(&mut self, input: C::I) -> C::O {
77        let low = rhdl::core::clock_reset(rhdl::core::clock::clock(false), rhdl::core::types::reset::reset(false));
78        self.circuit.sim(low, input, &mut self.state)
79    }
80
81    /// Like [`tick`](Self::tick), but also returns the output seen before
82    /// the edge (clock low), as `(before, after)`. A testbench needs the
83    /// `before` value of an output that depends combinationally on an input
84    /// it drives (a request gated by the testbench's `ready`): that is the
85    /// value the circuit presents while the edge latches it.
86    pub fn tick_both(&mut self, input: C::I) -> (C::O, C::O) {
87        self.cycles += 1;
88        let low = rhdl::core::clock_reset(rhdl::core::clock::clock(false), rhdl::core::types::reset::reset(false));
89        let high = rhdl::core::clock_reset(rhdl::core::clock::clock(true), rhdl::core::types::reset::reset(false));
90        let before = self.circuit.sim(low, input, &mut self.state);
91        let after = self.circuit.sim(high, input, &mut self.state);
92        (before, after)
93    }
94
95    /// Number of non-reset rising edges simulated so far.
96    pub fn cycles(&self) -> u64 {
97        self.cycles
98    }
99}