Skip to content

SystemVerilog · Module 8

Introduction to Interfaces

Why interfaces exist, signal bundling, the elimination of long port lists, and the backbone abstraction of every modern UVM testbench.

Module 8 · Page 8.1

Why port lists become unmanageable, how interfaces bundle signals into a single named connection, and how to write, instantiate, and use your first SystemVerilog interface. This page covers the problem interfaces solve, every part of an interface declaration, instantiation patterns, embedded tasks, the interface-vs-module-vs-struct decision, UVM/TB integration, five debugging labs from production projects, twelve interview questions, and ten best-practice rules.

The Problem Interfaces Solve

Imagine a simple APB bus. It has 10 signals: pclk, presetn, paddr, psel, penable, pwrite, pwdata, prdata, pready, and pslverr. Now your design has a master, a slave, a monitor, a driver, a scoreboard, and a testbench top. Every one of them needs all 10 signals.

Without interfaces, here is what your port list looks like for every single module:

SystemVerilog — without interfaces (painful)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// You write this SAME list in the master, slave, monitor, driver, and TB top
module apb_slave (
    input  logic        pclk,
    input  logic        presetn,
    input  logic [31:0] paddr,
    input  logic        psel,
    input  logic        penable,
    input  logic        pwrite,
    input  logic [31:0] pwdata,
    output logic [31:0] prdata,
    output logic        pready,
    output logic        pslverr
);
    // ... and again in every module that uses APB
endmodule

Now what happens when you add a new signal to the bus? You find and update every module's port list. Miss one and you have a compile error — or worse, a silent X. In a real project with dozens of modules, this is a constant source of bugs and rework.

  • Repeated port lists. The same 10-signal list is copy-pasted into every module. Adding or renaming one signal requires touching every file.
  • Direction confusion. The master drives paddr as an output; the slave receives it as input. Two modules, two different port declarations — easy to get them backwards.
  • Wire explosion in TB top. The testbench top must declare all individual wires and connect them one-by-one to every module. Ten signals × five modules = 50 wire declarations.
  • No reuse across projects. The bus protocol definition is scattered across every port list. There is no single source of truth for "what signals does APB have".

Interfaces solve all four problems by bundling the signals into a single named construct that you declare once and pass as a single connection everywhere.

What is an Interface?

An interface is a named bundle of signals — like a module, but its purpose is to carry connections between modules rather than to implement logic. You declare the signals inside it once, give the interface a name, and then pass that single name as a port to any module that needs those signals.

Think of it as a cable. An HDMI cable bundles dozens of wires — video, audio, power, control — into one connector. You plug one end into the TV, the other into the source. You do not manually route each wire separately. A SystemVerilog interface is exactly that cable, defined in software.

Declaring an Interface — Every Part Explained

The syntax is almost identical to a module. Replace module / endmodule with interface / endinterface.

SystemVerilog — APB interface declaration
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ① interface keyword + name + optional ports (usually just the clock)
interface apb_if (input logic pclk, presetn);
 
    // ② Signal declarations — no input/output here, just logic
    logic [31:0] paddr;
    logic        psel;
    logic        penable;
    logic        pwrite;
    logic [31:0] pwdata;
    logic [31:0] prdata;
    logic        pready;
    logic        pslverr;
 
    // ③ modport: defines directions for each side (covered in 8.2)
    modport master (input  pclk, presetn, prdata, pready, pslverr,
                    output paddr, psel, penable, pwrite, pwdata);
 
    modport slave  (input  pclk, presetn, paddr, psel, penable, pwrite, pwdata,
                    output prdata, pready, pslverr);
 
endinterface  // ← always endinterface, never endmodule

What each part means

The interface keyword opens the declaration. Ports in the port list are usually the clock and reset — signals driven from outside (not by either side of the bus). They can be passed straight through to the internal signal declarations.

Signal declarations inside an interface have no direction — they are not input or output here. Direction is specified per-module via modport. Without a modport, every signal is bidirectional by default.

The modport clauses define which signals each module sees as input vs output. The master module uses apb_if.master and sees prdata as input and paddr as output — exactly right. The slave sees the opposite. One interface, two views.

Instantiating an Interface & Connecting Modules

An interface is instantiated like a module — a single line in the parent. That instance is then passed to each sub-module as a port. From that module's perspective, it has one port that gives it access to the entire signal bundle.

SystemVerilog — testbench top instantiation
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_top;
    logic clk = 0;
    logic rst_n;
 
    // ── Step 1: Instantiate the interface ────────────────────────────
    apb_if apb (.pclk(clk), .presetn(rst_n));   // one line replaces 10 wires
 
    // ── Step 2: Connect modules via the interface ─────────────────────
    apb_master u_master (.bus(apb.master));    // master uses master modport
    apb_slave  u_slave  (.bus(apb.slave));     // slave  uses slave  modport
    apb_monitor u_mon   (.bus(apb));            // monitor uses full interface
 
    // ── Clock generation ─────────────────────────────────────────────
    always #5 clk = ~clk;
 
    initial begin
        rst_n = 0; #20 rst_n = 1;
    end
endmodule

Inside the master — how it receives the interface

SystemVerilog — module receiving an interface port
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Option A: Interface port with modport specified in the declaration ─
module apb_master (apb_if.master bus);    // modport enforced here
    always_ff @(posedge bus.pclk) begin
        if (!bus.presetn) begin
            bus.paddr   <= 0;
            bus.psel    <= 0;
            bus.penable <= 0;
        end else begin
            bus.paddr   <= 32'h4000_0000;   // access via dot notation
            bus.psel    <= 1;
            bus.penable <= 1;
            bus.pwrite  <= 1;
            bus.pwdata  <= 32'hA5A5_A5A5;
        end
    end
endmodule
 
 
// ── Option B: Generic interface port (modport not enforced) ────────────
module apb_monitor (apb_if bus);           // full interface — can read everything
    always @(posedge bus.pclk) begin
        if (bus.psel && bus.penable && bus.pready)
            $display("APB transfer: addr=%h data=%h write=%b",
                     bus.paddr, bus.pwrite ? bus.pwdata : bus.prdata, bus.pwrite);
    end
endmodule

Before and After — Side by Side

Here is the same testbench top — first without interfaces, then with. Read both. The difference makes the value of interfaces immediately obvious.

Without interfaces — 30 wire declarations and connections

SystemVerilog — TB top WITHOUT interface
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_top_old;
    // 10 wires declared individually
    logic clk, rst_n;
    logic [31:0] paddr;
    logic        psel, penable, pwrite;
    logic [31:0] pwdata, prdata;
    logic        pready, pslverr;
 
    // Master: 10 connections
    apb_master u_master (
        .pclk(clk), .presetn(rst_n), .paddr(paddr), .psel(psel),
        .penable(penable), .pwrite(pwrite), .pwdata(pwdata),
        .prdata(prdata), .pready(pready), .pslverr(pslverr)
    );
 
    // Slave: 10 more connections (same list, reversed directions)
    apb_slave u_slave (
        .pclk(clk), .presetn(rst_n), .paddr(paddr), .psel(psel),
        .penable(penable), .pwrite(pwrite), .pwdata(pwdata),
        .prdata(prdata), .pready(pready), .pslverr(pslverr)
    );
    // Monitor: 10 MORE — total: 30 connections. Adding a signal = 30 changes.
endmodule

With interfaces — 1 interface instance, 3 clean connections

SystemVerilog — TB top WITH interface
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_top_new;
    logic clk = 0, rst_n;
 
    // One interface instantiation replaces all 10 wires
    apb_if apb (.pclk(clk), .presetn(rst_n));
 
    // One connection per module — not ten
    apb_master  u_master (.bus(apb.master));
    apb_slave   u_slave  (.bus(apb.slave));
    apb_monitor u_mon    (.bus(apb));
 
    // Adding a signal to the bus: change ONLY the interface declaration.
    // Zero changes needed here or in any module.
endmodule

Tasks and Functions Inside an Interface

Interfaces can contain tasks and functions — these are protocol-level operations that any module connected via the interface can call. This is one of the most powerful and underused features of SystemVerilog interfaces. You write the APB transfer logic once, inside the interface, and call it from the driver, monitor, or any other TB component.

SystemVerilog — interface with embedded tasks
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interface apb_if (input logic pclk, presetn);
 
    logic [31:0] paddr, pwdata, prdata;
    logic        psel, penable, pwrite, pready, pslverr;
 
    // ── Task: perform one APB write ──────────────────────────────────
    task automatic write(
        input logic [31:0] addr,
        input logic [31:0] data
    );
        @(posedge pclk);
        paddr   = addr;  pwdata = data;
        psel    = 1;    pwrite = 1;
        @(posedge pclk);
        penable = 1;
        wait (pready);
        @(posedge pclk);
        psel = 0; penable = 0;
    endtask
 
    // ── Task: perform one APB read ───────────────────────────────────
    task automatic read(
        input  logic [31:0] addr,
        output logic [31:0] data
    );
        @(posedge pclk);
        paddr  = addr; psel = 1; pwrite = 0;
        @(posedge pclk);
        penable = 1;
        wait (pready);
        data = prdata;
        @(posedge pclk);
        psel = 0; penable = 0;
    endtask
 
    modport master (input  pclk, presetn, prdata, pready, pslverr,
                    output paddr, psel, penable, pwrite, pwdata,
                    import write, read);   // ← tasks visible through modport
 
endinterface
 
 
// Inside the driver — calling the interface task
module apb_driver (apb_if.master bus);
    initial begin
        bus.write(32'h4000_0000, 32'h1234_5678);  // one call, full APB transfer
        logic [31:0] rdata;
        bus.read(32'h4000_0000, rdata);
        $display("Read back: %h", rdata);
    end
endmodule

Interface vs Module vs Struct — When to Use Which

These three constructs can all group signals, but they serve different purposes.

Featureinterfacemodulestruct
PurposeBundle signals between modulesImplement hardware logicGroup data fields in memory
Has logic?Optional (tasks, functions, always)Yes — primary purposeNo — data only
Synthesisable?Signals inside are — tasks are notYesYes (packed struct)
modport support?Yes — defines per-module directionsNoNo
Passed as port?Yes — as a single portNo — connected by nameYes — as a packed data type
Use it forBus protocols, DUT↔TB connectionsRTL — counters, FSMs, datapathsPacket headers, register fields

Simple rule: if it connects two modules and carries dynamic signals during simulation, use an interface. If it groups fixed data fields (like a register map entry or a packet), use a struct.

What the Rest of Module 8 Covers

This page was the introduction. Here is the complete roadmap for Module 8:

  • 8.2 — Modport — Restricting Port Directions. How modport enforces correct signal directions for each side of a bus. Why it prevents accidental bidirectional connections. Named vs anonymous modports.
  • 8.3 — Clocking Blocks. Race-free testbench sampling using clocking blocks inside interfaces. Input/output skews, the ##N cycle-based delay operator, and why clocking blocks run in the Reactive region.
  • 8.4 — Virtual Interfaces. How class-based testbench components access interfaces using virtual interface handles. Why static interface handles do not work inside classes and how virtual interfaces fix that.
  • 8.5 — Interface Arrays & Parameterised Interfaces. Arrays of interface instances for multi-channel designs. Parameterising bus widths and depths directly in the interface declaration.

Quick Reference — Interface Syntax at a Glance

SystemVerilog — interface quick reference
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Declare an interface ──────────────────────────────────────────────
interface my_if (input logic clk, rst_n);
    logic [7:0] data;
    logic       valid, ready;
 
    // modport: restricts directions for each user
    modport source (input  clk, rst_n, ready,  output data, valid);
    modport sink   (input  clk, rst_n, data, valid, output ready);
endinterface
 
// ── Instantiate in TB top ─────────────────────────────────────────────
my_if bus (.clk(clk), .rst_n(rst_n));
 
// ── Connect modules ───────────────────────────────────────────────────
producer u_prod (.intf(bus.source));   // modport enforced
consumer u_cons (.intf(bus.sink));     // modport enforced
monitor  u_mon  (.intf(bus));           // full interface — no modport
 
// ── Module port declaration options ──────────────────────────────────
module producer (my_if.source intf);  // modport in port list
module monitor  (my_if        intf);  // full interface, no restriction
 
// ── Access signals inside a module ────────────────────────────────────
intf.data  = 8'hAB;
intf.valid = 1;
wait (intf.ready);
 
// ── Interface task (defined in interface, called from module) ─────────
task automatic send(input logic [7:0] d);
    // ... protocol logic
endtask
intf.send(8'h42);   // call from any connected module

Verification Usage — Interfaces as the TB/DUT Seam

In a UVM testbench the interface is the single physical connection point between the test code and the design. The DUT exposes the bus as a port; the testbench builds the same interface at top level; UVM components grab a virtual interface handle from the config database and drive the bus through it. Without interfaces, every signal would need its own virtual-interface-equivalent and the config_db lookup would be a nightmare.

SystemVerilog — Interface threading through TB top into UVM driver
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Step 1: declare the interface once, in a shared file ─────────
interface apb_if(input logic clk);
    logic [31:0] paddr, pwdata, prdata;
    logic        psel, penable, pwrite, pready;
endinterface
 
// ── Step 2: TB top declares the bus and wires DUT to it ─────────
module tb_top;
    logic clk;
    initial begin clk = 0; forever #5 clk = ~clk; end
 
    apb_if apb(.clk);
 
    apb_slave u_dut (.apb(apb));
 
    initial begin
        uvm_config_db#(virtual apb_if)::set(null, "*", "vif", apb);
        run_test();
    end
endmodule
 
// ── Step 3: UVM driver pulls the virtual interface handle ──────
class apb_driver extends uvm_driver;
    virtual apb_if vif;
 
    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
            `uvm_fatal("NOVIF", "No apb_if found in config_db")
    endfunction
 
    task run_phase(uvm_phase phase);
        forever begin
            apb_xact tr;
            seq_item_port.get_next_item(tr);
            vif.paddr  <= tr.paddr;
            vif.pwdata <= tr.pwdata;
            vif.pwrite <= tr.pwrite;
            vif.psel   <= 1'b1;
            @(posedge vif.clk);
            vif.penable <= 1'b1;
            @(posedge vif.clk);
            while (!vif.pready) @(posedge vif.clk);
            vif.psel    <= 1'b0;
            vif.penable <= 1'b0;
            seq_item_port.item_done();
        end
    endtask
endclass

Simulation Behavior — How Interfaces Connect at Elaboration

The elaborator treats an interface instantiation as a structural connection — exactly like a module instantiation, but with the special property that its signals are accessible from every connected module. There is no run-time machinery behind the abstraction; signals are wired structurally at elaboration and the simulator's signal database has them under the interface instance's hierarchical scope.

SystemVerilog — Probing the elaborated hierarchy at time 0
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interface bus_if;
    logic [7:0] data;
    logic       valid;
    initial $display("[%0t] %m elaborated", $time);
endinterface
 
module producer(bus_if bus);
    initial $display("[%0t] %m connected — bus path = %m", $time, bus);
endmodule
 
module tb;
    bus_if u_bus();
    producer u_p(.bus(u_bus));
endmodule
 
// Expected $display output at time 0:
//   [0] tb.u_bus elaborated
//   [0] tb.u_p connected — bus path = tb.u_bus
//
// Note: the interface has its own initial block and its own hierarchical
// node (tb.u_bus). It is treated identically to a sibling module.

Waveform Analysis — Interface Signals in the Viewer

Interface signals appear in the waveform viewer under the interface instance's hierarchical scope, indistinguishable from any other signal. Open Verdi, click into tb.u_apb, and you see paddr, pwdata, psel, … each with its own trace. They can be sampled, forced, dumped, and covered like any other signal — the interface is purely a syntactic grouping at the port boundary; the runtime treats each signal independently.

Expected waveform — APB write transaction through an interface
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cycle             0    1    2    3    4    5    6
 
clk             _│‾│_│‾│_│‾│_│‾│_│‾│_│‾│_│‾│_
 
tb.u_apb.paddr    --   100  100  100  100  --   --     ← setup + access phase
tb.u_apb.pwdata   --   AA   AA   AA   AA   --   --
tb.u_apb.pwrite   --   1    1    1    1    --   --
tb.u_apb.psel     0    1    1    1    1    0    0     ← active during setup+access
tb.u_apb.penable  0    0    1    1    1    0    0     ← active in access phase
tb.u_apb.pready   0    0    0    0    1    --   --     ← slave ready terminates
 
Reading guide:
  - Cycle 1: SETUP   (psel=1, penable=0) — addr/data/control valid
  - Cycle 2: ACCESS  (psel=1, penable=1) — slave starts processing
  - Cycle 4: PREADY  raised — transfer completes; signals deassert next edge
 
In Verdi, browse to tb.u_apb in the hierarchy panel; all 7 signals
appear together. Right-click → "Add to wave" adds the entire bundle.
This is the practical payoff of interfaces in debug.

Industry Insights — Interface Practices in Production Teams

Debugging Academy — 5 Real Interface Bugs

Each lab is a real failure mode pulled from production projects. Buggy code, symptom, root cause, fix.

1

Interface Instance Not Driven by Any Module — Floating Bus

UNDRIVEN BUS
Buggy Code
TB forgot to instantiate driver
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb;
    apb_if u_apb(.clk);
 
    // DUT connects to interface
    apb_slave u_dut(.apb(u_apb));
 
    // TB forgot to instantiate the driver / agent
    // Interface signals are never driven by anyone
 
    initial begin
        #100ns;
        if (u_dut.last_addr != 32'hABCD) $error("DUT didn't see address");
    end
endmodule
Symptom

All interface signals show as X in the waveform from time 0 onward. The DUT sees X on psel, penable, etc., and either hangs at reset or its outputs all go to X. The error message is misleading because the test never even started.

Root Cause

An interface instance with no driving module is purely a passive bundle of X-initialised storage. The TB declared the bus but forgot to instantiate either a UVM agent or a stimulus driver that writes to u_apb.* signals.

Fix

Always confirm interface signals are driven by adding an early-time-zero assertion: initial #1 assert (!$isunknown(u_apb.psel)) else $fatal("APB never initialised");. Better: use a UVM monitor with a "no traffic after N cycles" timeout that fires loud and clear when nothing happens. Best: have the testbench framework instantiate a default-empty-stimulus driver that at least drives idle values onto the bus during reset.

2

Multi-Driver Conflict — Two Modules Driving the Same Interface Signal

MULTI-DRIVER
Buggy Code
Two masters on one interface
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interface apb_if(input logic clk);
    logic [31:0] paddr, pwdata, prdata;
    logic        psel, penable, pwrite, pready;
endinterface
 
module tb;
    apb_if u_apb(.clk);
 
    apb_master u_drv(.apb(u_apb));   // drives paddr, pwdata, etc.
 
    apb_master u_drv2(.apb(u_apb));  // ← ALSO drives the same signals!
 
    apb_slave u_dut(.apb(u_apb));
endmodule
Symptom

paddr, pwdata, etc. show X in the waveform whenever both drivers are active. Without modports, the simulator allows both drivers to write the same wire; the resolved value is X. Tests pass when only one driver is doing anything; fail randomly when both fire near the same edge.

Root Cause

An interface signal can be driven by any module that connects to it. Without modport restrictions, the language doesn't prevent two writers from stomping on each other. The bug is invisible at elaboration; it surfaces only when both drivers are concurrently active.

Fix

Use modports (covered in 8.2) to restrict who can drive what. The interface declares a master modport with output direction on the address / data / control signals; connect at most one module via that modport. The compiler then errors at elaboration if a second module attaches to the master modport. Lint tools (Spyglass multi-driver) catch this even before elaboration.

3

Interface Signal Width Mismatch After Refactor

SIZE DRIFT
Buggy Code
Bus widened, consumer not updated
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Refactor: AXI bus widened from 64 to 128 in the interface
interface axi_if(input logic clk);
    logic [127:0] wdata;        // was [63:0]
    logic [127:0] rdata;        // was [63:0]
    // ... other signals
endinterface
 
// Consumer hardcoded a 64-bit local buffer:
module data_path(axi_if axi);
    logic [63:0] reg_buf;
    always_ff @(posedge axi.clk) reg_buf <= axi.wdata;  // silent truncation
endmodule
Symptom

DUT silently truncates the upper 64 bits of every write. Test cases that only exercise the lower bits pass; tests that target the upper half fail. The error pattern (failures on the most-significant bits but not the least-significant) is the giveaway.

Root Cause

The interface declaration changed but a consumer's internal signal didn't. SystemVerilog's implicit width conversion silently truncates the wider assignment to fit the narrower target. No compile error, only a warning that gets suppressed in build logs.

Fix

Two-step. (1) Parameterise the interface (covered in 8.5): interface axi_if #(parameter int DW = 128)(...);. Consumers declare their internal widths in terms of the same parameter: logic [DW-1:0] reg_buf;. Both sides change in lockstep when the width changes. (2) Enable width-mismatch as an error in CI (VCS +lint=PCWM-L -error=PCWM-L, Questa -pedanticerrors). Any future width refactor surfaces every truncation at the next build.

4

Interface Task with Race Against External Driver

DELTA-CYCLE RACE
Buggy Code
Blocking task vs NBA external driver
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interface apb_if(input logic clk);
    logic psel, penable, pwrite, pready;
    logic [31:0] paddr, pwdata, prdata;
 
    // Helper task inside the interface
    task automatic write(input logic [31:0] a, d);
        @(posedge clk);
        paddr  = a;            // blocking assign on interface signal
        pwdata = d;
        psel   = 1;
        // ...
    endtask
endinterface
 
// External driver also writes the same signals in always_ff
module ext_driver(apb_if apb);
    always_ff @(posedge apb.clk)
        if (en) apb.paddr <= other_addr;  // non-blocking, also drives paddr
endmodule
Symptom

paddr sometimes carries the task's value, sometimes the external driver's, depending on simulator scheduling order in the Active region. Test results are non-deterministic across simulator versions.

Root Cause

The interface task uses blocking assigns (Active region) on a signal that an external module drives via non-blocking (NBA region). When both fire at the same edge, the result depends on which side the scheduler runs first.

Fix

Apply the "one driver per signal" rule rigorously: the task and the external module cannot both drive paddr. Use modports to declare which module owns the signal, and structure the testbench so only one source ever writes it. If both must contribute, use a multiplexer signal in the interface and have each source drive its own input, with mux-select picking the actual paddr.

5

Interface Used in RTL Synthesis Path — Tool Drops Tasks Silently

SIM/SYNTH GAP
Buggy Code
RTL bridge calling a timing-control task
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interface apb_if(input logic clk);
    logic psel, penable, ...;
 
    // Helper task used by both TB and an RTL bridge
    task automatic single_write(input logic [31:0] a, d);
        @(posedge clk); paddr <= a; pwdata <= d; psel <= 1;
        @(posedge clk); penable <= 1;
        while (!pready) @(posedge clk);
        psel <= 0; penable <= 0;
    endtask
endinterface
 
// RTL bridge calls the task internally:
module dma_bridge(apb_if.master apb);
    always @(posedge axi_req)
        apb.single_write(axi_addr, axi_data);   // ← synthesis WARNS, drops
endmodule
Symptom

RTL simulation passes. Synthesis "succeeds" with a buried warning: "task call ignored in synthesis context." The gate-level netlist never asserts psel or penable at all. Silicon-level DMA writes are dropped. Bug surfaces at SoC validation as "DMA never reaches the slave."

Root Cause

Tasks containing timing controls (@, #, wait) are not synthesisable. The synthesis tool drops them and warns; the warning lives in a 50000-line log and gets ignored. The simulation behaviour and the synthesised behaviour diverge silently.

Fix

Two layered defences. (1) Put interface tasks behind a verification guard: `ifndef SYNTHESIS task ... endtask `endif — synthesis can't even see the task. (2) RTL bridges should implement the protocol with an always_ff state machine, not by calling a TB task. The task stays for testbench use; RTL uses the explicit FSM. (3) Promote "task-call-in-synth" warning to error in your synthesis flow; refuse to ship a netlist that triggers it.

Interview Q&A — 12 Questions on Interfaces

Drawn from real interviews at chip-design and verification companies. Try to answer before reading each response.

An interface is a named bundle of signals (and optionally tasks/functions) that encapsulates a communication protocol. It solves the "long port list" problem of Verilog: instead of declaring 30+ AXI ports on every module and wiring all 30 at every instantiation, you declare them once in interface axi_if;, then every module accepts axi_if axi as a single port and every parent wires .axi(u_axi). Eliminates typos at the bus boundary; makes spec changes (a new signal) a one-place edit.

Best Practices — Interface Rules to Walk Away With

  1. One interface per protocol, not per block. Define axi_if once for the entire SoC. Per-block interfaces inevitably drift.
  2. Naming convention is contractual. Interface type = {protocol}_if; instance = {role}_{protocol}_if; virtual handle = vif. Enforce in code review.
  3. Add a header contract comment. Protocol name, spec version, modport list, parameter contract. The first 20 lines of every interface file.
  4. Always declare a monitor modport. All signals inputs, no drives — locks down passive observers and prevents the "monitor drove the bus" bug class.
  5. Parameterise widths in the interface, not the consumers. Covered in 8.5. Width changes propagate from one place.
  6. Don't bundle unrelated signals. Two protocols = two interfaces. The bus stays clean.
  7. Guard tasks behind `ifndef SYNTHESIS. Or move them entirely to a verification package. Synthesis silently drops timing-control tasks.
  8. Save waveform groups for each interface. Verdi/DVE group definitions live in the repo. Debug starts faster.
  9. Add a time-0 assertion that signals are not X. initial #1 assert (!$isunknown(intf.psel)); — catches floating-bus / no-driver mistakes immediately.
  10. Use modports the moment you have two connected modules. Don't wait until a multi-driver bug bites; declare master/slave/monitor on day one (8.2).