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

Self-checking AXI testbench (find the bug)

Verilog Bus Protocols

Random AXI4 bursts, a byte-level scoreboard and protocol checks find a bug in an AXI4 memory slave.

Open in IDE
design.v
// An AXI4 memory slave: 4 KB of RAM on a 32-bit data bus, 4-bit IDs. It has a bug.
//   FIXED, INCR and WRAP bursts, narrow and unaligned transfers through WSTRB
//   one write transaction at a time; up to 4 read transactions queued, answered in order
//   READ_LAT  cycles from the AR handshake to the first R beat (2 or more)
//   ERR_BASE  addresses from ERR_BASE upwards belong to a disabled function: SLVERR
//   one exclusive access monitor (AxLOCK = 1): EXOKAY on success, OKAY on failure
//   TRACE     1: print the address of every beat the slave handles
module axi_mem #(
  parameter READ_LAT = 2,
  parameter ERR_BASE = 12'hF00,
  parameter TRACE    = 0
) (
  input             ACLK,
  input             ARESETn,
  // write address channel
  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,
  // write data channel
  input      [31:0] WDATA,
  input      [3:0]  WSTRB,
  input             WLAST,
  input             WVALID,
  output            WREADY,
  // write response channel
  output reg [3:0]  BID,
  output reg [1:0]  BRESP,
  output reg        BVALID,
  input             BREADY,
  // read address channel
  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,
  // read data channel
  output reg [3:0]  RID,
  output reg [31:0] RDATA,
  output reg [1:0]  RRESP,
  output reg        RLAST,
  output reg        RVALID,
  input             RREADY
);
  localparam FIXED = 2'b00, INCR = 2'b01, WRAP = 2'b10;
  localparam OKAY = 2'b00, EXOKAY = 2'b01, SLVERR = 2'b10;

  reg [31:0] mem [0:1023];               // 1024 words = 4 KB
  integer    i;

  initial begin
    for (i = 0; i < 1024; i = i + 1)
      mem[i] = 32'h0;
  end

  // Address of the next beat of a burst
  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;           // aligned address + size
      if (burst == FIXED) begin
        next_addr = a;
      end
      else if (burst == WRAP) begin
        wrap_bytes = bytes * len;                        // the wrap boundary block
        next_addr  = (a & ~(wrap_bytes - 12'd1)) | (incr & (wrap_bytes - 12'd1));
      end
      else begin
        next_addr = incr;
      end
    end
  endfunction

  // ---- Exclusive access monitor: one address, one ID ----
  reg        mon_valid;
  reg [3:0]  mon_id;
  reg [11:0] mon_addr;

  // ---- Write side: AW, then W beats, then B ----
  reg        w_active;                   // an address was accepted, data beats are due
  reg [3:0]  w_id;
  reg [11:0] w_addr;
  reg [7:0]  w_len;
  reg [2:0]  w_size;
  reg [1:0]  w_burst;
  reg        w_excl;                     // exclusive write
  reg        w_pass;                     // the exclusive write may update memory
  reg        w_err;

  assign AWREADY = !w_active && !BVALID;
  assign WREADY  = w_active;

  always @(posedge ACLK) begin
    if (!ARESETn) begin
      w_active <= 1'b0;
      BVALID   <= 1'b0;
    end
    else begin
      if (BVALID && BREADY)
        BVALID <= 1'b0;
      if (AWVALID && AWREADY) begin
        w_active <= 1'b1;
        w_id     <= AWID;
        w_addr   <= AWADDR;
        w_len    <= AWLEN;
        w_size   <= AWSIZE;
        w_burst  <= AWBURST;
        w_excl   <= AWLOCK;
        w_pass   <= !AWLOCK || (mon_valid && mon_id == AWID && mon_addr == AWADDR);
        w_err    <= 1'b0;
      end
      if (WVALID && WREADY) begin
        if (TRACE)
          $display("%0t      slave: write beat at 0x%h, WSTRB %b", $time, w_addr, WSTRB);
        if (w_addr >= ERR_BASE) begin
          w_err <= 1'b1;
        end
        else if (w_pass) begin
          if (WSTRB[0])
            mem[w_addr[11:2]][7:0]   <= WDATA[7:0];
          if (WSTRB[1])
            mem[w_addr[11:2]][15:8]  <= WDATA[15:8];
          if (WSTRB[2])
            mem[w_addr[11:2]][23:16] <= WDATA[23:16];
          if (WSTRB[3])
            mem[w_addr[11:2]][31:24] <= WDATA[31:24];
        end
        w_addr <= next_addr(w_addr, w_size, w_burst, w_len);
        if (WLAST) begin
          w_active <= 1'b0;
          BVALID   <= 1'b1;
          BID      <= w_id;
          if (w_err || w_addr >= ERR_BASE)
            BRESP <= SLVERR;
          else if (w_excl && w_pass)
            BRESP <= EXOKAY;
          else
            BRESP <= OKAY;
        end
      end
    end
  end

  // ---- Read side: a queue of 4 addresses, answered in order ----
  reg [3:0]  q_id    [0:3];
  reg [11:0] q_addr  [0:3];
  reg [7:0]  q_len   [0:3];
  reg [2:0]  q_size  [0:3];
  reg [1:0]  q_burst [0:3];
  reg        q_lock  [0:3];
  reg [15:0] q_time  [0:3];              // first cycle the data may be sent
  reg [1:0]  q_head;
  reg [1:0]  q_tail;
  reg [2:0]  q_count;
  reg [15:0] cycle;

  reg        r_active;                   // sending the beats of one burst
  reg [3:0]  r_id;
  reg [11:0] r_addr;
  reg [7:0]  r_len;
  reg [7:0]  r_left;                     // beats still to send after this one
  reg [2:0]  r_size;
  reg [1:0]  r_burst;
  reg        r_lock;

  wire ar_hs   = ARVALID && ARREADY;
  wire present = r_active && (!RVALID || RREADY);  // a new beat can go out this cycle
  wire can_load = q_count != 3'd0 && cycle >= q_time[q_head] &&
                  (!r_active || (present && r_left == 8'd0));

  assign ARREADY = (q_count < 3'd4);

  always @(posedge ACLK) begin
    if (!ARESETn) begin
      q_head   <= 2'd0;
      q_tail   <= 2'd0;
      q_count  <= 3'd0;
      cycle    <= 16'd0;
      r_active <= 1'b0;
      RVALID   <= 1'b0;
    end
    else begin
      cycle <= cycle + 16'd1;
      if (ar_hs) begin
        q_id[q_tail]    <= ARID;
        q_addr[q_tail]  <= ARADDR;
        q_len[q_tail]   <= ARLEN;
        q_size[q_tail]  <= ARSIZE;
        q_burst[q_tail] <= ARBURST;
        q_lock[q_tail]  <= ARLOCK;
        q_time[q_tail]  <= cycle + READ_LAT - 2;
        q_tail          <= q_tail + 2'd1;
      end
      q_count <= q_count + (ar_hs ? 3'd1 : 3'd0) - (can_load ? 3'd1 : 3'd0);

      if (RVALID && RREADY)
        RVALID <= 1'b0;
      if (present) begin
        if (TRACE)
          $display("%0t      slave: read beat at 0x%h", $time, r_addr);
        RVALID <= 1'b1;
        RID    <= r_id;
        RLAST  <= (r_left == 8'd0);
        if (r_addr >= ERR_BASE) begin
          RDATA <= 32'h0;
          RRESP <= SLVERR;
        end
        else begin
          RDATA <= mem[r_addr[11:2]];
          RRESP <= r_lock ? EXOKAY : OKAY;
        end
        r_addr <= next_addr(r_addr, r_size, r_burst, r_len);
        if (r_left == 8'd0)
          r_active <= 1'b0;
        else
          r_left <= r_left - 8'd1;
      end
      if (can_load) begin
        r_active <= 1'b1;
        r_id     <= q_id[q_head];
        r_addr   <= q_addr[q_head];
        r_len    <= q_len[q_head];
        r_left   <= q_len[q_head];
        r_size   <= q_size[q_head];
        r_burst  <= q_burst[q_head];
        r_lock   <= q_lock[q_head];
        q_head   <= q_head + 2'd1;
      end
    end
  end

  // ---- Exclusive monitor updates ----
  always @(posedge ACLK) begin
    if (!ARESETn) begin
      mon_valid <= 1'b0;
    end
    else begin
      if (can_load && q_lock[q_head]) begin
        mon_valid <= 1'b1;                       // an exclusive read starts monitoring
        mon_id    <= q_id[q_head];
        mon_addr  <= q_addr[q_head];
      end
      else if (WVALID && WREADY && w_pass && |WSTRB && w_addr[11:2] == mon_addr[11:2]) begin
        mon_valid <= 1'b0;                       // any write to the location ends it
      end
    end
  end
endmodule
testbench.v
// Testbench: random AXI4 bursts checked by a byte-level scoreboard and protocol checks
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         verbose = 0;               // 1: print every handshake
  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
        if (verbose)
          $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
        if (verbose)
          $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
        if (verbose)
          $display("%0t  B   id=%0d %0s", $time, BID, resp_name(BRESP));
        bn <= bn + 1;
      end
      if (ARVALID && ARREADY) begin
        if (verbose)
          $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
        if (verbose)
          $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

  // ---- Random traffic: write a set of bursts, then read every one back ----
  integer seed = 5;

  task random_traffic(input integer count);
    integer    k;
    integer    t;
    reg [1:0]  burst;
    reg [7:0]  len;
    reg [2:0]  size;
    reg [11:0] a;
    reg [11:0] bytes;
    begin
      for (k = 0; k < count; k = k + 1) begin
        t     = {$random(seed)} % 3;
        burst = (t == 0) ? FIXED : (t == 1) ? INCR : WRAP;
        size  = {$random(seed)} % 3;                      // 1, 2 or 4 bytes per beat
        bytes = 12'd1 << size;
        if (burst == WRAP)
          len = (8'd2 << ({$random(seed)} % 4)) - 8'd1;   // 2, 4, 8 or 16 beats
        else
          len = {$random(seed)} % 8;                      // 1 to 8 beats
        // every burst gets a 64-byte region of its own; the start is aligned to the beat size
        a = 12'h040 * k + (({$random(seed)} % 12'h40) & ~(bytes - 12'd1));
        if (burst != WRAP && ((a & 12'h03F) + bytes * (len + 12'd1)) > 12'h040)
          a = 12'h040 * k;                                // keep INCR and FIXED inside the region
        write(k % 16, a, len, size, burst, $random(seed), 0, 0, 0);
      end
      after_b = count;
      for (k = 0; k < count; k = k + 1)
        read(k % 16, aw_addr[k], aw_len[k], aw_size[k], aw_burst[k], 0, 0);
      bready_pat = 32'hF7BFDEF7;
      rready_pat = 32'hEFDF7BEF;
    end
  endtask

  // ---- Reference model and scoreboard ----
  reg  [7:0]  model [0:4095];            // one entry per byte of the slave
  integer     errors  = 0;
  integer     checked = 0;
  integer     wb = 0;                    // beat number inside the current write burst
  integer     wt = 0;                    // which write transaction the W beats belong to
  reg  [11:0] wa;                        // address of the current W beat
  integer     rb = 0;
  integer     rt = 0;
  reg  [11:0] ra;
  reg  [11:0] rbytes;
  integer     b;
  integer     mi;

  initial begin
    for (mi = 0; mi < 4096; mi = mi + 1)
      model[mi] = 8'h00;
  end

  always @(posedge ACLK) begin
    if (ARESETn) begin
      // W beats belong to the write transactions in order: AXI4 has no write interleaving
      if (WVALID && WREADY) begin
        if (wb == 0)
          wa = aw_addr[wt];
        for (b = 0; b < 4; b = b + 1)
          if (WSTRB[b])
            model[(wa & ~12'd3) + b] = WDATA[8*b +: 8];
        if (WLAST) begin
          wb = 0;
          wt = wt + 1;
        end
        else begin
          wb = wb + 1;
          wa = next_addr(wa, aw_size[wt], aw_burst[wt], aw_len[wt]);
        end
      end
      // R beats are compared with the model on their active byte lanes
      if (RVALID && RREADY) begin
        if (rb == 0)
          ra = ar_addr[rt];
        rbytes = 12'd1 << ar_size[rt];
        for (b = 0; b < 4; b = b + 1) begin
          if (b >= ra[1:0] && b < ((ra & ~(rbytes - 12'd1)) & 12'd3) + rbytes) begin
            checked = checked + 1;
            if (RDATA[8*b +: 8] !== model[(ra & ~12'd3) + b]) begin
              errors = errors + 1;
              if (errors <= 4)
                $display("%0t  MISMATCH read id=%0d beat %0d at 0x%h byte %0d: got 0x%h, expected 0x%h",
                         $time, RID, rb, ra, b, RDATA[8*b +: 8], model[(ra & ~12'd3) + b]);
            end
          end
        end
        if (RLAST) begin
          rb = 0;
          rt = rt + 1;
        end
        else begin
          rb = rb + 1;
          ra = next_addr(ra, ar_size[rt], ar_burst[rt], ar_len[rt]);
        end
      end
    end
  end

  // ---- Protocol checks ----
  reg         p_awv = 0, p_awr = 0, p_wv = 0, p_wr = 0, p_arv = 0, p_arr = 0;
  reg         p_rv  = 0, p_rr  = 0, p_bv = 0, p_br = 0;
  reg  [11:0] p_awaddr;
  reg  [11:0] p_araddr;
  reg  [31:0] p_wdata;
  reg  [31:0] p_rdata;
  integer     w_beats = 0;               // W beats seen in the current burst
  integer     w_tr    = 0;               // write transaction the W beats belong to
  integer     r_beats = 0;
  integer     r_tr    = 0;

  task fail(input [8*56-1:0] msg);
    begin
      if (errors < 4)
        $display("%0t  PROTOCOL: %0s", $time, msg);
      errors = errors + 1;
    end
  endtask

  always @(posedge ACLK) begin
    if (ARESETn) begin
      // Once VALID is high it stays high, with stable information, until READY
      if (p_awv && !p_awr && (!AWVALID || AWADDR != p_awaddr))
        fail("AWVALID dropped or AWADDR changed before AWREADY");
      if (p_wv && !p_wr && (!WVALID || WDATA != p_wdata))
        fail("WVALID dropped or WDATA changed before WREADY");
      if (p_arv && !p_arr && (!ARVALID || ARADDR != p_araddr))
        fail("ARVALID dropped or ARADDR changed before ARREADY");
      if (p_rv && !p_rr && (!RVALID || RDATA != p_rdata))
        fail("RVALID dropped or RDATA changed before RREADY");
      if (p_bv && !p_br && !BVALID)
        fail("BVALID dropped before BREADY");
      // WRAP bursts have 2, 4, 8 or 16 beats
      if (AWVALID && AWBURST == WRAP && AWLEN != 1 && AWLEN != 3 && AWLEN != 7 && AWLEN != 15)
        fail("AW WRAP burst length is not 2, 4, 8 or 16");
      // WLAST exactly on beat AWLEN + 1, RLAST exactly on beat ARLEN + 1, RID = ARID
      if (WVALID && WREADY) begin
        if (WLAST != (w_beats == aw_len[w_tr]))
          fail("WLAST on the wrong beat");
        if (WLAST) begin
          w_beats = 0;
          w_tr    = w_tr + 1;
        end
        else begin
          w_beats = w_beats + 1;
        end
      end
      if (RVALID && RREADY) begin
        if (RLAST != (r_beats == ar_len[r_tr]))
          fail("RLAST on the wrong beat");
        if (RID != ar_id[r_tr])
          fail("RID does not match ARID");
        if (RLAST) begin
          r_beats = 0;
          r_tr    = r_tr + 1;
        end
        else begin
          r_beats = r_beats + 1;
        end
      end
      // A write response only after the last data beat of its transaction
      if (BVALID && !p_bv && bn >= w_tr)
        fail("BVALID before the last write data beat");
      p_awv <= AWVALID;
      p_awr <= AWREADY;
      p_wv  <= WVALID;
      p_wr  <= WREADY;
      p_arv <= ARVALID;
      p_arr <= ARREADY;
      p_rv  <= RVALID;
      p_rr  <= RREADY;
      p_bv  <= BVALID;
      p_br  <= BREADY;
      p_awaddr <= AWADDR;
      p_araddr <= ARADDR;
      p_wdata  <= WDATA;
      p_rdata  <= RDATA;
    end
  end

  axi_mem #(
    .READ_LAT (3),
    .TRACE    (0)
  ) 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);
    random_traffic(24);
    #22 ARESETn = 1;
    wait (bn == awn && rlasts == arn && awi == awn && wi == wn);
    #20;
    if (errors == 0)
      $display("PASS: %0d bursts written and read back, %0d bytes checked, no errors", awn, checked);
    else
      $display("FAIL: %0d errors, %0d bytes checked", errors, checked);
    $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.