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.

diagram
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

systemverilog
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
endmodule

Everything 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

  1. "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.

  2. "What does virtual mean in virtual bus_if?" — a handle/reference to an elaborated instance; unrelated to virtual methods.

  3. "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.