Part 2 · OOP for Verification · Intermediate
Multi-Instance & Parameterized vifs
Arrays of vifs for multi-port DUTs, why parameterized interfaces make vif types distinct, and width strategies.
Arrays of vifs for multi-port DUTs
A 4-port switch needs four interface instances and four agents — but you do not write four binding paths. Because a vif is just a variable, you can build an array of vifs , fill each element from the corresponding static instance, and loop over agents. One wrinkle: the static instances themselves (bus_if bif[4](clk) via generate or an instance array) are hierarchy, not variables — you cannot assign the whole array in one statement. The standard idiom collects them element-by-element (or via a small generate block) into the dynamic array, then everything downstream is a plain loop.
module tb_top;
logic clk = 0;
always #5 clk = ~clk;
// Four REAL instances wired to the 4-port DUT
bus_if bif[4] (clk);
// Dynamic side: an array of references
virtual bus_if vifs[4];
initial begin
env e;
// collect static instances into the vif array
vifs[0] = bif[0]; vifs[1] = bif[1];
vifs[2] = bif[2]; vifs[3] = bif[3];
e = new(vifs);
e.run();
end
endmodule
class env;
agent agts[4];
function new(virtual bus_if vifs[4]);
foreach (agts[i])
agts[i] = new(vifs[i]); // per-port binding, one loop
endfunction
endclassMULTI-PORT BINDING
STATIC DYNAMIC
bif[0] ──────────────────────► vifs[0] ──► agts[0] (drv+mon)
bif[1] ──────────────────────► vifs[1] ──► agts[1]
bif[2] ──────────────────────► vifs[2] ──► agts[2]
bif[3] ──────────────────────► vifs[3] ──► agts[3]
│ │
hierarchy (generate/array variables (assignable,
instances, fixed at elab) loopable, passable)Parameterized interfaces make vif types distinct
Interfaces take parameters just like modules — bus_if #(.WIDTH(64)). The consequence mirrors parameterized classes: each parameterization is a distinct type , and so is the matching vif. A virtual bus_if #(32) cannot hold a reference to a bus_if #(64) instance, the two vifs cannot cross-assign, and they cannot share one array. The compiler is protecting you — a 32-bit driver blindly attached to 64-bit pins would be wrong — but it means 'one driver class for all widths' does not happen by accident; you must choose a strategy.
interface bus_if #(int WIDTH = 32) (input logic clk);
logic [WIDTH-1:0] data;
logic valid;
endinterface
module tb_top;
logic clk;
bus_if #(32) bif32 (clk);
bus_if #(64) bif64 (clk);
virtual bus_if #(32) v32;
virtual bus_if #(64) v64;
initial begin
v32 = bif32; // OK — types match exactly
// v32 = bif64; // COMPILE ERROR: distinct types
// v64 = v32; // COMPILE ERROR: vifs don't cross-assign
end
endmoduleWidth strategies
Strategy 1: typedef per width + parameterized components
Embrace the distinct types: typedef virtual bus_if #(32) vif32_t; in a package, and make the driver/monitor/agent classes parameterized with the same WIDTH so each specialization carries the matching vif type. Fully type-safe — a width mismatch is a compile error — at the cost of one class specialization per width (and remember from parameterized classes: separate statics per specialization).
class driver #(int WIDTH = 32);
typedef virtual bus_if #(WIDTH) vif_t;
vif_t vif;
function new(vif_t vif);
this.vif = vif;
endfunction
task drive(bit [WIDTH-1:0] d);
@(posedge vif.clk);
vif.data <= d; // width checked at compile time
vif.valid <= 1'b1;
endtask
endclass
driver #(32) d32 = new(bif32);
driver #(64) d64 = new(bif64);Strategy 2: generic max-width interface
Declare the interface at the maximum width the project needs and carry the active width as a runtime field (in the interface or config). Every port then shares one interface type, one vif type, and one unparameterized agent — arrays of vifs across mixed-width ports work again. The cost: width checking moves from compile time to your own runtime checks, and unused upper bits must be tied off and masked. Interview angle: 'your DUT has 32- and 64-bit ports — one agent or two?' has no single right answer; strong candidates present exactly this trade-off (type safety per width vs uniform reusable infrastructure) and pick based on how many widths and how much sharing the project needs.
Key takeaways
Arrays of vifs plus a foreach loop scale binding to N-port DUTs cleanly.
Static instance arrays are hierarchy — collect them into vif variables element-wise, then loop.
Each interface parameterization is a distinct type; its vif type is equally distinct — no cross-assignment.
Choose deliberately: typedef-per-width for compile-time safety, generic max-width for uniform infrastructure.
Common pitfalls
Assuming an instance array assigns to a vif array in one statement — collect element-by-element.
Mixing vifs of different parameterizations in one array — compile error; they are unrelated types.
Choosing max-width-generic and forgetting runtime width checks — silent truncation on narrow ports.
Forgetting that parameterized component classes get separate static members per specialization.