SystemVerilog · Module 17
Checkers
SystemVerilog checker construct — the dedicated container for reusable assertion and coverage decks. What lives inside a checker, automatic per-instance variables, default clocking and default disable iff, free variables for formal verification, instantiation inside a module / interface / via bind, a complete AHB-Lite protocol checker, and checker vs module.
Module 17 · Page 17.3
A checker is a dedicated SystemVerilog construct (checker … endchecker, IEEE 1800-2017 §17) that packages assertions, sequences, properties, coverage, and helper logic into one named, reusable unit. It looks almost like a module, but it is built for verification: its variables are automatic per-instance, it supports default clocking and default disable iff so you write the clock and reset condition once, and it supports free variables for formal verification. You instantiate it like a module — inside a design block, inside an interface, or non-invasively via bind (the previous lesson). This page covers what a checker may contain, the declaration and instantiation forms, a complete AHB-Lite protocol checker, free variables, and exactly when to reach for checker instead of module.
1. Engineering Problem — Assertions Need a Home
Once a block has more than a handful of assertions, writing them loose inside the RTL (or scattered across the testbench) breaks down:
- Boilerplate repeats. Every
assert propertyneeds@(posedge clk)anddisable iff (!rst_n). Twenty properties means twenty copies of the same clock and reset text — and one forgottendisable ifffires spuriously during reset. - No reuse. The same protocol rules (AHB, APB, AXI) get re-typed for every block instead of dropped in as a unit.
- Namespace clashes. A
property p_validhere collides with aproperty p_validthere when they live in the same scope. - No per-instance isolation. A module-as-checker has static variables by default — instantiate it twice and a shared counter silently corrupts across instances.
- No formal story. Pure-simulation assertions don't give a formal tool the universally-quantified properties it needs to prove correctness for all values.
The checker construct solves all of these at once: one container, one default clocking, one default disable iff, scoped sequence/property names, automatic per-instance variables, and free variables for formal — instantiable anywhere, including via bind onto RTL you don't own.
2. Mental Model — A Named, Reusable Verification Container
The picture every engineer carries:
A checker is a module-shaped box that holds verification, not hardware. Swap
module/endmoduleforchecker/endcheckerand you get a unit whose ports are observe-only (input/output, neverinout), whose local variables are automatic (each instance independent), and which understandsdefault clockinganddefault disable iffso the clock and reset condition are declared once for every property inside. You instantiate it like a module — directly in a design block, inside an interface, or viabind— and it sees exactly the signals wired to its ports, nothing hierarchically above.
Four invariants this picture preserves:
- Verification-only. A checker holds assertions, coverage, and helper tracking logic — never synthesizable design logic. Synthesis ignores it.
- Automatic by default. Local variables are per-instance, so binding one checker to ten FIFOs gives ten independent counters — no shared static state.
- Write clock/reset once.
default clockinganddefault disable iffapply to every concurrent assertion in the body, eliminating the per-property boilerplate that causes reset-time false failures. randmeans free variable, not stimulus. Inside a checker,randdeclares a formal free variable (universally quantified by a formal tool) — fundamentally different fromrandin a class.
3. Visual Explanation — Where a Checker Attaches
One checker, three instantiation contexts. The same checker unit drops into a design block, lives inside an interface so every user inherits it, or attaches non-invasively through bind.
What a checker body may contain (IEEE 1800-2017 §17):
Ports (input / output only)
Like a module, but no inout/ref. Directions default to input. The checker observes exactly the signals wired to these ports.
Automatic local variables
Variables are per-instance by default — bind to ten blocks and each gets its own independent copy. No shared static state.
default clocking / disable iff
One default clocking @(posedge clk) and one default disable iff (!rst_n) cover every property inside — written once, not per assertion.
Sequences & properties
Named sequence and property declarations scoped to the checker — no namespace clash with other checkers' identically-named properties.
assert / cover / assume
assert property for simulation checks, cover property for coverage, assume property for formal input constraints.
Free variables (rand)
rand here is a formal free variable — a formal tool considers all values, enabling universally-quantified properties. Not class-style stimulus.
4. Declaring a checker
The syntax mirrors a module — swap module/endmodule for checker/endchecker.
checker my_checker (input logic clk, rst_n, valid, data);
// default clocking — every property below uses posedge clk unless overridden
default clocking cb @(posedge clk); endclocking
// default reset — every concurrent assertion disables during reset
default disable iff (!rst_n);
// local variable — automatic: each instance gets its own copy
int valid_count = 0;
// named sequence — scoped to this checker
sequence s_valid_data;
valid ##1 data;
endsequence
// named property
property p_data_follows_valid;
valid |-> ##1 !$isunknown(data);
endproperty
// labelled assertion — the label appears in logs / waveforms / formal reports
data_valid_chk: assert property (p_data_follows_valid)
else $error("data is X/Z one cycle after valid!");
// coverage
cov_valid_seen: cover property (s_valid_data);
// helper tracking logic — clocked here; bare `always` defaults to always_comb
always @(posedge clk)
if (valid) valid_count++;
endchecker // ← endchecker, not endmoduleReading it: checker declares the verification unit (the compiler applies checker rules — no inout, automatic variables); the port list is the only window into the instantiation context (a checker cannot reach parent signals hierarchically); default clocking and default disable iff apply body-wide; sequence/property names are scoped to the checker; and every assert/cover should carry a label so a failure names the rule, not just a file and line.
5. Instantiating a checker
A checker instantiates like a module — named, positional, or .* connections — and can live in a design block, an interface, or be attached via bind.
// ── Context 1 — directly inside a design block ──────────────────────
module ahb_master (input logic hclk, hresetn, hready, output logic [1:0] htrans);
// ... RTL ...
ahb_chk u_ahb_chk (.clk(hclk), .rst_n(hresetn), .hready(hready), .htrans(htrans));
// or, if checker port names match the signal names: ahb_chk u_ahb_chk (.*);
endmodule
// ── Context 2 — via bind (non-invasive, separate file) ──────────────
bind ahb_master ahb_chk u_ahb_chk (
.clk(hclk), .rst_n(hresetn), .hready(hready), .htrans(htrans)
);
// ── Context 3 — inside an interface (every user inherits the checker) ─
interface ahb_if (input logic hclk, hresetn);
logic [1:0] htrans;
logic hready;
ahb_chk u_chk (.clk(hclk), .rst_n(hresetn), .hready(hready), .htrans(htrans));
endinterfaceThe bind form (Context 2) is the production best practice for IP verification: a reusable checker unit attached without editing the RTL — the two Module 17 mechanisms compose directly.
6. Full Working Example — AHB-Lite Protocol Checker
A real protocol checker for the AHB-Lite bus, enforcing three rules. Note how the single default clocking and default disable iff keep every property clean.
checker ahb_lite_chk (
input logic hclk, // bus clock
input logic hresetn, // active-low reset
input logic [1:0] htrans, // transfer type: IDLE/BUSY/NONSEQ/SEQ
input logic [2:0] hburst, // burst type
input logic hready, // slave ready
input logic [31:0] haddr, // address
input logic hwrite // 1 = write, 0 = read
);
localparam logic [1:0] IDLE = 2'b00, BUSY = 2'b01, NONSEQ = 2'b10, SEQ = 2'b11;
default clocking ahb_cb @(posedge hclk); endclocking
default disable iff (!hresetn);
// Rule 1 — a NONSEQ held during a wait state must stay NONSEQ or become SEQ
property p_nonseq_hold;
(htrans == NONSEQ && !hready) |=> (htrans inside {NONSEQ, SEQ});
endproperty
nonseq_hold_chk: assert property (p_nonseq_hold)
else $error("AHB Protocol Violation: NONSEQ not held when hready=0");
// Rule 2 — BUSY is illegal immediately after IDLE (BUSY is a mid-burst state)
property p_no_busy_after_idle;
(htrans == IDLE) |=> (htrans != BUSY);
endproperty
busy_after_idle_chk: assert property (p_no_busy_after_idle)
else $error("AHB Protocol Violation: BUSY seen immediately after IDLE");
// Rule 3 — address must be stable while hready is de-asserted
property p_addr_stable;
(htrans != IDLE && !hready) |=> $stable(haddr);
endproperty
addr_stable_chk: assert property (p_addr_stable)
else $error("AHB Protocol Violation: haddr changed while hready=0");
// Coverage — was every transfer type exercised?
cov_idle: cover property (htrans == IDLE);
cov_nonseq: cover property (htrans == NONSEQ);
cov_seq: cover property (htrans == SEQ);
cov_busy: cover property (htrans == BUSY);
cov_write: cover property (htrans == NONSEQ && hwrite);
cov_read: cover property (htrans == NONSEQ && !hwrite);
endchecker// Attach it — one line, zero RTL impact (.* matches ports to signal names)
module ahb_master (
input logic hclk, hresetn, hready,
output logic [1:0] htrans,
output logic [2:0] hburst,
output logic [31:0] haddr,
output logic hwrite
);
// ... RTL logic ...
ahb_lite_chk u_ahb_chk (.*);
endmodule$ xrun ahb_master.sv ahb_lite_chk.sv tb_ahb.sv -sv
Elaboration complete. Running simulation...
ERROR: AHB Protocol Violation: NONSEQ not held when hready=0
File: ahb_lite_chk.sv Time: 520ns Assertion: nonseq_hold_chk
Instance: top.u_dut.u_ahb_chk
Coverage Report:
cov_idle — hit 42 times
cov_nonseq — hit 18 times
cov_seq — hit 7 times
cov_busy — NEVER HIT (0 times) ← coverage hole7. Free Variables — For Formal Verification
A checker-exclusive feature with no module equivalent. A free variable is a variable declared rand inside a checker. In simulation it takes a random value; in a formal tool it is universally quantified — the tool considers every possible value at once. That lets one property say "for any data value, if it is written it must eventually appear at the output," without enumerating values.
checker fifo_data_integrity (
input logic clk, rst_n,
input logic wr_en, rd_en,
input logic [7:0] din, dout,
input logic full, empty
);
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
// Free variable — formal considers ALL 8-bit values; sim picks one at random
rand logic [7:0] ref_data;
// If ref_data is written, it must eventually be read back out unchanged
property p_data_preserved;
(wr_en && !full && (din == ref_data))
|-> strong(##[1:$] (rd_en && !empty && (dout == ref_data)));
endproperty
data_preserved_chk: assert property (p_data_preserved)
else $error("FIFO data integrity violation: written data not seen at output");
endchecker8. checker vs module — Which to Use
Both can hold assertions; the checker construct is purpose-built for it.
| Feature | checker | module used as a checker |
|---|---|---|
| Keyword | checker / endchecker | module / endmodule |
| Local variables | automatic (per-instance) by default | static by default — shared unless declared automatic |
| Ports | input / output only | input / output / inout / ref |
default clocking | supported — covers all properties | not supported in a module body |
default disable iff | supported — one declaration covers all | must be written in every property |
Free variables (rand) | supported — formal universal quantification | not available |
| Synthesizable | no — verification only | yes (if no TB constructs inside) |
Use with bind / interface | yes / yes | yes / yes |
| Formal targeting | excellent — designed for it | workable but less clean |
9. Industry Usage — Where Checkers Show Up
- Reusable protocol-checker libraries — AHB/APB/AXI/SPI decks shipped as
checkerunits, bound onto every bus instance. - Formal sign-off — checkers with free variables and
assume propertyare the natural unit a formal tool targets for end-to-end proofs. - Interface-embedded checkers — a
checkerinside aninterfaceso every block that uses the interface inherits the protocol checks automatically. - Coverage decks —
cover propertycollections that report holes back to the testbench (the BUSY example) as a verification to-do list. - UVM environments — checkers bound onto the DUT complement class-based scoreboards with cycle-accurate temporal checks the scoreboard can't easily express.
10. Debugging Guide — The Mistakes Everyone Makes Once
checker — bugs every engineer hits the first time
Symptom: Compile error at the port declaration.
Cause: Checkers support only input and output ports — they observe, they don't drive bidirectional signals.
checker my_chk (inout logic data); // ❌ illegalFix: change inout/ref to input. If you genuinely need bidirectional or reference access, use a module, not a checker.
Guardrail: a checker is an observer — every port is input unless it's reporting a status output.
Symptom: Spurious assertion failures at simulation time 0 / on every delta.
Cause: Without default clocking, a property with no explicit @(posedge clk) is evaluated continuously rather than on a clock edge.
Fix: make default clocking cb @(posedge clk); endclocking the first statement in the checker; all properties inherit it.
Guardrail: clock first, reset second — the two default declarations are the checker's opening lines.
Symptom: Dozens of spurious failures at the start of every simulation.
Cause: Without default disable iff (!rst_n), assertions evaluate during reset, when signals are undefined or all-zero.
default clocking cb @(posedge clk); endclocking
// ❌ no default disable iff — every assertion fires during resetFix: add default disable iff (!rst_n); right after the default clocking; it covers every concurrent assertion.
Guardrail: any concurrent assertion deck that runs through reset needs a body-wide disable — the checker gives you exactly one place for it.
Symptom: Simulator reports "assertion failed" with only a file/line — in a 20-assertion checker you can't tell which rule broke.
Cause: A bare assert property (p_rule); has no name in the report.
Fix: label every assertion: rule1_chk: assert property (p_rule1) else $error(...);. The label appears in logs, waveforms, and formal reports.
Guardrail: every assert/cover gets a label: — it is the only way to identify the failing check instantly.
Symptom: A rand variable in a checker never behaves like constrained-random stimulus.
Cause: In a checker, rand is a formal free variable, not a CRV stimulus member — there is no randomize() call driving it.
checker c(...); rand logic [7:0] pkt_data; ... endchecker // free variable, not stimulusFix: generate stimulus in a class with randomize(). Use checker rand only when you intend a formal free variable for universal quantification.
Guardrail: stimulus lives in classes/sequences; checker rand is a formal-proof tool.
Symptom: Synthesis error, or the logic is silently stripped from the netlist.
Cause: A checker is verification-only by definition; it cannot contribute design logic.
Fix: keep always blocks to tracking/counting/monitoring only — no driving of RTL signals. If logic must become gates, put it in a module.
Guardrail: nothing inside a checker should ever reach synthesis — assertions, coverage, and trackers only.
11. Q & A
12. Cross-References & What's Next
This lesson covered the checker construct — its contents, automatic semantics, default clocking/disable iff, free variables, the three instantiation contexts, and checker vs module.
- Previous: 17.2 — The bind Construct — the non-invasive way to attach a checker to RTL you don't own; checkers and
bindcompose directly. - Module index: Module 17 — Advanced Topics — the advanced-feature arc.
Related material elsewhere in the curriculum:
- Concurrent Assertions — the
property/sequencemachinery a checker packages. - Intro to SVA — assertion fundamentals; a checker is where a reusable assertion deck lives.
13. Quick Reference
// ── Declare a checker ───────────────────────────────────────────────
checker my_chk (input logic clk, rst_n, sig_a, sig_b);
default clocking cb @(posedge clk); endclocking // write clock once
default disable iff (!rst_n); // write reset once
int count = 0; // automatic — per instance
rand logic [7:0] free_val; // FREE VARIABLE (formal), not CRV stimulus
sequence s_handshake; sig_a ##1 sig_b; endsequence
property p_b_follows_a; sig_a |-> ##[1:3] sig_b; endproperty
b_follows_a: assert property (p_b_follows_a) // ALWAYS label
else $error("sig_b did not follow sig_a within 3 cycles");
cov_handshake: cover property (s_handshake);
assume property (sig_a |-> !sig_b); // formal input constraint
endchecker
// ── Instantiate: in a module / via bind ─────────────────────────────
my_chk u_chk (.clk(clk), .rst_n(rst_n), .sig_a(a), .sig_b(b));
my_chk u_chk (.*); // implicit connection
bind target_mod my_chk u_chk (.clk(clk), .rst_n(rst_n), .sig_a(a), .sig_b(b));
// Rules: input/output ports only · automatic vars · verification-only (no synthesis)14. Summary
A checker is SystemVerilog's purpose-built home for assertions: a module-shaped, verification-only container with automatic per-instance variables, body-wide default clocking and default disable iff, scoped sequence/property names, and free variables for formal proof. Those features fix the real problems of loose assertions — repeated clock/reset boilerplate, namespace clashes, shared static state across instances, and the missing formal story.
You instantiate a checker like a module — directly in a design block, inside an interface so every user inherits it, or non-invasively via bind onto RTL you don't own (the production pattern, composing with the previous lesson). The AHB-Lite example shows the payoff: three protocol rules and a coverage deck in one reusable unit, where the coverage report doubles as a to-do list for the testbench. Choose checker over module for any new assertion deck — the automatic variables and one-place clock/reset defaults make it safer and cleaner — and reserve module for the rare cases needing inout ports or synthesizable behaviour. A checker never reaches synthesis: assertions, coverage, and trackers only.