Video 3 of 4 · ~9 minutes
Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF
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.
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.
“task and function are the same thing. I'll use whichever.”
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.
#10 inside a function — simulator rejects it. Try to drive stimulus with a function — same. Tasks are what the spec designed for this job.
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
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.
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
// 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
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.
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
~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.
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.
① 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.
🔗 Transfer
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.