UVM-SystemC Bridge: Reporting, Registers, and Final Checks
UVM reporting, verbosity, register abstraction, prediction, scoreboards, and end-of-test discipline.
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.
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.
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.
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.
Under the Hood: Macro Optimization and RAL Blocking
The UVM-SystemC macros provide significant C++ performance optimizations. When you write UVM_INFO("ID", "Complex string " + std::to_string(val), uvm::UVM_HIGH);, the macro explicitly checks the component's verbosity before evaluating the string concatenation arguments. Under the hood, it roughly expands to: if (uvm_report_enabled(uvm::UVM_HIGH, uvm::UVM_INFO, "ID")) uvm_report_info("ID", ...);. This ensures that heavy string formatting operations in your testbench cost zero CPU cycles if the verbosity level is too low to print them.
Regarding the Register Abstraction Layer (RAL), when a sequence calls my_reg.write(status, value), it appears as a simple synchronous C++ function call. However, underneath, it is a complex blocking operation:
- The RAL generates a dynamic
uvm_reg_itemcontaining the read/write intent. - It passes this item to the connected
uvm_reg_adapter, which dynamically instantiates and returns a concrete bus transaction (e.g., an AXIuvm_sequence_item). - The RAL forwards this transaction to the
uvm_sequencerand immediately callssc_core::wait()on an internalsc_event. - Once the driver finishes the TLM phase and calls
item_done(), the sequencer fires the event, waking up the RAL thread. - The RAL updates its internal mirror of the register and finally returns the C++ function call to the sequence.
Comments and Corrections