Chapter 4: TLM and Platforms

Generic Payload Extensions

How to attach protocol-specific metadata to TLM transactions without replacing tlm_generic_payload.

tlm_generic_payload covers common transaction fields, but real platforms often need extra metadata: privilege level, cache attributes, security state, transaction ID, burst information, debug flags, or initiator identity.

TLM extensions let you attach that metadata without inventing a new payload class, adhering to the IEEE 1666 LRM extension mechanism.

Extension Shape and Complete Example

An extension derives from tlm::tlm_extension<T>. The clone and copy functions matter because payloads can be reused, copied, or managed by memory managers.

Here is a complete, fully compilable example demonstrating how to declare, attach, and read a privilege extension across a blocking transport call.

#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
 
using namespace sc_core;
 
// 1. Define the Extension
struct PrivilegeExtension : public tlm::tlm_extension<PrivilegeExtension> {
  bool privileged = false;
 
  tlm_extension_base* clone() const override {
    return new PrivilegeExtension(*this);
  }
 
  void copy_from(tlm_extension_base const& ext) override {
    privileged = static_cast<PrivilegeExtension const&>(ext).privileged;
  }
};
 
// 2. The Target reading the Extension
SC_MODULE(TargetDevice) {
  tlm_utils::simple_target_socket<TargetDevice> socket{"socket"};
 
  SC_CTOR(TargetDevice) {
    socket.register_b_transport(this, &TargetDevice::b_transport);
  }
 
  void b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) {
    PrivilegeExtension* priv_ext = nullptr;
    trans.get_extension(priv_ext); // Attempt to retrieve the extension
 
    if (priv_ext && priv_ext->privileged) {
      std::cout << "[TARGET] Privileged access granted.\n";
      trans.set_response_status(tlm::TLM_OK_RESPONSE);
    } else {
      std::cout << "[TARGET] Error: Unprivileged access denied!\n";
      trans.set_response_status(tlm::TLM_COMMAND_ERROR_RESPONSE);
    }
  }
};
 
// 3. The Initiator attaching the Extension
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;
    
    // Allocate and configure the extension
    PrivilegeExtension* priv_ext = new PrivilegeExtension();
    priv_ext->privileged = true;
    trans.set_extension(priv_ext); // Attach to transaction
    
    trans.set_command(tlm::TLM_READ_COMMAND);
    socket->b_transport(trans, delay);
    
    // Always clear extensions if managing memory manually, or if reusing payloads
    trans.clear_extension(priv_ext);
    delete priv_ext; 
    
    wait(delay);
  }
};
 
int sc_main(int argc, char* argv[]) {
  CpuInitiator cpu("cpu");
  TargetDevice tgt("tgt");
  cpu.socket.bind(tgt.socket);
  sc_start();
  return 0;
}

For long-lived transactions or non-blocking payloads, use an ownership strategy that matches the payload lifetime. Do not attach a pointer to a stack object if the transaction may outlive the call (as is the case with non-blocking PEQ logic).

Targets and Absent Extensions

Targets should treat absent extensions deliberately. Either define a default behavior (e.g. assume unprivileged) or report an error for protocols that require the metadata.

Extension Hygiene

Extensions are powerful, but they can make models implicit. Keep them documented:

  • Name every extension after the protocol concept it represents.
  • Define who creates it, who reads it, and who owns it (memory management is key).
  • State whether it is optional or required.
  • Include it in transaction logs when relevant.

Good extension design lets a generic payload stay generic while preserving protocol-specific meaning.

Comments and Corrections