Module 6: Verilog for Testbench & Simulation ยท 6.1 Testbench Architecture

The Four Parts of a Testbench

Every useful testbench, from a fifty-line gate check to a verification environment for a whole chip, has the same four parts. Not because a committee said so, but because that is what falls out when you ask what a testbench has to do. Knowing the shape before writing saves you from the commonest failure: one enormous initial block mixing driving, reading and printing, which works once and can never be extended or handed on.

The parts

  1. The instance of the design under test, wired to local signals.
  2. The stimulus, which drives the inputs over time.
  3. The checker, which samples the outputs and compares them with what was expected.
  4. The report, which says at the end how many checks ran and how many failed.

Each has one job (Figure 1). The stimulus does not judge; the checker does not drive; the report only counts. That is what lets you change the stimulus without touching the checker and tighten the checker without touching the stimulus.

Four parts
Figure 1: Instance, stimulus, checker, report

No ports

module tb_adder_4bit();
endmodule

Empty parentheses. The testbench is the top of the hierarchy and nothing connects to it from above; it drives the design through internal signals. Adding ports out of habit compiles and does nothing useful.

reg and wire

Inputs to the design are driven by the testbench’s procedural code, so they are reg on the testbench side. Outputs are driven by the design, so they are wire (Figure 2). That rule never varies. The one wrinkle is a bidirectional port, which is a wire in the testbench driven through a tri-state, and none of the designs in this section have one.

reg and wire
Figure 2: Inputs are reg, outputs are wire, from the testbench's side

A skeleton

// adder_4bit.v
module adder_4bit (
    input  wire [3:0] a,
    input  wire [3:0] b,
    input  wire       cin,
    output wire [3:0] sum,
    output wire       cout
);
    assign {cout, sum} = a + b + cin;
endmodule
`timescale 1ns / 1ps

module tb_adder_4bit();

    // signals: inputs reg, outputs wire
    reg  [3:0] a_tb;
    reg  [3:0] b_tb;
    reg        cin_tb;
    wire [3:0] sum_tb;
    wire       cout_tb;

    // 1. the instance
    adder_4bit dut (
        .a   (a_tb),
        .b   (b_tb),
        .cin (cin_tb),
        .sum (sum_tb),
        .cout(cout_tb)
    );

    // 2. stimulus
    initial begin
        $dumpfile("tb_adder_4bit.vcd");
        $dumpvars(0, tb_adder_4bit);

        a_tb = 4'h0; b_tb = 4'h0; cin_tb = 1'b0; #10;
        a_tb = 4'h5; b_tb = 4'h3; cin_tb = 1'b0; #10;    // 8, no carry
        a_tb = 4'hF; b_tb = 4'h1; cin_tb = 1'b0; #10;    // 0, carry
        a_tb = 4'h7; b_tb = 4'h8; cin_tb = 1'b1; #10;    // 0, carry

        // 4. report
        $display("--- done ---");
        $finish;
    end

    // 3. observer; the checker comes later in the section
    always @(a_tb or b_tb or cin_tb) begin
        #1;
        $display("  %0d + %0d + %0d = %0d (cout=%b)", a_tb, b_tb, cin_tb, sum_tb, cout_tb);
    end

endmodule
iverilog -o sim_adder tb_adder_4bit.v adder_4bit.v
vvp sim_adder

Four lines of results, and you are still the checker. The fourth and fifth articles put the checker in the code. Even now, with the checker a stub, the four sections are in place, and adding to them later costs nothing. Note also the named port connections, .a(a_tb); a positional list silently miswires if the design’s port order ever changes, and a miswired test gives you false confidence, which is worse than no test.

The next article is about the stimulus: driving a combinational design is trivial, and driving a clocked one at the right moment relative to its clock is not.

โ† Guilty Until Proven Innocent Driving the Inputs โ†’