keccak_rhdl/engine.rs
1//! Area-oriented, one-round-per-cycle `Keccak-f[1600]` engine.
2
3use rhdl::prelude::*;
4use rhdl_fpga::core::dff::DFF;
5use rhdl_primitives::NoResetDff;
6
7use crate::{KeccakState, keccak_round};
8
9/// Request presented to [`IterativeKeccak`].
10#[derive(Debug, Default, Copy, Clone, PartialEq, Digital)]
11pub struct IterativeInput {
12 /// Start a new permutation when the engine is ready.
13 pub start: bool,
14 /// Initial Keccak state captured with `start`.
15 pub state: KeccakState,
16}
17
18/// Status and result produced by [`IterativeKeccak`].
19#[derive(Debug, Default, Copy, Clone, PartialEq, Digital)]
20pub struct IterativeOutput {
21 /// True when a request can be accepted on the next rising edge.
22 pub ready: bool,
23 /// True while the engine is applying rounds.
24 pub busy: bool,
25 /// One-cycle pulse indicating that `state` is the completed permutation.
26 pub done: bool,
27 /// Current internal state; valid as a result whenever `done` is true.
28 pub state: KeccakState,
29}
30
31/// A synthesizable `Keccak-f[1600]` engine that reuses one round cell.
32///
33/// Handshake and latency:
34///
35/// 1. Present `start = true` and `state` while `ready = true`.
36/// 2. The request is captured on that rising edge.
37/// 3. Exactly 24 subsequent rising edges apply rounds 0 through 23.
38/// 4. After the round-23 edge, `done = true`, `busy = false`, and `state` is
39/// the completed result. `done` clears on the following edge.
40///
41/// Thus the hardware acceptance-edge to result-visible-edge latency is 24
42/// clock periods. RHDL's `synchronous_sample()` observes values immediately
43/// *before* each rising edge: its accepted-start sample has index `S`, busy
44/// occupies samples `S+1..=S+24`, and it first observes `done` at sample
45/// `S+25`. The extra sample index is an observation convention, not an extra
46/// datapath round or clock period.
47///
48/// A `start` asserted while `busy` is true is ignored. This baseline uses the
49/// 1,600-bit state uses a resetless datapath register. Only the round counter and
50/// handshake control have architectural reset; the state is ignored unless
51/// `busy` or `done` says it is valid.
52#[derive(Debug, Clone, Synchronous, SynchronousDQ)]
53#[rhdl(dq_no_prefix)]
54pub struct IterativeKeccak {
55 state: NoResetDff<KeccakState>,
56 round: DFF<b5>,
57 busy: DFF<bool>,
58 done: DFF<bool>,
59}
60
61impl Default for IterativeKeccak {
62 fn default() -> Self {
63 Self {
64 state: NoResetDff::new(),
65 round: DFF::new(b5(0)),
66 busy: DFF::new(false),
67 done: DFF::new(false),
68 }
69 }
70}
71
72impl SynchronousIO for IterativeKeccak {
73 type I = IterativeInput;
74 type O = IterativeOutput;
75 type Kernel = iterative_kernel;
76}
77
78/// State-transition kernel for [`IterativeKeccak`].
79#[kernel]
80#[allow(clippy::used_underscore_binding)] // The RHDL macro consumes this port.
81pub fn iterative_kernel(_cr: ClockReset, input: IterativeInput, q: Q) -> (IterativeOutput, D) {
82 let mut d = D::dont_care();
83 d.state = q.state;
84 d.round = q.round;
85 d.busy = q.busy;
86 d.done = false;
87
88 if q.busy {
89 d.state = keccak_round(q.state, q.round);
90 if q.round == b5(23) {
91 d.busy = false;
92 d.done = true;
93 } else {
94 d.round = q.round + b5(1);
95 }
96 } else if input.start {
97 d.state = input.state;
98 d.round = b5(0);
99 d.busy = true;
100 }
101
102 let output = IterativeOutput {
103 ready: !q.busy,
104 busy: q.busy,
105 done: q.done,
106 state: q.state,
107 };
108 (output, d)
109}