The CPU Wrapper (ISS)
Wrapping a simple instruction sequence to act as a bus master, and assembling the final Virtual Platform.
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.
Building a Virtual Platform: The CPU Wrapper
In a production Virtual Platform (VP), the CPU is usually an Instruction Set Simulator (ISS) provided by a vendor like ARM (FastModels) or Imperas. The ISS decodes cross-compiled .elf binaries. Whenever the executing software performs a Load (LDR) or Store (STR) to physical memory, the ISS generates a TLM transaction and pushes it out its initiator socket.
For our VP, we will build a "Dummy CPU" acting as the bus master. It executes a hardcoded sequence of TLM reads and writes to configure the Timer and test the RAM. Now let's look at how this operates within the Accellera kernel.
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.
Memory Management in Production CPUs
Under the Hood: In our simple example below, we allocate a tlm::tlm_generic_payload trans directly on the stack for convenience. However, a production ISS executing an operating system might execute 100 million memory instructions per second. If the CPU wrapper were to call new tlm_generic_payload() for every transaction, the C++ heap allocator overhead would destroy simulation performance.
The IEEE 1666 standard solves this by providing the tlm_mm_interface. Production CPU wrappers instantiate a global Memory Manager that maintains a pre-allocated array of payload objects. The ISS calls trans = mm->allocate(), populates it, and sends it. Once the transaction completes, it calls trans->release(), returning the object to the pool without any delete or heap fragmentation.
The Complete Assembled Virtual Platform
This file acts as the culmination of the Virtual Platform chapter. It contains the Mock CPU, the Router (from Chapter 12.2), the RAM, the Timer (from 12.3), and the sc_main that binds them together.
This model perfectly conforms to the Accellera Simple Bus AT/LT specifications.
#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
#include <vector>
// --- Targets (Peripherals) ---
class RAM : public sc_core::sc_module {
public:
tlm_utils::simple_target_socket<RAM> socket;
unsigned char* memory;
RAM(sc_core::sc_module_name name) : sc_core::sc_module(name) {
memory = new unsigned char[0x40000]; // 256 KB
socket.register_b_transport(this, &RAM::b_transport);
}
~RAM() { delete[] memory; }
void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
if (trans.get_command() == tlm::TLM_WRITE_COMMAND) {
memcpy(&memory[trans.get_address()], trans.get_data_ptr(), trans.get_data_length());
}
trans.set_response_status(tlm::TLM_OK_RESPONSE);
delay += sc_core::sc_time(10, sc_core::SC_NS);
}
};
class Timer : public sc_core::sc_module {
public:
tlm_utils::simple_target_socket<Timer> socket;
unsigned int counter = 0;
Timer(sc_core::sc_module_name name) : sc_core::sc_module(name) {
socket.register_b_transport(this, &Timer::b_transport);
}
void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
if (trans.get_command() == tlm::TLM_WRITE_COMMAND && trans.get_address() == 0x0) {
counter = 100; // Mock setting the timer
}
trans.set_response_status(tlm::TLM_OK_RESPONSE);
}
};
// --- Router (Interconnect) ---
class Router : public sc_core::sc_module {
public:
tlm_utils::simple_target_socket<Router> target_socket;
tlm_utils::simple_initiator_socket<Router> init_ram;
tlm_utils::simple_initiator_socket<Router> init_timer;
Router(sc_core::sc_module_name name) : sc_core::sc_module(name), target_socket("target_socket"), init_ram("init_ram"), init_timer("init_timer") {
target_socket.register_b_transport(this, &Router::b_transport);
}
void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
sc_dt::uint64 addr = trans.get_address();
if (addr >= 0x00000000 && addr <= 0x0003FFFF) {
init_ram->b_transport(trans, delay);
} else if (addr >= 0x40000000 && addr <= 0x40000FFF) {
trans.set_address(addr - 0x40000000);
init_timer->b_transport(trans, delay);
trans.set_address(addr); // Restore!
} else {
trans.set_response_status(tlm::TLM_ADDRESS_ERROR_RESPONSE);
}
}
};
// --- Initiator (Mock CPU) ---
class CPU : public sc_core::sc_module {
public:
tlm_utils::simple_initiator_socket<CPU> socket;
SC_HAS_PROCESS(CPU);
CPU(sc_core::sc_module_name name) : sc_core::sc_module(name), socket("socket") {
SC_THREAD(execute_firmware);
}
private:
void execute_firmware() {
tlm::tlm_generic_payload trans;
sc_core::sc_time delay = sc_core::SC_ZERO_TIME;
unsigned int data;
std::cout << "@" << sc_core::sc_time_stamp() << " [CPU] Booting..." << std::endl;
// 1. Write to Timer Control (Address 0x40000000)
data = 1;
trans.set_command(tlm::TLM_WRITE_COMMAND);
trans.set_address(0x40000000);
trans.set_data_ptr(reinterpret_cast<unsigned char*>(&data));
trans.set_data_length(4);
trans.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE);
socket->b_transport(trans, delay);
// Under the Hood: Context Switch
// The b_transport call completed. We yield to the sc_simcontext
// scheduler to advance global time by the accumulated 'delay'.
wait(delay);
// 2. Write to RAM (Address 0x00000100)
delay = sc_core::SC_ZERO_TIME;
data = 0xDEADBEEF;
trans.set_address(0x00000100);
trans.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE);
socket->b_transport(trans, delay);
wait(delay);
std::cout << "@" << sc_core::sc_time_stamp() << " [CPU] Firmware execution halted." << std::endl;
}
};
// --- Top Level Assembly ---
int sc_main(int argc, char* argv[]) {
CPU cpu("cpu");
Router router("router");
RAM ram("ram");
Timer timer("timer");
// Bind CPU -> Router
cpu.socket.bind(router.target_socket);
// Bind Router -> Peripherals
router.init_ram.bind(ram.socket);
router.init_timer.bind(timer.socket);
std::cout << "Starting Virtual Platform Simulation..." << std::endl;
sc_core::sc_start();
return 0;
}Conclusion
Congratulations! You have built a fully functional Electronic System Level (ESL) Virtual Platform.
You constructed standard targets (RAM, Timer), a Doulos-compliant routing interconnect, and an initiator that acts as the bus master. Silicon companies use variations of this exact architecture to develop, test, and boot massive operating systems (like Linux or Android) months or years before the physical silicon is ever manufactured in a fab.
Comments and Corrections