Skip to main content

rhdl_sha512_pipelined/
folded.rs

1//! A folded SHA-512 core: **S round stages in a ring**, `S / 80` blocks per
2//! clock, for lines smaller than one full pipeline.
3//!
4//! ```text
5//!            ┌──────────────────── recirculate (round < 80) ───────────────┐
6//!            ▼                                                             │
7//!  ShaIn ─▶ [skid] ─▶ mux ─▶ round ─▶ round ─▶ … ─▶ round ──┬─────────────┘
8//!                            stage 0  stage 1      stage S−1 └─▶ [final add] ─▶ ShaOut
9//!                                                                (round = 80)
10//! ```
11//!
12//! # Why this exists
13//!
14//! **Problem.** The full [`Sha512Pipeline`](crate::Sha512Pipeline) gives one
15//! block per clock for ~43k LUTs. The Ed25519 modules need far less: the
16//! default Ed25519 chip hashes about 25M blocks per second, an eighth of a
17//! block per clock. Before this core the line came only in whole and half
18//! blocks per clock (the half-rate "folded" core was a plan), so most of its
19//! LUTs bought nothing, and a line shrunk below ~23k could not be built.
20//!
21//! **Reasoning.** All 80 rounds are the same step with a different constant.
22//! A block does not need 80 stages of its own: it can go round a ring of `S`
23//! stages `80 / S` times. Every stage carries the block's round counter, and
24//! the constant `K_t` is read from a 128-entry ROM by that counter (a 7-input
25//! function per bit, two LUT6 each), so a stage serves any round. The ring
26//! holds `S` blocks at once, each for 80 cycles: **`S / 80` blocks per
27//! clock**, and the latency stays 82 cycles.
28//!
29//! **Decision.** One core per stage count `S` that divides 80:
30//! [`Sha512Folded2`], [`Sha512Folded4`], [`Sha512Folded5`],
31//! [`Sha512Folded8`], [`Sha512Folded10`], [`Sha512Folded16`],
32//! [`Sha512Folded20`] and [`Sha512Folded40`] ([`FOLDS`]). `S = 80` would be
33//! a full pipeline with a ROM per stage, so the line uses
34//! [`Sha512Pipeline`](crate::Sha512Pipeline) there. The cores share one
35//! control kernel ([`fold_control`]) and differ only in their ring segment
36//! ([`FoldSeg10`] and friends: at most ten children each, every stage wired
37//! as one expression, because RHDL lowers array-element writes to
38//! whole-array copies).
39//! The planner ([`crate::planner`]) builds the line from any mix of folded
40//! cores and full pipelines, the fastest that fits the line's LUTs, so the
41//! line's rate moves in steps of 1/40 block per clock instead of 1/2.
42//!
43//! **Status.** Every size is bit-exact against `sha2::compress512` in RHDL
44//! simulation at its full rate and under back-pressure, and lowers to
45//! Verilog that passes RHDL's syntax check; the 2-, 5- and 10-stage Verilog
46//! matches the Rust model cycle by cycle in Icarus (`tests/folded.rs`). Every
47//! core routes out of context at 4.878 ns (205 MHz) with positive slack;
48//! the planner uses those routed LUTs (`planner::ROUTED_LUT`), and
49//! [`folded_lut`] remains the older Yosys estimate.
50//!
51//! # Ports and flow control
52//!
53//! The same [`ShaIn`] and [`ShaOut`] as the full pipeline, but with
54//! back-pressure: [`ShaCoreOut::ready`] is low while the ring is full. A
55//! client presents a block and holds it until `ready` is high in that
56//! cycle (a block presented while `ready` is low is ignored).
57//!
58//! Inside, a one-entry **skid buffer** holds the accepted block until the
59//! slot arriving at stage 0 is free. A slot is busy while its block still
60//! has rounds to do; it frees the cycle its block finishes round 79, and the
61//! buffered block takes it. Blocks finish in the order they entered.
62//!
63//! # Where to go next
64//!
65//! | You want to… | Start here |
66//! |---|---|
67//! | see one stage | [`FoldRound`], [`fold_round_kernel`] |
68//! | see the ring and the skid buffer | [`fold_control`] |
69//! | change what the planner thinks a fold costs | [`folded_lut`], [`crate::planner`] |
70
71use rhdl::prelude::*;
72use rhdl_fpga::core::dff::DFF;
73
74use crate::{IV, K, ShaCoreOut, ShaIn, ShaOut, Stage, round_kernel};
75
76/// The round constants padded to 128 entries, so a 7-bit round counter can
77/// index them (entries 80–127 are zero and never used).
78pub const K128: [b64; 128] = {
79    let mut k = [b64(0); 128];
80    let mut i = 0;
81    while i < 81 {
82        k[i] = K[i];
83        i += 1;
84    }
85    k
86};
87
88/// Cycles from accepting a block (into the skid buffer) to presenting its
89/// result when the ring has room: the buffer, 80 rounds and the output
90/// register. The same as the full pipeline's [`LATENCY`](crate::LATENCY).
91pub const FOLDED_LATENCY: usize = 82;
92
93/// Estimated LUTs of a core with a ring of `stages` round stages: 1,020 per
94/// stage (round, schedule step, constant ROM, counter) plus 3,450 for the
95/// skid buffer, the stage-0 mux and the final addition. Fitted to Yosys
96/// `synth_xilinx -family xcup` of the 2-, 5- and 10-stage cores (5,491,
97/// 8,550 and 13,654 LUTs). An **estimate**: Yosys is not Vivado, and
98/// nothing here is placed or timed.
99#[must_use]
100pub const fn folded_lut(stages: u32) -> u32 {
101    1_020 * stages + 3_450
102}
103
104/// A block's state between stages, with its round counter.
105#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
106pub struct FoldStage {
107    /// The block's state (valid, tag, chain, working variables, window and
108    /// the look-ahead `p`).
109    pub stage: Stage,
110    /// The round this state performs next (0–79), or 80 when done.
111    pub t: b7,
112}
113
114/// One registered ring stage: performs round `t` of whatever block arrives.
115///
116/// Only the valid bit is reset. The 2,136-bit payload is a
117/// [`DFF::resetless`] register: it needs no reset (an invalid stage's data
118/// is never used), and keeping it off the reset net is what lets the tools
119/// meet 205 MHz (DSig's build v5 failed timing with every datapath register
120/// on the reset).
121#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
122pub struct FoldRound {
123    /// A block occupies the stage (reset to empty).
124    valid: DFF<bool>,
125    /// The block's state (no reset).
126    data: DFF<FoldStage>,
127}
128
129impl Default for FoldRound {
130    fn default() -> Self {
131        Self {
132            valid: DFF::new(false),
133            data: DFF::resetless(),
134        }
135    }
136}
137
138impl SynchronousIO for FoldRound {
139    type I = FoldStage;
140    type O = FoldStage;
141    type Kernel = fold_round_kernel;
142}
143
144/// Round `t` with `K_{t+1}` from the ROM for the next round's look-ahead.
145#[kernel]
146pub fn fold_round_kernel(_cr: ClockReset, input: FoldStage, q: FoldRoundQ) -> (FoldStage, FoldRoundD) {
147    let k_next = K128[input.t + b7(1)];
148    let mut out = q.data;
149    out.stage.valid = q.valid;
150    (
151        out,
152        FoldRoundD {
153            valid: input.stage.valid,
154            data: FoldStage {
155                stage: round_kernel(input.stage, k_next),
156                t: input.t + b7(1),
157            },
158        },
159    )
160}
161
162/// One ring segment: `$first` takes the segment's input, every later stage
163/// the previous one's output, and the segment presents its last stage. Each
164/// child is written as one expression (no array element assignments, which
165/// RHDL lowers to whole-array copies). Segments of round stages hold at most
166/// five: a kernel's child inputs become one Verilog literal (5 × 2,136 bits),
167/// and Icarus cannot scan a token over 16 KB, so larger rings are built from
168/// segments of segments.
169macro_rules! segment {
170    ($(#[$meta:meta])* $name:ident, $q:ident, $d:ident, $kernel:ident, $child:ty,
171     [$first:ident $(, $field:ident)*], [$($prev:ident),*], $last:ident) => {
172        $(#[$meta])*
173        #[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
174        pub struct $name {
175            $first: $child,
176            $($field: $child,)*
177        }
178
179        impl SynchronousIO for $name {
180            type I = FoldStage;
181            type O = FoldStage;
182            type Kernel = $kernel;
183        }
184
185        #[doc = concat!("Chain the stages of [`", stringify!($name), "`] in series.")]
186        #[kernel]
187        pub fn $kernel(_cr: ClockReset, input: FoldStage, q: $q) -> (FoldStage, $d) {
188            (q.$last, $d { $first: input, $($field: q.$prev,)* })
189        }
190    };
191}
192
193segment!(
194    /// Two ring stages.
195    FoldSeg2, FoldSeg2Q, FoldSeg2D, fold_seg2_kernel, FoldRound, [r0, r1], [r0], r1
196);
197segment!(
198    /// Four ring stages.
199    FoldSeg4, FoldSeg4Q, FoldSeg4D, fold_seg4_kernel, FoldRound, [r0, r1, r2, r3], [r0, r1, r2], r3
200);
201segment!(
202    /// Five ring stages.
203    FoldSeg5, FoldSeg5Q, FoldSeg5D, fold_seg5_kernel, FoldRound, [r0, r1, r2, r3, r4], [r0, r1, r2, r3], r4
204);
205segment!(
206    /// Eight ring stages (two four-stage segments).
207    FoldSeg8, FoldSeg8Q, FoldSeg8D, fold_seg8_kernel, FoldSeg4, [a, b], [a], b
208);
209segment!(
210    /// Ten ring stages (two five-stage segments).
211    FoldSeg10, FoldSeg10Q, FoldSeg10D, fold_seg10_kernel, FoldSeg5, [a, b], [a], b
212);
213segment!(
214    /// Sixteen ring stages (two eight-stage segments).
215    FoldSeg16, FoldSeg16Q, FoldSeg16D, fold_seg16_kernel, FoldSeg8, [a, b], [a], b
216);
217segment!(
218    /// Twenty ring stages (two ten-stage segments).
219    FoldSeg20, FoldSeg20Q, FoldSeg20D, fold_seg20_kernel, FoldSeg10, [a, b], [a], b
220);
221segment!(
222    /// Forty ring stages (four ten-stage segments).
223    FoldSeg40, FoldSeg40Q, FoldSeg40D, fold_seg40_kernel, FoldSeg10, [a, b, c, d], [a, b, c], d
224);
225
226/// What the ring control decides in one cycle.
227#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
228pub struct FoldControl {
229    /// The core's outputs (`ready` and the finished block).
230    pub out: ShaCoreOut,
231    /// The skid buffer's next value.
232    pub skid: FoldStage,
233    /// What enters stage 0.
234    pub first: FoldStage,
235    /// The output register's next value.
236    pub done: ShaOut,
237}
238
239/// The ring control, the same for every stage count. `leaving` is the state
240/// leaving the last stage this cycle: it recirculates into stage 0 while it
241/// has rounds left, and is added to its chaining value when it has done
242/// round 79. A free slot takes the buffered block; the buffer takes the
243/// client's block when it is empty or being placed (`ready`).
244#[kernel]
245pub fn fold_control(cr: ClockReset, input: ShaIn, skid: FoldStage, leaving: FoldStage, out: ShaOut) -> FoldControl {
246    let resetting = cr.reset.any();
247    let busy = leaving.stage.valid && leaving.t != b7(80);
248    let finished = leaving.stage.valid && leaving.t == b7(80);
249    // Stage 0 takes the recirculating block, else the buffered one.
250    let admit = skid.stage.valid && !busy;
251    let mut first = skid;
252    if busy {
253        first = leaving;
254    }
255    first.stage.valid = (busy || admit) && !resetting;
256    // The buffer keeps a block it could not place; otherwise it takes the
257    // client's (if any).
258    let keep = skid.stage.valid && !admit;
259    let chain = if input.use_iv { IV } else { input.chain };
260    let entering = FoldStage {
261        stage: Stage {
262            valid: input.valid && !resetting,
263            tag: input.tag,
264            chain,
265            work: chain,
266            w: input.block,
267            p: chain[7] + K[0] + input.block[0],
268        },
269        t: b7(0),
270    };
271    let mut next_skid = entering;
272    if keep {
273        next_skid = skid;
274    }
275    next_skid.stage.valid = next_skid.stage.valid && !resetting;
276    // The feed-forward addition for a finished block.
277    let last = leaving.stage;
278    let state = [
279        last.chain[0] + last.work[0],
280        last.chain[1] + last.work[1],
281        last.chain[2] + last.work[2],
282        last.chain[3] + last.work[3],
283        last.chain[4] + last.work[4],
284        last.chain[5] + last.work[5],
285        last.chain[6] + last.work[6],
286        last.chain[7] + last.work[7],
287    ];
288    FoldControl {
289        out: ShaCoreOut {
290            ready: !keep && !resetting,
291            done: out,
292        },
293        skid: next_skid,
294        first,
295        done: ShaOut {
296            valid: finished && !resetting,
297            tag: last.tag,
298            state,
299        },
300    }
301}
302
303/// A folded core: the skid buffer, one ring segment and the output register.
304macro_rules! folded_core {
305    ($(#[$meta:meta])* $name:ident, $q:ident, $d:ident, $kernel:ident, $seg:ty, $stages:expr) => {
306        $(#[$meta])*
307        #[derive(Clone, Debug, Synchronous, SynchronousDQ)]
308        pub struct $name {
309            // Valid bits reset; payloads do not (see `FoldRound`).
310            skid_valid: DFF<bool>,
311            skid: DFF<FoldStage>,
312            ring: $seg,
313            out_valid: DFF<bool>,
314            out: DFF<ShaOut>,
315        }
316
317        impl $name {
318            /// Ring stages: the core finishes this many blocks every 80
319            /// cycles.
320            pub const STAGES: u32 = $stages;
321        }
322
323        impl Default for $name {
324            fn default() -> Self {
325                Self {
326                    skid_valid: DFF::new(false),
327                    skid: DFF::resetless(),
328                    ring: <$seg>::default(),
329                    out_valid: DFF::new(false),
330                    out: DFF::resetless(),
331                }
332            }
333        }
334
335        impl SynchronousIO for $name {
336            type I = ShaIn;
337            type O = ShaCoreOut;
338            type Kernel = $kernel;
339        }
340
341        #[doc = concat!("The ring control around [`", stringify!($name), "`]'s segment.")]
342        #[kernel]
343        pub fn $kernel(cr: ClockReset, input: ShaIn, q: $q) -> (ShaCoreOut, $d) {
344            let mut skid = q.skid;
345            skid.stage.valid = q.skid_valid;
346            let mut out = q.out;
347            out.valid = q.out_valid;
348            let c = fold_control(cr, input, skid, q.ring, out);
349            (
350                c.out,
351                $d {
352                    skid_valid: c.skid.stage.valid,
353                    skid: c.skid,
354                    ring: c.first,
355                    out_valid: c.done.valid,
356                    out: c.done,
357                },
358            )
359        }
360    };
361}
362
363folded_core!(
364    /// 2 stages: 1/40 block per clock.
365    Sha512Folded2, Sha512Folded2Q, Sha512Folded2D, sha512_folded2_kernel, FoldSeg2, 2
366);
367folded_core!(
368    /// 4 stages: 1/20 block per clock.
369    Sha512Folded4, Sha512Folded4Q, Sha512Folded4D, sha512_folded4_kernel, FoldSeg4, 4
370);
371folded_core!(
372    /// 5 stages: 1/16 block per clock.
373    Sha512Folded5, Sha512Folded5Q, Sha512Folded5D, sha512_folded5_kernel, FoldSeg5, 5
374);
375folded_core!(
376    /// 8 stages: 1/10 block per clock.
377    Sha512Folded8, Sha512Folded8Q, Sha512Folded8D, sha512_folded8_kernel, FoldSeg8, 8
378);
379folded_core!(
380    /// 10 stages: 1/8 block per clock.
381    Sha512Folded10, Sha512Folded10Q, Sha512Folded10D, sha512_folded10_kernel, FoldSeg10, 10
382);
383folded_core!(
384    /// 16 stages: 1/5 block per clock.
385    Sha512Folded16, Sha512Folded16Q, Sha512Folded16D, sha512_folded16_kernel, FoldSeg16, 16
386);
387folded_core!(
388    /// 20 stages: 1/4 block per clock.
389    Sha512Folded20, Sha512Folded20Q, Sha512Folded20D, sha512_folded20_kernel, FoldSeg20, 20
390);
391folded_core!(
392    /// 40 stages: 1/2 block per clock.
393    Sha512Folded40, Sha512Folded40Q, Sha512Folded40D, sha512_folded40_kernel, FoldSeg40, 40
394);
395
396/// Ring stage counts that have a core, smallest first.
397pub const FOLDS: [u32; 8] = [2, 4, 5, 8, 10, 16, 20, 40];