SystemC - TLM
Transaction-Level Modeling 2.0 (TLM-2.0) is a modeling methodology defined in IEEE 1666-2023.
Instead of communicating through individual signals at every clock cycle, modules exchange complete transactions (read/write requests) using C++ function calls.
This raises the abstraction level, dramatically increasing simulation speed and enabling early software development before RTL is available.
1. Introduce
TLM-2.0 replaces pin-level signal connections with high-level function calls carrying a tlm_generic_payload transaction object. An initiator (e.g., a CPU model) calls transport functions on a target (e.g., a memory) through typed sockets and a bus/router.
Why use TLM?
- Simulation speed: 10x–100x faster than signal-level or RTL simulation.
- Software development: A processor model can run firmware against a bus and memory model long before hardware is taped out.
- IP reuse: Standardized sockets and payloads allow mixing IP blocks from different vendors.
- Virtual prototyping: Enables full system-on-chip (SoC) simulation.
2. Transaction-Level Modeling, Use Cases, and Abstraction

-
TLM models systems at a high abstraction level, focusing on communication (transactions) instead of signal-level details.
-
TLM-2.0 separates:
- Interfaces (APIs): Standard communication (e.g., b_transport, nb_transport)
- Coding styles: Flexible, use-case dependent
-
A TLM model requires multiple communicating processes; single-threaded models are not TLM.
3. Coding Styles
A coding style is a set of programming idioms that work well together, not a specific abstraction level or API. Including:
- Untimed: No
wait()calls. Pure functional behavior, no time passes. Used for early algorithmic exploration. - Loosely-Timed (LT): Uses blocking
b_transport()with ansc_timedelay annotation. Fast simulation, low timing detail. Best for software development. - Approximately-Timed (AT): Uses non-blocking
nb_transport_fw/bw()with explicit protocol phases (BEGIN_REQ,END_REQ,BEGIN_RESP,END_RESP). Models pipeline stages and bus protocols. Best for hardware architecture analysis.# Loosely-Timed (LT): initiator --b_transport(payload, delay)--> target (one blocking call, time annotated via delay) # Route 1. Initiator calls `socket->b_transport(trans, delay)` (blocking). 2. Bus/router decodes the address and forwards to the appropriate target. 3. Target executes the read/write, sets `response_status = TLM_OK_RESPONSE`. 4. Call returns; initiator advances local time by `delay`. # Approximately-Timed (AT): initiator --nb_transport_fw(payload, BEGIN_REQ, t)--> target target --nb_transport_bw(payload, END_REQ, t) --> initiator target --nb_transport_bw(payload, BEGIN_RESP, t)--> initiator initiator --nb_transport_fw(payload, END_RESP, t) --> target # Route 1. Initiator calls `socket->nb_transport_fw(trans, phase=BEGIN_REQ, t)`. 2. Bus/router or target returns `TLM_ACCEPTED` (pipeline continues). 3. Target sends `nb_transport_bw(trans, BEGIN_RESP, t)` when data is ready. 4. Initiator consumes the response and calls `nb_transport_fw(trans, END_RESP, t)`.
Timing Annotation: represents timing behavior by passing a delay value along with a transaction.
// Initiator ---------------------------
sc_time delay = SC_ZERO_TIME;
socket->b_transport(trans, delay); // send transaction
wait(delay); // apply accumulated delay afterward
// Target ------------------------------
void b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) {
delay += sc_time(10, SC_NS); // annotate 10 ns processing time
}
Temporal Decoupling is an optimization technique where a process delays synchronization with the SystemC kernel to reduce simulation overhead.
- Each initiator maintains a
local timeoffset (quantum) - Transactions execute without blocking on
wait()each time - Synchronization happens only when the
local timeexceeds theglobal quantum need_sync()checks if thelocal timehas exceeded theglobal quantum.tlm_utils::tlm_quantumkeeper qk; qk.set_global_quantum(sc_time(100, SC_NS)); // quantum budget = 100 ns void cpu_thread() { qk.reset(); // local time = 0 while (true) { sc_time delay = SC_ZERO_TIME; // Transaction 1 socket->b_transport(trans, delay); // target adds 20 ns -> delay = 20 ns // accumulate local time instead of waiting qk.inc(delay); // local time = 20 ns if (qk.need_sync()) { /* */ } // 20 ns < 100 ns -> keep going // Transaction 2 socket->b_transport(trans, delay); // target adds 40 ns -> delay = 40 ns qk.inc(delay); // local time = 60 ns if (qk.need_sync()) { /* */ } // 60 ns < 100 ns -> keep going // Transaction 3 socket->b_transport(trans, delay); // target adds 50 ns -> delay = 50 ns qk.inc(delay); // local time = 110 ns if (qk.need_sync()) { // 110 ns > 100 ns -> SYNC wait(qk.get_local_time()); // advance kernel to 110 ns qk.reset(); // local time = 0 } } } // Without temporal decoupling, every b_transport() call would require a kernel context switch. // With it, hundreds of transactions can execute before a single sync. void cpu_thread() { while (true) { sc_time delay = SC_ZERO_TIME; socket->b_transport(trans, delay); // target adds 20 ns -> delay = 20 ns wait(delay); // SYNC with kernel immediately socket->b_transport(trans, delay); // target adds 40 ns -> delay = 40 ns wait(delay); // SYNC with kernel immediately socket->b_transport(trans, delay); // target adds 50 ns -> delay = 50 ns wait(delay); // SYNC with kernel immediately } }
3.1. Loosely-Timed
Loosely-Timed style uses timing annotation and temporal decoupling together.
- Target annotates delay instead of calling
wait() - Initiator accumulates local time via quantum keeper
- Syncs with kernel only when quantum is exceeded
3.2. Approximately-timed
Approximately-timed style uses explicit protocol phases and non-blocking transport — no wait() allowed, multiple transactions can be in-flight simultaneously.
4. Initiators, Targets, Sockets, and Transaction Bridges
| Object | Description |
|---|---|
| Initiator | A module that initiates transactions (e.g., a CPU or DMA). Owns an tlm_initiator_socket. |
| Target | A module that responds to transactions (e.g., memory, peripheral). Owns a tlm_target_socket. |
| Socket | Typed connector that binds initiators to targets and carries the transport interface. tlm_initiator_socket<> and tlm_target_socket<>. |
Generic Payload (tlm_generic_payload) |
The standard transaction object containing address, command, data pointer, byte enables, response status, and optional extensions. |
Phase (tlm_phase) |
Marks the state of a non-blocking (AT) transaction: BEGIN_REQ, END_REQ, BEGIN_RESP, END_RESP. |
| Path / Interface | tlm_fw_transport_if (forward: initiator –> target) and tlm_bw_transport_if (backward: target –> initiator). |
tlm_generic_payload members
| Member | Accessor | Description |
|---|---|---|
| Command | set_command(TLM_READ_COMMAND) |
Read or write |
| Address | set_address(addr) |
Target address (64-bit) |
| Data pointer | set_data_ptr(ptr) |
Pointer to read/write buffer |
| Data length | set_data_length(len) |
Number of bytes |
| Byte enables | set_byte_enable_ptr(be) |
Per-byte write mask |
| Response status | set_response_status(TLM_OK_RESPONSE) |
Result of the transaction |
| DMI allowed | set_dmi_allowed(true) |
Hint that DMI path is available |
| Extensions | set_extension(ext) |
User-defined metadata |
4.1. Initiator / Target

A transaction object is a data structure passed between initiators and targets using function calls.
AnInitiator is a module that can create new transaction objects and pass them on by calling a method using the core interface.
A Target is a module that acts as the final destination for a transaction.
An Interconnect component is a module that has both a target socket and an initiator socket.
The roles of initiator, interconnect, and target can change dynamically.
4.2. Path
Forward path: A transaction object is created by an initiator and passed to other modules.
Return path: The transaction object is returned automatically.
Backward path: Other modules send the transaction object back by calling a certain method.
4.3. Socket

Sockets are used to pass transactions between initiators and targets in TLM. It combines a port with an export.
Initiator sockethas aportfor theforward pathand anexportfor thebackward path.Target socket: has aexportfor theforward pathand anportfor thebackward path.- In TLM, sockets are connected using
bind()oroperator().
5. Interface (TLM-2.0 Core Interfaces )
An interface defines how transactions are communicated between components (initiator and target). There are three types of interface:
- Transport interface: are the primary interfaces, used to transport transactions between initiators, targets and interconnect components.
- Direct memory interface (DMI): providing direct access to an area of memory owned by a target, has F/BW interface
- Debug transport interface: providing debug access to an area of memory owned by a target, only has FW interface.
5.1. Direct Memory Interface (DMI)
DMI bypasses the transport interface, initiator accesses target memory directly without sending transactions. Has both forward and backward paths.
// Initiator: request DMI access
tlm::tlm_dmi dmi_data;
if (socket->get_direct_mem_ptr(trans, dmi_data)) {
// read/write directly to target memory, no b_transport() needed
unsigned char* ptr = dmi_data.get_dmi_ptr();
memcpy(ptr, data, length);
}
// Target grant DMI access
bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) {
dmi_data.set_dmi_ptr(memory); // expose memory pointer
dmi_data.set_start_address(0x0);
dmi_data.set_end_address(0xFFFF);
return true; // grant access
}
// Target, invalidate DMI if memory changes (backward path)
socket->invalidate_direct_mem_ptr(0x0, 0xFFFF);
5.2. Debug Transport Interface
DTI provides debug access to target memory without affecting simulation time, no wait(), no delay. Forward path only.
// Initiator — request debug access
unsigned int bytes_read = socket->transport_dbg(trans);
// Target — handle debug request
unsigned int transport_dbg(tlm::tlm_generic_payload& trans) {
unsigned char* ptr = trans.get_data_ptr();
sc_dt::uint64 addr = trans.get_address();
unsigned int len = trans.get_data_length();
memcpy(ptr, &memory[addr], len); // copy memory, no timing side effects
return len;
}
6. Blocking Transport Interface
Blocking Transport Interface is a communication mechanism where the initiator sends a transaction and waits until the target completes it before continuing (like a function call)
- Uses only the forward path (no separate backward path).
- A complete transaction is executed within a single function call (
b_transport). - The initiator is blocked until the target finishes processing.
Roles:
- The
b_transportmethod may callwait(). - It must NOT be called from an
SC_METHODprocess. Timing Model:- Each transaction has only two timing points:
- START: when
b_transportis called - END: when
b_transportreturns
- START: when
- The
delayparameter is used for timing annotation. It represents relative time, not absolute simulation time.
- Each transaction has only two timing points:
Transaction Object:
- The transaction is passed by reference.
- It can be reused across multiple calls.
- It should not contain timing information.All timing must be modeled using the
delayargument.
6.1. Blocking Transport

- The blocking transport (
b_transport) may return immediately or after some simulation time. - While one call is waiting, another thread in the initiator can call
b_transportconcurrently.
6.2. Temporal Decoupling

- A temporally decoupled initiator can run ahead of simulation time using a local time offset.
- It passes this offset as the delay argument to
b_transport. - Both initiator and target may increase this delay to model time.
- The effective completion time = simulation time + delay.
- However, simulation time only advances when the initiator calls
wait() - If
b_transportitself callswait(), the local time must reset to 0.
6.3. Time quantum

- A temporally decoupled initiator may run ahead of simulation time, but only up to a fixed time quantum.
- The initiator accumulates local time using the delay variable.
- When the local time offset exceeds the quantum, the initiator must synchronize by calling
wait(delay). - This synchronization allows other initiators to execute and catch up in simulation time.
- After synchronization, the local time offset is reset to zero.
- Within a quantum, transactions execute sequentially without advancing simulation time.
- The SystemC scheduler does not track local time; it only advances on
wait()calls.
7. Non-blocking Transport Interface
Non-blocking Transport Interface is a communication mechanism where the initiator sends a transaction and continues immediately without waiting — the target responds later via a backward path. (like sending an email)
- A transaction is break downed into multiple
phrases transition. - Each phrases transition is associated with a
timing point - Each
call/returnfrom thenon-blocking transport methodmaycorrespond toaphrase transition - By restricting the number of timing points to two, it is possible to use the nb transport interface with the LT code style (not recommend)
- It uses all paths, and interfaces
tlm_fw_nonblocking_transport_ifvstlm_bw_nonblocking_transport_if
7.1. Paths nb_transport_fw and nb_transport_bw
A path is the direction in which a transaction travels between the initiator and target.
nb_transportmethods shall not callwait- Several successive calls to
nb_transport_fwfrom the same process could each initiate separate transactions without having to wait for the first transaction to complete - The
final timing point of a transactionmay be marked by a call to or areturnfrom Snb_transporton either the forward path or the backward path.
7.1. Transaction Argument
A transaction argument is the single object passed between initiator and target that carries all transaction data :address, data, command, response status.
- 1 transaction = 1 object (while it is active)
- That object moves back and forth between modules
- Everyone touches the same object, not copies
=> Don’t overwrite data too early or reuse object before transaction finishes
7.2. Phrase Argument
A phase argument tracks the current state of a transaction as it moves between initiator and target.
BEGIN_REQ --> END_REQ --> BEGIN_RESP --> END_RESP
- phase = control signal for “who can touch the transaction and when”, including:
- BEGIN_REQ: initiator controls
- END_REQ: target takes over
- BEGIN_RESP: target controls
- END_RESP: initiator finishes
7.3. <tlm_sync_enum> Return Value
- TLM_ACCEPTED:
- The callee must not modify the transaction object, phase, or time argument.
- Indicates that the return path is not used.
- The caller typically needs to wait/yield for a future response.
- TLM_UPDATED:
- Indicates that the return path is used.
- The protocol state has advanced (phase transition occurred).
- The caller must inspect updated arguments and react accordingly.
- TLM_COMPLETED:The callee has completed the transaction (at this socket).
- The transaction object and time may be modified.
- The phase is undefined and should be ignored.
- No further nb_transport calls are allowed for this transaction on this socket.
- Completion does not guarantee success, need to check response status.
8. Examples
8.1 Connection
Module and socket:
#include <systemc>
#include <tlm>
using namespace sc_core;
// Initiator Module with socket
class InitiatorModule : public sc_module,
public tlm::tlm_bw_transport_if<> { // 1. inherit the backward interface
public:
tlm::tlm_initiator_socket<> ini_socket; // 2. declare a socket
SC_CTOR(InitiatorModule) {
ini_socket(*this); // 3. connect module to socket
}
};
// Target Module with socket
class TargetModule : public sc_module, public tlm::tlm_fw_transport_if<> {
tlm::tlm_target_socket<> target_socket;
SC_CTOR(TargetModule) { target_socket(*this); }
};
Socket and another socket
#include <systemc>
#include <tlm>
using namespace sc_core;
using namespace tlm;
class TargetModule : public sc_module,
virtual public tlm::tlm_fw_transport_if<> {
public:
tlm::tlm_target_socket<> target_socket;
SC_CTOR(TargetModule) { target_socket(*this); }
// Methods for forward path <tlm_fw_transport_if>
tlm_sync_enum nb_transport_fw(tlm_generic_payload& trans, tlm_phase& phase,
sc_time& delay) override {
// @TODO:
return TLM_COMPLETED;
}
bool get_direct_mem_ptr(tlm_generic_payload&, tlm_dmi& dmi_data) override {
// @TODO:
return true;
}
unsigned int transport_dbg(tlm_generic_payload& trans) override {
// @TODO:
return 0;
}
void b_transport(tlm::tlm_generic_payload& trans,
sc_core::sc_time& t) override {
// @TODO:
}
};
class InitiatorModule: public sc_module,
public tlm::tlm_bw_transport_if<> {
public:
tlm::tlm_initiator_socket<> initiator_socket;
// target module connect to
TargetModule* target_module;
SC_CTOR(InitiatorModule) {
initiator_socket.bind(*this);
target_module = new TargetModule("target_m");
// initiator socket connect to garget socket
initiator_socket.bind(target_module->target_socket);
}
};
8.2. Communication Through Generic Payload
Initiator and target communicate by calling functions. All functions must be defined in both modules before communication begins.
#include <systemc>
#include <tlm>
using namespace sc_core;
using namespace tlm;
class TargetModule : public sc_module,
virtual public tlm::tlm_fw_transport_if<> {
public:
tlm::tlm_target_socket<> target_socket;
SC_CTOR(TargetModule) { target_socket(*this); }
// Methods for forward path <tlm_fw_transport_if>
// used in non-blocking transaction
tlm_sync_enum nb_transport_fw(tlm_generic_payload& trans, tlm_phase& phase,
sc_time& delay) override {
// @TODO:
return TLM_COMPLETED;
}
// used in Direct memory interface
bool get_direct_mem_ptr(tlm_generic_payload&, tlm_dmi& dmi_data) override {
// @TODO:
return true;
}
// used in Debug memory interface
unsigned int transport_dbg(tlm_generic_payload& trans) override {
// @TODO:
return 0;
}
// used in blocking transaction
void b_transport(tlm::tlm_generic_payload& trans,
sc_core::sc_time& t) override {
// @TODO:
}
};
// Combined interface required by socket: inherit the backward interface
class InitiatorModule : public sc_module, public tlm::tlm_bw_transport_if<> {
public:
// initiator socket, protocol type defaults to base protocol
tlm::tlm_initiator_socket<> initiator_socket;
// target module connect to
TargetModule* target_module;
SC_CTOR(InitiatorModule) {
// initiator socket bound to module itself
// initiator_socket(*this);
initiator_socket.bind(*this);
target_module = new TargetModule("target_m");
// initiator socket connect to garget socket
initiator_socket.bind(target_module->target_socket);
}
// main function for non-blocking transaction
tlm_sync_enum nb_transport_bw(tlm::tlm_generic_payload& trans,
tlm::tlm_phase& phase,
sc_core::sc_time& t) override {
// @TODO
}
// used in direct memory interface
void invalidate_direct_mem_ptr(sc_dt::uint64 start_range,
sc_dt::uint64 end_range) override {
// @TODO
}
};
Blocking interface: Initiator module > Target module (Initiator socket > Target socket) Step 1: Create transaction object (tlm::tlm_generic_payload object) Step 2: Set values or information for transaction object by using methods of tlm::tlm_generic_payload Step 3: From Initiator socket call b_transport function