文章目录
- 4.UVM环境中的通信机制?
- 5.UVM中的port、export和imp?
- 6.UVM中run_phase和main_phase的区别?
- 7.UVM中sequence要去调用sequencer中的东西,怎么调用?
- 8.UVM中工厂覆盖机制和callback的区别?
以下为个人答案,水平有限。如有解释不对的地方,欢迎大家评论区留言一起学习交流。
前面的题目连接:《IC验证笔试题之UVM(一)》
4.UVM环境中的通信机制?
使用TLM进行组件之间的通信。优点有一下:
-
TLM通信保证了相邻组件之间的通信不再通过显式句柄引用,而是独立于组件的通信方式,为验证组件的复用提供了很好地封闭性。
-
同时,使用 TLM 通信提高了仿真速度,传输中的事务又可以保证足够大的信息量和准确性。
这道题也有可能是在问 config机制。换个角度去考虑,从顶层 set() 传递需要配置的变量、接口或者封装类(把多个变量封装在一个类中),然后底层再通过 get() 获取配置,也是顶层与底层之间的通信。
使用config时注意:
-
set() 和get() 传递的第三个参数必须一致;
-
set() 和 get() 中传递的前两个参数是路径索引,set() 中表示要传递给底层的那个组件,get() 中表示要传递给组件中哪一个变量、虚接口或者类声明的句柄;
5.UVM中的port、export和imp?
首先我们把事务发起端称为 initiator 端,事务的响应端称为 target 端。
TLM通信的端口按照类型可以分为3种:
- port:经常作为 initiator 的发起端,initiator 凭借 port 才可以访问 target 的 TLM 通信方法;
- export:作为 initiator 和 target 中间层次的端口;
- imp:只能作为 target 接受 request 的末端,它无法作为中间层次的端口,所以 imp 到的连接无法再次延伸。
注意:
- 在 initiator 端例化 port ,在中间层例化 export ,在 target 端例化 imp ,在 build_phase 中例化;
- 多个 port 可以连接到同一个 export 或者 imp ;但是单个 port 或者export 无法连接到多个 imp 上。可以理解成多个 initiator 可以对同一个 targe t发起 request ,但是同一个 initiator 无法连接多个 target 。(注:使用 Analysis Port或者 Analysis TLM FIFO通信管道可以实现一端对多端通信)
- port 可以连接 port 、export 和 imp;export 可以连接到 export 和imp 上;imp 是数据传输的终点,无法扩展连接。
6.UVM中run_phase和main_phase的区别?
main_phase( )是12个分支任务 phase 中的一个,run_phase 的执行贯穿这12个分支 phase。
run_phase( )和 main_phase( )是并行执行的,只有等12个分支phase全部执行完,才开始执行 extract_phase 等后面的phase。
run_phase( ) 和 main_phase( )的区别:
首先,我知道Objection机制是UVM中唯一可以控制仿真开始和结束的方式。在任务 phase 中,至少有一个任务 phase 要在消耗第一条消耗仿真时间的语句执行之前要挂起 Objection。
- 如果12个分支中有一个任务phase(比如main_phase)挂起了 Objection ,那么 run_phase 中不需要挂起 Objection 就可以执行其中的代码;但是这时,run_phase 的运行时间被动地受这个挂起 Objection 的分支任务phase的控制。
- 而如果在 run_phase 中挂起了 Objection ,没有在main_phase中挂起,main_phase中的操作则不会执行。
注:详细的可以参考张强大佬的《UVM实战-卷1》第五章
7.UVM中sequence要去调用sequencer中的东西,怎么调用?
通过宏 uvm_declare_p_sequencer( base_test_virtual_sequencer ) ,声明了一个 base_test_virtual_sequencer 类型(这里只是举例,根据传递不同的sequencer类型而改变)的变量p_sequencer,通过这个p_sequencer 可以动态的索引到 base_test_virtual_sequencer 中声明的各个 sequencer 的句柄和变量。
PS,这里扩展一下:
首先,继承于 uvm_sequence 的类中本身就有一个成员变量 m_sequencer(m_sequencer 的类型为 uvm_sequencer_base ),是 sequence 在启动后所使用的sequencer的指针。因为在启动时,在顶层test中会调用 my_sequence.start(my_sequencer),将 my_sequence 挂载在 my_sequencer 上,这样在底层可以通过m_sequencer做一些操作。
通过调用宏 uvm_declare_p_sequencer(SEQUENCER),UVM会做先定义一个 p_sequencer 变量,然后做 $cast(p-sequencer,m_sequencer)的动态转换,这样最后在顶层 TEST 中将 sequence 挂载到 virtual sequencer 上时,sequence 就可以通过变量 p_sequencer 访问到 virtual sequencer 内部定义的各个 sequencer 句柄和变量了。
8.UVM中工厂覆盖机制和callback的区别?
工厂覆盖(override)是通过创建一个继承于原有类型的覆盖类型,将原有类型来修改和替换。
优点:
- 替换 transaction,构建异常的测试用例。通过继承于原有的 transaction,将内部约束做更改来生成异常激励,使用UVM工厂重载时,无需重新定义一个新的 sequence (或者更改原有的 sequence)发送这些异常的transaction,可以继续使用原来的 sequence 发送。
- 替换 component。对于一个DUT来说,可能需要建立很多的参考模型来模拟异常的激励情况,如果将所有的参考模型都放在一个参考模型中,代码会非常多且臃肿,不易维护和阅读。通过将这些参考模型分散成零散的,当处理不同的异常情况时,建立对应的异常的测试 sequence,然后将处理异常激励的参考模型替换掉正常测试的参考模型。
回调(uvm_callback)是为了提高验证平台的代码复用性,也可以用来构建测试用例。
优点:
- 如果一个类中有些成员方法需要修改或者扩展,不需要用户添加新的方法而只是延展之前的方法,如果采用工厂覆盖,那么需要继承于原先的类,再覆盖。一种更简便的方法,就是使用回调函数。
UVM中添加回调的步骤:
- 定义回调类 my_callback ,继承于 uvm_callback;还需要在my_callback 类中定义 do_trans() 任务,且声明为虚;
- 在底层组件中(例如Driver),通过宏 uvm_register_cb( ) 绑定回调类和组件,并通过宏 uvm_do_callbacks( ) 在组件中插入回调函数;
- 在顶层,先声明和例化 my_callback 对象(cb1和cb2),通过 uvm_callbacks #(T,CB) 类的静态方法 add( ) 来添加成对的 uvm_object 对象(c1)和 callback 对象(m_cb1)
下面是对照例码:
class cb1 extends uvm_callback;//定义回调类
virtual function void do_trans(edata d);
endclass
class cb2 extends cb1;
function void do_trans(edata d);
endclass
class comp1 extends uvm_component;
`uvm_register_cb(comp1,cb1);//将回调函数cb1与comp1绑定,可以防止调用出错
edata d;
`uvm_do_callback(comp1 , cb1 , do_trans(d))//插入回调cb1
endclass
class env extends uvm_env;
comp1 comp1;
cb1 m_cb1;
cb2 m_cb2;
m_cb1 = new("m_cb1");
m_cb2 = new("m_cb2");
c1 = comp1::type_id::create("c1",this);
uvm_callbacks #(comp1)::add(c1,m_cb1);
uvm_callbacks #(comp1)::add(c1,m_cb2);//因为cb2是cb1的子类,调用cb2之前,会调用cb1
//在绑定回调函数时,绑定了父类cb1,就不用绑定子类cb2
endclass