Day 6 · Verification Methodology

Tasks for Organization

Video 3 of 4 · ~9 minutes

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

AnatomySelf-CheckingTasksFile-Driven

🌍 Where This Lives

In Industry

Production testbenches at Apple, Qualcomm, or AMD contain hundreds of test cases. Every one is one-line call to a task with meaningful parameters. The top-level initial block reads like a test plan. When you look at a senior engineer's testbench, the test cases are scannable: you see the intent, not the boilerplate.

In This Course

Today's pattern applies to every testbench you write from here forward. Video 4 combines tasks with file-driven test vectors — the combination that enables the thousand-case testbenches in Day 11 UART validation.

⚠️ Tasks Are Not Functions — And That's Why They Matter Here

❌ Wrong Model

task and function are the same thing. I'll use whichever.”

✓ Right Model

A function returns a value in zero time — no delays, no clock waits. A task can consume simulation time: #delay, @(posedge clk), wait(cond). Testbenches need both stimulus (takes time) and checks (zero time), so tasks are the right tool for test cases that combine them.

The receipt: Try to call #10 inside a function — simulator rejects it. Try to drive stimulus with a function — same. Tasks are what the spec designed for this job.

👁️ I Do — Before: Copy-Paste Stimulus

initial begin
    // Test case 1: 5 + 3
    a = 4'd5; b = 4'd3;
    #10;
    if (sum !== 5'd8) begin tests_failed=tests_failed+1; $display("FAIL: 5+3"); end
    else $display("PASS: 5+3 = %d", sum);
    tests_run = tests_run + 1;

    // Test case 2: 7 + 9
    a = 4'd7; b = 4'd9;
    #10;
    if (sum !== 5'd16) begin tests_failed=tests_failed+1; $display("FAIL: 7+9"); end
    else $display("PASS: 7+9 = %d", sum);
    tests_run = tests_run + 1;

    // Test case 3: 15 + 15  ...
    // 20 more cases, 5 lines each = 100 lines of almost-identical code
end
The problem: 5 lines per test case × 20 cases = 100 lines of nearly-identical code. Adding a new check (e.g. timing) means editing all 20 cases. Any student who's written this once has felt the pain.

👁️ I Do — After: Stimulus-and-Check Task

task test_add;
    input [3:0] a_in, b_in;
    input [4:0] expected;
    begin
        a = a_in;
        b = b_in;
        #10;                  // wait for combinational settle
        check_eq(sum, expected, $sformatf("%d + %d", a_in, b_in));
    end
endtask

initial begin
    test_add(4'd5,  4'd3,  5'd8);
    test_add(4'd7,  4'd9,  5'd16);
    test_add(4'd15, 4'd15, 5'd30);
    test_add(4'd0,  4'd0,  5'd0);
    test_add(4'd8,  4'd8,  5'd16);
    $display("=== %0d passed, %0d failed ===", tests_run - tests_failed, tests_failed);
end
The fix: One task, one-line call per test case. The initial block now reads like a test plan — each case's intent visible at a glance.

🤝 We Do — Sequential Stimulus Task

// For a clocked DUT: drive inputs at posedge, sample after
task apply_and_check;
    input [7:0] d_in;
    input       en_in;
    input [7:0] q_expected;
    begin
        @(posedge clk);
        d  = d_in;
        en = en_in;
        @(posedge clk);        // wait for next edge — DUT captures
        #1;                     // slight delay to sample after edge
        check_eq(q, q_expected, $sformatf("d=%h en=%b", d_in, en_in));
    end
endtask
Together: The task handles clock synchronization, input driving, and output checking — three concerns in one reusable unit. Each test case is one line, and edge-alignment is centralized in one place where bugs can be found once and fixed everywhere.

🧪 You Do — Refactor This

initial begin
    rst = 1; #10; rst = 0;
    en = 1; d = 8'hAA; @(posedge clk); #1;
    if (q !== 8'hAA) $display("FAIL A"); else $display("PASS A");

    en = 1; d = 8'h55; @(posedge clk); #1;
    if (q !== 8'h55) $display("FAIL B"); else $display("PASS B");

    en = 0; d = 8'hFF; @(posedge clk); #1;
    if (q !== 8'h55) $display("FAIL C"); else $display("PASS C");   // hold
end

Define apply_and_check and rewrite the three cases as one-liners.

Sketch:
reset_dut();
apply_and_check(1, 8'hAA, 8'hAA);  // load
apply_and_check(1, 8'h55, 8'h55);  // update
apply_and_check(0, 8'hFF, 8'h55);  // en=0: hold
▶ LIVE DEMO

Refactor a Real Testbench

~5 minutes — before/after

▸ COMMANDS

cd labs/week2_day06/ex3_tasks/
wc -l tb_before.v tb_after.v
diff tb_before.v tb_after.v | head -40
make sim    # both produce identical results

▸ EXPECTED STDOUT

 120 tb_before.v
  45 tb_after.v

PASS: 5+3 = 8
PASS: 7+9 = 16
...
=== 20 passed, 0 failed ===

▸ KEY OBSERVATION

Same output. 63% fewer lines of code. More importantly: if a new check is needed, it's one edit in the task, not 20 edits across cases.

🤖 Check the Machine

Ask AI: “Refactor this 40-line testbench using a task so each test case becomes a single line. Also use $sformatf for the labels.”

TASK

Ask AI to refactor to task-based.

BEFORE

Predict: one task with inputs + one-line cases in initial.

AFTER

Strong AI uses $sformatf for labels. Weak AI concatenates strings manually.

TAKEAWAY

AI is reliable for refactoring; easy productivity boost for any test-heavy file.

Key Takeaways

 Tasks can consume time; functions cannot.

 Encapsulate stimulus + check in one task per pattern.

 One-line test cases make the initial block a test plan.

 Use $sformatf for dynamic test labels.

If you have copy-paste in your testbench, you have a task you haven't written yet.

🔗 Transfer

File-Driven Testing

Video 4 of 4 · ~10 minutes

▸ WHY THIS MATTERS NEXT

Tasks handle repetition of structure. But what if you have thousands of test vectors? Video 4 introduces $readmemh — loading test vectors from external files so your testbench can run 100,000 cases without a 100,000-line initial block.