Chapter 10: UVM-SystemC

UVM-SystemC Bridge: Objects, Factory, and Policy Classes

uvm_object, copy/compare/print hooks, factory creation, and reusable transaction design.

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.

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

This 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;
}

Design Rules for Transactions

  1. 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 (like address or data), breaking your scoreboards.
  2. Use Factory Macros: Always use UVM_OBJECT_UTILS(class_name) to register the object. Without this, type_id::create will not compile.
  3. 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.

Comments and Corrections