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

APB Wait States and Error Responses

Verilog Bus Protocols

An APB3 completer with a slow buffer (two wait states), a read-only ID register and PSLVERR for illegal accesses.

Open in IDE
design.v
// 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 APB requester with write and read tasks
module tb;
  reg         PCLK    = 0;
  reg         PRESETn = 0;
  reg         PSEL    = 0;
  reg         PENABLE = 0;
  reg         PWRITE  = 0;
  reg  [11:0] PADDR   = 0;
  reg  [31:0] PWDATA  = 0;
  wire [31:0] PRDATA;
  wire        PREADY;
  wire        PSLVERR;
  reg  [31:0] rdata;

  apb_wait dut (
    .PCLK    (PCLK),
    .PRESETn (PRESETn),
    .PSEL    (PSEL),
    .PENABLE (PENABLE),
    .PWRITE  (PWRITE),
    .PADDR   (PADDR),
    .PWDATA  (PWDATA),
    .PRDATA  (PRDATA),
    .PREADY  (PREADY),
    .PSLVERR (PSLVERR)
  );

  always #5 PCLK = ~PCLK;

  // Setup phase: PSEL high, PENABLE low. Access phase: PENABLE high until PREADY.
  task apb_write(input [11:0] addr, input [31:0] data);
    begin
      @(posedge PCLK);
      PSEL    <= 1;
      PENABLE <= 0;
      PWRITE  <= 1;
      PADDR   <= addr;
      PWDATA  <= data;
      @(posedge PCLK);
      PENABLE <= 1;
      @(posedge PCLK);
      while (!PREADY)
        @(posedge PCLK);
      $display("%0t  write 0x%h = 0x%h%s", $time, addr, data, PSLVERR ? "  PSLVERR" : "");
      PSEL    <= 0;
      PENABLE <= 0;
    end
  endtask

  task apb_read(input [11:0] addr, output [31:0] data);
    begin
      @(posedge PCLK);
      PSEL    <= 1;
      PENABLE <= 0;
      PWRITE  <= 0;
      PADDR   <= addr;
      @(posedge PCLK);
      PENABLE <= 1;
      @(posedge PCLK);
      while (!PREADY)
        @(posedge PCLK);
      data = PRDATA;
      $display("%0t  read  0x%h = 0x%h%s", $time, addr, data, PSLVERR ? "  PSLVERR" : "");
      PSEL    <= 0;
      PENABLE <= 0;
    end
  endtask

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);
    #22 PRESETn = 1;
    apb_write(12'h000, 32'h0000_0003);   // CTRL: no wait states
    apb_write(12'h014, 32'hBEEF_0014);   // BUF[1]: two wait states
    apb_read (12'h014, rdata);
    apb_read (12'h020, rdata);           // ID register
    apb_write(12'h020, 32'hFFFF_FFFF);   // write to a read-only register: error
    apb_read (12'h100, rdata);           // unmapped: error
    #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.