Part 4 · Assertions (SVA) · Intermediate

Assertion Control & System Functions

$assertoff/$asserton/$assertkill scoping, reset methodology, $assertcontrol, assertion coverage extraction, and global clocking.

Why you need to switch assertions off

During reset, signals are X, FSMs are mid-initialization, and protocols are deliberately violated. disable iff handles this per-property, but it must be written into every property and it cannot help with phases the property author never anticipated — error-injection tests, retention power-down, post-silicon pattern replay. The assertion control system tasks act from the outside: testbench code switches whole subtrees of assertions off and on at runtime, without touching the properties themselves.

systemverilog
// The three classic controls — all take (levels, list_of_scopes)
$assertoff (0, top.dut);        // stop NEW attempts in dut and below;
                                // attempts already running continue
$assertkill(0, top.dut);        // kill running attempts too — instant silence
$asserton  (0, top.dut);        // re-enable from here on

// levels argument:
//   0          = the named scope and EVERYTHING below it
//   1          = the named scope only
//   n          = n levels down from the named scope
// scope list can name modules, instances, or individual assertions:
$assertoff (0, top.dut.u_fifo.ap_no_overflow);   // one assertion

The reset/init methodology pattern

systemverilog
initial begin : assertion_reset_ctrl
    // Silence everything before reset is applied
    $assertkill(0, tb_top.dut);
    wait (tb_top.rst_n === 1'b1);
    repeat (2) @(posedge tb_top.clk);   // settle margin after deassertion
    $asserton(0, tb_top.dut);
    $display("[%0t] assertions enabled", $time);
end

// Error-injection test: silence ONLY the protocol checker being violated
task automatic inject_bad_parity();
  $assertoff(0, tb_top.dut.u_link.par_chk);
  drive_corrupt_frame();
  @(posedge clk);
  $asserton (0, tb_top.dut.u_link.par_chk);
endtask
diagram
ASSERTION CONTROL THROUGH A TEST — timeline

 time  ──────────────────────────────────────────────────────────►
        │ reset (X soup) │ settle │   main test    │ inject │ rest │
 rst_n  ____________________┌──────────────────────────────────────
 ctrl   $assertkill         $asserton          $assertoff  $asserton
        (whole DUT)         (whole DUT)        (par_chk)   (par_chk)

 attempts:
   off  ──── none started, running ones killed
   on        ████████████████████████████  (every edge, all props)
   selective off            par_chk only: ──────
                            all others:   ██████████████████████

 Why kill (not off) at time 0: attempts that started before reset
 could still be running and would fire on X garbage; kill removes them.

The difference between $assertoff and $assertkill matters exactly here: $assertoff stops new attempts but lets in-flight attempts finish — an attempt spawned one cycle before the off call can still fail three cycles later. $assertkill also terminates the in-flight threads. At time zero and around reset, kill is what you want.


$assertcontrol — the general mechanism

SystemVerilog 2012 generalized the three tasks into $assertcontrol, which adds control over pass/fail action blocks and over which directive types are affected. The classic tasks are now shorthands for specific control values.

systemverilog
// $assertcontrol(control_type [, assertion_types
//                [, directive_types [, levels [, scopes...]]]]);
$assertcontrol(4);              // Off    — same effect as $assertoff(0)
$assertcontrol(3);              // On     — same as $asserton(0)
$assertcontrol(5);              // Kill   — same as $assertkill(0)
$assertcontrol(8);              // PassOff: suppress PASS action blocks
$assertcontrol(10);             // FailOff: suppress FAIL action blocks
                                //          (attempts still counted!)

// Common use: keep checking but silence pass-action printing noise
initial $assertcontrol(8);      // PassOff globally
  • Control types you should recognize: 3=On, 4=Off, 5=Kill, 8=PassOn-off pair (8 PassOff/7 PassOn), 10 FailOff/9 FailOn — exact numbers are LRM table material; know On/Off/Kill and that action-block printing is separately controllable.

  • assertion_types argument selects concurrent vs simple/deferred immediate assertions — you can silence immediate assertions in imported VIP while keeping your concurrent ones live.

  • FailOff suppresses the action block, not the failure itself — tools still count it; useful for rate-limiting a known noisy failure during triage.

  • Vendor flags (e.g. +assert disable) do similar things per-tool; $assertcontrol is the portable, runtime, scope-aware way.


Assertion coverage extraction

Tools record, for every assertion: attempts started, real passes, vacuous passes , and failures — and for every cover property, hit counts. This is assertion-based coverage: it answers "did my checks actually exercise?" independently of functional covergroups. The LRM provides a conceptual API ($coverage_control, $coverage_get, $coverage_save) for querying and saving this data from testbench code, though in practice most teams use tool commands and merged coverage databases rather than the system tasks directly.

systemverilog
// Conceptual runtime query (tool support varies; know the idea):
initial begin
  // ... at end of test ...
  void'($coverage_save(`SV_COV_ASSERTION, "assert_cov", "run1.db"));
end

// What the report tells you per assertion:
//   ap_req_ack:  attempts 100000   passes 142   vacuous 99858   fails 0
//                                              ^^^^^^^^^^^^^
//                antecedent almost never true → add cover property:
cp_req_seen: cover property (@(posedge clk) $rose(req));

Global clocking and $global_clock

A global clocking @(posedge clk); endclocking declaration nominates one clock as the design's formal reference clock ; properties can then write @($global_clock) instead of naming a clock. This matters mainly in formal flows and for the LRM's *_gclk sampled-value functions ($rose_gclk, $past_gclk and friends). For simulation-focused work you only need to recognize the syntax and say what it is for: a single agreed time base that formal tools and clock-abstracted properties reference.

Interview angle

The high-yield question is the off/kill distinction: "You called $assertoff at reset but still got failures — why?" Answer: attempts already in flight keep running under $assertoff; $assertkill terminates them. Second probe: "disable iff vs $assertoff — when each?" — disable iff is per-property, declarative, reacts to a signal automatically, and is part of the spec; $assertoff is testbench policy, scope-wide, and handles cases the property author never knew about (error injection, power phases). Senior loop bonus: mention that FailOff suppresses action blocks while still counting failures — useful for noise triage without losing data.

Key takeaways

  • $assertoff stops new attempts; $assertkill also terminates in-flight ones — use kill around reset.

  • Levels argument: 0 = scope and all below; scopes can be modules, instances, or single assertions.

  • disable iff is per-property spec; $assertoff/$asserton is runtime testbench policy — both have a place.

  • $assertcontrol generalizes on/off/kill and separately controls pass/fail action-block execution.

  • Assertion coverage (attempts/passes/vacuous/fails per assertion) is the audit trail proving your checks ran.

Common pitfalls

  • Using $assertoff at time 0 and wondering why in-flight attempts still fail — that is what $assertkill is for.

  • Forgetting to call $asserton after reset — entire regressions pass with every assertion disabled.

  • Scoping $assertoff(0, top) for an error-injection test — silences the whole DUT instead of the one checker under test.

  • Treating FailOff as 'failures gone' — the action block is suppressed but failures are still counted in the database.

  • Confusing global clocking with default clocking — default is a per-scope convenience; global nominates the formal reference clock.