Interviews
UVM Interview Questions
Practise the question, then the follow-ups an interviewer asks next.
Start with a Question TreeAI Mock Interview
- 35
- Question trees
- 188
- Follow-ups
- 213
- Wrong answers explained
- 19
- Runnable on a simulator
Flagship Questions · With follow-ups
Question Trees
Each question opens into the follow-ups an interviewer asks next, the wrong answers people give, and what a fresher's and a senior's answer sound like.
-
Why Teams Use UVM
You already know how to build a class-based SystemVerilog testbench. Why do most teams use UVM instead? 5 follow-ups · 6 pitfalls -
Components vs Objects
What is the difference between auvm_componentand auvm_object? Is a transaction a component? 4 follow-ups · 6 pitfalls -
The Order of UVM Phases
A test builds an env, which builds a scoreboard and an agent with a driver. Each prints in build, connect, report and final. What prints, in what order? 5 follow-ups · 8 pitfalls · 1 runnable -
A Test That Passed in Zero Time
A new test that sends 200 items has passed in every regression. A reviewer opens its log, shown below. What went wrong? 7 follow-ups · 8 pitfalls · 1 runnable -
Ending a Test While Results Are in Flight
This test drops its objection as soon as the sequence returns. The DUT has a three-clock pipeline. Write an end of test that still checks the last results. 5 follow-ups · 7 pitfalls · 1 runnable -
Deciding That Verification Is Done
Walk me through how you decided that verification of your last block was done. 7 follow-ups · 8 pitfalls -
What a Monitor Samples
What does a UVM monitor do? At which moment should it turn pin activity into a transaction? 7 follow-ups · 7 pitfalls · 1 runnable -
An In-Order Scoreboard
Write a scoreboard for a pipelined DUT whose results leave in the same order as the inputs went in. 4 follow-ups · 4 pitfalls · 1 runnable -
A Scoreboard for Out-of-Order Responses
Reads carry an ID, and the DUT may return different IDs in any order. Write the scoreboard. 5 follow-ups · 5 pitfalls · 1 runnable -
Choosing a Reference Model
Your scoreboard needs expected results for a complex block. When do you use a transaction-level predictor, a C model through DPI, or a cycle-accurate model? 5 follow-ups · 4 pitfalls -
Triaging a Scoreboard Mismatch
A smoke test sends 16, 32, 48, 64 and 80 to an incrementer and fails with this log. Where do you look first, and what is the likely cause? 5 follow-ups · 4 pitfalls -
A Random Test That Fails on Some Seeds
A random test passes on most seeds but fails on about one seed in 25. Here are two runs of the same build. How do you reproduce the failure and find its cause? 5 follow-ups · 5 pitfalls -
A Test That Passed with Its Errors Hidden
An error-injection test passes with zero UVM errors, as in this log. A colleague then finds a real data bug in the same run. What happened? 5 follow-ups · 3 pitfalls · 1 runnable -
An Override That Did Nothing
Your error test sets a type override fromfifo_drivertoerr_driver. The log still shows the normal driver. The env builds it withdrv = new("drv", this);. Why? 5 follow-ups · 7 pitfalls · 1 runnable -
Type and Instance Overrides Together
Three agents each create abus_driver. The test registers these overrides. Which class does each agent's driver get? 5 follow-ups · 8 pitfalls · 1 runnable -
Who Wins in the Config Database
The env setsdepthto 16 for its agent. The test, above it, sets 4. What does the agent read? 6 follow-ups · 8 pitfalls · 2 runnable -
A Config Database Get That Found Nothing
The driver stops with this fatal error, yet the top module does set the interface. Config tracing was on. What is wrong? 6 follow-ups · 8 pitfalls · 1 runnable -
Handing the Interface to the Driver
Write the code that gets this interface instance from the top module to the driver. 5 follow-ups · 7 pitfalls -
Walking Through a FIFO Test Plan
Walk me through your test plan for a synchronous FIFO with a configurable depth. 6 follow-ups · 5 pitfalls -
Designing the Bench for a DMA Engine
Design the testbench for a DMA engine. It has an APB register port, an AXI master port to memory, and an interrupt line. What goes where? 6 follow-ups · 5 pitfalls -
Desired and Mirrored Register Values
The DUT holds CTRL at 0x01 after reset, and auto-prediction is on. What are the desired and mirrored values after each line? 5 follow-ups · 5 pitfalls · 1 runnable -
Frontdoor vs Backdoor Register Access
What is the difference between a frontdoor and a backdoor register access? When would you use each? 5 follow-ups · 4 pitfalls -
Auto vs Explicit Register Prediction
Your register model uses auto-prediction. A boot sequence also writes registers directly on the bus. What goes wrong, and how do you set up prediction instead? 5 follow-ups · 6 pitfalls · 1 runnable -
Writing a Register Adapter
Write the adapter between a register model and an APB agent. What doreg2bus,bus2regandprovides_responsesdo? 5 follow-ups · 4 pitfalls -
A Scoreboard Across a Mid-Test Reset
A test asserts reset while a result is inside the DUT, then sends more traffic. This scoreboard ignores reset. What does it report? 6 follow-ups · 5 pitfalls · 1 runnable -
Active and Passive Agents
What does this agent build whenis_activeisUVM_PASSIVE? What is wrong with its connect phase? 5 follow-ups · 5 pitfalls -
Designing a Block Environment for Reuse
Your block environment will be reused in the subsystem bench and on the next chip. What do you design in from day one? 5 follow-ups · 3 pitfalls -
Callbacks vs Factory Overrides for Error Injection
You must corrupt the parity on chosen beats from one test, without changing the driver for anyone else. Do you use a callback or a factory override? 5 follow-ups · 5 pitfalls -
The Driver Loop and Item Done
Write the driver loop that takes items from the sequencer. What breaks ifitem_done()is missing? 6 follow-ups · 7 pitfalls · 1 runnable -
Responses That Went Missing
The driver returns a response for every read withitem_done(rsp). How many responses does this sequence collect? 5 follow-ups · 8 pitfalls · 1 runnable -
Coordinating Two Interfaces
Your DUT has an AXI host port and a UART. A test must configure it over AXI, then send UART traffic while AXI polls status. How do you structure the stimulus? 5 follow-ups · 7 pitfalls -
Lock vs Grab on a Busy Sequencer
Two sequences share a sequencer when an urgent one arrives. Predict the order the driver sees withlock()and withgrab(). 6 follow-ups · 8 pitfalls · 1 runnable -
Ports, Exports and Imps
Explain ports, exports and imps using this put connection. Who implementsput(), and which way does theconnectcall go? 5 follow-ups · 7 pitfalls -
One Item, Two Subscribers
A monitor's analysis port feeds a coverage subscriber and a scoreboard. What does the scoreboard see, and why? 6 follow-ups · 8 pitfalls · 1 runnable -
Analysis FIFOs vs Write Functions
A scoreboard takes inputs from one monitor and outputs from another. Compare twouvm_analysis_imp_declwrite functions with twouvm_tlm_analysis_fifos. 6 follow-ups · 8 pitfalls
No question matches these filters.
Quick revision · One flat list
Question Bank
All 189 UVM questions, main and follow-up. Open a track, then a topic. Answer out loud first, then open the short answer.
Jump to a Track
189 questions · 35 topicsUVM
Why Teams Use UVM5
You already know how to build a class-based SystemVerilog testbench. Why do most teams use UVM instead? L0L1L3
UVM is a standard class library, IEEE 1800.2, that gives every testbench the same parts and the same rules. Its real value is control without editing code. The factory lets a test swap a class deep inside the environment. The configuration database passes settings down the tree. Phases give every component the same start and end. So an agent written by one team plugs into another team's environment, at the cost of a large library to learn.
QuestionPractise Why Teams Use UVM →Compare your layered testbench with a UVM bench. What changes for the generator, the mailbox and the end of the test? L1
The generator becomes a sequence, which is an object, run on a sequencer, which is a component. The mailbox becomes the driver's seq_item_port, and the driver now says when it has finished each item. Building with new() becomes type_id::create(). A fixed delay at the end becomes an objection that drops when the work is done.
What if two teams both use UVM, but your agent still will not drop into their environment? L2
UVM gives the mechanisms, not the agreements. Reuse breaks when an agent reads its settings from hard-coded paths, reaches into another component by name, or cannot run passive. Agree on one configuration object per agent, connect only through TLM ports, and make every agent work with its driver switched off.
Follow-upPractise Two UVM Benches That Still Do Not Fit Together →Why is the factory usually the first UVM feature people mention? L1
It removes the most common reason to edit shared code. Without it, a test that needs a different driver must change the environment, and every other test then gets that driver too. With it, one test registers an override and only that test gets the new class.
Follow-upPractise Why the Factory Is Mentioned First →Your manager asks whether a small arbiter block needs a full UVM bench. How do you decide? L3
Decide by who will reuse the bench and how the block is best checked. A small control block with a few clean properties is often better proved with formal than simulated with UVM. A block whose agents will be reused at subsystem level earns UVM, because that reuse pays for the setup. Count the cost too: a few minutes of compile per run and engineers who can debug the library.
Follow-upPractise When Not to Use UVM →Components vs Objects4
What is the difference between a uvm_component and a uvm_object? Is a transaction a component? L0L1L2
A component is a fixed part of the testbench tree. It is built once in the build phase, has a parent and a full name, and runs every phase. An object is data with no place in the tree and no phases. You can create and discard it at any time. Drivers, monitors, agents and scoreboards are components. Transactions, sequences and configuration objects are objects.
QuestionPractise Components vs Objects →Why can't the scoreboard be created in connect_phase or run_phase? L1L2
Every phase after build walks a tree that must already be complete. Connections, configuration lookups and end-of-elaboration checks all assume that no new parts appear. The library enforces this. Once build is done, new() on a component reports a fatal ILLCRT error.
Compare a sequence item with a sequence. Both are objects, so what is the difference? L1
An item is one transaction's data, such as an address and a payload. A sequence is an object whose body() task creates items and sends them through a sequencer. A sequence can also start other sequences, which is how you build long scenarios from small ones.
What if a driver changes an item after item_done(), while the sequence still holds the same handle? L2
Both sides see the change, because they share one object. Agree on ownership. After sending, the sender does not touch the item. A receiver that must change it works on a clone(). Check this when a sequence reads back fields of an item it already sent.
The Order of UVM Phases5
A test builds an env, which builds a scoreboard and an agent with a driver. Each prints in build, connect, report and final. What prints, in what order? L0L1
Build runs top-down: test, env, then the children, then the driver. A parent must build first, because its build_phase creates its children. Connect and report run bottom-up, so the driver prints first and the test last. Final runs top-down again. In uvm-core the agent builds before the scoreboard, although the scoreboard was created first.
Why must build run top-down? What would break if it ran bottom-up? L1
A child does not exist until its parent's build_phase creates it, so bottom-up has nothing to start from. Top-down also lets a parent set configuration for its children before they read it. That is how a test sets a value that an agent reads two levels below.
What if the test sets a config value in connect_phase instead of build_phase? L1L2
The children read their configuration during build, so the late set has no effect on them. Nothing warns you, because the earlier get already returned. Set configuration before or during build. check_config_usage() lists sets that no component read, which exposes this mistake.
Compare build_phase with run_phase. Why is one a function and the other a task? L1
Build, connect, check and report must finish in zero simulation time, so they are functions and cannot wait. run_phase is where time passes, so it is a task that can wait on clocks and events. The twelve run-time phases, such as reset_phase and main_phase, run alongside run_phase as tasks too.
What if the agent's build_phase reads a handle from the scoreboard, its sibling? L2
It works or crashes depending on which sibling builds first, and that order is not something to design around. Keep each build self-contained. Make links between siblings in the parent's connect_phase, when every component exists.
A Test That Passed in Zero Time6
A new test that sends 200 items has passed in every regression. A reviewer opens its log, shown below. What went wrong? L1L2
The whole test ran at time 0, and nothing was sent or checked. No component raised an objection, so the run phase ended in its first time step. UVM then killed the sequence before its first item. Starting a sequence does not keep the phase open. The test must raise an objection before seq.start() and drop it afterwards.
Why does the phase end at exactly time 0, and not when the sequence finishes? L1
The run phase ends when its objection count reaches zero, and the count starts at zero. After the run tasks start, the phase sees nobody objecting and ends in the same time step. It then kills every run task that is still running, including the sequence.
Follow-upPractise Why the Run Phase Ends at Time 0 →Break it: the test raises, starts the sequence, and drops when start() returns. When can that still end too early? L1L2
start() returns when the sequence body ends, after the last item_done(). The last transaction may still be inside the DUT, with its result not yet checked. Dropping then ends the phase and kills the scoreboard's wait. Add a drain time, or let the scoreboard object until its queue is empty.
Compare raising the objection in the test with letting the sequence object for itself. L2
A test-level objection is simple and visible: the test knows what it started. A sequence can object for itself with set_automatic_phase_objection(1), which raises before its body and drops after. That suits default sequences started without a test task. Pick one owner, so the count is easy to read when you trace it.
What if someone fixes the test by adding #10us at the end of run_phase, inside an objection? L1
It passes today and breaks later. If the sequence grows or the DUT slows, the delay ends the test too early. If the work is short, every run wastes the extra time. Tie the end to the work: drop the objection when the sequence returns, and cover in-flight results separately.
Follow-upPractise Ending a Test After a Fixed Delay →How does the opposite failure look: an objection that is raised and never dropped? L2
The run phase never ends, so the test hangs until UVM's global timeout of 9200 seconds of simulated time. With a running clock, that can take hours. Set a timeout that fits the test with uvm_root::get().set_timeout(), and trace objections to see who still holds one.
Ending a Test While Results Are in Flight5
This test drops its objection as soon as the sequence returns. The DUT has a three-clock pipeline. Write an end of test that still checks the last results. L1L2L3
The sequence returns when the driver finishes the last item, but two results are still in the pipe. So check_phase sees three sent and one checked. A drain time keeps the phase open a little longer, but it is a guess. A better fix lets the scoreboard object while it still owes results. It raises when its expected queue becomes non-empty and drops when it empties. Keep an end check for anything still outstanding.
QuestionPractise Ending a Test While Results Are in Flight →Compare a drain time, a scoreboard objection and phase_ready_to_end(). When would you pick each? L2
A drain time is a fixed wait after the last drop: simple, but a guess. A scoreboard objection follows the real outstanding work. phase_ready_to_end() is a callback that runs when the count first reaches zero. There a busy component can raise again until it is idle, without objecting during normal traffic.
What if phase_ready_to_end() raises an objection but never drops it? L2L3
The phase never ends, and the test hangs until the global timeout. So the component must drop the objection when its work is done, on every path, including errors. The callback can also run again each time the count returns to zero, up to a limit. A timeout with a clear message keeps a hang from looking like a slow test.
Follow-upPractise A Ready-to-End Raise That Never Drops →What if the DUT loses one result, so the scoreboard's queue never empties? L2
The scoreboard's objection never drops, so the test hangs until the timeout. Give the scoreboard a bound: a watchdog that fails after the longest legal latency with the outstanding items listed. That turns a silent hang into a precise error message.
Follow-upPractise A Result That Never Arrives →At subsystem level you have six interfaces and an interrupt that may fire long after traffic stops. Who decides when the test ends? L3
No single test can know every latency, so give each checker its own "still busy" rule. Each scoreboard objects or uses phase_ready_to_end() while it owes results. The interrupt checker waits a bounded time for a pending interrupt. The test only owns its stimulus. Add a heartbeat or watchdog, so a stuck component fails with its name instead of hanging.
Deciding That Verification Is Done6
Walk me through how you decided that verification of your last block was done. L3L4
"Done" is a list of exit criteria agreed with design at the start, each one measured. Every feature in the plan has a test and a checker that can fail. Functional coverage is closed, or each hole is waived in a review. Code coverage is reviewed for logic nobody exercised. Nightly random regressions stay clean for an agreed period, and the bug rate has flattened.
QuestionPractise Deciding That Verification Is Done →How could your green regression be hiding a broken checker, and how do you rule that out? L2L3
A checker can be disconnected, demoted to a warning, or even compare a value with itself. Then every test passes. Prove each checker by injecting a known bug, such as a wrong data bit or a dropped response. Then confirm that the right check fails. Keep a few such runs in the regression.
Follow-upPractise How You Know a Checker Can Fail →What if the planted bug makes the run fail, but not through the check you meant to prove? L2L3
Then the planned check is still unproven, even though the run fails. Find out why it stayed quiet: it may be disabled, wired to the wrong signal, or compare too late. Make the planted-bug run match the expected message, not just "some error".
Follow-upPractise A Planted Bug Caught by the Wrong Check →Compare the bug-discovery trend with coverage as a sign-off signal. Which do you trust more? L3
Neither alone. Coverage says what the tests exercised, as far as the model goes. The bug trend says whether new tests still find problems. A flat bug curve with low coverage means the tests repeat themselves. High coverage with bugs still arriving means the model or the checkers miss something.
Follow-upPractise Bug Rate vs Coverage as a Sign-Off Signal →A derivative chip changes two blocks and keeps twenty. How does your sign-off change? L4
Spend the effort where risk changed. The two changed blocks get full block-level closure. Unchanged blocks re-run their existing regressions on the new RTL to show nothing moved. Integration gets the new work: connectivity, address maps and the paths that touch the changed blocks. Write down what you chose not to re-verify, so the risk is visible.
Follow-upPractise Sign-Off for a Derivative Chip →What if tape-out is in a week and 3% of functional coverage is still open? L4
Rank the holes by risk, not by count. Close the ones on paths that real software uses, if a directed test can reach them in time. Review the rest with design: some are unreachable and get waived, others become known risks with a plan for post-silicon tests. Record every decision, because someone will ask about it after silicon returns.
Follow-upPractise Tape-Out While Coverage Is Still Open →What a Monitor Samples6
What does a UVM monitor do? At which moment should it turn pin activity into a transaction? L0L1
A monitor only watches the pins; it never drives them. It builds a transaction at the edge where the DUT accepts the data, for example where valid and ready are both 1. It then publishes the transaction through an analysis port to the scoreboard and coverage. It creates a new object for every transaction, because write() passes a handle, not a copy.
Why does a reused object break the scoreboard, while a coverage collector on the same port looks fine? L1
A coverage collector samples the fields inside write() and keeps nothing afterwards. A scoreboard stores the handle so it can compare later. When the monitor refills the same object, the stored item changes under the scoreboard. A new object per beat gives each stored item its own data.
Compare creating a new object per beat in the monitor with cloning the item inside each subscriber. L1L2
A new object in the monitor costs one allocation per beat and protects every subscriber at once. Cloning in subscribers puts the duty on every listener, and one careless listener can corrupt the others. The usual rule: the monitor creates, subscribers treat the item as read-only, and a subscriber that must change it clones it first.
Follow-upPractise New Object in the Monitor vs Clone in the Subscriber →What if ready stays low for three cycles while valid is high? L1
A monitor that samples on valid alone reports that one beat four times. The scoreboard then sees four inputs for one transfer and fails later, far from the cause. Coverage also counts stall cycles as transfers. Sample only at an edge where valid && ready is true.
Should protocol checks live in the monitor, or as assertions in the interface? L2L3
Cycle rules, such as "valid holds until ready", fit best as SVA in the interface or in a bound checker. They fire at the exact cycle, and formal tools can reuse them. Transaction rules, such as a length that matches its payload, fit in the monitor or a checker class. Many teams keep both, with a switch such as checks_enable per agent.
At SoC level, the same agent watches an internal bus that nothing in the bench drives. What must the monitor already do right? L2L3
It must work with no driver and no sequencer, using only the pins. It must not read the driver's variables or rely on its timing. Its analysis port must carry complete transactions that a system scoreboard can use. A monitor that peeks into the driver breaks the first time its agent is made passive.
Follow-upPractise A Monitor That Survives SoC Reuse →An In-Order Scoreboard4
Write a scoreboard for a pipelined DUT whose results leave in the same order as the inputs went in. L0L1
Give the scoreboard two analysis imps: one for inputs and one for results. On each input, compute the expected result with a reference function and push it onto a queue. On each result, pop the front of the queue and compare. In check_phase, report any expected results still waiting. A result that arrives while the queue is empty is also an error.
Why treat a result that arrives with an empty queue as an error, rather than skipping it? L1
It means the DUT produced a result nobody asked for, or the input monitor missed a transfer. Both are real bugs. Skipping the result would hide a spurious output and could shift every later comparison.
Follow-upPractise A Result with Nothing Expected →Compare exp == act with exp.compare(act) when the expected and actual items are class objects. L1
On class handles, == asks whether both handles point to the same object. Two separate objects with equal fields are not equal under ==. compare() calls the item's do_compare(), which compares the fields you chose. That also lets you leave out fields the DUT may legally change.
What if the DUT may legally return a different value in one field, such as a timestamp? L2
Leave that field out of do_compare(), or check it against a range instead of one value. Write down in the item why the field is skipped. A silent skip that nobody documents later hides a real bug in that field.
A Scoreboard for Out-of-Order Responses5
Reads carry an ID, and the DUT may return different IDs in any order. Write the scoreboard. L2
Store the expected responses in an associative array of queues, keyed by ID. On a request, push its expected data onto the queue for its ID. On a response, pop the front of that ID's queue and compare. Use a queue per ID, because responses with the same ID must still return in order on buses such as AXI. At the end, every queue must be empty.
QuestionPractise A Scoreboard for Out-of-Order Responses →What if the response carries no ID, only an address? L2
Key by whatever the response carries that names its request. If two open requests share a key, the protocol must define their order, or the scoreboard cannot tell them apart. Then ask the designers how the DUT orders them, and check that rule.
Follow-upPractise Choosing the Key for Out-of-Order Checking →Compare the cost of an out-of-order scoreboard with an in-order one. L2
The in-order version pops one queue, so each check costs constant time. The keyed version adds a lookup and must track what is still open for each key. Its errors are also harder to read, because a lost response shows only at the end or at a timeout. Use it only where the protocol really allows reordering.
Follow-upPractise The Cost of Out-of-Order Checking →How would a response that never comes show up, and how do you catch it early? L2L3
With only an end-of-test check, it appears as one leftover entry after a long run. Stamp each expected entry with its request time. A watchdog then reports any entry older than the allowed latency, with its ID and address. That points straight at the stuck request.
Follow-upPractise Catching a Response That Never Comes →Scale it: an interconnect with four masters and three slaves. How does the checking change? L3L4
Keep one keyed scoreboard per master port for its own responses. A routing model maps each address to its slave and checks that each request appears on the right slave port. The interconnect may widen or remap IDs, so the model must apply the same mapping. Finally, a global check makes sure every request ends exactly once.
Follow-upPractise Checking an Interconnect →Choosing a Reference Model5
Your scoreboard needs expected results for a complex block. When do you use a transaction-level predictor, a C model through DPI, or a cycle-accurate model? L3
Use an untimed transaction-level predictor in SystemVerilog when the function is clear and timing is not the point. Use a C or C++ model through DPI when the architects already have one, such as a codec. Avoid a cycle-accurate model unless timing itself is the specification. It copies the RTL so closely that it can copy the RTL's bugs. Whoever writes the model should work from the spec, not from the RTL.
QuestionPractise Choosing a Reference Model →An untimed model produces results at once, but the DUT takes many cycles. How do you compare them? L3
Compare at sync points that both sides define: the end of a transaction, a frame or an interrupt. The model pushes its expected result into a queue as soon as it sees the input. The scoreboard compares when the DUT's output for that point appears. Nothing between sync points is compared.
Follow-upPractise Comparing an Untimed Model with Timed RTL →How does a badly chosen sync point hide a bug? L3
Say you compare only at the end of a frame. A wrong value in the middle, later overwritten by the DUT, is never seen. Choose sync points where the value is visible to software or to the next block. Add assertions for the rules between them.
Follow-upPractise A Sync Point That Hides a Bug →What breaks most often at the DPI boundary of a C reference model? L3
Type mapping and memory ownership. A 4-state value passed to an int argument loses its X and Z bits. A C function that keeps a pointer to SystemVerilog data after the call may read freed memory. Keep the interface to simple 2-state types or packed arrays, and copy data during the call.
Scale it: how do you reuse the same model from block level up to the full chip? L3L4
Keep the model behind a transaction interface with no pins and no clocks. At block level, the block's monitors feed it. At chip level, the same model listens to monitors on the chip boundary or to an internal passive agent. Only the connections change, so the model's own bugs are found once.
Follow-upPractise One Reference Model from Block to Chip →Triaging a Scoreboard Mismatch5
A smoke test sends 16, 32, 48, 64 and 80 to an incrementer and fails with this log. Where do you look first, and what is the likely cause? L1L2
Start at the first error, not the last. Each "wrong" value is the result for the next input: 49 arrives where 33 was expected, then 65 where 49 was expected. That pattern is a shift, so one result went missing; the arithmetic is fine. The end check agrees: one result never arrived. Look at the output pins around the time 33 was due, then decide whether the DUT dropped it or the monitor missed it.
QuestionPractise Triaging a Scoreboard Mismatch →Compare the evidence for "the DUT dropped the result" with "the output monitor missed it". L2
If the pins never show out_valid for 33, the DUT dropped it. If the pins show it but the monitor did not publish it, the monitor's sampling condition is wrong. A transaction count per interface settles it quickly. So does an independent check on the pins, such as an assertion or a short $display.
What if the waveform shows the DUT driving 33 correctly at the right time? L2
Then the bench lost it. Check the output monitor's sampling edge and condition, then the analysis connection, then the scoreboard's queue handling. Fix it and add a check that would have caught it, such as equal input and output counts. A bench bug that hides one result can hide real bugs too.
Follow-upPractise When the Waveform Clears the DUT →Scale it: 200 tests fail overnight with scoreboard errors. How do you triage? L3
Group the failures by the text of their first error and by the commits since the last good run. One root cause usually gives one group. Rerun one failure per group with its seed and with waves on. Assign groups to people, not single tests, so two people never debug the same bug.
Follow-upPractise Triage When Many Tests Fail →Why should error messages be written so that they group well? L3
Automatic grouping works on the message text. A message with a fixed ID and a stable start, followed by the values, groups cleanly. A message that starts with a time or a counter makes every failure look unique. Use the report ID for the name of the check.
Follow-upPractise Error Messages That Group Well →A Random Test That Fails on Some Seeds6
A random test passes on most seeds but fails on about one seed in 25. Here are two runs of the same build. How do you reproduce the failure and find its cause? L2
Rerun the failing seed on the same build; with the same code and seed, the run repeats exactly. The error shows y=0 where 255 was expected, and only the input 255 gives that pair. The spec says the output saturates at 255, but the DUT wrapped to 0. Uniform 8-bit stimulus picks 255 rarely, so most seeds never test it. Fix the DUT, then add weighted stimulus and a coverage bin for 255.
QuestionPractise A Random Test That Fails on Some Seeds →Why does the same seed reproduce the run exactly? L1L2
Every random value comes from generators that start from the seed. Each thread and object gets its own generator, seeded in a fixed order. With the same code, build and seed, every call returns the same value. So the failure happens again at the same time.
Follow-upPractise Why the Same Seed Repeats the Run →What if the failing seed passes when you rerun it? L2L3
Then something besides the seed changed. Check the build: a newer RTL or bench commit, other compile options, or another simulator version. Check the plusargs and any files the test reads. Also suspect a race between processes, which can resolve differently after a small code change. Record the commit, seed and command line with every failure.
Follow-upPractise A Failing Seed That Now Passes →How can a race make a failure depend on an unrelated change? L3
When two processes read and write one variable in the same time step, the language allows either order. A small edit can change the order a simulator picks. The failure then moves with the edit, not with the seed. Clocking blocks and nonblocking writes remove such races.
Follow-upPractise A Race That Moves with Unrelated Edits →Compare how UVM seeds its objects with plain SystemVerilog thread stability. L3
In plain SystemVerilog, a new object's generator is seeded from the thread that creates it, in creation order. Creating one extra object earlier shifts every later one. UVM reseeds components and sequence items from their type name, full name and a global seed. So adding an unrelated component leaves other items' values alone, but renaming a sequence changes them.
Follow-upPractise UVM Seeding vs Thread Stability →Scale it: a test fails once in 500 nightly runs. How should the regression system handle seeds so that such failures are caught and kept? L3L4
Run new random seeds every night, and keep a list of every seed that once failed. Rerun that list on every commit as a fixed guard set. Store the seed, commit and command line with each result. Rank seeds by the coverage they add, and keep the ones that add most.
Follow-upPractise Seeds in a Regression System →A Test That Passed with Its Errors Hidden5
An error-injection test passes with zero UVM errors, as in this log. A colleague then finds a real data bug in the same run. What happened? L1L2
A report catcher turned every SB error into an info message. It was meant to hide only the one fault the test injects. The real mismatch was demoted too, so the error count stayed at 0. The catcher summary gives it away: 2 demoted errors for 1 injected fault. A catcher must match the exact expected message and count what it demotes.
Compare a report catcher with set_report_severity_id_override(). L2
A severity override changes every message with that ID from one component, with no conditions. A catcher runs code for each message, so it can match the text, the values and a count. Use an override only for a whole check you have decided to waive. Use a catcher for one known, specific message.
Follow-upPractise Report Catcher vs Severity Override →How should an error-injection test check that the bench caught the injected fault? L2
Count the expected errors the catcher demoted, and compare that count with the faults the test injected. Equal counts pass; anything else fails. Every other error stays an error. A test that expects an error and sees none must fail too.
Follow-upPractise Checking That an Injected Fault Was Caught →Scale it: what makes a regression's pass or fail decision trustworthy? L3
A test passes only when three things hold. It prints its own pass line, the summary shows zero errors and fatals, and its checkers report real work. Waivers live in one reviewed file, each with an owner and an end date. The regression also tracks demoted messages per test and flags any change in that count.
Follow-upPractise What Makes a Pass Trustworthy →How can a test pass with zero errors and still check nothing? L3
If the monitor is not connected, or the scoreboard never gets items, nothing is compared and nothing fails. A final check that the scoreboard compared at least the expected number of items catches this. Coverage that stays empty points the same way.
Follow-upPractise A Pass with Nothing Checked →An Override That Did Nothing5
Your error test sets a type override from fifo_driver to err_driver. The log still shows the normal driver. The env builds it with drv = new("drv", this);. Why? L1L2
Calling new() runs the constructor directly, so the factory never sees the request and cannot substitute anything. Only type_id::create() asks the factory, which checks for overrides before it builds. The override was registered correctly, and nothing warned, because nobody asked for that type. Create every component and object that a test might replace with create().
Why does type_id::create() see the override when new() cannot? What happens inside it? L2
type_id is a small proxy class that the utils macro adds for each registered type. Its create() calls the factory with that proxy, the parent and the name. The factory looks for an instance override that matches the full path, then a type override. It builds the winner and casts it back to the requested type.
What if the override names a class that is not derived from the requested type? L2
The factory builds the override class, then casts it back to the requested type. The cast fails, because the new class is not a subclass. The uvm-core library then stops the run with a fatal error at that create call. So an override must name a class derived from the original.
Compare a factory override with a configuration knob such as inject_errors = 1. Which would you use for an error-injecting driver? L2
A knob changes values inside one class, so the base driver needs an if for every behaviour any test might want. An override swaps in a derived class with new behaviour, leaving the base driver clean. Use knobs for numbers like delays and lengths. Use an override when the behaviour itself changes, such as corrupting parity.
What if the test registers the driver override in run_phase instead of build_phase? L1
The driver was built during the build phase, so it keeps its class. An override registered in run_phase still affects objects created later, such as sequence items. So the same line can work for items and silently fail for components.
Type and Instance Overrides Together6
Three agents each create a bus_driver. The test registers these overrides. Which class does each agent's driver get? L1L2L3
agt0 gets err_driver from the type override. agt1 gets slow_driver, because a matching instance override beats a type override, whatever the order. agt2 also gets err_driver. The factory's own method takes a full path, so "env.agt2.drv" matched nothing. The type_id form is relative to this, which made "env.agt1.drv" correct.
Why does the factory check instance overrides before type overrides? L2
An instance override is the more specific request: "this one component". A type override means "everywhere else". Checking the specific case first lets a test change all agents except one, without caring about the order of the calls.
Follow-upPractise Why Instance Overrides Win →What if the instance override path is written relative to the wrong scope? L2
The component method set_inst_override_by_type() puts the caller's full name in front of the path. The factory method of the same name takes the path as written, from the top. Mix them up and the path matches nothing. The factory then builds the original type and reports nothing, so print the created type to check.
How can an instance override for *.agt*.drv beat a later override for env.agt1.drv? L3
Instance overrides are searched in the order they were registered, and the first match wins. If the wildcard comes first, it also matches agt1, and the specific override is never reached. Register specific instance overrides before general ones, often in a base test that derived tests call first.
Follow-upPractise A Wildcard Override That Beat a Specific One →Compare overriding by type with overriding by name, including from the command line. L2
By type uses the proxies, so a misspelt class name fails to compile. By name uses strings, so a typo only fails when the factory looks the name up. Command-line plusargs such as +uvm_set_type_override are by name. They are handy for a quick experiment, but they move test intent out of the code where reviewers see it.
In a forty-agent environment, how do you prove which overrides took effect? L2L3
Print the topology after build with uvm_top.print_topology(), which shows every component's class. Print the factory with uvm_factory::get().print() to list registered overrides. For one doubtful request, debug_create_by_type() explains which override would be chosen and why. A regression check can compare the topology against an expected list.
Who Wins in the Config Database6
The env sets depth to 16 for its agent. The test, above it, sets 4. What does the agent read? L1L2
It reads 4. During the build phase, a set made from higher in the hierarchy wins over one made lower down. The test is above the env, so the test's value wins. This lets a test change an environment default without editing the environment. When both sets come from the same component, or after build, the latest set wins.
QuestionPractise Who Wins in the Config Database →Why did UVM choose "higher in the tree wins" during build? L1
The environment is written once and reused by many tests, so its sets are defaults. The test is the most specific description of one scenario, so it must be able to override them. Making the shallower setter win gives the test that power without editing shared code.
Follow-upPractise Why the Higher Set Wins →What if the env sets a value again during run_phase, after the test set it in build? Who wins now? L2
The env's new set wins. Depth gives precedence only while the build phase runs. After build, every set gets the same precedence, so the most recent set wins. A component that reads the value again then sees the env's value.
Follow-upPractise Config Precedence After Build →What if a test changes depth with uvm_config_db::set in the middle of run_phase? L2
The database holds the new value, and the latest set wins. But the agent read depth once in build, so its variable does not change. A component that must react has to call get again, or wait with uvm_config_db::wait_modified(). For run-time control, a shared config object or an event is usually clearer.
Compare setting ten separate fields for an agent with setting one agent configuration object. L2
Ten fields mean ten string lookups, ten chances to misspell a name and no single place that lists the agent's options. One config object is typed, holds the virtual interface and the active or passive choice, and needs one get. The test builds it, fills it and sets it once for the agent's path.
Build time of your SoC bench has grown to minutes, and profiling points at uvm_config_db. What do you change? L3
Every get searches the entries whose scope might match, and broad wildcards like * match every lookup. Scope sets to the exact paths that need them. Replace many scalar sets with one object per agent. Get each value once in build, never in a per-item loop.
A Config Database Get That Found Nothing6
The driver stops with this fatal error, yet the top module does set the interface. Config tracing was on. What is wrong? L1L2
A get matches only when three things agree: the type parameter, the scope and the field name. It returns 0 and never says which one differed. The trace lines show the cause here. The set used the scope uvm_test_top.env.agent.*, but the driver is uvm_test_top.env.agt.drv. Compare each set with the failing get, string by string.
Show how you would trace every set and get in the config database. L1
Run with +UVM_CONFIG_DB_TRACE, or call uvm_config_db_options::turn_on_tracing() before run_test. Each set and each get prints a line with its scope and field. At the end, check_config_usage() lists sets that no component ever read, which is how typos usually show up.
What if the trace shows the set and the get, but they never meet? What do you compare? L1L2
Compare the scope strings first: a missing level, a typo or a wrong wildcard shows at once. Then compare the field names. Last, compare the types, because the database is keyed by type too. A set of int is not found by a get of bit [31:0].
What if one initial block sets the interface and a different initial block calls run_test? L2
Both blocks start at time 0, and the standard does not fix their order. The bench may work on one simulator and fail on another, or after an unrelated edit. Put the set before run_test in the same block, so the order is written in the code.
How can set(null, "*", "vif", bus0) break a bench with two agents on two buses, without any error? L2
The * scope matches every component, so both agents get bus0. Neither get fails, so nothing reports a problem. The second agent drives and watches the wrong bus, and its checks see the first bus's traffic. Set each interface for its own agent path.
Compare uvm_config_db with uvm_resource_db. Why do most benches use only the first? L3
Both store values in the same resource pool. uvm_config_db adds the component tree: scopes are built from a context component, and build-phase precedence follows the hierarchy. uvm_resource_db takes raw scope strings and has no hierarchy rules. Benches use config_db because its rules match how tests override environments.
Handing the Interface to the Driver5
Write the code that gets this interface instance from the top module to the driver. L0L1
In the top module, before run_test, call uvm_config_db #(virtual bus_if)::set(null, "uvm_test_top.env.agt.*", "vif", bus);. In the driver's build_phase, call get(this, "", "vif", vif) with the same type. If it returns 0, stop with uvm_fatal. A class cannot hold an interface instance, only a virtual interface: a handle that points to one.
Why can't the driver class just instantiate bus_if itself? L0
Interfaces and modules are static: they are built at elaboration, before time 0. Classes are created during simulation. So a class cannot instantiate an interface. It holds a virtual interface, which is a variable that points at an interface instance someone else built.
Follow-upPractise Why a Class Needs a Virtual Interface →What if the driver's virtual interface handle is still null when run_phase starts? L0L1
The first access through it is a null access, and the simulation stops there. Usually the get in build failed and nobody checked its return value. Check the return of uvm_config_db::get, and call uvm_fatal with the path when it fails. Then the log names the missing set.
Compare each component getting the interface itself with the agent getting it once and passing it down. L1
When each of the driver and monitor calls get, there are two lookups and two places a path can be wrong. When the agent gets its config object, which holds the handle, it assigns drv.vif and mon.vif itself. Then only one path must match, and a passive agent simply skips the driver.
Your bench must also run on an emulator, where class code cannot touch signals directly. How do you split it? L3
Put all signal activity in a bus functional model inside the interface or a module, with tasks such as drive(item). The class side calls those tasks through a handle, an abstract class or the virtual interface. On the emulator, that pin-level half is compiled into hardware, and only transactions cross the boundary.
Walking Through a FIFO Test Plan6
Walk me through your test plan for a synchronous FIFO with a configurable depth. L2L3
I start from the spec and list every feature: ordering, full, empty, the almost-full flag, and reset. For each feature I say how it is checked: a scoreboard for order and data, assertions for flag timing and illegal pushes. Then I say how it is driven: random push and pop rates, plus directed fill-to-full and drain-to-empty. Each feature gets coverage, such as fill level crossed with push and pop. I finish with exit criteria: tests passing on many seeds, coverage closed, and no open serious bugs.
QuestionPractise Walking Through a FIFO Test Plan →Why list features before tests? L2
Designers and architects can review a feature list against the spec. Each feature then maps to checks and coverage you can measure. A list of tests cannot show what is missing. Features also stay stable when tests are renamed or merged.
Follow-upPractise Why the Plan Lists Features Before Tests →What if a feature in the plan has no check and no coverage item mapped to it? L2
Then the plan shows a hole, which is the point of the format. Either add a check and a cover for it, or record why it is out of scope, approved by the architect. At sign-off, every feature row needs a check, a cover and a result.
Follow-upPractise A Feature Row with No Check →What if the depth is a parameter that can be anything from 2 to 1024? L2
Pick configurations that stress different logic. Use the smallest depth, a power of two, a depth that is not a power of two, and the largest. Run the full plan on a few and a smoke set on the rest. Collect coverage per configuration, because "full" at depth 2 says nothing about depth 1000.
Follow-upPractise Planning for a Parameterised Depth →Which FIFO rules go in assertions, and which in the scoreboard? L2
Cycle rules go in assertions: full rises in the cycle the last slot fills, and no push is accepted while full. Data rules go in the scoreboard: every popped value matches the oldest pushed value. Some rules need both, such as the count after reset. Formal tools can then prove the assertion side on its own.
Scale it: how do you get the plan signed off, and how do you keep it alive? L3
Review it with the designer and the architect, row by row, and record who agreed. Link each row to its checks and coverage items, so the plan shows live status. Review it again whenever the spec changes. At sign-off, every row is closed or formally waived with a reason.
Follow-upPractise Getting a Test Plan Signed Off →Designing the Bench for a DMA Engine7
Design the testbench for a DMA engine. It has an APB register port, an AXI master port to memory, and an interrupt line. What goes where? L3L4
An APB agent with a register model and explicit prediction programs the DMA. A reactive AXI slave agent answers reads from a memory model and records writes. A predictor reads each programmed transfer from the register model and computes the expected memory writes. A scoreboard compares those with the writes the AXI monitor saw. An interrupt monitor checks when the interrupt rises and clears, and a virtual sequence ties the steps together.
QuestionPractise Designing the Bench for a DMA Engine →Why model the memory with a reactive slave agent, rather than a plain SystemVerilog memory module? L3
A plain memory module answers the same way every time. A reactive agent takes its responses from sequences, so a test can add wait states, back-pressure and error responses on chosen beats. Its monitor also publishes every transfer for the scoreboard and coverage. The memory storage itself can stay a simple model that both the agent and the predictor read.
Follow-upPractise Why Memory Is a Reactive Agent →What if a test needs the memory to return an error on one beat of one transfer? How does it do that? L3
The test starts a different responder sequence on the slave agent. That sequence answers normally until the chosen address and beat, then returns an error response. The driver is unchanged, because the sequence picks the reply. The scoreboard is told the same plan, so it expects the DMA to stop or report.
Follow-upPractise An Error Response on One Chosen Beat →Compare checking memory contents at the end with checking each write as it happens. L3
An end-state check is simple and catches wrong final data. It misses writes outside the buffer that were later overwritten, repeated writes and wrong order. A stream check compares each write as it arrives, so it points at the exact transfer. Many benches do both: stream checks for debug, and an end check as a safety net.
Follow-upPractise End-State Check vs Stream Check →What if the DMA follows a chain of descriptors that it reads from memory? L3
Then the descriptors live in the memory model, and the predictor must read the same memory, not the register model. Build the chain in the test, write it into memory by backdoor, and point the DMA at its head. Check the descriptor reads too, not only the data writes.
Follow-upPractise A DMA That Follows Descriptors in Memory →What kind of bug would this bench miss? L3L4
One that the predictor shares with the RTL, such as the same misreading of the descriptor format. Both agree, so every check passes. An independent model author, spec reviews, and a few directed tests with results worked out by hand protect against this.
Follow-upPractise The Bug This Bench Would Miss →Scale it: at SoC level, what of this bench survives? L4
The APB agent turns passive behind the CPU, and real memory replaces the slave model. The predictor and scoreboard survive if they listen only to monitors. The predictor must read memory through an interface that either the model or a backdoor to real memory can serve. Software or a CPU model replaces the virtual sequence.
Follow-upPractise What of the DMA Bench Survives at SoC Level →Desired and Mirrored Register Values5
The DUT holds CTRL at 0x01 after reset, and auto-prediction is on. What are the desired and mirrored values after each line? L1L2
set(5a) changes only the desired value; the mirror stays 1, so an update is needed. update() writes 5a on the bus, and the mirror follows. write(33) sets both to 33. predict(77) sets both to 77 in the model only, while the DUT still holds 33. mirror(UVM_CHECK) reads 33, reports a mismatch against 77, and sets both values to 33.
Why keep a mirror at all, when the bench could simply read the register? L1
Reads cost bus cycles and can have side effects, such as clear-on-read. The mirror tells the bench the expected value at any time, with no extra bus traffic. It also lets mirror(UVM_CHECK) compare a real read with what the bench believes. That comparison is how the register layer finds register bugs.
How does a DUT reset in the middle of the test break the mirror? L2
The DUT goes back to its reset values, but the model keeps the old mirror. Every later check then fails. Call regs.reset() when the bench sees the DUT reset, so the model returns to the same reset values.
What if the field is a status counter that the hardware changes by itself? L2
Its mirror can never be exact, so a check against it fails at random. Configure the field as volatile. In uvm-core that also sets its compare mode to UVM_NO_CHECK, so mirror(UVM_CHECK) skips it. Check the counter some other way, such as a scoreboard that predicts its value from observed events.
Compare write(v) with set(v) followed by update(). L1L2
write(v) always performs a bus write and updates both values. set() plus update() lets you change several fields first and then write only the registers that changed. That suits configuring a block from randomized register values. It also avoids writing registers that already hold the right value.
Frontdoor vs Backdoor Register Access5
What is the difference between a frontdoor and a backdoor register access? When would you use each? L1L2
A frontdoor access runs a real bus transfer through the adapter and the agent. It takes clock cycles and tests the bus decode and the register logic. A backdoor access reads or writes the register storage directly, in zero time. Use the frontdoor to verify the registers themselves. Use the backdoor to set up state quickly, or to check a value without disturbing the bus.
QuestionPractise Frontdoor vs Backdoor Register Access →Compare how the mirror is updated after a frontdoor access and after a backdoor access. L2
A backdoor access always updates the mirror, because the model knows the exact value it deposited or read. A frontdoor access updates it only through auto-prediction or through a predictor watching the bus. So a frontdoor write with neither one leaves the mirror stale.
Follow-upPractise How Each Door Updates the Mirror →What if a frontdoor write seems to work, but you suspect it went to the wrong register? L1L2
Write through the frontdoor, then peek() the intended register through the backdoor. Peek its neighbours too. If the value landed somewhere else, the address decode is wrong. The built-in uvm_reg_access_seq does this for every register: it writes one way and reads the other.
How can a backdoor check pass while the register is broken? L2
Say the HDL path points at the wrong signal, such as the next-state value instead of the flop. The backdoor then reads what it expects. uvm_reg_mem_hdl_paths_seq only checks that each path exists. A wrong path that exists still passes it. Only a frontdoor and backdoor comparison, such as the access test, catches it.
Scale it: what happens to backdoor paths when the same tests run on the gate-level netlist? L3
Synthesis renames, flattens or merges register signals, so the RTL paths no longer exist. Teams either keep a second path map for the netlist, under its own HDL path kind, or use only the frontdoor there. Plan this before the netlist arrives, because a broken backdoor makes many tests fail at once.
Follow-upPractise Backdoor Paths in Gate-Level Simulation →Auto vs Explicit Register Prediction6
Your register model uses auto-prediction. A boot sequence also writes registers directly on the bus. What goes wrong, and how do you set up prediction instead? L2L3
Auto-prediction updates the mirror only for accesses made through the register model. The boot write is invisible to it, so the mirror stays at the reset value and the next mirror(UVM_CHECK) fails. Use explicit prediction instead: connect a uvm_reg_predictor to the bus monitor. The predictor converts every observed transfer with the adapter and updates the right register. Turn auto-prediction off, so each access is predicted once.
Why does explicit prediction matter more once the block sits inside an SoC? L3
At SoC level, a CPU model, a DMA or a debug port may write the same registers through other paths. The block's register model never starts those writes. Only a predictor listening on the block's own bus sees them all. The same prediction setup then works unchanged at both levels.
Follow-upPractise Why Explicit Prediction Matters at SoC Level →What if the predictor sees a bus write to an address that the register map does not contain? L2L3
The predictor updates nothing. The uvm-core library reports it only as an info message at UVM_FULL verbosity, so a normal run prints nothing. That can hide a decode bug, or a register missing from the model. Raise the verbosity for that message in a check run, or count such writes yourself.
What if a register write ends with an error response, such as PSLVERR, and the register did not change? L3
It depends on the adapter and the UVM version. In uvm-core, the predictor passes the status from bus2reg(), and the register skips a prediction marked UVM_NOT_OK. So bus2reg() must map the error to UVM_NOT_OK. The UVM Cookbook, written for UVM 1.1, says the status is ignored there. Older benches therefore filter errored transfers before the predictor.
Prove it: write the connect-phase code that sets up explicit prediction. L2
Give the map its sequencer and adapter, and turn auto-prediction off. Point the predictor at the map, so it can find a register by address. Give it the adapter, so it can convert bus items. Then connect the bus monitor's analysis port to the predictor's bus_in.
Compare the two modes. When is auto-prediction good enough? L2
In a unit bench where the register model is the only master on the bus. It needs no monitor or predictor, so it is quick to set up. As soon as anything else can write the registers, switch to explicit prediction.
Follow-upPractise When Auto-Prediction Is Good Enough →Writing a Register Adapter5
Write the adapter between a register model and an APB agent. What do reg2bus, bus2reg and provides_responses do? L2
reg2bus() turns a register operation into a bus item: kind, address and write data. bus2reg() does the reverse, filling kind, address, data and status from a completed bus item. The model calls it after each frontdoor access, and the predictor calls it for every observed transfer. Set provides_responses to 1 only if the driver returns separate responses with put_response(). Otherwise the model waits for a response that never comes.
Why does the register layer need an adapter at all? L1L2
The register model knows registers, addresses and values, but nothing about APB or AXI. The adapter is the only bus-specific piece. Swap the adapter and the same model and register sequences run over another bus.
Follow-upPractise Why the Register Layer Needs an Adapter →What if the bus can return an error, and the adapter's bus2reg ignores it? L2
Then every register access reports UVM_IS_OK, even when the bus answered with an error. A test of a missing or protected register then passes. Map the bus response into the status field, for example UVM_NOT_OK on an error. Then register sequences can check the status of each access.
What if the bus has byte strobes, and you want to write one field without touching the rest of the register? L2
Set supports_byte_enable to 1 and map rw.byte_en onto the strobes in reg2bus(). Then a field write can touch only its own bytes. With it at 0, uvm-core warns that the bus has no byte enables and accesses the complete register instead.
Compare running register sequences directly on the bus sequencer with layering a register sequencer on top of it. L3
On the bus sequencer directly, register items compete with other bus sequences, and a test can grab the sequencer. A layered register sequencer gives central, bus-independent control. But register traffic then cannot mix with bus-specific sequences or use their grab and notification hooks. Most benches use the direct form.
A Scoreboard Across a Mid-Test Reset6
A test asserts reset while a result is inside the DUT, then sends more traffic. This scoreboard ignores reset. What does it report? L2
It reports a false mismatch. Reset destroyed the result for input 32, but its prediction, 33, stays at the front of the queue. The first result after reset, 81, is compared with 33. Then the prediction 81 waits forever, and the end check reports it missing. The scoreboard must treat reset as an event: count its open predictions as canceled and empty the queue.
QuestionPractise A Scoreboard Across a Mid-Test Reset →What should the driver do with the item it was driving when reset arrived? L2
Stop driving it and drop it without calling item_done(). stop_sequences() has already emptied the sequencer's request FIFO. An item_done() after that finds no outstanding request, and uvm-core stops the run with a fatal error.
Why does an item_done() after stop_sequences() stop the run instead of being ignored? L2
item_done() tells the sequencer that the driver finished the oldest outstanding request. stop_sequences() has removed every request, so nothing is left to finish. The sequencer treats that as a broken handshake and reports a fatal error. So on reset the driver drops its item without calling item_done().
Compare handling reset inside run_phase with jumping back to reset_phase. L3
Inside run_phase, each component watches reset and cleans up its own state. A phase jump re-runs the reset and main phases for the whole phase domain. That only works if every component, including bought VIP, restarts cleanly from those phases. Many teams avoid phase jumping for that reason and treat reset as an event.
How could a reset test pass without testing reset at all? L2L3
If reset arrives when nothing is in flight, the scoreboard has nothing to cancel, and even the broken scoreboard passes. Check that at least one prediction was canceled and one result was checked after reset. A ledger such as inputs = checked + canceled makes this visible in every run.
Follow-upPractise A Reset Test That Tests No Reset →Scale it: a chip has three reset domains that reset independently. How does the bench handle reset now? L3L4
Each agent and scoreboard listens to the reset of its own domain. A reset in one domain cancels only the predictions for traffic inside that domain. Traffic that crosses domains needs a written rule: is a request lost, retried or completed? Put that rule in the test plan and check it.
Follow-upPractise Independent Reset Domains →Active and Passive Agents6
What does this agent build when is_active is UVM_PASSIVE? What is wrong with its connect phase? L0L1
A passive agent builds only the monitor; an active agent adds the sequencer and the driver. This connect phase uses drv and sqr even in passive mode, where both are null. So the simulation stops with a null-handle error. Guard the connection with the same get_is_active() test. Use passive agents where the bench only watches, such as an internal bus at SoC level.
Who decides whether an agent is active, and how? L1
Usually the environment or the test, through the configuration database or an agent config object. In uvm-core, uvm_agent reads an is_active setting from the configuration database during its build_phase. get_is_active() returns it, and a subclass may override that function to read its own config object instead.
What if the agent is set passive, but its build_phase still creates the driver and sequencer? L1L2
The driver then exists, and its run_phase may still drive the bus. At SoC level that fights the real master on the same pins. Call super.build_phase first, then build the driver and sequencer only when get_is_active() returns UVM_ACTIVE. Guard their connection with the same test.
Compare a passive agent with simply instantiating the monitor on its own. L1L2
The passive agent keeps the same structure, config object and analysis port as the active one. Scoreboards and coverage connect to it the same way at every level. A bare monitor works too, but each environment then wires it differently. The agent is the unit you reuse.
Follow-upPractise A Passive Agent vs a Bare Monitor →Scale it: at SoC level, which of your block agents become passive? L2L3
Any agent whose interface is now driven by real RTL turns passive, such as the block's internal bus. Agents on the chip boundary stay active, because the bench still drives those pins. The passive agents keep feeding their scoreboards and coverage, so the block checks keep running inside the chip.
Follow-upPractise Which Agents Turn Passive at SoC Level →What if the agent models a memory slave that answers the DUT? Is it active or passive? L2
Active, even though the DUT starts every transfer. The agent drives the response pins, so it needs a driver and usually a sequence that produces the responses. Passive means the agent drives nothing at all.
Follow-upPractise Is a Responding Slave Agent Active? →Designing a Block Environment for Reuse5
Your block environment will be reused in the subsystem bench and on the next chip. What do you design in from day one? L3
Vertical reuse puts the block environment inside a bigger one; horizontal reuse moves it to another project. For both, each agent takes a config object with is_active, and nothing reaches for global signals or the test's handles. The register model accepts a parent block and a base address. Scoreboards take inputs only from monitors, so they still work when agents turn passive.
Compare what breaks in vertical reuse with what breaks in horizontal reuse. L3
Vertical reuse breaks on assumptions about being at the top. The environment drives pins that are now internal, or owns the register model. Horizontal reuse breaks on assumptions about the design: fixed widths, depths or protocol options. Parameters and config objects fix the second kind. Passive mode and a parent register block fix the first.
Follow-upPractise What Breaks in Vertical vs Horizontal Reuse →What if the block scoreboard took its expected stream from the driver? L3
In the subsystem the block's driver is gone, because real RTL drives that interface. The scoreboard gets no inputs and quietly checks nothing. Build the expected stream from the input monitor instead, so the same scoreboard works in active and passive mode.
Follow-upPractise A Scoreboard Fed by the Driver →How do you make sure a reused scoreboard is really checking something? L3
Count what it compared, and fail the test if the count is zero or below a set minimum. Print the count for each instance in the summary. A scoreboard that checks nothing must fail loudly, not pass quietly.
Follow-upPractise Proving a Reused Scoreboard Is Checking →Scale it: ten block teams feed one SoC bench. What rules do you set? L4
One config object per agent, one analysis port per observed interface, and no hierarchical references into other components. Register models are generated from one source. Each team delivers its environment with a passive-mode test that runs at block level. Integration then wires environments together; it never edits them.
Follow-upPractise Reuse Rules for Many Block Teams →Callbacks vs Factory Overrides for Error Injection5
You must corrupt the parity on chosen beats from one test, without changing the driver for anyone else. Do you use a callback or a factory override? L2
Both work without editing the driver. A factory override swaps in a subclass, so the test can change any virtual method, but only one subclass per instance. A callback runs code at hooks the driver author placed, and several callbacks can stack on one instance. Use a callback when the driver has the hook you need. Use an override when you must change behaviour the author did not expose.
QuestionPractise Callbacks vs Factory Overrides for Error Injection →Why are UVM callbacks added through a typed pool, uvm_callbacks #(T, CB), rather than a plain list? L2
The pool checks at compile time that the callback type fits that component type. The component must also declare the pair with uvm_register_cb, or adding a callback reports a warning. The pool can add a callback to one instance or to every instance of the type. The driver then runs only the callbacks registered for itself.
What if two callbacks on the same driver both change the item? Which runs first? L2
They run in the order they were added, unless one is added with UVM_PREPEND. Both get the same item, so the second sees the first's change. Two error injections on one item can even cancel each other. If the order matters, add them in one place, such as the test.
Compare injecting the error in the driver with an error field in the sequence item. L2
An error field, such as bad_parity, lets the sequence choose which beats to corrupt, and it appears in the item's print. The driver must already understand the field. A driver callback needs no change to the item, but the choice of beats then lives in the callback. Prefer the item field when you own the agent.
Scale it: the VIP is encrypted and has no hook where you need one. What now? L3
You cannot add a callback where no hook exists, and you cannot safely override methods you cannot read. Ask the vendor for the hook, and check whether the VIP already has an error knob in its config or items. Meanwhile, inject the error between the driver and the DUT with a small module on the interface. Record the gap in the test plan.
Follow-upPractise Error Injection in Closed VIP →The Driver Loop and Item Done5
Write the driver loop that takes items from the sequencer. What breaks if item_done() is missing? L0L1
Loop forever: call seq_item_port.get_next_item(req), drive the item on the interface, then call seq_item_port.item_done(). get_next_item waits until a sequence's item is granted. item_done tells the sequencer the item is finished, which lets the sequence's finish_item return. Without it, the sequence waits forever in finish_item. The next get_next_item also reports an error every time.
Why do sequences randomize the item after start_item() and not before? L1
start_item() returns when the sequencer grants this sequence the driver. Randomizing after that, "late randomization", lets the constraints use the latest state, such as a FIFO level a monitor just reported. Randomizing earlier could pick values that were true when the item was made but are stale when it is driven.
What if randomize() fails after start_item() returns? What must the sequence still do? L1L2
The sequencer has granted this sequence, and the driver waits for the item. If the sequence never calls finish_item(), the driver waits forever and the test hangs. So check the return value, report an error, and either finish the item or end the test. A failed call leaves the fields at their old values.
Compare the uvm_do macros with writing start_item, randomize and finish_item yourself. L1
The macros hide create, start, randomize and finish in one line. That is short, but you cannot see or change the steps, and a failed randomize is easy to miss. Writing the three calls makes each step visible, so you can check randomize() and add code in between. Many teams ban the macros for that reason.
What if the bus is pipelined, with a new address every clock while earlier data is still coming back? L2
Calling item_done only after the data returns would leave the address phase idle. Call item_done() when the address phase is accepted, so the sequence can send the next item. Return the data later with put_response(), carrying the request's IDs. One thread drives addresses and another collects data.
Responses That Went Missing6
The driver returns a response for every read with item_done(rsp). How many responses does this sequence collect? L2L3
It collects 8, not 10. Each sequence keeps its responses in a queue that holds 8 by default. This sequence reads none until all ten are sent, so the ninth and tenth are dropped. In uvm-core 2020.3.1 the overflow error is off by default, so nothing is reported. Call get_response() after each finish_item, or use a response handler.
Why must the driver call rsp.set_id_info(req) before returning a response? L1
Several sequences can share one sequencer, so a response has to say whose it is. set_id_info() copies the sequence ID and transaction ID from the request into the response. The sequencer uses them to put the response in the right sequence's queue.
What if the driver creates the response with new and forgets set_id_info()? L1L2
The response then has no sequence ID, so the sequencer cannot route it. The uvm-core library stops the run with a fatal error: "Driver put a response with null sequence_id". So the mistake shows at once, not as a hang. Call rsp.set_id_info(req) right after creating the response.
Compare calling get_response() with use_response_handler(1) and a response_handler() function. L2
get_response() blocks the sequence until a response arrives, so each request waits for its answer. With use_response_handler(1), the sequencer calls your response_handler() function for each response as it arrives. The sequence keeps sending without waiting, which suits pipelined traffic. Responses then bypass the queue, so it cannot overflow.
How could two sequences on one sequencer end up waiting on each other's responses? L2
They cannot, as long as the driver copies the IDs: each response goes to its own sequence's queue. The problem appears when a driver builds the response from a stale request or a shared handle. Then one sequence gets two responses and the other hangs. Within one sequence, get_response(rsp, id) waits for a specific transaction.
Why not set set_response_queue_depth(-1) in the base sequence and forget about overflow? L3
It hides a sequence that never reads its responses. In a long test, those responses pile up in memory, and nobody checks them. Prefer reading responses or a response handler. If you do raise the depth, also enable the overflow report, so a real problem still shows.
Follow-upPractise An Unbounded Response Queue →Coordinating Two Interfaces5
Your DUT has an AXI host port and a UART. A test must configure it over AXI, then send UART traffic while AXI polls status. How do you structure the stimulus? L2L3
Use a virtual sequence: a sequence that drives no pins itself. It holds handles to the AXI and UART sequencers and starts ordinary agent sequences on them. First it runs the configuration sequence on AXI. Then it forks the UART traffic and the AXI status polling. The scenario lives in one place, and each agent's sequences stay reusable.
QuestionPractise Coordinating Two Interfaces →Compare running a virtual sequence on a virtual sequencer with assigning sequencer handles to it directly. L2
With a virtual sequencer, the env builds a sequencer that only holds handles, and the sequence reaches them through p_sequencer. That ties the sequence to one sequencer class. With direct handles, the test or a base sequence assigns them, and the sequence runs with start(null). That is fewer classes, and the sequence works in any env that can supply the handles.
Why can a virtual sequence run with start(null) when it has no sequencer? L2
A virtual sequence sends no items itself. It only starts child sequences on real sequencers, through its handles. So it needs no sequencer of its own, and start(null) just runs its body(). A child sequence is different, because it must send items through a sequencer.
What if UART traffic must start only after the AXI write that enables the UART, not at time 0? L2
Put the order in the virtual sequence: run the configuration sequence to completion, then fork the traffic. When the trigger is inside another running sequence, share a uvm_event or a barrier. The AXI side triggers it after the enabling write, and the UART side waits on it.
How do you reuse the block's virtual sequences when the block becomes one of four in a subsystem bench? L3
Give each block virtual sequence its sequencer handles from outside, never by searching paths. The subsystem virtual sequence holds handles to each block environment's sequencers and starts the block sequences on them. Interfaces that become internal at subsystem level have no driver, so those agents turn passive and their sequences are not started.
Follow-upPractise Reusing Block Virtual Sequences at Subsystem Level →Lock vs Grab on a Busy Sequencer5
Two sequences share a sequencer when an urgent one arrives. Predict the order the driver sees with lock() and with grab(). L1L2
By the usual description, a grab takes the very next slot, while a lock waits behind B1, which was already queued. In both cases A1, already with the driver, finishes first. On uvm-core 2020.3.1 the run shows the same order for both: A0 B0 A1 U0 U1 B1. This library grants any lock request at the next arbitration unless another lock blocks it. Here, a grab differs only by going ahead of locks that are already waiting.
QuestionPractise Lock vs Grab on a Busy Sequencer →Compare the sequencer arbitration modes. When does a sequence's priority matter? L2
The default, UVM_SEQ_ARB_FIFO, grants requests in arrival order and ignores priority. UVM_SEQ_ARB_WEIGHTED picks randomly with priority as the weight. The strict modes always serve the highest priority first, by arrival or at random. UVM_SEQ_ARB_RANDOM ignores priority, and UVM_SEQ_ARB_USER calls your own function. Set the mode with set_arbitration().
What if a high-priority sequence keeps asking under UVM_SEQ_ARB_STRICT_FIFO? L2
Lower priorities wait until it stops asking, so they starve. That is fine for a short interrupt burst, but not for long background traffic. UVM_SEQ_ARB_WEIGHTED shares the driver in proportion to priority, with no bound on any one wait. If a low-priority sequence has a deadline, check it with a timeout.
What if the urgent sequence services an interrupt? How do you structure it? L2
Run it in the background for the whole test. It waits for the interrupt, then grabs the bus sequencer and reads the status. It services the cause, clears the interrupt and ungrabs. Normal traffic pauses only for that window. Use grab when the interrupt must jump ahead of queued traffic, and lock when waiting a turn is acceptable.
Follow-upPractise An Interrupt Service Sequence →How can two virtual sequences that each lock two sequencers hang the test? L3
Sequence one locks the AXI sequencer, then asks for the UART one. Sequence two has already locked UART and now asks for AXI. Each lock() blocks until it is granted, so both wait forever. Always take locks in one fixed order across the bench, or take one lock at a time.
Ports, Exports and Imps5
Explain ports, exports and imps using this put connection. Who implements put(), and which way does the connect call go? L1L2
A port is the calling side: it needs someone to implement the method. An imp is the side that implements it, inside the component that owns the method. An export passes a connection through a parent's boundary to an imp deeper inside. You always connect from the port toward the implementation: prod.out.connect(cons.in). Here the consumer implements put(). With a get port, the producer would implement get() instead.
Compare put() with try_put(). When does the difference matter? L1
put() is a task and may wait until the receiver accepts. try_put() is a function: it returns at once, with 1 if the receiver took the item and 0 if not. Use the non-blocking form in code that must not wait, such as a function or a monitor sampling every clock.
Why not connect straight to the imp deep inside an agent? What does an export add? L2
An export lets the agent offer one connection point on its boundary, whatever sits inside. The outside connects to agt.in_export, and the agent connects that to the right child. If the inside changes, outside code still works. Connecting to agt.child.grandchild.imp ties every user to today's structure.
What if the outside connects to the agent's export, but nothing inside connects the export to an imp? L2
The export then resolves to no imp. Before the run starts, uvm-core reports an error: the connection count of 0 does not meet the required minimum of 1. An analysis port, by contrast, may legally have no listeners. So a forgotten inner link is caught, but a forgotten listener is not.
Follow-upPractise An Export Left Unconnected Inside →When would you use TLM-2.0 sockets instead of TLM-1 ports? L3
TLM-2.0 sends one standard generic payload with timing notes, through sockets that carry calls in both directions. It fits memory-mapped buses and connecting to SystemC models or virtual platforms that already speak it. For ordinary agent-to-scoreboard traffic, TLM-1 ports and analysis ports are simpler and enough.
Follow-upPractise When to Use TLM-2.0 Sockets →One Item, Two Subscribers6
A monitor's analysis port feeds a coverage subscriber and a scoreboard. What does the scoreboard see, and why? L1L2L3
It sees 0c, 05 and 00, so every compare fails. write() passes the same handle to each subscriber in turn, in zero time. The coverage subscriber masks the data in place. In uvm-core it runs before the scoreboard, because subscribers are called in name order. Subscribers must treat the item as read-only. One that needs to change it edits a clone().
What if someone renames cov to zcov, and the mismatches disappear? L2
The bug is still there; only the order changed. Subscribers run in name order in this library, so the scoreboard now reads the item before coverage edits it. Any later subscriber still sees corrupted data. Treat a fix that depends on names or order as a symptom, and remove the in-place edit.
Follow-upPractise A Rename That Hid the Bug →Why is the analysis write() a function and not a task? L1
A monitor must never be held up by its listeners, or it would miss the next transfer on the bus. A function cannot wait, so every subscriber finishes in zero time. A subscriber that needs time puts the item in an analysis FIFO and works on it in its own thread.
Follow-upPractise Why Analysis Write Is a Function →What if one subscriber changes the item inside its write()? L1L2
Every subscriber gets the same object, not a copy. So the change is seen by every subscriber called after it, and by the monitor too. Delivery order is not the connect order, so the effect can move when a component is renamed. Clone the item before changing it.
Follow-upPractise A Subscriber That Changes the Item →Compare an analysis port with a put port for sending monitored items. L2
An analysis port goes to any number of subscribers, including none, and never blocks. A put port goes to exactly one receiver and may block until it accepts. Monitors use analysis ports, so adding a coverage collector never changes the monitor's timing.
Follow-upPractise Analysis Port vs Put Port →At SoC level one monitor feeds thirty subscribers: scoreboards, coverage, loggers. What risks do you manage? L3
All thirty run inside the monitor's time step, so heavy work there slows every cycle. One careless subscriber that edits the item corrupts the rest. Keep write() short, move slow work into FIFOs read by separate threads, and make items read-only by rule. Loggers that print every item should be off by default.
Analysis FIFOs vs Write Functions6
A scoreboard takes inputs from one monitor and outputs from another. Compare two uvm_analysis_imp_decl write functions with two uvm_tlm_analysis_fifos. L1L2L3
With imps, write_in() and write_out() each run inside the monitor's call, in zero time. The scoreboard must store and match items inside those functions. With analysis FIFOs, writes are queued. The scoreboard pulls them with get() in its own run_phase, so it can wait for the other side. Imps suit simple immediate checks. FIFOs suit checks that wait or match in order across streams.
Why does a scoreboard with two analysis inputs need uvm_analysis_imp_decl? L1
A uvm_analysis_imp calls a method named exactly write in its parent. Two inputs would both call that one method, and the scoreboard could not tell them apart. The macro declares a new imp class, such as uvm_analysis_imp_in, that calls write_in() instead.
What if both inputs arrive in the same time step, and the scoreboard compares inside write_out()? L2
The two calls come from separate monitor processes, so their order in one time step is not fixed. If write_out() runs first, the expected item is not there yet and the compare fails falsely. Store both sides and compare when both are present. Or use analysis FIFOs and get from both in run_phase.
Compare an analysis FIFO with a plain queue that write_in() pushes into. L2
Both store items until their partner arrives. A plain queue is lighter, and it is enough when write_out() does the matching. It has no blocking read, though, so a thread that wants to wait for an item must poll. The FIFO gives a blocking get(), plus peek() and used(), so a matching thread can simply wait.
What if one FIFO still holds items when the test ends? L2
Those items never met a partner, so something is wrong: the DUT lost a response, or the test ended too early. In check_phase, report used() for each FIFO as an error, with the first leftover item printed. Then a lost response fails the test instead of vanishing.
A ten-million-transaction test slowly runs out of memory. The bench uses analysis FIFOs. Where do you look first? L3
Print used() for each analysis FIFO during the run. If one keeps growing while its partner stays empty, nothing is consuming it. Perhaps the matching thread died or waits on the wrong FIFO. An unbounded FIFO hides that for millions of items. Fail the test when used() passes the most transactions that can legally be in flight.
Your Review Queue
Saved review reminders are coming soon.