Module 6: Verilog for Testbench & Simulation · 6.1 Testbench Architecture

Guilty Until Proven Innocent

You have spent two weeks on a 500-line SPI controller: all four clock modes, a prescaler, full duplex, FIFOs both ways. Synthesis is clean, timing closes at 100 MHz, the bitfile loads, you attach a logic analyser and send a transfer. Nothing. MOSI stays low, the clock looks wrong, chip select never releases. Where is the bug? You have 500 lines and a bad oscilloscope trace and no way to connect them, and you could have found it in thirty seconds last Tuesday, with a testbench written before the design.

What the simulator does

Icarus is not a compiler in the software sense. It builds an event-driven engine. Every signal has a value; a change is an event; events are queued by time, and the simulator’s whole job is to work through the queue (Figure 1). a = 1; #10; a = 0; means set a now, put an event ten units ahead, and when it fires, set a again. The #10 is not a sleep; it is an instruction to the queue.

Event-driven simulation
Figure 1: Events queued by time, processed in order

When a clock toggles, that is an event; every block sensitive to it runs, non-blocking updates are scheduled for the end of the step, they land, signals change, more events are queued. There is no program counter. There is a queue, and the simulator jumps from event to event. That is also why the model predicts hardware: hardware is event-driven too.

Why hardware is not software

To check a Python function you call it, assert on the result, add a print anywhere, attach a debugger. None of that exists on an FPGA. The signals are wires and flip-flops at clock speed, there is no pause, no debugger, no hot reload, and the only observation points are the pins. And a wrong value on pin 12 is a symptom; the cause might be a flip-flop five stages deep behind a clock domain crossing, and finding it from the pin can take days. In simulation you can see every node and trace a wrong value back to its source in seconds. Companies have whole teams for post-silicon debug; every bug they find is a gap in someone’s testbench.

A first testbench

An AND gate, all four inputs, outputs printed:

// and_gate.v
module and_gate (
    input  wire a,
    input  wire b,
    output wire y
);
    assign y = a & b;
endmodule
`timescale 1ns / 1ps

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

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

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

        a = 0; b = 0; #10;
        $display("a=%b b=%b  y=%b  (expected 0)", a, b, y);

        a = 0; b = 1; #10;
        $display("a=%b b=%b  y=%b  (expected 0)", a, b, y);

        a = 1; b = 0; #10;
        $display("a=%b b=%b  y=%b  (expected 0)", a, b, y);

        a = 1; b = 1; #10;
        $display("a=%b b=%b  y=%b  (expected 1)", a, b, y);

        $display("done");
        $finish;
    end
endmodule
iverilog -o sim_and tb_and_gate.v and_gate.v
vvp sim_and

Four lines of output, the last with y=1. Things to notice: the testbench module has no ports, because it is the top of the hierarchy; the inputs to the design are reg in the testbench, because an initial block drives them, and the output is a wire; $dumpfile and $dumpvars record a waveform for GTKWave; $display prints and does not exist in the gate; $finish ends the run.

reg here does not mean a register. It means “driven by procedural code”, which every input to the design is, from the testbench’s side.

This testbench checks nothing. You read the numbers and decide. That is fine for four cases, and the fifth article of this section makes the testbench decide instead.

The assumption

In most software the default assumption is that the code is probably right; tests are a safety net added when something breaks. In RTL the default has to be the reverse. The design is wrong until you have shown otherwise, not from pessimism but because the cost of a bug that gets through is a re-spin or a recall, with no stack trace to help. The testbench is how you earn the right to trust the design. Every input combination you test is evidence; every one you skip is a hole. Verification engineers call the measure of this coverage, and whole methodologies exist to chase it. This module starts at the bottom of that.

The next article is the shape of a testbench: the four parts every good one has.

The Four Parts of a Testbench →