Part 6 · Testbench Architecture · Intermediate
Self-Checking Discipline
PASS/FAIL from the test's own checks, error counting, max-error abort, exit codes, and banner conventions.
No waveform-eyeballing sign-off
The rule is absolute: a test passes only if its own checkers say so . Waveform inspection is for debugging a failure, never for declaring a pass. If a human has to look at anything to know the result, the test cannot run in a 500-run regression — and a check that lives only in someone's eyes disappears the day they leave the project.
The mechanism is simple: the scoreboard and checkers increment a shared error_count; at end of test, the environment prints a single-line verdict banner and calls $finish (or $fatal for a non-zero exit code). Automation greps the banner; the simulator's exit code backs it up.
THE SELF-CHECKING CONTRACT
checkers / scoreboard
│ on mismatch: error_count++
▼
┌─────────────────────────────┐
│ error_count == 0 ? │
│ YES → "*** TEST PASSED ***" exit code 0
│ NO → "*** TEST FAILED ***" exit code 1 ($fatal)
└─────────────────────────────┘
│
▼
regression script: grep banner + check exit code
(both must agree — belt and suspenders)Error counting and max-error abort
A central error counter lives in one place (a status singleton or the env). Every checker reports through it. A max-error threshold aborts the run early: after the first few mismatches, the remaining 10,000 transactions usually produce noise, not information, and burn license hours.
class tb_status;
static int error_count = 0;
static int max_errors = 10;
static function void report_error(string who, string msg);
error_count++;
$display("[%0t] ERROR (%s): %s", $time, who, msg);
if (error_count >= max_errors) begin
$display("[%0t] FATAL: max errors (%0d) reached, aborting",
$time, max_errors);
print_verdict();
$fatal(1);
end
endfunction
static function void print_verdict();
$display("==================================================");
if (error_count == 0)
$display("*** TEST PASSED ***");
else
$display("*** TEST FAILED *** (%0d errors)", error_count);
$display("==================================================");
endfunction
endclass// Scoreboard reports through the central counter
class scoreboard;
task check(txn exp, txn act);
if (!exp.compare(act))
tb_status::report_error("SCB",
$sformatf("mismatch addr=0x%0h exp=0x%0h act=0x%0h",
exp.addr, exp.data, act.data));
endtask
endclass
// End-of-test in the environment
task env::run_test();
gen.start();
wait_for_done(); // objection/event based drain
scb.report_final(); // flush, check leftovers
tb_status::print_verdict();
if (tb_status::error_count != 0) $fatal(1); // non-zero exit code
$finish;
endtaskBanner conventions and exit codes
Why the banner format matters
One fixed string per verdict — "*** TEST PASSED ***" — so regression scripts grep one pattern, not five variants.
Print the verdict exactly once, at the very end — multiple banners (one per checker) make scripts mis-classify runs.
Include the error count in the FAIL banner — the triage script can sort by severity without parsing the whole log.
Back the banner with an exit code: $fatal(1) on fail, plain $finish on pass — catches truncated logs and sim crashes.
The exit code is the safety net. If the simulator crashes or the log is cut off, there is no PASS banner — but a script that only greps for FAILED would call that run a pass. The robust rule: a run passes only if the PASS banner is present AND the exit code is zero . Everything else is a failure or an infrastructure error.
Interview angle
"How do you know your test passed?" — verdict from the TB's own checkers, single banner, exit code; never waveforms.
"Why abort on max errors?" — after the first real failure, later errors are usually cascade noise; save the license hours.
"What if the sim crashes mid-run?" — absence of PASS banner + non-zero exit code means fail; default to fail, never to pass.
Key takeaways
PASS/FAIL comes from the test's own checks — waveforms are for debug, not sign-off.
One central error counter, one final banner, one exit code — all three must agree.
Max-error abort saves regression time; cascading errors after the first are noise.
Default to FAIL: a missing PASS banner is a failure, not an unknown.
Common pitfalls
Grep-for-ERROR-only scripts — a crashed sim with an empty log counts as a pass.
Checkers that $display mismatches but never increment the shared error counter.
Multiple or inconsistent banner strings — regression scripts mis-bucket results.
$finish on failure instead of $fatal(1) — exit code 0 hides the failure from make/CI.