The valid/ready contract
Nearly every streaming interface in modern RTL — AXI-Stream, most internal bus protocols, and every pipeline in this track — uses some form of valid/ready handshaking. The rules are simple to state but easy to get subtly wrong:
- The source asserts
in_validand drivesin_datato indicate it has data to send. - The sink asserts
in_readyto indicate it can accept data this cycle. - A transfer happens exactly on cycles where both
in_validandin_readyare1. On any other cycle, no transfer occurs, no matter whatin_datashows. - The source must hold
in_dataandin_validstable oncein_validis asserted, until a transfer actually occurs (i.e. it may not “give up” and dropin_validwhile the sink isn’t ready — that would be data loss, since the source can’t tell whether the sink already acted on it). - The sink may assert or deassert
in_readyon any cycle, for any reason (e.g. its own internal buffer is full) — the source cannot assume anything about whenin_readywill go high.
This problem is the simplest possible correct implementation of that contract: a wire-through with no registers and no buffering, where the block’s own ready is just a direct reflection of what’s downstream.
Interface
| Signal | Direction | Description |
|---|---|---|
in_data |
input | Data from the upstream source. |
in_valid |
input | High when in_data is a genuine offer to transfer. |
in_ready |
output | High when this block can accept in_data this cycle. |
out_data |
output | Data forwarded to the downstream sink. |
out_valid |
output | High when out_data is a genuine offer to transfer. |
out_ready |
input | High when the downstream sink can accept out_data. |
Correctness constraints
- No data may ever be silently dropped: whenever
in_valid && in_readyis true, that same data must appear onout_datawithout_validtrue in the same cycle. out_validmust be low wheneverin_validis low — this block must never “invent” a transfer.in_readymust faithfully reflect whether accepting data this cycle would actually be safe to forward — for this purely combinational block, that meansin_readysimply equalsout_ready, since there is no buffering to decouple the two sides.- Because there are no registers on the data path, this block introduces zero cycles of latency, but also zero cycles of decoupling — if
out_readyis combinationally driven off logic that also depends onin_readysomewhere upstream, that’s a real timing hazard in a large design (motivating the registered stages in the next problems in this category).