VLSI

Writing a Verilog Testbench

By CourseTron Team ·  2 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: A solid Verilog testbench instantiates the design under test, drives a clean clock and reset, applies directed or randomized stimulus, and uses a self-checking $display/$fatal scoreboard so the simulator itself tells you pass or fail — no eyeballing waveforms.

    Ask any verification engineer where the real hours of a chip project go and the answer is rarely "writing RTL." It is verification — typically 60–70% of the total effort on a modern SoC. For a student or a junior engineer, the single highest-leverage skill is being able to write a testbench that checks itself. This guide walks through every piece of a practical Verilog testbench, using a small design so the mechanics stay visible.

    What a testbench actually is

    A testbench is a Verilog module with no ports. It is never synthesized — it exists only inside the simulator (Synopsys VCS, Cadence Xcelium, or Siemens QuestaSim are the three industry standards). Its job is to surround the design under test (DUT) with the environment the DUT would see on silicon: clocks, resets, input traffic, and a judge that decides whether the outputs are correct.

    Anatomy of a self-checking testbench testbench (no ports) Clock + Reset (Steps 1-2) Stimulus Reference model DUT counter4 Checker / Scoreboard q !== expected clk, rst en q[3:0] same inputs expected value CourseTron · coursetron.com
    The four jobs of a testbench: generate clock/reset, apply stimulus, and compare DUT outputs against a reference.

    Our DUT for this article is a simple 4-bit synchronous counter with synchronous active-high reset and an enable:

    module counter4 (input clk, input rst, input en, output reg [3:0] q);
      always @(posedge clk)
        if (rst)      q <= 4'b0000;
        else if (en)  q <= q + 1'b1;
    endmodule

    Step 1 — Clock generation

    A free-running clock is the heartbeat. The cleanest idiom toggles a register every half-period. For a 100 MHz clock the period is 10 ns, so toggle every 5 ns:

    reg clk = 1'b0;
    always #5 clk = ~clk;   // 10 ns period = 100 MHz

    Always initialise clk to a known value (0), not x. An undriven or x clock makes every flip-flop evaluate on an unknown edge and poisons the whole simulation with x propagation.

    Step 2 — Reset driver

    Reset must be asserted long enough to be seen by at least one active clock edge, then de-asserted cleanly away from an edge to avoid timing races in the simulator's event queue:

    reg rst;
    initial begin
      rst = 1'b1;          // assert
      repeat (2) @(posedge clk);
      rst = 1'b0;          // release after 2 clocks
    end

    Using @(posedge clk) rather than a fixed #17 delay keeps the reset synchronous to the clock and portable if you later change the clock period.

    Step 3 — Instantiate the DUT

    Always connect by name, never by position. Positional connection is the classic source of silent bugs when a port list is reordered:

    wire [3:0] q;
    reg en;
    counter4 dut (.clk(clk), .rst(rst), .en(en), .q(q));
    counter4 as wired by the testbench counter4 instance: dut clk rst en q[3:0] .clk .rst .en rst = 1: q = 0000 rst = 0, en = 1: q + 1 rst = 0, en = 0: q holds CourseTron · coursetron.com
    The DUT has three inputs and one 4-bit output, connected by name; behaviour is synchronous on the rising clock edge.

    Step 4 — Stimulus generation

    There are two families. Directed stimulus hits specific scenarios you care about (reset mid-count, enable glitches, rollover from 15 to 0). Constrained-random stimulus throws many legal combinations at the DUT to find corners you did not imagine — the foundation of modern UVM flows. For a counter, directed is enough to start:

    initial begin
      en = 1'b0;
      @(negedge rst);        // wait until reset releases
      en = 1'b1;             // count freely
      repeat (20) @(posedge clk);
      en = 1'b0;             // freeze and check hold
      repeat (3) @(posedge clk);
      $finish;
    end

    Drive inputs on the negedge or use non-blocking assignments so stimulus changes do not collide with the DUT's own posedge sampling — a race the IEEE 1364 event scheduler does not resolve deterministically otherwise.

    Step 5 — Self-checking output comparison

    This is what separates a toy testbench from a real one. Instead of staring at a waveform, maintain a reference model (a golden expected value computed independently) and compare it to the DUT output every cycle. The simulator reports the verdict:

    reg [3:0] expected;
    always @(posedge clk) begin
      if (rst)       expected <= 4'b0;
      else if (en)   expected <= expected + 1'b1;
    end
    
    // Check one delta after the edge so both regs have settled
    always @(posedge clk) begin
      #1;
      if (q !== expected)
        $fatal(1, "MISMATCH @%0t: dut=%0d exp=%0d", $time, q, expected);
      else
        $display("OK @%0t: q=%0d", $time, q);
    end

    Use the case-equality operator !==, not !=. !== treats x and z as real mismatches, so an uninitialised DUT output cannot sneak past your checker as "equal."

    Counter advances on each rising clock edge clk q 0 1 2 3 4 5 6 Highlight = current q value; it steps forward at every rising edge (en = 1) CourseTron · coursetron.com
    Each rising clock edge increments the counter; the self-checker compares this value against the reference model every cycle.

    Directed vs constrained-random: when to use which

    AspectDirectedConstrained-random
    Effort per testHigh (one scenario each)Low (one constraint set, many runs)
    Corner coverageOnly what you anticipateFinds the unexpected
    Typical useSmoke tests, bug reproductionRegression, SoC-level
    ToolingPlain VerilogSystemVerilog + UVM

    Start directed to prove the DUT is alive, then layer randomisation with functional coverage once the basics pass. If you are moving on to sequential logic, the same checker pattern plugs directly into a finite state machine design, and the clock/reset scaffolding here is identical to what you will reuse in a full SystemVerilog UVM environment.

    Dumping waveforms (for when a check fails)

    A self-checking test tells you that something broke; waveforms tell you why. Add a dump block so every run produces a viewable database:

    initial begin
      $dumpfile("counter4.vcd");
      $dumpvars(0, tb_counter4);
    end

    Open the resulting VCD in GTKWave (open source) or Verdi (Synopsys) to trace the failing signal back in time from the $fatal timestamp.

    Want to build this hands-on? CourseTron's VLSI verification track walks you from this counter testbench all the way to constrained-random UVM, with lab machines and tool licences included. Explore the full course catalogue to get started, or see industry-grade design validation at Avecas design services.

    Frequently asked questions

    Why does my testbench show all x's at time 0?

    Almost always an uninitialised clock or a reset that never asserts. Give clk a starting value and assert rst in an initial block before the first edge. Unknown values propagate forward, so one x at the source turns the whole simulation red.

    Should I use blocking or non-blocking assignments in a testbench?

    Use non-blocking (<=) inside clocked always blocks that model sequential behaviour (like the reference model), and blocking (=) for procedural stimulus in initial blocks. Mixing them in the same clocked block is the fastest way to create a simulator race.

    How is a self-checking testbench different from just printing values?

    Printing requires a human to read every line and judge correctness. A self-checking testbench encodes the expected answer in code and calls $fatal or $error automatically, so it scales to thousands of nightly regression runs where nobody is watching.

    Do I need SystemVerilog, or is plain Verilog enough?

    Plain Verilog is enough to learn the mechanics and verify small blocks. For SoC-scale verification you will want SystemVerilog's randomisation, assertions (SVA), and the UVM methodology — but the four jobs in this article stay exactly the same.

    Advertisement