The bind
construct in SystemVerilog allows you to attach code—such as assertions, covergroups, or even whole modules—to existing module or interface instances at elaboration time.
This is especially useful for:
- Adding checkers or monitors to third-party IP without editing the RTL
- Injecting functional coverage into designs without touching the source
- Keeping verification logic separate from design logic It’s aimed at verification engineers and RTL designers who want to instrument designs in a non-intrusive way for simulation or formal verification.
- Bind to a module: Add verification logic to all instances of a module
In this tutorial, I’ll be using the UART example from Siemens’ Verification Academy. The UART DUT is located in the RTL directory. To demonstrate the use of the bind construct, I will attach assertions and coverage to the uart_tx_fifo design.
My chosen approach for using bind is as follows: in the tb directory, I created a file named bind.sv. This file contains two modules—one dedicated to FIFO assertions and the other to FIFO coverage.
The first module, uart_tx_fifo_assert, takes as input several FIFO status signals—such as the number of stored items and flags indicating near-empty or near-full conditions. For the complete implementation, please refer to bind.sv
module uart_tx_fifo_assert(
input clk,
input fifo_empty,
input fifo_full,
input [4:0] count
);
property fifo_full_prop;
@(posedge clk)
fifo_full |-> (count == 5'b10000);
endproperty: fifo_full_prop
...
TX_FIFO_FULL_CHK: assert property(fifo_full_prop);
...
TX_FIFO_FULL_COV: cover property (fifo_full_prop);
...
endmodule
The second module, uart_tx_fifo_cov, connects to all FIFO terminals as inputs and collects coverage to ensure that every aspect of FIFO functionality is exercised. For the full implementation, please see bind.sv.
module uart_tx_fifo_cov(
input clk,
input rstn,
input push,
input pop,
input [7:0] data_in,
input fifo_empty,
input fifo_full,
input [7:0] data_out,
input [4:0] count
);
parameter int DEPTH = 16;
parameter int WIDTH = 8;
// -------------------------
// 1) Operation mode & flags
// -------------------------
covergroup cg_ops_flags @ (posedge clk);
option.name = "cg_ops_flags";
cp_op: coverpoint ({push,pop}) iff (rstn) {
bins idle = {2'b00};
bins wr_only = {2'b10};
bins rd_only = {2'b01};
bins both = {2'b11};
}
// Flag states
cp_full : coverpoint fifo_full iff (rstn) { bins lo={0}; bins hi={1}; }
...
endgroup
cg_ops_flags u_cg_ops_flags = new;
...
endmodule
Now comes the interesting part.
In the bind module, the two modules—uart_tx_fifo_cov and uart_tx_fifo_assert—are instantiated and bound to the uart_tx_fifo.
In this example, if multiple instances of uart_tx_fifo existed in the design, the bind would apply to all of them.
It is also possible to bind to a specific instance or a group of instances;
please refer to the LRM for additional binding options.
The general structure of a bind statement is as follows:
- the keyword bind, followed by the name of the target module (in this case uart_tx_fifo).
- specify the module you wish to bind (e.g., the assertion or coverage module).
- The ports of the bound module must align with the signal names in the target FIFO; otherwise, a compilation error will occur.
- Finally, as with any other module instantiation, you must provide an instance name—here, they are fifo_assert and fifo_cov.
module binds ();
bind uart_tx_fifo uart_tx_fifo_assert fifo_assert (
.clk(clk),
.fifo_empty(fifo_empty),
.fifo_full(fifo_full),
.count(count)
);
bind uart_tx_fifo uart_tx_fifo_cov fifo_cov (
.clk(clk),
.rstn(rstn),
.fifo_empty(fifo_empty),
.fifo_full(fifo_full),
.count(count),
.data_in(data_in),
.data_out(data_out),
.pop(pop),
.push(push)
);
endmodule
The final step is to instantiate the binds module within the testbench top module—in this case, uart_tb.
module uart_tb;
import uvm_pkg::*;
import uart_test_pkg::*;
logic PCLK;
logic PRESETn;
...
binds u_binds ();
...
endmodule: uart_tb
After running the simulation, the two bound modules appear in the simulator hierarchy as if they were sub-blocks of the FIFO.
📚 References IEEE 1800 SystemVerilog LRM – Section 23.11 "Binding auxiliary code to scopes or instances"