Skip to main content

rhdl_dsig_scalable_core/verify/
unit.rs

1//! One verify unit: its HBM record reader, its verify core, its verdict writer.
2//!
3//! ```text
4//!   HBM records{u} ──16-beat bursts──▶ RecordReader ──beats──▶ VerifyCore ──verdicts──▶ VerdictWriter ──▶ HBM verdicts{u}
5//!                                       (64-beat FIFO)            │  ▲                 (64 per beat)
6//!                                                       cert request  answer
7//! ```
8//!
9//! * The [`RecordReader`] streams the unit's `count × 26` beats from its
10//!   record buffer in 1 KiB bursts. It asks for a burst only when its FIFO
11//!   has room for all of it, so it can always take read data (`rready` high),
12//!   and it hands out up to one beat per clock.
13//! * The [`VerdictWriter`] packs verdict bytes 64 to a beat and writes each
14//!   beat once it is full (or holds the last verdict), one beat per burst.
15//!   A run is done only when every beat's write response came back.
16
17use crate::axi4::{RESP_OKAY, ReadRequest, ReadResponse, WriteRequest, WriteResponse};
18use crate::verify::RECORD_BEATS;
19use crate::verify::certs::{CertRequest, CertResult};
20use crate::verify::datapath::{Beat, CertWarm, CoreInput, Verdict, VerifyCore};
21use rhdl::prelude::*;
22use rhdl_fpga::core::{
23    dff::DFF,
24    ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
25};
26
27/// Beats per read burst (1 KiB; a 4 KiB-aligned base keeps bursts in a page).
28pub const BURST_BEATS: usize = 16;
29/// Beat FIFO capacity.
30pub const FIFO_BEATS: usize = 64;
31
32const _: () = assert!(
33    RECORD_BEATS == 26 || RECORD_BEATS == 27,
34    "the beat count below is count·16 + count·8 + count·2 (+ count for 27-beat records)"
35);
36/// Profile 2's records have a 27th beat (see [`crate::verify::RECORD_BYTES`]).
37const EXTRA_BEAT: bool = RECORD_BEATS == 27;
38
39// --------------------------------------------------------------- reader
40
41/// Reader inputs.
42#[derive(Clone, Copy, Debug, PartialEq, Digital)]
43pub struct ReaderInput {
44    /// Byte address of record 0 (4 KiB aligned).
45    pub base: b64,
46    /// Beats to read: records × [`RECORD_BEATS`].
47    pub beats: b32,
48    /// Issue reads (high while the kernel runs).
49    pub run: bool,
50    /// The memory's read channel.
51    pub memory: ReadResponse,
52    /// The consumer takes the presented beat this cycle.
53    pub take: bool,
54}
55
56/// Reader outputs.
57#[derive(Clone, Copy, Debug, PartialEq, Digital)]
58pub struct ReaderOutput {
59    /// Read requests to memory.
60    pub memory: ReadRequest,
61    /// The next beat, in order.
62    pub beat: Beat,
63    /// A read response was not OKAY (sticky).
64    pub error: bool,
65}
66
67/// Reader counters and flags.
68#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
69#[doc(hidden)]
70pub struct ReaderControl {
71    /// `beats` and `run`, registered: they come from the cluster through a
72    /// delay line and feed wide compares.
73    beats: b32,
74    run: bool,
75    /// The read request on the memory port, held in registers so that it
76    /// leaves the unit without logic (it crosses to another die).
77    ar_valid: bool,
78    ar_addr: b64,
79    ar_len: b8,
80    /// Beats requested (including the one on the port).
81    requested: b32,
82    /// Beats received into the FIFO.
83    received: b32,
84    /// Beats read out of the FIFO.
85    drained: b32,
86    /// A FIFO read is in flight (its data arrives next cycle).
87    reading: bool,
88    /// Beats in the output buffer (0..=2).
89    count: b2,
90    /// Oldest output-buffer entry.
91    head: bool,
92    /// Sticky response error.
93    error: bool,
94}
95
96/// The record reader.
97#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
98pub struct RecordReader {
99    /// 64 × 64-byte beat FIFO.
100    fifo: SyncBRAM<[b8; 64], 6>,
101    /// Counters.
102    control: DFF<ReaderControl>,
103    /// Two-entry output buffer.
104    out: DFF<[[b8; 64]; 2]>,
105}
106
107impl Default for RecordReader {
108    /// An idle reader.
109    fn default() -> Self {
110        Self {
111            fifo: SyncBRAM::default(),
112            control: DFF::new(ReaderControl::default()),
113            out: DFF::resetless(),
114        }
115    }
116}
117
118impl SynchronousIO for RecordReader {
119    type I = ReaderInput;
120    type O = ReaderOutput;
121    type Kernel = record_reader_kernel;
122}
123
124/// The reader's logic.
125#[kernel]
126pub fn record_reader_kernel(clock_reset: ClockReset, input: ReaderInput, q: RecordReaderQ) -> (ReaderOutput, RecordReaderD) {
127    // No reset gating here: every register this logic feeds resets itself,
128    // and keeping the reset out of the logic cones keeps it off the routed
129    // kernel's failing paths (verifier build v1).
130    let resetting = clock_reset.reset.any();
131    let _ = resetting;
132    let mut c = q.control;
133    let mut out = q.out;
134    c.beats = input.beats;
135    c.run = input.run;
136    // AR: the request register empties when the port takes it; it loads
137    // the next burst when it is empty and a whole burst fits in the FIFO,
138    // so a 16-beat burst can go out every other clock.
139    if q.control.ar_valid && input.memory.arready {
140        c.ar_valid = false;
141    }
142    let in_fifo = q.control.requested - q.control.drained;
143    let room = in_fifo <= b32((FIFO_BEATS - BURST_BEATS) as u128);
144    let remaining = q.control.beats - q.control.requested;
145    let burst: b32 = if remaining < b32(BURST_BEATS as u128) { remaining } else { b32(BURST_BEATS as u128) };
146    let load = !q.control.ar_valid && q.control.run && room && q.control.requested < q.control.beats;
147    if load {
148        c.ar_valid = true;
149        c.ar_addr = input.base + (q.control.requested.resize::<64>() << 6);
150        c.ar_len = (burst - b32(1)).resize();
151        c.requested = q.control.requested + burst;
152    }
153    // R → FIFO.
154    let write = input.memory.rvalid;
155    if write {
156        c.received = q.control.received + b32(1);
157        if input.memory.rresp != RESP_OKAY {
158            c.error = true;
159        }
160    }
161    // Output buffer: the beat read last cycle arrives.
162    let mut count = q.control.count;
163    if q.control.reading {
164        let slot = q.control.head ^ (count == b2(1));
165        if slot {
166            out[1] = q.fifo;
167        } else {
168            out[0] = q.fifo;
169        }
170        count = count + b2(1);
171    }
172    // The consumer takes the head.
173    let have = q.control.count != b2(0);
174    if input.take && have {
175        c.head = !q.control.head;
176        count = count - b2(1);
177    }
178    c.count = count;
179    // Read the next FIFO beat while the buffer will have room for it (the
180    // beat taken this cycle frees its entry, so one beat per clock flows).
181    let pending = q.control.count.resize::<3>() + if q.control.reading { b3(1) } else { b3(0) }
182        - if input.take && have { b3(1) } else { b3(0) };
183    let read = q.control.received != q.control.drained && pending < b3(2);
184    c.reading = read;
185    if read {
186        c.drained = q.control.drained + b32(1);
187    }
188    let head = if q.control.head { q.out[1] } else { q.out[0] };
189    let d = RecordReaderD {
190        fifo: RamIn::<[b8; 64], 6> {
191            read_addr: q.control.drained.resize(),
192            write: RamWrite::<[b8; 64], 6> {
193                addr: q.control.received.resize(),
194                value: input.memory.rdata,
195                enable: write,
196            },
197        },
198        control: c,
199        out,
200    };
201    (
202        ReaderOutput {
203            memory: ReadRequest {
204                arvalid: q.control.ar_valid,
205                araddr: q.control.ar_addr,
206                arlen: q.control.ar_len,
207                rready: true,
208            },
209            beat: Beat {
210                valid: have,
211                data: head,
212            },
213            error: q.control.error,
214        },
215        d,
216    )
217}
218
219// --------------------------------------------------------------- writer
220
221/// Writer inputs.
222#[derive(Clone, Copy, Debug, PartialEq, Digital)]
223pub struct WriterInput {
224    /// Byte address of verdict 0 (64-byte aligned).
225    pub base: b64,
226    /// Verdicts expected.
227    pub count: b32,
228    /// The next verdict.
229    pub verdict: Verdict,
230    /// The memory's write channel.
231    pub memory: WriteResponse,
232}
233
234/// Writer outputs.
235#[derive(Clone, Copy, Debug, PartialEq, Digital)]
236pub struct WriterOutput {
237    /// Write requests to memory.
238    pub memory: WriteRequest,
239    /// The writer takes a verdict this cycle.
240    pub ready: bool,
241    /// Every verdict is written and acknowledged.
242    pub done: bool,
243    /// A write response was not OKAY (sticky).
244    pub error: bool,
245}
246
247/// Writer state.
248#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
249#[doc(hidden)]
250pub struct WriterControl {
251    /// Bytes in the accumulator (0..64).
252    fill: b7,
253    /// Verdicts taken.
254    taken: b32,
255    /// A full beat waits to be written.
256    sending: bool,
257    /// Its address was accepted.
258    aw_done: bool,
259    /// Its data was accepted.
260    w_done: bool,
261    /// Beats sent (the next beat's number).
262    sent: b32,
263    /// Beats acknowledged.
264    acked: b32,
265    /// Sticky response error.
266    error: bool,
267}
268
269/// Writer data.
270#[derive(Clone, Copy, Debug, PartialEq, Digital)]
271#[doc(hidden)]
272pub struct WriterData {
273    /// Verdicts being collected.
274    acc: [b8; 64],
275    /// The beat being written.
276    beat: [b8; 64],
277}
278
279/// The verdict writer.
280#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
281pub struct VerdictWriter {
282    /// Counters and handshake flags.
283    control: DFF<WriterControl>,
284    /// Accumulator and send buffer (reset to zero so padding bytes are zero).
285    data: DFF<WriterData>,
286}
287
288impl Default for VerdictWriter {
289    /// An idle writer with zeroed buffers.
290    fn default() -> Self {
291        Self {
292            control: DFF::new(WriterControl::default()),
293            data: DFF::new(WriterData {
294                acc: [b8(0); 64],
295                beat: [b8(0); 64],
296            }),
297        }
298    }
299}
300
301impl SynchronousIO for VerdictWriter {
302    type I = WriterInput;
303    type O = WriterOutput;
304    type Kernel = verdict_writer_kernel;
305}
306
307/// The writer's logic.
308#[allow(clippy::needless_range_loop)]
309#[kernel]
310pub fn verdict_writer_kernel(clock_reset: ClockReset, input: WriterInput, q: VerdictWriterQ) -> (WriterOutput, VerdictWriterD) {
311    // No reset gating here: every register this logic feeds resets itself,
312    // and keeping the reset out of the logic cones keeps it off the routed
313    // kernel's failing paths (verifier build v1).
314    let resetting = clock_reset.reset.any();
315    let _ = resetting;
316    let mut c = q.control;
317    let mut data = q.data;
318    // Send: AW and W independently, then the beat is out.
319    let awvalid = q.control.sending && !q.control.aw_done;
320    let wvalid = q.control.sending && !q.control.w_done;
321    let aw_now = q.control.aw_done || (awvalid && input.memory.awready);
322    let w_now = q.control.w_done || (wvalid && input.memory.wready);
323    if q.control.sending {
324        if aw_now && w_now {
325            c.sending = false;
326            c.aw_done = false;
327            c.w_done = false;
328            c.sent = q.control.sent + b32(1);
329        } else {
330            c.aw_done = aw_now;
331            c.w_done = w_now;
332        }
333    }
334    // B: count acknowledgements.
335    if input.memory.bvalid {
336        c.acked = q.control.acked + b32(1);
337        if input.memory.bresp != RESP_OKAY {
338            c.error = true;
339        }
340    }
341    // Take a verdict while no beat waits to be sent.
342    let ready = !q.control.sending;
343    if ready && input.verdict.valid {
344        let mut acc = q.data.acc;
345        for i in 0..64 {
346            if q.control.fill == b7(i as u128) {
347                acc[i] = input.verdict.byte;
348            }
349        }
350        let taken = q.control.taken + b32(1);
351        c.taken = taken;
352        if q.control.fill == b7(63) || taken == input.count {
353            data.beat = acc;
354            data.acc = [b8(0); 64];
355            c.fill = b7(0);
356            c.sending = true;
357        } else {
358            data.acc = acc;
359            c.fill = q.control.fill + b7(1);
360        }
361    }
362    let done = q.control.taken == input.count && !q.control.sending && q.control.acked == q.control.sent;
363    let d = VerdictWriterD { control: c, data };
364    (
365        WriterOutput {
366            memory: WriteRequest {
367                awvalid,
368                awaddr: input.base + (q.control.sent.resize::<64>() << 6),
369                awlen: b8(0),
370                wvalid,
371                wdata: q.data.beat,
372                wlast: true,
373                bready: true,
374            },
375            ready,
376            done,
377            error: q.control.error,
378        },
379        d,
380    )
381}
382
383// ----------------------------------------------------------------- unit
384
385/// Unit inputs.
386#[derive(Clone, Copy, Debug, PartialEq, Digital)]
387pub struct UnitInput {
388    /// The kernel runs.
389    pub run: bool,
390    /// Address of the unit's records.
391    pub records_base: b64,
392    /// Address of the unit's verdicts.
393    pub verdicts_base: b64,
394    /// Records for this unit.
395    pub count: b32,
396    /// Record read channel.
397    pub records: ReadResponse,
398    /// Verdict write channel.
399    pub verdicts: WriteResponse,
400    /// The arbiter took this unit's certificate request.
401    pub cert_taken: bool,
402    /// An Ed25519 answer for this unit.
403    pub cert_result: CertResult,
404}
405
406/// Unit outputs.
407#[derive(Clone, Copy, Debug, PartialEq, Digital)]
408pub struct UnitOutput {
409    /// Record read channel.
410    pub records: ReadRequest,
411    /// Verdict write channel.
412    pub verdicts: WriteRequest,
413    /// A certificate check for the Ed25519 core.
414    pub cert_request: CertRequest,
415    /// Records retired.
416    pub retired: b32,
417    /// Valid records retired.
418    pub valid: b32,
419    /// Certificate checks issued.
420    pub cert_checks: b32,
421    /// Certificate checks that failed.
422    pub cert_fails: b32,
423    /// All verdicts written and acknowledged.
424    pub done: bool,
425    /// A read or write response failed: bit 0 read, bit 1 write.
426    pub error: b2,
427}
428
429/// One verify unit.
430#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
431pub struct VerifyUnit<const TABLE_MASK: usize> {
432    /// Record stream from HBM.
433    reader: RecordReader,
434    /// The verify core.
435    core: VerifyCore<TABLE_MASK>,
436    /// Verdicts to HBM.
437    writer: VerdictWriter,
438}
439
440impl<const TABLE_MASK: usize> Default for VerifyUnit<TABLE_MASK> {
441    /// An idle unit.
442    fn default() -> Self {
443        Self {
444            reader: RecordReader::default(),
445            core: VerifyCore::default(),
446            writer: VerdictWriter::default(),
447        }
448    }
449}
450
451impl<const TABLE_MASK: usize> SynchronousIO for VerifyUnit<TABLE_MASK> {
452    type I = UnitInput;
453    type O = UnitOutput;
454    type Kernel = verify_unit_kernel<TABLE_MASK>;
455}
456
457/// The unit's wiring.
458#[kernel]
459pub fn verify_unit_kernel<const TABLE_MASK: usize>(
460    _clock_reset: ClockReset,
461    input: UnitInput,
462    q: VerifyUnitQ<TABLE_MASK>,
463) -> (UnitOutput, VerifyUnitD<TABLE_MASK>) {
464    // count × 26 = count·16 + count·8 + count·2 (count × 27: plus count).
465    let beats = (input.count << 4) + (input.count << 3) + (input.count << 1) + (if EXTRA_BEAT { input.count } else { b32(0) });
466    let take = q.reader.beat.valid && q.core.beat_ready;
467    let d = VerifyUnitD::<TABLE_MASK> {
468        reader: ReaderInput {
469            base: input.records_base,
470            beats,
471            run: input.run,
472            memory: input.records,
473            take,
474        },
475        core: CoreInput {
476            beat: q.reader.beat,
477            slot: b4(0),
478            check_epoch: false,
479            expected_epoch: b64(0),
480            warm: CertWarm {
481                valid: false,
482                slot: b4(0),
483                commitment: [b8(0); 64],
484                signature: [b8(0); 64],
485            },
486            verdict_ready: q.writer.ready,
487            cert_taken: input.cert_taken,
488            cert_result: input.cert_result,
489        },
490        writer: WriterInput {
491            base: input.verdicts_base,
492            count: input.count,
493            verdict: q.core.verdict,
494            memory: input.verdicts,
495        },
496    };
497    (
498        UnitOutput {
499            records: q.reader.memory,
500            verdicts: q.writer.memory,
501            cert_request: q.core.cert_request,
502            retired: q.core.retired,
503            valid: q.core.valid,
504            cert_checks: q.core.cert_checks,
505            cert_fails: q.core.cert_fails,
506            done: q.writer.done,
507            error: (if q.reader.error { b2(1) } else { b2(0) }) | (if q.writer.error { b2(2) } else { b2(0) }),
508        },
509        d,
510    )
511}