Ready-to-run snippets

Code Examples

Ready-to-run Verilog, SystemVerilog, and UVM examples. Click any to open it live in the IDE.

← All examples

AXI4 transaction IDs and out-of-order reads

Verilog Bus Protocols

A slave with two read slots returns a fast read before a slow one when their IDs differ.

Open in IDE
design.v
// A read-only AXI4 slave that can return read data out of order.
//   Two read slots, so two read transactions can be in progress at once.
//   Addresses below 0x800 are fast (LAT_FAST cycles), 0x800 and above are slow (LAT_SLOW cycles).
//   Each beat returns its own address as data, so the log shows where every beat came from.
//   Transactions with different IDs may finish in any order; with the same ID, in the order received.
//   The write channels are not used: AWREADY and WREADY stay low.
module axi_ooo_mem #(
  parameter LAT_FAST = 2,
  parameter LAT_SLOW = 8
) (
  input             ACLK,
  input             ARESETn,
  input      [3:0]  AWID,
  input      [11:0] AWADDR,
  input      [7:0]  AWLEN,
  input      [2:0]  AWSIZE,
  input      [1:0]  AWBURST,
  input             AWLOCK,
  input             AWVALID,
  output            AWREADY,
  input      [31:0] WDATA,
  input      [3:0]  WSTRB,
  input             WLAST,
  input             WVALID,
  output            WREADY,
  output     [3:0]  BID,
  output     [1:0]  BRESP,
  output            BVALID,
  input             BREADY,
  input      [3:0]  ARID,
  input      [11:0] ARADDR,
  input      [7:0]  ARLEN,
  input      [2:0]  ARSIZE,
  input      [1:0]  ARBURST,
  input             ARLOCK,
  input             ARVALID,
  output            ARREADY,
  output reg [3:0]  RID,
  output reg [31:0] RDATA,
  output reg [1:0]  RRESP,
  output reg        RLAST,
  output reg        RVALID,
  input             RREADY
);
  assign AWREADY = 1'b0;
  assign WREADY  = 1'b0;
  assign BID     = 4'd0;
  assign BRESP   = 2'b00;
  assign BVALID  = 1'b0;

  // Two read slots
  reg        s_busy  [0:1];
  reg [3:0]  s_id    [0:1];
  reg [11:0] s_addr  [0:1];
  reg [7:0]  s_left  [0:1];
  reg [2:0]  s_size  [0:1];
  reg [15:0] s_time  [0:1];              // first cycle the data is ready
  reg [15:0] s_seq   [0:1];              // arrival order
  reg [15:0] cycle;
  reg [15:0] seq;
  reg        sending;                    // a burst is being sent from slot cur
  reg        cur;

  // A slot may send when its data is ready and no older slot holds the same ID
  wire ready0 = s_busy[0] && cycle >= s_time[0] && !(s_busy[1] && s_id[1] == s_id[0] && s_seq[1] < s_seq[0]);
  wire ready1 = s_busy[1] && cycle >= s_time[1] && !(s_busy[0] && s_id[0] == s_id[1] && s_seq[0] < s_seq[1]);
  wire pick   = (ready0 && ready1) ? (s_seq[1] < s_seq[0]) : ready1;   // the older one if both are ready
  wire free   = s_busy[0] ? 1'b1 : 1'b0;                              // the slot a new address goes to

  assign ARREADY = !s_busy[0] || !s_busy[1];

  always @(posedge ACLK) begin
    if (!ARESETn) begin
      s_busy[0] <= 1'b0;
      s_busy[1] <= 1'b0;
      cycle     <= 16'd0;
      seq       <= 16'd0;
      sending   <= 1'b0;
      RVALID    <= 1'b0;
    end
    else begin
      cycle <= cycle + 16'd1;
      if (ARVALID && ARREADY) begin
        s_busy[free] <= 1'b1;
        s_id[free]   <= ARID;
        s_addr[free] <= ARADDR;
        s_left[free] <= ARLEN;
        s_size[free] <= ARSIZE;
        s_time[free] <= cycle + ((ARADDR >= 12'h800) ? LAT_SLOW : LAT_FAST) - 2;
        s_seq[free]  <= seq;
        seq          <= seq + 16'd1;
      end
      if (RVALID && RREADY)
        RVALID <= 1'b0;
      if (!sending && (ready0 || ready1)) begin
        sending <= 1'b1;
        cur     <= pick;
      end
      else if (sending && (!RVALID || RREADY)) begin
        RVALID      <= 1'b1;
        RID         <= s_id[cur];
        RDATA       <= {20'h0, s_addr[cur]};
        RRESP       <= 2'b00;
        RLAST       <= (s_left[cur] == 8'd0);
        s_addr[cur] <= s_addr[cur] + (12'd1 << s_size[cur]);   // INCR bursts only
        if (s_left[cur] == 8'd0) begin
          s_busy[cur] <= 1'b0;
          sending     <= 1'b0;
        end
        else begin
          s_left[cur] <= s_left[cur] - 8'd1;
        end
      end
    end
  end
endmodule
testbench.v
// Testbench: read data from different IDs may return out of order, from the same ID it may not
module tb;
  reg         ACLK    = 0;
  reg         ARESETn = 0;
  // write address channel
  wire [3:0]  AWID;
  wire [11:0] AWADDR;
  wire [7:0]  AWLEN;
  wire [2:0]  AWSIZE;
  wire [1:0]  AWBURST;
  wire        AWLOCK;
  wire        AWVALID;
  wire        AWREADY;
  // write data channel
  wire [31:0] WDATA;
  wire [3:0]  WSTRB;
  wire        WLAST;
  wire        WVALID;
  wire        WREADY;
  // write response channel
  wire [3:0]  BID;
  wire [1:0]  BRESP;
  wire        BVALID;
  wire        BREADY;
  // read address channel
  wire [3:0]  ARID;
  wire [11:0] ARADDR;
  wire [7:0]  ARLEN;
  wire [2:0]  ARSIZE;
  wire [1:0]  ARBURST;
  wire        ARLOCK;
  wire        ARVALID;
  wire        ARREADY;
  // read data channel
  wire [3:0]  RID;
  wire [31:0] RDATA;
  wire [1:0]  RRESP;
  wire        RLAST;
  wire        RVALID;
  wire        RREADY;

  localparam FIXED = 2'b00, INCR = 2'b01, WRAP = 2'b10;
  localparam BYTE = 3'd0, HALF = 3'd1, WORD = 3'd2;

  // ---- Command lists ----
  reg  [3:0]  aw_id    [0:63];
  reg  [11:0] aw_addr  [0:63];
  reg  [7:0]  aw_len   [0:63];
  reg  [2:0]  aw_size  [0:63];
  reg  [1:0]  aw_burst [0:63];
  reg         aw_lock  [0:63];
  integer     aw_delay [0:63];           // idle cycles before AWVALID
  integer     aw_after [0:63];           // read bursts that must complete first
  reg  [31:0] w_data   [0:255];
  reg  [3:0]  w_strb   [0:255];
  reg         w_last   [0:255];
  integer     w_delay  [0:255];          // idle cycles before WVALID
  reg  [3:0]  ar_id    [0:63];
  reg  [11:0] ar_addr  [0:63];
  reg  [7:0]  ar_len   [0:63];
  reg  [2:0]  ar_size  [0:63];
  reg  [1:0]  ar_burst [0:63];
  reg         ar_lock  [0:63];
  integer     ar_delay [0:63];           // idle cycles before ARVALID
  integer     ar_after [0:63];           // write responses that must arrive first
  integer     awn = 0, wn = 0, arn = 0;  // list lengths
  integer     awi = 0, wi = 0, ari = 0;  // next entry to send
  integer     aw_cnt = 0, w_cnt = 0, ar_cnt = 0;
  integer     bn = 0;                    // write responses received
  integer     rlasts = 0;                // read bursts completed
  integer     max_reads = 4;             // read transactions allowed in flight
  reg  [31:0] bready_pat = 32'hFFFFFFFF; // BREADY pattern, one bit per cycle
  reg  [31:0] rready_pat = 32'hFFFFFFFF; // RREADY pattern, one bit per cycle
  integer     cyc = 0;
  reg         run = 0;                   // VALIDs may rise only after reset
  integer     after_r = 0;               // ordering between channels for the next write ...
  integer     after_b = 0;               // ... and the next read

  // Address of the next beat (FIXED, INCR or WRAP)
  function [11:0] next_addr(input [11:0] a, input [2:0] size, input [1:0] burst, input [7:0] len);
    reg [11:0] bytes;
    reg [11:0] incr;
    reg [11:0] wrap_bytes;
    begin
      bytes = 12'd1 << size;
      incr  = (a & ~(bytes - 12'd1)) + bytes;
      if (burst == FIXED) begin
        next_addr = a;
      end
      else if (burst == WRAP) begin
        wrap_bytes = bytes * (len + 12'd1);
        next_addr  = (a & ~(wrap_bytes - 12'd1)) | (incr & (wrap_bytes - 12'd1));
      end
      else begin
        next_addr = incr;
      end
    end
  endfunction

  // A write burst: one AW entry and len+1 W entries. Beat k carries d0 + k on its byte lanes;
  // with pattern = 1, every byte carries the low 8 bits of its own address instead.
  task write(input [3:0] id, input [11:0] a0, input [7:0] len, input [2:0] size, input [1:0] burst,
             input [31:0] d0, input pattern, input lock, input integer delay);
    integer    k;
    integer    b;
    reg [11:0] a;
    reg [11:0] bytes;
    reg [1:0]  lo;
    reg [1:0]  hi;
    begin
      aw_id[awn]    = id;
      aw_addr[awn]  = a0;
      aw_len[awn]   = len;
      aw_size[awn]  = size;
      aw_burst[awn] = burst;
      aw_lock[awn]  = lock;
      aw_delay[awn] = delay;
      aw_after[awn] = after_r;
      awn = awn + 1;
      bytes = 12'd1 << size;
      a = a0;
      for (k = 0; k <= len; k = k + 1) begin
        lo = a[1:0];                                     // lowest byte lane of this beat
        hi = ((a & ~(bytes - 12'd1)) + bytes - 12'd1) & 12'd3;   // highest byte lane
        w_strb[wn] = 4'b0000;
        w_data[wn] = 32'h0;
        for (b = 0; b < 4; b = b + 1) begin
          if (b >= lo && b <= hi) begin
            w_strb[wn][b] = 1'b1;
            if (pattern)
              w_data[wn][8*b +: 8] = (a & ~12'd3) + b;
          end
        end
        if (!pattern)
          w_data[wn] = (d0 + k) << (8 * ((a & ~(bytes - 12'd1)) & 12'd3));
        w_last[wn]  = (k == len);
        w_delay[wn] = 0;
        wn = wn + 1;
        a = next_addr(a, size, burst, len);
      end
    end
  endtask

  task read(input [3:0] id, input [11:0] a0, input [7:0] len, input [2:0] size, input [1:0] burst,
            input lock, input integer delay);
    begin
      ar_id[arn]    = id;
      ar_addr[arn]  = a0;
      ar_len[arn]   = len;
      ar_size[arn]  = size;
      ar_burst[arn] = burst;
      ar_lock[arn]  = lock;
      ar_delay[arn] = delay;
      ar_after[arn] = after_b;
      arn = arn + 1;
    end
  endtask

  // ---- Channel outputs: VALID never depends on READY ----
  assign AWVALID = run && awi < awn && aw_cnt >= aw_delay[awi] && rlasts >= aw_after[awi];
  assign AWID    = aw_id[awi];
  assign AWADDR  = aw_addr[awi];
  assign AWLEN   = aw_len[awi];
  assign AWSIZE  = aw_size[awi];
  assign AWBURST = aw_burst[awi];
  assign AWLOCK  = aw_lock[awi];
  assign WVALID  = run && wi < wn && w_cnt >= w_delay[wi];
  assign WDATA   = WVALID ? w_data[wi] : 32'h0;
  assign WSTRB   = WVALID ? w_strb[wi] : 4'h0;
  assign WLAST   = WVALID ? w_last[wi] : 1'b0;
  assign ARVALID = run && ari < arn && ar_cnt >= ar_delay[ari] && bn >= ar_after[ari] && (ari - rlasts) < max_reads;
  assign ARID    = ar_id[ari];
  assign ARADDR  = ar_addr[ari];
  assign ARLEN   = ar_len[ari];
  assign ARSIZE  = ar_size[ari];
  assign ARBURST = ar_burst[ari];
  assign ARLOCK  = ar_lock[ari];
  assign BREADY  = run && bready_pat[cyc % 32];
  assign RREADY  = run && rready_pat[cyc % 32];

  function [8*6-1:0] resp_name(input [1:0] r);
    resp_name = (r == 2'b00) ? "OKAY" : (r == 2'b01) ? "EXOKAY" : (r == 2'b10) ? "SLVERR" : "DECERR";
  endfunction

  function [8*5-1:0] burst_name(input [1:0] b);
    burst_name = (b == FIXED) ? "FIXED" : (b == INCR) ? "INCR" : "WRAP";
  endfunction

  // ---- Handshakes: log them and move each list forward ----
  always @(posedge ACLK) begin
    if (ARESETn) begin
      run <= 1'b1;
      cyc <= cyc + 1;
      aw_cnt <= aw_cnt + 1;
      w_cnt  <= w_cnt + 1;
      ar_cnt <= ar_cnt + 1;
      if (AWVALID && AWREADY) begin
        $display("%0t  AW  id=%0d addr=0x%h len=%0d size=%0d %0s%0s", $time, AWID, AWADDR, AWLEN, 1 << AWSIZE,
                 burst_name(AWBURST), AWLOCK ? " exclusive" : "");
        awi <= awi + 1;
        aw_cnt <= 0;
      end
      if (WVALID && WREADY) begin
        $display("%0t  W   data=0x%h strb=%b%0s", $time, WDATA, WSTRB, WLAST ? " last" : "");
        wi <= wi + 1;
        w_cnt <= 0;
      end
      if (BVALID && BREADY) begin
        $display("%0t  B   id=%0d %0s", $time, BID, resp_name(BRESP));
        bn <= bn + 1;
      end
      if (ARVALID && ARREADY) begin
        $display("%0t  AR  id=%0d addr=0x%h len=%0d size=%0d %0s%0s", $time, ARID, ARADDR, ARLEN, 1 << ARSIZE,
                 burst_name(ARBURST), ARLOCK ? " exclusive" : "");
        ari <= ari + 1;
        ar_cnt <= 0;
      end
      if (RVALID && RREADY) begin
        $display("%0t  R   id=%0d data=0x%h %0s%0s", $time, RID, RDATA, resp_name(RRESP), RLAST ? " last" : "");
        if (RLAST)
          rlasts <= rlasts + 1;
      end
    end
  end

  axi_ooo_mem #(
    .LAT_FAST (2),
    .LAT_SLOW (8)
  ) dut (
    .ACLK    (ACLK),
    .ARESETn (ARESETn),
    .AWID    (AWID),
    .AWADDR  (AWADDR),
    .AWLEN   (AWLEN),
    .AWSIZE  (AWSIZE),
    .AWBURST (AWBURST),
    .AWLOCK  (AWLOCK),
    .AWVALID (AWVALID),
    .AWREADY (AWREADY),
    .WDATA   (WDATA),
    .WSTRB   (WSTRB),
    .WLAST   (WLAST),
    .WVALID  (WVALID),
    .WREADY  (WREADY),
    .BID     (BID),
    .BRESP   (BRESP),
    .BVALID  (BVALID),
    .BREADY  (BREADY),
    .ARID    (ARID),
    .ARADDR  (ARADDR),
    .ARLEN   (ARLEN),
    .ARSIZE  (ARSIZE),
    .ARBURST (ARBURST),
    .ARLOCK  (ARLOCK),
    .ARVALID (ARVALID),
    .ARREADY (ARREADY),
    .RID     (RID),
    .RDATA   (RDATA),
    .RRESP   (RRESP),
    .RLAST   (RLAST),
    .RVALID  (RVALID),
    .RREADY  (RREADY)
  );

  always #5 ACLK = ~ACLK;

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);
    // Different IDs: the slow read (ID 1) is issued first, the fast read (ID 2) second
    read(1, 12'h800, 3, WORD, INCR, 0, 0);              // slow region
    read(2, 12'h100, 3, WORD, INCR, 0, 0);              // fast region
    // The same ID: ID 3 issues a slow read, then a fast read
    after_b = 0;
    read(3, 12'h900, 3, WORD, INCR, 0, 12);
    read(3, 12'h200, 3, WORD, INCR, 0, 0);
    #22 ARESETn = 1;
    wait (bn == awn && rlasts == arn && awi == awn && wi == wn);
    #20 $finish;
  end
endmodule
Admin mode — edit and delete controls are visible on each row. Use the IDE to add new snippets via File → Save as Snippet.