The interviewer asks: walk me through the valid/ready handshake, and tell me exactly when the source may change its data.
This tests flow control without loss or duplication. The answer is two rules plus the reason neither side may use combinational shortcuts across the link.
A transfer happens if and only if valid and ready are high in the same cycle. Before that cycle the source owns the payload; after it the sink does.
Signal | Driven by | Rule |
|---|---|---|
valid | Source | Once high, stays high with a stable payload until ready joins it. |
ready | Sink | May rise and fall with capacity. A receiver may wait for valid before asserting ready; a source must not wait for ready before offering valid. |
payload | Source | Stable while valid waits. It may change only after the handshake. |
Assert valid from pending work, not as a prerequisite-dependent response to ready. A sink may assert ready proactively or register a decision after seeing valid. AXI forbids combinational paths between interface inputs and outputs; a generic internal ready/valid link can use combinational ready propagation only when its composition is proven loop-free and meets timing.
The bottom row, data, holds still through the stall and then moves once while stalled. That move is the violation.
Slot 0: idle. valid 0, ready 1, data 0x00.
Slot 1: the source offers 0xA1 (valid 1). The sink stalls (ready 0).
Slot 2: still stalled. data holds 0xA1, as the contract requires.
Slot 3: data moves to 0xB2 while valid stays high and ready stays low. This is a stability violation.
Slot 4: ready rises. The handshake completes and carries the wrong word 0xB2.
Slot 5: back-to-back transfer of 0xC3 with valid and ready both high.
Slot 6: valid drops. The bus is idle.
The sink asserted ready, then deasserted it with valid still high. Legal. Only the source side is constrained; ready may move at any time.
The sink needs a cycle to decide but must not lose the word. A one-deep skid buffer accepts the word while the downstream stalls, then replays it. The valid/ready technical lesson covers the pattern.
The checker remembers the preceding rising-edge stall. At the next rising edge it requires valid and the same payload, even if ready is now high. Separate mutations drop valid and change data on the accepting edge; each must be detected once.
None. No transfer completes without ready. Every word offered so far is still pending, and the counter advance is already a stability violation.
Nothing in the protocol, as long as the sink truly accepts every word the cycle it arrives. The risk is a later change that adds latency without adding backpressure.
Ready acknowledges like a pulse, so the source may change data right after ready blips. Ready is a level, not a pulse. The transfer is the coincidence of the two levels, and the payload must be stable through it.
The source may refresh stalled data to keep it current. Freshness is the sink's decision after the handshake. Refreshing under stall breaks the one guarantee the sink relies on.
Tie ready high and skip the checker, since this link never stalls. Links that never stall still need the checker. The day one stalls, the bug is silent without it.
The sampled stability rule as an SVA snippet. The procedural implementation below uses the same preceding-edge obligation. Reset cancels the pending obligation.
// snippet
// The stability rule as a property (the testbench below checks the same rule procedurally):
// assert property (@(posedge clk) disable iff (!rst_n)
// (valid && !ready) |=> valid && $stable(payload));The full source, sink, and checker below drive stimulus on falling edges and sample offers before rising-edge NBA updates. The clean scoreboard samples delivered data after NBA settlement. Each fault trial resets the source/sink; the data mutation is applied after a sampled stall and before its accepting edge.
`timescale 1ns/1ps
module vr_src (
input logic clk, rst_n, ready,
output logic valid,
output logic [7:0] payload
);
logic [7:0] next_val;
always_ff @(posedge clk) begin
if (!rst_n) begin
valid <= 1'b0;
payload <= 8'h00;
next_val <= 8'h00;
end else if (!valid || (valid && ready)) begin
payload <= next_val;
valid <= 1'b1;
next_val <= next_val + 8'h01;
end
end
endmodule
module vr_sink (
input logic clk, rst_n, valid,
input logic [7:0] payload,
output logic ready,
output logic [7:0] got,
output logic took
);
logic [3:0] phase;
always_ff @(posedge clk) begin
if (!rst_n) begin
phase <= 4'd0;
got <= 8'h00;
took <= 1'b0;
end else begin
phase <= phase + 4'd1;
took <= valid && ready;
if (valid && ready) got <= payload;
end
end
assign ready = (phase[1:0] != 2'b10); // stall one cycle in four
endmodule
Source, sink and checker bench Run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It is checker evidence for the finite regression, not a proof over all sequences. Each fault trial resets the source and sink.