5.1 phase机制
*5.1.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并行运行。后者称为动态运行(runtime)的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
UVM提供了如此多的phase,在一般的应用中,无论是function phase还是task phase都不会将它们全部用上。使用频率最高的是build_phase、connect_phase和main_phase。这么多phase除了方便验证人员将不同的代码写在不同的phase外,还有利于其他验证方法学向UVM迁移。一般的验证方法学都会把仿真分成不同的阶段,但是这些阶段的划分通常没有UVM分得这么多、这么细
致。所以一般来说,当其他验证方法学向UVM迁移的时候,总能找到一个phase来对应原来方法学中的仿真阶段,这为迁移提供
了便利。
5.1.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。
关于跳转的内容,请参考5.1.7节。
*5.1.3 phase的执行顺序
5.1.1节笼统地说明了phase是自上而下执行的,而在3.5.4节时曾经提到过,build_phase是一种自上而下执行的。但这两种“自
上而下”是有不同含义的。
5.1.1节中的自上而下是时间的概念,不同的phase按照图5-1中所示的phase顺序自上而下执行。而3.5.4节所说的自上而下是空间的概念,即在图3-2中,先执行的是my_case的build_phase,其次是env的build_phase,一层层往下执行。这种自上而下的顺序其实是唯一的选择。
对于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:
//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节。
*5.1.4 UVM树的遍历
在图3-2中,除了兄弟关系的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.1.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中几乎没有做任何相关的事情:
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;
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;
endfunction
function void uvm_component::connect(); return; endfunction
function void uvm_component::start_of_simulation(); return; endfunction
function void uvm_component::end_of_elaboration(); return; endfunction
task uvm_component::run(); return; endtask
function void uvm_component::extract(); return; endfunction
function void uvm_component::check(); return; endfunction
function void uvm_component::report(); return; endfunction
function void uvm_component::final_phase(uvm_phase phase); return; endfunction
task uvm_component::pre_reset_phase(uvm_phase phase); return; endtask
task uvm_component::reset_phase(uvm_phase phase); return; endtask
task uvm_component::post_reset_phase(uvm_phase phase); return; endtask
task uvm_component::pre_configure_phase(uvm_phase phase); return; endtask
task uvm_component::configure_phase(uvm_phase phase); return; endtask
task uvm_component::post_configure_phase(uvm_phase phase); return; endtask
task uvm_component::pre_main_phase(uvm_phase phase); return; endtask
task uvm_component::main_phase(uvm_phase phase); return; endtask
task uvm_component::post_main_phase(uvm_phase phase); return; endtask
task uvm_component::pre_shutdown_phase(uvm_phase phase); return; endtask
task uvm_component::shutdown_phase(uvm_phase phase); return; endtask
task 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。