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

Self-Checking APB Testbench (find the bug)

Verilog Bus Protocols

A reference model, scoreboard and protocol checks find an address-decode bug in an APB register block.

Open in IDE
design.v
// The four-register APB completer, with a bug in its address decode.
// No wait states (PREADY tied high) and no errors (PSLVERR tied low).
module apb_regs (
  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] r [0:3];
  integer    i;

  wire [1:0] idx = PADDR[2:1];              // BUG: the word index is bits 3:2

  // A write takes effect in the Access phase: PSEL and PENABLE both high
  always @(posedge PCLK) begin
    if (!PRESETn) begin
      for (i = 0; i < 4; i = i + 1)
        r[i] <= 32'h0;
    end
    else if (PSEL && PENABLE && PWRITE) begin
      r[idx] <= PWDATA;
    end
  end

  // Read data is driven during the transfer
  always @(*) begin
    if (PSEL && !PWRITE)
      PRDATA = r[idx];
    else
      PRDATA = 32'h0;
  end

  assign PREADY  = 1'b1;
  assign PSLVERR = 1'b0;
endmodule
testbench.v
// Testbench: verify an APB completer with random transfers, a reference
// model (scoreboard) and protocol checks
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] model [0:3];               // what each register should hold
  reg  [31:0] rdata;
  reg  [31:0] wdata;
  reg  [11:0] addr;
  integer     i;
  integer     errors = 0;
  integer     reads  = 0;
  integer     seed   = 1;

  apb_regs 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;

  // ---- Protocol checks, sampled on every rising edge ----
  reg        prev_sel = 0;
  reg        prev_en  = 0;
  reg        prev_rdy = 0;
  reg [11:0] prev_addr;
  reg        prev_wr;

  always @(posedge PCLK) begin
    // An Access phase must follow a Setup phase: PENABLE rises only when PSEL was already high
    if (PENABLE && !prev_en && !prev_sel) begin
      $display("%0t  PROTOCOL: PENABLE without a Setup phase", $time);
      errors = errors + 1;
    end
    // PADDR and PWRITE stay stable from Setup until the transfer completes
    if (prev_sel && PSEL && !(prev_en && prev_rdy) && (PADDR != prev_addr || PWRITE != prev_wr)) begin
      $display("%0t  PROTOCOL: PADDR/PWRITE changed during a transfer", $time);
      errors = errors + 1;
    end
    prev_sel  <= PSEL;
    prev_en   <= PENABLE;
    prev_rdy  <= PREADY;
    prev_addr <= PADDR;
    prev_wr   <= PWRITE;
  end

  // ---- Requester tasks ----
  task apb_write(input [11:0] a, input [31:0] d);
    begin
      @(posedge PCLK);
      PSEL    <= 1;
      PENABLE <= 0;
      PWRITE  <= 1;
      PADDR   <= a;
      PWDATA  <= d;
      @(posedge PCLK);
      PENABLE <= 1;
      @(posedge PCLK);
      while (!PREADY)
        @(posedge PCLK);
      PSEL    <= 0;
      PENABLE <= 0;
      model[a[3:2]] = d;                 // update the reference model
    end
  endtask

  task apb_read(input [11:0] a, output [31:0] d);
    begin
      @(posedge PCLK);
      PSEL    <= 1;
      PENABLE <= 0;
      PWRITE  <= 0;
      PADDR   <= a;
      @(posedge PCLK);
      PENABLE <= 1;
      @(posedge PCLK);
      while (!PREADY)
        @(posedge PCLK);
      d = PRDATA;
      PSEL    <= 0;
      PENABLE <= 0;
      reads = reads + 1;
      // ---- Scoreboard: compare against the model ----
      if (d !== model[a[3:2]]) begin
        $display("%0t  MISMATCH read 0x%h: got 0x%h, expected 0x%h", $time, a, d, model[a[3:2]]);
        errors = errors + 1;
      end
    end
  endtask

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);
    for (i = 0; i < 4; i = i + 1)
      model[i] = 32'h0;                  // reset values
    #22 PRESETn = 1;

    // Directed: write every register, then read every register back
    for (i = 0; i < 4; i = i + 1)
      apb_write(i * 4, 32'hA000_0000 + i);
    for (i = 0; i < 4; i = i + 1)
      apb_read(i * 4, rdata);

    // Random: 40 transfers to random registers
    for (i = 0; i < 40; i = i + 1) begin
      addr  = {$random(seed)} % 4 * 4;
      wdata = $random(seed);
      if ($random(seed) & 1)
        apb_write(addr, wdata);
      else
        apb_read(addr, rdata);
    end

    if (errors == 0)
      $display("PASS: %0d reads checked, no errors", reads);
    else
      $display("FAIL: %0d errors in %0d reads", errors, reads);
    $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.