rhdl_signatures/lib.rs
1//! # RHDL signatures on one U280: the contributor guide
2//!
3//! # Building for Wolverine's U280
4//!
5//! `cargo build` builds Rust tools and libraries. A card image requires a second
6//! pipeline: Rust/RHDL → generated RTL bundle → Vitis kernel package (`.xo`) →
7//! U280 link (`.xclbin`) → routed acceptance → a separately checked card run.
8//!
9//! Choose the crate's front-page **Building for Wolverine's U280** section:
10//!
11//! | Target | Crate documentation | Build role |
12//! |---|---|---|
13//! | Ed25519 card | [Ed25519](../rhdl_ed25519_scalable_core/index.html) | `export_card`, then `wolverine-card.sh` |
14//! | DSig card | [DSig](../rhdl_dsig_scalable_core/index.html) | `dsig_engine_export`, then `wolverine-engine.sh` |
15//! | SHA-512 | [SHA-512](../rhdl_sha512_pipelined/index.html) | Component included in an enclosing kernel |
16//! | Planner | [Planner](../rhdl_fpga_planner/index.html) | Host-side budget selection |
17//! | Earlier Ed25519 core | [Fast core](../rhdl_ed25519_fast_core/index.html) | Component; use the scalable crate for the card flow |
18//! | Full RHDL uBFT | [uBFT](../rhdl_ubft/index.html) | Integration and hardware acceptance remain open |
19//!
20//! Run commands from the workspace root. Before compilation or RTL generation,
21//! record ownership in `PROJECT_COORDINATION.md` and
22//! `/home/sahasta/Development/FPGA_COORDINATION.md`, and use the applicable
23//! nonblocking `local-heavy.lock`; `crates/ed25519/tools/bounded.sh` wraps local
24//! Cargo commands with that lock and resource limits. Reserve Wolverine builds
25//! in both ledgers too, then use `crates/dsig/tools/with-u280-lock.sh` to hold
26//! `u280.lock`. Do not bypass a busy lock.
27//!
28//! The card build uses Vivado/Vitis 2023.2, the installed U280 `.xpfm`
29//! platform and XRT. Replace the absolute example paths below with real paths
30//! on Wolverine; the bundle must be copied there with its `BUNDLE.txt` intact,
31//! and the output directory must be new. Keep the repository checkout available
32//! on Wolverine because the scripts use its Tcl and host sources. Building an
33//! image does not program the card or establish measured performance.
34//!
35//! Welcome. This guide is written to be read from top to bottom, like a
36//! story: why the project exists, the two questions it answers, the pieces
37//! that answer them, how we decide what to build, where things stand, and
38//! what you can do on your first day. Every section says *why*, not just
39//! *what*, because the reasoning is the part you will need when you change
40//! something we did not foresee.
41//!
42//! **In numbers.**
43//! - Ed25519 signs at 4.41M/s in a core that meets 205 MHz (148,691 LUT,
44//! routed).
45//! - The 200 % plan signs at 6.42M/s, and its core meets 205 MHz too
46//! (254,817 LUT, +0.050 ns).
47//! - Known-key verification runs at 1.52M/s in 150,200 LUT.
48//! - DSig signs at 12.04M/s on the card (build v13, 388,542 LUT).
49//! - The Tuner predicts a routed core's LUTs within −1.0 … +2.3 % on cores
50//! it was not fitted to (80 Vivado routes, Oct 16–19 2026).
51//! - Every hardware result in simulation equals `ed25519-dalek` or the DSig
52//! software reference.
53//!
54//! # The story in five minutes
55//!
56//! **The problem.** A server CPU core signs a few tens of thousands of
57//! Ed25519 signatures per second. Some systems want tens of millions. That
58//! is a gap of about a thousand times.
59//!
60//! **The bet.** An FPGA can start thousands of small multiplications every
61//! clock. A signature is mostly multiplications. If we feed them to the chip
62//! as one steady stream and never let a multiplier sit idle, one card can
63//! close most of that gap. We also bet on one language, RHDL (Rust that
64//! describes hardware), so the same tests run from the algorithm down to the
65//! gates.
66//!
67//! **The first core was slow:** 895 cycles per signature, about 229,000 per
68//! second. It still lives inside DSig, where that is plenty. Each idea after
69//! it did one thing:
70//! - build the multiplier from DSP cascades, one 255-bit product per clock;
71//! - let 32 signatures take turns on it, so it never waits;
72//! - write the algorithms as Rust programs and compile them into the
73//! machine, so a new algorithm takes minutes;
74//! - do fewer multiplications per signature (better tables, one inversion
75//! shared by 32 points);
76//! - keep more signatures in flight, instead of adding hardware.
77//!
78//! Together they give 4.41M signatures/s in a core that meets 205 MHz, and
79//! 6.42M at twice the budget.
80//!
81//! **Then the chip got crowded.** Two papers share this one chip: Ed25519
82//! alone, and DSig, which signs with cheap hash-based keys and needs Ed25519
83//! only now and then. Deciding what to build by hand became guesswork, so we
84//! wrote the Tuner. You give it a budget, and it says what to build and what
85//! you will get. At first it guessed from synthesis, and its first full-chip
86//! plan did not even fit the chip's block RAM. So we routed 80 builds in
87//! Vivado and taught it the real costs. It now predicts a routed core's LUTs
88//! to within a few percent.
89//!
90//! **Then the clock fought back.** Every block met 205 MHz on its own, and no
91//! whole core did. We read each failing path and put one register in the
92//! right place. After twelve such fixes, seven whole cores meet 205 MHz.
93//! Every fix was simulated again, because a register can change behaviour.
94//! One did: simulation caught a lost point that no timing report would have
95//! shown.
96//!
97//! **Where it stands.** The largest verifier (24 machines, 842k LUTs) still
98//! misses timing by 2.2 ns. No whole chip (signer, verifier and SHA line
99//! together) has been routed, and no Ed25519 build has run on the card yet.
100//! That is the frontier.
101//!
102//! **Where you come in.** You do not need to know FPGAs to help. Half of the
103//! work is ordinary Rust: the Tuner, the host model, the tests. The other
104//! half is a puzzle with a scoreboard. Every idea gets a number (cycles,
105//! LUTs, nanoseconds of slack), and you know within a day whether it worked.
106//! [Good first contributions](#good-first-contributions) lists tasks that
107//! take from an afternoon to a few weeks.
108//!
109//! # Reading order
110//!
111//! The documentation is one story told across five crates. Build it all
112//! with `cargo docs` at the workspace root, then `cargo guide` opens this
113//! page. Read in this order; each part ends by pointing at the next.
114//!
115//! | # | Read | It answers |
116//! |---|---|---|
117//! | 1 | this guide | why the project exists, the two papers, who owns what, how we work |
118//! | 2 | [`rhdl_fpga_planner::tui`] (the Tuner guide) | what the Tuner shows, one plan start to finish, how it became accurate, and how far to trust it |
119//! | 2b | [`rhdl_fpga_planner::decisions`] | how the planner turns your budget and knobs into a build: every heuristic, why it exists, what it writes |
120//! | 3 | [`rhdl_ed25519_scalable_core`] | how Ed25519 got from one slow core to millions of signatures per second, chapter by chapter |
121//! | 4 | [`rhdl_dsig_scalable_core`] | DSig: hash-based one-time keys (the HBSS foreground) certified by Ed25519 (the background), with diagrams |
122//! | 5 | [`rhdl_sha512_pipelined`] | the shared SHA-512 line |
123//!
124//! # Contents
125//!
126//! 0. [The story in five minutes](#the-story-in-five-minutes)
127//! 1. [Why this exists](#1-why-this-exists)
128//! 2. [Two questions, one chip](#2-two-questions-one-chip)
129//! 3. [The cast: one crate per job](#3-the-cast-one-crate-per-job)
130//! 4. [How a plan is made](#4-how-a-plan-is-made)
131//! 5. [The planner on screen](#5-the-planner-on-screen)
132//! 6. [How we think](#6-how-we-think)
133//! 7. [The road so far](#7-the-road-so-far)
134//! 8. [What is proven and what is a model](#8-what-is-proven-and-what-is-a-model)
135//! 9. [Open problems](#9-open-problems)
136//! 10. [Your first day](#10-your-first-day), and
137//! [good first contributions](#good-first-contributions)
138//! 11. [Where to go next](#11-where-to-go-next)
139//! 12. [House rules](#12-house-rules)
140//! 13. [Glossary](#13-glossary)
141//!
142//! # 1. Why this exists
143//!
144//! Digital signatures are everywhere a system must prove who said what, and
145//! at data-center scale they are a bottleneck: a CPU core manages tens of
146//! thousands of Ed25519 signatures per second, and some systems need tens
147//! of millions. Two answers are interesting enough to write papers about:
148//!
149//! - **Make Ed25519 itself fast** by building it in hardware.
150//! - **Use a different scheme**: DSig signs with cheap hash-based one-time
151//! keys and needs Ed25519 only occasionally, to certify batches of those
152//! keys.
153//!
154//! Both are built here, for one board (an AMD Alveo U280) at one clock
155//! (**205 MHz**), so they can be compared fairly on the same silicon and the
156//! same budget. Everything is written in **RHDL**, Rust that describes
157//! hardware. Verilog is only ever an *output* handed to Vivado; we never
158//! write it, edit it or import it. That keeps one language, one test
159//! harness, and one source of truth from the algorithm down to the gates.
160//!
161//! # 2. Two questions, one chip
162//!
163#![doc = concat!("<div style=\"overflow-x:auto\">", include_str!("../docs/images/two-papers.svg"), "</div>")]
164//!
165//! The budget is the same for both papers: **485,000 LUTs and 4,520 DSPs**,
166//! each adjustable from 0 to 200 % to ask "what if we had more, or less?".
167//! A **SHA-512 line** takes its allowance out of that budget. By default the
168//! planner sizes it to what the engines hash (*auto*), so it never limits
169//! the chip and holds no LUTs it does not need; it is built from full
170//! pipelines and folded SHA-512 cores, in steps of 1/80 block per clock.
171//! Set by hand, it is 65,000 LUTs and 20 DSPs by default (so the engines
172//! keep the 420,000 and 4,500 they always had). The **LUT split** says where the LUTs go: SHA | DSig |
173//! Ed25519 in the DSig paper, SHA | Ed25519 in the Ed25519 paper, each
174//! summing to 100 %. A bigger line costs the engines LUTs; a smaller one
175//! gives them LUTs. It can be shrunk to nothing.
176//!
177//! ## Signing and verifying are separate
178//!
179//! A chip can be built to sign a lot and verify little, or the opposite, so
180//! the planner never ties one to the other. The budget is split into a
181//! **sign side** and a **verify side** by two powers (percentages you set,
182//! or that the B key chooses), and each side reports its own delivered rate
183//! and latency. A side may need more than one module, and the planner
184//! divides the side's share between them automatically.
185//!
186//! ## The Ed25519 paper
187//!
188//! *How many Ed25519 signatures and how many verifications per second fit
189//! in the budget?* Each side has one module:
190//!
191//! ```text
192//! signatures/s = Ed25519 sign capacity
193//! verifications/s = Ed25519 verify capacity
194//! both scaled down together if 3 x signatures + 2 x verifications > SHA supply
195//! ```
196//!
197//! *Why 3 and 2:* each Ed25519 signature hashes three SHA-512 blocks (the
198//! nonce, and the two-block challenge `H(R || A || M)`); each verification
199//! hashes two.
200//!
201//! Verification here is **known-key strict** verification. *Why known
202//! keys:* the expensive part of verifying is multiplying the signer's
203//! public key by a scalar. If keys are known in advance (true for DSig's
204//! verifiers, and for most services that verify a fixed set of signers),
205//! each key gets precomputed tables once, and verification becomes almost
206//! as cheap as signing (775 machine instructions instead of ~3,050).
207//!
208//! ## The DSig paper
209//!
210//! *How many DSig signatures and verifications per second fit, and what does
211//! the Ed25519 part of DSig cost?* DSig signs with hash-based one-time keys
212//! (W-OTS+, Winternitz `w`). It needs one Ed25519 signature per **batch of
213//! `K` keys** (128 by default), over the batch's Merkle root. A verifier
214//! needs one Ed25519 verification per batch it has not seen. After that it
215//! holds the verified root and skips Ed25519 for the rest of the batch.
216//!
217//! How often that happens depends on traffic, so the planner asks for a
218//! **best-case expectation** `b`, from 0 to 100 %: how often a verification
219//! finds its batch already verified. At best, every signature after a
220//! batch's first is a hit:
221//!
222//! ```text
223//! h = (b / 100) x (K - 1)/K (cache-hit fraction)
224//! signatures/s = min( DSig sign capacity, K x Ed25519 sign capacity ) (chip's signer certifies)
225//! verifications/s = min( DSig verify capacity, Ed25519 verify capacity / (1 - h) )
226//! both scaled down together if the SHA-512 line cannot supply everyone's blocks
227//! ```
228//!
229//! **What this means:**
230//! - At `b = 100 %`, Ed25519 needs only about `1/K` of DSig's rate on each
231//! side, so it shrinks to a corner of the chip.
232//! - At `b = 0`, every DSig verification needs a full Ed25519 verification,
233//! and on the verify side Ed25519 becomes the thing to build.
234//! - The planner moves each side's area between DSig and Ed25519
235//! accordingly.
236//!
237//! **Three splits, each summing to 100 %.** The DSig paper's budget is
238//! divided twice before any module sees it:
239//!
240//! ```text
241//! budget = HBSS foreground (100 - B %) + EdDSA background (B %)
242//! foreground = DSig sign (s %) + DSig verify (100 - s %) <- sign/verify powers
243//! background = Ed25519 sign (e %) + Ed25519 verify (100 - e %)
244//! ```
245//!
246//! `B` and `e` can be set by hand or left on auto. On auto the planner
247//! picks the background that delivers the most and shows it as **needed**:
248//! the share of EdDSA this DSig chip actually needs at this budget, best
249//! case and batch size (about 8.5 % at the defaults). Too little starves the
250//! certificates or the cache misses; too much takes area from the HBSS.
251//!
252//! **Who signs the certificates.** By default the chip's Ed25519 sign
253//! module does, sized for `1/K` of DSig's rate. The DSig kernel built today
254//! carries its own small Ed25519 signer instead; choose "Ed25519 certs:
255//! inside DSig" to plan that. DSig is then capped at `K x 205 MHz / 895`
256//! (29M/s), and the chip's Ed25519 sign module is not needed (its need
257//! drops to 0), so the signer is never counted twice.
258//!
259//! **The scheme knobs are shared.** HBSS (W-OTS+; HORS is offered but not
260//! implemented, so it plans nothing and says so), chain hash, SHA lanes,
261//! `w` and `K` must be the same for the signer and the verifier, so the TUI
262//! lists them once, under DSig sign, and sets both.
263//!
264//! **The SHA-512 line in this paper.** DSig's chain hash is a knob
265//! (Haraka, BLAKE3 or SHA-512):
266//! - With Haraka or BLAKE3, or SHA-512 on DSig's own lanes, DSig takes
267//! nothing from the line, and the whole line serves Ed25519.
268//! - With SHA-512 on the shared line, DSig draws its chain steps from it
269//! too (204 blocks per signature at `w = 4`). The line then becomes the
270//! limit quickly: about 1M DSig signatures/s per block-per-clock of line.
271//!
272//! **The B key.** The DSig paper does not prescribe how many verifications
273//! follow each signature, so the **mix** (verifications per signature, 1 by
274//! default) is the target of the B key. B picks the sign/verify powers that
275//! maximize `min(signatures, verifications / mix)`: that is what "100 %"
276//! means. The powers can also be set by hand.
277//!
278//! # 3. The cast: one crate per job
279//!
280//! | Crate | Directory | Its job | Owner |
281//! |---|---|---|---|
282//! | `rhdl_ed25519_scalable_core` | `crates/ed25519` | Ed25519 signing and known-key strict verification | Ed25519 |
283//! | `rhdl_dsig_scalable_core` | `crates/dsig` | The DSig signer: slices, chain lanes, BLAKE3, Merkle trees, certificates | DSig |
284//! | `rhdl_sha512_pipelined` | `crates/sha512` | The SHA-512 line: a fully pipelined compression unit and its variants | DSig |
285//! | `rhdl_fpga_planner` | `crates/planner` | The planning model and the one TUI. Pure Rust, no hardware | Ed25519 |
286//! | `rhdl_signatures` | the repository root (`src/`) | This guide, the two papers' chips, the `tuner` binary; pulls in the others by feature | Ed25519 |
287//! | (vendored) | `vendor/rhdl` | RHDL itself, upstream `c99d5cc` plus our patches, listed in `vendor/rhdl/PROVENANCE.md` | shared |
288//!
289//! **Why one crate per engine:** each engine is complete on its own, with
290//! its own tests, exports and docs, and can be built and verified without
291//! the others.
292//!
293//! **Why one workspace:** a chip combines engines, and Rust only lets them
294//! share hardware types if they build against the very same RHDL. One
295//! workspace, one vendored RHDL, and that problem disappears.
296//!
297//! ## Features: build only what you use
298//!
299//! The root package (this crate) is the one place everything meets, and
300//! Cargo features decide which engines it pulls in. Each engine stays its
301//! own crate, so a feature that is off means that crate is not compiled at
302//! all, not merely hidden.
303//!
304//! | Feature | Default | Pulls in | Gives you |
305//! |---|---|---|---|
306//! | `ed25519` | yes | `rhdl_ed25519_scalable_core` (BSD-3-Clause) | the Ed25519 paper; re-exported as [`ed25519`] |
307//! | `sha512` | via `dsig` | `rhdl_sha512_pipelined` (AGPL-3.0-or-later) | the real SHA-512 line component; re-exported as [`sha512`] |
308//! | `dsig` | yes | `rhdl_dsig_scalable_core` (AGPL-3.0-or-later), and turns on `ed25519` and `sha512` | the DSig paper; re-exported as [`dsig`] |
309//! | `tuner` | yes | the planner's terminal UI (ratatui) | the `tuner` binary (also needs `ed25519`) |
310//!
311//! **Why `dsig` turns on `ed25519`:** DSig certifies its one-time keys with
312//! Ed25519 and verifies those certificates with Ed25519, so the DSig paper
313//! always has Ed25519 modules in it. The reverse is not true: the Ed25519
314//! paper needs nothing from DSig.
315//!
316//! **Why `sha512` is separate:** the Ed25519 paper hashes through the same
317//! SHA-512 line, but planning it needs only the line's cost model. Without
318//! `sha512`, the planner uses the Ed25519 crate's copy of that model
319//! ([`Sha512Estimate`](rhdl_ed25519_scalable_core::planner::Sha512Estimate)),
320//! so an Ed25519-only build links no AGPL code.
321//!
322//! **The common builds:**
323//!
324//! | You want | Run |
325//! |---|---|
326//! | both papers (the default) | `cargo tuner` |
327//! | the Ed25519 paper alone | `cargo tuner-ed25519` (`--no-default-features --features ed25519,tuner`) |
328//! | the engines as a library, no TUI | `cargo build --no-default-features --features dsig` |
329//! | everything's docs | `cargo docs` |
330//!
331//! The TUI offers exactly the papers the build has: without `dsig` the
332//! paper control and P stay on the Ed25519 paper, and the headline shows
333//! one paper ([`paper`] lists which slots each feature adds).
334//!
335//! DSig's own hardware shape (slice count, chain hash) is not a feature of
336//! this crate. It is fixed per bitstream and has to fail closed when host
337//! and kernel disagree, so the DSig crate reads it from build variables
338//! (`DSIG_SLICES=2|4|8`, default 8; `DSIG_CHAIN=haraka|blake3|sha512`,
339//! default `haraka`) and rejects any other value. Features stay additive,
340//! so `--all-features` builds too.
341//!
342//! Each engine's front page continues the story in detail. Start with
343//! [`rhdl_ed25519_scalable_core`], which explains, chapter by chapter, how
344//! Ed25519 went from 229,000 signatures per second to 6.42M in a core that
345//! meets 205 MHz (and 7.76M in simulation, not yet routed).
346//!
347//! # 4. How a plan is made
348//!
349//! The planner answers one question: *given a budget, how should the chip
350//! be divided, and what does it deliver?* It does that in four steps, all in
351//! [`rhdl_fpga_planner`]:
352//!
353//! 1. **Guaranteed shares.** The sign and verify powers divide the budget's
354//! LUTs, DSPs, BRAM and URAM between the two sides. Within a side the
355//! planner divides the side's share between its modules. The SHA-512
356//! line gets its own allowance instead.
357//! 2. **Best build per share.** Each module (a
358//! [`Component`](rhdl_fpga_planner::Component)) knows its own building
359//! blocks and their measured or estimated costs. Given a share, it returns
360//! the fastest build that fits: its **capacity** (operations per second if
361//! never starved), resources, latency, and the SHA-512 blocks it needs.
362//! 3. **Delivered rates.** Capacities do not add up to a result; on each
363//! side the slowest link sets the pace. Each paper says how many
364//! operations of each kind one delivered operation needs (the formulas
365//! above, in [`Mode::need`](rhdl_fpga_planner::Mode::need)). A side
366//! delivers the smallest *capacity ÷ need*, and both sides together must
367//! fit the SHA-512 supply.
368//! 4. **Lending, in real units.** A percentage split hands every module
369//! the same fraction of *every* resource, but modules are lopsided: DSig
370//! signing uses almost no DSPs, an Ed25519 verifier runs out of URAM
371//! long before its LUTs. So after the first plans the planner counts
372//! what each module left unused (LUTs, DSPs, BRAM36, URAM, as counts)
373//! and offers it to the others. It keeps the re-plan that raises the
374//! delivered rates most, and repeats until nobody gains
375//! ([`plan_chip`](rhdl_fpga_planner::plan_chip)). The powers still say
376//! who is guaranteed what; nothing sits idle that someone could use.
377//! 5. **Balance.** Within a side, the planner splits the side's share between
378//! its modules to deliver the most. Between sides, the B key picks the
379//! powers for the mix.
380//!
381//! **Why rates keep growing with the budget.** Builds come in steps, and a
382//! step that cannot be taken looks like a wall. Three walls were removed:
383//! the uniform percentage split (fixed by lending); one Ed25519 core per
384//! module, whose credit pool stops it at 8.82M signatures/s (modules are
385//! now 1–4 identical cores); and DSig slice counts of only 1, 2, 4 and 8
386//! (now any count 1–16, with 2, 4 and 8 marked built). The chip test
387//! `rates_scale_with_luts_and_dsps` prints the LUT % x DSP % table and
388//! checks that more budget never delivers less.
389//!
390//! The whole decision path, heuristic by heuristic, with the reason for
391//! each and the build flags it produces, is told in
392//! [`rhdl_fpga_planner::decisions`]. Read it before changing the planner.
393//!
394//! **Why model it this way:** it keeps each module honest about its own
395//! costs (the people who build a module know them best) while the
396//! chip-level question stays a few lines of arithmetic anyone can check.
397//!
398//! # 5. The planner on screen
399//!
400//! The Tuner has its own guide, [`rhdl_fpga_planner::tui`]: one plan
401//! followed from budget to Verilog, every cost constant with its source,
402//! and the measured accuracy. This section is the short version.
403//!
404//! `cargo tuner` (short for `cargo run --release -p rhdl_signatures
405//! --bin tuner`) opens one screen for both papers (`cargo tuner-ed25519`
406//! for the Ed25519 paper alone); P switches paper, and the
407//! top line shows both papers' rates. On the left are the controls:
408//!
409//! - **Paper**: which chip.
410//! - **LUT budget, DSP budget**: 0–200 % of 485k LUTs and 4,520 DSPs.
411//! - **LUT split**: the SHA-512 line's part of the LUTs (Left/Right moves it
412//! by 0.5 %, from 0 up to 432k LUTs), then DSig and Ed25519's parts of the
413//! rest.
414//! - **BRAM budget, URAM budget**: 0–100 % of the block RAM of the U280
415//! shell's dynamic region (1,776 BRAM36; the device has 2,016, but DSig's
416//! build v4 showed only the dynamic region's 3,552 BRAM18 sites are
417//! ours), and of the device's UltraRAM. Less URAM moves memories to block
418//! RAM; less of both moves machine banks to LUT RAM, using spare LUTs.
419//! - **Sign power, verify power**: each side's share of the budget.
420//! - **Mix**: the verifications per signature the B key balances for.
421//! - **Best case** (DSig paper only): the expectation `b`. The screen also
422//! shows the resulting cache-hit fraction.
423//! - Each module's **guaranteed share**, as a percentage and in real
424//! counts (LUT, DSP, BRAM, URAM). A card that says "uses resources other
425//! modules left free" was planned with borrowed resources.
426//! - Each Ed25519 module's **shape**: "block ratio" auto, or manual with
427//! machine : SHA ring : scalar ring weights; and **batch compress** auto /
428//! on / off. Off builds have no compress machine; each field machine
429//! inverts and encodes its own point (a longer program; a model).
430//! - Each module's **knobs**: build options such as the
431//! memory layout, its own SHA rings or the shared line, the verify mode,
432//! DSig's HBSS, chain hash, `w`, `K` and certificate source, the S-box
433//! placement, and the SHA line's LUT and DSP allowance. A knob marked
434//! "(sets all 2)" is shared by DSig sign and verify.
435//!
436//! On the right is what the chip delivers:
437//!
438//! - **Delivered signatures and verifications**, each side on its own line
439//! with its bottleneck and latency. Below them is every role's delivered
440//! rate next to its capacity: Ed25519 sign and verify, and in the DSig
441//! paper also DSig sign and verify.
442//! - **Latency**: *idle* (one request alone) and *loaded* (at full capacity:
443//! requests in flight ÷ throughput, Little's law). A module on the shared
444//! SHA-512 line adds one pass through the line per block it hashes.
445//! - **Every module's resources** (LUT, DSP, BRAM, URAM), each marked
446//! *measured* (Vivado) or *model*, its limiting resource, and **next**:
447//! how many more LUTs the next faster build needs and what it delivers.
448//! Builds come in whole blocks (a field machine is ~11k LUTs, a DSig
449//! slice ~110–130k), so a rate that stays flat while you add LUTs is
450//! waiting for that step; "next" tells you how far it is.
451//! - **The bottleneck**, and the SHA-512 blocks used against those supplied.
452//! Shrink the SHA line and its throughput falls. Once it becomes the
453//! limit, it and every module hashing on it are marked *SHA-limited*, and
454//! every delivered rate falls with it.
455//!
456//! Two keys do the work:
457//!
458//! - **B** balances: it picks the sign/verify powers for the mix (the
459//! "100 %" split).
460//! - **W** writes `build-config.toml`, the compile-time settings (profile
461//! sizes, features) of every module in the current plan, to feed the
462//! exports.
463//!
464//! # 6. How we think
465//!
466//! Five ideas carry almost all of the performance. Use them as a lens for
467//! any change.
468//!
469//! 1. **Count the work, then the hardware.** Every operation is a known
470//! number of field multiplications or hash blocks. A signature is ~392
471//! machine instructions of curve arithmetic, three SHA-512 blocks and two
472//! scalar reductions; a known-key verification is 775 instructions and
473//! two blocks. Throughput is *hardware ÷ work*, so first reduce the work
474//! (better algorithms), then add hardware.
475//! 2. **Keep every expensive unit busy every clock.** A 255-bit multiplier
476//! is 225 DSPs, and it must start a product every clock to earn its
477//! place. Barrel threading (32 independent jobs rotating through one
478//! pipeline) hides its long latency. Credits make sure nothing waits on
479//! something that waits on it: deadlock freedom is designed, not hoped
480//! for.
481//! 3. **Find the binding resource.** LUTs, DSPs, BRAM and URAM run out at
482//! different sizes, and the planner says which one stops the next larger
483//! build. On a full chip BRAM ran out first, so we moved register banks
484//! into UltraRAM and constants into LUT ROM.
485//! 4. **Specialize.** A module that does one job well beats a general one.
486//! The verify machine scans two tables instead of doing a general
487//! multiplication; the compress machine only inverts; the small signer
488//! fuses everything for DSig's modest rate.
489//! 5. **Measure before believing.** Every claim climbs the same ladder.
490//! - The host model equals the reference library.
491//! - The RHDL equals the host model, bit for bit.
492//! - Vivado maps each block.
493//! - The planner is recalibrated from the maps.
494//!
495//! A number is not done until it is measured, and the docs say which
496//! rung each number is on.
497//!
498//! When you start something new, follow the same order:
499//! 1. Write down the work count.
500//! 2. Find which resource binds.
501//! 3. Prototype on the host model (seconds).
502//! 4. Then the RHDL (minutes).
503//! 5. Then Vivado (hours).
504//!
505//! # 7. The road so far
506//!
507//! | Step | Why it was needed | Effect | Where | Status |
508//! |---|---|---|---|---|
509//! | Multiplier as 15 DSP cascades | LUT multipliers are huge and slow | one 255-bit product per clock, 225 DSPs | ed25519 `fe17` | routed, +0.688 ns |
510//! | Barrel-threaded field machine | the multiplier's 22-clock latency would idle it | multiplier ~99 % busy | ed25519 `machine` | simulated |
511//! | Algorithms as compiled Rust programs | algorithms change daily | new algorithm in minutes, checked on the host | ed25519 `microcode`, `programs` | simulated |
512//! | Mixed radix-32/16 fixed-base tables | fewer instructions per signature | 492 → 392 instructions | ed25519 `programs` | simulated |
513//! | Batch inversion, 32 points | one inversion costs 265 multiplications | 13 per point | ed25519 `compress_stage` | simulated |
514//! | 2,048 credits, shorter hold | more hardware did not help (Little's law) | 44.1 → 26.43 cycles per signature | ed25519 `sign_core` | simulated |
515//! | Two-clock scalar reduction | the small core missed 205 MHz by 0.273 ns | +0.118 ns | ed25519 `scalar_ring` | routed |
516//! | Known-key verification | general verification costs ~4 signatures | 775 instructions per verification | ed25519 `verify_core` | simulated, equals `verify_strict` |
517//! | Shared SHA-512 port | eleven private SHA rings cost ~50k LUTs | modules drop their rings | ed25519 `sha_client` | simulated |
518//! | Memory layout: UltraRAM banks, LUT-ROM constants | BRAM ran out first | 72 → 24.5 BRAM36 per machine | ed25519 `machine` | routed, +0.848 ns |
519//! | Per-machine memory placement in the plan | a plan put every machine in UltraRAM and failed placement (2,646 BRAM36 > 2,016) | plans export `uram_machines`/`lut_machines`; only buildable layouts offered | planner, ed25519 `frontier` | routed |
520//! | Twelve timing fixes (limb-serial adders, registered arbiters, read-ahead) | whole cores missed 205 MHz by 0.6–5.8 ns | L100 signer +0.218, verifier +0.043; L200 signer +0.050 | ed25519 chapter 10 | routed; L200 verifier misses (−2.225) |
521//! | Planner costs fitted to routed cores | whole cores were 6.5–7.6 % over the sum of blocks | −1.0 … +2.3 % LUT out of sample | ed25519 `frontier` | measured |
522//! | Fully pipelined SHA-512 | everyone needs blocks; rings are slow | one block per clock | sha512 | simulated |
523//! | HDL-only export | exporting the large core needed 3.5 GB | 190 MB, same Verilog | `vendor/rhdl` patch | measured |
524//! | Fold constant bytes out of Haraka | 640 S-boxes per chain step | 494 live S-boxes | dsig `chain_step` | simulated, pinned by test |
525//! | Three chain lanes in series | a chain is 3 dependent steps | one whole chain per clock | dsig `slice_chain` | simulated |
526//! | 68-cycle modulo schedule | ~47 dependent BLAKE3 compressions per key | one key per 68 clocks per slice | dsig `slice_schedule`, `slice_factory` | simulated, bit-exact |
527//! | Pipelined certificates, 32 in flight | Ed25519 takes ~22,000 cycles per signature | one batch certificate per ~895 cycles | dsig `cert_unit` | simulated |
528//! | Replicated slices | one slice = 3.0M keys/s | 2/4/8 slices; 2 slices = 301,037 kernel LUT | dsig `slice`, `engine` | routed (2 slices) |
529//! | Key caches in UltraRAM, registered inputs | first link missed 205 MHz by 2.167 ns | post-placement WNS +0.004 ns; routed result pending | dsig `cache_bank` | in progress |
530//! | Three chain hashes | compare Haraka, BLAKE3, SHA-512 chains | build variable `DSIG_CHAIN=haraka|blake3|sha512` | dsig `chain_lane` | simulated (gates rerunning) |
531//!
532//! The DSig crate's front page tells these steps as a story; its
533//! "Where to go next" section is the place to start working on DSig.
534//!
535//! # 8. What is proven and what is a model
536//!
537//! - **Measured in Vivado** (2023.2, out of context, routed at 4.878 ns;
538//! `measure/results.tsv`):
539//! - the small Ed25519 signer: 29,055 LUTs, 248 DSPs, +0.306 ns;
540//! - every Ed25519 block, in all three memory layouts, on the RTL that
541//! meets timing;
542//! - whole Ed25519 cores that meet 205 MHz: signers 4/0/1/1, 8/0/2/1,
543//! 9/0/2/1 and 12/0/3/1, and verifier 6/0/1/1;
544//! - the 24-machine verifier misses: −2.225 ns on fix12 (−5.763 on fix9),
545//! with the failure spread over 207k endpoints.
546//! - **Measured on the card:** DSig signing, 12.04M/s (v13). No Ed25519
547//! build has run on the card.
548//! - **Synthesis only:** the large 15/11/3/1 signer, at 263,810 LUTs and
549//! 3,669 DSPs.
550//! - **Measured in simulation:**
551//! - the planned Ed25519 builds' throughput (`measure/native-results.tsv`),
552//! every result checked; and 15/11/3/1 at 26.43 cycles per signature
553//! (7.76M/s);
554//! - verification equal to `verify_strict` on valid signatures and every
555//! invalid class;
556//! - DSig keys, certificates and frames bit-exact against the software
557//! reference for all three chain hashes (leaf gates).
558//! - **Modeled:**
559//! - the controllers' and rings' LUTs, fitted to routed whole cores (the
560//! Tuner guide, section 5, tells how far they are trusted);
561//! - the SHA-512 line's throughput (its cores' LUTs are routed);
562//! - DSig verification;
563//! - DSig at 4 and 8 slices, and DSig sign latency;
564//! - most latencies.
565//!
566//! # 9. Open problems
567//!
568//! Ranked by how much they would change the answer:
569//!
570//! 1. **Close the 24-machine verifier** at 205 MHz, then **route a whole
571//! Ed25519 chip** (sign + verify + SHA line together).
572//! 2. **SHA-512 line above one block per clock in 65k LUTs + 20 DSPs.** The
573//! goal is ~1.75. One generic pipeline is about one block per clock at
574//! ~43k LUTs (estimate); two would need ~86k. Candidates: a fixed-length
575//! single-block variant, carry-save rounds, DSP adders. (DSig.)
576//! 3. **Measure latency.** Latencies are cycle models, except the small
577//! signer's single-request latency (22,240 cycles). The compress
578//! machine's batch wait dominates them. The verifier test now reports
579//! isolated and saturated latency (`ED25519_ISOLATED=1`).
580//! 4. **DSig verification hardware** is being built (verify units plus a
581//! 1/1/1/1 known-key verifier for certificate misses). Until it routes,
582//! its numbers are a model. (DSig.)
583//! 5. **A reduce-only scalar ring** for verification, and the 150-DSP
584//! multiplier re-routed on the timing-fixed RTL.
585//! 6. **Replay simulation vectors** against the exported Verilog.
586//! 7. **Strict verification for arbitrary keys** (variable-base), if a
587//! workload needs it.
588//!
589//! # 10. Your first day
590//!
591//! 1. **Read** [`rhdl_ed25519_scalable_core`]'s front page (the Ed25519
592//! story) and the DSig crate's.
593//! 2. **Run the planner:**
594//! `cargo tuner` (from the repository root).
595//! Switch papers, move the best case from 100 % to 0, shrink the SHA
596//! line, and watch where the area goes and what limits the chip.
597//! 3. **Run the cheap tests:** `cargo test -p rhdl_fpga_planner` (seconds).
598//! Then run the host model of the Ed25519 programs (see that crate's
599//! "How to work").
600//! 4. **Pick an open problem** from the list above, write down its work
601//! count and its binding resource, and say in the ledger what you are
602//! starting.
603//!
604//! The workspace builds with Rust **1.95** or newer. `rust-toolchain.toml`
605//! pins 1.96.0 for rustup users; the system `cargo` (1.95) works as well.
606//!
607//! | You want to | Run (from the repository root) | Time |
608//! |---|---|---|
609//! | Read the docs with diagrams | `cargo docs`, then `cargo guide` | 1 min |
610//! | Plan a chip | `cargo tuner` (or `cargo tuner-ed25519`) | 2 min first build |
611//! | Planner tests | `cargo test -p rhdl_fpga_planner` | 10 s |
612//! | Ed25519 hardware tests | `cargo test --profile sim -p rhdl_ed25519_scalable_core --lib` | ~4 min |
613//! | DSig hardware tests | see the DSig crate | |
614//! | Export Verilog | see each crate's export docs | minutes |
615//!
616//! ## Good first contributions
617//!
618//! Each task below is real, open, and checkable. Pick one, write it in the
619//! ledger, and ask in the coordination file if you are stuck.
620//!
621//! | Task | Size | You will learn | You are done when |
622//! |---|---|---|---|
623//! | **Show timing in the Tuner.** Mark each Ed25519 build "meets 205 MHz" or "misses by x ns", from `measure/results.tsv` | an afternoon; pure Rust | how plans map to built cores | a test checks the label of one known build |
624//! | **Remove a duplicate.** `compress_stage::encode_y` repeats `sign_core`'s point encoding | an hour | point encoding | the Ed25519 tests still pass |
625//! | **Measure a latency.** Run verifier 6/0/1/1 with `ED25519_ISOLATED=1` and put the result in the Tuner's latency model | a day | the verifier pipeline | `measure/native-results.tsv` has the row, and the Tuner shows the latency as *measured* |
626//! | **Replay vectors on the Verilog.** `crates/ed25519/tools/verilog_vectors_tb.v` exists but has never been run | a few days | the export path and a Verilog simulator | one small core's replay matches the native model |
627//! | **A fair output arbiter.** Machine outputs are taken lowest index first; make it round-robin | a few days | ready/valid handshakes | a test saturates one machine and bounds every other machine's wait |
628//! | **A reduce-only scalar ring** for verification | a few weeks | DSP pipelines | the ring routes at 205 MHz and the verifier uses fewer LUTs in the Tuner |
629//! | **Close the 24-machine verifier** | open-ended | timing closure on a big chip | it routes with positive slack |
630//!
631//! # 11. Where to go next
632//!
633//! Find the row for what you want to do. Each lists where to read, in order,
634//! and what to run first. Every crate's front page also ends with its own
635//! "Where to go next" for its internals.
636//!
637//! | You want to... | Go to | Then |
638//! |---|---|---|
639//! | **see the whole picture in numbers** | the `tuner` ([section 5](#5-the-planner-on-screen)) | switch papers, move best case from 100 % to 0, shrink the SHA line |
640//! | **understand Ed25519 in hardware** | [`rhdl_ed25519_scalable_core`] chapters 1–7 | its host-model tests (20 s) |
641//! | **understand Ed25519 verification** | [`rhdl_ed25519_scalable_core`] chapter 8, then its `verify_core` | its known-key verifier test |
642//! | **understand DSig** | [`rhdl_dsig_scalable_core`]'s front page (a 12-chapter story), then its "Where to go next" | its leaf tests |
643//! | **work on the SHA-512 line** | [`rhdl_sha512_pipelined`], then the Ed25519 `sha_client` (how modules use it) | its pipeline test against `sha2` |
644//! | **change how plans are made** | [section 4](#4-how-a-plan-is-made), [`rhdl_fpga_planner`] (`Mode::need`, `deliver`, `balance`) | `cargo test -p rhdl_fpga_planner` |
645//! | **add a module to the chip** | [`rhdl_fpga_planner::Component`], an existing implementation (`rhdl_ed25519_scalable_core::planner`), [`paper`] | add it to [`paper::slots`] and watch it in the `tuner` |
646//! | **change a module's costs** | the module's cost table (Ed25519: `frontier`; DSig and SHA-512: `planner`), marked measured or projected | rerun the `tuner`; a projected number should become measured |
647//! | **map something in Vivado** | the crate's `export` docs, then [house rules](#12-house-rules) on the U280 host | claim it in the ledger first |
648//! | **pick an open problem** | [section 9](#9-open-problems) | write down its work count and binding resource, then claim it |
649//!
650//! # 12. House rules
651//!
652//! - **RHDL only.** Hardware is written in RHDL. Generated Verilog is an
653//! output for Vivado; it is never edited or read back.
654//! - **One heavy job at a time.** The development host is shared, and a
655//! runaway RHDL lowering can exhaust its memory.
656//! - Claim the job in `~/Development/FPGA_COORDINATION.md` (append-only,
657//! newest first).
658//! - Run it through `tools/bounded.sh`, which caps memory and time and
659//! holds an exclusive lock.
660//! - Record the release when you are done.
661//! - **The U280 host (Wolverine)** is shared too; claim it in both ledgers.
662//! - **Docs are rustdoc.** Each module's `//!` docs explain what it does
663//! and why. Diagrams are SVG files pulled in with `include_str!`. When you
664//! change behavior, change the story too.
665//!
666//! # 13. Glossary
667//!
668//! | Term | Meaning |
669//! |---|---|
670//! | Balance | the power split that delivers the most; "100 %" |
671//! | Batch (DSig) | 128 one-time keys certified by one Ed25519 signature |
672//! | Best case `b` | how often a DSig verification finds its batch already verified |
673//! | Bundle, instruction | one step of a field machine: a multiply, an add, a table step |
674//! | Capacity | operations per second a module could do if never starved |
675//! | Delivered | operations per second the whole chip sustains |
676//! | Idle / loaded latency | time per request alone / at full capacity (Little's law) |
677//! | Known-key verification | verification for public keys whose tables were built in advance |
678//! | Mix | verifications per signature |
679//! | Power | a module's share of the budget |
680//! | SHA-512 line | the shared SHA-512 compression unit, on its own allowance |
681
682#![warn(missing_docs)]
683
684pub mod paper;
685
686/// The planner model and TUI (always built; pure Rust).
687pub use rhdl_fpga_planner as planner;
688
689/// The Ed25519 engine (feature `ed25519`).
690#[cfg(feature = "ed25519")]
691pub use rhdl_ed25519_scalable_core as ed25519;
692
693/// The SHA-512 line (feature `sha512`).
694#[cfg(feature = "sha512")]
695pub use rhdl_sha512_pipelined as sha512;
696
697/// The DSig engine (feature `dsig`).
698#[cfg(feature = "dsig")]
699pub use rhdl_dsig_scalable_core as dsig;