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. Writing Assertion Modules for bind

Part 7 · Advanced & Integration · Intermediate

Writing Assertion Modules for bind

Input-only checker modules, a valid-ready checker, and binding to internal signals.

On this page

    Checker modules only observe

    A module intended for bind has one job: observe and judge. It executes inside someone else's design scope, so any drive or side effect becomes an invisible RTL modification.

    • Inputs only. Every port is an input: no outputs, no inouts, no internal assigns onto port nets. The checker is physically incapable of disturbing the DUT.

    • No timing influence. No driven delays, no force statements. Assertions, covers, messages, and local bookkeeping registers only.

    • Parameterized widths. Never hardcode a bus width. Take parameters so one checker binds to every configuration of the target.

    • Self-contained clocks. Declare default clocking and reset handling inside. The checker must not assume any testbench infrastructure exists.

    • Named properties. Label every assertion (a_*) and cover (c_*). Labels are the debug handles in waves and reports.

    A valid-ready checker

    The most reused checker in any environment judges valid-ready stability on a stream port. The table lists its rules; the full module with its testbench sits under Testbench below.

    Check

    Rule

    a_valid_stable

    valid && !ready |=> valid (no retraction)

    a_data_stable

    valid && !ready |=> $stable(data)

    a_data_known

    valid |-> !$isunknown(data)

    c_b2b

    (valid && ready) ##1 (valid && ready)

    a_ready_bound

    valid && !ready |-> ##[1:MAX_STALL] ready

    Watch valid against ready. Slot 3 is the bug the stability rule forbids.

    • Slot 0: idle, valid 0 and ready 0.

    • Slot 1: offer, valid 1 with data a5 while ready stays 0.

    • Slot 2: stall, valid held and data stable at a5.

    • Slot 3: retraction, valid drops with ready still 0 and data churns to 3c. a_valid_stable and a_data_stable both fire here.

    • Slot 4: re-offer accepted, valid 1 and ready 1 with data a5.

    • Slot 5: back-to-back, new data 7e accepted at once.

    The liveness bound uses a ranged delay. Icarus 13 cannot parse ranged delays such as ##[1:5]. Verilator 5.048 runs them when the property names its own clock, so the runnable checker below carries the bound in that form.

    design.sv
    // snippet
    a_ready_bound : assert property (valid && !ready |-> ##[1:MAX_STALL] ready);

    Seeing inside the target

    The bound instance resolves its port map in the target's scope, including signals that never reach the target's ports. Whitebox assertions on FSM state, counters, and grants connect like ports, without exporting them.

    arb_checker.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // snippet
    // Inside arbiter.sv (RTL, unmodified): grant_q and state_q are internal.
    // The checker takes them as plain inputs; the bind maps them by name.
    module arb_checker #(parameter int N = 4) (
      input logic         clk, rst_n,
      input logic [N-1:0] grant_q,
      input logic [1:0]   state_q
    );
      default clocking cb @(posedge clk); endclocking
      default disable iff (!rst_n);
    
      a_grant_onehot : assert property ($onehot0(grant_q));
      a_gnt_in_gnt   : assert property (|grant_q |-> state_q == 2'd2);
    endmodule
    
    bind arbiter arb_checker #(.N(REQ_N)) chk (
      .clk(clk), .rst_n(rst_n),
      .grant_q(grant_q),
      .state_q(state_q)
    );
    Diagram
    VISIBILITY: BOUND CHECKER vs TB-SIDE CHECKER
    
      TB-side checker            Bound checker
      ────────────────           ─────────────────────────
      sees: DUT ports only       sees: ports + ALL internals
      path: tb.dut.u_arb.        path: resolved by name in
            grant_q (fragile           target scope (moves
            hierarchical ref)          with the module)

    Common mistakes

    Common pitfall

    An output port or a stray continuous assign in the checker silently drives the DUT through the bind. Keep every port an input and grep bound modules for undriven-port lint warnings.

    Interview

    Expect: “write the assertions for a valid-ready port.” State the three rules (no retraction, stable payload, known payload), then add the bounded-liveness question and say which subset runs in each tool.

    Check yourself The checker needs the target's FIFO depth. How does it get it without hardcoding?

    As a parameter. The checker declares DEPTH with a default, and the bind forwards the target's own parameter with #(.DEPTH(DEPTH)).

    Check yourself Why must every checker port be an input?

    The instance elaborates inside the RTL scope. An output or an internal drive would modify the design invisibly, bypassing RTL review and possibly synthesis.

    Testbench

    The checker, a passthrough link that exposes clock and reset for the bind, and a testbench driving four beats across a 3-cycle stall. The checker carries the ##[1:MAX_STALL] bound, and a stall counter in the testbench cross-checks it.

    vr_checker.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
    // engine: verilator
    timeunit 1ns; timeprecision 1ps;
    
    // A valid-ready checker bound to a link; the counter shadows the stall bound.
    module vr_checker #(
      parameter int WIDTH     = 8,
      parameter int MAX_STALL = 8
    ) (
      input logic             clk,
      input logic             rst_n,
      input logic             valid,
      input logic             ready,
      input logic [WIDTH-1:0] data
    );
      default clocking cb @(posedge clk); endclocking
      default disable iff (!rst_n);
    
      a_valid_stable : assert property (valid && !ready |=> valid)
        else $error("valid dropped before ready");
      a_data_stable : assert property (valid && !ready |=> $stable(data))
        else $error("data changed during stall");
      a_data_known : assert property (valid |-> !$isunknown(data))
        else $error("X on data while valid");
      // Explicit clock: Verilator 5.048 fails on a ranged delay under default clocking.
      a_ready_bound : assert property (@(posedge clk) disable iff (!rst_n)
                                       valid && !ready |-> ##[1:MAX_STALL] ready)
        else $error("ready did not arrive within MAX_STALL cycles");
    
      c_b2b : cover property (valid && ready ##1 valid && ready);
    endmodule
    
    module axis_stub #(parameter int WIDTH = 8) (
      input  logic             clk,
      input  logic             rst_n,
      input  logic             s_valid,
      output logic             s_ready,
      input  logic [WIDTH-1:0] s_data,
      output logic             m_valid,
      input  logic             m_ready,
      output logic [WIDTH-1:0] m_data
    );
      assign m_valid = s_valid;
      assign m_data  = s_data;
      assign s_ready  = m_ready;
    endmodule
    
    bind axis_stub vr_checker #(.WIDTH(WIDTH), .MAX_STALL(8)) chk_link (
      .clk(clk), .rst_n(rst_n), .valid(m_valid), .ready(m_ready), .data(m_data)
    );
    

    Related topics

    • Bind Fundamentals

    • Reusable Checker Libraries

    Previous

    191 / 259

    Next: Bind File Organization

    Up next

    Up nextBind File Organization

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Reusable Checker Libraries
    • bind Debug & Limitations
    clkvalidreadydata0a53ca57e
    A valid-ready link with a stall, a retraction, and back-to-back accepts