The VP Router (Interconnect)
Building a TLM-2.0 interconnect to decode addresses and route transactions to peripherals using Doulos Simple Bus concepts.
Building a Virtual Platform: The Router
The Router (or Interconnect) is the central address decoder of any Virtual Platform. It sits between the initiators (CPUs, DMAs) and the targets (Memory, Peripherals).
When an initiator sends a read transaction for physical address 0x4000_0004, the Router must determine which target socket is mapped to that address, subtract the target's base address, forward the transaction, and properly restore the address on the return path.
The Complete Router Example
This example demonstrates a strictly LRM-compliant, Doulos Simple Bus-style TLM 2.0 Router. It connects to a mock CPU and routes to two generic memory blocks acting as RAM and Timer.
#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
#include <vector>
// 1. The Interconnect Router
SC_MODULE(VPRouter) {
// Single target socket facing the CPU
tlm_utils::simple_target_socket<VPRouter> target_socket;
// Array of initiator sockets facing the peripherals
std::vector<tlm_utils::simple_initiator_socket<VPRouter>*> init_sockets;
SC_CTOR(VPRouter) : target_socket("target_socket") {
target_socket.register_b_transport(this, &VPRouter::b_transport);
// Dynamically create two sockets for our 2 peripherals (RAM and Timer)
init_sockets.push_back(new tlm_utils::simple_initiator_socket<VPRouter>("init_ram"));
init_sockets.push_back(new tlm_utils::simple_initiator_socket<VPRouter>("init_timer"));
}
~VPRouter() {
for (auto* socket : init_sockets) delete socket;
}
private:
void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
sc_dt::uint64 original_address = trans.get_address();
sc_dt::uint64 local_address = 0;
int target_port = -1;
// Address Decoding Map
if (original_address >= 0x00000000 && original_address <= 0x0003FFFF) {
// RAM Region (256 KB)
target_port = 0;
local_address = original_address - 0x00000000;
}
else if (original_address >= 0x40000000 && original_address <= 0x40000FFF) {
// Timer Region (4 KB)
target_port = 1;
local_address = original_address - 0x40000000; // Subtract Base Address!
}
if (target_port != -1) {
// Modify the transaction address so the peripheral sees a local offset (0x0 to 0xFFF)
trans.set_address(local_address);
std::cout << "@" << sc_core::sc_time_stamp()
<< " [Router] Routing global addr 0x" << std::hex << original_address
<< " to port " << target_port << " as local addr 0x" << local_address << std::endl;
// Forward transaction
(*init_sockets[target_port])->b_transport(trans, delay);
// Restoring Address (CRITICAL RULE)
trans.set_address(original_address);
} else {
std::cout << "@" << sc_core::sc_time_stamp()
<< " [Router] ERROR: Address 0x" << std::hex << original_address
<< " is unmapped." << std::endl;
trans.set_response_status(tlm::TLM_ADDRESS_ERROR_RESPONSE);
}
// Add minimal routing delay
delay += sc_core::sc_time(2, sc_core::SC_NS);
}
};
// 2. Mock CPU Initiator
SC_MODULE(MockCPU) {
tlm_utils::simple_initiator_socket<MockCPU> socket;
SC_CTOR(MockCPU) : 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 = 0;
trans.set_command(tlm::TLM_READ_COMMAND);
trans.set_data_ptr(reinterpret_cast<unsigned char*>(&data));
trans.set_data_length(4);
// Send a transaction to the Timer base address
trans.set_address(0x40000000);
socket->b_transport(trans, delay);
// Send a transaction to an unmapped address
trans.set_address(0xFFFFFFFF);
socket->b_transport(trans, delay);
}
};
// 3. Mock Peripheral
SC_MODULE(MockPeripheral) {
tlm_utils::simple_target_socket<MockPeripheral> socket;
SC_CTOR(MockPeripheral) : socket("socket") {
socket.register_b_transport(this, &MockPeripheral::b_transport);
}
void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
trans.set_response_status(tlm::TLM_OK_RESPONSE);
}
};
// 4. Top Level
int sc_main(int argc, char* argv[]) {
MockCPU cpu("cpu");
VPRouter router("router");
MockPeripheral ram("ram");
MockPeripheral timer("timer");
// Bindings
cpu.socket.bind(router.target_socket);
(*router.init_sockets[0]).bind(ram.socket);
(*router.init_sockets[1]).bind(timer.socket);
sc_core::sc_start();
return 0;
}The Address Restoration Rule
A target peripheral should never care that it is mapped at 0x4000_0000. It only cares about its internal offsets (e.g., register 0x00 is Control, 0x04 is Counter). This allows the IP block to be perfectly reusable across any SoC project. The Router achieves this by passing original_address - base_address.
However, the CPU initiator expects the payload it sent to return unaltered. Therefore, after the target returns from b_transport, the router must immediately restore the original address (trans.set_address(original_address)). If it fails to do so, advanced initiators that inspect returning payloads to match transaction queues will crash or corrupt simulation memory.
Error Handling
If an address doesn't match any mapped region, the Router immediately assigns TLM_ADDRESS_ERROR_RESPONSE and returns. The initiator CPU will inspect this response and typically trigger a hardware exception (e.g., an ARM Data Abort) inside its Instruction Set Simulator (ISS).
Comments and Corrections