Advanced UVM
Virtual sequences, the register model, objections, callbacks, and running on three simulators.
Coordinating multiple agents
A regular sequence runs on one sequencer, driving one agent. A virtual sequence runs on a virtual sequencer and coordinates multiple real sequencers — essential when your test needs synchronized stimulus across interfaces (e.g., configure via APB, then traffic via AXI).
class v_sequencer extends uvm_sequencer; axi_sequencer axi_seqr; apb_sequencer apb_seqr; `uvm_component_utils(v_sequencer) endclass class config_then_traffic_seq extends uvm_sequence; `uvm_object_utils(config_then_traffic_seq) `uvm_declare_p_sequencer(v_sequencer) task body(); apb_config_seq cfg = apb_config_seq::type_id::create("cfg"); axi_traffic_seq tr = axi_traffic_seq::type_id::create("tr"); cfg.start(p_sequencer.apb_seqr); // configure first tr.start(p_sequencer.axi_seqr); // then traffic endtask endclass
uvm_reg, adapter, predictor
The UVM register model mirrors the DUT's register map in SystemVerilog. You get typed field access (regmodel.status.error.read()) instead of manual bus transactions, plus shadow registers, mirror/desired value tracking, and built-in access tests.
uvm_reg_field— a bit range inside a register.uvm_reg— a single addressable register.uvm_reg_block— a collection of registers and sub-blocks.uvm_reg_map— maps registers to an address space.uvm_reg_adapter— translates register operations to bus transactions.uvm_reg_predictor— updates mirrored values from observed bus traffic.
function void connect_phase(uvm_phase phase); adapter = axi_reg_adapter::type_id::create("adapter"); predictor = uvm_reg_predictor#(axi_txn)::type_id::create("pred", this); regmodel.default_map.set_sequencer(agent.seqr, adapter); predictor.map = regmodel.default_map; predictor.adapter = adapter; agent.mon.ap.connect(predictor.bus_in); endfunction
When does run_phase finish?
UVM's run_phase ends when no component has an active objection. A test raises an objection when it has work pending and drops it when that work is done. The simulator doesn't exit until the objection count is zero.
class smoke_test extends base_test; `uvm_component_utils(smoke_test) task run_phase(uvm_phase phase); axi_basic_seq seq; phase.raise_objection(this); // hold the phase open seq = axi_basic_seq::type_id::create("seq"); seq.start(env.agent.seqr); phase.phase_done.set_drain_time(this, 100); // let residuals flush phase.drop_objection(this); endtask endclass
set_drain_time asks UVM to wait a bit longer before actually ending the phase — long enough for the last expected response to arrive.Injecting behavior without subclassing
UVM callbacks let you hook into a component's behavior from outside. Register a callback class, and the target calls it at well-defined points — without the target having to know about the callback's existence.
Common use: error injection. A test registers a callback that corrupts 5% of outgoing transactions, without rewriting the driver.
class axi_driver_cb extends uvm_callback; virtual function void pre_drive(axi_txn t); endfunction endclass class corrupt_cb extends axi_driver_cb; function void pre_drive(axi_txn t); if ($urandom_range(0,99) < 5) t.data ^= 32'h1; endfunction endclass // In driver.run_phase: `uvm_do_callbacks(axi_driver, axi_driver_cb, pre_drive(req))
VCS, Xcelium, Questa
UVM code is simulator-agnostic, but each tool has its own command-line conventions.
# Synopsys VCS vcs -sverilog -ntb_opts uvm-1.2 -full64 \ -timescale=1ns/1ps +define+UVM_NO_DPI \ -l compile.log tb_top.sv ./simv +UVM_TESTNAME=smoke_test -l run.log # Cadence Xcelium xrun -sv -uvm -timescale 1ns/1ps \ tb_top.sv +UVM_TESTNAME=smoke_test -l xrun.log # Siemens Questa vlog -sv +incdir+$UVM_HOME/src $UVM_HOME/src/uvm_pkg.sv tb_top.sv vsim -c -do "run -all; quit" tb_top \ +UVM_TESTNAME=smoke_test -l questa.log
Control verbosity with +UVM_VERBOSITY=UVM_MEDIUM. Dial individual components up/down with +uvm_set_verbosity=env.agent.drv,_ALL_,UVM_HIGH,time.