SystemC+TLM2.0基础学习
单纯的systemc很好懂,最朴素的systemc语法就是拿cpp当rtl写:
https://www.cnblogs.com/sasasatori/p/17865550.html
https://www.cnblogs.com/sasasatori/p/17899997.html
但实际上这样做并不好,写的level太贴近rtl了,那还不如直接写rtl。实际上做systemc这个阶段的els,更多是做“有关键的时序和完整的功能的抽象模型”,即不需要准确的接口&依赖时序实现的功能,而是把整个硬件抽象成几个大的function block,每个block之间采用“事务传输”替代硬件的接口连线,每个block内部采用行为实现+耗时描述替代依赖时序实现的行为。举个例子,比如cpu对存储器发起访问,那么tlm会直接将访问的行为抽象为存储器的读写“事务”,而非通过具体的wen,addr,din,dout这些物理信号接口实现。同时,在读写事务里也不需要写清楚物理信号的各种时序关系,只需要清楚通信的开始到结束的时间,然后直接描述这个时间间隔即可。
具体来说,在tlm2.0中,一次“事务”被抽象为一个socket,“事务”的发起者为initiator,“事务”的响应方为target,initiator socket和target socket之间完成信息交互。

这里的信息交互可以分为两种,分别为阻塞传输(b_transport)和非阻塞传输(nb_transport),一般前者用于简单的function modeling,后者可用于复杂的performance modeling。
对于阻塞传输来说:需要在target中为target socket注册b_transport函数,实际执行时,initiator通过调用b_transport将信息发送给target。b_transport函数的接口包含了tlm_generic_payload和sc_time,前者是一种标准化的信息标识格式,后者则是基于systemc仿真器的时间。
对于非阻塞传输来说:需要在initiator和target中都构建nb_transport函数,在initiator中为initiator socket 注册nb_transport_bw,在target中为target socket注册nb_transport_fw,除了阻塞传输中包含的tlm_generic_payload和sc_time,nb_transport还需要引入额外的tlm_phase来标识非阻塞传输中的状态。默认的tlm_phase有四个:BEGIN_REQ,END_REQ,BEGIN_RESP,END_RESP。如果交互协议较为复杂的话,可以通过TLM_DECLARE_EXTENTED_PHASE来进一步增加phase。
接下来对两种传输方式的具体代码给出示例 & 解析:
阻塞传输:
#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;
}
上面的代码共创建了三个类,MyInitiator,MyTarget和MyTop,每个类都继承sc_module,等效于用宏SC_MODULE进行声明。
MyInitiator的构造函数首先需要通过宏SC_HAS_PROCESS声明,构造函数中需要通过宏SC_THREAD创建主线程MainThread,另外类成员m_initiator_port(即initiator的socket接口)在构造时初始化(使用tlm提供的simple_initiator_socket类)。主线程MainThread的主要功能为每个周期都发起一次传输事务,一方面通过对sc_time的操作更新时间,另一方面创建新的传输信息tlm_generic_payload,并其更新地址。随后将信息与时间,通过调用simple_initiator_socket类型提供的b_transport方法进行发出。
MyTarget的构造过程与MyInitiator类似,但在构造时初始化的是m_target_port(即target的socket接口,使用tlm提供的simple_target_socket类型),构造函数中调用了simple_target_socket提供的register_b_transport方法,并基于该方法在接收到发送过来的信息时,自动调用函数RecvReqFunc来处理initiator发送来的payload,解包时间以及发送的地址。
MyTop在构造时初始化MyInitiator和MyTarget各一个,并通过调用simple_initiator_socket提供的bind方法与simple_target_socket完成连接。
在sc_main中例化MyTop,并启动一个20ns的仿真。
上面的代码的执行结果为:
[0 s] call b_transport 10001 delay cycle 1
[1 ns] RecvReqFunc 10001
[2 ns] call b_transport 10002 delay cycle 2
[4 ns] RecvReqFunc 10002
[5 ns] call b_transport 10003 delay cycle 3
[8 ns] RecvReqFunc 10003
[9 ns] call b_transport 10004 delay cycle 4
[13 ns] RecvReqFunc 10004
[14 ns] call b_transport 10005 delay cycle 5
[19 ns] RecvReqFunc 10005
对时序行为进行一个详细的解析,initiator本身发起一次传输需要耗时1ns,并且在等待一次传输完成之前,传输过程会阻塞住。target接收过程的delay由initiator传输的t_delay控制,是一个随着发送次数逐次增加的延迟。因此最后整体的时序变成了:0s initiator发起第一次传输;1ns target等待1ns收到该传输,第一次传输结束;2ns initiator等待1ns后发起第二次传输;4ns target等待2ns收到该传输,第二次传输结束;5ns initiator等待1ns后发起第三次传输;8ns target等待3ns收到该传输,第三次传输结束;9ns initiator等待1ns后发起第四次传输;13ns target等待4ns收到该传输,第四次传输结束;14ns initiator等待1ns后发起第五次传输;19ns target等待5ns收到该传输,第五次传输结束;20ns 仿真时间达到,仿真结束。
非阻塞传输:
#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 until 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;
}
非阻塞传输的代码比阻塞传输的代码显然复杂了不少。
上面的代码共创建了三个类,MyInitiator_Nb,MyTarget_Nb和MyTop。
MyInitiator_Nb的构造函数初始化了成员m_initiator_port和m_test_peq,m_initiator_port仍然是initiator的socket,而m_test_peq则是一个特殊的工具,用于管理信息传输过程中的时序(使用了tlm提供的peq_with_get类)。构造函数中调用了simple_initiator_socket的simple_register_nb_transport_bw方法,并基于此进一步调用函数nb_transport_bw_func来处理接收到的反馈信息,并创建了SendReqThread和SendEndRespThread两个线程。
如之前所述,非阻塞传输可以分为BEGIN_REQ,END_REQ,BEGIN_RESP,END_RESP四个phase。其先后顺序如下图所示:

BEGIN_REQ由initiator发起,target响应,之后再target发起END_REQ,initiator进行响应。随后target发起BEGIN_RESP,initiator进行响应,最后由initiator发起END_RESP,target进行响应。至此完整的4个phase的非阻塞传输过程结束。
nb_transport_bw_func主要是对target需要响应的END_REQ和BEGIN_RESP进行处理,打印对应的时间戳和payload信息地址,并且在END_REQ phase中,需要调用类成员m_slv_end_req_evt(sc_event类)的notify方法向SendReqThread线程发送通知;在BEGIN_RESP phase中,需要调用m_test_peq的notify方法,这一方法的一般以一个tlm_generic_payload变量和一个sc_time变量为入参,目的是规定该payload在未来的特定时间被处理,在sc_time变量缺省的情况下,默认是SC_ZERO_TIME,即需要立刻处理。
SendReqThread和SendEndRespThread则分别对应initiator需要发起的BEGIN_REQ和END_RESP两个phase。
SendReqThread需要打包tlm_generic_payload,sc_time,以及tlm_phase,并调用simple_initiator_socket的nb_transport_fw方法进行发送,随后一直等待到接收target发起的END_REQ,nb_transport_bw_func触发m_slv_end_req_evt。随后再延迟1ns之后,initiator侧的SendReqThread的一轮执行结束,开始发起第二轮的BEGIN_REQ。
对于SendEndRespThread,需要注意的是在构造函数中声明了其敏感列表为m_test_peq.get_event(),即在nb_transport_bw_func中,m_test_peq在BEGIN_RESP phase中被触发时,SendEndRespThread会才会继续执行,否则会一直wait。这里调用了m_test_peq的get_next_transaction方法,确保m_test_peq中的全部传输都解包完成,才算一轮通信的完整结束。此外需要注意的是,此处不能够引入任何延迟或者事件等待,否则会导致获取的传输时间不精确。
MyTarget_Nb的构造函数调用了simple_target_socket的simple_register_nb_transport_bw并创建了两个线程 MainThread与BeginRespThread,并在构造时初始化成员m_target_port作为target的socket。类成员中还包含了两个fifo,分别是m_req_fifo和m_resp_fifo(均为sc_fifo类),sc_fifo的特性是可以进行跨线程的读写操作,写满的情况下会阻塞后续的写操作,直到fifo里的值被读出;读空的情况下会阻塞后续的读操作,直到fifo里有值被写入。
simple_register_nb_transport_bw调用函数nb_transport_fw_func,用来处理target对BEGIN_REQ和END_RESP的响应,除了常规的payload和sc_time解包之外,BEGIN_REQ的phase中,还通过调用m_req_fifo的write方法进行了payload的写入。
MainThread处理了target需要发起的END_REQ,首先通过m_req_fifo的read方法将initiator发送来的payload从m_req_fifo中读出,然后进行了解包,执行发送,再通过m_resp_fifo的write方法将payload进行写入,随后等待1ns。
BeginRespThread处理了target需要发起的START_RESP,首先通过m_resp_fifo的方法读取payload,然后进行解包,执行发送,随后等待1ns。
MyTop在构造时初始化MyInitiator_Nb和MyTarget_Nb各一个,并通过调用simple_initiator_socket提供的bind方法与simple_target_socket完成连接。
在sc_main中例化MyTop,并启动一个20ns的仿真。
通过一个表格来对上述过程进行总结:

上面的代码的执行结果为:
my_top_module_nb.init [0 s] call nb_transport_fw, BEGIN_REQ phase, addr=0x10001 delay cycle 1
my_top_module_nb.targ [1 ns] nb_transport_fw_func recv BEGIN_REQ phase, addr=0x10001
my_top_module_nb.targ [1 ns] call nb_transport_bw, END_REQ phase, addr=0x10001
my_top_module_nb.init [1 ns] nb_transport_bw_func recv END_REQ phase, addr=0x10001
my_top_module_nb.targ [1 ns] call nb_transport_bw, BEGIN_RESP phase, addr=0x10001
my_top_module_nb.init [1 ns] nb_transport_bw_func recv BEGIN_RESP phase, addr=0x10001
my_top_module_nb.init [1 ns] call nb_transport_fw, END_RESP phase, addr=0x10001
my_top_module_nb.targ [1 ns] nb_transport_fw_func recv END_RESP phase, addr=0x10001
my_top_module_nb.init [2 ns] call nb_transport_fw, BEGIN_REQ phase, addr=0x10002 delay cycle 2
my_top_module_nb.targ [4 ns] nb_transport_fw_func recv BEGIN_REQ phase, addr=0x10002
my_top_module_nb.targ [4 ns] call nb_transport_bw, END_REQ phase, addr=0x10002
my_top_module_nb.init [4 ns] nb_transport_bw_func recv END_REQ phase, addr=0x10002
my_top_module_nb.targ [4 ns] call nb_transport_bw, BEGIN_RESP phase, addr=0x10002
my_top_module_nb.init [4 ns] nb_transport_bw_func recv BEGIN_RESP phase, addr=0x10002
my_top_module_nb.init [4 ns] call nb_transport_fw, END_RESP phase, addr=0x10002
my_top_module_nb.targ [4 ns] nb_transport_fw_func recv END_RESP phase, addr=0x10002
my_top_module_nb.init [5 ns] call nb_transport_fw, BEGIN_REQ phase, addr=0x10003 delay cycle 3
my_top_module_nb.targ [8 ns] nb_transport_fw_func recv BEGIN_REQ phase, addr=0x10003
my_top_module_nb.targ [8 ns] call nb_transport_bw, END_REQ phase, addr=0x10003
my_top_module_nb.init [8 ns] nb_transport_bw_func recv END_REQ phase, addr=0x10003
my_top_module_nb.targ [8 ns] call nb_transport_bw, BEGIN_RESP phase, addr=0x10003
my_top_module_nb.init [8 ns] nb_transport_bw_func recv BEGIN_RESP phase, addr=0x10003
my_top_module_nb.init [8 ns] call nb_transport_fw, END_RESP phase, addr=0x10003
my_top_module_nb.targ [8 ns] nb_transport_fw_func recv END_RESP phase, addr=0x10003
my_top_module_nb.init [9 ns] call nb_transport_fw, BEGIN_REQ phase, addr=0x10004 delay cycle 4
my_top_module_nb.targ [13 ns] nb_transport_fw_func recv BEGIN_REQ phase, addr=0x10004
my_top_module_nb.targ [13 ns] call nb_transport_bw, END_REQ phase, addr=0x10004
my_top_module_nb.init [13 ns] nb_transport_bw_func recv END_REQ phase, addr=0x10004
my_top_module_nb.targ [13 ns] call nb_transport_bw, BEGIN_RESP phase, addr=0x10004
my_top_module_nb.init [13 ns] nb_transport_bw_func recv BEGIN_RESP phase, addr=0x10004
my_top_module_nb.init [13 ns] call nb_transport_fw, END_RESP phase, addr=0x10004
my_top_module_nb.targ [13 ns] nb_transport_fw_func recv END_RESP phase, addr=0x10004
my_top_module_nb.init [14 ns] call nb_transport_fw, BEGIN_REQ phase, addr=0x10005 delay cycle 5
my_top_module_nb.targ [19 ns] nb_transport_fw_func recv BEGIN_REQ phase, addr=0x10005
my_top_module_nb.targ [19 ns] call nb_transport_bw, END_REQ phase, addr=0x10005
my_top_module_nb.init [19 ns] nb_transport_bw_func recv END_REQ phase, addr=0x10005
my_top_module_nb.targ [19 ns] call nb_transport_bw, BEGIN_RESP phase, addr=0x10005
my_top_module_nb.init [19 ns] nb_transport_bw_func recv BEGIN_RESP phase, addr=0x10005
my_top_module_nb.init [19 ns] call nb_transport_fw, END_RESP phase, addr=0x10005
my_top_module_nb.targ [19 ns] nb_transport_fw_func recv END_RESP phase, addr=0x10005
接下来我们结合上述的分析,来详细解释一下这里的运行结果,首先很明显的,在一轮完整的非阻塞传输过程中,分别经历了之前所述的:initiator发起BEGIN_REQ,target响应BEGIN_REQ;target发起END_REQ,initiator响应END_REQ;target发起BEGIN_RESP,initiator响应BEGIN_RESP;initiator发起END_RESP,target响应END_RESP。
每轮非阻塞传输过程,在initiator发起BEGIN_REQ的SendReqThread延迟自增,target的nb_transport_fw_func中执行对延迟的等待。除此之外,每个操作之间没有引入额外的延时。
最后针对tlm2.0给原始的systemc引入的新特性做一个总结,首先需要明确的是tlm2.0并没有任何对systemc功能上的取代,主要的目的是提升systemc的抽象建模能力。对于原始的systemc来说,一个很大的痛点是针对复杂的数据传输行为(例如各种各样的总线协议),使用systemc原生的sc_port机制进行实现是一个费时费力的过程。所谓的tlm即transaction level modeling将这种复杂的数据传输行为进行了高效的抽象封装,化简为阻塞传输和非阻塞传输两种类型。更重要的事情是可以将cpp的tlm2.0和systemverilog的uvm利用socket进行无缝的衔接,从而大大便利验证的过程。根据需求,实际上tlm2.0也可以和sc_port机制联合使用。
https://blog.csdn.net/xiaohaolaoda/article/details/131117987
https://blog.csdn.net/zgcjaxj/article/details/126512927
https://blog.csdn.net/ocarvb/article/details/108450311
https://www.shincbm.com/SystemC/docs/tlm/doxygen/html/a02600.html
https://blog.csdn.net/zgcjaxj/article/details/104877270

浙公网安备 33010602011771号