UVM-SystemC Bridge: Objects, Factory, and Policy Classes
uvm_object, copy/compare/print hooks, factory creation, and reusable transaction design.
How to Read This Lesson
UVM-SystemC Bridge: Objects, Factory, and Policy Classes
While uvm_component forms the static structural hierarchy of your testbench, uvm_object is the fundamental base class for all dynamic, transient data in UVM-SystemC.
Transactions, sequences, and configuration objects all inherit from uvm_object.
Standard and source context
Transaction Objects (Sequence Items)
A transaction object describes an atomic operation (e.g., a bus read, an ethernet packet, a register write). In UVM, transactions inherit from uvm_sequence_item (which itself inherits from uvm_object).
To ensure a transaction is fully reusable across scoreboards, drivers, and monitors, it must implement Policy Hooks.
Policy Hooks: Print, Copy, Compare, Pack
UVM-SystemC objects provide standard virtual methods that you are expected to override:
do_print(uvm_printer&): How the object represents itself as text.do_copy(const uvm_object&): How to deep-copy the object.do_compare(const uvm_object&, uvm_comparer&): How to check if two objects are equivalent.do_pack(uvm_packer&)/do_unpack(uvm_packer&): How to serialize the object to/from raw bit arrays.
By implementing these hooks, generic UVM components (like scoreboards) can compare transactions without knowing their specific underlying types.
The UVM Factory
The UVM Factory is a design pattern that allows you to substitute one object type for another dynamically at runtime.
If a testbench instantiates bus_transaction via the factory, a specific test can instruct the factory to substitute error_bus_transaction instead. The environment will automatically use the new type without a single line of the environment's source code being changed.
Complete Example: Transactions, Policies, and Factory Overrides
Here is a complete sc_main example demonstrates how to write a fully compliant uvm_sequence_item, override its policy hooks, register it with the factory, and dynamically override it from a test.
#include <systemc>
#include <uvm>
#include <string>
// 1. A Baseline Transaction Object
class bus_item : public uvm::uvm_sequence_item {
public:
// Register with the UVM object factory
UVM_OBJECT_UTILS(bus_item);
sc_dt::uint64 address;
sc_dt::uint32 data;
bool is_write;
// Default constructor is required by the factory
bus_item(const std::string& name = "bus_item")
: uvm::uvm_sequence_item(name), address(0), data(0), is_write(false) {}
// 2. Implement the Print hook
void do_print(uvm::uvm_printer& printer) const override {
uvm::uvm_sequence_item::do_print(printer);
printer.print_field_int("address", address, 64, uvm::UVM_HEX);
printer.print_field_int("data", data, 32, uvm::UVM_HEX);
printer.print_field_int("is_write", is_write, 1, uvm::UVM_BIN);
}
// 3. Implement the Copy hook
void do_copy(const uvm::uvm_object& rhs) override {
const bus_item* rhs_cast = dynamic_cast<const bus_item*>(&rhs);
if (rhs_cast == nullptr) {
UVM_FATAL("COPY", "Cast failed in do_copy");
}
uvm::uvm_sequence_item::do_copy(rhs);
this->address = rhs_cast->address;
this->data = rhs_cast->data;
this->is_write = rhs_cast->is_write;
}
// 4. Implement the Compare hook
bool do_compare(const uvm::uvm_object& rhs, uvm::uvm_comparer* comparer) const override {
const bus_item* rhs_cast = dynamic_cast<const bus_item*>(&rhs);
if (rhs_cast == nullptr) return false;
bool match = uvm::uvm_sequence_item::do_compare(rhs, comparer);
match &= comparer->compare_field_int("address", this->address, rhs_cast->address, 64);
match &= comparer->compare_field_int("data", this->data, rhs_cast->data, 32);
match &= comparer->compare_field_int("is_write", this->is_write, rhs_cast->is_write, 1);
return match;
}
};
// 5. An Error-Injection Subclass
class error_bus_item : public bus_item {
public:
UVM_OBJECT_UTILS(error_bus_item);
bool force_error;
error_bus_item(const std::string& name = "error_bus_item")
: bus_item(name), force_error(true) {}
void do_print(uvm::uvm_printer& printer) const override {
bus_item::do_print(printer); // Print parent fields
printer.print_field_int("force_error", force_error, 1, uvm::UVM_BIN);
}
};
// 6. A Component that uses the transaction
class generic_driver : public uvm::uvm_component {
public:
UVM_COMPONENT_UTILS(generic_driver);
generic_driver(uvm::uvm_component_name name) : uvm::uvm_component(name) {}
void run_phase(uvm::uvm_phase& phase) override {
phase.raise_objection(this);
// We ask the factory to create a "bus_item".
// If an override is set, we might get an "error_bus_item" instead!
bus_item* item = bus_item::type_id::create("item");
UVM_INFO("DRIVER", "Driver created transaction:", uvm::UVM_LOW);
item->print(); // Uses do_print() under the hood
phase.drop_objection(this);
}
};
// 7. The Top-Level Test
class factory_test : public uvm::uvm_test {
public:
UVM_COMPONENT_UTILS(factory_test);
generic_driver* driver;
factory_test(uvm::uvm_component_name name) : uvm::uvm_test(name) {}
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_test::build_phase(phase);
// 8. Factory Override:
// Instruct the factory: Anytime someone asks to create a "bus_item",
// give them an "error_bus_item" instead.
set_type_override("bus_item", "error_bus_item");
driver = generic_driver::type_id::create("driver", this);
}
};
int sc_main(int argc, char* argv[]) {
// Run the test. The driver will ask for a bus_item, but will receive
// an error_bus_item due to the factory override in build_phase.
uvm::run_test("factory_test");
return 0;
}Under the Hood: The Factory and Dynamic Casting
UVM-SystemC implements the factory pattern via a central singleton called uvm_default_factory.
When set_type_override (or set_type_override_by_type) is called, the factory populates a map std::map<uvm_object_wrapper*, uvm_object_wrapper*> (conceptually). Later, when bus_item::type_id::create() executes, it consults the factory to resolve the most derived override. It then invokes the create_object() virtual method on the proxy wrapper to perform the C++ new allocation.
Furthermore, notice the mandatory use of dynamic_cast<const bus_item*>(&rhs) inside do_copy and do_compare. Because UVM-SystemC relies heavily on polymorphism, the framework passes generic uvm_object& references to policy hooks. C++ requires RTTI (Run-Time Type Information) to safely downcast the object back to the user-defined bus_item to access member variables like address and data.
A critical C++ implementation detail to be aware of: uvm_object::clone() is heavily used in verification environments (e.g., when a monitor captures a transaction). Under the hood, clone() executes uvm_object* obj = this->create(); obj->copy(this);. Therefore, failing to register your class with UVM_OBJECT_UTILS breaks create(), which catastrophically breaks clone().
Design Rules for Transactions
- Implement Policies: If you don't override
do_compare,item_a->compare(item_b)will only compare base class properties, silently ignoring your custom fields (likeaddressordata), breaking your scoreboards. - Use Factory Macros: Always use
UVM_OBJECT_UTILS(class_name)to register the object. Without this,type_id::createwill not compile. - Avoid Simulator Resources: Transactions should not own SystemC events (
sc_event), ports, or modules. They are pure data containers designed to be passed around, cloned, and deleted instantly.
Can you answer these clearly?
Keep moving when you can answer each question without looking back at the lesson.
Comments and Corrections