Expand description
Read 16-byte messages from HBM and deal them to the SLICES slices.
§The problem
Every signature signs one message, and the messages start out in HBM,
written there by the host before the run. The slices need
them one at a time, each slice at its own pace, and every resulting frame
must be traceable back to the exact message it signed. This module is the
single AXI4 read master (see crate::axi4) that fetches the messages
and hands them out like a card dealer.
§The reasoning
- Fetch in bursts, deal one by one. HBM is efficient with long bursts and inefficient with tiny reads. A message is only 16 bytes, so the reader fetches 16-beat (1 KiB, 64-message) bursts into a small block-RAM FIFO (first-in, first-out queue) and unpacks four messages from each 64-byte beat.
- Round-robin, in order. Message
nalways goes to slicen mod SLICES. The rule is fixed, so the host knows in advance which slice’s frame region will hold each signature, and no tag or arbiter is needed. BecauseSLICESis a power of two,modis a bit mask (SLICE_MASK); RHDL kernels avoid/and%. - Credits instead of backpressure. A read address is issued only when
the FIFO has room for the whole burst. Data that the reader asked for
therefore always fits, and
rreadycan be tied high. That is simpler and safer than stalling the R channel.
§How it works
The host writes count messages back to back starting at base (4 KiB
aligned). Each 64-byte beat holds four messages. Message n goes to slice
n mod SLICES, carrying its index n, which the slice writes into the frame
padding so the host can check every frame against the right message.
HBM ──16-beat bursts──▶ beat FIFO (64 beats, credit-limited) ──▶ unpack 4/beat
│ msg n
slice (n mod SLICES) buffer ◀┘Read addresses are issued only while the FIFO has room for the whole burst,
so read data can always be accepted (rready is constant high). A slice
buffer holds one message; dealing stalls when the target slice’s buffer is
still full, which keeps messages strictly in order.
§Four counters track every beat
All bookkeeping is in beats (64 bytes = 4 messages) except dealt,
which counts messages:
consumed ≤ received ≤ requested ≤ ceil(count / 4)
requested − consumed beats the FIFO has promised room for (≤ 64)
received − consumed beats sitting in the FIFO, ready to unpack
lane which of the 4 messages in the head beat is next
dealt messages handed to slices = index of the next oneThe FIFO is a 64-entry ring of 64-byte words: the low 6 bits of
received are the write slot and the low 6 bits of consumed the read
slot, so the ring wraps for free.
§Timing
The block-RAM FIFO has one clock of read latency. Moving a beat into the
head register therefore takes two steps: a cycle that sets
read_pending, then a cycle that captures the RAM output. After that the
dealer can hand out at most one message per clock, so one beat (4
messages) is unpacked in 4 clocks when the target slices are free. A
slice consumes a message far less often than that (one key per 68 clocks,
see crate::slice_schedule), so the reader is not the bottleneck.
§Neighbours
crate::engine supplies base, count and run from its registers,
forwards the AXI read channel, and wires each slice’s message_taken
back here as taken. The Message type, and the rule that a message
is held until taken, come from crate::slice_signer. The dealt count
is the host-visible DEALT register and error is FAULT bit 1.
Structs§
- Message
Reader - The message reader and dealer.
- Reader
Control - Resettable control.
- Reader
Data - Resetless data.
- Reader
Input - Reader inputs, sampled every clock.
- Reader
Output - Reader outputs.
Constants§
- BURST_
BEATS - Beats per read burst (1 KiB).
- FIFO_
BEATS - Beat FIFO capacity.
Functions§
- message_
reader_ kernel - The reader’s logic.