UVM
End-to-End Verification Flow
A complete UVM run from run_test through build, connect, and the run phase to objection-controlled end-of-test and the final PASS/FAIL.
Introduction to UVM · Module 2 · Page 2.9
The Engineering Problem
You have met every piece of UVM separately — the architecture, the five mental-model ideas, the cast of base classes. But you have never watched them run together, from start to finish. And a verification engineer must be able to narrate the whole journey: from typing "run the simulation" to reading "PASS" — what exactly happens, in what order, and what makes it stop?
This is not optional knowledge. Almost every "my UVM testbench does nothing" bug is a misunderstanding of the end-to-end flow: the test isn't selected, the tree isn't built, the sequence never starts, or — most commonly — the run ends at time zero because nothing held it open. You cannot debug a flow you cannot narrate. So this capstone walks one complete, minimal UVM run end to end, tying Module 2 together into a single moving picture.
What is the complete end-to-end flow of a UVM simulation — from
run_test()through building, connecting, running stimulus, checking, and measuring, to the objection-controlled end-of-test and the final PASS/FAIL?
Motivation — why you must be able to narrate the whole run
The pieces only become useful as a running whole, and the whole is what you actually debug:
- The flow is the debug map. When a testbench "does nothing," the cause is always a specific point in the flow — test not selected, tree not built, sequence not started, objection not raised. Knowing the ordered flow turns a mystery into a checklist.
- It is the bridge from reading to writing. You've read components in isolation; running one end to end is what lets you assemble your own. The minimal complete testbench in this chapter is the template every real environment elaborates.
- It makes the abstract concrete. "Build-then-connect-then-run" and "sequences start on sequencers" stop being slogans the moment you see them as ordered events in one simulation with one entry point and one exit condition.
- End-of-test is a real mechanism, not magic. A UVM run doesn't stop when stimulus "feels done" — it stops when all objections drop. Understanding that is the difference between a test that runs and one that silently ends at time zero.
The motivation, in one line: you cannot verify a chip with pieces you can only describe individually — you must be able to run them as one flow, narrate that flow, and therefore debug and build it.
Mental Model
Hold the run as a play in two acts and a curtain:
Act 1 — Setup (zero time). The simulator compiles and elaborates;
run_test()is the single entry point, which selects the test by name, then the framework drives the construction phases: the test builds the component tree top-down (build_phase) and connects it (connect_phase). No simulation time has passed — the cast exists and is wired, but nothing has happened yet. Act 2 — Performance (consumes time). Therun_phaseexecutes. The test raises an objection (the "keep the curtain up" signal), starts a sequence on the sequencer, and the assembly line runs: sequence → sequencer → driver → DUT pins → monitor → scoreboard and coverage, transaction after transaction. Simulation time advances. The curtain — End and report. When stimulus is done, the test drops its objection; with no objections left, the run phase ends, and the report phase tallies PASS/FAIL.
Two acts and a curtain, with the objection as the rule for when the curtain falls. Every UVM run, however large, is this same play.
Visual Explanation — the end-to-end flow
The whole run is a fixed sequence of stages. Drawing it once gives you the debug map: any "nothing happened" symptom is a failure at a specific, locatable stage.
Read it as a debug map. "Wrong test ran" → stage 1 (the +UVM_TESTNAME selection). "A component is null" → stage 2 (it wasn't built). "A connection is missing" → stage 3. "Nothing was driven / sim ended instantly" → stage 4 (the objection or the sequence). "No verdict" → stage 5. Every failure mode lives at a stage, and the flow is the map that locates it. The two zero-time stages (build, connect) assemble the cast; the run phase is where the verification actually happens.
RTL / Simulation Perspective — a complete minimal run
Here is an entire, minimal UVM run: the top module that is the simulation's entry point, and the test that builds the environment and drives stimulus. Everything from Module 2 appears, assembled.
// ── TOP MODULE: the simulation entry point (a real Verilog module, not UVM) ──
module top;
bit clk; always #5 clk = ~clk;
my_if vif(clk); // the interface to the DUT
dut u_dut(vif); // the DUT
initial begin
uvm_config_db#(virtual my_if)::set(null, "*", "vif", vif); // configure from the top
run_test(); // UVM entry: builds + runs +UVM_TESTNAME
end
endmodule
// ── THE TEST: configures the env, then drives stimulus in the run phase ──
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_env env;
function void build_phase(uvm_phase phase);
env = my_env::type_id::create("env", this); // build the tree top-down (zero time)
endfunction
task run_phase(uvm_phase phase);
my_seq seq = my_seq::type_id::create("seq"); // a sequence is an OBJECT...
phase.raise_objection(this); // keep the run alive while stimulus runs
seq.start(env.agent.sqr); // ...STARTED on the sequencer (a component)
phase.drop_objection(this); // stimulus done → the run may end
endtask
endclassThis is the whole flow in two blocks. The top module is plain Verilog — it instantiates the DUT and interface, sets the interface into the config database (configure from the top), and calls run_test(), the single UVM entry point that selects and runs the test named by +UVM_TESTNAME. The test builds the environment in build_phase (zero-time construction), then in run_phase does the three things that are the performance: raise_objection (keep the run alive), seq.start(...) (start the transient sequence object on the persistent sequencer), and drop_objection (stimulus done, the run may end). Notice every Module 2 idea is here — components vs objects, build-then-run, configure-from-the-top, sequence-started-on-a-sequencer — operating as one flow.
Verification Perspective — the cast collaborating during the run
Once the run phase begins, the components you met as a static cast spring into a coordinated collaboration. The test orchestrates; the agent's workers move each transaction along the assembly line; the checkers judge and measure.
This is Module 1's methodology loop and Module 2's architecture, fused and running: the test/sequence/sequencer/driver path is stimulus, the scoreboard is checking, coverage is measurement, and the monitor's broadcast is what feeds the last two without coupling them to the first. The test sits above it all, orchestrating — it picks the sequence and, crucially, holds the objection that keeps the whole performance alive. Drop that objection too early and the curtain falls mid-scene; never raise it and the curtain never rises. The collaboration is real-time and continuous, one transaction after another, until the stimulus is exhausted.
Runtime / Execution Flow — the run phase and the objection that governs it
Zoom into the run phase itself and the loop becomes explicit: raise the objection, start the sequence, process items until the sequence is done, then drop the objection — which is the only thing that lets the run end.
The objection is the heart of end-of-test, and it is explicit and programmable, not automatic. A UVM run does not end because the simulator decides stimulus looks finished; it ends because every raised objection has been dropped. The test raises one before starting stimulus and drops it after — so the run lasts exactly as long as there is work objecting to its end. This is the single most important runtime fact in UVM, and getting it wrong produces the most common beginner symptom of all: a simulation that ends at time zero because nothing ever objected. The per-item loop (the back-edge) runs the assembly line; the objection brackets it.
Waveform Perspective — the run on a timeline
Put the whole flow on a time axis and the two acts and the curtain are visible: zero-time setup, then the run phase with the objection held while transactions flow, then the objection dropping and the simulation ending.
A UVM run on a timeline — setup (zero time), run with objection held, then end
12 cyclesThe objection track is the whole story of when. While it is high, the run phase stays alive and transactions flow on the pins; the moment it drops (stimulus complete), the run has no reason to continue and ends, handing off to reporting. Notice the build/connect region carries no pin activity — it is zero-time elaboration, the cast being assembled before the performance. The entire run is bracketed by the objection: raised to begin the performance, dropped to bring down the curtain. Master this one signal's behaviour and you understand UVM end-of-test.
DebugLab — the simulation that ended at time zero
run_phase had no objection — so the curtain fell before the play began
A newly-assembled UVM testbench compiled, elaborated, selected the right test, and built its tree correctly — and then the simulation finished at time zero. Nothing was driven, the DUT never moved, the scoreboard saw no transactions, and the report said PASS (of nothing). Every component existed and was connected; the run simply never ran.
The run_phase raised no objection, so UVM ended it immediately. The run phase stays alive only while at least one objection is held; with none raised, there was nothing telling UVM the run had work to do, so it ended the phase before the sequence could drive anything:
run_phase: seq.start(env.agent.sqr); // sequence started...
(no phase.raise_objection) // ...but NOTHING objected to ending the run
UVM: no objections raised → run_phase ends immediately (time 0)
result: seq.start never gets to drive; DUT idle; "PASS" of zero transactions
fix: phase.raise_objection(this); before start, drop_objection(this) afterA UVM run does not last until stimulus "looks done" — it lasts until all objections drop. With none ever raised, the end condition was satisfied instantly, so the performance was over before it began.
The tell is a clean build that finishes at time zero with no activity. Diagnose end-of-test problems at the objection:
- Check for
raise_objectionin the run-time path. If the test (or sequence) starts stimulus without raising an objection, the run phase ends immediately. The symptom — instant finish, zero transactions — is unmistakable once you know it. - Confirm raise precedes start and drop follows it. The order must be raise → start (and run) → drop. Dropping before stimulus finishes ends the run early; never raising ends it at time zero.
- Distinguish "didn't run" from "didn't drive." Time-zero finish means the phase didn't stay alive (objection problem); a run that lasts but drives nothing is a different bug (sequence/connection). The simulation's end time tells you which.
End-of-test is explicit — make the objection bracket your stimulus:
- Raise before stimulus, drop after. Bracket every run-phase stimulus with
raise_objectionbefore anddrop_objectionafter, so the run lives exactly as long as there is work to do. - Understand the rule: the run ends when all objections drop. It is not time- or activity-based; it is objection-based. Internalising this prevents both the time-zero finish (never raised) and the premature end (dropped too soon).
- Let the entity that owns the stimulus manage the objection. Whoever starts the sequence (the test, or the sequence itself) is responsible for raising and dropping — so the lifetime of the objection matches the lifetime of the work.
The one-sentence lesson: a UVM run lasts exactly as long as an objection is held — raise one before your stimulus and drop it after, because with no objection the run ends at time zero and "PASS" means you verified nothing.
Common Mistakes
- No objection in
run_phase. Without a raised objection, the run ends at time zero — clean build, instant finish, zero transactions, hollow PASS. Bracket stimulus withraise_objection/drop_objection. - Dropping the objection too early. Dropping before the sequence finishes ends the run mid-stimulus. The objection's lifetime must match the work's lifetime.
- Expecting a sequence built in
build_phaseto run. Sequences are objects, started on a sequencer inrun_phase, not components built into the tree. (The ecosystem chapter's bug, seen again in the full flow.) - Forgetting
run_test()or+UVM_TESTNAME. The top module must callrun_test(), and the right test must be selected by name. Miss either and the wrong test (or no test) runs. - Not configuring from the top. If the virtual interface (or other config) isn't set into the config database before
run_test(), components fail to find it inbuild_phase— a setup-stage failure, not a run-phase one. - Misreading the end time. A time-zero finish is an objection problem; a long run with no activity is a sequence/connection problem. The simulation's end time tells you which stage to inspect.
Senior Design Review Notes
Interview Insights
The top module — plain Verilog — instantiates the DUT and interface, sets the interface into the config database, and calls run_test(), the single UVM entry point, which selects the test named by +UVM_TESTNAME. The framework then drives the phases: in build_phase the test builds the environment, which builds its agents and scoreboard, which build their children — the whole tree, top-down, in zero simulation time. In connect_phase the ports and exports are wired (driver to sequencer, monitor to scoreboard and coverage), still at zero time. Then run_phase executes and consumes time: the test raises an objection to keep the run alive, starts a sequence on the agent's sequencer, and transactions flow — sequencer to driver to DUT pins, monitor observing and broadcasting to the scoreboard (which checks) and coverage (which measures). When stimulus is done the test drops the objection; with no objections remaining, the run phase ends and report_phase tallies and prints the final PASS/FAIL.
Exercises
- Narrate it. Without looking, list the ordered stages of a UVM run from
run_test()to PASS/FAIL, marking which are zero-time and which consume time, and which stage the objection governs. - Map symptom to stage. For each, name the flow stage at fault: (a) the wrong test executed; (b) a component handle is null in
connect_phase; (c) the simulation ends at time zero; (d) the run lasts but drives nothing; (e) no PASS/FAIL is printed. - Bracket the stimulus. Given a
run_phasethat creates and starts a sequence but finishes at time zero, write (in prose) the two statements you add and exactly where, and explain the rule that makes them necessary. - Locate Module 2. In the minimal testbench of this chapter, point to one concrete instance each of: a component, an object, configure-from-the-top, build-then-run, and a sequence started on a sequencer.
Summary
- A UVM run is one fixed end-to-end flow: compile →
run_test()selects the test →build_phaseconstructs the tree top-down (zero time) →connect_phasewires it (zero time) →run_phaseexecutes (consumes time) →report_phasegives PASS/FAIL. - The flow is a debug map: each "nothing happened" symptom lives at a specific stage — wrong test (selection), null component (build), missing wire (connect), instant finish (objection), no drive (sequence) — so narrating the flow locates the bug.
- The run phase is the performance: the test raises an objection, starts a sequence on the sequencer, and the cast collaborates — sequencer → driver → DUT → monitor → scoreboard + coverage — one transaction at a time, until stimulus is done.
- Objections govern end-of-test: the run lasts exactly as long as an objection is held. Raise none and the run ends at time zero (a hollow PASS); the most common beginner bug, and the proof that end-of-test is explicit, not automatic.
- The durable rule of thumb: every UVM run is the same two acts and a curtain — zero-time build-and-connect, then a run phase bracketed by an objection — so narrate that flow, and you can build it, debug it, and locate any failure at its stage.
Next — Verification Environment Overview: Module 2 gave you UVM's history, architecture, mental model, cast, and the end-to-end flow. Module 3 begins building for real — a deeper, hands-on look at the UVM testbench architecture and how to construct each layer of the environment you've now seen run end to end.