AMBA AXI · Module 15
The Skid Buffer
Build the workhorse of pipelined AXI — a two-entry skid buffer that fully registers a VALID/READY handshake for timing closure while sustaining one-beat-per-cycle throughput. Why a single register stalls, how the skid (overflow) register absorbs the bubble, and the FSM that runs it.
Every AXI channel is a VALID/READY handshake, and at speed those handshakes must be registered on both sides to close timing — an unregistered path lets the consumer's READY ripple combinationally back to the producer, creating a long, often critical, path across a chip. But naively registering the handshake either breaks throughput (you drop to one beat every two cycles) or breaks the protocol (you lose data when the downstream stalls). The skid buffer is the standard solution: a small, two-entry buffer that registers both VALID and READY, breaks the combinational path in both directions, and still sustains full one-beat-per-cycle throughput. It's the most reused piece of AXI RTL there is — in interconnect ports, FSM outputs, FIFO front-ends, and pipeline stages. This chapter builds it and explains exactly why the "skid" (overflow) register is necessary.
1. The Problem: Registering a Handshake Naively
You want to insert a pipeline register in an AXI channel so neither side sees a combinational path through the other. The naive options both fail:
- Register only the data +
VALID(a "pipe" stage), passREADYstraight through combinationally. This breaks the forward path but leavesREADYrippling backward — timing isn't fully closed. - Register
READYtoo, with a single data register. Now both directions are registered, but there's a hazard: in the cycle the downstream assertsREADYafter a stall, the upstream — which only just sawREADYgo high (it's registered, so one cycle late) — may have already placed a new beat on the bus. With only one register, you have nowhere to put the beat that's "in flight" whenREADYtoggles. You either drop it (data loss) or you must have held off accepting it, costing a bubble every timeREADYdeasserts — half throughput.
The fix is a second register — the skid or overflow register — to catch exactly that one in-flight beat. Two registers are both necessary and sufficient for a fully-registered, full-throughput handshake.
2. The Two-Entry Structure
A skid buffer holds up to two beats: a main register that feeds the output, and a skid register that captures the overflow beat that arrives in the cycle the buffer becomes full. Upstream READY is asserted whenever the skid register is empty (i.e. there's room for one more in-flight beat), so the upstream never sees a combinational READY and is never starved while space exists.
module skid_buffer #(parameter int W = 32) (
input logic clk, rstn,
// upstream (sink)
input logic [W-1:0] s_data,
input logic s_valid,
output logic s_ready,
// downstream (source)
output logic [W-1:0] m_data,
output logic m_valid,
input logic m_ready
);
logic [W-1:0] skid_data;
logic skid_valid;
// Accept upstream while we have room for the overflow beat
assign s_ready = !skid_valid;
always_ff @(posedge clk) begin
if (!rstn) begin
m_valid <= 1'b0; skid_valid <= 1'b0;
end else begin
if (s_ready) begin
// room: capture incoming beat into skid if output is busy,
// else straight into the main register
if (m_valid && !m_ready) begin
// output stalled and we just took a beat → it skids
if (s_valid) begin skid_data <= s_data; skid_valid <= 1'b1; end
end else begin
// output free (or being consumed): load main directly
m_data <= s_valid ? s_data : m_data;
m_valid <= s_valid ? 1'b1 : (m_ready ? 1'b0 : m_valid);
end
end
// when output is consumed and a skid beat waits, promote it
if (m_valid && m_ready) begin
if (skid_valid) begin
m_data <= skid_data; m_valid <= 1'b1; skid_valid <= 1'b0;
end else if (!s_ready || !s_valid) begin
m_valid <= 1'b0;
end
end
end
end
endmodule(The exact coding varies — many equivalent forms exist — but the invariant is the same: s_ready = !skid_valid, the skid register catches the overflow beat, and a consumed output promotes the skid beat.)
3. The State Machine
The skid buffer's occupancy is a tiny FSM over three states by fill level: EMPTY (no beat; s_ready=1, m_valid=0), ONE (main holds a beat; s_ready=1, m_valid=1 — still room in skid), and FULL (main + skid both hold beats; s_ready=0, m_valid=1). Throughput is full because in the common streaming case the buffer sits in ONE, accepting and emitting a beat every cycle.
4. The Timing: Full Throughput Through a Stall
The waveform shows the payoff: a stream of beats flows one-per-cycle; when the downstream drops m_ready for a cycle, the skid register catches the in-flight beat so no data is lost and no upstream bubble is created — when m_ready returns, the skid beat is emitted and streaming resumes at full rate.
Skid buffer absorbing a one-cycle stall
9 cycles5. Common Misconceptions
6. Debugging Insight
7. Verification Insight
8. Interview Questions
9. Summary
A skid buffer fully registers an AXI VALID/READY handshake — flopping both the forward (VALID/data) and backward (READY) directions — to close timing, while sustaining one beat per cycle. The key insight is that registering the backpressure makes the upstream see READY one cycle late, so a beat can be in flight when the buffer fills; a single register can't hold it (forcing data loss or a half-throughput bubble), so a second register — the skid/overflow slot — catches exactly that one in-flight beat. The canonical design sets s_ready = !skid_valid (registered backpressure: accept while the overflow slot is free), drives the output from a main register, and promotes the skid beat into main when the output is consumed. Occupancy is a tiny EMPTY/ONE/FULL FSM whose steady state is ONE with a self-loop (one in, one out, every cycle).
The cost is one cycle of latency and two registers; throughput is untouched. Two entries are provably the exact minimum for a fully-registered, full-throughput handshake. Verification centers on the throughput-through-a-stall test (continuous input, m_ready toggled) plus formal data-conservation/ordering and payload-stability properties — small enough to prove exhaustively. The skid buffer is the most-reused AXI primitive: interconnect ports, FSM outputs, FIFO front-ends, and pipeline stages all rely on it. Next, we generalize it into pipelining a handshaked path across multiple stages.
10. What Comes Next
You've built the timing-closure workhorse; next we use it to pipeline a handshaked path:
- 15.6 — Ready/Valid Pipelining (coming next) — chaining registered handshakes into a multi-stage pipeline without losing data or throughput, the general form of what the skid buffer does for one stage.
Previous: 15.4 — AXI Read FSM. Related: 3.1 — The VALID/READY Handshake for the contract the skid buffer registers, 3.3 — Backpressure & Stalls for the stall behavior it absorbs, and 3.5 — Handshake Dependency & Deadlock Rules for why VALID must not depend on READY.