Skip to main content

Crate rhdl_signatures

Crate rhdl_signatures 

Source
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:

TargetCrate documentationBuild role
Ed25519 cardEd25519export_card, then wolverine-card.sh
DSig cardDSigdsig_engine_export, then wolverine-engine.sh
SHA-512SHA-512Component included in an enclosing kernel
PlannerPlannerHost-side budget selection
Earlier Ed25519 coreFast coreComponent; use the scalable crate for the card flow
Full RHDL uBFTuBFTIntegration 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-dalek or 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.

#ReadIt answers
1this guidewhy 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

  1. The story in five minutes
  2. Why this exists
  3. Two questions, one chip
  4. The cast: one crate per job
  5. How a plan is made
  6. The planner on screen
  7. How we think
  8. The road so far
  9. What is proven and what is a model
  10. Open problems
  11. Your first day, and good first contributions
  12. Where to go next
  13. House rules
  14. 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

Ed25519 paper Ed25519 alone; the whole budget LUT budget 420k x LUT %, DSP 4.5k x DSP % Ed25519 sign fixed-base machines, scalar rings, compress Ed25519 verify known-key machines, key loader, compress SHA-512 line: on top of the budget, 0-65k LUT, 0-20 DSP

DSig paper DSig and the Ed25519 it relies on share the budget DSig sign slices DSig verify (model) Ed25519 sign 1 per K Ed25519 verify (1 - h) LUT budget 420k x LUT %, DSP 4.5k x DSP % SHA-512 line: same allowance (Ed25519’s hashes)

How the planner turns a split into delivered rates 1. power % per module share = power x budget 2. best build per share capacity, LUT, DSP, BRAM 3. per side: min over roles capacity / need per signature and SHA supply / blocks 4. sides: sign / verify B: powers for the mix

Needs per delivered operation of each side. Ed25519 paper: sign 1; verify 1. DSig paper: sign = DSig sign 1 + Ed25519 sign 1/K; verify = DSig verify 1 + Ed25519 verify (1 - h), h = best case x (K-1)/K. SHA-512 line: Ed25519 3 blocks/sign, 2/verify; DSig only with its SHA-512 chain hash on the shared line.

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 supply

Why 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 blocks

What this means:

  • At b = 100 %, Ed25519 needs only about 1/K of 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

CrateDirectoryIts jobOwner
rhdl_ed25519_scalable_corecrates/ed25519Ed25519 signing and known-key strict verificationEd25519
rhdl_dsig_scalable_corecrates/dsigThe DSig signer: slices, chain lanes, BLAKE3, Merkle trees, certificatesDSig
rhdl_sha512_pipelinedcrates/sha512The SHA-512 line: a fully pipelined compression unit and its variantsDSig
rhdl_fpga_plannercrates/plannerThe planning model and the one TUI. Pure Rust, no hardwareEd25519
rhdl_signaturesthe repository root (src/)This guide, the two papers’ chips, the tuner binary; pulls in the others by featureEd25519
(vendored)vendor/rhdlRHDL itself, upstream c99d5cc plus our patches, listed in vendor/rhdl/PROVENANCE.mdshared

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.

FeatureDefaultPulls inGives you
ed25519yesrhdl_ed25519_scalable_core (BSD-3-Clause)the Ed25519 paper; re-exported as [ed25519]
sha512via dsigrhdl_sha512_pipelined (AGPL-3.0-or-later)the real SHA-512 line component; re-exported as [sha512]
dsigyesrhdl_dsig_scalable_core (AGPL-3.0-or-later), and turns on ed25519 and sha512the DSig paper; re-exported as [dsig]
tuneryesthe 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 wantRun
both papers (the default)cargo tuner
the Ed25519 paper alonecargo tuner-ed25519 (--no-default-features --features ed25519,tuner)
the engines as a library, no TUIcargo build --no-default-features --features dsig
everything’s docscargo 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]:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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, K and 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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:

  1. Write down the work count.
  2. Find which resource binds.
  3. Prototype on the host model (seconds).
  4. Then the RHDL (minutes).
  5. Then Vivado (hours).

§7. The road so far

StepWhy it was neededEffectWhereStatus
Multiplier as 15 DSP cascadesLUT multipliers are huge and slowone 255-bit product per clock, 225 DSPsed25519 fe17routed, +0.688 ns
Barrel-threaded field machinethe multiplier’s 22-clock latency would idle itmultiplier ~99 % busyed25519 machinesimulated
Algorithms as compiled Rust programsalgorithms change dailynew algorithm in minutes, checked on the hosted25519 microcode, programssimulated
Mixed radix-32/16 fixed-base tablesfewer instructions per signature492 → 392 instructionsed25519 programssimulated
Batch inversion, 32 pointsone inversion costs 265 multiplications13 per pointed25519 compress_stagesimulated
2,048 credits, shorter holdmore hardware did not help (Little’s law)44.1 → 26.43 cycles per signatureed25519 sign_coresimulated
Two-clock scalar reductionthe small core missed 205 MHz by 0.273 ns+0.118 nsed25519 scalar_ringrouted
Known-key verificationgeneral verification costs ~4 signatures775 instructions per verificationed25519 verify_coresimulated, equals verify_strict
Shared SHA-512 porteleven private SHA rings cost ~50k LUTsmodules drop their ringsed25519 sha_clientsimulated
Memory layout: UltraRAM banks, LUT-ROM constantsBRAM ran out first72 → 24.5 BRAM36 per machineed25519 machinerouted, +0.848 ns
Per-machine memory placement in the plana plan put every machine in UltraRAM and failed placement (2,646 BRAM36 > 2,016)plans export uram_machines/lut_machines; only buildable layouts offeredplanner, ed25519 frontierrouted
Twelve timing fixes (limb-serial adders, registered arbiters, read-ahead)whole cores missed 205 MHz by 0.6–5.8 nsL100 signer +0.218, verifier +0.043; L200 signer +0.050ed25519 chapter 10routed; L200 verifier misses (−2.225)
Planner costs fitted to routed coreswhole cores were 6.5–7.6 % over the sum of blocks−1.0 … +2.3 % LUT out of sampleed25519 frontiermeasured
Fully pipelined SHA-512everyone needs blocks; rings are slowone block per clocksha512simulated
HDL-only exportexporting the large core needed 3.5 GB190 MB, same Verilogvendor/rhdl patchmeasured
Fold constant bytes out of Haraka640 S-boxes per chain step494 live S-boxesdsig chain_stepsimulated, pinned by test
Three chain lanes in seriesa chain is 3 dependent stepsone whole chain per clockdsig slice_chainsimulated
68-cycle modulo schedule~47 dependent BLAKE3 compressions per keyone key per 68 clocks per slicedsig slice_schedule, slice_factorysimulated, bit-exact
Pipelined certificates, 32 in flightEd25519 takes ~22,000 cycles per signatureone batch certificate per ~895 cyclesdsig cert_unitsimulated
Replicated slicesone slice = 3.0M keys/s2/4/8 slices; 2 slices = 301,037 kernel LUTdsig slice, enginerouted (2 slices)
Key caches in UltraRAM, registered inputsfirst link missed 205 MHz by 2.167 nspost-placement WNS +0.004 ns; routed result pendingdsig cache_bankin progress
Three chain hashescompare Haraka, BLAKE3, SHA-512 chainsbuild variable `DSIG_CHAIN=harakablake3sha512`

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_strict on valid signatures and every invalid class;
    • DSig keys, certificates and frames bit-exact against the software reference for all three chain hashes (leaf gates).
  • 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:

  1. Close the 24-machine verifier at 205 MHz, then route a whole Ed25519 chip (sign + verify + SHA line together).
  2. 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.)
  3. 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).
  4. 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.)
  5. A reduce-only scalar ring for verification, and the 150-DSP multiplier re-routed on the timing-fixed RTL.
  6. Replay simulation vectors against the exported Verilog.
  7. Strict verification for arbitrary keys (variable-base), if a workload needs it.

§10. Your first day

  1. Read [rhdl_ed25519_scalable_core]’s front page (the Ed25519 story) and the DSig crate’s.
  2. 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.
  3. 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”).
  4. 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 toRun (from the repository root)Time
Read the docs with diagramscargo docs, then cargo guide1 min
Plan a chipcargo tuner (or cargo tuner-ed25519)2 min first build
Planner testscargo test -p rhdl_fpga_planner10 s
Ed25519 hardware testscargo test --profile sim -p rhdl_ed25519_scalable_core --lib~4 min
DSig hardware testssee the DSig crate
Export Verilogsee each crate’s export docsminutes

§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.

TaskSizeYou will learnYou are done when
Show timing in the Tuner. Mark each Ed25519 build “meets 205 MHz” or “misses by x ns”, from measure/results.tsvan afternoon; pure Rusthow plans map to built coresa test checks the label of one known build
Remove a duplicate. compress_stage::encode_y repeats sign_core’s point encodingan hourpoint encodingthe 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 modela daythe verifier pipelinemeasure/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 runa few daysthe export path and a Verilog simulatorone small core’s replay matches the native model
A fair output arbiter. Machine outputs are taken lowest index first; make it round-robina few daysready/valid handshakesa test saturates one machine and bounds every other machine’s wait
A reduce-only scalar ring for verificationa few weeksDSP pipelinesthe ring routes at 205 MHz and the verifier uses fewer LUTs in the Tuner
Close the 24-machine verifieropen-endedtiming closure on a big chipit 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 toThen
see the whole picture in numbersthe 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–7its host-model tests (20 s)
understand Ed25519 verification[rhdl_ed25519_scalable_core] chapter 8, then its verify_coreits 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 madesection 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), paperadd it to paper::slots and watch it in the tuner
change a module’s coststhe module’s cost table (Ed25519: frontier; DSig and SHA-512: planner), marked measured or projectedrerun the tuner; a projected number should become measured
map something in Vivadothe crate’s export docs, then house rules on the U280 hostclaim it in the ledger first
pick an open problemsection 9write 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.
  • 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 with include_str!. When you change behavior, change the story too.

§13. Glossary

TermMeaning
Balancethe power split that delivers the most; “100 %”
Batch (DSig)128 one-time keys certified by one Ed25519 signature
Best case bhow often a DSig verification finds its batch already verified
Bundle, instructionone step of a field machine: a multiply, an add, a table step
Capacityoperations per second a module could do if never starved
Deliveredoperations per second the whole chip sustains
Idle / loaded latencytime per request alone / at full capacity (Little’s law)
Known-key verificationverification for public keys whose tables were built in advance
Mixverifications per signature
Powera module’s share of the budget
SHA-512 linethe 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.