Non-Blocking TLM and Protocol Phases
How BEGIN_REQ, END_REQ, BEGIN_RESP, and END_RESP express pipelined transaction protocols.
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.
The Four-Phase Base Protocol
The TLM-2.0 base protocol commonly uses:
BEGIN_REQ: initiator begins a requestEND_REQ: target accepts the requestBEGIN_RESP: target begins the responseEND_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.
Comments and Corrections