UVM基础-TLM通信机制(二)
目录
TLM 2.0 通信
端口定义
传送数据
时间标记
同步通信元件
uvm_event
总结
uvm_barrier
uvm_callback
TLM 2.0 通信
TLM 2.0是System C模型之间的核心传输方式,通常情况下我们都用TLM 1通信,但当遇到模型是使用System C编写的时,UVM要将System C模型继承到验证环境里做reference model的时候会用到TLM 2.0通信。
端口定义
与TLM 1.0相比,TLM 2.0提供更加强大的传输特性,主要包括:① 双向的阻塞或者非阻塞接口;(TLM 1.0也有) ② 时间标记; (从一个组件到另一个组件到底花了多长数据传输)③ 统一的数据包。通过这些特性,TLM 2.0使得接口之间的通信趋于标准化,更容易为系统构建抽象模型。
在定义TLM 2.0的过程中,仍然有initiator和target的概念,也有port、export以及imp端口类型。对于port类型,它是用来发起请求并调用target一端的传输方法;export用来传导这一需求;最后由imp端口所在组件来实现数据传输方法。
为了区别于TLM 1.0对于端口类型的称谓,UVM将TLM 2.0端口类型称之为socket。它们是由port、export和imp组合而成的。一个socket是双向传输的。socket则按照blocking 和 unblocking的传输方式,组合initiator或者target的发起端区别,可以分为:
socket类型都继承与uvm_port_base,具有同TLM 1.0端口一样的基础函数,而在这些socket内部,它们是通过例化port、export以及imp最终实现数据双向传输的。匹配的方法不再是put()、get()、peek()而是变成了b_transport()、nb_transport_fw()和nb_transport_bw()。
socket通过内置这些端口,就可以实现了数据的双向传输。
传送数据
TLM 1.0中传输的数据类型是用户自定义的,这就会产生一些限制,如果端口传输数据类型不同,则端口无法连接,针对传输不同数据类型的TLM端口,相应的传送方法也要做出调整。
TLM 2.0对传送数据的类型提出了一致性的要求,统一的数据类型由uvm_tlm_generic_payload 表示,即传输方法中使用的数据类型都应该为 uvm_tlm_generic_payload。
TLM 2.0标准制定的背景就是为了解决总线级别的抽象问题,所以它的统一数据格式也是按照总线数据的内容来定义的。
bit [63:0] m_address:数据的读写地址。14.28
uvm_tlm_command_e m_command:数据的读写命令。
byte unsigned data[]:写入的数据或者读出的数据,由byte unsigned类型构成动态数组。
int unsigned length:data数组的长度。
uvm_tlm_response_status_e m_response_status:由target返回的状态值,表示数据传输是否完成和有效。
byte unsigned m_byte_enable[]:用来标记写入数据的有效性,标记哪个byte应该写入。
int unsigned m_byte_enable_length:该数值应该等于m_byte_enable数组的容量值。
m_stream_width:用来表示连续传输时的数据传输长度。
uvm_tlm_extension_base m_extensions[uvm_tlm_extension_base]:如果一些数据不在上面的部分,那么可以在这个数据延伸域中添加。
时间标记
不同的时间标记间隔是System C可以构建不同时间精度模型的重要手段。
在TLM 2.0传输中,由于可以标定延时时间,使得target端可以模拟延迟,并且在准确的延迟时刻做出响应。为了便于标记延迟时间,譬如1.1ns(SV和Verilog一样,只能使用整数的延迟方式),UVM新建了一个时间类uvm_tlm_time。这个时间类使得用户可以随时设置它的时间单位,也解决了不同模块或数据包之间出现的不同时间单位和精度单位的问题。
上面的代码定义了两个component类型,一个从从comp1一个comp2,以及调用它们的顶层env1。可以看到,我们通过“uvm_tlm_b_initiator_socket”在comp1中定义了一个initiator,自然的我们就需要在comp2中定义一个target类型的端口和它对应“uvm_tlm_b_target_socket”,TLM端口用的都是socket类型。
同步通信元件
SV中用来做线程同步的几种元件为:event、semaphore和mailbox。在UVM中,需要同步线程不仅仅在同一个对象中,还需要解决不同组件之间的线程同步问题。
UVM为了解决封闭性的问题,定义了如下的类来满足组件之间的同步要求:
※ uvm_event, uvm_event_pool和uvm_event_callback
※ uvm_barrier, uvm_barrier_pool
上面的两组类分别用于两个组件之间的同步和多个组件之间的同步。
uvm_event
uvm_event
不同组件可以共享同一个uvm_event,这不需要通过跨层次传递uvm_event对象句柄来实现共享,该共享方式是通过uvm_event_pool这一全局资源池来实现的。uvm_event_pool是uvm_object_string_pool #(T) 的子类,它可以生成和获取通过字符串索引的uvm_event对象。
通过全局资源池uvm_event_pool(唯一的),环境中的任何组件都可以从资源池获取共享的对象句柄,这就避免了组件之间的互相依赖(跨层次通信)。
下面来看一个结合了uvm_event、uvm_event_pool和回调函数的代码:
代码输出结果:

pre_trigger有返回值,如果返回值为1,则表示uvm_event不会被trigger,也就不会再执行post_trigger,如果返回值为0,则会继续triiger该事件对象。

总结
uvm_event类与event相比,有一定的区别:
① event被->触发之后,会触发使用@等待该事件的对象;uvm_event通过trigger()来触发,会触发使用wait_trigger()等待该事件的对象。
② 如果要再次等待事件触发,event只需要再次用->来触发,而uvm_event需要先通过reset()方法重置初始状态,再使用trigger()来触发。
③ event无法携带更多的信息,而uvm_event可以通过trigger(T data = null)的可选参数,将伴随触发的数据对象都写入到该触发事件中,而等待该事件的对象可以通过方法wait_trigger_data(output T data)来获取事件触发时写入的数据对象。
④ event触发时无法直接触发回调函数,而uvm_event可以通过add_callback(uvm_event_callback cb, bit append = 1)函数来添加回调函数。
⑤ event无法直接获取等待它的进程数目,而uvm_event可以通过get_num_waiters()来获取等待它的进程数目。
uvm_event解决了一个重要问题,那就是在一些uvm_object和uvm_component对象之间如果要发生同步,但是无法通过TLM完成数据传输,因为TLM传输必须是在组件和组件之间进行的。如果要在sequence与sequence之间进行同步,或者sequence与driver之间进行同步,就可以借助uvm_event来实现。
uvm_barrier
UVM提供了一个新的类uvm_barrier来对多个组件进行同步协调, 同时为了解决组件独立运作的封闭性需要,也定义了新的类uvm_barrier_pool来全局管理这些uvm_barrier对象。uvm_barrier_pool同之前的uvm_event_pool一样,也是基于通用参数类uvm_object_string_pool来定义的。
uvm_barrier可以设置一定的等待阈值(threshold),当有不少于该阈值的进程在等待对象时,才会触发该事件,同时激活所有正在等待的进程,使其可以继续进行。
代码输出结果:

c1、c2和env1都共享该对象,这使得c1和c2可以通过wait_for()来等待激活,而env1可以设置阈值来调控什么时间来“开阀”。
在一开始的时候,阈值设置为3,但由于等待该barrier的进程只有2个,无法达到阈值条件,使得两个进程都无法激活。在env1将b1的阈值设置为2时,等待该barrier的两个进程都被激活。通过uvm_barrier::set_threshold()和uvm_barrier::wait_for()这样的方式,可以实现多个组件之间的同步,同时可以保持各个组件之间的独立性。

uvm_callback
通常情况下得到了一个封闭的包(pkg),其中的类如果有些成员方法需要修改,或者需要扩展新的方法时,应该怎么做呢?如果这个包是外来的,那么维护方法不建议去修改这个类本身。callback方法可以为用户提供自定义的处理方法。
uvm_object本身提供了一些callback方法供用户定义:
copy() <= do_copy()
print() <= do_print()
compare() <= do_compare()
pack() <= do_pack()
默认情况下,这些回调函数do_xxx是定义为空的。如果用户执行了uvm_object::copy()函数,那么在该函数执行末尾会自动执行uvm_object::do_copy()。UVM是通过两个相关类uvm_callback_iter和uvm_callbacks #(T,CB)来实现函数回调的顺序和继承性的。
输出结果:

如果要将component同callback作关联,需要对其进行注册,使用uvm_register_cb将callback类和component作绑定,绑定后可以通过在build_phase中使用uvm_callbacks #(comp1)::add(c1, m_cb1) 添加callback。使用uvm_register_cb之后,如果调用的T与CB不匹配,那么在检查完匹配注册表之后系统会打印warning信息,提示用户使用回调函数的潜在问题。
此外,宏`uvm_do_callbacks_exit_on(T, CB, METHOD, VAL)可以进一步控制执行回调函数的层次,简单来讲,回调函数会保持执行直到返回值与给如的VAL值相同才会返回,譬如说我们给一个component绑定了三个回调函数,顺序执行,假设执行到第二个回调函数的时候返回值和VAL值相同,那么就直接返回而不会执行第三个回调函数。这点使得回调函数方法在执行顺序上面有了更多的可控性。
callback函数使用总结:
首先定义一个回调函数继承于uvm_callback,并在其中定义function;然后使用`uvm_register_cb(T, CB)和`uvm_do_callbacks(comp1, cb1, do_trans())绑定及插入callback函数;在顶层把组件和callback都作例化,然后将 uvm_callbacks #(comp1)::add(c1, m_cb2); 把回调函数添加到组件中,形成一个组件-callback对子。