What UVM Is — and What It Is Not
Methodology, library, and framework — where the three layers differ.
UVM Fundamentals · Module 1
The Problem That Created UVM
It is 2008. You are a verification engineer at Company A. You have spent six months building a reusable testbench for a PCIe IP block. Your driver, monitor, scoreboard, and scoreboard are polished. The IP ships. Two years later, the same IP needs to be integrated into a subsystem at Company B — a partner who already has their own testbench infrastructure. Your six months of work is useless to them. Their six months of work is useless to you.
This was the industry-wide problem in the mid-2000s. Every company, every team, sometimes every engineer, built testbenches differently. Cadence had the eRM (e Reuse Methodology). Synopsys had their own approach. ARM had AVM (Advanced Verification Methodology). Mentor had OVM (Open Verification Methodology). None of them were compatible.
A driver written for OVM could not be dropped into an AVM environment. An IP vendor could not ship a single verification component and have it work everywhere. Every integration was a rewrite.
UVM did not solve this problem by being a clever library. It solved it by being a standard agreement — a contract that says: "if you structure your testbench this way, anyone else who follows the same standard can drop their component into your environment and it will just work."
That is the single most important sentence in this module. UVM's power is not in its code. It is in its agreement.
Three Layers You Must Separate
Engineers new to UVM use the word "UVM" to mean all three of these things interchangeably. That causes real confusion. Keep them separate.
- Methodology — The rules, patterns, and conventions for how to structure a verification environment. No code required to state these. Example: "Every active component that drives stimulus must be a uvm_driver."
- Library — The actual SystemVerilog source code — classes, macros, and utilities — that implement the methodology's concepts. This is what you compile alongside your testbench. Example: the uvm_driver class definition.
- Framework — The runtime infrastructure — phase scheduling, component hierarchy construction, factory resolution, config_db lookups — that runs automatically when your simulation starts. Example: the phase engine calling build_phase() on every component, top-down, before simulation begins.
A concrete way to feel the difference: you can read the methodology in a white paper without touching any code. You can browse the library source in $UVM_HOME/src/ and read its class definitions. The framework only becomes visible when your simulation runs — it is behaviour, not text.
MethodologyRules and Conventions
How to name components, when to use sequences vs. direct stimulus, how the scoreboard connects to the monitor, when phases start and stop. Written in standards documents and training material.
LibrarySystemVerilog Class Definitions
uvm_component, uvm_object, uvm_driver, uvm_monitor, uvm_scoreboard, macros, utilities. Static code you compile once.
FrameworkRuntime Infrastructure
Phase engine, factory, config_db, TLM connection resolution, report server, root singleton. Operates automatically during simulation. You configure it; it runs itself.
The Methodology Layer
The methodology answers the question: "How should I structure my testbench?" It says: put your stimulus generator in a sequencer, let a driver pull items from it and drive the DUT interface, have a monitor observe and report, feed observations to a scoreboard that checks correctness. Group these into an agent. Put agents and the scoreboard into an environment. Write tests on top.
This structure — sequence → sequencer → driver → DUT → monitor → scoreboard — is not enforced by the compiler. It is a contract you choose to honour. The payoff is that every UVM-compliant environment looks the same to every UVM-trained engineer.
The Library Layer
The library is the concrete code that implements the methodology. It is a set of SystemVerilog classes and macros you include in every project. When you write class my_driver extends uvm_driver #(my_seq_item);, you are using the library. The class uvm_driver already implements TLM port declarations, phase callbacks, and factory registration hooks — you inherit all of that for free.
The library lives in a package. You bring it into scope with one line:
// Every UVM file starts with these two lines
`include "uvm_macros.svh" // macro definitions (`uvm_info, `uvm_component_utils, ...)
import uvm_pkg::*; // class definitions (uvm_component, uvm_driver, ...)
// That's it. The entire UVM library is now visible in this file.The Framework Layer
The framework is what happens between the end of elaboration and the first line of your run_phase(). When you call run_test("my_test") in your top-level module, you hand control to the framework. It uses the factory to instantiate the right test class, calls every component's build_phase() top-down, calls connect_phase() bottom-up, then runs all run_phase() tasks in parallel — and you never write a single line to manage any of that.
What UVM Is NOT
These misconceptions show up in interviews, in forum posts, and occasionally in production code. Know them before you write a line.
| Misconception | The Reality |
|---|---|
| "UVM is a simulator" | UVM is pure SystemVerilog. The simulator is Questa, VCS, or Xcelium. UVM runs inside those simulators, exactly like your own SV code does. |
| "UVM is a language" | UVM is written in SystemVerilog and extends it with classes and macros. It is not a separate language. If SV can't do something, neither can UVM. |
| "UVM generates tests automatically" | UVM gives you the structure to write tests. You still write every sequence, constraint, and checker yourself. UVM does not write your coverage or know what "correct" means for your DUT. |
| "UVM makes testbenches faster to simulate" | UVM adds overhead — phase management, factory lookups, reporting. The benefit is reusability and structure, not simulation speed. Large UVM environments are often slower than flat procedural testbenches at the same task. |
| "Just include uvm_pkg and you're doing UVM" | Importing the package gives you the library. Following the methodology — proper component hierarchy, phasing, factory registration, TLM connections — is what makes it UVM. You can import the package and write terrible, non-reusable code. |
| "UVM is only for big projects" | UVM has a learning curve, but the factory, config_db, and messaging infrastructure are useful even on small IP-level testbenches — especially when that IP will later be integrated into a larger design. |
IEEE 1800.2 — The Standard Behind UVM
UVM started as an Accellera working group standard in 2011. The reference implementation — a set of SystemVerilog source files — is freely available. Every major simulator vendor takes this reference implementation, validates it against their simulator, and ships it as part of their tool installation.
In 2017, UVM 1.2 was adopted as IEEE 1800.2-2017, and updated as IEEE 1800.2-2020. The standard defines not just the classes but the behaviour the classes must exhibit — so code written against the standard should behave identically on any compliant simulator.
Which Version Should You Use?
| Version | Status | Notes |
|---|---|---|
| UVM 1.0 | Obsolete | First Accellera release. Do not use for new work. |
| UVM 1.1d | Legacy | Still found in older projects. Be aware of differences from 1.2. |
| UVM 1.2 | Widely deployed | Became IEEE 1800.2-2017. Most production environments use this or the 2020 revision. Recommended baseline for new projects. |
| IEEE 1800.2-2020 | Current standard | Adds clarifications and new utilities. Support varies by simulator version — check your tool release notes. |
UVM's Key Building Blocks — A First Look
Before diving into each topic in the modules that follow, you need a map. These are the seven pillars that every UVM environment rests on. Each one gets its own module — this is just a preview so nothing feels like it came out of nowhere.
- Component Hierarchy — Every structural piece — agent, driver, monitor, scoreboard — is a uvm_component. Components form a named tree. Each knows its parent and can be found by path.
- Phasing — UVM orders the testbench lifecycle: build → connect → start_of_simulation → run → extract → check → report → final. You override the phases you care about. The framework calls them in order.
- Factory — A registry that maps type names to constructors. You register every class. You can then override any class with a derived class — at type level or instance level — without editing the original code.
- Config DB — A global hierarchical database for passing configuration — virtual interfaces, integers, objects — from the top of the hierarchy down to components that need it, without long constructor argument chains.
- TLM Ports — Transaction-Level Modelling ports let components communicate without knowing each other's types. A monitor writes to an analysis port; a scoreboard binds to it. No direct references needed.
- Sequences — Sequences generate the actual stimulus — packets, register accesses, protocol transactions. They run on a sequencer and hand items to the driver one at a time.
- Reporting — A centralised message infrastructure with severity levels, verbosity control, and action tables. One consistent log format across every component in the environment.
The diagram below shows how these blocks connect in a typical single-agent UVM environment. UVM TEST CLASSclass my_test extends uvm_testUVM ENVIRONMENTclass my_env extends uvm_envUVM AGENT (ACTIVE MODE) class my_agent extends uvm_agentSEQUENCEuvm_sequenceGenerates and sendssequence items tothe sequencerruntime objectSEQUENCERuvm_sequencerArbitrates betweensequences. Managesitem flow to driverseq_item_export (TLM)DRIVERuvm_driverDrives DUT signalsvia virtual interface.Pulls from sequencerseq_item_port (TLM)MONITORuvm_monitorPassively observes DUTsignals. Never drivesany signal on the busanalysis_port (broadcast)get_next_itemitem_doneSCOREBOARDextends uvm_scoreboardExpected TXNFrom reference modelor protocol specqueue[ $ ]Actual TXNObserved by monitorfrom DUT outputwrite() callback≟✓ UVM_INFO / ✗ UVM_ERRORuvm_report_server logs result to transcript and log fileuvm_analysis_impwrite(txn) — called on every observed transactionfans out to all connected scoreboard / subscriber componentsanalysis_port→ analysis_impVirtual Interface (vif)DUTDesign Under Test (RTL)inputs driven · outputs observeddrives signalsobservesLEGENDTLM port / export (seq-item handshake)analysis_port → analysis_impVirtual interface — driver drivesVirtual interface — monitor observes Figure 1 — Typical single-agent UVM environment: sequence → sequencer → driver → DUT, with monitor → scoreboard connected via analysis port
Your First UVM Skeleton — Every Part Labelled
Before you understand the details, read through this complete — minimal — UVM testbench skeleton. Every construct is labelled. Nothing here is accidental; every line exists for a reason that will be explained in later modules.
// ── Step 1: Pull in the UVM library ───────────────────────────────
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Step 2: A transaction (data object, not a component) ──────────
class my_seq_item extends uvm_sequence_item;
`uvm_object_utils(my_seq_item) // registers with the factory
rand bit [7:0] data;
rand bit write;
function new(string name = "my_seq_item");
super.new(name);
endfunction
endclass
// ── Step 3: A driver (structural component, drives the DUT) ───────
class my_driver extends uvm_driver #(my_seq_item);
`uvm_component_utils(my_driver) // registers with the factory
function new(string name, uvm_component parent);
super.new(name, parent); // parent sets position in hierarchy
endfunction
task run_phase(uvm_phase phase);
my_seq_item req;
forever begin
seq_item_port.get_next_item(req); // pull from sequencer
`uvm_info("DRV", $sformatf("Driving data=0x%0h", req.data), UVM_MEDIUM)
// ... drive interface signals here ...
seq_item_port.item_done(); // signal done to sequencer
end
endtask
endclass
// ── Step 4: A test (the top of the UVM hierarchy) ─────────────────
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_driver drv;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv", this); // factory creation
endfunction
endclass
// ── Step 5: The top-level module hands control to UVM ─────────────
module tb_top;
initial
run_test("my_test"); // framework takes over from here
endmoduleNotice what you did not write: no phase scheduling, no constructor call for the test, no ordering of build_phase before run_phase. The framework handles all of that. You wrote the methodology-prescribed structure. You used the library classes. The framework ran the rest.
When to Use UVM — and When Not To
UVM is not the right answer for every verification task. Knowing when to use it is as important as knowing how.
Use UVM when…
| Situation | Why UVM Helps |
|---|---|
| IP-level verification with reuse intent | The IP will be integrated into a subsystem. A UVM-compliant VIP can be dropped in by anyone following the same standard. |
| Multiple engineers on one testbench | UVM's hierarchy and phasing give everyone a shared mental model. A new engineer can navigate any UVM environment with the same map. |
| Complex stimulus with many variations | Sequences, virtual sequences, and the factory override mechanism let you vary stimulus without touching the testbench structure. |
| Need for runtime test configuration | config_db lets you pass parameters to any component at runtime, driven by plusargs or test code — without recompilation. |
| Regression at subsystem or SoC level | The reporting infrastructure, phase-controlled drain time, and standardised end-of-test mechanisms make UVM environments predictable in regressions. |
Consider alternatives when…
| Situation | Better Approach |
|---|---|
| Quick sanity check on a small RTL block | A directed flat testbench in SystemVerilog or even Verilog is faster to write and has zero overhead. Use UVM when the testbench needs to grow. |
| One-time gate-level simulation | GLS typically uses vectors or waveforms. UVM overhead is unnecessary. |
| Formal verification | Formal tools work from SystemVerilog Assertions and properties, not testbenches. UVM does not apply. |
| Team has no UVM experience and deadline is tight | UVM has a real learning curve. On a short schedule, a well-structured flat testbench delivers faster than a poorly understood UVM environment. |
What This Series Covers
This module is organised to build knowledge in dependency order. Each topic assumes the ones before it.
- 01 What UVM Is — You Are Here Methodology vs. library vs. framework. The standard. When to use UVM.
- 02 UVM Class Library Hierarchy
uvm_void→uvm_object→uvm_component. The class tree every UVM class lives in and what each level adds. - 03 UVM Package Structure and Compilation How to structure your file hierarchy, manage compilation order, and avoid circular dependency errors in multi-agent environments.
- 04 uvm_object vs uvm_component Lifecycle, hierarchy membership, and the construction rules that differ between the two root classes. The most common structural mistake explained.
- 05–07 UVM Factory — Registration, Override, and Debug How the factory works internally. Type override vs. instance override. Printing the override chain to diagnose misbehaviour.
- 08–09 uvm_config_db — Architecture and Gotchas The get/set protocol, scoping rules, phase ordering requirements, and the debugging techniques for missing or mismatched config entries.
- 10–11 UVM Messaging and Verbosity The report server, severity levels, action tables, and how to control verbosity at runtime and compile time in CI regression systems.
- 12–14 TLM 1.0, Analysis Ports, and FIFO Push and pull models, analysis broadcast architecture, and buffered communication between producers and consumers.
- 15–19 Callbacks, Field Macros, Cloning, and Resources Non-invasive extension mechanisms, automation macros vs. manual do_* methods, object cloning semantics, and the resource database alternative.
- 20–21 uvm_root, Singleton Pattern, and File Logging The UVM root singleton, its role in phase management, and configuring the report handler for file-based logging in regression systems.
Code Examples — From Hello-World to Real Verification
The fastest way to understand UVM's three layers is to watch them interact in code. These four examples build progressively — start from the simplest possible UVM class and end at a pattern you'd find in production testbenches.
Example 1 — Beginner: Your First UVM Component
Every UVM driver, monitor, and scoreboard is built on exactly this pattern. The macro, the constructor, the factory call — these three things never change.
// File: hello_uvm.sv
// Compile: vlog -sv hello_uvm.sv
// Simulate: vsim -c hello_uvm_top -do "run -all"
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── A minimal UVM component ────────────────────────────────────
class hello_comp extends uvm_component;
`uvm_component_utils(hello_comp) // registers with factory
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("HELLO", "build_phase: I exist in the hierarchy", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("HELLO", "run_phase: I am alive at simulation time", UVM_LOW)
#10ns;
phase.drop_objection(this);
endtask
endclass
// ── Minimal test ───────────────────────────────────────────────
class hello_test extends uvm_test;
`uvm_component_utils(hello_test)
hello_comp comp;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp = hello_comp::type_id::create("comp", this);
endfunction
endclass
module hello_uvm_top;
initial run_test("hello_test");
endmodule
// Expected Output:
// UVM_INFO @ 0: uvm_test_top.comp [HELLO] build_phase: I exist in the hierarchy
// UVM_INFO @ 0: uvm_test_top.comp [HELLO] run_phase: I am alive at simulation timeExample 2 — Intermediate: Transaction and Sequence
A transaction is a data object that travels between components. A sequence generates those transactions. This is the stimulus layer of every UVM testbench.
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Transaction: the unit of data that flows between components ───
class apb_txn extends uvm_sequence_item;
`uvm_object_utils_begin(apb_txn)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(write, UVM_ALL_ON)
`uvm_object_utils_end
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit write;
constraint c_addr { addr[1:0] == 2'b00; } // word-aligned
function new(string name = "apb_txn");
super.new(name);
endfunction
endclass
// ── Sequence: generates 5 transactions and sends them ─────────────
class apb_write_seq extends uvm_sequence #(apb_txn);
`uvm_object_utils(apb_write_seq)
function new(string name = "apb_write_seq");
super.new(name);
endfunction
task body();
apb_txn txn;
repeat(5) begin
txn = apb_txn::type_id::create("txn");
start_item(txn);
if(!txn.randomize() with { write == 1; })
`uvm_fatal("SEQ", "Randomization failed")
finish_item(txn);
`uvm_info("SEQ", $sformatf(
"Sent WRITE: addr=0x%0h data=0x%0h", txn.addr, txn.data),
UVM_MEDIUM)
end
endtask
endclass
// Key insight:
// apb_txn extends uvm_sequence_item → it's a DATA OBJECT (uvm_object branch)
// apb_write_seq extends uvm_sequence → it's also a DATA OBJECT
// Neither has phases. Both are created/discarded dynamically.Example 3 — Verification: Scoreboard Comparison Pattern
A scoreboard is a uvm_component that receives transactions from a monitor, compares them against predicted values, and reports pass or fail. This is the checking layer — where real verification happens.
`include "uvm_macros.svh"
import uvm_pkg::*;
class apb_scoreboard extends uvm_scoreboard;
`uvm_component_utils(apb_scoreboard)
// Analysis imp: receives transactions from monitor
uvm_analysis_imp #(apb_txn, apb_scoreboard) mon_imp;
int pass_count = 0;
int fail_count = 0;
apb_txn expected_q[$]; // queue of expected transactions
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon_imp = new("mon_imp", this);
endfunction
// Called automatically by analysis port on every observed transaction
function void write(apb_txn actual);
apb_txn expected;
if(expected_q.size() == 0) begin
`uvm_error("SCB", "Received txn but expected queue is empty!")
return;
end
expected = expected_q.pop_front();
// Compare key fields
if(actual.addr != expected.addr || actual.data != expected.data) begin
`uvm_error("SCB", $sformatf(
"MISMATCH! Got addr=%0h data=%0h, Expected addr=%0h data=%0h",
actual.addr, actual.data, expected.addr, expected.data))
fail_count++;
end else begin
`uvm_info("SCB", "Transaction MATCH ✓", UVM_HIGH)
pass_count++;
end
endfunction
function void check_phase(uvm_phase phase);
super.check_phase(phase);
`uvm_info("SCB", $sformatf(
"=== Results: PASS=%0d FAIL=%0d ===", pass_count, fail_count),
UVM_NONE)
if(fail_count > 0)
`uvm_error("SCB", "Test FAILED — check mismatches above")
endfunction
endclassExample 4 — Tricky Corner Case: Factory Override Impact
This example shows the single most powerful feature of UVM's Library layer: swapping an entire component implementation without touching a single line of the environment or test code.
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Base driver — normal operation ─────────────────────────────────
class normal_driver extends uvm_driver#(apb_txn);
`uvm_component_utils(normal_driver)
function new(string n, uvm_component p); super.new(n,p); endfunction
task run_phase(uvm_phase phase);
`uvm_info("DRV", "Normal driver running", UVM_LOW)
endtask
endclass
// ── Error-inject driver — inherits from normal ─────────────────────
class error_inject_driver extends normal_driver;
`uvm_component_utils(error_inject_driver)
function new(string n, uvm_component p); super.new(n,p); endfunction
task run_phase(uvm_phase phase);
`uvm_info("DRV", "Error-inject driver: corrupting data!", UVM_LOW)
endtask
endclass
// ── Test: override normal_driver with error_inject_driver ──────────
class error_test extends uvm_test;
`uvm_component_utils(error_test)
normal_driver drv; // declared as normal_driver
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Override: any request for normal_driver → get error_inject_driver
normal_driver::type_id::set_type_override(
error_inject_driver::get_type());
drv = normal_driver::type_id::create("drv", this);
// drv is actually an error_inject_driver — factory substituted it
`uvm_info("TEST", $sformatf("Driver type: %s", drv.get_type_name()), UVM_LOW)
endfunction
endclass
// Output: Driver type: error_inject_driver ← factory swapped it!
// The environment code never changed. Pure Methodology power.Common Bugs and Debugging — What Every Engineer Gets Wrong First
These five bugs account for roughly 80% of the "UVM isn't working" questions on every new project. They're invisible to the compiler, produce confusing runtime behaviour, and have burned every engineer who learned UVM from scratch.
// ❌ WRONG — using object macro on a component
class my_driver extends uvm_driver#(apb_txn);
`uvm_object_utils(my_driver) // ← WRONG! No phases will fire
task run_phase(uvm_phase phase);
`uvm_info("DRV", "This will NEVER print", UVM_LOW)
endtask
endclass
// ✓ CORRECT
class my_driver extends uvm_driver#(apb_txn);
`uvm_component_utils(my_driver) // ← Correct macro for components
function new(string name, uvm_component parent);
super.new(name, parent); // ← parent required for component tree
endfunction
task run_phase(uvm_phase phase);
`uvm_info("DRV", "This WILL print", UVM_LOW)
endtask
endclass⚠️ Bug 2 — Missing super.build_phase()
Symptom: config_db::get() always fails. Virtual interfaces are never received. Your driver has a null handle and crashes on the first signal assignment.
Root cause: UVM's config_db lookup mechanism and field automation happen inside the parent class's build_phase. Skip the super call and none of it executes.
// ❌ WRONG — no super.build_phase() call
function void build_phase(uvm_phase phase);
// missing: super.build_phase(phase);
drv = my_driver::type_id::create("drv", this);
// config_db lookups will silently fail from here
endfunction
// ✓ CORRECT — always call super first
function void build_phase(uvm_phase phase);
super.build_phase(phase); // ← FIRST line, always
drv = my_driver::type_id::create("drv", this);
if(!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
`uvm_fatal("CFG", "Virtual interface not found")
endfunction
// Rule: super.build_phase() → always FIRST
// super.connect_phase() → always FIRST
// super.run_phase() → context-dependent, usually FIRST
// super.check_phase() → LAST (after your checks)// ❌ WRONG — creating a component in run_phase
task run_phase(uvm_phase phase);
phase.raise_objection(this);
my_cov_collector cov = my_cov_collector::type_id::create("cov", this);
// cov exists but its run_phase will NEVER be called
// because build/connect cascade already finished
endtask
// ✓ CORRECT — all component creation in build_phase
my_cov_collector cov; // class-level declaration
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cov = my_cov_collector::type_id::create("cov", this);
// cov will receive all phase callbacks correctly
endfunctionReady-to-Run Complete Example
This is a complete, self-contained UVM testbench demonstrating all three layers — Methodology, Library, and Framework — in a single file. Copy it to a file named uvm_intro_demo.sv and run it.
// ================================================================
// uvm_intro_demo.sv — Complete UVM intro demonstration
// All three layers: Methodology + Library + Framework
//
// Compile (Questa):
// vlog -sv -L uvm_1_2 uvm_intro_demo.sv
// vsim -c -L uvm_1_2 uvm_intro_demo_top // -do "run -all; quit"
//
// Compile (VCS):
// vcs -sverilog -ntb_opts uvm-1.2 uvm_intro_demo.sv
// ./simv
//
// Compile (Xcelium):
// xrun -sv -uvm uvm_intro_demo.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── METHODOLOGY LAYER: "Transactions carry data between components"
class simple_txn extends uvm_sequence_item;
`uvm_object_utils(simple_txn)
rand bit[7:0] data;
rand bit[3:0] id;
function new(string name = "simple_txn");
super.new(name);
endfunction
function string convert2string();
return $sformatf("[id=%0d data=0x%02h]", id, data);
endfunction
endclass
// ── METHODOLOGY: "Sequences generate stimulus"
class count_seq extends uvm_sequence#(simple_txn);
`uvm_object_utils(count_seq)
int num_txns = 4;
function new(string name = "count_seq");
super.new(name);
endfunction
task body();
simple_txn txn;
for(int i = 0; i < num_txns; i++) begin
txn = simple_txn::type_id::create($sformatf("txn_%0d", i));
start_item(txn);
txn.id = i;
txn.data = i * 16;
finish_item(txn);
end
endtask
endclass
// ── LIBRARY LAYER: uvm_driver handles TLM port management
class demo_driver extends uvm_driver#(simple_txn);
`uvm_component_utils(demo_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
simple_txn txn;
forever begin
seq_item_port.get_next_item(txn);
`uvm_info("DRV", $sformatf("Driving %s", txn.convert2string()), UVM_LOW)
#5ns; // simulate driving time
seq_item_port.item_done();
end
endtask
endclass
// ── METHODOLOGY: Agent groups driver + sequencer
class demo_agent extends uvm_agent;
`uvm_component_utils(demo_agent)
demo_driver drv;
uvm_sequencer#(simple_txn) seqr;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = demo_driver::type_id::create("drv", this);
seqr = uvm_sequencer#(simple_txn)::type_id::create("seqr", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
drv.seq_item_port.connect(seqr.seq_item_export);
endfunction
endclass
// ── METHODOLOGY: Environment holds agents + checkers
class demo_env extends uvm_env;
`uvm_component_utils(demo_env)
demo_agent agent;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agent = demo_agent::type_id::create("agent", this);
endfunction
endclass
// ── FRAMEWORK: Test selects stimulus and drives simulation
class demo_test extends uvm_test;
`uvm_component_utils(demo_test)
demo_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = demo_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
count_seq seq;
phase.raise_objection(this);
seq = count_seq::type_id::create("seq");
seq.num_txns = 4;
seq.start(env.agent.seqr);
phase.drop_objection(this);
endtask
endclass
// ── Top module — FRAMEWORK takes control from here
module uvm_intro_demo_top;
initial run_test("demo_test"); // Framework: factory creates demo_test
endmodule
// ================================================================
// Expected Output:
// UVM_INFO @ 0ns: [DRV] Driving [id=0 data=0x00]
// UVM_INFO @ 5ns: [DRV] Driving [id=1 data=0x10]
// UVM_INFO @ 10ns: [DRV] Driving [id=2 data=0x20]
// UVM_INFO @ 15ns: [DRV] Driving [id=3 data=0x30]
// UVM_INFO: UVM_ERROR: 0 UVM_FATAL: 0 → PASS
// ================================================================Interview Questions — From Fresher to Senior
These are real questions asked in SoC verification interviews at semiconductor companies. The beginner questions test whether you understand UVM conceptually. The tricky ones test whether you've actually debugged UVM on a real project.
Beginner Level
- Q1 What is the difference between uvm_object and uvm_component? Answer:
uvm_objectis the base for data objects — transactions, sequences, configuration objects. It has no parent, no phases, and can be freely created and destroyed.uvm_componentextends uvm_object and adds: a fixed position in the component hierarchy (it has a parent), phase callbacks (build, connect, run, etc.), and persistence throughout simulation. The rule: if it moves data, it extends uvm_object. If it processes data and lives in the testbench topology, it extends uvm_component. - Q2 What does run_test() actually do? Answer:
run_test()(or equivalentlyuvm_root::get().run_test()) instructs the UVM framework to: (1) use the factory to create an instance of the test class specified by the+UVM_TESTNAMEplusarg or the argument string, (2) begin executing the standard UVM phase schedule (build → connect → start_of_simulation → run → ... → final), and (3) call$finishwhen all phases complete. It's the single line that hands control from your SystemVerilog module to the UVM framework.
Intermediate Level
- Q3 Why does UVM use a factory? What problem does it solve? Answer: The factory allows type substitution without modifying the original code. In a shared VIP, the driver creates transactions using
my_txn::type_id::create(). A test can override that type:my_txn::type_id::set_type_override(extended_txn::get_type()). Now the driver createsextended_txnobjects transparently. Without the factory, you'd need to modify the VIP source — which breaks the Methodology's reuse principle. Factory = runtime polymorphism at the object creation level. - Q4 Explain the role of raise_objection() and drop_objection() in run_phase. Answer: UVM's Framework layer monitors objection counts to decide when run_phase is complete.
raise_objection(this)increments the counter — "I'm not done yet, don't end simulation."drop_objection(this)decrements it. When the count reaches zero, run_phase ends for all components and the framework advances to extract_phase. A common bug: forgetting to raise_objection causes simulation to exit at time 0 before any stimulus is driven.
Senior / Tricky Level
- Q5 If you call set_type_override() AFTER super.build_phase() in your test, the override doesn't work. Why? Answer:
super.build_phase()triggers the environment's build_phase, which triggers the agent's build_phase, and so on — the entire hierarchy builds recursively. Alltype_id::create()calls happen inside this cascade. If you register the override after this cascade completes, the factory has already served up all the objects with the original type. Override registrations must happen before the creation cascade — i.e., before callingsuper.build_phase(). - Q6 Is UVM synthesizable? Can you use UVM classes in RTL? Answer: No. UVM is entirely non-synthesizable. The Library is built on SystemVerilog classes, dynamic memory allocation, virtual methods, and string operations — none of which map to hardware. UVM lives exclusively in the verification domain. Using any UVM construct in RTL code will either be rejected by the synthesis tool or silently ignored, potentially masking design bugs. Keep all UVM files strictly in verification directories and never include them in synthesis file lists.
Best Practices — What Production Teams Actually Follow
These aren't academic guidelines. They're the rules that teams enforce in code review on real SoC projects because violating them has caused real bugs.
| Category | Practice | Reason |
|---|---|---|
| Factory | Always use type_id::create(), never new() for UVM objects | Direct new() bypasses factory — type overrides will silently fail |
| Factory | Register overrides before super.build_phase() | Build cascade executes creates — overrides must be registered first |
| Phases | Call super.build_phase() as the first line in build_phase | Parent class may set up config_db lookups and field automation |
| Phases | Never create components outside build_phase | Components created elsewhere miss phase callbacks entirely |
| Macros | Use uvm_component_utils` for components, uvm_object_utils` for data | Wrong macro = no phases for components, no $cast issues for objects |
| Objections | Raise objection at the start of run_phase, drop only after all stimulus | Premature drop causes simulation to end with unprocessed transactions |
| Config DB | Always check config_db::get() return value and fatal on failure | Silent null handle crashes during signal drive, far from the actual bug |
| Logging | Use UVM_NONE verbosity for PASS/FAIL, UVM_LOW for important events | Regression logs should show critical information without noise |
Summary — What You Should Walk Away With
UVM is not just a library of classes. It is an industry agreement about how verification environments should be structured, and a framework that enforces that agreement through phasing, factory substitution, and TLM communication. The three layers — Methodology, Library, Framework — are not just abstract concepts. They determine where your code lives, why it works the way it does, and what you can and cannot change without breaking reuse.
| Layer | What it is | Your job |
|---|---|---|
| Methodology | Rules: sequence→sequencer→driver, agent contains driver+monitor, tests select sequences | Follow the rules. Structure your testbench according to the patterns. |
| Library | Code: uvm_component, uvm_sequence_item, uvm_driver, macros, factory | Extend the right base class. Use the right macros. Call the right factory methods. |
| Framework | Engine: phase schedule, objection mechanism, config_db, TLM connections | Trust it. Override it in specific places (phases, callbacks). Don't fight it. |
The reason UVM became an IEEE standard and the reason every major semiconductor company uses it comes down to one thing: shared VIPs. When a company's PCIe team builds an agent, the DDR team can drop it into their environment unchanged. When an IP vendor ships a MIPI VIP, every customer can integrate it without modifying source files. That only works when everyone follows the same Methodology, using the same Library, trusting the same Framework. You're now part of that agreement.