Difficulty: Hard · Puzzle 6 of 6 · Topic: Valid/Ready Handshake Puzzles
Three one-stage pipeline implementations see the same input: the source always has a beat (and holds it until accepted) and the sink READY follows 11110011111111 over 14 cycles. A = ready from the stage's own register only (in_ready = !full); B = in_ready = !full || out_ready; C = skid buffer with registered ready. How many beats does each deliver and how many arrive wrong or out of order? Which would you choose for timing closure at full speed?
`timescale 1ns/1ns
// A: ready comes only from the stage's own register (no combinational path from out_ready)
module slice_reg(input bit clk, input bit in_valid, output bit in_ready, input bit [7:0] in_data,
output bit out_valid, input bit out_ready, output bit [7:0] out_data);
bit v_q = 0; bit [7:0] d_q = 0;
assign in_ready = !v_q;
assign out_valid = v_q; assign out_data = d_q;
always @(posedge clk) begin
if (in_valid && in_ready) begin v_q <= 1; d_q <= in_data; end
else if (out_ready) v_q <= 0;
end
endmodule
// B: ready also looks at out_ready (combinational ready path)
module slice_comb(input bit clk, input bit in_valid, output bit in_ready, input bit [7:0] in_data,
output bit out_valid, input bit out_ready, output bit [7:0] out_data);
bit v_q = 0; bit [7:0] d_q = 0;
assign in_ready = !v_q || out_ready;
assign out_valid = v_q; assign out_data = d_q;
always @(posedge clk) begin
if (in_valid && in_ready) begin v_q <= 1; d_q <= in_data; end
else if (out_ready) v_q <= 0;
end
endmodule
// C: skid buffer: registered ready, full throughput
module slice_skid(input bit clk, input bit in_valid, output bit in_ready, input bit [7:0] in_data,
output bit out_valid, input bit out_ready, output bit [7:0] out_data);
bit v_main = 0, v_skid = 0; bit [7:0] d_main = 0, d_skid = 0;
assign in_ready = !v_skid;
assign out_valid = v_main; assign out_data = d_main;
always @(posedge clk) begin
if (out_ready || !v_main) begin
if (v_skid) begin d_main <= d_skid; v_main <= 1; v_skid <= 0; end
else begin d_main <= in_data; v_main <= in_valid; end
end else if (in_valid && in_ready) begin
d_skid <= in_data; v_skid <= 1;
end
end
endmodule
module env #(parameter int KIND = 0) (input bit clk, input bit out_ready);
bit [7:0] next = 0, expect_d = 0;
int out_cnt = 0, bad = 0;
wire in_valid = 1; // source always has a beat and holds it until accepted
wire in_ready, out_valid; wire [7:0] out_data;
if (KIND == 0) slice_reg u(clk, in_valid, in_ready, next, out_valid, out_ready, out_data);
if (KIND == 1) slice_comb u(clk, in_valid, in_ready, next, out_valid, out_ready, out_data);
if (KIND == 2) slice_skid u(clk, in_valid, in_ready, next, out_valid, out_ready, out_data);
always @(posedge clk) begin
if (in_valid && in_ready) next <= next + 1;
if (out_valid && out_ready) begin
out_cnt++;
if (out_data !== expect_d) bad++;
expect_d <= expect_d + 1;
end
end
endmodule
module tb;
bit clk = 0;
always #5 clk = ~clk;
localparam int N = 14;
// cycle: 01234567890123
localparam bit [0:N-1] SINK = 14'b11110011111111;
int cyc = 0;
always @(posedge clk) cyc <= cyc + 1;
wire out_ready = SINK[cyc];
env #(0) a(clk, out_ready);
env #(1) b(clk, out_ready);
env #(2) c(clk, out_ready);
initial begin
#(10*N);
$display("A registered-ready only : out=%0d wrong=%0d", a.out_cnt, a.bad);
$display("B combinational ready : out=%0d wrong=%0d", b.out_cnt, b.bad);
$display("C skid buffer : out=%0d wrong=%0d", c.out_cnt, c.bad);
$finish;
end
endmoduleIn A, a full stage cannot accept in the same cycle it drains. Count how many cycles per beat.
1) Sink ready pattern: cycles 0-3 high, 4-5 low, 6-13 high.
2) A: the stage is full after accepting, so in_ready=0 while it drains, then it accepts again: one beat per two cycles while the sink is ready -> 6 beats in 14 cycles.
3) B: in_ready follows out_ready, so a full stage drains and refills every cycle -> 11 beats (cycles 1-3 and 6-13). It has a combinational ready path from sink to source.
4) C: the skid register absorbs the one beat that arrives in the cycle ready was still high while the main register was blocked, so ready can be a flop output and throughput is full -> 11 beats.
5) No design loses or reorders data (wrong=0 everywhere). A is not broken, only slow.Answer: A delivers 6, B delivers 11, C delivers 11; wrong=0 in all three. Choose C: full throughput without a combinational ready chain through the pipeline.
Checked by running the code in Verilator 5; the printed output supports the answer above. Same traffic for all three stages: the registered-only slice delivers 6 beats, the combinational-ready and skid versions deliver 11, none corrupts data.
A passes every data-integrity check: scoreboards report zero errors. The bug is bandwidth (50%), which only a throughput or latency check catches. And B 'fixes' it with a combinational ready path that ripples through a chain of stages and kills the timing.
What does the skid buffer cost and why does it hold exactly one extra beat? Model answer: One extra data register plus a mux per stage. Because ready is registered, the stage learns that it is blocked one cycle late, so exactly one more beat can already be in flight; the skid register stores that one beat, and ready is deasserted so no second extra beat can arrive.