Chapter 4: TLM and Platforms

Non-Blocking TLM and Protocol Phases

How BEGIN_REQ, END_REQ, BEGIN_RESP, and END_RESP express pipelined transaction protocols.

How to Read This Lesson

For TLM, resist the temptation to picture pins. Picture a C++ function call carrying a transaction object, then add timing only where the architectural question needs it.

Blocking transport models a transaction as one call. Non-blocking transport models a transaction as a sequence of phases. According to the IEEE 1666-2023 LRM Section 14, non-blocking transport is used when protocol ordering, pipelining, and response timing matter.

Source and LRM Trail

For TLM, use the IEEE 1666 TLM clauses in Docs/LRMs/SystemC_LRM_1666-2023.pdf as the portable contract. Then inspect .codex-src/systemc/src/tlm_core/tlm_2: tlm_generic_payload, tlm_fw_transport_if, tlm_bw_transport_if, tlm_initiator_socket, tlm_target_socket, tlm_dmi, and tlm_quantumkeeper.

The Four-Phase Base Protocol

The TLM-2.0 base protocol commonly uses:

  • BEGIN_REQ: initiator begins a request
  • END_REQ: target accepts the request
  • BEGIN_RESP: target begins the response
  • END_RESP: initiator accepts the response

This lets a target accept a request now, freeing the initiator to do other work, and complete it later. The initiator calls the forward path (nb_transport_fw), and the target can call back on the backward path (nb_transport_bw). The split reflects protocol direction.

Return Status

Non-blocking transport returns a synchronization enum (tlm_sync_enum):

  • TLM_ACCEPTED: transaction accepted, more phases will arrive later via the backward/forward path.
  • TLM_UPDATED: phase and delay were updated immediately.
  • TLM_COMPLETED: transaction completed without more callbacks.

The status is part of the protocol. Ignoring it is a violation of the TLM-2.0 standard.

Payload Event Queue and Complete Example

tlm_utils::peq_with_get helps targets schedule transactions for later processing. A PEQ keeps the target from trying to complete everything inside the transport call.

Here is a fully compilable, standards-compliant AT (Approximately-Timed) non-blocking example utilizing the Payload Event Queue.

#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
#include <tlm_utils/peq_with_get.h>
 
using namespace sc_core;
 
SC_MODULE(NBTarget) {
  tlm_utils::simple_target_socket<NBTarget> socket{"socket"};
  tlm_utils::peq_with_get<tlm::tlm_generic_payload> peq;
 
  SC_CTOR(NBTarget) : peq("peq") {
    socket.register_nb_transport_fw(this, &NBTarget::nb_transport_fw);
    SC_THREAD(process_transactions);
  }
 
  tlm::tlm_sync_enum nb_transport_fw(tlm::tlm_generic_payload& trans,
                                     tlm::tlm_phase& phase, sc_time& delay) {
    if (phase == tlm::BEGIN_REQ) {
      // Accept request and schedule for processing
      peq.notify(trans, delay);
      phase = tlm::END_REQ;
      return tlm::TLM_UPDATED;
    }
    if (phase == tlm::END_RESP) {
      return tlm::TLM_COMPLETED;
    }
    return tlm::TLM_ACCEPTED;
  }
 
  void process_transactions() {
    while (true) {
      wait(peq.get_event()); // Wait for PEQ notification
      
      tlm::tlm_generic_payload* trans;
      while ((trans = peq.get_next_transaction()) != nullptr) {
        // Process scheduled work here.
        wait(10, SC_NS); // Simulated processing time
        
        trans->set_response_status(tlm::TLM_OK_RESPONSE);
        tlm::tlm_phase bw_phase = tlm::BEGIN_RESP;
        sc_time bw_delay = SC_ZERO_TIME;
        
        // Call back to initiator
        socket->nb_transport_bw(*trans, bw_phase, bw_delay);
      }
    }
  }
};
 
SC_MODULE(NBInitiator) {
  tlm_utils::simple_initiator_socket<NBInitiator> socket{"socket"};
 
  SC_CTOR(NBInitiator) {
    socket.register_nb_transport_bw(this, &NBInitiator::nb_transport_bw);
    SC_THREAD(run);
  }
 
  void run() {
    tlm::tlm_generic_payload trans;
    sc_time delay = SC_ZERO_TIME;
    tlm::tlm_phase phase = tlm::BEGIN_REQ;
    unsigned char data = 0xFF;
    
    trans.set_command(tlm::TLM_WRITE_COMMAND);
    trans.set_address(0x100);
    trans.set_data_ptr(&data);
    trans.set_data_length(1);
 
    // Send request
    tlm::tlm_sync_enum status = socket->nb_transport_fw(trans, phase, delay);
    
    if (status == tlm::TLM_UPDATED && phase == tlm::END_REQ) {
        std::cout << "Target accepted request at " << sc_time_stamp() << "\n";
    }
    wait(); // Yield
  }
 
  tlm::tlm_sync_enum nb_transport_bw(tlm::tlm_generic_payload& trans,
                                     tlm::tlm_phase& phase, sc_time& delay) {
    if (phase == tlm::BEGIN_RESP) {
      std::cout << "Initiator received response at " << sc_time_stamp() << "\n";
      phase = tlm::END_RESP;
      return tlm::TLM_COMPLETED;
    }
    return tlm::TLM_ACCEPTED;
  }
};
 
int sc_main(int argc, char* argv[]) {
  NBInitiator init("init");
  NBTarget tgt("tgt");
  
  init.socket.bind(tgt.socket);
  sc_start(100, SC_NS);
  return 0;
}

When to Avoid Non-Blocking TLM

Do not use non-blocking transport just because it feels more professional. Use it when the model needs:

  • multiple outstanding transactions
  • pipelined request/response behavior
  • approximately timed modeling
  • timing points visible to both initiator and target
  • protocol-level backpressure

For simple memory-mapped platforms doing sequential fetch-decode-execute loops, blocking transport (b_transport) is often better and significantly faster.

Under the Hood: The TLM Base Protocol State Machine

The non-blocking transport (nb_transport_fw and nb_transport_bw) implements a state machine using the tlm_phase enum. In src/tlm_core/tlm_2/tlm_generic_payload/tlm_phase.h, the base protocol defines four standard phases: BEGIN_REQ, END_REQ, BEGIN_RESP, END_RESP. When a master calls nb_transport_fw(trans, phase, delay), it passes the phase by reference. The target can update the phase and return TLM_UPDATED, or return TLM_ACCEPTED indicating it will send the response later via nb_transport_bw. This entirely avoids SystemC wait() calls, achieving high simulation speeds while accurately modeling pipelined bus architectures like AXI.

Comments and Corrections