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

AHB-Lite: single and back-to-back transfers

Verilog Bus Protocols

A pipelined AHB-Lite master runs single and back-to-back transfers into a 1 KB memory slave.

Open in IDE
design.v
// An AHB-Lite memory slave: 1 KB of RAM, byte/halfword/word transfers.
//   WAIT_NONSEQ  wait states for a NONSEQ transfer (the first beat of a burst)
//   WAIT_SEQ     wait states for a SEQ transfer (the following beats)
//   WAIT_WRITES  0: writes never wait (the slave buffers them), 1: writes wait like reads
//   RO_BASE      addresses from RO_BASE upwards are read-only: a write gets an ERROR response
module ahb_mem #(
  parameter WAIT_NONSEQ = 0,
  parameter WAIT_SEQ    = 0,
  parameter WAIT_WRITES = 1,
  parameter RO_BASE     = 10'h300
) (
  input             HCLK,
  input             HRESETn,
  input             HSEL,
  input      [31:0] HADDR,
  input      [1:0]  HTRANS,
  input             HWRITE,
  input      [2:0]  HSIZE,
  input      [31:0] HWDATA,
  input             HREADY,
  output            HREADYOUT,
  output            HRESP,
  output     [31:0] HRDATA
);
  reg [31:0] mem [0:255];                // 256 words = 1 KB
  integer    i;

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

  // Address phase signals, registered for the data phase
  reg        d_valid;                    // a NONSEQ or SEQ transfer is in its data phase
  reg [9:0]  d_addr;
  reg        d_write;
  reg [2:0]  d_size;
  reg [3:0]  wait_left;                  // wait states still to insert
  reg        err1;                       // first cycle of an ERROR response
  reg        err2;                       // second cycle of an ERROR response

  wire start = HREADY && HSEL && HTRANS[1];        // NONSEQ (10) or SEQ (11) accepted
  wire bad   = start && HWRITE && (HADDR[9:0] >= RO_BASE);

  always @(posedge HCLK) begin
    if (!HRESETn) begin
      d_valid   <= 1'b0;
      wait_left <= 4'd0;
      err1      <= 1'b0;
      err2      <= 1'b0;
    end
    else begin
      err2 <= err1;
      if (err1) begin
        err1    <= 1'b0;                 // second cycle of the ERROR response follows
        d_valid <= 1'b0;
      end
      else if (HREADY) begin
        // A new address phase completes: capture it
        d_valid   <= start && !bad;
        d_addr    <= HADDR[9:0];
        d_write   <= HWRITE;
        d_size    <= HSIZE;
        if (!start || bad || (HWRITE && !WAIT_WRITES))
          wait_left <= 4'd0;
        else if (HTRANS == 2'b10)
          wait_left <= WAIT_NONSEQ;
        else
          wait_left <= WAIT_SEQ;
        err1      <= bad;
      end
      else if (wait_left != 4'd0) begin
        wait_left <= wait_left - 4'd1;
      end
    end
  end

  // The data phase completes when no wait states are left
  assign HREADYOUT = err1 ? 1'b0 : (wait_left == 4'd0);
  assign HRESP     = err1 || err2;

  // Byte lanes for the write: little-endian, lane = address bits [1:0]
  wire [3:0] lanes = (d_size == 3'b000) ? (4'b0001 << d_addr[1:0]) :
                     (d_size == 3'b001) ? (d_addr[1] ? 4'b1100 : 4'b0011) :
                                          4'b1111;

  always @(posedge HCLK) begin
    if (d_valid && d_write && HREADYOUT) begin
      if (lanes[0])
        mem[d_addr[9:2]][7:0]   <= HWDATA[7:0];
      if (lanes[1])
        mem[d_addr[9:2]][15:8]  <= HWDATA[15:8];
      if (lanes[2])
        mem[d_addr[9:2]][23:16] <= HWDATA[23:16];
      if (lanes[3])
        mem[d_addr[9:2]][31:24] <= HWDATA[31:24];
    end
  end

  // Read data: the whole word, in the cycle the transfer completes.
  // The master picks the active byte lanes.
  assign HRDATA = (d_valid && !d_write && HREADYOUT) ? mem[d_addr[9:2]] : 32'h0;
endmodule
testbench.v
// Testbench: single AHB-Lite writes and reads, then pipelined back to back
module tb;
  reg         HCLK    = 0;
  reg         HRESETn = 0;
  wire [31:0] HADDR;
  wire [1:0]  HTRANS;
  wire        HWRITE;
  wire [2:0]  HSIZE;
  wire [2:0]  HBURST;
  wire [31:0] HWDATA;
  wire [31:0] HRDATA;
  wire        HREADY;
  wire        HRESP;

  // HTRANS encodings
  localparam IDLE = 2'b00, BUSY = 2'b01, NONSEQ = 2'b10, SEQ = 2'b11;
  // HBURST encodings
  localparam SINGLE = 3'b000, INCR = 3'b001, WRAP4 = 3'b010, INCR4 = 3'b011,
             WRAP8  = 3'b100, INCR8 = 3'b101, WRAP16 = 3'b110, INCR16 = 3'b111;
  // HSIZE encodings
  localparam BYTE = 3'b000, HALF = 3'b001, WORD = 3'b010;

  // The command list: one entry per address phase
  reg  [1:0]  c_trans [0:255];
  reg  [31:0] c_addr  [0:255];
  reg         c_write [0:255];
  reg  [2:0]  c_size  [0:255];
  reg  [2:0]  c_burst [0:255];
  reg  [31:0] c_wdata [0:255];
  integer     n  = 0;                    // number of commands
  integer     ap = 0;                    // command in the address phase
  integer     dp = -1;                   // command in the data phase (-1: none)
  integer     k;
  reg         skip = 0;                  // drive IDLE: the rest of a burst was cancelled
  reg         cancel_on_error = 1;

  // Append one address phase to the list
  task add(input [1:0] t, input w, input [31:0] a, input [2:0] s, input [2:0] b, input [31:0] d);
    begin
      c_trans[n] = t;
      c_write[n] = w;
      c_addr[n]  = a;
      c_size[n]  = s;
      c_burst[n] = b;
      c_wdata[n] = d << (8 * a[1:0]);    // put narrow data on its byte lanes
      n = n + 1;
    end
  endtask

  // Append a whole burst. beats = 1 for SINGLE; INCR needs an explicit length.
  task burst(input w, input [2:0] b, input [31:0] a0, input [2:0] s, input integer beats, input [31:0] d0);
    integer    i;
    reg [31:0] a;
    reg [31:0] bytes;
    reg [31:0] lo;
    begin
      bytes = 1 << s;
      a = a0;
      for (i = 0; i < beats; i = i + 1) begin
        add(i == 0 ? NONSEQ : SEQ, w, a, s, b, d0 + i);
        if (b == WRAP4 || b == WRAP8 || b == WRAP16) begin
          lo = a0 & ~(bytes * beats - 1);              // wrap boundary
          a  = lo + ((a + bytes - lo) % (bytes * beats));
        end
        else begin
          a = a + bytes;
        end
      end
    end
  endtask

  // Insert a BUSY cycle before the next beat (address and control of the next beat)
  task busy_next(input w, input [31:0] a, input [2:0] s, input [2:0] b);
    begin
      add(BUSY, w, a, s, b, 32'h0);
    end
  endtask

  // Address phase outputs
  assign HTRANS = (HRESETn && ap < n && !skip) ? c_trans[ap] : IDLE;
  assign HADDR  = (ap < n) ? c_addr[ap]  : (n > 0 ? c_addr[n-1]  : 32'h0);
  assign HWRITE = (ap < n) ? c_write[ap] : (n > 0 ? c_write[n-1] : 1'b0);
  assign HSIZE  = (ap < n) ? c_size[ap]  : (n > 0 ? c_size[n-1]  : 3'b0);
  assign HBURST = (ap < n) ? c_burst[ap] : (n > 0 ? c_burst[n-1] : 3'b0);
  // Data phase output
  assign HWDATA = (dp >= 0 && c_write[dp]) ? c_wdata[dp] : 32'h0;

  always @(posedge HCLK) begin
    if (HRESETn) begin
      if (HREADY) begin
        if (dp >= 0 && c_trans[dp][1]) begin
          $write("%0t  %s 0x%h = 0x%h  ", $time, c_write[dp] ? "write" : "read ", c_addr[dp][15:0],
                 c_write[dp] ? c_wdata[dp] : HRDATA);
          if (HRESP)
            $display("ERROR");
          else
            $display("OKAY");
        end
        if (skip) begin
          dp   <= -1;
          skip <= 0;
        end
        else begin
          dp <= (ap < n) ? ap : -1;
          if (ap < n)
            ap <= ap + 1;
        end
      end
      else if (HRESP && cancel_on_error && ap < n && c_trans[ap] != NONSEQ) begin
        // First cycle of an ERROR: cancel the rest of this burst
        skip <= 1;
        k = ap;
        while (k < n && c_trans[k] != NONSEQ)
          k = k + 1;
        ap <= k;
      end
    end
  end

  ahb_mem #(
    .WAIT_NONSEQ (0),
    .WAIT_SEQ    (0),
    .WAIT_WRITES (1)
  ) mem (
    .HCLK      (HCLK),
    .HRESETn   (HRESETn),
    .HSEL      (1'b1),
    .HADDR     (HADDR),
    .HTRANS    (HTRANS),
    .HWRITE    (HWRITE),
    .HSIZE     (HSIZE),
    .HWDATA    (HWDATA),
    .HREADY    (HREADY),
    .HREADYOUT (HREADY),
    .HRESP     (HRESP),
    .HRDATA    (HRDATA)
  );

  always #5 HCLK = ~HCLK;

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);

    // A single write and a single read, separated by IDLE cycles
    burst(1, SINGLE, 32'h010, WORD, 1, 32'hA1);
    add(IDLE, 0, 32'h010, WORD, SINGLE, 0);
    burst(0, SINGLE, 32'h010, WORD, 1, 0);
    add(IDLE, 0, 32'h010, WORD, SINGLE, 0);
    // Back to back: the address of each transfer overlaps the data of the previous one
    burst(1, SINGLE, 32'h020, WORD, 1, 32'hB1);
    burst(1, SINGLE, 32'h024, WORD, 1, 32'hB2);
    burst(0, SINGLE, 32'h020, WORD, 1, 0);
    burst(0, SINGLE, 32'h024, WORD, 1, 0);
    #22 HRESETn = 1;
    wait (ap == n && dp == -1);
    #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.