UVM-SystemC Bridge: Verification Architecture
How UVM-SystemC layers standard Universal Verification Methodology on top of the SystemC simulation kernel.
How to Read This Lesson
UVM-SystemC Bridge: Verification Architecture
UVM-SystemC brings the Universal Verification Methodology (UVM)—the gold standard for hardware verification in SystemVerilog—directly into the C++ SystemC ecosystem.
While SystemC provides the fundamental discrete-event simulation kernel and modeling paradigms (like TLM), UVM-SystemC provides the verification structure. It introduces standardized architectures for testbenches, phasing, configuration, and reporting, ensuring that verification environments are highly reusable and predictable.
Standard and source context
The UVM-SystemC Architecture
According to the Accellera UVM-SystemC Language Reference Manual, the methodology is built on a few core pillars:
uvm_object: The base class for dynamic, transient data (like transactions and sequences).uvm_component: The base class for structural, permanent hierarchy (like drivers, monitors, and scoreboards).- Phasing: A standardized execution flow (
build_phase,connect_phase,run_phase, etc.) that coordinates the entire testbench. - The Factory: A mechanism allowing types to be overridden at runtime, enabling tests to replace generic transactions or components with specialized ones without touching the original source code.
- Configuration DB: A centralized database (
uvm_config_db) for passing parameters and interface handles down the component hierarchy.
Typical Component Hierarchy
A UVM-SystemC environment strictly separates the Design Under Test (DUT) from the verification logic. The testbench hierarchy typically looks like this:
uvm_test: The top-level block. It configures the environment and starts the stimulus (sequences).uvm_env: The environment grouping agents and scoreboards.uvm_agent: A reusable block encapsulating a specific protocol (e.g., AXI, UART).uvm_sequencer: Arbitrates and feeds transactions to the driver.uvm_driver: Translates transactions into pin wiggles or TLM calls.uvm_monitor: Observes the bus and publishes observed transactions.
uvm_scoreboard: Compares observed transactions against expected behavior.
Complete Example: The Top-Level UVM Architecture
The following complete, compilable example demonstrates the absolute baseline architecture of a UVM-SystemC simulation. It defines a component hierarchy, implements the standard UVM phases, and uses uvm::run_test() to bootstrap the verification environment inside sc_main.
#include <systemc>
#include <uvm>
// 1. A dummy Driver Component
class my_driver : public uvm::uvm_driver<uvm::uvm_sequence_item> {
public:
UVM_COMPONENT_UTILS(my_driver); // Register with the UVM factory
my_driver(uvm::uvm_component_name name) : uvm::uvm_driver<uvm::uvm_sequence_item>(name) {}
// The run_phase consumes time and drives the simulation
void run_phase(uvm::uvm_phase& phase) override {
UVM_INFO("DRIVER", "Driver is starting execution...", uvm::UVM_LOW);
// Raise an objection to prevent the simulation from ending
phase.raise_objection(this);
sc_core::wait(100, sc_core::SC_NS);
UVM_INFO("DRIVER", "Driving transaction 1...", uvm::UVM_LOW);
sc_core::wait(100, sc_core::SC_NS);
UVM_INFO("DRIVER", "Driving transaction 2...", uvm::UVM_LOW);
// Drop objection to allow simulation to finish
phase.drop_objection(this);
}
};
// 2. An Agent grouping the driver
class my_agent : public uvm::uvm_agent {
public:
UVM_COMPONENT_UTILS(my_agent);
my_driver* driver;
my_agent(uvm::uvm_component_name name) : uvm::uvm_agent(name), driver(nullptr) {}
// The build_phase constructs children top-down
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_agent::build_phase(phase);
UVM_INFO("AGENT", "Building driver...", uvm::UVM_MEDIUM);
// Create the driver using the UVM Factory
driver = my_driver::type_id::create("driver", this);
}
};
// 3. The Environment grouping agents and scoreboards
class my_env : public uvm::uvm_env {
public:
UVM_COMPONENT_UTILS(my_env);
my_agent* agent;
my_env(uvm::uvm_component_name name) : uvm::uvm_env(name), agent(nullptr) {}
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_env::build_phase(phase);
UVM_INFO("ENV", "Building agent...", uvm::UVM_MEDIUM);
agent = my_agent::type_id::create("agent", this);
}
};
// 4. The Top-Level Test
class my_test : public uvm::uvm_test {
public:
UVM_COMPONENT_UTILS(my_test);
my_env* env;
my_test(uvm::uvm_component_name name) : uvm::uvm_test(name), env(nullptr) {}
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_test::build_phase(phase);
UVM_INFO("TEST", "Building environment...", uvm::UVM_MEDIUM);
env = my_env::type_id::create("env", this);
}
// Check phase runs after simulation finishes
void check_phase(uvm::uvm_phase& phase) override {
UVM_INFO("TEST", "Performing final end-of-test checks.", uvm::UVM_LOW);
}
};
// 5. The sc_main Entry Point
int sc_main(int argc, char* argv[]) {
// Instead of explicitly instantiating components and calling sc_start(),
// UVM-SystemC takes control of the simulation execution.
// run_test() automatically creates the test component specified by
// the +UVM_TESTNAME command-line argument (or passed dynamically).
// It then automatically executes the UVM phases and invokes sc_start() internally.
uvm::run_test("my_test");
return 0;
}Key Differences from Pure SystemC
- Instantiation: You do not use standard C++
neworSC_MODULEconstructors for the hierarchy. Instead, components are instantiated in thebuild_phaseusing the factory (type_id::create). This is what makes UVM reusable. - Execution Control:
sc_start()is completely hidden.uvm::run_test()manages the SystemC kernel for you. - Objections: The simulation stops automatically when all
run_phaseobjections are dropped, rather than running indefinitely or stopping via a hardcoded time limit.
Under the Hood: run_test and UVM_COMPONENT_UTILS
While UVM-SystemC abstractly mirrors SystemVerilog UVM, it heavily relies on C++ metaprogramming to bridge into the SystemC kernel.
When you call uvm::run_test("my_test"), the UVM-SystemC library fetches the singleton uvm_root::get(). uvm_root is a specialized uvm_component that serves as the invisible top-level module (the parent of my_test). Inside run_test(), the core library queries the UVM factory to instantiate the test string provided. It then manually invokes sc_core::sc_start() internally. During the run_phase, UVM-SystemC spawns threads (SC_THREAD) for every component's run_phase() method. When the global objection count hits zero, uvm_root calls sc_core::sc_stop().
The magic of the factory is implemented through the UVM_COMPONENT_UTILS(T) macro. In the uvm-systemc repository, this macro expands to declare a nested type_id struct and a static factory registration proxy. At C++ static initialization time (before main even starts), this proxy object registers a string-to-creator mapping in the global uvm_factory. This is why you can call type_id::create("name") and receive a dynamically allocated polymorphic instance without ever hardcoding the new keyword.
In the next tutorial, we will explore the uvm_object, the factory, and how to write reusable transaction data structures.
Can you answer these clearly?
Keep moving when you can answer each question without looking back at the lesson.
Comments and Corrections