Expand description
§Architecture and Contributor Guide
This document explains the current workspace as of 2026-07-12. It is the
canonical guide to the project structure, fast signing pipeline, parallelism,
cycle counts, and contribution workflow. HANDOFF.md is a historical snapshot
of the earlier compatibility-core work and should not be used for current fast
backend performance claims.
§1. What This Project Is
The project is an Ed25519 hardware backend derived from the behavior of Curve25519 Dalek. Dalek remains the software oracle; it is not instantiated in the synthesizable datapath. Hardware owns SHA-512 padding, key expansion, scalar arithmetic, Edwards arithmetic, point encoding, and signature assembly.
There are two hardware tracks because compatibility and throughput currently need different architectures.
§Compatibility backend
The compatibility backend is the general RHDL implementation. It supports:
- public-key derivation from a 32-byte RFC 8032 seed;
- cold signing and cached-key signing;
- strict signature verification;
- arbitrary-length and multipart message streams;
- a 4 KiB on-chip message replay cache;
- explicit cached-key clearing and error codes.
It is the semantic integration path for the Dalek-facing API. It is also useful for cycle-accurate RHDL simulation and Verilog generation. It is not the current high-throughput U280 datapath.
§Fixed-64 fast backend
The fast backend specializes one important workload: saturated cold signing of exactly 64 message bytes on an AMD Alveo U280. It trades generality for parallelism:
- the seed and complete 64-byte message arrive in one request;
- 32 top-level contexts can be in flight;
- duplicated workers handle independent cryptographic roles;
- DSP-heavy radix-17 multiplication is fully pipelined;
- signed multi-comb multiplication reduces sequential point work;
AandRare compressed together with one batch inversion.
The fast backend currently signs only. It does not yet implement the compatibility backend’s arbitrary message lengths, warm signing, verification, clear-key operation, or streaming transport.
§2. Ed25519 Dataflow
For seed sk, message M, basepoint B, field modulus p = 2^255 - 19, and
group order l, signing computes:
digest = SHA512(sk)
a = clamp(digest[0..32])
prefix = digest[32..64]
A = encode([a]B)
r = reduce_l(SHA512(prefix || M))
R = encode([r]B)
k = reduce_l(SHA512(R || A || M))
S = r + k*a mod l
public key = A
signature = R || SThe dependency graph matters more than the textual order. Once the seed hash
finishes, [a]B and the nonce hash can run concurrently. Once r is reduced,
[r]B can run while [a]B is still in flight. The challenge hash must wait for
both compressed points, and the final multiply-add must wait for reduced k.
flowchart LR
IN["seed + 64-byte message"] --> H0["SHA512(seed)"]
H0 --> A["clamp a"]
H0 --> P["prefix"]
A --> MA["multi-comb [a]B"]
P --> HR["SHA512(prefix || M)"]
HR --> RR["reduce r mod l"]
RR --> MR["multi-comb [r]B"]
MA --> C["paired compression"]
MR --> C
C --> EA["A"]
C --> ER["R"]
EA --> HK["SHA512(R || A || M)"]
ER --> HK
HK --> RK["reduce k mod l"]
RK --> S["S = r + k*a mod l"]
RR --> S
A --> S
S --> OUT["A and R || S"]
ER --> OUT§3. Fast Backend Pipeline
The fast top is
crates/rhdl_ed25519_fast_sign/rtl/fast64_sign_core.sv. It is a tagged task
pipeline rather than a single instruction pipeline. Each accepted request is
assigned a context. The context stores its seed, message, intermediate digests,
scalars, projective points, encoded points, and pending-work bits.
Workers pull ready tasks from those pending bits. A returned result carries a context tag, allowing the top to update the correct context even when another signature completes a different stage in the same cycle.
flowchart TB
Q["32 signature contexts"]
Q --> S0["SHA lane 0"]
Q --> S1["SHA lane 1"]
Q --> R0["nonce reducer"]
Q --> R1["challenge reducer"]
Q --> P0["[a]B multi-comb lane"]
Q --> P1["[r]B multi-comb lane"]
P0 --> PC["12-context paired compressor"]
P1 --> PC
Q --> SM["scalar multiply-add"]
S0 --> Q
S1 --> Q
R0 --> Q
R1 --> Q
PC --> Q
SM --> O["tagged public key + signature"]§Latency versus throughput
Three timing terms recur throughout the code:
- Latency is the delay from one accepted operation to its result.
- Initiation interval (II) is the number of cycles before that worker can accept another independent operation.
- Service interval is the long-run number of cycles per completed signature after all dependencies and shared resources are considered.
The field multiplier demonstrates why the distinction matters. Its latency is 45 cycles, but its II is one cycle. One dependent point context cannot use the next result until 45 cycles later. Several independent contexts can fill those otherwise empty issue slots.
§4. How The Pipelining Works
§Radix-17 field multiplication
Field elements are fifteen little-endian 17-bit limbs. Fifteen limbs exactly cover 255 bits, and 17-bit operands map naturally to DSP48E2 multipliers.
radix17_field_mul_pipe.sv computes a 15-by-15 wrapped convolution:
- 225 limb products are arranged as 15 systolic dot-product rows;
- products that wrap past bit 254 are multiplied by 19 because
2^255 = 19 mod p; - the MAC array is followed by two registered 15-level carry passes;
- a final conditional subtraction emits a canonical field element;
- the complete path has 45 cycles of latency and accepts one multiply per cycle.
Vivado maps one integrated field multiplier to 240 DSP48E2 blocks in the latest report. The extra blocks beyond the 225 plain limb products come from wrapped terms and implementation mapping.
Field addition and subtraction use three registered stages: raw arithmetic, high-bit folding, and conditional subtraction. They also accept one operation per cycle.
§Signed multi-comb fixed-base multiplication
Both [a]B and [r]B use multicomb_mul_stream.sv. The current table has four
blocks, eight teeth, and spacing eight. A scalar multiplication performs:
- 32 signed table lookups and mixed additions;
- seven point doublings between comb rounds;
- 280 field multiplications in total:
32 * 7 + 7 * 8.
Each point lane owns eight interleaved contexts and one II=1 field multiplier. The scheduler issues work from another context while a dependent result is moving through the 45-cycle multiplier.
The table lookup is constant-pattern. Each lookup scans 32 public groups in four physical BRAM banks. The secret candidate does not control a BRAM address, bank enable, scan count, or loop exit. Secret magnitude and sign are applied after the reads.
One point lane has 48,960 logical table bytes. Two point lanes therefore have 97,920 logical bytes and synthesize to 88 RAMB36 tiles because the 765-bit word width causes physical BRAM fragmentation.
§Three-phase SHA-512
sha512_compress_3phase.sv splits every SHA-512 round into three registered
phases:
- Compute the choice/majority and sigma terms and read the round constant.
- Finish message-schedule expansion and register
T1/T2partials. - Update the eight working words.
The schedule uses a 16-word circular window instead of storing all 80 expanded words. One compression block occupies a lane for 242 cycles. The top contains two independent lanes and constructs all padding blocks in hardware.
§Fixed-trip-count scalar arithmetic
The nonce and challenge reducers each consume a 512-bit digest in two phases
per input bit: calculate, then commit. Each reduction therefore takes 1,024
cycles. There are separate reducer instances for r and k.
The final r + k*a mod l lane first performs eight two-cycle normalization
steps and then 256 two-cycle multiply-add steps, for 528 cycles total. The loops
have fixed public trip counts.
§Paired point compression
Edwards-Y compression needs affine x = X/Z and y = Y/Z, so the expensive
operation is inversion. Compressing A and R separately would perform two
inversions. point_compress_pair_stream.sv instead computes one batch inverse:
z_product = Za * Zr
inverse = 1 / z_product
1 / Za = inverse * Zr
1 / Zr = inverse * ZaThe fixed chain and affine products require 272 field multiplications per point pair. Twelve contexts share one II=1 field multiplier. This is another latency hiding scheduler: one context waits on a dependency while another enters the multiplier.
§5. Levels of Parallelism
The design uses parallelism at four different levels.
§Within one field multiplication
The 15-by-15 convolution is spatially distributed across DSP MAC cells. This is fine-grained arithmetic parallelism.
§Across dependent point operations
Eight multi-comb contexts and twelve paired-compression contexts interleave on shared field multipliers. This is latency hiding, not duplicated arithmetic.
§Across cryptographic roles
The top duplicates workers where every signature needs two independent jobs:
| Role | Instances | Reason |
|---|---|---|
| SHA-512 compression | 2 | Four blocks per cold signature would bottleneck one lane |
| Wide scalar reduction | 2 | Nonce and challenge reductions have separate long service times |
| Fixed-base multiplication | 2 | [a]B and [r]B can overlap |
| Paired point compression | 1 | Batch inversion handles both points together |
| Scalar multiply-add | 1 | One 528-cycle operation per signature is below the main service interval |
§Across complete signatures
The top keeps 32 signatures in flight. Pending bits act like a hardware scoreboard. Small round-robin candidate scans choose work for each worker, and tags reconnect each completion to its owning context. This level is what lets the cryptographic dependency graph overlap across many messages.
When changing an arbiter, test both throughput and starvation. The current arbiters intentionally inspect a small candidate set per cycle to avoid a large priority tree in the timing path.
§6. Cycle Breakdown
This section records the cycle model and the latest focused test run on 2026-07-12. Cycle counts are clock-independent; rates require an assumed or closed clock frequency.
§Primitive and stage costs
| Stage | Work per cold 64-byte signature | Cycles | Parallelism or interpretation |
|---|---|---|---|
| Field add/sub | As required by point builders | latency 3, II 1 | Four add/sub lanes in each multi-comb point engine |
| Field multiply | As required by point/inversion chains | latency 45, II 1 | 240 DSPs per integrated multiplier |
| Seed hash | 1 SHA-512 block | 242 | Scheduled on either of two SHA lanes |
| Nonce hash | 1 SHA-512 block | 242 | Can overlap [a]B for another/current context |
| Challenge hash | 2 SHA-512 blocks | 484 | Second block continues from first block’s state |
| Total SHA work | 4 blocks | 968 lane-cycles | Two lanes give 484 cycles/signature of aggregate service demand |
| Nonce reduction | 1 wide digest | 1,024 | Dedicated r reducer |
| Challenge reduction | 1 wide digest | 1,024 | Dedicated k reducer |
[a]B fixed-base lane | 1 scalar | 1,104.583 modeled service | Eight interleaved contexts |
[r]B fixed-base lane | 1 scalar | 1,104.583 modeled service | Separate eight-context lane, so it overlaps [a]B |
Paired A,R compression | 1 point pair | 1,088.060 modeled service | Twelve contexts, 272 field products/pair |
| Final scalar multiply-add | 1 r + k*a | 528 | One lane |
The multi-comb and paired-codec numbers come from 20,000-completion scheduler tests:
8 contexts: 1104.583 cycles/scalar, field 0.253, lookup 0.956, builder 0.424
paired codec: 1088.060 cycles/signature, 229767 signatures/s at 250 MHzDo not add the rows to estimate end-to-end latency. Stages overlap across contexts, and several rows represent duplicated workers running concurrently.
§Latest integrated RTL run
The current rtl_fast64_sign_sustained test compiled the fast top with
Verilator, submitted 64 deterministic seed/message pairs, and compared every
public key and signature with pinned Dalek.
| Measurement | Result |
|---|---|
| Correct signatures | 64 / 64 |
| First output observed | cycle 24,182 |
| Final output observed | cycle 99,144 |
| Warmup outputs excluded | 32 |
| Measured output span | 33,051 cycles |
| Completion intervals in span | 31 |
| Average interval in measured span | 1,066.161 cycles/signature |
| Largest interval after warmup | 1,544 cycles |
| Finite-window projection at 250 MHz | 234,486 signatures/s |
The 234,486 signatures/s value is a projection from RTL cycles. It is not a 250 MHz timing-closure result and not an FPGA benchmark. The finite test can also drain work accumulated before the measurement window. For long-run planning, the eight-context fixed-base scheduler’s 1,104.583 cycles/scalar and the conservative 1,110-cycle service budget are safer current estimates. They project to roughly 226,330 and 225,225 signatures/s at 250 MHz, respectively.
§Clock and synthesis status
The latest archived integrated OOC synthesis report is
reports/generated/vivado_u280_fast64_sign_ctx32_point8_priority_300/:
| Item | Result |
|---|---|
| Device | xcu280-fsvh2892-2L-e |
| Requested clock | 300.03 MHz / 3.333 ns |
| WNS | -1.078 ns |
| LUTs | 360,288 |
| Registers | 154,783 |
| RAMB36 | 90 |
| DSP48E2 | 720 |
| URAM | 0 |
This is an optimized out-of-context synthesis result, not post-route timing. The design fits the U280’s absolute capacity but misses 300 MHz and exceeds the earlier 260,000-LUT project budget. There is no current evidence that the integrated design closes 250 MHz. Always report the cycle projection and the clock evidence as separate facts.
§7. Memory and Access Pattern
The fixed-64 core uses no external HBM or DDR as arithmetic scratch storage.
Its core-level command payload is 96 bytes (seed + message), and its result
payload is 96 bytes (public key + signature). A Vitis shell may add aligned
records and data-mover traffic; that traffic is not represented by the core’s
internal access count.
The main local memory is:
| Storage | Logical size | Latest physical result |
|---|---|---|
[a]B multi-comb table | 48,960 bytes | 44 RAMB36 |
[r]B multi-comb table | 48,960 bytes | 44 RAMB36 |
| Two SHA constant ROMs | 2 x 5,120 bits | 2 RAMB36 in integrated synthesis |
| Fast top context state | registers/LUTRAM | included in top LUT/FF totals |
The two point tables are duplicated so [a]B and [r]B can run concurrently.
The table is much smaller than a large host-style precomputed table, but its
wide constant-scan layout consumes more physical BRAM than its logical byte
count suggests.
§8. Workspace Structure
§Compatibility and API crates
| Crate | Responsibility |
|---|---|
rhdl_ed25519_api | Hardware transport trait plus Dalek Signer, Verifier, MultipartSigner, and MultipartVerifier facades |
rhdl_ed25519_model | Pinned-Dalek oracle, vectors, strict verification, and multipart reference behavior |
rhdl_ed25519_types | Wire-level command, message, pass-request, result, and error types |
rhdl_ed25519_sha512 | General streaming SHA-512 engine |
rhdl_ed25519_hash_feeder | Prefix insertion, framing, and 4 KiB message replay cache |
rhdl_ed25519_field | Compatibility radix-2^25.5 field arithmetic |
rhdl_ed25519_scalar | Compatibility scalar reduction, canonicality, recoding, and multiply-add |
rhdl_ed25519_point | Extended Edwards point operations |
rhdl_ed25519_scalar_mul | Compatibility fixed-pattern scalar multiplication controller |
rhdl_ed25519_point_codec | Point compression and decompression |
rhdl_ed25519_controller_types | Top-level controller state and handshake types |
rhdl_ed25519_transition | High-level next-state kernel |
rhdl_ed25519_commands | Commands sent to child arithmetic engines |
rhdl_ed25519_registers | Cache/work/point register updates |
rhdl_ed25519_result | Result and error assembly |
rhdl_ed25519_top | Compatibility-core composition and end-to-end tests |
rhdl_ed25519_sim | Cycle simulation, traces, checksums, and Verilog export |
The compatibility controller is partitioned across crates to keep RHDL macro expansion and Rust memory use manageable. It is one logical controller even though its transition, command, register, and result kernels compile separately.
§Fast backend crates
| Crate | Responsibility | Primary current RTL |
|---|---|---|
rhdl_ed25519_fast_field | Radix-17 field types and DSP black-box wrapper | radix17_field_mul_pipe.sv, radix17_field_addsub_pipe.sv |
rhdl_ed25519_fast_fixed_base | Generated tables, constant scan, and signed multi-comb scheduler | multicomb_lookup_rom.sv, multicomb_mul_stream.sv |
rhdl_ed25519_fast_sha512 | Fixed-64 SHA compression lane | sha512_compress_3phase.sv |
rhdl_ed25519_fast_point_codec | Single and paired projective-point compression | point_compress_pair_stream.sv |
rhdl_ed25519_fast_scalar | Wide reduction and final multiply-add | scalar_arithmetic_2phase.sv |
rhdl_ed25519_fast_sign | Context storage, scoreboarding, arbitration, and result assembly | fast64_sign_core.sv |
basepoint_lookup_rom.sv, fixed_base_mul_stream.sv, and
radix16_recode_stream.sv are predecessor radix-16 experiments. The integrated
fast top uses the multicomb_* modules and the
assets/multicomb_t8_n4_s8/ table.
§Tooling and evidence
| Path | Responsibility |
|---|---|
tools/basepoint_table_gen | Regenerates and checksums tables from pinned Dalek constants |
tools/vivado | OOC synthesis and implementation Tcl scripts |
bench | Common XRT host, AMD Vitis Security wrapper, and RHDL RTL shell |
reports/generated | Generated simulations and synthesis reports; never hand-edit |
upstream | Pinned source snapshots; treat as read-only |
§9. Contributor Workflow
§Choose the backend first
Make compatibility changes in the non-fast crates. Make fixed-64 throughput
changes in the fast crates. A fast-path optimization does not automatically
become a compatibility-core feature.
§Read in this order
For the fast path:
crates/rhdl_ed25519_fast_sign/rtl/fast64_sign_core.svcrates/rhdl_ed25519_fast_sha512/rtl/sha512_compress_3phase.svcrates/rhdl_ed25519_fast_scalar/rtl/scalar_arithmetic_2phase.svcrates/rhdl_ed25519_fast_fixed_base/rtl/multicomb_lookup_rom.svcrates/rhdl_ed25519_fast_fixed_base/rtl/multicomb_mul_stream.svcrates/rhdl_ed25519_fast_point_codec/rtl/point_compress_pair_stream.sv- The corresponding
tests/scheduler and RTL tests
For the compatibility path, begin with rhdl_ed25519_types, then
rhdl_ed25519_controller_types, rhdl_ed25519_top, and the arithmetic child
crates.
§Test from narrow to broad
# Software oracle and trait facade
cargo test -p rhdl_ed25519_model --release -j 1
cargo test -p rhdl_ed25519_api --release -j 1
# Fast arithmetic and RTL blocks
cargo test -p rhdl_ed25519_fast_field --release -j 1
cargo test -p rhdl_ed25519_fast_fixed_base --release -j 1
cargo test -p rhdl_ed25519_fast_sha512 --release -j 1
cargo test -p rhdl_ed25519_fast_scalar --release -j 1
cargo test -p rhdl_ed25519_fast_point_codec --release -j 1
# Explicit cycle models
cargo test -p rhdl_ed25519_fast_fixed_base --release \
--test multicomb_scheduler_model -- --nocapture
cargo test -p rhdl_ed25519_fast_point_codec --release \
--test scheduler_model -- --nocapture
# End-to-end fixed-64 correctness and finite sustained window
cargo test -p rhdl_ed25519_fast_sign --release \
--test rtl_fast64_sign_sustained -- --nocaptureKeep -j 1; .cargo/config.toml also sets one build job. Verilator tests set
their own compile parallelism to one.
§Regenerate tables deliberately
The multi-comb assets are generated from the pinned Dalek constants. Do not
edit .mem files by hand.
cargo run -p basepoint_table_gen --release -j 1Review the generated manifest, logical size, and SHA-256 values. Then rerun lookup, scalar-multiplication, and end-to-end signature tests.
§Preserve constant-pattern behavior
For secret-dependent code, review emitted RTL and tests for all of the following:
- memory addresses are public and follow the same sequence for every scalar;
- bank enables do not depend on a secret digit;
- loop counts and valid timing are fixed;
- zero and nonzero digits execute the same point-operation schedule;
- output tags do not expose secret-dependent stalls.
§Treat performance evidence as a stack
A performance claim needs all layers below it:
- Arithmetic or Dalek differential correctness.
- Scheduler-model service interval.
- RTL simulation with every output validated.
- OOC synthesis resource and timing report.
- Post-route timing closure.
- U280 execution and XRT/Vitis profiling.
Do not combine a cycle result from one revision with a clock report from another revision. Do not call a 250 MHz cycle projection a 250 MHz FPGA result.
§10. Terms To Use When Discussing The Design
- Compatibility core: the general RHDL derive/sign/verify implementation.
- Fast fixed-64 backend: the specialized cold signer for 64-byte messages.
- Context: per-signature state retained while workers process other jobs.
- Lane: a physical worker instance, such as one SHA compressor or one point multiplier.
- II=1 field multiplier: 45-cycle result latency with a new independent input accepted every cycle.
- Signed multi-comb: the fixed-base scalar multiplication method using the four-block, eight-tooth precomputed table.
- Constant scan: every secret lookup reads the same public address sequence and selects after memory access.
- Batch inversion: compressing
AandRwith one inversion ofZa*Zr. - Cycle projection: simulated cycles divided into an assumed clock.
- Closed throughput: sustained hardware throughput after post-route timing closure and output validation.
The short project description is: the fast backend combines a DSP-pipelined radix-17 field multiplier, constant-scan signed multi-comb point multiplication, context interleaving, duplicated cryptographic workers, and paired point compression to overlap many complete Ed25519 signatures without external scratch memory.