Expand description
§Architecture And System Guide
This is the canonical explanation of the repository as of 2026-07-14. It is
written for someone who needs to understand the cryptography, trace the RTL,
reason about throughput and area, and decide whether a result is actually
supported by evidence. For commands and contribution policy, also read
CONTRIBUTING.md.
The most important distinction is that the workspace contains two backends:
- a general RHDL compatibility backend with the Dalek-facing feature set;
- a specialized SystemVerilog backend for cached-key signing of 64-byte messages at high throughput on an AMD Alveo U280.
They use the same Ed25519 semantics and the same pinned Dalek oracle, but they do not currently have the same interface or feature set.
§1. Evidence Vocabulary
Performance discussions use four different evidence levels. Keep them separate.
| Term | Meaning |
|---|---|
| RTL cycles | A simulator counted cycles and checked outputs |
| OOC projection | RTL cycles combined with out-of-context optimized-netlist timing |
| Placed/routed attempt | Vivado completed physical implementation; timing may still fail |
| Routed result | A complete implementation met timing after placement and routing |
| Hardware result | An xclbin ran on the U280 and returned validated outputs |
An OOC result is useful for architecture work, but it does not include the platform shell, final placement, routing congestion, or runtime behavior. Likewise, a finite completion span is not automatically a steady initiation interval: a pipeline may accumulate work before the first measured output and then drain a burst.
Every result in this guide names its source scope. The latest checked-in fast source has a matching focused RTL benchmark and optimized OOC synthesis. A builder-pipeline predecessor has both a standalone routed-core result and a fully placed/routed Vitis platform attempt. The standalone core closes setup at 199 MHz, but the platform attempt misses timing and emits no xclbin. The latest DSP-heavy source has not yet been placed or routed.
§2. What The Two Backends Do
§Compatibility backend
The compatibility backend is assembled from RHDL synchronous devices. It owns the broad behavioral contract:
- derive a public key from a 32-byte RFC 8032 seed;
- sign with a cold key or a cached key;
- strictly verify signatures;
- accept arbitrary-length and multipart message streams;
- hash entirely in hardware;
- cache up to 4 KiB for signing replay and request later bytes again;
- report framing, point, scalar, cache, and equation errors;
- clear cached key material.
Its top is rhdl_ed25519_top::Ed25519Core. The controller is split across
several small crates because compiling one very large RHDL transition function
consumes too much host memory. The split is a compile-time ownership boundary,
not a collection of independent accelerators.
§Fast fixed-64 backend
The fast backend is the current U280 throughput experiment. It supports:
- hardware expansion and caching of one 32-byte seed;
- derivation and caching of the corresponding public key;
- cached-key Ed25519 signing of exactly 64 message bytes;
- tagged, many-request operation with 64 signature contexts;
- explicit key clearing while idle.
It does not currently provide arbitrary-length messages, per-request cold key expansion, verification, or the compatibility command protocol. It is a specialized backend, not a drop-in replacement for the RHDL top.
The current top is:
crates/rhdl_ed25519_fast_sign/rtl/fast64_cached_sign_core.sv§3. Ed25519 Work
For seed sk, message M, Edwards basepoint B, field modulus
p = 2^255 - 19, and scalar order l, Ed25519 signing computes:
h = SHA512(sk)
a = clamp(h[0..32])
prefix = h[32..64]
A = encode([a]B)
r = SHA512(prefix || M) mod l
R = encode([r]B)
k = SHA512(R || A || M) mod l
S = r + k*a mod l
public key = A
signature = R || SThe compatibility backend performs that graph for each cold signing command
or reuses cached a, prefix, and A for a cached-key command.
The fast backend makes the cache boundary explicit. Key loading performs the
seed hash and A derivation once. Message requests are accepted only after
key_loaded is true, so every timed signature reuses the expanded key.
flowchart LR
K["32-byte seed"] --> HS["SHA512 seed block"]
HS --> AP["clamp a and retain prefix"]
AP --> AB["multi-comb [a]B"]
AB --> AC["compress A"]
AC --> KC["cached a, prefix, A"]
M["64-byte message"] --> NR["SHA512 prefix || M"]
KC --> NR
NR --> RR["reduce r mod l"]
RR --> RB["multi-comb [r]B"]
RB --> RC["pairwise compress R"]
RC --> CH["SHA512 R || A || M"]
KC --> CH
M --> CH
CH --> RK["reduce k mod l"]
RK --> SM["S = r + k*a mod l"]
RR --> SM
KC --> SM
RC --> OUT["R || S"]
SM --> OUT§4. Fast Top-Level Interface
The fast top is synchronous and uses ready/valid input handshakes:
| Signal group | Contract |
|---|---|
load_key_valid, load_key_ready, key_seed_in | Accept one seed when both handshake signals are high |
key_loaded, cached_public_key | Expanded key and public key are available |
valid_in, ready_in, message_in, tag_in | Accept one 64-byte message and caller tag |
valid_out, signature_out, tag_out | Present one tagged signature for one cycle |
clear_key | Clear the cached key only while idle |
internal_error | Sticky protocol or internal queue error until reset |
There is no output backpressure signal. Any deployment wrapper must consume
every valid_out pulse or place a FIFO immediately after the core.
load_key_ready is high only when no key is valid and no key load is active.
To replace a key, wait for all signatures to finish, assert clear_key, and
then load a new seed. Clearing during key expansion or while a signing context
is active sets internal_error and does not perform the idle clear operation.
The clear operation zeros the cached seed, secret scalar, prefix, and public key. It does not scrub every inactive context register or LUTRAM entry. That is the implemented zeroization boundary; a stronger claim needs more RTL and tests.
§AXI deployment shell
bench/rhdl/rtl/fast64_cached_sign_io_kernel.sv wraps the core with AXI-Lite
control and four independent 512-bit AXI masters:
| Argument/port | HBM bank | Record shape |
|---|---|---|
messages / gmem0 | 0 | one 64-byte message line per signature |
commands / gmem1 | 1 | one 64-byte seed-command line per key load |
signatures / gmem2 | 2 | one 64-byte `R |
summary / gmem3 | 3 | one 64-byte summary line per launch |
The scalar arguments are message_stride_lines, message_len, batch, and
operation. The shell accepts LoadCachedKey, SignWarm, and
ClearCachedKey; it rejects unsupported operations and warm signing without a
loaded key. The argument order and bank mapping are identical in
amd_fast64_cached_sign_io_kernel.cpp.
The shell contains a 64-entry completion FIFO because the core has a pulse
output and no backpressure input. It also submits one internal dummy signature
for odd batch sizes so the paired compressor cannot strand a final real point;
the dummy never causes an external read or signature write. The summary records
payload cycles, core window, output span, key-load cycles, exact AXI beats,
completion count, error flags, and queue high-water marks. Its packed C++
definition is bench/include/ed25519_bench_abi.hpp.
§5. Tagged Task Graph
The signer is not a serial FSM. It is a scoreboard of 64 per-signature
contexts connected to independent workers. Each context retains its message,
nonce/challenge scalars, encoded R, challenge chaining state, caller tag, and
pending-work bits.
Workers select ready pending bits, perform fixed-latency work, and return the context tag. A completion updates only that context and makes the next dependency eligible. Different signatures can therefore occupy every stage at once and may finish internal stages out of order.
The current worker inventory is:
| Worker | Instances | Internal concurrency | Per cached signature |
|---|---|---|---|
| SHA-512 block builders | 2 | one block under construction per builder | 3 blocks |
| SHA-512 compression workers | 4 | one block per worker | 3 blocks |
| 512-bit scalar reducers | 2 | fully pipelined, II 1 | one r, one k |
| Fixed-base point engine | 1 | 16 scalar contexts, four field multipliers | one [r]B |
| Point result FIFO | 1 | 32 projective points | one point |
| Pair compressors | 4 | four contexts per way | half a pair on average |
| Scalar multiply-add | 1 | fully pipelined, II 1 | one S |
Key expansion temporarily uses one SHA worker, the point engine, and a compression way. Signing requests are not accepted until expansion is done, so key work does not compete with steady cached signing.
§What “pipeline” means in this design
The 512 in the standard throughput test is a request count, not a pipeline
depth. The fast signer is also not one linear chain of 512 register stages. It
is a tagged task graph with several independently pipelined or iterative
workers:
| Quantity | Meaning |
|---|---|
| 64 signing contexts | Maximum messages whose dependency state can be live concurrently |
| 16 point contexts | Scalars that can be interleaved through the shared fixed-base engine |
| 17 field-multiplier positions | Physical valid/data pipeline inside each radix-51 multiplier |
| 46 scalar-reducer positions | Three fixed-product folds plus final canonical reductions, all II 1 |
| 69 scalar-multiply-add positions | 22-position wide multiply, one add register, and one 46-position reducer |
| 3 SHA round phases | Registered datapath reused for each of 80 SHA-512 rounds |
| 242 SHA worker cycles | Occupancy of one SHA worker for one compression block |
| 512 benchmark requests | Sample population used to measure the steady completion span |
Latency through a recirculating worker is not the same as physical pipeline depth. For example, a SHA block spends about 242 cycles in a worker, but the worker contains a three-phase round datapath that is reused 80 times. Likewise, the 9,503 cycles between completion of the measured key load and the first cached signature include repeated SHA rounds, multi-comb rounds, and a fixed inversion chain. They are not 9,503 independently occupied register stages.
The throughput metric is the completion interval after all workers overlap:
101,438 completion-span cycles / 511 intervals
= 198.508806 cycles/signature§One signing request, stage by stage
The following table is the shortest complete trace through
fast64_cached_sign_core.sv. “Eligibility” names the state that makes the
next task visible to an arbiter. Every worker request carries the six-bit
context index; the completion tag writes the result back to that same context.
| Stage | Eligibility/input | Function | Completion and next dependency |
|---|---|---|---|
| 1. Allocate | valid_in && ready_in | Search an eight-context rotating window, register the 64-byte message and caller tag, then claim one free context | Set active[i] and nonce_hash_pending[i] |
| 2. Build nonce block | nonce_hash_pending[i] | A SHA builder emits the 16 words containing the prefix, message, padding, and length; byte reversal and padding are performed by sha_block_word | Submit HASH_NONCE with SHA IV and context tag |
| 3. Hash nonce | Free member of the four-worker SHA pool | Perform all 80 SHA-512 rounds using the three-phase round datapath and 16-word circular schedule | Put the 512-bit digest and context into nonce_fifo0 or nonce_fifo1 |
| 4. Reduce nonce | Non-empty nonce digest FIFO | Convert the SHA digest to a little-endian integer and reduce it modulo l with the DSP-folded reducer | Write nonce_scalar[i] = r; set point_r_pending[i] |
| 5. Compute nonce point | point_r_pending[i] and a free point context | Compute [r]B with the signed multi-comb engine: 32 constant scans/mixed additions and seven doublings | Push projective (X,Y,Z) and the context tag into the 32-entry point FIFO |
| 6. Compress nonce point | Two queued signing points, or one key point | Pair two projective points, share one inversion between them, recover affine coordinates, and encode Edwards-Y | Serialize the two encoded results through encoded_write_count; store encoded_r[i]; set challenge0_pending[i] |
| 7. Hash challenge block 0 | challenge0_pending[i] | Build the exact 128-byte concatenation of R, A, and M, then run one SHA compression from the IV; this block has no inline padding because it is full | Store the returned chaining state in a bank selected by context parity/high bit; set challenge1_pending[i] |
| 8. Hash challenge block 1 | challenge1_pending[i] | Compress the all-padding second block with bit length 0x400, starting from the saved block-0 chaining state | Put the final digest and context into challenge_fifo0 or challenge_fifo1 |
| 9. Reduce challenge | Non-empty challenge digest FIFO | Reduce the digest modulo l using the second dedicated DSP reducer | Write challenge_scalar[i] = k; set muladd_pending[i] |
10. Compute S | muladd_pending[i] | Compute the wide product k*a, add r, and reduce the sum modulo l | Return canonical S with the context tag |
| 11. Retire | Multiply-add completion | Read the banked encoded R, assemble signature_out = concat(R,S), and restore the caller tag | Pulse valid_out; clear active[i], making the context reusable |
The dependency chain can be read directly from the pending bits and queues:
nonce_hash_pending
-> nonce_fifo[0|1]
-> reducer_r
-> point_r_pending
-> point FIFO
-> paired codec / encoded_write_count
-> challenge0_pending
-> saved challenge state
-> challenge1_pending
-> challenge_fifo[0|1]
-> reducer_k
-> muladd_pending
-> valid_out and active=0This is a scoreboard, not an FSM that forces all signatures through the same
stage together. Context 7 can be in challenge SHA while context 19 is in point
multiplication and context 42 is retiring. Internal completion order may differ
from input order; tag_in/tag_out gives the caller the association.
§One-time key-load path
Key loading uses separate global pending flags because it owns the shared workers before signing begins:
key_hash_pendingbuilds and hashes the 32-byte seed with hardware padding.digest_secret_scalarclamps the low digest half intoa, whiledigest_prefixretains the high half as the deterministic nonce prefix.key_point_pendingsendsathrough the same multi-comb engine used for nonce points.- The point FIFO marks this result as a key point. The paired codec supplies
the identity as its unused partner and returns
A = encode([a]B). - The completion stores
key_secret_scalar,key_prefix, andkey_public_key, clearskey_load_busy, and assertskey_valid_q.
ready_in remains low until step 5. Consequently, the measured warm-signing
service interval does not hide key expansion inside message work.
§Arbitration, queues, and backpressure
The top deliberately uses bounded rotating searches instead of 64-way global priority muxes:
- allocation, point, and multiply-add arbiters inspect eight candidates;
- the two SHA request arbiters inspect eight candidates of one parity and then advance their bases by 16;
- the point and multiply-add bases advance by eight;
- the four digest FIFOs separate nonce/challenge work and the two SHA return ports before the dedicated scalar reducers;
- the 32-entry point FIFO decouples multi-comb completion from pair compression;
encoded_write_countserializes a two-result codec completion into the banked encoded-Rstore.
Input backpressure is explicit through ready_in. Worker boundaries use
pending bits, ready/valid handshakes, or FIFOs. The core output is a one-cycle
pulse and has no ready_out; the AXI shell therefore provides a 64-entry
completion FIFO. Overflow or an illegal clear operation sets the sticky
internal_error flag.
The active point engine has two modular add/subtract lanes. During mixed
addition its fourth field multiplier, otherwise unused in that phase,
calculates 2Z; point doubling still schedules a separate fixed-pattern twice
operation. Builder choice and operands are registered before entering the
add/subtract lanes.
§Context banking
Wide message, encoded-point, and challenge-state arrays are divided by context parity and high context bit into four banks. SHA request logic reads from the appropriate small bank instead of synthesizing a single 64-way, 512-bit mux.
Several arbiters inspect only a small rotating candidate window. That choice is intentional. A complete 64-way priority tree can save occasional idle cycles but often creates a worse clock path and high-fanout routing.
§Digest FIFOs
SHA completions can arrive while a downstream reducer is busy or selecting the other SHA return lane. Four FIFOs decouple those boundaries:
- nonce digest from SHA return port 0;
- nonce digest from SHA return port 1;
- challenge digest from SHA return port 0;
- challenge digest from SHA return port 1.
Each FIFO has 32 entries. An entry contains a 512-bit digest and six-bit context tag, so the four FIFOs hold 66,304 logical bits in total. The two reducers choose between the corresponding return-port FIFOs without mixing nonce and challenge work.
§6. SHA-512 Hardware
sha512_compress_3phase.sv performs every SHA-512 round in three registered
phases:
- Read the round word/constant and calculate sigma plus choice/majority terms.
- finish schedule expansion and register
T1/T2partials; - update the eight working words.
The schedule is a 16-word circular window. It does not store all 80 expanded words. One compression occupies a worker for 242 cycles. The four-worker pool has two request ports and two result ports. At most two workers are launched in one cycle, so at most two deterministic-latency completions coincide.
The two top-level builders assemble one 1,024-bit block over 16 cycles and send it to any free compression worker. Padding and length fields are generated in the top; the host never prehashes.
For a cached 64-byte signature:
prefix || Mis 96 bytes and fits in one padded block;R || A || Mis exactly 128 bytes, so it uses one full data block and one padding block;- total steady SHA demand is three compression blocks.
The ideal aggregate SHA floor is therefore:
3 blocks/signature * 242 cycles/block / 4 workers
= 181.5 cycles/signatureThe current matching-source integrated interval is 198.509 cycles/signature. SHA remains the largest ideal service demand, with 17.009 cycles/signature, or about 9.37%, of scheduling, builder, queue, and downstream backpressure above its 181.5-cycle floor. The current source marks the 64-bit state and adder registers for DSP implementation. Matching OOC synthesis maps 12 DSP48E2s to each worker, or 48 DSPs across the four-worker pool; the boolean functions, rotates, state, and circular schedule still use LUTs and registers.
§7. Field Arithmetic
§Representation
The fast datapath exposes a packed canonical 255-bit field element. The current
multiplier internally uses five radix-2^51 limbs. This is different from the
compatibility backend’s ten alternating 26/25-bit limbs and from the fast
backend’s predecessor 15-limb radix-2^17 multiplier.
§Radix-51 multiplier
radix51_field_mul_pipe.sv decomposes every 51x51 limb product into nine
17x17 products. Across the 5x5 limb matrix that is still 225 small products,
which map naturally to DSP48E2 multipliers. Wrapped coefficients use
2^255 = 19 mod p, followed by two radix-51 carry passes and a final canonical
subtraction.
Properties of one current field multiplier:
- 17-position valid pipeline;
- initiation interval one;
- fixed operation schedule;
- 523 DSP48E2 blocks in the current matching OOC report;
- canonical packed result below
p.
The point engine owns four such multipliers, and the four compression ways own one each. Those eight multipliers account for 4,184 DSPs. The increased DSP count comes from mapping product reconstruction, coefficient accumulation, and carry-stage additions into DSPs as well as the 225 explicit 17x17 products.
§Addition and subtraction
radix17_field_addsub_pipe.sv retains its historical filename but operates on
packed canonical field elements. Its registers separate input capture, raw
add/subtract, high-bit folding by 19, and final canonical subtraction. It
accepts one operation per cycle.
The two active add/subtract instances each map to 12 DSP48E2s in the current matching OOC report. Field and point-formula arithmetic therefore account for 4,208 DSPs: 4,184 in eight multipliers plus 24 in the two add/subtract lanes. The current worst OOC path is no longer the old point-builder input mux. It is inside the scalar reducer’s registered conditional-subtraction chain.
§8. Scalar Arithmetic
The fast scalar pipeline uses the identity:
l = 2^252 + q
q = 0x14def9dea2f79cd65812631a5cf5d3edInstead of a 512-step serial divider, scalar_reduce_wide_dsp_pipe folds the
high portion around bit 252 using fixed-size radix-2^16 constant products.
Three fold stages and final conditional reductions produce a canonical scalar.
Every product and normalization stage has a public fixed schedule.
There are two reduction pipelines, one for r and one for k. Each accepts a
new digest every cycle. The final scalar_muladd_dsp_pipe computes the wide
product k*a, adds r, and passes the sum through the same reducer. It also
has initiation interval one.
Current integrated DSP attribution is:
| Scalar block | DSP48E2 |
|---|---|
| Nonce reducer | 626 |
| Challenge reducer | 626 |
| Multiply-add | 1,237 |
| Scalar total | 2,489 |
This is the main deliberate shift from LUT-heavy serial scalar arithmetic to DSP-heavy throughput arithmetic. Inside the multiply-add hierarchy, the wide multiplier accounts for 338 DSPs and its nested reducer accounts for 626. The remaining 273 DSPs are inferred by the registered accumulation and normalization logic owned directly by the multiply-add hierarchy.
§9. Signed Multi-Comb Fixed-Base Multiplication
multicomb_mul_stream.sv computes [s]B with a signed 4x8x8 multi-comb. It
adjusts and recodes the scalar into fixed public rounds, then performs:
- 32 table lookups and mixed additions;
- seven point doublings between comb rounds;
- no secret-dependent loop count or early exit.
The point engine holds 16 independent scalar contexts. Four field multipliers
launch all products in a formula phase together. While one context waits for a
17-position field result, lookup and builder logic can advance another context.
Only two add/subtract lanes are instantiated. The common mixed-add path uses
multiplier lane three as a constant-times-two unit for 2Z, saving a third
add/subtract lane; the rarer doubling path retains a separate twice task.
In the current 512-signature integrated profile, 513 point requests are issued and completed: one key point and two nonce points for each paired compression request after batching. Point issue spans 109,789 cycles and completion spans 109,907 cycles. The point FIFO reaches a high-water mark of only two entries. These integrated counters are the relevant current evidence; short isolated point bursts can hide accumulated work before their first output.
§Constant-scan lookup
The logical table has four banks. Each bank contains four public comb blocks by 32 public candidate groups. One entry stores three 255-bit Projective Niels coordinates, for a 765-bit word.
Each lookup reads all 128 candidates in four cycles:
4 physical banks * 8 public groups/cycle * 4 cycles
= 128 words read per lookupThe secret candidate never controls a BRAM address, enable, cycle count, or exit condition. Its low two bits choose among already-read bank outputs; its high five bits match the public group tag after the reads. The sign is applied to the selected coordinates after scanning.
The logical table is:
4 candidate banks * 4 public blocks * 32 groups * 765 bits
= 391,680 bits
= 48,960 bytesEight read copies of each logical bank provide 32 parallel words per cycle. Vivado maps the resulting wide/deep arrangement to 352 RAMB36 tiles. This is why the 48,960-byte logical size and physical BRAM count appear so different.
§10. Point Compression
Edwards-Y compression converts projective (X,Y,Z) to affine coordinates and
encodes y with the parity of x. Inversion of Z dominates the work.
point_compress_pair_stream.sv amortizes one inversion across two points:
z_product = Za * Zb
inverse = 1 / z_product
1 / Za = inverse * Zb
1 / Zb = inverse * ZaThe current wrapper has four physical compression ways. Each way owns four contexts and one radix-51 field multiplier. This creates 16 in-flight point pairs without a global 16-way operand mux. A four-entry result queue per way absorbs bursty completions.
The point engine pushes projective results into a 32-entry FIFO. Normal signing work pops two nonce points and compresses them as one pair. Key loading may present a single point; the second member is the identity, and only the key result is consumed.
The active cached signer instantiates four compression ways. Its integrated
512-signature profile issues and completes 257 pair requests, including the
single key point paired with the identity. Pair completion spans 109,908
cycles. A focused six-lane experiment currently reports 223.659 cycles/pair,
but that test explicitly overrides LANES=6; it must not be attributed to the
active four-lane signer. Until the four-lane isolated test is rerun against the
current source, use the integrated counters rather than a standalone codec
service claim.
§11. Current Integrated Test
The current cached benchmark loads one deterministic key in hardware, submits 512 deterministic 64-byte messages, and validates the public key and aggregate signature checksum against pinned Dalek. It was rerun against the current DSP-accumulation and two-limb-normalization source on 2026-07-14:
PROFILE input=512 span=98864 stalls=98353 sha=1537 span=119080
reduce_r=512 span=98958 reduce_k=512 span=101443
PROFILE point_issue=513 span=109789 point=513 span=109907
codec_issue=257 span=109907 codec=257 span=109908
muladd_issue=512 span=101438 muladd=512 span=101438
max_point_fifo=2
PASS cached_benchmark=512 cycles=119459 key_load=8517 first=18020 span=101438The counts are internally consistent. One seed expansion plus three SHA blocks
per signature gives 1 + 3*512 = 1,537 SHA completions. One key point plus one
nonce point per signature gives 513 point completions. Pairing those points for
compression gives ceil(513/2) = 257 codec requests. Both scalar reducers and
the final multiply-add complete exactly 512 signing operations.
Correct throughput interpretation is:
completion intervals = 512 - 1 = 511
steady interval = 101,438 / 511
= 198.508806 cycles/signature
finite batch average = 119,459 / 512
= 233.318359 cycles/signatureThe steady interval uses the first-to-last completion span and excludes the
distance from benchmark start to the first output. The finite batch average
includes key expansion, pipeline fill, and drain. The large stalls value is
input backpressure while all 64 signing contexts are occupied; it is not a
cryptographic failure or an external-memory wait.
This test proves current-source RTL behavior for its deterministic 512-message workload and Dalek-derived checksum. It is not a full-workspace test, an AXI platform test, or hardware evidence. Existing real-core AXI batch-1 and batch-65 simulations and the latest archived broad workspace log were produced from predecessor source and remain regression evidence only.
§12. Current U280 OOC Results
The latest report whose source matches the current DSP-heavy RTL is an optimized out-of-context run, not placement or routing:
reports/generated/
u280_fast64_cached_singlepoint_dspacc_norm2_ooc200_20260714/§Current matching-source resource use
| Resource | Used | U280 available | U280 use | Project budget | Budget status |
|---|---|---|---|---|---|
| CLB LUTs | 202,344 | 1,303,680 | 15.52% | 260,000 | 57,656 under |
| Registers | 219,803 | 2,607,360 | 8.43% | 520,000 | 300,197 under |
| DSP48E2 | 6,745 | 9,024 | 74.75% | 9,000 | 2,255 under |
| RAMB36 | 356 | 2,016 | 17.66% | not specified | fits |
| RAMB18 | 0 | 4,032 | 0% | not specified | fits |
| URAM | 0 | 960 | 0% | not specified | fits |
The direct hierarchy is:
| Direct hierarchy | LUTs | Registers | RAMB36 | DSP |
|---|---|---|---|---|
| Point compression, four ways | 84,295 | 67,702 | 0 | 2,092 |
| Fixed-base point engine | 82,095 | 84,605 | 352 | 2,116 |
| Scalar multiply-add | 2,253 | 23,343 | 0 | 1,237 |
| Nonce reducer | 2,722 | 11,982 | 0 | 626 |
| Challenge reducer | 2,844 | 11,987 | 0 | 626 |
| Four-worker SHA pool | 18,489 | 12,088 | 4 | 48 |
| Top-level own logic | 10,126 | 8,096 | 0 | 0 |
The direct rows report 202,824 LUTs before cross-hierarchy optimization. Vivado combines 480 LUTs across hierarchy boundaries, producing the adjusted top-level total of 202,344. Registers, DSPs, and block RAM sum exactly. Do not add nested field-multiplier or reducer rows to these direct rows.
§Current primitive ownership
| Direct hierarchy | Function | Current primitive mapping |
|---|---|---|
| Top-level own logic | Own 64 contexts, scoreboards, banked messages and points, arbitration, tags, and queues | Control and storage use LUTs, LUTRAM, SRLs, and FFs; no DSP or block RAM is directly owned |
| SHA pool | Expand the key seed and process nonce and challenge blocks | Four constant ROMs use four RAMB36s; state and registered 64-bit additions infer 12 DSPs per worker, 48 total |
| Two reducers | Produce canonical nonce r and challenge k | Each three-fold radix-16 pipeline uses 626 DSPs and accepts one digest per cycle |
| Fixed-base point engine | Compute [a]B at key load and [r]B for signing | Four 523-DSP field multipliers, two 12-DSP add/sub lanes, context state, and the 352-RAMB36 constant-scan table total 2,116 DSPs |
| Four-way point compression | Batch-invert and encode projective points | One 523-DSP field multiplier per way plus independent context/control state totals 2,092 DSPs |
| Scalar multiply-add | Compute S = r + k*a mod l | 338 DSPs in the wide multiplier, 626 in its reducer, and 273 in owned accumulation/normalization logic total 1,237 DSPs |
DSP attribution is independently checkable:
8 field multipliers * 523 4,184
2 point add/sub lanes * 12 24
2 standalone scalar reducers 1,252
scalar multiply-add 1,237
4 SHA workers * 12 48
-----
core total 6,745 DSP48E2Block RAM remains exactly 352 RAMB36s for replicated constant-scan tables plus four RAMB36s for SHA constants. Current OOC synthesis also reports 16,540 LUTRAM primitives and 2,948 SRLs. No external scratch memory or URAM is inferred.
§Current matching-source timing
At the 200 MHz request, period 5.000 ns, optimized OOC timing reports WNS
+0.520 ns; setup timing is met. The worst path is:
- source:
muladd/reducer/biased_reg[25]; - destination:
muladd/reducer/reduced1_reg[247]; - data path: 4.462 ns;
- 14 logic levels: one DSP input stage, six DSP ALU stages, six DSP output stages, and one LUT3;
- 89.041% logic delay and 10.959% estimated routing delay.
This proves only optimized OOC setup timing at 200 MHz. The report warns that 788 inputs and 786 outputs have no I/O delays, and it was generated with maximum-delay analysis, so it does not establish hold timing. It also does not prove 250 MHz: a 250 MHz clock has a 4.000 ns period, while the reported worst data path is 4.462 ns. The current source has not been placed or routed.
§Current throughput projection
Combining the current RTL interval with 199 and 200 MHz clock assumptions gives:
| Assumed clock | Steady, clock / 198.508806 | Finite 512-item batch, clock / 233.318359 |
|---|---|---|
| 199 MHz | 1,002,474 signatures/s | 852,912 signatures/s |
| 200 MHz | 1,007,512 signatures/s | 857,198 signatures/s |
These are simulation-plus-OOC projections. They are not measurements from an xclbin or a U280 board.
§Predecessor standalone-core route
The immediately preceding builder-pipeline source was fully placed and routed as a standalone core. It is preserved because it provides physical routing evidence, but its 3,376-DSP datapath does not match the current 6,745-DSP RTL. The package checksum manifest now reports eight mismatches against the working tree, including all seven active RTL files and the host source.
At 200 MHz that routed checkpoint missed by only 0.007 ns. Rechecking the same
checkpoint at 199 MHz produced WNS +0.018 ns, TNS zero, and no setup failures.
All 569,193 routable nets were fully routed with no route errors. Its routed
resources were 248,852 LUTs, 237,472 registers, 356 RAMB36s, 3,376 DSPs, and no
URAM. It was 11,148 LUTs below the 260,000-LUT project budget.
The report still omitted I/O delays on 788 inputs and 786 outputs. Its DRC contained 748 warnings: 719 unpipelined-DSP-input warnings, 8 DSP PREG warnings, 20 DSP MREG warnings, and one net-with-no-routable-load warning. There were no DRC errors. Those warnings motivated the newer DSP input and accumulator pipelining.
Evidence directories are:
reports/generated/u280_fast64_cached_quadmul_ctx16_codec4x4_addsub2_mul2z_builderpipe_route200_20260713/
reports/generated/u280_fast64_builderpipe_route199_timing_20260713/§Predecessor full U280 platform placement
Vitis also synthesized, placed, and fully routed that predecessor inside the U280 platform. Placement took 2 h 18 min 53 s and routing took 3 h 21 min 50 s; the complete link ran for 7 h 01 min 49 s. The result is a routed checkpoint, but timing failed and Vitis stopped before bitstream and xclbin generation.
At the 199 MHz kernel clock, period 5.025 ns, the routed platform reported WNS
-1.836 ns, TNS -91,954.969 ns, and 137,269 failing setup endpoints out of
1,014,742. Hold timing passed with WHS +0.006 ns and zero hold failures. The
6.404 ns worst path was 89% route delay and ran from point-context state to a
point field multiplier DSP input. There is therefore no valid predecessor
platform throughput result.
The predecessor kernel’s post-synthesis estimate was 256,641 LUTs, 238,210 registers, 363 RAMB36 plus one RAMB18, 3,376 DSPs, and no URAM. The full routed device, including the fixed U280 platform, used 381,802 LUTs, 419,571 registers, 558 RAMB36s, six RAMB18s, and 3,380 DSPs. Subtracting the report’s fixed column leaves 274,173 LUTs, 283,485 registers, 365 RAMB36 plus one RAMB18, and 3,376 DSPs in the dynamic region; that delta includes AXI/interconnect and is not a kernel-only number. SLR1 reached 73.98% CLB, 52.12% LUT, 64.43% BRAM, and 31.18% DSP occupancy, with 16,623 SLR crossings and no SLL pipeline registers.
The archived platform evidence is:
reports/generated/u280_fast64_io_reference_link199_timing_failed_20260714/The correct conclusion is deliberately narrow: placement and full routing have occurred for the predecessor, and standalone-core timing closed at 199 MHz. The full predecessor platform did not close timing, and the current source has not yet reached placement. No xclbin or current hardware benchmark exists.
§13. Memory And Access Analysis
§External HBM/DDR
The fast cryptographic core performs no external scratch reads or writes. It retains the expanded key, messages in flight, scalar state, point state, SHA state, and lookup table on chip.
At the core interface, one cached signature requires:
- 64 input bytes for the message;
- one caller tag;
- 64 output bytes for
R || S; - one returned tag.
The AXI shell makes the external traffic exact. For batch B:
LoadCachedKey: 1 key read + 1 summary write = 128 HBM bytes
SignWarm: B message reads + B signature writes + 1 summary write
= 128*B + 64 HBM bytesThus warm signing has 128 payload bytes per signature plus 64/B bytes of
summary overhead. The batch-65 real-core simulation observed exactly 65
message reads and 65 signature writes; it also exercised the odd-batch dummy
without adding external traffic. Reads and writes are currently single-beat
transactions rather than coalesced bursts. PCIe traffic in the host runner is
64*B bytes host-to-device and 64*B + 64 bytes device-to-host per measured
run, excluding one-time command setup.
The compute-only benchmark still generates messages and reduces signatures to checksums in logic. Use it for core service analysis, and use the AXI shell for memory and host-visible comparison.
§Local BRAM table traffic
One fixed-base scalar multiplication performs 32 constant scans. Each scan reads 32 words per cycle for four cycles:
32 lookups * 4 cycles * 32 words * 765 bits
= 3,133,440 local bits/signature
= 391,680 byte-equivalent/signatureThis is distributed across 352 local RAMB36 tiles and is not a DRAM/HBM transaction count. At the current projected sustained rates it represents about 392.649 GB/s at 199 MHz and 394.622 GB/s at 200 MHz of aggregate local read-equivalent bandwidth. Both numbers are achieved through replicated parallel block-RAM ports, not one memory bus.
§Other local storage
| Storage | Logical organization | Synthesized resource |
|---|---|---|
| Multi-comb table | 48,960 logical bytes, eight read copies per bank | 352 RAMB36 |
| SHA constants | 80 x 64 bits per worker, four workers | 4 RAMB36 |
| Digest FIFOs | 4 x 32 x 518 bits | LUTRAM |
| Point FIFO | 32 x (3 x 255-bit coordinates + tag) | distributed storage |
| Context state | 64 messages, scalars, flags, tags, encoded points | registers and LUTRAM |
The initialized table copies contain 391,680 logical bytes. Wide-port BRAM fragmentation reserves 12,976,128 physical bits, or 1,622,016 bytes of RAMB36 capacity. This is capacity, not data read from HBM. The current core OOC report contains 16,540 LUTRAMs and 2,948 SRLs. The previously reported seven-RAMB36 plus one-RAMB18 shell delta belongs to the predecessor source; a matching-source shell has not yet been synthesized or placed. No current-source URAM is used.
§14. Security And Constant-Pattern Properties
The design aims for fixed secret-dependent work, not for a formal side-channel proof.
Current structural properties include:
- SHA round counts depend only on the fixed public message shape;
- scalar reduction and multiply-add pipelines have fixed stage counts;
- multi-comb performs the same 32 additions and seven doublings for every scalar;
- every table lookup reads all 128 candidates in the same public sequence;
- secret candidates do not control BRAM addresses or enables;
- point compression uses fixed inversion chains;
- scheduler tags and completion timing depend on worker occupancy and public request order, not scalar digits.
Things this evidence does not prove:
- resistance to power, electromagnetic, or fault attacks;
- physical leakage introduced by place-and-route;
- complete zeroization of all stale context storage;
- security of a surrounding AXI, DMA, or host stack;
- formal equivalence to Dalek for every possible input.
Any change to lookup addressing, valid timing, early exits, context allocation, or key clearing deserves a security review in addition to a functional test.
§15. Compatibility Backend Map
The compatibility path is intentionally more modular:
| Crate | Responsibility |
|---|---|
rhdl_ed25519_api | Dalek Signer, Verifier, MultipartSigner, and MultipartVerifier facade |
rhdl_ed25519_model | Pinned-Dalek oracle and vector behavior |
rhdl_ed25519_types | Commands, message words, pass requests, results, errors |
rhdl_ed25519_sha512 | General streaming SHA-512 |
rhdl_ed25519_hash_feeder | Prefix insertion, padding, framing, 4 KiB replay cache |
rhdl_ed25519_field | Ten-limb radix-2^25.5 field arithmetic |
rhdl_ed25519_scalar | Scalar reduction, canonicality, recoding, multiply-add |
rhdl_ed25519_point | Extended Edwards point operations |
rhdl_ed25519_scalar_mul | Fixed-pattern scalar multiplication controller |
rhdl_ed25519_point_codec | Edwards-Y compression and decompression |
rhdl_ed25519_controller_types | Controller state and child handshakes |
rhdl_ed25519_transition | High-level next-state logic |
rhdl_ed25519_commands | Child-engine command generation |
rhdl_ed25519_registers | Cache, work, and point register updates |
rhdl_ed25519_result | Result and error assembly |
rhdl_ed25519_top | General core composition and end-to-end tests |
rhdl_ed25519_sim | Cycle simulation, traces, waveforms, Verilog export |
The compatibility message protocol uses 64-bit words with byte keep and
last metadata. Signing may need repeated message passes. The hash feeder
caches the first 4 KiB and requests replay only beyond that offset, avoiding a
large on-chip arbitrary-message buffer while keeping SHA-512 in hardware.
Verification is strict by default: encoded points must decode canonically,
S must be canonical, small-order inputs are rejected, and the Edwards
verification equation must hold. Error codes separate framing, cache,
canonicality, point, and equation failures.
§16. Fast Backend Map
| Crate | Current role |
|---|---|
rhdl_ed25519_fast_field | Radix-51 multiplier and packed field add/sub pipelines |
rhdl_ed25519_fast_fixed_base | Generated multi-comb table, constant scan, point scheduler |
rhdl_ed25519_fast_sha512 | Three-phase compressor and four-worker pool |
rhdl_ed25519_fast_scalar | DSP-folded reduction and multiply-add |
rhdl_ed25519_fast_point_codec | Four-way paired projective compression |
rhdl_ed25519_fast_sign | Key cache, 64 contexts, scoreboards, FIFOs, result assembly |
Predecessor files remain for regression and design history. In particular,
radix17_field_mul_pipe.sv, basepoint_lookup_rom.sv,
fixed_base_mul_stream.sv, radix16_recode_stream.sv, and
fast64_sign_core.sv are not the datapaths used by the latest cached OOC run.
Follow instantiations from fast64_cached_sign_core.sv instead of assuming the
newest-looking filename is active.
§17. Test Strategy
Correctness is layered:
- Rust model tests check RFC and Dalek behavior.
- Big-integer tests check field and scalar arithmetic.
- Icarus/Verilator tests check individual RTL blocks with back-to-back input.
- Point tests verify projective invariants and compressed basepoint multiples.
- End-to-end tests compare cached signatures or checksums with Dalek.
- Compatibility simulation checks command framing, message replay, errors, derive/sign/verify, and selected malformed inputs.
- Vivado reports check the synthesized structure, area, and timing.
- A final claim requires routed hardware with every output validated outside the timed region.
The current fast-path tests to understand first are:
crates/rhdl_ed25519_fast_field/tests/rtl_radix51_pipeline.rs
crates/rhdl_ed25519_fast_field/tests/rtl_addsub_pipeline.rs
crates/rhdl_ed25519_fast_fixed_base/tests/rtl_multicomb_lookup.rs
crates/rhdl_ed25519_fast_fixed_base/tests/rtl_multicomb_mul.rs
crates/rhdl_ed25519_fast_sha512/tests/rtl_compress.rs
crates/rhdl_ed25519_fast_scalar/tests/rtl_scalar_dsp_pipeline.rs
crates/rhdl_ed25519_fast_point_codec/tests/rtl_pair_throughput.rs
crates/rhdl_ed25519_fast_sign/tests/rtl_fast64_cached_sign.rs
crates/rhdl_ed25519_fast_sign/tests/rtl_fast64_cached_benchmark.rs
bench/rhdl/tb/fast64_cached_sign_io_kernel_tb.svThe current short scheduler-model tests for multi-comb and compression retain constants from predecessor field pipelines. They remain useful for historical algorithm experiments, but their printed service numbers are not evidence for the active radix-51, four-multiplier, four-way codec RTL.
§18. Benchmarking And AMD Comparison
There are now three benchmark layers:
fast64_benchmark_kernelgenerates deterministic messages and checksum reductions in logic. It isolates algorithm service and key-load cost.fast64_cached_sign_io_kernelandamd_fast64_cached_sign_io_kernelexpose the same fixed-64 cached-key operation through four 512-bit AXI ports. The sharedfast64_io_benchhost measures both implementations.ed25519_benchuses the common XRT/AXI ABI for general RHDL and AMD kernels. It covers the broader compatibility operations and message lengths.
These are not interchangeable timing numbers. A fair RHDL-versus-AMD result needs the same operation mode, message length, batch size, data movers, memory banks, warmups, run count, validation, and clock evidence.
The fair-comparison AMD fixed-64 wrapper has this HLS estimate:
| Resource | AMD estimate |
|---|---|
| LUTs | 101,959 |
| Registers | 88,510 |
| BRAM18 | 120 |
| DSP | 14 |
| URAM | 0 |
| Estimated path | 4.680 ns against a 5.00 ns request |
There is no completed equivalent AMD U280 benchmark JSON or xclbin in the repository. The project therefore has no measured current RHDL-versus-AMD speedup claim. The operation and external ports now match, but the resource numbers remain different evidence classes: the RHDL row is Vivado optimized OOC utilization and the AMD row is an HLS estimate. The older general AMD wrapper’s 261,168-LUT report remains relevant only to the general benchmark.
fast64_io_bench performs five warmups and 30 measured runs by default,
reports p50/p95 kernel and end-to-end latency/rate, checks exact beat counters,
poisons and verifies the output buffer outside timing, and writes the raw
signatures for pinned-Dalek validation. On this local machine its build is
blocked by missing XRT headers under /opt/xilinx/xrt/include; use the
configured U280 host before claiming comparison results.
§19. Historical U280 Baseline
reports/generated/u280_baseline/ contains a routed 220 MHz xclbin and U280
run for an older single-scan, digest-FIFO, cold-signing architecture. It closed
with +0.001 ns WNS and measured approximately 201,933 steady signatures/s at
p50 over a 4,160-signature workload.
That result proves an earlier architecture family ran on the board. It does not validate the current cached 198.509-cycle pipeline, current resources, or current OOC clock. Keep the historical revision label attached to it.
§20. How To Think About Optimizations
Start with service demand, then timing, then area.
§Service demand
For each shared worker, calculate operations per signature divided by physical instances. The largest known ideal demand is the first throughput candidate:
SHA: 3 * 242 / 4 = 181.5 cycles/signature
point scan: 32 * 4 = 128 active scan cycles/signature
scalar: II 1 pipelines, below these demandsThe active four-lane codec’s isolated current-source interval has not been rerun, so it is intentionally omitted from this service table. The measured 198.509 cycles/signature is consistent with SHA being the largest known demand, with 17.009 cycles/signature of integrated overhead above the ideal floor. Adding arithmetic lanes without changing SHA and scheduler pressure is unlikely to improve top-level throughput materially. A fifth SHA worker could reduce the floor only if builders, request/return ports, context state, and timing can feed it.
§Timing
After pipelining arithmetic, wide context muxes, cascaded DSP arithmetic, and high fanout can dominate. Read the critical path before adding a register. A register improves clock only if the tag/control pipeline and all dependent state transitions move with it. The current matching-source OOC path is a 4.462 ns scalar-reducer subtraction path and passes at 200 MHz. The predecessor standalone core routes at 199 MHz, while its full platform route fails because point-state-to-DSP routing stretches the worst path to 6.404 ns. The current source has no placed timing result.
§Area
Use hierarchical reports. A logical optimization can move cost rather than remove it:
- extra BRAM ports replicate memories;
- fewer contexts reduce state but may expose field latency;
- more DSPs can reduce LUT arithmetic but add routing pressure;
- wider constant scans reduce scan cycles but multiply BRAM copies;
- duplicated codecs remove global muxes but duplicate inversion state.
The right target is the complete kernel under its clock and throughput goals, not the smallest isolated primitive.
§Evidence
For every optimization, retain a before/after table with:
- exact source identity;
- validated vector count;
- latency, II, and measured service window;
- LUT, FF, LUTRAM, SRL, BRAM18/36, URAM, and DSP;
- target clock, WNS, critical path, and implementation stage;
- whether the result is simulation, OOC, routed, or hardware.
§21. Evidence Map
| Evidence | Path |
|---|---|
| Current cached benchmark source and rerun command | crates/rhdl_ed25519_fast_sign/tests/rtl_fast64_cached_benchmark.rs; cargo test -p rhdl_ed25519_fast_sign --test rtl_fast64_cached_benchmark -j 1 -- --nocapture |
| Current core OOC evidence | reports/generated/u280_fast64_cached_singlepoint_dspacc_norm2_ooc200_20260714/ |
| Predecessor standalone route at 200 MHz | reports/generated/u280_fast64_cached_quadmul_ctx16_codec4x4_addsub2_mul2z_builderpipe_route200_20260713/ |
| Predecessor standalone route recheck at 199 MHz | reports/generated/u280_fast64_builderpipe_route199_timing_20260713/ |
| Predecessor full U280 platform route and timing failure | reports/generated/u280_fast64_io_reference_link199_timing_failed_20260714/ |
| Predecessor package manifest | reports/generated/FAST64_PACKAGE_SHA256SUMS; mismatches current working source and must not identify it |
| Predecessor real-core AXI batch-65 simulation | reports/generated/fast64_io_real_core_batch65_verilator_20260713.log |
| AMD equal-I/O HLS estimate | reports/generated/amd_fast64_cached_sign_io_kernel_csynth_20260713.rpt |
| Older source checksum set | reports/generated/SOURCE_SHA256SUMS; verify before assigning it to a revision |
| Predecessor full workspace tests | reports/generated/full_workspace_tests_addsub3_20260713.log |
| Historical routed U280 baseline | reports/generated/u280_baseline/ |
| General AMD HLS estimate | reports/generated/amd_ed25519_kernel_csynth.rpt |
| Compatibility simulation summary | reports/generated/simulation_results.json |
| External traffic analysis | reports/generated/traffic_analytics.json |
| Generated RTL checksums | reports/generated/checksums.sha256 |
§22. Current Gaps
- Generate a checksum manifest for the exact current source revision.
- Complete and archive a full-workspace test run for the current source.
- Synthesize the current AXI shell and verify shell-inclusive resource use.
- Place and route the exact current source in the full U280 platform.
- Close platform timing and produce a loadable xclbin before making a hardware throughput claim.
- Run and archive an equivalent AMD U280 benchmark.
- Check every returned signature in final hardware validation, not only an XOR checksum.
- Either update the old scheduler models to current radix-51/four-way timing or label/remove them more explicitly in code.
- Decide whether the fast backend should gain arbitrary message lengths and verification or remain a deliberately separate signing accelerator.
- Strengthen zeroization if the deployment threat model requires all context storage to be scrubbed.
§23. Glossary
- Cached signing: seed expansion and public-key derivation happen once;
subsequent messages reuse
a,prefix, andA. - Context: per-signature state retained while other signatures use shared workers.
- Latency: cycles from accepting one operation to its result.
- Initiation interval (II): cycles between accepting independent operations into one worker.
- Service interval: long-run cycles between completed signatures after all shared-resource demands are included.
- Multi-comb: fixed-base scalar multiplication using a structured precomputed table and fixed sequence of additions/doublings.
- Constant scan: read every public candidate address in the same pattern, then select the secret candidate from returned data.
- Batch inversion: obtain inverses for two projective points using one field inversion plus multiplications.
- OOC: out-of-context synthesis/optimization without the final platform shell and placement/routing.
- WNS: worst negative setup slack; negative means the requested clock does not meet timing.
The compact mental model is: a 64-context tagged task graph feeds a four-worker hardware SHA pool, DSP-folded scalar pipelines, one four-multiplier signed multi-comb point engine, and four paired-compression ways. The current RTL steady interval is 198.509 cycles/signature. The matching-source core fits the requested area limits and meets 200 MHz only in optimized OOC analysis, projecting about 1.008 million cached 64-byte signatures/s at that clock. A predecessor standalone core closes routed timing at 199 MHz, but its full U280 platform route fails timing and emits no xclbin. Current-source placement and hardware throughput therefore remain open, while the architecture still uses one 64-byte HBM read and one 64-byte write per warm signature and no external scratch memory.