原文链接:http://blog.eetop.cn/blog-1561828-5940103.html
在之前SV的章节中,我们为大家介绍了SV中用来做线程间同步的几种方法,它们分别是semaphore、event和mailbox。然而在UVM中,同步的不再只局限于同一个对象中的各个线程,而是还有各个组件之间的同步问题。一旦发生同步的要求发生在各个组件之间,这就要求组件之间通过某种可以同步的方法来实现。而考虑到UVM各个组件的封闭性原则,我们并不推荐通过层次索引的形式在组件中来索引公共的event或者semaphore。UVM为了解决封闭性的问题,定义了如下的类来满足组件之间的同步:
-
uvm_event,uvm_event_pool和uvm_event_callback
-
uvm_barrier, uvm_barrier_pool
这两组类的用途分别用作了了两个组件之间的同步和多个组件之间的同步。此外,回调函数作为一种也以实现基类复用的方式,在UVM中也被进一步封装为一个类uvm_callback,它不但具备回调函数可以在函数执行前后调用的特点,还增加了更丰富的特性用来组织层次化的调用。所以,uvm_callback类作为最后我们对函数调用的同步手段来讲解。下面我们给出实例来讲解这三组类的特性和用法。
uvm_event应用
uvm_event类与event相比,它有下面的几个重要特性:
-
event被->触发之后,会触发使用@等待该事件的对象;uvm_event通过trigger()来触发,会触发使用wait_trigger()等待的对象。如果要再次等待事件触发,event只需要再次用->来触发,而uvm_event需要先通过reset()方法重置初始状态,再使用trigger()来触发。
-
event无法携带更多的信息,而uvm_event可以通过trigger(uvm_event data = null)的可选参数,将所要伴随触发的数据信息都写入到该触发事件中,而等待该事件的对象可以通过方法wait_trigger_data(output uvm_object data)来获取事件触发时写入的数据对象。
-
event触发时无法直接触发回调函数,而uvm_event可以通过add_callback(uvm_event_callback cb, bit append = 1)函数来添加回调函数。
-
event无法直接获取等待它的进程数目,而uvm_event不但可以通过get_num_waiters()来获取等待它的进程数目。
不同的组件可以共享同一个uvm_event,这不是通过跨层次传递uvm_event对象句柄来实现共享的,因为这并不符合组件环境封闭的原则。这种共享同一个uvm_event对象是通过uvm_event_pool这一全局资源池来实现的。这个资源池类是uvm_object_string_pool #(T)的子类,它可以生成和获取通过字符串来索引的uvm_event对象。通过全局资源池对象(唯一的),在环境中任何一个地方的组件都可以从资源池中获取共同的对象,这就避免了组件之间的互相依赖。接下来,我们就结合uvm_event、uvm_event_pool和uvm_event_callback来讲解一个典型用例。
输出结果:
UVM_INFO @ 0: reporter [RNTST] Running test test1...
UVM_INFO @ 0: uvm_test_top.env.c2 [ESYNC] wait sync event at 0 ps
UVM_INFO @ 10000: reporter [EPRETRIG] before trigger event e1
UVM_INFO @ 10000: reporter [EPOSTRIG] after trigger event e1
UVM_INFO @ 10000: uvm_test_top.env.c1 [ETRIG] trigger sync event at 10000 ps
UVM_INFO @ 10000: uvm_test_top.env.c2 [ESYNC] get data 100 after sync at 10000 ps
上面的例子里,组件c1和c2之间进行了从c1到c2的同步,而且在同步的过程中间也通过uvm_event e1传递了数据edata,也调用了回调函数类ecb的pre_trigger()和post_trigger()方法。关于这个用例, 有几点需要读者注意:
-
无论有多少个组件,只要它们共同寻求同一个名称的uvm_event,将会共享该uvm_event对象的句柄。例如上面c1和c2通过uvm_event_pool::get_global("e1")来获取同一个名称的uvm_event对象。即便该对象不存在,也会在第一次调用get_global()函数时创建这样一个对象。
-
如果要传递数据,用户可以自定义扩展uvm_object的子类,并且通过uvm_event::trigger(uvm_object data = null)来传递给数据对象。而在等待uvm_event一侧的组件,则需要通过uvm_event::wait_trigger_data(output uvm_object data)来获取该对象。
-
同样地,用户也可以扩展uvm_event_callback类,自定义在uvm_event被trigger前后的调用方法。pre_trigger()需要有返回值,如果返回值为1,则表示uvm_event不会被trigger,也不会再执行post_trigger()方法;如果返回值为0,则会继续trigger该事件对象。
-
如果用户无法确定在等待事件之前,uvm_event是否已经被trigger,那么用户还可以通过方法wait_ptrigger()和wait_ptrigger_data()来完成等待。这样即便在调用等待方法之前,事件已经被触发,方法仍然不会被阻塞,可以继续执行下去。
那在日常应用中,什么情况下会使用uvm_event呢?我们在之前的组件通信中已经提到过,组件之间的常规的数据流向是通过TLM通信方法实现的,比如sequencer与driver之间,或者monitor与scoreboard之间。然而有些时候,数据的传输的偶然触发的,并且需要立即响应,这个时候uvm_event就是得力的助手了。同时,uvm_event也解决了一个重要问题,那就是在一些uvm_object和uvm_component对象之间如果要发生同步,那么无法通过TLM传输,因为TLM传输必须是在组件(component)和组件之间进行的。譬如,在sequence与sequence之间要进行同步,或者sequence与driver之间要进行同步,都可以借助uvm_event来实现。
uvm_barrier应用
在SV章节中,对于多个线程的同步除了可以通过semaphore和mailbox来进行,也可以通过fork-join的结构控制语句块来控制整体的运行节奏。然而,对于UVM环境中的多个独立组件,SV的这些方法都受到了作用域的局限。UVM提供了一个新的类uvm_barrier用来对多个组件进行同步协调,同时为了解决组件独立运作的封闭性需要,也定义了新的类uvm_barrier_pool来全局管理这些uvm_barrier对象。这里的uvm_barrier_pool同之前的uvm_event_pool一样,也是基于uvm_object_string_pool的通用参数类来进一步定义的:
typedef uvm_object_string_pool #(uvm_barrier) uvm_barrier_pool;
typedef uvm_object_string_pool #(uvm_event#(uvm_object)) uvm_event_pool;
uvm_barrier可以设置一定的等待阈值(threshold),待有不少于该阈值的进程在等待该对象时,也会触发该事件,同时激活所有正在等待的进程,使得可以继续进行。下面我们给出一个实际用例供用户来参考该类的使用方法:
输出结果:
UVM_INFO @ 0: reporter [RNTST] Running test test1...
UVM_INFO @ 0: uvm_test_top.env [BSYNC] env set b1 threshold 3 at 0 ps
UVM_INFO @ 10000: uvm_test_top.env.c1 [BSYNC] c1 wait for b1 at 10000 ps
UVM_INFO @ 20000: uvm_test_top.env.c2 [BSYNC] c2 wait for b1 at 20000 ps
UVM_INFO @ 50000: uvm_test_top.env [BSYNC] env set b1 threshold 2 at 50000 ps
UVM_INFO @ 50000: uvm_test_top.env.c1 [BSYNC] c1 is activated at 50000 ps
UVM_INFO @ 50000: uvm_test_top.env.c2 [BSYNC] c2 is activated at 50000 ps
从上面这个例子来看,c1和c2的run_phase任务之间需要同步,而同步它们的则是顶层的一个uvm_barrier b1。由于c1、c2和env1都共享该对象,这使得c1和c2可以通过wait_for()来等待激活,而env1则可以设置阈值来调控什么时间来“开阀”。从仿真结果可以看到,在一开始的时候,阈值设置为3,但由于等待该barrier的进程只有2个,无法达到阈值条件,使得两个进程都无法激活。而在env1将b1的阈值调低为2时,则等待该barrier的两个进程都被激活。因此通过set_threshold()和wait_for()这样的方式,可以实现多个组件之间的同步,同时又可以保持各个组件之间的独立性。
谢谢你对路科验证的关注,也欢迎你分享和转发真正的技术价值,你的支持是我们保持前行的动力。