Expand description
The interactive terminal front end (feature tui).
§The Tuner: plan a U280 signature chip and know how far to trust it
The Tuner answers one question in a second: with this much of an Alveo U280, how many signatures and verifications per second does the chip deliver, and what exactly do we build? You set a budget and a few knobs. It searches every build it knows, shows the rates, the latency and every module’s estimated resources, and writes a TOML record of proposed compile-time settings. The current build scripts read environment variables; saving TOML alone does not apply those settings to a build. Check the supported application path before claiming that a displayed plan became generated hardware.
Why it exists, in four sentences. Two papers share one chip, and by hand nobody could say how to divide it. The first planner added up costs from synthesis, and its first full-chip plan did not fit the chip’s block RAM. So we routed the plans it made, 80 Vivado runs in four days, and fitted its costs to what Vivado really used. Now it predicts a routed core’s LUTs to within a few percent, and this page shows you how far to trust it and where it is still wrong.
In numbers.
-
Measured against 88 Vivado routes of the planned designs (Vivado 2023.2, out of context, 4.878 ns = 205 MHz, Oct 16–20 2026), the Tuner predicts a whole core’s LUTs within −1.0 … +2.3 % on cores its costs were not fitted to:
Core Predicted Routed fix10 L200 signer 12,0,3,1 255,219 254,817 fix11 verifier 6,0,1,1 150,198 150,873 fix12 verifier 6,0,1,1 150,198 151,645 fix12 L200 verifier 24,0,2,1 840,023 842,345 fix13 L200 verifier 24,0,2,1 (hard route) 840,023 827,202 fix12 verifier 1,1,1,1 (DSig’s certificate-miss core) 84,058 82,184 -
DSPs are exact. BRAM36 is within 2.3 %, and URAM within 1.8 % except on the smallest core (1,1,1,1: +4.5 %, 184 predicted against 176).
-
Its throughput model is within 0–8 % of cycle-accurate simulation, and it errs low. Every simulated signature equals
ed25519-dalekand every verification equalsverify_strict. -
Seven whole cores it planned meet 205 MHz routed: signers 4,0,1,1 (two builds), 8,0,2,1, 9,0,2,1 and 12,0,3,1, and verifiers 6,0,1,1 and 1,1,1,1. Since then 8,0,2,0, 8,0,3,1 and 12,0,1,1 have met it too.
-
The L200 verifier 24,0,2,1 (827k LUT, ~64 % of the device) now meets 205 MHz too, by 0.008 ns, with the hard route directives (fix13, job 007zzq). On fix12 it missed by 2.225 ns. It is the largest core to meet timing, and the thinnest margin.
Reproduce the accuracy table with
cargo run --release --example predict_builds (it writes
measure/accuracy.tsv). Open the screen with cargo tuner.
§Contents
- What you see
- One plan, start to finish
- Where the resources go
- How it is built
- How it became accurate
- What is proven
- Every cost constant
- Limits
- Working on it
- Glossary
§1. What you see
The screen has two halves.
On the left are the controls.
- The paper: Ed25519, or DSig with an Ed25519 background. A build with a uBFT service also offers its uBFT mode (below).
- LUT and DSP budgets, from 0 % up to the whole device: 268.8 % of 485,000 LUTs and 199.6 % of 4,520 DSPs.
- BRAM and URAM budgets, from 0 to 100 % of the shell’s dynamic region: 1,776 BRAM36 and 960 URAM.
- The LUT split between the SHA-512 line and the engines.
- The sign and verify powers, and the mix of verifications per signature.
- The best case
b, in the DSig paper only. - Each module’s share, shape and knobs.
On the right is what the chip delivers.
- Each side’s rate, with its bottleneck.
- Idle latency (one request on an idle core) and loaded latency (in flight ÷ throughput, Little’s law).
- Each module’s build. It shows the profile, the cores, where its memory went and which multiplier it uses. It also shows the resources, each marked measured or model, and the limiting resource.
- next: +N LUT → rate, the distance to the next faster build.
- The SHA-512 blocks used against those supplied, and the chip’s gauges.
Keys.
| Key | Does |
|---|---|
| Up / Down | select a control |
| Left / Right (Shift: ×5) | adjust it |
| B | balance the sign/verify powers for the mix (what “100 %” means) |
| P | next paper (every paper this build has, then the uBFT modes) |
| W | write build-config.toml |
| Q / Esc | quit |
§2. One plan, start to finish
Follow one plan, the Ed25519 paper at LUT 100 %, DSP 100 %. These are
the numbers in measure/configs/predictions.tsv.
-
The budget becomes shares. The SHA-512 line sizes itself first. The engines at this budget want about 0.2 SHA-512 blocks per clock, so the line is one fold-16 core of 16,887 LUT instead of the 65k default. Sign and verify split the rest by their powers. →
decisions, chapter 1. -
Each side finds its best build. The signer searches every profile
fb,sha,sc,cpup to 40 machines, with both multipliers and 1–4 cores. The fastest build that fits is 7,0,2,1: 7 field machines, the shared SHA line, 2 scalar rings and 1 compress machine.- Its URAM share holds 6 machines’ banks. The placer moves the seventh machine’s banks to block RAM.
- The verifier is 14,0,1,1 with the 150-DSP multiplier, half its machines in URAM and half in block RAM. → chapter 2.
-
Lending. After the first plans, resources one side left unused are offered, in real counts, to whichever side gains most from them. →
plan_chip, chapter 3. -
Delivery. The signer is machine-bound at 3.55M signatures/s (134,076 LUT, 1,846 DSP). The verifier delivers 3.70M verifications/s (333,987 LUT, 2,423 DSP). → chapter 4.
-
W writes the build. One TOML table per module. The signer’s table is:
ED25519_SCALABLE_PROFILE = "7,0,2,1" cores = 1 SH = 1 memory = "SPECIALIZED" multiplier = "DSP225" uram_machines = 6 lut_machines = 0 buildable = truemeasure/export_build.pyturns that table into the environment forexport_scalable, and the Verilog goes to Vivado. → chapter 6.
Why this matters: every number on the screen can be traced to one function and one table, and every build it writes can be exported and routed. Section 5 tells how that last part became true.
§Beside a uBFT replica
uBFT runs consensus on the same card. Each slot needs one signature and
2f verifications of peers’ shares, far below what even one small core
delivers, so the rate is the wrong headline. In a uBFT mode
(Mode::UbftEd25519, Mode::UbftDsig) the Tuner changes three things:
- Only uBFT modules take part. The replica, and the services built for
its streams. The papers’ modules and the shared SHA-512 line stay out,
and the papers never plan a uBFT module (
Mode::admits). - The budget is the whole device (
Budget::whole_device), and the replica’s routed design comes off it: 346,232 LUT and 36 DSP. That is an upper bound, because it counts the shell. Switching back to a paper restores the papers’ budget. - The headline is a slot’s crypto time: sign one share, then verify
2fshares. uBFT toleratesf = (n − 1) / 2faults, so a quorum is nearly every replica. The first verification takes the verifier’s idle latency, and the rest follow at its rate. It is shown for 3, 7 and 11 replicas, beside the inline Vitis Ed25519 that uBFT calls today (3.07M cycles to sign and 10.4M to verify, one after another, at its 161.3 MHz).
| Ed25519 service, n replicas | Slot crypto (model) | Inline HLS | Less |
|---|---|---|---|
| 3 (2 verifies) | 367 µs | 148 ms | 404× |
| 7 (6 verifies) | 382 µs | 406 ms | 1,065× |
| 11 (10 verifies) | 397 µs | 665 ms | 1,675× |
The signer (1,1,1,0) is measured, and the verifier (1,1,1,1) is routed but
not yet measured inside the service. The service’s own logic (6k LUT) and
the 16-slot key tables (+57 BRAM36) are estimates. The model ignores
network time. → rhdl_fpga_planner::ubft, planner::ubft in the Ed25519
crate, and chapter 12 of its guide.
§3. Where the resources go
A signing core is mostly field machines. A verifier is mostly machines plus a large fixed controller.
| Part | LUT | DSP | Note |
|---|---|---|---|
| field machine (sign) | 14,615 | 225 | one 255-bit product per clock; ~11 % of a 100 % chip’s DSPs; 1,505 less inside a core |
| known-key verify machine | 14,091 | 225 | 13,863 routed + 228 of controller logic per machine |
| compress machine | 16,569 | 225 | batch inversion, 32 points per inversion |
| key loader (verifier) | 14,518 | 225 | builds a key’s tables, ~108k cycles per key |
| verify controller | 30,657 | 0 | admission, contexts, compare, decode; fixed |
| signing controller | 10,099 | 0 | fixed; 3,115 with no compress machine |
| scalar ring (sign) | 7,871 | 23 | 6,194 routed alone plus its dispatch |
| scalar ring (verify) | 2,408 | 23 | fitted with the verify controller |
| SHA client port | 1,500 | 0 | per core on the shared line |
What binds.
- DSPs bind the Ed25519 paper first, because every machine brings a 225-DSP multiplier. That is why the verifier above takes the 150-DSP Karatsuba multiplier, which costs +4,440 LUT and saves 75 DSP.
- URAM binds the verifier next. Each verify machine holds 56 URAM: 32 of banks and 24 of key tables.
- When URAM runs out, the placer moves machine banks to block RAM. When block RAM runs out too, it moves them to LUT RAM, which costs +22,830 LUT per machine. That is how spare LUTs still buy machines.
Throughput is the slowest stage of the cycle model:
- machines:
392 / fb / 0.97cycles per signature; - SHA:
243 / sha / 0.886; - scalar:
64 / sc / 0.9; - admission: 2;
- compress credits: 23.25 cycles, which caps one core at 8.82M/s.
A verification is 775 machine bundles instead of 392.
§4. How it is built
| Module | Owns | Read |
|---|---|---|
crates/planner/src/lib.rs | Component, budgets, shares, lending, delivery, balance, build config | first: the crate page |
decisions | the whole decision path, heuristic by heuristic, with why | second |
tui (this page) | the screen, keys and the plan cache | third |
crates/ed25519/src/planner.rs | the Ed25519 modules as Components; their knobs and build flags | for Ed25519 plans |
crates/ed25519/src/frontier.rs | the Ed25519 cost tables (slimmed_costs), the search, the placer | to change a cost |
crates/dsig/src/planner.rs | DSig sign and verify, and the SHA-512 line model | for DSig plans |
src/paper.rs | which modules each paper has | to add a module |
examples/build_configs.rs, examples/predict_builds.rs | the plan grid, and predicted against routed | to check accuracy |
The planner knows nothing about Ed25519 or DSig. Each module knows its own blocks and their costs. Why: the people who build a module know its costs, and the chip-level arithmetic stays a few lines anyone can check.
§5. How it became accurate
The first plans on screen were confident and wrong. Each step below fixed one way they were wrong. It gives the measurement that showed the problem, the idea, and the result.
- Synthesis costs said too little.
- Problem: block costs came from synthesis, and routing changes LUT counts.
- Idea: route every block out of context at 4.878 ns and use those numbers.
- Result: the field machine, verify machine, compress machine, key
loader, SHA ring and scalar ring are all routed costs, and each meets
205 MHz on its own.
→
frontier::slimmed_costs(commitb34eba7).
- Plans that could not be built.
- Problem: the first L200 verifier plan, 27,0,2,1, put every machine in its preferred layout. Vivado stopped before placement: it needed 2,646 BRAM36 on a device with 2,016 (job 023).
- Idea: place memory per machine (URAM, then block RAM, then LUT RAM)
inside the RAM budget, export the counts as
uram_machinesandlut_machines, and offer only layouts the RTL can build. - Result: no plan since has failed placement for memory.
→
frontier::place(bb9dbb2).
- The dynamic region is smaller than the device.
- Problem: DSig’s build v4 asked for 4,033 BRAM18 in a region with 3,552.
- Idea: budget 1,776 BRAM36, the dynamic region, not 2,016.
- Result: the BRAM budget is what the shell gives us.
→
decisions, chapter 1.
- A whole core is more than its blocks.
- Problem: whole cores routed 6.5–7.6 % above the sum of their routed
blocks (
fb9b7ec,measure/accuracy.tsv). - Idea: treat rings and controllers as unknowns, and fit them to routed whole cores: the routed total less the routed blocks.
- Result: within 0.5 % in sample, and −3.4 … −1.3 % out of sample.
→
11019b8.
- Problem: whole cores routed 6.5–7.6 % above the sum of their routed
blocks (
- Projected fallbacks were optimistic.
- Problem: LUT-RAM banks were projected at +16,500 LUT and the 150-DSP multiplier at +2,500 LUT.
- Idea: build and route both.
- Result: LUT-RAM banks measured +22,830 (37,440 against 14,615 for
the same machine), and the multiplier +4,367 … +4,441 LUT for −75 DSP.
Both are now measured costs.
→
frontier::LUT_BANKS_LUT,lean_mul.
- Meeting 205 MHz changed the costs.
- Problem: whole cores missed timing on long paths: 255-bit carries,
the scalar ring, the verifier’s decode and arbitration (fix3 to
fix12, Oct 17–19). Each fix added registers.
- The verify controller grew from 17,540 + 716 per machine to 30,657 + 228.
- The field machine grew from 14,070 to 14,615.
- Idea: refit the costs to the RTL that meets timing (fix9), then check against cores routed later.
- Result: fix10 to fix12 cores land at +0.2 %, −0.4 %, −1.0 % and
+2.3 % LUT (the last is the one-machine verifier).
→
467e39e.
- Problem: whole cores missed timing on long paths: 255-bit carries,
the scalar ring, the verifier’s decode and arbitration (fix3 to
fix12, Oct 17–19). Each fix added registers.
- The throughput model, checked cycle by cycle.
- Problem: the cycle model is arithmetic. A deadlock or a stalled stage would not show in it.
- Idea: simulate the planned builds natively and compare the steady
rate.
measure/native_runs.shruns 3,000–6,000 operations past the 2,048-context pool and counts the 50–90 % window. - Result: see the table in section 6. The model errs low by up to 8 %.
- A rejected alternative: we planned fix10’s verifier on the cycle model alone. Simulation caught that it lost a coordinate on a flush and failed verification 585. It was fixed in fix11 before it routed.
- A verifier the core cannot build.
- Problem: the verify search offered builds with no compress stage.
In the DSig paper it chose
1,0,1,0for the certificate-miss verifier, at 63,174 LUT.KnownKeyVerifyCorealways has exactly one compress stage, so five grid plans werebuildable = false. - Idea: known-key verification searches
cp = 1only. The strict and batch modes, which are models, keep 0–2. - Result: the DSig paper plans
1,0,1,1at 79,743 LUT and 698 DSP. The routed own-ring1,1,1,1is 82,184 LUT and 698 DSP (job 007zzi). Every Ed25519 plan in the grid is now buildable. →frontier::plan_verify_shaped.
- Problem: the verify search offered builds with no compress stage.
In the DSig paper it chose
- A starved side that needed two modules.
- Problem: when a side’s share holds no build, the planner sets aside a module’s smallest build and replans. It did that one module at a time. DSig signing needs two modules, the DSig signer and the Ed25519 signer that certifies it, so flooring either alone never helped.
- Idea: floor every starved module of a side in one trial.
- Result: a side is rescued whenever its modules’ smallest builds fit
together. →
floor_starvedin the planner crate.
- Loaded latency five times too high.
- Problem: loaded latency is requests in flight ÷ throughput, and the Tuner assumed every core holds all 2,048 compress credits. The saturated 6,0,1,1 verifier holds ~404, so its loaded latency was about 5× too high.
- Idea: count what a core can actually hold: its machines’ 32
contexts each, plus the requests that left the machines and have not
finished, at one per
cycles per operation. Measure the peak in flight in simulation to check it (max in_flightin the logs). - Result: verify 6,0,1,1: 393 modeled, 404 measured. Sign 8,0,2,1 held 1,026, against 880 at first. The gap was the 8,000-cycle credit overhead that the rate model already uses. With it the Tuner says 952 at its own rate (1,012 at the simulated rate).
- The idle latencies had a gap of their own: the code said it added
the shared SHA line’s round trip, and it did not. Measured
isolated, the line costs ~210 cycles per block. Signing is now
40,406 modeled against 40,446 measured, and verification 52,392
against 52,350.
→
loaded_in_flight,LINE_BLOCK_CYCLES(2026-10-19).
- The scalar ring was hiding in the other terms.
- Problem: we routed four signers chosen to pull the fit apart: 4,0,1,1, 8,0,3,1 (one more ring than any fitted core), 8,0,2,0 (no compress machine, never built before) and 8,0,2,1 with the 150-DSP multiplier. The fit said a ring costs 2,408 LUT, yet a ring routed alone is 6,194. 8,0,3,1 came in 3.6 % above the prediction, and 8,0,2,0 5.5 % below it.
- Idea: fit the controller, the per-ring cost and a per-machine in-core change together, by least squares on all four signers with a compress machine.
- Result: a ring costs 7,871 in a core (6,194 plus ~1,700 for its dispatch), the controller 10,099, and each fixed-base machine 1,505 less than alone. All four land within 102 LUT. The fused 8,0,2,0 gets its own controller, 3,115, with no credit pool to keep.
- A side effect worth knowing: a SHA line set by hand far below what
it needs (2k) borrows LUTs back only when the engines leave some.
After this refit they used them all, and the screen said the line
limits both sides; with fix14’s constant ROM (step 12) fewer engines
fit and the line borrows again. “auto” sizes it correctly either way.
→
frontier::slimmed_costs,FB_IN_CORE_LUT,sign_controller_fused(2026-10-20).
- A bug the card found, and its price.
- Problem: the card rejected valid keys. Vivado had turned a
constant bank into LUT RAM and scrambled 16 bits of
d(see the Ed25519 crate’s chapter 11). - Idea: build the constants as a literal and a mux (fix14), and price the change.
- Result: the routed 1,1,1,1 verifier grew from 82,184 to 84,435
LUT: 2,251 across its three field machines, 750 each. The model
moves by the same 2,250, so this core keeps the error it had
before the fix. Every field machine now carries
CONST_ROM_LUT;predict_buildstakes it off for cores routed before fix14, so their errors still mean what they meant.
- Problem: the card rejected valid keys. Vivado had turned a
constant bank into LUT RAM and scrambled 16 bits of
- Timing on screen.
- Problem: the Tuner plans resources. A plan could look perfect and miss 205 MHz by a nanosecond.
- Idea: keep a table of every whole core routed on current RTL and show the verdict on the card.
- Result: each build ends with routed: meets 205 MHz (+0.159 ns,
007zzm fix13), routed in another layout: …, or timing: not
routed. Add a row to
ROUTEDincrates/ed25519/src/planner.rswhen a route lands.
§6. What is proven
Whole cores routed (LUT, predicted against routed; measure/accuracy.tsv):
| Build | RTL | Predicted | Routed | Error | WNS (ns) |
|---|---|---|---|---|---|
| sign 8,0,2,1 u6l0 (L100) | fix9 | 148,691 | 148,691 | 0.0 % (fitted) | +0.218 |
| verify 6,0,1,1 | fix9 | 150,198 | 150,200 | 0.0 % (fitted) | +0.043 |
| sign 12,0,3,1 u5l2 (L200) | fix9 | 255,219 | 255,209 | 0.0 % (fitted) | −0.358 |
| verify 24,0,2,1 u2l19 (L200) | fix9 | 840,023 | 839,728 | 0.0 % (fitted) | −5.763 |
| sign 9,0,2,1 | fix7 | 163,306 | 166,422 | −1.9 % | +0.114 |
| sign 12,0,3,1 u5l2 (L200) | fix10 | 255,219 | 254,817 | +0.2 % | +0.050 |
| verify 6,0,1,1 | fix11 | 150,198 | 150,873 | −0.4 % | −0.190 |
| verify 6,0,1,1 | fix12 | 150,198 | 151,645 | −1.0 % | +0.019 |
| verify 1,1,1,1 | fix12 | 84,058 | 82,184 | +2.3 % | +0.094 |
| verify 24,0,2,1 u2l19 (L200) | fix12 | 840,023 | 842,345 | −0.3 % | −2.225 |
verify 24,0,2,1 u2l19, route_hard | fix12 | 840,023 | 842,267 | −0.3 % | −1.047 |
verify 24,0,2,1 u2l19, route_hard | fix13 | 840,023 | 827,202 | +1.5 % | +0.008 |
| verify 12,0,1,1 | fix13 | 234,744 | 233,052 | +0.7 % | +0.031 |
| sign 4,0,1,1 | fix13 | 88,479 | 88,532 | −0.1 % (fitted) | +0.024 |
| sign 8,0,3,1 | fix13 | 156,661 | 156,664 | 0.0 % (fitted) | +0.159 |
| sign 8,0,2,0 (no compress machine) | fix13 | 125,237 | 125,237 | 0.0 % (fitted) | +0.397 |
| sign 8,0,2,1, 150-DSP multiplier | fix13 | 188,750 | 188,537 | +0.1 % | +0.103 |
| verify 1,1,1,1 (constant ROM) | fix14 | 86,308 | 84,435 | +2.2 % (ROM fitted) | +0.072 |
The fix9 rows and the fix13 signers are the fit, so 0.0 % there is by construction, not a test. The others are the test. The 12-machine verifier sits between the two fitted verifiers (6 and 24) and lands at +0.7 %. The signer rows above were predicted with the 2026-10-20 refit, and the older signers moved with it (8,0,2,1 +0.1 %, 12,0,3,1 0.0 %, the fix7 9,0,2,1 −2.7 %). The lean 8,0,2,1 was routed after the refit and was not part of it: +0.1 %. The 1,1,1,1 core shows the fit’s weak spot: with one machine, the fixed controller and key loader are most of the core, and the controller’s fixed and per-machine parts were fitted on 6 and 24 machines.
Throughput in simulation (native RHDL, 205 MHz assumed;
measure/native-results.tsv):
| Build | Simulated | Tuner | Model error |
|---|---|---|---|
| sign 8,0,2,1 (fix10) | 46.46 cycles = 4.41M/s | 4.06M/s | −8 % |
| sign 9,0,2,1 (fold10 line) | 4.58M/s | 4.57M/s | −0.2 % |
| sign 12,0,3,1 | 6.42M/s | 6.09M/s | −5 % |
| verify 6,0,1,1 (fix11) | 134.93 cycles = 1.52M/s | 1.50M/s | −1 % |
| verify 2,0,1,1 | 0.53M/s | 0.50M/s | −6 % |
| verify 14,0,1,1 | 3.68–3.75M/s (time-bounded) | 3.70M/s | ~0 % |
Not proven yet.
-
No whole chip has been routed: sign, verify and the SHA line together.
-
The L200 verifier (24,0,2,1) meets timing by only 0.008 ns, and only with the hard route directives, on fix13 (job 007zzq). It has not been routed on fix14, whose constant ROM adds LUTs (the 1,1,1,1 verifier lost 0.022 ns of slack to it), or with default directives. On fix12 it missed by 2.225 ns (007zzg), or 1.047 ns with the hard route directives (007zzj), with failures spread over all 24 machines. The Tuner plans resources, not timing; near ~65 % of the device read the timing verdict on screen as a measurement of one route, not a promise.
-
No Ed25519 build has run on the card.
-
DSig verification is a model. DSig is building it.
-
Latency is checked against simulation on four builds, not routed hardware:
Build Isolated (cycles) Saturated median / p90 Peak in flight sign 1,1,1,0 (small) 22,240 — — sign 8,0,2,1 40,446 (model 40,406) 45,431 / 55,844 1,026 (model 952) verify 1,1,1,1 52,670 63,026 / 81,738 86 (model 69) verify 6,0,1,1 52,350 (model 52,392) 54,533 / 72,184 404 (model 393) The Tuner shows the mean (Little’s law). At full load the p90 is 20–30 % above the median.
-
The strict and batch verify modes are models.
§7. Every cost constant
All costs are in crates/ed25519/src/frontier.rs. They are routed on the
fix9 RTL unless marked fitted or derived.
| Constant | Value | Unit | Source |
|---|---|---|---|
fb_machine | 14,615 / 225 / 25 / 32 | LUT / DSP / BRAM36 / URAM | routed 007zq, +0.848 ns |
verify_machine_known_key | 13,863 + 228 / 225 / 25 / 56 | same | routed 007zt; +228 fitted |
cp_machine | 16,569 / 225 / 2 / 64 | same | routed 007zu |
key_loader | 14,518 / 225 / 10 / 48 | same | routed 007zv |
sha_ring | 4,315 | LUT | routed 007zx |
scalar_ring | 7,871 | LUT | fitted to 4 signers (2026-10-20) |
sign_controller | 10,099 | LUT | fitted to 4 signers |
sign_controller_fused | 3,115 | LUT | fitted to 8,0,2,0 (one core) |
FB_IN_CORE_LUT | −1,505 | LUT per fixed-base machine | fitted to 4 signers |
CONST_ROM_LUT | 750 | LUT per field machine | fix14, one core (007zzr against 007zzi) |
verify_scalar_ring | 2,408 | LUT | fitted with the verify controller (2026-10-18) |
verify_controller | 30,657 | LUT | fitted |
VERIFY_PER_MACHINE_LUT | 228 | LUT per verify machine | fitted |
bram_layout (fb / verify) | 14,615 / 57 BRAM36 / 0 URAM; 14,094 / 57 / 24 | routed 007zy, 007zz | |
LUT_BANKS_LUT | 22,830 | LUT per machine | routed 007zza, 007zzb |
lean_mul | +4,440 / −75 | LUT / DSP per multiplier | routed before the fixes |
small_signer | 29,055 / 248 / 40 / 32 | routed (fix4), +0.306 ns | |
SHA_CLIENT_LUT | 1,500 | LUT per core | estimate |
verify_machine_strict | fb + 2,500 LUT, +22 BRAM36 | derived, not built |
The budget constants:
| Constant | Value | Why |
|---|---|---|
BASE_LUT / BASE_DSP | 420,000 / 4,500 | the engines’ budget; 485k / 4,520 includes the 65k / 20 SHA line default |
| dynamic-region BRAM36 | 1,776 | the shell’s region (3,552 BRAM18), not the device’s 2,016 |
U280_URAM | 960 |
§8. Limits
- DSig slice counts. Plans with 3 or 6 slices are marked not
buildable:
DSIG_SLICESbuilds 2, 4 or 8. - Lending is greedy. It is checked by the scaling tests, not proven optimal.
- One RTL at a time. The costs describe fix9 to fix12. Change the RTL and the costs must be refitted. The out-of-sample check is the alarm.
§9. Working on it
- Change a cost in
frontier::slimmed_costs, and write its source next to it (job, date, WNS). - Run
cargo run --release --example build_configs -- measure/configsto regenerate the grid andpredictions.tsv. - Export and route a plan (
measure/README.md). - Run
python3 measure/pull_results.pyandcargo run --release --example predict_builds. Read the error column. - A fitted number is only trusted once a core it was not fitted to lands within a few percent.
Help wanted. Each of these is pure Rust and needs no FPGA:
- Timing from the table.
ROUTEDis typed in by hand. Generate it frommeasure/results.tsvinbuild.rs, so a pulled route shows up on screen with no edit. - The one-machine fit. The 1,1,1,1 verifier is +2.3 % LUT and +4.5 % URAM, the weakest prediction. Fit the controller’s fixed part on a one-machine core and check that the other cores stay within 1 %.
- Latency percentiles. The Tuner shows the mean loaded latency. The
measured p90 is 20–30 % higher. Fit a p90 factor from the four runs in
section 6 and show it beside the mean.
ED25519_ISOLATED=1and the saturated runs print every number you need.
§10. Glossary
| Term | Meaning |
|---|---|
profile a,b,c,d | sign: field machines, SHA rings (0 = shared line), scalar rings, compress machines; verify: machines, SHA rings, scalar rings, compress (always 1) |
uNlM | N machines with banks in UltraRAM, M in LUT RAM, the rest in block RAM |
| fixN | the Nth timing-fix revision of the RTL (fix9 met 205 MHz on the L100 cores) |
| OOC | Vivado out of context: one module routed without the shell |
| WNS | worst negative slack; ≥ 0 meets 205 MHz |
| measured / model | routed or simulated / computed from the cost tables or cycle model |
| fitted | solved from routed whole cores (total less routed blocks) |
| buildable | the RTL can build exactly this plan |
| next | the LUTs the next faster build needs, and what it delivers |
§uBFT evidence boundary
The uBFT slot panel reports projected cryptographic time only. It excludes network, memory visibility, recovery and application work. It does not display a speedup against the HLS wrappers: those measurements use a one-byte message, whereas a selected service may use a different message layout and key policy. The historical 161.3 MHz value is the linker-selected frequency after timing failure, not a runtime readback. The historical whole-design LUT/DSP total includes the shell and is only a planning allowance for a replica; its missing BRAM/URAM values are unknown, not measured zeros. A plan fitting these numbers does not establish that a complete RHDL system fits the card.
Structs§
- App
- The planner’s state.
Enums§
- Control
- One row of the control list.
Functions§
- draw
- Draw one frame.
- next_
step - The next faster build of
slotwith more LUTs and everything else as inshare:(extra LUTs, its capacity), probing in 1k steps up to 64k. Builds come in whole blocks (a field machine is ~11k LUTs), so a rate that stays flat while LUTs grow is waiting for this step. - run
- Run the planner in the terminal.
- run_on
- Run the planner on any backend until Q.
- snapshot
- Render
appinto text (for tests and documentation).