Skip to main content

rhdl_ubft/
share.rs

1//! Streaming a peer's share out of HBM, realigned, into the verify request.
2//!
3//! A certify share is `SB` 64-byte beats (Ed25519 1, DSig 26) at word
4//! `certify + (slot·N + node)·16·SB`. ubft_new's mailboxes start 60 bytes
5//! into a 64-byte line, so a share straddles lines. [`ShareReader`] reads
6//! `SB + 1` lines in one burst and shifts each pair of lines into one
7//! aligned beat, so the share flows from memory to the verifier with no
8//! buffer, at one beat per cycle while the verifier keeps up.
9
10use rhdl::prelude::*;
11
12use crate::ports::{AxiRead, AxiReadResp, Axis512, idle512, idle_read};
13
14/// A read request.
15#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
16pub struct ShareReq {
17    /// Start (ignored while busy).
18    pub start: bool,
19    /// Buffer base (byte address).
20    pub base: b64,
21    /// First word of the share.
22    pub word: b32,
23    /// Beats in the share (1..=31).
24    pub beats: b5,
25}
26
27/// Inputs of [`ShareReader`].
28#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
29pub struct ShareIn {
30    /// The request.
31    pub req: ShareReq,
32    /// The memory's answers.
33    pub axi: AxiReadResp,
34    /// The consumer takes `out` this cycle if it is valid.
35    pub out_ready: bool,
36}
37
38/// Outputs of [`ShareReader`].
39#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
40pub struct ShareOut {
41    /// Reading.
42    pub busy: bool,
43    /// The next aligned beat (`last` on the share's final beat).
44    pub out: Axis512,
45    /// Requests to the memory.
46    pub axi: AxiRead,
47}
48
49/// The reader's registers.
50#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
51pub struct ShareState {
52    /// 0 idle, 2 reading.
53    pub phase: b2,
54    /// Aligned byte address of the next burst to request.
55    pub addr: b64,
56    /// Lines in the whole read.
57    pub lines: b6,
58    /// Lines of the next burst to request; 0 when all are requested.
59    pub ar_lines: b7,
60    /// Lines of the burst after it (a read crossing a 4 KiB boundary).
61    pub ar_next: b7,
62    /// Beats in the share.
63    pub beats: b5,
64    /// Byte offset of the share in its first line (multiple of 4).
65    pub shift: b6,
66    /// The previous line, waiting for the next.
67    pub prev: [b64; 8],
68    /// Lines received.
69    pub got: b6,
70    /// Beats sent.
71    pub sent: b5,
72}
73
74/// Realigning share reader.
75#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
76pub struct ShareReader {
77    state: rhdl_fpga::core::dff::DFF<ShareState>,
78}
79
80impl Default for ShareReader {
81    fn default() -> Self {
82        Self { state: rhdl_fpga::core::dff::DFF::new(ShareState::default()) }
83    }
84}
85
86impl SynchronousIO for ShareReader {
87    type I = ShareIn;
88    type O = ShareOut;
89    type Kernel = share_kernel;
90}
91
92/// Bytes `shift..shift+64` of the 128 bytes `lo ‖ hi`.
93#[kernel]
94pub fn align(lo: [b64; 8], hi: [b64; 8], shift: b6) -> [b64; 8] {
95    // Word granularity: shift is a multiple of 4 bytes.
96    let mut words = [b32(0); 32];
97    for k in 0..8 {
98        words[2 * k] = lo[k].resize();
99        words[2 * k + 1] = (lo[k] >> b64(32)).resize();
100        words[16 + 2 * k] = hi[k].resize();
101        words[16 + 2 * k + 1] = (hi[k] >> b64(32)).resize();
102    }
103    let off: b4 = (shift >> b6(2)).resize();
104    let mut out = [b64(0); 8];
105    for k in 0..8 {
106        let mut a = b32(0);
107        let mut b = b32(0);
108        for j in 0..16 {
109            if off == b4(j as u128) {
110                a = words[j + 2 * k];
111                b = words[j + 2 * k + 1];
112            }
113        }
114        out[k] = a.resize::<64>() | (b.resize::<64>() << b64(32));
115    }
116    out
117}
118
119/// The reader's logic.
120#[kernel]
121pub fn share_kernel(_cr: ClockReset, i: ShareIn, q: ShareReaderQ) -> (ShareOut, ShareReaderD) {
122    let r = q.state;
123    let mut s = r;
124    let mut o = ShareOut { busy: r.phase != b2(0), out: idle512(), axi: idle_read() };
125    if r.phase == b2(0) && i.req.start {
126        let byte = i.req.base + (i.req.word.resize::<64>() << b64(2));
127        let shift: b6 = byte.resize();
128        s.addr = byte & !b64(63);
129        s.shift = shift;
130        s.beats = i.req.beats;
131        // An unaligned share spans one more line than it has beats.
132        let lines = i.req.beats.resize::<6>() + (if shift == b6(0) { b6(0) } else { b6(1) });
133        s.lines = lines;
134        // An AXI4 burst may not cross a 4 KiB boundary: split the read there.
135        let to_boundary: b7 = b7(64) - ((byte >> b64(6)) & b64(63)).resize::<7>();
136        let first = if to_boundary < lines.resize::<7>() { to_boundary } else { lines.resize::<7>() };
137        s.ar_lines = first;
138        s.ar_next = lines.resize::<7>() - first;
139        s.got = b6(0);
140        s.sent = b5(0);
141        s.phase = b2(2);
142    }
143    if r.phase == b2(2) && r.ar_lines != b7(0) {
144        o.axi.ar_valid = true;
145        o.axi.ar_addr = r.addr;
146        o.axi.ar_len = (r.ar_lines - b7(1)).resize();
147        if i.axi.ar_ready {
148            s.addr = r.addr + (r.ar_lines.resize::<64>() << b64(6));
149            s.ar_lines = r.ar_next;
150            s.ar_next = b7(0);
151        }
152    }
153    if r.phase == b2(2) {
154        let aligned = r.shift == b6(0);
155        // Unaligned: the first line only primes `prev`; each later line
156        // completes one beat. Aligned: every line is a beat.
157        let priming = !aligned && r.got == b6(0);
158        let beat = if aligned { i.axi.r_data } else { align(r.prev, i.axi.r_data, r.shift) };
159        let emit = i.axi.r_valid && !priming;
160        o.out = Axis512 { valid: emit, data: beat, keep: !b64(0), last: r.sent + b5(1) == r.beats };
161        // A line is taken when it primes, or when its beat is taken.
162        let take = i.axi.r_valid && (priming || i.out_ready);
163        o.axi.r_ready = priming || i.out_ready;
164        if take {
165            s.prev = i.axi.r_data;
166            s.got = r.got + b6(1);
167            if emit {
168                s.sent = r.sent + b5(1);
169            }
170            // Counted: a split read's first r_last does not end it.
171            if r.got + b6(1) == r.lines {
172                s.phase = b2(0);
173            }
174        }
175    }
176    (o, ShareReaderD { state: s })
177}