Expand description
§Dalek-compatible Ed25519 hardware in RHDL
This crate is the hardware facade for the workspace. Its modules re-export
the real SHA-512, field, scalar, point, message-feeder, and wire-type crates
used by the synthesizable design. It contains no Dalek implementation code:
the rhdl_ed25519_model crate uses the pinned Dalek snapshot only as a
differential-test oracle.
The workspace has two hardware tracks:
- The compatibility core implements public-key derivation, cold and
cached-key signing, strict verification, multipart byte streams, and key
clearing.
rhdl_ed25519_top::Ed25519Coreassembles the modules exported here into the end-to-end synchronous design. - The fixed-64 fast path is an optimization track for hardware key
expansion followed by saturated cached-key signing of 64-byte messages on
an AMD Alveo U280. Its
fast_field,fast_fixed_base,fast_sha512,fast_point_codec,fast_scalar, andfast_signcrates contain the specialized SystemVerilog datapath, RHDL wrapper types, cycle models, and RTL tests. It is not yet a replacement for the compatibility core.
Implementation boundary: the optimized fixed-64 datapath is handwritten SystemVerilog and is compiled directly by Icarus, Verilator, and Vivado. It is not emitted from RHDL. The SystemVerilog backend manual documents every module, parameter, helper function, port group, state machine, memory image, and build boundary in that rewrite.
§End-to-end signing
For an RFC 8032 seed sk and message M, the hardware executes:
SHA512(sk) -----------------> clamp -> a -------> [a]B -> encode -> A
|
+-----------------------> prefix
SHA512(prefix || M) --------> reduce mod l -> r -> [r]B -> encode -> R
SHA512(R || A || M) --------> reduce mod l -> k
S = r + k*a mod l
signature = R || SThe host never supplies a prehash, expanded key, reduced nonce, challenge, or intermediate point. SHA-512 padding, clamping, reduction, scalar multiplication, point compression, and signature assembly all execute in hardware.
§Fixed-64 pipeline anatomy
The cached fixed-64 signer is a tagged task graph, not a 512-stage linear
pipeline. 512 is the number of requests in the standard throughput test.
The physical concurrency is 64 live signing contexts, 16 interleaved point
contexts, a three-phase SHA-512 round datapath reused for 80 rounds, and a
17-position data/valid pipeline in each radix-51 field multiplier. A SHA worker
occupies one block for 242 cycles even though it has only three round
phases; worker occupancy and physical register depth are different values.
One cached signing request advances through these dependency stages:
| Stage | Function | Main hardware and completion state |
|---|---|---|
| 1. Allocate | Accept a 64-byte message and caller tag into a free context | An eight-context rotating search writes banked context registers and raises nonce_hash_pending |
| 2. Build nonce block | Form `prefix | |
| 3. Hash nonce | Execute all 80 SHA-512 rounds | One of four three-phase compression workers returns a 512-bit digest through a nonce FIFO |
| 4. Reduce nonce | Compute canonical r = digest mod l | The dedicated 626-DSP, 46-position nonce reducer writes nonce_scalar and raises point_r_pending |
| 5. Compute nonce point | Compute [r]B | The 16-context signed multi-comb engine performs 32 constant scans/mixed additions and seven doublings, then enqueues projective (X,Y,Z) |
| 6. Compress nonce point | Encode projective [r]B as Edwards-Y R | One of four pair-codec ways shares one inversion across two points, stores R, and raises challenge0_pending |
| 7. Hash challenge block 0 | Compress the full 128-byte `R | |
| 8. Hash challenge block 1 | Compress the padding-only block for a 1024-bit input | A SHA worker emits the final challenge digest through a challenge FIFO |
| 9. Reduce challenge | Compute canonical k = digest mod l | The second 626-DSP, 46-position reducer writes challenge_scalar and raises muladd_pending |
10. Compute S | Compute canonical S = r + k*a mod l | A 69-position, 1,237-DSP multiply-add pipeline returns S with the context tag |
| 11. Retire | Assemble `signature = R |
Pending bits and bounded FIFOs form the scoreboard between stages. Workers
can therefore service different requests simultaneously and complete
internal stages out of input order; the six-bit context tag associates every
completion with its retained state. The AXI wrapper adds a 64-entry result
FIFO because the core result is a one-cycle pulse. See architecture for
the exact eligibility rules, queue topology, key-load path, backpressure,
banking, and stage-by-stage source map.
§Strict verification
Verification rejects malformed inputs before evaluating the group equation:
canonical(S)?
decode(A) -> reject malformed or small-order A
decode(R) -> reject malformed or small-order R
SHA512(R || A || M) -> reduce mod l -> k
compare R with encode([S]B - [k]A)Dedicated result codes distinguish framing errors, a missing cached key,
malformed points, non-canonical S, small-order points, and equation
failure. See types for the wire-level contract.
§Arithmetic hierarchy
The compatibility backend follows Dalek’s serial 32-bit organization:
fieldrepresents values modulop = 2^255 - 19with ten alternating 26/25-bit limbs stored in 32-bit lanes.scalarrepresents values modulo the Ed25519 group orderland implements wide reduction, 256-bit reduction, canonicality, andr + k*a mod l.pointuses extended Edwards coordinates(X:Y:Z:T), Projective Niels points, balanced radix-16 recoding, and fixed-pattern candidate scans.sha512implements padding, a 16-word circular schedule, and all 80 compression rounds in synchronous logic.hash_feederinserts hash prefixes and retains the first 4 KiB of a message for the second signing pass.
§Fast-path component and primitive map
The current matching-source U280 optimized out-of-context core report divides into seven direct children. Registers, RAMB36s, and DSPs sum exactly. Vivado combines 480 LUTs across hierarchy boundaries, so the adjusted LUT total is smaller than the raw direct-row sum. A nested field multiplier must not be added to its parent a second time.
| Component | Function | LUTs | Registers | RAMB36 | DSP48E2 |
|---|---|---|---|---|---|
| Top-level context/control | Own 64 contexts, banked state, scoreboards, arbiters, tags, and queues | 10,126 | 8,096 | 0 | 0 |
| Four-worker SHA pool | Build and compress seed, nonce, and challenge blocks | 18,489 | 12,088 | 4 | 48 |
| Nonce reducer | Reduce the 512-bit nonce digest modulo l | 2,722 | 11,982 | 0 | 626 |
| Challenge reducer | Reduce the 512-bit challenge digest modulo l independently | 2,844 | 11,987 | 0 | 626 |
| Fixed-base point engine | Compute [a]B and [r]B with signed multi-comb arithmetic | 82,095 | 84,605 | 352 | 2,116 |
| Four-way point codec | Batch-invert and compress projective nonce points | 84,295 | 67,702 | 0 | 2,092 |
| Scalar multiply-add | Compute and canonicalize r + k*a mod l | 2,253 | 23,343 | 0 | 1,237 |
| Raw direct-row sum | 202,824 | 219,803 | 356 | 6,745 | |
| Adjusted core total | 202,344 | 219,803 | 356 | 6,745 |
SHA uses LUTs for Boolean/rotate logic, FFs for working words and schedules,
one RAMB36 constant ROM per worker, and 12 DSPs per worker for marked 64-bit
state/add operations. Each 512-bit scalar reducer uses 626 DSPs. Every
radix-51 field multiplier uses 523 DSPs and has a 17-position,
initiation-interval-one pipeline: four in the point engine and four in the
codec account for 4,184 DSPs. Two field add/sub lanes add 24 DSPs. Scalar
arithmetic accounts for 2,489 DSPs: two 626-DSP digest reducers and the
1,237-DSP multiply-add hierarchy. The remaining 48 DSPs are in SHA. The 356
RAMB36 total is exactly 352 multi-comb table memories plus four SHA constant
ROMs. architecture records LUTRAM/SRL use, timing paths, memory traffic,
placement boundaries, and evidence filenames.
§Memory and traffic
Arithmetic engines use registers and local BRAM/ROM only. They do not use
HBM or DDR as scratch storage. For a signing message of N bytes, the 4 KiB
cache gives the logical external message traffic
N + max(N - 4096, 0)before AXI-line padding. Verification reads N message bytes once. The
top-level result separately reports logical stream bytes and external read
bytes; host/AXI/HBM traffic must also count command records, result records,
line padding, and data-mover behavior.
§Secret-dependent work
Scalar loops have fixed trip counts. Point-table lookup reads every candidate bank at the same public address and selects only after the reads. A secret digit must not select a BRAM address, enabled bank, iteration count, or stall pattern. Zero digits execute the same point-operation schedule as nonzero digits. These properties require emitted-RTL address and enable tests in addition to functional Rust tests.
§Source map
The modules below are the stable way to navigate arithmetic and protocol APIs. Their items are re-exported from the owning implementation crates. Higher-level control is intentionally split into separate crates:
| Crate | Responsibility |
|---|---|
rhdl_ed25519_top | child wiring, arbitration, and top-level synchronous design |
rhdl_ed25519_controller_types | top-level I/O, states, and status |
rhdl_ed25519_transition | pure high-level next-state kernel |
rhdl_ed25519_commands | child-engine command generation |
rhdl_ed25519_registers | retained cache/work/point register updates |
rhdl_ed25519_result | result and error classification |
rhdl_ed25519_api | Dalek signing traits over a driver/simulator transport |
rhdl_ed25519_model | pinned-Dalek reference behavior and vectors |
rhdl_ed25519_sim | cycle simulation, traces, RTL export, and checksums |
architecture | current backend map, pipeline, cycle breakdown, and contributor guide |
§Evidence and performance status
The compatibility core has end-to-end RHDL simulation evidence, including exact RFC 8032 signing and strict verification. The recorded 64-byte signing run takes 90,928 core cycles. A U280 out-of-context synthesis constrained to 300 MHz records 237,294 LUTs, 94,129 registers, 800 DSPs, and 6 block-RAM tiles, but has negative WNS and therefore must not be described as a closed 300 MHz implementation.
The current cached fixed-64 source passed a 512-signature RTL benchmark against a Dalek-derived public key and signature checksum. The first-to-last output span is 101,438 cycles across 511 intervals, or 198.508806 cycles/signature. One hardware key load plus fill/drain gives a 119,459-cycle finite batch, or 233.318359 cycles/signature.
Matching-source U280 optimized OOC core synthesis uses 202,344 LUTs, 219,803 registers, 356 RAMB36 tiles, 6,745 DSPs, and no URAM. It is 57,656 LUTs, 300,197 registers, and 2,255 DSPs below the project budgets. At the 200 MHz request, WNS is +0.520 ns. The 4.462 ns worst path is inside the scalar reducer, so this report does not establish a 4.000 ns/250 MHz clock. Combining 200 MHz with the RTL interval projects 1,007,512 sustained cached signatures/s and 857,198 signatures/s for the finite batch. These are simulation-plus-OOC projections, not routed or measured FPGA throughput.
Physical implementation currently belongs to the preceding 3,376-DSP builder-pipeline source. Its standalone core is fully routed and closes setup at 199 MHz with +0.018 ns WNS. Its full U280 platform is also fully placed and routed, but misses setup by 1.836 ns with 137,269 failing endpoints; Vitis therefore emits no bitstream or xclbin. Seven active RTL files and the host source differ from that predecessor package. The current 6,745-DSP source has not been placed or routed, so predecessor timing must not be combined with current cycles as a routed claim.
The completed U280 result belongs to the preceding single-scan digest-FIFO
revision. Its packaged 220 MHz xclbin closed with +0.001 ns WNS and measured
201,933 steady signatures/s (p50) over a 4,160-signature batch, five warm-ups,
and 30 runs; output checksums matched the Dalek-derived reference. The
packaged kernel synthesis used 210,098 LUTs, 101,849 registers, 90 RAMB36,
and 720 DSPs. It is historical evidence and must not be attributed to the
current cached pipeline. See architecture for the current hierarchy,
evidence matrix, memory analysis, and AMD comparison status. A matching
fixed-64 AMD wrapper now has the same AXI ABI and HBM bank map, but neither
implementation has a current comparable U280 hardware benchmark.
§Contributor workflow
The repository-level CONTRIBUTING.md is the complete workflow. In short:
- Start in the owning module below, not in generated Verilog.
- Add an independent arithmetic or Dalek differential test first.
- Compile the affected RHDL kernel and simulate emitted RTL when lowering or a SystemVerilog black box is involved.
- Inspect secret-dependent address, enable, and valid traces.
- Run the end-to-end simulator before changing a compatibility claim.
- Regenerate Vivado evidence before changing an area or timing claim.
On memory-constrained hosts, use one Cargo job and iterate on narrow crates:
cargo test -p rhdl_ed25519_model --release -j 1
cargo test -p rhdl_ed25519_core --release -j 1
cargo test --workspace -j 1
cargo doc --workspace --no-deps -j 1Re-exports§
pub use types::*;
Modules§
- architecture
- Architecture And System Guide
- field
- Field arithmetic modulo
2^255 - 19. - hash_
feeder - Streaming SHA-512 input assembly and the 4 KiB message replay cache.
- point
- Edwards-point arithmetic, scalar multiplication, and point encoding.
- scalar
- Scalar arithmetic modulo the Ed25519 group order.
- sha512
- End-to-end hardware SHA-512.
- types
- Public command, message-stream, pass-request, result, flag, and error encodings shared by the core, simulator, and FPGA shell.