Part 1 · Language Foundations · Intermediate
Why Classes Need virtual interface
The static vs dynamic world divide, a minimal virtual interface example, and the pointer to the OOP section for full depth.
Two worlds that elaborate differently
Everything in this topic so far lives in the static world : modules and interfaces are elaborated once, before time zero, into a fixed hierarchy of real nets — nothing is created or destroyed afterward. Classes live in the dynamic world : objects are constructed with new() at runtime, in any number, and garbage-collected when unreferenced. The two worlds cannot reference each other symmetrically. A module cannot instantiate a class object at elaboration as hardware, and — the half that matters here — a class cannot declare an interface instance, because a class body is a runtime template and an interface instance is elaboration-time structure. Yet the entire point of a class-based driver is to wiggle pins that exist only in the static hierarchy. The bridge is the virtual interface: a class-side handle (a reference) whose value is set at runtime to point at one real, elaborated interface instance.
STATIC WORLD DYNAMIC WORLD
(elaborated once, before t=0) (constructed at runtime)
tb_top class driver;
├── bus_if bus ◄────────┐ virtual bus_if vif; ──┐
│ real nets: │ │
│ req gnt addr ... └────────────────────────────────────┘
│ handle assignment at runtime:
└── dut u_dut (.bus(bus)) drv.vif = <points at tb_top.bus>
"virtual" here means: a HANDLE to an interface instance,
not the instance itself. (Unrelated to virtual methods/classes.)The keyword overloads badly and interviewers know it: virtual on an interface declaration inside a class has nothing to do with virtual methods or virtual classes. It simply marks the declaration as a reference to an interface rather than an instance of one — closer in spirit to a class handle than to polymorphism.
Minimal working example
interface bus_if (input logic clk);
logic req, gnt;
clocking cb @(posedge clk);
default input #1step output #2;
output req;
input gnt;
endclocking
endinterface
class driver;
virtual bus_if vif; // handle: null until assigned
function new(virtual bus_if vif_in);
vif = vif_in; // runtime binding to a real instance
endfunction
task run();
@(vif.cb); // same clocking discipline as before
vif.cb.req <= 1'b1;
@(vif.cb iff vif.cb.gnt);
vif.cb.req <= 1'b0;
endtask
endclass
module tb_top;
logic clk = 0; always #5 clk = ~clk;
bus_if bus (.clk(clk)); // static instance: real nets
initial begin
driver drv = new(bus); // implicit conversion: instance → handle
drv.run(); // class wiggles tb_top.bus pins
end
endmoduleEverything learned in this topic carries through the handle unchanged: vif.cb gives the class the same race-free sampling and driving, interface methods are callable as vif.read(a, d), and a parameterized interface demands that the handle name the exact specialization (the typed-vif consequence from the parameterization lesson). The one new failure mode is the handle itself: it starts null, and the first vif.cb on an unassigned handle is a fatal null-access at runtime — the single most common day-one UVM crash.
Where this goes next
Passing one handle through one constructor scales to exactly one driver. A real environment has dozens of components needing handles, constructed in a hierarchy that should not thread interface arguments through every level — which is the problem the UVM configuration database solves (uvm_config_db#(virtual bus_if)), covered with full depth in the OOP and UVM sections: handle distribution patterns, null-checking discipline in build_phase, multi-instance benches mapping arrays of interfaces to arrays of agents, and the parameterized-vif strategies sketched earlier. The foundation to carry forward from here is the mental model: static world owns the pins, dynamic world holds references, and the virtual interface is the one sanctioned doorway between them.
Interview angle
"Why can't a class just instantiate an interface?" — interfaces are elaboration-time structure; classes are runtime objects; a class can only hold a reference.
"What does virtual mean in virtual bus_if?" — a handle/reference to an elaborated instance; unrelated to virtual methods.
"Most common vif bug?" — using a null handle because nothing assigned it before run-time access.
Key takeaways
Static world: elaborated-once modules/interfaces with real nets. Dynamic world: runtime class objects.
virtual interface = a class-side handle bound at runtime to one elaborated interface instance.
Clocking blocks, methods, and parameterization rules all apply unchanged through the handle.
Handles start null — assign before use, and expect the config-db distribution pattern in UVM.
Common pitfalls
Accessing vif before assignment — null-handle fatal at runtime, the classic first UVM crash.
Reading virtual as polymorphism — it only marks a reference, nothing is overridable about it.
Declaring a plain (non-virtual) bus_if inside a class — illegal; classes cannot contain instances.
Hardcoding a parameterized vif specialization in a reusable class — welded to one bus configuration.