interface construct bundles related signals into a single reusable block, so connecting to a design becomes a one-line job regardless of how many signals it actually carries.What Is an Interface?
An interface is a way to encapsulate signals into a block. All related signals are grouped together to form an interface block, so the same interface can be reused across other projects. It also becomes easier to connect to the DUT and other verification components, since the connection no longer has to be made one signal at a time.
7 min read | Intermediate Level
What You'll Learn
- What a SystemVerilog interface is and the problem it solves for connecting a DUT to a testbench
- Why interface signals are typically declared
logicinstead ofwireorreg - How
modportdefines different signal directions for different components sharing the same interface - How a clocking block controls exactly when testbench signals are sampled and driven relative to a clock
A Basic Interface Example
APB bus protocol signals are put together in the interface below. Signals are declared between interface and endinterface:
interface apb_if (input pclk);
logic [31:0] paddr;
logic [31:0] pwdata;
logic [31:0] prdata;
logic penable;
logic pwrite;
logic psel;
endinterface
Why Are Signals Declared logic?
logic is a data type that lets you drive signals of this type both via assign statements and from within a procedural block. In Verilog, you could only drive a reg from a procedural block, and only drive a wire from an assign statement — but that flexibility is only one reason.
Signals connected to the DUT should support 4 states, so that X/Z values can be caught. If these signals were declared bit instead, any X or Z value would show up as a plain 0, and you'd miss the fact that the DUT ever drove an uninitialized or high-impedance value in the first place.
Quick Check
Q: If an interface signal connected to the DUT were declared bit instead of logic, and the DUT accidentally drove that signal to X, what would the testbench see?
Show Answer
It would see 0, not X. bit is a 2-state type, so any X or Z value gets silently converted to 0 — the testbench would have no way to tell an uninitialized signal apart from a legitimately-driven zero.
How Do You Define Port Directions?
Interface signals can be used within various verification components as well as the DUT, so modport is used to define signal directions. Different modport definitions can be passed to different components, letting each one see a different set of input/output directions for the very same signals:
interface myBus (input clk);
logic [7:0] data;
logic enable;
// From TestBench perspective, 'data' is input and 'write' is output
modport TB (input data, clk, output enable);
// From DUT perspective, 'data' is output and 'enable' is input
modport DUT (output data, input enable, clk);
endinterface
SystemVerilog Modport covers this construct in more depth.
How Do You Connect an Interface to a DUT?
An interface object should be created in the top testbench module where the DUT is instantiated, and passed to the DUT. It's essential to make sure the correct modport is assigned to the DUT:
module dut (myBus busIf);
always @ (posedge busIf.clk)
if (busIf.enable)
busIf.data <= busIf.data+1;
else
busIf.data <= 0;
endmodule
// Filename : tb_top.sv
module tb_top;
bit clk;
// Create a clock
always #10 clk = ~clk;
// Create an interface object
myBus busIf (clk);
// Instantiate the DUT; pass modport DUT of busIf
dut dut0 (busIf.DUT);
// Testbench code : let's wiggle enable
initial begin
busIf.enable <= 0;
#10 busIf.enable <= 1;
#40 busIf.enable <= 0;
#20 busIf.enable <= 1;
#100 $finish;
end
endmodule
Note that dut0 is instantiated with busIf.DUT specifically — the DUT modport — while the testbench code above drives busIf.enable directly using the interface's full, undirected view.
Where Are Interfaces Used?
Interfaces are the standard way protocol testbenches (AMBA, AXI, APB, and similar) connect a reusable verification component to whatever DUT it's plugged into — the same interface and its verification IP can be reused across many different projects and DUT configurations without ever having to touch a port list.
What Are the Advantages?
Interfaces can contain tasks, functions, parameters, variables, functional coverage, and assertions. This lets you monitor and record transactions via the interface itself. It also makes connecting to the design easier regardless of how many ports it has, since all of that information is encapsulated in one interface object:
//Before interface
dut dut0 (.data (data),
.enable (enable),
// all other signals
);
// With interface - higher level of abstraction possible
dut dut0 (busIf.DUT);
How Do You Parameterize an Interface?
The same way you would for a module — letting the same interface definition adapt its signal widths to whatever design it's connected to. SystemVerilog Interface Parameters covers this in depth, including how to keep an interface's parameter and a connected design's parameter in sync.
What Are Clocking Blocks?
Signals specified inside a clocking block are sampled and driven with respect to that clock. An interface can contain multiple clocking blocks. This is specifically for testbench-related signals — it lets you control precisely when the testbench drives and samples signals from the DUT, which solves part of the race-condition problem (though not entirely). Skew values can also be parameterized:
interface my_int (input bit clk);
// Rest of interface code
clocking cb_clk @(posedge clk);
default input #3ns output #2ns;
input enable;
output data;
endclocking
endinterface
In this example, by default, inputs are sampled 3ns before the posedge of clk, and outputs are driven 2ns after the posedge of clk. SystemVerilog Clocking Blocks covers this in more depth.
Quick Check
Q: With default input #3ns output #2ns;, if clk has a posedge at 100ns, when is an input signal sampled, and when is an output signal driven?
Show Answer
The input is sampled at 97ns (3ns before the 100ns posedge), and the output is driven at 102ns (2ns after the 100ns posedge).
How Do You Use a Clocking Block?
// To wait for posedge of clock
@busIf.cb_clk;
// To use clocking block signals
busIf.cb_clk.enable = 1;
Notice that you don't have to explicitly wait for the posedge of clk before assigning 1 to enable. Because the clocking block already defines an output skew, you're assured enable will be driven 2ns after the next posedge of clk, without writing that timing logic yourself.
Common Beginner Mistakes
Mistake #1: Declaring interface signals as bit, wire, or reg
DUT-connected signals should be logic, not bit. Why? bit is 2-state and silently turns any X or Z into 0, hiding exactly the kind of uninitialized-signal bug you'd most want your testbench to catch.
Mistake #2: Passing the wrong modport when instantiating the DUT
It's easy to accidentally connect busIf.TB where busIf.DUT was intended, or vice versa. Why? Since the two modports typically define opposite signal directions for the same physical signals, passing the wrong one usually causes a direction-mismatch compile error — or worse, silently wrong behavior if the mismatch happens to compile.
Mistake #3: Driving or sampling DUT signals directly instead of through a clocking block
Accessing busIf.data directly from testbench code, instead of through a clocking block like busIf.cb_clk.data, reintroduces the exact race condition clocking blocks exist to avoid. Why? Without a defined input/output skew, whether the testbench samples a signal before or after the DUT updates it in the same time step becomes a matter of simulator scheduling order, not a well-defined outcome.
Practice Exercise
Challenge: Add a Read-Only MONITOR Modport
Using the myBus interface from earlier (signals data, enable, and clk), add a third modport called MONITOR that only ever reads signals — it should never drive anything.
Hint
A modport that only reads declares every one of its signals as input, including ones that other modports (like DUT) declare as output.
Solution
interface myBus (input clk);
logic [7:0] data;
logic enable;
modport TB (input data, clk, output enable);
modport DUT (output data, input enable, clk);
// A passive observer - every signal is an input, nothing is ever driven
modport MONITOR (input data, enable, clk);
endinterface
MONITOR declares data as input even though DUT declares it as output — each modport is just a different view of the same underlying signals, so a passive monitor component can observe everything without ever risking driving a signal it shouldn't.
Summary
In this tutorial, you learned:
- An interface bundles related signals into one reusable block, simplifying connections to the DUT and other verification components
- Interface signals are declared
logicso they support both procedural and continuous drives, and preserve 4-stateX/Zvisibility modportgives different components a different set of signal directions over the same underlying interface signals- An interface is passed into a module using the appropriate modport, such as
busIf.DUT - A clocking block defines exactly when testbench signals are sampled and driven relative to a clock, avoiding the race conditions that come from accessing DUT signals directly
Continuing the Interfaces & Modports Chapter
Continue with SystemVerilog Interface Intro, Interface Bundles, Clocking Blocks, and Modport for a deeper look at each of these topics.