Expand description
Where each AES S-box lives: LUTs, block RAM or UltraRAM.
§Why this question matters
The default chain hash, Haraka-512, is built from AES rounds. The heart
of an AES round is the S-box: a fixed table that replaces each byte with
another byte. Every chain lane needs hundreds of them, and a slice has
three lanes (crate::slice_chain). Built from LUTs (the FPGA’s small
logic cells), S-boxes eat a large share of the LUT budget. But the U280
also has on-chip RAM blocks that the design does not otherwise fill. A ROM
is just a RAM that nobody writes, so an S-box can live in a RAM block
instead, freeing its LUTs.
Every S-box is a 256 × 8 ROM. The same ROM costs very different things in the three places a U280 can hold it:
| Place | Cost per S-box | Notes |
|---|---|---|
| LUT ROM | ~32 CLB LUTs | the default; LUTs are the binding budget |
| Block RAM | 1 BRAM18 (½ BRAM36 tile) | preloaded by the bitstream |
| UltraRAM | 1 URAM | cannot be preloaded: a per-lane loader writes it after reset |
A lane has 494 live S-boxes (LIVE_SBOX). The build chooses, per lane,
how many go to UltraRAM and how many to block RAM:
DSIG_SBOX_PLACEMENT="uram=136,bram=358" cargo build ... # per laneThey are assigned in order of (round, position) over the live S-boxes:
UltraRAM first, then block RAM, the rest stay in LUTs. ULTRA_MASK and
BLOCK_MASK record the result as one 64-bit mask per AES round; the
S-box stages read them as compile-time constants, so only UltraRAM boxes
get a write port.
A second, coarser choice is per round. DSIG_TABLE_MASK (bit r = AES
round r, only rounds 3..=8) computes whole rounds with block-RAM
T-tables (TableChainStepLane):
a 256 × 32 ROM that returns an S-box output already multiplied into its
MixColumns column, so a table round saves the S-boxes and most of the
MixColumns XORs. TABLE_MASK holds it. Table rounds have no S-box
stages, so the placement above skips them: with DSIG_TABLE_MASK=0x1f8
only the 142 live S-boxes of rounds 0, 1, 2 and 9 are left to place.
config predicts what a placement costs and how fast it is.
(The LUT figure in the table above is an estimate; config explains how
it was derived.)
§How the pieces fit
DSIG_SBOX_PLACEMENT ─▶ build.rs ─▶ $OUT_DIR/sbox_placement.rs
URAM_PER_LANE, BRAM_PER_LANE
│ include!
▼
chain_step::LIVE_SBOX ─────────▶ masks() ─▶ ULTRA_MASK, BLOCK_MASK
│
▼
S-box stages (crate::aes_table) pick LUT / BRAM / URAMThe decision is made when the crate is compiled, not at run time: a different placement is a different bitstream. This lets synthesis see plain constants and build only the memory each S-box needs.
Constants§
- BLOCK_
MASK - Which positions of each round are block-RAM S-boxes.
- BRAM_
PER_ LANE - S-boxes per lane in block RAM (
DSIG_SBOX_PLACEMENT, orsbox-bram). - LANE_
NEEDS_ LOAD - The lane has at least one UltraRAM S-box, so it must be loaded after reset.
- TABLE_
MASK - AES rounds computed by block-RAM T-tables (
DSIG_TABLE_MASK). - ULTRA_
MASK - Which positions of each round are UltraRAM S-boxes.
- URAM_
PER_ LANE - S-boxes per lane in UltraRAM (
DSIG_SBOX_PLACEMENT).