The UVM Phasing Mechanism
Learn how UVM phases structure the lifecycle of a testbench, from construction to execution and cleanup.
In a standard SystemC simulation, you have elaboration (construction) and simulation (sc_start()). The Universal Verification Methodology (UVM) imposes a much more rigorous, standardized execution schedule on top of SystemC called the Phasing Mechanism.
Phases ensure that all components in the verification environment instantiate, connect, run, and shut down in a predictable, synchronized manner.
The Three Categories of UVM Phases
Phases in UVM are executed sequentially. Every component in the hierarchy must complete a phase before the entire environment transitions to the next phase. The phases are divided into three main categories: Pre-run, Run-time, and Post-run.
1. Pre-run Phases (Zero Time)
Pre-run phases are used for structural setup. They execute in zero simulation time. In UVM-SystemC, these map conceptually to SystemC's elaboration steps.
build_phase(Top-down): This is where you instantiate your sub-components and retrieve configuration settings from theuvm_config_db. Because it executes top-down, parents can set configurations before their children are built.connect_phase(Bottom-up): Once all components are built, TLM ports and exports are bound together here.end_of_elaboration_phase(Bottom-up): Final structural adjustments and topology checks.start_of_simulation_phase(Bottom-up): Pre-run activities like printing banners, dumping the testbench topology, or initializing debug files.
2. Run-time Phases (Consumes Time)
Run-time phases are where the actual simulation stimulus and protocol execution happen.
run_phase: This is the primary workhorse. Unlike the pre-run phases,run_phaseis spawned as a concurrent thread process (using SystemC'ssc_spawnunder the hood). Every component'srun_phaseexecutes concurrently.
UVM also defines parallel sub-phases within the run-time domain (such as reset_phase, configure_phase, main_phase, and shutdown_phase), but run_phase is the most commonly used for general component logic.
3. Post-run Phases (Zero Time)
Once the run-time phases are explicitly terminated, the simulation moves into cleanup and checking.
extract_phase(Bottom-up): Retrieve final data from coverage collectors and scoreboards.check_phase(Bottom-up): Validate the extracted data to determine if the test passed or failed.report_phase(Bottom-up): Print the final results (e.g., "TEST PASSED" or coverage percentages).final_phase(Top-down): Final teardown, like closing open file handles.
Implementing a Phase
To participate in a phase, a component simply overrides the virtual method for that phase. Below is a complete, fully compilable example demonstrating all three categories of UVM phases.
#include <systemc>
#include <uvm>
class my_transaction : public uvm::uvm_transaction {
public:
UVM_OBJECT_UTILS(my_transaction);
my_transaction(const std::string& name = "my_transaction") : uvm::uvm_transaction(name) {}
};
class my_monitor : public uvm::uvm_monitor {
public:
UVM_COMPONENT_UTILS(my_monitor);
uvm::uvm_analysis_port<my_transaction> ap;
my_monitor(uvm::uvm_component_name name) : uvm::uvm_monitor(name), ap("ap") {}
// Pre-run: Construction
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_monitor::build_phase(phase);
UVM_INFO("MON", "Building monitor...", uvm::UVM_LOW);
}
// Run-time: Execution (consumes time)
void run_phase(uvm::uvm_phase& phase) override {
// Objections control when the simulation finishes
phase.raise_objection(this);
for(int i = 0; i < 3; i++) {
// Wait for simulated time to pass
sc_core::wait(10, sc_core::SC_NS);
UVM_INFO("MON", "Sampling bus...", uvm::UVM_LOW);
// Broadcast dummy transaction
my_transaction tx;
ap.write(tx);
}
phase.drop_objection(this);
}
// Post-run: Cleanup
void report_phase(uvm::uvm_phase& phase) override {
UVM_INFO("MON", "Simulation finished successfully.", uvm::UVM_LOW);
}
};
class my_test : public uvm::uvm_test {
public:
my_monitor* mon;
UVM_COMPONENT_UTILS(my_test);
my_test(uvm::uvm_component_name name) : uvm::uvm_test(name) {}
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_test::build_phase(phase);
mon = my_monitor::type_id::create("mon", this);
}
};
int sc_main(int argc, char* argv[]) {
uvm::run_test("my_test");
return 0;
}Controlling the Run Phase: Objections
Because run_phase executes concurrently across many components, UVM needs a way to know when the test is "done." If it waited for all run_phase threads to exit, simulations might run forever (due to infinite while(true) loops in monitors and drivers).
UVM solves this with Objections.
raise_objection(): "I am busy executing the test, do not end the simulation."drop_objection(): "I am done with my part of the test."
The run_phase (and the entire simulation) ends when all raised objections have been dropped. This logic is typically handled in the uvm_test or inside a uvm_sequence.
By standardizing when things happen (phases) and how we agree to finish (objections), UVM creates highly deterministic and predictable testbenches out of independent, modular components.
Comments and Corrections