本文主要是介绍芯片验证 | UVM的phase机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
最近在尝试搭建UVM的环境,作为一个入门新手,关于phase的概念不是很清晰,这里记录一下。
看了蛮多的Blog,内容都很不错。最后还是打开了白皮书,书的内容更加的全面。以下内容全部来自白皮书。
phase机制
1 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。
上述所有的phase都会按照图中的顺序自上而下自动执行:
文件:src/ch5/section5.1/5.1.1/my_case0.sv
4 class my_case0 extends base_test;
5 string tID = get_type_name();
…
11 virtual function void build_phase(uvm_phase phase);
12 super.build_phase(phase);
13 uvm_info(tID, "build_phase is executed", UVM_LOW)
14 endfunction
15 …
26 virtual function void start_of_simulation_phase(uvm_phase phase);
27 super.start_of_simulation_phase(phase);
28 uvm_info(tID, "start_of_simulation_phase is executed", UVM_LOW)
29 endfunction
30 31 virtual task run_phase(uvm_phase phase);
32 `uvm_info(tID, "run_phase is executed", UVM_LOW)
33 endtask
34 35 virtual task pre_reset_phase(uvm_phase phase);
36 `uvm_info(tID, "pre_reset_phase is executed", UVM_LOW)
37 endtask
…
79 virtual task post_shutdown_phase(uvm_phase phase);
80 `uvm_info(tID, "post_shutdown_phase is executed", UVM_LOW)
81 endtask
82 83 virtual function void extract_phase(uvm_phase phase);
84 super.extract_phase(phase);
85 `uvm_info(tID, "extract_phase is executed", UVM_LOW)
86 endfunction
…
98 virtual function void final_phase(uvm_phase phase);
99 super.final_phase(phase);
100 `uvm_info(tID, "final_phase is executed", UVM_LOW)
101 endfunction
102 103 104 endclass
运行上述代码,可以看到各phase被依次执行。
在这些phase中,令人疑惑的是task phase。
-
对于function phase来说,在同一时间只有一个phase在执行;
-
但是task phase中,run_phase和pre_reset_phase等12个小的phase并行运行。
后者称为动态运行(run-time)的phase。对于task phase,从全局的观点来看其顺序大致如下:
forkbeginrun_phase();endbeginpre_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
UVM提供了如此多的phase,在一般的应用中,无论是function phase还是task phase都不会将它们全部用上。使用频率最高的是build_phase、connect_phase和main_phase。
这么多phase除了方便验证人员将不同的代码写在不同的phase外,还有利于其他验证方法学向UVM迁移。一般的验证方法学都会把仿真分成不同的阶段,但是这些阶段的划分通常没有UVM分得这么多、这么细致。
所以一般来说,当其他验证方法学向UVM迁移的时候,总能找到一个phase来对应原来方法学中的仿真阶段,这为迁移提供了便利。
2 动态运行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。
3 phase的执行顺序
1.1节笼统地说明了phase是自上而下执行的,而在3.5.4节时曾经提到过,build_ phase是一种自上而下执行的。但这两种“自上而下”是有不同含义的。
1.1节中的自上而下是时间的概念,不同的phase按照图5-1中所示的phase顺序自上而下执行。而3.5.4节所说的自上而下是空间的概念,即在图3-2中,先执行的是my_case的build_phase,其次是env的build_phase,一层层往下执行。这种自上而下的顺序其实是唯一的选择。
心中有着S的视觉
对于UVM树来说,共有三种顺序可以选择,一是自上而下,二是自下而上,三是随机序。最后一种方式是不受人控制的,在编程当中,这种不受控制的代码越少越好。因此可以选择的无非就是自上而下或者自下而上。
假如UVM不使用自上而下的方式执行build_phase,那会是什么情况呢?
UVM的设计哲学就是在build_phase中做实例化的工作,driver和monitor都是agent的成员变量,所以它们的实例化都要在agent的build_phase中执行。
如果在agent的build_phase之前执行driver的build_phase,此时driver还根本没有实例化,所以调用driver.build_phase只会引发错误。
UVM是在build_phase中做实例化工作,这里的实例化指的是uvm_component及其派生类变量的实例化,假如在其他phase实例化一个uvm_component,那么系统会报错。如果是uvm_object的实例化,则可以在任何phase完成,当然也包括build_phase了。
除了自上而下的执行方式外,UVM的phase还有一种执行方式是自下而上。事实上,除了build_phase之外,所有不耗费仿真时间的phase(即function phase)都是自下而上执行的。如对于connect_phase即先执行driver和monitor的connect_phase,再执行agent的connect_phase。
无论是自上而下还是自下而上,都只适应于UVM树中有直系关系的component。对于同一层次的、具有兄弟关系的component,如driver与monitor,它们的执行顺序如何呢?
一种猜测是按照实例化的顺序。如代码清单5-3中,A_inst0到A_inst3的build_phase是顺序执行的,这种猜测是错误的。
通过分析源代码,读者可以发现执行顺序是按照字典序的。这里的字典序的排序依据new时指定的名字。假如monitor在new时指定的名字为aaa,而driver的名字为bbb,那么将会先执行monitor的build_phase。
反之若monitor为mon,driver为drv,那么将会先执行driver的build_phase。如下面的代码:
文件:ch5/section5.1/5.1.3/brother/my_env.sv
4 class my_env extends uvm_env;
5 6 A A_inst0;
7 A A_inst1;
8 A A_inst2;
9 A A_inst3;
…
16 virtual function void build_phase(uvm_phase phase);
17 super.build_phase(phase);
18 19 A_inst0 = A::type_id::create("dddd", this);
20 A_inst1 = A::type_id::create("zzzz", this);
21 A_inst2 = A::type_id::create("jjjj", this);
22 A_inst3 = A::type_id::create("aaaa", this);
23 24 endfunction
25 26 `uvm_component_utils(my_env)
27 endclass
其中A的代码为:
文件:ch5/section5.1/5.1.3/brother/A.sv
3 class A extends uvm_component;
…
12 endclass
13 14 function void A::build_phase(uvm_phase phase);
15 super.build_phase(phase);
16 `uvm_info("A", "build_phase", UVM_LOW)
17 endfunction
18 19 function void A::connect_phase(uvm_phase phase);
20 super.connect_phase(phase);
21 `uvm_info("A", "connect_phase", UVM_LOW)
22 endfunction
输出的结果将会是:
# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.aaaa [A] build_phase
# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.dddd [A] build_phase
# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.jjjj [A] build_phase
# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.zzzz [A] build_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.aaaa [A] connect_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.dddd [A] connect_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.jjjj [A] connect_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.zzzz [A] connect_phase
这里可以清晰地看出无论是自上而下(build_phase)还是自下而上(connect_phase)的phase,其执行顺序都与实例化的顺序无关,而是严格按照实例化时指定名字的字典序。
只是这个顺序是在UVM1.1d源代码中找到的,UVM并未保证一直会是这个顺序。如果代码的执行必须依赖于这种顺序,例如要求必须先执行driver的build_phase,再执行monitor的build_phase,那么应该立即修改代码,杜绝这种依赖性在代码中出现。
类似run_phase、main_phase等task_phase也都是按照自下而上的顺序执行的。但是与前面function phase自下而上执行不同的是,这种task phase是耗费时间的,所以它并不是等到“下面”的phase(如driver的run_phase)执行完才执行“上面”的phase(如agent的run_phase),而是将这些run_phase通过fork…join_none的形式全部启动。
对于同一component来说,其12个run-time的phase是顺序执行的,但是它们也仅仅是顺序执行,**并不是说前面一个phase执行完就立即执行后一个phase。**以main_phase和post_main_phase为例,对于A component来说,其main_phase在0时刻开始执行,100时刻执行完毕:
所以,更准确的说法是自下而上的启动,同时在运行。
文件:src/ch5/section5.1/5.1.3/phase_wait/A.sv
19 task A::main_phase(uvm_phase phase);
20 phase.raise_objection(this);
21 `uvm_info("A", "main phase start", UVM_LOW)
22 #100;
23 `uvm_info("A", "main phase end", UVM_LOW)
24 phase.drop_objection(this);
25 endtask
26
27 task A::post_main_phase(uvm_phase phase);
28 phase.raise_objection(this);
29 `uvm_info("A", "post main phase start", UVM_LOW)
30 #300;
31 `uvm_info("A", "post main phase end", UVM_LOW)
32 phase.drop_objection(this);
33 endtask
对于B component来说,其main_phase在0时刻开始执行,200时刻执行完毕:
文件:src/ch5/section5.1/5.1.3/phase_wait/B.sv
13 task B::main_phase(uvm_phase phase);
14 phase.raise_objection(this);
15 `uvm_info("B", "main phase start", UVM_LOW)
16 #200;
17 `uvm_info("B", "main phase end", UVM_LOW)
18 phase.drop_objection(this);
19 endtask
20 21 task B::post_main_phase(uvm_phase phase);
22 phase.raise_objection(this);
23 `uvm_info("B", "post main phase start", UVM_LOW)
24 #200;
25 `uvm_info("B", "post main phase end", UVM_LOW)
26 phase.drop_objection(this);
27 endtask
此时整个验证平台的main_phase才执行完毕,接下来执行post_main_phase,即A和B的post_main_phase都是在200时刻开始执行。
假设A的post_main_phase执行完毕需要300个时间单位,而B只需要200个时间单位,无论是A或者B,其后续都没有其他耗时间的phase了,整个验证平台会在500时刻关闭。上述代码的执行结果如下:
# UVM_INFO B.sv(15) @ 0: uvm_test_top.env.B_inst [B] main phase start
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.A_inst [A] main phase start
# UVM_INFO A.sv(23) @ 100: uvm_test_top.env.A_inst [A] main phase end
# UVM_INFO B.sv(17) @ 200: uvm_test_top.env.B_inst [B] main phase end
# UVM_INFO B.sv(23) @ 200: uvm_test_top.env.B_inst [B] post main phase start
# UVM_INFO A.sv(29) @ 200: uvm_test_top.env.A_inst [A] post main phase start
# UVM_INFO B.sv(25) @ 400: uvm_test_top.env.B_inst [B] post main phase end
# UVM_INFO A.sv(31) @ 500: uvm_test_top.env.A_inst [A] post main phase end
可以看到对于A来说,main_phase在100时刻结束,其post_main_phase在200时刻开始执行。在100~200时刻,A处于等待B的状态,除了等待不做任何事情。B的post_ main_phase在400时刻结束,之后就处于等待A的状态。
这个过程如图5-2所示。
无论从A还是B的角度来看,都存在一段空白等待时间。但是从整个验证平台的角度来看,各个task phase之间是没有任何空白的。
上述的这种同步不仅适用于不同component的动态运行(run-time)phase之间,还适用于run_phase与run_phase之间。
这两种同步都是不同component之间的相同phase之间的同步。除了这两种同步外,还存在一种run_phase与post_shutdown_phase之间的同步。
这种同步的特殊之处在于,它是同一个component的不同类型phase(两类task phase,即run_phase与run-time phase)之间的同步,即同一个component的run_phase与其post_ shutdown_phase全部完成才会进入下一个phase(extract_phase)。
例如,假设整个验证平台中只在A中控制objection:
文件:src/ch5/section5.1/5.1.3/phase_wait2/A.sv
19 task A::post_shutdown_phase(uvm_phase phase);
20 phase.raise_objection(this);
21 `uvm_info("A", "post shutdown phase start", UVM_LOW)
22 #300;
23 `uvm_info("A", "post shutdown phase end", UVM_LOW)
24 phase.drop_objection(this);
25 endtask
26 27 task A::run_phase(uvm_phase phase);
28 phase.raise_objection(this);
29 `uvm_info("A", "run phase start", UVM_LOW)
30 #200;
31 `uvm_info("A", "run phase end", UVM_LOW)
32 phase.drop_objection(this);
33 endtask
在上述代码中,post_shutdown_phase在300时刻完成,而run_phase在200时刻完成。验证平台进入extract_phase的时刻是300。
从整个验证平台的角度来说,只有所有component的run_phase和post_shutdown_ phase都完成才能进入extract_phase。
无论是run-time phase之间的同步,还是run_phase与post_shutdown_phase之间的同步,或者是run_phase与run_phase之间的同步,它们都与objection机制密切相关。关于这一点,请参考5.2.1节。
4 UVM树的遍历
除了兄弟关系的component,还有一种叔侄关系的component,如my_ scoreboard与my_driver,从树的层次结构上来说,scoreboard级别是高于driver的,但是,这两者build_phase的执行顺序其实也是不确定的。
这两者的执行顺序除了上节提到的字典序外,还用到了图论中树的遍历方式:广度优先或是深度优先。
所谓广度优先,指的是如果i_agt的build_phase执行完毕后,接下来执行的是其兄弟component的build_phase,当所有兄弟的build_phase执行完毕后,再执行其孩子的build_ phase。
所谓深度优先,指的是如果i_agt的build_phase执行完毕后,它接下来执行的是其孩子的build_phase,如果孩子还有孩子,那么再继续执行下去,一直到整棵以i_agt为树根的UVM子树的build_phase执行完毕,之后再执行i_agt的兄弟的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
反之,如果i_agt实例化时是bbb,而scb为aaa,则会先执行scb的build_phase,再执行i_agt的build_phase,接下来是driver、monitor及sequencer的build_phase。
如果读者的代码中要求scoreboard的build_phase先于driver的build_phase执行,或者要求两者的顺序反过来,那么应该立即修改这种代码,去除这种对顺序的要求。
5 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中几乎没有做任何相关的事情:
来源:UVM源代码
function void uvm_component::connect_phase(uvm_phase phase);connect();return;
endfunction
function void uvm_component::start_of_simulation_phase(uvm_phase phase);start_of_simulation();return;
endfunction
function void uvm_component::end_of_elaboration_phase(uvm_phase phase);end_of_elaboration();return;
endfunction
task uvm_component::run_phase(uvm_phase phase);run();return;
endtask
function void uvm_component::extract_phase(uvm_phase phase);extract();return;
endfunction
function void uvm_component::check_phase(uvm_phase phase);check();return;
endfunction
function void uvm_component::report_phase(uvm_phase phase);report();return;endfunctionfunction void uvm_component::connect(); return; endfunctionfunction void uvm_component::start_of_simulation(); return; endfunctionfunction void uvm_component::end_of_elaboration(); return; endfunctiontask uvm_component::run(); return; endtaskfunction void uvm_component::extract(); return; endfunctionfunction void uvm_component::check(); return; endfunctionfunction void uvm_component::report(); return; endfunctionfunction void uvm_component::final_phase(uvm_phase phase); return;endfunctiontask uvm_component::pre_reset_phase(uvm_phase phase); return; endtasktask uvm_component::reset_phase(uvm_phase phase); return; endtasktask uvm_component::post_reset_phase(uvm_phase phase); return; endtasktask uvm_component::pre_configure_phase(uvm_phase phase); return; endtasktask uvm_component::configure_phase(uvm_phase phase); return; endtasktask uvm_component::post_configure_phase(uvm_phase phase); return; endtasktask uvm_component::pre_main_phase(uvm_phase phase); return; endtasktask uvm_component::main_phase(uvm_phase phase); return; endtasktask uvm_component::post_main_phase(uvm_phase phase); return; endtasktask uvm_component::pre_shutdown_phase(uvm_phase phase); return; endtasktask uvm_component::shutdown_phase(uvm_phase phase); return; endtasktask uvm_component::post_shutdown_phase(uvm_phase phase); return; endtask
由如上代码可以看出,除build_phase外,在写其他phase时,完全可以不必加上super.xxxx_phase语句,如第2章中所有的super.main_phase都可以去掉。
当然,这个结论只适用于直接扩展自uvm_component的类。如果是扩展自用户自定义的类,如base_test类,且在其某个phase,如connect_phase中定义了一些重要内容,那么在具体测试用例的connect_phase中就不应该省略super.connect_phase。
6 build阶段出现UVM_ERROR停止仿真
如果使用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_ERROR my_driver.sv(16) @ 0: uvm_test_top.env.i_agt.drv [my_driver] UVM_ ERROR test
# UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errors
这里给出的uvm_fatal是UVM内部自定义的。在end_of_elaboration_phase及其前的phase中,如果出现了一个或多个UVM_ERROR,那么UVM就认为出现了致命错误,会调用uvm_fatal结束仿真。
UVM的这个特性在小型设计中体现不出优势,但是在大型设计中,这一特性非常有用。大型设计中,真正仿真前的编译、优化可能会花费一个多小时的时间。
完成编译、优化后开始仿真,几秒钟后,出现一个uvm_fatal就停止仿真。当修复了这个问题后,再次重新运行,发现又有一个uvm_fatal出现。
如此反复,可能会耗费大量时间。但是如果将这些uvm_fatal替换为uvm_error,将所有类似的问题一次性暴露出来,一次性修复,这会极大缩减时间,提高效率。
7 phase的跳转
在之前的所有表述中,各个phase都是顺序执行的,前一个phase执行完才执行后一个。但是并没有介绍过当后一个phase执行后还可以再执行一次前面的phase。
而“跳转”这个词则完全打破了这种观念:phase之间可以互相跳来跳去。
phase的跳转是比较高级的功能,这里仅举一个最简单的例子,实现main_phase到reset_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
reset_phase主要做一些清理工作,并等待复位完成。main_phase中一旦监测到reset_n为低电平,则马上跳转到reset_phase。
在top_tb中,控制复位信号代码如下:
文件:src/ch5/section5.1/5.1.7/top_tb.sv
43 initial begin
44 rst_n = 1'b0;
45 #1000;
46 rst_n = 1'b1;
47 #3000;
48 rst_n = 1'b0;
49 #3000;
50 rst_n = 1'b1;
51 end
在my_case中控制objection代码如下:
文件:src/ch5/section5.1/5.1.7/my_case0.sv
14 task my_case0::reset_phase(uvm_phase phase);
15 `uvm_info("case0", "reset_phase", UVM_LOW)
16 endtask
17 18 task my_case0::main_phase(uvm_phase phase);
19 phase.raise_objection(this);
20 `uvm_info("case0", "main_phase", UVM_LOW)
21 #10000;
22 phase.drop_objection(this);
23 endtask
运行上述的例子,则显示:
# UVM_INFO my_case0.sv(15) @ 0: uvm_test_top [case0] reset_phase
# UVM_INFO my_driver.sv(25) @ 0: uvm_test_top.env.i_agt.drv [driver] reset phase
# UVM_INFO my_case0.sv(20) @ 1100: uvm_test_top [case0] main_phase
# UVM_INFO my_driver.sv(34) @ 1100: uvm_test_top.env.i_agt.drv [driver] main phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1314) @ 4000: repo-
rter[PH_JUMP] phase main (schedule uvm_sched, domain uvm) is jumping to
phase reset
# UVM_WARNING @ 4000: main_objection [OBJTN_CLEAR] Object 'uvm_top' cleared
ob jection counts for main_objection
# UVM_INFO my_case0.sv(15) @ 4000: uvm_test_top [case0] reset_phase
# UVM_INFO my_driver.sv(25) @ 4000: uvm_test_top.env.i_agt.drv [driver] reset
phase
# UVM_INFO my_case0.sv(20) @ 7100: uvm_test_top [case0] main_phase
# UVM_INFO my_driver.sv(34) @ 7100: uvm_test_top.env.i_agt.drv [driver] main phase
很明显,整个验证平台都从main_phase跳转到了reset_phase。在上述运行结果中,出现了一个UVM_WARNING。这是因为在my_driver中调用jump时,并没有把my_case0中提起的objection进行撤销。加入跳转后,整个验证平台phase的运行图实现变为如图5-3所示形式。
图中灰色区域的phase在整个运行图中出现了两次。
跳转中最难的地方在于跳转前后的清理和准备工作。如上面的运行结果中的警告信息就是因为没有及时对objection进行清理。对于scoreboard来说,这个问题可能尤其严重。
在跳转前,scoreboard的expect_queue中的数据应该清空,同时要容忍跳转后DUT可能输出一些异常数据。在my_driver中使用了jump函数,它的原型是:
来源:UVM源代码
function void uvm_phase::jump(uvm_phase phase);
jump函数的参数必须是一个uvm_phase类型的变量。在UVM中,这样的变量共有如下几个:
来源:UVM源代码
uvm_build_phase::get();
uvm_connect_phase::get();
uvm_end_of_elaboration_phase::get();
uvm_start_of_simulation_phase::get();
uvm_run_phase::get();
uvm_pre_reset_phase::get();
uvm_reset_phase::get();
uvm_post_reset_phase::get();
uvm_pre_configure_phase::get();
uvm_configure_phase::get();
uvm_post_configure_phase::get();
uvm_pre_main_phase::get();
uvm_main_phase::get();
uvm_post_main_phase::get();
uvm_pre_shutdown_phase::get();
uvm_shutdown_phase::get();
uvm_post_shutdown_phase::get();
uvm_extract_phase::get();
uvm_check_phase::get();
uvm_report_phase::get();
uvm_final_phase::get();
但并不是所有的phase都可以作为jump的参数。如代码清单5-10中将jump的参数替换为uvm_build_phase::get(),那么运行验证平台后会给出如下结果:
UVM_FATAL /home/landy/uvm/uvm-1.1d/src/base/uvm_root.svh(922) @ 4000: reporte
r [RUNPHSTIME] The run phase must start at time 0, current time is 4000. No non
-zero delays are allowed before run_test(), and pre-run user defined phases ma y
not consume simulation time before the start of the run phase.
所以往前跳转到从build到start_of_simulation的function phase是不可行的。如果把参数替换为uvm_run_phase::get(),也是不可行的:
UVM_FATAL /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1697) @ 4000: reporte
r [PH_BADJUMP] phase run is neither a predecessor or successor of phase main or is non
-existant, so we cannot jump to it. Phase control flow is now undefined so the
simulation must terminate
UVM会提示出run_phase不是main_phase的先驱phase或者后继phase。这非常容易理解。在图5-1中,run_phase是与12个动态运行的phase并行运行的,不存在任何先驱或者后继的关系。
那么哪些phase可以作为jump的参数呢?在代码清单5-14中,uvm_pre_reset_ phase::get()后的所有phase都可以。
代码清单5-10中从main_phase跳转到reset_phase是一种向前跳转,这种向前跳转中,只能是main_phase前的动态运行phase中的一个。
除了向前跳转外,还可以向后跳转。如从main_phase跳转到shutdown_phase。
在向后跳转中,除了动态运行的phase外,还可以是函数phase,如可以从main_phase跳转到final_phase。
8 phase机制的必要性
Verilog中有非阻塞赋值和阻塞赋值,相对应的,在仿真器中要实现分为NBA区域和Active区域,这样在不同的区域做不同的事情,可以避免因竞争关系导致的变量值不确定的情况。
同样的,验证平台是很复杂的,要搭建一个验证平台是一件相当繁杂的事情,要正确地掌握并理顺这些步骤是一个相当艰难的过程。
举一个最简单的例子,一个env下面会实例化agent、scoreboard、reference model等,agent下面又会有sequencer、driver、monitor。
并且,这些组件之间还有连接关系,如agent中monitor的输出要送给scoreboard或reference model,这种通信的前提是要先将reference model和scoreboard连接在一起。那么可以:
scoreboard = new;
reference_model = new;
reference_model.connect(scoreboard);
agent = new;
agent.driver = new;
agent.monitor = new;
agent.monitor.connect(scoreboard);
这里面反应出来的问题就是最后一句话一定要放在最后写,因为连接的前提是所有的组件已经实例化。但是,reference_model.connect(scoreboard)的要求则没有那么高,只需要在上述代码中reference_model = new之后任何一个地方编写即可。可以看出,代码的书写顺序会影响代码的实现。
若要将代码顺序的影响降低到最低,可以按照如下方式编写:
scoreboard = new;
reference_model = new;
agent = new;
agent.driver = new;
agent.monitor = new;
reference_model.connect(scoreboard);
agent.monitor.connect(scoreboard);
只要将连接语句放在最后两行写就没有关系了。UVM采用了这种方法,它将前面实例化的部分都放在build_phase来做,而连接关系放在connect_phase来做,这就是phase最初始的来源。
在不同时间做不同的事情,这就是UVM中phase的设计哲学。但是仅仅划分成phase是不够的,phase的自动执行功能才极大方便了用户。
在代码清单5-16中,当new语句执行完成后,后面的connect语句肯定就会自动执行。现引入phase的概念,将前面new的部分包裹进build_phase里面,把后面的connect语句包裹进connect_phase里面,很自然的,当build_phase执行结束就应该自动执行connect_phase。phase的引入在很大程度上解决了因代码顺序杂乱可能会引发的问题。
遵循UVM的代码顺序划分原则(如build_phase做实例化工作,connect_phase做连接工作等),可以在很大程度上减少验证平台开发者的工作量,使其从一部分杂乱的工作中解脱出来。
简直是太神奇!!!
9 phase的调试
UVM的phase机制是如此的复杂,如果碰到问题后每次都使用uvm_info在每个phase打印不同的信息显然是不能满足要求的。
UVM提供命令行参数UVM_PHASE_TRACE来对phase机制进行调试,其使用方式为:
<sim command> +UVM_PHASE_TRACE
这个命令的输出非常直观,下面列出了部分输出信息:
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1124) @ 0: reporter
[PH/TRC/STRT] Phase 'uvm.uvm_sched.reset' (id=184) Starting phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1203) @ 0: reporter
[PH/TRC/SKIP] Phase 'uvm.uvm_sched.reset' (id=184) No objections raised, skipping
phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1381) @ 0: reporter
[PH/TRC/DONE] Phase 'uvm.uvm_sched.reset' (id=184) Completed phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1403) @ 0: reporter
[PH/TRC/SCHEDULED] Phase 'uvm.uvm_sched.post_reset' (id=196) Scheduled from phase
uvm.uvm_sched.reset
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1124) @ 0: reporter
[PH/TRC/STRT] Phase 'uvm.uvm_sched.post_reset' (id=196) Starting phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1203) @ 0: reporter
[PH/TRC/SKIP] Phase 'uvm.uvm_sched.post_reset' (id=196) No objections raised,
skipping phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1381) @ 0: reporter
[PH/TRC/DONE] Phase 'uvm.uvm_sched.post_reset' (id=196) Completed phase
10 超时退出
在验证平台运行时,有时测试用例会出现挂起(hang up)的情况。在这种状态下,仿真时间一直向前走,driver或者monitor并没有发出或者收到transaction,也没有UVM_ ERROR出现。
一个测试用例的运行时间是可以预计的,如果超出了这个时间,那么通常就是出错了。在UVM中通过uvm_root的set_timeout函数可以设置超时时间:
文件: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
set_timeout函数有两个参数,第一个参数是要设置的时间,第二个参数表示此设置是否可以被其后的其他set_timeout语句覆盖。如上的代码将超时的时间定为500ns。
如果达到500ns时,测试用例还没有运行完毕,则会给出一条uvm_fatal的提示信息,并退出仿真。
默认的超时退出时间是9200s,是通过宏UVM_DEFAULT_TIMEOUT来指定的:
来源:UVM源代码
`define UVM_DEFAULT_TIMEOUT 9200s
除了可以在代码中设置超时退出时间外,还可以在命令行中设置:
<sim command> +UVM_TIMEOUT=<timeout>,<overridable>
其中timeout是要设置的时间,overridable表示能否被覆盖,其值可以是YES或者NO。如将超时退出时间设置为300ns,且可以被覆盖,代码如下:
<sim command> +UVM_TIMEOUT="300ns, YES"
这篇关于芯片验证 | UVM的phase机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!