Skip to content

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 RID to 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 manager issues two reads ID0 and ID1; the subordinate returns R beats intermixed by RID — ID0, ID1, ID0, ID1 — and the manager sorts each beat to its read by RID.AXI3 read interleaving — beats from two reads intermixedManagerSubordinateAR — read ID0, thenread ID1R beat — RID=ID0(D0)R beat — RID=ID1(E0) ← interleavedR beat — RID=ID0(D1, RLAST)R beat — RID=ID1(E1, RLAST)
Figure 1 — AXI3 read-data interleaving. The manager has two reads outstanding (ID0 and ID1); the subordinate returns their beats intermixed on the single R channel — D0(ID0), E0(ID1), D1(ID0,RLAST), E1(ID1,RLAST) — and the manager demuxes by RID into the right read. AXI4 forbids this: it would return D0,D1 (all of ID0) then E0,E1 (all of ID1).

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 cycles
Over four cycles the RID alternates between ID0 and ID1 as beats from two reads are interleaved; ID0's RLAST is on cycle 3 and ID1's RLAST on cycle 4. AXI4 forbids this interleaving.two reads interleaved by RID (AXI3)two reads interleaved by RID(AXI3)RID switches ID0→ID1 mid-window = interleavingRID switches ID0→ID1mid-window = interleavingID0 RLASTID0 RLASTID1 RLASTID1 RLASTaclkrvalidrreadyrdataXD0E0D1E1XXridXID0ID1ID0ID1XXrlastt0t1t2t3t4t5t6
Figure 2 — AXI3 read interleaving on the R channel. Across cycles 1–4, RID alternates ID0, ID1, ID0, ID1 — beats from two reads intermixed. Each read's RLAST lands on its own last beat (ID0 at cycle 3, ID1 at cycle 4). On AXI4 this is illegal: RID would stay ID0 until ID0's RLAST, then switch to ID1. RID changing mid-window before a RLAST is the interleaving signature.

4. 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, through RLAST) 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.

Out-of-order completion returns whole reads in any order but each contiguous and is allowed in AXI4; interleaving intermixes beats of different reads and is removed in AXI4.Out-of-order completionwhole reads reordered, eachcontiguousAXI4: ALLOWEDe.g. all ID1 beats, then all ID0beatsInterleavingbeats of different reads intermixedAXI4: REMOVEDID0, ID1, ID0, ID1 — illegal in AXI412
Figure 3 — the key distinction. Out-of-order completion (kept in AXI4): whole reads return in any order, but each is contiguous — e.g., all of ID1 then all of ID0. Interleaving (removed in AXI4): beats of different reads intermixed within a window — ID0, ID1, ID0, ID1. AXI4 allows reordering reads; it forbids intermixing their beats.

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.

AXI3 interleaving requires per-RID reassembly buffers in the manager; AXI4 contiguous read data means one active read at a time and simpler managers, while out-of-order completion is retained.AXI3interleaved beats — demux + bufferper RIDComplex managerstrack interleave depth, reassembleAXI4contiguous per read — one activeread on RSimpler managersout-of-order completion kept12
Figure 4 — why AXI4 removed read interleaving (mirroring the WID removal). AXI3 interleaving forced every manager to keep a reassembly buffer per outstanding RID and track interleave depth. AXI4's contiguous-per-read rule means one active read at a time on R — far simpler managers — while out-of-order completion still provides the reordering that matters.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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 RREADY is low the subordinate holds RVALID and its payload steady — but a subordinate that has not yet asserted RVALID may drive anything on RID, including X. 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wire beat = rvalid && rready;   // the ONLY cycles that carry protocol meaning

Stalls and idle cycles carry no RID meaning

6 cycles
Across five cycles, only those with RVALID and RREADY both high are accepted beats; a stall cycle and an idle cycle carry no checkable RID.beat: RVALID & RREADYbeat: RVALID & RREADYmanager stall - held, not transferredmanager stall - held, nottransferredRVALID low - RID is don't-careRVALID low - RID isdon't-carebeat: RLAST closes the burstbeat: RLAST closes theburstaclkrvalidrreadyridXID0ID0XID0Xrlastbeatt0t1t2t3t4t5
Figure 5 — why the checker must count accepted transfers. Only cycles with RVALID and RREADY both high are beats, and only their RID is meaningful. The stall cycle holds the payload without transferring it; the idle cycle has RVALID low, so RID is genuinely don't-care and may show anything, X included. A checker sampling RID on every clock edge reads those two cycles as interleaving and reports a violation on a compliant bus.

The checker

One piece of state does the whole job: is a burst currently open, and under which ID?

axi_rid_contiguity_checker.sv — bindable to any AXI4 read channel
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────────
//  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
 
endmodule

Proving 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.

tb_rid_contiguity.sv — self-checking; run it before trusting the checker
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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
 
endmodule

Expected output.

simulation log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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

1

AXI4 manager reads correct data in directed tests and garbage under random multi-ID traffic

UN-DE-INTERLEAVED-BRIDGE
Observed Symptom

An 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 vs Actual

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.

Diagnostic

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:

  1. 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.
  2. Does RID change before an RLAST? On the AXI4 side, capture the R channel and look only at accepted beats. RID switching mid-burst is the interleaving signature, and on AXI4 it is a violation — this is precisely what a_rid_contiguous reports.
  3. 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.
Root Cause

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.

Fix

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.

Prevention

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:

Previous: 5.4 — Read Latency. For the broader protocol catalog, see the AMBA family overview doc.