UVM Interview Q&A Bank — VLSI Trainers
VLSI Trainers
UVM Series · Interview Prep

UVM Question & Answer Bank

105 questions covering the complete UVM series — architecture, phases, factory, sequences, RAL, debug, and advanced topics. Filter by topic or difficulty.

120Questions
12Topics
3Difficulty levels
UVM 1.2Standard
01 — UVM Basics & Architecture 10 Q
Q001 What is UVM and why is it used in hardware verification?
Easy

UVM (Universal Verification Methodology) is a standardised SystemVerilog class-library framework for building reusable, scalable verification environments. It provides a base class library, factory mechanism, phase system, and TLM communication infrastructure.

Key reasons: structured hierarchy scaling from block to system level; factory-based component substitution without modifying source; phase system synchronising construction, connection, and stimulus; constrained-random stimulus with functional coverage closing; and a common vocabulary enabling IP reuse across teams and projects.

Q002 Describe the standard five-level UVM testbench hierarchy.
Easy

From top to bottom: Test → Environment → Agent → Driver/Sequencer/Monitor → Sequence Item.

  • Test — creates config, builds env, starts virtual sequences in run_phase
  • Env — contains agents, scoreboard, coverage, register model
  • Agent — groups driver + sequencer + monitor; active or passive
  • Driver/Sequencer/Monitor — leaf components touching the virtual interface
  • Sequence Item — the data transaction passed between sequence and driver
Q003 What is the difference between uvm_object and uvm_component?
Easy

uvm_component: fixed position in hierarchy, has a parent, persists entire simulation, participates in phases. Examples: driver, monitor, agent, env, test. Registered with `uvm_component_utils.

uvm_object: no parent, no phases, created/destroyed dynamically. Examples: sequence items, sequences, config objects, RAL objects. Registered with `uvm_object_utils.

Rule: If it drives/monitors signals or lives in the hierarchy → component. If it carries data or configuration → object.
Q004 What does run_test() do and where is it called?
Easy

run_test() is a global function that: reads the test name from +UVM_TESTNAME= plusarg; creates the test instance via the factory; then kicks off the UVM phase scheduler from build through final phase.

Always called inside an initial block in the top-level SystemVerilog module — the only module in a UVM testbench that is not a UVM component.

initial begin
  uvm_config_db #(virtual apb_if)::set(null, "uvm_test_top", "vif", APB);
  run_test();   // test name from +UVM_TESTNAME=
end
Q005 What do `uvm_component_utils and `uvm_object_utils do internally?
Medium

Both macros register the class with the UVM factory and generate boilerplate:

  • Call uvm_factory::register() so the class can be overridden
  • Define a type_id typedef and static get_type() method used in override calls
  • Implement get_type_name() returning the class name as a string
  • Component utils also generates create(string name, uvm_component parent); object utils generates create(string name)
Important: Parameterised classes cannot use these macros directly — create a typedef specialisation first.
Q006 What is the two-kingdom problem and how is it solved?
Medium

SystemVerilog has two worlds: the static HDL world (modules, interfaces — elaborated at compile time) and the dynamic OOP world (UVM classes — created at runtime). Classes cannot directly hold references to interface instances.

Solution: virtual interface. A virtual interface is a class-compatible handle pointing to a specific interface instance:

// In tb_top (static world) — bridge into config_db
uvm_config_db #(virtual apb_if)::set(null, "uvm_test_top", "vif", APB);
// In driver (dynamic world) — retrieve
virtual apb_if vif;
uvm_config_db #(virtual apb_if)::get(this, "", "vif", vif);
Q007 Why must component constructors only call super.new() and covergroup construction?
Medium

The build phase is top-down and deliberate so each level can push configuration to the level below before that level builds its children. Constructing children in the constructor happens before the phase system starts — before any config_db values exist.

Additionally, factory overrides may not yet be registered at construction time, so the wrong type gets created.

Rule: Constructor = super.new() + covergroup new() ONLY. All component/port creation and config_db retrieval go in build_phase().
Q008 What is uvm_top and what role does it play?
Medium

uvm_top is the singleton uvm_root instance sitting above all user-created components. It is the implicit parent of uvm_test_top (the test instance created by run_test()).

Roles: manages phase scheduling; provides print_topology(); serves as the root scope for config_db wildcard lookups when context is null; holds the global timeout setting.

Q009 What is the abstract/concrete pattern and when is it needed?
Hard

When a driver needs to access signals in a legacy Verilog BFM module, a class cannot extend a module.

  • Abstract class (in a package): extends uvm_driver, declares pure virtual tasks for BFM operations. The environment holds a handle of this type.
  • Concrete class (inside the interface/module): extends the abstract class, implements the pure virtual tasks with actual signal-driving code — visible because it lives inside the module scope.

A factory instance override connects the two: the env creates the abstract type; the override replaces it with the concrete type. The concrete class is the exception to the “all classes in packages” rule.

Q010 How does a UVM event pool enable inter-component synchronisation without direct handles?
Hard

uvm_event_pool is a singleton string-keyed pool of uvm_event objects accessible from anywhere:

// Producer (no direct handle to consumer)
uvm_event e = uvm_event_pool::get_global("xfer_done");
e.trigger();

// Consumer (no direct handle to producer)
uvm_event e = uvm_event_pool::get_global("xfer_done");
e.wait_trigger();

Use when direct TLM connections are impractical — synchronising a coverage component with a stimulus event, or notifying a watchdog sequence that a handshake completed.

02 — Phases 10 Q
Q011 List all 12 UVM phases in order, grouped by type.
Easy

Build group (top-down, functions): build_phase → connect_phase → end_of_elaboration_phase → start_of_simulation_phase

Run group (concurrent, tasks — consumes simulation time): run_phase (with reset → configure → main → shutdown sub-phases)

Cleanup group (bottom-up, functions): extract_phase → check_phase → report_phase → final_phase

Q012 What is the difference between function phases and task phases?
Easy

Function phases (build, connect, end_of_elaboration, start_of_simulation, extract, check, report, final): zero-time functions, execute once per component in hierarchy order, cannot use delays or wait statements.

Task phases (run_phase and all sub-phases): can consume simulation time via delays, event waits, and clock edges. All components’ run_phase tasks execute concurrently — the scheduler forks them all and waits for objections to drain.

Q013 Why does build_phase execute top-down while connect_phase executes bottom-up?
Easy

build_phase top-down: A parent must create its children before they can exist. The test’s build_phase creates the env; the env’s build_phase creates agents; agents create drivers and monitors. Top-down ensures parents exist first.

connect_phase bottom-up: A child’s ports must exist before a parent can connect to them. Bottom-up ensures leaf-level ports (driver’s seq_item_port, monitor’s ap) are fully constructed before the agent’s connect_phase wires them together.

Q014 Explain the objection mechanism. What happens if no component raises an objection?
Medium

Objections are reference-counted gatekeepers on task phases. A phase ends only when its count reaches zero. The test raises an objection at the start of run_phase and drops it when stimulus is complete:

task run_phase(uvm_phase phase);
  phase.raise_objection(this, "test running");
  my_seq.start(m_env.m_agent.m_seqr);
  phase.drop_objection(this, "test done");
endtask

If no component ever raises an objection, the run_phase ends immediately at time 0 — stimulus never runs, the test appears to pass vacuously. This is the most common new-testbench bug.

Q015 What is drain time and when do you use it?
Medium

Drain time is an additional wait after all objections are dropped but before the phase actually ends. It allows in-flight pipeline transactions to complete and monitors to capture final responses.

function void end_of_elaboration_phase(uvm_phase phase);
  uvm_objection obj = phase.get_objection();
  obj.set_drain_time(this, 100ns);
endfunction
Better alternative: use phase_ready_to_end() in the scoreboard for precise FIFO drain control rather than a blanket time delay.
Q016 What is phase_ready_to_end() and when do you override it?
Medium

Called on every component when the phase objection count first reaches zero. A component can raise a new objection inside this callback to delay the phase end while it finishes work (e.g. scoreboard FIFO drain):

function void phase_ready_to_end(uvm_phase phase);
  if (phase.get_name() != "run") return;
  if (m_fifo.size() > 0) begin
    phase.raise_objection(this);
    fork begin
      wait(m_fifo.size() == 0);
      phase.drop_objection(this);
    end join_none
  end
endfunction
Q017 What is start_of_simulation_phase used for?
Medium

Runs after all connections are complete and before simulation time advances. Common uses:

  • Printing configuration summaries with `uvm_info
  • Calling uvm_top.print_topology() to verify the hierarchy
  • Calling uvm_factory::get().print(1) to verify overrides
  • Setting drain time on the run_phase objection
  • Asserting that expected analysis port connections exist
Q018 Can you jump phases in UVM? How and why?
Hard

Yes. phase.jump(uvm_reset_phase::get()) forces UVM to restart from the named phase across all components. The scheduler kills the current phase and re-runs from the jump target.

Typical use: a watchdog sequence detects a fatal bus error, triggers a DUT hardware reset, then calls phase.jump(reset_phase) to cleanly re-run the reset and configure sub-phases without ending the simulation.

Caution: All components must be designed to re-enter reset_phase cleanly — local state must be reset, FIFOs cleared, and objections managed carefully to avoid deadlocks.
Q019 What happens to a run_phase task that never returns? Does simulation deadlock?
Hard

No — the phase scheduler does not wait for run_phase tasks to return; it waits only for the objection count to reach zero. Once all objections are dropped, UVM kills all outstanding run_phase tasks (via disable fork) and moves to extract_phase.

A monitor’s forever begin ... end run_phase is the canonical example — it never returns, yet simulation ends cleanly.

Key insight: run_phase task returning and objection dropping are independent events. The phase ends on zero objections, not on task completion.
Q020 How do you add a UVM simulation timeout? What happens when it fires?
Hard

A timeout kills simulation if run_phase does not end within the specified time:

uvm_top.set_timeout(500us, 1);
// Or command-line: +UVM_TIMEOUT=500000,YES

When the timeout fires, UVM issues a `uvm_fatal showing which phases were still active and which components held outstanding objections. The second argument (1=YES) makes it fatal; 0=NO makes it a warning.

Best practice: set a timeout in every base test to prevent runaway simulations consuming infinite regression time.
03 — Factory & Config DB 10 Q
Q021 What problem does the UVM factory solve?
Easy

Without the factory, substituting a component (e.g. error-injecting driver) requires editing every line of source code that creates it. The factory decouples class creation from class identity: type_id::create() checks for an override and returns the override type instead, without any change to calling code.

Overrides can be registered from the test, command-line plusargs, or config objects — enabling per-test, per-instance component substitution with zero source changes.

Q022 What is the difference between a type override and an instance override?
Easy

Type override — replaces every instance everywhere:

my_driver::type_id::set_type_override(error_driver::get_type());

Instance override — replaces only at a specific hierarchy path:

my_driver::type_id::set_inst_override(
  error_driver::get_type(), "uvm_test_top.m_env.m_agent.*");

Instance overrides take precedence over type overrides when both match the same path.

Q023 Why must you always use type_id::create() instead of new()?
Easy

Using new() bypasses the factory entirely — no overrides are applied, the object cannot be inspected by factory debug tools, and print_topology() cannot display its type.

// Wrong — bypasses factory, overrides silently ignored
m_drv = new("m_drv", this);
// Correct — checks override table before creating
m_drv = my_driver::type_id::create("m_drv", this);
Exception: TLM ports/exports/imps (uvm_analysis_port, etc.) are infrastructure — always use new("name", this) for these.
Q024 What are the rules for a valid factory override?
Medium

The override type must be a subclass of the original type (Liskov Substitution Principle). The factory rejects overrides where the override is not derived from the original.

Override must be registered before the first create() call for that type — typically in the test’s build_phase, before creating the environment.

Q025 How do you debug a factory override that is not taking effect?
Medium

Three steps in order:

  1. Print the factory registry: uvm_factory::get().print(1) in end_of_elaboration_phase. Check the override appears with correct type names.
  2. Check override was set before create(): Overrides in test build_phase must appear before m_env = gpio_env::type_id::create(...). Order matters.
  3. Verify the substitute class is compiled and imported: If the override class’s package is not imported in tb_top, it was never registered with the factory.
// Simulate what factory would return at a specific path:
uvm_factory::get().debug_create_by_type(
  my_driver::get_type(),
  "uvm_test_top.m_env.m_agent", "m_drv");
Q026 How do you apply a factory override from the command line?
Medium

Use plusargs — no recompilation required:

// Type override
+uvm_set_type_override=my_driver,error_driver
// Instance override
+uvm_set_inst_override=my_driver,error_driver,
  uvm_test_top.m_env.m_agent.*
// Enable factory trace
+UVM_FACTORY_TRACE

UVM processes these plusargs during factory initialisation, before build_phase runs. Useful in regression to test variants without separate test classes.

Q027 Why can parameterised classes not use `uvm_component_utils directly?
Hard

Parameterised classes are templates — the factory uses a string name as the registration key, and a template has no single name until specialised with parameter values. Attempting to register base_driver_t #(32) and base_driver_t #(64) would conflict.

Solution: create a typedef specialisation and register that:

class base_drv_t #(parameter int DW=32) extends uvm_driver;
  // No `uvm_component_utils
endclass
typedef base_drv_t #(32) base_drv_32;
`uvm_component_utils(base_drv_32)
Q028 What is the Russian-doll (nested config) pattern for testbench configuration?
Hard

Each hierarchy level has its own config object, and the parent config holds handles to child configs:

class gpio_env_cfg extends uvm_object;
  apb_agent_cfg m_apb_cfg;  // env holds agent config
  gpio_reg_block gpio_rm;
endclass

The test pushes only the top-level env_cfg into config_db. The env’s build_phase extracts agent configs from it and distributes them downward. Adding a new agent only requires a new handle in env_cfg — not a new config_db::set() in every test.

Q029 What is the four-part key of uvm_config_db::set()?
Medium
uvm_config_db #(T)::set(
  context,         // 1. scope root (component or null)
  "instance_path", // 2. path relative to context
  "field_name",    // 3. key name string
  value            // 4. value to store
);

The get() call uses the same four parts. The database resolves a match by walking up the component hierarchy from the getter’s path until a matching set() entry is found. Wildcards ("*") match any path segment.

Q030 Two config_db::set() calls use the same key — which one wins?
Hard

The most specific path wins, then last-write among equal specificity. A set() with "uvm_test_top.m_env.m_agent" wins over one with "*" even if the wildcard was set later.

Trap: If a parent sets config for "*" and a child later sets the same field for a specific path, the child’s more specific entry wins regardless of call order. Add +UVM_CONFIG_DB_TRACE to see which value is returned by get().
04 — Sequences & Sequence Items 10 Q
Q031 What is a sequence item? What fields does it contain?
Easy

A sequence item (extends uvm_sequence_item) is a single bus transaction — the unit of communication between the sequence and driver. Fields fall into four categories:

  • Stimulus (rand) — fields the sequence drives: address, write data, byte enables
  • Response (non-rand) — fields the driver populates: read data, error status
  • Control — metadata: transaction type, delay count
  • Constraints — restrict stimulus to legal values
do_compare() must NOT compare response fields — they are the DUT output and will differ between expected and actual. Compare only stimulus fields in do_compare().
Q032 Explain the start_item() / finish_item() handshake.
Easy
task body();
  apb_seq_item req = apb_seq_item::type_id::create("req");
  start_item(req);          // ① wait for sequencer grant
  if (!req.randomize() with {addr == 12'h004;})
    `uvm_fatal("SEQ", "randomize failed")
  finish_item(req);         // ② send to driver; blocks until item_done()
endtask

Randomisation happens between the two calls so callbacks and constraint overrides can affect the values. Never randomise before start_item().

Q033 Why should you avoid `uvm_do and `uvm_do_with macros?
Easy

These macros combine item creation, randomisation, and sending into one opaque call. Problems:

  • No randomisation check — silent constraint failures produce invalid stimulus
  • No code between start_item and finish_item — cannot perform operations between arbitration and send
  • Opaque debugging — errors reported inside macro expansion, not your code line

Always use the explicit pattern: create → start_item → randomize with assertion → finish_item.

Q034 What is a virtual sequence and when is it needed?
Medium

A virtual sequence coordinates stimulus across multiple agents. It holds handles to their sequencers and starts sub-sequences on each — enabling cross-interface ordering and synchronisation.

class sys_vseq extends uvm_sequence;
  apb_sequencer m_apb_seqr;
  spi_sequencer m_spi_seqr;
  task body();
    fork
      apb_init.start(m_apb_seqr, this);
      spi_xfer.start(m_spi_seqr, this);
    join
  endtask
endclass
Q035 What are the six sequencer arbitration modes?
Medium
  • SEQ_ARB_FIFO (default) — round-robin in arrival order
  • SEQ_ARB_WEIGHTED — random weighted by priority value
  • SEQ_ARB_RANDOM — uniform random among all active sequences
  • SEQ_ARB_STRICT_FIFO — FIFO within priority groups; higher priority drains first
  • SEQ_ARB_STRICT_RANDOM — random only within the highest-priority group
  • SEQ_ARB_USER — custom user-defined arbitration callback

Set with: m_seqr.set_arbitration(SEQ_ARB_STRICT_FIFO);

Q036 What is the difference between grab() and lock()?
Medium

Both give exclusive access to a sequencer, but differ in when exclusivity starts:

lock() — queued. Waits for currently-granted sequences to finish, then claims exclusive access. Polite.

grab() — immediate. Takes the sequencer right now, interrupting any pending requests. Used for ISR-style urgent sequences that must respond without delay.

Both released with unlock() / ungrab().

Q037 How do you pass response data from the driver back to the sequence?
Medium

In-place update (most common): the driver writes response fields into the same req object before calling item_done(). The sequence reads them after finish_item() returns:

// In driver (before item_done):
req.read_data = vif.PRDATA;
req.error     = vif.PSLVERR;
seq_item_port.item_done();    // unblocks finish_item()
// In sequence, after finish_item():
`uvm_info("SEQ", $sformatf("read=0x%08h", req.read_data), UVM_MEDIUM)

Response FIFO: driver calls item_done(rsp) with a separate object; sequence calls get_response(rsp). Used when request and response are different types.

Q038 Explain sequence layering and the translator sequence pattern.
Hard

Sequence layering decomposes high-level protocol transactions into lower-level physical transactions. A translator sequence runs on the downstream sequencer but holds a handle to the upstream sequencer:

task body();
  forever begin
    up_seqr.get_next_item(hi_item);        // pull high-level item
    foreach (hi_item.payload[i]) begin
      lo_item = lo_item::type_id::create("lo");
      start_item(lo_item);
      lo_item.flit = hi_item.payload[i];
      finish_item(lo_item);
    end
    up_seqr.item_done();   // AFTER all flits sent
  end
endtask
Critical: item_done() after ALL downstream items. Early item_done() causes the upstream sequence to produce the next item while current flits are still in flight.
Q039 How do you pass the register model to a sequence? Why can’t you use this?
Hard

Sequences are uvm_object — not in the component hierarchy. Passing this as config_db context would use a non-component for path resolution, giving wrong results.

Use m_sequencer — the handle to the sequencer that started the sequence, which IS a component with a valid hierarchy path:

if (!uvm_config_db #(gpio_reg_block)::get(
      m_sequencer,  // ← sequencer, not this
      "", "gpio_rm", rm))
  `uvm_fatal("SEQ", "No register model!")
Q040 What is a slave sequence and how does it differ from a master sequence?
Hard

A master sequence initiates transactions — calls start_item/finish_item to push items to the driver.

A slave sequence responds to bus requests — models a slave device. The driver receives a request phase item (address/command), presents it to the sequence, waits for a response item, then drives the response back. The sequence provides the response (look up data, introduce errors).

Used to verify master DUTs where the testbench acts as the responding slave/memory.

05 — Agent, Driver & Monitor 9 Q
Q041 What is the difference between an active and a passive agent?
Easy

Active agent (is_active = UVM_ACTIVE): contains driver + sequencer + monitor. Drives stimulus onto the interface AND observes.

Passive agent (is_active = UVM_PASSIVE): contains monitor ONLY. Observes but drives nothing. Used at integration level to watch interfaces driven by other blocks.

// In agent build_phase — zero code change to agent class:
if (m_cfg.is_active == UVM_ACTIVE) begin
  m_seqr = uvm_sequencer::type_id::create("m_seqr", this);
  m_drv  = my_driver::type_id::create("m_drv",  this);
end
Q042 What are the five golden rules of a UVM monitor?
Easy
  1. Never drive signals — read-only on the interface
  2. Reconstruct complete transactions — assemble all phases before broadcasting
  3. Always broadcast via analysis port — never communicate with scoreboard directly
  4. Never consume time in write() — ap.write() is a function; time-consuming work goes in a separate thread
  5. Use type_id::create() for transactions — allows transaction type to be factory-overridden
Q043 Explain the driver’s get_next_item() / item_done() protocol.
Easy
task run_phase(uvm_phase phase);
  apb_seq_item req;
  vif.PSELx <= 0; vif.PENABLE <= 0;
  @(posedge vif.PRESETn);
  forever begin
    seq_item_port.get_next_item(req); // ① block until item ready
    drive_bus(req);                   // ② drive hardware
    seq_item_port.item_done();        // ③ release; unblocks finish_item()
  end
endtask

item_done() must be called exactly once per get_next_item(). Too early = race. Never called = deadlock.

Q044 When must response fields be populated relative to item_done()?
Medium

Before calling item_done(). item_done() unblocks the sequence which may immediately read the response fields. Fields populated after item_done() create a race — the sequence may read uninitialised values.

do @(posedge vif.PCLK); while (!vif.PREADY);
req.error     = vif.PSLVERR;           // ① populate response
if (req.read_not_write) req.read_data = vif.PRDATA;
seq_item_port.item_done();             // ② THEN unblock
Q045 Why does the agent forward the monitor’s analysis port through its own boundary?
Medium

The agent acts as a black box — the environment connects to the agent’s stable public interface, not to internal sub-components. If the env connected directly to m_agent.m_mon.ap, any internal restructuring of the agent (e.g. splitting the monitor into two) would break all environments using it.

// In agent connect_phase — forward, not a new port:
m_mon.ap.connect(this.ap);
Q046 What is try_next_item() and when do you use it instead of get_next_item()?
Medium

try_next_item(req) is non-blocking — returns immediately with a non-null req if available, or null if nothing is ready. Use it when the protocol requires the driver to actively drive idle signals on every clock even when no transaction is pending:

forever begin
  seq_item_port.try_next_item(req);
  if (req != null) begin
    drive_xfer(req); seq_item_port.item_done();
  end else
    drive_idle_cycle();   // keep bus active with idle
end
Q047 How should a driver handle DUT reset mid-sequence?
Hard
task run_phase(uvm_phase phase);
  forever begin
    fork
      begin: main
        @(posedge vif.PRESETn);   // wait for reset deassert
        forever begin
          seq_item_port.get_next_item(req);
          drive_xfer(req);
          seq_item_port.item_done();
        end
      end
      begin: rst
        @(negedge vif.PRESETn);   // fire on reset assert
        idle_bus();
      end
    join_any
    disable fork;
  end
endtask
Q048 What is the pipelined driver model and what does set_id_info() do?
Hard

A pipelined driver accepts new requests before previous responses arrive. It uses get() (non-blocking) — the sequence unblocks immediately. Responses return via put(rsp):

fork
  forever begin                    // request thread
    seq_item_port.get(req);
    drive_pipeline(req);
  end
  forever begin                    // response thread
    collect_rsp(rsp);
    rsp.set_id_info(req);          // copy sequence + txn IDs
    seq_item_port.put(rsp);
  end
join_none

set_id_info(req) copies the sequence ID and transaction ID from the request so the sequencer can route the response to the correct sequence instance.

Q049 When does the monitor broadcast — transaction start or completion?
Hard

Always at transaction completion — when all fields of the transaction are known.

Broadcasting at start means response fields (read_data, error, status) are not yet valid, making scoreboard comparisons incorrect.

For split-transaction protocols (AXI), the monitor assembles a complete transaction — both request and response phases — before calling ap.write(txn). This may require buffering partial transactions in an associative array keyed by transaction ID.

06 — TLM & Analysis Ports 9 Q
Q050 What is the difference between a TLM port, export, and imp?
Easy
  • Port (uvm_*_port): initiator end — the component that calls put/get/peek. Holds the call.
  • Export (uvm_*_export): pass-through connector — forwards a connection through a component boundary.
  • Imp (uvm_*_imp): terminal end — implements the actual method (scoreboard’s write(), FIFO’s put). Lives in the final receiver.

Connection: port.connect(export) or port.connect(imp). Exports chain to imps.

Q051 How does an analysis port differ from a TLM put port?
Easy
FeatureAnalysis portTLM put port
Connectivity0 to many exportsExactly one export
Blocking?No — functionYes (blocking variant)
DirectionPush broadcastPush unicast
Typical useMonitor → scoreboard/covDriver ↔ sequencer FIFO
Q052 How do you connect one analysis port to multiple subscribers?
Medium
// In env connect_phase — three lines, one port
m_agent.ap.connect(m_sb.analysis_export);
m_agent.ap.connect(m_cov.analysis_export);
m_agent.ap.connect(m_reg_pred.bus_in);
// One ap.write() call broadcasts to all three

The analysis port maintains a dynamic list of subscribers. Each connect() adds to that list. Broadcast happens in connection order.

Q053 What is uvm_subscriber and how does it simplify code?
Medium

uvm_subscriber #(T) pre-declares an analysis_export and requires one method: write(T t). Saves three lines of boilerplate per subscriber:

// Without — you declare+build port yourself
class my_sb extends uvm_component;
  uvm_analysis_imp #(my_item, my_sb) analysis_export;
  function void build_phase(uvm_phase phase);
    analysis_export = new("analysis_export", this); ...

// With uvm_subscriber — just implement write()
class my_sb extends uvm_subscriber #(my_item);
  function void write(my_item t); ... endfunction
endclass
Q054 When and how do you use `uvm_analysis_imp_decl?
Medium

When a component needs multiple analysis inputs of the same type — e.g. a scoreboard receiving from two different monitors. Since only one write() function can exist per class, multiple imps need different method names.

`uvm_analysis_imp_decl(_expected)
`uvm_analysis_imp_decl(_actual)
class my_sb extends uvm_scoreboard;
  uvm_analysis_imp_expected #(my_item, my_sb) exp_export;
  uvm_analysis_imp_actual   #(my_item, my_sb) act_export;
  function void write_expected(my_item t); ... endfunction
  function void write_actual(my_item t);   ... endfunction
endclass
Q055 What is tlm_analysis_fifo and when do you use it?
Medium

tlm_analysis_fifo #(T) bridges between the synchronous analysis broadcast (function, zero-time) and a component that processes items in simulation time (task, blocking).

  • analysis_export — receives write() calls, stores in internal queue. Non-blocking.
  • get_export — scoreboard task calls blocking get() here.
// Scoreboard run_phase can now block freely
forever begin
  m_exp_fifo.get(exp_t);   // blocks until expected arrives
  m_act_fifo.get(act_t);   // blocks until actual arrives
  compare(exp_t, act_t);
end
Q056 How does the RAL predictor connect to the analysis network?
Hard
// In env connect_phase:
m_reg_pred.map     = gpio_rm.APB_map;  // tell predictor which map
m_reg_pred.adapter = m_adapter;        // tell predictor which adapter
m_agent.ap.connect(m_reg_pred.bus_in); // wire monitor → predictor

Every transaction observed by the monitor feeds the predictor’s bus_in analysis export. The predictor calls adapter.bus2reg() to decode the transaction, then calls predict() to update the RAL mirror values automatically.

Q057 What connection error occurs if an analysis port is not connected?
Hard

Analysis ports allow zero connections — a disconnected port is legal; write() becomes a no-op. No compile-time or elaboration-time error. The symptom is silent failure: scoreboard receives nothing, all comparisons produce zero matches, tests pass vacuously.

Debug: Add a check in start_of_simulation_phase: if the port has no subscribers, issue a uvm_warning. Or run with +UVM_VERBOSITY=UVM_FULL and check for zero-subscriber warnings in the port summary.
Q058 What is the seq_item_port and why are TLM ports constructed with new() not type_id::create()?
Hard

seq_item_port is a uvm_seq_item_pull_port #(REQ, RSP) pre-declared in uvm_driver. It provides: get_next_item(), item_done(), get(), put(), try_next_item().

TLM ports, exports, and imps are infrastructure objects — not registered with the factory and not user-extensible via overrides. Using type_id::create() would fail with “type not registered.”

// Always use new() for TLM infrastructure:
ap       = new("ap",       this);
put_port = new("put_port", this);
07 — Scoreboard 6 Q
Q059 What is the predictor/evaluator split and why separate them?
Easy

Predictor: subscribes to the stimulus path. For each transaction driven, computes the expected DUT output — a software model of the DUT’s functional behaviour.

Evaluator (comparator): receives both predicted (from predictor) and actual (from monitor) transactions. Compares and reports mismatches.

Keeping them separate: the predictor is DUT-specific (changes when spec changes); the evaluator is generic (FIFO-based comparison, always reusable). Different evaluation strategies (in-order, out-of-order) plug into the same predictor.

Q060 How does an in-order scoreboard differ from an out-of-order one?
Medium

In-order: expected and actual must arrive in the same sequence. Two FIFOs (expected, actual) — dequeue the front of each and compare. Simple but fails for protocols allowing transaction reordering.

Out-of-order: uses an associative array keyed by transaction ID. When an actual arrives, looks up by ID and compares regardless of arrival order. Required for AXI, PCIe, or any protocol with multiple outstanding transactions that may complete out of order.

Q061 What is a shadow register model in a scoreboard?
Medium

A shadow register is a software copy of the DUT’s register file maintained by the scoreboard. Every observed write is applied to the shadow (including W1C logic). Every observed read is compared against the shadow value.

function void write(apb_seq_item t);
  int idx = t.addr[3:2];
  if (!t.read_not_write) begin
    if (idx == 3) shadow[3] &= ~t.write_data;  // W1C
    else shadow[idx] = t.write_data;
  end else if (t.read_data !== shadow[idx])
    `uvm_error("SB", "MISMATCH")
endfunction
Q062 How do you prevent early phase exit when the scoreboard has pending comparisons?
Hard

Override phase_ready_to_end(). When called, check if FIFOs or queues are non-empty. If so, raise a new objection and fork a drain thread:

function void phase_ready_to_end(uvm_phase phase);
  if (phase.get_name() != "run") return;
  if (m_exp_q.size() > 0 || m_act_q.size() > 0) begin
    phase.raise_objection(this, "SB draining");
    fork begin
      wait(m_exp_q.size()==0 && m_act_q.size()==0);
      phase.drop_objection(this);
    end join_none
  end
endfunction
Q063 Why use `uvm_error not `uvm_fatal in write() for mismatches?
Hard

uvm_fatal kills simulation immediately. Calling it on the first mismatch loses all subsequent mismatch context — you get one error and stop. All cascade effects, subsequent violations, and error count statistics are hidden.

uvm_error logs the error and continues. All mismatches are reported in one run, making root cause identification far easier.

Pattern: use uvm_error in write(). In report_phase, if total error count > 0, optionally issue uvm_error to ensure a non-zero simulator exit code.
Q064 How do you verify the scoreboard actually received transactions (non-silent pass)?
Hard

Add minimum transaction count assertion in check_phase:

function void check_phase(uvm_phase phase);
  if (m_checks == 0)
    `uvm_error("SB",
      "Zero comparisons — possible disconnected analysis port!")
  else if (m_checks < min_expected)
    `uvm_error("SB", $sformatf(
      "Only %0d checks, expected ≥%0d", m_checks, min_expected))
endfunction

Guards against vacuous pass: disconnected analysis port, misconfigured test, or reset never deasserted.

08 — Functional Coverage 7 Q
Q065 What is the difference between code coverage and functional coverage?
Easy

Code coverage: automatic — simulator instruments RTL and measures which lines, branches, and expressions were executed. Tells you that every line ran, not that every meaningful scenario was tested.

Functional coverage: manual — the engineer writes covergroups based on the specification, modelling which features and corner cases must be exercised. 100% code coverage can still miss specification-level bugs if functional coverage was not defined for them.

Q066 What is the CDV loop?
Easy

Coverage-Driven Verification: Plan → Generate → Check → Measure → Refine.

  1. Write a coverage plan listing all features to test
  2. Generate constrained-random stimulus
  3. Check correctness with the scoreboard
  4. Measure functional coverage — identify unvisited bins (holes)
  5. Refine constraints or add directed sequences to target holes
  6. Repeat until coverage reaches the target
Q067 Explain coverpoints, bins, and cross coverage with an APB example.
Medium
covergroup apb_cg;
  cp_rw: coverpoint txn.read_not_write {
    bins rd={1}; bins wr={0};
  }
  cp_addr: coverpoint txn.addr {
    bins data_reg={12'h000}; bins dir_reg={12'h004};
    bins ien_reg ={12'h008}; bins isr_reg ={12'h00C};
  }
  // Cross: every addr×rw combination — 8 bins
  cx_rw_addr: cross cp_rw, cp_addr;
endgroup

A cross bin is hit only when both constituent bins are hit in the same sample — verifying reads AND writes to each register independently.

Q068 Where should a coverage collector live — inside the agent or at env level?
Medium

At the env level, not inside the agent. The agent is protocol-focused and should not know about verification intent. Env-level placement allows:

  • Enable/disable coverage per test via config object without modifying the agent
  • Swap different coverage models via factory override
  • Keep the agent package protocol-only — reusable across projects with different coverage requirements
Q069 Why must covergroups be constructed with new() in the constructor?
Medium

SystemVerilog LRM requirement: covergroup instances must be created with new() in the class constructor. Attempting to construct in build_phase or any other method is a language violation.

function new(string name, uvm_component parent);
  super.new(name, parent);
  my_cg = new();   // ← ONLY in constructor
endfunction

All other object construction (ports, sub-components) goes in build_phase. Covergroup construction is the exception.

Q070 What are transition bins? Give a protocol example.
Hard

Transition bins track sequences of consecutive values across sample events. A bin is hit only when the coverpoint transitions through a specific sequence:

cp_state: coverpoint apb_state {
  bins setup_to_access = (SETUP => ACCESS);
  bins idle_to_setup   = (IDLE  => SETUP);
  bins back2back       = (ACCESS => SETUP => ACCESS);
}

APB protocol example: verifying that back-to-back transfers (ACCESS→SETUP without returning to IDLE) are exercised, and that the illegal IDLE→ACCESS transition never occurs.

Q071 How do you target coverage holes programmatically?
Hard

After measuring coverage, query which bins are uncovered and bias constraints toward them:

// Read individual bin coverage
foreach (apb_cg.cp_addr.get_bins()[b])
  if (apb_cg.cp_addr.b.get_coverage() < 1.0)
    `uvm_info("COV",$sformatf("Hole: %s",b.get_name()),UVM_LOW)

// Directed constraint to target the hole
if (!item.randomize() with {
      addr == 12'h008; read_not_write == 1; })
  `uvm_fatal("SEQ", "rand fail")

Merge coverage databases across multiple regression seeds using the simulator’s coverage merge tool for cumulative results.

09 — Register Abstraction Layer (RAL) 10 Q
Q072 What is the UVM RAL and what problem does it solve?
Easy

The Register Abstraction Layer provides protocol-independent register access. Without RAL, sequences contain APB/AHB/AXI-specific bus calls — changing the bus requires rewriting all sequences. With RAL, sequences call reg.write(status, value); the adapter translates to bus transactions. Swapping the bus only requires changing the adapter.

RAL also provides: automatic mirror tracking; built-in test sequences (hw_reset, bit_bash, access); field-level access with proper masking.

Q073 Describe the five-layer RAL hierarchy.
Easy
  • uvm_reg_field: one named bit-group within a register
  • uvm_reg: one register, containing one or more fields
  • uvm_reg_block: a set of registers at one address space
  • uvm_reg_map: the address map — byte offset per register, bus access type
  • uvm_reg_adapter: translates RAL generic ops to/from specific bus transactions
Also important: uvm_reg_predictor — subscribes to monitor’s ap and keeps the mirror in sync with hardware.
Q074 What is the difference between desired and mirrored values?
Easy

Desired: what software intends the hardware to contain. Set by reg.set(value) with no bus activity. Committed to hardware with reg.update().

Mirrored: last known hardware state, updated by the predictor after each observed bus transfer.

After…DesiredMirrored
Resetreset valuereset value
set(0xFF)0xFF0x00 (no bus cycle)
update()0xFF0xFF (bus write done)
Q075 What is the difference between write(), update(), poke(), and get()?
Medium
  • write(): front-door bus write — generates actual bus cycles, updates mirror
  • set(): sets desired only, no bus activity
  • update(): if desired≠mirror, does a bus write. No activity if already equal.
  • poke(): backdoor write — zero time, forces RTL flip-flop values directly
  • read(): front-door bus read — generates bus cycle, updates mirror
  • peek(): backdoor read — zero time, reads RTL directly
  • get(): returns current mirror value, zero time, no bus access
Use the set/update pattern for multi-field config: set each field, one update() writes the assembled value in a single bus cycle.
Q076 What are the three mandatory RAL wiring lines in env connect_phase?
Medium
// ① Stimulus path: reg methods → sequencer → driver → DUT
gpio_rm.APB_map.set_sequencer(m_agent.m_seqr, m_adapter);

// ② Predictor knows which map and adapter to use
m_reg_pred.map     = gpio_rm.APB_map;
m_reg_pred.adapter = m_adapter;

// ③ Mirror update: monitor → predictor
m_agent.ap.connect(m_reg_pred.bus_in);

Missing ①: reg method calls produce no bus activity. Missing ②: predictor crashes on null map dereference. Missing ③: mirror never updated, all mirror checks pass vacuously.

Q077 What does lock_model() do and what fails if you forget it?
Medium

lock_model() must be the last call in the register block’s build(): it resolves all address offsets, verifies no address collisions, marks the model immutable, and enables address-based register lookup (used by adapter and predictor).

Without it: every register appears to be at address 0; front-door accesses all go to offset 0; the predictor cannot find registers by address — null pointer errors at runtime.

Q078 What does provides_responses in the RAL adapter control?
Medium
  • 0 (default): driver uses item_done(). RAL reads response from the same item after item_done() returns.
  • 1: driver uses get()/put(rsp). RAL waits for explicit put(rsp) before reading response data.

If set wrong:

  • 0 when should be 1: reg.read() returns garbage immediately (reads before put(rsp) arrives)
  • 1 when should be 0: simulation deadlocks — RAL waits forever for a put(rsp) that never comes
Q079 How do you implement a W1C (write-1-to-clear) register field?
Hard
class gpio_isr_reg extends uvm_reg;
  uvm_reg_field int_status;
  virtual function void build();
    int_status = uvm_reg_field::type_id::create("int_status");
    // .configure(parent, size, lsb_pos, access,
    //            volatile, reset, has_reset, is_rand, individually_accessible)
    int_status.configure(this, 32, 0, "W1C", 1, 32'h0, 1, 0, 1);
  endfunction
endclass

RAL access types include: RO, RW, RC, WC, WS, W1C, W1S, W1T, W0C and others. volatile=1 indicates hardware can update this field, so the mirror may diverge from the desired value.

Q080 What are the five UVM built-in register test sequences?
Hard
  • uvm_reg_hw_reset_seq: reads all registers, checks against configured reset values
  • uvm_reg_bit_bash_seq: walking 1s and 0s on all RW bits — verifies data path connectivity
  • uvm_reg_access_seq: front-door write + back-door read; back-door write + front-door read — verifies both paths hit the same flip-flop
  • uvm_mem_walk_seq: walking-ones on every memory location
  • uvm_reg_mem_built_in_seq: runs all of the above on an entire register block in one call
Q081 How do you build a sub-system register map for integration-level verification?
Hard
class pss_reg_block extends uvm_reg_block;
  rand gpio_reg_block gpio;
  rand uart_reg_block uart;
  uvm_reg_map AHB_map;
  virtual function void build();
    AHB_map = create_map("AHB_map", 32'h0, 4, UVM_LITTLE_ENDIAN);
    gpio = gpio_reg_block::type_id::create("gpio");
    gpio.configure(this); gpio.build();
    AHB_map.add_submap(gpio.default_map, 32'h0000);
    uart = uart_reg_block::type_id::create("uart");
    uart.configure(this); uart.build();
    AHB_map.add_submap(uart.default_map, 32'h1000);
    lock_model();
  endfunction
endclass
10 — Debug & End-of-Test 8 Q
Q082 What are the four UVM message severities and when do you use each?
Easy
  • `uvm_info: informational, filtered by verbosity. For trace, status, debug messages.
  • `uvm_warning: non-fatal unexpected condition. Simulation continues. For recoverable anomalies.
  • `uvm_error: verification failure. Simulation continues, error count increments. For scoreboard mismatches, protocol violations.
  • `uvm_fatal: unrecoverable. Simulation terminates immediately. For missing interfaces, null handles, configuration errors.
Q083 What are the UVM verbosity levels in order?
Easy

Ascending verbosity: UVM_NONE (0) → UVM_LOW (100) → UVM_MEDIUM (200, default) → UVM_HIGH (300) → UVM_FULL (400) → UVM_DEBUG (500).

A message is printed only if its level ≤ the current verbosity setting. Set globally: +UVM_VERBOSITY=UVM_HIGH. Per-component: +uvm_set_verbosity=path,id,level,phase.

Q084 List the key UVM debug plusargs and what each does.
Medium
PlusargPurpose
+UVM_VERBOSITY=UVM_HIGHSets global verbosity threshold
+UVM_CONFIG_DB_TRACETraces all config_db set/get calls
+UVM_FACTORY_TRACETraces all factory create() calls
+UVM_TESTNAME=xxxSelects test class to run
+UVM_TIMEOUT=500000,YESSets run_phase timeout in ps
+UVM_MAX_QUIT_COUNT=10Stop after N uvm_errors
+uvm_set_type_override=A,BFactory type override from CLI
+UVM_OBJECTION_TRACETraces all raise/drop objection calls
Q085 Simulation hangs and never ends. What is the debugging procedure?
Medium
  1. Add +UVM_OBJECTION_TRACE — find any raise without a matching drop
  2. Add +UVM_TIMEOUT=1000000,YES — force kill and get a backtrace showing active phases
  3. Check if driver is blocking in get_next_item() with no sequence running
  4. Check if sequence is blocking in finish_item() because driver never called item_done()
  5. Check if a wait() or @(event) condition is waiting for a signal that never transitions
Q086 How do you use print_topology() to debug build phase errors?
Medium
function void end_of_elaboration_phase(uvm_phase phase);
  uvm_top.print_topology();
  uvm_factory::get().print(1);   // show overrides too
endfunction

print_topology() shows the full component hierarchy with each component’s type and instance name. If a component is missing (null handle), it will be absent from the tree. If a factory override did not take effect, the type name will reveal the issue.

Q087 Scoreboard reports zero checks at end of simulation. What are the likely causes?
Medium
  1. Analysis port not connected — missing m_agent.ap.connect(m_sb.analysis_export)
  2. Monitor trigger condition wrong — sampling on PSELx alone instead of PSELx && PENABLE && PREADY
  3. Reset never deasserted — monitor waits on PRESETn posedge that never comes
  4. Scoreboard conditionally disabled — has_scoreboard flag = 0 in config
  5. Test ended before DUT responded — objection dropped before bus transactions complete
Q088 What are report hooks and how do you use them to suppress known errors?
Hard
class my_catcher extends uvm_report_catcher;
  virtual function action_e catch();
    if (get_severity()==UVM_ERROR && get_id()=="KNWN")
      return CAUGHT;   // suppress this known issue
    return THROW;      // pass everything else through
  endfunction
endclass
// Register globally:
uvm_report_cb::add(null, new my_catcher());

Useful in regression to suppress known expected errors, convert warnings to errors during gate-level simulation, or count specific error types without stopping simulation.

Q089 How do you configure UVM to stop after a maximum number of errors?
Hard
// Programmatically in test build_phase:
uvm_root::get().set_report_max_quit_count(10);
// Or command-line (no recompile):
// +UVM_MAX_QUIT_COUNT=10,YES

After 10 uvm_errors, simulation terminates with a fatal. Useful in regressions where a single bug floods the log with thousands of identical errors — limiting to 10 keeps logs readable and simulation time short.

11 — Advanced Topics 9 Q
Q090 Explain the signal-wait pattern on a config object.
Medium

Instead of exposing virtual interface signals directly to sequences, timing helper tasks are added to the config object. The config holds the virtual interface handle, so it can implement the timing tasks:

class bus_agent_cfg extends uvm_object;
  virtual bus_if vif;
  task wait_for_clock(int n=1); repeat(n) @(posedge vif.clk); endtask
  task wait_for_interrupt(); @(posedge vif.irq); endtask
endclass
// In sequence body():
finish_item(item);
m_cfg.wait_for_clock(4);   // 4-cycle inter-transfer gap
Q091 What is vertical testbench reuse and how does the passive agent enable it?
Medium

Vertical reuse means block-level verification components (agent, scoreboard, coverage) work at integration level without modification.

The passive agent only instantiates the monitor. The monitor’s analysis port feeds the same scoreboard and coverage components used at block level. No sequences run (or run on null sequencer). The same agent package is compiled once and instantiated in two different modes at the two hierarchy levels.

Q092 How does bind expose internal DUT signals to a passive monitor?
Medium

bind attaches an interface inside a target RTL module without modifying its source:

bind core_block apb_if apb_probe (
  .PCLK(clk), .PRESETn(rstn),
  .PADDR(core_block.apb_addr),
  .PWDATA(core_block.apb_wdata)
  // ...
);

The passive agent’s virtual interface points to apb_probe, and the monitor observes internal DUT transactions non-invasively — no DUT port modifications required.

Q093 How do you implement an interrupt handler sequence?
Hard

Fork two threads: background stimulus (low priority) and interrupt monitor. When IRQ fires, start a high-priority ISR sequence using grab():

task body();
  fork
    bg_seq.start(seqr, this, 100);   // low priority
    forever begin
      m_cfg.wait_for_interrupt();
      isr_seq = apb_isr_seq::type_id::create("isr");
      isr_seq.start(seqr, this, 500); // high priority
    end
  join_any
endtask

ISR sequence: grab() → read ISR reg → W1C clear → ungrab().

Q094 How do you cleanly stop a background forever-loop sequence?
Hard

Use a stop flag with a while(!stop) loop — the sequence exits cleanly at the end of its current item without interrupting a mid-handshake transfer:

class stoppable_seq extends uvm_sequence;
  bit stop_flag = 0;
  task body();
    while(!stop_flag) begin
      start_item(req);
      if(!req.randomize()) `uvm_fatal("SEQ","rand fail")
      finish_item(req);
    end
  endtask
endclass
// From virtual sequence:
seq.stop_flag = 1;   // exits after current item completes
Q095 Why should you never include a class definition in two packages?
Hard

A class’s fully-qualified type is package::class_name. Including the same source file into two packages creates two distinct types — even though the code is identical.

Effects:

  • Factory overrides fail silently — factory maps pkg_a::my_driver, override registers pkg_b::my_driver
  • $cast() returns 0 between the two — pkg_a::apb_item and pkg_b::apb_item are different types
  • Analysis port type errors — port parameterised on pkg_a::T will not accept pkg_b::T

Fix: every class lives in exactly one package. Other packages import it — never `include it again.

Q096 Describe the complete execution path from a RAL write() call to hardware changing.
Hard
  1. rm.data_reg.write(status, 32'hDEAD, .parent(this)) — sequence calls RAL
  2. RAL creates uvm_reg_bus_op (kind=WRITE, addr=0x00, data=0xDEAD)
  3. adapter.reg2bus(rw) → creates apb_seq_item (read_not_write=0, addr=0x00, write_data=0xDEAD)
  4. RAL calls start_item/finish_item on the sequencer
  5. Driver’s get_next_item() returns the apb_seq_item
  6. Driver drives SETUP phase (PADDR, PWDATA, PSELx=1, PENABLE=0)
  7. Driver drives ACCESS phase (PENABLE=1), waits for PREADY
  8. DUT register updates; driver captures PSLVERR, calls item_done()
  9. Monitor observes PSELx && PENABLE && PREADY, broadcasts txn via ap
  10. Predictor receives txn, calls adapter.bus2reg(), calls predict() — mirror updated
Q097 What is the package compilation hierarchy and why does it matter?
Hard
uvm_pkg                     ← base
    ↑
agent_pkg / register_pkg   ← import uvm_pkg only
    ↑
env_pkg / sequence_pkg     ← import uvm_pkg + agents
    ↑
test_pkg                   ← imports everything

Why it matters: each class lives in exactly one package; other packages import it. This prevents type duplication, enables staged compilation (changed packages recompile only their dependents), and ensures factory registration when the test package is imported in tb_top.

Common mistake: forgetting to import the test package in tb_top. Test classes are never registered → run_test(“my_test”) fails with “type not found.”
Q098 How do you implement a watchdog sequence?
Medium
class watchdog_seq extends uvm_sequence;
  `uvm_object_utils(watchdog_seq)
  int timeout_cycles = 1000;
  task body();
    my_agent_cfg cfg;
    uvm_config_db #(my_agent_cfg)::get(m_sequencer,"","cfg",cfg);
    cfg.wait_for_clock(timeout_cycles);
    `uvm_fatal("WD", "DUT did not complete within timeout!")
  endtask
endclass
// In run_phase: start watchdog and main sequence in fork/join_any
// If main sequence finishes first, watchdog abandoned cleanly
12 — Complete Testbench Patterns 7 Q
Q099 Walk through the execution order from run_test() to the first driver transaction.
Easy
  1. run_test() → factory creates test (uvm_test_top)
  2. build_phase top-down: test → env → agent → driver, monitor, sequencer
  3. connect_phase bottom-up: driver↔sequencer TLM, agent ap forwarding
  4. end_of_elaboration: topology print, factory print
  5. start_of_simulation: drain time, coverage group setup
  6. run_phase fork: all components’ run_phase tasks start concurrently
  7. Test raises objection, starts sequence
  8. sequence.start(seqr) → body() executes
  9. start_item(req) waits for sequencer grant
  10. finish_item(req) → driver’s get_next_item() returns → driver executes drive_xfer()
Q100 What is the correct order in a test’s build_phase?
Medium
  1. Create config objects (factory create)
  2. Populate configs (retrieve vif from config_db, set is_active, set parameters)
  3. Push configs into config_db for sub-components
  4. Create the environment (after pushing config)
Common mistake: creating the env BEFORE pushing its config into config_db. The env’s build_phase runs immediately after creation — if config_db::get() runs before config_db::set(), it fails with a fatal error. Push config first, create env second.
Q101 What is a UVM reuse checklist?
Medium
  • All components created via factory (type_id::create)
  • Virtual interface accessed only through config object
  • Active/passive mode controlled by config object flag
  • No hardcoded paths in config_db lookups
  • Sequences use RAL for register access (protocol-independent)
  • Analysis port forwarded through agent boundary
  • Scoreboard uses predictor/evaluator split
  • No $display — all messages use UVM macros
  • All classes in packages, no duplicate includes
  • Test package imported in tb_top
Q102 Why must `include "uvm_macros.svh" appear before all UVM class definitions?
Hard

SystemVerilog compilation is sequential. The UVM macros (`uvm_object_utils, `uvm_fatal, etc.) expand at the point of use — if the include hasn’t been processed yet, the compiler sees undefined identifiers. Similarly, import uvm_pkg::* brings UVM types into scope — any class definition extending uvm_object or uvm_component requires them visible at the point of definition.

Never duplicate the include inside a module — it belongs at the file top (before all class definitions), NOT inside the top module body.
Q103 How does EDA Playground’s design.sv/testbench.sv split map to the UVM two-kingdom concept?
Hard

design.sv = HDL kingdom: RTL modules (gpio_dut) and SystemVerilog interfaces (apb_if). No UVM includes. Compiled first.

testbench.sv = OOP kingdom: all UVM classes (driver, monitor, agent, env, test) plus the static top module that bridges the two kingdoms. The bridge is the initial block in gpio_tb_top — it calls config_db::set(null, ..., "vif", APB), crossing a handle from the HDL world (interface instance APB) into the UVM class world (config_db entry).

Q104 How would you modify the GPIO testbench to support a 64-bit data width without touching any sequence code?
Hard

Three changes, none in sequence code:

  1. test_params_pkg: change parameter int DW = 32 to DW = 64
  2. apb_if: PWDATA/PRDATA width uses the package parameter — logic [DW-1:0] PWDATA, PRDATA
  3. apb_seq_item: rand logic [DW-1:0] write_data uses the parameter

Sequences call rm.data_reg.write() — the RAL handles field width internally. The scoreboard shadow is logic [DW-1:0]. Everything adapts automatically through the one parameter change.

This is exactly why RAL + config objects + parameterised interfaces exist: one parameter change propagates through the entire testbench.
Q105 What common interview mistake do candidates make when describing the UVM factory?
Hard

Describing the factory as only a “registry” without explaining the override mechanism — which is the entire verification purpose.

A complete answer covers five points:

  1. Registration: `uvm_component_utils registers types at elaboration
  2. Creation via factory: type_id::create() checks override table before creating
  3. Type override: set_type_override() redirects all instances everywhere
  4. Instance override: scoped to a specific hierarchy path
  5. Use case: swap error-injecting driver for one test with a single override call in test build_phase, zero other code changes

Second common mistake: saying “call new() through the factory”. You never do — new() bypasses the factory. Always type_id::create().

13 — Additional Practice 10 Q
Q106 What is the UVM heartbeat mechanism and when do you use it?
Medium

A UVM heartbeat is a watchdog that monitors whether registered components are making periodic progress. Components register with a uvm_heartbeat object and must call beat() within the configured timeout window — otherwise a fatal or error is issued.

Use cases: detecting hung drivers waiting forever for PREADY; detecting sequences deadlocked waiting for a response; catching hangs in long simulations where a simple phase timeout alone is insufficient because the timeout fires too early or too late.

Q107 What is uvm_resource_db and how does it relate to uvm_config_db?
Medium

uvm_config_db is actually a convenience wrapper around uvm_resource_db — it adds hierarchical scoping and priority resolution on top of the raw resource database. Directly using uvm_resource_db skips the scope matching and is used for global configuration that does not depend on component hierarchy.

Example use: setting RAL exclusion attributes on registers with the "REG::" prefix — these are global settings that any sequence can see regardless of where in the hierarchy it runs.

uvm_resource_db #(bit)::set(
  {"REG::", rm.isr_reg.get_full_name()},
  "NO_REG_HW_RESET_TEST", 1);
Q108 How do you handle unexpected transactions in an out-of-order scoreboard?
Medium

In an ID-keyed associative array: if m_exp_map.exists(txn.id) returns false when an actual arrives, report an error — unexpected transaction with no matching prediction.

In check_phase, verify the expected map is empty — any remaining entries are predictions that never arrived (missing actual responses):

function void check_phase(uvm_phase phase);
  if (m_exp_map.size() > 0)
    `uvm_error("SB", $sformatf(
      "%0d expected txns never matched", m_exp_map.size()))
endfunction
Q109 What is the difference between automatic bins and explicit bins in a coverpoint?
Medium

Automatic bins: the simulator partitions the value space into equal bins (default 64 bins). Fast to write but may create thousands of bins for wide signals, making reports unreadable and coverage uninformative.

Explicit bins: the engineer defines which values map to which bin with meaningful names:

cp_size: coverpoint txn.burst_len {
  bins single    = {0};
  bins short     = {[1:15]};
  bins max_burst = {255};
  bins illegal   = default;  // catches unspecified values
}

Explicit bins are always preferred for protocol verification — they map directly to specification corner cases.

Q110 What is the difference between $cast() and direct assignment in UVM?
Medium

A parent class handle can be directly assigned from a child handle (upcast). But a child handle cannot be directly assigned from a parent handle (downcast) — this requires $cast().

In UVM, downcasting is critical in do_copy() and do_compare() where the argument type is uvm_object:

function void do_copy(uvm_object rhs);
  apb_seq_item r;
  if (!$cast(r, rhs))
    `uvm_fatal("ITEM","Type mismatch in do_copy")
  super.do_copy(rhs);
  addr = r.addr; write_data = r.write_data;
endfunction
Q111 Explain the abstract/concrete pattern for BFM-based drivers in detail.
Hard

When a driver needs to access signals in a legacy Verilog BFM module, a class cannot extend a module. The pattern:

  • Abstract class (in a package) — extends uvm_driver, declares pure virtual tasks for all BFM operations: pure virtual task drive_write(input logic [11:0] addr, input logic [31:0] data).
  • Concrete class (inside the interface or BFM module) — extends the abstract class, implements the pure virtual tasks with actual signal-driving code. Lives in the module scope, so it can see local signals directly.

A factory instance override connects them: the env creates the abstract type via type_id::create(); the override replaces it with the concrete class defined inside the BFM module. The concrete class is the exception to the “all classes in packages” rule — it intentionally lives inside the static world to access those signals.

Q112 What is the correct way to write do_compare() for a sequence item?
Medium

do_compare() must compare only protocol-meaningful stimulus fields — not timestamps, sequence IDs, or response fields that differ by construction:

function bit do_compare(uvm_object rhs, uvm_comparer c);
  apb_seq_item r;
  if (!$cast(r, rhs)) return 0;
  return (addr           == r.addr &&
          read_not_write == r.read_not_write &&
          byte_en        == r.byte_en &&
          (read_not_write
            ? read_data  == r.read_data    // compare DUT response
            : write_data == r.write_data));
endfunction
Do NOT compare: sequence ID, transaction ID, timestamp fields. DO compare: read_data when comparing expected (predictor) vs actual (monitor) — that IS the functional check.
Q113 How do you use the virtual configuration method pattern in a base test?
Hard

Define a protected virtual configuration method in the base test. Derived tests override only the configuration, not the entire build_phase:

class gpio_base_test extends uvm_test;
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_cfg = gpio_env_cfg::type_id::create("m_cfg");
    configure_env(m_cfg);       // virtual hook
    // ... rest of build
  endfunction
  virtual function void configure_env(gpio_env_cfg cfg);
    cfg.has_coverage = 1;
    cfg.has_scoreboard = 1;
  endfunction
endclass

class gpio_fast_test extends gpio_base_test;
  virtual function void configure_env(gpio_env_cfg cfg);
    super.configure_env(cfg);
    cfg.has_coverage = 0;       // disable for speed
  endfunction
endclass
Q114 Why must super.build_phase() always be the first call in an overriding build_phase?
Hard

super.build_phase(phase) must be called first because the base class build_phase processes field automation set via `uvm_component_utils_begin/.._end macros, applies config_db values bound through the automation system, and performs infrastructure initialisation that subsequent factory create() calls depend on.

If not called, automated field population from config_db is skipped — certain tool-specific UVM infrastructure may not initialise — leading to subtle failures that are hard to trace because the component works correctly in simple cases but fails in regression.

Rule: always call super.build_phase(phase) as the very first statement.
Q115 How do you verify that no class in your testbench was included in multiple packages?
Hard

Symptoms of duplicate includes: $cast() fails between identical-looking types; factory overrides silently ignored; analysis port type errors at connect_phase.

Prevention checklist:

  • Every .sv class file is included in exactly one package file — search for `include "my_class.sv" across all package files and ensure no duplicates
  • Use a file dependency diagram: arrow from pkg_A to pkg_B means A imports B — no circular dependencies
  • Enable +UVM_FACTORY_TRACE and look for the same class name registered from two different package paths
  • If a $cast() fails unexpectedly, print both objects with get_type_name() — if they show the same name but cast fails, the type is in two packages
Q116 How do you exclude a register from a built-in test sequence?
Medium

Use uvm_resource_db with the "REG::" prefix:

uvm_resource_db #(bit)::set(
  {"REG::", m_cfg.gpio_rm.isr_reg.get_full_name()},
  "NO_REG_HW_RESET_TEST", 1);

Available attributes: NO_REG_HW_RESET_TEST, NO_REG_BIT_BASH_TEST, NO_REG_ACCESS_TEST. Common exclusion reasons: W1C registers (reset test fails because read clears them), write-only registers (readback always returns 0), clock-control registers (random writes halt the clock).

Q117 How do you set verbosity for a single component without changing source code?
Hard

Via command-line plusarg targeting a specific component path:

+uvm_set_verbosity=uvm_test_top.m_env.m_agent.m_drv,_ALL_,UVM_FULL,run

Or programmatically in the test:

m_env.m_agent.m_drv.set_report_verbosity_level(UVM_FULL);
// Global hierarchy:
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);

The component-specific verbosity overrides the global +UVM_VERBOSITY setting, allowing you to see full driver trace without drowning in monitor and scoreboard messages.

Q118 Where should randomisation happen in the start_item/finish_item window, and why?
Medium

After start_item() and before finish_item(). This is the canonical UVM pattern.

Reason: start_item() may invoke a mid_do() callback that applies directed constraint overrides. If randomisation happens before the grant, these overrides cannot affect values. Randomising in the window between the two calls allows callbacks and constraint inheritance from the parent sequence to work correctly.

Pattern: create item → start_item (wait for grant) → randomize with assertion → finish_item (send and wait for done).
Q119 What is the difference between get_next_item() and try_next_item()? When does each one apply?
Medium

get_next_item(req): blocking — driver waits until the sequence provides an item. The driver is idle until stimulus arrives. Use for simple non-pipelined protocols where idle cycles are acceptable.

try_next_item(req): non-blocking — returns immediately with null if no item is ready. Use for protocols that require the bus to be driven on every clock cycle (e.g. AXI where TVALID must toggle, or protocols that detect idle by missing valid transitions).

forever begin
  seq_item_port.try_next_item(req);
  if (req != null) begin drive_xfer(req); seq_item_port.item_done(); end
  else drive_idle_cycle();
end
Q120 What does a well-structured UVM test package look like? What must be in tb_top?
Medium

test_pkg.sv:

package test_pkg;
  import uvm_pkg::*;
  import gpio_agent_pkg::*;
  import gpio_env_pkg::*;
  `include "uvm_macros.svh"
  `include "gpio_base_test.sv"
  `include "gpio_rand_test.sv"
endpackage

tb_top (essential elements):

`include "uvm_macros.svh"
import uvm_pkg::*;
import test_pkg::*;   // registers all test classes

initial begin
  uvm_config_db #(virtual apb_if)::set(
    null, "uvm_test_top", "vif", APB);
  run_test();  // +UVM_TESTNAME selects test
end
Scroll to Top