Skip to main content

Module config

Module config 

Source
Expand description

The planner: predicts signatures per second and resources for every build and picks the best one for a LUT budget.

§The problem

A Vivado run of this design takes hours. You cannot try every size of the engine on real tools just to learn which one fits the chip and how fast it signs. So before you build anything, you want a cheap answer to two questions:

  1. Does this build fit? (LUTs, block RAM, UltraRAM, DSPs.)
  2. How many signatures per second will it make?

This module answers both with plain arithmetic. It takes costs that were measured once on real place-and-route runs, multiplies them by how many copies a build has, and compares the result with the device and with the project’s 420,000-LUT cap. Nothing here touches hardware; it is a spreadsheet written in Rust.

§The reasoning

The engine is made of identical slices (see crate::slice). Each slice is one complete signer. So the cost of a build is close to linear: slices × (cost of one slice) + (things that exist once). The “things that exist once” are the Ed25519 certificate core (crate::ed25519) and the engine glue (the certificate unit, control and HBM I/O in crate::engine).

The one big lever besides the slice count is where the AES S-boxes live. (An S-box is the 256-entry byte-substitution table at the heart of AES; Haraka-512 is built from AES rounds.) A slice has three Haraka lanes (crate::chain_step) with 494 live S-boxes each. In LUTs an S-box costs roughly 32 LUTs (an estimate, see LUTS_PER_LUT_SBOX); in block RAM or UltraRAM it costs one memory instead. LUTs are the scarce budget, and the U280 has RAM that the design does not otherwise use. So moving S-boxes into spare RAM buys LUTs, which can buy more slices.

§The decision

Model only the choices that matter (slices: 2, 4 or 8; S-boxes per lane in UltraRAM and block RAM), list the two sensible placements per slice count (all-LUT and RAM-filled, see Build::all), and pick the fastest one that fits (best). The binary dsig_plan prints the answer; the chip planner component in planner.rs (Cargo feature planner) reuses these same constants.

  measured per-block costs ──┐
  (SLICE_LUT, ED25519_LUT,   │
   GLUE_LUT, ...)            ▼
                      ┌──────────────┐   Resources   ┌──────────┐
  Build {slices,  ──▶ │ resources()  │ ────────────▶ │ fits(%)  │──┐
    uram, bram}       └──────────────┘               └──────────┘  │
       │              ┌───────────────────────────┐                ▼
       └────────────▶ │ signatures_per_second(f)  │ ──▶  best(): fastest
                      │ = min(slices, certs,      │      build that fits,
                      │       frame writes)       │      ties → fewer LUTs
                      └───────────────────────────┘

A Build is a slice count (DSIG_SLICES=2|4|8) plus where each lane’s 494 live AES S-boxes live: UltraRAM, block RAM or LUTs (DSIG_SBOX_PLACEMENT, see sbox_placement). Budgets are percentages of LUT_CAP (420,000 CLB LUTs, the budget DSig and Ed25519 share for the comparison).

§Throughput model

Build::signatures_per_second is the slowest of these stages:

StageSignatures per secondWhy
slicesSLICES × f / 68each slice makes one key per 68-cycle period (slice_schedule)
certificates128 × f / 895the Ed25519 core finishes one batch per ~895 cycles with ≥ 32 in flight
frame writesSLICES × port bandwidth / 2,048 Bone HBM port per slice, 64 B per clock

times STEADY_STATE_EFFICIENCY, the fraction of the slice ceiling that the cycle-accurate throughput test measures in steady state.

§Resource model

Per-block costs come from Vivado 2023.2 out-of-context place-and-route on the U280 part (xcu280-fsvh2892-2L-e), except where a field says estimate:

BlockCLB LUTSource
Slice, all S-boxes in LUTs127,502first routed slices-2 link, in context (59,946 of them LUT-RAM, mostly S-box ROMs)
Haraka chain-step lane23,902Vivado OOC routed
BLAKE3 lane16,422Vivado OOC routed
Ed25519SignCoreSmall25,383same link, in context (26,408 OOC)
Engine glue (cert unit’s BLAKE3 lane, control, HBM I/O)20,564same link: kernel 301,037 − slices − Ed25519

S-boxes placed in RAM go in dual-port pairs (SboxPair): two per URAM, or two per BRAM18 (four per BRAM36). Either way it frees about LUTS_PER_LUT_SBOX LUTs. That LUT figure is an estimate (a 256 × 8 ROM is eight 8-input functions, four LUT6s each). Build::fill places as many S-boxes as the device’s spare UltraRAM and block RAM allow, keeping RAM_HEADROOM of each free.

§Worked example (the measured anchor)

The all-LUT slices-2 build is the one build that was actually routed, so the model should reproduce it:

  2 × SLICE_LUT      2 × 127,502 = 255,004
+ ED25519_LUT                       25,383
+ GLUE_LUT                          20,564
= kernel                           300,951  (routed link: 301,037)

GLUE_LUT was itself derived from that same link (kernel minus slices minus Ed25519), so matching it is a consistency check, not an independent validation. Every other build is an extrapolation from this one point plus the S-box estimate.

Structs§

Build
One buildable configuration.
Resources
Predicted resources.

Constants§

DESIGN_MHZ
Design clock in MHz.
DEVICE_BRAM36
Physical BRAM36 tiles of the xcu280.
DEVICE_DSP
Physical DSP48E2 slices of the xcu280.
DEVICE_URAM
Physical UltraRAMs of the xcu280.
ED25519_BRAM36
Ed25519SignCoreSmall BRAM36 tiles in context.
ED25519_CYCLES_PER_SIGN
Measured Ed25519 cycles per signature with ≥ 32 requests in flight.
ED25519_DSP
Ed25519SignCoreSmall DSPs.
ED25519_LUT
Ed25519SignCoreSmall in context (routed slices-2 link).
FRAME_BYTES
Bytes written per signature (one 2 KiB frame slot, 26 beats used).
GLUE_BRAM36
Engine glue BRAM36 (certificate records, message FIFO), same link.
GLUE_LUT
Engine glue outside slices and the Ed25519 core (routed slices-2 link).
KERNEL_BRAM36
BRAM36 tiles a kernel can actually use: the platform’s dynamic region.
KEY_PERIOD
Cycles per key in one slice (the modulo schedule’s period).
LANES_PER_SLICE
Haraka chain-step lanes per slice.
LOADER_LUT
LUTs of one lane’s UltraRAM loader (counter + one LUT S-box): an estimate.
LUTS_PER_LUT_SBOX
LUTs per LUT-ROM S-box. Estimate: the routed link’s 59,946 LUT-RAM per slice over its 1,482 S-boxes is ~40; 32 (8 outputs × 4 LUT6) is the conservative floor used here.
LUT_CAP
Project LUT cap (100 %): the comparison budget shared with Ed25519.
RAM_HEADROOM
Fraction of the device’s UltraRAM and block RAM the planner leaves free.
SBOXES_PER_LANE
Live S-boxes per lane.
SLICE_BRAM36
BRAM36 tiles of one slice (caches in UltraRAM, S-boxes in LUTs), from two kernel syntheses: build v3 used 202 tiles at 2 slices; build v5 used 1,362.5 at 4 slices, of which 12 lanes × 88 are T-tables, leaving 306.5. The difference, (306.5 − 202) / 2 ≈ 52 per slice, is the slice’s FIFOs, frame buffers and Merkle RAMs.
SLICE_LUT
Measured slice cost with every S-box in LUTs (routed slices-2 link).
SLICE_URAM
UltraRAMs of one slice (4 banks × 18), Vivado-confirmed.
STEADY_STATE_EFFICIENCY
Fraction of the slice ceiling reached in steady state. Set from the cycle-accurate throughput test (tests/throughput.rs); 1.0 until measured.

Functions§

best
The fastest build that fits the budget, if any.
sweep
A human-readable table of every build.