Chapter 10: UVM-SystemC

UVM-SystemC Bridge: Verification Architecture

How UVM-SystemC layers standard Universal Verification Methodology on top of the SystemC simulation kernel.

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.

The UVM-SystemC Architecture

According to the Accellera UVM-SystemC Language Reference Manual, the methodology is built on a few core pillars:

  1. uvm_object: The base class for dynamic, transient data (like transactions and sequences).
  2. uvm_component: The base class for structural, permanent hierarchy (like drivers, monitors, and scoreboards).
  3. Phasing: A standardized execution flow (build_phase, connect_phase, run_phase, etc.) that coordinates the entire testbench.
  4. 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.
  5. 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

  1. Instantiation: You do not use standard C++ new or SC_MODULE constructors for the hierarchy. Instead, components are instantiated in the build_phase using the factory (type_id::create). This is what makes UVM reusable.
  2. Execution Control: sc_start() is completely hidden. uvm::run_test() manages the SystemC kernel for you.
  3. Objections: The simulation stops automatically when all run_phase objections are dropped, rather than running indefinitely or stopping via a hardcoded time limit.

In the next tutorial, we will explore the uvm_object, the factory, and how to write reusable transaction data structures.

Comments and Corrections