Bind problems surface at elaboration, and the messages point at the bind file even when the root cause is an RTL change. The three classic failures cover nearly every case.
Port or signal not found. The bind maps .count(count) but the RTL renamed the signal. Fix it in the bind file; renames are routine maintenance after RTL drops.
Width or parameter mismatch. The checker's default no longer matches the target. Forward the target's parameters instead of hardcoding them.
Target not found. The bind names a module or instance path that no longer exists. Instance-path binds are the usual victims; prefer module binds where the check is generic.
TRIAGE FLOW
elaboration error in <subsys>_binds.sv
│
├─ "signal not found" → diff the RTL module's signals
│ vs the bind's port map; update map
├─ "width mismatch" → check parameter forwarding
│ #(.DEPTH(DEPTH)) not #(.DEPTH(16))
└─ "module/instance → module renamed? hierarchy moved?
not found" convert instance binds to module
binds where the check is genericThe bound instance lives under the target's hierarchy path, not under the testbench. To watch the checker inside the DMA command FIFO, expand top.u_dma.u_cmd_fifo.chk. Teams new to bind routinely hunt the testbench tree and conclude the checker is missing.
WAVE BROWSER VIEW
top
├─ tb_env <- TB classes are NOT here either way
└─ u_dma
└─ u_cmd_fifo [RTL]
├─ count[4:0]
├─ ...rtl signals...
└─ chk [DV] <- bound checker lives HERE
├─ a_no_push_full <- assertion status
├─ a_count_range
└─ ports (clk, push, count...)Binding to a module also reaches instances created inside generate loops, with one checker per generated copy. Binding into a specific generate block by path is fragile: the path carries block labels and indices, and unlabeled blocks get tool-assigned names that differ across simulators. Label every generate block or avoid the path.
Assertions are cheap singly, not by the thousand. Profile a bind farm before assuming it is free.
Long liveness bounds are the usual hotspot. Each ##[1:4096] window keeps many in-flight attempts per instance.
Use the SVA_ON knobs. Per-subsystem macros isolate a slowdown to one checker family in minutes.
Covergroups multiply fastest. Hundreds of instances times a per-instance covergroup is a memory problem, not just CPU.
A favorite senior discussion is why not to write assertions inside the RTL directly. The honest answer is a tradeoff between ownership, reuse, and intent.
Inline (in RTL) | bind (DV-owned) | |
|---|---|---|
Ownership | RTL team | DV team |
RTL edits needed | Yes | No |
Designer intent | Captured at write time | Reconstructed later |
Internal access | Trivial | Through the port map |
Synthesis | Needs pragmas or guards | Excluded: omitted from synthesis lists, plus the guard |
Reuse | None (design-specific) | Library, bound anywhere |
Survives refactor | Moves with the code | Bind file maintenance |
Best for | Invariants the designer knows | Protocol checks and reusable whitebox |
The RTL renamed or removed a signal the bind maps. DV updates the port map in the subsystem bind file; the RTL side changes nothing for verification.
When only the designer knows the invariant: designer intent captured at write time outweighs the ownership and reuse advantages of bind.