← Design Verification Interviews

Interview Question · Debug

An Override That Did Nothing

L1 JuniorL2 Mid-Leveluvmfactorydebug

The Question

The interviewer asks

Your error test sets a type override from fifo_driver to err_driver. The log still shows the normal driver. The env builds it with drv = new("drv", this);. Why?

log
UVM_INFO @ 0: reporter [RNTST] Running test err_test...
uvm_test_top.env.drv is a fifo_driver
UVM_ERROR :    0
Reveal the Model Answer

Calling new() runs the constructor directly, so the factory never sees the request and cannot substitute anything. Only type_id::create() asks the factory, which checks for overrides before it builds. The override was registered correctly, and nothing warned, because nobody asked for that type. Create every component and object that a test might replace with create().

Two Ways to Build the DriverArrows are calls. The fifo_driver and err_driver boxes are the objects that get built. The test has registered a type override from fifo_driver to err_driver.
Two ways to build the driverArrows are calls. The fifo_driver and err_driver boxes are the objects that get built. The test has registered a type override from `fifo_driver` to `err_driver`. component env build_phase Builds drv, declaredas fifo_driver call new() new("drv", this) runsthe constructordirectly call type_id::create Asks the factory fora fifo_driver factory Factory Instance overridesfirst, then typeoverrides component fifo_driver Built: the override isnever seen component err_driver Built: the typeoverride wins the bugconstructsthe fixrequestoverride found
Read the diagram as text
  • env build_phase: Builds drv, declared as fifo_driver
  • new(): new("drv", this) runs the constructor directly
  • type_id::create: Asks the factory for a fifo_driver
  • Factory: Instance overrides first, then type overrides
  • fifo_driver: Built: the override is never seen
  • err_driver: Built: the type override wins
  • env build_phase → new(): the bug
  • new() → fifo_driver: constructs
  • env build_phase → type_id::create: the fix
  • type_id::create → Factory: request
  • Factory → err_driver: override found

The override sits in the factory. Only a call that goes through the factory can find it.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Junior)

A good junior answer points at the new() call within seconds. It rewrites that line as type_id::create("drv", this).

Cross-Questions

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

WhyWhat IfProve ItCompare

  1. An Override That Did Nothing
  2. WhyWhat Create Does UnderneathL2 Mid-Level

    Why does type_id::create() see the override when new() cannot? What happens inside it?

    Reveal the Model Answer

    type_id is a small proxy class that the utils macro adds for each registered type. Its create() calls the factory with that proxy, the parent and the name. The factory looks for an instance override that matches the full path, then a type override. It builds the winner and casts it back to the requested type.

    Four Requests to the FactoryA test registers an instance override for one driver and a type override for an item. Arrows show calls: overrides registered, create calls, and the factory's lookups in its two tables.
    Four requests to the factoryA test registers an instance override for one driver and a type override for an item. Arrows show calls: overrides registered, create calls, and the factory's lookups in its two tables. test err_test build_phase component port0 agent create("drv", this) component port1 agent create("drv", this) phase end_of_elaboration create("made"), new("raw") component Factory instance first, then type table Instance overrides uvm_test_top.env.port1.drv table Type overrides incr_item: wrap_item registercreatecreatecreatepath matches?type matches?
    1. Register firstbuild (illustrative)

      err_test registers both overrides before it calls super.build_phase. The this argument turns "env.port1.drv" into uvm_test_top.env.port1.drv.

      Follow the calls, values and waits
      1. call set_inst_override: err_test → Factory
      2. call set_type_override: err_test → Factory
    2. Port 0 asks for a driverbuild (illustrative)

      The path is uvm_test_top.env.port0.drv. No instance override matches it, and no type override names incr_driver, so the factory builds an incr_driver.

      Follow the calls, values and waits
      1. call create("drv"): port0 agent → Factory
      2. call port0.drv? no: Factory → Instance overrides
      3. call incr_driver? no: Factory → Type overrides
      4. data incr_driver: Factory → port0 agent
    3. Port 1 asks for a driverbuild (illustrative)

      The path uvm_test_top.env.port1.drv matches the instance override. A matching instance override wins, so the factory builds an err_driver.

      Follow the calls, values and waits
      1. call create("drv"): port1 agent → Factory
      2. call port1.drv? yes: Factory → Instance overrides
      3. data err_driver: Factory → port1 agent
    4. An item through the factoryend of elaboration (illustrative)

      create("made") matches no instance override, then finds the type override for incr_item. The run prints create: wrap_item, x=255.

      Follow the calls, values and waits
      1. call create("made"): end_of_elaboration → Factory
      2. call made? no: Factory → Instance overrides
      3. call incr_item? yes: Factory → Type overrides
      4. data wrap_item, x=255: Factory → end_of_elaboration
    5. An item made with newend of elaboration (illustrative)

      new("raw") never asks the factory, so no override can reach it. The run prints new: incr_item, x=0.

    Read the diagram as text
    • err_test: build_phase
    • port0 agent: create("drv", this)
    • port1 agent: create("drv", this)
    • end_of_elaboration: create("made"), new("raw")
    • Factory: instance first, then type
    • Instance overrides: uvm_test_top.env.port1.drv
    • Type overrides: incr_item: wrap_item
    • err_test → Factory: register
    • port0 agent → Factory: create
    • port1 agent → Factory: create
    • end_of_elaboration → Factory: create
    • Factory → Instance overrides: path matches?
    • Factory → Type overrides: type matches?

    The factory decides the class at each create call, from the path and the requested type.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "The override edits the class, so even new() builds the new type."

    Classes cannot change at run time. The factory only chooses which class to build when it is asked.

    Source uvm-core 2020.3.1 (IEEE 1800.2 library) source, base/uvm_registry.svh, uvm_component_registry::create

  3. Prove ItSolve U15.2.1: The Poison Test with a Short LedgerSimulator-graded

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

    Open Problem U15.2.1

  4. What IfAn Override That Is Not a SubclassL2 Mid-Level

    What if the override names a class that is not derived from the requested type?

    Reveal the Model Answer

    The factory builds the override class, then casts it back to the requested type. The cast fails, because the new class is not a subclass. The uvm-core library then stops the run with a fatal error at that create call. So an override must name a class derived from the original.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "An override can name any class with the same fields."

    The factory casts the result back to the requested type. A class outside that type's hierarchy fails the cast.

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

  5. CompareFactory Override vs a Configuration KnobL2 Mid-Level

    Compare a factory override with a configuration knob such as inject_errors = 1. Which would you use for an error-injecting driver?

    Reveal the Model Answer

    A knob changes values inside one class, so the base driver needs an if for every behaviour any test might want. An override swaps in a derived class with new behaviour, leaving the base driver clean. Use knobs for numbers like delays and lengths. Use an override when the behaviour itself changes, such as corrupting parity.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Knobs are always simpler."

    They are simpler at first. After ten tests each add a flag, the base driver is full of branches that every test pays for.

    Source Accellera, UVM 1.1 User's Guide, §3.1.2 Defining Control Fields ("Knobs") (PDF p41)

  6. What IfAn Override Set in the Run PhaseL1 Junior

    What if the test registers the driver override in run_phase instead of build_phase?

    Reveal the Model Answer

    The driver was built during the build phase, so it keeps its class. An override registered in run_phase still affects objects created later, such as sequence items. So the same line can work for items and silently fail for components.

    The Rule Must Exist When the Item Is CreatedArrows show calls to the factory, the returned item and its trip to the pins. The first steps follow a test that sets its override in build. The last steps follow a separate run that sets it too late.
    The rule must exist when the item is createdArrows show calls to the factory, the returned item and its trip to the pins. The first steps follow a test that sets its override in build. The last steps follow a separate run that sets it too late. test boundary_test.build Install the item override sequence one_seq.body Asks for family_item test late test: afterstart Install the same ruleafter the work factory Factory Looks up the rule atcreate() object Created item Its class is fixed atcreation design Observed pins Monitor samples at 15ns set overridecreate("item")new itemprepare, drive,observeset override
    1. Install the rulebuild: 0 ns (illustrative)

      boundary_test selects boundary_item before the sequence starts.

      Follow the calls, values and waits
      1. call override family_item → boundary_item: boundary_test.build → Factory
    2. Create the selected itemrun: 0 ns (illustrative)

      The unchanged sequence requests family_item. The factory returns a boundary_item; its prepare() chooses ff.

      Follow the calls, values and waits
      1. call create family_item: one_seq.body → Factory
      2. return boundary_item object: Factory → Created item
    3. Check the limit15 ns (illustrative)

      The monitor observes x=ff, parity=0, y=00 and error=0. The DUT check and the boundary test’s goal both pass.

      Follow the calls, values and waits
      1. data x=ff → y=00: Created item → Observed pins
    4. Start a fresh late runfault run: 0 ns (illustrative)

      This separate run has no override yet. The sequence receives a family_item; prepare() chooses 10.

      Follow the calls, values and waits
      1. call create family_item: one_seq.body → Factory
      2. return family_item object: Factory → Created item
      3. Waiting: late test: after start — inside super.exercise() until 20 ns
    5. The wrong case still worksfault run: 15 ns (illustrative)

      x=10 produces y=11 with error=0. That is a correct DUT result, but the test intended to exercise ff.

      Follow the calls, values and waits
      1. data x=10 → y=11: Created item → Observed pins
    6. A later rule cannot change itfault run: 20 ns (illustrative)

      start() returns at 20 ns, then the late test installs its override. The observed input remains 10, so check_phase reports TEST_GOAL.

      Follow the calls, values and waits
      1. call override family_item → boundary_item: late test: after start → Factory
      2. Released: late test: after start
    Read the diagram as text
    • boundary_test.build: Install the item override
    • one_seq.body: Asks for family_item
    • late test: after start: Install the same rule after the work
    • Factory: Looks up the rule at create()
    • Created item: Its class is fixed at creation
    • Observed pins: Monitor samples at 15 ns
    • boundary_test.build → Factory: set override
    • one_seq.body → Factory: create("item")
    • Factory → Created item: new item
    • Created item → Observed pins: prepare, drive, observe
    • late test: after start → Factory: set override

    Both runs use the same environment and sequence. Only the test’s override timing changes.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "It will apply at the next phase."

    Nothing rebuilds components. Overrides act only at create time.

    Source Accellera, UVM 1.1 User's Guide, §6.2.3 (PDF p140)

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Misconception

"Registering the class with the factory macro is enough for overrides to apply."

Registration lets the factory build the class. The factory still only acts when the code asks it, through create().

Run It

The interview's env and test, with a line that reports which class was built.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: uvm_test_top.env.drv is a fifo_driver FAIL: new() never asks the factory, so the type override was ignored
See the Fix
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;
class fifo_driver extends uvm_driver #(uvm_sequence_item);
  `uvm_component_utils(fifo_driver)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
class err_driver extends fifo_driver;     // injects parity errors
  `uvm_component_utils(err_driver)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
class fifo_env extends uvm_env;
  `uvm_component_utils(fifo_env)
  fifo_driver drv;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    drv = fifo_driver::type_id::create("drv", this);   // the fix: ask the factory
  endfunction
endclass
class err_test extends uvm_test;
  `uvm_component_utils(err_test)
  fifo_env env;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    fifo_driver::type_id::set_type_override(err_driver::get_type());
    env = fifo_env::type_id::create("env", this);
  endfunction
  function void end_of_elaboration_phase(uvm_phase phase);
    $display("%s is a %s", env.drv.get_full_name(), env.drv.get_type_name());
    if (env.drv.get_type_name() != "err_driver") $display("FAIL: new() never asks the factory, so the type override was ignored");
    else $display("PASS: create() asked the factory and got the override");
  endfunction
endclass
module tb;
  initial run_test("err_test");
endmodule

Prints: uvm_test_top.env.drv is a err_driver PASS: create() asked the factory and got the override

Missed Corner

"Set the override wherever is convenient, even after the env is built."

An override changes only create calls made after it is registered. Components that already exist keep their class.

Source Accellera, UVM 1.1 User's Guide, §6.2.3 Component Overrides (PDF p140-141)

Buzzword Without Mechanism

"The factory is a design pattern for making objects."

That names the pattern without the mechanism. What matters is substitution: a test changes which class create() returns, by type or by instance path.

Learn It, Then Prove It