Part 2 · OOP for Verification · Intermediate

Why Virtual Interfaces Exist

The static instance world vs the dynamic class world, the binding problem, and what a vif actually stores.

Two worlds that cannot see each other

SystemVerilog has two object lifetimes. The static world — modules, interfaces, nets — is built once during elaboration , before simulation time zero. Every instance has a fixed hierarchical path like tb_top.bif, known at compile time. The dynamic world — class objects — is created at runtime with new(), lives on the heap, has no hierarchical path, and can be created in any quantity at any time.

The problem: a driver class needs to wiggle DUT pins, but it cannot contain an interface instance (classes cannot instantiate static objects), and hard-coding a hierarchical path like tb_top.bif.addr inside the class would weld the class to one specific testbench — destroying reuse, which was the entire reason to use classes. The class needs a way to be pointed at some interface instance, decided at runtime, without naming it in source.

diagram
THE BINDING PROBLEM

  STATIC WORLD                       DYNAMIC WORLD
  (fixed at elaboration)             (created at runtime)
  ──────────────────────             ─────────────────────
  tb_top                              env = new()
   ├─ bus_if bif0 (clk)               ├─ drv = new()
   ├─ bus_if bif1 (clk)               │    needs pins... WHICH pins?
   └─ dut u_dut (bif0, bif1)          └─ mon = new()
                                           needs pins... WHICH pins?
        │                                     ▲
        │     virtual bus_if vif = bif0;      │
        └────────────── BINDING ──────────────┘
              a runtime reference from the
              dynamic world to one static instance

What a virtual interface actually stores

A virtual bus_if variable stores a reference (conceptually a pointer) to one elaborated interface instance. It does not copy the interface, does not create a new one, and weighs nothing — assigning it copies only the reference, exactly like copying a class handle. Two drivers holding the same vif see the same signals; reassigning a vif retargets the class to different pins without touching class source code. Before any assignment its value is null, and dereferencing it crashes — the subject of the next lesson.

systemverilog
interface bus_if (input logic clk);
  logic [31:0] addr;
  logic [31:0] data;
  logic        valid;
endinterface

class driver;
  virtual bus_if vif;   // a REFERENCE — null until assigned

  task run();
    forever begin
      @(posedge vif.clk);     // dereference: follows the reference
      vif.addr  <= $urandom;  // drives the REAL tb_top signal
      vif.valid <= 1'b1;
    end
  endtask
endclass

Reference semantics in action

systemverilog
module tb_top;
  logic clk;
  bus_if bif0 (clk);
  bus_if bif1 (clk);

  initial begin
    driver d = new();
    d.vif = bif0;        // d now drives bif0's signals
    // ... later, same object, different pins:
    d.vif = bif1;        // retargeted — zero class changes
    // also legal: two objects sharing one instance
    // monitor m = new(); m.vif = bif0;
  end
endmodule

Why this design and not alternatives

What the language could have done instead

  • Hierarchical paths in classes (tb_top.bif.addr) — compiles, but the class is unusable in any other testbench or for a second interface instance. Reuse dies.

  • Passing hundreds of individual signals as task arguments — unmanageable, and loses interface-level grouping, modports, and clocking blocks.

  • Instantiating the interface inside the class — illegal; static instances need elaboration-time positions, classes do not have one.

The vif gets the best of both: the interface keeps its static-world privileges (synthesizable connection to the DUT, clocking blocks, assertions inside it), while class code gets a late-bound, swappable reference. Interview angle: 'why can't a class just contain an interface?' is the classic phrasing — the answer is the lifetime mismatch above, and the follow-up 'so what does the vif store?' expects the word reference .

Key takeaways

  • Static world is fixed at elaboration; class objects are created at runtime — a vif bridges them.

  • A vif stores a reference to one elaborated interface instance, never a copy.

  • Reassigning a vif retargets a class to different pins with zero source changes — that is the reuse win.

  • An unassigned vif is null; every pin access through it is a dereference.

Common pitfalls

  • Hard-coding hierarchical paths inside classes — works once, kills reuse forever.

  • Thinking the vif copies the interface — two objects with the same vif share the same signals.

  • Trying to instantiate an interface inside a class — illegal; lifetimes are incompatible.

  • Comparing vifs across different parameterizations — differently parameterized interfaces are different vif types (covered in multi-instance lesson).