Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
    • Classes, Objects & Handles
    • Inheritance and Polymorphism
    • Static, Encapsulation & Parameterized Classes
    • Virtual Interfaces: the Class-to-Pins Bridge
      • Why Virtual Interfaces Exist
      • Declaring & Binding a vif
      • Driving & Sampling via Clocking Blocks
      • vif Configuration Patterns
      • Multi-Instance & Parameterized vifs
    • Events, Semaphores, Mailboxes and Processes
    • OOP Patterns for Testbenches
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Virtual Interfaces: the Class-to-Pins Bridge
  1. Topics
  2. SystemVerilog
  3. Object-Oriented SystemVerilog for Verification
  4. Virtual Interfaces: the Class-to-Pins Bridge
  5. Why Virtual Interfaces Exist

Part 2 · OOP for Verification · Intermediate

Why Virtual Interfaces Exist

Static versus dynamic worlds, the binding problem, and what a virtual interface reference stores.

On this page

    Why this matters

    A driver class worked in one testbench and could not be reused in the next, because it reached its pins through a hard-coded hierarchical path. A virtual interface hands the class its pins instead.

    After this lesson you can: explain why a class cannot hold an interface, say what a virtual interface stores, and retarget a driver without changing its code.

    Two worlds that cannot see each other

    Modules and interfaces are elaborated before time zero. Every instance owns a fixed hierarchical path.

    Class objects are created later by new(). They live on the heap with no hierarchical path and no pins.

    Workaround

    Why it fails

    Hierarchical paths in class code (tb_top.bif.addr)

    Welds the class to one testbench, so a second instance cannot reuse it.

    Passing every signal as a task argument

    Hundreds of ports with no grouping, no modports, and no clocking blocks.

    Instantiating an interface inside the class

    Illegal: static instances need an elaboration-time position, which classes lack.

    What a virtual interface stores

    A virtual bus_if variable stores a reference to one elaborated interface instance. Assigning it copies only the handle, never the signals.

    Two objects holding the same vif observe the same pins. Reassigning a vif retargets the object without touching class code.

    Before any assignment the vif is null, and every pin access through it is a dereference. The raw vif.addr drive in the bench uses <=, so DUT flops on that edge still capture the old value. Driving & Sampling via Clocking Blocks moves every drive onto vif.cb, which makes the timing explicit and the same for every component.

    Common mistakes

    Common pitfall

    Hard-coding a hierarchical path inside a class binds once and kills reuse. Thinking the vif copies the interface hides sharing bugs: two objects with the same vif drive the same physical signals.

    Interview

    Likely questions: “Why can a class not contain an interface?” and “What does a virtual interface store?” The answers are the lifetime mismatch (static elaboration versus runtime creation) and one word: a reference.

    Check yourself One driver object must verify two interface instances in turn. What changes in the class?

    Nothing. Reassign its vif from the first instance to the second. The class holds a reference, so retargeting needs no source change.

    Check yourself Two drivers hold the same vif and both drive addr. What does the DUT see?

    One contested signal. Both objects drive the same physical pins through the shared reference, so the testbench has a multiple-driver bug, not two independent streams.

    Testbench

    The listing below binds one driver, retargets it, and checks that only the second instance is driven. Two words appear before their lessons: $urandom returns a random 32-bit number, and fork ... join_none starts d.run() in the background and carries on at once.

    Predict first The driver's vif is set to bif0 and then to bif1 before run() starts. After 100 ns, which instance's valid is 1?

    Only bif1's. The vif holds one reference, and the second assignment replaced the first before any drive happened.

    bus_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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);    // follows the reference to the real instance
          vif.addr  <= $urandom; // raw NBA drive; the clocking lesson moves this onto vif.cb
          vif.valid <= 1'b1;
        end
      endtask
    endclass
    
    module tb_top;
      timeunit 1ns;
      timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      bus_if bif0 (clk);
      bus_if bif1 (clk);
    
      initial begin
        driver d;       // declare first: an initializer here would run at time 0
        d = new();
        d.vif = bif0;   // d now drives bif0's signals
        d.vif = bif1;   // retargeted -- zero class changes
        fork d.run(); join_none
        #100;
        if (bif1.valid !== 1'b1 || bif0.valid === 1'b1)
          $fatal(1, "FAIL: retargeted vif did not move the drive to bif1");
        $display("PASS: retargeted vif drives bif1 and leaves bif0 alone");
        $finish;
      end
    endmodule
    Terminal
    PASS: retargeted vif drives bif1 and leaves bif0 alone

    Project step

    Write pkt_driver, PacketFIFO's first class-based driver: it holds a virtual pkt_if, receives it in new(), and has a send() task that drives a packet's bytes with valid/ready and marks the last one. Send the Part 0 packet to an always-ready sink.

    One way to write it. The drives are raw nonblocking assignments for now; two lessons on they move onto a clocking block.

    pkt_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    interface pkt_if (input logic clk, input logic rst_n);
      logic       valid, ready, last;
      logic [7:0] data;
    endinterface
    
    // A sink that is always ready and records what it accepts.
    module pkt_sink (pkt_if p);
      logic [7:0] got [$];
      bit saw_last = 0;
      assign p.ready = 1'b1;
      always @(posedge p.clk)
        if (p.rst_n && p.valid && p.ready) begin
          got.push_back(p.data);
          if (p.last) saw_last <= 1;
        end
    endmodule
    
    // PacketFIFO's first driver: a class that reaches the pins through a virtual interface.
    class pkt_driver;
      virtual pkt_if vif;
    
      function new(virtual pkt_if vif);
        this.vif = vif;
      endfunction
    
      task send(bit [7:0] bytes [$]);
        foreach (bytes[i]) begin
          @(posedge vif.clk);
          vif.valid <= 1'b1;                       // nonblocking: the sink sees it next edge
          vif.data  <= bytes[i];
          vif.last  <= (i == bytes.size() - 1);
          do @(posedge vif.clk); while (!vif.ready);
          vif.valid <= 1'b0;
          vif.last  <= 1'b0;
        end
      endtask
    endclass
    
    module tb_pkt_driver;
      logic clk = 0, rst_n = 0;
      always #5 clk = ~clk;
      pkt_if   bus (.clk(clk), .rst_n(rst_n));
      pkt_sink u_snk (.p(bus));
      initial begin
        pkt_driver drv;
        bus.valid = 0; bus.last = 0; bus.data = 0;
        drv = new(bus);                            // the static instance, handed to the class
        @(negedge clk) rst_n = 1;
        drv.send('{8'h22, 8'hA5, 8'h3C, 8'hBB});
        @(posedge clk);
        $display("sink got %p", u_snk.got);
        if (u_snk.got.size() == 4 && u_snk.got[3] == 8'hBB && u_snk.saw_last)
          $display("PASS: pkt_driver sent the 4-byte packet through its virtual pkt_if");
        else $fatal(1, "FAIL: sink got %0d bytes", u_snk.got.size());
        $finish;
      end
    endmodule
    Terminal
    sink got '{'h22, 'ha5, 'h3c, 'hbb}
    PASS: pkt_driver sent the 4-byte packet through its virtual pkt_if

    Remember

    • Interfaces are built before time 0; class objects are built later by new().

    • A virtual interface holds a reference to one interface instance, never a copy.

    • Two objects with the same vif drive the same pins.

    Rule of thumb: classes reach pins only through a vif handed down from the top.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.

    Related topics

    • Next: Declaring & Binding a vif

    Previous

    49 / 259

    Next: Declaring & Binding a vif

    Up next

    Up nextDeclaring & Binding a vif

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Driving & Sampling via Clocking Blocks
    • vif Configuration Patterns
    • Multi-Instance & Parameterized vifs