UVM-SystemC Bridge: Components, Phasing, and Configuration
uvm_component hierarchy, build/connect/run behavior, configuration/resource patterns, and test structure.
How to Read This Lesson
UVM-SystemC is methodology in C++ clothing. Keep the verification intent in view: reusable components, controlled stimulus, reporting, and phase-aware execution.
UVM-SystemC Bridge: Components, Phasing, and Configuration
While SystemC relies on sc_core::sc_module and SC_CTOR to build structural hierarchy, UVM-SystemC introduces the uvm_component class and an advanced, multi-stage Phasing mechanism to orchestrate testbenches uniformly.
Source and LRM Trail
For UVM-SystemC, use Docs/LRMs/uvm-systemc-language-reference-manual.pdf as the methodology contract. In source, inspect .codex-src/uvm-systemc/src/uvmsc: components, phases, factory macros, sequences, sequencers, TLM ports, reporting, and configuration helpers.
UVM Phasing
Standard SystemC has a limited elaboration phase followed directly by simulation (sc_start).
UVM-SystemC expands this significantly to enforce a standard lifecycle for all verification components:
build_phase: Components instantiate their children from the top-down.connect_phase: Components connect their TLM ports, exports, and interfaces from the bottom-up.end_of_elaboration_phase: Final topology checks before simulation starts.start_of_simulation_phase: Printing topologies, configuring banners.run_phase: The only time-consuming phase. Stimulus generation and signal driving happen here.extract_phase: Data is gathered from scoreboards and coverage collectors.check_phase: Final pass/fail assertions are evaluated.report_phase: Test results and logs are dumped to the terminal or file.
By strictly adhering to these phases, UVM components can be plugged together reliably without initialization race conditions.
The Configuration Database (uvm_config_db)
The uvm_config_db is a type-safe, hierarchical registry used to pass configuration data down the component tree. Instead of passing long lists of constructor arguments, a parent component (like a Test) can place a configuration object in the database, and a deeply nested child (like a Driver) can retrieve it during its build_phase.
Complete Example: Phasing and uvm_config_db
Here is a complete example demonstrates the exact execution order of UVM phases and shows how a top-level test configures a child driver's behavior using the uvm_config_db.
#include <systemc>
#include <uvm>
#include <string>
// 1. A Reusable Driver Component
class my_driver : public uvm::uvm_component {
public:
UVM_COMPONENT_UTILS(my_driver);
int max_transactions;
my_driver(uvm::uvm_component_name name)
: uvm::uvm_component(name), max_transactions(1) {} // Default is 1
// Phase 1: Build Phase
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_component::build_phase(phase);
UVM_INFO("DRIVER", "build_phase executing...", uvm::UVM_LOW);
// Attempt to retrieve 'max_transactions' from the config DB
// The first argument 'this' provides the hierarchical path context.
if (!uvm::uvm_config_db<int>::get(this, "", "max_transactions", max_transactions)) {
UVM_WARNING("DRIVER", "No config found for max_transactions. Using default.");
} else {
UVM_INFO("DRIVER", "Config retrieved successfully!", uvm::UVM_LOW);
}
}
// Phase 2: Connect Phase
void connect_phase(uvm::uvm_phase& phase) override {
UVM_INFO("DRIVER", "connect_phase executing...", uvm::UVM_LOW);
}
// Phase 5: Run Phase (Time Consuming)
void run_phase(uvm::uvm_phase& phase) override {
phase.raise_objection(this);
UVM_INFO("DRIVER", "run_phase started.", uvm::UVM_LOW);
for (int i = 0; i < max_transactions; ++i) {
sc_core::wait(10, sc_core::SC_NS);
UVM_INFO("DRIVER", "Driving transaction...", uvm::UVM_LOW);
}
phase.drop_objection(this);
}
// Phase 7: Check Phase
void check_phase(uvm::uvm_phase& phase) override {
UVM_INFO("DRIVER", "check_phase executing...", uvm::UVM_LOW);
}
};
// 2. The Top-Level Test
class config_test : public uvm::uvm_test {
public:
UVM_COMPONENT_UTILS(config_test);
my_driver* driver;
config_test(uvm::uvm_component_name name) : uvm::uvm_test(name) {}
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_test::build_phase(phase);
UVM_INFO("TEST", "build_phase executing...", uvm::UVM_LOW);
// Place a configuration value into the DB for the driver.
// We target the exact hierarchical path of the driver ("driver").
uvm::uvm_config_db<int>::set(this, "driver", "max_transactions", 3);
// Instantiate the driver AFTER setting the config, so the driver
// can retrieve it during its own build_phase.
driver = my_driver::type_id::create("driver", this);
}
void report_phase(uvm::uvm_phase& phase) override {
UVM_INFO("TEST", "report_phase: Test completed successfully.", uvm::UVM_NONE);
}
};
// 3. Simulation Entry Point
int sc_main(int argc, char* argv[]) {
// Start the UVM test.
// This will automatically sequence the phases:
// build -> connect -> end_of_elaboration -> start_of_simulation -> run -> extract -> check -> report
uvm::run_test("config_test");
return 0;
}Best Practices
- Top-Down Build:
build_phaseexecutes top-down. The parent builds first, allowing it to set configurations in theuvm_config_dbbefore the child'sbuild_phaseruns. - Bottom-Up Connect:
connect_phaseexecutes bottom-up. You must not attempt to send TLM transactions or use ports duringbuild_phasebecause they are not connected yet. - Objections: The
run_phaseoperates concurrently across all components in the hierarchy. The simulation only ends when all components have dropped their objections viaphase.drop_objection(this). Do not forget to drop your objection, or the simulation will hang forever.
Under the Hood: The uvm_resource_pool and Phase Execution
While uvm_config_db<T>::set() feels like a simple map insertion, it is actually a wrapper around the global uvm_resource_pool.
When you set a value, the library creates a type-safe uvm_resource<T> object on the heap and inserts it into a central database. The string hierarchical paths (like "driver") are compiled into POSIX regular expressions. When uvm_config_db<T>::get() is called, the pool iterates over the resources and attempts a regex match against the current component's absolute path (e.g., uvm_test_top.driver).
Because of this regex evaluation, uvm_config_db accesses can be extremely slow if called dynamically during run_phase. A major C++ performance optimization rule in UVM-SystemC is to only perform .get() calls during build_phase, caching the retrieved values locally in member variables for use during run_phase.
Regarding execution, run_phase is the only phase that actually consumes SystemC simulation time. The UVM-SystemC core uses sc_core::sc_spawn to spin off an individual SC_THREAD for the run_phase of every instantiated component. The phasing state machine explicitly yields to the SystemC scheduler (sc_start()), allowing these spawned threads to advance time. Once the global objection counter drops to zero, the uvm_root singleton triggers sc_stop() to forcibly terminate the simulation delta cycles.
Comments and Corrections