Chapter 12: Virtual Platform Construction

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_signal update 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 raw std::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