LRM Bridge: TLM Reference Map
Blocking transport, non-blocking transport, generic payload, phases, sockets, DMI, debug transport, and temporal decoupling.
How to Read This Lesson
This lesson is an LRM bridge. We translate standard language into the questions you actually ask while debugging and reviewing models.
Transaction Level Modeling (TLM 2.0), defined in the IEEE 1666 standard, allows models to communicate through abstract C++ data structures (tlm_generic_payload) rather than individual pin toggles. This can dramatically increase simulation speed when the abstraction is chosen well.
Now let's look at how the Accellera TLM kernel defines these constructs under the hood.
Source and LRM Trail
This chapter is the LRM bridge. The primary reference is Docs/LRMs/SystemC_LRM_1666-2023.pdf; the secondary reference is .codex-src/systemc. Read the LRM first for the rule, then read the source to understand why the rule produces the behavior you see in a debugger.
Complete TLM 2.0 Base Protocol Example
This sc_main example demonstrates the essential components of TLM: the generic payload, socket binding, blocking transport, and correct target response mechanics.
#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
// 1. TLM Initiator
SC_MODULE(TLM_Initiator) {
tlm_utils::simple_initiator_socket<TLM_Initiator> socket;
SC_CTOR(TLM_Initiator) : 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 = 0x12345678;
// Populate the Generic Payload (Strict LRM rules)
trans.set_command(tlm::TLM_WRITE_COMMAND);
trans.set_address(0x1000);
trans.set_data_ptr(reinterpret_cast<unsigned char*>(&data));
trans.set_data_length(4);
trans.set_streaming_width(4);
trans.set_byte_enable_ptr(0); // 0 means all bytes enabled
trans.set_dmi_allowed(false);
trans.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE);
std::cout << "@" << sc_core::sc_time_stamp() << " [Init] Sending WRITE to 0x1000" << std::endl;
// Blocking Transport Call
socket->b_transport(trans, delay);
// Check Response
if (trans.is_response_error()) {
SC_REPORT_ERROR("TLM", "Transaction returned with error response!");
} else {
std::cout << "@" << sc_core::sc_time_stamp() << " [Init] WRITE Success. Delay returned: "
<< delay << std::endl;
wait(delay); // Yield to the scheduler to account for annotated delay
}
}
};
// 2. TLM Target
SC_MODULE(TLM_Target) {
tlm_utils::simple_target_socket<TLM_Target> socket;
SC_CTOR(TLM_Target) : socket("socket") {
socket.register_b_transport(this, &TLM_Target::b_transport);
}
void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
if (trans.get_address() == 0x1000 && trans.get_command() == tlm::TLM_WRITE_COMMAND) {
// Annotate processing delay
delay += sc_core::sc_time(10, sc_core::SC_NS);
// MANDATORY: Target must explicitly set the response status
trans.set_response_status(tlm::TLM_OK_RESPONSE);
} else {
trans.set_response_status(tlm::TLM_ADDRESS_ERROR_RESPONSE);
}
}
};
int sc_main(int argc, char* argv[]) {
TLM_Initiator init("init");
TLM_Target target("target");
init.socket.bind(target.socket);
sc_core::sc_start();
return 0;
}Transport Interfaces & Implementations
Blocking Transport (b_transport)
Used for Loosely Timed (LT) modeling. Under the Hood: b_transport is simply a purely virtual C++ method declared in tlm_fw_transport_if. When an initiator calls it, because of sc_port proxy resolution, the execution jumps directly into the target's function implementation within the very same OS thread context. No event queues are involved.
Non-Blocking Transport (nb_transport_fw / nb_transport_bw)
Used for Approximately Timed (AT) models. Splits a transaction into explicit hardware phases (e.g., BEGIN_REQ, END_REQ, BEGIN_RESP, END_RESP) using the tlm_phase enumeration class. This supports deep pipelining and complex interconnect arbitration but is significantly slower to simulate because each phase boundary often requires a sc_event::notify() and a context switch.
Advanced Interfaces
Direct Memory Interface (DMI)
DMI allows an initiator to request a direct C++ pointer to a target's memory array.
Under the Hood: An initiator calls get_direct_mem_ptr which populates a tlm_dmi struct. This struct holds unsigned char* dmi_ptr, bounding addresses dmi_start_address/dmi_end_address, and dmi_read_allowed/dmi_write_allowed enums. If granted, the initiator bypasses the b_transport socket entirely, performing native C++ array dereferencing (dmi_ptr[offset]). The LRM requires targets to use invalidate_direct_mem_ptr to revoke this pointer if the memory map changes.
Debug Transport (transport_dbg)
Debug transport is a side-effect-free access path. It is used exclusively by debuggers, GDB stubs, or memory preloaders. A debug read to a FIFO must not pop the FIFO (you must peek instead), and it does not advance simulation time.
Temporal Decoupling
Temporal decoupling allows initiators to run ahead of global simulation time locally within a "quantum".
Under the Hood: The Accellera TLM library provides the tlm_quantumkeeper class. Initiators accumulate delays internally (m_local_time) without calling wait(). The keeper continuously calculates the difference between m_local_time and sc_core::sc_time_stamp(). Only when this difference exceeds a globally defined tlm_global_quantum does the keeper call wait() to sync back up with the discrete-event scheduler. This drastically reduces context switches, offering the highest possible simulation speed.
Standard and Source Deep Dive: Port Binding
Port binding is the topological glue of a SystemC model. The IEEE 1666-2023 LRM Sections 4.2.1 (Elaboration) and Section 6.11-6.13 (Ports, Exports, Interfaces) rigidly define how structural connections are made and verified.
Inside the Accellera Source: sc_port_b and sc_port_registry
In src/sysc/communication/sc_port.h/cpp, all specialized sc_port<IF> classes derive from a non-template base class sc_port_b.
When you declare sc_port<BusIf> bus{"bus"};, the constructor ultimately calls sc_simcontext::get_port_registry()->insert(this).
The sc_port_registry (located in src/sysc/kernel/sc_simcontext.cpp) is the global list of every port in the simulation.
When you write cpu.bus.bind(subsystem.target); in your C++ code, you are invoking the bind() method on sc_port. However, this does not immediately resolve the C++ pointer! Instead, the port simply stores a generic pointer to the bound object in an internal array (because a port can be bound to multiple channels if the port's N parameter is > 1).
The Elaboration Phase: complete_binding()
The real magic happens when sc_start() is called.
Before simulation begins, sc_start() invokes sc_simcontext::elaborate(), which ultimately calls sc_port_registry::complete_binding().
If you trace sysc/kernel/sc_simcontext.cpp, you will see complete_binding() iterate over every single port in the design. For each port:
- It traverses the binding tree. If Port A is bound to Port B, and Port B is bound to Channel C, it recursively walks from A -> B -> C to find the actual
sc_interfaceimplementation. - Type Checking: It uses C++ RTTI (
dynamic_cast) to verify that the target object actually implements the interface required by the port.// Abstract representation of the kernel's check: sc_interface* target_if = dynamic_cast<sc_interface*>(bound_object); if (!target_if) { SC_REPORT_ERROR("Port binding failed: interface mismatch"); } - It resolves the final interface pointer and stores it directly inside the port's
m_interfacepointer array.
Zero-Overhead Simulation Dispatch
Why delay pointer resolution until complete_binding()? Because once elaboration finishes, the port has an absolute, direct C++ pointer to the implementing channel.
In src/sysc/communication/sc_port.h, the overloaded operator-> is extraordinarily simple:
template <class IF>
inline IF* sc_port<IF>::operator -> () {
return m_interface;
}During simulation, when a thread executes bus->write(0x10, data);, there are no map lookups, no string comparisons, and no routing tables. It is exactly equivalent to a direct C++ virtual function call on the channel object.
Comments and Corrections