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}