How Virtual Interface can be pass using uvm_config_db in the UVM Environment?

In short: A virtual interface is a handle that lets your class-based UVM code reach a real, static interface connected to the DUT. You pass it in with uvm_config_db#(virtual my_if)::set from the top module, then each component reads it with get in build_phase. This keeps the run-time class world and the static module world connected without hard-coded paths.

UVM components are classes, and classes are created at run time. The DUT and its interface are static, elaborated once at time zero. A class cannot point straight at a static interface, so UVM uses a virtual interface, a handle to the real interface, and passes that handle down using uvm_config_db.

Why a virtual interface is needed

A testbench has two worlds. The class-based world (test, environment, agents, driver, monitor) is created at run time. The structural world (the DUT and the interface wired to it) is static. The driver has to wiggle real pins, so it needs a way to reach into the static interface from inside a class. That bridge is the virtual interface.

Top-level module holding the class-based UVM test and environment above, and the structural dut_if interface and DUT module below
The class-based part (test and environment) is created at run time, while the interface and DUT are static. A virtual interface links the two.

A simple way to picture it

Think of a TV remote. The remote (your class-based driver) does not hold the wires inside the TV. It holds a link to the TV so it can change channels. The virtual interface is that link: the driver keeps a handle to the real interface and drives the pins through it, without owning the wires itself.

Step 1: declare the interface and connect it in the top module

First define an interface, instance it in the top module, and connect it to the DUT. Then place the handle in the config database so the testbench can find it.

interface apb_if(input logic pclk);
  logic        preset_n;
  logic [31:0] paddr;
  logic        pwrite;
  logic [31:0] pwdata;
  logic [31:0] prdata;
  logic        psel, penable, pready;
endinterface

module top;
  logic pclk = 0;
  always #5 pclk = ~pclk;

  apb_if apb_vif(pclk);          // real interface instance

  apb_dut dut(.apb(apb_vif));    // connect to DUT

  initial begin
    // put the handle in the database for the whole testbench
    uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.agt*",
                                        "vif", apb_vif);
    run_test("apb_test");
  end
endmodule

Step 2: pass it down with uvm_config_db

The interface handle now travels down the hierarchy through the config database. The test does not need to touch it; UVM carries it to the agents, which then hand it to the driver and monitor. The diagram below shows this flow.

Virtual interface captured from the real interface using the config_db API, stored in a configuration object in the test, and passed down through the hierarchy to the agent
The config_db API captures the real interface as a virtual interface, stores it in the test, then passes it down to the agent, driver, and monitor.

Step 3: get the handle in the driver and monitor

Each component that touches pins reads the handle in its build_phase. Always check the return value, and stop the test with a clear message if the handle is missing.

class apb_driver extends uvm_driver #(apb_seq_item);
  `uvm_component_utils(apb_driver)
  virtual apb_if vif;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "No virtual interface set for the driver")
  endfunction

  task run_phase(uvm_phase phase);
    forever begin
      apb_seq_item tr;
      seq_item_port.get_next_item(tr);
      // drive real pins through the virtual interface
      @(posedge vif.pclk);
      vif.psel   <= 1;
      vif.paddr  <= tr.addr;
      vif.pwrite <= tr.write;
      vif.pwdata <= tr.data;
      seq_item_port.item_done();
    end
  endtask
endclass

The monitor reads the same handle the same way, then samples vif signals instead of driving them. Both sit inside the agent, so the wildcard path env.agt* from the top reaches them together.

Where to set and where to get

ActionWhereWhy
set the handleTop module before run_testThe interface is static and known at time zero
get the handleDriver and monitor build_phaseChildren build after parents, so the value is ready
check return bitEvery get callA missing handle should fail early, not mid test
use wildcard pathenv.agt* on the setOne set reaches every matching agent

Common mistakes to avoid

  • Getting the handle in run_phase instead of build_phase, so early code sees a null handle.
  • A path mismatch: setting on agt but the real instance is agent. The names must match or use a wildcard.
  • Skipping the return check on get, then hitting a null virtual interface at the first pin access.
  • Setting the wrong type, for example virtual apb_if on the set and a different interface type on the get.
  • Connecting the interface to the DUT but forgetting the set call, so the handle never enters the database.

Keep learning

This builds directly on the config database. Read more about uvm_config_db and uvm_resource_db, see the typical UVM testbench architecture, and find related topics in the UVM category and the interview questions.

Frequently asked questions

What is a virtual interface in UVM?

A virtual interface is a handle to a real, static SystemVerilog interface. It lets class-based UVM components, which are created at run time, reach and drive the static pins connected to the DUT.

Why do we pass the virtual interface with uvm_config_db?

The config database carries the handle down the hierarchy without hard-coded paths. The top module sets it once, and every driver and monitor that needs it reads it in build_phase.

Where should I set the virtual interface?

Set it in the top module before run_test, because the interface is static and known at time zero. Use a path such as uvm_test_top.env.agt* so it reaches every agent.

Where should I get the virtual interface?

Get it in the build_phase of the driver and monitor. Children build after their parents, so the value set at the top is already available when the component builds.

What happens if the get fails?

get returns 0 when no matching handle is found. Guard it with an if and raise uvm_fatal so the test stops early with a clear message instead of hitting a null handle during the run.

Can one set reach several agents?

Yes. A wildcard path like env.agt* on the set call delivers the same handle to every matching agent, so you do not need a separate set for each one.

Similar Posts

7 Comments

  1. Hi,
    I think that instead of:
    1. passing the virtual interface to the config object
    2. passing the config object to config_db
    one could do it in a single step: pass the virtual interface using config_db
    Isn’t it better this way?

    1. Hi Zeev,

      Yeah, I do agree with you that we can do with a single step as well but in that example or for reusability purposes we usually keep all the configurations in a separate class or file so whenever we need it we can reuse it anywhere in the UVM environment. I hope you got the point.

      Thank you

Comments are closed.