Skip to main content

rhdl_dsig_scalable_core/
relay.rs

1//! Relay stations and delay lines: registers that let slice traffic cross dies.
2//!
3//! # The problem: handshakes that span three SLRs
4//!
5//! U280 is three dies (SLRs) stacked on an interposer. The HBM ports, the
6//! AXI-Lite control port and therefore the engine's shared blocks (control
7//! registers, [message reader](crate::msg_reader) and
8//! [HBM writers](crate::hbm_writer)) belong at the bottom of SLR0, the
9//! [certificate unit](crate::cert_unit) sits in SLR1, and build v9
10//! (2026-10-17) pinned each [slice](crate::slice) wholly into one SLR
11//! (`fpga/u280/engine/floorplan.tcl`) so that no slice straddles a die.
12//!
13//! That worked: every slice landed inside its SLR. But every signal between
14//! a slice and a shared block was built for neighbours, with logic on both
15//! ends of the wire. The slices in SLR2 were two die crossings away, and v9
16//! routed at WNS -4.715 ns (TNS -81,716). Of its 5,000 worst paths:
17//!
18//! | paths | link |
19//! |---|---|
20//! | 2,809 | writer → HBM: the writer's `wvalid` is combinational from the slice's beat, and `beat_ready` from HBM's `wready` goes straight back into the signer |
21//! | 875 | control registers → the slices' BLAKE3 lanes (run configuration) |
22//! | ~180 | certificate unit ↔ slices (certificates in, roots out) |
23//! | ~30 | slice status counters → the register-read mux |
24//!
25//! The worst one ran from a writer counter in SLR1 through the writer's logic,
26//! into slice 3's signer in SLR2, and back down to the HBM interconnect in
27//! SLR0: 9.6 ns for a 4.878 ns clock, 93 % of it wire.
28//!
29//! # The decision: registers on both ends of every long wire
30//!
31//! A long wire is cheap if a flip-flop drives it and at most one LUT sits in
32//! front of the flip-flop it reaches. So every slice-boundary signal goes
33//! through registers in the engine ([`crate::engine`]), by kind:
34//!
35//! * **Full-rate handshakes** (frame beats out) go through a [`RelayPipe`]:
36//!   [`PIPE_STAGES`] Carloni relay stations ([`Relay`]). A handshake cannot
37//!   simply be delayed, because the receiver's "taken" must reach the sender
38//!   in the same clock.
39//! * **Slower handshakes** (messages and certificates in) go through a
40//!   [`ToggleLink`]: plain registers plus a one-bit toggle protocol. It moves
41//!   one item per 6 clocks but costs almost no LUTs (relays cost 21,031 LUTs
42//!   on these two paths in build v12). A signer takes at most one message per
43//!   message-hash round trip and one certificate per 128 signatures.
44//! * **Everything else** (run configuration and `run`, subtree-root pulses,
45//!   the `roots_popped` credit, status counters and the fault bit) goes
46//!   through a [`Delay`]: [`DELAY_STAGES`] plain registers. These signals
47//!   tolerate latency: configuration is static during a run, a root is a
48//!   one-clock pulse that arrives two clocks later, a late credit only makes
49//!   the slice more cautious, and the host reads status long after it
50//!   changes.
51//!
52//! # Relay stations
53//!
54//! A *relay station* (L. P. Carloni, "From Latency-Insensitive Design to
55//! Communication-Based System-Level Design", Proc. IEEE 2015, figure 4) is a
56//! skid buffer: two data registers and a two-state controller inserted into a
57//! valid/ready link. Its outputs in **both** directions come from flip-flops:
58//!
59//! * `void_out` and the data come from the `main` register;
60//! * `stop_out` ("not ready") is just "the controller is in Stall".
61//!
62//! It still moves one item per clock: when the far side stops, the item
63//! already in flight is caught in `aux` instead of being lost. Each station
64//! adds one clock of latency.
65//!
66//! ```text
67//!   SLR2: signer ─▶ [relay 0] ─┐
68//!   SLR1:                      └▶ [relay 1] ─┐
69//!   SLR0:                                    └▶ [relay 2] ─▶ writer ─▶ HBM
70//!            ◀── stop (registered) ── each station ── stop ◀── !beat_ready
71//! ```
72//!
73//! RHDL ships the same station as `rhdl_fpga::lid::carloni::Carloni`. This
74//! one differs in one way: its two data registers are resetless
75//! ([`DFF::resetless`]), so the wide payloads (a certificate is 1,345 bits)
76//! add no loads to the engine's reset tree (build v8's worst paths started at
77//! the reset). Only `void` and the state reset.
78//!
79//! # Protocol mapping
80//!
81//! | handshake | relay |
82//! |---|---|
83//! | `valid` | `!void` |
84//! | the receiver took the item this clock (`beat_ready`, `message_taken`, `certificate_taken`) | `!stop` |
85//!
86//! The receivers' answers are all "taken this clock", so they are low when
87//! nothing is offered. A station with an empty output ignores `stop`, so
88//! that is harmless. The sender sees "taken" as `valid && !stop_out` of the
89//! first station.
90
91use rhdl::prelude::*;
92use rhdl_fpga::core::dff::DFF;
93
94/// Relay stations in each [`RelayPipe`].
95pub const PIPE_STAGES: usize = 3;
96
97/// Index of a pipe's last station (RHDL kernel loops need a named bound,
98/// not an expression).
99const PIPE_LAST: usize = PIPE_STAGES - 1;
100
101/// Registers in each [`Delay`].
102pub const DELAY_STAGES: usize = 2;
103
104/// Index of a delay line's last register.
105const DELAY_LAST: usize = DELAY_STAGES - 1;
106
107/// Inputs of one relay station (Carloni's names).
108#[derive(Clone, Copy, Debug, PartialEq, Digital)]
109pub struct RelayIn<T: Digital> {
110    /// The item from upstream (ignored while `void_in`).
111    pub data_in: T,
112    /// Upstream offers nothing this clock.
113    pub void_in: bool,
114    /// Downstream cannot take an item this clock.
115    pub stop_in: bool,
116}
117
118/// Outputs of one relay station; every field comes from a register.
119#[derive(Clone, Copy, Debug, PartialEq, Digital)]
120pub struct RelayOut<T: Digital> {
121    /// The item held for downstream.
122    pub data_out: T,
123    /// No item is held for downstream.
124    pub void_out: bool,
125    /// Upstream must hold its item (the station is full).
126    pub stop_out: bool,
127}
128
129/// One Carloni relay station carrying a `T`.
130#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
131pub struct Relay<T: Digital> {
132    /// The item presented downstream (resetless).
133    main: DFF<T>,
134    /// The item caught while downstream was stopped (resetless).
135    aux: DFF<T>,
136    /// `main` holds no item; resets to empty.
137    void: DFF<bool>,
138    /// Stall: `aux` holds an item and upstream is stopped; resets to Run.
139    stall: DFF<bool>,
140}
141
142impl<T: Digital> Default for Relay<T> {
143    /// Empty and running after reset; data registers don't-care.
144    fn default() -> Self {
145        Self {
146            main: DFF::resetless(),
147            aux: DFF::resetless(),
148            void: DFF::new(true),
149            stall: DFF::new(false),
150        }
151    }
152}
153
154impl<T: Digital> SynchronousIO for Relay<T> {
155    type I = RelayIn<T>;
156    type O = RelayOut<T>;
157    type Kernel = relay_kernel<T>;
158}
159
160/// Carloni's controller (figure 4), with the state as one bit.
161///
162/// * **Run** (`stall` low): `main` loads the incoming item whenever
163///   downstream is not stopped, or `main` is empty. If downstream stops while
164///   both `main` and the input hold items, the input item goes into `aux`
165///   and the station moves to Stall.
166/// * **Stall**: `stop_out` is high. When downstream releases, `main` takes
167///   the item from `aux` and the station returns to Run (still stopping
168///   upstream for that one clock, so nothing new arrives while `aux` drains).
169#[kernel]
170pub fn relay_kernel<T: Digital>(cr: ClockReset, i: RelayIn<T>, q: RelayQ<T>) -> (RelayOut<T>, RelayD<T>) {
171    let mut d = RelayD::<T>::dont_care();
172    // Control signals, as named in the paper's figure.
173    let mut sel = false;
174    let mut main_en = false;
175    let mut aux_en = false;
176    let mut stop_out = false;
177    let void_out = q.void;
178    let will_stall = i.stop_in && !i.void_in && !void_out;
179    d.stall = q.stall;
180    if !q.stall {
181        if !i.stop_in || (!i.void_in && void_out) {
182            main_en = true;
183        } else if will_stall {
184            d.stall = true;
185            aux_en = true;
186        }
187    } else {
188        stop_out = true;
189        if !i.stop_in {
190            sel = true;
191            main_en = true;
192            d.stall = false;
193        }
194    }
195    d.aux = if aux_en { i.data_in } else { q.aux };
196    let data = if sel { q.aux } else { i.data_in };
197    d.main = if main_en { data } else { q.main };
198    let void = if sel { false } else { i.void_in };
199    d.void = if main_en { void } else { q.void };
200    let mut o = RelayOut::<T> {
201        data_out: q.main,
202        void_out: q.void,
203        stop_out,
204    };
205    if cr.reset.any() {
206        o.void_out = true;
207        o.stop_out = true;
208    }
209    (o, d)
210}
211
212/// Inputs of a [`RelayPipe`].
213#[derive(Clone, Copy, Debug, PartialEq, Digital)]
214pub struct PipeIn<T: Digital> {
215    /// The sender's item.
216    pub data: T,
217    /// The sender offers `data` this clock.
218    pub valid: bool,
219    /// The receiver took the pipe's output item this clock.
220    pub taken: bool,
221}
222
223/// Outputs of a [`RelayPipe`]; both come from registers.
224#[derive(Clone, Copy, Debug, PartialEq, Digital)]
225pub struct PipeOut<T: Digital> {
226    /// The item for the receiver.
227    pub data: T,
228    /// An item is offered to the receiver.
229    pub valid: bool,
230    /// The pipe can take the sender's item this clock (the sender's item was
231    /// taken if it also offered one).
232    pub ready: bool,
233}
234
235/// [`PIPE_STAGES`] relay stations in a row: sender side first.
236#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
237pub struct RelayPipe<T: Digital> {
238    /// The stations; `stages[0]` takes the sender's item.
239    stages: [Relay<T>; PIPE_STAGES],
240}
241
242impl<T: Digital> Default for RelayPipe<T> {
243    /// Every station empty.
244    fn default() -> Self {
245        Self {
246            stages: core::array::from_fn(|_| Relay::default()),
247        }
248    }
249}
250
251impl<T: Digital> SynchronousIO for RelayPipe<T> {
252    type I = PipeIn<T>;
253    type O = PipeOut<T>;
254    type Kernel = relay_pipe_kernel<T>;
255}
256
257/// Wire the stations: data and void flow toward the receiver, stop flows
258/// back.
259#[kernel]
260pub fn relay_pipe_kernel<T: Digital>(_cr: ClockReset, i: PipeIn<T>, q: RelayPipeQ<T>) -> (PipeOut<T>, RelayPipeD<T>) {
261    let mut d = RelayPipeD::<T>::dont_care();
262    d.stages[0].data_in = i.data;
263    d.stages[0].void_in = !i.valid;
264    for k in 1..PIPE_STAGES {
265        d.stages[k].data_in = q.stages[k - 1].data_out;
266        d.stages[k].void_in = q.stages[k - 1].void_out;
267    }
268    for k in 0..PIPE_LAST {
269        d.stages[k].stop_in = q.stages[k + 1].stop_out;
270    }
271    d.stages[PIPE_LAST].stop_in = !i.taken;
272    let last = q.stages[PIPE_LAST];
273    (
274        PipeOut::<T> {
275            data: last.data_out,
276            valid: !last.void_out,
277            ready: !q.stages[0].stop_out,
278        },
279        d,
280    )
281}
282
283/// Forward data registers in a [`ToggleLink`] after its `held` register.
284pub const TOGGLE_STAGES: usize = 2;
285
286/// Index of a toggle link's last forward register.
287const TOGGLE_LAST: usize = TOGGLE_STAGES - 1;
288
289/// A valid/ready link for rare, wide items, built from plain registers.
290///
291/// # Why not a relay pipe
292///
293/// A [`Relay`] station chooses between its `main` and `aux` registers, which
294/// costs a LUT per data bit per station. For the certificate path that came
295/// to 4,760 LUTs per slice in build v12 (19,037 of its 410,003), to carry one
296/// certificate per 128 signatures. This link carries the same handshake with
297/// no per-bit logic: the data only ever moves forward, through plain
298/// registers, and one bit in each direction carries the protocol.
299///
300/// # The protocol: a toggle (two-phase) handshake
301///
302/// ```text
303///   sender side                                         receiver side
304///   item ─▶ [held] ─▶ [fwd 0] ─▶ [fwd 1] ─────────────▶ data, valid = seq != last
305///           [seq ] ─▶ [seq 0] ─▶ [seq 1] ──────────────┘
306///   ready = seq == ack ◀── [ack 1] ◀── [ack 0] ◀── [last] ◀── taken
307/// ```
308///
309/// * The sender side loads an item into `held` and flips `seq`, but only when
310///   `seq == ack`: the receiver has taken everything sent so far.
311/// * `held` and `seq` travel forward together through [`TOGGLE_STAGES`]
312///   registers. The receiver sees a new item while the delayed `seq` differs
313///   from `last`, the `seq` of the item it took most recently.
314/// * Taking the item copies the delayed `seq` into `last`, which ends `valid`
315///   at the next clock and travels back as `ack`.
316///
317/// `held` keeps the item until the receiver has taken it, so the forward
318/// registers need no enable and no reset: they copy every clock. Every
319/// output comes from a register or compares two registers. One item makes a
320/// round trip in `2 * TOGGLE_STAGES + 2` clocks (6 with two stages), so this
321/// link suits items that arrive far less often than that.
322#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
323pub struct ToggleLink<T: Digital> {
324    /// The item being delivered (resetless; loaded only when `ready`).
325    held: DFF<T>,
326    /// Flips once per item loaded; resets low.
327    seq: DFF<bool>,
328    /// `held`, delayed (resetless).
329    fwd: [DFF<T>; TOGGLE_STAGES],
330    /// `seq`, delayed alongside `fwd`.
331    fwd_seq: [DFF<bool>; TOGGLE_STAGES],
332    /// The `seq` of the item the receiver took most recently; resets low.
333    last: DFF<bool>,
334    /// `last`, delayed back to the sender side.
335    ack: [DFF<bool>; TOGGLE_STAGES],
336}
337
338impl<T: Digital> Default for ToggleLink<T> {
339    /// Empty and ready after reset; data registers don't-care.
340    fn default() -> Self {
341        Self {
342            held: DFF::resetless(),
343            seq: DFF::new(false),
344            fwd: core::array::from_fn(|_| DFF::resetless()),
345            fwd_seq: core::array::from_fn(|_| DFF::new(false)),
346            last: DFF::new(false),
347            ack: core::array::from_fn(|_| DFF::new(false)),
348        }
349    }
350}
351
352impl<T: Digital> SynchronousIO for ToggleLink<T> {
353    type I = PipeIn<T>;
354    type O = PipeOut<T>;
355    type Kernel = toggle_link_kernel<T>;
356}
357
358/// Load, shift and compare (see [`ToggleLink`]).
359#[kernel]
360pub fn toggle_link_kernel<T: Digital>(cr: ClockReset, i: PipeIn<T>, q: ToggleLinkQ<T>) -> (PipeOut<T>, ToggleLinkD<T>) {
361    let mut d = ToggleLinkD::<T>::dont_care();
362    let ready = q.seq == q.ack[TOGGLE_LAST];
363    let load = i.valid && ready;
364    d.held = if load { i.data } else { q.held };
365    d.seq = if load { !q.seq } else { q.seq };
366    d.fwd[0] = q.held;
367    d.fwd_seq[0] = q.seq;
368    for k in 1..TOGGLE_STAGES {
369        d.fwd[k] = q.fwd[k - 1];
370        d.fwd_seq[k] = q.fwd_seq[k - 1];
371    }
372    let valid = q.fwd_seq[TOGGLE_LAST] != q.last;
373    d.last = if i.taken && valid { q.fwd_seq[TOGGLE_LAST] } else { q.last };
374    d.ack[0] = q.last;
375    for k in 1..TOGGLE_STAGES {
376        d.ack[k] = q.ack[k - 1];
377    }
378    let mut o = PipeOut::<T> {
379        data: q.fwd[TOGGLE_LAST],
380        valid,
381        ready,
382    };
383    if cr.reset.any() {
384        o.valid = false;
385        o.ready = false;
386    }
387    (o, d)
388}
389
390/// [`DELAY_STAGES`] plain registers: the output is the input from
391/// `DELAY_STAGES` clocks ago. Resets to `T::dont_care()` (all zeros), so a
392/// delayed `valid`, `run` or fault bit is low after reset.
393#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
394pub struct Delay<T: Digital> {
395    /// The registers; `stages[0]` takes the input.
396    stages: [DFF<T>; DELAY_STAGES],
397}
398
399impl<T: Digital> Default for Delay<T> {
400    /// Every register reset to zero.
401    fn default() -> Self {
402        Self {
403            stages: core::array::from_fn(|_| DFF::new(T::dont_care())),
404        }
405    }
406}
407
408impl<T: Digital> SynchronousIO for Delay<T> {
409    type I = T;
410    type O = T;
411    type Kernel = delay_kernel<T>;
412}
413
414/// Shift the input one register along per clock.
415#[kernel]
416pub fn delay_kernel<T: Digital>(_cr: ClockReset, i: T, q: DelayQ<T>) -> (T, DelayD<T>) {
417    let mut d = DelayD::<T>::dont_care();
418    d.stages[0] = i;
419    for k in 1..DELAY_STAGES {
420        d.stages[k] = q.stages[k - 1];
421    }
422    (q.stages[DELAY_LAST], d)
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    /// Test payload: a number and some filler.
430    type Item = (b16, [b8; 8]);
431
432    /// The item carrying number `n`.
433    fn item(n: u32) -> Item {
434        (b16(u128::from(n)), core::array::from_fn(|k| b8(u128::from((n + k as u32) & 0xff))))
435    }
436
437    /// Drive `count` items through a link (a [`RelayPipe`] or a
438    /// [`ToggleLink`]) with the given sender
439    /// and receiver patterns (`true` = offer / take this clock, by cycle
440    /// number). The receiver takes an item only when one is offered and its
441    /// pattern allows, like the engine's receivers. Returns the received
442    /// item numbers and the cycles it took.
443    fn run<P>(pipe: P, count: u32, offer: impl Fn(u32) -> bool, accept: impl Fn(u32) -> bool) -> (Vec<u32>, u32)
444    where
445        P: Synchronous + SynchronousIO<I = PipeIn<Item>, O = PipeOut<Item>>,
446    {
447        let mut state = pipe.init();
448        let input = |n: u32, valid: bool, taken: bool| PipeIn::<Item> {
449            data: item(n),
450            valid,
451            taken,
452        };
453        // Two clocks of reset.
454        for _ in 0..2 {
455            pipe.sim(clock_reset(clock(false), reset(true)), input(0, false, false), &mut state);
456            pipe.sim(clock_reset(clock(true), reset(true)), input(0, false, false), &mut state);
457        }
458        let mut sent = 0;
459        let mut received = Vec::new();
460        let mut cycle = 0;
461        while received.len() < count as usize {
462            assert!(cycle < 100 * count + 100, "pipe stalled");
463            // The sender offers its next item when its pattern says so.
464            let offering = sent < count && offer(cycle);
465            // Both outputs are registered, so they do not depend on this
466            // clock's inputs; read them in the low half of the clock.
467            let out = pipe.sim(clock_reset(clock(false), reset(false)), input(sent, offering, false), &mut state);
468            // The receiver takes the output item when offered and allowed.
469            let take = out.valid && accept(cycle);
470            if take {
471                received.push(out.data.0.raw() as u32);
472                assert_eq!(out.data, item(out.data.0.raw() as u32), "payload intact");
473            }
474            let was_sent = offering && out.ready;
475            // Present the final inputs, then the rising edge.
476            let now = input(sent, offering, take);
477            pipe.sim(clock_reset(clock(false), reset(false)), now, &mut state);
478            pipe.sim(clock_reset(clock(true), reset(false)), now, &mut state);
479            if was_sent {
480                sent += 1;
481            }
482            cycle += 1;
483        }
484        (received, cycle)
485    }
486
487    /// Always offering and always taking, the pipe moves one item per clock
488    /// after its [`PIPE_STAGES`] clocks of latency.
489    #[test]
490    fn full_rate_with_three_clocks_of_latency() {
491        let (got, cycles) = run(RelayPipe::<Item>::default(), 200, |_| true, |_| true);
492        assert_eq!(got, (0..200).collect::<Vec<_>>());
493        assert!(cycles <= 200 + PIPE_STAGES as u32 + 1, "{cycles} cycles");
494    }
495
496    /// Random gaps on both sides: every item arrives once, in order, intact.
497    #[test]
498    fn random_backpressure_keeps_order() {
499        // xorshift patterns with fixed seeds; `keep` of 8 clocks are on.
500        let pattern = |seed: u64, keep: u64| {
501            move |c: u32| {
502                let mut x = seed ^ (u64::from(c) + 1).wrapping_mul(0x9e37_79b9_7f4a_7c15);
503                x ^= x << 13;
504                x ^= x >> 7;
505                x ^= x << 17;
506                x % 8 < keep
507            }
508        };
509        for (up, down) in [(7, 3), (3, 7), (5, 5), (8, 1), (1, 8)] {
510            let (got, _) = run(RelayPipe::<Item>::default(), 300, pattern(0x1234 + up, up), pattern(0x9876 + down, down));
511            assert_eq!(got, (0..300).collect::<Vec<_>>(), "offer {up}/8, take {down}/8");
512            let (got, _) = run(ToggleLink::<Item>::default(), 100, pattern(0x1234 + up, up), pattern(0x9876 + down, down));
513            assert_eq!(got, (0..100).collect::<Vec<_>>(), "toggle: offer {up}/8, take {down}/8");
514        }
515    }
516
517    /// Always offering and always taking, a [`ToggleLink`] delivers one item
518    /// per round trip of `2 * TOGGLE_STAGES + 2` clocks, never two at once.
519    #[test]
520    fn toggle_link_one_item_per_round_trip() {
521        let round_trip = 2 * TOGGLE_STAGES as u32 + 2;
522        let (got, cycles) = run(ToggleLink::<Item>::default(), 50, |_| true, |_| true);
523        assert_eq!(got, (0..50).collect::<Vec<_>>());
524        assert!(cycles <= 50 * round_trip + round_trip, "{cycles} cycles");
525        assert!(cycles >= 49 * round_trip, "{cycles} cycles: faster than a round trip?");
526    }
527
528    /// A [`ToggleLink`]'s outputs depend only on its registers.
529    #[test]
530    fn toggle_link_outputs_are_registered() {
531        let link = ToggleLink::<Item>::default();
532        let mut state = link.init();
533        let idle = PipeIn::<Item> {
534            data: item(0),
535            valid: false,
536            taken: false,
537        };
538        link.sim(clock_reset(clock(false), reset(true)), idle, &mut state);
539        link.sim(clock_reset(clock(true), reset(true)), idle, &mut state);
540        let a = link.sim(clock_reset(clock(false), reset(false)), idle, &mut state);
541        let busy = PipeIn::<Item> {
542            data: item(1),
543            valid: true,
544            taken: true,
545        };
546        let b = link.sim(clock_reset(clock(false), reset(false)), busy, &mut state);
547        assert_eq!(a, b);
548    }
549
550    /// The pipe's outputs in both directions come from registers: changing
551    /// only this clock's inputs (no clock edge) changes nothing.
552    #[test]
553    fn outputs_are_registered() {
554        let pipe = RelayPipe::<Item>::default();
555        let mut state = pipe.init();
556        let idle = PipeIn::<Item> {
557            data: item(0),
558            valid: false,
559            taken: false,
560        };
561        pipe.sim(clock_reset(clock(false), reset(true)), idle, &mut state);
562        pipe.sim(clock_reset(clock(true), reset(true)), idle, &mut state);
563        let a = pipe.sim(clock_reset(clock(false), reset(false)), idle, &mut state);
564        let busy = PipeIn::<Item> {
565            data: item(1),
566            valid: true,
567            taken: true,
568        };
569        let b = pipe.sim(clock_reset(clock(false), reset(false)), busy, &mut state);
570        assert_eq!(a, b);
571    }
572
573    /// A [`Delay`] outputs zero during and after reset until real inputs
574    /// have shifted through, then the input from [`DELAY_STAGES`] clocks ago.
575    #[test]
576    fn delay_shifts_by_its_stages() {
577        let delay = Delay::<b16>::default();
578        let mut state = delay.init();
579        for _ in 0..2 {
580            delay.sim(clock_reset(clock(false), reset(true)), b16(0xffff), &mut state);
581            delay.sim(clock_reset(clock(true), reset(true)), b16(0xffff), &mut state);
582        }
583        for t in 0..10_u128 {
584            let out = delay.sim(clock_reset(clock(false), reset(false)), b16(t + 1), &mut state);
585            let expected = if t < DELAY_STAGES as u128 { 0 } else { t + 1 - DELAY_STAGES as u128 };
586            assert_eq!(out, b16(expected), "clock {t}");
587            delay.sim(clock_reset(clock(true), reset(false)), b16(t + 1), &mut state);
588        }
589    }
590}