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];