Expand description
§RHDL signatures on one U280: the contributor guide
§Building for Wolverine’s U280
cargo build builds Rust tools and libraries. A card image requires a second
pipeline: Rust/RHDL → generated RTL bundle → Vitis kernel package (.xo) →
U280 link (.xclbin) → routed acceptance → a separately checked card run.
Choose the crate’s front-page Building for Wolverine’s U280 section:
| Target | Crate documentation | Build role |
|---|---|---|
| Ed25519 card | Ed25519 | export_card, then wolverine-card.sh |
| DSig card | DSig | dsig_engine_export, then wolverine-engine.sh |
| SHA-512 | SHA-512 | Component included in an enclosing kernel |
| Planner | Planner | Host-side budget selection |
| Earlier Ed25519 core | Fast core | Component; use the scalable crate for the card flow |
| Full RHDL uBFT | uBFT | Integration and hardware acceptance remain open |
Run commands from the workspace root. Before compilation or RTL generation,
record ownership in PROJECT_COORDINATION.md and
/home/sahasta/Development/FPGA_COORDINATION.md, and use the applicable
nonblocking local-heavy.lock; crates/ed25519/tools/bounded.sh wraps local
Cargo commands with that lock and resource limits. Reserve Wolverine builds
in both ledgers too, then use crates/dsig/tools/with-u280-lock.sh to hold
u280.lock. Do not bypass a busy lock.
The card build uses Vivado/Vitis 2023.2, the installed U280 .xpfm
platform and XRT. Replace the absolute example paths below with real paths
on Wolverine; the bundle must be copied there with its BUNDLE.txt intact,
and the output directory must be new. Keep the repository checkout available
on Wolverine because the scripts use its Tcl and host sources. Building an
image does not program the card or establish measured performance.
Welcome. This guide is written to be read from top to bottom, like a story: why the project exists, the two questions it answers, the pieces that answer them, how we decide what to build, where things stand, and what you can do on your first day. Every section says why, not just what, because the reasoning is the part you will need when you change something we did not foresee.
In numbers.
- Ed25519 signs at 4.41M/s in a core that meets 205 MHz (148,691 LUT, routed).
- The 200 % plan signs at 6.42M/s, and its core meets 205 MHz too (254,817 LUT, +0.050 ns).
- Known-key verification runs at 1.52M/s in 150,200 LUT.
- DSig signs at 12.04M/s on the card (build v13, 388,542 LUT).
- The Tuner predicts a routed core’s LUTs within −1.0 … +2.3 % on cores it was not fitted to (80 Vivado routes, Oct 16–19 2026).
- Every hardware result in simulation equals
ed25519-dalekor the DSig software reference.
§The story in five minutes
The problem. A server CPU core signs a few tens of thousands of Ed25519 signatures per second. Some systems want tens of millions. That is a gap of about a thousand times.
The bet. An FPGA can start thousands of small multiplications every clock. A signature is mostly multiplications. If we feed them to the chip as one steady stream and never let a multiplier sit idle, one card can close most of that gap. We also bet on one language, RHDL (Rust that describes hardware), so the same tests run from the algorithm down to the gates.
The first core was slow: 895 cycles per signature, about 229,000 per second. It still lives inside DSig, where that is plenty. Each idea after it did one thing:
- build the multiplier from DSP cascades, one 255-bit product per clock;
- let 32 signatures take turns on it, so it never waits;
- write the algorithms as Rust programs and compile them into the machine, so a new algorithm takes minutes;
- do fewer multiplications per signature (better tables, one inversion shared by 32 points);
- keep more signatures in flight, instead of adding hardware.
Together they give 4.41M signatures/s in a core that meets 205 MHz, and 6.42M at twice the budget.
Then the chip got crowded. Two papers share this one chip: Ed25519 alone, and DSig, which signs with cheap hash-based keys and needs Ed25519 only now and then. Deciding what to build by hand became guesswork, so we wrote the Tuner. You give it a budget, and it says what to build and what you will get. At first it guessed from synthesis, and its first full-chip plan did not even fit the chip’s block RAM. So we routed 80 builds in Vivado and taught it the real costs. It now predicts a routed core’s LUTs to within a few percent.
Then the clock fought back. Every block met 205 MHz on its own, and no whole core did. We read each failing path and put one register in the right place. After twelve such fixes, seven whole cores meet 205 MHz. Every fix was simulated again, because a register can change behaviour. One did: simulation caught a lost point that no timing report would have shown.
Where it stands. The largest verifier (24 machines, 842k LUTs) still misses timing by 2.2 ns. No whole chip (signer, verifier and SHA line together) has been routed, and no Ed25519 build has run on the card yet. That is the frontier.
Where you come in. You do not need to know FPGAs to help. Half of the work is ordinary Rust: the Tuner, the host model, the tests. The other half is a puzzle with a scoreboard. Every idea gets a number (cycles, LUTs, nanoseconds of slack), and you know within a day whether it worked. Good first contributions lists tasks that take from an afternoon to a few weeks.
§Reading order
The documentation is one story told across five crates. Build it all
with cargo docs at the workspace root, then cargo guide opens this
page. Read in this order; each part ends by pointing at the next.
| # | Read | It answers |
|---|---|---|
| 1 | this guide | why the project exists, the two papers, who owns what, how we work |
| 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 |
| 2b | [rhdl_fpga_planner::decisions] | how the planner turns your budget and knobs into a build: every heuristic, why it exists, what it writes |
| 3 | [rhdl_ed25519_scalable_core] | how Ed25519 got from one slow core to millions of signatures per second, chapter by chapter |
| 4 | [rhdl_dsig_scalable_core] | DSig: hash-based one-time keys (the HBSS foreground) certified by Ed25519 (the background), with diagrams |
| 5 | [rhdl_sha512_pipelined] | the shared SHA-512 line |
§Contents
- The story in five minutes
- Why this exists
- Two questions, one chip
- The cast: one crate per job
- How a plan is made
- The planner on screen
- How we think
- The road so far
- What is proven and what is a model
- Open problems
- Your first day, and good first contributions
- Where to go next
- House rules
- Glossary
§1. Why this exists
Digital signatures are everywhere a system must prove who said what, and at data-center scale they are a bottleneck: a CPU core manages tens of thousands of Ed25519 signatures per second, and some systems need tens of millions. Two answers are interesting enough to write papers about:
- Make Ed25519 itself fast by building it in hardware.
- Use a different scheme: DSig signs with cheap hash-based one-time keys and needs Ed25519 only occasionally, to certify batches of those keys.
Both are built here, for one board (an AMD Alveo U280) at one clock (205 MHz), so they can be compared fairly on the same silicon and the same budget. Everything is written in RHDL, Rust that describes hardware. Verilog is only ever an output handed to Vivado; we never write it, edit it or import it. That keeps one language, one test harness, and one source of truth from the algorithm down to the gates.
§2. Two questions, one chip
The budget is the same for both papers: 485,000 LUTs and 4,520 DSPs, each adjustable from 0 to 200 % to ask “what if we had more, or less?”. A SHA-512 line takes its allowance out of that budget. By default the planner sizes it to what the engines hash (auto), so it never limits the chip and holds no LUTs it does not need; it is built from full pipelines and folded SHA-512 cores, in steps of 1/80 block per clock. Set by hand, it is 65,000 LUTs and 20 DSPs by default (so the engines keep the 420,000 and 4,500 they always had). The LUT split says where the LUTs go: SHA | DSig | Ed25519 in the DSig paper, SHA | Ed25519 in the Ed25519 paper, each summing to 100 %. A bigger line costs the engines LUTs; a smaller one gives them LUTs. It can be shrunk to nothing.
§Signing and verifying are separate
A chip can be built to sign a lot and verify little, or the opposite, so the planner never ties one to the other. The budget is split into a sign side and a verify side by two powers (percentages you set, or that the B key chooses), and each side reports its own delivered rate and latency. A side may need more than one module, and the planner divides the side’s share between them automatically.
§The Ed25519 paper
How many Ed25519 signatures and how many verifications per second fit in the budget? Each side has one module:
signatures/s = Ed25519 sign capacity
verifications/s = Ed25519 verify capacity
both scaled down together if 3 x signatures + 2 x verifications > SHA supplyWhy 3 and 2: each Ed25519 signature hashes three SHA-512 blocks (the
nonce, and the two-block challenge H(R || A || M)); each verification
hashes two.
Verification here is known-key strict verification. Why known keys: the expensive part of verifying is multiplying the signer’s public key by a scalar. If keys are known in advance (true for DSig’s verifiers, and for most services that verify a fixed set of signers), each key gets precomputed tables once, and verification becomes almost as cheap as signing (775 machine instructions instead of ~3,050).
§The DSig paper
How many DSig signatures and verifications per second fit, and what does
the Ed25519 part of DSig cost? DSig signs with hash-based one-time keys
(W-OTS+, Winternitz w). It needs one Ed25519 signature per batch of
K keys (128 by default), over the batch’s Merkle root. A verifier
needs one Ed25519 verification per batch it has not seen. After that it
holds the verified root and skips Ed25519 for the rest of the batch.
How often that happens depends on traffic, so the planner asks for a
best-case expectation b, from 0 to 100 %: how often a verification
finds its batch already verified. At best, every signature after a
batch’s first is a hit:
h = (b / 100) x (K - 1)/K (cache-hit fraction)
signatures/s = min( DSig sign capacity, K x Ed25519 sign capacity ) (chip's signer certifies)
verifications/s = min( DSig verify capacity, Ed25519 verify capacity / (1 - h) )
both scaled down together if the SHA-512 line cannot supply everyone's blocksWhat this means:
- At
b = 100 %, Ed25519 needs only about1/Kof DSig’s rate on each side, so it shrinks to a corner of the chip. - At
b = 0, every DSig verification needs a full Ed25519 verification, and on the verify side Ed25519 becomes the thing to build. - The planner moves each side’s area between DSig and Ed25519 accordingly.
Three splits, each summing to 100 %. The DSig paper’s budget is divided twice before any module sees it:
budget = HBSS foreground (100 - B %) + EdDSA background (B %)
foreground = DSig sign (s %) + DSig verify (100 - s %) <- sign/verify powers
background = Ed25519 sign (e %) + Ed25519 verify (100 - e %)B and e can be set by hand or left on auto. On auto the planner
picks the background that delivers the most and shows it as needed:
the share of EdDSA this DSig chip actually needs at this budget, best
case and batch size (about 8.5 % at the defaults). Too little starves the
certificates or the cache misses; too much takes area from the HBSS.
Who signs the certificates. By default the chip’s Ed25519 sign
module does, sized for 1/K of DSig’s rate. The DSig kernel built today
carries its own small Ed25519 signer instead; choose “Ed25519 certs:
inside DSig” to plan that. DSig is then capped at K x 205 MHz / 895
(29M/s), and the chip’s Ed25519 sign module is not needed (its need
drops to 0), so the signer is never counted twice.
The scheme knobs are shared. HBSS (W-OTS+; HORS is offered but not
implemented, so it plans nothing and says so), chain hash, SHA lanes,
w and K must be the same for the signer and the verifier, so the TUI
lists them once, under DSig sign, and sets both.
The SHA-512 line in this paper. DSig’s chain hash is a knob (Haraka, BLAKE3 or SHA-512):
- With Haraka or BLAKE3, or SHA-512 on DSig’s own lanes, DSig takes nothing from the line, and the whole line serves Ed25519.
- With SHA-512 on the shared line, DSig draws its chain steps from it
too (204 blocks per signature at
w = 4). The line then becomes the limit quickly: about 1M DSig signatures/s per block-per-clock of line.
The B key. The DSig paper does not prescribe how many verifications
follow each signature, so the mix (verifications per signature, 1 by
default) is the target of the B key. B picks the sign/verify powers that
maximize min(signatures, verifications / mix): that is what “100 %”
means. The powers can also be set by hand.
§3. The cast: one crate per job
| Crate | Directory | Its job | Owner |
|---|---|---|---|
rhdl_ed25519_scalable_core | crates/ed25519 | Ed25519 signing and known-key strict verification | Ed25519 |
rhdl_dsig_scalable_core | crates/dsig | The DSig signer: slices, chain lanes, BLAKE3, Merkle trees, certificates | DSig |
rhdl_sha512_pipelined | crates/sha512 | The SHA-512 line: a fully pipelined compression unit and its variants | DSig |
rhdl_fpga_planner | crates/planner | The planning model and the one TUI. Pure Rust, no hardware | Ed25519 |
rhdl_signatures | the repository root (src/) | This guide, the two papers’ chips, the tuner binary; pulls in the others by feature | Ed25519 |
| (vendored) | vendor/rhdl | RHDL itself, upstream c99d5cc plus our patches, listed in vendor/rhdl/PROVENANCE.md | shared |
Why one crate per engine: each engine is complete on its own, with its own tests, exports and docs, and can be built and verified without the others.
Why one workspace: a chip combines engines, and Rust only lets them share hardware types if they build against the very same RHDL. One workspace, one vendored RHDL, and that problem disappears.
§Features: build only what you use
The root package (this crate) is the one place everything meets, and Cargo features decide which engines it pulls in. Each engine stays its own crate, so a feature that is off means that crate is not compiled at all, not merely hidden.
| Feature | Default | Pulls in | Gives you |
|---|---|---|---|
ed25519 | yes | rhdl_ed25519_scalable_core (BSD-3-Clause) | the Ed25519 paper; re-exported as [ed25519] |
sha512 | via dsig | rhdl_sha512_pipelined (AGPL-3.0-or-later) | the real SHA-512 line component; re-exported as [sha512] |
dsig | yes | rhdl_dsig_scalable_core (AGPL-3.0-or-later), and turns on ed25519 and sha512 | the DSig paper; re-exported as [dsig] |
tuner | yes | the planner’s terminal UI (ratatui) | the tuner binary (also needs ed25519) |
Why dsig turns on ed25519: DSig certifies its one-time keys with
Ed25519 and verifies those certificates with Ed25519, so the DSig paper
always has Ed25519 modules in it. The reverse is not true: the Ed25519
paper needs nothing from DSig.
Why sha512 is separate: the Ed25519 paper hashes through the same
SHA-512 line, but planning it needs only the line’s cost model. Without
sha512, the planner uses the Ed25519 crate’s copy of that model
(Sha512Estimate),
so an Ed25519-only build links no AGPL code.
The common builds:
| You want | Run |
|---|---|
| both papers (the default) | cargo tuner |
| the Ed25519 paper alone | cargo tuner-ed25519 (--no-default-features --features ed25519,tuner) |
| the engines as a library, no TUI | cargo build --no-default-features --features dsig |
| everything’s docs | cargo docs |
The TUI offers exactly the papers the build has: without dsig the
paper control and P stay on the Ed25519 paper, and the headline shows
one paper (paper lists which slots each feature adds).
DSig’s own hardware shape (slice count, chain hash) is not a feature of
this crate. It is fixed per bitstream and has to fail closed when host
and kernel disagree, so the DSig crate reads it from build variables
(DSIG_SLICES=2|4|8, default 8; DSIG_CHAIN=haraka|blake3|sha512,
default haraka) and rejects any other value. Features stay additive,
so --all-features builds too.
Each engine’s front page continues the story in detail. Start with
[rhdl_ed25519_scalable_core], which explains, chapter by chapter, how
Ed25519 went from 229,000 signatures per second to 6.42M in a core that
meets 205 MHz (and 7.76M in simulation, not yet routed).
§4. How a plan is made
The planner answers one question: given a budget, how should the chip
be divided, and what does it deliver? It does that in four steps, all in
[rhdl_fpga_planner]:
- Guaranteed shares. The sign and verify powers divide the budget’s LUTs, DSPs, BRAM and URAM between the two sides. Within a side the planner divides the side’s share between its modules. The SHA-512 line gets its own allowance instead.
- Best build per share. Each module (a
Component) knows its own building blocks and their measured or estimated costs. Given a share, it returns the fastest build that fits: its capacity (operations per second if never starved), resources, latency, and the SHA-512 blocks it needs. - Delivered rates. Capacities do not add up to a result; on each
side the slowest link sets the pace. Each paper says how many
operations of each kind one delivered operation needs (the formulas
above, in
Mode::need). A side delivers the smallest capacity ÷ need, and both sides together must fit the SHA-512 supply. - Lending, in real units. A percentage split hands every module
the same fraction of every resource, but modules are lopsided: DSig
signing uses almost no DSPs, an Ed25519 verifier runs out of URAM
long before its LUTs. So after the first plans the planner counts
what each module left unused (LUTs, DSPs, BRAM36, URAM, as counts)
and offers it to the others. It keeps the re-plan that raises the
delivered rates most, and repeats until nobody gains
(
plan_chip). The powers still say who is guaranteed what; nothing sits idle that someone could use. - Balance. Within a side, the planner splits the side’s share between its modules to deliver the most. Between sides, the B key picks the powers for the mix.
Why rates keep growing with the budget. Builds come in steps, and a
step that cannot be taken looks like a wall. Three walls were removed:
the uniform percentage split (fixed by lending); one Ed25519 core per
module, whose credit pool stops it at 8.82M signatures/s (modules are
now 1–4 identical cores); and DSig slice counts of only 1, 2, 4 and 8
(now any count 1–16, with 2, 4 and 8 marked built). The chip test
rates_scale_with_luts_and_dsps prints the LUT % x DSP % table and
checks that more budget never delivers less.
The whole decision path, heuristic by heuristic, with the reason for
each and the build flags it produces, is told in
[rhdl_fpga_planner::decisions]. Read it before changing the planner.
Why model it this way: it keeps each module honest about its own costs (the people who build a module know them best) while the chip-level question stays a few lines of arithmetic anyone can check.
§5. The planner on screen
The Tuner has its own guide, [rhdl_fpga_planner::tui]: one plan
followed from budget to Verilog, every cost constant with its source,
and the measured accuracy. This section is the short version.
cargo tuner (short for cargo run --release -p rhdl_signatures --bin tuner) opens one screen for both papers (cargo tuner-ed25519
for the Ed25519 paper alone); P switches paper, and the
top line shows both papers’ rates. On the left are the controls:
- Paper: which chip.
- LUT budget, DSP budget: 0–200 % of 485k LUTs and 4,520 DSPs.
- LUT split: the SHA-512 line’s part of the LUTs (Left/Right moves it by 0.5 %, from 0 up to 432k LUTs), then DSig and Ed25519’s parts of the rest.
- BRAM budget, URAM budget: 0–100 % of the block RAM of the U280 shell’s dynamic region (1,776 BRAM36; the device has 2,016, but DSig’s build v4 showed only the dynamic region’s 3,552 BRAM18 sites are ours), and of the device’s UltraRAM. Less URAM moves memories to block RAM; less of both moves machine banks to LUT RAM, using spare LUTs.
- Sign power, verify power: each side’s share of the budget.
- Mix: the verifications per signature the B key balances for.
- Best case (DSig paper only): the expectation
b. The screen also shows the resulting cache-hit fraction. - Each module’s guaranteed share, as a percentage and in real counts (LUT, DSP, BRAM, URAM). A card that says “uses resources other modules left free” was planned with borrowed resources.
- Each Ed25519 module’s shape: “block ratio” auto, or manual with machine : SHA ring : scalar ring weights; and batch compress auto / on / off. Off builds have no compress machine; each field machine inverts and encodes its own point (a longer program; a model).
- Each module’s knobs: build options such as the
memory layout, its own SHA rings or the shared line, the verify mode,
DSig’s HBSS, chain hash,
w,Kand certificate source, the S-box placement, and the SHA line’s LUT and DSP allowance. A knob marked “(sets all 2)” is shared by DSig sign and verify.
On the right is what the chip delivers:
- Delivered signatures and verifications, each side on its own line with its bottleneck and latency. Below them is every role’s delivered rate next to its capacity: Ed25519 sign and verify, and in the DSig paper also DSig sign and verify.
- Latency: idle (one request alone) and loaded (at full capacity: requests in flight ÷ throughput, Little’s law). A module on the shared SHA-512 line adds one pass through the line per block it hashes.
- Every module’s resources (LUT, DSP, BRAM, URAM), each marked measured (Vivado) or model, its limiting resource, and next: how many more LUTs the next faster build needs and what it delivers. Builds come in whole blocks (a field machine is ~11k LUTs, a DSig slice ~110–130k), so a rate that stays flat while you add LUTs is waiting for that step; “next” tells you how far it is.
- The bottleneck, and the SHA-512 blocks used against those supplied. Shrink the SHA line and its throughput falls. Once it becomes the limit, it and every module hashing on it are marked SHA-limited, and every delivered rate falls with it.
Two keys do the work:
- B balances: it picks the sign/verify powers for the mix (the “100 %” split).
- W writes
build-config.toml, the compile-time settings (profile sizes, features) of every module in the current plan, to feed the exports.
§6. How we think
Five ideas carry almost all of the performance. Use them as a lens for any change.
-
Count the work, then the hardware. Every operation is a known number of field multiplications or hash blocks. A signature is ~392 machine instructions of curve arithmetic, three SHA-512 blocks and two scalar reductions; a known-key verification is 775 instructions and two blocks. Throughput is hardware ÷ work, so first reduce the work (better algorithms), then add hardware.
-
Keep every expensive unit busy every clock. A 255-bit multiplier is 225 DSPs, and it must start a product every clock to earn its place. Barrel threading (32 independent jobs rotating through one pipeline) hides its long latency. Credits make sure nothing waits on something that waits on it: deadlock freedom is designed, not hoped for.
-
Find the binding resource. LUTs, DSPs, BRAM and URAM run out at different sizes, and the planner says which one stops the next larger build. On a full chip BRAM ran out first, so we moved register banks into UltraRAM and constants into LUT ROM.
-
Specialize. A module that does one job well beats a general one. The verify machine scans two tables instead of doing a general multiplication; the compress machine only inverts; the small signer fuses everything for DSig’s modest rate.
-
Measure before believing. Every claim climbs the same ladder.
- The host model equals the reference library.
- The RHDL equals the host model, bit for bit.
- Vivado maps each block.
- The planner is recalibrated from the maps.
A number is not done until it is measured, and the docs say which rung each number is on.
When you start something new, follow the same order:
- Write down the work count.
- Find which resource binds.
- Prototype on the host model (seconds).
- Then the RHDL (minutes).
- Then Vivado (hours).
§7. The road so far
| Step | Why it was needed | Effect | Where | Status |
|---|---|---|---|---|
| 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 |
| Barrel-threaded field machine | the multiplier’s 22-clock latency would idle it | multiplier ~99 % busy | ed25519 machine | simulated |
| Algorithms as compiled Rust programs | algorithms change daily | new algorithm in minutes, checked on the host | ed25519 microcode, programs | simulated |
| Mixed radix-32/16 fixed-base tables | fewer instructions per signature | 492 → 392 instructions | ed25519 programs | simulated |
| Batch inversion, 32 points | one inversion costs 265 multiplications | 13 per point | ed25519 compress_stage | simulated |
| 2,048 credits, shorter hold | more hardware did not help (Little’s law) | 44.1 → 26.43 cycles per signature | ed25519 sign_core | simulated |
| Two-clock scalar reduction | the small core missed 205 MHz by 0.273 ns | +0.118 ns | ed25519 scalar_ring | routed |
| Known-key verification | general verification costs ~4 signatures | 775 instructions per verification | ed25519 verify_core | simulated, equals verify_strict |
| Shared SHA-512 port | eleven private SHA rings cost ~50k LUTs | modules drop their rings | ed25519 sha_client | simulated |
| Memory layout: UltraRAM banks, LUT-ROM constants | BRAM ran out first | 72 → 24.5 BRAM36 per machine | ed25519 machine | routed, +0.848 ns |
| 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 |
| 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) |
| 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 |
| Fully pipelined SHA-512 | everyone needs blocks; rings are slow | one block per clock | sha512 | simulated |
| HDL-only export | exporting the large core needed 3.5 GB | 190 MB, same Verilog | vendor/rhdl patch | measured |
| Fold constant bytes out of Haraka | 640 S-boxes per chain step | 494 live S-boxes | dsig chain_step | simulated, pinned by test |
| Three chain lanes in series | a chain is 3 dependent steps | one whole chain per clock | dsig slice_chain | simulated |
| 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 |
| Pipelined certificates, 32 in flight | Ed25519 takes ~22,000 cycles per signature | one batch certificate per ~895 cycles | dsig cert_unit | simulated |
| Replicated slices | one slice = 3.0M keys/s | 2/4/8 slices; 2 slices = 301,037 kernel LUT | dsig slice, engine | routed (2 slices) |
| 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 |
| Three chain hashes | compare Haraka, BLAKE3, SHA-512 chains | build variable `DSIG_CHAIN=haraka | blake3 | sha512` |
The DSig crate’s front page tells these steps as a story; its “Where to go next” section is the place to start working on DSig.
§8. What is proven and what is a model
- Measured in Vivado (2023.2, out of context, routed at 4.878 ns;
measure/results.tsv):- the small Ed25519 signer: 29,055 LUTs, 248 DSPs, +0.306 ns;
- every Ed25519 block, in all three memory layouts, on the RTL that meets timing;
- whole Ed25519 cores that meet 205 MHz: signers 4/0/1/1, 8/0/2/1, 9/0/2/1 and 12/0/3/1, and verifier 6/0/1/1;
- the 24-machine verifier misses: −2.225 ns on fix12 (−5.763 on fix9), with the failure spread over 207k endpoints.
- Measured on the card: DSig signing, 12.04M/s (v13). No Ed25519 build has run on the card.
- Synthesis only: the large 15/11/3/1 signer, at 263,810 LUTs and 3,669 DSPs.
- Measured in simulation:
- the planned Ed25519 builds’ throughput (
measure/native-results.tsv), every result checked; and 15/11/3/1 at 26.43 cycles per signature (7.76M/s); - verification equal to
verify_stricton valid signatures and every invalid class; - DSig keys, certificates and frames bit-exact against the software reference for all three chain hashes (leaf gates).
- the planned Ed25519 builds’ throughput (
- Modeled:
- the controllers’ and rings’ LUTs, fitted to routed whole cores (the Tuner guide, section 5, tells how far they are trusted);
- the SHA-512 line’s throughput (its cores’ LUTs are routed);
- DSig verification;
- DSig at 4 and 8 slices, and DSig sign latency;
- most latencies.
§9. Open problems
Ranked by how much they would change the answer:
- Close the 24-machine verifier at 205 MHz, then route a whole Ed25519 chip (sign + verify + SHA line together).
- SHA-512 line above one block per clock in 65k LUTs + 20 DSPs. The goal is ~1.75. One generic pipeline is about one block per clock at ~43k LUTs (estimate); two would need ~86k. Candidates: a fixed-length single-block variant, carry-save rounds, DSP adders. (DSig.)
- Measure latency. Latencies are cycle models, except the small
signer’s single-request latency (22,240 cycles). The compress
machine’s batch wait dominates them. The verifier test now reports
isolated and saturated latency (
ED25519_ISOLATED=1). - DSig verification hardware is being built (verify units plus a 1/1/1/1 known-key verifier for certificate misses). Until it routes, its numbers are a model. (DSig.)
- A reduce-only scalar ring for verification, and the 150-DSP multiplier re-routed on the timing-fixed RTL.
- Replay simulation vectors against the exported Verilog.
- Strict verification for arbitrary keys (variable-base), if a workload needs it.
§10. Your first day
- Read [
rhdl_ed25519_scalable_core]’s front page (the Ed25519 story) and the DSig crate’s. - Run the planner:
cargo tuner(from the repository root). Switch papers, move the best case from 100 % to 0, shrink the SHA line, and watch where the area goes and what limits the chip. - Run the cheap tests:
cargo test -p rhdl_fpga_planner(seconds). Then run the host model of the Ed25519 programs (see that crate’s “How to work”). - Pick an open problem from the list above, write down its work count and its binding resource, and say in the ledger what you are starting.
The workspace builds with Rust 1.95 or newer. rust-toolchain.toml
pins 1.96.0 for rustup users; the system cargo (1.95) works as well.
| You want to | Run (from the repository root) | Time |
|---|---|---|
| Read the docs with diagrams | cargo docs, then cargo guide | 1 min |
| Plan a chip | cargo tuner (or cargo tuner-ed25519) | 2 min first build |
| Planner tests | cargo test -p rhdl_fpga_planner | 10 s |
| Ed25519 hardware tests | cargo test --profile sim -p rhdl_ed25519_scalable_core --lib | ~4 min |
| DSig hardware tests | see the DSig crate | |
| Export Verilog | see each crate’s export docs | minutes |
§Good first contributions
Each task below is real, open, and checkable. Pick one, write it in the ledger, and ask in the coordination file if you are stuck.
| Task | Size | You will learn | You are done when |
|---|---|---|---|
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 |
Remove a duplicate. compress_stage::encode_y repeats sign_core’s point encoding | an hour | point encoding | the Ed25519 tests still pass |
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 |
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 |
| 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 |
| 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 |
| Close the 24-machine verifier | open-ended | timing closure on a big chip | it routes with positive slack |
§11. Where to go next
Find the row for what you want to do. Each lists where to read, in order, and what to run first. Every crate’s front page also ends with its own “Where to go next” for its internals.
| You want to… | Go to | Then |
|---|---|---|
| see the whole picture in numbers | the tuner (section 5) | switch papers, move best case from 100 % to 0, shrink the SHA line |
| understand Ed25519 in hardware | [rhdl_ed25519_scalable_core] chapters 1–7 | its host-model tests (20 s) |
| understand Ed25519 verification | [rhdl_ed25519_scalable_core] chapter 8, then its verify_core | its known-key verifier test |
| understand DSig | [rhdl_dsig_scalable_core]’s front page (a 12-chapter story), then its “Where to go next” | its leaf tests |
| work on the SHA-512 line | [rhdl_sha512_pipelined], then the Ed25519 sha_client (how modules use it) | its pipeline test against sha2 |
| change how plans are made | section 4, [rhdl_fpga_planner] (Mode::need, deliver, balance) | cargo test -p rhdl_fpga_planner |
| 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 |
| 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 |
| map something in Vivado | the crate’s export docs, then house rules on the U280 host | claim it in the ledger first |
| pick an open problem | section 9 | write down its work count and binding resource, then claim it |
§12. House rules
- RHDL only. Hardware is written in RHDL. Generated Verilog is an output for Vivado; it is never edited or read back.
- One heavy job at a time. The development host is shared, and a
runaway RHDL lowering can exhaust its memory.
- Claim the job in
~/Development/FPGA_COORDINATION.md(append-only, newest first). - Run it through
tools/bounded.sh, which caps memory and time and holds an exclusive lock. - Record the release when you are done.
- Claim the job in
- The U280 host (Wolverine) is shared too; claim it in both ledgers.
- Docs are rustdoc. Each module’s
//!docs explain what it does and why. Diagrams are SVG files pulled in withinclude_str!. When you change behavior, change the story too.
§13. Glossary
| Term | Meaning |
|---|---|
| Balance | the power split that delivers the most; “100 %” |
| Batch (DSig) | 128 one-time keys certified by one Ed25519 signature |
Best case b | how often a DSig verification finds its batch already verified |
| Bundle, instruction | one step of a field machine: a multiply, an add, a table step |
| Capacity | operations per second a module could do if never starved |
| Delivered | operations per second the whole chip sustains |
| Idle / loaded latency | time per request alone / at full capacity (Little’s law) |
| Known-key verification | verification for public keys whose tables were built in advance |
| Mix | verifications per signature |
| Power | a module’s share of the budget |
| SHA-512 line | the shared SHA-512 compression unit, on its own allowance |
Re-exports§
pub use rhdl_fpga_planner as planner;pub use rhdl_ed25519_scalable_core as ed25519;pub use rhdl_sha512_pipelined as sha512;pub use rhdl_dsig_scalable_core as dsig;
Modules§
- paper
- The modules of each paper’s chip, as planner slots.