Chapter 12: Virtual Platform Construction

The VP Peripherals

Building RAM and Hardware Timer memory-mapped peripherals using TLM 2.0 Simple Target Sockets.

Building a Virtual Platform: The Peripherals

In the previous step, our Router forwarded transactions to specific sockets based on physical memory addresses. Now, we build the targets on the other side of those sockets: a Memory block (RAM) and a Hardware Timer.

Complete Peripheral Example

This complete, runnable example demonstrates the exact TLM 2.0 implementations for a standard RAM array and a register-based Hardware Timer.

#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
 
// 1. A Simple TLM RAM Peripheral
class RAM_Peripheral : public sc_core::sc_module {
public:
    tlm_utils::simple_target_socket<RAM_Peripheral> socket;
    
    SC_HAS_PROCESS(RAM_Peripheral);
    RAM_Peripheral(sc_core::sc_module_name name, unsigned int size_bytes) 
        : sc_core::sc_module(name), size(size_bytes) {
        
        memory = new unsigned char[size];
        memset(memory, 0, size);
        socket.register_b_transport(this, &RAM_Peripheral::b_transport);
    }
    ~RAM_Peripheral() { delete[] memory; }
 
private:
    unsigned char* memory;
    unsigned int size;
 
    void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
        tlm::tlm_command cmd = trans.get_command();
        sc_dt::uint64    adr = trans.get_address();
        unsigned char*   ptr = trans.get_data_ptr();
        unsigned int     len = trans.get_data_length();
 
        // Check if the transaction exceeds the boundaries of this specific RAM
        if (adr + len > size) {
            trans.set_response_status(tlm::TLM_ADDRESS_ERROR_RESPONSE);
            return;
        }
 
        if (cmd == tlm::TLM_READ_COMMAND) {
            memcpy(ptr, &memory[adr], len);
        } else if (cmd == tlm::TLM_WRITE_COMMAND) {
            memcpy(&memory[adr], ptr, len);
        }
 
        // Advance simulation time to model access latency
        delay += sc_core::sc_time(10, sc_core::SC_NS);
        trans.set_response_status(tlm::TLM_OK_RESPONSE);
    }
};
 
// 2. A Register-Based Hardware Timer Peripheral
class Timer_Peripheral : public sc_core::sc_module {
public:
    tlm_utils::simple_target_socket<Timer_Peripheral> socket;
 
    SC_HAS_PROCESS(Timer_Peripheral);
    Timer_Peripheral(sc_core::sc_module_name name) : sc_core::sc_module(name) {
        socket.register_b_transport(this, &Timer_Peripheral::b_transport);
        SC_THREAD(timer_tick_thread);
    }
 
private:
    bool running = false;
    unsigned int counter = 0;
    sc_core::sc_event start_event;
 
    void timer_tick_thread() {
        while(true) {
            // Wait for software to enable the timer
            if (!running) wait(start_event);
            
            // Wait 1 microsecond hardware tick
            wait(1, sc_core::SC_US); 
            if (running) counter++;
        }
    }
 
    void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
        sc_dt::uint64 adr = trans.get_address();
        unsigned char* ptr = trans.get_data_ptr();
 
        if (trans.get_command() == tlm::TLM_WRITE_COMMAND) {
            if (adr == 0x00) { 
                // Offset 0x00: Control Register (Write 1 to Start)
                uint32_t val;
                memcpy(&val, ptr, sizeof(val));
                running = (val != 0);
                if (running) {
                    std::cout << "@" << sc_core::sc_time_stamp() << " [Timer] Started." << std::endl;
                    start_event.notify();
                } else {
                    std::cout << "@" << sc_core::sc_time_stamp() << " [Timer] Stopped." << std::endl;
                }
            }
        } else if (trans.get_command() == tlm::TLM_READ_COMMAND) {
            if (adr == 0x04) { 
                // Offset 0x04: Counter Register (Read Only)
                memcpy(ptr, &counter, sizeof(counter));
                std::cout << "@" << sc_core::sc_time_stamp() << " [Timer] CPU Read Counter: " << counter << std::endl;
            }
        }
 
        delay += sc_core::sc_time(5, sc_core::SC_NS);
        trans.set_response_status(tlm::TLM_OK_RESPONSE);
    }
};
 
// 3. Mock Initiator to Drive the Timer
SC_MODULE(CPU_Driver) {
    tlm_utils::simple_initiator_socket<CPU_Driver> socket;
    SC_CTOR(CPU_Driver) : socket("socket") { SC_THREAD(run); }
 
    void run() {
        tlm::tlm_generic_payload trans;
        sc_core::sc_time delay = sc_core::SC_ZERO_TIME;
        uint32_t data = 1; // Start command
        
        // 1. Write 1 to Timer Control Register (Offset 0x00)
        trans.set_command(tlm::TLM_WRITE_COMMAND);
        trans.set_address(0x00);
        trans.set_data_ptr(reinterpret_cast<unsigned char*>(&data));
        trans.set_data_length(4);
        socket->b_transport(trans, delay);
        wait(delay); 
 
        // 2. Wait for 5 microseconds of simulation time to pass
        wait(5, sc_core::SC_US);
 
        // 3. Read from Timer Counter Register (Offset 0x04)
        delay = sc_core::SC_ZERO_TIME;
        trans.set_command(tlm::TLM_READ_COMMAND);
        trans.set_address(0x04);
        socket->b_transport(trans, delay);
        wait(delay);
    }
};
 
int sc_main(int argc, char* argv[]) {
    CPU_Driver cpu("cpu");
    Timer_Peripheral timer("timer");
    
    // Direct binding for demonstration
    cpu.socket.bind(timer.socket);
    
    sc_core::sc_start();
    return 0;
}

Architectural Design

  • The RAM Module simply allocates memory and uses memcpy to move data directly. It serves as a generic bulk storage endpoint.
  • The Hardware Timer Module is modeled around Memory-Mapped Registers. Instead of moving bulk memory, its b_transport acts as an if/else switch statement, triggering specific internal C++ events or threads when a particular offset (e.g., 0x00) is written to.

These paradigms form the foundational building blocks of every single peripheral (UARTs, DMA Controllers, Interrupt Controllers) inside a Virtual Platform.

Comments and Corrections