A sensor block streams one sample per cycle into a filter. The interface is just valid and data. It works perfectly until the filter gains a feature that occasionally needs two cycles for one sample. In those cycles a sample arrives anyway and is lost, and the filter's output develops small, rare glitches that look like noise in the sensor.
When is a stream with no way to say "wait" good enough, and what does it cost when it is not?
The obvious design for a stream is the cheapest one: the producer drives valid high whenever its data is real, and the consumer captures every valid word. No other signal, no other logic. It is correct exactly as long as the consumer can accept a word in every cycle the producer might send one. The moment the consumer cannot, for one cycle, there is nowhere for the word to go and no way to tell the producer. That is called overrun: data arrives faster than the receiver can take it, and the excess is lost.
A valid-only interface is not missing a feature; it moves a promise from the protocol into the design. Instead of the receiver saying "wait" when it needs to, the receiver promises never to need to: it will accept a word in every cycle, at the producer's peak rate, forever. If that promise holds, valid-only is the best interface possible: no extra wires, no handshake timing paths, no stalls.
The promise usually comes from one of three places:
The consumer is fixed-rate and at least as fast as the producer: a pipeline that always takes one word per cycle.
A buffer absorbs bursts: a FIFO sized for the longest burst at the peak rate, drained faster than the average arrival rate.
The data may be dropped: some streams (sensor samples, video in some cases) prefer losing a sample to stalling the source.
The testbench streams ten words, one per cycle, to a consumer that is busy in three cycles. The consumer has no way to push back, so the words that arrive in those cycles are lost. The checker counts them.
// A valid-only stream: the producer sends a word whenever it has one, and the
// consumer has no way to say "not now". The consumer takes one word per cycle except
// while it is busy (for example writing to a slow memory). Any word that arrives while
// it is busy is lost. The checker counts lost words and confirms the count matches the
// busy cycles that overlapped the stream.
module tb;
logic clk = 0;
always #5 clk = ~clk;
logic rst_n = 0;
initial begin
$dumpfile("wave.vcd"); $dumpvars(0, tb);
#12 rst_n = 1;
end
int cyc = 0;
always @(posedge clk) if (rst_n) cyc <= cyc + 1;
// Producer: words 0..9, one per cycle, starting in cycle 1. No way to be told to wait.
logic vld;
logic [7:0] data;
assign vld = (cyc >= 1 && cyc <= 10);
assign data = vld ? 8'(cyc - 1) : 8'h00;
// Consumer: busy in cycles 4, 5 and 8 (it cannot accept anything then).
wire busy = (cyc == 4 || cyc == 5 || cyc == 8);
wire lost = vld && busy; // the word on the wire this cycle is dropped
logic [7:0] got [0:9]; int n = 0, nlost = 0;
always @(posedge clk) if (rst_n && vld) begin
if (busy) nlost++;
else begin got[n] = data; n++; end
end
initial begin
#160;
if (n == 7 && nlost == 3 && got[3] == 8'd5)
$display("PASS: 3 of 10 words lost while the consumer was busy; valid alone cannot push back");
else $display("FAIL: received %0d, lost %0d", n, nlost);
$finish;
end
endmoduleHow to read it, slot by slot:
Slots 1 to 10: vld is high and data carries words 0 to 9, one per cycle. The producer never waits, because nothing tells it to.
Slots 4 and 5: the consumer is busy. Words 3 and 4 are on the wire, and lost is high: they vanish.
Slot 8: busy again; word 7 is lost.
Result: the consumer receives 0, 1, 2, 5, 6, 8, 9. Nothing in the interface reports the gaps.
If you keep a valid-only interface but put a FIFO in front of the consumer, the FIFO must hold every word that arrives while the consumer is behind. With illustrative numbers: a producer bursts at one word per cycle for up to 64 cycles, and the consumer drains at 3 words every 4 cycles.
During a 64-cycle burst:
words arriving = 64
words drained = 64 x 3/4 = 48
FIFO must hold = 64 - 48 = 16 words (more if bursts can come back to back)
And on average the consumer must keep up: average arrival rate < 0.75 words per cycle,
otherwise no finite FIFO is enough.That is the general trade: without back-pressure, the receiver's buffering must cover the worst-case burst, and its long-run rate must beat the producer's. With back-pressure (the rest of this part), the producer simply waits, and a small buffer suffices.
Option | What it costs | When it wins |
|---|---|---|
Valid only | Nothing extra, but the consumer must keep up every cycle | Fixed-rate pipelines; data that may be dropped |
Valid only + FIFO sized for the worst burst | Buffer area; a guaranteed average rate | Bursty sources that cannot be stalled (for example a sensor or a link) |
Valid + ready (lesson 3.3) | One more wire and a timing path backwards | Almost all on-chip streams where the producer can wait |
Credits (Part 4) | A counter and a return path | Long paths where a per-cycle ready would arrive late |
Protocol | Its choice |
|---|---|
AXI-Stream | Defines TREADY for back-pressure: a transfer happens only when TVALID and TREADY are both high. A stream that never needs back-pressure can hold TREADY high, which turns it into a valid-only stream. |
UART | No back-pressure in the basic protocol: the receiver must take every character, and buffering (or optional flow-control wires) is the receiver's problem. |
Both show the same principle from opposite ends: AXI-Stream builds back-pressure in and lets a design opt out; UART leaves it out and lets a design add it.
The opening story, as debug steps (illustrative):
Symptom: rare, small glitches in the filter's output that look like sensor noise.
Evidence: a counter of words received over a fixed time is slightly lower than the number sent, and the shortfall matches the number of two-cycle samples.
Root cause: a valid-only interface whose consumer broke its promise to accept every cycle.
Fix: either restore the promise (keep the filter at one sample per cycle), add a FIFO sized for the worst case, or add a ready signal so the sensor path can wait. The cheapest permanent fix is an assertion on the consumer: "never busy while valid is high", so the broken promise is caught the day it is introduced.
During a 4-cycle burst the consumer drains 2 words, so 2 must be buffered (plus margin). Between bursts it has 12 cycles to drain them, far more than needed, so the average rate is fine. A buffer of a few words makes it safe.
Nothing in a valid-only interface reports it: data simply goes missing, and the symptom appears far downstream as corrupted results.
It turns an AXI-Stream interface into a valid-only stream: the consumer promises to accept every transfer, and back-pressure is never used.
A valid-only stream moves a promise into the design: the consumer must accept a word in every cycle the producer may send one.
When that promise breaks, words are lost silently (overrun).
Without back-pressure, buffering must cover the worst-case burst and the consumer must beat the producer's average rate.