90+ free UVM questions for design verification interviews: objects and components, phases, the factory, config DB, sequences and the driver handshake, agents, analysis ports, scoreboards, objections, RAL and reporting. Includes 18 worked answers with code, 8 solved problems, a failing regression log walked through step by step, a regression planner and a rapid-fire round.
A questions-only bank from the factory to real verification scenarios, with 195 questions on testbench components and 95 on debugging, architecture and scenarios.
UVM interviews almost always begin with "draw your testbench". Everything after that is a follow-up on the drawing: who creates each box, in which phase, how the boxes talk, what happens when a test overrides one of them, and how you'd know if something silently went wrong.
Start with the cheat sheet and the table of who builds what, skim the 50 questions by topic, then work through the worked answers with the code in front of you. Finish with the log walkthrough: reading a failing regression log is the most practical senior question there is.
Objects vs components, the phase order, what the factory is for, what config DB stores, the parts of an agent. You should be able to draw a full environment in two minutes.
A sequence body, a driver run_phase, a config DB set and get, a factory override, a scoreboard with two analysis inputs. Small code, exact API.
"The test ends at time 0", "my override does nothing", "the scoreboard fails but the waves look right", "reuse this agent at SoC level". They want method and trade-offs.
Test, env, agents, scoreboard, the arrows between them. Every later answer can point at the drawing.
Create in build, connect in connect, run stimulus in run, check in check. Many bugs are just something done in the wrong phase.
start_item, finish_item on one side; get_next_item, item_done on the other. Interviewers love the step-by-step.
Active or passive, number of agents, timeouts, error injection: all from config objects or the config DB, never from if (test_name == …).
Agents know a protocol, not a test. Say what would have to change to use your agent on another project; the right answer is "nothing".
print_topology(), factory.print(), +UVM_CONFIG_DB_TRACE, +UVM_OBJECTION_TRACE. Naming them shows you've debugged real environments.
The rules behind most one-line questions, in one place.
uvm_object: new(name), created any time · uvm_component: new(name, parent), built in build_phase, has phasesT::type_id::create(name, parent) · overrides: set_type_override, set_inst_override, instance beats typeset(cntxt, inst_path, field, value) · get(this, "", field, var) returns 1 if foundDuring build, a set from higher in the hierarchy wins; after build, the last set wins.start_item → randomize → finish_item · drv: get_next_item → drive → item_doneanalysis_port.write(): one to many, never blockswrite/read front door · poke/peek back door · set + update · mirror checks · desired vs mirrored valuelock() queues, grab() jumps the queueHow long a nightly regression takes on your compute, and how many failures you'll have to triage in the morning.
"Where is this created, and how does it connect?" follows every architecture question. This is the answer for each piece.
| PIECE | KIND | CREATED BY / IN | CONNECTED IN | WORKS IN |
|---|---|---|---|---|
| Test | Component | run_test(), by name | — | build (env, config, overrides); run (starts sequences) |
| Environment | Component | Test, build_phase | connect_phase: monitors to scoreboard and coverage | Holds agents, scoreboard, virtual sequencer |
| Agent | Component | Env, build_phase | connect_phase: driver to sequencer | Builds driver and sequencer only when active |
| Sequencer | Component | Agent, build_phase (active only) | Agent's connect_phase | Arbitrates between sequences at run time |
| Driver | Component | Agent, build_phase (active only) | seq_item_port to sequencer | run_phase: pulls items, drives pins via the virtual interface |
| Monitor | Component | Agent, build_phase (always) | analysis_port to subscribers | run_phase: samples pins, writes transactions |
| Scoreboard | Component | Env, build_phase | Analysis imps or TLM FIFOs | run_phase compares; check_phase reports leftovers |
| Sequence | Object | Test or virtual sequence, any time | start(sequencer) | body(), at run time |
| Sequence item | Object | Sequence, per transaction | Passed by handle | Randomised, driven, monitored, compared |
A sample of what DV interviewers ask most. The 616-question ebook covers each topic in much more depth, chapter by chapter.
Why UVM, and what it's made of.
`uvm_*_utils macros actually register?run_test() do, and where does the test name come from?Swapping classes without editing code.
type_id::create() instead of new()?Passing settings and handles down the tree.
set() mean?get() that returns 0?When things happen, and when they stop.
run_phase?Generating stimulus.
start_item() and finish_item() each wait for?lock() and grab()?p_sequencer?Turning transactions into pins and back.
get_next_item() and try_next_item()?Packaging for reuse.
Moving and checking transactions.
Registers as objects.
Finding out what really happened.
print_topology() tell you?24 chapters, one for each layer of a UVM environment, plus debugging methodology, scalable architecture and 35 real-world scenarios. Questions only, so you practise producing answers.
Answer out loud (or sketch the code) first, then open each one. Every answer shows what a strong reply covers, what the interviewer is listening for, and the slip that costs the most.
build_phase, have a parent and a place in the hierarchy, take part in every phase, and live for the whole simulation (driver, monitor, agent, env, test).class bus_item extends uvm_sequence_item; // object
`uvm_object_utils(bus_item)
function new(string name = "bus_item");
super.new(name);
endfunction
endclass
class bus_driver extends uvm_driver #(bus_item); // component
`uvm_component_utils(bus_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclasscreate() asks the factory for an object. If a test has registered an override, the factory returns the replacement class instead; new() always builds exactly the class named in the code.
// in the agent
drv = bus_driver::type_id::create("drv", this);
// in a test's build_phase, before the env is built
bus_driver::type_id::set_type_override(err_driver::get_type()); // everywhere
bus_driver::type_id::set_inst_override(err_driver::get_type(),
"env.agt0.drv", this); // one instanceThe replacement must extend the original class and be registered with `uvm_component_utils. Instance overrides take priority over type overrides.
The top module, which owns the real interface instance, puts a handle into the config DB; the driver (and monitor) fetch it in build_phase.
// tb_top
initial begin
uvm_config_db#(virtual bus_if)::set(null, "uvm_test_top.env.agt*", "vif", bus0);
run_test();
end
// driver build_phase
if (!uvm_config_db#(virtual bus_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "virtual interface not set for this agent")Better still, put the handle in the agent's config object, so all of an agent's settings travel together.
// sequence
task body();
repeat (10) begin
req = bus_item::type_id::create("req");
start_item(req); // 1. wait for the sequencer to grant
if (!req.randomize() with { kind == WRITE; })
`uvm_error("RAND", "randomize failed")
finish_item(req); // 2. hand over; wait for item_done
end
endtask
// driver
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // 3. blocks until an item is granted
drive(req); // 4. wiggle pins via the vif
seq_item_port.item_done(); // 5. releases finish_item
end
endtaskRandomising between start_item and finish_item means the item reflects the latest state (late randomisation). For a response, the driver sets the ID with rsp.set_id_info(req) and the sequence calls get_response().
An active agent drives the interface (sequencer, driver and monitor); a passive agent only observes (monitor alone). The same agent class is used both ways, controlled by configuration.
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = bus_monitor::type_id::create("mon", this);
if (get_is_active() == UVM_ACTIVE) begin
sqr = bus_sequencer::type_id::create("sqr", this);
drv = bus_driver ::type_id::create("drv", this);
end
endfunction
function void connect_phase(uvm_phase phase);
if (get_is_active() == UVM_ACTIVE)
drv.seq_item_port.connect(sqr.seq_item_export);
endfunctionPassive agents matter for reuse: at SoC level the real block drives the bus, and your agent just watches and feeds the scoreboard.
`uvm_analysis_imp_decl(_exp)
`uvm_analysis_imp_decl(_act)
class bus_sb extends uvm_scoreboard;
`uvm_component_utils(bus_sb)
uvm_analysis_imp_exp #(bus_item, bus_sb) exp_imp;
uvm_analysis_imp_act #(bus_item, bus_sb) act_imp;
bus_item expq[$];
function new(string name, uvm_component parent);
super.new(name, parent);
exp_imp = new("exp_imp", this);
act_imp = new("act_imp", this);
endfunction
function void write_exp(bus_item t); expq.push_back(t); endfunction
function void write_act(bus_item t);
bus_item e;
if (expq.size() == 0) begin `uvm_error("SB/UNEXP", t.convert2string()) return; end
e = expq.pop_front();
if (!t.compare(e))
`uvm_error("SB/MISMATCH", $sformatf("exp %s got %s", e.convert2string(), t.convert2string()))
endfunction
function void check_phase(uvm_phase phase);
if (expq.size()) `uvm_error("SB/LEFT", $sformatf("%0d expected items never seen", expq.size()))
endfunction
endclassIn the env: ref_model.ap.connect(sb.exp_imp); out_mon.ap.connect(sb.act_imp);. The same monitor port can also connect to a coverage subscriber.
Each run-time phase keeps a count of raised objections. It ends when the count returns to zero (plus any drain time). If nobody raises one, the phase ends at time 0 and nothing runs.
task run_phase(uvm_phase phase);
bus_seq seq = bus_seq::type_id::create("seq");
phase.raise_objection(this, "main traffic");
seq.start(env.agt.sqr);
phase.get_objection().set_drain_time(this, 200ns); // let the last items reach the scoreboard
phase.drop_objection(this, "main traffic");
endtaskRaise in the test or the top-level sequence, not in drivers and monitors that run forever; and not per transaction, which slows large simulations.
A virtual sequence doesn't generate items itself; it coordinates sequences on several agents. It usually runs on a virtual sequencer that holds handles to the real sequencers, reached through p_sequencer.
class top_vseq extends uvm_sequence;
`uvm_object_utils(top_vseq)
`uvm_declare_p_sequencer(top_vsqr) // top_vsqr has apb_sqr and axi_sqr handles
function new(string name = "top_vseq"); super.new(name); endfunction
task body();
apb_cfg_seq cfg = apb_cfg_seq ::type_id::create("cfg");
axi_traffic trf = axi_traffic ::type_id::create("trf");
cfg.start(p_sequencer.apb_sqr); // configure the DUT first
trf.start(p_sequencer.axi_sqr); // then send traffic
endtask
endclassThe env sets the sequencer handles in the virtual sequencer during connect_phase. Some teams skip the virtual sequencer and pass handles straight into the sequence; either is fine if it's consistent.
uvm_status_e st;
uvm_reg_data_t v;
regmodel.ctrl.write(st, 32'h11); // front door: a real bus transaction via the adapter
regmodel.ctrl.read (st, v);
regmodel.ctrl.peek (st, v); // back door: direct HDL access, no bus, zero time
regmodel.ctrl.enable.set(1'b1); // change the desired value only
regmodel.ctrl.update(st); // write it if desired differs from mirrored
regmodel.ctrl.mirror(st, UVM_CHECK); // read the DUT and compare with the mirrorclass drv_cb extends uvm_callback;
virtual task pre_drive(bus_driver d, bus_item t); endtask
endclass
// in bus_driver
`uvm_register_cb(bus_driver, drv_cb)
...
`uvm_do_callbacks(bus_driver, drv_cb, pre_drive(this, req))
drive(req);
// in a test: corrupt every 10th item
uvm_callbacks #(bus_driver, drv_cb)::add(env.agt.drv, my_err_cb);`uvm_info("DRV", $sformatf("sent %s", req.convert2string()), UVM_HIGH)
// command line: +UVM_VERBOSITY=UVM_HIGH (show HIGH and below)
// +UVM_MAX_QUIT_COUNT=10 (stop after 10 errors)
// demote one known, waived error in one test
env.sb.set_report_severity_id_override(UVM_ERROR, "SB/LEN", UVM_WARNING);
// silence one noisy ID
env.agt.mon.set_report_id_action("MON/IDLE", UVM_NO_ACTION);Verbosity filters only `uvm_info; warnings, errors and fatals always print unless their action is changed. Good message IDs make logs searchable and let you control one message without touching others.
run_phase (or the sequence was started in a sub-phase that nobody objected to), so the phase ended immediately.main_phase doesn't keep run_phase alive, and the reverse.start() call, or started on a null sequencer handle (passive agent, wrong path).+UVM_TESTNAME misspelt, so the base test with no stimulus ran.Turn on +UVM_OBJECTION_TRACE and check the "Running test" line at the top of the log.
new(): the factory never sees it. Every overridable class must be created with type_id::create().super.build_phase() built the env, or in connect_phase.get_full_name().`uvm_component_utils, or a string-based override names a type that doesn't exist (a warning, easy to miss).factory.print() and uvm_top.print_topology() answer most of these in seconds.
+UVM_CONFIG_DB_TRACE: it prints every set and get, with paths and field names.set() must match the getter's full name (wildcards included); compare against get_full_name()."vif" vs "m_vif"; it's a plain string match.uvm_config_db#(T) must be identical on both sides.run_test() is too late.do_compare includes a field that shouldn't be compared (a timestamp) or skips one that should.Print both transactions at the failing time and compare them to the waveform field by field.
+UVM_OBJECTION_TRACE shows who still holds it.item_done() not called, so finish_item() never returns.get_response() for a response the driver never sends.lock() or grab() and never released it.If the agent needs code changes to move up a level, say what you'd fix in its design so the next reuse needs none.
Precedence, ordering and counting questions come up in written tests and as quick checks. Work each out first, then tap to check.
build_phase and connect_phase run?Show solution ↓build (top-down): test → env → agent → driver, monitor connect (bottom-up): driver, monitor → agent → env → test
Note: never rely on the order between siblings such as driver and monitor.
is_active = UVM_PASSIVE for "env.agt*"; the env then sets is_active = UVM_ACTIVE for "agt". What does the agent get?Show solution ↓During build_phase, a set from higher in the hierarchy wins. test (higher) → UVM_PASSIVE env (lower) → ignored for this field Agent is PASSIVE.
Note: this is what lets a test override an environment's defaults. After build, the last set wins instead.
bus_driver → drv_a and an instance override bus_driver → drv_b for "env.agt0.*". Which class does each of agt0 and agt1 get?Show solution ↓agt0.drv : instance override matches → drv_b agt1.drv : no instance match → type override → drv_a
Note: instance overrides are checked first, then type overrides.
count: 0 ns → 1, 10 ns → 2, 100 ns → 1, 250 ns → 0 count reaches 0 at 250 ns, drain 50 ns run_phase ends at 300 ns
Note: if something raises again during the drain, the phase keeps running.
UVM_SEQ_ARB_WEIGHTED, with priorities 300 and 100. What share of grants does each get?Show solution ↓Total weight = 300 + 100 = 400 A: 300 / 400 = 75% B: 100 / 400 = 25%
Note: with the default FIFO arbitration, priority is ignored and requests are granted in arrival order.
8'hA5 through the front door, then call mirror(UVM_CHECK). The RTL actually ties bit 7 to 0. What happens?Show solution ↓Mirror after write: 8'hA5 (1010_0101) DUT read back: 8'h25 (0010_0101) mirror(UVM_CHECK) → error: expected A5, read 25
Note: the check doesn't say which side is wrong. Compare the register spec: either the RTL has a bug or bit 7 should be modelled read-only.
Runs = 40 × 25 = 1,000 CPU time = 1,000 × 12 min = 12,000 min = 200 h Wall-clock = 200 h / 50 = 4 h Failures = 2% × 1,000 = 20
Note: group the 20 by error ID and first message; they're often two or three real bugs. The calculator above does the same sums.
+UVM_VERBOSITY=UVM_MEDIUM, which print: an info at UVM_NONE, an info at UVM_MEDIUM, an info at UVM_HIGH, and a `uvm_warning?Show solution ↓info UVM_NONE → printed (always) info UVM_MEDIUM → printed (≤ MEDIUM) info UVM_HIGH → hidden (> MEDIUM) warning → printed (not filtered by verbosity)
Note: use UVM_LOW for "one line per test" messages, HIGH and above for per-transaction detail.
"Here's last night's failure. What do you look at first?" is a classic senior DV question. This trimmed log hides three separate problems behind 40 messages.
UVM_INFO @ 0: reporter [RNTST] Running test err_inject_test... UVM_WARNING @ 0: reporter [BDTYP] Cannot create a component of type 'bus_err_drvier' because it is not registered with the factory. (1) UVM_INFO @ 0: uvm_test_top.env [CFG] 2 agents, scoreboard on, coverage on UVM_ERROR @ 1250: uvm_test_top.env.sb [SB/MISMATCH] addr 0x40: exp data=0x1f got data=0x00 (2) UVM_ERROR @ 1290: uvm_test_top.env.sb [SB/MISMATCH] addr 0x44: ... ... 36 more SB/MISMATCH, all after 1250 ... (3) UVM_FATAL @ 9200000: reporter [PH_TIMEOUT] Explicit timeout of 9200000 hit, indicating a probable testbench issue (4) --- UVM Report Summary --- UVM_INFO : 412 UVM_WARNING : 1 UVM_ERROR : 38 UVM_FATAL : 1 [BDTYP] 1 [SB/MISMATCH] 38 [PH_TIMEOUT] 1 (5)
drvier), so the error-injection driver was never created. The test ran with the normal driver while claiming to inject errors. It's only a warning.0x00 often means the monitor sampled before the data was valid, or the read happened before the write completed.item_done() or a response.The BDTYP warning. Until the override works, this test isn't testing what its name says, so its other failures may mean nothing. Then debug the first mismatch at 1250 ns, then the timeout.
Use type-based overrides (bus_driver::type_id::set_type_override(bus_err_driver::get_type())), which fail at compile time if the class name is wrong. Or promote the warning in the base test: set_report_severity_id_override(UVM_WARNING, "BDTYP", UVM_ERROR).
+UVM_OBJECTION_TRACE to see who still holds an objection, a lower timeout so the run fails faster, and +UVM_VERBOSITY=UVM_HIGH on the driver to see the last item it started.
For triage, yes: one test, one seed. But record all three signatures separately so the timeout isn't forgotten once the mismatch is fixed. The ebook has full chapters on debugging methodology and real-world scenarios like this.
Screening calls often open like this. Answer each in a few seconds, then tap to check.
An object.
run_phase (and its runtime sub-phases).
build_phase.
connect_phase.
`uvm_component_utils.
Instance.
0.
The driver's item_done().
try_next_item().
Only a monitor.
Zero or more.
write().
When all objections are dropped, plus drain time.
UVM_SEQ_ARB_FIFO.
grab().
peek().
The adapter (reg2bus / bus2reg).
No, only to info messages.
+UVM_CONFIG_DB_TRACE.
+UVM_TESTNAME=<name>.
"Driver, monitor, agent, scoreboard" isn't an answer. Say what each does, in which phase, and how they connect.
It works until the first test tries to override something. Use type_id::create() everywhere.
An unchecked config_db::get() or randomize() turns a setup mistake into a mystery much later.
None at all ends the test at time 0; one in a forever-running driver means the test never ends.
Checks for test names inside agents or the env make them impossible to reuse. Configure instead.
Without coverage and a check that every expected item was seen, a passing test may simply have done nothing.
Testbench architecture (objects, components, phases), the factory and config DB, the sequence-driver handshake, agents and analysis ports, scoreboards, objections and RAL, then debugging and reuse questions for experienced candidates.
Yes. Classes, inheritance, virtual methods, parameterised classes, constraints and fork-join are all used directly in UVM code. The SystemVerilog questions are the place to start.
The concepts are the same across UVM 1.1d, 1.2 and the IEEE 1800.2 versions. A few method names and deprecations differ; mention the version your project used if a detail depends on it.
EDA Playground lets you run UVM testbenches in the browser for free, which is enough to write a small agent, sequence and scoreboard end to end.
Object vs component, the phase order, the factory and create(), config DB for virtual interfaces, the sequence-driver handshake, an agent's structure, analysis ports and a simple scoreboard.
Usually short pieces: a sequence body, a driver run_phase, a config DB get, a factory override or a scoreboard write function. Getting the exact API calls right matters.
Book a 1:1 mock interview with a mentor working in this exact domain.
Book a mock interview