Skip to main content

rhdl_ubft/
control.rs

1//! The host's view: ubft_new's register map, plus what rhdl_ubft measured.
2//!
3//! The arguments sit at the offsets of the HLS kernel's `xubft_hw.h`, so
4//! ubft_new's host code (or XRT's `set_arg`) drives rhdl_ubft unchanged.
5//! `ap_ctrl` behaves as `ap_ctrl_hs`: write bit 0 to start; bit 1
6//! (`ap_done`) rises when the run ends and clears when read; bit 2
7//! (`ap_idle`) is high while stopped.
8//!
9//! rhdl_ubft adds one argument ubft_new lacks: `session` at `0x080`/`0x084`
10//! (64 bits), a host-chosen id that is never reused and is signed into every
11//! certify share (sign layout v2, so a share from an old run does not verify).
12//!
13//! Writes to `0x800..0xFFF` while stopped go to the signature scheme's
14//! configuration window ([`crate::ports::CfgWrite`]).
15//!
16//! From [`regs::STATUS`] on, rhdl_ubft adds read-only counters the HLS kernel
17//! does not have. The host reads them after a run instead of guessing from
18//! wall-clock time:
19//!
20//! | Offset | Word |
21//! |---|---|
22//! | `0x100` | `MAGIC` = "RUB1" ([`MAGIC`]) |
23//! | `0x104` | `SHAPE`: build nodes bits 0..8, window 8..16, scheme 16..24 |
24//! | `0x108` | `DECIDED`: slots decided |
25//! | `0x10c` | `FAST`: decided on the fast path |
26//! | `0x110` | `SLOW`: decided on the slow path |
27//! | `0x114` | `VIEWS`: view changes |
28//! | `0x118` | `CYCLES`: clock cycles of the run (lo) |
29//! | `0x11c` | `FIRST`: cycle of the first decision |
30//! | `0x120` | `LAST`: cycle of the last decision |
31//! | `0x124` | `SLOT_MIN`: fewest cycles from a slot's start to its decision |
32//! | `0x128` | `SLOT_MAX`: most |
33//! | `0x12c` | `SLOT_SUM` lo; `0x130` hi |
34//! | `0x134` | `VERIFIED`: certify shares verified (valid) |
35//! | `0x138` | `REJECTED`: shares that failed verification |
36//! | `0x13c` | `STATE`: submachine bits 0..2, state 8..14, view 16..32 |
37//! | `0x140` | `FAULT` (see [`Status::fault`]) |
38
39use rhdl::prelude::*;
40
41use crate::ports::{AxiLiteIn, AxiLiteOut, CfgWrite, idle_lite_out, regs};
42
43/// "RUB1".
44pub const MAGIC: u32 = 0x3142_5552;
45
46/// The run's arguments, as the host wrote them.
47#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
48pub struct Args {
49    /// `network_ptr`: the mailbox buffer's device address.
50    pub network_ptr: b64,
51    /// `message_ptr`: the public keys' buffer.
52    pub message_ptr: b64,
53    /// `node_num`.
54    pub node_num: b32,
55    /// `id`.
56    pub id: b32,
57    /// `execution_cycles`: how long the run lasts, in clock cycles.
58    pub execution_cycles: b32,
59    /// `wait`: polls before a timeout.
60    pub wait: b32,
61    /// `sk0..sk3`: the replica's private key (32 bytes, LE).
62    pub sk: [b64; 4],
63    /// `session`: never reused across runs; bound into every signed share.
64    pub session: b64,
65}
66
67/// What the protocol reports to the host.
68#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
69pub struct Status {
70    /// The run ended (cycles used up); latch `ap_done`.
71    pub finished: bool,
72    /// `SHAPE`.
73    pub shape: b32,
74    /// `DECIDED`.
75    pub decided: b32,
76    /// `FAST`.
77    pub fast: b32,
78    /// `SLOW`.
79    pub slow: b32,
80    /// `VIEWS`.
81    pub views: b32,
82    /// `CYCLES`.
83    pub cycles: b32,
84    /// `FIRST`.
85    pub first: b32,
86    /// `LAST`.
87    pub last: b32,
88    /// `SLOT_MIN`.
89    pub slot_min: b32,
90    /// `SLOT_MAX`.
91    pub slot_max: b32,
92    /// `SLOT_SUM`.
93    pub slot_sum: b64,
94    /// `VERIFIED`.
95    pub verified: b32,
96    /// `REJECTED`.
97    pub rejected: b32,
98    /// `STATE`.
99    pub state: b32,
100    /// `FAULT`: bit 0 bad arguments (refused to start), 1 sequence or view
101    /// bound reached (run stopped), 2 a signature answer of the wrong length
102    /// or tag, 3 a verdict for the wrong peer, 4 a new view for another view
103    /// or a slot outside the window, 5 the signature scheme's fault.
104    pub fault: b32,
105}
106
107/// Inputs of [`Control`].
108#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
109pub struct ControlIn {
110    /// The host's AXI4-Lite requests.
111    pub axi: AxiLiteIn,
112    /// The protocol's report.
113    pub status: Status,
114}
115
116/// Outputs of [`Control`].
117#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
118pub struct ControlOut {
119    /// AXI4-Lite answers.
120    pub axi: AxiLiteOut,
121    /// The arguments.
122    pub args: Args,
123    /// A run is in progress.
124    pub running: bool,
125    /// The run starts this cycle (one-cycle pulse, arguments valid).
126    pub start: bool,
127    /// A host write into the signature scheme's window (0x800..0xFFF).
128    pub cfg: CfgWrite,
129}
130
131/// The control block's registers.
132#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
133pub struct ControlState {
134    /// Arguments.
135    pub args: Args,
136    /// Running.
137    pub running: bool,
138    /// Start pulse pending.
139    pub start: bool,
140    /// `ap_done` latched, not yet read.
141    pub done: bool,
142    /// `GIE`.
143    pub gie: bool,
144    /// `IER` bit 0.
145    pub ier: bool,
146    /// `ISR` bit 0.
147    pub isr: bool,
148    /// Write address captured.
149    pub aw_have: bool,
150    /// Its address.
151    pub aw_addr: b12,
152    /// Write data captured.
153    pub w_have: bool,
154    /// Its word.
155    pub w_data: b32,
156    /// B response pending.
157    pub b_valid: bool,
158    /// R response pending.
159    pub r_valid: bool,
160    /// Its word.
161    pub r_data: b32,
162}
163
164/// The register block.
165#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
166pub struct Control {
167    state: rhdl_fpga::core::dff::DFF<ControlState>,
168}
169
170impl Default for Control {
171    fn default() -> Self {
172        Self { state: rhdl_fpga::core::dff::DFF::new(ControlState::default()) }
173    }
174}
175
176impl SynchronousIO for Control {
177    type I = ControlIn;
178    type O = ControlOut;
179    type Kernel = control_kernel;
180}
181
182/// The value of 64-bit argument `v` at register offset `at` (low word at
183/// `base`, high at `base + 4`).
184#[kernel]
185pub fn arg64_word(v: b64, at: b12, base: b12) -> (bool, b32) {
186    if at == base {
187        (true, v.resize())
188    } else if at == base + b12(4) {
189        (true, (v >> b64(32)).resize())
190    } else {
191        (false, b32(0))
192    }
193}
194
195/// `v` with the 32-bit word at `at` replaced by `data`, if `at` is its low
196/// (`base`) or high (`base + 4`) word.
197#[kernel]
198pub fn set_arg64(v: b64, at: b12, base: b12, data: b32) -> b64 {
199    if at == base {
200        (v & (b64(0xffff_ffff) << b64(32))) | data.resize::<64>()
201    } else if at == base + b12(4) {
202        (v & b64(0xffff_ffff)) | (data.resize::<64>() << b64(32))
203    } else {
204        v
205    }
206}
207
208/// The control block's logic.
209#[kernel]
210pub fn control_kernel(_cr: ClockReset, i: ControlIn, q: ControlQ) -> (ControlOut, ControlD) {
211    let r = q.state;
212    let mut s = r;
213    let mut o = ControlOut {
214        axi: idle_lite_out(),
215        args: r.args,
216        running: false,
217        start: false,
218        cfg: CfgWrite { valid: false, addr: b12(0), data: b32(0) },
219    };
220    s.start = false;
221    if r.running && i.status.finished {
222        s.running = false;
223        s.done = true;
224        s.isr = true;
225    }
226
227    // Writes: take address and data in any order, answer once.
228    o.axi.aw_ready = !r.aw_have && !r.b_valid;
229    o.axi.w_ready = !r.w_have && !r.b_valid;
230    if i.axi.aw_valid && !r.aw_have && !r.b_valid {
231        s.aw_have = true;
232        s.aw_addr = i.axi.aw_addr;
233    }
234    if i.axi.w_valid && !r.w_have && !r.b_valid {
235        s.w_have = true;
236        s.w_data = i.axi.w_data;
237    }
238    if r.aw_have && r.w_have {
239        let a = r.aw_addr;
240        let d = r.w_data;
241        s.aw_have = false;
242        s.w_have = false;
243        s.b_valid = true;
244        if a == b12(regs::AP_CTRL as u128) && (d & b32(1)) == b32(1) && !r.running {
245            s.running = true;
246            s.start = true;
247        }
248        if a == b12(regs::GIE as u128) {
249            s.gie = (d & b32(1)) == b32(1);
250        }
251        if a == b12(regs::IER as u128) {
252            s.ier = (d & b32(1)) == b32(1);
253        }
254        if a == b12(regs::ISR as u128) && (d & b32(1)) == b32(1) {
255            s.isr = false;
256        }
257        // The scheme's window, while stopped.
258        if !r.running && a >= b12(0x800) {
259            o.cfg = CfgWrite { valid: true, addr: a - b12(0x800), data: d };
260        }
261        // Arguments are taken only while stopped.
262        if !r.running {
263            s.args.network_ptr = set_arg64(r.args.network_ptr, a, b12(regs::NETWORK_PTR as u128), d);
264            s.args.message_ptr = set_arg64(r.args.message_ptr, a, b12(regs::MESSAGE_PTR as u128), d);
265            if a == b12(regs::NODE_NUM as u128) {
266                s.args.node_num = d;
267            }
268            if a == b12(regs::ID as u128) {
269                s.args.id = d;
270            }
271            if a == b12(regs::EXECUTION_CYCLES as u128) {
272                s.args.execution_cycles = d;
273            }
274            if a == b12(regs::WAIT as u128) {
275                s.args.wait = d;
276            }
277            for k in 0..4 {
278                s.args.sk[k] = set_arg64(r.args.sk[k], a, b12((regs::SK0 + 12 * k) as u128), d);
279            }
280            s.args.session = set_arg64(r.args.session, a, b12(regs::SESSION as u128), d);
281        }
282    }
283    o.axi.b_valid = r.b_valid;
284    if r.b_valid && i.axi.b_ready {
285        s.b_valid = false;
286    }
287
288    // Reads.
289    o.axi.ar_ready = !r.r_valid;
290    if i.axi.ar_valid && !r.r_valid {
291        let a = i.axi.ar_addr;
292        let st = i.status;
293        let mut v = b32(0);
294        if a == b12(regs::AP_CTRL as u128) {
295            v = (if r.start || r.running { b32(1) } else { b32(0) })
296                | (if r.done { b32(2) } else { b32(0) })
297                | (if !r.running { b32(4) } else { b32(0) })
298                | (if r.done { b32(8) } else { b32(0) });
299            s.done = false;
300        }
301        if a == b12(regs::GIE as u128) {
302            v = if r.gie { b32(1) } else { b32(0) };
303        }
304        if a == b12(regs::IER as u128) {
305            v = if r.ier { b32(1) } else { b32(0) };
306        }
307        if a == b12(regs::ISR as u128) {
308            v = if r.isr { b32(1) } else { b32(0) };
309        }
310        let (h0, w0) = arg64_word(r.args.network_ptr, a, b12(regs::NETWORK_PTR as u128));
311        if h0 {
312            v = w0;
313        }
314        let (h1, w1) = arg64_word(r.args.message_ptr, a, b12(regs::MESSAGE_PTR as u128));
315        if h1 {
316            v = w1;
317        }
318        let (h2, w2) = arg64_word(r.args.session, a, b12(regs::SESSION as u128));
319        if h2 {
320            v = w2;
321        }
322        if a == b12(regs::NODE_NUM as u128) {
323            v = r.args.node_num;
324        }
325        if a == b12(regs::ID as u128) {
326            v = r.args.id;
327        }
328        if a == b12(regs::EXECUTION_CYCLES as u128) {
329            v = r.args.execution_cycles;
330        }
331        if a == b12(regs::WAIT as u128) {
332            v = r.args.wait;
333        }
334        // Status words (the secret key has no read path).
335        let words = [
336            b32(0x3142_5552),
337            st.shape,
338            st.decided,
339            st.fast,
340            st.slow,
341            st.views,
342            st.cycles,
343            st.first,
344            st.last,
345            st.slot_min,
346            st.slot_max,
347            st.slot_sum.resize(),
348            (st.slot_sum >> b64(32)).resize(),
349            st.verified,
350            st.rejected,
351            st.state,
352            st.fault,
353        ];
354        for k in 0..17 {
355            if a == b12((regs::STATUS + 4 * k) as u128) {
356                v = words[k];
357            }
358        }
359        s.r_valid = true;
360        s.r_data = v;
361    }
362    o.axi.r_valid = r.r_valid;
363    o.axi.r_data = r.r_data;
364    if r.r_valid && i.axi.r_ready {
365        s.r_valid = false;
366    }
367    o.axi.interrupt = r.gie && r.ier && r.isr;
368    o.args = r.args;
369    o.running = r.running;
370    o.start = r.start;
371    (o, ControlD { state: s })
372}