Skip to main content

UVM Interview Questions

This is the last page of the UVM curriculum, and a different kind of page than everything before it: not new material, but a set of original questions, phrased the way an interviewer might actually ask them, for reviewing what the previous 36 pages covered. Each question is followed by a concise model answer and a link back to the page that covers it in full depth — read the question, try answering it before expanding, then follow the link if anything feels shaky.

Foundations & Architecture​

Q: What's the actual difference between uvm_object and uvm_component, and why does it matter which one a class extends?

uvm_component is for persistent, structural testbench pieces — it has a fixed position in the component hierarchy and participates in phasing. uvm_object is for transient data with no fixed hierarchy and no phasing at all — transactions, sequences, config objects. Extending the wrong one breaks an assumption the other classes rely on: a transaction built as a uvm_component would need a hierarchical parent it doesn't actually have, and a driver built as a uvm_object would never get build_phase/run_phase called on it at all.

See Base Classes.

Q: Why does type_id::create() exist when new() already constructs an object?

new() always builds exactly the class named, with no way to intercept it. create() asks the factory what type is currently registered for that name — normally the same class, but redirectable to a derived class via a type or instance override, without touching the line that calls create() at all. Using new() anywhere inside a component that's meant to be overridable quietly removes that override capability for that one spot.

See The UVM Factory.

Q: What actually determines whether an agent builds a driver and sequencer, versus only a monitor?

The agent's is_active property (UVM_ACTIVE vs. UVM_PASSIVE), checked in build_phase/connect_phase — typically set from a test via uvm_config_db, not hardcoded into the agent itself, so the same agent class can be reused as either active or passive depending on which environment drops it in.

See The Agent & Environment.

Q: Why does a driver retrieve its virtual interface through uvm_config_db instead of receiving it as a constructor argument?

A constructor argument would need to be threaded by hand through every layer between testbench-top and the driver, and re-plumbed every time the tree's shape changes. uvm_config_db::set() lets testbench-top publish the interface once; any component anywhere in the tree retrieves the identical handle via get(), independent of how deep it's nested.

See Resource DB & Config DB.

Phasing & Objections​

Q: Why does starting a sequence in run_phase require raising an objection first?

Without an active objection, UVM has no way to know run_phase is still doing work — the phase (and potentially the whole test) can end the instant it's entered. Raising an objection before starting the sequence, and dropping it only after the sequence completes, is what keeps the phase open for exactly as long as the stimulus actually takes.

See Phases & Objections.

Q: Why can't build_phase contain a statement like @(posedge clk)?

build_phase is a function-phase, not a task-phase — it executes in zero simulation time by definition, top-down through the tree. Only task-phases like run_phase can consume real time; putting a time-consuming statement in a function-phase is a category error the phase mechanism doesn't support.

See Phases & Objections.

Q: When is a custom (user-defined) phase actually the right tool, and why is it called a "last resort"?

Only for a genuinely new, cross-cutting synchronization point that doesn't map onto any of the nine built-in phases or twelve run-time sub-phases. It's a last resort because it commits every future reuse of that environment to a nonstandard phase schedule — a cost the standard phases don't carry, since they're what every other UVM environment already assumes.

See User-Defined Phases.

Factory, Configuration & TLM​

Q: What's the difference between a type override and an instance override, and which one wins if both could apply?

A type override redirects every instance of a class, everywhere in the tree. An instance override redirects only the component at one specific hierarchical path. When both could apply to the same component, the instance override wins — it's the more specific match.

See The UVM Factory.

Q: Why does uvm_config_db::get() fail instead of waiting for a matching set() that hasn't run yet?

uvm_config_db has no concept of waiting at all — get() is a synchronous, one-shot lookup against whatever's been set so far. This is why set() calls have to run before the matching get(), which mostly resolves itself naturally since build_phase runs top-down and testbench-top's set() calls happen before run_test() even starts phasing.

See Resource DB & Config DB.

Q: What's the practical difference between a blocking and a non-blocking TLM put port?

A blocking put() suspends the calling process until the operation completes. A non-blocking try_put()/can_put() pair returns immediately either way — it polls for success/failure rather than waiting, and the contract is a function, not a task, precisely because it must never consume simulation time.

See Blocking Put & Get Ports and Non-Blocking Put & Get Ports.

Q: What's the practical relationship between uvm_config_db and uvm_resource_db, and why does this curriculum only ever use the former?

uvm_config_db is a type-safe, hierarchy-aware convenience layer built on top of the more general-purpose, lower-level uvm_resource_db store underneath it. Almost every testbench should reach for uvm_config_db directly rather than dropping down to uvm_resource_db — the relationship is worth knowing mainly so uvm_resource_db doesn't look like an unrelated, competing mechanism the first time it shows up in someone else's code or an error message.

See Resource DB & Config DB.

Q: Why might a scoreboard need a uvm_tlm_fifo in front of it instead of a plain analysis export?

Buffering — a uvm_tlm_fifo decouples the rate a monitor produces transactions from the rate a scoreboard consumes them, so a momentary mismatch between the two doesn't block the producing side or drop data.

See The TLM FIFO.

Sequences​

Q: What does `uvm_do save over calling start_item/finish_item manually?

`uvm_do bundles create, randomize, start_item, and finish_item into one macro call. Calling start_item/finish_item manually is more verbose but gives explicit control over exactly what happens between them — an inline constraint on the randomize call, or other setup that needs to run after start_item but before finish_item.

See Sequence Execution & Macros.

Q: Why is there no distinct "virtual sequencer" base class in UVM?

A virtual sequence runs on an entirely ordinary uvm_sequencer — its only job is coordinating other sequencers, referenced through p_sequencer handles, not driving items of its own. No special base class is needed because nothing about the mechanism differs from any other sequencer.

See Virtual Sequences & Sequencers.

Q: Why might a sequence's explicit priority argument have no visible effect on execution order?

Priority is only consulted by arbitration modes that actually weigh it — under the default FIFO or RANDOM arbitration modes, priority is silently a no-op. It only starts mattering once the sequencer's arbitration mode is switched to one that uses it.

See Sequence Libraries & Arbitration.

Reporting, RAL & Advanced Topics​

Q: What does `uvm_info give you that a plain $display doesn't?

Integration with UVM's verbosity system — filterable per severity, per ID, per component — plus centralized counting and the ability to redirect or catch messages through the reporting infrastructure. $display has none of that; it's an unconditional, uncategorized print with no relationship to the rest of the testbench's reporting.

See Reporting & Messaging.

Q: What's the actual difference between a register's desired value and its mirrored value?

Desired is a shadow value queued to be written next, set via set() (or as a side effect of write()), not necessarily reflected in the DUT yet. Mirrored is RAL's current belief about what the DUT register actually holds right now, updated once a frontdoor access is predicted (or a backdoor access runs).

See The Register Abstraction Layer.

Q: Why would a testbench prefer explicit prediction over AUTO_PREDICT for its register model?

AUTO_PREDICT only updates the mirror when an access goes through the RAL API itself — it's blind to a status bit the DUT clears on its own, or any raw bus transaction that didn't originate from a reg.write()/read() call. Explicit prediction, via a predictor watching the bus monitor, catches all of that traffic regardless of who issued it.

See The Register Abstraction Layer.

Q: What's the risk of forgetting to call uvm_hdl_release() after a uvm_hdl_force()?

The forced signal keeps overriding every other driver indefinitely — not just "lingering," but actively masking any genuine RTL conflict or bug underneath it for the rest of the simulation, with no warning anywhere that a signal is still forced.

See HDL Backdoor Access Routines.

Q: Why is uvm_root a singleton, and what would go wrong if it weren't?

Exactly one root of the component tree has to exist for phasing and hierarchy navigation to mean anything consistent. Multiple roots would fragment both — there'd be no single, unambiguous answer to "what phase is the whole testbench currently in."

See The Singleton Pattern.

Reusable Verification Components​

Q: Why is it risky for a class inside a reusable VC to call new() internally instead of type_id::create()?

It silently removes factory overridability for that one construction point. The VC's own original testbench never notices, since it never needed to override that spot — the break only surfaces on a downstream project that does, making it a genuinely hard bug to trace back to a new() call buried inside someone else's VC.

See Reusable Verification Components.

Q: Where should a project-specific data-integrity check on a reused agent's traffic actually live?

In the environment composing the VC — a scoreboard sitting alongside the agent inside the environment — not inside the VC's own driver or monitor, which should stick to protocol-level correctness only. fifo_scoreboard living in fifo_env, not fifo_agent, is exactly this split.

See Using Verification Components.

Q: What ties the factory, uvm_config_db, and callbacks together, despite being three unrelated-looking mechanisms?

All three let a class's behavior or data be customized from outside it, without editing that class's own source: the factory swaps which class gets built, uvm_config_db supplies configuration values to an already-fixed class, and callbacks attach extra behavior to an already-built, unmodified instance. Three different points of leverage over the same underlying goal — reuse without forking.

See The UVM Factory, Resource DB & Config DB, and Callbacks, Events & Pools.

Testbench Components & TLM Roundup​

Q: What's the actual difference between a driver and a monitor, given both connect to the same virtual interface?

The driver actively asserts signals to make the DUT do something — it owns the pins it drives. The monitor only ever reads pins passively, never driving anything, so it can independently observe whatever actually happened on the bus without any risk of interfering with it. A monitor that accidentally drove a signal would corrupt the very traffic it's supposed to be neutrally observing.

See The Driver and The Monitor & Subscriber.

Q: What are the three TLM roles — port, export, imp — and why does an analysis port only ever need two of them?

Port is declared by whichever component initiates a connection; imp is declared by whichever component implements the behavior; export is a pass-through for an intermediate component that does neither. Analysis ports are one-to-many, fire-and-forget broadcasts straight from initiator to implementer, so they only ever need a port and an imp — export only enters the picture once a connection has to cross an intermediate hierarchy level that isn't the source or the destination.

See TLM Basics & Analysis Ports.

Q: What does print() actually configure, and why does UVM need three separate built-in printer styles?

print() itself just triggers formatting through a uvm_printer policy object — the three built-in styles (table, tree, line) produce different layouts from the exact same underlying field data, for different debugging needs: a table for a flat field list, a tree for deeply nested objects, and a compact single line for embedding inside a `uvm_info call where a multi-line dump would be unwieldy.

See Printer & Comparer Policies.

Q: Why can't a component just declare a second plain uvm_analysis_imp when it needs to receive the same transaction type from two different monitors?

The imp-to-method binding is by type, not by which imp instance triggered it — two plain uvm_analysis_imp #(fifo_txn, ...) declarations on the same component would both need to call the identical write(fifo_txn) method, with no way to tell which monitor's data arrived. `uvm_analysis_imp_decl(_a) (and a matching _b) generates distinctly-named imp classes, each calling its own suffixed callback (write_a()/write_b()), so each monitor connects to a genuinely distinct imp.

See TLM Sockets & Multiple Imps.

That's the full curriculum

Foundations, testbench architecture, phasing, the factory, configuration, TLM, sequences, reporting, RAL, advanced topics, and reusable-component discipline — 37 pages, two running examples (the FIFO testbench and the PWM register block), built end to end. Anything that still feels unfamiliar above is worth revisiting on its linked page before moving on.