Skip to main content

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}