一、验证组件和层次构建
首先将各个package中的SV组件替换为UVM组件,替换过程中需要遵循以下规则:
1、实现组件对应原则
SV | UVM |
---|---|
transaction | uvm_sequence_item |
driver | uvm_driver |
generator | uvm_sequence + uvm_sequencer |
monitor | uvm_monitor |
agent | uvm_agent |
env | uvm_env |
checker | uvm_scoreboard |
reference model | uvm_component |
coverage model | uvm_component |
test | uvm_test |
2、在进行类的转换时需要注意:
- SV的上述类均需继承于其对应的UVM类
- 类定义过程中一定要
'uvm_component_utils()
和'uvm_object_utils()
进行注册
3、使用上述工厂注册宏的时候会伴随着域的自动化。一般建议在定义sequence item时,也要进行域的自动化声明,方便后续对sequence item对象进行拷贝、打印等操作。
4、一定要注意构建函数new()的声明方式,uvm_component的构建函数有两个参数new(string name, uvm_component parent),而uvm_object的构建函数只有一个参数new(string name)。
5、在组件之间的层次关系构建中,依然按照之前SV组件的层次关系,只需要在不同的phase阶段完成组件的例化和连接。
注意
改写时容易出现的错误
- 漏了注册、或者parent参数
- build_phase里需要用type_id::create来创建组件,不采用new
哪些phase应该加上super.xxxx_phase, 哪些又可以不加呢?
- 对于build_phase来说, uvm_component对其做的最重要的事情就是自动获取通过config_db::set设置的参数。 如果要关掉这个功能, 可以在自己的build_phase中不调用super.build_phase
- 除了build_phase外, UVM在其他phase中几乎没有做任何相关的事情,完全可以不必加上super.xxxx_phase语句
- 上述说法只适用于直接扩展自uvm_component的类。 如果是扩展自用户自定义的类, 如base_test类, 且在其某个phase, 如connect_phase中定义了一些重要内容, 那么在具体测试用例的connect_phase中就不应该省略super.connect_phase。
代码改写Q&A
chnl_pkg
1、do_drive()中为什么修改后需要加上动态转换?
因为SVlab里我们自己定义的clone函数返回的是子类chnl_trans句柄,可以直接传递句柄;而uvm调用的clone函数返回的永远是uvm_object父类句柄。而我们需要访问子类对象,所以需要将父类的句柄转化为子类句柄。转化可以成功,因为该父类句柄指向的是子类对象。
2、chnl_generator不是要改成sequence和sequencer吗?
在uvmlab2里暂时不改造,等我们学到相应的部分再进行修改。(uvmlab4再改)这里先暂时继承于uvm_component。
3、create和new分别在什么时候用?
①如果是object类,在不需要被覆盖或者配置的前提下,那么用new或者type_id::create来创建都可以;如果有可能被覆盖,那就用type_id::create来创建。
对小白来说,可以统一在build_phase里用type_id::create来创建;
②如果是组件,那就一定要采用create创建组件。这不仅可以在工厂里注册,方便做覆盖,而且帮助实现了层次化结构,为之后的配置提供方便。
③既不是object也不是component类的,用new来创建。比如端口类port是继承于uvm_void,就只能用new来创建。
4、改造后的chnl_generator里的sprint不加super会如何?
如果省略super,那么会存在一个同名的问题。因为uvm这个函数也叫sprint,所以编译器会认为你要调用的是当前的函数。
如果把当前的sprint函数改下名字,那么问题就可以解决。因为当前类不存在sprint函数,所以会自动去父类里搜索sprint函数。首先去uvm_component搜,搜不到就继续往上,直到uvm_object里。借助域的自动化,找到sprint。所以此时不加super也没问题。
5、driver、monitor、agent声明中的local string name为什么不需要了?
在组件中,name是已经预先定义的变量名,不需要再定义了。new函数中的name指的就是预先定义好的变量。
6、chnl_agent里例化组件部分放置的位置区别
在sv中,创建组件就是在new函数中进行;而在uvm中,创建组件一般在build_phase中进行。更详细的说明见reg_pkg部分的第一点说明:mailbox实例的创建是放在new还是build_phase里
7、uvm中不需要分别调用子组件的run,那谁在控制运行?
层次的执行以及phase之间的顺序都由uvm_root自动安排,不需要手动执行子组件的run函数了。
对于run_phase这个空的方法,其实可以直接整个删掉也无所谓。此处保留只是为了对比而已。
reg_pkg
1、实例的创建是放在new还是build_phase里?
我们前面提到,在sv中,创建组件就是在new函数中进行;而在uvm中,创建组件需要在build_phase中进行。那么实例的创建要放在哪里咧?
分两种情况。
①如果组件是用new例化的,那么就放new函数里或者是build_phase都可以的。
②如果是使用create例化的,那就一定要放在build_phase中,才能保证覆盖、配置正常进行。
或者最简单的,可以把所有实例的创建都放在build_phase里,这样一定不会有问题。
mcdf_pkg
1、为什么要把do_compare改名为do_data_compare?
do_compare是compare预定义好的回调函数,改名可以防止和预定义的函数起冲突。
2、为什么都用uvm_config_db传递接口了,还保留着set_interface函数?
在mcdf_env中,路桑暂时保留了mcdf_env以及各个子组件的set_ingerface函数,只是改造了TB和mcdf_env的接口传递而已。具体就是:通过uvm_config_db完成了各接口从TB(硬件一侧)到验证环境mcdf_env(软件一侧)的传递,之后再通过mcdf_env与其各个子组件的set_interface进行接口的传递;
当然,我们也可以移除所有的set_interface函数,完全采用uvm_config_db set和get方法,从而使mcdf_env与其各个子组件也实现层次剥离,进一步促进组件之间的独立性。
3、改写成uvm后,$finish()去掉了,仿真如何结束?
phase.raise_objection(this);
......
phase.drop_objection(this);
uvm中是通过在某个uvm_test里的run_phase里raise_objection,防止仿真退出,并在执行结束后drop_objection;不需要单独调用finish来结束仿真。因为所有phase执行结束后会自动退出仿真。
4、如果改写完后的某个phase是空的,可以删除吗?
virtual function void report_phase(uvm_phase phase);
super.report_phase(phase);
// this.env.do_report();
// rpt_pkg::do_report();
endfunction
对于上面这个report_phase,其内容是空的,可以直接删掉。run_phase如果是空的也可以直接删去。
二、测试的开始和结束
代码改写
tb
1、uvm_base_test还能通过set_interface来传递接口吗?
以前是通过在仿真的时候传递一个TESTNAME,在运行的时候就可以直接把TESTNAME对应的set_interface和run函数调用起来(前提是已经提前把每个test都例化了),实现接口的层层传递,最终到达底层的组件。
而在uvm中是通过uvm_config_db来传递接口的,在组件还没有创建之前就先把接口放在中转库。运行则是通过run_test。
可能有人问,可否和之前一样还是通过set_interface来传递接口?
不行,调用组件方法set_interface的前提是组件已经例化了。而实际上,uvm中组件的例化是在run_test()中(因为run_test会执行所有的phase,包括build_phase),所以不行。
那如果把run_test放在set_interface之前呢?
也不行。因为run_test开始之后会进入build_phase。此时会发现没有interface的指针,所以也会有问题。
总结:
1、SV中接口传递的问题:
- 环境准备好了,组件例化了后才能传递;
- 每一层都要有set_interface,然后层层传递进去(好几个类中都要声明set_interface)这不利于软件环境的封装和复用。
2、而通过uvm_config_db可以将接口先放在一个中转库,可以使得接口的传递和获取彻底分开,直接将接口传递一步到位,而不是层层传递;无需担心测试台层次结构中任何组件的深度。
2、run_test()中有无参数的区别何在?
如果有参数,意味着mcdf_data_consistence_basic_test是默认的test,如果仿真选项中没有指定UVM_TESTNAME,那么就默认执行它;
run_test("mcdf_data_consistence_basic_test");
如果没有参数,也就意味着没有默认test。那么仿真选项就必须要指定test,否则会报错;
如果通过UVM_TESTNAME指定了某个test,那么UVM会从命令行中寻找测试用例的名字,创建它的实例并运行。
run_test();
- 选择正确的test
- 通过test构建层次
- 让每个phase按顺序执行
- 通过objection机制控制仿真的结束