TLM2.0
在数字芯片验证SystemVerilog UVM中TLM 2.0Transaction-Level Modeling的通用净核类Generic Payload和套接字Socket是实现高效组件通信的核心机制。TLM2.0将模块间事务的传递抽象出发起者、目标、套接字和桥发起者通过发起者套接字向目标套接字发送消息目标从目标套接字接收消息并处理。1 通用净核类 (Generic Payload - tlm_generic_payload)为了在不同模块间实现标准化通信TLM2.0定义了一个通用的事务包 tlm_generic_payload它几乎包含了总线事务所需的所有信息m_address地址64位。m_command操作类型读 TLM_READ_COMMAND、写TLM_WRITE_COMMAND 或忽略。m_data数据阵列byte 数组。m_length传输的数据长度。m_response_status返回状态是否成功、是否发生错误如TLM_OK_RESPONSE。m_byte_enable字节使能用于部分写操作。通用净核类 (Generic Payload)是 TLM 2.0 统一的数据语言。大家都用同一种格式的数据包组件之间才能无缝对接。2 套接字 (Sockets)Socket 是TLM2.0最重要的概念之一它是 TLM 2.0 统一的硬件接口。它在内部封装了 port、export 和 imp极大地简化了模块间接口的连接实现了“即插即用”。主要分为tlm_initiator_socket由事务发起方Initiator使用。通常留在 Driver 或 Master 组件中用来发起事务。tlm_target_socket由事务接收方Target使用。通常留在 Monitor、Scoreboard 或 Slave 模型中用来接收并处理事务。每个socket又根据传输方式分为阻塞b和非阻塞nb类型。套接字将发送端口Port 和 接收端口Export 组合成了一个双向的通道。它既能发送请求Request又能同时接收回应Response。3 核心接口阻塞与非阻塞传输阻塞传输 (Blocking Transport)通过 b_transport 函数实现。调用会阻塞发起方线程直到目标方完成事务处理并返回。适合对时序要求不高的快速模型。非阻塞传输 (Non-blocking Transport)通过 nb_transport_fw (前向) 和 nb_transport_bw (后向) 函数实现。将一次完整事务拆分为请求和响应多个阶段通过返回值 tlm_sync_enum 和引用参数 phase 来同步状态。适合需要精细时序建模的场景。非阻塞传输nb_transport_fw (Forward, 前向)由发起端Initiator 调用把消息传给目标端Target。nb_transport_bw (Backward, 后向)由目标端Target 调用把消息传回发起端Initiator。举例一个 MasterInitiator 和一个 SlaveTarget之间要传输一个 tlm_generic_payload简称 trans。在传输时需要用到两个辅助参数phase阶段类型为 tlm_phase用来标记当前走到哪一步了。delay延迟时序控制。1Phase1发送请求 (Master - Slave)Master 准备好了数据把 phase 设置为 BEGIN_REQ开始请求然后调用 nb_transport_fw 发送。// Master 内部代码tlm_phase phaseBEGIN_REQ;uvm_tlm_time delaynew(delay,0);// Master发送请求statusinitiator_socket.nb_transport_fw(trans,phase,delay);2Phase2确认请求 (Slave - Master)Slave 收到请求后发现是 BEGIN_REQ。Slave 把 phase 改为 END_REQ结束请求通过 nb_transport_bw 发回给 Master。// Slave 内部代码在 nb_transport_fw 的实现中或者稍后通过另一个 threadphaseEND_REQ;// Slave 确认请求statustarget_socket.nb_transport_bw(trans,phase,delay);此时Master 收到 END_REQ知道 Slave 已经成功接单Master 就可以去准备下一个数据或者等待了。3Phase3发送响应 (Slave - Master)Slave 内部的存储器真正完成了写操作或者准备好了读数据。它把 phase 改为 BEGIN_RESP开始响应再次通过 nb_transport_bw 发给 Master。// Slave 内部代码trans.set_response_status(TLM_OK_RESPONSE);// 写入成功phaseBEGIN_RESP;// Slave 发送响应statustarget_socket.nb_transport_bw(trans,phase,delay);4Phase4确认响应 (Master - Slave)Master 收到 BEGIN_RESP检查 trans 的状态发现成功了。Master 把 phase 改为 END_RESP结束响应通过 nb_transport_fw 传给 Slave宣告这次传输彻底结束。// Master 内部代码在 nb_transport_bw 的实现中if(phaseBEGIN_RESP)begin phaseEND_RESP;// Master 确认响应statusinitiator_socket.nb_transport_fw(trans,phase,delay);end核心返回值tlm_sync_enum在写代码时不管是调用 fw 还是 bw都会收到一个返回值上面代码中的 status。这个返回值决定了是不是立刻返回结果TLM_ACCEPTED含义消息收到了对方需要花时间处理晚点会异步调用另一个方向的函数把结果返回来。对应现实上面的例子中如果大家动作都慢每一步都返回 TLM_ACCEPTED那就是标准的 4 次函数调用。TLM_UPDATED含义对方是个快手在调用函数把请求发过去的同时他在同一个函数返回时就把 phase 改了并把结果返回。好处可以把 4 阶段缩短为 3 阶段甚至 2 阶段极大地减少函数调用次数提高仿真速度。TLM_COMPLETED含义传输结束不需要后续的任何握手了。阻塞传输b_transport发起方调用后阻塞直到事务完成。对比特性b_transport(阻塞)nb_transport_fw (非阻塞前向)完整函数名void b_transport(…)tlm_sync_enum nb_transport_fw(…)执行行为调用后阻塞等待目标处理完毕返回调用后立即返回事务异步进行 参数特征(payload, delay)(payload, phase, delay)建模风格LT (Loosely Timed)适合快速架构探索AT (Approximately Timed)适合精确时序/流水线建模配套反向接口无需配合 nb_transport_bw 实现双向通信4 时间建模TimingTLM2.0通过 sc_time 对象来携带时间信息。b_transport 函数中的 delay 参数按引用传递允许目标方在返回前增加延时从而模拟读写延迟或总线仲裁延迟5 核心时间模式TLM 2.0 主要有两种模式LT 模式 (Loose-Timely宽松时序模式)LT模式使用基于wait()的阻塞传送接口当一个调用请求发出后直到请求被处理完成后该调用才返回。LT模式支持“时间解耦”用于平衡仿真速度与仿真精度。应用场景只关心功能不关心具体时钟周期比如快速搭建的虚拟原型、软件算法模型。工作原理Initiator 通过 Socket 调用 b_transport(payload, delay)。这是一个阻塞Blocking方法。数据包Payload送过去后由 Target 直接修改 Payload 中的数据或状态函数返回后Initiator 直接从同一个 Payload 里读取结果。AT 模式 (Approximately-Timely近似时序模式)AT模式使用基于相位(phase)的非阻塞传送接口当请求发出后发送方只需获知接收方已经接收消息接收方在处理完成事务后主动通知发送方事务已完成。典型的相位变化如BEGIN_REQ - END_REQ - BEGIN_RESP - END_RESP。应用场景需要精确到时钟周期的总线仿真比如 AXI 的 4 阶段握手。工作原理通过 Socket 调用 nb_transport_fw前向和 nb_transport_bw后向。这是一个非阻塞Non-blocking方法。数据包Payload在 Initiator 和 Target 之间像打乒乓球一样来回传送每次传送代表一个状态如开始地址、开始数据、结束等待等。在发起者socket与目标socket中注册forward或者backward的接口需要根据实际需要确定。通常发起者socket与目标socket相连接发起方会调用目标socket的transport_fw()方法实现向前传输目标方会调用发起者socket的transport_bw()方法实现向后传输。6 代码示例下面以一个发起方Initiator向一个目标方Target Memory发起写并读回验证的完整流程为例。6.1 阻塞传输1、目标方 (Target) 实现目标方需要实现 b_transport 方法并注册到其socket上// target.h#includesystemc#includetlm.h#includetlm_utils/simple_target_socket.hclassMemory:publicsc_core::sc_module{public:// 1. 定义目标方套接字接收来自发起方的事务tlm_utils::simple_target_socketMemorysocket;SC_CTOR(Memory):socket(socket){// 2. 将 b_transport 方法注册到套接字上socket.register_b_transport(this,Memory::b_transport);// 初始化内存数组for(inti0;i256;i)mem[i]0;}// 3. 实现核心的阻塞传输函数voidb_transport(tlm::tlm_generic_payloadtrans,sc_core::sc_timedelay){// 获取事务详情tlm::tlm_command cmdtrans.get_command();sc_dt::uint64 addrtrans.get_address();unsignedchar*ptrtrans.get_data_ptr();unsignedintlentrans.get_data_length();// 模拟延迟 (例如 10ns)delaysc_core::sc_time(10,sc_core::SC_NS);if(cmdtlm::TLM_WRITE_COMMAND){std::coutsc_core::sc_time_stamp() [Target] WRITE to addr addrstd::endl;// 执行写操作for(unsignedinti0;ilen;i){mem[addri]ptr[i];}}elseif(cmdtlm::TLM_READ_COMMAND){std::coutsc_core::sc_time_stamp() [Target] READ from addr addrstd::endl;// 执行读操作for(unsignedinti0;ilen;i){ptr[i]mem[addri];}}// 设置事务完成状态trans.set_response_status(tlm::TLM_OK_RESPONSE);}private:unsignedcharmem[256];// 模拟内存};2、发起方 (Initiator) 实现发起方包含一个线程持续发起事务// initiator.h#includesystemc#includetlm.h#includetlm_utils/simple_initiator_socket.hstructInitiator:sc_core::sc_module{// 1. 定义发起方套接字tlm_utils::simple_initiator_socketInitiatorsocket;SC_CTOR(Initiator){SC_THREAD(thread_process);}voidthread_process(){// 初始化一个通用事务包tlm::tlm_generic_payload trans;sc_core::sc_timedelay(sc_core::SC_ZERO_TIME);// --- 执行一次写事务 ---unsignedintwrite_data0xDEADBEEF;trans.set_command(tlm::TLM_WRITE_COMMAND);trans.set_address(0x10);trans.set_data_ptr(reinterpret_castunsignedchar*(write_data));trans.set_data_length(4);std::coutsc_core::sc_time_stamp() [Initiator] Sending WRITE transactionstd::endl;// 2. 调用 socket 的 b_transport这将最终调用目标方的 b_transportsocket-b_transport(trans,delay);std::coutsc_core::sc_time_stamp() [Initiator] WRITE completed, status trans.get_response_status()std::endl;// --- 执行一次读事务 ---unsignedintread_data0;trans.set_command(tlm::TLM_READ_COMMAND);trans.set_address(0x10);trans.set_data_ptr(reinterpret_castunsignedchar*(read_data));trans.set_data_length(4);std::coutsc_core::sc_time_stamp() [Initiator] Sending READ transactionstd::endl;socket-b_transport(trans,delay);std::coutsc_core::sc_time_stamp() [Initiator] READ completed, data 0xstd::hexread_datastd::endl;}};3、顶层连接 (Top-Level)在顶层模块中将两个socket绑定在一起// main.cpp#includesystemc#includeinitiator.h#includetarget.hintsc_main(intargc,char*argv[]){Initiatorinitiator(initiator);Memorymemory(memory);// 将发起方套接字绑定到目标方套接字initiator.socket.bind(memory.socket);sc_core::sc_start();return0;}进阶引入时间标记如果希望精确控制延迟目标方可以修改传入的 delay 引用而不是直接使用 # 等待。在目标方 b_transport 中添加如下代码// 模拟不同地址的访问延迟if(addr0x00){delaysc_core::sc_time(20,sc_core::SC_NS);// 特殊地址慢一些}else{delaysc_core::sc_time(5,sc_core::SC_NS);}6.2 非阻塞传输1、目标方Target实现 —— 接受请求延迟响应Target 需要实现 nb_transport_fw前向接口并拥有一个内部线程来处理延迟响应。// target_nb.h#includesystemc#includetlm.h#includetlm_utils/simple_target_socket.hclassTargetNB:publicsc_core::sc_module{public:// 目标方套接字tlm_utils::simple_target_socketTargetNBsocket;SC_CTOR(TargetNB):socket(socket),pending_trans(nullptr){// 注册前向非阻塞传输函数socket.register_nb_transport_fw(this,TargetNB::nb_transport_fw);// 启动响应处理线程SC_THREAD(response_thread);sensitiveresp_event;dont_initialize();}// 核心函数接收来自 Initiator 的请求前向路径tlm::tlm_sync_enumnb_transport_fw(tlm::tlm_generic_payloadtrans,tlm::tlm_phasephase,sc_core::sc_timedelay){// 只处理请求开始阶段if(phasetlm::BEGIN_REQ){std::coutsc_core::sc_time_stamp() [Target] Received BEGIN_REQ, addr0xstd::hextrans.get_address(), delaydelay.to_string()std::endl;// 保存事务指针以便在响应线程中处理pending_transtrans;// 模拟总线延迟假设需要 50ns 才能准备好数据// 注意这里不阻塞当前调用而是通知线程在 50ns 后触发resp_event.notify(50,sc_core::SC_NS);// 返回 TLM_ACCEPTED 表示我收下了请求但不会立即完成// 发起方可以去做其他事情不用等我。returntlm::TLM_ACCEPTED;}// 如果收到 END_RESP来自发起方的确认可以忽略或做清理returntlm::TLM_COMPLETED;}// 内部线程延迟后通过后向路径发送响应voidresponse_thread(){while(true){// 等待事件触发50ns 延时到达wait(resp_event);if(pending_trans){std::coutsc_core::sc_time_stamp() [Target] Processing response (DELAY expired)std::endl;// 执行实际的读/写操作if(pending_trans-get_command()tlm::TLM_WRITE_COMMAND){unsignedint*data_ptr(unsignedint*)pending_trans-get_data_ptr();mem[pending_trans-get_address()/4]*data_ptr;std::cout [Target] WRITE completedstd::endl;}else{// TLM_READ_COMMANDunsignedintvalmem[pending_trans-get_address()/4];memcpy(pending_trans-get_data_ptr(),val,4);std::cout [Target] READ value 0xstd::hexvalstd::endl;}pending_trans-set_response_status(tlm::TLM_OK_RESPONSE);// ----- 关键步骤发起后向传输Backward Path-----tlm::tlm_phase phasetlm::BEGIN_RESP;sc_core::sc_time delaysc_core::SC_ZERO_TIME;// 此处不再额外延时// 通过套接字调用 Initiator 实现的 nb_transport_bw// 这会立即触发 Initiator 的接收函数tlm::tlm_sync_enum statussocket-nb_transport_bw(*pending_trans,phase,delay);// 如果 Initiator 返回 TLM_UPDATED意味着它把 phase 改为了 END_RESP// 表示对方已收下响应本目标可以释放资源了if(statustlm::TLM_UPDATED){std::coutsc_core::sc_time_stamp() [Target] Initiator confirmed END_RESP, transaction done.std::endl;}pending_transnullptr;}}}private:unsignedintmem[256];// 模拟内存tlm::tlm_generic_payload*pending_trans;// 当前待处理的事务sc_core::sc_event resp_event;// 触发响应的事件};2、发起方Initiator实现 —— 发送请求接收异步响应Initiator 需要实现 nb_transport_bw后向接口并在内部线程中发起请求。// initiator_nb.h#includesystemc#includetlm.h#includetlm_utils/simple_initiator_socket.hclassInitiatorNB:publicsc_core::sc_module{public:// 发起方套接字tlm_utils::simple_initiator_socketInitiatorNBsocket;SC_CTOR(InitiatorNB):socket(socket),response_received(false){// 注册后向非阻塞传输函数接收 Target 发来的响应socket.register_nb_transport_bw(this,InitiatorNB::nb_transport_bw);SC_THREAD(run_thread);}// 核心函数接收来自 Target 的响应后向路径tlm::tlm_sync_enumnb_transport_bw(tlm::tlm_generic_payloadtrans,tlm::tlm_phasephase,sc_core::sc_timedelay){if(phasetlm::BEGIN_RESP){std::coutsc_core::sc_time_stamp() [Initiator] Received BEGIN_RESP from Target!std::endl;// 读取返回的数据如果是读操作if(trans.get_command()tlm::TLM_READ_COMMAND){unsignedintval;memcpy(val,trans.get_data_ptr(),4);read_resultval;std::cout [Initiator] Data received 0xstd::hexvalstd::endl;}// 告诉 Target我已经收下响应事务可以彻底结束phasetlm::END_RESP;response_receivedtrue;done_event.notify(sc_core::SC_ZERO_TIME);// 通知主线程任务完成returntlm::TLM_UPDATED;// 返回 UPDATED告知 Target 阶段已变}returntlm::TLM_COMPLETED;}// 主线程发起请求voidrun_thread(){// 等待仿真启动稳定wait(sc_core::SC_ZERO_TIME);// ----- 发起一次读请求 -----tlm::tlm_generic_payload trans;unsignedintread_data0;trans.set_command(tlm::TLM_READ_COMMAND);trans.set_address(0x100);trans.set_data_ptr((unsignedchar*)read_data);trans.set_data_length(4);trans.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE);tlm::tlm_phase phasetlm::BEGIN_REQ;sc_core::sc_time delaysc_core::SC_ZERO_TIME;std::coutsc_core::sc_time_stamp() [Initiator] Sending READ BEGIN_REQ (Non-blocking)std::endl;// 调用 Target 的 nb_transport_fwtlm::tlm_sync_enum statussocket-nb_transport_fw(trans,phase,delay);if(statustlm::TLM_ACCEPTED){// 对方接受了请求但尚未完成。我们阻塞主线程等待 done_eventstd::coutsc_core::sc_time_stamp() [Initiator] Request accepted, waiting for async response...std::endl;wait(done_event);// 主线程挂起等待后向路径触发}// 到这里异步响应已完成std::coutsc_core::sc_time_stamp() [Initiator] Transaction fully completed. Final data 0xstd::hexread_resultstd::endl;sc_core::sc_stop();}private:boolresponse_received;unsignedintread_result;sc_core::sc_event done_event;};3、顶层连接Top-Level// main.cpp#includeinitiator_nb.h#includetarget_nb.hintsc_main(intargc,char*argv[]){InitiatorNBinitiator(initiator);TargetNBmemory(memory);// 绑定套接字initiator.socket.bind(memory.socket);sc_core::sc_start();return0;}