VHDL · Chapter 18.2 · Advanced RTL Design
Handshaking Protocols
A bare pipeline assumes the consumer is always ready, but real systems need the two sides to agree on when data moves. The standard mechanism is valid and ready handshaking, the AXI-Stream style. The producer raises valid when it has data, the consumer raises ready when it can accept, and a transfer happens on the clock edge only when both are high. This gives backpressure for free, since the consumer simply drops ready to stall the producer. Two rules keep it correct and deadlock-free. The producer must hold its data and valid stable until the transfer completes and never drop an offered word, and valid must not depend on ready or you create a combinational loop. When the two sides need to run independently, a skid buffer or FIFO decouples them. This lesson covers the contract, backpressure, the stability and dependency rules, and decoupling.
Foundation15 min readVHDLRTLHandshakingvalid/readyBackpressureAXI-Stream
1. Engineering intuition — both sides must say yes
Moving data between two blocks is a negotiation, not a shove. The producer signals "I have a word" (valid); the
consumer signals "I can take one" (ready); and the word actually moves only when both agree on the same
clock edge. That single rule gives you flow control in both directions: if the consumer is busy it drops ready
and the producer simply waits, holding its word — that is backpressure. The discipline that makes it work
is honesty and stability: once the producer offers a word it must keep offering the same word until it is
taken (no take-backs), and it must decide valid without peeking at ready (otherwise the two chase each
other combinationally). Get those right and any two blocks can be wired together and will throttle each other
correctly.
2. Formal explanation — the valid/ready contract
-- TRANSFER occurs on a clock edge when valid AND ready are BOTH high.
transfer <= valid and ready; -- one word moves this cycle iff both are asserted
-- PRODUCER: assert valid with data; HOLD both stable until accepted; advance only on transfer.
process (clk) begin
if rising_edge(clk) then
if rst = '1' then valid <= '0';
elsif (valid = '1' and ready = '1') then -- accepted → produce next
data <= next_data;
valid <= have_next;
elsif valid = '0' and have_next = '1' then -- start offering a word
data <= next_data;
valid <= '1';
end if; -- if valid and NOT ready → HOLD (data/valid unchanged)
end if;
end process;
-- RULES:
-- • valid must NOT depend (combinationally) on ready → ready may depend on valid (not both).
-- • producer must HOLD data+valid stable while valid='1' and ready='0' (never drop an offered word).A transfer happens when valid and ready on a clock edge. The producer holds data and valid stable
until accepted and advances only on transfer; the consumer asserts ready when able. valid must not depend
on ready (avoids a combinational loop); ready may depend on valid.
3. Production usage — backpressure and decoupling
-- BACKPRESSURE: the consumer stalls the producer simply by deasserting ready.
ready <= can_accept; -- low when the consumer is busy/full → producer holds
-- DECOUPLE producer and consumer (so neither's combinational timing affects the other):
-- a SKID BUFFER (1-2 entry) or a FIFO sits between them, presenting valid/ready on each side.
-- • absorbs a cycle of latency in ready, prevents the two sides' logic from forming long paths
-- • lets a producer keep streaming while the consumer occasionally stalls (FIFO smooths bursts)
-- Typical wiring: producer.valid → fifo.wr_valid ; fifo.wr_ready → producer.ready
-- fifo.rd_valid → consumer.valid ; consumer.ready → fifo.rd_readyWhat hardware does this become? A small amount of control logic per interface — a valid register on the
producer, a ready from the consumer, and the valid and ready transfer condition — wrapping whatever data path
flows between them. Backpressure is just the consumer's ready going low, which (because the producer holds)
stalls the flow with no data loss. A skid buffer or FIFO in the middle decouples the two: it breaks
long combinational ready paths (helping timing) and absorbs bursts so a producer can keep running through brief
consumer stalls. This valid/ready pattern is the universal interface contract — AXI-Stream, internal block
boundaries, pipeline plumbing — because any two compliant blocks compose correctly.
4. Structural interpretation — producer/consumer valid/ready
5. Simulation interpretation — transfer and stall
valid/ready: transfer when both high; stall holds data
8 cycles6. Debugging example — dropped data or a valid/ready loop
Expected: lossless flow with correct backpressure. Observed (a): words are lost or duplicated when the
consumer stalls. Observed (b): the design has a combinational loop / unstable handshake, or deadlocks.
Root cause (a): the producer changed or dropped data/valid while valid='1' and ready='0' — it did
not hold the offered word until accepted, so a stalled word was lost. Root cause (b): valid was made to
depend combinationally on ready (and ready on valid), forming a loop — or both sides waited on each other.
Fix: hold data and valid stable until valid and ready (advance only on transfer), and ensure
valid does not depend on ready (only ready may depend on valid); decouple with a skid buffer/FIFO to
break long ready paths. Engineering takeaway: never drop an offered word and never let valid depend on
ready — hold until accepted and keep the dependency one-way, or you get data loss or a handshake loop.
-- BUG: producer advances/drops valid regardless of ready → stalled word lost.
-- if rising_edge(clk) then data <= next; valid <= have_next; end if; -- ignores ready
-- FIX: advance only on transfer; hold otherwise.
if rising_edge(clk) then
if valid='1' and ready='1' then data <= next; valid <= have_next; end if; -- hold when not accepted
end if;7. Common mistakes & what to watch for
- Dropping an offered word. Hold
data/validstable untilvalid and ready; advance only on transfer, or data is lost. validdepending onready. That forms a combinational loop; keep the dependency one-way (readymay depend onvalid).- No backpressure handling. If the consumer can stall, the producer must honor
ready; otherwise it overruns. - Long combinational ready chains. Decouple with a skid buffer/FIFO to break timing-critical ready paths.
- Deadlock from mutual waiting. Ensure progress is possible (one side can always eventually assert); avoid circular ready/valid dependencies across blocks.
8. Engineering insight & continuity
Valid/ready handshaking is the universal RTL flow-control contract: a transfer occurs only when valid and ready, the consumer applies backpressure by dropping ready, the producer holds its word until
accepted, and valid never depends on ready — with skid buffers/FIFOs to decouple the two sides. Any two
compliant blocks compose losslessly. The most important decoupling buffer is the FIFO itself — sized storage that
smooths bursts and bridges rate mismatches — which is the next lesson, FIFO Design: the architecture,
pointers, and full/empty logic of the queue at the heart of streaming RTL.