Skip to main content

Module optimization

Module optimization 

Source
Expand description

Beginner-oriented optimization workflow, resource budgeting, and experiments.

§Optimization Lab

§Start Here: FPGA In Ten Minutes

An FPGA is not a processor running Rust instructions. RHDL turns restricted Rust functions into circuits. A b17 * b17 expression can become a DSP multiplier; a DFF stores a value at each rising clock edge; a SyncBRAM stores many values with a registered read. A loop with a fixed bound usually builds repeated hardware, not a software loop that takes many cycles.

Four quantities matter:

QuantityMeaningCommon mistake
LatencyCycles from accepting one request to its resultCalling its reciprocal throughput
Initiation interval (II)Average cycles between requests in a busy pipelineMeasuring only a short completion burst
ClockRising edges per second after timing closureAssuming the requested clock was achieved
OccupancyIndependent requests held in flightAdding arithmetic lanes without enough contexts

At an assumed 205 MHz, 2M signs/s needs II <= 102.5 cycles. 4M needs 51.25 cycles and 5M needs 41 cycles. These are design requirements, not measurements. For latency L and target interval I, start with at least ceil(L/I) contexts, then add room for queueing and uneven stage service.

LUTs implement logic and multiplexers. DSPs perform registered arithmetic. Flip-flops (FFs) hold individual bits. BRAM holds arrays; read ports and width packing determine its cost. More pipeline registers can increase clock but also increase latency and the contexts needed to hide it. Registers are not free, and DSPs cannot replace every LUT in a controller.

“LUT” can mean two different things here. An FPGA logic LUT implements a Boolean function; a cryptographic lookup table stores point data and can live in BRAM. Budget them separately. The wide topology has three fixed-base table instances with 9,400,320 initialized payload bits before BRAM packing.

There is no DRAM/HBM scratch port in Fast64Core. Each cached sign consumes one 64-byte message and returns one 64-byte signature; its three SHA blocks reuse local state. At 2M/s that is 128 MB/s of message payload and 128 MB/s of signature payload. These are interface-level arithmetic counts, not measured AXI/HBM traffic: command metadata, burst padding, PCIe transfers, and the surrounding controller can add traffic.

§Implemented Experiment

The default wide-sign feature replicates only the signing bottlenecks:

Signing stageCompact (--no-default-features)Wide (default)
SHA-512 compressors48
Multi-comb engines12
Pair-compression engines12
Scalar reduction/multiply-addshared pipelinessame pipelines
Strict verifierunchangedunchanged
Signing request contexts128128
Structural DSP estimate, whole core5,6906,440

The DSP row counts 150 partial products per field multiplier, 576 per scalar reducer, and 512 in the wide scalar multiplier. Constant propagation and technology mapping can change this count. It is not a utilization report. Wide adds five field multipliers, not a second entire signer. Completion mergers have BRAM queues and outstanding-credit limits; tags survive arbitrary interleaving. parallel.rs is the place to study that change.

The ideal SHA-only throughput ceiling rises from approximately 1.129M to 2.259M/s at 205 MHz, using the 242-cycle compression latency. End-to-end throughput can be lower because of acceptance bubbles, point arithmetic, compression, context starvation, or scheduler bubbles.

The first wide experiment retained 64 contexts. All 192 signatures matched Dalek, but its short-window rate was burst-biased. The logged completion-64/128 window gives 149.75 cycles/sign. Doubling capacity to 128 then passed 384 distinct signatures; a conservative 128-interval middle window gives 90.984375 cycles/sign, or 2.253M/s at an assumed 205 MHz. Raw edges and the corrected window analysis are in reports/bench. This is native cycle evidence, not a routed clock or full-core resource-fit result.

§Six Tiles Instead Of Nine

The DSP48E2 multiplier accepts signed 27-bit and 18-bit operands. Nonnegative 26-bit and 17-bit operands fit after zero extension. Split a 51-bit limb as a = a0 + 2^26*a1 and b = b0 + 2^17*b1 + 2^34*b2. Their product has only six partial products. Each is at most 43 bits, each row at most 77 bits, and the final product at most 102 bits. The six registered stages are split, multiply, row-low/high, row-merge, final-merge, and output delay. Keeping the latency unchanged preserves every downstream valid/tag alignment.

This replaces the previous nine-tile cell without changing field radix or the external interface. It reduces the wide full-core structural count from 8,540 to 6,440. A leaf mapping test must confirm inference; full-core synthesis must still measure LUTs, FFs, BRAM packing, and timing.

The first Yosys 0.62 xcup leaf experiment measured 6 DSP48E2, 155 LUT2, and 928 FDRE for this cell. LUT-only mapping measured 7,642 LUT primitives and 928 FDRE, plus carry and wide-mux primitives. These are one-cell counts, not a full-core estimate suitable for the TUI’s measurement import.

An important mapper limitation appeared: AREG/BREG/MREG/PREG stayed zero. The upstream Yosys 0.62 packing pass performs its general input/multiply/output register packing for xc7, not xcup. See the upstream pass. Naively repeating 928 external FFs across the wide core’s 700 limb cells gives 649,600 FFs, already above the 520k cap. Do not call this mapping a full-core fit. Check Vivado DSP-register absorption first, then reduce redundant data delays and register-file costs if the complete mapped design still exceeds cap.

§Resource TUI

Build and launch from the project directory:

bash tools/bounded.sh cargo build --features tuner --bin resource_tuner -j1
target/debug/resource_tuner

Use Up/Down to select LUT allowance, DSP allowance, assumed clock, target, or implemented topology. Left/Right changes the selected value. L toggles linked sliders. S saves a plan and R reloads measurements. Q exits.

Percentages are allowances of the project’s caps, not utilization claims. Independent mode permits 100% LUT plus 100% DSP. Moving below a combined 100% raises the other slider. Linked mode always totals 100%. A 0-DSP/100-LUT plan requests LUT-only synthesis; it may exceed the LUT budget and be rejected. A 100-DSP/0-LUT plan is a valid slider endpoint but cannot fit a real core’s control logic. The tool never promises otherwise.

Headless examples:

target/debug/resource_tuner --search --dsp 75 --lut 100
target/debug/resource_tuner --write --out reports/tuning/dsp75 --dsp 75 --lut 100
target/debug/resource_tuner --import reports/tuning/dsp75/measured.json

The saved plan.json contains the exact export command. Run it under the bounded wrapper, then run the generated synth.tcl on a reserved Vivado host. Vivado’s -max_dsp constrains mapping; a post-synthesis count rejects an oversized result. Synthesis timing is not routed timing. The generated record leaves achieved_mhz and sign_ii null until measured separately.

The TUI initially has no full-core mapped LUT/FF data. It reports unknowns. After two observations at different DSP limits, it interpolates the LUT tradeoff between those limits. Extrapolation is deliberately refused. Source fingerprints stop old netlist measurements being silently reused after edits. An interpolated candidate still requires synthesis; only an exact matching mapped observation can receive the mapped fit label. This is a small, auditable heuristic search, not an opaque agent that launches expensive jobs.

§Reproducible Optimization Loop

  1. Pick one bottleneck using sustained measurements, not intuition alone.
  2. State a hypothesis: e.g. doubling SHA lanes should halve the hash service interval, but cannot improve a compression-limited stream.
  3. Change the RHDL module and add a tag/backpressure/differential regression.
  4. Run native simulation under the memory/CPU cap. Use CycleSim for long runs; use tracing only for short waveform captures.
  5. Measure steady state after warmup and before drain. Check every result against Dalek outside any hardware-timing interval.
  6. Compile the changed leaf, then the composed top. Syntax acceptance alone does not prove equivalence or timing.
  7. Synthesize at several DSP ceilings. Import measured counts into the tuner.
  8. Route promising candidates; check FF/LUT/DSP/BRAM, WNS, congestion, and actual clock. Re-run functional RTL tests after any mapping change.
  9. Keep the smallest candidate meeting the measured target. Archive rejected candidates’ reason, source hash, command, and resource peak.

Long native benchmark (ignored in the quick suite):

bash tools/bounded.sh cargo test --profile sim --lib --locked -j1 \
  sign::tests::sustained_signing_matches_dalek -- --ignored --exact --nocapture

This writes reports/bench/native-wide.json. Repeat with --no-default-features for compact. The bench checks all signatures, excludes 128 warmup completions and 128 drain completions, and labels MHz as assumed. It also records every acceptance/completion edge and context-full cycles. The interval starts at the last warmup completion, including the boundary gap before the first measured completion. Recheck raw records with jq -f tools/analyze_throughput.jq reports/bench/native-wide.json. Increase ED25519_BENCH_COUNT to check convergence of the interval, not just the correctness of a single burst.

§Next Avenues, In Order

Before adding more lanes, resolve the FF mapping issue above. In particular, the two-write encoded-R register file has 32,768 data FFs. Serializing paired completion writes into two mirrored BRAM read copies could replace most of them, but requires a bounded completion queue and explicit write-commit acknowledgements before the scheduler exposes each context to hashing.

  1. Context capacity: measure the time spent at zero free contexts. A wider pipeline can be starved by the same old request-context limit. Add BRAM contexts and widen tags only with wraparound and mixed-traffic tests.
  2. Inversion batching: pairs amortize inversion. Batches of 4 or 8 can reduce field multiplies per encoded point more cheaply than duplicating an entire point engine. Account for tail latency and zero-Z handling.
  3. Table service: the constant-address multi-comb scan takes four cycles. More banks or a different comb geometry can improve it, but replicated BRAM and selection fanout must be mapped. Never use secret RAM addresses.
  4. Scalar overprovisioning: II-one reducers have far more throughput than a roughly 100-cycle signer needs. Time-sharing a pipelined scalar datapath could recover DSPs for point operations. Preserve reduction/canonical rules.
  5. DSP-aware arithmetic: compare current 17-bit tiles with DSP48E2-friendly asymmetric products and fused multiply-add stages. Prove carry bounds before changing radix. Benchmark squaring separately from multiplication.
  6. Scheduler timing: hierarchical arbitration and registered operand selection can shrink large mux cones. More stages require retiming tags and credits, not merely inserting DFFs into data paths.

With the six-tile cell, three signing ways give a structural estimate of 7,190 DSPs; four ways give 7,940. That is below 9,000, opening a concrete 4M/s research path that the previous nine-tile arithmetic could not afford. It does not establish LUT/FF fit. Four-way SHA alone has an ideal ceiling of about 4.52M/s at 205 MHz; 5M/s needs at least 226.875 MHz before any scheduler or point-arithmetic overhead. Research rows in the TUI are bounds, not exportable implementations. No physical fit or 2M/4M/5M claim follows from them.

§Safe Key Lifecycle

The current interface has no cancellation result. Drain accepted signing requests before clear/reload, and do not clear while key expansion is in flight. clear_key clears cached fields, not every intermediate pipeline register. For tenant changes, apply a full synchronous reset after draining. Constant-pattern arithmetic is not proof of resistance to power/EM leakage.

§DSig Integration

The companion DSig project may sign a versioned 64-byte certificate envelope: domain8, version/profile4 LE, batch-size4 LE, epoch8 LE, batch-id8 LE, root32. Words512::from_le_bytes packs that exact message and does not prehash it. The caller owns domain separation and batching semantics. This is not byte compatible with a protocol that signs only a 32-byte root. Report online one-time signatures and background Ed25519 certifications as distinct rates.