Skip to main content

Module machine

Module machine 

Source
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.
Taking turns: 32 contexts share one multiplier Each square is one clock. Colour = which context issues. The multiplier is busy every clock. 0 1 2 3 … 31 0 1 … 32 clocks between turns ≥ 22-clock multiply + write-back: no waiting, no hazards Program ROM392 instructions Register banksblock RAM, per context Multiplier225 DSPs, 1/clock Adderadd / subtract Table scannerconstant-time lookup Every instruction can use all three units at once (multiply + add + one table step).

§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§

ConstRom
The constant ROM C: CONST_SLOTS constants as a literal and a registered read, with the ports of a [SyncBRAM] (the write port is ignored).
DigitsWrite
Digits write for the table scanner (sign-magnitude: bit 5 = sign).
FieldMachine
Barrel field machine. K = 2^KB contexts; RA/IA are bank-M/A and bank-I address widths (index * K + ctx); PB is the program-counter width. TB and KT are the base-point and key table address widths. See the module docs for the pipeline and bank layout.
InputWrite
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.
KeyTablesIn
Port bundle of KeyTables: one shared read row, one coordinate write.
MachineIn
Machine inputs, all sampled every cycle and independent of each other.
MachineMemory
Where a field machine keeps its memories.
MachineOut
Machine outputs.
Niels17
Affine Niels point in radix-2^17 digits: 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 OUT bundle 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).
RegBanksIn
Port bundle of RegBanks.
RegBanksOut
Read data of RegBanks, one cycle after the read address.
ScanMems
Table-scan memories: bank L, the scan accumulator, digits, the base-point table (2^TB rows) and the key table (2^KT rows). A machine that never scans uses TB = KT = 1, and one without key lookups KT = 1.
ScanMemsIn
Port bundle of ScanMems.
ScanMemsOut
Read data of ScanMems.
StartReq
Start request: context ctx (idle, inputs loaded) begins at bundle entry.
TableWrite
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 (indices CONST_BASE and 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_addr into the register.
encode_bundle
Encode a compiled bundle as a ROM word.
field_machine_kernel
State transition for FieldMachine.
field_neg
-x mod p for canonical x (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 write bits(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 k of a core’s replicated machines (fixed-base machines in a signer, verify machines in a verifier) in this build: MachineMemory::SPECIALIZED for the first URAM_MACHINES, MachineMemory::LUT_BANKS for the next LUT_MACHINES, and MachineMemory::BRAM_BANKS for 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.

Type Aliases§

FieldMul
The field multiplier of this build (ED25519_MULTIPLIER).
KeySlot
A key-slot number.