Chapter 10: UVM-SystemC

The UVM Configuration Database (uvm_config_db)

Discover how to pass virtual interfaces, integers, and objects across your UVM hierarchy without hardcoding paths using the uvm_config_db.

In a large verification environment, passing configuration parameters down a deep component hierarchy can be extremely tedious. If a monitor deeply nested inside an agent needs to know whether the bus is configured for 32-bit or 64-bit mode, passing that boolean down through the test, the environment, the agent, and finally the monitor requires boilerplate code at every level.

Worse, what if you need to pass a handle to the physical pins (a virtual interface or a SystemC signal pointer)?

The UVM Configuration Database (uvm_config_db) solves this. It acts as a central repository where any component can store a variable (a set), and any component can retrieve that variable (a get), provided they know the correct hierarchy path and variable name. According to the IEEE 1800.2 standard, the database utilizes type-safe parameterized classes built upon a generic resource pool.

How uvm_config_db works

The uvm_config_db is a parameterized class. You must specify the type of the data you are storing or retrieving.

The two primary static methods are set() and get():

static void set(uvm_component* cntxt, const std::string& inst_name, const std::string& field_name, const T& value);
 
static bool get(uvm_component* cntxt, const std::string& inst_name, const std::string& field_name, T& value);
  • cntxt: The starting point in the UVM hierarchy (usually this). If setting globally, use uvm_top (or nullptr).
  • inst_name: The relative hierarchical path to the component(s) that should receive this configuration. Wildcards (*) are heavily used here.
  • field_name: The string identifier for the variable you are storing.
  • value: The actual data being stored (for set) or the variable to populate (for get).

Complete Configuration Example

Below is a complete, fully compilable sc_main program demonstrating how to pass a physical interface pointer (virtual interface) and a configuration integer from the top-level test bench down to a nested driver component using the uvm_config_db.

#include <systemc>
#include <uvm>
 
// A dummy transaction for the driver
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) {}
};
 
// A dummy physical interface wrapper
class my_bus_if {
public:
    sc_core::sc_signal<bool> clk;
    my_bus_if(const char* name) : clk(name) {}
};
 
// 1. The Driver retrieves the configuration
class my_driver : public uvm::uvm_driver<my_transaction> {
public:
    UVM_COMPONENT_UTILS(my_driver);
    
    my_bus_if* vif;
    int is_active_;
 
    my_driver(uvm::uvm_component_name name) : uvm::uvm_driver<my_transaction>(name) {}
 
    void build_phase(uvm::uvm_phase& phase) override {
        uvm::uvm_driver<my_transaction>::build_phase(phase);
 
        // Retrieve the virtual interface
        if (!uvm::uvm_config_db<my_bus_if*>::get(this, "", "vif", vif)) {
            UVM_FATAL("DRV/NOVIF", "No virtual interface specified for this driver instance");
        }
        
        // Retrieve the active flag (default to active if not found)
        if (!uvm::uvm_config_db<int>::get(this, "", "is_active", is_active_)) {
            is_active_ = 1;
        }
    }
 
    void run_phase(uvm::uvm_phase& phase) override {
        phase.raise_objection(this);
        if (is_active_) {
            UVM_INFO("DRV", "Driver is active, driving virtual interface pins.", uvm::UVM_LOW);
            vif->clk.write(true);
        } else {
            UVM_INFO("DRV", "Driver is passive.", uvm::UVM_LOW);
        }
        phase.drop_objection(this);
    }
};
 
// 2. The Agent instantiates the driver
class my_agent : public uvm::uvm_agent {
public:
    my_driver* driver;
    UVM_COMPONENT_UTILS(my_agent);
 
    my_agent(uvm::uvm_component_name name) : uvm::uvm_agent(name) {}
 
    void build_phase(uvm::uvm_phase& phase) override {
        uvm::uvm_agent::build_phase(phase);
        driver = my_driver::type_id::create("driver", this);
    }
};
 
// 3. The Environment instantiates the agent
class my_env : public uvm::uvm_env {
public:
    my_agent* agent;
    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);
        agent = my_agent::type_id::create("agent", this);
    }
};
 
// 4. The Test sets configurations for the descendants
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);
        
        // Target: "env.agent.driver" inside this context
        uvm::uvm_config_db<int>::set(this, "env.agent.driver", "is_active", 0); 
        // Notice we are turning the driver off (passive) for this specific test
        
        env = my_env::type_id::create("env", this);
    }
};
 
// 5. The Top-Level sets the virtual interface globally
int sc_main(int argc, char* argv[]) {
    // Instantiate the physical bus interface
    my_bus_if bus("bus");
 
    // Pass the pointer to the bus into the UVM configuration database
    // We use a global context (nullptr) and target all instances ("*")
    uvm::uvm_config_db<my_bus_if*>::set(nullptr, "*", "vif", &bus);
 
    // Start the UVM test
    uvm::run_test("my_test");
    return 0;
}

Best Practices

  1. Do it in build_phase: Setting and getting configurations should generally happen during the build_phase. If you wait until connect_phase or run_phase, children may have already been constructed with incorrect defaults.
  2. Check the return value of get(): Always verify that get() returns true, and provide a sensible default (or issue a UVM_FATAL) if it returns false.
  3. Minimize wildcards: While setting a configuration to "*" is easy, it can cause performance issues in massive testbenches and makes it hard to track who is configuring what. Be as specific as possible with the inst_name argument (e.g., "env.agent.driver").
  4. Configuration Objects: Instead of passing dozens of integers and booleans individually, wrap them in a configuration class deriving from uvm_object, and pass a pointer to that object via the database.

Comments and Corrections