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-Lite register slave

Verilog Bus Protocols

Four registers behind an AXI4-Lite interface; write address and data arrive in different orders.

Open in IDE
design.v
// An AXI4-Lite register slave with four 32-bit registers.
//   0x00  CTRL    read/write
//   0x04  DATA    read/write, byte writes through WSTRB
//   0x08  COUNT   read-only: number of writes accepted so far
//   0x0C  ID      read-only: 0x41584C31 ("AXL1")
//   anything else: SLVERR. Writes to the read-only registers are ignored.
// The write address and write data are accepted independently, in either order.
module axil_regs (
  input             ACLK,
  input             ARESETn,
  // write address channel
  input      [11:0] AWADDR,
  input      [2:0]  AWPROT,
  input             AWVALID,
  output            AWREADY,
  // write data channel
  input      [31:0] WDATA,
  input      [3:0]  WSTRB,
  input             WVALID,
  output            WREADY,
  // write response channel
  output reg [1:0]  BRESP,
  output reg        BVALID,
  input             BREADY,
  // read address channel
  input      [11:0] ARADDR,
  input      [2:0]  ARPROT,
  input             ARVALID,
  output            ARREADY,
  // read data channel
  output reg [31:0] RDATA,
  output reg [1:0]  RRESP,
  output reg        RVALID,
  input             RREADY
);
  localparam OKAY = 2'b00, SLVERR = 2'b10;

  reg [31:0] ctrl;
  reg [31:0] data;
  reg [31:0] count;

  // Holding registers: an address or data that arrived before its partner
  reg        aw_full;
  reg [11:0] aw_addr;
  reg        w_full;
  reg [31:0] w_data;
  reg [3:0]  w_strb;

  assign AWREADY = !aw_full;
  assign WREADY  = !w_full;

  // The write happens once both halves are here and the response channel is free
  wire do_write = aw_full && w_full && (!BVALID || BREADY);

  always @(posedge ACLK) begin
    if (!ARESETn) begin
      aw_full <= 1'b0;
      w_full  <= 1'b0;
      BVALID  <= 1'b0;
      ctrl    <= 32'h0;
      data    <= 32'h0;
      count   <= 32'h0;
    end
    else begin
      if (AWVALID && AWREADY) begin
        aw_full <= 1'b1;
        aw_addr <= AWADDR;
      end
      if (WVALID && WREADY) begin
        w_full <= 1'b1;
        w_data <= WDATA;
        w_strb <= WSTRB;
      end
      if (BVALID && BREADY)
        BVALID <= 1'b0;
      if (do_write) begin
        aw_full <= 1'b0;
        w_full  <= 1'b0;
        BVALID  <= 1'b1;
        BRESP   <= (aw_addr[11:4] == 8'h00) ? OKAY : SLVERR;
        if (aw_addr[11:4] == 8'h00)
          count <= count + 32'd1;
        if (aw_addr == 12'h000)
          ctrl <= w_data;
        if (aw_addr == 12'h004) begin
          if (w_strb[0])
            data[7:0]   <= w_data[7:0];
          if (w_strb[1])
            data[15:8]  <= w_data[15:8];
          if (w_strb[2])
            data[23:16] <= w_data[23:16];
          if (w_strb[3])
            data[31:24] <= w_data[31:24];
        end
      end
    end
  end

  // Reads: one at a time, the address is accepted when the read data channel is free
  assign ARREADY = !RVALID;

  always @(posedge ACLK) begin
    if (!ARESETn) begin
      RVALID <= 1'b0;
    end
    else begin
      if (RVALID && RREADY)
        RVALID <= 1'b0;
      if (ARVALID && ARREADY) begin
        RVALID <= 1'b1;
        RRESP  <= (ARADDR[11:4] == 8'h00) ? OKAY : SLVERR;
        case (ARADDR)
          12'h000: RDATA <= ctrl;
          12'h004: RDATA <= data;
          12'h008: RDATA <= count;
          12'h00C: RDATA <= 32'h41584C31;
          default: RDATA <= 32'h0;
        endcase
      end
    end
  end
endmodule
testbench.v
// Testbench: an AXI4-Lite register slave; address and data arrive in different orders
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

  // The AXI4-Lite slave has no IDs and no RLAST: tie them for the full AXI master
  assign BID   = 4'd0;
  assign RID   = 4'd0;
  assign RLAST = 1'b1;

  axil_regs dut (
    .ACLK    (ACLK),
    .ARESETn (ARESETn),
    .AWADDR  (AWADDR),
    .AWPROT  (3'b000),
    .AWVALID (AWVALID),
    .AWREADY (AWREADY),
    .WDATA   (WDATA),
    .WSTRB   (WSTRB),
    .WVALID  (WVALID),
    .WREADY  (WREADY),
    .BRESP   (BRESP),
    .BVALID  (BVALID),
    .BREADY  (BREADY),
    .ARADDR  (ARADDR),
    .ARPROT  (3'b000),
    .ARVALID (ARVALID),
    .ARREADY (ARREADY),
    .RDATA   (RDATA),
    .RRESP   (RRESP),
    .RVALID  (RVALID),
    .RREADY  (RREADY)
  );

  always #5 ACLK = ~ACLK;

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);
    // write(id, addr, len, size, burst, data, pattern, lock, delay): AXI4-Lite uses len 0, full-width size
    write(0, 12'h000, 0, WORD, INCR, 32'h0000_00C1, 0, 0, 0);   // address and data together
    write(0, 12'h004, 0, WORD, INCR, 32'h1122_3344, 0, 0, 3);   // data first, address 3 cycles later
    write(0, 12'h004, 0, WORD, INCR, 32'hAA00_0000, 0, 0, 0);   // address first, data later
    w_delay[2] = 8;                                             // the data waits 8 cycles
    w_strb[2]  = 4'b1000;                                       // ... and writes only the top byte
    write(0, 12'h00C, 0, WORD, INCR, 32'hFFFF_FFFF, 0, 0, 0);   // read-only: ignored
    write(0, 12'h040, 0, WORD, INCR, 32'h1234_5678, 0, 0, 0);   // unmapped: SLVERR
    after_b = 5;
    read(0, 12'h000, 0, WORD, INCR, 0, 0);
    read(0, 12'h004, 0, WORD, INCR, 0, 0);
    read(0, 12'h008, 0, WORD, INCR, 0, 0);
    read(0, 12'h00C, 0, WORD, INCR, 0, 0);
    read(0, 12'h040, 0, 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.