AMBA AXI · Module 3
The VALID/READY Handshake
The single transfer contract every AXI channel uses — VALID, READY, transfer on both-high, the stability rule, and the cardinal rule that VALID must never wait for READY.
There is exactly one transfer mechanism in AXI, and every channel on every memory-mapped variant uses it: the VALID/READY handshake. Learn it once — truly, precisely — and you know how AW, W, B, AR, and R all move data; the channels differ only in their payload. This chapter nails the contract: the source asserts VALID when it has something to send, the destination asserts READY when it can take it, and a single beat transfers on the clock edge where both are high. Then come the two rules that separate engineers who use AXI from those who only recognise it — the stability rule and the cardinal rule that VALID must never wait for READY. The deadlock dependency graph across channels is Module 3.5; here we make the single-channel contract exact.
1. One Handshake, Every Channel
Every AXI channel is a one-directional pipe with two control signals and a payload:
VALID— driven by the source (the side sending information on this channel). It means "the payload is valid right now; take it."READY— driven by the destination (the receiving side). It means "I can accept a payload this cycle."- The payload — the channel's actual content (an address on AW/AR, data on W/R, a response on B).
A transfer — one beat — happens on the rising clock edge where VALID and READY are both high. That's the entire mechanism. It is identical on all five channels; only who is the source changes (the manager sources AW/W/AR; the subordinate sources B/R) and what the payload is. Internalise this once and four-fifths of "learning AXI signals" is already done.
2. The Transfer Condition
Say it as a single line, because everything else builds on it:
A beat transfers on the rising clock edge where
VALID && READYare both asserted.
Not when VALID rises. Not when READY rises. On the edge where both are simultaneously high. If VALID is high but READY is low, nothing transfers — the source is offering, the destination isn't taking. If READY is high but VALID is low, nothing transfers — the destination is willing, the source has nothing. Only the coincidence of both high moves a beat. (Exactly when in the cycle this is sampled — the rising-edge timing — is Chapter 3.2's focus; here, the logical condition is what matters.)
3. The Stability Rule
The contract has a crucial obligation on the source: once you assert VALID, you must keep it asserted — and keep the payload stable — until the transfer happens. You cannot offer a beat, then change your mind or change the data before the destination has taken it.
Concretely: if a source raises VALID with address A, and the destination's READY is low for three cycles, the source must hold VALID high and keep the address at A for all three cycles, until the cycle where READY is finally high and the beat transfers. Dropping VALID early, or mutating the payload mid-wait, breaks the contract and corrupts or loses the transfer.
This is what makes the handshake robust: the destination can take its time, confident the offered payload won't vanish or mutate underneath it.
In RTL, correct source behaviour is short — and the wrong version is the classic bug:
// Conceptual — correct source: VALID comes from having data, never from READY.
assign valid = have_data; // ✅ assert when I have something to send
// hold `valid` AND the payload stable until the beat moves:
wire transfer = valid && ready; // one beat moves on this edge
// ❌ WRONG — VALID must never be a function of READY:
// assign valid = ready; // can deadlock (see §6) and breaks the contract4. A Normal Transfer
The clean case: the destination is ready, so beats move back-to-back, one per cycle.
Normal transfer — both high, one beat per cycle
6 cycles5. A Stalled Transfer
Now the destination isn't ready immediately. The source asserts VALID with D0 and holds it stable while READY is low; the beat transfers only when READY finally rises.
Stalled transfer — VALID held stable until READY rises
7 cyclesThe destination holding READY low to delay a transfer is backpressure — the universal flow-control mechanism — and it's the subject of Chapter 3.3. The point here is narrower: whatever the destination does, the source's job during a stall is to hold steady.
6. The Cardinal Rule — VALID Must Not Wait for READY
Now the rule that prevents the single most common AXI hang. The two sides are not symmetric:
- The destination's
READYmay depend onVALID. It's legal for a destination to wait until it seesVALIDbefore assertingREADY(e.g., "I'll only signal ready when there's actually something to take"). - The source's
VALIDmust never depend onREADY. A source must assertVALIDbased only on its own data being available — never "I'll assertVALIDonce I seeREADY."
The reason is deadlock. If the source waits for READY before asserting VALID, and the destination waits for VALID before asserting READY, then neither ever fires — both sides wait forever for the other to move first. By forbidding the source from waiting, AXI guarantees at least one side always makes the first move, so the handshake can always complete.
Hold this as a one-line guardrail: a source asserts VALID from its own readiness; a destination may key READY off VALID; never the reverse. The full cross-channel dependency graph (which VALID/READY pairs may legally depend on which, across AW/W/B/AR/R) builds on exactly this and is Chapter 3.5.
7. Why One Contract Is So Powerful
It's worth pausing on the design elegance, because it explains why AXI is learnable at all. By making every channel use the identical handshake:
- You learn it once. Understand
VALID/READYhere and you can read transfers on any channel of any memory-mapped AXI variant — the payload changes, the handshake doesn't. - Backpressure is universal. Every channel can be throttled the same way (destination lowers
READY), so flow control composes across the whole interconnect. - The building blocks are reusable. Skid buffers, FIFOs, and pipeline registers built around
VALID/READYwork on any channel (Module 15) — and the same monitor/assertion ideas verify any channel (Module 16).
The handshake is the atom. Decoupling (Chapter 2.4) is what you get by running five of these atoms independently; everything else in the protocol is payload and rules layered on top.
8. Common Misconceptions
9. Debugging Insight
10. Verification Insight
11. The Source in RTL
Everything above is a contract. Here it is as hardware — a minimal source that offers one beat at a time. It is deliberately small: the point is not to build an AXI manager, it is to make the two rules executable.
// Offers one beat at a time from an upstream producer.
// ACLK / ARESETn are AXI's clock and active-low reset.
module axi_source #(
parameter int DATA_W = 32
) (
input logic aclk,
input logic aresetn,
// producer side
input logic prod_en, // producer offers a new item this cycle
input logic [DATA_W-1:0] prod_data,
output logic prod_stall, // producer must wait: a beat is still in flight
// AXI-style channel out
output logic m_valid,
input logic m_ready,
output logic [DATA_W-1:0] m_data
);
always_ff @(posedge aclk or negedge aresetn) begin
if (!aresetn) begin
// AXI requires the source to drive VALID low out of reset.
m_valid <= 1'b0;
m_data <= '0;
end
else if (!m_valid || m_ready) begin
// Either we are idle, or the beat we were offering just transferred.
// In both cases we are free to take a new item — or go idle.
m_valid <= prod_en;
if (prod_en) m_data <= prod_data;
end
// else (m_valid && !m_ready): STALLED. No branch fires, so VALID and the
// payload keep their values. That "do nothing" case IS the stability rule.
end
assign prod_stall = m_valid && !m_ready;
endmoduleThree details carry the whole lesson.
The stalled case is written as absence. There is no else clause for m_valid && !m_ready, and that is the point — a flop that is not assigned holds. The stability rule of §3 is not enforced by extra logic; it is enforced by declining to write.
m_ready appears in the condition, and that is legal. This is worth pausing on, because it looks like a violation of §6 and is not. The cardinal rule forbids READY from deciding whether VALID rises. Here m_valid rises from prod_en — the source's own data. m_ready only participates in deciding when the source may release the beat it already offered, which is exactly what completion means. A source that could not observe READY could never know its beat had been taken.
Reset drives VALID low, not the payload's correctness. A source may leave m_data at any value while m_valid is low — no transfer can occur, so there is nothing for the destination to sample. Zeroing it here is tidiness, not protocol.
12. The Bug — VALID Waiting for READY
§6 explained why this deadlocks and §9 explained how to spot it on a capture. Here is the actual code that causes it, so the shape is recognisable in review.
// ❌ BROKEN. VALID is a function of READY.
always_ff @(posedge aclk or negedge aresetn) begin
if (!aresetn) m_valid <= 1'b0;
else m_valid <= have_data && m_ready; // "only offer once they're ready"
endIt reads like caution — don't assert until the receiver can take it — and that is precisely why it survives review.
Observed symptom. The channel is silent. m_valid and m_ready are both low forever; no beat ever transfers; the transaction count stays at zero and any surrounding test times out.
Expected behaviour. m_valid rises within a cycle of have_data going high, independently of anything the destination does.
Actual behaviour. m_valid never rises at all.
Likely causes. Either the source gates VALID on READY (this bug), or the producer never asserted have_data, or the channel is held in reset. The three are easy to separate — see the diagnostic below.
Diagnostic. Look at have_data and m_valid together. If have_data is high while m_valid stays low, the source is the fault, and the VALID equation is where to look. The four-combination reading in §9 localises it; this narrows it to a line.
Root cause. The destination asserts READY only after it observes VALID — which is legal — while the source waits for READY before asserting VALID — which is not. Neither side can move first, so neither ever moves.
Fix. Drive VALID from the source's own data availability, as in §11: m_valid <= prod_en on the idle-or-just-transferred branch, and hold otherwise.
Prevention. The structural version of this bug is invisible to a functional test that happens to use an always-ready destination. Two things catch it: a READY-delay model that sometimes waits for VALID, and the assertions in §13. Both belong in the channel's standard checker, not in each test.
13. The Assertions That Enforce It
The two normative rules are small enough to state as properties, and because every channel shares the contract, the same properties bind to AW, W, B, AR and R with only the signal names changed.
// `valid` / `ready` / `payload` are one channel's signals.
// Identical on AW, W, B, AR and R — only the names change.
// NORMATIVE: once asserted, VALID must remain asserted until the beat transfers.
property p_valid_held_until_transfer;
@(posedge aclk) disable iff (!aresetn)
(m_valid && !m_ready) |=> m_valid;
endproperty
// NORMATIVE: the payload must not change while the offer is outstanding.
property p_payload_stable_while_offered;
@(posedge aclk) disable iff (!aresetn)
(m_valid && !m_ready) |=> $stable(m_data);
endproperty
// NORMATIVE: the source drives VALID low while in reset.
property p_valid_low_in_reset;
@(posedge aclk) (!aresetn) |-> !m_valid;
endproperty
a_valid_held : assert property (p_valid_held_until_transfer)
else $error("VALID dropped before the beat transferred");
a_payload_stable : assert property (p_payload_stable_while_offered)
else $error("payload changed while VALID was high and READY low");
a_reset_valid_low : assert property (p_valid_low_in_reset)
else $error("VALID asserted during reset");What p_valid_held_until_transfer checks. On any cycle where the source is offering and the destination has not taken it, the next cycle must still show VALID high. Why it matters: a source that withdraws an offer leaves the destination having possibly already committed to accepting it. What failure looks like: the assertion fires on the exact cycle VALID fell, which is one cycle after the last unaccepted offer — so the waveform cursor lands directly on the withdrawing source.
What p_payload_stable_while_offered checks. The same window, applied to the data. Why it matters: this is the silent one. A mutating payload does not hang anything — the beat still transfers, carrying whichever value happened to be present at the accepting edge. What failure looks like: intermittent, data-dependent corruption downstream with no protocol-level symptom, which is why it must be caught by assertion rather than by observation.
A note on scope, because it is easy to over-claim. AXI places no upper bound on how long a destination may hold READY low; a permanently stalling but otherwise well-behaved receiver is not a protocol violation. So a timeout check is engineering guidance, not a normative requirement — it encodes your design's intent, not the specification's:
// Bound by your architecture's latency budget, not by AXI.
property p_offer_accepted_within_budget;
@(posedge aclk) disable iff (!aresetn)
m_valid |-> ##[0:MAX_STALL] m_ready;
endpropertyWhere these rules are written down: the handshake, its stability requirements, and the VALID/READY dependency restriction are specified in Arm's AMBA AXI Protocol Specification (IHI 0022) — worth reading once in the original, because the dependency rules are stated more tersely there than any tutorial can afford to be.
14. Proving It — a Self-Checking Harness
An example that has to be eyeballed proves nothing. This one drives the two interesting cases — a clean offer, then three cycles of backpressure — and fails loudly if either rule is broken.
module tb_axi_source;
localparam int DATA_W = 32;
localparam int STALL_CYC = 3;
logic aclk = 1'b0, aresetn = 1'b0;
always #5 aclk = ~aclk; // 100 MHz
logic prod_en = 1'b0;
logic [DATA_W-1:0] prod_data = '0;
logic prod_stall;
logic m_valid;
logic m_ready = 1'b0;
logic [DATA_W-1:0] m_data;
axi_source #(.DATA_W(DATA_W)) dut (.*);
int errors = 0;
logic [DATA_W-1:0] offered;
task automatic check(input bit cond, input string msg);
if (!cond) begin
errors++;
$error("[FAIL] %s", msg);
end
endtask
initial begin
// ---- reset: VALID must be low before the channel is live ----
repeat (2) @(posedge aclk);
check(m_valid === 1'b0, "VALID must be low during reset");
aresetn <= 1'b1;
@(posedge aclk);
// ---- offer one beat while the destination is NOT ready ----
prod_en <= 1'b1;
prod_data <= 32'hA5A5_0001;
@(posedge aclk);
prod_en <= 1'b0;
@(posedge aclk);
check(m_valid === 1'b1, "VALID should assert once the producer offered data");
offered = m_data;
// ---- backpressure: READY low; VALID and payload must not move ----
repeat (STALL_CYC) begin
@(posedge aclk);
check(m_valid === 1'b1, "VALID must stay high while READY is low");
check(m_data === offered, "payload must stay stable while READY is low");
end
// ---- release: the beat transfers on the both-high edge ----
m_ready <= 1'b1;
@(posedge aclk);
m_ready <= 1'b0;
@(posedge aclk);
check(m_valid === 1'b0, "VALID should drop after the beat transferred");
if (errors == 0) $display("PASS - stability held through %0d stall cycles, beat transferred", STALL_CYC);
else $display("FAIL - %0d check(s) failed", errors);
$finish;
end
endmoduleRun it against §11's source and it prints PASS. Swap in §12's broken source and it fails at the first check after reset — VALID should assert once the producer offered data — because m_valid never rises while m_ready is low. That single line is the deadlock, reproduced in four cycles of simulation instead of found in a lab.
The one thing this harness deliberately does not do is keep READY high throughout. A destination that is always ready never exercises the stall path, and the stability rule only has teeth during a stall — which is why §10's READY-delay stimulus is not an optional refinement but the part that does the testing.
Two places this goes next: the same held-beat problem, solved without losing throughput, is the skid buffer and its ready/valid pipelining treatment; and the cross-channel version of §12's deadlock is handshake deadlock rules, with more field symptoms collected in stuck VALID/READY and handshake common bugs.
15. Interview Questions
16. Summary
Every AXI channel moves data with one contract: the source asserts VALID when its payload is valid, the destination asserts READY when it can accept, and a beat transfers on the rising clock edge where both are high. Two rules make it robust. The stability rule: once VALID is asserted, the source holds VALID and the payload steady until the transfer completes — no withdrawing, no mutating. The cardinal rule: VALID must never depend on READY (though READY may depend on VALID), because a mutual wait deadlocks — this single banned dependency keeps the handshake alive.
Read a channel by its two control signals: VALID high with READY low means the destination is blocking; the reverse means the source has nothing; both low forever is the VALID-waits-for-READY deadlock; payload changing under a held VALID is a stability violation. Verify it with a small, reusable set of stability, independence, and liveness assertions plus per-channel READY-delay stimulus. Because this one handshake is identical on AW, W, B, AR, and R, mastering it here is most of "knowing AXI signals." Next we sharpen the timing of the transfer itself — exactly when, on the clock edge, the beat is considered to have moved.
17. What Comes Next
You have the contract. Module 3 now refines it — the precise transfer instant, then backpressure, throughput, and the cross-channel dependency rules:
- 3.2 — The Transfer Event (coming next) — exactly when a beat transfers (VALID && READY sampled on the rising edge) and why the sampling instant matters.
- 3.3 — Backpressure & Stalls (coming soon) — how READY de-assertion throttles a channel and propagates upstream.
Previous: 2.5 — AXI4 vs AXI3 vs AXI4-Lite vs AXI4-Stream. For the broader protocol catalog, see the AMBA family overview doc.