This is the appendix to Agentic AI for FPGA Design: How the Loop Works. That article uses a 32 bit to 128 bit AXI4-Stream width converter as its worked example and hands the design to an agent. This page is for the person who wants to understand the block itself: what the interface promises, what the converter must do on every clock edge, one way to build it, and what the testbench in the companion repository checks. Read it before you run the loop, or after, when a mismatch report needs decoding.
The AXI4-Stream handshake
AXI4-Stream carries data in one direction, from a master to a slave, in beats. Every beat is a bundle of signals qualified by two handshake lines.[1] The converter has two such interfaces: a slave side that receives 32 bit beats, prefixed s_, and a master side that sends 128 bit words, prefixed m_.
| Signal | Driven by | Meaning |
|---|---|---|
tdata | master | The payload of the beat |
tvalid | master | The beat on tdata is real and may be taken |
tready | slave | The slave can accept a beat this cycle |
tlast | master | This beat ends a packet |
tkeep | master | One bit per byte of tdata: 1 if the byte carries data |
A beat transfers on a clock edge where tvalid and tready are both high. The specification adds three rules that every AXI4-Stream block lives by, and the second exists to make chains of blocks deadlock free:
- Once the master raises
tvalid, it keeps it high, and keepstdata,tkeepandtlastunchanged, until the transfer happens. The master does not get to change its mind. - The master must not wait for
treadybefore raisingtvalid. A slave is allowed to wait fortvalidbefore raisingtready. If both waited for each other, nothing would ever move. treadymay go high and low freely whiletvalidis low. Only the cycle where both are high means anything.
Our converter has the slave role on its input and the master role on its output, so it must obey rule 1 on m_tvalid and is free to drive s_tready from its own state, including from m_tready.
What the converter does
The input carries 32 bit beats, four bytes each, all of them valid; there is no s_tkeep. The output carries 128 bit words. Beat i of a packet lands in output word i / 4, in lane i mod 4, where lane 0 is bits 31:0 and lane 3 is bits 127:96. The first beat of a word sits in the least significant lanes.
A word is complete when it holds four beats, or when the beat it just took was marked s_tlast. The second case is the whole difficulty. A packet whose length is not a multiple of four ends with a word that is only partly filled, and the converter has to emit it anyway, with two things set correctly:
m_tkeepmarks which bytes hold data. A word withnbeats has its low4nbits set:000Ffor one beat,00FFfor two,0FFFfor three,FFFFfor four. The bytes above are unspecified and a downstream block must not read them.m_tlastis high on that word and on no other.
Two more rules follow from "packets are never merged". The word after an m_tlast word starts a new packet with its first beat in lane 0, whatever the previous packet's length was. And a packet of length exactly four, or eight, or any multiple, ends with a full word that has tkeep = FFFF and m_tlast high at the same time; that is one word, not a full word followed by an empty one.
This is where the bug in the main article's walkthrough lives. A converter that only emits on "four beats collected" never flushes the partial word, so the last one to three beats of every packet whose length is not a multiple of four vanish. The checker then reports a mismatch on the next packet's first word, because the reference model was still expecting the word that never came.
Back pressure
The output can stall: m_tready goes low while the converter is holding a word. Rule 1 says the converter must keep m_tvalid high and the word unchanged. The question is what happens to the input meanwhile.
With the simplest architecture, one output register, the answer is that the input stalls too: s_tready follows m_tready whenever the register is occupied. Beat b4 sits on the input for two cycles and is accepted on the very edge the word drains, because on that edge the register is being emptied and refilled at once. That is legal and it is what the reference model expects. What is not legal is accepting b4 while the word is still held, because the only place to put it is the register that rule 1 says must not change.
The other direction is simpler. If s_tvalid drops in the middle of a packet, the converter waits. It holds the beats it has, emits nothing, and continues when the next beat arrives.
An architecture
The specification does not prescribe a structure, and the agent in the main article is free to find its own. This is the one the reference solution uses, and it is close to the smallest thing that works.
| Register | Width | Role |
|---|---|---|
data_r | 128 | The word being assembled and, once complete, the word being presented. Each lane has its own write enable. |
fill | 2 | How many beats the pending word holds. Counts 0, 1, 2, 3 and returns to 0 on complete. |
valid_r | 1 | m_tvalid. Set on complete, cleared when the word transfers. |
keep_r | 16 | m_tkeep. Loaded on complete from fill. |
last_r | 1 | m_tlast. Loaded on complete from s_tlast. |
The control is three equations:
s_tready = rst_n and (not valid_r or m_tready). The input may deliver when the output register is empty, or when it is full but draining this cycle, and never during reset.accept = s_tvalid and s_tready. On accept,s_tdatais written into lanefillofdata_r.complete = accept and (fill = 3 or s_tlast). On complete,valid_ris set,keep_rgets the low4 (fill + 1)bits,last_rgetss_tlast, andfillreturns to 0. On an accept that does not complete a word,fillincrements. When nothing is accepted,fillholds.
One subtlety is worth tracing. On the edge where a full word drains and a new beat is accepted at the same time, valid_r is cleared by the transfer and, if that beat also completes a word (a single beat packet, say), set again by complete. Complete has to win, so the assignment for complete comes after the assignment for the transfer in the same process. Get the order wrong and single beat packets that arrive during a drain disappear.
A second subtlety is that data_r doubles as the assembly buffer and the output register. That works because the lanes not being written hold their value, and because s_tready guarantees no lane is written while the word is being held. Lane 0 of the next word is written on the same edge the previous word transfers, which is fine: the downstream block sampled the old value on that edge.
What this architecture gives up is a registered s_tready. It is a combinational function of m_tready, so a long tready path through a chain of such blocks adds up. The standard cure is a skid buffer on the 32 bit input, which lets s_tready come from a register for about 34 more flops, one beat of data, tlast and a valid bit. On the 100 MHz project clock in the repository the simple version has room to spare, so the exercise does not need it, but it is the first thing to reach for when it does not.
What the testbench checks
The testbench in the companion repository is written from the specification and does not know how the converter is built. Three parts matter.
The reference model watches the input handshake, packs the beats exactly as described above, and queues the word it expects next, with its tkeep and tlast. It also handles the zero latency case where a word completes on the same edge it is transferred, so a combinational converter is judged fairly.
The checker compares each transferred output word against the head of that queue. Data is compared only on the bytes tkeep marks as valid. It also enforces the handshake: m_tvalid dropping or the word changing while m_tready is low is a protocol error, and so is a tkeep pattern that is not one of the four legal ones. Beat payloads are {packet, beat} in the upper and lower 16 bits, so in a mismatch line a lane reading 0025_0002 is packet 37, beat 2, and the hex dump shows lane 3 on the left and lane 0 on the right.
Coverage is fourteen functional points, collected by the checker and reported at the end of every run: words of every fill from one to four, stalls while holding each of those, tlast on a full word, single beat packets, a stall of three or more cycles, back to back transfers, a gap inside a packet on the input, and an input beat held while s_tready is low. Out of the box the stimulus drives random packet lengths and random gaps on the input but keeps m_tready high, so the six points that need a stall are not reachable, and python run.py cov fails even on a correct design. Adding back pressure in tb/stimulus.sv is part of the exercise, and it is the gap the agent finds in step 5 of the main article.
Once back pressure is in, the report the agent reads ends like this:
WORDS: 633
OUTPUT STALL CYCLES: 561
INPUT STALL CYCLES: 365
MISMATCHES: 0
PROTOCOL ERRORS: 0
COVERAGE: 14/14
RESULT: PASS
The mistakes the checker catches
These are the failures the reference solution was mutated into, one at a time, to confirm the testbench sees them. Each one is also a plausible first attempt.
| Mistake | What the report shows |
|---|---|
Only emit on four beats, ignore s_tlast | Mismatches on every packet whose length is not a multiple of four: the model expects a partial word that never arrives, so the next packet's first word is compared against it. |
m_tkeep always FFFF | Mismatches on every partial word, data correct, keep wrong. |
Clear m_tvalid every cycle instead of on transfer | Protocol errors: valid dropped while stalled. Words are lost, so mismatches follow. |
s_tready high while the output is held | Protocol errors: the held word changes under the downstream block. Mismatches follow. |
| Beats packed into the wrong lanes | Mismatches on every word, with the payload showing which beat landed where. |
If you are running the loop from the main article, this table is a decoder for the agent's third and fifth steps. If you are writing the converter yourself, it is the checklist.
References
2 sources
- Arm IHI 0051, AMBA 4 AXI4-Stream Protocol Specification: the signal definitions, the handshake rules, and the byte qualifiers.
- Agentic AI for FPGA Design: How the Loop Works: the article this page accompanies.
Our FPGA and AI training builds the loop from the main article on designs like this one, with the checks and limits in place.
Comments
Loading comments...
Leave a Comment