System Verification, SCV & UVM-SystemC
Moving from simple testbenches to robust verification using the SystemC Verification Library (SCV) and UVM-SystemC.
How to Read This Lesson
UVM-SystemC is methodology in C++ clothing. Keep the verification intent in view: reusable components, controlled stimulus, reporting, and phase-aware execution.
System Verification & UVM-SystemC
When building simple models, a basic sc_main with a few std::cout statements and assert() calls is sufficient. However, for industrial System-on-Chips (SoCs), you need robust verification environments capable of constrained random stimulus, functional coverage, and automated checking.
This is where the SystemC Verification Library (SCV) and the IEEE standard UVM-SystemC come into play.
Source and LRM Trail
For UVM-SystemC, use Docs/LRMs/uvm-systemc-language-reference-manual.pdf as the methodology contract. In source, inspect .codex-src/uvm-systemc/src/uvmsc: components, phases, factory macros, sequences, sequencers, TLM ports, reporting, and configuration helpers.
The SystemC Verification Library (SCV)
SCV is an Accellera standard library built on top of SystemC. It provides three main features:
- Data Introspection: Dynamically inspecting the fields of a C++ struct or class using macro-generated trait classes.
- Constrained Randomization: Generating random data that mathematically adheres to complex hardware constraints.
- Transaction Recording: Automatically dumping TLM generic payloads to a database for waveform viewing (like GTKWave).
Under the Hood (Accellera SCV source):
When you use scv_constraint, the SCV kernel transforms your C++ boolean expressions (like p->address() >= 0x1000) into an Abstract Syntax Tree (AST). It then passes this AST to its internal constraint solver, which traditionally uses Binary Decision Diagrams (BDDs) to find a valid mathematical solution space before picking a random vector. scv_smart_ptr handles the user-facing API by heavily overloading operator-> and implicitly managing memory.
Complete SCV Constrained Randomization Example
In standard C++, rand() generates uniform distributions. SCV introduces scv_smart_ptr and scv_constraint to build powerful declarative generators similar to SystemVerilog's randc.
#include <systemc>
#include <scv.h>
// 1. Define a standard C++ struct representing a Bus Packet
struct Packet {
int address;
int payload[4];
};
// 2. Define introspection (so SCV knows the memory layout)
SCV_EXTENSIONS(Packet) {
public:
scv_extensions<int> address;
scv_extensions<int[4]> payload;
SCV_EXTENSIONS_CTOR(Packet) {
SCV_FIELD(address);
SCV_FIELD(payload);
}
};
// 3. Create a constraint class
class PacketConstraint : public scv_constraint_base {
public:
scv_smart_ptr<Packet> p;
SCV_CONSTRAINT_CTOR(PacketConstraint) {
// Constrain address between 0x1000 and 0x2000
SCV_CONSTRAINT( p->address() >= 0x1000 && p->address() <= 0x2000 );
// Constrain payload values to be strictly positive
for(int i = 0; i < 4; i++) {
SCV_CONSTRAINT( p->payload()[i] > 0 );
}
}
};
SC_MODULE(Testbench) {
SC_CTOR(Testbench) {
SC_THREAD(generator_thread);
}
void generator_thread() {
PacketConstraint gen("gen");
std::cout << "--- Generating Constrained Random Packets ---" << std::endl;
for(int i = 0; i < 5; i++) {
gen.next(); // Solves constraints and generates a new packet
std::cout << "Packet " << i << " | Addr: 0x" << std::hex << gen.p->address()
<< " | Payload[0]: " << std::dec << gen.p->payload()[0] << std::endl;
}
}
};
int sc_main(int argc, char* argv[]) {
Testbench tb("tb");
sc_core::sc_start();
return 0;
}UVM-SystemC
The Universal Verification Methodology (UVM) is the industry standard for verifying RTL logic using SystemVerilog. UVM-SystemC is an Accellera standard that ports the entire UVM architecture (phases, components, configuration databases, TLM connections) to standard C++.
Under the Hood (Accellera uvm-systemc repository):
UVM-SystemC is intricately tied to the SystemC discrete-event kernel:
uvm_componentdirectly inherits fromsc_core::sc_module.- The UVM Factory (
UVM_COMPONENT_UTILS) uses static class initialization and C++ RTTI to register class string names ("MyDriver") into a globaluvm_factorysingleton map beforesc_maineven executes. - The UVM Phasing mechanism (like
build_phase,run_phase) bridges withsc_simcontext. Therun_phasedoes not useSC_THREADdirectly in the constructor. Instead, the UVM phase manager issues ansc_spawnduring simulation to dynamically create ansc_thread_processthat wraps yourrun_phasemethod. It synchronizes across components usingsc_eventphase barriers.
UVM-SystemC Architecture Example
Just like SystemVerilog UVM, you build components derived from standard base classes like uvm_driver, uvm_monitor, and uvm_scoreboard.
#include <systemc>
#include <uvm>
class MyTransaction : public uvm::uvm_sequence_item {
public:
int data;
UVM_OBJECT_UTILS(MyTransaction);
MyTransaction(const std::string& name = "MyTransaction") : uvm::uvm_sequence_item(name), data(0) {}
};
class MyDriver : public uvm::uvm_driver<MyTransaction> {
public:
UVM_COMPONENT_UTILS(MyDriver);
MyDriver(uvm::uvm_component_name name) : uvm::uvm_driver<MyTransaction>(name) {}
// The run phase executes as a coroutine spawned by the UVM phase manager
void run_phase(uvm::uvm_phase& phase) {
while(true) {
MyTransaction req;
// In a real environment, this blocks until a sequencer sends an item
// seq_item_port->get_next_item(req);
UVM_INFO("DRIVER", "Driving transaction to DUT...", uvm::UVM_LOW);
wait(10, sc_core::SC_NS); // Simulate driving delay on the bus
// seq_item_port->item_done();
// Break to avoid infinite loop in this mock example
break;
}
}
};
int sc_main(int argc, char* argv[]) {
// In a real UVM environment, you would call uvm::run_test();
// This example isolates the UVM driver for structural clarity.
MyDriver driver("driver");
sc_core::sc_start(50, sc_core::SC_NS);
return 0;
}Summary
If you are building a quick prototype, standard sc_main C++ testing is fine. But for production-grade IP modeling, leveraging SCV for constrained randomization and UVM-SystemC for hierarchical testbench architecture ensures your models are rigorously verified and interoperable across the hardware engineering lifecycle.
Comments and Corrections