Getting Started with UVM
The single most common misconception for someone new to UVM: expecting to "install" it like a standalone tool. There's no UVM application, no package manager, nothing to launch. UVM is a SystemVerilog class library — a directory of .sv files defining classes like uvm_component and uvm_driver — that gets compiled directly alongside your own testbench code. If you can already compile a SystemVerilog project, you already have almost everything you need.
Where the library actually comes from
- It's usually already on your machine. Every major commercial simulator (Questa, VCS, Xcelium) ships a copy of the UVM source under its own install tree, since the standard requires it. Check there first before downloading anything separately.
- Accellera — the standards body that maintains UVM — also publishes the reference implementation directly (
uvm-core, on GitHub), for cases where a project needs a specific version pinned independently of whatever a given simulator bundles. - The library implements IEEE 1800.2, the standard that formalized UVM (see Introduction to UVM for how it got there). Different simulators may bundle slightly different point releases, which is a real, if usually minor, source of cross-project compatibility friction — worth knowing about even if it rarely bites.
- For quick experiments with no local setup at all, browser-based tools like EDA Playground have UVM preloaded against multiple simulators.
The compile-time mechanics
Getting the library into your build comes down to two things: telling the compiler where the source lives, and importing the package.
# Typical simulator invocation — exact flags vary by tool
vlog -incdir $UVM_HOME/src $UVM_HOME/src/uvm_pkg.sv my_testbench.sv
$UVM_HOME— an environment variable pointing at the UVM installation root. This is a convention, not something UVM enforces itself, but nearly every project and simulator's own examples assume it exists.-incdir— makes`includedirectives inside the UVM source resolvable.uvm_pkg.sv— the actual package file being compiled; everything else is pulled in through its own includes.- In your own source, this shows up as two lines that need to appear together at the top of any file using UVM:
import uvm_pkg::*;
`include "uvm_macros.svh"
$UVM_HOME set in one terminal session vanishes the moment that shell closes, unless it's persisted in a shell profile (.bashrc, .cshrc, or your CI environment's own config). A build that works today and mysteriously "can't find UVM" tomorrow is very often just a lost environment variable, not a real compile problem. On a team, mixing UVM versions across engineers' environments is also a real source of subtle, hard-to-reproduce bugs — worth standardizing early.
A minimal UVM testbench, with no DUT at all
Before wiring up a real interface, it's worth seeing the smallest possible thing UVM will actually run — deliberately with no DUT, no interface, no stimulus. The goal here is purely to see the class-based mechanics move, so they're not new and unfamiliar later when real signals are added.
class hello_env extends uvm_env;
`uvm_component_utils(hello_env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
class hello_test extends uvm_test;
`uvm_component_utils(hello_test)
hello_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 = hello_env::type_id::create("env", this);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_top.print_topology();
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("HELLO", "Hello, UVM!", UVM_LOW)
phase.drop_objection(this);
endtask
endclass
module tb_top;
initial run_test("hello_test");
endmodule
A few things worth noticing here specifically because they'll recur constantly from this point on:
envis built viatype_id::create(), nevernew(). This one habit, formed now, is what makes the factory (covered later in this section) work at all later — see Utility & Field Macros for why.uvm_top.print_topology()inend_of_elaboration_phasedumps the entire component tree UVM actually built, as text, before simulation runs. On a real testbench with a dozen components, this is one of the fastest ways to confirm the hierarchy is what you think it is — it's worth reaching for immediately whenever a connection seems to be silently not working.- The objection around
`uvm_infomatters more than it looks — withoutraise_objection/drop_objection,run_phasehas no reason to stay open at all, so nothing here would print reliably. This is covered in full in Phases & Objections.
Running it produces one line of output and a printed tree with exactly two components (uvm_test_top and its env child) — small enough to read at a glance, which is the point. Everything from here builds on top of this same skeleton: a test that builds an environment and starts something in run_phase.
Selecting the test and verbosity without recompiling
initial run_test("hello_test"); hardcodes which test runs — fine for this one-test example, but a real testbench usually has many tests sharing one compiled image. Two command-line plusargs make that practical:
+UVM_TESTNAME=<name>— passed at simulation time (not compile time), this tellsrun_test()which factory-registered test to construct, overriding (or replacing entirely) a hardcoded argument. Writinginitial run_test();with no argument at all and always selecting the test via+UVM_TESTNAMEon the command line is the more common pattern once a project has more than a couple of tests — it means switching tests never requires recompiling.+UVM_VERBOSITY=<level>— sets the initial verbosity (UVM_NONE,UVM_LOW,UVM_MEDIUM,UVM_HIGH,UVM_FULL) for every component at simulation start, without editing any`uvm_infocall. This is what actually decides whether`uvm_info("HELLO", "Hello, UVM!", UVM_LOW)above prints at all — its message only shows if the active verbosity isUVM_LOWor higher.
Both are read directly off the simulator's command line at elaboration time, before run_phase starts — no testbench code has to parse them explicitly.