Part 1 · Language Foundations · Intermediate

Interface Basics

Bundling signals, instantiating interfaces, connecting to module ports, and generic interface ports — with a before/after refactor.

The problem interfaces solve

A bus protocol is one logical thing, but classic Verilog forces you to shred it into individual ports, repeat that port list on every module that touches the bus, and wire each signal by hand at every level of hierarchy. Each repetition is a chance to swap two signals, mis-size one, or forget one — and the compiler happily accepts most such mistakes because each net is just a net. An interface restores the protocol's identity: declare the signal bundle once, instantiate it like a module, and pass the single instance through ports. The signals physically travel together, so the class of cross-wiring bugs disappears at the language level, not by code-review vigilance.

systemverilog
// BEFORE: every signal a separate port, repeated everywhere
module dut_old (
  input  logic        clk, rst_n,
  input  logic        req,
  output logic        gnt,
  input  logic [31:0] addr,
  inout  wire  [63:0] data,
  output logic        err
);
  // ...
endmodule
// tb_old must redeclare the same 7 nets AND connect all 7, twice

// AFTER: the protocol is one named thing
interface bus_if (input logic clk, input logic rst_n);
  logic        req;
  logic        gnt;
  logic [31:0] addr;
  wire  [63:0] data;
  logic        err;
endinterface

module dut (bus_if bus);          // one port
  assign bus.gnt = bus.req;       // dot into the bundle
endmodule

Instantiating and connecting

An interface is instantiated exactly like a module — it elaborates into real nets and variables living at the instantiation point. The instance then connects to module ports declared with the interface type. Inside the module, members are referenced with dot notation. Note the interface's own ports (here clk, rst_n) — clocks and resets usually enter the interface as ports so the interface can host clocking blocks and assertions that need them.

systemverilog
module tb_top;
  logic clk = 0, rst_n;
  always #5 clk = ~clk;

  bus_if bus (.clk(clk), .rst_n(rst_n));   // instantiate the bundle

  dut        u_dut (.bus(bus));            // one connection, not seven
  tb_driver  u_drv (.bus(bus));            // same instance shared

  initial begin
    rst_n = 0; repeat (4) @(posedge clk); rst_n = 1;
  end
endmodule

module tb_driver (bus_if bus);
  initial begin
    wait (bus.rst_n);
    @(posedge bus.clk);
    bus.req  <= 1'b1;
    bus.addr <= 32'h0000_1000;
  end
endmodule
diagram
CONNECTION TOPOLOGY

  tb_top
   ├── bus_if bus ──────────────┐   one instance,
   │     req gnt addr data err  │   real nets live here
   │                            │
   ├── dut       u_dut (.bus(bus))   sees bus.req, bus.gnt...
   └── tb_driver u_drv (.bus(bus))   sees the SAME nets
                                       no per-signal wiring,
                                        no swap/missing-wire bugs

Generic interface ports

A module port may be declared with the keyword interface instead of a specific interface name, accepting any interface that provides the members the module uses. This duck-typed flexibility is occasionally useful for utility modules (a generic protocol monitor), but it costs early type checking — errors surface at elaboration of a particular connection instead of at the module's own compile. Most teams use named interface ports everywhere and reserve generic ports for deliberately polymorphic infrastructure. Elaboration semantics worth knowing for interviews: each interface instance is a distinct copy of the nets; two modules communicate only when handed the same instance, and an interface can itself instantiate other interfaces.

systemverilog
// generic: accepts ANY interface with .clk and .err
module err_counter (interface i);
  int count;
  always @(posedge i.clk)
    if (i.err) count++;
endmodule

// named (preferred): compile-time checked against bus_if
module bus_monitor (bus_if bus);
  always @(posedge bus.clk)
    if (bus.req && bus.gnt)
      $display("[%0t] grant addr=%h", $time, bus.addr);
endmodule

Interview angle

  • "What problems do interfaces solve?" — port-list duplication, cross-wiring bugs, and a home for shared timing/assertion constructs.

  • "Is an interface a module?" — it elaborates like one (instance = real nets) but cannot contain module instances of hardware design intent; it is connective tissue.

  • "Named vs generic interface port?" — named gives compile-time checking; generic defers to elaboration and is for polymorphic utilities only.

Key takeaways

  • Declare the bus once as an interface; instantiate it like a module; connect one port instead of N signals.

  • All modules holding the same instance share the same physical nets — communication by construction.

  • Bring clk/rst into the interface through its own ports so clocking blocks and assertions can live inside.

  • Prefer named interface ports; generic 'interface' ports trade type safety for polymorphism.

Common pitfalls

  • Instantiating two bus_if instances and connecting DUT to one, TB to the other — both compile, nothing communicates.

  • Forgetting that each instance is a separate copy of the nets — interfaces are not global namespaces.

  • Leaving clk outside the interface — clocking blocks and concurrent assertions inside it then have no clock.

  • Overusing generic interface ports — connection errors surface late and read cryptically.