第五章 UVM验证平台的运行

phase机制

task phase与function phase

UVM中的phase,按照其是否消耗仿真时间($time打印出的时间)的特性,可以分成两大类,一类是function phase,如 build_phase、connect_phase等,这些phase都不耗费仿真时间,通过函数来实现;另外一类是task phase,如run_phase等,它们耗费 仿真时间,通过任务来实现。给DUT施加激励、监测DUT的输出都是在这些phase中完成的。在图5-1中,灰色背景所示的是task phase,其他为function phase。

UVM中的phase

UVM中的phase

上述所有的phase都会按照图中的顺序自上而下自动执行。

运行上述代码,可以看到各phase被依次执行。在这些phase中,令人疑惑的是task phase。对于function phase来说,在同一时 间只有一个phase在执行;但是task phase中,run_phase和pre_reset_phase等12个小的phase并行运行。后者称为动态运行(run- time)的phase。对于task phase,从全局的观点来看其顺序大致如下:

fork
begin
run_phase();
end
begin
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();
end
join

动态运行phase

动态运行(run-time)phase是UVM1.0引入的新的phase,其他phase则在UVM1.0之前(即UVM1.0EA版和OVM中)就已经存在了。

UVM为什么引入这12个小的phase呢?分成小的phase是为了实现更加精细化的控制。reset、configure、main、shutdown四个 phase是核心,这四个phase通常模拟DUT的正常工作方式,在reset_phase对DUT进行复位、初始化等操作,在configure_phase则进 行DUT的配置,DUT的运行主要在main_phase完成,shutdown_phase则是做一些与DUT断电相关的操作。通过细分实现对DUT更 加精确的控制。假设要在运行过程中对DUT进行一次复位(reset)操作,在没有这些细分的phase之前,这种操作要在 scoreboard、reference model等加入一些额外的代码来保证验证平台不会出错。但是有了这些小的phase之后,分别在scoreboard、 reference model及其他部分(如driver、monitor等)的reset_phase写好相关代码,之后如果想做一次复位操作,那么只要通过phase 的跳转,就会自动跳转回reset_phase。

phase的执行顺序

对于UVM树来说,共有三种顺序可以选择,一是自上而下,二是自下而上,三是随机序。最后一种方式是不受人控制的,在编程当中,这种不受控制的代码越少越好。因此可以选择的无非就是自上而下或者自下而上。

除了自上而下的执行方式外,UVM的phase还有一种执行方式是自下而上。事实上,除了build_phase之外,所有不耗费仿真时间的phase(即function phase)都是自下而上执行的。如对于connect_phase即先执行driver和monitor的connect_phase,再执行agent的 connect_phase。

对于同一个层次的,具有兄弟关系的component,比如driver与monitor,执行顺序是按照字典顺序排列的。

UVM树的遍历

在图3-2中,除了兄弟关系的component,还有一种叔侄关系的component,如my_scoreboard与my_driver,从树的层次结构上来说,scoreboard级别是高于driver的,但是,这两者build_phase的执行顺序其实也是不确定的。这两者的执行顺序除了上节提到的字典序外,还用到了图论中树的遍历方式:广度优先或是深度优先。

UVM中采用的是深度优先的原则,对于图3-2中的scoreboard及driver的build_phase的执行顺序,i_agt实例化时名字为“i_agt”,而scb为“scb”,那么i_agt的build_phase先执行,在执行完毕后,接下来执行driver、monitor及sequencer的build_phase。当全部执行完毕后再执行scoreboard的build_phase:

# UVM_INFO my_agent.sv(29) @ 0: uvm_test_top.env.i_agt [agent] build_phase
# UVM_INFO my_driver.sv(16) @ 0: uvm_test_top.env.i_agt.drv [driver] build_phase
# UVM_INFO my_agent.sv(29) @ 0: uvm_test_top.env.o_agt [agent] build_phase
# UVM_INFO my_scoreboard.sv(23) @ 0: uvm_test_top.env.scb [scb] build_phase

super.phase的内容

在前文的代码中,有时候出现super.xxxx_phase语句,有些时候又不会出现。如在main_phase中,有时出现super.main_phase,有时又不会;在build_phase中,则一般会出现super.build_phase。那么uvm_component在其各个phase中都默认做了哪些事情呢?哪些phase应该加上super.xxxx_phase,哪些又可以不加呢?

对于build_phase来说,uvm_component对其做的最重要的事情就是3.5.3节所示的自动获取通过config_db::set设置的参数。如果要关掉这个功能,可以在自己的build_phase中不调用super.build_phase。

除了build_phase外,UVM在其他phase中几乎没有做任何相关的事情:

build阶段出现UVM_ERROR停止仿真

在2.2.4节的代码清单2-18中,如果使用config_db::get无法得到virtual interface,就会直接调用uvm_fatal结束仿真。由于virtual interface对于一个driver来说是必须的,所以这种uvm_fatal直接退出的使用方式是非常常见的。

但是,事实上,如果这里使用uvm_error,也会退出:

文件:src/ch5/section5.1/5.1.6/my_driver.sv
12 virtual function void build_phase(uvm_phase phase);
13 super.build_phase(phase);
14 if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
15 `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
16 `uvm_error("my_driver", "UVM_ERROR test")
17 endfunction

这里给出的uvm_fatal是UVM内部自定义的。在end_of_elaboration_phase及其前的phase中,如果出现了一个或多个UVM_ERROR,那么UVM就认为出现了致命错误,会调用uvm_fatal结束仿真。

UVM的这个特性在小型设计中体现不出优势,但是在大型设计中,这一特性非常有用。大型设计中,真正仿真前的编译、优化可能会花费一个多小时的时间。完成编译、优化后开始仿真,几秒钟后,出现一个uvm_fatal就停止仿真。当修复了这个问题后,再次重新运行,发现又有一个uvm_fatal出现。如此反复,可能会耗费大量时间。但是如果将这些uvm_fatal替换为uvm_error,将所有类似的问题一次性暴露出来,一次性修复,这会极大缩减时间,提高效率。

phase的跳转

phase之间可以互相跳来跳去。

假如在验证平台中监测到reset_n信号为低电平,则马上从main_phase跳转到reset_phase。driver的代码如下:

文件:src/ch5/section5.1/5.1.7/my_driver.sv
23 task my_driver::reset_phase(uvm_phase phase);
24 phase.raise_objection(this);
25 `uvm_info("driver", "reset phase", UVM_LOW)
26 vif.data <= 8'b0;
27 vif.valid <= 1'b0;
28 while(!vif.rst_n)
29 @(posedge vif.clk);
30 phase.drop_objection(this);
31 endtask
32
33 task my_driver::main_phase(uvm_phase phase);
34 `uvm_info("driver", "main phase", UVM_LOW)
35 fork
36 while(1) begin
37 seq_item_port.get_next_item(req);
38 drive_one_pkt(req);
39 seq_item_port.item_done();
40 end
41 begin
42 @(negedge vif.rst_n);
43 phase.jump(uvm_reset_phase::get());
44 end
45 join
46 endtask

phase机制的必要性

只要将连接语句放在最后两行写就没有关系了。UVM采用了这种方法,它将前面实例化的部分都放在build_phase来做,而连接关系放在connect_phase来做,这就是phase最初始的来源。

phase的调试

UVM的phase机制是如此的复杂,如果碰到问题后每次都使用uvm_info在每个phase打印不同的信息显然是不能满足要求的。UVM提供命令行参数UVM_PHASE_TRACE来对phase机制进行调试,其使用方式为:

<sim command> +UVM_PHASE_TRACE

超时退出(time out)

文件:src/ch5/section5.1/5.1.10/base_test.sv
18 function void base_test::build_phase(uvm_phase phase);
19 super.build_phase(phase);
20 env = my_env::type_id::create("env", this);
21 uvm_top.set_timeout(500ns, 0);
22 endfunction

objection机制

objection与task phase

objection字面的意思就是反对、异议。在验证平台中,可以通过drop_objection来通知系统可以关闭验证平台。当然,在撤销之前首先要raise_objection。想象一下,如果读者与别人交流时事先并没有提出异议,然后忽然说:我撤销刚才的反对意见(objection)。那么,事先并没有提出任何反对意见的你一定会令对方迷惑不已,所以,为了良好的沟通,在drop_objection之前,一定要先raise_objection:

task main_phase(uvm_phase phase);
phase.raise_objection(this);
…
phase.drop_objection(this);
endtask

参数phase的必要性

在UVM中所有phase的函数/任务参数中,都有一个phase:

这个输入参数中的phase是什么意思?为什么要加入这样一个东西?看了上一小节的例子,应该能够回答这个问题了。因为要便于在任何component的main_phase中都能raise_objection,而要raise_objection则必须通过phase.raise_objection来完成,所以必须将phase作为参数传递到main_phase等任务中。可以想象,如果没有这个phase参数,那么想要提起一个objection就会比较麻烦了。

控制objection的最佳选择

在整棵UVM树中,树的结点是如此之多,那么在什么地方控制objection最合理呢?driver中、monitor中、agent中、scoreboard中抑或是env中?

在第2章的例子中,最初是在driver中raise_objection,但是事实上,在driver中raise_objection的时刻并不多。这是因为driver中通常都是一个无限循环的代码。

UVM的设计哲学就是全部由sequence来控制激励的生成,因此一般情况下只在sequence中控制objection。

set_drain_time的使用

无论任何功能的模块,都有其处理延时。如图5-5a所示,0时刻DUT开始接收输入,直到p时刻才有数据输出。

Untitled

在sequence中,n时刻发送完毕最后一个transaction,如果此时立刻drop_objection,那么最后在n+p时刻DUT输出的包将无法接收到。因此,在sequence中,最后一个包发送完毕后,要延时p时间才能drop_objection:

objection的调试

与phase的调试一样,UVM同样提供了命令行参数来进行objection的调试:

<sim command> +UVM_OBJECTION_TRACE

domain的应用

domain是UVM中一个用于组织不同组件的概念。先来看一个例子,假设DUT分成两个相对独立的部分,这两个独立的部分可以分别复位、配置、启动,但如果没有domain的概念,那么这两块独立的部分则必须同时在reset_phase复位,同时在configure_phase配置,同时进入main_phase开始正常工作。这种协同性当然是没有问题的,但是没有体现出独立性。图5-6中画出了这两个部分的driver位于同一domain的情况。

Untitled

两个driver位于同一个domain

Untitled

两个driver位于不同domain

Last modification:February 28, 2022
恰饭环节