一、概述
SV的验证环境构建中,传统的硬件设计模型在仿真开始前,已经完成例化和连接了,而SV的软件部分对象例化则需要在仿真开始后执行。虽然对象例化通过调用构建函数new()
来实现,但是单单通过new()
函数无法解决在验证环境实现层次化时,无法保证例化的先后关系,以及各个组件在例化后的连接。如果需要实现高级功能,例如在顶层到到底层的配置时,SV也无法在底层组件例化之前完成对底层的配置逻辑。因此,UVM在验证环境构建时,引入了phase机制,通过该机制可以将UVM仿真阶段层次化,不单单是各个phase的先后执行顺序,而且处于同一phase中的层次化组件之间的phase也有先后关系。
二、9个phase执行机制
对于UVM组件,主要关心各个phase执行的先后顺序。在定义了各个phase虚方法后,UVM环境会按照phase的顺序分别调用这些方法。
这9个phase对于一个测试环境的生命周期而言,是有固定的先后执行顺序的,同时对于同一个phase中的组件,执行也会按照层次的顺序或者自顶向下或者自顶向上来执行。对于build_phase
,执行顺序按照自顶向下,这符合验证结构建设的逻辑,因为只有先例化高层组件,才会创建空间来容纳底层组件。只有uvm_component
及其继承于uvm_component
的子类,才会按照phase机制将上面的9个phase先后执行完毕,这些phase在uvm_component
中通过_phase
的后缀完成了虚方法的定义,比如build_phase()
可以定义一些组件例化和配置的任务。
class subcomp extends uvm_component;
`uvm_component_utils(subcomp)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
`uvm_info("build_phase", "", UVM_LOW)
endfunction
function void connect_phase(uvm_phase phase);
`uvm_info("connect_phase", "", UVM_LOW)
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
`uvm_info("end_of_elaboration_phase", "", UVM_LOW)
endfunction
function void start_of_simulation_phase(uvm_phase phase);
`uvm_info("start_of_simulation_phase", "", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
`uvm_info("run_phase", "", UVM_LOW)
endtask
function void extract_phase(uvm_phase phase);
`uvm_info("extract_phase", "", UVM_LOW)
endfunction
function void check_phase(uvm_phase phase);
`uvm_info("check_phase", "", UVM_LOW)
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("report_phase", "", UVM_LOW)
endfunction
function void final_phase(uvm_phase phase);
`uvm_info("final_phase", "", UVM_LOW)
endfunction
endclass
class topcomp extends subcomp;
subcomp c1, c2;
...
function void build_phase(uvm_phase phase);
`uvm_info("build_phase", "", UVM_LOW)
c1 = subcomp::type_id::create("c1", this);
c2 = subcomp::type_id::create("c2", this);
endfunction
endclass
class test1 extends uvm_test;
topcomp t1;
...
function void build_phase(uvm_phase phase);
t1 = topcomp::type_id::create("t1", this);
endfunction
endclass
仿真结果:
从输出结果来看,build
是一个自顶向下的执行顺序,先把顶层的t1
创建出来,然后再创建t1
里面的c1
和c2
,然后connect
是自底向上的执行顺序,先创建c1
和c2
然后再创建t1
,其他也同样按照phase所规定的执行顺序进行创建。
在所有的phase中,只有run_phase
方法是一个可以耗时的任务,这意味着该方法可以完成一些等待、激励、采样的任务。对于其它phase对于的方法都是函数,必须立即返回(0耗时)。在run_phase
中,如果要完成测试,通常需要组织下面的激励序列:上电、复位、寄存器配置、发送主要测试内容、等待DUT完成测试。
三、12个分支phase
发送激励的一种简单方式是,在run_phase
中完成上面所有的激励,另一种方式是,可以将上面几种典型序列划分到不同区间,让对应的激励按区间顺序发送的话,可以让测试更有层次。因此run_phase
又可以分为以下12个phase:
- pre_reset_phase
- reset_phase
- post_reset_phase
- pre_configure_phase
- configure_phase
- post_configure_phase
- pre_main_phase
- main_phase
- post_main_phase
- pre_shutdown_phase
- shutdown_phase
- post_shutdown_phase
实际上run_phase
任务和上面细分的12个phase是并行的,即在start_of_simulation_phase
任务执行以后,run_phase
和reset_phase
开始执行,而在shutdown_phase
执行完成之后,需要等待run_phase
执行完才可以进入extract_phase
。
四、UVM编译和运行顺序
- 首先在加载硬件模型调用仿真器之前,需要完成编译和建模阶段。
- 接下来在开始仿真之前,会分别执行硬件的always/initial语句,以及UVM的调用测试方法
run_test
和几个phase,分别是build、connect、end_of_elaboration、start_of_simulation。 - 在开始仿真后,将会执行run_phase或者对应的12个细分phase。
- 在仿真结束后,将会执行剩余的phase,分别是extract、check、report和final。
UVM仿真开始
要在仿真开始时建立验证环境,可以选择:
- 可以通过全局函数(由
uvm_pkg
提供)run_test()
来选择性地指定要运行哪一个uvm_test
。这里的test类均继承于uvm_test
。这样的话,指定的test类将被例化并指定为顶层的组件。一般而言,run_test
()函数可以在合适module/program
中的initial进程块中调用。 - 如果没有任何参数传递给
run_test()
,那么可以在仿真时通过传递参数+UVM_TESTNAME=<test_name>
,来指定仿真时调用的uvm_test
。即便run_test()
函数在调用时已经有test名称传递,在仿真时也可以从顶层覆盖已指定的test。
无论上面哪一种方式,都必须在顶层调用全局函数run_test()
,而全局函数run_test()
的重要性,正是从uvm_root
创建了一个UVM世界。
task run_test(string test_name="");
uvm_root top;
uvm_coreservice_t cs;
cs = uvm_coreservice_t::get();
top = cs.get_root();
top.run_test(test_name);
endtask
UVM顶层类uvm_root
,该类也继承于uvm_component
,它也是UVM环境结构中的一员,而它可以作为顶层结构类。它提供了一些像run_test()
的这种方法,来充当了UVM世界中的核心角色。在uvm_pkg
中,有且只有一个顶层类uvm_root
所例化的对象,即uvm_top
。
uvm_top
的核心作用包括:
- 作为隐形的UVM验证结构的顶层,任何其它的组件实例都在它之下,通过创建组件时指定parent来构成层次。如果parent设置为null,那么它将作为uvm_top的子组件。
- 控制所有组件的phase顺序。
- 索引功能。通过层次名称来索引组件实例。
- 报告配置。通过
uvm_top
来全局配置报告的繁简度。 - 全局报告设备。由于可以全局访问到
uvm_top
实例,所以UVM报告设备在组件内部和组件外部(例如module
和sequence
)都可以访问。
通过uvm_top调用方法run_test(test_name),uvm_top做了如下的初始化:
- 得到正确的
test_name
。 - 初始化objection机制(控制仿真退出)。
- 创建
uvm_test_top
实例。 - 调用phase控制方法,安排所有组件的phase方法执行顺序。
- 等待所有phase执行结束,关闭phase控制进程。
- 报告总结和结束仿真。
UVM仿真结束
UVM结束仿真的机制有且只有一种,那就是利用objection
挂起机制来控制仿真结束。uvm_objection
类提供了一种供所有component
和sequence
共享的计数器,如果有组件来挂起objection
,那么它还应该记得落下objection
。参与到objection
机制中的参与组件,可以独立的各自挂起objection,来防止run_phase
退出,但是只有这些组件都落下objection
后,uvm_objection
共享的counter
才会变为0,这意味run_phase
退出的条件满足,因此可以退出run_phase
。
对于uvm_objection类,用来反停止的控制方法包括:
//挂起objection
raise_objection(uvm_object obj=null, string description="", int count=1)
//落下objection
drop_objection(uvm_object obj=null, string description="", int count=1)
//设置退出时间
set_drain_time(uvm_object obj=null, time drain)
这几种方法,对于component()
而言,可以在run_phase()
中使用phase.raise_objection()
或者phase.drop_objection()
来控制run_phase
退出。最好为参数description
字符串提供说明,利于后期的调试,应该使用默认count
值,对于uvm_top
或者uvm_test_top
应该尽可能少地使用set_drain_time()
。
objection防止仿真退出
class test1 extends uvm_test;
...
task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("run_phase", "entered...", UVM_LOW)
#1us;
`uvm_info("run_phase", "exited...", UVM_LOW)
phase.drop_objection(this);
endtask
endclass