AMBA AXI · Module 5
Read Data Interleaving
Read data interleaving by ID in AXI3 and why AXI4 removed it — and the crucial difference between beat-level interleaving (gone) and out-of-order completion (still allowed).
When a manager has several reads outstanding, their data shares one R channel — and there are two very different ways it can come back. Read data interleaving means beats from different reads are intermixed on R (RID0, RID1, RID0, RID1…), each sorted by its RID. AXI3 allowed it; AXI4 removed it. But the removal is narrower than it sounds, and the distinction is the whole point of this chapter: AXI4 still permits reads to complete out of order (a whole later read returning before an earlier one) — it just forbids interleaving their beats. This is the read-side counterpart of the write WID story (4.4), and confusing "interleaving" with "out-of-order completion" is the classic mistake.
1. Sharing the R Channel Among Reads
A manager can have multiple reads outstanding (different ARIDs, Chapter 5.2). Their data all returns on the single R channel, and RID tags every beat with the read it belongs to. The question is how the subordinate is allowed to schedule those beats onto the one channel:
- Contiguous (AXI4): return all of one read's beats (through its
RLAST), then all of the next read's. Beats are never intermixed. - Interleaved (AXI3): return beats from different reads intermixed — RID0, RID1, RID0, RID1 — relying on
RIDto let the manager demux them.
Both rely on RID to identify beats; the difference is whether the subordinate may switch between reads mid-burst. AXI3 said yes (up to a depth); AXI4 says no.
2. What Read Interleaving Was (AXI3)
In AXI3, a subordinate serving several reads could return whatever data was ready first, beat by beat, even if that meant alternating between transactions. If read ID0 had its first beat ready and ID1's was still being fetched, the subordinate could send ID0's beat, then ID1's when it arrived, then ID0's next — intermixing them on R. The manager used RID on each beat to route it to the correct per-transaction buffer.
The motivation was utilization: a subordinate (or interconnect merging several slaves) needn't sit idle waiting for one read's slow beat when another read's beat is ready — it could fill the channel with whatever data is available. The cost was paid by the manager: to reassemble interleaved beats it needed a separate buffer per outstanding RID, and to track interleaving depth.
3. Read Interleaving on a Waveform
On R, interleaving shows up as RID changing between beats within the same window — the giveaway that two transactions are sharing the channel beat-by-beat.
read-interleave — RID alternates per beat (AXI3 only)
7 cycles4. Interleaving ≠ Out-of-Order Completion
This is the distinction that matters most, and the one people get wrong. AXI4 removed interleaving (beat-level intermixing) but kept out-of-order completion (whole-transaction reordering):
- Out-of-order completion (allowed in AXI4): reads with different
ARIDs may complete in any order — a later-issued read can return entirely (all its beats, throughRLAST) before an earlier one. This is the latency-hiding behavior from independent channels and read latency, and AXI4 keeps it — governed by different-ID ordering. - Interleaving (removed in AXI4): the beats of two reads intermixed on R within the same window. AXI4 requires each read's beats to be contiguous — all of one read (through
RLAST) before the next begins.
So in AXI4 you can still get reads back in a different order than you asked — but each read arrives as one unbroken run of beats. The subordinate may choose which read to return next, but once it starts a read's data it must finish it before switching.
5. Why AXI4 Removed It
The trade was the same as the write-side WID removal (4.4): interleaving's benefit was marginal, its cost to managers was real. To reassemble interleaved read data, every manager needed a buffer per outstanding RID and logic to track interleaving depth — complexity paid on every master, for a utilization gain that mattered only in narrow cases. AXI4 made read data contiguous per transaction, so a manager handling the R channel deals with one active read at a time (it still demuxes by RID across completions, but never mid-burst). Simpler managers, negligible performance loss.
When bridging AXI3 → AXI4, a converter must therefore de-interleave read data (buffer the AXI3 interleaved beats and emit each read's beats contiguously) — the read-side analogue of de-interleaving write data, and another behaviour a generation bridge must reconcile.
6. Single-Read Data Is Always In Order
One invariant holds across all AXI versions and is worth stating plainly: the beats of a single read (same ARID) always return in order. Interleaving (AXI3) only ever intermixed beats of different ARIDs; it never reordered the beats within one read. So a manager can always assume beat 0, then beat 1, … for a given RID, ending at that read's RLAST. Out-of-order and interleaving are about relationships between transactions; a single transaction's data is sequential, always.
// Conceptual — demux R beats by RID. Within an RID, beats are always in order.
read_buf[rid].push(rdata); // append to this read's buffer (in order)
if (rlast) complete_read(rid);
// AXI3: rid may change between consecutive beats (interleaving) → need a buffer per RID.
// AXI4: rid is stable until that read's RLAST (contiguous) → one active read at a time,
// though different reads may still COMPLETE out of order.7. Checking It — the RID-Contiguity Checker
Everything above is a claim about what may appear on R. A checker turns it into something a simulation can enforce, and building one exposes a trap that sinks a surprising number of hand-written protocol monitors.
First: a beat is an accepted transfer, not a clock cycle
The contiguity rule is about beats, and on AXI a beat exists only where RVALID && RREADY are both high — the handshake from 3.1. Everything else on the R channel is the subordinate holding data, or the manager back-pressuring, and RID on those cycles carries no protocol meaning at all.
Write the check against clock cycles instead of accepted transfers and it fails in both directions:
- False failures during back-pressure. While
RREADYis low the subordinate holdsRVALIDand its payload steady — but a subordinate that has not yet assertedRVALIDmay drive anything onRID, includingX. A per-cycle checker reads that as an interleaved beat and reports a violation on a fully compliant bus. - False passes across a gap. A cycle-based tracker that resets its "burst in progress" state on any idle cycle will happily accept
RID=0, gap,RID=1, gap,RID=0— which is interleaving, spread across stalls.
Both disappear the moment the checker's unit of time is the transfer:
wire beat = rvalid && rready; // the ONLY cycles that carry protocol meaningStalls and idle cycles carry no RID meaning
6 cyclesThe checker
One piece of state does the whole job: is a burst currently open, and under which ID?
// ─────────────────────────────────────────────────────────────────────────────
// AXI4 read-data contiguity checker.
// bind axi_slave axi_rid_contiguity_checker #(.ID_W(4)) u_rid (.*);
//
// ENFORCES (AXI4): once a read burst's first beat is accepted, every further
// accepted beat carries the SAME RID until that burst's RLAST.
//
// DELIBERATELY DOES NOT ENFORCE: the order in which different bursts start.
// AXI4 keeps out-of-order completion, so "all of ID1, then all of ID0" is
// legal and must pass. A checker that also pinned issue order would reject
// compliant traffic - which is the most common way this check is written wrong.
// ─────────────────────────────────────────────────────────────────────────────
module axi_rid_contiguity_checker #(
parameter int ID_W = 4
) (
input logic aclk,
input logic aresetn,
input logic rvalid,
input logic rready,
input logic rlast,
input logic [ID_W-1:0] rid
);
// The unit of protocol time. Not a clock edge - an accepted transfer.
wire beat = rvalid && rready;
logic burst_open; // is a burst mid-flight on R?
logic [ID_W-1:0] open_id; // ...and whose
always_ff @(posedge aclk or negedge aresetn) begin
if (!aresetn) begin
burst_open <= 1'b0;
open_id <= '0;
end else if (beat) begin
if (!burst_open) begin
// First beat of a burst. A single-beat burst (RLAST on beat 0) opens
// and closes in the same transfer, so burst_open must stay low.
open_id <= rid;
burst_open <= !rlast;
end else if (rlast) begin
burst_open <= 1'b0; // this burst is done; any ID may start next
end
end
end
// THE rule. Note both guards: `beat` (only accepted transfers) and
// `burst_open` (the opening beat of a burst legitimately introduces a new ID).
a_rid_contiguous:
assert property (@(posedge aclk) disable iff (!aresetn)
(beat && burst_open) |-> (rid == open_id))
else $error("[%0t] READ INTERLEAVING: beat with RID=%0h arrived during an open RID=%0h burst",
$time, rid, open_id);
// A burst that never closes is a different defect - a missing RLAST leaves
// the channel permanently mid-burst and every later burst looks interleaved.
// Bound it so the real cause is reported instead of the symptom.
a_rlast_arrives:
assert property (@(posedge aclk) disable iff (!aresetn)
$rose(burst_open) |-> ##[1:1024] (beat && rlast))
else $error("[%0t] RID=%0h burst ran past 1024 beats with no RLAST", $time, open_id);
// Covers. Both checks are implications, so a quiet channel passes them while
// proving nothing. These say the interesting shapes actually occurred.
c_multi_beat: cover property (@(posedge aclk) disable iff (!aresetn)
beat && burst_open); // a burst longer than one beat
c_back_to_back: cover property (@(posedge aclk) disable iff (!aresetn)
(beat && rlast) ##1 (beat && !burst_open)); // new burst immediately after
c_stall_mid_burst: cover property (@(posedge aclk) disable iff (!aresetn)
burst_open && rvalid && !rready); // back-pressure inside a burst
endmoduleProving the checker distinguishes the two cases
A checker that never fires is indistinguishable from a checker that cannot fire. This testbench drives three sequences past it — the legal reordering, the illegal interleave, and a stalled burst — and asserts which of them should produce an error.
`timescale 1ns/1ps
module tb_rid_contiguity;
localparam int ID_W = 4;
logic aclk = 0, aresetn = 0;
logic rvalid = 0, rready = 1, rlast = 0;
logic [ID_W-1:0] rid = '0;
always #5 aclk = ~aclk;
axi_rid_contiguity_checker #(.ID_W(ID_W)) dut (.*);
// Count checker firings by watching the assertion's failure action.
int errors_seen = 0;
always @(dut.a_rid_contiguous) ; // placeholder for tool-specific hookup
// Simpler and fully portable: mirror the rule and count independently.
int mirror_errors = 0;
always @(posedge aclk) if (aresetn && dut.beat && dut.burst_open && rid !== dut.open_id)
mirror_errors++;
// Drive one accepted beat.
task automatic send(input logic [ID_W-1:0] id, input logic last);
@(negedge aclk);
rid <= id; rlast <= last; rvalid <= 1'b1;
@(posedge aclk); // RVALID && RREADY -> one beat
@(negedge aclk);
rvalid <= 1'b0; rlast <= 1'b0;
endtask
// Drive a beat that the manager stalls for `n` cycles before accepting.
task automatic send_stalled(input logic [ID_W-1:0] id, input logic last, input int n);
@(negedge aclk);
rid <= id; rlast <= last; rvalid <= 1'b1; rready <= 1'b0;
repeat (n) @(posedge aclk); // held, NOT transferred
@(negedge aclk) rready <= 1'b1;
@(posedge aclk); // now it is a beat
@(negedge aclk);
rvalid <= 1'b0; rlast <= 1'b0;
endtask
int base;
initial begin
repeat (2) @(posedge aclk);
aresetn <= 1'b1;
repeat (2) @(posedge aclk);
// ── CASE 1: out-of-order COMPLETION. Legal in AXI4. ──────────────────
// All of ID1 (contiguous), then all of ID0 (contiguous). The bursts
// complete in the opposite order to any issue order - and that is fine.
base = mirror_errors;
send(4'h1, 1'b0); send(4'h1, 1'b1);
send(4'h0, 1'b0); send(4'h0, 1'b1);
$display(" case 1 out-of-order completion : %0d error(s) [expect 0]",
mirror_errors - base);
// ── CASE 2: INTERLEAVING. Illegal in AXI4. ───────────────────────────
// ID0 opens, ID1 cuts in before ID0's RLAST.
base = mirror_errors;
send(4'h0, 1'b0);
send(4'h1, 1'b0); // ← the violation
send(4'h0, 1'b1);
send(4'h1, 1'b1);
$display(" case 2 beat-level interleaving : %0d error(s) [expect > 0]",
mirror_errors - base);
// ── CASE 3: a contiguous burst with heavy back-pressure. Legal. ──────
// This is the case a cycle-based checker gets wrong.
base = mirror_errors;
send_stalled(4'h2, 1'b0, 3);
send_stalled(4'h2, 1'b1, 5);
$display(" case 3 stalled contiguous burst: %0d error(s) [expect 0]",
mirror_errors - base);
$finish;
end
endmoduleExpected output.
case 1 out-of-order completion : 0 error(s) [expect 0]
** Error: READ INTERLEAVING: beat with RID=1 arrived during an open RID=0 burst
** Error: READ INTERLEAVING: beat with RID=0 arrived during an open RID=1 burst
case 2 beat-level interleaving : 2 error(s) [expect > 0]
case 3 stalled contiguous burst: 0 error(s) [expect 0]Read the three results together, because each rules out a different way of writing this check wrongly.
Case 1 passing is what separates a contiguity checker from an ordering checker. Two whole bursts arrived in the reverse of any plausible issue order and nothing fired — because AXI4 removed interleaving and kept out-of-order completion. A checker that also enforced issue order would reject this compliant traffic, and that mistake is easy to make while "tightening" the check.
Case 2 firing twice is the checker working. The second error is worth understanding rather than deduplicating: once ID1 cuts into the open ID0 burst, the tracker is following a burst the subordinate has abandoned, so the return to ID0 also reads as interleaving. One protocol fault, two reported beats — the first error is the diagnosis and the rest is echo, exactly as in an ordering scoreboard.
Case 3 passing is the payoff from beat = rvalid && rready. The same two-beat burst was stalled for three and five cycles, RID sat unchanged on the wire through every stall, and no transfer occurred on those edges. A cycle-based checker would have found several "extra" RID samples here and reported a compliant burst as interleaved.
8. Common Misconceptions
9. Debug Lab — A Bridge That Passes Every Directed Test and Corrupts Random Traffic
AXI4 manager reads correct data in directed tests and garbage under random multi-ID traffic
UN-DE-INTERLEAVED-BRIDGEAn AXI4 manager behind an AXI3→AXI4 bridge returns correct data in every directed test and corrupted data once the test issues more than one read at a time with distinct ARIDs. The corruption is not random noise: the returned buffers contain real data from the other read, cleanly swapped at beat boundaries.
Expected. Each read's beats arrive contiguously; the manager appends them to one buffer until RLAST.
Actual. The AXI3 side interleaved by RID — legal on AXI3 — and the bridge forwarded the beats untouched. The AXI4 manager, entitled to assume contiguity, keeps one active-read register rather than a buffer per outstanding RID, so beats from two reads land in whichever buffer was active.
That single-buffer assumption is not a bug in the manager. It is the design freedom AXI4 bought by removing interleaving.
The symptom shape narrows it before any waveform is opened: plausible data in the wrong buffer means beats were mis-attributed, not mis-generated. Three checks confirm it:
- Does it need concurrent distinct IDs? Directed tests pass because they issue one read at a time — with only one outstanding read, interleaving cannot occur and the bug is unreachable. A failure that requires concurrency points at attribution.
- Does
RIDchange before anRLAST? On the AXI4 side, capture the R channel and look only at accepted beats.RIDswitching mid-burst is the interleaving signature, and on AXI4 it is a violation — this is precisely whata_rid_contiguousreports. - Which side is non-compliant? Bind the §7 checker at both bridge ports. Firing on the AXI3 port only is expected (interleaving is legal there, so bind it AXI4-side only, or expect the noise). Firing on the AXI4 port is the defect.
The bridge is a pass-through on the read-data path. Converting AXI3→AXI4 requires it to de-interleave: buffer incoming beats per RID and re-emit each read's beats as one contiguous run through its RLAST.
The reason this survives directed testing is structural — de-interleaving is only exercised when two reads are in flight with different IDs, which a single-transaction directed suite never creates. The bridge is not partly correct; it is untested in the only regime where the conversion means anything.
In the bridge: a per-RID reassembly buffer on the AXI3 side, and an arbiter that emits one read at a time on the AXI4 side, holding the chosen RID until its RLAST. That is the same state machine as §7's checker, run as a producer rather than a monitor — burst_open / open_id become the arbiter's grant.
In verification: bind axi_rid_contiguity_checker on the AXI4 port permanently, and treat c_multi_beat and c_back_to_back at zero as a coverage failure, because a checker that never saw a multi-beat burst has not tested the conversion at all.
Constrain stimulus for concurrency, not just for legality. The class of bug that only appears with ≥ 2 outstanding reads on distinct IDs is invisible to any suite that issues one transaction at a time, so make multi-ID concurrency a constraint rather than something random traffic may occasionally produce — the outstanding-verification coverage model exists for exactly this axis.
And bind protocol checkers at every generation boundary. A bridge is where one specification's freedom becomes another's violation; both of its ports need their own rules enforced, because neither side alone can tell you the conversion happened.
10. Debugging Insight
11. Verification Insight
12. Interview Questions
13. Summary
When multiple reads are outstanding, their beats share one R channel, and AXI3 allowed interleaving — intermixing beats of different-ID reads (RID0, RID1, RID0…), demuxed by the RID already present on every beat. AXI4 removed read interleaving, requiring each read's data to be contiguous (all of one read through its RLAST before the next). The removal mirrors the write-side WID/interleaving removal: interleaving's benefit was marginal and it forced every manager to keep a per-RID reassembly buffer, so AXI4 traded it for simpler managers.
The distinction to keep crisp: AXI4 dropped beat-level interleaving but kept out-of-order completion — whole reads (different IDs) may still return in any order, each contiguous — which is what hides latency. And in every version, a single read's beats are always in order. The interleaving signature on a capture is RID changing mid-burst before a RLAST (legal on AXI3, a violation on AXI4); an AXI3→AXI4 bridge must de-interleave. Next, Module 5 closes with annotated end-to-end read waveforms.
14. Where This Is Specified
Read-data ordering is normative in the Arm AMBA AXI Protocol Specification: the read-data channel and RID/RLAST semantics in §A3, transaction identifiers and ordering in §A5, and the AXI4 change list that records the removal of read-data interleaving (alongside WID and write-data interleaving). Arm publishes it on the AMBA AXI documentation page.
Two things are worth reading in the specification rather than inferring, because both are routinely conflated. First, that removing interleaving did not remove reordering — out-of-order completion of whole bursts survives in AXI4 and is what hides latency; the §7 checker deliberately permits it. Second, the definition of a transfer: a beat occurs where RVALID and RREADY are both asserted, which is the sentence that makes "count accepted transfers, not clock cycles" a protocol fact rather than a coding preference.
The SVA in §7 is IEEE Std 1800 (SystemVerilog) clause 16 — see concurrent assertions for the sampling and implication semantics, and for why each check there is paired with a cover.
Related lessons. The handshake that defines a beat is VALID/READY, with stalls in backpressure and stalls. The channel itself is the R channel and RLAST/RRESP. For ordering, same-ID ordering and different-ID ordering; for the concurrency this all serves, outstanding verification.
15. What Comes Next
The read path's mechanics are complete; next, see them assembled:
- 5.6 — Read Transaction Waveforms (coming next) — annotated end-to-end read waveforms: single, burst, and multi-ID.
Previous: 5.4 — Read Latency. For the broader protocol catalog, see the AMBA family overview doc.