Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
    • Testbench Anatomy & Layering
    • Stimulus: Generators, Transactions & Drivers
    • Monitors, Scoreboards & Reference Models
    • Self-Checking Tests & Regression
      • Self-Checking Discipline
      • Seeds & Reproducibility
      • Logging & Verbosity Strategy
      • The Regression Flow
    • From Hand-Built TB to UVM
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Self-Checking Tests & Regression
  1. Topics
  2. SystemVerilog
  3. Testbench Architecture in SystemVerilog
  4. Self-Checking Tests & Regression
  5. Self-Checking Discipline

Part 6 · Testbench Architecture · Intermediate

Self-Checking Discipline

One error counter, one verdict banner, and an exit code that automation can trust.

On this page

    The verdict contract

    A test passes only if its own checkers say so. Waveform inspection is for debugging a failure, never for declaring a pass.

    The scoreboard and checkers count problems in one shared counter. At end of test the environment prints one verdict banner and calls $finish, or $fatal for a non-zero exit.

    Banner

    Exit code

    Regression verdict

    PASS: present

    0

    Pass

    FAIL: present

    Non-zero

    Fail, with the first problem line as its signature

    No banner, or banner and code disagree

    Any

    Fail as infrastructure trouble, never as a pass

    The exit code is the safety net. A script that greps for failure text alone would call a crashed simulation with an empty log a pass.

    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.

    One subtlety: $fatal(1, msg) stops with finish number 1, not with exit code 1. The finish number is defined by IEEE 1800; the process exit status on $fatal is tool-defined. Icarus 13 and Verilator 5.048 exit with status 1, and commercial simulators also exit non-zero, which the regression scrapes alongside the banner.

    A minimal self-checking testbench

    The listing below is complete and runs under Icarus 13 and Verilator. An 8-bit adder is checked against a one-line reference model on 20 vectors.

    The pattern is the whole discipline: count problems in errors, print exactly one verdict line, then end with $finish on PASS or $fatal(1, "FAIL: ...") on FAIL, so the exit code follows the verdict.

    adder8.sv
    1
    2
    3
    4
    5
    6
    7
    module adder8 (
      input  logic [7:0] a, b,
      output logic [7:0] sum
    );
      timeunit 1ns; timeprecision 1ps;
      assign sum = a + b;
    endmodule
    

    Four directed vectors cover zero, wrap-around, and the carry into bit 7. Sixteen loop vectors cover the rest, so the PASS line quotes 20 checks.

    Count in one place, abort early

    Every checker reports through one counter, so logging a problem and failing the test are the same act. A checker that prints a mismatch but never increments the counter lets the banner lie.

    A max-error threshold aborts the run early. After the first few real problems the remaining transactions produce cascade noise, not information, and burn license hours.

    The class listing below is a class with no top module, so this course's checks do not run it. It shows the production shape of the same counter. Reporting could be a function: $display and $fatal take no time, so IEEE 1800 allows them in functions, and UVM's own die() is a function. A task is used here so the report can later wait, for example on a drain event, before the verdict.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // not-run: class only, no top module
    class tb_status;
      static int error_count = 0;
      static int max_errors = 10;
    
      static task report_error(string who, string msg);
        error_count++;
        $display("[%0t] LOG_ERROR (%s): %s", $time, who, msg);
        if (error_count >= max_errors) begin
          $display("[%0t] aborting after %0d recorded problems", $time, max_errors);
          print_verdict();
          $fatal(1, "max problems reached");
        end
      endtask
    
      static function void print_verdict();
        $display("==================================================");
        if (error_count == 0)
          $display("PASS: test matched its reference on every check");
        else
          $display("FAIL: test recorded problems; see the log above");
        $display("==================================================");
      endfunction
    endclass

    Parse runtime plusargs once into a config object and let every component read that object. $value$plusargs then appears in exactly one place, which the seeds lesson shows.

    Check yourself A checker prints a mismatch line but the run still banners PASS. What broke?

    The checker never incremented the shared counter. The banner follows the counter, so the verdict and the log disagree.

    Check yourself Could report_error in the class listing be a function instead of a task?

    Yes. $display and $fatal take no simulation time, and IEEE 1800 bars only time-consuming statements from functions. A task is a choice, not a rule: it leaves room to wait, for example on a drain event, before the verdict.

    Common mistakes

    Common pitfall

    Grep-for-text-only verdict scripts count a crashed simulation with an empty log as a pass. Require the PASS banner and exit code zero together, and treat a missing banner as infrastructure failure.

    Interview

    Likely questions: “How do you know your test passed?”, “Why abort on max errors?”, and “What if the simulator crashes mid-run?”. Each answer points at the counter, the single banner, and the default-to-fail rule.

    Check yourself The FAIL branch uses $fatal(1, msg). What does the 1 mean, and where does the exit code come from?

    The 1 is the finish number defined by IEEE 1800, not an exit code. The non-zero process exit is tool-defined; Icarus 13, Verilator 5.048 and commercial simulators all provide it, and the regression scrapes it.

    Check yourself Two checkers each print their own PASS banner at different times. Why does this hurt?

    The regression script must now grep several patterns and can mis-classify runs. Print the verdict exactly once, at the very end, with the problem count in the FAIL line.

    Previous

    179 / 259

    Next: Seeds & Reproducibility

    Up next

    Up nextSeeds & Reproducibility

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Logging & Verbosity Strategy
    • The Regression Flow