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. vif Configuration Patterns

Part 2 · OOP for Verification · Intermediate

vif Configuration Patterns

Scale vif distribution with constructor chaining, config objects, and config_db concepts.

On this page

    Why this matters

    Adding one interface to a bench meant editing six constructors that never used it, only passed it along. A config object carries every vif in one handle.

    After this lesson you can: relay vifs by constructor chaining, gather them in one shared config object checked before build, and explain what config_db adds.

    The relay problem

    One vif binds one driver easily. A real hierarchy relays each vif through layers that never use it, down to the leaves that do. This lesson borrows the names of those layers from the Testbench part: an agent groups the driver and monitor for one interface, and an env holds the agents.

    Constructor chaining passes every vif as an argument at every level. The dependency is visible, but each new interface edits every relay layer.

    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
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    interface bus_if (input logic clk);
      logic [31:0] addr;
      logic        valid;
    endinterface
    
    class driver;
      virtual bus_if vif;
      function new(virtual bus_if vif);
        this.vif = vif;
      endfunction
    endclass
    
    class monitor;
      virtual bus_if vif;
      function new(virtual bus_if vif);
        this.vif = vif;
      endfunction
    endclass
    
    class agent;
      driver  drv;
      monitor mon;
      function new(virtual bus_if vif);
        drv = new(vif);   // relay to the leaves
        mon = new(vif);
      endfunction
    endclass
    
    class env;
      agent agt;
      function new(virtual bus_if vif);   // env never USES vif: it only relays it
        agt = new(vif);
      endfunction
    endclass
    
    module tb_chain;
      logic clk = 0;
      always #5 clk = ~clk;
      bus_if bif (clk);
    
      initial begin
        env e;
        e = new(bif);                       // one argument per vif, at every level
        e.agt.drv.vif.addr = 32'h40;        // the leaf reaches the real pins
        #1;
        if (bif.addr == 32'h40 && e.agt.mon.vif.addr == 32'h40)
          $display("PASS: the vif relayed through env and agent reached both leaves");
        else $fatal(1, "FAIL: relay lost the vif");
        $finish;
      end
    endmodule
    Terminal
    PASS: the vif relayed through env and agent reached both leaves

    Arity is compile-checked, but passing the wrong instance still compiles. Chaining guarantees presence, never correctness.

    One config object for the whole bench

    A central config class gathers every vif and knob into one handle. Relay layers pass that handle unchanged, so new interfaces touch only the config and the leaf.

    Sharing one handle matters. The top fills it once, and every component observes the same vifs.

    The config records each vif through a setter, so check() can prove every one was filled before anything is built:

    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
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    interface bus_if (input logic clk);
      logic [31:0] addr;
      logic        valid;
    endinterface
    
    interface irq_if (input logic clk);
      logic irq;
    endinterface
    
    class tb_config;
      virtual bus_if bus_vif;   // ALL vifs for the whole bench live here
      virtual irq_if irq_vif;
      bit is_active = 1;
      local bit bus_set, irq_set;
    
      function void set_bus(virtual bus_if v);
        bus_vif = v;
        bus_set = 1;
      endfunction
    
      function void set_irq(virtual irq_if v);
        irq_vif = v;
        irq_set = 1;
      endfunction
    
      function void check();    // one clear failure at the top, before anything is built
        if (!bus_set) $fatal(1, "tb_config: bus_vif unset");
        if (!irq_set) $fatal(1, "tb_config: irq_vif unset");
      endfunction
    endclass
    
    class driver;
      virtual bus_if vif;
      function new(virtual bus_if vif);
        this.vif = vif;
      endfunction
    endclass
    
    class agent;
      driver drv;
      function new(tb_config cfg);
        if (cfg.is_active) drv = new(cfg.bus_vif);   // the leaf picks what it needs
      endfunction
    endclass
    
    class env;
      agent agt;
      function new(tb_config cfg);   // ONE argument, forever:
        agt = new(cfg);              // adding vifs never changes this line
      endfunction
    endclass
    
    module tb_cfg;
      logic clk = 0;
      always #5 clk = ~clk;
      bus_if bif (clk);
      irq_if iif (clk);
    
      initial begin
        tb_config cfg;       // declare only: an initializer here would run at time 0
        env       e;
        cfg = new();
        cfg.set_bus(bif);    // fill once at the top
        cfg.set_irq(iif);
        cfg.check();         // guard BEFORE building
        e = new(cfg);        // built only after the guard passed
        e.agt.drv.vif.addr = 32'h80;
        #1;
        if (bif.addr == 32'h80) $display("PASS: one config handle carried both vifs; the driver reached bus_if through it");
        else $fatal(1, "FAIL: config did not reach the driver");
        $finish;
      end
    endmodule
    Terminal
    PASS: one config handle carried both vifs; the driver reached bus_if through it
    Chaining relays every vif through every level; a config object relays one handle.

    check() runs before building anything. One clear fatal at the top beats a null-vif crash deep in a run.

    Common pitfall

    Do not write env e = new(cfg); inside the initial block. A variable declared with an initializer there is static, so its new() runs once at time 0, before cfg is filled and checked. Declare the handle first and assign it in order.

    What config_db does

    UVM's config database is the same distribution with the plumbing removed. The top publishes each vif under a string key, and each leaf looks it up by scope, with no relay layer involved.

    String keys are checked at run time, not compile time. Every get() needs its return-value guard: the null-vif check reborn.

    UVM is outside this course's checks, so the sketch below is partial and not run here. It shows the two calls and the mandatory guard.

    design.sv
    1
    2
    3
    4
    5
    6
    // snippet
    // tb_top: publish the vif under a string key and a scope pattern
    uvm_config_db#(virtual bus_if)::set(null, "uvm_test_top.env.agt.*", "vif", bif);
    // agent leaf, build phase: look it up and never ignore the return value
    if (!uvm_config_db#(virtual bus_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "config_db get failed")

    Common mistakes

    Common pitfall

    Copying config contents per component instead of sharing the handle means late top-level fixes never reach the copies. Forgetting one config field moves the crash far from its cause, so keep the check() call.

    Interview

    “How would you pass a vif without UVM?” expects the two hand-built patterns above. “What does config_db buy you?” expects decoupled distribution, paid for with run-time string lookup.

    Check yourself The bench grows a second interface and every relay constructor changes. Which pattern removes that churn?

    The central config object. Relay layers carry the one handle, so only the config class and the leaf that uses the new vif change.

    Check yourself A leaf's config_db::get() fails and the sim later fatals on a null vif. Where should it have failed?

    At the get() call itself. Ignoring the return value turns a lookup miss into a distant null-vif crash, so fatal there with the key in the message.

    Remember

    • Chaining shows every dependency but edits every level for each new vif.

    • A shared config handle is filled once, checked once, and read by the leaves.

    • config_db looks vifs up by string at run time: guard every get().

    Rule of thumb: fill the config at the top, check it before building anything.

    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.

    The config_db lines are UVM and are shown as a fragment; UVM is outside this course's checks.

    Related topics

    • Next: Multi-Instance & Parameterized vifs

    Previous

    52 / 259

    Next: Multi-Instance & Parameterized vifs

    Up next

    Up nextMulti-Instance & Parameterized vifs

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why Virtual Interfaces Exist
    • Declaring & Binding a vif