一、概述
- 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(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_event
对象句柄来实现共享,该共享方式是通过uvm_event_pool
这一全局资源池来实现的。这个资源池类是uvm_object_string_pool #(T)
的子类,它可以生成和获取通过字符串来索引的uvm_event
对象。通过全局资源池(唯一的),环境中的任何组件都可以从资源池获取共享的对象句柄,这就避免了组件之间的互相依赖。
二、uvm_event应用
class edata extends uvm_object;
int data;
`uvm_object_utils(edata)
...
endclass
class ecb extends uvm_event_callback;
`uvm_object_utils(ecb)
function bit pre_trigger(uvm_event e, uvm_object data=null);
`uvm_info("EPRETRIG", $sformatf("before trigger event %s", e.get_name()), UVM_LOW)
return 0;
endfunction
function void post_trigger(uvm_event e, uvm_object data=null);
`uvm_info("EPOSTTRIG", $sformatf("after trigger event %s", e.get_name()), UVM_LOW)
endfunction
endclass
class comp1 extends uvm_component;
uvm_event e1;
`uvm_component_utils(comp1)
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
e1 = uvm_event_pool::get_global("e1");
endfunction
task run_phase(uvm_phase phase);
edata d = new();
ecb cb = new();
d.data = 100;
#10ns;
e1.add_callback(cb);
e1.trigger(d);
`uvm_info("ETRIG", $sformatf("trigger sync event at %t ps", $time), UVM_LOW)
endtask
endclass
class comp2 extends uvm_component;
uvm_event e1;
`uvm_component_utils(comp2)
function void build_phase(uvm_phase phase);
super.build_phase(phase)
e1 = uvm_event_pool::get_global("e1");
endfunction
task run_phase(uvm_phase phase);
uvm_object tmp;
edata d;
`uvm_info("ESYNC", $sformatf("wait sync event at %t ps", $time), UVM_LOW)
e1.wait_trigger_data(tmp);
void'($cast(d, tmp));
`uvm_info("ESYNC", $sformatf("get data %0d after sync at %t ps", d.data, $time), UVM_LOW)
endtask
endclass
class env1 extends uvm_env;
comp1 c1;
comp2 c2;
`uvm_component_utils(env1)
...
endclass
输出结果:
组件c1
和c2
之间完成了从c1到c2的同步,而且在同步过程中通过uvm_event e1
传递了数据edata
,并且调用了回调函数类ecb
的pre_trigger()
和post_trigger()
方法。
三、uvm_event总结
- 组件之间的常规数据流向是通过TLM通信方法实现的,比如sequence与driver之间,或者monitor与scoreboard之间。
- 有些时候,数据传输是偶然触发的,并且需要立即响应,这个时候需要uvm_event。
- uvm_event同时也解决了一个重要问题,那就是在一些uvm_object和uvm_component对象之间如果要发生同步,但是无法通过TLM完成数据传输,因为TLM传输必须是在组件和组件之间进行的。
- 如果要在sequence与sequence之间进行同步,或者sequence与driver之间进行同步,就可以使用uvm_event来实现。
四、uvm_barrier介绍
- 在SV模块中,多个线程的同步除了可以通过semaphore和mailbox来进行,也可以通过fork_join的结构控制语句块来控制整体的运行节奏。
- 然而对于UVM环境中的多个独立组件,SV的这些方法都受到了作用域的局限。UVM提供了一个新的类
uvm_barrier
来对多个组件进行同步协调,同时为了解决组件独立运作的封闭性需要,也定义了新的类uvm_barrier_pool
来全局管理这些uvm_barrier
对象。
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_barrier应用
class comp1 extends uvm_component;
uvm_barrier b1;
`uvm_component_utils(comp1)
function void build_phase(uvm_phase phase);
super.build_phase(phase);
b1 = uvm_barrier_pool::get_global("b1");
endfunction
task run_phase(uvm_phase phase);
#10ns;
`uvm_info("BSYNC", $sformatf("c1 wait for b1 at %0t ps", $time), UVM_LOW)
b1.wait_for();
`uvm_info("BSYNC", $sformatf("c1 is activated at %0t ps", $time), UVM_LOW)
endtask
endclass
class comp2 extends uvm_component;
uvm_barrier b1;
`uvm_component_utils(comp2)
function void build_phase(uvm_phase phase);
super.build_phase(phase);
b1 = uvm_barrier_pool::get_global("b1");
endfunction
task run_phase(uvm_phase phase);
#20ns;
`uvm_info("BSYNC", $sformatf("c2 wait for b1 at %0t ps", $time), UVM_LOW)
b1.wait_for();
`uvm_info("BSYNC", $sformatf("c2 is activated at %0t ps", $time), UVM_LOW)
endtask
endclass
class env1 extends uvm_env;
comp1 c1;
comp2 c2;
uvm_barrier b1;
`uvm_component_utils(env1)
function void build_phase(uvm_phase phase);
super.build_phase(phase);
c1 = comp1::type_id::create("c1", this);
c2 = comp2::type_id::create("c2", this);
b1 = uvm_barrier_pool::get_global("b1");
endfunction
task run_phase(uvm_phase phase);
b1.set_threshold(3);
`uvm_info("BSYNC", $sformatf("env set b1 threshold %d at %0t ps", b1.get_threshold(), $time), UVM_LOW)
#50ns;
b1.set_threshold(2);
`uvm_info("BSYNC", $sformatf("env set b1 threshold %d at %0t ps", b1.get_threshold(), $time), UVM_LOW)
endtask
endclass
输出结果:
从这个示例来看,c1
和c2
的run_phase
任务之间需要同步,而同步它们的元件则是来自于顶层的一个uvm_barrier b1
。由于c1
、c2
和env1
都共享该对象,这使得c1
和c2
可以通过wait_for()
来等待激活,而env1
可以设置阈值来调控什么时间来“开阀”。因此通过uvm_barrier::set_threshold()
和uvm_barrier::wait_for()
这样的方式,可以实现多个组件之间的同步,同时可以保持各个组件之间的独立性。
六、uvm_callback介绍
- 除了UVM提供新的类方便组件之间的同步之外,另外一种同步方式回调函数也方便了类的封装复用。
- 通常情况下得到了一个封闭的包,其中的类如果有些成员方法需要修改,或者需要扩展新的方法时,如果通过类的继承来满足这一要求,又无法在该包环境中用新的子类替换原来的父类,那么此时可以使用UVM的覆盖机制。
- 除了覆盖机制,还有callback也可以提供自定义的处理方法,这使得如果不需要添加新方法,而是想延展之前的方法,就无需通过继承类的方式,而只需要通过在后期定义callback方法来实现。
uvm_object本身提供了一些callback方法:
copy()/do_copy()
print()/do_print()
compare()/do_compare()
pack()/do_pack()
unpack()/do_unpack()
record()/do_record()
- 默认情况下,这些回调函数
do_XXX
是定义为空的。 - 如果执行了
uvm_object::copy()
函数,那么在函数执行末尾会自动执行uvm_object::do_copy()
。do_copy()
是copy()
的回调函数,uvm_object
会在copy()
的执行尾端勾住callback
函数即do_copy()
。如果自定义了这些回调函数,就可以在对应函数执行结束后再执行扩展后的回调方法。 - 通过这个新添加的类,使得函数回调有了顺序和继承性,关于顺序和继承性的实现,UVM是通过两个相关类
uvm_callback_iter
和uvm_callbacks #(T,CB)
来实现的。
七、uvm_callback应用
class edata extends uvm_object;
int data;
`uvm_object_utils(edata)
...
endclass
class cb1 extends uvm_callback;
`uvm_object_utils(cb1)
...
virtual function void do_trans(edata d);
d.data = 200;
`uvm_info("CB", $sformatf("cb1 executed with data %0d", d.data), UVM_LOW)
endfunction
endclass
class cb2 extends cb1;
`uvm_object_utils(cb2)
...
function void do_trans(edata d);
d.data = 300;
`uvm_info("CB", $sformatf("cb2 executed with data %0d", d.data), UVM_LOW)
endfunction
endclass
class comp1 extends uvm_component;
`uvm_component_utils(comp1)
`uvm_register_cb(comp1, cb1)
...
task run_phase(uvm_phase phase);
edata d = new();
d.data = 100;
`uvm_info("RUN", $sformatf("proceeding data %0d", d.data), UVM_LOW)
`uvm_do_callbacks(comp1, cb1, do_trans(d))
endtask
endclass
class env1 extends uvm_env;
comp1 c1;
cb1 m_cb1;
cb2 m_cb2;
`uvm_component_utils(env1)
function new(string name, uvm_component parent);
super.new(name, parent);
m_cb1 = new("m_cb1");
m_cb2 = new("m_cb2");
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
c1 = comp1::type_id::create("c1", this);
uvm_callbacks #(comp1)::add(c1, m_cb1);
uvm_callbacks #(comp1)::add(c1, m_cb2);
endfunction
endclass
输出结果:
八、uvm_callback总结
uvm_callback
类使得钩子属性变得更加容易控制和继承
uvm_callback
可以通过继承的方式来满足更多的定制,例如上面的cb2
继承于cb1
。- 为了保证调用
uvm_callback
的组件类型T
与uvm_callback
类型CB
保持匹配,最好在T
中声明T
与CB
的匹配,该声明可以通过宏'uvm_register_cb(T,CB)
来实现。 uvm_callback
建立了回调函数执行的层次性,因此在实现方面,不再是在T
的方法中直接呼叫某一个回调方法,而是通过宏'uvm_callbacks #(T,CB,METHOD)
来实现。该宏最直观的作用在于会循环执行已经与该对象结对的uvm_callback
类的方法。此外宏'uvm_do_callbacks_exit_on #(T,CB,METHOD,VAL)
可以进一步控制执行回调函数的层次,简单来说,回调函数会保持执行直到返回值与给入的VAL
值相同才会返回,这一点使得回调方法在执行顺序上面有了更多的可控性。- 有了
'uvm_do_callbacks
宏还不够,在执行回调方法时,依赖的是已经例化的uvm_callback
对象,所以最后一步需要例化uvm_callback
对象,上面的例子中分别例化了cb1
和cb2
,通过“结对子”的方式,通过uvm_callbacks #(T,CB)
类的静态方法add()
来添加成对的uvm_object
对象和uvm_callback
对象。
继承uvm_callback并定义CB -> 使用uvm_register_cb以及uvm_do_callbacks来绑定和插入CB对于的方法 - > 在顶层例化组件和CB,add()添加CB
---------------------
作者:煎丶包
来源:CSDN
原文:https://blog.csdn.net/qq_39794062/article/details/114218534
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件