Skip to content

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:

SystemVerilog — importing the UVM library
// 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.

MisconceptionThe 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?

VersionStatusNotes
UVM 1.0ObsoleteFirst Accellera release. Do not use for new work.
UVM 1.1dLegacyStill found in older projects. Be aware of differences from 1.2.
UVM 1.2Widely deployedBecame IEEE 1800.2-2017. Most production environments use this or the 2020 revision. Recommended baseline for new projects.
IEEE 1800.2-2020Current standardAdds 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.

SystemVerilog — Minimal UVM skeleton (annotated)
// ── 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
endmodule

Notice 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…

SituationWhy UVM Helps
IP-level verification with reuse intentThe 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 testbenchUVM'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 variationsSequences, virtual sequences, and the factory override mechanism let you vary stimulus without touching the testbench structure.
Need for runtime test configurationconfig_db lets you pass parameters to any component at runtime, driven by plusargs or test code — without recompilation.
Regression at subsystem or SoC levelThe reporting infrastructure, phase-controlled drain time, and standardised end-of-test mechanisms make UVM environments predictable in regressions.

Consider alternatives when…

SituationBetter Approach
Quick sanity check on a small RTL blockA 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 simulationGLS typically uses vectors or waveforms. UVM overhead is unnecessary.
Formal verificationFormal tools work from SystemVerilog Assertions and properties, not testbenches. UVM does not apply.
Team has no UVM experience and deadline is tightUVM 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_voiduvm_objectuvm_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.

SystemVerilog — Beginner: Minimal UVM Component
// 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 time

Example 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.

SystemVerilog — Intermediate: Transaction + Sequence Generation
`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.

SystemVerilog — Verification: Scoreboard with Analysis Port
`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
endclass

Example 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.

SystemVerilog — Tricky: Factory Override Changes Behavior
`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.

SystemVerilog — Bug 1: Wrong Macro and Fix
// ❌ 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.

SystemVerilog — Bug 2: Missing super Call and Fix
// ❌ 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)
SystemVerilog — Bug 3: Component Creation Outside build_phase
// ❌ 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
endfunction

Ready-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.

SystemVerilog — Complete Runnable UVM Demo (uvm_intro_demo.sv)
// ================================================================
// 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_object is the base for data objects — transactions, sequences, configuration objects. It has no parent, no phases, and can be freely created and destroyed. uvm_component extends 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 equivalently uvm_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_TESTNAME plusarg or the argument string, (2) begin executing the standard UVM phase schedule (build → connect → start_of_simulation → run → ... → final), and (3) call $finish when 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 creates extended_txn objects 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. All type_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 calling super.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.

CategoryPracticeReason
FactoryAlways use type_id::create(), never new() for UVM objectsDirect new() bypasses factory — type overrides will silently fail
FactoryRegister overrides before super.build_phase()Build cascade executes creates — overrides must be registered first
PhasesCall super.build_phase() as the first line in build_phaseParent class may set up config_db lookups and field automation
PhasesNever create components outside build_phaseComponents created elsewhere miss phase callbacks entirely
MacrosUse uvm_component_utils` for components, uvm_object_utils` for dataWrong macro = no phases for components, no $cast issues for objects
ObjectionsRaise objection at the start of run_phase, drop only after all stimulusPremature drop causes simulation to end with unprocessed transactions
Config DBAlways check config_db::get() return value and fatal on failureSilent null handle crashes during signal drive, far from the actual bug
LoggingUse UVM_NONE verbosity for PASS/FAIL, UVM_LOW for important eventsRegression 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.

LayerWhat it isYour job
MethodologyRules: sequence→sequencer→driver, agent contains driver+monitor, tests select sequencesFollow the rules. Structure your testbench according to the patterns.
LibraryCode: uvm_component, uvm_sequence_item, uvm_driver, macros, factoryExtend the right base class. Use the right macros. Call the right factory methods.
FrameworkEngine: phase schedule, objection mechanism, config_db, TLM connectionsTrust 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.