一、TLM通信概述
- 系统原型阶段和芯片验证阶段均使用了TLM通信方式。前者为了更快地实现硬件原型之间的数据通信,后者为了更快地实现验证组件之间的数据通信。
- TLM是一种提高数据传输抽象级的标准,可以用来表示宽松时间跨度内的硬件通信数据。通过将低颗粒硬件周期内的数据打包成一个大数据,可以有效地提升整体环境的仿真速度。
- 基本概念
1)两个通信对象:initial
(发起通信请求的一方)和target
(发起通信的响应方)。
2)按照transaction的流向区分两个对象:producer
(数据产生一方)和consumer
(数据流向方)。
※initial
与target
的关系同producer
和consumer
的关系是不固定的。 - TLM通信步骤
1)在两个对象中创建TLM端口
2)在target中实现TLM通信方法
3)在更高的层次中将两个对象的端口进行连接 - 分类
①按照端口类型:
1)port:作为initiator
的发起端
2)export:作为initial
和target
中间层次的端口
3)imp:只能作为target
接收request的末端,无法再次延伸。
②按照数据流向
1)单向传输
2)双向传输
👉数据流向和端口类型加以组合,TLM端口共可以分为六类。
uvm_UNDIR_port #(trans_t)
uvm_UNDIR_export #(trans_t)
uvm_UNDIR_imp #(trans_t, imp_parent_t) // 不仅需指定transaction类型,还需指定所在component类型。
uvm_BIDIR_port #(req_trans_t, rsp_trans_t)
uvm_BIDIR_export #(req_trans_t, rsp_trans_t)
uvm_BIDIR_imp #(req_trans_t, rsp_trans_t, imp_parent_t)
- 端口使用注意问题
1)多个port可以连接到同一个export或者imp,但是单个port或者export无法连接多个imp。
2)port可以连接port、export或者imp;export可以连接export或者imp;imp只能作为数据传送的终点,无法扩展连接。
二、单向通信
- 阻塞传输方式(blocking前缀)对应方法类型为task;非阻塞传输方式(nonblocking前缀)对应方法类型为function。
- peek同get的区别在于peek只是没把原数据移除。
try_xxx()
方法和can_xxx()
方法的区别在于can_xxx()
函数先试探target是否可以接收数据,如果可以,再通过try_xxx()
函数发送,提高数据发送的成功率。
//example
class comp1 extends uvm_component;
uvm_blocking_put_port #(itrans) bp_port;
uvm_nonblocking_get_port #(otrans) nbg_port;
...
// inside run phase
this.bp_port.put(xx);
...
if(this.nbg_port.try_get(xx)==1) ...;
...
endclass
class comp2 extends uvm_component;
uvm_blocking_put_imp #(itrans, comp2) bp_imp;
uvm_nonblocking_get_imp #(otrans, comp2) nbg_imp;
...
task put(itrans t);
...
endtask
function bit try_get(output otrans t);
...
endfunction
endclass
class env1 extends uvm_env;
comp1 c1;
comp2 c2;
...
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
c1.bp_port.connect(c2.bp_imp);
c1.nbg_port.connect(c2.nbg_imp); //左侧initiator, 右侧target
endfunction: connect_phase
endclass
三、双向通信
- 双向通信中的两端同时扮演着producer和consumer的角色,而initiator作为request发起方,在发起request之后,还会等待response返回。
四、多向通信
- 多向通信仍然是两个组件之间的通信,是针对initiator与target之间相同TLM端口数目超过一个时的处理解决办法。
- 通过端口宏声明方式解决。eg. `uvm_blocking_put_imp_decl(SFX)
`uvm_blocking_put_imp_decl(_p1)
`uvm_blocking_put_imp_dec1(_p2)
class comp1 extends uvm_component; //组件1不受任何影响,目前并不知道最后连接到哪里
uvm_blocking_put_port #(itrans) bp_port1;
uvm_blocking_put_port #(itrans) bp_port2;
...
// inside run_phase
this.bp_port1.put(xx);
...
this.bp_port2.put(xx);
endclass
class comp2 extends uvm_component;
uvm_blocking_put_imp_p1 #(itrans, comp2) bt_imp_p1;
uvm_blocking_put_imp_p1 #(itrans, comp2) bt_imp_p2;
semaphore key; //对于共享资源,做互斥访问的保护
...
task put_p1(itrans t);
key.get();
...
endtask
task put_p2(itrans t);
key.get();
...
endtask
endclass
class env1 extends uvm_env;
comp1 c1;
comp2 c2;
...
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
c1.bp_port1.connect(c2.bt_imp_p1);
c1.bp_port2.connect(c2.bt_imp_p2);
endfunction: connect_phase
endclass
五、管道通信
👉解决问题:1)不自己实现target一端需要实现的传输方法;2)解决一端到多端的传输
- TLM_FIFO
多数情况下,需要实现的传输方法都是相似的,方法的主要内容即是为了实现一个数据缓存功能。TLM FIFOuvm_tlm_fifo
类是一个新组件,继承于uvm_component
类,而且已经预先内置了多个端口以及实现了多个对应方法供用户使用。
- Analysis Port
1)针对一个initiator端到多个target端的方式;需要调用的是write()
函数来实现数据传输。
2)类似于其它TLM端口,按照传输方法和端口方向组合可以将analysis port
分为uvm_analysis_port
、uvm_analysis_export
以及uvm_analysis_imp
。
3)在initiator端调用write()
函数时,实际上通过循环的方式将所有连接的target端内置的write()函数进行了调用。 - Analysis TLM FIFO
uvm_tlm_analysis_fifo
类继承于uvm_tlm_fifo
,这表明它本身具有面向单一TLM端口的数据缓存特性,而同时该类又有一个uvm_analysis_imp
端口analysis_export
并实现了write()
函数。
注意,虽然名为export但实际上是imp
类型的端口,并且下图中的target端的端口类型变成了port
类型,整体的数据流向与原来的是相同的。
- 双向通信管道
1)uvm_tlm_req_rsp_channel
2)uvm_tlm_transport_channel
uvm_tlm_req_rsp_channel
继承于uvm_tlm_req_rsp_channel
六、TLM2通信
- TLM2强大的传输特性包括: 1)双向的阻塞或者非阻塞接口;2)时间标记;3)统一的数据包。
- 为了区别与TLM1对于端口类型的称谓,UVM将TLM2.0端口类型称为socket。
七、同步通信元件
-
uvm_event
1)不同组件可以共享同一个uvm_event,不需要通过跨层次传递uvm_event对象句柄来实现共享(这不符合组件环境封闭的原则),该共享方式是通过uvm_event_pool
这一全局资源池来实现。
2)如果要传递数据,用户可以定义扩展于uvm_object
的数据子类,并通过uvm_event::trigger(T data=null)
来传递数据对象。在等待uvm_event一侧的组件则需要通过uvm_event::wait_trigger_data(output T data)
来获取该对象。
3)用户可以扩展uvm_event_callback
类,定义uvm_event被trigger前后的调用方法pre_trigger()
和post_trigger()
。pre_trigger()
要有返回值,如果返回值为1,则不会继续向下执行。
4)如果无法确定在等待事件之前,事件是否已经被trigger,那么还可通过wait_ptrigger()
和wait_ptrigger_data()
来完成等待。
5)组件之间的常规数据流向是通过TLM通信方法实现的。对于uvm_object和uvm_component之间或者uvm_object之间的同步可以借助uvm_event实现。 -
uvm_barrier
👉对多个组件进行同步协调,uvm_barrier
可以设置一定的等待阈值xx.set_threshod(2)
,当有不少于该阈值的进程在等待该对象xx.wait_for()
时才会触发该事件,同时激活所有正在等待的进程,使其可以继续进行。 -
uvm_callback
类的复用除了通过继承还可以通过回调函数实现。
1)为了保证调用uvm_callback的组件类型T与uvm_callback类型CB保持匹配,可通过宏声明uvm_register_cb(T, CB)
来实现。
2)uvm_callback建立了回调函数执行的层次性,不再是在T中直接调用某个回调函数,而是通过宏uvm_do_callback(T, CB, METHOD)
来声明。
3)在执行回调方法时,依赖的是已经例化的uvm_callback对象。
class env1 extends uvm_env;
comp1 c1;
cb1 m_cb1;
cb2 m_cb2;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
c1 = comp1::type_id::create("c1", this);
uvm_callback #(comp1)::add(c1, m_cb1);
uvm_callback #(comp2)::add(c1, m_cb2);
endfunction
endclass