UVM-SystemC Bridge: Reporting, Registers, and Final Checks
UVM reporting, verbosity, register abstraction, prediction, scoreboards, and end-of-test discipline.
UVM-SystemC Bridge: Reporting, Registers, and Final Checks
A sophisticated verification environment requires disciplined logging, standardized register access mechanisms, and rigorous end-of-test evaluations. Silent success is the enemy of verification; a test only passes if it can explicitly prove why it passed.
UVM Reporting
UVM-SystemC replaces std::cout and SC_REPORT_* with a highly configurable, hierarchical reporting system via the uvm_report_server. Every message is qualified by:
- Severity:
UVM_INFO,UVM_WARNING,UVM_ERROR,UVM_FATAL. - Verbosity:
UVM_NONE,UVM_LOW,UVM_MEDIUM,UVM_HIGH,UVM_FULL,UVM_DEBUG. - Context: The hierarchical path of the component generating the message.
- ID: A string tag identifying the message category (e.g., "SCOREBOARD", "AXI_DRIVER").
You can filter messages globally or per-component using set_report_verbosity_level(), allowing you to keep normal test logs concise while enabling deep debug traces when a failure occurs.
Register Abstraction Layer (RAL)
Modern SoCs contain thousands of control and status registers. Hardcoding their addresses in sequences makes tests brittle.
The UVM Register Abstraction Layer (RAL) provides an object-oriented mirror of the hardware's register map. Instead of generating a raw bus transaction to address 0x1004, a sequence calls:
my_reg_block.timer_ctrl.enable.write(status, 1);
The RAL automatically translates this into the correct bus protocol transaction (via an adapter), issues the read/write, and updates its internal predicted state. This allows scoreboards to instantly check if the hardware's reset values and read/write policies (e.g., write-one-to-clear) match the specification.
End-of-Test Discipline
A UVM simulation does not merely "stop" when the sequences finish. It transitions into final validation phases:
extract_phase: Retrieves final coverage data and scoreboard queues.check_phase: Evaluates final assertions. Are there any pending transactions that were never completed? Did the scoreboard reconcile all expected data?report_phase: Summarizes the run.
Most importantly, you must query the uvm_report_server to verify that zero UVM_ERROR or UVM_FATAL messages were logged. A test with fatal exceptions is obviously broken, but a test that logs a UVM_ERROR and continues running must still be marked as a failure at the end.
Complete Example: Reporting and Final Checks
The following complete sc_main example demonstrates how to configure verbosity, log various severities, and perform a strict end-of-test check using the uvm_report_server during the report_phase.
#include <systemc>
#include <uvm>
class mock_scoreboard : public uvm::uvm_component {
public:
UVM_COMPONENT_UTILS(mock_scoreboard);
int expected_packets = 5;
int received_packets = 3; // Intentionally causing a mismatch
mock_scoreboard(uvm::uvm_component_name name) : uvm::uvm_component(name) {}
void run_phase(uvm::uvm_phase& phase) override {
phase.raise_objection(this);
// This will print because default verbosity is UVM_MEDIUM
UVM_INFO("SCOREBOARD", "Starting packet processing...", uvm::UVM_MEDIUM);
// This will NOT print unless verbosity is raised to UVM_HIGH
UVM_INFO("SCOREBOARD", "Processing internal data block 1...", uvm::UVM_HIGH);
sc_core::wait(10, sc_core::SC_NS);
// We log an error, but the simulation CONTINUES running!
UVM_ERROR("SCOREBOARD", "Data corruption detected in packet payload!");
phase.drop_objection(this);
}
void check_phase(uvm::uvm_phase& phase) override {
UVM_INFO("SCOREBOARD", "Executing Check Phase...", uvm::UVM_LOW);
if (expected_packets != received_packets) {
UVM_ERROR("SCOREBOARD", "Packet count mismatch at end of test!");
}
}
};
class test_reporting : public uvm::uvm_test {
public:
UVM_COMPONENT_UTILS(test_reporting);
mock_scoreboard* sb;
test_reporting(uvm::uvm_component_name name) : uvm::uvm_test(name) {}
void build_phase(uvm::uvm_phase& phase) override {
uvm::uvm_test::build_phase(phase);
sb = mock_scoreboard::type_id::create("sb", this);
// Dynamically elevate verbosity for the scoreboard specifically
sb->set_report_verbosity_level(uvm::UVM_HIGH);
}
// The Report Phase is the ultimate arbiter of test success
void report_phase(uvm::uvm_phase& phase) override {
uvm::uvm_report_server* server = uvm::uvm_report_server::get_server();
int err_count = server->get_severity_count(uvm::UVM_ERROR);
int fatal_count = server->get_severity_count(uvm::UVM_FATAL);
std::cout << "\n===================================================\n";
std::cout << " FINAL TEST SUMMARY \n";
std::cout << "===================================================\n";
if (err_count == 0 && fatal_count == 0) {
std::cout << "TEST PASSED!\n";
UVM_INFO("TEST", "Simulation completed with 0 errors.", uvm::UVM_NONE);
} else {
std::cout << "TEST FAILED! (Errors: " << err_count
<< ", Fatals: " << fatal_count << ")\n";
UVM_INFO("TEST", "Please review the log for failure details.", uvm::UVM_NONE);
}
std::cout << "===================================================\n";
}
};
int sc_main(int argc, char* argv[]) {
// Run the test.
// It will generate errors, which will be caught in the report_phase.
uvm::run_test("test_reporting");
return 0;
}Why UVM_ERROR doesn't stop the simulation
By default, UVM_FATAL terminates the simulation immediately, while UVM_ERROR increments an error counter and allows the simulation to proceed. This is a deliberate design choice: it allows the testbench to uncover and report multiple independent failures in a single run, rather than halting at the very first bug it encounters. The test is ultimately failed during the report_phase by inspecting the server's error count.
Comments and Corrections