UVM-SystemC Factory and Config Internals
How UVM-SystemC uses factories, type IDs, component hierarchy, config_db, and resource lookup to build reusable verification.
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 Factory and Config Internals
UVM-SystemC gives verification code a standard architecture: components, objects, factory creation, configuration, phases, reports, and TLM communication. The core of this flexibility is the UVM Factory.
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.
Factory Purpose
The factory lets a test replace one type with another without rewriting the whole environment. This relies heavily on the macros UVM_COMPONENT_UTILS and UVM_OBJECT_UTILS, which register string names and type IDs with a centralized factory singleton.
Typical uses:
- replace a driver with an error-injecting driver
- replace a monitor with a coverage monitor
- choose a sequence item subtype
- configure tests through type overrides
Under the Hood: C++ Implementation in Accellera UVM-SystemC
How does the UVM factory actually instantiate C++ classes dynamically by string name or override rules? In C++, dynamic creation without reflection requires a robust boilerplate pattern. The Accellera repository implements this in src/uvmsc/base/uvm_factory.*.
- The Registry Pattern: When you use
UVM_COMPONENT_UTILS(my_driver), the macro injects a static registry class insidemy_driver. This nested class (type_id) registers a proxy creator object with the globaluvm_factorysingleton during the C++ static initialization phase (beforesc_maineven begins!). - Override Queues: The
uvm_factorymaintains two primary structures:m_type_overridesandm_inst_overrides. Whenmy_driver::type_id::create("drv", this)is called, the factory intercepts the call, searches its override queues using the hierarchical path string and requested type string, and determines if a substitution is required. - The Proxy Creator: Once the final type is resolved, the factory invokes a virtual
create_component()method on the proxy object registered by the target class. This method simply callsnew error_driver(name)under the hood and returns the baseuvm_componentpointer.
Factory Overrides Example
Below is a complete, fully compilable sc_main example showing how to use the factory to perform a type override. We will define a base_driver and an error_driver that inherits from it, and then instruct the factory to swap them out at runtime.
#include <systemc>
#include <uvm>
// A dummy transaction
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) {}
};
// 1. Define a base driver
class base_driver : public uvm::uvm_driver<my_transaction> {
public:
UVM_COMPONENT_UTILS(base_driver);
base_driver(uvm::uvm_component_name name) : uvm::uvm_driver<my_transaction>(name) {}
void run_phase(uvm::uvm_phase& phase) override {
phase.raise_objection(this);
UVM_INFO("DRV", "Base driver executing NORMAL behavior", uvm::UVM_LOW);
sc_core::wait(10, sc_core::SC_NS);
phase.drop_objection(this);
}
};
// 2. Define an error-injecting driver that inherits from the base
class error_driver : public base_driver {
public:
UVM_COMPONENT_UTILS(error_driver);
error_driver(uvm::uvm_component_name name) : base_driver(name) {}
void run_phase(uvm::uvm_phase& phase) override {
phase.raise_objection(this);
UVM_ERROR("DRV_ERR", "Error driver executing FAULTY behavior");
sc_core::wait(10, sc_core::SC_NS);
phase.drop_objection(this);
}
};
// 3. Define an environment that instantiates the base driver
class my_env : public uvm::uvm_env {
public:
base_driver* drv;
UVM_COMPONENT_UTILS(my_env);
my_env(uvm::uvm_component_name name) : uvm::uvm_env(name) {}
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_env::build_phase(phase);
// We MUST use the factory to create it, otherwise overrides won't work!
drv = base_driver::type_id::create("drv", this);
}
};
// 4. Define a test that sets up the factory override
class my_test : public uvm::uvm_test {
public:
my_env* env;
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);
// OVERRIDE: Tell the factory that whenever 'base_driver' is requested,
// it should instantiate 'error_driver' instead.
base_driver::type_id::set_type_override(error_driver::get_type());
// Now when the env creates 'drv', it will actually be an 'error_driver'
env = my_env::type_id::create("env", this);
}
void run_phase(uvm::uvm_phase& phase) override {
phase.raise_objection(this);
UVM_INFO("TEST", "Printing topology to verify override:", uvm::UVM_LOW);
uvm::uvm_root::get()->print_topology();
phase.drop_objection(this);
}
};
int sc_main(int argc, char* argv[]) {
// Start the test
uvm::run_test("my_test");
return 0;
}Source Implementation Shape
If you are exploring the UVM-SystemC implementation according to the IEEE 1800.2 standard, useful official source reading paths include:
src/uvmsc/base/uvm_component.*src/uvmsc/base/uvm_object.*src/uvmsc/base/uvm_factory.*src/uvmsc/conf/uvm_config_db.hsrc/uvmsc/conf/uvm_resource_pool.*
The config database is built on resource lookup ideas. That explains why names, scopes, and wildcard patterns matter so much.
Best Practice
- Always construct dynamic components using
type_id::createrather than C++new. If you usenew, the factory is bypassed and overrides will fail. - Use config DB for testbench configuration, not for every transaction.
- Retrieve configuration in build/connect phases and store it in clear member variables.
- Avoid broad wildcard settings unless the intent is truly global.
Relation to CCI
CCI is better for model configuration and tool inspection. UVM config DB is better for verification environment configuration.
In a combined project, use CCI to configure the DUT or VP model and UVM config DB to configure agents, monitors, sequences, and scoreboards.
Comments and Corrections