Module 6: Verilog for Testbench & Simulation ยท 6.4 Writing Self-Checking Tests

Why Checking Has to Be Automatic

Sections 6.1 to 6.3 built the infrastructure. This section is about the checker, and it starts with why there has to be one. A 16-bit adder has four billion input pairs. At one second per waveform, full coverage takes 136 years. The numbers are not there to discourage; they are there to make one thing plain: for anything past a toy, checking by eye is not slow, it is impossible.

The input space

Input width Combinations At ten a second, by hand
8 bits 256 26 seconds
12 bits 4096 7 minutes
16 bits 65 536 under 2 hours
24 bits 16 million 19 days
32 bits 4 billion 13 years

A simulator with a self-checking testbench does the 16-bit case in seconds and the 32-bit case in minutes. It is not cleverer than you; it does not tire and it does not skip. And where exhaustive testing is out of reach, the discipline still holds: thousands of well-chosen inputs checked by code beat a dozen looked at by eye.

Regression

You have a working ALU. You fix a bug in the shifter, re-run your tests, all pass. Two weeks later somebody finds the adder is broken, because the shifter fix shared a resource with the carry chain, and you did not re-test the adder. That is a regression: a change that fixes one thing and breaks another unnoticed. Self-checking testbenches make it practical to re-run everything on every change, in seconds, so that the break is flagged on the commit that caused it rather than a fortnight later.

What a bug costs

Found in Relative cost
simulation 1
after synthesis 10
on an FPGA prototype 100
in silicon 1000 and up

The figures are approximate and the order of magnitude is real. Thorough simulation is not perfectionism; it is finding defects at the moment they cost the least, when the fix is a text edit.

By eye and by code

Two testbenches for one 8-bit adder. The first prints:

`timescale 1ns / 1ps

module tb_adder_manual();
    reg  [7:0] a, b;
    wire [8:0] sum;

    adder_8bit dut (.a(a), .b(b), .sum(sum));

    initial begin
        a = 8'd10;  b = 8'd20;  #10;
        $display("10 + 20 = %0d", sum);
        a = 8'd200; b = 8'd100; #10;
        $display("200 + 100 = %0d", sum);
        a = 8'd255; b = 8'd1;   #10;
        $display("255 + 1 = %0d", sum);
        $finish;
    end
endmodule

You read three numbers and think about each. Maybe you misread one. Maybe you check two and move on.

The second checks:

`timescale 1ns / 1ps

module tb_adder_selfcheck();
    reg  [7:0] a, b;
    wire [8:0] sum;
    integer    error_count, test_count;
    integer    i, j;

    adder_8bit dut (.a(a), .b(b), .sum(sum));

    initial begin
        error_count = 0;
        test_count  = 0;

        for (i = 0; i < 256; i = i + 1) begin
            for (j = 0; j < 256; j = j + 1) begin
                a = i[7:0];
                b = j[7:0];
                #10;
                test_count = test_count + 1;
                if (sum !== (i + j)) begin
                    $error("a=%0d b=%0d expected=%0d got=%0d", i, j, i + j, sum);
                    error_count = error_count + 1;
                end
            end
        end

        if (error_count == 0)
            $display("PASSED: all %0d tests.", test_count);
        else
            $display("FAILED: %0d of %0d tests.", error_count, test_count);
        $finish;
    end
endmodule

All 65 536 pairs, in a fraction of a second, with the failing inputs named. One line to read.

The mistake to avoid at this point is writing a self-checking testbench and then only checking the same three or four inputs you would have looked at anyway. The automation only pays when you use it to check far more than you could by hand.

A printing testbench needs a person in the loop to give a verdict, and the person can be tired, or hurried, or wrong. A self-checking one puts the judgement in code, where it is repeatable and reviewable and can run at midnight. From here on every testbench in the course checks itself, and the next article is about the tools Verilog gives you to report what it finds.

โ† Two Clocks $error, $fatal and Assertions โ†’