VP Memory Map and Register Contract
Defining the memory map and software-to-hardware register contracts for Virtual Platform peripherals.
How to Read This Lesson
For virtual platforms, imagine a firmware engineer trying to boot real software on your model. Every abstraction choice should help that person move faster without lying about the hardware.
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.
Source and LRM Trail
Virtual platform lessons combine standard TLM behavior with architecture practice. Use Docs/LRMs/SystemC_LRM_1666-2023.pdf for TLM and kernel rules, .codex-src/systemc/src/tlm_core/tlm_2 for sockets and payloads, .codex-src/cci for configurable platforms, and .codex-src/systemc-common-practices for reusable patterns.
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
| Offset | Register Name | Access | Description |
|---|---|---|---|
0x00 | UART_CTRL | R/W | Bit 0: Enable. Bit 1: TX Interrupt Enable. |
0x04 | UART_STATUS | R/O | Bit 0: TX Ready. Bit 1: RX Full. |
0x08 | UART_TX_DATA | W/O | Write 8-bit character to transmit. |
End-to-End Register Peripheral Example
Here is a complete sc_main example models the UART contract defined above, strictly utilizing TLM 2.0 payload mechanics.
Under the Hood (TLM Kernel Data Arrays):
In tlm_generic_payload, the payload data is managed via a raw unsigned char* m_data. The standard assumes Host Endianness. When extracting a 32-bit integer from this byte array, we use memcpy. This is not just a stylistic choice; modern C++ compilers enforce strict-aliasing rules. Casting unsigned char* directly to uint32_t* can cause undefined behavior or alignment faults (SIGBUS) on architectures like ARM. memcpy is explicitly optimized by compilers and guarantees safe TLM data extraction.
#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;
// Strict aliasing compliant extraction
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 and adhering to the raw byte-pointer logic demanded by the TLM 2.0 generic payload, the Virtual Platform acts as a perfect hardware digital twin. This enables native, unmodified Linux drivers to boot directly on the SystemC models exactly as they would on the physical silicon.
Comments and Corrections