[UVM源代码研究] 聊聊uvm_component与uvm_root(uvm-1.2版)
源代码截图都来自uvm-1.2的版本
uvm_component
uvm_component 的派生关系
uvm_component 派生自 uvm_object,而且并不是直接派生,在两者之间还有一个uvm_report_object。
uvm_component类派生关系图
uvm_object 是一个相对简单的类,它主要提供了两大类的方法接口:
- 用于field_automation 机制的方法,如print, copy, pack 等;
- 用于鉴别身份的方法,如 get_name, get_type_name, get_full_name 等。
其中关于 field_automation 机制的一些代码相对比较复杂。这个留在后面讲述 field_automation 机制时再仔细研究。
uvm_report_object 用于提供了UVM 中信息报告机制的一系列方法接口,如uvm_report_info, uvm_report_error, set_report_verbosity_level等,即我们在基础课程中学习的message机制相关方法都是在这里定义的(具体实现有些是在另外的类中完成的,如uvm_report_handler,这里只是做了一层封装而已),后面会专门介绍这种信息报告机制。
无论是 uvm_object 还是uvm_report_object,更多的只是提供一些方法接口,并不涉及什么高深的UVM处理机制,相对来说,uvm_component 里面的内容才更具有实质性意义的。
对于uvm_component 来说,有两大特点:
- 它的每个实例都是UVM树形结构的节点,每个节点创建时都需要指定parent
- 它有 phase 的概念
关于phase的部分将会放在phase 机制中讲述,本章中重点讲述 uvm_component 的树形组织结构。
uvm_component 树形结构的实现
src/base/uvm_component.svh中的源代码
上图uvm_component中1650-1652行定义的父节点和子节点构成了UVM的树形结构,从中我们
看到父节点和子节点有两点不同:
- 节点访问权限,m_parent的访问权限是public(缺省权限),而m_children/m_children_by_handle则是protected,即任何uvm_component的父节点都是可以从外部获取的,但是他所包含的子节点只有它和它的派生类中才可以访问。
- 节点数量,任何uvm_component都有且只有一个父节点m_parent,但是可以有多个子节点,子节点可以用两个联合数组表示,分别以string和uvm_component类型(类类型寻址实例)进行索引。
1653行声明的m_add_child,代码实现在1925-1947行,作用就是将子节点添加到父节点中定义的两个联合数组中,并且我们可以到该函数是在该uvm_component的构造函数new()中1900行调用的,这就是为什么我们在创建uvm_component的派生类时必须要指定parent的原因,因为它会将该类的实例添加到该parent中的子节点联合数组中,并且还会检查是否出现同名子节点重复添加的情况。
通过在每一个节点指定其父节点,就实现了UVM树形结构从数根树干、树叶的树形生长,并且同一层次的节点不允许同名,每个节点可以有多个子节点但只有一个父节点。
uvm_root
uvm_root的单例模式
src/base/uvm_root.svh中的源代码
src/base/uvm_coreservoce.svh中的源代码
单例模式(目的是让某个类有且只有一个实例)的实现步骤:
-
该类中声明一个static本类型的对象m_inst (242行)
-
需要将该类的构造函数new声明为protected类型(226行),即无法在类外部访问,只有通过该类中定义的get 函数中才能调用其构造函数new(334-337行),get函数必须是static类型的(90行),方便以uvm_root类调用,而不是通过实例调用。
-
get 函数中记录被调用的次数,如果这是第一次调用,那么就执行new函数,并将该实例赋值给步骤1中定义的m_inst并返回该实例;如果不是第一次调用,则直接将第一次创建的实例m_inst返回。(280-281行)
可以看到uvm_root具备了单例模式的所有因素,所以调用uvm_root在整个UVM环境运行时有且只有一个实例,并且在uvm_root的325行还定义了一个全局可见的uvm_root的实例常量uvm_top,这就是为什么我们可以直接使用uvm_top来调用uvm_root中定义的诸如print_topology()之类的函数的原因。
uvm_root在树形结构中的位置
src/base/uvm_root.svh中的源代码
src/base/uvm_component.svh中的源代码
从上文uvm_component的学习中我们知道uvm_root的父类是uvm_component,因而其创建的时候必须指定parent,但是我们看到uvm_root的构造函数new并未传递任何参数(不仅没有parent,甚至连name都没用),只是调用了父类中的new函数,传入参数name=“top”,parent=null,我们在回头看uvm_component中的new函数时发现1818-1821行对专门针对uvm_root这种构造函数new的特殊处理,这里并没有像其他uvm_component创建时调用m_add_child()将uvm_root添加到其父节点的子节点联合数组中,而是直接返回,因而uvm_root不存在父节点,显而易见,对于一个不存在父节点的节点,在树形结构中只能是处于最顶层的树根。
1823-1824行指定将uvm_root的实例赋值给了top,1853-1854、1896以及1900-1901行的作用是将任何指定parent为NULL的uvm_component实例的parent都指定为uvm_root的唯一实例,这样就保证了UVM有且只有一个树形结构,且树根为uvm_root的实例。1853行其实还暗含了name != “top”,否则函数在1820行就返回了,所以说虽然parent都是NULL,但是uvm_root是树根,其他的uvm_component只能按照1854行所做的作为uvm_root单例的子节点,1900-1901行就实现了添加为子节点的操作。
run_test()
src/base/uvm_globals.svh中的源代码
UVM基础课程中我们知道tb中调用run_test()可以启动UVM的验证环境,是如何做到的呢?
首先,我们在tb里调用的run_test()其实是UVM源代码封装在uvm_globals.svh中的一个全局函数,其内部实现如上图所示,本质上还是通过获取uvm_root的单例进而调用了uvm_root中的run_test()任务。
src/base/uvm_root.svh中的源代码
我们挑run_test()中比较重要的几个知识点讲下:
- 410行表明,run_test()是可以传递参数的(允许缺省为空字符串),437和462-465行(分别对应是否define宏UVM_NO_DPI的两个分支)表明如果运行时命令行通过UVM_TESTNAME传进的参数会覆盖run_test()调用时传进来的参数。
src/base/uvm_root.svh中的源代码
- 470行判断是否有传入参数给test_name,471-472获取uvm_factory的单例方便后续调用其中的函数。474-478判断uvm_root是否已经添加了名为”uvm_test_top”的子节点,如果有,则报错退出仿真。419、479-480行是利用前面产生的uvm_factory的单例调用其中的create_component_by_name()函数产生一个指向uvm_component的uvm_object_wrapper类型的指针并downcast给uvm_test_top,factory机制我们后面会有专门的章节来讲,这里可以简单理解为根据我们传进来的testcase的名字产生了一个实例名为uvm_test_top的uvm_component类型的句柄,该实例在UVM树形结构中的路径为uvm_test_top,并且父节点为NULL,由前面的分析我们可以知道,虽然父节点声明为NULL,但实际父节点会是一个不体现在树形结构路径上的uvm_root单例,所以我们传进来的这个testcase就是我们打印topology时的树根uvm_test_top,而其数据类型则是传进来的testcase所对应的数据类型(uvm_test的派生类)。482-487行则是判断上面生成uvm_test_top是否为NULL,为NULL表明传递参数有误,仿真终止。
src/base/uvm_root.svh中的源代码
- 490-498是判断uvm_root的m_children是否为空,为空则强制报错退出仿真。根据我们前面对uvm_component以及uvm_root的分析,上面创建uvm_test_top这个实例的同时,其实隐含着一个信息,就是会将该uvm_test_top实例添加为uvm_root单例的子节点,这样uvm_root里的m_children必然就不为空了,所以这里才有这么一个判断。
src/base/uvm_root.svh中的源代码
- 500-507行是打印要允许的case的信息。
src/base/uvm_root.svh中的源代码
-
510-521行可以理解为将phase机制运行起来,并等待所有phase执行结束然后kill上面开启的进程,我们后面会有专门的章节介绍phase机制,这里就不细说了。
-
523-527行执行的就是打印最终的report信息,包括UVM_INFO、UVM_ERROR等的统计信息,随后结束仿真。
通过以上对run_test()任务的分析,我们对于tb里面调用run_test()就能将UVM环境中具体的testcase跑起来,并最终在执行完所有的phase后结束仿真就有了一个比较清晰的认识,并且对于UVM树形结构的树根也有了比较全面的了解,即uvm_root的单例是树形结构的树根,但是该单例不会显示在topology上,topology上的树形结构的树根是uvm_test_top,它是所有testcase的实例名。