Interfaces
Named signal bundles, ports in interfaces, modports for direction control and task/function access, specify blocks, tasks and functions inside interfaces (import, export, extern forkjoin), parameterised interfaces, virtual interfaces, and hierarchical access to interface objects.
🔗 What Interfaces Solve
In Verilog-2001, the communication between design blocks is described by long, repetitive port lists. Adding a signal to a bus means editing every module declaration and every instantiation site. SystemVerilog interfaces solve this by encapsulating a bundle of nets and variables as a named type that passes through a port as a single item.
At higher levels of abstraction, interfaces also encapsulate the functionality of a communication protocol — tasks and functions — so that a module need only call a.read(addr) rather than manually toggling protocol signals. Swapping the interface for a different-abstraction version leaves the connected modules unchanged.
- Signal bundling — replace a long port list with one interface port.
- Direction control — modports assign per-module view of directions.
- Protocol encapsulation — tasks/functions in the interface perform bus transactions.
- Abstract/reusable testbenches — virtual interfaces decouple test code from specific signal paths.
📄 Interface Syntax and Instantiation
// Basic interface declaration interface my_ifc; // ... nets, variables, tasks, functions, clocking blocks, modports endinterface : my_ifc // optional closing name // With a clock port interface simple_bus (input bit clk); // ... signals endinterface // Instantiation (same syntax as a module) simple_bus sb_intf(clk); // one instance myinterface #(100) scalar1; // parameterised instance myinterface #(100) vector[9:0]; // array of 10 instances // Connecting an interface to a module — three equivalent forms: memMod mem(sb_intf, clk); // positional cpuMod cpu(.b(sb_intf), .clk(clk)); // named memMod mem2(.*); // implicit .* (when port names match) // Modules can be declared/instantiated IN interfaces? // NO — modules cannot be declared or instantiated inside interfaces. // Interfaces CAN be instantiated hierarchically inside modules.
📋 Named Bundle — simple_bus
Without interface — repetitive ports
module memMod( input bit req, clk, start, input logic[1:0]mode, input logic[7:0]addr, inout wire[7:0] data, output bit gnt, rdy); // same list repeated on cpuMod and top endmodule
With interface — single port
interface simple_bus; logic req, gnt; logic[7:0] addr, data; logic[1:0] mode; logic start, rdy; endinterface module memMod(simple_bus a, input bit clk); always @(posedge clk) a.gnt <= a.req; endmodule
module top; logic clk = 0; simple_bus sb_intf(); // instantiate interface memMod mem(sb_intf, clk); // connect by position cpuMod cpu(.b(sb_intf), .clk(clk)); // connect by name endmodule // When port and interface names match — .* works too module memMod (simple_bus sb_intf, input bit clk); ... endmodule memMod mem(.*); // sb_intf and clk auto-connected
📋 Generic Interface Ports
A module can accept any interface type by using the keyword interface as a placeholder. The actual interface is specified at instantiation time. Generic interface ports require ANSI-style port declarations and cannot use implicit .* connections.
// Generic interface — accepts any interface module memMod (interface a, input bit clk); ... endmodule module cpuMod (interface b, input bit clk); ... endmodule module top; simple_bus sb_intf(); // any interface type memMod mem (.a(sb_intf), .clk(clk)); // named connection required for generic port cpuMod cpu (.b(sb_intf), .clk(clk)); endmodule // Can mix .* with explicit for the generic port memMod mem (.*, .a(sb_intf)); // .* handles clk; .a explicit for generic
.* connection cannot infer a generic interface port because there is no type information to match. Always connect generic interface ports explicitly using .port_name(interface_instance).
📋 Ports in Interfaces
Signals declared inside an interface are shared among all connected modules. Signals declared as ports of the interface can additionally be connected externally — making them accessible from outside the interface when it is instantiated.
// Simple interface with one port (clk shared externally) interface simple_bus (input bit clk); logic req, gnt; logic[7:0] addr, data; endinterface module memMod(simple_bus a); // clk available as a.clk always @(posedge a.clk) a.gnt <= a.req; endmodule // Two interface instances sharing the same external clk module top; logic clk = 0; simple_bus sb_intf1(clk); // both share the same clk simple_bus sb_intf2(clk); memMod mem1(.a(sb_intf1)); cpuMod cpu1(.b(sb_intf1)); memMod mem2(.a(sb_intf2)); cpuMod cpu2(.b(sb_intf2)); endmodule
↔ Modports
A modport declares the signal directions as seen from a particular module role. Without a modport, all interface signals are accessible as inout (nets) or ref (variables). Modports add direction restrictions and — crucially — control which tasks and functions are visible through the connection.
interface i2; wire a, b, c, d; modport master (input a, b, output c, d); // master drives c,d; reads a,b modport slave (output a, b, input c, d); // slave drives a,b; reads c,d endinterface // Used in module header module m (i2.master i); ... endmodule // i2 with master directions module s (i2.slave i); ... endmodule module top; i2 i(); m u1(.i(i)); // interface instance connected to module with master modport s u2(.i(i)); // interface instance connected to module with slave modport endmodule
📋 Modport in Module Header vs Port Connection
The modport can be specified either in the module’s port declaration or in the instantiation’s port connection. Both are equivalent. If specified in both places, the two modport names must be identical.
Modport in the module header
// Modport fixed at module definition time module memMod(simple_bus.slave a); // always sees slave directions endmodule memMod mem(.a(sb_intf)); // no modport in connection
Modport in the port connection
// Module accepts any modport — direction specified at instantiation module memMod(simple_bus a); // access all signals endmodule // Instantiation specifies modport memMod mem(sb_intf.slave); // slave modport applied here cpuMod cpu(sb_intf.master); // master modport applied here
Nested modports — hierarchical interface
interface i1; interface i3; wire a, b, c, d; modport master (input a, b, output c, d); modport slave (output a, b, input c, d); endinterface i3 ch1(), ch2(); modport master2 (ch1.master, ch2.master); // modport of nested interfaces endinterface // Modport restriction: all names used must be declared by the SAME interface. // Cannot reference names from an enclosing interface or from nowhere.
📋 Modport Expressions
Like named port expressions on modules, modport entries can use array slices, struct members, or expressions as explicitly-named ports — visible only through that modport connection.
interface I; logic [7:0] r; const int x = 1; bit R; // .P maps to r[3:0] in modport A, to r[7:4] in modport B modport A (output .P(r[3:0]), input .Q(x), R); modport B (output .P(r[7:4]), input .Q(2), R); endinterface module M (interface i); initial i.P = i.Q; // P and Q mean different bits depending on modport endmodule module top; I i1; M u1(i1.A); // u1.i.P → i1.r[3:0], i1.i.Q → i1.x (=1) M u2(i1.B); // u2.i.P → i1.r[7:4], u2.i.Q → constant 2 initial #1 $display("%b", i1.r); // displays 00010010 endmodule
⏱ Modports and Clocking Blocks
A modport can include clocking blocks for synchronous testbench access alongside asynchronous DUT modports.
interface A_Bus(input bit clk); wire req, gnt; wire [7:0] addr, data; clocking sb @(posedge clk); input gnt; output req, addr; inout data; property p1; req ##[1:3] gnt; endproperty endclocking modport DUT (input clk, req, addr, output gnt, inout data); modport STB (clocking sb); // synchronous testbench modport TB (input gnt, output req, addr, inout data); // async TB endinterface program T (A_Bus.STB b1, A_Bus.STB b2); assert property (b1.p1); // assert property from inside program initial begin b1.sb.req <= 1; // synchronous drive via clocking block wait(b1.sb.gnt == 1); b1.sb.req <= 0; b2.sb.req <= 1; wait(b2.sb.gnt == 1); end endprogram
📋 Interfaces and Specify Blocks
When an interface is connected as a module port, each signal in the interface becomes an available timing-check terminal. Directions from the modport (or default inout) determine input/output role in the specify block. ref ports cannot be used as terminals.
interface itf; logic c, q, d; modport flop (input c, d, output q); endinterface module dtype (itf.flop ch); always_ff @(posedge ch.c) ch.q <= ch.d; specify (posedge ch.c => (ch.q+:ch.d)) = (5, 6); $setup(ch.d, posedge ch.c, 1); endspecify endmodule
▶ Tasks and Functions in Interfaces
Tasks and functions defined directly inside an interface are available to all connected modules, enabling transaction-level modelling without knowing the underlying signal details.
interface simple_bus (input bit clk); logic req, gnt; logic [7:0] addr, data; logic [1:0] mode; logic start, rdy; task masterRead(input logic[7:0] raddr); // ... set up req, addr; wait for gnt; read data endtask task slaveRead; // ... respond to req; drive data endtask endinterface // cpuMod calls the interface method directly module cpuMod(interface b); always @(posedge b.clk) if(instr == read) b.masterRead(raddr); // call interface task endmodule
📋 Import and Export via Modports
Modports can import tasks/functions defined in the interface (making them callable by the connected module) or export tasks/functions defined in the connected module (making them callable by other modules through the interface).
import — interface tasks available to modules
modport slave ( input req, addr, mode, start, clk, output gnt, rdy, ref data, import task slaveRead(), task slaveWrite()); // Module connected with slave modport // can call slaveRead() and slaveWrite() // through the interface
export — module tasks visible through interface
modport slave ( input req, addr, mode, start, clk, output gnt, rdy, ref data, export task Read(), task Write()); // The slave module MUST define // a.Read and a.Write tasks. // Other modules call them via the interface.
// Module defines the exported task using interface.task_name syntax module memMod(interface a); task a.Read; // defines Read for this specific interface connection // ... read implementation endtask task a.Write; // ... write implementation endtask endmodule // cpuMod calls the slave's exported Read/Write through the interface module cpuMod(interface b); always @(posedge b.clk) if(instr == read) b.Read(raddr); // calls memMod's implementation endmodule // Master: import requires full prototype modport master( ..., import task Read(input logic [7:0] raddr), task Write(input logic [7:0] waddr));
🔂 extern forkjoin — Multiple Exports
Normally only one module can export a given task. When multiple instances of the same module connect to one interface (e.g. two memory banks), use extern forkjoin to declare the task — calling it from the interface executes all connected implementations concurrently.
interface simple_bus (input bit clk); int slaves = 0; extern forkjoin task countSlaves(); // each connected module runs it extern forkjoin task Read (input logic[7:0] raddr); extern forkjoin task Write(input logic[7:0] waddr); modport slave (..., export Read, Write, countSlaves); initial begin slaves = 0; countSlaves; // runs mem1.a.countSlaves AND mem2.a.countSlaves $display("slaves = %d", slaves); // = 2 end endinterface module memMod #(parameter int minaddr=0, maxaddr=0) (interface a); task a.countSlaves(); a.slaves++; endtask task a.Read(input logic[7:0] raddr); if(raddr >= minaddr && raddr <= maxaddr) ... // only active memory responds endtask task a.Write(input logic[7:0] waddr); ... endtask endmodule module top; simple_bus sb_intf(clk); memMod #(0,127) mem1(sb_intf.slave); memMod #(128,255) mem2(sb_intf.slave); cpuMod cpu(sb_intf.master); endmodule // Disable behaviour: // disable sb_intf.Read → disables BOTH mem1 and mem2's Read tasks // disable mem1.a.Read → disables ONLY mem1's Read task
📋 Parameterised Interfaces
Interfaces support parameters the same way modules do — widths and types can be parameterised, with defaults overridden at instantiation.
interface simple_bus #(AWIDTH = 8, DWIDTH = 8) (input bit clk); logic req, gnt; logic [AWIDTH-1:0] addr; logic [DWIDTH-1:0] data; modport master(..., import task masterRead(input logic[AWIDTH-1:0] raddr)); endinterface module top; simple_bus sb_intf(clk); // default: 8-bit addr and data simple_bus #(.DWIDTH(16)) wide_intf(clk); // 16-bit data bus memMod mem (sb_intf.slave); cpuMod cpu (sb_intf.master); memMod memW(wide_intf.slave); // 16-bit wide memory cpuMod cpuW(wide_intf.master); // 16-bit wide cpu endmodule
📋 Virtual Interfaces
A virtual interface is a variable that holds a reference to an interface instance. It allows the same task or class method to operate on different interface instances at different times — decoupling testbench logic from specific signal paths.
// Declare a virtual interface variable virtual SBus bus; // default value: null — must be initialised before use // Assign to a real interface instance SBus s1(), s2(); bus = s1; // bus now refers to s1 bus = s2; // now refers to s2 bus = null; // detach // Reusable transactor — independent of which device it talks to class SBusTransactor; virtual SBus bus; // virtual interface as class property function new(virtual SBus s); bus = s; endfunction task request(); bus.req <= 1'b1; endtask task wait_for_bus(); @(posedge bus.grant); endtask endclass module top; SBus s[1:4] (); // 4 interface instances initial begin SBusTransactor t[1:4]; t[1] = new(s[1]); // each transactor bound to a different interface t[2] = new(s[2]); // test t[1:4] end endmodule
Virtual interface restrictions
- Must be initialised before use — accessing a null virtual interface is a fatal runtime error.
- Allowed operations: assignment (
=), equality (==/!=), comparison withnull. - Not permitted as ports, interface items, or union members.
- Once initialised, all interface components accessible via dot notation.
- Component access only in procedural statements — not in continuous assignments or sensitivity lists.
- To drive nets via virtual interface, the interface must provide a clocking block or a driver from a continuous assignment within the interface.
typedef virtual SBus VI; to give the virtual interface type a shorter name, especially when passing as task arguments: task do_it(VI v);
⏱ Virtual Interfaces with Clocking Blocks
interface SyncBus(input bit clk); wire a, b, c; clocking sb @(posedge clk); input a; output b; inout c; endclocking endinterface typedef virtual SyncBus VI; // shorthand type alias task do_it(VI v); if(v.sb.a == 1) v.sb.b <= 0; // synchronous access via clocking block else v.sb.c <= ##1 1; endtask module top; bit clk; SyncBus b1(clk), b2(clk); initial begin VI v[2] = {b1, b2}; // array of virtual interfaces repeat(20) do_it(v[$urandom_range(0,1)]); // randomly pick an interface end endmodule // With modport + virtual for abstract synchronous models: // typedef virtual A_Bus.STB SYNCTB; // task request(SYNCTB s); s.sb.req <= 1; endtask
📋 Access to Interface Objects
All objects in an interface are always accessible via hierarchical reference, regardless of whether a modport is in use. When a modport is specified, port reference access is restricted to the modport’s listed items. However, hierarchical access still works for everything.
interface ebus_i; integer I; // not in modport mp typedef enum {Y,N} choice; choice Q; parameter True = 1; modport mp(input Q); // only Q is accessible through modport endinterface module sub(interface.mp i); typedef i.choice yes_no; // import type from interface — hierarchical OK yes_no P; assign P = i.Q; // port reference — Q is in modport, OK initial Top.s1.Q = i.True; // hierarchical reference — always works initial Top.s1.I = 0; // hierarchical reference to I — OK // i.I = 0; // ILLEGAL: I not in modport mp endmodule
typedef), parameters, and any object can always be reached via hierarchical name from any scope where the interface is visible. Modport only restricts what can be accessed using the port variable (i.name) — it does not hide anything from hierarchical paths (Top.s1.name).
📋 Quick Reference
Interface capabilities at a glance
| Feature | How to use | Key point |
|---|---|---|
| Signal bundle | interface name; logic …; endinterface | All signals default inout/ref access |
| Interface port | interface name(input clk); … | Port signal shared externally via clk |
| Named interface port | module m(my_ifc port) | Must connect exact type |
| Generic interface port | module m(interface port) | Accepts any interface; named connection only |
| Modport signal dirs | modport mp(input a, output b) | Directions from the module’s perspective |
| Modport in header | module m(ifc.mp port) | Fixed at definition |
| Modport at instance | m u1(ifc_inst.mp) | Applied at instantiation |
| Import task | modport mp(…, import task t()) | Interface-defined task callable by module |
| Export task | modport mp(…, export task t()) | Module must define task; others call it via interface |
| forkjoin export | extern forkjoin task t() | Multiple modules export same task; all run concurrently |
| Parameterised | interface i #(W=8)(input clk) | Override with #(.W(16)) at instantiation |
| Virtual interface | virtual ifc_type var | Holds reference to interface instance; decouples testbench code |
