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.
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
endinterfaceKeeping 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.
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
);
endmoduleTyped 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.
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 widthInterview 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.