The interviewer slows the consumer mid-stream: downstream deasserts ready for several cycles. The design must hold every item, deliver each exactly once, and resume on its own.
What is really under test is the valid/ready contract under backpressure. The candidate must show where the item waits, what ready means while it waits, and why the pipe cannot wedge.
A stalled item waits in its stage register: valid stays high, data holds, and ready is low, so no transfer occurs. A bubble is different: valid is low because the slot is empty. Backpressure can fill earlier empty slots; it must not turn an unaccepted valid item into a bubble.
The acceptance rule is in_ready = !full || out_ready. An empty stage always accepts. A full stage accepts only while the consumer takes the held item in the same cycle, so nothing is ever overwritten.
Signal | Role | Rule during a stall |
|---|---|---|
| This cycle carries an item | Stays high with stable data until the handshake. |
| The receiver can take it | Falls while full and blocked; never drops an item. |
Data | The item itself | Held unchanged until |
Deadlock freedom follows from two facts. The stage accepts whenever it is empty, independent of downstream, and it empties whenever downstream is ready, independent of upstream.
A source must never wait for ready before asserting valid. If the peer's ready also waits for valid, the handshake deadlocks (or forms a combinational loop).
Each slot shows the handshake just after a clock edge. A transfer happens only on an edge where valid and ready are both high.
Slots 2 to 6: out_ready stays low for five cycles. The held data stays flat, in_ready stays low, and valid never drops.
Slot 7: ready returns, so the held item leaves while the waiting item is captured in the same cycle. Two items move at once.
Progress: the stage accepts whenever it is empty and empties whenever downstream is ready, so the link resumes on its own once ready returns.
May ready depend on valid? Within one stage, ready routinely reflects state that valid also affects. The forbidden direction is valid gated combinationally by ready, which stalls the very signal needed to advance.
Does this stage scale to a long chain? It passes ready through combinationally, so in a chain of N stages ready ripples through N gates in one cycle. Long chains need a registered-ready skid buffer (two entries) to cut that path.
How does a stall differ from a flush? A stall holds items in place and resumes the same stream. A flush discards in-flight items and restarts, which needs an explicit kill and re-issue policy.
What if the consumer never asserts ready again? The pipe fills and parks with all valids high. That is correct backpressure, not a logic deadlock; recovery belongs to a timeout or watchdog above the link.
A weaker candidate drops the item and relies on a retry. Links without retry buffers lose data permanently, and the re-ask logic costs more than the holding register.
Another gates the clock to freeze the pipe. Gated clocks complicate timing closure and scan, and they freeze neighbours that should keep running; a register hold via ready is the standard answer.
A third ties ready high to keep the numbers up. Under real backpressure the held item is overwritten, which the scoreboard below would catch as a skipped sequence number.
in_ready and the held data on the next edge?in_ready is low and the data is unchanged. The item waits; the producer holds its outputs for another cycle.
Two: the held item leaves while the new item is captured. The rule accepts exactly because the consumer takes in the same cycle.
The finite bench pushes sixteen items through a five-cycle stall, checks sampled valid/data stability through the accepting edge, accounts accepted = delivered + cancelled + pending (cancelled is zero here), and drains for eight cycles rejecting extra deliveries. Progress assumes the scripted consumer eventually restores ready. A watchdog cannot cancel or reclaim an accepted item; timeout recovery needs an owner and explicit cancellation/replay protocol.
module stall_stage #(parameter int W = 8) (
input logic clk, rst_n,
input logic in_valid,
input logic [W-1:0] in_data,
output logic in_ready,
output logic out_valid,
output logic [W-1:0] out_data,
input logic out_ready
);
logic full;
logic [W-1:0] held;
assign out_valid = full;
assign out_data = held;
assign in_ready = !full || out_ready; // empty always accepts; full only on pass-through
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin full <= 1'b0; held <= '0; end
else if (in_valid && in_ready) begin held <= in_data; full <= 1'b1; end
else if (out_valid && out_ready) full <= 1'b0;
endmodule