Day 6 · Verification Methodology

Testbench Anatomy

Video 1 of 4 · ~12 minutes

Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF

AnatomySelf-CheckingTasksFile-Driven

🌍 Where This Lives

In Industry

At Intel, Apple, NVIDIA, and every serious ASIC company: verification engineering is larger than design engineering, typically 2:1 in headcount. Tapeouts are killed or shipped based on coverage metrics. “Designed it” is 20% of a working chip; “proved it works” is the other 80%. Senior verification engineers routinely out-earn senior designers.

In This Course

Every make sim you've run used a testbench someone wrote. Today you start writing them yourself. Day 6 videos 2-4 add self-checking, tasks for organization, and file-driven test vectors. By Day 14 you'll write assertions. You become the verification engineer.

Career tag: “Can you write a testbench?” is table stakes for any RTL position. Writing good testbenches is what distinguishes senior from junior.

⚠️ A Testbench Is a Module. Just a Weird One.

❌ Wrong Model

“A testbench is a different kind of thing — some special file with $display and timing. Different syntax than my design.”

✓ Right Model

A testbench is just a module with no ports. It instantiates your Design Under Test (DUT) as a sub-module, drives the DUT's inputs, reads the DUT's outputs, and compares them to expected values. Same Verilog language you've been writing all week — plus a handful of simulation-only constructs (initial, $display, #delay) that tools like Yosys ignore.

The receipt: A testbench file typically starts module tb_foo; — no parentheses, no port list. It lives in the top of your simulation hierarchy, orchestrating the test from outside.

👁️ I Do — The Testbench Template

`timescale 1ns/1ps         // simulation resolution
module tb_adder;            // no ports — top of sim hierarchy

    // 1. Declare DUT signals
    reg  [3:0] a, b;        // inputs to DUT → reg (driven by tb)
    wire [4:0] sum;         // outputs from DUT → wire (read by tb)

    // 2. Instantiate the DUT
    adder dut (.i_a(a), .i_b(b), .o_sum(sum));

    // 3. Clock generator (if sequential) — always block with period
    reg clk = 0;
    always #5 clk = ~clk;   // 10 ns period = 100 MHz

    // 4. Waveform dump (for GTKWave)
    initial begin
        $dumpfile("tb_adder.vcd");
        $dumpvars(0, tb_adder);
    end

    // 5. Stimulus and checks
    initial begin
        a = 0; b = 0;
        #10; a = 4'd5; b = 4'd3;
        #10; $display("5+3 = %d", sum);
        #10; $finish;
    end
endmodule
Five standard parts: (1) signal declarations, (2) DUT instantiation, (3) clock generator, (4) waveform dump, (5) stimulus/checks. Every testbench you ever write has these five parts.

🤝 We Do — The reg/wire Rule

In a testbench, why must DUT inputs be declared as reg in the testbench?

Answer: The testbench drives those signals procedurally (inside initial). Day 2 rule: anything assigned in an always/initial block must be declared reg. DUT outputs are driven by the DUT, so they must be wire on the testbench side.
Common bug: Declaring a DUT input as wire in the testbench. The simulator will refuse to let you assign to it. Easy fix, confusing error message.

🧪 You Do — Predict the Simulation

At what simulation time does the third $display fire?

initial begin
    #5   $display("first at t=%0t", $time);
    #10  $display("second at t=%0t", $time);
    #20  $display("third at t=%0t", $time);
    $finish;
end
Answer: t=35. Delays are cumulative. #5 at start → t=5 → first display. #10 → t=15 → second. #20 → t=35 → third. Then $finish.
▶ LIVE DEMO

Build a Testbench from Scratch

~5 minutes

▸ COMMANDS

cd labs/week2_day06/ex1_first_tb/
# Start from empty tb_adder.v
# Build: decls → instantiate → clock → dump → stim
iverilog -g2012 -o sim.vvp tb_adder.v adder.v
vvp sim.vvp
gtkwave tb_adder.vcd &

▸ EXPECTED STDOUT

VCD info: dumpfile
  tb_adder.vcd opened
5+3 = 8
7+9 = 16
15+1 = 16
tb_adder.v:25: $finish called

▸ GTKWAVE

Signals a · b · sum. Watch the values change at the delays you scheduled. If you forgot $dumpvars your waveform will be empty — the most common testbench failure mode.

🔧 What Did the Tool Build?

Testbenches don't synthesize — they're simulation-only:

$ yosys -p "read_verilog tb_adder.v; synth_ice40 -top tb_adder" -q
ERROR: Module `\tb_adder' is not synthesizable.
  - contains initial block (simulation only)
  - contains $display (simulation only)
  - contains timing control (#10) (simulation only)
Expected. Testbenches use initial, $display, and #delay — none of which synthesize. That's the point. The testbench orchestrates the simulation; it's not meant to become hardware.
Checkpoint: Yosys being this specific about what's unsynthesizable is useful — if your DUT contains any of these constructs, you have a bug.

🤖 Check the Machine

Ask AI: “Here's my 4-bit counter. Write a self-contained testbench that exercises reset, counts to 15, and verifies rollover.”

TASK

Ask AI for a full testbench for your counter.

BEFORE

Predict: 5-part structure with reset + 16 clock cycles + rollover check.

AFTER

Good AI: complete 5 parts. Weak AI misses $dumpfile or uses wrong reg/wire.

TAKEAWAY

AI is actually good at testbench boilerplate. Verify the dumpvars line exists.

Key Takeaways

 A testbench is just a Verilog module with no ports.

 Five parts: decls, DUT instance, clock, dumpfile, stimulus.

 DUT inputs → reg in tb. DUT outputs → wire in tb.

 Testbenches don't synthesize — that's the whole point.

Verification is bigger than design. Today you start becoming that engineer.

🔗 Transfer

Self-Checking Testbenches

Video 2 of 4 · ~12 minutes

▸ WHY THIS MATTERS NEXT

Printing values and eyeballing waveforms doesn't scale. Video 2 introduces self-checking testbenches — the PASS/FAIL pattern you've been reading all week, from the inside. By the end, every testbench you write will check itself and tell you exactly what broke.