UVM Phases
所有testbench的组件都是继承uvm_component来的,每一个组件都包括一些预定义的同名phase,在没有执行完所有组件的当前phase之前绝对不会去执行所有组件的下一个phase ,这就保证了所有组件之间是同步的!UVM就是利用phase机制实现了各个组件之间的同步!
因为所有的phase都被定义为回调(callback)方法,因此各个组件可以自动执行回调phase方法.
phase机制存在的意义在于:传统的硬件设计模型在仿真开始之前就已经完成了例化和连接,而SV的软件部分对象例化则在仿真开始之后执行。在SV中对象例化通过new()来实现,但是简单的new无法解决的一个重要问题就是验证环境在实现层次化时如何保证例化的先后关系,以及各个组件在例化之后的连接。此外,如果要实现高级功能,例如在顶层到底层的配置时,SV也无法在底层组件例化之前完成对底层的配置。所以,UVM在验证环境构建时引入了phase机制,通过该机制我们可以很清晰的将UVM仿真阶段层次化。这里的层次化,不仅仅是指各个phase的先后执行顺序,处于同一phase中的层次化组件之间的phase也有先后关系。
在定义了各个phase虚方法后,UVM环境会按照phase的顺序分别调用这些方法。首先来看phase主要分为哪些:
总的来说,phase可以分为3大类,如上图:
(1)build time phase
(2)run time phase
(3)clean-up phase
3大类中每一类又包括几种phase,总共的phase为9种,如下表所示:
前4种属于build time phase;第5中为run time phase;后4种属于clean-up phase;在testbench中9种phase按上表的顺序依次执行。在9种phase中常用的phase包括build_phase、connect_phase、run_phase和report_phase,它们分别完成了组件的创建、连接、运行和报告.这些phase在uvm_component中通过_phase的后缀完成了虚方法的定义,例如build_phase可以定义一些组件例化和配置的任务.
逻辑上,首先要创建各个组件的对象,所以最先执行的就是build_phase,只有先创建高层次的组件才会有空间来容纳低层次的组件,所以它的执行顺序是自顶向下的;然后要进行各个组件的连接,所以执行connect_phase,首先要进行底层组件的连接,所以它的执行顺序是自底向上的;build time phase的后两种phase基本不会用到,主要用来显示UVM的层次结构信息;测试激励在run_phase被发送到DUT中,run_phase和其他run time phase之间都是并行的;
在所有的phase中,只有run_phase是一个可以消耗时间的任务,这意味该phase可以完成一些等待、激励、采样的任务;其他的的phase都是函数,必须立即返回(0耗时).
在run_phase中,用户如果要完成测试,通常需要组织下面的激励序列:
(1)上电和复位
(2)寄存器配置
(3)发送测试的内容
(4)等待DUT完成测试
用户发送激励的一种简单方式是在run_phase中完成上面所有的激励;另一种方式是将上面的几种序列划分到不同的区间,让对应的激励按区间顺序发送,则可以让测试更有层次。因此,run_phase又划分为12个phase:
reset_phase主要对DUT进行复位、初始化等操作;configure_phase主要是对DUT中的进行配置;main_phase中完成DUT的运行;shutdown_phase完成对DUT的断电相关操作;
请注意,run_phase与这12个phase之间是并行执行的,12个phase是顺序执行的!!顺序执行也仅仅是在单个组件当中,也就是说在一个组件当中这12个phase是顺序执行的,但是这并不意味着是立刻执行;例如,存在组件A和组件B,组件A的main_phase在100ns时完成,而组件B的main_phase在200ns时完成,此时A和B的post_main_phase都是在200ns后开始执行的!
在介绍完各个phase以及它们之间的执行顺序之后,结合硬件和软件的编译来统一理解UVM世界中的编译运行顺序,如下图:
- 在加载硬件模型调用仿真器之前,要完成编译和建模阶段;
- 在仿真开始之前,分别执行硬件的always/initial语句,以及UVM的调用测试方法run_test()和几个phase,分别是build_phase、connect_phase、end_of_elaboration_phase、start_of_simulation_phase;
- 在仿真开始后,执行run_phase或对应的12个细分的phase;
- 在仿真结束后,执行剩余的phase,分别是extract_phase、check_phase、report_phase、final_phase;
对于phase机制,这里有一些建议:
(1)避免使用12个细分的phase;
(2)避免phase的跳跃;
(3)避免自定义phase的使用
如何开始UVM仿真
从UVM的应用角度出发,在仿真开始时建立验证环境,可以考虑选择下面几种方式:
(1)通过全局函数(由uvm_pkg提供)run_test()来选择性的指定要运行哪个uvm_test,这里的test均派生自uvm_test,指定的test将被例化并指定为顶层的组件;一般来说,run_test函数可以在合适的module/program中的intial块中调用;
(2)如果没有任何参数传递给run_test,可以在仿真时通过传递参数+UVM_TESTNAME=<test_name>指定仿真时要调用的uvm_test;当然,即使run_test函数在调用时已经有test参数传递,也可以通过 +UVM_TESTNAME=<test_name>从顶层覆盖指定的test;
如何结束UVM仿真
UVM利用objection机制来结束仿真
uvm中通过objection机制来控制phase的运行与结束,objection机制一般用于run_phase和sequence的控制,uvm会检查该phase中是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后结束该phase的运行.两个方法的定义如下:
virtual function void raise_objection (
uvm_object obj,
string description = "",
int count = 1
)virtual function void drop_objection (
uvm_object obj,
string description = "",
int count = 1
)//一般只会用到第一个参数
看下面的例子:
class my_driver extends uvm_driver;`uvm_component_utils(my_driver)function new(string name = "my_driver", uvm_component parent = null);super.new(name, parent);`uvm_info("my_driver", "new is called", UVM_LOW);endfunctionextern virtual task main_phase(uvm_phase phase);
endclasstask my_driver::main_phase(uvm_phase phase);phase.raise_objection(this);//开启phase`uvm_info("my_driver", "main_phase is called", UVM_LOW);top_tb.rxd <= 8'b0; top_tb.rx_dv <= 1'b0;while(!top_tb.rst_n)@(posedge top_tb.clk);for(int i = 0; i < 256; i++)begin@(posedge top_tb.clk);top_tb.rxd <= $urandom_range(0, 255);top_tb.rx_dv <= 1'b1;`uvm_info("my_driver", "data is drived", UVM_LOW);end@(posedge top_tb.clk);top_tb.rx_dv <= 1'b0;phase.drop_objection(this);//结束phase
endtask
raise_phase和drop_phase都是成对出现的,raise_phase语句必须出现在run_phase中第一条消耗时间的语句之前,否则无法起作用! 对于sequence中,一般在body()方法中调用raise_objection()和drop_objection()方法.
参数 phase 的含义
在 UVM 的所有component的 phase 自动执行函数(任务) 的参数中,都有一个 phase:
task main_phase(uvm_phase phase);
....
endtask
因为要便于在任何 component 的 main_phase中都能 raise_objection,而要 raise_objection 则必须通过 phase.raise_objection 来完成,所以必须把 phase 做为参数传递到 main_phase 等任务中。可以想像,如果没有这个phase 参数,那么想要 raise 一个 objection 就会比较麻烦了;
这里比较有意思的一个问题是:类似 build_phase 等 function phase 是否可以 raise 和 drop objection 呢?
这个问题的答案是肯定的。在 1.1 版本中是可以的,系统并不会报错。不过,一般不会这么用。 phase 的引入是为了解决何时结束仿真的问题,它更多的面向类似driver 和 scoreboard 在 main_phase 中的无限循环, 而不是面向 function phase;
一般在什么地方 raise_objection
事实上,在 driver中 raise_objection 的时刻并不多。这是因为, driver 中通常都是一个无限循环的代码,如下所示:
task driver::main_phase(uvm_phase pahse);
super.main_phase(phase);
while(1) begin
seq_item_port.get_next_item(req);
…//drive the interface according to the information in req
end
endtask
如果是在 while(1)的前面 raise_objection,在 while 循环的 end 后面 drop_objection,那么由于无限循环的特性, phase.drop_objection 永远不会被执行到。
一般在 sequence 中把 sequencer 的 objection 给 raise 起来,当 sequence 完成后,再 drop 此 objection,从而控制验证平台的关闭,如下所示:
virtual task body();if(starting_phase != null)starting_phase.raise_objection(this);repeat (10) begin`uvm_do(m_trans)end#100;if(starting_phase != null)starting_phase.drop_objection(this);endtask
但是在 sequence 中进行 raise_objection 的一个问题是, raise_objection 是属于phase 的一个函数,即我们只能以如下的方式调用此函数:
phase.raise_objection(this);
而 phase 是属于 component 的一个概念,是 component 专属的东西,而 sequence的本质是一个 object,是没有 phase 的。那么怎么办?
这个问题其实非常简单,我们可以在 uvm_sequence 中加一个指向 phase 的指针,然后当 sequencer 在 main_phase 中启动 default_sequence 时,把 sequencer 的 main_phase中的 phase 赋值给 sequence 中这个指针。这样在 sequence 中就可以进行 objection 操作了。
UVM 中就是这么做的。在 sequence 中,这个指向 phase 的指针的名字是starting_phase。因此,我们可以在 sequence 中这么做:
task my_sequencer::main_phase(uvm_phase phase);
my_sequence my_seq;
super.main_phase(phase);
my_seq = new("my_seq");
my_seq.starting_phase = phase;//将phase赋给starting_phase
my_seq.start(this);
endtask
task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
…
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
由于 starting_phase 只是一个指针,所以为了保险起见,要判断一下它是否为 null;
Creating user-defined phases
UVM中允许用户自定义phase,下表示自定义phase与UVM phase的执行顺序:
使用自定义phase的步骤:
(1)创建并定义一个新phase类;
(2)将新创建的phase加入到schedule;
(3)在支持该phase的组件中使用这个phase;
用 domain 来划分不同的家庭
什么是 domain? 通俗点来说, UVM 中的 domain 就是家庭单位。 在同一 domain中,大家都听从同一个家长(phase)的安排,所有的 component 在同一时刻进入某一 phase(如 main_phase),又在同一时刻退出 main_phase(即使某个 component 已经执行完了 main_phase,它也要等在那里, 等待其它 component 完成 main_phase),但是在不同的 domain 中,家长(phase)是不一样的,一个 domain 中的 component不需要听从另外一个 domain 中家 长(phase)的安排,所以两个 domain 中的 component何时进入某一 task phase 并不需要一致;
domain 的例子
假设我们的 DUT 分成两个相对独立的部分,这两个独立的部分可以分别复位,分别配置,分别启动,那么如果没有 domain 的概念,那么这两块独立的部分必须都同时在 reset_phase 复位,同时在 configure_phase 配置,同时进入main_phase 开始正常工作。这种协同性当然是没有问题的,但是没有体现出独立性。
要体现出独立性,那么两个部分的 reset_phase, configure_phae, main_phase 等就不应该同步,这里就要用到 domain 的概念:
domain 把两块时钟域隔开,之后两个时钟域内的各个动态运行(run_time)的phase 就可以不必同步。注意,这里 domain 只能把 run_time 的 phase 给隔离开来,对于其它的 phase,其实还是同步的,即两个 domain 的 run_phase 依然是同步的,其
它的 function phase 也是同步的;
多 domain 与单 domain 的区别
多 domain 与单 domain 的区别主要体现在 phase 和 objection 上。前面我们得到了结论,所有的 component 的 main_phase 都是同一个 phase,但是这个结论只适应于单 domain。如果两个 component 分别属于不同的 domain,那么这两个 component的 main_phase(及其它 run_time phase)就不是同一个 phase。这个事实其实从上节的图中也可以看的出来。
由这个事实带来的一个问题就是在 objection 的控制上。我们在上面说,要在某一个 run_time 的 phase(如 main_phase)中执行语句,让仿真时间前移,那么至少要在一个 component 的相应的 phase 中 raise_objection(如至少要在 driver 的 main_phase中 raise_objection)。在不同的 domain 中,各自的 run_time phase 各不相同(domain A的 main_phase 与 domain B 的 main_phase 不相同),因此要在各自的 domain 中至少要找一个 component 来 raise_objection,使用的时候要注意这一点;