在systemC建模中,对于initiator socket 和 target socket 之间的payload信息交互,有blocking 和 non blocking transport的区别。Spec中有如下的解释。
The blocking transport interface is only able to model the start and end of a transaction, with the transaction being completed within a single function call. The non-blocking transport interface allows a transaction to be broken down into multiple timing points, and typically requires multiple function calls for a single transaction.
一般情况下,blocking transport用于function module建模,non-blocking transport 一般用于 performance module 建模。
对于blocking transport ,slave Module中,需要在构造函数中 对target socket 注册b_transport 函数。实际执行中,Master Module通过initiator socket 调用 b_transport 函数 来将payload信息传递给slave Module。调用 b_transport 函数结束,表明本次 request 成功发送给slave,并且slave成功将 response信息传递给了master。
对于non-blocking transport,slave Module中,需要在构造函数中 对target socket 注册nb_transport_fw 函数; master Module中,需要在构造函数中 对initiator socket 注册nb_transport_bw 函数。tlm phase用于标识non-blocking transport过程中的状态。tlm_core/tlm_2/tlm_generic_payload/tlm_phase.h 中定义了四种最基本的phase : BEGIN_REQ=1, END_REQ, BEGIN_RESP, END_RESP。下图是一个模拟两个channel (request 和 response)的协议接口间交互示意图。同时,nb_transport_bw中需要对接收的phase为END_REQ和BEGIN_RESP 的情况进行分类处理;nb_transport_fw则需要对接收到的phase为BEGIN_REQ和END_RESP进行处理。
一般情况下,在target不阻塞时,target收到BEGIN_REQ phase后,当拍就会调用initiator的nb_transport_bw,发送END_REQ phase。如果需要模拟request方向target 对initiator的反压,则可以在收到BEGIN_REQ phase后,wait block logic,阻塞解除后,再发送END_REQ phase。一般在 nb_transport_fw和 nb_transport_bw函数中不加显式或隐式的wait。
如果master和slave之间的交互协议较为复杂,可以通过TLM_DECLARE_EXTENDED_PHASE 来增加 phase。比如要模拟AXI 协议的5个channel,则可以新增 BEGIN_ { AXI_AW / AXI_AR / AXI_W / AXI_R / AXI_B } 和 END_ { AXI_AW / AXI_AR / AXI_W / AXI_R / AXI_B } 这 10个phase。
一个 blocking transport的示例如下。需要注意,slave Module (代码中为class MyTarget) 中注册的 b_transport函数(代码中为 RecvReqFunc)必须为这样的形式void (tlm::tlm_generic_payload &payload, sc_core::sc_time &delay_time),两个参数必须是payload & 和 sc_time &。
// execute:
// g++ -g -Wall -lsystemc -m64 -pthread main.cpp
// -L/$(your systemc path)/lib-linux64
// -I/$(your systemc path)/include -I/$(your systemc
// path)/src/tlm_utils -o sim
#include <systemc>
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
class MyInitiator : public sc_core::sc_module
{
public:
SC_HAS_PROCESS(MyInitiator); // must have this line
explicit MyInitiator(sc_core::sc_module_name name)
: sc_core::sc_module(name),
m_initiator_port("initiator_port")
{
SC_THREAD(MainThread);
}
void MainThread()
{
int t_cycle_cnt = 0;
sc_core::sc_time t_delay = sc_core::SC_ZERO_TIME;
while (1)
{
t_cycle_cnt++;
t_delay = sc_core::sc_time(t_cycle_cnt, sc_core::SC_NS);
tlm::tlm_generic_payload *t_payload = new tlm::tlm_generic_payload();
t_payload->set_address(0x10000 + t_cycle_cnt);
std::cout << "\033[34m [" << sc_core::sc_time_stamp() << "]"
<< " call b_transport " << std::hex << t_payload->get_address()
<< " delay cycle " << t_cycle_cnt
<< " \033[0m" << std::endl;
m_initiator_port->b_transport(*t_payload, t_delay); // here will call MyTarget::RecvReqFunc
wait(1, sc_core::SC_NS);
}
}
tlm_utils::simple_initiator_socket<MyInitiator> m_initiator_port;
};
class MyTarget : public sc_core::sc_module
{
public:
SC_HAS_PROCESS(MyTarget); // must have this line
explicit MyTarget(sc_core::sc_module_name name)
: sc_core::sc_module(name), m_target_port("target_port")
{
m_target_port.register_b_transport(this, &MyTarget::RecvReqFunc);
}
//void at_target_2_phase::b_transport(tlm::tlm_generic_payload &payload, sc_core::sc_time &delay_time)
void RecvReqFunc(tlm::tlm_generic_payload &payload, sc_core::sc_time &delay)
{
wait(delay);
std::cout << "\033[35m [" << sc_core::sc_time_stamp() << "]"
<< " RecvReqFunc " << std::hex << payload.get_address() << " \033[0m" << std::endl;
}
tlm_utils::simple_target_socket<MyTarget> m_target_port;
};
class MyTop : public sc_core::sc_module
{
public:
SC_HAS_PROCESS(MyTop);
explicit MyTop(sc_core::sc_module_name name)
: sc_core::sc_module(name),
m_init("init"),
m_target("target")
{
m_init.m_initiator_port.bind(m_target.m_target_port);
}
MyInitiator m_init;
MyTarget m_target;
};
int sc_main(int argc, char **argv)
{
MyTop m_top_module("my_top_module");
sc_core::sc_start(20, sc_core::SC_NS);
return 0;
}
nb_transport 一个示例如下。
// execute:
// g++ -g -Wall -lsystemc -m64 -pthread main.cpp
// -L/$(your systemc path)/lib-linux64
// -I/$(your systemc path)/include -I/$(your systemc
// path)/src/tlm_utils -o sim
#include <systemc>
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
#include "tlm_utils/peq_with_get.h"
class MyInitiator_Nb : public sc_core::sc_module
{
public:
SC_HAS_PROCESS(MyInitiator_Nb); // must have this line
explicit MyInitiator_Nb(sc_core::sc_module_name name)
: sc_core::sc_module(name),
m_initiator_port("initiator_port"), m_test_peq("peq")
{
m_initiator_port.register_nb_transport_bw(this, &MyInitiator_Nb::nb_transport_bw_func);
SC_THREAD(SendReqThread);
SC_THREAD(SendEndRespThread);
sensitive << m_test_peq.get_event(); // sensitive event list
}
void SendReqThread()
{
int t_cycle_cnt = 0;
tlm::tlm_phase t_phase = tlm::BEGIN_REQ;
sc_core::sc_time t_delay = sc_core::SC_ZERO_TIME;
while (1)
{
t_cycle_cnt++;
t_delay = sc_core::sc_time(t_cycle_cnt, sc_core::SC_NS);
tlm::tlm_generic_payload *t_payload = new tlm::tlm_generic_payload();
t_payload->set_address(0x10000 + t_cycle_cnt);
std::cout << this->name() << "\033[34m [" << sc_core::sc_time_stamp() << "]"
<< " call nb_transport_fw, BEGIN_REQ phase, addr=0x" << std::hex << t_payload->get_address()
<< " delay cycle " << t_cycle_cnt
<< " \033[0m" << std::endl;
m_initiator_port->nb_transport_fw(*t_payload, t_phase, t_delay);
/***********************************/
// here can add your block logic,
// basic block logic: need wait recv END_REQ phase
wait(m_slv_end_req_evt);
/***********************************/
wait(1, sc_core::SC_NS);
}
}
tlm::tlm_sync_enum nb_transport_bw_func(tlm::tlm_generic_payload &payload,
tlm::tlm_phase &phase, sc_core::sc_time &delay)
{
switch (phase)
{
case tlm::END_REQ:
std::cout << this->name()
<< "\033[35m [" << sc_core::sc_time_stamp() << "]"
<< " nb_transport_bw_func recv END_REQ phase, addr=0x" << std::hex << payload.get_address()
<< " \033[0m" << std::endl;
m_slv_end_req_evt.notify();
break;
case tlm::BEGIN_RESP:
std::cout << this->name() << "\033[35m [" << sc_core::sc_time_stamp() << "]"
<< " nb_transport_bw_func recv BEGIN_RESP phase, addr=0x" << std::hex << payload.get_address()
<< " \033[0m" << std::endl;
m_test_peq.notify(payload);
break;
default:
assert(false);
}
return tlm::TLM_ACCEPTED;
}
void SendEndRespThread()
{
tlm::tlm_generic_payload *t_get = NULL;
tlm::tlm_phase t_phase = tlm::END_RESP;
sc_core::sc_time t_delay = sc_core::SC_ZERO_TIME;
while (1)
{
wait(); // wait sensitive event list
// here must get next transaction entil t_get is NULL
while ((t_get = m_test_peq.get_next_transaction()) != NULL)
{
std::cout << this->name() << "\033[34m [" << sc_core::sc_time_stamp() << "]"
<< " call nb_transport_fw, END_RESP phase, addr=0x" << std::hex << t_get->get_address()
<< " \033[0m" << std::endl;
m_initiator_port->nb_transport_fw(*t_get, t_phase, t_delay);
t_get = NULL;
// in this block, must can't wait any event or cycle delay
// if not, the time of transaction obtained will not accurate
}
}
}
sc_core::sc_event m_slv_end_req_evt;
tlm_utils::simple_initiator_socket<MyInitiator_Nb> m_initiator_port;
tlm_utils::peq_with_get<tlm::tlm_generic_payload> m_test_peq;
};
class MyTarget_Nb : public sc_core::sc_module
{
public:
SC_HAS_PROCESS(MyTarget_Nb); // must have this line
explicit MyTarget_Nb(sc_core::sc_module_name name)
: sc_core::sc_module(name), m_target_port("target_port_nb")
{
m_target_port.register_nb_transport_fw(this, &MyTarget_Nb::nb_transport_fw_func);
SC_THREAD(MainThread);
SC_THREAD(BeginRespThread);
}
tlm::tlm_sync_enum nb_transport_fw_func(tlm::tlm_generic_payload &payload,
tlm::tlm_phase &phase, sc_core::sc_time &delay)
{
wait(delay); // here can add wait logic, also can delete wait & ignore the delay
switch (phase)
{
case tlm::BEGIN_REQ:
m_req_fifo.write(&payload);
std::cout << this->name()
<< "\033[35m [" << sc_core::sc_time_stamp() << "]"
<< " nb_transport_fw_func recv BEGIN_REQ phase, addr=0x" << std::hex << payload.get_address()
<< " \033[0m" << std::endl;
break;
case tlm::END_RESP:
std::cout << this->name() << "\033[35m [" << sc_core::sc_time_stamp() << "]"
<< " nb_transport_fw_func recv END_RESP phase, addr=0x" << std::hex << payload.get_address()
<< " \033[0m\n"
<< std::endl;
break;
default:
assert(false);
}
return tlm::TLM_ACCEPTED;
}
void MainThread()
{
tlm::tlm_phase t_phase = tlm::END_REQ;
sc_core::sc_time t_delay = sc_core::SC_ZERO_TIME;
while (1)
{
tlm::tlm_generic_payload *t_payload = m_req_fifo.read();
/***********************************/
// here can add your block logic, for back pressure use
/***********************************/
std::cout << this->name() << "\033[38m [" << sc_core::sc_time_stamp() << "]"
<< " call nb_transport_bw, END_REQ phase, addr=0x" << std::hex << t_payload->get_address()
<< " \033[0m" << std::endl;
m_target_port->nb_transport_bw(*t_payload, t_phase, t_delay);
/***********************************/
// after END_REQ phase, indicate slave recv req successfully,
// then handle req, return BEGIN_RESP
/***********************************/
m_resp_fifo.write(t_payload);
wait(1, sc_core::SC_NS);
}
}
void BeginRespThread()
{
tlm::tlm_phase t_phase = tlm::BEGIN_RESP;
// sc_core::sc_time t_delay = sc_core::SC_ZERO_TIME;
sc_core::sc_time t_delay = sc_core::sc_time(1, sc_core::SC_NS);
while (1)
{
tlm::tlm_generic_payload *t_payload = m_resp_fifo.read();
std::cout << this->name() << "\033[38m [" << sc_core::sc_time_stamp() << "]"
<< " call nb_transport_bw, BEGIN_RESP phase, addr=0x" << std::hex << t_payload->get_address()
<< " \033[0m" << std::endl;
m_target_port->nb_transport_bw(*t_payload, t_phase, t_delay);
wait(1, sc_core::SC_NS);
}
}
tlm_utils::simple_target_socket<MyTarget_Nb> m_target_port;
sc_core::sc_fifo<tlm::tlm_generic_payload *> m_req_fifo;
sc_core::sc_fifo<tlm::tlm_generic_payload *> m_resp_fifo;
};
class MyTop : public sc_core::sc_module
{
public:
SC_HAS_PROCESS(MyTop);
explicit MyTop(sc_core::sc_module_name name)
: sc_core::sc_module(name),
m_init("init"),
m_target("targ")
{
m_init.m_initiator_port.bind(m_target.m_target_port);
}
MyInitiator_Nb m_init;
MyTarget_Nb m_target;
};
int sc_main(int argc, char **argv)
{
MyTop m_top_module("my_top_module_nb");
sc_core::sc_start(20, sc_core::SC_NS);
return 0;
}