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
  • Advanced Language & Integration25
    • DPI-C: Calling C from SystemVerilog
    • bind: Attaching Verification to RTL
      • Bind Fundamentals
      • Writing Assertion Modules for bind
      • Bind File Organization
      • Reusable Checker Libraries
      • bind Debug & Limitations
    • Macros, Defines & Compilation
    • Plusargs, Files & Runtime Configuration
    • Simulation Performance & TB Memory
    • RTL/TB Boundaries, Races & Timing
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹bind: Attaching Verification to RTL
  1. Topics
  2. SystemVerilog
  3. Advanced Language & Integration
  4. bind: Attaching Verification to RTL
  5. Reusable Checker Libraries

Part 7 · Advanced & Integration · Intermediate

Reusable Checker Libraries

Parameterized one-hot and FIFO checkers, the checker construct, and library releases.

On this page

    One check, every interface

    Most assertions are not design-specific. Handshake stability, one-hot encodings, and FIFO occupancy sanity repeat on dozens of interfaces, so each generic check is packaged once as a parameterized, bind-safe module and bound everywhere.

    Diagram
    CHECKER LIBRARY — STAMP EVERYWHERE
    
      lib_checkers/ (versioned, project-independent)
      ┌────────────────────────────────────────────┐
      │ chk_handshake   #(WIDTH, MAX_STALL)        │
      │ chk_onehot      #(N, ALLOW_ZERO)           │
      │ chk_fifo        #(DEPTH, STRICT_FLAGS)     │
      │ chk_req_gnt     #(MAX_WAIT)                │
      └────────────────────────────────────────────┘
            │ bound via subsystem bind files
            ▼
      dma:  3x handshake, 2x fifo, 1x req-gnt
      eth:  5x handshake, 4x fifo, 2x onehot
            — zero new assertion code per subsystem

    Library-grade means fully parameterized, single-concern, and severity-controllable, with a header comment stating exactly what is assumed and what is checked. Compose several small binds rather than building one mega-checker.

    Two library checkers

    Both checkers below rely on the default clocking declared inside them. A concurrent assertion with no clocking event and no default clock in scope is illegal, so that declaration is load-bearing, not style.

    The FIFO flags carry one documented assumption. Strict definitional equality of full and empty against count holds for a textbook FIFO only, so STRICT_FLAGS gates those two properties for variants like fall-through or almost-full designs.

    chk_onehot.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
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    // engine: verilator
    timeunit 1ns; timeprecision 1ps;
    
    // Library pair on one link: one-hot control plus FIFO occupancy.
    module chk_onehot #(
      parameter int N          = 4,
      parameter bit ALLOW_ZERO = 1
    ) (
      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 is not one-hot-0");
        end else begin : g_oh
          a_onehot : assert property ($onehot(vec))
            else $error("vector is not one-hot");
        end
      endgenerate
    endmodule
    
    module chk_fifo #(
      parameter int DEPTH        = 8,
      parameter bit STRICT_FLAGS = 1
    ) (
      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);
    
      generate
        if (STRICT_FLAGS) begin : g_strict
          a_full_def  : assert property (full  == (count == DEPTH));
          a_empty_def : assert property (empty == (count == 0));
        end
      endgenerate
    endmodule
    
    module ctrl_stub (
      input logic       clk, rst_n,
      input logic [3:0] vec
    );
    endmodule
    
    module mini_fifo #(parameter int DEPTH = 8) (
      input  logic clk, rst_n,
      input  logic push, pop,
      output logic full, empty,
      output logic [$clog2(DEPTH):0] count
    );
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) count <= '0;
        else case ({push && !full, pop && !empty})
          2'b10:   count <= count + 1'b1;
          2'b01:   count <= count - 1'b1;
          default: count <= count;
        endcase
      end
      assign full  = (count == DEPTH);
      assign empty = (count == '0);
    endmodule
    
    bind ctrl_stub chk_onehot #(.N(4), .ALLOW_ZERO(1)) chk_oh (
      .clk(clk), .rst_n(rst_n), .vec(vec)
    );
    bind mini_fifo chk_fifo #(.DEPTH(DEPTH), .STRICT_FLAGS(1)) chk_occ (
      .clk(clk), .rst_n(rst_n), .push(push), .pop(pop),
      .full(full), .empty(empty), .count(count)
    );
    

    The checker construct in one box

    SystemVerilog also offers a dedicated checker block: verification-only, compiler-enforced no-drive discipline, and bindable like a module. Unlike modules, checkers take no #() parameter list: constants arrive as formal arguments. Many teams stay with plain modules for portability and reach for checkers where the enforced discipline pays off.

    The full treatment, including clock inference and free variables, lives in the SVA topic. The listing below is not run in this course's checks: Icarus 13 has no checker support, and Verilator 5.048 rejects the port default and stops with an internal error on the ranged delay.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // not-run: Icarus 13 has no checker support; Verilator 5.048 rejects the port default and hits an internal error on ##[1:MAX_WAIT]
    // No #() list: MAX_WAIT is a formal argument bound to a constant.
    checker chk_gnt_after_req (
      input logic clk, rst_n, req, gnt,
      input int   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 like a module, with the constant passed as an argument:
    bind arbiter chk_gnt_after_req chk_rg (
      .clk(clk), .rst_n(rst_n), .req(req0), .gnt(gnt0), .MAX_WAIT(16));

    Versioning a library

    The library is its own repo with semantic-version tags, and projects pin a tag, never head. Changing a shipped 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 stubs that intentionally violate each assertion, proving every check still fires. A project that needs a tweak parameterizes or contributes the option upstream behind a default-off parameter.

    Testbench

    The runnable shadow of that self-test idea: a manual one-hot monitor in plain Verilog injects three illegal vectors and must flag exactly those three. No concurrent assertions are involved, so the violation traffic itself is the stimulus.

    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
    25
    26
    27
    timeunit 1ns; timeprecision 1ps;
    
    module tb_onehot_selftest;
      reg [3:0] vec = 4'b0000;
      integer flagged = 0;
      integer errors = 0;
    
      always @(vec) begin
        if ((vec & (vec - 4'd1)) !== 4'b0000) flagged = flagged + 1;
      end
    
      initial begin
        #1 vec = 4'b0001;
        #1 vec = 4'b0010;
        #1 vec = 4'b0011;
        #1 vec = 4'b0100;
        #1 vec = 4'b1100;
        #1 vec = 4'b0000;
        #1 vec = 4'b1111;
        #1 vec = 4'b1000;
        #1;
        if (flagged !== 3) errors = errors + 1;
        if (errors == 0) $display("PASS: manual one-hot monitor flagged all 3 injected violations");
        else $fatal(1, "FAIL: one-hot self-test flagged %0d of 3 injected violations", flagged);
        $finish;
      end
    endmodule

    Common pitfall

    Copy-pasting checker source into each project forks the library silently. Five diverged copies later, a fix lands in one and the other four stay wrong.

    Check yourself When does a FIFO variant set STRICT_FLAGS to 0?

    When its flags are not definitional: fall-through latency, almost-full or almost-empty thresholds, or read-latency variants where full need not equal count == DEPTH.

    Check yourself A library release only renames an assertion label. Which version field moves?

    Treat it as a major bump. Labels are the library's public handles: $assertoff paths, waiver files and triage scripts key on them, so a rename can break consumers even though no check changes.

    Related topics

    • Writing Assertion Modules for bind

    • bind Debug & Limitations

    Previous

    193 / 259

    Next: bind Debug & Limitations

    Up next

    Up nextbind Debug & Limitations

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Bind Fundamentals
    • Writing Assertion Modules for bind