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.

diagram
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.

systemverilog
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
systemverilog
// 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;
endtask

Banner 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.