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-to-APB bridge

Verilog Bus Protocols

An AHB-Lite master reaches an APB completer with wait states and PSLVERR through a bridge.

Open in IDE
design.v
// An AHB-Lite to APB bridge. It is an AHB slave on one side and the APB requester on the other.
// Each AHB transfer becomes one APB transfer; the bridge holds HREADYOUT low until the APB
// transfer completes. PSLVERR becomes a two-cycle AHB ERROR response.
module ahb2apb (
  input             HCLK,
  input             HRESETn,
  // AHB-Lite slave side
  input             HSEL,
  input      [31:0] HADDR,
  input      [1:0]  HTRANS,
  input             HWRITE,
  input      [31:0] HWDATA,
  input             HREADY,
  output            HREADYOUT,
  output            HRESP,
  output     [31:0] HRDATA,
  // APB requester side
  output            PSEL,
  output            PENABLE,
  output reg        PWRITE,
  output reg [11:0] PADDR,
  output     [31:0] PWDATA,
  input      [31:0] PRDATA,
  input             PREADY,
  input             PSLVERR
);
  localparam IDLE = 2'd0, SETUP = 2'd1, ACCESS = 2'd2, ERR2 = 2'd3;

  reg [1:0] state;

  wire start = HREADY && HSEL && HTRANS[1];        // an AHB transfer for the bridge
  wire done  = (state == ACCESS) && PREADY;         // the APB transfer completes

  always @(posedge HCLK) begin
    if (!HRESETn) begin
      state <= IDLE;
    end
    else begin
      case (state)
        SETUP:
          state <= ACCESS;
        ACCESS:
          if (PREADY)
            state <= PSLVERR ? ERR2 : (start ? SETUP : IDLE);
        default:                                    // IDLE and ERR2
          state <= start ? SETUP : IDLE;
      endcase
      if (start) begin
        PADDR  <= HADDR[11:0];                      // capture the address phase
        PWRITE <= HWRITE;
      end
    end
  end

  // APB side: Setup and Access phases
  assign PSEL    = (state == SETUP) || (state == ACCESS);
  assign PENABLE = (state == ACCESS);
  assign PWDATA  = HWDATA;                          // the AHB master holds HWDATA while we wait

  // AHB side: wait until the APB transfer is done; PSLVERR gives a two-cycle ERROR
  assign HREADYOUT = (state == IDLE) || (state == ERR2) || (done && !PSLVERR);
  assign HRESP     = (done && PSLVERR) || (state == ERR2);
  assign HRDATA    = PRDATA;
endmodule

// The bridge with the APB completer from the APB chapter behind it
module ahb_apb_sys (
  input         HCLK,
  input         HRESETn,
  input  [31:0] HADDR,
  input  [1:0]  HTRANS,
  input         HWRITE,
  input  [2:0]  HSIZE,
  input  [31:0] HWDATA,
  output        HREADY,
  output        HRESP,
  output [31:0] HRDATA
);
  wire        psel;
  wire        penable;
  wire        pwrite;
  wire [11:0] paddr;
  wire [31:0] pwdata;
  wire [31:0] prdata;
  wire        pready;
  wire        pslverr;

  ahb2apb bridge (
    .HCLK      (HCLK),
    .HRESETn   (HRESETn),
    .HSEL      (1'b1),
    .HADDR     (HADDR),
    .HTRANS    (HTRANS),
    .HWRITE    (HWRITE),
    .HWDATA    (HWDATA),
    .HREADY    (HREADY),
    .HREADYOUT (HREADY),
    .HRESP     (HRESP),
    .HRDATA    (HRDATA),
    .PSEL      (psel),
    .PENABLE   (penable),
    .PWRITE    (pwrite),
    .PADDR     (paddr),
    .PWDATA    (pwdata),
    .PRDATA    (prdata),
    .PREADY    (pready),
    .PSLVERR   (pslverr)
  );

  apb_wait periph (
    .PCLK    (HCLK),
    .PRESETn (HRESETn),
    .PSEL    (psel),
    .PENABLE (penable),
    .PWRITE  (pwrite),
    .PADDR   (paddr),
    .PWDATA  (pwdata),
    .PRDATA  (prdata),
    .PREADY  (pready),
    .PSLVERR (pslverr)
  );
endmodule
// An APB3 completer with wait states and error responses.
//   0x000         CTRL    read/write, no wait states
//   0x010 - 0x01C BUF[4]  read/write, two wait states (a slow internal memory)
//   0x020         ID      read-only, reads 0x0A9B_0001; a write returns PSLVERR
//   anything else         unmapped: PSLVERR
module apb_wait (
  input             PCLK,
  input             PRESETn,
  input             PSEL,
  input             PENABLE,
  input             PWRITE,
  input      [11:0] PADDR,
  input      [31:0] PWDATA,
  output reg [31:0] PRDATA,
  output            PREADY,
  output            PSLVERR
);
  reg [31:0] ctrl;
  reg [31:0] buf_mem [0:3];
  reg [1:0]  wcnt;                       // wait-state counter for BUF accesses

  wire hit_ctrl = (PADDR == 12'h000);
  wire hit_buf  = (PADDR[11:4] == 8'h01);
  wire hit_id   = (PADDR == 12'h020);
  wire access   = PSEL && PENABLE;

  // BUF accesses wait two cycles in the Access phase, everything else is ready at once
  assign PREADY  = hit_buf ? (wcnt == 2'd2) : 1'b1;

  // errors: writes to the read-only ID register, and any unmapped address
  assign PSLVERR = access && PREADY && ((hit_id && PWRITE) || !(hit_ctrl || hit_buf || hit_id));

  always @(posedge PCLK) begin
    if (!PRESETn)
      wcnt <= 2'd0;
    else if (access && hit_buf && !PREADY)
      wcnt <= wcnt + 2'd1;
    else
      wcnt <= 2'd0;
  end

  always @(posedge PCLK) begin
    if (!PRESETn) begin
      ctrl <= 32'h0;
    end
    else if (access && PREADY && PWRITE) begin
      if (hit_ctrl)
        ctrl <= PWDATA;
      else if (hit_buf)
        buf_mem[PADDR[3:2]] <= PWDATA;
    end
  end

  always @(*) begin
    if (hit_ctrl)
      PRDATA = ctrl;
    else if (hit_buf)
      PRDATA = buf_mem[PADDR[3:2]];
    else if (hit_id)
      PRDATA = 32'h0A9B_0001;
    else
      PRDATA = 32'h0;
  end
endmodule
testbench.v
// Testbench: an AHB-Lite master reaches an APB peripheral through a bridge
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_apb_sys fabric (
    .HCLK    (HCLK),
    .HRESETn (HRESETn),
    .HADDR   (HADDR),
    .HTRANS  (HTRANS),
    .HWRITE  (HWRITE),
    .HSIZE   (HSIZE),
    .HWDATA  (HWDATA),
    .HREADY  (HREADY),
    .HRESP   (HRESP),
    .HRDATA  (HRDATA)
  );

  always #5 HCLK = ~HCLK;

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

    burst(1, SINGLE, 32'h000, WORD, 1, 32'h3);           // CTRL: no APB wait states
    burst(1, SINGLE, 32'h014, WORD, 1, 32'hBEEF0014);    // BUF[1]: two APB wait states
    burst(0, SINGLE, 32'h014, WORD, 1, 0);
    burst(0, SINGLE, 32'h020, WORD, 1, 0);               // ID register
    burst(1, SINGLE, 32'h020, WORD, 1, 32'hFFFFFFFF);    // read-only: PSLVERR
    burst(0, SINGLE, 32'h000, 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.