在之前SV的篇章中,读者可以看到,传统的硬件设计模型在仿真开始前,已经完成例化和连接了;而SV的软件部分,类的例化则需要在仿真开始后完成。虽然类的例化通过调用构建函数new()来实现,但是单单通过new()函数无法解决一个重要的问题,那就是验证环境层次化时,需要保证例化的先后关系,以及在确立了各个组件均完成例化后的连接。此外,如果需要实现高级功能,例如顶层到底层的配置时,也无法在底层组件例化之前,提前完成配置逻辑。因此,UVM的验证环境构建中,引入了phase机制,通过该机制,可以很清晰地将UVM仿真的阶段层次化。这里的层次化,不单单是对于各个phase的先后顺序,而且处于同一phase中的层次化组件之间的phase也有先后关系。本文将从机制和应用方面介绍phase概念,最后就UVM仿真的开始和结束方式进行阐述。
phase执行机制
如果暂时抛开phase的机制剖析,对于UVM组件的开发者而言,他们主要关心各个phase之间执行的先后顺序。在完成各个phase虚方法的实现之后,UVM环境会按照phase的顺序分别调用这些方法。
首先来看一看,UVM的phase有哪些?
关于执行的顺序,可以从下面这段简单的例码中得到佐证:
module common_phase_order;
import uvm_pkg::*;
`include "uvm_macros.svh"
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;
`uvm_component_utils(topcomp)
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)
c1 = subcomp::type_id::create("c1", this);
c2 = subcomp::type_id::create("c2", this);
endfunction
endclass
class test1 extends uvm_test;
topcomp t1;
`uvm_component_utils(test1)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
t1 = topcomp::type_id::create("t1", this);
endfunction
endclass
initial begin
//t1 = new("t1", null);
run_test("test1");
end
endmodule
输出结果:
UVM_INFO @ 0: reporter [RNTST] Running test test1...
UVM_INFO @ 0: uvm_test_top.t1 [build_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [build_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [build_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [connect_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [connect_phase]
UVM_INFO @ 0: uvm_test_top.t1 [connect_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [end_of_elaboration_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [end_of_elaboration_phase]
UVM_INFO @ 0: uvm_test_top.t1 [end_of_elaboration_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [start_of_simulation_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [start_of_simulation_phase]
UVM_INFO @ 0: uvm_test_top.t1 [start_of_simulation_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [run_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [run_phase]
UVM_INFO @ 0: uvm_test_top.t1 [run_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [extract_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [extract_phase]
UVM_INFO @ 0: uvm_test_top.t1 [extract_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [check_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [check_phase]
UVM_INFO @ 0: uvm_test_top.t1 [check_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [report_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [report_phase]
UVM_INFO @ 0: uvm_test_top.t1 [report_phase]
UVM_INFO @ 0: uvm_test_top.t1 [final_phase]
UVM_INFO @ 0: uvm_test_top.t1.c1 [final_phase]
UVM_INFO @ 0: uvm_test_top.t1.c2 [final_phase]
从这个例子可以看出,上面的九个phase,对于一个测试环境的声明周期而言,是有固定的执行先后顺序的;同时,对于处于同一个phase的组件之间,执行也会按照层次的顺序或者自顶向下、或者自底向上来执行。这个简单的环境中,顶层测试组件test1中,例化了一个t1组件,而t1组件内又进一步例化了c1和c2组件。从执行的打印结果来看,需要注意的地方有:
- 对于build phase,执行顺序按照自顶向下,这符合验证结构建设的逻辑。因为只有先创建高层的组件,才会创建空间来容纳低层的组件。
- 只有uvm_component及其继承与uvm_component的子类,才会按照phase机制将上面九个phase先后执行完毕。
上面介绍的九个phase中,常用的phase包括build、connect、run和report,它们分别完成了组件的建立、连接、运行和报告。这些phase在uvm_component中通过_phase的后缀完成了虚方法的定义,比如build_phase()中可以定义一些例化组件和配置的任务。在这九个phase中,只有run_phase方法是一个可以耗时的任务,这意味着该方法中可以完成一些等待、激励、采样的任务。对于其它phase对应的方法,都是函数,必须即时返回(0耗时)。
在run_phase中,用户如果要完成测试,则通常需要经历下面的激励序列:
- 上电
- 复位
- 寄存器配置
- 主要测试内容
- 等待DUT完成测试
一种简单的方式是,用户在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
上面的12个phase的执行顺序也是前后排列的。那么这12个phase与run_phase是什么关系呢?我们通过这段例码来看看:
module uvm_phase_order;
import uvm_pkg::*;
`include "uvm_macros.svh"
class test1 extends uvm_test;
`uvm_component_utils(test1)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void start_of_simulation_phase(uvm_phase phase);
`uvm_info("start_of_simulation", "", UVM_LOW)
endfunction
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
task reset_phase(uvm_phase phase);
`uvm_info("reset_phase", "", UVM_LOW)
endtask
task configure_phase(uvm_phase phase);
`uvm_info("configure_phase", "", UVM_LOW)
endtask
task main_phase(uvm_phase phase);
`uvm_info("main_phase", "", UVM_LOW)
endtask
task shutdown_phase(uvm_phase phase);
`uvm_info("shutdown_phase", "", UVM_LOW)
endtask
function void extract_phase(uvm_phase phase);
`uvm_info("extract_phase", "", UVM_LOW)
endfunction
endclass
initial begin
run_test("test1");
end
endmodule
输出结果:
UVM_INFO @ 0: reporter [RNTST] Running test test1...
UVM_INFO @ 0: uvm_test_top [start_of_simulation]
UVM_INFO @ 0: uvm_test_top [run_phase] entered ..
UVM_INFO @ 0: uvm_test_top [reset_phase]
UVM_INFO @ 0: uvm_test_top [configure_phase]
UVM_INFO @ 0: uvm_test_top [main_phase]
UVM_INFO @ 0: uvm_test_top [shutdown_phase]
UVM_INFO @ 1000000: uvm_test_top [run_phase] exited ..
UVM_INFO @ 1000000: uvm_test_top [extract_phase]
从这个例子可以看到,实际上,run_phase任务和上面细分的12个phase是并行进行的。在start_of_simulation_phase任务执行以后,run_phase和reset_phase开始执行,而在shutdown_phase执行完之后,需要等待run_phase执行完以后,才能进入extract_phase。关于执行的关系,可以从下面这张图中得出: