System Verification, SCV & UVM-SystemC
Moving from simple testbenches to robust verification using the SystemC Verification Library (SCV) and UVM-SystemC.
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.
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.
- 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).
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++.
Why use UVM-SystemC?
- Reusability via DPI-C: You can build a UVM-SystemC testbench to verify your high-level C++ Virtual Platform. Later, when the RTL is written in Verilog, you can reuse the exact same C++ testbench via SystemVerilog DPI-C to verify the final RTL.
- Standardization: Verification engineers already know UVM. UVM-SystemC allows them to apply their SystemVerilog knowledge directly to C++ ESL models using a nearly identical API.
UVM-SystemC Architecture Example
Just like SystemVerilog UVM, you build components derived from standard base classes like uvm_driver, uvm_monitor, and uvm_scoreboard. The phasing mechanism (run_phase) replaces standard SC_THREAD execution.
#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 (like SC_THREAD)
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