Expand description
The barrel-threaded field machine: the engine that does the arithmetic.
A FieldMachine owns one 255-bit multiplier (FieldMulPar, 225 DSPs, or the 150-DSP FieldMulKara with ED25519_MULTIPLIER=LEAN150,
a new product every clock, 23 clocks of latency), one adder
(FieldAddSub), one table scanner, a program ROM, and K = 2^KB
hardware contexts: independent jobs (signatures) resident at once.
§Why barrel threading
A pipelined multiplier only helps if something always has a multiply
ready. Instead of finding parallelism inside one job, the machine runs K
jobs that take turns: in clock t, context t mod K issues one bundle.
A context’s next turn is K >= 32 clocks later, after every result it
could need is back (the longest write-back is 25 clocks). So:
- programs need no latency scheduling and the hardware no hazard logic;
- the multiplier is busy every clock the program has a multiply;
- each context’s registers live in block RAM, addressed
register * K + context, so contexts cost BRAM rather than LUTs.
§Pipeline
cycle t-1 ROM prefetch for the next head context
cycle t S1 head context issues: bank, table-row and digit addresses
cycle t+1 S2 operands selected; scan accumulator updated
cycle t+2 S3 M and A units issue; OUT captured; lookup negated
cycle t+3 S4 bank L (one of two slots) committed
t+2+23 multiplier write-back t+2+5 adder write-back§Registers
Each bank has exactly one writer, so there are no write-port conflicts:
M (multiplier results), A (adder results), I (job inputs from
the loader), C (the program’s constants, one copy for all contexts),
and L (the point selected by the last table lookup, two slots per
context so one lookup can commit while the previous one is still being
read). Banks are replicated once per read port (four operands per
bundle). An operand in bank I with index 96 or more (CONST_BASE) reads C.
Register addresses interleave contexts: register r of context c is
word r * K + c (r << KB | c). Multiplier and adder tags carry
c << 7 | r so a result returning 23 or 5 cycles later finds its own
word without any bookkeeping.
§Where the memories live, and why
The first layout put every bank in block RAM, and on a full chip block RAM ran out before LUTs or DSPs: a fixed-base machine used 72 BRAM36 (banks M and A 16 each, bank I 16, base-point tables 22, program 2.5), and the compress machine 289, of which 228 were its input bank. Two observations fixed most of it:
- Banks M, A and I are written at run time and never preloaded, which is exactly what UltraRAM can hold; the chip barely used its 960 UltraRAMs. Constants used to sit in bank I as preloaded contents (one copy per context), which is what kept bank I in block RAM. So constants moved to their own 32-entry ROM C (a few LUTs, the same for every context), and the compress stage writes its identity defaults at reset instead of preloading them.
- Machines without inputs (fixed-base, verify) never write bank I, so it shrinks to the minimum and lives in LUT RAM.
MachineMemory::SPECIALIZED (the default) is that layout;
MachineMemory::AS_BUILT reproduces the one mapped in Vivado on
2026-10-14. The new layout is projected at about 24 BRAM36 + 32 URAM per
fixed-base machine; it is not yet mapped, and UltraRAM timing at
205 MHz is the thing to check.
§Instructions
An InstrW bundle has three slots, all usable at once: M (multiply;
7-bit destination, two operands of bank + 7-bit index), A (add,
subtract, or OUT), and T (one table-scan step: digit, entry pair,
9-bit table row, bank-L slot). Bundles are produced by
microcode::Builder; encode_bundle packs
them into ROM words.
§Constant-address table lookup
A lookup of a signed digit runs one T step per pair of table entries. Each
step reads both entries at a public row; the context’s accumulator keeps
the one whose number equals the digit’s magnitude, and the last step
negates it if the digit is negative and commits it to bank L. No secret
value forms an address, an enable or a branch. (A logical constant-time
property, not a power or EM guarantee.) The tables hold e · 2^offset(i) · B
for each of the 54 digits, 372 rows in two BRAM images.
§Backpressure
An OUT bundle issues only when the single output register and the
pipeline hold no other output; otherwise the context retries on its next
turn. This is the machine’s only stall.
§Parameters and cost
FieldMachine<KB, K, RA, IA, PB, TB, KT>: contexts K = 2^KB, bank
address widths RA (M/A) and IA (I), program-counter width PB,
base-point table rows 2^TB, key-table rows 2^KT.
FbMachine is <5, 32, 9, 5, 10, 9, 1>,
CpMachine is <6, 64, 12, 13, 9, 1, 1>.
Vivado (as-built layout, routed at 4.878 ns) put a fixed-base machine at
10,110 LUT, 13,795 FF, 225 DSP and 72 BRAM36, with +0.407 ns of slack.
Structs§
- Const
Rom - The constant ROM C:
CONST_SLOTSconstants as a literal and a registered read, with the ports of a [SyncBRAM] (the write port is ignored). - Digits
Write - Digits write for the table scanner (sign-magnitude: bit 5 = sign).
- Field
Machine - Barrel field machine.
K = 2^KBcontexts;RA/IAare bank-M/A and bank-I address widths (index * K + ctx);PBis the program-counter width.TBandKTare the base-point and key table address widths. See the module docs for the pipeline and bank layout. - Input
Write - Bank-I write: one input register of one context, from the loader.
- InstrW
- Encoded VLIW bundle stored in program ROM, one word per program counter.
- KeyTables
- Key tables: two images (even/odd entries) of three coordinate RAMs each, written one coordinate at a time.
- KeyTables
In - Port bundle of
KeyTables: one shared read row, one coordinate write. - Machine
In - Machine inputs, all sampled every cycle and independent of each other.
- Machine
Memory - Where a field machine keeps its memories.
- Machine
Out - Machine outputs.
- Niels17
- Affine Niels point in radix-
2^17digits: the form table entries are stored in, ready for a mixed addition. - OperandW
- Encoded operand: which bank to read and which register in it.
- OutEvent
- Output-port event from an
OUTbundle slot. - RegBanks
- Register banks M, A and I and the constant ROM C, one copy per read port. Bank I holds per-context inputs; C holds the program’s constants once for all contexts (32 entries, LUT ROM).
- RegBanks
In - Port bundle of
RegBanks. - RegBanks
Out - Read data of
RegBanks, one cycle after the read address. - Scan
Mems - Table-scan memories: bank L, the scan accumulator, digits, the base-point
table (
2^TBrows) and the key table (2^KTrows). A machine that never scans usesTB = KT = 1, and one without key lookupsKT = 1. - Scan
Mems In - Port bundle of
ScanMems. - Scan
Mems Out - Read data of
ScanMems. - Start
Req - Start request: context
ctx(idle, inputs loaded) begins at bundleentry. - Table
Write - One key-table coordinate write (the key-loading path): one coordinate of one entry per cycle, so a full entry pair takes six writes.
Constants§
- BANK_A
- Bank A (adder results).
- BANK_I
- Bank I (inputs, indices below
CONST_BASE) and the constant ROM C (indicesCONST_BASEand up). They share a code so the operand field stays two bits wide. - BANK_L
- Bank L (selected lookup point).
- BANK_M
- Bank encodings in
OperandW::bank: bank M (multiplier results). - CONST_
BASE - Bank-I operand index of constant 0; lower indices are inputs. 96 leaves room for the compress program’s 96 inputs (32 points x 3 coordinates).
- CONST_
SLOTS - Constants a program can address (operand index
CONST_BASE + c): 32, the size of ROM C, which fills the 7-bit index field above the inputs. - KEY_
SLOTS - Public keys a known-key verifier holds at once.
- KEY_
SLOT_ BITS - Bits of a key-slot number (feature
key-slots-16: 16 slots). - KEY_
TABLE_ BITS - Rows of a verify machine’s key table: 512 per slot.
- LUT_
MACHINES - Machines after those with LUT-RAM banks (
ED25519_LUT_MACHINES). - URAM_
MACHINES - Replicated machines with UltraRAM banks in this build (
ED25519_URAM_MACHINES).
Functions§
- const_
rom_ kernel - Read slot
read_addrinto the register. - encode_
bundle - Encode a compiled bundle as a ROM word.
- field_
machine_ kernel - State transition for
FieldMachine. - field_
neg -x mod pfor canonicalx(zero stays zero).- key_
tables_ kernel - Wiring for
KeyTables. - niels_
identity - The group identity (the point
(0, 1)) in Niels form:y + x = 1,y - x = 1,2dxy = 0. A lookup of digit 0 returns it, and adding it is a no-op, so zero digits need no special case in the program. - no_key
- Key slot 0, for requests that look up no key. A kernel generic over a
width (
CB) cannot writebits(0)here: rustc would infer that width. - pack_
digits - Host-side helper: pack signed digits into the hardware sign-magnitude form (base-point scalar in slots 0..54; slots 64.. stay zero). For machines without key lookups.
- pack_
two - Host-side helper: base-point digits in slots 0..54 and key digits in slots 64..118.
- placement
- The layout of machine
kof a core’s replicated machines (fixed-base machines in a signer, verify machines in a verifier) in this build:MachineMemory::SPECIALIZEDfor the firstURAM_MACHINES,MachineMemory::LUT_BANKSfor the nextLUT_MACHINES, andMachineMemory::BRAM_BANKSfor the rest. - reg_
banks_ kernel - Wiring for
RegBanks: ports pass straight through to the memories. - scan_
mems_ kernel - Wiring for
ScanMems: ports pass straight through to the memories. - table_
images - A fixed-base table as two BRAM images (even and odd entries of each
pair), row
table_row(i) + pair.