Loading header...

Simulation and Testbenches

Simulation is where you ask design questions before hardware makes them expensive. A testbench is not loaded into the FPGA; it is a verification environment that drives inputs, checks outputs, and leaves enough evidence to debug failures.

Learning Objectives

By the end of this lesson, you should be able to:

  • distinguish synthesizable RTL from simulation-only testbench code;
  • create a clock, reset sequence, stimulus, and checks;
  • use assertions or explicit comparisons to make tests self-checking;
  • generate waveforms for debugging rather than manual pass/fail judgment;
  • identify simulation habits that reduce hardware bring-up risk.

RTL vs Testbench Code

Design RTL Testbench
Synthesizes into FPGA logic Runs only in the simulator
Describes registers, combinational logic, memories, and interfaces Drives inputs and observes outputs
Must follow synthesis rules May use delays, file I/O, loops, tasks, procedures, and assertions
Must meet timing after place and route Must expose behavior clearly
Becomes hardware Becomes evidence

Treat the design as the device under test, often abbreviated DUT. The testbench should behave like a lab instrument: apply known conditions, measure the response, and report clear failures.

A Useful Testbench Structure

flowchart LR A["Clock and reset"] --> B["Stimulus"] B --> C["DUT"] C --> D["Self-checks"] C --> E["Waveform dump"] D --> F["PASS or FAIL"]

A waveform-only testbench is a viewing setup. A self-checking testbench is a verification tool.

Minimal Verilog Example

`timescale 1ns/1ps

module tb_counter8;
    reg clk = 1'b0;
    reg rst = 1'b1;
    reg en  = 1'b0;
    wire [7:0] count;

    counter8 dut (
        .clk(clk),
        .rst(rst),
        .en(en),
        .count(count)
    );

    always #5 clk = ~clk;  // 100 MHz illustrative clock

    initial begin
        $dumpfile("counter8.vcd");
        $dumpvars(0, tb_counter8);

        repeat (2) @(posedge clk);
        rst <= 1'b0;
        en  <= 1'b1;

        repeat (4) @(posedge clk);
        #1;
        if (count !== 8'd4) begin
            $display("FAIL: expected count 4, got %0d", count);
            $finish;
        end

        en <= 1'b0;
        repeat (2) @(posedge clk);
        #1;
        if (count !== 8'd4) begin
            $display("FAIL: enable hold broken");
            $finish;
        end

        $display("PASS: counter8");
        $finish;
    end
endmodule

The #1 after the clock edge lets nonblocking assignments update before the check. In a SystemVerilog environment you may use clocking blocks or structured sampling, but the beginner rule is simple: do not drive and check everything at the exact same simulator instant.

What Good Tests Check

Good simulations check requirements, not just activity:

  • reset state and reset release;
  • first valid output after enable;
  • normal transactions;
  • boundary values, overflow, and wrap;
  • hold or stall behavior;
  • invalid inputs and unused states;
  • back-to-back operations;
  • recovery after reset or error.

The best first tests are small and deterministic. Random tests are useful later, but only when you already know how to reproduce and debug a failure.

Worked Example: Reset Requirement

Requirement: while reset is active at a rising clock edge, count must become zero.

Test plan:

  1. Let the counter reach a nonzero value.
  2. Assert reset.
  3. Wait for the active clock edge.
  4. Check count == 0.
  5. Release reset and check that counting resumes.

This catches missing reset wiring, inverted reset polarity, incomplete reset assignments, and testbenches that never actually exercise reset.

Waveforms Help Explain Timing

title "Illustrative clocked simulation"
time start=0 end=80 unit=ns divisions=8

CLK: square label="clk" low=0 high=1 duty=50 cycles=8 unit=logic color=#2563eb
RST: pulse label="rst" low=0 high=1 at=0 width=20 unit=logic color=#dc2626
EN: step label="en" low=0 high=1 at=25 unit=logic color=#7c3aed
COUNT: sawtooth label="count rises" min=0 max=5 cycles=1 unit=count color=#16a34a

marker RELEASE at=20 label="reset off"
marker CHECK at=70 label="check"

This is an explanatory waveform, not a simulator result. A real VCD should reflect the exact RTL, simulator scheduling, and testbench stimulus.

Verification Workflow

For each block:

  1. Simulate reset and a simple valid transaction.
  2. Add edge cases.
  3. Make every expected behavior a check.
  4. Dump a waveform only when debugging or documenting.
  5. Keep the test command repeatable.
  6. Run the simulation before synthesis after every meaningful RTL change.

Simulation does not prove timing closure, pin constraints, electrical safety, or board wiring. It proves logic behavior under the modeled conditions.

Common Failure Symptoms

Symptom Likely cause
Output is X or U Missing reset, uninitialized signal, or multiple drivers
Test passes without checking anything Waveform-only testbench
One-cycle mismatch Checking before nonblocking or signal updates settle
Simulator never exits Missing $finish, std.env.stop, or final wait strategy
Failure disappears in waveform Race caused by driving inputs on the active edge
Hardware still fails Timing, constraints, CDC, reset, or board issue not modeled

Debugging Guidance

Read the first failure message first. Then inspect the smallest waveform window that includes the previous reset, the stimulus change, the active clock edge, and the failing output. Avoid changing both RTL and testbench at once; if you do, you will not know which change fixed the symptom.

When a design has many states, add checks for legal state transitions and timeouts. A timeout is important because a deadlocked FSM may otherwise leave the simulator running forever.

Common Mistakes

  • Treating "no simulator crash" as success.
  • Looking at a waveform once and never automating the check.
  • Forgetting reset behavior.
  • Driving inputs exactly on the active clock edge and creating artificial races.
  • Testing only the happy path.
  • Assuming simulation checks timing constraints or metastability.

Summary

A good testbench is a repeatable experiment. It creates clock and reset conditions, applies meaningful stimulus, checks expected behavior automatically, and uses waveforms to debug failures. Hardware testing is still required, but it should not be the first time your design is exercised.

Next: Verilog Simulation: Verilator, Icarus Verilog, and GTKWave.

Further Reading

Mind Map

mindmap root((Simulation)) Core Testbench drives DUT Checks decide pass Waves debug timing RTL stays synth Inputs clock reset stimulus corner cases Checks reset value first output wrap limits illegal states Tools Verilator Icarus GHDL GTKWave Practical repeatable command first failure small waveform window CI regression Mistakes waveform only race at edge no finish timing assumed