Chapter 5: Source Internals

Source Deep Dive: TLM Socket Internals

How simple initiator and target sockets wrap interfaces, callbacks, binding, and transport dispatch.

TLM sockets are convenient because they hide a lot of interface plumbing. To understand them, we must peel back the convenience layer. According to the IEEE 1666-2023 LRM Section 12, a socket is structurally composed of an sc_port and an sc_export bound to forward and backward transport interfaces.

The Interfaces Underneath

TLM defines core transport interfaces like tlm_fw_transport_if and tlm_bw_transport_if. A utility target socket (simple_target_socket) implements the forward interface internally and dispatches calls to your registered member function.

To see exactly what the socket does under the hood, here is a complete, fully compilable example where the target manually implements the raw tlm_fw_transport_if and uses standard sc_export to receive transactions, completely bypassing simple_target_socket for the target side!

#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
 
using namespace sc_core;
 
// 1. The Raw Core Interface Target (What simple_target_socket hides)
class RawMemoryTarget : public sc_module, public tlm::tlm_fw_transport_if<> {
public:
  // Expose the interface outward
  sc_export<tlm::tlm_fw_transport_if<>> target_export{"target_export"};
 
  SC_CTOR(RawMemoryTarget) {
    // Bind the export to 'this' module, which implements the interface
    target_export.bind(*this);
  }
 
  // --- Implement tlm_fw_transport_if ---
 
  void b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) override {
    std::cout << "[RawTarget] b_transport called with address 0x" 
              << std::hex << trans.get_address() << "\n";
    trans.set_response_status(tlm::TLM_OK_RESPONSE);
  }
 
  tlm::tlm_sync_enum nb_transport_fw(tlm::tlm_generic_payload& trans, 
                                     tlm::tlm_phase& phase, sc_time& delay) override {
    return tlm::TLM_COMPLETED;
  }
 
  bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, 
                          tlm::tlm_dmi& dmi_data) override {
    return false;
  }
 
  unsigned int transport_dbg(tlm::tlm_generic_payload& trans) override {
    return 0;
  }
};
 
// 2. Standard Utility Initiator
SC_MODULE(CpuInitiator) {
  tlm_utils::simple_initiator_socket<CpuInitiator> socket{"socket"};
 
  SC_CTOR(CpuInitiator) { SC_THREAD(run); }
 
  void run() {
    tlm::tlm_generic_payload trans;
    sc_time delay = SC_ZERO_TIME;
    
    trans.set_command(tlm::TLM_WRITE_COMMAND);
    trans.set_address(0x1000);
    
    // operator-> on the socket accesses the bound tlm_fw_transport_if!
    socket->b_transport(trans, delay);
  }
};
 
int sc_main(int argc, char* argv[]) {
  CpuInitiator cpu("cpu");
  RawMemoryTarget mem("mem");
 
  // The initiator socket contains an sc_port which binds to the sc_export
  cpu.socket.bind(mem.target_export);
 
  sc_start();
  return 0;
}

When you use target.register_b_transport(...), the utility socket creates a small internal channel object exactly like the RawMemoryTarget above, storing your object pointer and member function pointer, and dispatches the raw b_transport interface call into your callback.

Initiator Socket

The initiator socket acts like a typed access point to the target's transport interface. When you call socket->b_transport(...), that operator call reaches the bound target interface. The initiator does not need to know whether the target is a raw module implementing the interface or a utility socket doing callback dispatch.

Generic Payload Lifetime

Sockets dispatch payload references. They do not magically copy all transaction data. That means payload lifetime and extension ownership matter.

For blocking transport, a stack payload is fine (as shown in the example). For non-blocking transport with deferred completion, the transaction may outlive the call. Then you need a disciplined ownership strategy, often with a memory manager.

Why Socket Internals Matter

When a TLM model fails, the bug is often one of these:

  • socket not bound
  • callback not registered
  • payload reused too early
  • response status not set
  • delay not updated consistently

Understanding sockets as standard sc_port binding plus callback dispatch makes those bugs much easier to diagnose.

Comments and Corrections