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.

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

What 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

systemverilog
// 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));
endmodule

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

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