Skip to main content

Module aes_table

Module aes_table 

Source
Expand description

AES T-table ROM: one BRAM port replaces one S-box and its MixColumns share.

§The story so far

A DSig signature needs 204 Haraka chain steps, and each folded chain step needs 494 AES S-box evaluations (see crate::chain_step). An S-box (“substitution box”) is the one non-linear part of AES: a fixed table that maps every byte value 0..256 to another byte. Everything else in an AES round is XOR and wiring. So the S-boxes are where the chip area goes, and where each S-box physically lives is the biggest area decision in the signer.

This module owns that physical side. It builds every S-box and every T-table as a read-only memory (ROM) out of RHDL parts, and leaves the choice of memory type to crate::sbox_placement. Its neighbours:

  crate::haraka          the math: what an AES round computes (reference + kernels)
       │
       ▼
  crate::aes_table       this file: the S-box / T-table ROMs, registered
       │                 (SboxStage, SboxStages, SboxLoader, DualTableRom)
       ▼
  crate::chain_step      the lane: 20 stages that use these ROMs every cycle
       ▲
  crate::sbox_placement  which of the 494 live boxes are UltraRAM / BRAM / LUT

§One AES round, and which part this file builds

An AES round (Intel’s AESENC instruction) runs on a 16-byte state, laid out as four columns of four bytes. It has four steps:

  state ──▶ SubBytes ──▶ ShiftRows ──▶ MixColumns ──▶ AddRoundKey ──▶ state'
            (16 S-box    (row r moves   (each column   (XOR a 16-byte
             lookups)     left r places) times a fixed  round constant)
                                         4×4 matrix)
            └─── non-linear ───┘└───────────── linear (XOR only) ─────────┘

  In this crate:  SubBytes+ShiftRows  = SboxStage   (one register stage)
                  MixColumns+AddKey   = mix_key512_kernel in crate::haraka
                                        (the next register stage)

ShiftRows is only a re-ordering of bytes, so it costs no logic: it just decides which input byte each S-box reads. That is why SboxStage does SubBytes and ShiftRows together.

§The idea in one picture

An AES round turns each four-byte column (s0, s1, s2, s3) into

  out = 2·S(s0) ⊕ 3·S(s1) ⊕ S(s2) ⊕ S(s3)       (row 0, and rotations)

Because MixColumns is linear, each input byte’s contribution to the whole output column depends on that byte alone. Precompute it once:

  T0[x] = bytes (2·S(x), S(x), S(x), 3·S(x))      — a 256 × 32-bit table
  out   = T0[s0] ⊕ rotl8(T0[s1]) ⊕ rotl16(T0[s2]) ⊕ rotl24(T0[s3]) ⊕ key

In LUT logic an S-box costs about 32 LUTs. As a table it costs one read port of a block RAM, and the only LUTs left are one 5-input XOR per output bit. See sustained for the budget.

§Built only from RHDL parts

Every table here is an [rhdl_fpga] [SyncBRAM] that is filled at configuration time and never written, i.e. a ROM with a registered read:

ItemMade ofVivado maps it to
SboxStage32 × SboxPair (two S-box lookups each, SboxStageRoms)LUT ROMs (≈32 LUTs per lookup), or one BRAM18 / URAM per pair
DualTableRomone dual-port 256 × 32 ROM holding T0one BRAM18, two reads

The ROM registers its output like any synchronous BRAM read. In a pipeline that register is the stage register, so a table round has the same latency as a LUT round (see chain_step).

This is the textbook “T-table” software AES trick moved to FPGA block RAM. In software it leaks timing through caches. A dedicated ROM port has fixed single-cycle latency regardless of address, so that concern does not apply.

§Three places an S-box can live

The same 256 × 8 ROM can be built three ways on the U280. The numbers are the ones in crate::sbox_placement and sustained:

PlaceCost per S-boxFilled how
LUT ROM (the default)~32 CLB LUTsconstant logic, nothing to fill
Block RAM½ BRAM18 (an SboxPair serves two boxes)preloaded by the bitstream
UltraRAM½ URAM (an SboxPair serves two boxes)cannot be preloaded: SboxLoader writes it after reset

UltraRAM is the odd one out. Because the bitstream cannot fill it, every UltraRAM S-box needs a write port, and the lane needs a small loader that runs after every reset. Until the loader is done the lane reports “not ready” (ChainStepOutput::ready), and nothing upstream starts a key. That keeps the one-time-key rule safe: no key is ever spent on a lane whose tables are still empty.

§Reading order

  1. T0 and DualTableRom: the T-table trick (used by TableChainStepLane).
  2. SboxPair and SboxStageRoms: 32 dual-port S-box pairs for one round, wired through ShiftRows.
  3. SboxStage: the same hardware, plus a fast simulation model.
  4. SboxStages: ten of them, one per AES round of a Haraka-512 lane.
  5. SboxLoader: fills the UltraRAM boxes after reset.

Structs§

DualTableRom
Two registered T0 lookups per cycle from one true dual-port ROM.
LoaderOut
What the loader presents each cycle.
PairBytes
S(a) and S(b) for the addresses presented one cycle earlier.
PairRead
The two addresses of an SboxPair, and the loader’s write.
RomLoad
One write into an UltraRAM S-box: entry addr becomes value.
SboxLoader
Fills a lane’s UltraRAM S-boxes after reset: entry i gets S(i) for i = 0..256, all boxes in parallel, from one LUT S-box ROM.
SboxPair
Two registered S-box lookups per cycle from one dual-port memory.
SboxStage
Registered SubBytes ∘ ShiftRows for all four Haraka AES lanes.
SboxStageIn
Input of one S-box stage: the 64 state bytes, and the loader’s write.
SboxStageRoms
The hardware of SboxStage: 64 S-box ROMs (SyncBRAM<b8, 8>), one per output byte, addressed through ShiftRows.
SboxStages
The ten S-box stages of one chain-step lane, each placed by sbox_placement.
SboxStagesIn
Input of SboxStages: every round’s bytes plus the shared table load.
TableRead
Two independent byte addresses presented in the same cycle.
TableWords
T0 entries for the addresses presented one cycle earlier.

Enums§

PairStyle
Which memory an SboxPair is built from.

Constants§

T0
T0[x] = (2·S(x), S(x), S(x), 3·S(x)), row 0 in the low byte.

Functions§

rom_read8_kernel
A read-only SyncBRAM input: read address, never write.
sbox_loader_kernel
Read S(i) one cycle, write it the next.
sbox_read_kernel
Like rom_read8_kernel for a byte-wide ROM.
sbox_stage_kernel
ShiftRows decides which input byte each ROM reads; only UltraRAM boxes see the loader’s writes.
sbox_stages_kernel
Hand each round its bytes; broadcast the table load.