UVM世界观之七:phase机制(上)

本文转自: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);
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组件。从执行的打印结果来看,需要注意的地方有:

  1. 对于build phase,执行顺序按照自顶向下,这符合验证结构建设的逻辑。因为只有先创建高层的组件,才会创建空间来容纳低层的组件。
  2. 只有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中,用户如果要完成测试,则通常需要经历下面的激励序列:

  1. 上电
  2. 复位
  3. 寄存器配置
  4. 主要测试内容
  5. 等待DUT完成测试

一种简单的方式是,用户在run_phase中完成上面所有的激励;另外一种方式,如果可以将上面的几种典型的序列分到不同的区间,让对应的激励按区搁置的话,也能让测试更有层次。因此,run_phase又可以分为下面的12个phase:

  1. pre_reset_phase
  2. reset_phase
  3. post_reset_phase
  4. pre_configure_phase
  5. configure_phase
  6. post_configure_phase
  7. pre_main_phase
  8. main_phase
  9. post_main_phase
  10. pre_shutdown_phase
  11. shutdown_phase
  12. 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。关于执行的关系,可以从下面这张图中得出:

 

这里需要提醒用户的是,虽然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世界中的编译和运行顺序:

  1. 首先在加载硬件模型,调用仿真器之前,需要完成编译和建模阶段。
  2. 接下来,在开始仿真之前,会分别执行硬件的always/initial语句,以及UVM的调用测试方法run_test和几个phase,分别是build、connect、end_of_elaboration和start_of_simulation。
  3. 在开始仿真后,将会执行run_phase或者对应的12个细化phase。
  4. 在仿真结束后,将会执行剩余的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仿真的开始和结束,我们接下来则分别介绍这些方法的应用。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页