Part 7 · Advanced & Integration · Intermediate

bind Fundamentals

bind syntax with ports and parameters, why bind exists, module vs instance targeting, and what elaboration produces.

The bind statement

A bind statement names a target (an RTL module or a specific instance), a checker module to insert, an instance name for the inserted copy, and a port map. Ports connect by name to signals visible inside the target's scope — ports and internal signals alike.

systemverilog
// Target module: plain RTL, no knowledge of the checker
module fifo #(parameter DEPTH = 16, WIDTH = 32) (
  input  logic             clk, rst_n,
  input  logic             push, pop,
  output logic             full, empty
);
  logic [$clog2(DEPTH):0] count;    // internal signal
  // ... RTL ...
endmodule

// Checker module (defined elsewhere, covered next lesson)
// module fifo_checker #(parameter DEPTH) (input clk, rst_n, push, pop,
//                                         full, empty, [..:0] count);

// THE BIND: insert fifo_checker into every fifo instance
bind fifo fifo_checker #(.DEPTH(DEPTH)) chk (
  .clk   (clk),
  .rst_n (rst_n),
  .push  (push),
  .pop   (pop),
  .full  (full),
  .empty (empty),
  .count (count)      // internal signal — visible because the
);                    // bound instance lives inside fifo's scope

Why bind exists

  • No RTL edits — assertions reach the design without touching files DV does not own; RTL diffs stay pure design changes.

  • Separable ownership — checkers live in the DV repo with DV review, RTL stays synthesis-clean with zero verification clutter.

  • Full visibility — the bound instance sees internal signals (FSM state, counters) that a TB-side checker could only reach via fragile hierarchical paths.

  • Selective deployment — compile binds in simulation only; synthesis and emulation builds never see them.


Bind by module vs bind by instance

Targeting the module name binds the checker into every instance of that module in the design — usually what you want for generic protocol checks. Targeting a specific instance path binds only that one copy — used when instances are configured differently or only one is interesting.

systemverilog
// By module: every fifo in the design gets a checker
bind fifo fifo_checker #(.DEPTH(DEPTH)) chk (.*);

// By instance: only the command FIFO inside the DMA gets one
bind top.u_dma.u_cmd_fifo fifo_checker #(.DEPTH(8)) chk (
  .clk(clk), .rst_n(rst_n), .push(push), .pop(pop),
  .full(full), .empty(empty), .count(count)
);

// Multiple instances of one target, distinct checkers — name them
bind fifo fifo_checker        #(.DEPTH(DEPTH)) chk_proto (.*);
bind fifo fifo_cov_collector  #(.DEPTH(DEPTH)) chk_cov   (.*);

Note .* works when checker port names match target signal names exactly — convenient, but explicit maps survive renames better and make the connection auditable.


What elaboration produces

After elaboration, the bound instance is a real child of the target — present in the hierarchy under the target's path, in every instance of the target (for module binds). It behaves exactly as if the instantiation line were the last statement inside the target module.

diagram
ELABORATED HIERARCHY [RTL] [DV]

  source: bind fifo fifo_checker #(...) chk (...);

  top
   ├─ u_dma
   │   ├─ u_cmd_fifo (fifo)           [RTL]
   │   │   ├─ ...rtl contents...
   │   │   └─ chk (fifo_checker)      [DV]  ← inserted by bind
   │   └─ u_data_fifo (fifo)          [RTL]
   │       ├─ ...rtl contents...
   │       └─ chk (fifo_checker)      [DV]  ← every instance
   └─ u_eth
       └─ u_rx_fifo (fifo)            [RTL]
           └─ chk (fifo_checker)      [DV]

  Wave path:  top.u_dma.u_cmd_fifo.chk.a_no_push_full
  Scope:      chk sees fifo's clk, push, count, ... by name

Rules worth remembering

  • Parameters pass through normally — #(.DEPTH(DEPTH)) forwards the target's elaborated parameter into the checker.

  • The checker's ports may connect to any name resolvable in the target's scope, including signals deep in the target's declarations.

  • A bind target can be a module, an interface, or a specific instance; the bound unit can be a module, interface, program, or checker.

  • The same target can receive multiple binds — give each a distinct instance name.

Key takeaways

  • bind <target> <checker> <inst> (ports) inserts the checker inside the target's scope at elaboration.

  • Module binds hit every instance; instance binds hit one path — choose per check generality.

  • The bound instance is a real hierarchy child: visible in waves under the target's path.

  • Parameters forward from the target, so one checker scales across differently-sized instances.

Common pitfalls

  • Using .* and silently binding to the wrong same-named signal after an RTL rename — explicit maps fail loudly instead.

  • Forgetting that module binds multiply: 200 fifo instances means 200 checkers — fine for assertions, costly for heavy covergroups.

  • Binding by deep instance path that breaks on every hierarchy refactor — prefer module binds where the check is generic.

  • Assuming the checker can drive target signals — bound checkers must be observers only (next lesson).