本文转自:http://www.eetop.cn/blog/html/28/1561828-2331501.html
在之前SV的篇章中,读者可以看到,传统的硬件设计模型在仿真开始前,已经完成例化和连接了;而SV的软件部分,类的例化则需要在仿真开始后完成。虽然类的例化通过调用构建函数new()来实现,但是单单通过new()函数无法解决一个重要的问题,那就是验证环境层次化时,需要保证例化的先后关系,以及在确立了各个组件均完成例化后的连接。此外,如果需要实现高级功能,例如顶层到底层的配置时,也无法在底层组件例化之前,提前完成配置逻辑。因此,UVM的验证环境构建中,引入了phase机制,通过该机制,可以很清晰地将UVM仿真的阶段层次化。这里的层次化,不单单是对于各个phase的先后顺序,而且处于同一phase中的层次化组件之间的phase也有先后关系。本文将从机制和应用方面介绍phase概念,最后就UVM仿真的开始和结束方式进行阐述。
phase执行机制
如果暂时抛开phase的机制剖析,对于UVM组件的开发者而言,他们主要关心各个phase之间执行的先后顺序。在完成各个phase虚方法的实现之后,UVM环境会按照phase的顺序分别调用这些方法。
首先来看一看,UVM的phase有哪些?
phase | 函数/任务 | 执行顺序 | 功能 | 典型应用 |
build | 函数 | 自顶向下 | 创建和配置测试平台的结构 | 创建组件和寄存器模型,设置或者获取配置 |
connect | 函数 | 自底向上 | 建立组件之间的连接 | 连接TLM/TLM2的端口,连接寄存器模型和adapter |
end_of_elaboration | 函数 | 自底向上 | 测试环境的微调 | 显示环境结构,打开文件,为组件添加额外配置 |
start_of_simulation | 函数 | 自底向上 | 准备测试环境的仿真 | 显示环境结构,设置断点,设置初始运行的配置值 |
run | 任务 | 自底向上 | 激励设计 | 提供激励、采集数据和数据比较,与OVM兼容 |
extract | 函数 | 自底向上 | 从测试环境中收集数据 | 从测试平台提取剩余数据,从设计观察最终状态 |
check | 函数 | 自底向上 | 检查任何不期望的行为 | 检查不期望的数据 |
report | 函数 | 自底向上 | 报告测试结果 | 报告测试结果,将结果写入到文件中 |
final | 函数 | 自顶向下 | 完成测试活动结束仿真 | 关闭文件,结束联合仿真引擎 |
关于执行的顺序,可以从下面这段简单的例码中得到佐证:
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);
endfunctionfunction void build_phase(uvm_phase phase);`uvm_info("build_phase", "", UVM_LOW)
endfunctionfunction void connect_phase(uvm_phase phase);`uvm_info("connect_phase", "", UVM_LOW)
endfunctionfunction void end_of_elaboration_phase(uvm_phase phase);`uvm_info("end_of_elaboration_phase", "", UVM_LOW)
endfunctionfunction void start_of_simulation_phase(uvm_phase phase);`uvm_info("start_of_simulation_phase", "", UVM_LOW)
endfunctiontask run_phase(uvm_phase phase);`uvm_info("run_phase", "", UVM_LOW)
endtaskfunction void extract_phase(uvm_phase phase);`uvm_info("extract_phase", "", UVM_LOW)
endfunctionfunction void check_phase(uvm_phase phase);`uvm_info("check_phase", "", UVM_LOW)
endfunctionfunction void report_phase(uvm_phase phase);`uvm_info("report_phase", "", UVM_LOW)
endfunctionfunction void final_phase(uvm_phase phase);`uvm_info("final_phase", "", UVM_LOW)
endfunction
endclassclass topcomp extends subcomp;
subcomp c1, c2;
`uvm_component_utils(topcomp)
function new(string name, uvm_component parent);super.new(name, parent);
endfunctionfunction 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
endclassclass test1 extends uvm_test;
topcomp t1;
`uvm_component_utils(test1)function new(string name, uvm_component parent);super.new(name, parent);
endfunctionfunction void build_phase(uvm_phase phase);t1 = topcomp::type_id::create("t1", this);
endfunction
endclassinitial begin
//t1 = new("t1", null);
run_test("test1");
endendmodule
输出结果:
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);
endfunctionfunction void start_of_simulation_phase(uvm_phase phase);`uvm_info("start_of_simulation", "", UVM_LOW)
endfunctiontask 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);
endtasktask reset_phase(uvm_phase phase);`uvm_info("reset_phase", "", UVM_LOW)
endtasktask configure_phase(uvm_phase phase);`uvm_info("configure_phase", "", UVM_LOW)
endtasktask main_phase(uvm_phase phase);`uvm_info("main_phase", "", UVM_LOW)
endtasktask shutdown_phase(uvm_phase phase);`uvm_info("shutdown_phase", "", UVM_LOW)
endtaskfunction void extract_phase(uvm_phase phase);`uvm_info("extract_phase", "", UVM_LOW)
endfunction
endclassinitial beginrun_test("test1");
endendmodule
输出结果:
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。关于执行的关系,可以从下面这张图中得出:
这里需要提醒用户的是,虽然run_phase与细分的12个phase是并行执行的,而12个phase也是按照先后顺序执行的。为了避免不必要的干扰,用户可以选择run_phase,或者12个phase中的若干来完成激励,但是请不要将它们混合起来使用,因为这样容易导致执行关系的不明确。
如果要进一步深入phase机制的话,我们首先需要清晰下面的概念:phase、schedule和domain。
- phase即上面介绍的部分,特定的phase会完成特定的功能。
- schedule包含phase的关联数组,即若干个phase会由schedule按照安排的顺序先后执行。
- domain则内置一个schedule。
- schedule类uvm_schedule和domain类uvm_domain均继承于uvm_phase。
上面首先介绍的9个phase,共同构成了common domain;而另外12个phase,则共同构成了uvm domain。无论是domain、还是phase,它们在UVM环境中都只生成一个唯一的对象。关于common domain和uvm domain的联系和区别是:
- common domain无法被扩展或者取代,这是UVM phase机制的核心。也就是说,构成它的9个phase的顺序不能更改,也无法添加新的phase。同时,这一核心的domain也是为了与OVM的phase机制保持兼容,方便从OVM代码转换到UVM代码。
- uvm domain则包含了上面的12个phase,其调度也是按照先后顺序执行的。对于这一部分,与common domain不同的是,它们的执行是与run_phase同时开始,并且最后共同结束的。同时,用户还可以自定义一些phase,添加到uvm domain中,设置好与其他phase执行的先后关系。
上面的common domain和uvm domain中包含的phase在uvm_pkg中例化的唯一phase实例群如下:
在详细介绍完UVM的各个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。
对于使用phase机制,这里有一些建议:
- 避免使用reset_phase()、configure_phase()、main_phase()、shutdown_phase()和其它pre_/post_ phase。这12个phase尽管细化了run_phase(),但是也使得phase的跳转过为冗余,在将来的UVM版本中,这些phase将考虑被废除。为了控制reset、configure、main和shutdown各个阶段的任务调度和同步,用户可以考虑fork-join语句块,或者高级的同步方式,例如uvm_barrier和uvm_event。
- 避免phase的跳跃。实际上,用户可以指定个别组件的phase执行中,从phaseA跳跃到phaseC,而忽略了phaseB。但是这种方式不容易理解和调试,所以不建议使用这一特性。
- 避免自定义phase的使用。尽管uvm domain中允许用户自定义phase,并且规定新添加phase的执行顺序,但是目前的这一机制还不方便调试。用户应该尽量将置于新phase中的任务,剥离到新的任务中,并且在已有的phase中调用它们,完成任务调用的模块化。
从之前的例子和上面的图中,读者可以看到,UVM的环境建立和各个phase的先后调用的入口,都是从run_test()进入的。默认情况下,如果run_test()方法执行完毕,那么系统函数$finish则会被调用,来终止仿真。然而,有更多的方法来分别控制UVM仿真的开始和结束,我们接下来则分别介绍这些方法的应用。