在uvm_component组件内,注册到factory机制的组件,一般内部都会有运行phase的function或task,保证在验证过程,环境能够有序执行,完成验证任务。UVM提供了两大类phase,一类是用function实现的function phase,一类是用task实现的task phase,两大类phase各有各的特点和使用场景。UVM的完整phase机制包括下图所示内容。
function phase主要包括:build_phase、connect_phase、end_of_elaboration_phase、start_of_simulation_phase、extact_phase、check_phase、report_phase、finial_phase。他们的主要功能:
- build_phase主要功能为uvm树形结构的建立,组件的实例化一般在build_phase中完成,还有就是会接受一些环境变量的传递,比如driver接受vif,一般在build_phase通过config_db::get获取。
- connect_phase主要功能为uvm树形结构的连接,主要包括组件之间基于TLM的互联等功能实现。
- end_of_elaboration_phase主要功能是在仿真之前要做的一些操作,比如组件实例化和连接之后,要完成组件之间参数的传递等功能。
- start_of_simulation_phase一般为环境在进入动态仿真运行之前做的一些动作,比如辅助类成员变量的初始化等。
- extact_phase主要为环境运行过后对于一些数据的处理,比如要完成数据的运行比对之前,需要对比对数据包的处理等。
- check_phase主要为环境的检查phase,包括数据包比对结果的检查,信息汇总等。
- report_phase为环境汇总信息并打印的phase,一般在环境完成数据比对处理,完成TLM建模后输出的验证结果汇总,打印到log中,会从report_phase进行展示。
- finial_phase为结束运行的phase,控制验证环境到最后的结束,可以在结束之前做一些环境的处理动作,比如判断TLM FIFO的状态等。
一般情况下,function phase不会全部使用,使用的最多的是build_phase和connect_phase,如果有需要增加的环境操作,又和build_phase以及connect_phase中的动作有冲突,一般会选择增加phase,check_phase和report_phase一般在scoreboard中会使用。
function phase是自上而下执行的,也就是先执行build_phase,建立uvm树形结构,再执行connect_phase,建立树形结构的连接关系,然后执行到start_of_simulation_phase之后,进入task phase的运行,task_phase又包含两类主要的phase,一个是run_phase,run_phase贯穿于整个task_phase,另外一类是12个子phase,按类别分为reset phase,configuration phase,main phase,shutdown phase,每个phase又包含pre,main和post。
- reset_phase主要执行的是验证环境在进入仿真运行之前的复位动作,将环境变量,或者interface的信号先进行复位动作,初始化。
- configure_phase主要执行验证环境在运行时的一些配置,一般运行前需要将环境配置成场景适配的模式,也包括DUT的初始化寄存器配置任务等。
- main_phase是主要执行仿真的phase,包括激励的发送,环境数据的处理等等
- shutdown_phase主要控制仿真的运行停止过程,包括下电流程等。
一般12个task phase是顺序执行的,也就是运行完reset的三个phase后再执行configure的相关phase,而run_phase和12个task phase之间又是并行执行的关系,也就是说,run_phase会贯穿12个phase运行的全过程。他们之间的关系可以表达为:
1. fork
2. begin
3. run_phase(uvm_phase phase);
4. end
5. begin
6. pre_reset_phase(uvm_phase phase);
7. reset_phase(uvm_phase phase);
8. post_reset_phase(uvm_phase phase);
9. pre_configure_phase(uvm_phase phase);
10. configure_phase(uvm_phase phase);
11. post_configure_phase(uvm_phase phase);
12. pre_main_phase(uvm_phase phase);
13. main_phase(uvm_phase phase);
14. post_main_phase(uvm_phase phase);
15. pre_shutdown_phase(uvm_phase phase);
16. shutdown_phase(uvm_phase phase);
17. post_shutdown_phase(uvm_phase phase);
18. end
19. join
function_phase在uvm树形结构的建立和连接,以及验证环境静态变量的初始化过程起到作用,因此一些节点的建立,和连接,以及相应静态参数初始化,应当写在验证环境的function phase中。
task phase为验证环境运行过程的主要phase,用于操作数据,驱动激励,实现数据比对,控制仿真运行等过程,内部实现为数据的处理,激励驱动等和时间有关的功能。
对于单个组件的phase运行顺序为顺序执行,先build phase,再执行到connect_phase,最后执行finial_phase,如下图所示:
那么对于不同component之间的执行顺序是什么样的,想象一下,uvm在运行过程,需要先建立uvm树形结构,对于function phase而言,承载的任务有建立树形结构和连接组件关系的作用,因此,function phase无非只有自顶向下建立树形结构,和自底向上连接树的节点,因此,对于build_phase而言,执行规则是自顶向下的运行,也就是说先执行顶层的build_phase,例如test_case0的build_phase,再执行env的build_phase,自顶向下建立树形结构。而connect_phase,包括后续的end_of_elaboration_phase和start_of_simulation_phase,以及其他function phase,都是自底向上执行的。
这里重点说明build_phase,自顶向下建立树形结构的过程,其实是一个树节点遍历的过程,那么同层级的节点,执行顺序是什么样的?例如agent和rm以及scoreboard之间build_phase的执行顺序是什么?实际上,同层级节点上,build_phase是按照注册到factory机制后,实例化的参数名的字典序执行,也就是说,在父节点的build_phase中,通过component::type_id::create(“component_a”,this),create函数的第一个参数表示当前节点的实例化名,那么字典序越靠前,越先执行。
那么对于叔侄关系的节点,比如scoreboard和driver,他们的执行顺序如何,计算机体系结构中,树的遍历有广度优先和深度优先的顺序,UVM是按照深度优先序遍历执行,也就是说,如果agent和scoreboard的实例化参数名分别是“aaa”和“bbb”,那么先执行agent的build_phase,然后是driver的,monitor的,指导agent的根节点遍历完成,再执行scoreboard;那如果反过来,会先执行scoreboard,再执行agent和根节点。总结一下:
- 同层级节点build_phase按照create函数第一个参数的字典序执行;
- 不同层级的节点,uvm按照深度优先遍历执行。
上节提到,task phase是验证环境主要运行的phase,包括run phase和12个子phase,实际上,在uvm树形结构的不同节点下,或者说在验证环境的不同组件之间,task phase都是一起执行的,但是task phase往往要配合objection机制一起使用,而run_phase和12个phase之间是并行执行的,也就是说run_phase会持续到post_shutdown_phase完成后,run_phase才运行完成。本节中,我们以所有组件的相同phase内部都进行raise_objection和drop_objection为例,说明task phase在不同组件之间的运行情况,假设验证环境中只存在两个组件A和B,组件A中有如下代码:(取12个子phase中的reset_phase,configure_phase,main_phase和shutdown_phase为例):
1. class A extends uvm_component;
2. `uvm_component_utils(A)
3. function void new(string name="A", uvm_component parent);
4. super.new(name, parent);
5. endfunction
6.
7. virtual function void build_phase(uvm_phase phase);
8. ......
9. virtual task reset_phase(uvm_phase phase);
10. super.reset_phase(phase);
11. phase.raise_objection(this);
12. `uvm_info(get_type_name(), "reset_phase is begin", UVM_HIGH);
13. #80;
14. `uvm_info(get_type_name(), "reset_phase is done", UVM_HIGH);
15. phase.drop_objection(this);
16. endtask
17.
18. virtual task main_phase(uvm_phase phase);
19. super.main_phase(phase);
20. phase.raise_objection(this);
21. `uvm_info(get_type_name(), "main_phase is begin", UVM_HIGH);
22. #100;
23. `uvm_info(get_type_name(), "main_phase is done", UVM_HIGH);
24. phase.drop_objection(this);
25. endtask
26.
27. virtual task shutdown_phase(uvm_phase phase);
28. super.shutdown_phase(phase);
29. phase.raise_objection(this);
30. `uvm_info(get_type_name(), "shutdown_phase is begin", UVM_HIGH);
31. #30;
32. `uvm_info(get_type_name(), "shutdown_phase is done", UVM_HIGH);
33. phase.drop_objection(this);
34. endtask
35. endclass
组件B代码:
1. class B extends uvm_component;
2. `uvm_component_utils(B)
3. function void new(string name="B", uvm_component parent);
4. super.new(name, parent);
5. endfunction
6.
7. virtual function void build_phase(uvm_phase phase);
8. ......
9. virtual task reset_phase(uvm_phase phase);
10. super.reset_phase(phase);
11. phase.raise_objection(this);
12. `uvm_info(get_type_name(), "reset_phase is begin", UVM_HIGH);
13. #30;
14. `uvm_info(get_type_name(), "reset_phase is done", UVM_HIGH);
15. phase.drop_objection(this);
16. endtask
17.
18. virtual task configure_phase(uvm_phase phase);
19. super.configure_phase(phase);
20. phase.raise_objection(this);
21. `uvm_info(get_type_name(), "configure_phaseis begin", UVM_HIGH);
22. #50;
23. `uvm_info(get_type_name(), "configure_phaseis done", UVM_HIGH);
24. phase.drop_objection(this);
25. endtask
26.
27. virtual task main_phase(uvm_phase phase);
28. super.main_phase(phase);
29. phase.raise_objection(this);
30. `uvm_info(get_type_name(), "main_phase is begin", UVM_HIGH);
31. #120;
32. `uvm_info(get_type_name(), "main_phase is done", UVM_HIGH);
33. phase.drop_objection(this);
34. endtask
35.
36.
37. endclass
先看reset_phase:组件A和组件B都存在reset_phase,A的reset_phase存在延时:80ns,组件B的reset_phase延时:30ns;那么A和B的reset_phase并行执行,经过30ns后,组件B的reset_phase运行结束,而组件A的reset_phase继续执行剩余50ns,此时组件B会hold住,不会继续执行后面的phase,直到组件A的reset_phase完成。
再看configure phase:组件A中不存在configure phase,而组件B存在,且内部具有延时50ns;因为task phase在12个子phase之间是顺序执行的,而在组件之间是并行执行,那么此时组件A不会执行main_phase,而是等待组件B执行完configure phase,经过50ns延时之后,在继续往下执行。
再看main_phase:组件A和B都具有main_phase,且组件A中存在延时100ns,组件B中存在延时120ns;此时组件A和B同时执行main_phase,组件A先执行完,等待组件B将剩余的120ns执行完毕,然后再进shutdown phase。
最后看shutdown_phase:组件A存在shutdown_phase,而组件B不存在,那么组件A会执行shutdown phase,而组件B会停驻等待组件A完成shutdown phase的执行,最后完成仿真的运行。
实际上,上述实例,组件A和组件B在验证环境运行的过程中,phase执行顺序其实是这样的:
可见动态运行phase之间是并行执行的,且需要等其他组件的phase执行完之后,再执行接下来的phase。实际上,这种规则下,phase中必须结合objection机制才是这样的规则,也就是说,phase内部运行阶段被raise_objection(this),和drop_objection(this)修饰。
那么run_phase会是什么情况?对于同一个组件来讲,如果存在run_phase,那么run_phase一定是等待post_shutdown_phase运行结束之后,才停止,假设在组件A中存在run_phase:
virtual task run_phase(uvm_phase phase);
2. super.run_phase(phase);
3. phase.raise_objection(this);
4. #150ns;
5. phase.drop_objection(this);
6. endtask
那么A的run_phase一定是在时间轴运行到280ns,完成shutdown_phase之后,再退出。那么如果B中也加入run_phase,并被raise_objection和drop_objection修饰,那么就要等组件A和B的run_phase以及post_shutdown_phase都运行结束,才结束整个task phase的运行。
对于这个规则,实际上需要配合objection机制使用,如果在某个phase内部没有objection的raise和drop,运行情况就又不一样。此过程在objection章节讲解。
2.3 调用super.xxx_phase(phase)
super.xxx_phase(phase)的使用,这句话的含义是,在运行的function或task内部调用父类的该function或者task,那么也就是说,如果一个类继承自某个类,而那个类中还有相同的phase,需要被重载的子类调用父类的phase而执行某些过程,那么就需要写明这句话。
实际上,super.xxx_phase(phase)在组件继承的uvm_xxxx内部,并没有写过相关的流程和代码,也就是说,大部分情况下,组件如果继承自uvm的component或者component基类,调用super.xxx_phase(phase)不会有什么影响。唯一有区别的就是,在build_phase中,调用super.build_phase(phase),如果有变量通过config_db::set的方式发送给该组件,且该变量在收信组件中注册到了factory机制中,同时注册到了field_automation机制中,可以在收信组件省略get语句。也就是说,super.build_phase(phase)会自动get注册到field_automation机制中的变量。
另外一种情况,就是用户自定义的组件中存在继承关系,且父类的phase中有一些初始化的动作,必须要执行,那么子类就可以调用super.phase,减少重复的代码,提升代码可重用性。举个例子:
一般在定义uvm树顶节点的test_case0时,会将用户初始化的一些操作封装成test_base,在test_base的main_phase中做一些初始化的操作,比如配置寄存器等,而用例继承自test_base,需要在用例中调用test_base中的main_phase,先执行初始化动作,再执行具体的用例,那么就需要调用super.main_phase(phase)。
uvm还对phase机制提供了可跳转的功能,想象一个场景,在driver中,main_phase里写明了驱动过程,但是如果突发复位,那么需要在main_phase中执行reset过程,那么其实就会用到phase的跳转,phase跳转的语法为在需要跳转的phase中调用:phase.jump(uvm_xxx_phase::get());
写段driver的代码看看:
1. class driver extends uvm_driver#(uvm_sequence_item);
2. ......
3. extern virtual task drive_pkt();
4. extern virtual task reset_phase(uvm_phase phase);
5. extern virtual task main_phase(uvm_phase phase);
6. ......
7. endclass
8.
9. task driver::reset_phase(uvm_phase phase);
10. super.reset_phase(phase);
11. phase.raise_objection(this);
12. vif.a <= 1'b0;
13. vif.b <= 2'h0;
14. ......
15. phase.drop_objection(this);
16. endtask:reset_phase
17.
18. task main_phase(uvm_phase phase);
19. super.main_phase(phase);
20. fork
21. while(1) begin
22. seq_item_port.get_next_item(seq);
23. drive_pkt();
24. seq_item_port.item_done();
25. end
26. begin
27. @(negedge vif.reset_n);
28. phase.jump(uvm_reset_phase::get());
29. end
30. join
31. endtask
32. endclass
可以看到28行,在线程内部发生复位后,可利用phase的跳转,跳到reset_phase,函数的传参使用里uvm_reset_phase的静态方法get(),实际上,除了reset_phase,其他phase都可以调用该方法。
uvm_build_phase::get() |
uvm_pre_main_phase::get() |
uvm_connect _phase::get() |
uvm_main_phase::get() |
uvm_end_of_elaboration_phase::get() |
uvm_post_main_phase::get() |
uvm_start_of_simulation_phase::get() |
uvm_pre_shutdown_phase::get() |
uvm_pre_reset_phase::get() |
uvm_shutdown_phase::get() |
uvm_reset_phase::get() |
uvm_post_shutdown_phase::get() |
uvm_post_reset_phase::get() |
uvm_extract_phase::get() |
uvm_pre_configure_phase::get() |
uvm_check_phase::get() |
uvm_configure_phase::get() |
uvm_report_phase::get() |
uvm_post_configure_phase::get() |
uvm_finial_phase::get() |
如果发生跳转,那么其他组件的main_phase就会无法调用drop_objection,此时环境会报warning,那么发生跳转之后,验证环境的执行即从main phase跳转到reset_phase,而重新执行reset_phase->configure_phase->main_phase。
向前跳转不能够跳到build_phase执行,因为运行task phase已经产生了运行时间,也就是说,main_phase的执行,仿真时间已经从0开始往后走,回调到build_phase要重新执行task phase走仿真运行时间,是不允许的。那么也就是说,main_phase的跳转,不能跳转到build_phase到start_of_simulation_phase之间的任意phase。
phase的跳转可以向后跳转,也就是说,如果发生复位操作,或者环境想控制发生某个异常而直接结束仿真,那么就可以直接跳转到finial_phase,也就是run time phase之后的function phase,直接结束仿真。
实际工程中,这种方式其实并不实用,因为通常工程上的验证环境组件都特别多,尤其如果scoreboard多了之后,其他组件的phase跳转,可能会造成其他组件的异常,比如scoreboard在extract phase中进行收到的包流比对,有可能造成多包或者丢包的情况,严重的会造成假pass,因此发生phase跳转,要清空scoreboard的数据包缓存中的数据包。
总结一下:
- phase跳转发生在某个动态运行的phase中,使用phase.jump(uvm_xxx_phase::get())实现跳转。
- 跳转发生后,重新执行跳转到的phase和其之后的其他phase,且其他组件中会包没有drop_objection warning。
- phase跳转可以向前跳转,但是不能在已经走了仿真时间phase跳转到build_phase到start_of_simulation_phase之间的任意需要仿真从0执行的function phase。
- phase可以向后跳转,且可以直接调转到结束仿真的function phase,如finial phase。
- phase机制使用的时候要格外小心,一般尽量减少这个功能的使用,因为会增加仿真环境的调试难度(多组件协同方面)。
回顾一下,uvm的verbosity机制中,如果突发uvm_fatal,仿真会直接结束,那么如果突发uvm_error,其实仿真是不会直接停止的,会运行到最后,或者运行到uvm_error的出现的最大次数后,仿真停止。实际上,如果在build_phase中突发uvm_error,验证环境也会直接结束,屏幕打印会出现uvm_fatal,这是为什么?uvm_phase机制的build_phase是建立uvm树形结构的主要phase,那么如果在build_phase中发生了uvm_error,uvm会认为无法建立树形结构,或者环境的重要参数无法获取,也就没有继续仿真的必要,因此会直接停掉。
实际上,不止build_phase,在end_of_elaboration_phase及其之前的function phase中,如果发生uvm_error,uvm都会认为发生了致命错误而停止仿真。
这个机制在大型设计中十分有帮助,因为大型设计里,编译和初始化往往要花费大量的时间,如果都用uvm_fatal,那么就会形成一个现象,就是修复一个uvm_fatal,运行一次,又会出现另一个uvm_fatal,效率十分低下。如果将end_of_elaboration_phase之前的phase的uvm_fatal全部改为uvm_error,可以极大提升仿真效率。
另外,UVM还提供了仿真超时停止的设定,可以在某个组件中,一般是base_test中,使用uvm_root.set_timeout(500ns, 0)的形式设置。这句话的含义是,仿真环境运行500ns后停止。uvm_root的set_timeout函数有两个参数,一个是时间,也就是运行多久后仿真停止,作为运行时间的上限,第二个参数是是否可以被其后的其他set_timeout函数所覆盖。如果是1,则可以被后面运行到的set_timeout函数覆盖。如果是0则不可以。
实际上,uvm提供了一个超时的宏定义:UVM_DEFAULT_TIMEOUT 9200s,也就是说,uvm最大可以运行9200s,这个数字对于eda仿真来讲,是非常大的。在这种机制下,uvm还可以通过在仿真选项上传参,来修改这个值,以达到设置仿真超时的效果,用法如下:
<vcs sim command> +UVM_TIMEOUT=”1000ns, YES”
UVM_TIMEOUT包括两个参数,一个是超时的具体时间,另一个是是否可以被覆盖。
总结一下:
- uvm提供了两种仿真运行超时时间的设定方式:1是通过uvm_root.set_timeout(时间,0/1),参数两个含义,一个是超时的具体实践上限,另一个是是否可以被后面的set_timeout所覆盖。2是通过仿真命令传参,使用方式为:<vcs command> +UVM_TIMEOUT=”时间,YES/NO”,第一参数为超时时间,第二个参数为是否可以被覆盖。
- uvm内置了仿真最长时间宏:UVM_DEFAULT_TIMEOUT 9200s,最长可运行9200s,改变这个宏也可以改变超时时间。
phase机制在运行过程中,如果组件很多,那么debug会显得十分复杂,uvm也提供了phase的调试手段,可以在命令行输入:<vcs command> +UVM_PHASE_TRACE,进行phase的打印调试输出,方便debug。