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 burst types: FIXED, INCR, WRAP

Verilog Bus Protocols

One burst of each type; the slave prints the address of every beat.

Open in IDE
design.v
// An AXI4 memory slave: 4 KB of RAM on a 32-bit data bus, 4-bit IDs.
//   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 + 12'd1);              // 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: FIXED, INCR and WRAP bursts; the slave prints the address of every beat
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_mem #(
    .READ_LAT (2),
    .TRACE    (1)
  ) 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);
    write(1, 12'h080, 3, WORD, FIXED, 32'hF0, 0, 0, 0);  // 4 beats, all to 0x080
    write(1, 12'h090, 3, WORD, INCR,  32'h90, 0, 0, 0);  // 0x090, 0x094, 0x098, 0x09C
    write(1, 12'h0A8, 3, WORD, WRAP,  32'hB0, 0, 0, 0);  // 0x0A8, 0x0AC, 0x0A0, 0x0A4
    read(1, 12'h080, 0, WORD, INCR, 0, 30);
    read(1, 12'h0A0, 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.