Skip to main content

rhdl_dsig_scalable_core/verify/
engine.rs

1//! The verifier card kernel: `VERIFY_UNITS` verify units, HBM, registers.
2//!
3//! **Status:** interface only. The ports, the register map and the HBM layout
4//! below are fixed so the host tools can be written against them; the kernel
5//! body is being built (see [`super`]).
6//!
7//! # Ports
8//!
9//! | Port | Direction | Carries |
10//! |---|---|---|
11//! | `s_axi_control` | AXI4-Lite slave | the registers below |
12//! | `m_axi_records{u}` | AXI4 read master | unit `u`'s records |
13//! | `m_axi_verdicts{u}` | AXI4 write master | unit `u`'s verdict bytes |
14//!
15//! Both of unit `u`'s ports reach the same 1 GiB HBM group, `HBM[4u:4u+3]`.
16//!
17//! # HBM layout per unit
18//!
19//! * `records{u}`: `count` records of [`RECORD_BYTES`](super::RECORD_BYTES),
20//!   back to back, from a 4 KiB-aligned address.
21//! * `verdicts{u}`: `count` verdict bytes in record order, from a 64-byte
22//!   aligned address, padded to whole 64-byte beats. The host zero-fills it;
23//!   a written verdict always has bit 7 set.
24//!
25//! Every unit verifies the same `count` records. The host gives each unit
26//! whole batches (all 128 records of a batch in a row), so a unit asks the
27//! Ed25519 core for at most one certificate check per 128 records.
28//!
29//! # Register map (32-bit words)
30//!
31//! | Offset | Name | Access |
32//! |---|---|---|
33//! | `0x000` | `AP_CTRL`: bit 0 `ap_start`, 1 `ap_done` (clear on read), 2 `ap_idle`, 3 `ap_ready` | RW |
34//! | `0x004` | `GIE` | RW |
35//! | `0x008` | `IER` bit 0 | RW |
36//! | `0x00c` | `ISR` bit 0 (write 1 to toggle) | RW |
37//! | `0x010 + 0x10·u` | arg `records{u}` (u64) | W |
38//! | `0x018 + 0x10·u` | arg `verdicts{u}` (u64) | W |
39//! | `0x090` | arg `count`: records per unit (u32) | W |
40//! | `0x098 + 4w` | args `key0`..`key7`: trusted Ed25519 public key, bytes `4w..4w+4` little endian | W |
41//! | `0x200` | `MAGIC` = [`MAGIC`] ("DSV1") | R |
42//! | `0x204` | `ABI` = [`ABI_VERSION`] | R |
43//! | `0x208` | `SHAPE`: units bits 0..8, lanes 8..12, log2 contexts 12..16, table mask 16..28 | R |
44//! | `0x20c` | `FAULT`: bit 0 read error, 1 write error, 2 Ed25519 core error, 3 key rejected | R |
45//! | `0x210`/`0x214` | `TICKS` lo (snapshots hi) / hi | R |
46//! | `0x218`/`0x21c` | `START_TICK` lo/hi | R |
47//! | `0x220`/`0x224` | `DONE_TICK` lo/hi: the last verdict write acknowledged | R |
48//! | `0x228`/`0x22c` | `KEY_TICK` lo/hi: the trusted key finished loading | R |
49//! | `0x230` | `VALID`: records verified valid, all units | R |
50//! | `0x234` | `CERT_CHECKS`: Ed25519 verifications issued | R |
51//! | `0x238` | `CERT_FAILS`: Ed25519 verifications that failed | R |
52//! | `0x240 + 4u` | `RETIRED[u]`: verdicts produced by unit `u` | R |
53//!
54//! Arguments are accepted only while idle; a finished kernel stays done until
55//! reset (one run per reset), exactly as in [`crate::engine`]. The run is
56//! timed from `START_TICK` to `DONE_TICK`, which includes loading the trusted
57//! key into the Ed25519 core.
58
59use crate::VERIFY_UNITS;
60use crate::axi4::{AxiLiteIn, AxiLiteOut, ReadRequest, ReadResponse, WriteRequest, WriteResponse};
61use rhdl::prelude::*;
62
63/// `MAGIC` register value ("DSV1" in little-endian ASCII).
64pub const MAGIC: b32 = b32(0x3156_5344);
65/// Register-map version of the verifier kernel.
66pub const ABI_VERSION: b32 = b32(0x0001_0001);
67
68/// Offset of arg `records0`; unit `u`'s is `+ 0x10·u`.
69pub const ARG_RECORDS: usize = 0x010;
70/// Offset of arg `verdicts0`; unit `u`'s is `+ 0x10·u`.
71pub const ARG_VERDICTS: usize = 0x018;
72/// Offset of arg `count`.
73pub const ARG_COUNT: usize = 0x090;
74/// Offset of arg `key0`; word `w`'s is `+ 4w`.
75pub const ARG_KEY: usize = 0x098;
76/// Offset of `MAGIC`.
77pub const REG_MAGIC: usize = 0x200;
78/// Offset of `ABI`.
79pub const REG_ABI: usize = 0x204;
80/// Offset of `SHAPE`.
81pub const REG_SHAPE: usize = 0x208;
82/// Offset of `FAULT`.
83pub const REG_FAULT: usize = 0x20c;
84/// Offset of `TICKS` lo.
85pub const REG_TICKS: usize = 0x210;
86/// Offset of `START_TICK` lo.
87pub const REG_START_TICK: usize = 0x218;
88/// Offset of `DONE_TICK` lo.
89pub const REG_DONE_TICK: usize = 0x220;
90/// Offset of `KEY_TICK` lo.
91pub const REG_KEY_TICK: usize = 0x228;
92/// Offset of `VALID`.
93pub const REG_VALID: usize = 0x230;
94/// Offset of `CERT_CHECKS`.
95pub const REG_CERT_CHECKS: usize = 0x234;
96/// Offset of `CERT_FAILS`.
97pub const REG_CERT_FAILS: usize = 0x238;
98/// Offset of `RETIRED[0]`; unit `u`'s is `+ 4u`.
99pub const REG_RETIRED: usize = 0x240;
100
101/// Kernel inputs.
102#[derive(Clone, Copy, Debug, PartialEq, Digital)]
103pub struct VerifyEngineInput {
104    /// Host control channel.
105    pub axi: AxiLiteIn,
106    /// Record read channels, one per unit.
107    pub records: [ReadResponse; VERIFY_UNITS],
108    /// Verdict write channels, one per unit.
109    pub verdicts: [WriteResponse; VERIFY_UNITS],
110}
111
112/// Kernel outputs.
113#[derive(Clone, Copy, Debug, PartialEq, Digital)]
114pub struct VerifyEngineOutput {
115    /// Host control channel.
116    pub axi: AxiLiteOut,
117    /// Record read channels, one per unit.
118    pub records: [ReadRequest; VERIFY_UNITS],
119    /// Verdict write channels, one per unit.
120    pub verdicts: [WriteRequest; VERIFY_UNITS],
121}
122
123// ------------------------------------------------------------------ cluster
124
125use crate::engine::set_half_kernel;
126use crate::verify::certs::CertResult;
127use crate::verify::chains::{CONTEXTS, LANES};
128use crate::relay::{Delay, PipeIn, RelayPipe, ToggleLink};
129use crate::verify::certs::CertRequest;
130use crate::verify::unit::{UnitInput, VerifyUnit};
131use rhdl::core::ReplicatedSynchronous;
132use rhdl_fpga::core::dff::DFF;
133
134/// What the cluster sees of the Ed25519 verify core.
135#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
136pub struct EdStatus {
137    /// The core takes a request this cycle.
138    pub ready: bool,
139    /// A result pulse.
140    pub result_valid: bool,
141    /// The result verified.
142    pub result_ok: bool,
143    /// The result's tag (`unit << 6 | entry`).
144    pub result_tag: b16,
145    /// A key load may start.
146    pub load_key_ready: bool,
147    /// Slot 0's state: 0 empty, 1 loading, 2 ready, 3 invalid key.
148    pub key_state: b2,
149    /// The core's sticky invariant violation.
150    pub internal_error: bool,
151}
152
153/// What the cluster asks of the Ed25519 verify core.
154#[derive(Clone, Copy, Debug, PartialEq, Digital)]
155pub struct EdCommand {
156    /// Present a request.
157    pub valid: bool,
158    /// The 64-byte commitment as eight little-endian words.
159    pub message: [b64; 8],
160    /// `R ‖ S` as eight little-endian words.
161    pub signature: [b64; 8],
162    /// The trusted key as four little-endian words.
163    pub public_key: [b64; 4],
164    /// `unit << 6 | entry`.
165    pub tag: b16,
166    /// Load the trusted key into slot 0.
167    pub load_key_valid: bool,
168}
169
170/// Cluster inputs: the kernel's plus the Ed25519 core's outputs.
171#[derive(Clone, Copy, Debug, PartialEq, Digital)]
172pub struct ClusterInput {
173    /// The kernel's ports.
174    pub kernel: VerifyEngineInput,
175    /// The Ed25519 core.
176    pub ed: EdStatus,
177}
178
179/// Cluster outputs: the kernel's plus the Ed25519 core's inputs.
180#[derive(Clone, Copy, Debug, PartialEq, Digital)]
181pub struct ClusterOutput {
182    /// The kernel's ports.
183    pub kernel: VerifyEngineOutput,
184    /// The Ed25519 core.
185    pub ed: EdCommand,
186}
187
188/// Host arguments.
189#[derive(Clone, Copy, Debug, PartialEq, Digital)]
190pub struct VerifyArgs {
191    /// Record buffer per unit.
192    pub records: [b64; VERIFY_UNITS],
193    /// Verdict buffer per unit.
194    pub verdicts: [b64; VERIFY_UNITS],
195    /// Records per unit.
196    pub count: b32,
197    /// Trusted Ed25519 key, eight 32-bit words.
198    pub key: [b32; 8],
199}
200
201/// Register file, run state, timing and the certificate arbiter.
202#[derive(Clone, Copy, Debug, PartialEq, Digital)]
203#[doc(hidden)]
204pub struct ClusterControl {
205    /// Host arguments.
206    args: VerifyArgs,
207    /// Running.
208    running: bool,
209    /// Finished (sticky until reset).
210    done: bool,
211    /// `ap_done` not yet read.
212    done_unread: bool,
213    /// `GIE`.
214    gie: bool,
215    /// `IER`.
216    ier: bool,
217    /// `ISR`.
218    isr: bool,
219    /// Free-running tick counter.
220    ticks: b64,
221    /// Tick at start.
222    start_tick: b64,
223    /// Tick when the last verdict was acknowledged.
224    done_tick: b64,
225    /// Tick when the key settled.
226    key_tick: b64,
227    /// High half captured with `TICKS` lo.
228    ticks_hi: b32,
229    /// The key load was requested.
230    key_sent: bool,
231    /// The key settled (ready or invalid).
232    key_settled: bool,
233    /// Round-robin pointer of the certificate arbiter.
234    next_unit: b3,
235    /// AXI-Lite write address latch.
236    aw_addr: b12,
237    /// Write address latched.
238    aw_have: bool,
239    /// Write data latch.
240    w_data: b32,
241    /// Write data latched.
242    w_have: bool,
243    /// Write response pending.
244    b_valid: bool,
245    /// Read data pending.
246    r_valid: bool,
247    /// Read data.
248    r_data: b32,
249    /// `ed_request` holds a request the Ed25519 core has not taken yet.
250    ed_valid: bool,
251    /// The request presented to the Ed25519 core (see [`EdHeld`]).
252    ed_request: EdHeld,
253}
254
255/// The request presented to the Ed25519 core, held in registers: the
256/// core's input logic is deep, and fed straight from the arbiter's mux (or
257/// the key registers) it was the routed kernel's worst path into the core
258/// (verifier build v1, −1.24 ns, 11 levels).
259#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
260#[doc(hidden)]
261pub struct EdHeld {
262    message: [b64; 8],
263    signature: [b64; 8],
264    public_key: [b64; 4],
265    tag: b16,
266}
267
268impl Default for ClusterControl {
269    /// IDLE, zero arguments.
270    fn default() -> Self {
271        Self {
272            args: VerifyArgs {
273                records: [b64(0); VERIFY_UNITS],
274                verdicts: [b64(0); VERIFY_UNITS],
275                count: b32(0),
276                key: [b32(0); 8],
277            },
278            running: false,
279            done: false,
280            done_unread: false,
281            gie: false,
282            ier: false,
283            isr: false,
284            ticks: b64(0),
285            start_tick: b64(0),
286            done_tick: b64(0),
287            key_tick: b64(0),
288            ticks_hi: b32(0),
289            key_sent: false,
290            key_settled: false,
291            next_unit: b3(0),
292            aw_addr: b12(0),
293            aw_have: false,
294            w_data: b32(0),
295            w_have: false,
296            b_valid: false,
297            r_valid: false,
298            r_data: b32(0),
299            ed_valid: false,
300            ed_request: EdHeld::default(),
301        }
302    }
303}
304
305/// A read address on its way to HBM.
306#[derive(Clone, Copy, Debug, PartialEq, Digital)]
307pub struct ArItem {
308    /// Burst address.
309    pub araddr: b64,
310    /// Beats minus one.
311    pub arlen: b8,
312}
313
314/// A read beat on its way back to a unit.
315#[derive(Clone, Copy, Debug, PartialEq, Digital)]
316pub struct RItem {
317    /// A beat.
318    pub rvalid: bool,
319    /// Its data.
320    pub rdata: [b8; 64],
321    /// Its response code.
322    pub rresp: b2,
323    /// Last beat of its burst.
324    pub rlast: bool,
325}
326
327/// A write address on its way to HBM.
328#[derive(Clone, Copy, Debug, PartialEq, Digital)]
329pub struct AwItem {
330    /// Burst address.
331    pub awaddr: b64,
332    /// Beats minus one.
333    pub awlen: b8,
334}
335
336/// A write beat on its way to HBM.
337#[derive(Clone, Copy, Debug, PartialEq, Digital)]
338pub struct WItem {
339    /// Its data.
340    pub wdata: [b8; 64],
341    /// Last beat of its burst.
342    pub wlast: bool,
343}
344
345/// A write response on its way back to a unit.
346#[derive(Clone, Copy, Debug, PartialEq, Digital)]
347pub struct BItem {
348    /// A response.
349    pub bvalid: bool,
350    /// Its code.
351    pub bresp: b2,
352}
353
354/// What the cluster tells a unit (static during a run, latency tolerant).
355#[derive(Clone, Copy, Debug, PartialEq, Digital)]
356pub struct UnitControl {
357    /// The kernel runs.
358    pub run: bool,
359    /// The unit's record buffer.
360    pub records_base: b64,
361    /// The unit's verdict buffer.
362    pub verdicts_base: b64,
363    /// Records per unit.
364    pub count: b32,
365}
366
367/// What a unit reports (counters, latency tolerant).
368#[derive(Clone, Copy, Debug, PartialEq, Digital)]
369pub struct UnitStatus {
370    /// Records retired.
371    pub retired: b32,
372    /// Valid records retired.
373    pub valid: b32,
374    /// Certificate checks issued.
375    pub cert_checks: b32,
376    /// Certificate checks failed.
377    pub cert_fails: b32,
378    /// All verdicts written and acknowledged.
379    pub done: bool,
380    /// Bit 0 read error, bit 1 write error.
381    pub error: b2,
382}
383
384/// Everything of the kernel except the Ed25519 core.
385///
386/// Kept separate so simulations can answer certificate checks with a fast
387/// software model; [`VerifyEngine`] adds the real core.
388///
389/// Every signal between a unit and the shared logic or the HBM ports passes
390/// through registers ([`crate::relay`]), as in the signer: units sit in their
391/// own SLRs, and an unregistered crossing cannot meet 205 MHz. Handshaked
392/// channels (AR, AW, W) use relay pipes; channels without back-pressure (R,
393/// B, results, control, status) use delay lines; the wide, rare certificate
394/// requests use toggle links.
395#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
396pub struct VerifyCluster<const TABLE_MASK: usize> {
397    /// Registers, timing, arbiter.
398    control: DFF<ClusterControl>,
399    /// The verify units (one module, `VERIFY_UNITS` instances).
400    units: ReplicatedSynchronous<VerifyUnit<TABLE_MASK>, VERIFY_UNITS>,
401    /// Read addresses, unit → HBM.
402    ar: [RelayPipe<ArItem>; VERIFY_UNITS],
403    /// Read beats, HBM → unit.
404    r: [Delay<RItem>; VERIFY_UNITS],
405    /// Write addresses, unit → HBM.
406    aw: [RelayPipe<AwItem>; VERIFY_UNITS],
407    /// Write beats, unit → HBM.
408    w: [RelayPipe<WItem>; VERIFY_UNITS],
409    /// Write responses, HBM → unit.
410    b: [Delay<BItem>; VERIFY_UNITS],
411    /// Run configuration, cluster → unit.
412    to_unit: [Delay<UnitControl>; VERIFY_UNITS],
413    /// Certificate requests, unit → arbiter.
414    requests: [ToggleLink<CertRequest>; VERIFY_UNITS],
415    /// Certificate answers, arbiter → unit.
416    results: [Delay<CertResult>; VERIFY_UNITS],
417    /// Counters, unit → cluster.
418    status: [Delay<UnitStatus>; VERIFY_UNITS],
419}
420
421impl<const TABLE_MASK: usize> Default for VerifyCluster<TABLE_MASK> {
422    /// Power-on: IDLE, every unit reset.
423    fn default() -> Self {
424        Self {
425            control: DFF::new(ClusterControl::default()),
426            units: ReplicatedSynchronous::new(VerifyUnit::default()),
427            ar: core::array::from_fn(|_| RelayPipe::default()),
428            r: core::array::from_fn(|_| Delay::default()),
429            aw: core::array::from_fn(|_| RelayPipe::default()),
430            w: core::array::from_fn(|_| RelayPipe::default()),
431            b: core::array::from_fn(|_| Delay::default()),
432            to_unit: core::array::from_fn(|_| Delay::default()),
433            requests: core::array::from_fn(|_| ToggleLink::default()),
434            results: core::array::from_fn(|_| Delay::default()),
435            status: core::array::from_fn(|_| Delay::default()),
436        }
437    }
438}
439
440impl<const TABLE_MASK: usize> SynchronousIO for VerifyCluster<TABLE_MASK> {
441    type I = ClusterInput;
442    type O = ClusterOutput;
443    type Kernel = verify_cluster_kernel<TABLE_MASK>;
444}
445
446/// Eight little-endian 64-bit words of 64 bytes.
447#[allow(clippy::needless_range_loop)]
448#[kernel]
449pub fn words64_kernel(bytes: [b8; 64]) -> [b64; 8] {
450    let mut words = [b64(0); 8];
451    for w in 0..8 {
452        words[w] = bytes[8 * w].resize::<64>()
453            | (bytes[8 * w + 1].resize::<64>() << 8)
454            | (bytes[8 * w + 2].resize::<64>() << 16)
455            | (bytes[8 * w + 3].resize::<64>() << 24)
456            | (bytes[8 * w + 4].resize::<64>() << 32)
457            | (bytes[8 * w + 5].resize::<64>() << 40)
458            | (bytes[8 * w + 6].resize::<64>() << 48)
459            | (bytes[8 * w + 7].resize::<64>() << 56);
460    }
461    words
462}
463
464/// `SHAPE`: units, lanes, log2 contexts, table mask.
465const SHAPE: usize = VERIFY_UNITS | (LANES << 8) | ((CONTEXTS.trailing_zeros() as usize) << 12);
466
467/// The cluster's logic.
468#[allow(clippy::needless_range_loop, clippy::too_many_lines)]
469#[kernel]
470pub fn verify_cluster_kernel<const TABLE_MASK: usize>(
471    clock_reset: ClockReset,
472    input: ClusterInput,
473    q: VerifyClusterQ<TABLE_MASK>,
474) -> (ClusterOutput, VerifyClusterD<TABLE_MASK>) {
475    let resetting = clock_reset.reset.any();
476    let mut c = q.control;
477    let args = q.control.args;
478    c.ticks = q.control.ticks + b64(1);
479    let ed = input.ed;
480
481    // ------------------------------------------------ progress
482    let mut all_done = true;
483    let mut read_error = false;
484    let mut write_error = false;
485    let mut valid = b32(0);
486    let mut checks = b32(0);
487    let mut fails = b32(0);
488    for u in 0..VERIFY_UNITS {
489        let s = q.status[u];
490        all_done = all_done && s.done;
491        read_error = read_error || (s.error & b2(1)) != b2(0);
492        write_error = write_error || (s.error & b2(2)) != b2(0);
493        valid = valid + s.valid;
494        checks = checks + s.cert_checks;
495        fails = fails + s.cert_fails;
496    }
497    let key_bad = q.control.key_settled && ed.key_state == b2(3);
498    let fault_bits: b32 = (if read_error { b32(1) } else { b32(0) })
499        | (if write_error { b32(2) } else { b32(0) })
500        | (if ed.internal_error { b32(4) } else { b32(0) })
501        | (if key_bad { b32(8) } else { b32(0) });
502    // Unit status arrives through delay lines; only trust `done` after a
503    // start has had time to reach the units and come back.
504    let settled_start = q.control.ticks - q.control.start_tick > b64(16);
505    if q.control.running && all_done && q.control.key_settled && settled_start {
506        c.running = false;
507        c.done = true;
508        c.done_unread = true;
509        c.isr = true;
510        c.done_tick = q.control.ticks;
511    }
512
513    // ------------------------------------------------ trusted key
514    let mut key_words = [b64(0); 4];
515    for w in 0..4 {
516        key_words[w] = args.key[2 * w].resize::<64>() | (args.key[2 * w + 1].resize::<64>() << 32);
517    }
518    let load_key = q.control.running && !q.control.key_sent && ed.load_key_ready && !resetting;
519    if load_key {
520        c.key_sent = true;
521    }
522    if q.control.key_sent && !q.control.key_settled && (ed.key_state == b2(2) || ed.key_state == b2(3)) {
523        c.key_settled = true;
524        c.key_tick = q.control.ticks;
525    }
526
527    // ------------------------------------------------ certificate arbiter
528    // Oldest-pointer round robin: the first unit at or after `next_unit`
529    // with a request, while the core is ready and the key has settled.
530    let mut pick_found = false;
531    let mut pick = b3(0);
532    for i in 0..VERIFY_UNITS {
533        let u = VERIFY_UNITS - 1 - i;
534        if q.requests[u].valid && b3(u as u128) >= q.control.next_unit {
535            pick_found = true;
536            pick = b3(u as u128);
537        }
538    }
539    if !pick_found {
540        for i in 0..VERIFY_UNITS {
541            let u = VERIFY_UNITS - 1 - i;
542            if q.requests[u].valid {
543                pick_found = true;
544                pick = b3(u as u128);
545            }
546        }
547    }
548    // One request register in front of the core: it loads when empty and
549    // empties when the core takes it (a check is ~735 cycles, so issuing at
550    // most every other clock costs nothing).
551    if q.control.ed_valid && ed.ready {
552        c.ed_valid = false;
553    }
554    let issue = pick_found && !q.control.ed_valid && q.control.key_settled && !resetting;
555    let mut request = q.requests[0].data;
556    for u in 0..VERIFY_UNITS {
557        if pick == b3(u as u128) {
558            request = q.requests[u].data;
559        }
560    }
561    let mut held = q.control.ed_request;
562    held.public_key = key_words;
563    if issue {
564        c.next_unit = if pick == b3((VERIFY_UNITS - 1) as u128) { b3(0) } else { pick + b3(1) };
565        c.ed_valid = true;
566        held.message = words64_kernel(request.commitment);
567        held.signature = words64_kernel(request.signature);
568        held.tag = (pick.resize::<16>() << 6) | request.entry.resize::<16>();
569    }
570    c.ed_request = held;
571
572    // ------------------------------------------------ AXI-Lite write
573    let axi = input.kernel.axi;
574    if axi.aw_valid && !q.control.aw_have {
575        c.aw_addr = axi.aw_addr;
576        c.aw_have = true;
577    }
578    if axi.w_valid && !q.control.w_have {
579        c.w_data = axi.w_data;
580        c.w_have = true;
581    }
582    if q.control.b_valid && axi.b_ready {
583        c.b_valid = false;
584    }
585    if q.control.aw_have && q.control.w_have && !q.control.b_valid {
586        let a = q.control.aw_addr;
587        let v = q.control.w_data;
588        c.aw_have = false;
589        c.w_have = false;
590        c.b_valid = true;
591        if a == b12(0x000) && (v & b32(1)) == b32(1) && !q.control.running && !q.control.done {
592            c.running = true;
593            c.start_tick = q.control.ticks;
594        }
595        if a == b12(0x004) {
596            c.gie = (v & b32(1)) == b32(1);
597        }
598        if a == b12(0x008) {
599            c.ier = (v & b32(1)) == b32(1);
600        }
601        if a == b12(0x00c) && (v & b32(1)) == b32(1) {
602            c.isr = !q.control.isr;
603        }
604        if !q.control.running && !q.control.done {
605            for u in 0..VERIFY_UNITS {
606                if a == b12((ARG_RECORDS + 16 * u) as u128) || a == b12((ARG_RECORDS + 16 * u + 4) as u128) {
607                    c.args.records[u] = set_half_kernel(args.records[u], v, a == b12((ARG_RECORDS + 16 * u + 4) as u128));
608                }
609                if a == b12((ARG_VERDICTS + 16 * u) as u128) || a == b12((ARG_VERDICTS + 16 * u + 4) as u128) {
610                    c.args.verdicts[u] = set_half_kernel(args.verdicts[u], v, a == b12((ARG_VERDICTS + 16 * u + 4) as u128));
611                }
612            }
613            if a == b12(ARG_COUNT as u128) {
614                c.args.count = v;
615            }
616            for w in 0..8 {
617                if a == b12((ARG_KEY + 4 * w) as u128) {
618                    c.args.key[w] = v;
619                }
620            }
621        }
622    }
623
624    // ------------------------------------------------ AXI-Lite read
625    if q.control.r_valid && axi.r_ready {
626        c.r_valid = false;
627    }
628    let ar_ready = !q.control.r_valid;
629    if axi.ar_valid && ar_ready {
630        let a = axi.ar_addr;
631        let mut value = b32(0);
632        if a == b12(0x000) {
633            value = (if q.control.running { b32(1) } else { b32(0) })
634                | (if q.control.done_unread { b32(2) } else { b32(0) })
635                | (if !q.control.running { b32(4) } else { b32(0) })
636                | (if q.control.done_unread { b32(8) } else { b32(0) });
637            c.done_unread = false;
638        }
639        if a == b12(0x004) {
640            value = if q.control.gie { b32(1) } else { b32(0) };
641        }
642        if a == b12(0x008) {
643            value = if q.control.ier { b32(1) } else { b32(0) };
644        }
645        if a == b12(0x00c) {
646            value = if q.control.isr { b32(1) } else { b32(0) };
647        }
648        if a == b12(REG_MAGIC as u128) {
649            value = MAGIC;
650        }
651        if a == b12(REG_ABI as u128) {
652            value = ABI_VERSION;
653        }
654        if a == b12(REG_SHAPE as u128) {
655            value = b32((SHAPE | (TABLE_MASK << 16)) as u128);
656        }
657        if a == b12(REG_FAULT as u128) {
658            value = fault_bits;
659        }
660        if a == b12(REG_TICKS as u128) {
661            value = q.control.ticks.resize();
662            c.ticks_hi = (q.control.ticks >> 32).resize();
663        }
664        if a == b12((REG_TICKS + 4) as u128) {
665            value = q.control.ticks_hi;
666        }
667        if a == b12(REG_START_TICK as u128) {
668            value = q.control.start_tick.resize();
669        }
670        if a == b12((REG_START_TICK + 4) as u128) {
671            value = (q.control.start_tick >> 32).resize();
672        }
673        if a == b12(REG_DONE_TICK as u128) {
674            value = q.control.done_tick.resize();
675        }
676        if a == b12((REG_DONE_TICK + 4) as u128) {
677            value = (q.control.done_tick >> 32).resize();
678        }
679        if a == b12(REG_KEY_TICK as u128) {
680            value = q.control.key_tick.resize();
681        }
682        if a == b12((REG_KEY_TICK + 4) as u128) {
683            value = (q.control.key_tick >> 32).resize();
684        }
685        if a == b12(REG_VALID as u128) {
686            value = valid;
687        }
688        if a == b12(REG_CERT_CHECKS as u128) {
689            value = checks;
690        }
691        if a == b12(REG_CERT_FAILS as u128) {
692            value = fails;
693        }
694        for u in 0..VERIFY_UNITS {
695            if a == b12((REG_RETIRED + 4 * u) as u128) {
696                value = q.status[u].retired;
697            }
698        }
699        c.r_valid = true;
700        c.r_data = value;
701    }
702
703    // ------------------------------------------------ units and their links
704    let result_unit: b3 = (ed.result_tag >> 6).resize();
705    let mut units = [UnitInput {
706        run: false,
707        records_base: b64(0),
708        verdicts_base: b64(0),
709        count: b32(0),
710        records: input.kernel.records[0],
711        verdicts: input.kernel.verdicts[0],
712        cert_taken: false,
713        cert_result: CertResult::default(),
714    }; VERIFY_UNITS];
715    let mut ar = [PipeIn::<ArItem> {
716        data: ArItem { araddr: b64(0), arlen: b8(0) },
717        valid: false,
718        taken: false,
719    }; VERIFY_UNITS];
720    let mut r = [RItem {
721        rvalid: false,
722        rdata: [b8(0); 64],
723        rresp: b2(0),
724        rlast: false,
725    }; VERIFY_UNITS];
726    let mut aw = [PipeIn::<AwItem> {
727        data: AwItem { awaddr: b64(0), awlen: b8(0) },
728        valid: false,
729        taken: false,
730    }; VERIFY_UNITS];
731    let mut w = [PipeIn::<WItem> {
732        data: WItem { wdata: [b8(0); 64], wlast: false },
733        valid: false,
734        taken: false,
735    }; VERIFY_UNITS];
736    let mut b = [BItem { bvalid: false, bresp: b2(0) }; VERIFY_UNITS];
737    let mut to_unit = [UnitControl {
738        run: q.control.running,
739        records_base: args.records[0],
740        verdicts_base: args.verdicts[0],
741        count: args.count,
742    }; VERIFY_UNITS];
743    let mut requests = [PipeIn::<CertRequest> {
744        data: q.units[0].cert_request,
745        valid: false,
746        taken: false,
747    }; VERIFY_UNITS];
748    let mut results = [CertResult::default(); VERIFY_UNITS];
749    let mut status = [UnitStatus {
750        retired: b32(0),
751        valid: b32(0),
752        cert_checks: b32(0),
753        cert_fails: b32(0),
754        done: false,
755        error: b2(0),
756    }; VERIFY_UNITS];
757    let mut records = [input.kernel.records[0]; VERIFY_UNITS];
758    let mut verdicts = [input.kernel.verdicts[0]; VERIFY_UNITS];
759    let mut records_out = [q.units[0].records; VERIFY_UNITS];
760    let mut verdicts_out = [q.units[0].verdicts; VERIFY_UNITS];
761    for u in 0..VERIFY_UNITS {
762        let unit = q.units[u];
763        let port_r = input.kernel.records[u];
764        let port_w = input.kernel.verdicts[u];
765        // Unit side.
766        records[u] = ReadResponse {
767            arready: q.ar[u].ready,
768            rvalid: q.r[u].rvalid,
769            rdata: q.r[u].rdata,
770            rresp: q.r[u].rresp,
771            rlast: q.r[u].rlast,
772        };
773        verdicts[u] = WriteResponse {
774            awready: q.aw[u].ready,
775            wready: q.w[u].ready,
776            bvalid: q.b[u].bvalid,
777            bresp: q.b[u].bresp,
778        };
779        let control = q.to_unit[u];
780        units[u] = UnitInput {
781            run: control.run,
782            records_base: control.records_base,
783            verdicts_base: control.verdicts_base,
784            count: control.count,
785            records: records[u],
786            verdicts: verdicts[u],
787            cert_taken: unit.cert_request.valid && q.requests[u].ready,
788            cert_result: q.results[u],
789        };
790        // Links toward HBM.
791        ar[u] = PipeIn::<ArItem> {
792            data: ArItem {
793                araddr: unit.records.araddr,
794                arlen: unit.records.arlen,
795            },
796            valid: unit.records.arvalid,
797            taken: q.ar[u].valid && port_r.arready,
798        };
799        aw[u] = PipeIn::<AwItem> {
800            data: AwItem {
801                awaddr: unit.verdicts.awaddr,
802                awlen: unit.verdicts.awlen,
803            },
804            valid: unit.verdicts.awvalid,
805            taken: q.aw[u].valid && port_w.awready,
806        };
807        w[u] = PipeIn::<WItem> {
808            data: WItem {
809                wdata: unit.verdicts.wdata,
810                wlast: unit.verdicts.wlast,
811            },
812            valid: unit.verdicts.wvalid,
813            taken: q.w[u].valid && port_w.wready,
814        };
815        records_out[u] = ReadRequest {
816            arvalid: q.ar[u].valid,
817            araddr: q.ar[u].data.araddr,
818            arlen: q.ar[u].data.arlen,
819            rready: true,
820        };
821        verdicts_out[u] = WriteRequest {
822            awvalid: q.aw[u].valid,
823            awaddr: q.aw[u].data.awaddr,
824            awlen: q.aw[u].data.awlen,
825            wvalid: q.w[u].valid,
826            wdata: q.w[u].data.wdata,
827            wlast: q.w[u].data.wlast,
828            bready: true,
829        };
830        // Links back from HBM.
831        r[u] = RItem {
832            rvalid: port_r.rvalid,
833            rdata: port_r.rdata,
834            rresp: port_r.rresp,
835            rlast: port_r.rlast,
836        };
837        b[u] = BItem {
838            bvalid: port_w.bvalid,
839            bresp: port_w.bresp,
840        };
841        // Control, certificates, status.
842        to_unit[u] = UnitControl {
843            run: q.control.running,
844            records_base: args.records[u],
845            verdicts_base: args.verdicts[u],
846            count: args.count,
847        };
848        requests[u] = PipeIn::<CertRequest> {
849            data: unit.cert_request,
850            valid: unit.cert_request.valid,
851            taken: issue && pick == b3(u as u128),
852        };
853        results[u] = CertResult {
854            valid: ed.result_valid && result_unit == b3(u as u128),
855            entry: ed.result_tag.resize(),
856            ok: ed.result_ok,
857        };
858        status[u] = UnitStatus {
859            retired: unit.retired,
860            valid: unit.valid,
861            cert_checks: unit.cert_checks,
862            cert_fails: unit.cert_fails,
863            done: unit.done,
864            error: unit.error,
865        };
866    }
867    let d = VerifyClusterD::<TABLE_MASK> {
868        control: c,
869        units,
870        ar,
871        r,
872        aw,
873        w,
874        b,
875        to_unit,
876        requests,
877        results,
878        status,
879    };
880    (
881        ClusterOutput {
882            kernel: VerifyEngineOutput {
883                axi: AxiLiteOut {
884                    aw_ready: !q.control.aw_have,
885                    w_ready: !q.control.w_have,
886                    b_valid: q.control.b_valid,
887                    b_resp: b2(0),
888                    ar_ready,
889                    r_valid: q.control.r_valid,
890                    r_data: q.control.r_data,
891                    r_resp: b2(0),
892                    interrupt: q.control.gie && q.control.ier && q.control.isr,
893                },
894                records: records_out,
895                verdicts: verdicts_out,
896            },
897            ed: EdCommand {
898                valid: q.control.ed_valid,
899                message: q.control.ed_request.message,
900                signature: q.control.ed_request.signature,
901                public_key: q.control.ed_request.public_key,
902                tag: q.control.ed_request.tag,
903                load_key_valid: load_key,
904            },
905        },
906        d,
907    )
908}
909
910const _: () = assert!(VERIFY_UNITS <= 8, "unit numbers are 3 bits");
911
912// ------------------------------------------------------------------- kernel
913
914use rhdl_ed25519_live::sha_ring::{ShaDone, ShaRingOut};
915use rhdl_ed25519_live::verify_core::{KnownKeyVerifyCore, VerifyIn};
916use rhdl_ed25519_live::wire::{ScalarWords, VerifyRequest, Words512};
917
918/// The Ed25519 verify core of the kernel: one verify machine, its own
919/// SHA-512 ring, one scalar ring (`1,1,1,0`), sized for one certificate
920/// check per 128 DSig verifications.
921pub type CertCore = KnownKeyVerifyCore<1, 1, 1, 0>;
922
923/// The verifier card kernel: [`VerifyCluster`] plus the Ed25519 verify core.
924#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
925pub struct VerifyEngine<const TABLE_MASK: usize> {
926    /// Registers, units and the certificate arbiter.
927    cluster: VerifyCluster<TABLE_MASK>,
928    /// Strict Ed25519 verification of batch certificates.
929    ed: CertCore,
930}
931
932impl<const TABLE_MASK: usize> Default for VerifyEngine<TABLE_MASK> {
933    /// Power-on: IDLE, no key loaded.
934    fn default() -> Self {
935        Self {
936            cluster: VerifyCluster::default(),
937            ed: CertCore::default(),
938        }
939    }
940}
941
942impl<const TABLE_MASK: usize> SynchronousIO for VerifyEngine<TABLE_MASK> {
943    type I = VerifyEngineInput;
944    type O = VerifyEngineOutput;
945    type Kernel = verify_engine_kernel<TABLE_MASK>;
946}
947
948/// Wires the cluster to the Ed25519 core.
949#[kernel]
950pub fn verify_engine_kernel<const TABLE_MASK: usize>(
951    _clock_reset: ClockReset,
952    input: VerifyEngineInput,
953    q: VerifyEngineQ<TABLE_MASK>,
954) -> (VerifyEngineOutput, VerifyEngineD<TABLE_MASK>) {
955    let ed = q.ed;
956    let command = q.cluster.ed;
957    let d = VerifyEngineD::<TABLE_MASK> {
958        cluster: ClusterInput {
959            kernel: input,
960            ed: EdStatus {
961                ready: ed.ready,
962                result_valid: ed.valid,
963                result_ok: ed.ok,
964                result_tag: ed.tag,
965                load_key_ready: ed.load_key_ready,
966                key_state: ed.key_state[0],
967                internal_error: ed.internal_error,
968            },
969        },
970        ed: VerifyIn {
971            valid: command.valid,
972            request: VerifyRequest {
973                message: Words512 { words: command.message },
974                public_key: ScalarWords { words: command.public_key },
975                signature: Words512 { words: command.signature },
976                tag: command.tag,
977            },
978            load_key_valid: command.load_key_valid,
979            load_key: ScalarWords { words: command.public_key },
980            load_slot: b3(0).resize(),
981            sha_ext: ShaRingOut {
982                ready: false,
983                done: ShaDone {
984                    valid: false,
985                    tag: b16(0),
986                    state: [b64(0); 8],
987                },
988            },
989        },
990    };
991    (q.cluster.kernel, d)
992}