Why Learn Interfaces? Connecting a testbench to a DUT one signal at a time gets unwieldy fast — every new port means updating every instantiation that touches it, and it's easy for a testbench and DUT to quietly disagree about a signal's direction. SystemVerilog's 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 logic instead of wire or reg
  • How modport defines 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 logic so they support both procedural and continuous drives, and preserve 4-state X/Z visibility
  • modport gives 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.

Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5