Part 7 · Advanced & Integration · Intermediate
Reusable Checker Libraries
OVL-style generic checker libraries, parameterized handshake/onehot/fifo checkers, the checker construct with bind, and versioning across projects.
The library idea
Most assertions are not design-specific. Handshake stability, one-hot encodings, FIFO occupancy sanity, request-grant fairness — every project needs them on dozens of interfaces. The checker library pattern (popularized by the Accellera OVL — Open Verification Library) packages each generic check as a parameterized, bind-safe module, written and reviewed once, then bound everywhere.
CHECKER LIBRARY — STAMP EVERYWHERE [DV]
lib_checkers/ (versioned, project-independent)
┌────────────────────────────────────────────┐
│ chk_handshake #(WIDTH, MAX_STALL) │
│ chk_onehot #(N, ALLOW_ZERO) │
│ chk_fifo #(DEPTH) │
│ chk_req_gnt #(N, MAX_WAIT) │
│ chk_stable_when #(WIDTH) │
└────────────────────────────────────────────┘
│ bound via subsystem bind files
▼
dma: 3× chk_handshake, 2× chk_fifo, 1× chk_req_gnt
eth: 5× chk_handshake, 4× chk_fifo, 2× chk_onehot
pcie: 8× chk_handshake, 1× chk_req_gnt
— zero new assertion code written per subsystemWhat makes a checker library-grade
Fully parameterized — widths, depths, latency bounds, and optional-check enables all come in as parameters.
Single concern — one checker validates one protocol notion; compose several binds rather than building a mega-checker.
Severity and enable controls — a parameter (or plusarg hook) to downgrade errors to warnings during bring-up.
Documented contract — a header comment stating exactly what is assumed and what is checked, in spec language.
Two library checkers
// chk_onehot.sv — generic one-hot/one-hot0 monitor
module chk_onehot #(
parameter int N = 4,
parameter bit ALLOW_ZERO = 1 // 1: $onehot0, 0: $onehot
) (
input logic clk,
input logic rst_n,
input logic [N-1:0] vec
);
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
generate
if (ALLOW_ZERO) begin : g_oh0
a_onehot0 : assert property ($onehot0(vec))
else $error("vector not one-hot-0");
end else begin : g_oh
a_onehot : assert property ($onehot(vec))
else $error("vector not one-hot");
end
endgenerate
endmodule
// chk_fifo.sv — occupancy sanity for any fifo with push/pop/count
module chk_fifo #(parameter int DEPTH = 16) (
input logic clk, rst_n,
input logic push, pop, full, empty,
input logic [$clog2(DEPTH):0] count
);
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
a_no_push_full : assert property (full |-> !push || pop)
else $error("push into full fifo");
a_no_pop_empty : assert property (empty |-> !pop)
else $error("pop from empty fifo");
a_count_range : assert property (count <= DEPTH);
a_full_def : assert property (full == (count == DEPTH));
a_empty_def : assert property (empty == (count == 0));
endmoduleThe checker construct, and versioning
checker ... endchecker with bind
SystemVerilog also offers a dedicated checker construct — a unit purpose-built for assertions that can infer clock and reset from context, accept untyped expression ports, and legally contain only verification constructs. Checkers can be bound exactly like modules, and the compiler enforces the no-driver discipline you otherwise maintain by convention.
checker chk_gnt_after_req (
logic clk, rst_n, req, gnt,
int unsigned MAX_WAIT = 16
);
default clocking @(posedge clk); endclocking
default disable iff (!rst_n);
a_gnt_bound : assert property (
$rose(req) |-> ##[1:MAX_WAIT] gnt
) else $error("grant exceeded wait bound");
endchecker
// bound exactly like a module:
bind arbiter chk_gnt_after_req chk_rg (clk, rst_n, req[0], gnt[0]);In practice many teams stay with plain modules for library checkers — tool support is universal and the conventions are well understood — and adopt checker constructs where the compiler-enforced restrictions and clock inference pay off. Both bind identically; choose per team tooling.
Versioning across projects
The library is its own repo with semantic-version tags; projects pin a tag in their file-list generation — never head.
Additions are backward compatible; changing an existing checker's behavior is a major version bump because it can flip regressions in every consuming project.
Each release ships a self-test bench: tiny DUT stubs that intentionally violate each assertion, proving every check still fires.
A project that needs a tweak forks nothing — it parameterizes, or contributes the option upstream behind a default-off parameter.
Key takeaways
Generic checks (handshake, onehot, fifo, req-grant) belong in a parameterized library written once and bound everywhere.
Library-grade means: fully parameterized, single-concern, severity-controllable, with a documented assumption/check contract.
The checker construct adds compiler-enforced discipline and clock inference; plain modules remain the portable default.
Version the library like an external dependency: pinned tags, semantic versioning, self-test bench per release.
Common pitfalls
Copy-pasting checker source into each project — five diverged copies, none of which gets fixes.
Mega-checkers that validate a whole interface in one module — unusable when only half the signals exist on a port.
Changing a shipped checker's semantics without a version bump — regressions flip in other projects with no local diff.
No self-test for the library — a refactor silently makes a checker vacuous and nobody notices for months.