Skip to main content

rhdl_ed25519_scalable_core/
card.rs

1//! The Ed25519 card kernel: the build's signer behind HBM and a host port
2//! (feature `card`).
3//!
4//! # The problem
5//!
6//! Every Ed25519 card number so far came from cycle simulation or from an
7//! out-of-context route. A card measurement needs one top-level kernel that
8//! Vitis can link and XRT can drive. The kernel has to be RHDL all the way
9//! down: the July card builds instantiated hand-written SystemVerilog, so
10//! they don't count.
11//!
12//! # The idea
13//!
14//! ```text
15//!        AXI4-Lite (host, ap_ctrl_hs)                       HBM
16//!              │                                        ▲        │
17//!   ┌──────────▼────────────────────────────────────────┼────────┼──────┐
18//!   │ registers ─seed, count, addresses─┐               │        │      │
19//!   │    ▲ ticks, counters              ▼               │        ▼      │
20//!   │    │        SignatureWriter ◀─ FIFO ◀─ SelectedSigner ◀─ MessageReader
21//!   │    └─────────────────────────────────────────────────────────────┘
22//! ```
23//!
24//! - **Messages.** One 64-byte message is one HBM beat. The reader fetches
25//!   16-beat bursts into a 64-beat FIFO, never crossing a 4 KiB page
26//!   (a 4 KiB-aligned base is required).
27//! - **Signing.** The build-selected [`SelectedSigner`] signs message `i`
28//!   with tag `i`, under one cached key that the kernel expands from the
29//!   seed argument at start.
30//! - **Signatures.** The signer's completion is a pulse with no
31//!   backpressure, so completions go into a FIFO. A request is admitted only
32//!   while the FIFO has room for every request in flight, so the FIFO never
33//!   overflows. The writer stores signature `i` (`R‖S`, 64 bytes, one beat)
34//!   at `signatures + 64·i`.
35//! - **Measurement.** A free-running cycle counter is latched at start, when
36//!   the key is cached, and when memory acknowledges the last signature.
37//!
38//! A run signs at most 65,536 messages (the signer's tag is 16 bits). The
39//! host runs more by starting again.
40//!
41//! # Register map (32-bit words)
42//!
43//! | Offset | Name | Access |
44//! |---|---|---|
45//! | `0x00` | `AP_CTRL`: bit 0 `ap_start`, 1 `ap_done` (clear on read), 2 `ap_idle`, 3 `ap_ready` | RW |
46//! | `0x04`/`0x08`/`0x0c` | `GIE` / `IER` / `ISR` | RW |
47//! | `0x10`/`0x14` | arg `messages`: byte address, 4 KiB aligned | W |
48//! | `0x18`/`0x1c` | arg `signatures`: byte address, 64-byte aligned | W |
49//! | `0x20` | arg `count`: 1..=65,536 | W |
50//! | `0x28 + 4w` | args `seed0..seed7`: the Ed25519 seed (secret, no read path) | W |
51//!
52//! Arguments sit at Vitis's usual 8-byte steps from `0x10`.
53//! | `0x100` | `MAGIC` = "ECD1" | R |
54//! | `0x104` | `SIGNED`: signatures acknowledged by memory | R |
55//! | `0x108` | `FAULT`: bit 0 signer, 1 read error, 2 write error, 3 bad arguments | R |
56//! | `0x110`/`0x114` | `START_TICK` lo/hi | R |
57//! | `0x118`/`0x11c` | `KEY_TICK` lo/hi: the key is cached | R |
58//! | `0x120`/`0x124` | `DONE_TICK` lo/hi | R |
59//! | `0x128`/`0x12c` | `TICKS` lo/hi: the live counter (reading lo snapshots hi) | R |
60//! | `0x140 + 4w` | `PUBLIC_KEY` (8 words) | R |
61
62use rhdl::prelude::*;
63use rhdl_fpga::core::dff::DFF;
64use rhdl_fpga::fifo::synchronous::{In as FifoIn, SyncFIFO};
65
66use crate::export::SelectedSigner;
67use crate::sha_ring::{ShaDone, ShaRingOut};
68use crate::sign_core::{ScalableSignIn, ScalableSignOut};
69use crate::wire::{ScalarWords, SignRequest, Words512};
70
71/// "ECD1".
72pub const MAGIC: u32 = 0x3144_4345;
73/// Beats per read burst (1 KiB).
74pub const BURST: u32 = 16;
75/// log2 of the message FIFO depth (64 beats).
76pub const MSG_FIFO_BITS: usize = 6;
77/// log2 of the signature FIFO depth (2,048 signatures).
78pub const SIG_FIFO_BITS: usize = 11;
79
80/// AXI4 read master signals (kernel → memory).
81#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
82pub struct AxiRead {
83    /// AR valid.
84    pub ar_valid: bool,
85    /// AR address.
86    pub ar_addr: b64,
87    /// AR len (beats − 1).
88    pub ar_len: b8,
89    /// R ready.
90    pub r_ready: bool,
91}
92
93/// AXI4 read responses (memory → kernel).
94#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
95pub struct AxiReadResp {
96    /// AR ready.
97    pub ar_ready: bool,
98    /// R valid.
99    pub r_valid: bool,
100    /// R data.
101    pub r_data: [b64; 8],
102    /// R resp (0 OKAY).
103    pub r_resp: b2,
104    /// R last.
105    pub r_last: bool,
106}
107
108/// AXI4 write master signals (kernel → memory): single-beat writes.
109#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
110pub struct AxiWrite {
111    /// AW valid.
112    pub aw_valid: bool,
113    /// AW address.
114    pub aw_addr: b64,
115    /// W valid.
116    pub w_valid: bool,
117    /// W data.
118    pub w_data: [b64; 8],
119    /// B ready.
120    pub b_ready: bool,
121}
122
123/// AXI4 write responses (memory → kernel).
124#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
125pub struct AxiWriteResp {
126    /// AW ready.
127    pub aw_ready: bool,
128    /// W ready.
129    pub w_ready: bool,
130    /// B valid.
131    pub b_valid: bool,
132    /// B resp (0 OKAY).
133    pub b_resp: b2,
134}
135
136/// AXI4-Lite requests from the host.
137#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
138pub struct LiteIn {
139    /// AW valid.
140    pub aw_valid: bool,
141    /// AW address.
142    pub aw_addr: b12,
143    /// W valid.
144    pub w_valid: bool,
145    /// W data.
146    pub w_data: b32,
147    /// B ready.
148    pub b_ready: bool,
149    /// AR valid.
150    pub ar_valid: bool,
151    /// AR address.
152    pub ar_addr: b12,
153    /// R ready.
154    pub r_ready: bool,
155}
156
157/// AXI4-Lite responses.
158#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
159pub struct LiteOut {
160    /// AW ready.
161    pub aw_ready: bool,
162    /// W ready.
163    pub w_ready: bool,
164    /// B valid.
165    pub b_valid: bool,
166    /// AR ready.
167    pub ar_ready: bool,
168    /// R valid.
169    pub r_valid: bool,
170    /// R data.
171    pub r_data: b32,
172    /// Interrupt (`ap_done` with `GIE` and `IER`).
173    pub interrupt: bool,
174}
175
176/// Kernel inputs.
177#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
178pub struct CardIn {
179    /// Host control.
180    pub ctl: LiteIn,
181    /// Message reads.
182    pub rd: AxiReadResp,
183    /// Signature writes.
184    pub wr: AxiWriteResp,
185}
186
187/// Kernel outputs.
188#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
189pub struct CardOut {
190    /// Host control.
191    pub ctl: LiteOut,
192    /// Message reads.
193    pub rd: AxiRead,
194    /// Signature writes.
195    pub wr: AxiWrite,
196}
197
198/// A completed signature, waiting for memory.
199#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
200pub struct SigEntry {
201    /// Its message index.
202    pub tag: b16,
203    /// `R‖S`.
204    pub sig: [b64; 8],
205}
206
207/// The kernel's control registers.
208#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
209pub struct CardControl {
210    /// Free-running cycle counter.
211    pub ticks: b64,
212    /// `messages`.
213    pub messages: b64,
214    /// `signatures`.
215    pub signatures: b64,
216    /// `count`.
217    pub count: b32,
218    /// The seed.
219    pub seed: [b32; 8],
220    /// Running.
221    pub running: bool,
222    /// The previous run's key is being cleared (the cycle after start).
223    pub clearing: bool,
224    /// The key load was offered and taken.
225    pub key_taken: bool,
226    /// The key is cached (latched once per run).
227    pub key_ticked: bool,
228    /// Done, `ap_done` not yet read.
229    pub done: bool,
230    /// Ever done (for `ap_idle`).
231    pub finished: bool,
232    /// `GIE`, `IER`, `ISR`.
233    pub gie: bool,
234    /// `IER` bit 0.
235    pub ier: bool,
236    /// `ISR` bit 0.
237    pub isr: bool,
238    /// Latched ticks.
239    pub start_tick: b64,
240    /// Key cached.
241    pub key_tick: b64,
242    /// Last signature acknowledged.
243    pub done_tick: b64,
244    /// Beats requested from memory.
245    pub requested: b32,
246    /// Beats received into the message FIFO.
247    pub received: b32,
248    /// Messages admitted to the signer.
249    pub admitted: b32,
250    /// Signatures completed by the signer.
251    pub completed: b32,
252    /// Signatures handed to memory (AW and W both taken).
253    pub sent: b32,
254    /// Signatures acknowledged (B).
255    pub acked: b32,
256    /// AR outstanding: requested address and length accepted flag.
257    pub ar_pending: bool,
258    /// The write in progress: AW taken, W taken.
259    pub aw_done: bool,
260    /// W taken.
261    pub w_done: bool,
262    /// The head signature is being written.
263    pub writing: bool,
264    /// The signature being written.
265    pub cur: SigEntry,
266    /// Faults (`FAULT`).
267    pub fault: b4,
268    /// AXI4-Lite: write address captured.
269    pub aw_have: bool,
270    /// Its address.
271    pub aw_addr: b12,
272    /// Write data captured.
273    pub w_have: bool,
274    /// Its word.
275    pub w_data: b32,
276    /// B pending.
277    pub b_valid: bool,
278    /// R pending.
279    pub r_valid: bool,
280    /// R word.
281    pub r_data: b32,
282    /// `TICKS` high word, snapshotted when the low word is read.
283    pub ticks_hi: b32,
284}
285
286/// The Ed25519 card kernel.
287#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
288pub struct Ed25519Card {
289    control: DFF<CardControl>,
290    signer: SelectedSigner,
291    msg_fifo: SyncFIFO<[b64; 8], MSG_FIFO_BITS>,
292    sig_fifo: SyncFIFO<SigEntry, SIG_FIFO_BITS>,
293}
294
295impl Default for Ed25519Card {
296    fn default() -> Self {
297        Self {
298            control: DFF::new(CardControl::default()),
299            signer: SelectedSigner::default(),
300            msg_fifo: SyncFIFO::default(),
301            sig_fifo: SyncFIFO::default(),
302        }
303    }
304}
305
306impl SynchronousIO for Ed25519Card {
307    type I = CardIn;
308    type O = CardOut;
309    type Kernel = ed25519_card_kernel;
310}
311
312/// `v` with its low (`hi` false) or high 32 bits replaced by `w`.
313#[kernel]
314pub fn set_half(v: b64, w: b32, hi: bool) -> b64 {
315    if hi {
316        (v & b64(0xffff_ffff)) | (w.resize::<64>() << b64(32))
317    } else {
318        (v & (b64(0xffff_ffff) << b64(32))) | w.resize::<64>()
319    }
320}
321
322/// The kernel's logic.
323#[allow(clippy::too_many_lines)]
324#[kernel]
325pub fn ed25519_card_kernel(_cr: ClockReset, i: CardIn, q: Ed25519CardQ) -> (CardOut, Ed25519CardD) {
326    let r = q.control;
327    let mut c = r;
328    let sig: ScalableSignOut = q.signer;
329    c.ticks = r.ticks + b64(1);
330    let mut o = CardOut {
331        ctl: LiteOut { aw_ready: false, w_ready: false, b_valid: r.b_valid, ar_ready: false, r_valid: r.r_valid, r_data: r.r_data, interrupt: r.gie && r.ier && r.isr },
332        rd: AxiRead { ar_valid: false, ar_addr: b64(0), ar_len: b8(0), r_ready: false },
333        wr: AxiWrite { aw_valid: false, aw_addr: b64(0), w_valid: false, w_data: [b64(0); 8], b_ready: true },
334    };
335
336    // ---------------------------------------------------- host writes
337    o.ctl.aw_ready = !r.aw_have && !r.b_valid;
338    o.ctl.w_ready = !r.w_have && !r.b_valid;
339    if i.ctl.aw_valid && !r.aw_have && !r.b_valid {
340        c.aw_have = true;
341        c.aw_addr = i.ctl.aw_addr;
342    }
343    if i.ctl.w_valid && !r.w_have && !r.b_valid {
344        c.w_have = true;
345        c.w_data = i.ctl.w_data;
346    }
347    let idle = !r.running;
348    if r.aw_have && r.w_have {
349        let a = r.aw_addr;
350        let d = r.w_data;
351        c.aw_have = false;
352        c.w_have = false;
353        c.b_valid = true;
354        if a == b12(0x00) && (d & b32(1)) == b32(1) && idle {
355            // Start: arguments are checked first.
356            let ok = r.count != b32(0)
357                && r.count <= b32(65_536)
358                && (r.messages & b64(0xfff)) == b64(0)
359                && (r.signatures & b64(0x3f)) == b64(0);
360            if ok {
361                c.running = true;
362                c.done = false;
363                c.start_tick = r.ticks;
364                // Clear any previous run's key before loading this seed.
365                c.clearing = true;
366                c.key_taken = false;
367                c.key_ticked = false;
368                c.requested = b32(0);
369                c.received = b32(0);
370                c.admitted = b32(0);
371                c.completed = b32(0);
372                c.sent = b32(0);
373                c.acked = b32(0);
374                c.fault = b4(0);
375            } else {
376                c.fault = b4(8);
377                c.done = true;
378                c.finished = true;
379                c.isr = true;
380            }
381        }
382        if a == b12(0x04) {
383            c.gie = (d & b32(1)) == b32(1);
384        }
385        if a == b12(0x08) {
386            c.ier = (d & b32(1)) == b32(1);
387        }
388        if a == b12(0x0c) && (d & b32(1)) == b32(1) {
389            c.isr = false;
390        }
391        if idle {
392            if a == b12(0x10) {
393                c.messages = set_half(r.messages, d, false);
394            }
395            if a == b12(0x14) {
396                c.messages = set_half(r.messages, d, true);
397            }
398            if a == b12(0x18) {
399                c.signatures = set_half(r.signatures, d, false);
400            }
401            if a == b12(0x1c) {
402                c.signatures = set_half(r.signatures, d, true);
403            }
404            if a == b12(0x20) {
405                c.count = d;
406            }
407            for w in 0..8 {
408                if a == b12((0x28 + 4 * w) as u128) {
409                    c.seed[w] = d;
410                }
411            }
412        }
413    }
414    if r.b_valid && i.ctl.b_ready {
415        c.b_valid = false;
416    }
417
418    // ---------------------------------------------------- host reads
419    o.ctl.ar_ready = !r.r_valid;
420    if i.ctl.ar_valid && !r.r_valid {
421        let a = i.ctl.ar_addr;
422        let mut v = b32(0);
423        if a == b12(0x00) {
424            v = (if r.running { b32(1) } else { b32(0) })
425                | (if r.done { b32(2) } else { b32(0) })
426                | (if !r.running { b32(4) } else { b32(0) })
427                | (if r.done { b32(8) } else { b32(0) });
428            c.done = false;
429        }
430        if a == b12(0x04) {
431            v = if r.gie { b32(1) } else { b32(0) };
432        }
433        if a == b12(0x08) {
434            v = if r.ier { b32(1) } else { b32(0) };
435        }
436        if a == b12(0x0c) {
437            v = if r.isr { b32(1) } else { b32(0) };
438        }
439        if a == b12(0x100) {
440            v = b32(0x3144_4345);
441        }
442        if a == b12(0x104) {
443            v = r.acked;
444        }
445        if a == b12(0x108) {
446            v = r.fault.resize();
447        }
448        if a == b12(0x110) {
449            v = r.start_tick.resize();
450        }
451        if a == b12(0x114) {
452            v = (r.start_tick >> b64(32)).resize();
453        }
454        if a == b12(0x118) {
455            v = r.key_tick.resize();
456        }
457        if a == b12(0x11c) {
458            v = (r.key_tick >> b64(32)).resize();
459        }
460        if a == b12(0x120) {
461            v = r.done_tick.resize();
462        }
463        if a == b12(0x124) {
464            v = (r.done_tick >> b64(32)).resize();
465        }
466        if a == b12(0x128) {
467            v = r.ticks.resize();
468            c.ticks_hi = (r.ticks >> b64(32)).resize();
469        }
470        if a == b12(0x12c) {
471            v = r.ticks_hi;
472        }
473        for w in 0..4 {
474            let pk = sig.public_key.words[w];
475            if a == b12((0x140 + 8 * w) as u128) {
476                v = pk.resize();
477            }
478            if a == b12((0x144 + 8 * w) as u128) {
479                v = (pk >> b64(32)).resize();
480            }
481        }
482        c.r_valid = true;
483        c.r_data = v;
484    }
485    if r.r_valid && i.ctl.r_ready {
486        c.r_valid = false;
487    }
488
489    // ---------------------------------------------------- key
490    let mut seed_words = [b64(0); 4];
491    for w in 0..4 {
492        seed_words[w] = r.seed[2 * w].resize::<64>() | (r.seed[2 * w + 1].resize::<64>() << b64(32));
493    }
494    c.clearing = false;
495    let offer_key = r.running && !r.clearing && !r.key_taken && !sig.key_loaded;
496    if offer_key && sig.load_key_ready {
497        c.key_taken = true;
498    }
499    if r.running && sig.key_loaded && !r.key_ticked {
500        c.key_ticked = true;
501        c.key_tick = r.ticks;
502    }
503
504    // ---------------------------------------------------- message reads
505    // Bursts of 16 beats while the FIFO has room for one more burst; the
506    // last burst is shorter. A 4 KiB-aligned base keeps every burst inside
507    // its page.
508    let room = (r.requested - r.admitted) <= b32((64 - 16) as u128);
509    let left = r.count - r.requested;
510    let len = if left < b32(16) { left } else { b32(16) };
511    if r.running && !r.ar_pending && left != b32(0) && room {
512        o.rd.ar_valid = true;
513        o.rd.ar_addr = r.messages + (r.requested.resize::<64>() << b64(6));
514        o.rd.ar_len = (len - b32(1)).resize();
515        if i.rd.ar_ready {
516            c.requested = r.requested + len;
517            c.ar_pending = true;
518        }
519    }
520    let msg_full = q.msg_fifo.full;
521    o.rd.r_ready = !msg_full;
522    let take_beat = i.rd.r_valid && !msg_full;
523    if take_beat {
524        c.received = r.received + b32(1);
525        if i.rd.r_resp != b2(0) {
526            c.fault = c.fault | b4(2);
527        }
528        if i.rd.r_last {
529            c.ar_pending = false;
530        }
531    }
532
533    // ---------------------------------------------------- signing
534    // Admit only while the signature FIFO can take everything in flight.
535    let in_flight = r.admitted - r.sent;
536    let sig_room = in_flight < b32(((1 << SIG_FIFO_BITS) - 2) as u128);
537    let mut head_msg = [b64(0); 8];
538    let mut have_msg = false;
539    match q.msg_fifo.data {
540        Some(m) => {
541            head_msg = m;
542            have_msg = true;
543        }
544        None => {}
545    }
546    let admit = r.running && r.key_ticked && have_msg && sig.ready && sig_room && r.admitted < r.count;
547    if admit {
548        c.admitted = r.admitted + b32(1);
549    }
550    if sig.valid {
551        c.completed = r.completed + b32(1);
552    }
553    if sig.internal_error {
554        c.fault = c.fault | b4(1);
555    }
556
557    // ---------------------------------------------------- signature writes
558    let mut pop_sig = false;
559    if !r.writing {
560        match q.sig_fifo.data {
561            Some(e) => {
562                c.cur = e;
563                c.writing = true;
564                c.aw_done = false;
565                c.w_done = false;
566                pop_sig = true;
567            }
568            None => {}
569        }
570    }
571    if r.writing {
572        o.wr.aw_valid = !r.aw_done;
573        o.wr.aw_addr = r.signatures + (r.cur.tag.resize::<64>() << b64(6));
574        o.wr.w_valid = !r.w_done;
575        o.wr.w_data = r.cur.sig;
576        let aw = r.aw_done || i.wr.aw_ready;
577        let w = r.w_done || i.wr.w_ready;
578        c.aw_done = aw;
579        c.w_done = w;
580        if aw && w {
581            c.writing = false;
582            c.sent = r.sent + b32(1);
583        }
584    }
585    if i.wr.b_valid {
586        c.acked = r.acked + b32(1);
587        if i.wr.b_resp != b2(0) {
588            c.fault = c.fault | b4(4);
589        }
590    }
591
592    // ---------------------------------------------------- done
593    if r.running && r.acked == r.count && r.count != b32(0) {
594        c.running = false;
595        c.done = true;
596        c.finished = true;
597        c.isr = true;
598        c.done_tick = r.ticks;
599    }
600
601    let mut d = Ed25519CardD::dont_care();
602    d.control = c;
603    d.signer = ScalableSignIn {
604        load_key_valid: offer_key,
605        key_seed: ScalarWords { words: seed_words },
606        clear_key: r.clearing,
607        valid: admit,
608        request: SignRequest { message: Words512 { words: head_msg }, tag: r.admitted.resize() },
609        sha_ext: ShaRingOut { ready: false, done: ShaDone { valid: false, tag: b16(0), state: [b64(0); 8] } },
610    };
611    d.msg_fifo = FifoIn::<[b64; 8]> { data: if take_beat { Some(i.rd.r_data) } else { None }, next: admit };
612    d.sig_fifo = FifoIn::<SigEntry> {
613        data: if sig.valid { Some(SigEntry { tag: sig.tag, sig: sig.signature.words }) } else { None },
614        next: pop_sig,
615    };
616    (o, d)
617}
618
619#[cfg(test)]
620mod tests {
621    use super::*;
622    use crate::simulation::CycleSim;
623    use ed25519_dalek::{Signer, SigningKey};
624
625    /// One AXI4-Lite write, driven to completion.
626    fn lite_write(sim: &mut CycleSim<Ed25519Card>, mem: &mut Mem, addr: u32, data: u32) {
627        let mut ctl = LiteIn { aw_valid: true, aw_addr: b12(u128::from(addr)), w_valid: true, w_data: b32(u128::from(data)), b_ready: true, ..Default::default() };
628        for _ in 0..20 {
629            let o = step(sim, mem, ctl);
630            if o.ctl.aw_ready {
631                ctl.aw_valid = false;
632            }
633            if o.ctl.w_ready {
634                ctl.w_valid = false;
635            }
636            if o.ctl.b_valid && !ctl.aw_valid && !ctl.w_valid {
637                step(sim, mem, LiteIn::default());
638                return;
639            }
640        }
641        panic!("AXI-Lite write {addr:#x} stuck");
642    }
643
644    fn lite_read(sim: &mut CycleSim<Ed25519Card>, mem: &mut Mem, addr: u32) -> u32 {
645        let ctl = LiteIn { ar_valid: true, ar_addr: b12(u128::from(addr)), r_ready: true, ..Default::default() };
646        let o = step(sim, mem, ctl);
647        assert!(o.ctl.ar_ready);
648        let o = step(sim, mem, LiteIn { r_ready: true, ..Default::default() });
649        assert!(o.ctl.r_valid);
650        o.ctl.r_data.raw() as u32
651    }
652
653    /// HBM: messages in, signatures out, with a fixed read latency and a
654    /// write path that stalls every third cycle.
655    struct Mem {
656        messages: Vec<[u8; 64]>,
657        signatures: Vec<Option<[u8; 64]>>,
658        msg_base: u64,
659        sig_base: u64,
660        reads: std::collections::VecDeque<(u64, u64, bool)>,
661        now: u64,
662        b_pending: u32,
663        aw: Option<u64>,
664        w: Option<[b64; 8]>,
665    }
666
667    fn step(sim: &mut CycleSim<Ed25519Card>, mem: &mut Mem, ctl: LiteIn) -> CardOut {
668        mem.now += 1;
669        let mut i = CardIn { ctl, ..Default::default() };
670        i.rd.ar_ready = true;
671        if let Some(&(t, addr, last)) = mem.reads.front() {
672            if t <= mem.now {
673                let idx = ((addr - mem.msg_base) / 64) as usize;
674                let m = mem.messages[idx];
675                i.rd.r_valid = true;
676                i.rd.r_data = std::array::from_fn(|k| b64(u128::from(u64::from_le_bytes(m[8 * k..8 * k + 8].try_into().unwrap()))));
677                i.rd.r_last = last;
678            }
679        }
680        let stall = mem.now % 3 == 0;
681        i.wr.aw_ready = !stall && mem.aw.is_none();
682        i.wr.w_ready = !stall && mem.w.is_none();
683        i.wr.b_valid = mem.b_pending > 0;
684        let o = sim.peek(i);
685        if o.rd.ar_valid {
686            let base = o.rd.ar_addr.raw() as u64;
687            let len = o.rd.ar_len.raw() as u64;
688            for b in 0..=len {
689                mem.reads.push_back((mem.now + 64, base + 64 * b, b == len));
690            }
691        }
692        if i.rd.r_valid && o.rd.r_ready {
693            mem.reads.pop_front();
694        }
695        if o.wr.aw_valid && i.wr.aw_ready {
696            mem.aw = Some(o.wr.aw_addr.raw() as u64);
697        }
698        if o.wr.w_valid && i.wr.w_ready {
699            mem.w = Some(o.wr.w_data);
700        }
701        if i.wr.b_valid && o.wr.b_ready {
702            mem.b_pending -= 1;
703        }
704        if let (Some(addr), Some(data)) = (mem.aw, mem.w) {
705            let idx = ((addr - mem.sig_base) / 64) as usize;
706            let mut s = [0u8; 64];
707            for k in 0..8 {
708                s[8 * k..8 * k + 8].copy_from_slice(&(data[k].raw() as u64).to_le_bytes());
709            }
710            assert!(mem.signatures[idx].is_none(), "signature {idx} written twice");
711            mem.signatures[idx] = Some(s);
712            mem.aw = None;
713            mem.w = None;
714            mem.b_pending += 1;
715        }
716        sim.tick(i);
717        o
718    }
719
720    /// The whole kernel, driven as a host would: arguments, start, poll
721    /// `ap_done`. Every signature equals ed25519-dalek's, the public key
722    /// register matches, and the ticks are ordered.
723    #[test]
724    #[ignore = "native simulation of the build's signer, several minutes; run with --profile sim"]
725    fn card_kernel_signs_like_dalek() {
726        let n = 24usize;
727        let seed = [0x42u8; 32];
728        let key = SigningKey::from_bytes(&seed);
729        let messages: Vec<[u8; 64]> = (0..n).map(|i| std::array::from_fn(|b| (i * 31 + b * 7) as u8)).collect();
730        let mut mem = Mem {
731            messages: messages.clone(),
732            signatures: vec![None; n],
733            msg_base: 0x1_0000,
734            sig_base: 0x8_0040,
735            reads: Default::default(),
736            now: 0,
737            b_pending: 0,
738            aw: None,
739            w: None,
740        };
741        let mut sim = CycleSim::new(Ed25519Card::default());
742        let (msg_base, sig_base) = (mem.msg_base as u32, mem.sig_base as u32);
743        lite_write(&mut sim, &mut mem, 0x10, msg_base);
744        lite_write(&mut sim, &mut mem, 0x14, 0);
745        lite_write(&mut sim, &mut mem, 0x18, sig_base);
746        lite_write(&mut sim, &mut mem, 0x1c, 0);
747        lite_write(&mut sim, &mut mem, 0x20, n as u32);
748        for w in 0..8 {
749            lite_write(&mut sim, &mut mem, 0x28 + 4 * w, u32::from_le_bytes(seed[4 * w as usize..4 * w as usize + 4].try_into().unwrap()));
750        }
751        lite_write(&mut sim, &mut mem, 0x00, 1);
752        let mut polls = 0u64;
753        loop {
754            for _ in 0..5000 {
755                step(&mut sim, &mut mem, LiteIn::default());
756            }
757            polls += 1;
758            assert!(polls < 400, "no ap_done after {} cycles", mem.now);
759            if lite_read(&mut sim, &mut mem, 0x00) & 2 != 0 {
760                break;
761            }
762        }
763        assert_eq!(lite_read(&mut sim, &mut mem, 0x108), 0, "FAULT");
764        assert_eq!(lite_read(&mut sim, &mut mem, 0x104), n as u32);
765        for (i, m) in messages.iter().enumerate() {
766            let want = key.sign(m).to_bytes();
767            assert_eq!(mem.signatures[i], Some(want), "signature {i}");
768        }
769        let mut pk = [0u8; 32];
770        for w in 0..8 {
771            pk[4 * w..4 * w + 4].copy_from_slice(&lite_read(&mut sim, &mut mem, 0x140 + 4 * w as u32).to_le_bytes());
772        }
773        assert_eq!(pk, key.verifying_key().to_bytes());
774        let mut tick = |lo: u32| u64::from(lite_read(&mut sim, &mut mem, lo)) | (u64::from(lite_read(&mut sim, &mut mem, lo + 4)) << 32);
775        let (start, keyt, done) = (tick(0x110), tick(0x118), tick(0x120));
776        assert!(start < keyt && keyt < done);
777        println!("{n} signatures: key {} cycles, signing {} cycles ({:.1} cycles/signature)", keyt - start, done - keyt, (done - keyt) as f64 / n as f64);
778    }
779
780    /// Bad arguments refuse to start and say so.
781    #[test]
782    fn bad_arguments_refuse_to_start() {
783        let mut mem = Mem { messages: vec![], signatures: vec![], msg_base: 0, sig_base: 0, reads: Default::default(), now: 0, b_pending: 0, aw: None, w: None };
784        let mut sim = CycleSim::new(Ed25519Card::default());
785        lite_write(&mut sim, &mut mem, 0x10, 0x123); // not 4 KiB aligned
786        lite_write(&mut sim, &mut mem, 0x20, 4);
787        lite_write(&mut sim, &mut mem, 0x00, 1);
788        assert_eq!(lite_read(&mut sim, &mut mem, 0x108), 8);
789        assert_eq!(lite_read(&mut sim, &mut mem, 0x100), MAGIC);
790    }
791}