[UVM源代码研究] 如何通俗易懂的理解UVM环境的运行机制
引言
一直想从宏观一点的角度讲讲UVM环境从启动到运行的完整过程而不必拘泥于一些细枝末节的源代码实现,由于工作上比较忙一直没有静下心来好好研究研究,今天正好有时间决定着手边研究边写,其实一切的动机还是源自于作者本身对于phase机制的一些实现还不甚了解,就比如一些基本概念:
-
uvm_phase是个什么样的类?为什么我们实际搭建uvm环境的时候很少会用到,但是在实现phase方法的时候又需要传递一个uvm_phase类型的参数?这个参数究竟是做什么用的?
-
phase机制我们虽然没有实例化uvm_phase实例,但是我们写的那些phase方法分别是在什么时候被调用的,uvm源代码是怎么实现phase机制的调度的?自顶向下、自下向上调用这些phase方法是如何实现的?
本文试图从宏观角度搭配一些简单的代码截图,浅显易懂的将上述几个问题讲述明白,想要具体了解实现机理,可以参考之前写过的一篇关于UVM的PHASE机制的文章:[UVM源代码研究] 浅谈UVM PHASE机制的运行
UVM源代码溯源
我们在 [UVM源代码研究] 当我们在tb里调用run_test()时uvm环境是如何启动的 一文中讲述了UVM验证环境是如何从run_test()任务启动的,其中关于下图1中的m_run_phases()这个任务并没有展开讲解,而UVM中的PHASE机制就是在m_run_phases()中实现的。
图1 src/base/uvm_root.svh中的代码截图
图1中截图可以看到,整个UVM环境的执行过程都是在m_run_phases()中完成的,run_test()后面做的只是在等待m_phase_all_done的值变为1,然后就是打印结果退出仿真了。而这个变量m_phase_all_done的赋值也是在所有phase的最后一个phase执行完之后赋值的(后面我们分析代码的时候还会讲到)。
m_run_phases()的代码如图2所示。
图2 src/base/uvm_phase.svh中的代码截图
在 [UVM源代码研究] 浅谈UVM PHASE机制的运行 一文中我们已经对get_common_domain有过具体的分析这里就不在赘述,m_run_phases()这个任务可以理解为将common_domain(这里简化问题起见,不考虑用户自定义的uvm_phase_domain)里添加的所有phase节点按照顺序分别调用他们的execute_phase()任务,由于使用的是fork … join_none,这些所有的phase节点的execute_phase()任务都会在同一时间被执行,但是execute_phase()中加了相关的控制,必须是common_domain中的所有phase节点组成的有向无环图中的前一个phase节点执行完才会执行后一个节点的excute_phase()中的内容,如图3所示,这样就实现了phase机制中的phase必须按照一定的顺序执行这一特点。
图3 src/base/uvm_phase.svh中的代码截图
等到前序节点都执行完就开始后续节点的执行,如图4所示。
图4 src/base/uvm_phase.svh中的代码截图
1147行的m_imp指向的是第一个phase节点即uvm_build_phase的单例,调用其中的traverse函数,由于uvm_build_phase是从uvm_topdown_phase继承而来,先执行其中的build_phase()函数才执行其children中的build_phase,而他的children是在其build_phase()函数中才会被创建的,这就是为什么uvm_build_phase必须是一个uvm_topdown_phase,否则连children都没有创建该如何去遍历traverse()其中的build_phase()函数。关于这个traverse()任务在uvm_phase类中并没有具体实现,在uvm_phase的子类(uvm_topdown_phase,uvm_bottomup_phase,uvm_task_phase等等)中才分别有具体的实现,如图5所示。
图5 src/base/uvm_topdown_phase.svh中的代码截图
traverse()中的核心函数是execute,execute中又会根据具体的phase类型调用对应的phase方法,相关代码截图如图6、图7所示。
图6 src/base/uvm_topdown_phase.svh中的代码截图
图7 src/base/uvm_common_phase.svh中的代码截图
至于其他phase节点的执行过程类似于uvm_build_phase类的分析方法,区别无非就是先执行父节点的phase方法还是先遍历执行子类的phase方法。
分析到这里,我们发现将phase机制跟最终执行的某个uvm_component中的phase方法关联起来的UVM源代码方法执行路径是:
run_test() -> m_run_phases() -> execute_phase() -> traverse() -> execute() -> execute_func(comp, phase)/execute_task(comp, phase)
最后我们再看下前文讲run_test()提到的m_phase_all_done变量是在哪里赋值的,如图8所示,即执行到最后一个phase节点(m_successor.size() == 0表明这是最后一个节点)的execute_phase()结束的时候赋值。
图8 src/base/uvm_phase.svh中的代码截图
总结
本文从run_test()任务作为切入点,循着m_run_phase()执行的轨迹找到了我们在各个uvm_component中定义的phase方法是如何被调用执行的,回答了引言中提出的几个问题,从宏观上更好地理解了PHASE机制运行的原理。
最后附一张UVM中的phase类的继承关系以及相应的方法调用关系图。