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

UVM Question & Answer Bank

75 curated questions across all major UVM topics — from basics to RAL, sequences, TLM, and debug. Filter by topic or difficulty.

75Questions
10Topics
3Difficulty levels
01 — UVM Basics & Architecture 8 Q
01 What is UVM and why is it used for verification? Easy

UVM (Universal Verification Methodology) is a standardised, class-library-based SystemVerilog framework for building modular, scalable testbenches.

Key reasons:

  • Reuse — components can be shared across projects and integration levels.
  • Standardisation — all engineers follow the same structure.
  • Scalability — block-level environments compose into integration-level environments.
  • Built-in infrastructure — factory, config_db, TLM ports, phase system, and reporting are provided out of the box.
UVM is built on top of SystemVerilog OOP — classes, polymorphism, and inheritance are central to every UVM component.
02 Describe the standard UVM testbench hierarchy from top to bottom. Easy

From top to bottom:

  1. tb_top module — instantiates DUT and interfaces, sets virtual interfaces in config_db, calls run_test().
  2. Test (uvm_test) — configures the environment; starts sequences.
  3. Environment (uvm_env) — contains agents, scoreboards, coverage collectors, register model.
  4. Agent (uvm_agent) — groups driver + sequencer + monitor for one interface protocol.
  5. Driver — converts sequence items into pin-level signals.
  6. Sequencer — arbitrates between sequences and feeds items to the driver.
  7. Monitor — passively observes interface and broadcasts transactions.
03 What are UVM phases? Name all 12 and group them. Easy

Build phases (top-down, functions): build → connect → end_of_elaboration → start_of_simulation

Run-time (task-based, consumes time): run_phase (contains reset → configure → main → shutdown sub-phases)

Cleanup phases (bottom-up, functions): extract → check → report → final

Key: Build phases execute top-down. Connect and cleanup execute bottom-up. Only task-based phases consume simulation time.
04 What is the difference between uvm_component and uvm_object? Medium

uvm_component — base for structural testbench elements. Has a parent, participates in phases, lives for the entire simulation. Examples: driver, monitor, agent, env, test. Registered with `uvm_component_utils.

uvm_object — base for transient data objects. No parent, no phases, created and destroyed on demand. Examples: sequence items, sequences, config objects, register fields. Registered with `uvm_object_utils.

05 What is the difference between active and passive agents? Medium

Active agent (is_active = UVM_ACTIVE) builds driver, sequencer, AND monitor. Drives stimulus and observes.

Passive agent (is_active = UVM_PASSIVE) builds monitor ONLY. Observes but drives nothing. Used at integration level to monitor internal buses.

The switch is made through the config object — zero code changes to the agent class itself:

if (m_cfg.is_active == UVM_ACTIVE) begin
  m_seqr = ...; m_drv = ...
end
Passive agents still fire their analysis ports. Block-level scoreboards and coverage keep working at integration level.
06 How does uvm_config_db work? Explain set() and get(). Medium

uvm_config_db is a global typed key-value database. Any component can store data for any other component without direct handle references.

// set() — store a value
uvm_config_db #(virtual apb_if)::set(
  null, "uvm_test_top", "vif", APB);

// get() — retrieve it
if (!uvm_config_db #(virtual apb_if)::get(
      this, "", "vif", vif))
  `uvm_fatal("DRV", "No vif!")

The lookup matches the set() scope against the component’s path. Wildcards like "*" match any path. The key (4th argument of set, 3rd of get) must match exactly.

07 Why must uvm_component constructors only call super.new() and covergroup construction? Hard

The UVM build phase is top-down and deliberate: the test builds the env, the env builds agents, agents build drivers. This ordering lets each level push config to the level below before it constructs its children.

If children are built in the constructor, construction happens before the phase system starts — before the parent has received any configuration. config_db lookups fail, virtual interfaces are null, and the is_active flag has not been set.

Additionally, factory overrides may not yet be registered. Constructing children before elaboration means the override table is incomplete — the wrong type gets created.

Rule: Constructor = super.new() + covergroup construction only. Everything else goes in build_phase().
08 What is the two-kingdoms problem and how is it solved? Hard

SystemVerilog has two worlds that cannot reference each other: the static world (modules, interfaces — created at elaboration) and the dynamic world (classes — created at runtime).

A driver (class) needs to drive signals on an interface (static). Classes cannot directly hold references to module/interface instances.

Solution: virtual interface. A virtual keyword makes an interface handle class-compatible, bridging the two kingdoms:

// tb_top: set interface into config_db
uvm_config_db #(virtual apb_if)::set(null, "uvm_test_top", "vif", APB);

// driver: get it back as a class-type variable
virtual apb_if vif;
uvm_config_db #(virtual apb_if)::get(this, "", "vif", vif);
02 — Factory 6 Q
09 What is the UVM factory and why is it used? Easy

The UVM factory is a registry mapping type names to class definitions. When a component is created via type_id::create(), the factory checks for overrides and returns the overriding type if one exists.

Purpose: substitute component types without modifying testbench code. An error-injecting driver can replace a normal driver for a specific test with a single override call. Requires registration (`uvm_component_utils) and creation via type_id::create().

10 What is the difference between a type override and an instance override? Medium

Type override — replaces every instance of a type everywhere:

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

Instance override — replaces only at a specific component hierarchy path:

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

Instance overrides take precedence over type overrides. Both are also available from the command line via +uvm_set_type_override and +uvm_set_inst_override.

11 Why must objects always be created with type_id::create() instead of new()? Medium

Using new() directly bypasses the factory — no overrides are applied, the object cannot be debugged via factory tools, and print_topology cannot inspect it.

// Wrong — bypasses factory
m_drv = new("m_drv", this);

// Correct — goes through factory
m_drv = my_driver::type_id::create("m_drv", this);
Exception: TLM ports, exports, and imps (uvm_analysis_port, etc.) are infrastructure objects — always use new("name", this) for these.
12 How do you debug a factory override that doesn’t seem to be taking effect? Medium

Three steps:

  1. Print the factory registry: uvm_factory::get().print(1); in end_of_elaboration_phase. Verify the override appears and type names match exactly.
  2. Check override was set before create(): Overrides must be registered before the factory creates the component. Override in test build_phase, before creating the env.
  3. Verify the substitute class is registered: If the override class’s package was not imported into tb_top, it was never registered with the factory.
// Check what type would be created at a specific path
uvm_factory::get().debug_create_by_type(
  my_driver::get_type(),
  "uvm_test_top.m_env.m_agent", "m_drv");
13 Why can parameterised classes not use `uvm_component_utils directly? Hard

Parameterised classes are templates, not concrete types. The factory registration macros use the class name as a string key — a template has no single name until specialised with parameter values.

Solution: create a typedef specialisation and register that:

class base_drv_t #(parameter int DW=32) extends uvm_driver;
  // No `uvm_component_utils — cannot register template
endclass

typedef base_drv_t #(32) base_drv;
`uvm_component_utils(base_drv)

Each parameter combination requires a separate typedef.

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

When a driver needs to access signals in a Verilog/VHDL BFM module (the static world), a class cannot extend a module.

The pattern:

  • 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 module/interface) — extends the abstract class, implements the pure virtual tasks with actual signal-driving code. It lives inside the module scope where it can see local signals.

A factory instance override connects them: the env creates the abstract type; the override replaces it with the concrete type defined inside the module. Concrete classes are the exception to the ‘define all classes in packages’ rule.

03 — Sequences & Sequence Items 10 Q
15 What is the start_item / finish_item handshake in a sequence? Easy

start_item(item) blocks until the sequencer grants access to the driver (arbitration). finish_item(item) sends the item to the driver and blocks until the driver calls item_done().

task body();
  item = my_item::type_id::create("item");
  start_item(item);                 // wait for arbitration
  if (!item.randomize() with {addr==12'h004;})
    `uvm_fatal("SEQ","randomize failed")
  finish_item(item);                // send, wait for done
endtask
16 What fields should a sequence item contain? Stimulus vs response? Easy

Stimulus (rand) — values the sequence drives: address, write data, byte enables. Randomised.

Response (non-rand) — values the driver fills in after the bus transfer: read data, error status. Not randomised — they represent what the DUT returned.

Control — transaction type, delay counts.

Constraints — restrict stimulus to legal values.

do_compare() must only compare stimulus fields. Comparing response fields causes false scoreboard failures because both sides carry the same DUT output data.
17 Why should you avoid `uvm_do and `uvm_do_with macros? Medium

The `uvm_do/`uvm_do_with macros hide the start_item/finish_item handshake. Problems:

  • No randomisation check — silent constraint failures produce invalid stimulus.
  • No code between start_item and finish_item — cannot perform operations between the two calls.
  • Opaque debugging — errors are reported inside macro expansion.

Always use the explicit pattern:

item = my_item::type_id::create("item");
start_item(item);
if (!item.randomize() with {addr==12'h000;})
  `uvm_fatal("SEQ","randomize failed")
finish_item(item);
18 What is a virtual sequence and when is it used? Medium

A virtual sequence coordinates stimulus across multiple sequencers without itself generating items. It holds handles to real sequencers and starts sub-sequences on each.

Used when a test needs synchronised activity on more than one interface — e.g. APB config while simultaneously driving SPI data.

class sys_vseq extends uvm_sequence;
  apb_sequencer apb_seqr; spi_sequencer spi_seqr;
  task body();
    fork
      apb_seq.start(apb_seqr, this);
      spi_seq.start(spi_seqr, this);
    join
  endtask
endclass
19 How does sequence arbitration work? What modes are available? Medium

When multiple sequences call start_item() simultaneously, the sequencer arbitrates. Six built-in modes:

  • SEQ_ARB_FIFO (default) — first-come, first-served.
  • SEQ_ARB_WEIGHTED — random weighted by priority.
  • SEQ_ARB_RANDOM — uniformly random.
  • SEQ_ARB_STRICT_FIFO — FIFO within priority groups; higher priority drains first.
  • SEQ_ARB_STRICT_RANDOM — random within highest-priority group.
  • SEQ_ARB_USER — custom user function.

Priority is the third argument of seq.start(seqr, parent, priority). Higher value = higher priority.

20 What is the difference between grab() and lock() on a sequencer? Medium

Both give exclusive access to a sequencer, blocking all other sequences.

  • lock() — queues a lock request. Granted after all currently-pending requests ahead of it complete.
  • grab() — immediately takes exclusive control, pre-empting any pending requests. The next get_next_item() only serves the grabbing sequence.

Both released with unlock()/ungrab(). Grab is appropriate for ISR-style sequences that must respond immediately to an interrupt.

21 Explain sequence layering. When would you use it? Hard

Sequence layering models multi-layer protocols (USB, PCIe, MIPI). A layering component contains intermediate sequencers and runs translator sequences that pull high-level items from an upstream sequencer and decompose them into low-level items for the downstream sequencer.

task body();
  forever begin
    up_seqr.get_next_item(hi_item);
    foreach (hi_item.payload[i]) begin
      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
22 How do you pass the register model to a sequence? Why can’t you use ‘this’ as context? Hard

Sequences are uvm_object — not in the component hierarchy. Passing this as context in config_db::get() 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, "", "gpio_rm", rm))
  `uvm_fatal("SEQ","No register model!")
23 What are slave sequences and when are they used? Hard

A slave sequence models the responding side of a protocol. Unlike normal sequences that generate stimulus, a slave sequence waits for the driver to present a request item, then randomises and returns a response.

The driver uses two get_next_item() calls — once for the request phase (address/command), once for the response phase (slave provides data).

Used to verify master DUTs (DUT initiates transactions; testbench acts as the slave/memory).

24 Why must item_done() be called after all downstream items in a translator sequence? Hard

Calling up_seqr.item_done() unblocks the upstream sequence, which immediately generates its next item. If called before all downstream items (flits) for the current upstream item are sent, the upstream sequence produces the next item while current flits are still in flight.

Result: flits from two different upstream items are interleaved at the physical layer, causing ordering violations and scoreboard mismatches.

Rule: call up_seqr.item_done() after the last finish_item() for all downstream items from the current upstream item.
04 — Driver 6 Q
25 What is the difference between get_next_item()+item_done() and get()+put()? Easy

get_next_item() + item_done(): Holds the sequence in a suspended state until item_done(). The sequence cannot generate the next item until item_done() is called. Response is in the same item object. Used for non-pipelined drivers.

get() + put(): Non-holding. The sequence unblocks immediately after get() — it can generate the next item while the driver processes the current one. Response returned via separate put(rsp). Used for pipelined drivers.

26 How should a driver handle reset? Show the pattern. Medium
task run_phase(uvm_phase phase);
  forever begin
    fork
      begin : drive_loop
        @(posedge vif.PRESETn);
        forever begin
          seq_item_port.get_next_item(req);
          drive_xfer(req);
          seq_item_port.item_done();
        end
      end
      begin : reset_detect
        @(negedge vif.PRESETn);
      end
    join_any
    disable drive_loop;
    drive_idle_signals();
  end
endtask
27 What is try_next_item() and when should it be used? Medium

try_next_item(req) is non-blocking. If a sequence item is available it populates req and returns non-null. If nothing is ready it returns null immediately.

Use it when the protocol requires the driver to actively drive idle signals on every clock even when no transaction is pending:

@(posedge vif.PCLK);
seq_item_port.try_next_item(req);
if (req != null) begin drive_xfer(req); seq_item_port.item_done(); end
else drive_idle();
28 In a bidirectional driver (APB), when must response fields be populated relative to item_done()? Medium

Response fields (read data, error) must be populated before calling item_done(). item_done() unblocks the sequence, which may immediately read the response fields. If fields are populated after item_done(), the sequence reads 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
29 Explain pipelined driver operation. What must the sequence do to support it? Hard

A pipelined driver uses get() (immediate sequence unblock). Responses arrive later via put(rsp). Two forked threads handle stimulus and response separately:

fork
  forever begin              // stimulus thread
    seq_item_port.get(req);
    drive_request(req);
  end
  forever begin              // response thread
    get_pipeline_rsp(rsp);
    rsp.set_id_info(req);    // REQUIRED
    seq_item_port.put(rsp);
  end
join_none

set_id_info(req) is mandatory — copies sequence ID and transaction ID so the sequencer routes the response to the correct sequence.

30 What does provides_responses mean in a RAL adapter and what goes wrong if it’s set incorrectly? Hard

provides_responses tells the RAL how the driver returns response data:

  • 0 — driver uses item_done(). RAL reads response from the item after item_done() returns.
  • 1 — driver uses get()/put(rsp). RAL waits for an explicit put(rsp) before reading response.

Mismatched:

  • 0 when should be 1: reg.read() returns garbage data immediately (read before put(rsp) arrives).
  • 1 when should be 0: simulation deadlocks — RAL waits forever for a put(rsp) that never comes.
05 — Analysis & TLM Ports 7 Q
31 What is an analysis port and how does it differ from other TLM ports? Easy

An analysis port is one-to-many and non-blocking. ap.write(t) calls write(t) on ALL connected exports simultaneously and returns immediately — the analysis path cannot stall the driver.

Other TLM ports are point-to-point (exactly one export) and can be blocking (operation may wait). Analysis ports allow 0 to N connections; other TLM ports require exactly 1.

32 What is uvm_subscriber? When do you extend it instead of uvm_component? Easy

uvm_subscriber #(T) pre-declares a uvm_analysis_imp named analysis_export and requires one method: write(T t). Extend it for simple single-input subscribers — scoreboards, coverage collectors, predictors.

Extend uvm_component directly when you need more than one analysis input (e.g. a scoreboard with separate expected and actual streams requiring two different write() implementations).

33 How do you connect multiple analysis exports to the same component? Medium

Use `uvm_analysis_imp_decl to create uniquely-named imp classes with unique write() method suffixes:

`uvm_analysis_imp_decl(_from_a)
`uvm_analysis_imp_decl(_from_b)

class my_sb extends uvm_scoreboard;
  uvm_analysis_imp_from_a #(txn_a, my_sb) a_export;
  uvm_analysis_imp_from_b #(txn_b, my_sb) b_export;
  function void write_from_a(txn_a t); /*...*/ endfunction
  function void write_from_b(txn_b t); /*...*/ endfunction
endclass
34 What is tlm_analysis_fifo and how does it decouple the scoreboard from the monitor? Medium

The monitor’s ap.write() is a function — it must return immediately. A scoreboard run_phase needs to block on get() calls. tlm_analysis_fifo buffers between the two:

  • analysis_export — receives write() calls, stores in queue. Non-blocking.
  • get_export — scoreboard task calls blocking get() from here.
task run_phase(uvm_phase phase);
  forever begin
    m_exp_fifo.get(exp_t); m_act_fifo.get(act_t);
    compare(exp_t, act_t);
  end
endtask
35 What is the difference between blocking_put, blocking_get, and blocking_transport? Medium
  • blocking_put — initiator pushes data to responder. put(t) blocks until responder accepts. Direction: initiator → responder.
  • blocking_get — initiator pulls data from responder. get(t) blocks until data available. Direction: responder → initiator.
  • blocking_transport — send request AND receive response atomically. transport(req, rsp) blocks for the complete round-trip. For request-response pairs without pipelining.
36 What is seq_item_port and what makes it different from a standard TLM port? Hard

seq_item_port is a uvm_seq_item_pull_port #(REQ,RSP) built into every uvm_driver. It provides the complete driver-sequencer protocol: get_next_item, item_done, get, put, try_next_item, peek_next_item.

Unlike standard TLM ports, it is declared and constructed automatically by the uvm_driver base class. You never create it — just connect it: m_drv.seq_item_port.connect(m_seqr.seq_item_export) in agent connect_phase.

37 Why must TLM ports be constructed with new() rather than type_id::create()? Hard

TLM ports are infrastructure objects — not user-extensible types that would ever need factory overrides. They are not registered with `uvm_component_utils and cannot be created via the factory.

// Correct — always use new() for TLM infrastructure
ap       = new("ap",       this);
put_port = new("put_port", this);
m_fifo   = new("m_fifo",   this);
06 — Scoreboard 6 Q
38 What is the predictor-evaluator scoreboard architecture? Easy

Predictor — receives the same stimulus as the DUT and computes expected responses. A model of what the DUT should produce.

Evaluator (comparator) — receives predicted and actual transactions, compares them, reports mismatches.

Separation makes maintenance easier: specification changes only require updating the predictor. The comparator is generic and reusable. For simple register-like DUTs, a shadow register model in write() is often sufficient.

39 How does phase_ready_to_end() work in a scoreboard and why is it needed? Medium

When the test drops its last objection, UVM calls phase_ready_to_end() on each component. If the scoreboard has uncompared transactions in its FIFOs, it raises a temporary objection to delay the phase end:

function void phase_ready_to_end(uvm_phase phase);
  if (phase.get_name() != "run") return;
  if (m_exp_fifo.size() > 0 || m_act_fifo.size() > 0) begin
    phase.raise_objection(this);
    fork begin
      wait(m_exp_fifo.size()==0 && m_act_fifo.size()==0);
      phase.drop_objection(this);
    end join_none
  end
endfunction
40 How would you implement an out-of-order scoreboard? Medium

Store expected transactions in an associative array keyed by unique identifier. When actual arrives, look up by key instead of by FIFO position:

class ooo_sb extends uvm_scoreboard;
  my_txn m_exp[int];  // key = txn.tag
  function void write_expected(my_txn t);
    m_exp[t.tag] = t;
  endfunction
  function void write_actual(my_txn t);
    if (!m_exp.exists(t.tag))
      `uvm_error("SB","No matching expected!")
    else if (!t.compare(m_exp[t.tag]))
      `uvm_error("SB","MISMATCH")
    m_exp.delete(t.tag);
  endfunction
endclass
41 Why should do_compare() exclude certain metadata fields? Hard

Fields like timestamps, sequence IDs, and internal counters are per-item metadata — they differ between the expected (predictor-generated) and actual (monitor-observed) transaction naturally, with no bug. Including them in do_compare() causes false failures on every comparison.

Compare only semantically meaningful protocol fields: addresses, data values, command types, and protocol-specific flags that the DUT is supposed to preserve or modify according to the spec.

Response fields like read_data SHOULD be compared — that is the functional check. Metadata that differs by construction (sequence ID, timestamp) should NOT.
42 How do you report scoreboard results cleanly in report_phase? Hard
function void report_phase(uvm_phase phase);
  if (m_errors > 0)
    `uvm_error("SB", $sformatf(
      "%0d matches, %0d MISMATCHES — FAILED",
      m_matches, m_errors))
  else if (m_matches == 0)
    `uvm_warning("SB", "No comparisons made")
  else
    `uvm_info("SB", $sformatf(
      "%0d comparisons, 0 errors — PASSED", m_matches), UVM_LOW)
endfunction

Using `uvm_error in report_phase increments the count seen by regression scripts. The zero-comparison warning catches empty tests that pass vacuously.

43 What is the role of uvm_in_order_comparator vs a custom scoreboard? Hard

uvm_in_order_class_comparator #(T) provides two analysis exports, two FIFOs, and an in-order comparison run task. Suitable for simple, single-stream, in-order DUTs where all comparison logic fits in do_compare().

Use a custom scoreboard when:

  • Out-of-order response matching is required.
  • Multiple transaction streams must be correlated.
  • Complex checking logic cannot fit in do_compare().
  • State tracking across multiple transactions is needed (e.g. verifying a FIFO fills and drains correctly).
07 — Functional Coverage 5 Q
44 What is the difference between code coverage and functional coverage? Easy

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

Functional coverage — measures whether specific design scenarios and feature combinations were exercised. Written manually by the verification engineer based on the spec. 100% code coverage can still miss important corner cases if functional coverage was not defined for them.

45 Why must covergroups be constructed inside the constructor? Medium

This is a SystemVerilog LRM requirement. Covergroups are special constructs — instances must be created with new() in the 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();   // covergroup: ONLY in constructor
endfunction

All other object construction goes in build_phase.

46 What is cross coverage and when would you use it? Medium

Cross coverage measures the Cartesian product of two or more coverpoints — verifying that specific combinations of values occurred together.

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};
  }
  cx_rw_addr: cross cp_rw, cp_addr;
  // 4 bins: rd+data, rd+dir, wr+data, wr+dir
endgroup

Use when the spec says every register must be both read and written, or all error conditions tested in both read and write modes.

47 How do you conditionally disable coverage sampling during reset? Hard

Option 1 — iff clause:

covergroup apb_cg;
  cp_addr: coverpoint txn.addr iff(coverage_enabled) {...}
endgroup

Option 2 — guard in write() (preferred in UVM):

function void write(my_txn t);
  if (!m_cfg.reset_active) begin
    cov_txn = t; my_cg.sample();
  end
endfunction

Option 2 is preferred — the coverage component gets its reset state from config_db without tight coupling to any specific signal.

48 What is the coverage-driven verification loop and how does UVM support it? Hard

CDV loop:

  1. Define coverage model (covergroups for all required scenarios).
  2. Generate constrained-random stimulus.
  3. Measure coverage — identify unvisited bins (holes).
  4. Refine constraints to target holes. Repeat.

UVM support: rand fields + constraint blocks for stimulus; coverage collectors connected to monitor ap (data flows automatically); factory overrides to swap stimulus sequences; config_db to enable/disable coverage per test.

08 — Register Abstraction Layer (RAL) 10 Q
49 What problem does the RAL solve? What are the five RAL objects? Easy

Problem: Without RAL, every register sequence must know the bus protocol. RAL provides protocol-independent register access — reg.write() works regardless of bus type; swapping the adapter changes the bus.

Five objects (smallest to largest):

  • uvm_reg_field — one named bit-group
  • uvm_reg — one register
  • uvm_mem — a memory region
  • uvm_reg_map — named address map with byte offsets
  • uvm_reg_block — top-level container
50 What is the difference between desired and mirrored values in RAL? Easy

Desired — what software intends the hardware to contain. Modified by set() with no bus activity. Committed with update().

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

  • After reset: both equal the reset value.
  • After set(0xFF): desired=0xFF, mirror=0x00 (no bus cycle).
  • After update(): both=0xFF (bus write done).
  • After hardware modifies a volatile field: mirror updated by predictor, desired unchanged.
51 Explain the difference between write(), set()/update(), and poke(). Medium
  • write(status, value) — front-door bus write, generates actual bus cycles.
  • set(value) — sets desired only, no bus cycle, hardware not yet written.
  • update(status) — if desired≠mirror, performs a bus write. No activity if already equal. Commits multiple set() calls in one transfer.
  • poke(status, value) — backdoor write, zero time, forces RTL flip-flop values directly.
Use the set/update pattern for multi-field config — set each field, then one update() writes the assembled value. Avoids multiple bus cycles and read-modify-write pitfalls.
52 What is the RAL adapter and what are reg2bus() and bus2reg() responsible for? Medium

The adapter translates between generic uvm_reg_bus_op and bus-specific sequence items (e.g. apb_seq_item).

reg2bus(rw) — converts uvm_reg_bus_op → bus item. Returns the item to send.

bus2reg(bus_item, rw) — converts bus item back → uvm_reg_bus_op after the driver completes. Extracts read data and status.

virtual function void bus2reg(uvm_sequence_item item, ref uvm_reg_bus_op rw);
  apb_seq_item a; $cast(a, item);
  rw.data   = a.read_data;    // ← PRDATA for reads
  rw.status = a.error ? UVM_NOT_OK : UVM_IS_OK;
endfunction
53 What is the RAL predictor and why is explicit prediction preferred over auto prediction? Medium

uvm_reg_predictor #(T) subscribes to the monitor’s analysis port, decodes every bus transfer using the adapter, and calls predict() to update the mirror.

Auto prediction — updates mirror after each RAL-initiated access only.

Explicit prediction (default) — observes ALL bus traffic including accesses from other bus masters, DMA, or raw bus sequences. The only mode that keeps the mirror accurate in a multi-master environment.

Always use explicit prediction in production environments.
54 What does lock_model() do and what happens if you forget to call it? Medium

lock_model() resolves all address offsets, verifies no address collisions, marks the model as immutable, and enables address-based register lookup.

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

lock_model() must be the last call in the register block’s build() method.
55 How do you build a sub-system register map for integration-level verification? Hard

Multiple block register models are composed using add_submap():

class pss_reg_block extends uvm_reg_block;
  rand gpio_reg_block gpio; rand spi_reg_block spi;
  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);
    spi = spi_reg_block::type_id::create("spi");
    spi.configure(this); spi.build();
    AHB_map.add_submap(spi.default_map, 32'h1000);
    lock_model();
  endfunction
endclass
56 What are the UVM built-in register sequences and what does each test? Hard
  • uvm_reg_hw_reset_seq — reads every register, checks against configured reset value.
  • uvm_reg_bit_bash_seq — walking 1s/0s on all RW bits — verifies data path.
  • uvm_reg_access_seq — front-door write + back-door read; back-door write + front-door read. Verifies both paths access the same flip-flop.
  • uvm_mem_walk_seq — walking-ones pattern on every memory location.
  • uvm_reg_mem_built_in_seq — runs all the above on an entire register block.
57 What are the three mandatory RAL wiring connections in env connect_phase? Hard
// ① Stimulus path: reg.write/read → sequencer → driver → DUT
m_cfg.gpio_rm.APB_map.set_sequencer(m_agent.m_seqr, m_adapter);

// ② Predictor config: map + adapter for decoding bus items
m_reg_pred.map = m_cfg.gpio_rm.APB_map;
m_reg_pred.adapter = m_adapter;

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

Missing ①: reg methods produce no bus activity. Missing ②: predictor crashes on null map. Missing ③: mirror never updated — all mirror checks pass vacuously.

58 How do you exclude a specific register from a built-in test sequence? Hard

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: clock-control registers (random writes halt the clock), W1C status registers (reset test fails — read clears the register), write-only registers (readback always returns 0).

09 — Debug & End-of-Test 8 Q
59 Simulation hangs and never ends. Most likely cause and diagnosis? Easy

Most likely cause: an objection was raised but never dropped. Other causes: a sequence blocking on an event that never fires, or a forever loop with no exit condition.

Diagnosis: add +UVM_OBJECTION_TRACE to the run command. It logs every raise/drop with component path and running count. Find the last raise without a matching drop.

Also useful: +UVM_TIMEOUT=5ms,YES to force a timeout — the fatal message shows which phases were still active and which components held outstanding objections.

60 How do you use UVM_CONFIG_DB_TRACE to debug a config_db::get() returning 0? Easy

Add +UVM_CONFIG_DB_TRACE to the run command. This logs every set() and get() with scope, key, and result.

Common mismatches to look for:

  • Scope mismatch — set() scope is not a prefix of the get() component’s path.
  • Name mismatch — set() used "vif" but get() asked for "APB_vif". Keys are case-sensitive.
  • Type mismatch — set() stored virtual apb_if but get() requested virtual spi_if.
  • Ordering — get() executed before set() in the build-phase top-down order.
61 What does print_topology() show and when should you call it? Medium

uvm_top.print_topology() prints the complete component hierarchy in tree form with every component’s type and dot-path. Call from end_of_elaboration_phase — all components are built but simulation has not started.

Verifies: all expected components were created; factory overrides took effect (type shown = override type); hierarchy matches what config_db paths expect.

function void end_of_elaboration_phase(uvm_phase phase);
  uvm_top.print_topology();
  uvm_factory::get().print(1);  // show overrides too
endfunction
62 What is UVM_VERBOSITY and how do you set verbosity per component without changing source? Medium

+UVM_VERBOSITY=UVM_HIGH sets the global verbosity (default UVM_MEDIUM). Messages at UVM_HIGH and below are printed when this flag is set.

Per-component via command line:

// +uvm_set_verbosity=path,id,level,phase
// e.g. +uvm_set_verbosity=uvm_test_top.m_env.m_agent.m_drv,DRV,UVM_HIGH,run

From inside the testbench:

m_drv.set_report_verbosity_level(UVM_HIGH);
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
63 How does the UVM objection mechanism control end-of-test? Medium

The objection mechanism is a reference counter. The run phase ends when count reaches zero. Pattern:

  1. Test raises the objection before stimulus begins.
  2. Sequences run — driver drives DUT.
  3. Test drops the objection after all sequences complete.
  4. UVM calls phase_ready_to_end() on all components. Scoreboard may re-raise temporarily to drain.
  5. When count stays at zero past drain time, run phase ends.
Never raise/drop objections per transaction — extreme simulation overhead. One raise/drop per test.
64 What is drain_time and set_drain_time() used for? Hard

Drain time adds a delay after the last objection drops before the run phase actually ends. It allows in-flight pipeline transactions to complete and be observed by the monitor before the phase closes.

function void end_of_elaboration_phase(uvm_phase phase);
  uvm_objection obj = phase.get_objection();
  obj.set_drain_time(this, 100ns);
endfunction

Without drain time, the run phase ends immediately on last drop, leaving the monitor’s last transaction unbroadcast and uncompared. A better alternative for predictable drain is phase_ready_to_end() in the scoreboard — explicit FIFO drain logic.

65 How do you set a simulation timeout and what happens when it fires? Hard
uvm_top.set_timeout(5ms, 1); // from testbench
// +UVM_TIMEOUT=5ms,YES       // from command line

When the timeout fires, UVM issues a `uvm_fatal and the simulation ends immediately. The message identifies which phases were still active and which components held outstanding objections.

Always set a timeout in every base test to prevent runaway simulations from consuming infinite regression compute time.

66 What is UVM_MAX_QUIT_COUNT and when would you change it from the default? Hard

Maximum number of `uvm_error messages before simulation stops. Default = 5.

  • Increase (e.g. 50): A single root-cause bug triggers a cascade of downstream errors. Higher limit lets all cascade errors accumulate for a complete picture.
  • Set to 0 (unlimited): For error-counting stress tests where you want total count without stopping.
  • Decrease to 1: When the first error is always the meaningful one and subsequent output is noise.
uvm_root::get().set_report_max_quit_count(50);
// +UVM_MAX_QUIT_COUNT=50,YES
10 — Advanced Topics 9 Q
67 What is vertical reuse and what four mechanisms enable it? Medium

Vertical reuse means block-level verification components continue working at integration level without modification.

  1. Active → Passive switchingis_active = UVM_PASSIVE in config. Zero agent code changes.
  2. Nested environments — integration env wraps block-level envs as sub-components.
  3. Nested config objects — integration config contains block-level configs; test distributes downward.
  4. Signal bindingbind attaches an interface to internal RTL hierarchy without modifying the RTL source.
68 How does SystemVerilog bind work for internal signal monitoring? Medium

bind instantiates an interface inside a target RTL module’s scope without touching the RTL:

bind pss_dut.u_ahb_apb_bridge apb_if apb_probe (
  .PCLK(PCLK), .PRESETn(PRESETn),
  .PADDR(PADDR), .PSELx(PSEL),
  .PWDATA(PWDATA), .PRDATA(PRDATA)
);

The bound instance is accessible in tb_top as pss_tb_top.apb_probe and set into config_db as a virtual interface. Internal APB signals are now visible to a passive APB monitor without any DUT port additions.

69 Why should you avoid `uvm_field_* automation macros? Medium

Problems with `uvm_field_int and related macros:

  • Performance — each macro expands to a policy-object loop over all fields. Much slower than direct field access.
  • Indiscriminate — all fields compared including sequence metadata and timestamps that should not be compared, causing false failures.
  • All-or-nothing — cannot exclude some fields without complex workarounds.
  • Debug opacity — errors reported inside the macro expansion, hard to trace.

Use manually implemented do_copy(), do_compare(), and convert2string() for precise control.

70 How do you add hardware synchronisation to a sequence without using a virtual interface directly? Hard

Add timing tasks to the agent config object. Sequences retrieve the config via m_sequencer and call these tasks:

// In config object
task wait_for_clock(int n=1);
  repeat(n) @(posedge vif.clk);
endtask
task wait_for_interrupt();
  @(posedge vif.irq);
endtask

// In sequence body()
uvm_config_db #(bus_agent_cfg)::get(m_sequencer,"","cfg",m_cfg);
finish_item(item);
m_cfg.wait_for_clock(4);
71 What is the package compilation hierarchy and why does it matter? Hard
uvm_pkg
    ↑
agent_pkg(s) / register_pkg    ← import uvm_pkg only
    ↑
env_pkg / sequence_pkg          ← import uvm_pkg + agents
    ↑
test_pkg                        ← import everything above

Why it matters:

  • Type uniqueness — a class included into two packages creates two different types. Factory overrides, $cast(), and analysis ports all fail silently.
  • Staged compilation — only changed packages need recompilation.
  • tb_top must import the test package — without it, test classes are never registered and run_test() fails with ‘type not found’.
72 What is the ISR sequence pattern in UVM? Hard

Two forked threads: background traffic (low priority) and interrupt monitor. When IRQ fires, high-priority ISR sequence grabs exclusive access:

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() for immediate exclusive access → read ISR reg → W1C clear → ungrab(). Set SEQ_ARB_STRICT_FIFO arbitration so high-priority ISR runs before pending background requests.

73 Why should you never include a class definition in multiple places? Hard

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

Effects:

  • Factory failures — factory may store/return the wrong definition; overrides fail silently.
  • $cast() failures — pkg_a::my_class and pkg_b::my_class are different types; $cast returns 0.
  • 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.

74 How do you create a base test with a virtual configuration method for derived tests? 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);
    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;
  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
75 What common interview mistake do candidates make about the UVM factory? Hard

The most common mistake: describing the factory only as a ‘registry’ without explaining the override mechanism — which is the entire verification purpose of the factory.

A complete answer covers five points:

  1. Registration — `uvm_component_utils/`uvm_object_utils register types at elaboration.
  2. Creation via factory — type_id::create() checks for overrides before creating.
  3. Type override — set_type_override() redirects every instance of a type everywhere.
  4. Instance override — scoped to a specific hierarchy path.
  5. Use case — swap a normal driver with an error-injecting driver for one test with a single override call at the start of test build_phase.

Second common mistake: saying ‘you call new() through the factory’. You never do — new() bypasses the factory. Always use type_id::create().

Scroll to Top