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
| 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
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