VP Architecture Review for Technical Leads
A lead-engineer checklist and compliant architectural example for reviewing a SystemC virtual platform before deployment.
VP Architecture Review for Technical Leads
This page is written for the technical lead who must decide whether a Virtual Platform (VP) is ready for firmware bring-up, architecture exploration, or customer release. A VP is only useful if it acts as a reliable, unambiguous contract between hardware designers and software engineers.
The Review Checklist
Before a VP is deployed, it must be evaluated against the following criteria:
1. Memory Map Contract
Check that every region has a strictly defined:
- Base address and size (preventing overlaps and undefined holes).
- Access width policy and Endianness.
- Error response policy (what happens on unmapped accesses?).
- Debug transport support (
transport_dbg).
2. Register Quality
If a firmware engineer cannot write a driver from the documentation and the model's behavior, the VP is incomplete. Every peripheral must enforce:
- Reset values and reserved bits.
- Read-only vs Write-only semantics.
- Side effects (e.g., clear-on-read).
- Interrupt generation policies.
3. Timing Contract
The VP must state exactly what simulation time means. Examples:
- "RAM access latency is modeled as a constant 20ns TLM delay."
- "UART transmission is one character delay per byte."
- "Interrupt propagation uses SystemC
sc_signalupdate semantics."
4. Configuration and Observability
- CCI parameters must be named stably, documented, and properly locked if structural.
- The VP must detect unconsumed presets.
- Use standardized SystemC reporting macros (
SC_REPORT_INFO,SC_REPORT_FATAL) instead of rawstd::cout.
Complete Example: The "Review-Ready" Compliant Block
The following complete, compilable example demonstrates a peripheral that strictly adheres to the review criteria above. It models a compliant timer peripheral with strict register semantics, debug transport, CCI configuration, proper memory bounds checking, and explicitly stated timing contracts.
#include <systemc>
#include <tlm>
#include <tlm_utils/simple_target_socket.h>
#include <cci_configuration>
using namespace sc_core;
using namespace tlm;
class CompliantTimer : public sc_module {
public:
// Memory map socket
tlm_utils::simple_target_socket<CompliantTimer> socket;
// Interrupt out
sc_out<bool> irq_out;
// CCI Configuration (Review #4)
cci::cci_param<int> base_frequency_hz;
SC_HAS_PROCESS(CompliantTimer);
CompliantTimer(sc_module_name name)
: sc_module(name)
, socket("socket")
, irq_out("irq_out")
, base_frequency_hz("base_frequency_hz", 1000, "Base clock frequency in Hz")
{
socket.register_b_transport(this, &CompliantTimer::b_transport);
socket.register_transport_dbg(this, &CompliantTimer::transport_dbg);
SC_THREAD(timer_process);
}
private:
// Registers (Review #2: Register Quality)
// 0x00: CTRL (Bit 0: Enable, Bit 1: Interrupt Enable)
// 0x04: STATUS (Bit 0: Timer Fired - Clear on Write 1)
uint32_t reg_ctrl = 0;
uint32_t reg_status = 0;
sc_event ev_timer_fired;
// Review #3: Timing Contract.
// Register access is modeled as a fixed 10ns delay.
void b_transport(tlm_generic_payload& trans, sc_time& delay) {
tlm_command cmd = trans.get_command();
sc_dt::uint64 addr = trans.get_address();
unsigned char* ptr = trans.get_data_ptr();
unsigned int len = trans.get_data_length();
// Review #1: Memory Map Contract (Bounds and access width checking)
if (addr > 0x04 || len != 4) {
trans.set_response_status(TLM_ADDRESS_ERROR_RESPONSE);
return;
}
if (cmd == TLM_READ_COMMAND) {
uint32_t val = (addr == 0x00) ? reg_ctrl : reg_status;
memcpy(ptr, &val, 4);
} else if (cmd == TLM_WRITE_COMMAND) {
uint32_t val;
memcpy(&val, ptr, 4);
if (addr == 0x00) {
reg_ctrl = val & 0x03; // Mask reserved bits
SC_REPORT_INFO("Timer", "CTRL register updated.");
} else if (addr == 0x04) {
// Clear on write 1
if (val & 0x01) {
reg_status &= ~0x01;
irq_out.write(false);
}
}
}
delay += sc_time(10, SC_NS); // Apply timing contract
trans.set_response_status(TLM_OK_RESPONSE);
}
// Review #1 & #6: Debug transport bypasses delays and side-effects
unsigned int transport_dbg(tlm_generic_payload& trans) {
sc_dt::uint64 addr = trans.get_address();
if (addr > 0x04 || trans.get_data_length() != 4) return 0;
uint32_t val = (addr == 0x00) ? reg_ctrl : reg_status;
memcpy(trans.get_data_ptr(), &val, 4);
return 4; // Bytes read
}
void timer_process() {
while (true) {
// Wait for 1 tick based on CCI parameter
sc_time tick_period(1.0 / base_frequency_hz.get_value(), SC_SEC);
wait(tick_period);
if (reg_ctrl & 0x01) { // If Enabled
reg_status |= 0x01; // Set status
if (reg_ctrl & 0x02) { // If Interrupt Enabled
irq_out.write(true);
}
}
}
}
};
// --- Top Level Testbench ---
int sc_main(int argc, char* argv[]) {
// Setup Broker (Review #4)
cci::cci_register_broker(new cci_utils::consuming_broker("Global_Broker"));
// Instantiate and bind
CompliantTimer timer("timer");
sc_signal<bool> sig_irq;
timer.irq_out(sig_irq);
// Dummy transaction to test the contract
tlm_generic_payload trans;
sc_time delay = SC_ZERO_TIME;
uint32_t data = 0x03; // Enable + IRQ Enable
trans.set_command(TLM_WRITE_COMMAND);
trans.set_address(0x00);
trans.set_data_ptr(reinterpret_cast<unsigned char*>(&data));
trans.set_data_length(4);
trans.set_response_status(TLM_INCOMPLETE_RESPONSE);
// Send transaction (Simulating a CPU)
timer.socket->b_transport(trans, delay);
wait(delay); // Accumulate time
if (trans.get_response_status() == TLM_OK_RESPONSE) {
SC_REPORT_INFO("CPU", "Successfully configured timer.");
}
sc_start(2, SC_MS); // Run to see IRQ fire
return 0;
}The Approval Bar
A Virtual Platform is considered "lead-review ready" only when:
- Software-visible behaviors (registers, interrupts) perfectly match documentation.
- Extraneous configuration parameters are locked.
- The boundary between Loosely Timed (LT) approximations and Cycle Accurate (CA) behavior is formally recorded.
- Debug inspection (
transport_dbg) operates independently from time-advancing data paths.
Comments and Corrections