Topic 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

Where it shows up

A production verification file at Apple or Qualcomm has hundreds of test scenarios, and the top of the file reads like prose: send_packet(BAD_CRC); send_packet(GOOD_CRC); send_packet(TRUNCATED);. You see what the test is doing at a glance. You don't see the setup boilerplate — it's been factored out, once, into a named procedure the rest of the file calls.

When it goes wrong

Knight Capital lost $440M in 45 minutes in August 2012 because the same chunk of setup code lived in eight copies on eight servers, and seven were updated. Anywhere “I'll copy-paste this and tweak it” appears in test infrastructure, the copies drift over time, and the bug hides between the versions that almost agree. The eye reads them as identical. The CPU does not.

⚠️ 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 × 20 cases = 100 lines of near-identical code. Adding a new check means editing all 20. Block diagram on the next slide shows the duplication structure.

🧱 Before — Stamped-Out Block Diagram

Copy-paste stimulus: same five-line block repeated 20 times
Stamped-out: the same five-line block is duplicated N times inside a single initial. The same signals (a, b, sum) and the same counters (tests_run, tests_failed) appear in every copy — if you change the check, you change it 20 times.

👁️ 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 test_add; one-line call per case. Each call passes three colored values (a_in, b_in, expected). Diagram next.

🧱 After — One Block, Many Callers

task test_add: one canonical block, many one-line calls feeding it
Reading the diagram: the test plan on the left fires three colored arrows (a_in, b_in, expected) into the single task body on the right. Stimulus + check + label are centralized — one edit, every case benefits.

🤝 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
apply_and_check task — clock-aligned drive + sample + check
Together: clock synchronization, input driving, output checking — three concerns in one task. The diagram shows the five timed steps (@posedge → drive → @posedge → sample → check). Edge alignment lives in one place.

🧪 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
Exercise DUT: 8-bit enable flop with reset

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 lecture_examples/week2_day06/d06_s1_ex1/
wc -l tb_before.v tb_after.v
diff tb_before.v tb_after.v | head -40
make sim TB=tb_before.v   # 86-line monolith
make sim TB=tb_after.v    # 54-line task-based — same result

▸ EXPECTED STDOUT

 86 tb_before.v
 54 tb_after.v

PASS: 0+0
PASS: 1+2
PASS: 5+7
...
=== 12 passed, 0 failed ===

▸ KEY OBSERVATION

Same output. ~37% fewer lines of code (and the savings grow linearly with each new test case). More importantly: if a new check is needed, it's one edit in the task, not 12 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.