0
0
VerilogHow-ToBeginner · 4 min read

How to Write a Self Checking Testbench in Verilog

A self checking testbench in Verilog uses initial blocks to apply inputs and always blocks or if statements to compare outputs against expected values. It automatically reports mismatches using $display or $error, so you don't need to manually check waveforms.
📐

Syntax

A self checking testbench typically includes these parts:

  • reg and wire declarations for inputs and outputs.
  • initial block to apply stimulus (test inputs).
  • always block or initial block with if statements to check output correctness.
  • Use of $display or $error to report mismatches.
  • $finish to end simulation after tests.
verilog
module testbench();
  reg clk, reset;
  reg [3:0] in;
  wire [3:0] out;

  // Instantiate the design under test (DUT)
  my_module dut(.clk(clk), .reset(reset), .in(in), .out(out));

  initial begin
    // Apply inputs
    in = 4'b0000;
    reset = 1;
    #10 reset = 0;
    #10 in = 4'b0011;
    // ... more stimulus
  end

  always @(posedge clk) begin
    // Check outputs
    if (out !== expected_value) begin
      $display("Error: output mismatch at time %0t", $time);
    end
  end

  initial begin
    // Clock generation
    clk = 0;
    forever #5 clk = ~clk;
  end

  initial begin
    #100 $finish;
  end
endmodule
💻

Example

This example shows a self checking testbench for a simple 2-input AND gate module. It applies inputs, checks outputs, and reports errors automatically.

verilog
module and_gate(input a, input b, output y);
  assign y = a & b;
endmodule

module testbench();
  reg a, b;
  wire y;

  and_gate dut(.a(a), .b(b), .y(y));

  integer errors = 0;

  initial begin
    // Test all input combinations
    a = 0; b = 0; #10 check_output(0);
    a = 0; b = 1; #10 check_output(0);
    a = 1; b = 0; #10 check_output(0);
    a = 1; b = 1; #10 check_output(1);

    if (errors == 0) $display("All tests passed.");
    else $display("%0d errors found.", errors);
    $finish;
  end

  task check_output(input expected);
    if (y !== expected) begin
      $display("Error at time %0t: a=%b b=%b y=%b expected=%b", $time, a, b, y, expected);
      errors = errors + 1;
    end
  endtask
endmodule
Output
All tests passed.
⚠️

Common Pitfalls

  • Not using non-blocking assignments (<=) in sequential logic can cause timing issues.
  • Forgetting to check all input combinations leads to incomplete verification.
  • Using == instead of === can miss unknown (X) or high-impedance (Z) states.
  • Not ending simulation with $finish causes infinite runs.
  • Not reporting errors clearly makes debugging harder.
verilog
/* Wrong: Using == instead of === */
if (out == expected) begin
  // This may miss X or Z states
end

/* Right: Use === for exact comparison */
if (out === expected) begin
  // Correctly detects unknowns
end
📊

Quick Reference

Tips for writing self checking testbenches:

  • Use initial blocks to apply test inputs.
  • Use always or initial blocks with if statements to verify outputs.
  • Use $display or $error to report mismatches.
  • Use === for comparisons to catch unknowns.
  • End simulation with $finish.

Key Takeaways

Use automated checks with if statements and $display to catch errors without manual waveform inspection.
Compare outputs with === to detect unknown or invalid states.
Apply all relevant input combinations to fully verify the design.
End simulation cleanly with $finish to avoid infinite runs.
Report errors clearly to simplify debugging.