Chapter 12: Virtual Platform Construction

VP Memory Map and Register Contract

Defining the memory map and software-to-hardware register contracts for Virtual Platform peripherals.

VP Memory Map and Register Contract

In a Virtual Platform, hardware behavior is entirely dictated by Memory-Mapped Registers. The CPU (running embedded C firmware) configures physical IP blocks by writing specific bit patterns to specific hexadecimal offsets.

This creates a rigid contract between the software and the hardware. If the C code expects the UART Transmit Register to be at offset 0x04, the SystemC peripheral must intercept transactions at 0x04 and trigger transmission logic.

The Register Contract

A well-architected peripheral does not hardcode global addresses like 0x4000_1004. It relies on the Router to subtract the base address, so it only observes local offsets (e.g., 0x00 to 0xFF).

Example: UART Register Contract

OffsetRegister NameAccessDescription
0x00UART_CTRLR/WBit 0: Enable. Bit 1: TX Interrupt Enable.
0x04UART_STATUSR/OBit 0: TX Ready. Bit 1: RX Full.
0x08UART_TX_DATAW/OWrite 8-bit character to transmit.

End-to-End Register Peripheral Example

This complete sc_main example models the UART contract defined above, strictly utilizing TLM 2.0 payload mechanics.

#include <systemc>
#include <tlm>
#include <tlm_utils/simple_target_socket.h>
 
SC_MODULE(UART_Peripheral) {
    tlm_utils::simple_target_socket<UART_Peripheral> socket;
 
    // Internal Register State
    uint32_t reg_ctrl = 0;
    uint32_t reg_status = 0x01; // Initial state: TX Ready is true (Bit 0 = 1)
 
    SC_CTOR(UART_Peripheral) : socket("socket") {
        socket.register_b_transport(this, &UART_Peripheral::b_transport);
    }
 
private:
    void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
        sc_dt::uint64 addr = trans.get_address();
        unsigned char* ptr = trans.get_data_ptr();
        unsigned int len = trans.get_data_length();
 
        if (len != 4) { // Enforce 32-bit aligned access
            trans.set_response_status(tlm::TLM_BURST_ERROR_RESPONSE);
            return;
        }
 
        if (trans.get_command() == tlm::TLM_WRITE_COMMAND) {
            handle_write(addr, ptr);
        } else if (trans.get_command() == tlm::TLM_READ_COMMAND) {
            handle_read(addr, ptr);
        }
 
        delay += sc_core::sc_time(10, sc_core::SC_NS);
        trans.set_response_status(tlm::TLM_OK_RESPONSE);
    }
 
    void handle_write(sc_dt::uint64 addr, unsigned char* ptr) {
        uint32_t val;
        memcpy(&val, ptr, 4);
 
        switch (addr) {
            case 0x00: // UART_CTRL
                reg_ctrl = val;
                std::cout << "@" << sc_core::sc_time_stamp() << " [UART] Control Reg updated: 0x" 
                          << std::hex << reg_ctrl << std::endl;
                break;
            case 0x08: // UART_TX_DATA
                if (reg_ctrl & 0x01) { // If UART is enabled
                    std::cout << "@" << sc_core::sc_time_stamp() << " [UART] Transmitted char: " 
                              << (char)(val & 0xFF) << std::endl;
                }
                break;
            default:
                SC_REPORT_WARNING("UART", "Write to read-only or invalid register.");
                break;
        }
    }
 
    void handle_read(sc_dt::uint64 addr, unsigned char* ptr) {
        uint32_t val = 0;
        switch (addr) {
            case 0x00: val = reg_ctrl; break;
            case 0x04: val = reg_status; break;
            default:   val = 0; break;
        }
        memcpy(ptr, &val, 4);
    }
};
 
int sc_main(int argc, char* argv[]) {
    UART_Peripheral uart("uart");
    // Standalone compilation check
    return 0;
}

By strictly honoring the register contract mapped by the embedded software engineers, the Virtual Platform acts as a perfect hardware digital twin, allowing native unmodified Linux drivers to boot directly on the SystemC models.

Comments and Corrections