Part 1 · Language Foundations · Intermediate

Parameterized Interfaces

Width and type parameters, generate inside interfaces, matching DUT parameters, and what typed virtual interfaces mean downstream.

Parameters make one interface serve every configuration

Real buses come in families: the same protocol at 32 or 64 data bits, with or without parity, with ID widths set per SoC. An interface takes parameters exactly as a module does — value parameters for widths and feature counts, parameter type for payload types — and each distinct parameterization elaborates into its own specialization. The key mental shift: bus_if#(32) and bus_if#(64) are different types after elaboration, as different as two unrelated interfaces. Everything that names the interface type — ports, and especially virtual interface handles later — must name the same specialization, or the connection is a type error.

systemverilog
interface bus_if #(
  parameter int  ADDR_W = 32,
  parameter int  DATA_W = 64,
  parameter type meta_t = logic [3:0]    // type parameter
)(
  input logic clk
);
  logic              req, gnt;
  logic [ADDR_W-1:0] addr;
  logic [DATA_W-1:0] wdata, rdata;
  meta_t             meta;

  // generate: structure follows the parameters
  generate
    if (DATA_W > 32) begin : g_wide
      logic [DATA_W/8-1:0] byte_en;     // byte enables only when wide
    end
  endgenerate

  clocking cb @(posedge clk);
    default input #1step output #2;
    output req, addr, wdata, meta;
    input  gnt, rdata;
  endclocking
endinterface

Keeping interface and DUT parameters in lock-step

The DUT has the same width parameters, and the two sets must agree at every instantiation — a 64-bit interface driving a 32-bit DUT port truncates silently on some tools and errors on others, and neither outcome is one to discover at midnight. The robust pattern repeats the package discipline from the typedef lesson: widths live once in a package, and both the interface instantiation and the DUT instantiation read from it. Better still, forward the interface's own parameters into the DUT so disagreement is impossible by construction.

systemverilog
package soc_cfg_pkg;
  parameter int ADDR_W = 40;
  parameter int DATA_W = 128;
endpackage

module tb_top;
  import soc_cfg_pkg::*;
  logic clk = 0;  always #5 clk = ~clk;

  // one source of truth for BOTH sides:
  bus_if #(.ADDR_W(ADDR_W), .DATA_W(DATA_W)) bus (.clk(clk));

  dut #(
    .ADDR_W (ADDR_W),         // same package constants —
    .DATA_W (DATA_W)          // interface and DUT cannot diverge
  ) u_dut (.bus(bus));
endmodule

// DUT port: parameterized interface port carries its params
module dut #(
  parameter int ADDR_W = 32,
  parameter int DATA_W = 64
)(
  bus_if.dut bus      // tools check bus's params against usage
);
endmodule

Typed virtual interfaces — the downstream consequence

Parameterization echoes into the class world. A virtual interface handle is typed with the full specialization: virtual bus_if #(40, 128) vif; — and only an instance of exactly that specialization can be assigned to it. A UVM environment that hardcodes one specialization cannot grab a differently sized bus; the standard cures are parameterizing the component classes with the same widths, normalizing on package constants so there is effectively one specialization per bench, or wrapping per-width code behind a polymorphic API. The full treatment belongs to the OOP section; what matters here is that every width you parameterize becomes part of the interface's type identity , and downstream code must speak that exact type.

diagram
PARAMETERIZATION = TYPE IDENTITY

  bus_if #(.ADDR_W(32), .DATA_W(64))   ──►  type A
  bus_if #(.ADDR_W(40), .DATA_W(128))  ──►  type B   (UNRELATED to A)

  virtual bus_if #(32, 64) vif;
      vif = <instance of type A>;   
      vif = <instance of type B>;    compile error

  remedies:
    1. package constants  one specialization per bench
    2. parameterize the classes: class driver #(int ADDR_W, ...)
    3. polymorphic wrapper API hiding the width

Interview angle

  • "Are bus_if#(32) and bus_if#(64) compatible?" — no; each parameterization is a distinct type after elaboration.

  • "How do you keep DUT and interface widths in sync?" — one package source of truth feeding both instantiations.

  • "Why does parameterization complicate virtual interfaces?" — the vif must name the exact specialization; mismatches are type errors.

Key takeaways

  • Interfaces take value and type parameters like modules; generate adapts internal structure to them.

  • Each parameterization is a distinct type — ports and vif handles must name the exact specialization.

  • Feed interface and DUT parameters from one package so they cannot diverge.

  • Plan for typed-vif fallout early: package constants or parameterized classes, chosen per bench.

Common pitfalls

  • Connecting a #(64) interface to a DUT compiled for 32 bits — silent truncation or late elaboration error.

  • Hardcoding the vif specialization in a reusable class — the class is now welded to one bus width.

  • Letting two specializations of the same interface coexist in one bench unintentionally — components silently miss each other.

  • Forgetting that a type parameter change (meta_t) changes the interface type just as surely as a width does.