Q020FreeSystemVerilog
How a Ready/Valid Skid Buffer Handles Backpressure
Question
Design a two-entry ready/valid skid buffer that can accept one extra item after downstream stalls, keeps output data stable while stalled, and removes the combinational ready path from downstream to upstream. Assume a positive static data width W and the usual sender rule: hold valid and payload until accepted. Transfers and stall obligations apply on rising edges outside reset. Active-low asynchronous reset discards all buffered items; release it safely for this clock before traffic resumes.
Implementation scaffold
module skid_buffer2 #(
parameter int unsigned W = 32
) (
input logic clk,
input logic rst_n,
input logic s_valid,
output logic s_ready,
input logic [W-1:0] s_data,
output logic m_valid,
input logic m_ready,
output logic [W-1:0] m_data
);
logic [W-1:0] main_q, skid_q;
logic [1:0] count_q;
logic push, pop;
assign s_ready = (count_q < 2);
assign m_valid = (count_q != 0);
assign m_data = main_q;
assign push = s_valid && s_ready;
assign pop = m_valid && m_ready;
always_ff @(posedge clk or negedge rst_n) begin : buffer_state
// TODO: Implement buffer_state using the supplied state and interface.
end
property p_stable_output;
// TODO: Implement stable_output with the stated clock, reset and timing contract.
endproperty
a_stable_output: assert property (p_stable_output);
endmodule
Trace one case
start with occupancy=1 and front=A
cycle 1: s_valid=1,B; m_ready=0
cycle 2: s_valid=1,C; m_ready=1 (buffer is full)
cycle 3: s_valid=1,C; m_ready=1cycle 1: B is accepted into the second slot; output A stays stable; occupancy=2
cycle 2: A retires, s_ready=0 so C is not accepted; occupancy=1
cycle 3: B retires and C is accepted; occupancy remains 1The second slot captures B after the stall. Because ready is registered from occupancy, a pop observed while full cannot simultaneously admit C on cycle 2.
Requirements
- Accept input only on s_valid && s_ready and retire output only on m_valid && m_ready.
- Preserve ordering across push-only, pop-only, and simultaneous push/pop cycles.
- Drive s_ready from registered occupancy rather than combinationally from m_ready; when already full, a newly observed pop cannot also accept a replacement that edge.
- When m_valid is high and m_ready is low, hold m_valid and m_data stable.
Short answer
Implement the buffer as a two-entry FIFO with registered occupancy. Set upstream ready whenever occupancy is below two, output the head entry whenever occupancy is nonzero, and update storage from the four push/pop combinations. A stalled output keeps its head value unchanged; a full buffer may insert one bubble because ready cannot anticipate a same-edge pop.
Why this reasoning works
Compute push only from s_valid and registered-capacity s_ready, and pop only from m_valid and m_ready. A push into an empty buffer fills the head; a second push fills the spare slot. A pop from two entries promotes the spare. When one entry is simultaneously popped and replaced, the incoming item becomes the new head.
Deriving s_ready solely from occupancy breaks the long combinational path from downstream ready to upstream ready. The tradeoff is deliberate: at occupancy two, an output handshake cannot also authorize an unadvertised input handshake on that edge, so the buffer can briefly bubble at the full boundary. Stability assertions should cover every downstream stall.
Interview takeaways
- Use depth-two occupancy
- Freeze output under stall
- Expect a full-boundary bubble
