← Design Verification Interviews

Interview Question · Code Writing

Handing the Interface to the Driver

L0 FresherL1 Junioruvmvirtual interfaceconfiguration

The Question

The interviewer asks

Write the code that gets this interface instance from the top module to the driver.

systemverilog
interface bus_if (input logic clk);
  logic       valid, ready;
  logic [7:0] data;
endinterface
// Get this interface instance, in the top module, to the driver inside
// uvm_test_top.env.agt.drv.
Reveal the Model Answer

In the top module, before run_test, call uvm_config_db #(virtual bus_if)::set(null, "uvm_test_top.env.agt.*", "vif", bus);. In the driver's build_phase, call get(this, "", "vif", vif) with the same type. If it returns 0, stop with uvm_fatal. A class cannot hold an interface instance, only a virtual interface: a handle that points to one.

From the Interface Instance to the DriverArrows from the config database carry a virtual interface handle; the arrow into bus is pin activity through that handle.
From the interface instance to the driverArrows from the config database carry a virtual interface handle; the arrow into `bus` is pin activity through that handle. interface bus bus_if instance, builtbefore time 0 module top module Calls set beforerun_test; the scopecovers everycomponent in agt table uvm_config_db Entry: that scope,field vif, type virtualbus_if component agt.drv build_phase: get(this,"", "vif", vif);uvm_fatal if itreturns 0 component agt.mon The same get; samplesthe pins through vif setget returns thehandleget returns thehandledrives through vif
Read the diagram as text
  • bus: bus_if instance, built before time 0
  • top module: Calls set before run_test; the scope covers every component in agt
  • uvm_config_db: Entry: that scope, field vif, type virtual bus_if
  • agt.drv: build_phase: get(this, "", "vif", vif); uvm_fatal if it returns 0
  • agt.mon: The same get; samples the pins through vif
  • top module → uvm_config_db: set
  • uvm_config_db → agt.drv: get returns the handle
  • uvm_config_db → agt.mon: get returns the handle
  • agt.drv → bus: drives through vif

The interface is static and exists before any class. Classes receive a handle that points to it, never the interface itself.

Set once in the top module, get with the same type, and stop at once if the get fails.
module tb;
  bit clk;
  bus_if bus (clk);
  dut u_dut (.clk(clk), .valid(bus.valid), .ready(bus.ready), .data(bus.data));
  initial begin
    uvm_config_db #(virtual bus_if)::set(null, "uvm_test_top.env.agt.*", "vif", bus);
    run_test();
  end
endmodule

class bus_driver extends uvm_driver #(bus_item);
  virtual bus_if vif;
  function void build_phase(uvm_phase phase);
    if (!uvm_config_db #(virtual bus_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", {"no bus_if for ", get_full_name()})
  endfunction
endclass

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Fresher)

A good fresher answer writes the set and the get with the same type. It explains why a class needs a virtual interface, which is a handle to the real interface.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfProve ItCompareScale It

  1. Handing the Interface to the Driver
  2. WhyWhy a Class Needs a Virtual InterfaceL0 Fresher

    Why can't the driver class just instantiate bus_if itself?

    Reveal the Model Answer

    Interfaces and modules are static: they are built at elaboration, before time 0. Classes are created during simulation. So a class cannot instantiate an interface. It holds a virtual interface, which is a variable that points at an interface instance someone else built.

    Each Driver Reaches the Port It Was GivenWithout UVM: arrows from the top module carry a virtual interface handle into a constructor. Arrows from a driver are drives through that handle to real pins.
    Each driver reaches the port it was givenWithout UVM: arrows from the top module carry a virtual interface handle into a constructor. Arrows from a driver are drives through that handle to real pins. testbench tb module knows which port eachdriver gets object d0: rd_driver vif refers to port0 object d1: rd_driver vif refers to port1 object d2: rd_driver vif is null interface port0 rd_if instance interface port1 rd_if instance new(.., port0)new(.., port1)new(.., null)vif.cb drivesvif.cb drives
    1. The instances exist first

      rd_if port0 (clk); and rd_if port1 (clk); are part of the elaborated design. They exist before time 0 and never move.

    2. Hand each driver its portt=0 (illustrative)

      The top module passes port0 to d0 and port1 to d1. Each constructor stores a handle; no signals are copied.

      Follow the calls, values and waits
      1. data handle: port0: tb module → d0: rd_driver
      2. data handle: port1: tb module → d1: rd_driver
    3. A driver with no portt=0 (illustrative)

      d2 is built with null. Its handle refers to nothing until something assigns it.

      Follow the calls, values and waits
      1. data null: tb module → d2: rd_driver
      2. Waiting: d2: rd_driver — vif == null
    4. Both drivers sendt=7 (illustrative)

      Forked, both drivers wait for the edge at 5 and drive through vif.cb. With output skew 2, each pin changes at t=7, on that driver's own port.

      Follow the calls, values and waits
      1. data rvalid=1, rdata=0a: d0: rd_driver → port0
      2. data rvalid=1, rdata=1b: d1: rd_driver → port1
    5. Two ports, two beatst=15 (illustrative)

      At the edge at 15 the module prints port0 carries 0a and port1 carries 1b.

    6. The null check reports

      d2.send() checks vif first and prints drv2: no interface configured; nothing sent instead of failing on a null handle.

      Follow the calls, values and waits
      1. Waiting: d2: rd_driver — no interface configured
    Read the diagram as text
    • tb module: knows which port each driver gets
    • d0: rd_driver: vif refers to port0
    • d1: rd_driver: vif refers to port1
    • d2: rd_driver: vif is null
    • port0: rd_if instance
    • port1: rd_if instance
    • tb module → d0: rd_driver: new(.., port0)
    • tb module → d1: rd_driver: new(.., port1)
    • tb module → d2: rd_driver: new(.., null)
    • d0: rd_driver → port0: vif.cb drives
    • d1: rd_driver → port1: vif.cb drives

    One driver class serves any number of ports. Which pins a driver reaches is decided by the handle it was given.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Virtual means the interface is abstract, like a virtual method."

    Here virtual means a reference to a real instance, not an unimplemented one.

    Source IEEE 1800-2017 §25.9

  3. What IfA Virtual Interface That Is Still NullL0 FresherL1 Junior

    What if the driver's virtual interface handle is still null when run_phase starts?

    Reveal the Model Answer

    The first access through it is a null access, and the simulation stops there. Usually the get in build failed and nobody checked its return value. Check the return of uvm_config_db::get, and call uvm_fatal with the path when it fails. Then the log names the missing set.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "If the get fails, UVM stops the test for me."

    get just returns 0 and leaves the handle null. Without a check, the failure appears later, at the first access.

    Source uvm-core 2020.3.1 (IEEE 1800.2 library) source, base/uvm_config_db.svh

  4. CompareGetting the Interface Once per AgentL1 Junior

    Compare each component getting the interface itself with the agent getting it once and passing it down.

    Reveal the Model Answer

    When each of the driver and monitor calls get, there are two lookups and two places a path can be wrong. When the agent gets its config object, which holds the handle, it assigns drv.vif and mon.vif itself. Then only one path must match, and a passive agent simply skips the driver.

    One Handle in, Two Handles OutOne configuration object per agent, passed down one layer at a time. Each arrow is one set or one get; a get returns a handle and leaves the entry in place.
    One handle in, two handles outOne configuration object per agent, passed down one layer at a time. Each arrow is one `set` or one `get`; a `get` returns a handle and leaves the entry in place. test chip_test Createschip_env_cfg and oneincr_agent_cfg perport config store config db: "env" Entry cfg =chip_env_cfg handle env chip_env Knows only its ownchildren config store config db:"port_a","port_b" Entries cfg.port_aand cfg.port_b agent port_a lane 0, UVM_ACTIVE agent port_b lane 1,UVM_PASSIVE set for "env"get returns handleset, relative namesgetget
    1. Test build_phase

      The test fills chip_env_cfg and its two agent objects, then sets the parent object once, for "env".

      Follow the calls, values and waits
      1. data chip_env_cfg handle: chip_test → config db: "env"
    2. Env build_phase: get

      The environment gets the parent object.

      Follow the calls, values and waits
      1. data chip_env_cfg handle: config db: "env" → chip_env
    3. Env build_phase: set

      It sets cfg.port_a for "port_a" and cfg.port_b for "port_b", names relative to itself.

      Follow the calls, values and waits
      1. data cfg.port_a handle: chip_env → config db: "port_a", "port_b"
      2. data cfg.port_b handle: chip_env → config db: "port_a", "port_b"
    4. Agent build_phase

      Each agent gets its own object, copies is_active from it and builds its children.

      Follow the calls, values and waits
      1. data cfg.port_a handle: config db: "port_a", "port_b" → port_a
      2. data cfg.port_b handle: config db: "port_a", "port_b" → port_b
    5. A run-time change5–10 ns (illustrative)

      The test clears checks_on in port_b's object at 5 ns. port_b reads 0 at 10 ns, with no new set or get.

    Read the diagram as text
    • chip_test: Creates chip_env_cfg and one incr_agent_cfg per port
    • config db: "env": Entry cfg = chip_env_cfg handle
    • chip_env: Knows only its own children
    • config db: "port_a", "port_b": Entries cfg.port_a and cfg.port_b
    • port_a: lane 0, UVM_ACTIVE
    • port_b: lane 1, UVM_PASSIVE
    • chip_test → config db: "env": set for "env"
    • config db: "env" → chip_env: get returns handle
    • chip_env → config db: "port_a", "port_b": set, relative names
    • config db: "port_a", "port_b" → port_a: get
    • config db: "port_a", "port_b" → port_b: get

    No layer names a grandchild. Renaming port_b changes only the environment.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "The monitor and driver must each get it, for reuse."

    Reuse comes from the agent's config object. Child components that look up their own paths are harder to move.

    Source Verification Academy, UVM Cookbook, Connections to DUT Interfaces (PDF p72)

  5. Prove ItSolve 6.4.1: Give Each Driver Its Own BusSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem 6.4.1

  6. Scale ItDriving a DUT Without a Virtual InterfaceL3 Senior

    Your bench must also run on an emulator, where class code cannot touch signals directly. How do you split it?

    Reveal the Model Answer

    Put all signal activity in a bus functional model inside the interface or a module, with tasks such as drive(item). The class side calls those tasks through a handle, an abstract class or the virtual interface. On the emulator, that pin-level half is compiled into hardware, and only transactions cross the boundary.

    Self-Check

    Tick each key point you covered in your answer.

    Wrong Altitude

    "Keep @(posedge vif.clk) in the driver; the emulator will handle it."

    For co-emulation the class side must be untimed: clock waits and delays are not allowed there. Timing lives in the synthesizable BFM.

    Source Verification Academy, UVM Cookbook, Emulation/SeparateTopLevels (PDF p411)

What People Get Wrong

Common wrong answers and why they fail.

Misconception

"Give the driver class an interface port, like a module."

Classes have no ports. A class can only hold a virtual interface variable that refers to an instance.

Source IEEE 1800-2017 §25.9

Missed Corner

Calls get and ignores its return value.

The handle stays null, and the bench crashes much later at the first vif. access, far from the real cause.

Source Verification Academy, UVM Cookbook, Resources/config db: always check that get succeeded (PDF p140)

Half-Truth

"Put the interface handle in a package variable instead."

It works for one bus. With two instances of the agent, both read the same global, and reuse is gone.

Learn It, Then Prove It