目录
1. uvm_object
2. uvm_component
3. 为什么在uvm_component 例化是需要指定一个parent?
4.uvm_component 的树形结构是如何组织起来的?
5. 静态函数与非静态函数的区别:
6. uvm_root 的单实例实现思路:
7. run_test 的背后
1. uvm_object
uvm_object是一个相对简单的类,它提供了一些基本的接口,如用于 field_automation 机制的 print,copy,pack 等,一些鉴别身份的,如 get_name,get_type_name,get_full_name 等。
2. uvm_component
对于 uvm_component 来说,有两大特点,一是它是一种树形组织结构中的一个结点,在其 new 的时候都要指定一个 parent,二是它有 phase 的概念。
3. 为什么在uvm_component 例化时需要指定一个parent?
假如不指定parent,compoent A 中有一个成员变量component B,在B例化的时候使用B=new(“B”),不添加parent,这样会导致在整体的环境框架中A 不知道B是他的孩子,因为没有指定,如果有些用户需要遍历整个UVM 树,那他是找不到的。所以我们一般会规范的加上parent,一旦加上parent,在A的m_children 数组中,会出现B 的指针。只有这样才能让 A 知道 B 是自己的孩子,同时也才能让B知道 A 是自己的父母。
4.uvm_component 的树形结构是如何组织起来的?
实现这样的功能当然离不开三个重要的变量他们分别是:
- m_parent, component类型,用于表示父类节点,在new 函数的源码里边有 m_parent= parent ; 主要目的是实现树形结构的父类定位,这样在子类进行例化的时候就能顺其自然的找到自己的父类;
- m_children, string类型关联数组,实际上在m_parent= parent后,会调用m_parent的m_add_child函数,将自己的孩子加入到自己的函数中,其中在m_add_child函数中就有这么一条语句m_children[child.get_name()] = child; 这里的child 就是变量实例化的时候赋值的string, 父类会将其加入到关联数组中。也就有了一个叫B的孩子;
- m_child_by_handle,component 类型的关联数组,在m_children[child.get_name()] = child后,会紧跟这这样一条命令m_children_by_handle[child] = child;表示其索引和内容都是child的指针。
5. 静态函数与非静态函数的区别:
- 静态函数必须要先例化,才可以调用其内部的方法,而非静态函数则不需要;
- 静态函数的调用使用点.进行,而非静态函数使用双冒号::
6. uvm_root 的单实例实现思路:
我们知道uvm_root 作为uvm环境的根,具有唯一性,另外,uvm_root 作为一个根,它是开始,在new的时候,第一个new的调用也是我们在代码实现的过程中必须要考虑的问题,因为一般而言,只要先实例化才可以使用其内部的函数,就比如new().所以当前两个问题需要解决:
先解决第一个,如何在不实例化的前提下,调用其内部的new()函数,使用静态函数可以做到,这也是上边介绍静态函数和非静态函数的初衷,在uvm_component.new()中,对uvm_top::get()的调用目的就在于此,get()的内部会继续调用uvm_root.new(),因为root 在调用new()之前没有实例化,所以uvm_root 的方法必须声明为static,其类型的变量也一样;
第二个问题,uvm_root的唯一性如何实现,其实很简单,在调用函数是首先判断uvm_root 指针是否为null,如果不为null,就说明已经有一个root了.如果没有root,就应该对他调用new函数。当然uvm_root 的对象也应该是static类型,不然调用会出错哦。
在源码中,将这种执行方式封装到get函数里边,get是静态函数,所以在uvm_component.new函数的源码里边会看到uvm_root top; top = uvm_root::get();
在271行,提到了uvm_root::new(),其实在uvm_root::new()中不需要任何参数,它调用 super.new,传入的 name 参数为__top__, 而 parent 参数为 null。
又回到uvm_component.new(),看一下下边关于uvm_root相关的代码:
在uvm_component 中,判断parent=null && name == “__top__”的时候,就知道这是uvm的根,直接返回。
但是,如果在分支的实例化中,将uvm_component 传入null有会发生什么?
在new函数中还有这样的一条判断:
如果传入的parent == null,默认该节点就是top。
7. run_test 的背后
我们知道在验证环境搭建好后,启动仿真的方式有两种:一种是直接使用run_test(“ ”), 直接将case名称写进去;另一种方法是在运行的时候输入控制命令+UVM_TESTNAME=“ ”;
这是我们能看到的,也是我们经常用到的,实际上背后是这样的:
在调用tun_test函数后,run_test 会调用 uvm_root 的 run_test,uvm_root::run_test的主要代码如下:
329行调用了uvm_cmdline_processsor的get_arg_values函数,对UVM_TESTNAME后边的字符进行统计,返回值是统计的个数,具体的字符是被放在test_names的队列中。下边有两个判断,第一个判断如果返回的test_name_count>0, 说明有传case名称进来,直接默认就为一个,拿队列index=0 的值(此处不是get,值还在),第二个判断如果发现test_name_count > 1,会报出warning,并打印。
拿到case名称之后,会进行创建实例,uvm_root会首先判断该实例是否已经被创建,如下363行,如果创建说明有问题。一般不会创建。
整个 run_test 关键的部分在于 368 和 369 行的 cast 函数中调用factory. create_ component_ by_name,这个函数将会根据输入的 case 的名字来创建这个 case 的一个实例。371 行用于判断实例创建是否成功,如果没有成功,说明这个 case 根本没有在 factory 中注册过,会给出出错提示。一般的,当我们在指定 case 的时候如果输错了名字就会给出这个错误提示。