See it move
The idea
In a long , the ready path back to the source can become the critical timing path. The natural fix is to register ready, but that creates a new problem: the upstream block sees the "stop" a cycle too late, and it has already sent one more word. That word needs somewhere to go.
A skid buffer supplies one spare storage slot for that word. The name comes from a car skidding a little further after the brakes are applied.
How it works
- Normal flow: data passes straight through, and the spare is empty.
- Downstream stops: the word already in flight lands in the spare.
readyto upstream goes low, since the spare is full.- Downstream resumes: the spare empties first, and then
readyrises again.
assign in_ready = !spare_full;
assign out_valid = spare_full || in_valid;
assign out_data = spare_full ? spare : in_data;
always_ff @(posedge clk)
if (rst)
spare_full <= 1'b0;
else if (spare_full) begin
if (out_ready) spare_full <= 1'b0;
end else if (in_valid && !out_ready) begin
spare <= in_data;
spare_full <= 1'b1;
endin_ready now comes from a flop, so the combinational path from out_ready has been cut.
Comparing the options
| Design | Ready path | Throughput | Extra storage |
|---|---|---|---|
| Simple pipeline register | Combinational | Full | None |
| Registered ready, no spare | Cut | Words get lost | None |
| Skid buffer | Cut | Full | One entry |
Full designs often register the output data as well as ready, cutting both directions.
Check it with stress
Test with random , because this kind of bug appears only with particular patterns. Check that every word arrives once, in order. In the animation above, switch between the two modes and toggle ready in different cycles.
Common mistake
Sizing the spare without counting the delay. One registered stage means one word in flight. With more register stages in the ready path, you need more entries, and the structure becomes a small .
Key ideas
- Registering ready adds a cycle of delay.
- One extra entry holds data already in flight.
- It is effectively a small FIFO, keeping full throughput.
Your exercise
Find the bug
This code has one deliberate bug. Read the symptom, then click the line you think is wrong.
The fix
assign out_data = spare_full ? spare : in_data;
Why it failed
The spare holds the oldest word, so while it is full the output has to come from it. Taking in_data instead skips that word, and the spare is then cleared as if it had been sent. Order is the thing to check in a skid buffer.
Interview questions
coreWhy not simply register ready?
The upstream sees it late, and a word sent in that cycle would be lost without a spare entry.
basicWhat problem does a skid buffer solve?
In a long pipeline with full-throughput stages, ready is a combinational chain running back through every stage, and it can become the critical timing path. Putting a flop on ready cuts that path, but the sender then hears 'stop' one cycle late and sends one more word. A skid buffer adds one spare entry to catch that word, so nothing is lost and throughput stays at one word per cycle.
basicIf ready reaches the sender through three register stages instead of one, how many spare entries do you need to avoid losing data?
Three. Each cycle of delay on the stop signal is one more word the sender may launch before it sees the stop. With three cycles of delay, up to three words are in flight, and each needs a place to land. At that size the structure is really a small FIFO, with an almost-full threshold set by the delay.
coreThis skid buffer sometimes delivers a word twice. Find the bug.
always @(posedge clk)
if (rst) spare_full <= 1'b0;
else if (spare_full) begin
if (out_ready) spare_full <= 1'b0;
end else if (!out_ready) begin
spare <= in_data;
spare_full <= 1'b1;
endThe capture condition omits in_valid, so if the downstream stalls in a cycle when no word is offered, the buffer still captures in_data and marks the spare full. A phantom copy of whatever is on the data bus is then delivered later. In simulation, after word 5 transferred, a stall with in_valid low caused word 5 to be delivered a second time. The condition must be in_valid && !out_ready.
coreIn the lesson's skid buffer, in_ready is !spare_full. Why does that cut the long ready path?
spare_full is a flop output, so in_ready no longer depends combinationally on out_ready. The upstream stage sees a clean signal that starts at a register, and the downstream ready only has to reach that flop's input within the cycle. Each skid buffer therefore ends one timing segment of the ready chain, instead of being one more link in it.
coreThe lesson's skid buffer cuts the ready path. Which path through it is still combinational, and why might that matter?
The forward path. When the spare is empty, out_valid and out_data come straight from in_valid and in_data through a mux. A chain of these buffers in flowing mode is therefore one long combinational data path. Full designs often add an output register too, which needs two entries of storage in total, to cut both directions.
coreHow would you verify a skid buffer?
Drive it with a sender that obeys the protocol, offering words with random gaps. Toggle out_ready randomly, including long stalls and single-cycle blips. Number the words, and have a checker confirm that every word arrives exactly once and in order, and that no word arrives that was never sent. Also check that throughput reaches one word per cycle when out_ready is held high, because a buffer that is correct but slow is still wrong.
advancedFour of the lesson's skid buffers are chained, words are offered every cycle, and the final out_ready drops and stays low. How many more words does the source send, and where do they go?
Four. The stop moves back one buffer per cycle, because each buffer's in_ready comes from its own spare_full flop. In each of those four cycles, the source launches one more word, and each word fills the spare of one buffer. Then the source's ready goes low. In simulation, the source had sent 9 words and the sink had received 5 when it all froze, with one word held in each of the four spares.
advancedHow does a skid buffer that also registers its output differ from the lesson's version?
It has a main output register plus the spare, so two entries. The output register drives out_valid and out_data from flops, cutting the forward path. The spare catches the word that arrives in the cycle when the downstream stalls but the upstream hasn't yet seen the registered ready. Throughput stays at one word per cycle, and the cost is one more register and a little more control logic.