UVM采用树形的组织结构来管理验证平台的各个部分。sequencer、driver、monitor、agent、model、 scoreboard、env等都是树的一个结点。为什么要用树的形式来组织呢?因为作为一个验证平台,它必须能够掌握自己治下的所 有“人口”,只有这样做了,才利于管理大家统一步伐做事情,而不会漏掉谁。树形结构是实现这种管理的一种比较简单的方式。
(1)uvm_component中的parent参数
UVM通过uvm_component来实现树形结构。所有的UVM树的结点本质上都是一个uvm_component。每个uvm_component都有 一个特点:它们在new的时候,需要指定一个类型为uvm_component、名字是parent的变量:
function new(string name, uvm_component parent);
一般在使用时,parent通常都是this。假设A和B均派生自uvm_component,在A中实例化一个B:
class B extends uvm_component;
…
endclass
class A extends uvm_component;
B b_inst;
virtual function void build_phase(uvm_phase phase);
b_inst = new("b_inst", this);
endfunction
endclass
在b_inst实例化的时候,把this指针传递给了它,代表A是b_inst的parent。为什么要指定这么一个parent呢?一种常见的观点 是,b_inst是A的成员变量,自然而然的,A就是b_inst的parent了,无需再在调用new函数的时候指定,即b_inst在实例化时可以这 样写:
b_inst = new("b_inst");
这种写法看似可行,其实忽略了一点,b_inst是A的成员变量,那么在SystemVerilog仿真器一级,这种关系是确定的、可知 的。假定有下面的类:
class C extends uvm_component;
A a_inst;
function void test();
…
a_inst.b_inst =
…;
a_inst.d_inst =
…;
…
endfunction
endclass
可以在C类的test函数中使用a_inst.b_inst来得到B的值或者给B赋值,但是不能用a_inst.d_inst来给D赋值。因为D根本就不存在 于A里面。SystemVerilog仿真器会检测这种成员变量的从属关系,但是关键问题是它即使检测到了后也不会告诉A:你有一个成员 变量b_inst,没有一个成员变量d_inst。A是属于用户写出来的代码,仿真器只负责检查这些代码的合理性,它不会主动发消息给代 码,所以A根本就没有办法知道自己有这么一个孩子。
换个角度来说,如果在test中想得到A中所有孩子的指针,应该怎么办?读者可能会说,因为A是自己写出的,它就只有一个 孩子,并且孩子的名字叫b_inst,所以可以直接使用a_inst.b_inst就可以了。问题是,假设要把整棵UVM树遍历一下,即要找到每个结点及结点的孩子的指针,那如何写呢?似乎根本就没有办法实现。
解决这个问题的方法是,当b_inst实例化的时候,指定一个parent的变量,同时在每一个component的内部维护一个数组 m_children,当b_inst实例化时,就把b_inst的指针加入到A的m_children数组中。只有这样才能让A知道b_inst是自己的孩子,同时也 才能让b_inst知道A是自己的父母。当b_inst有了自己的孩子时,即在b_inst的m_children中加入孩子的指针。
(2)UVM树的根
UVM是以树的形式组织在一起的,作为一棵树来说,其树根在哪里?其树叶又是哪些呢?从第2章的例子来看,似乎树根应 该就是uvm_test。在测试用例里实例化env,在env里实例化scoreboard、reference model、agent、在agent里面实例化sequencer、 driver和monitor。scoreboard、reference model、sequencer、driver和monitor都是树的叶子,树到此为止,没有更多的叶子了。
关于叶子的判断是正确的,但是关于树根的推断是错误的。UVM中真正的树根是一个称为uvm_top的东西,完整的UVM树如下 图所示。
uvm_top是一个全局变量,它是uvm_root的一个实例(而且也是唯一的一个实例 ,它的实现方式非常巧妙),而uvm_root 派生自uvm_component,所以uvm_top本质上是一个uvm_component,它是树的根。uvm_test_top的parent是uvm_top,而uvm_top的 parent则是null。UVM为什么不以uvm_test派生出来的测试用例(即uvm_test_top)作为树根,而是搞了这么一个奇怪的东西作为树 根呢?
在之前的例子中,所有的component在实例化时将this指针传递给parent参数,如my_env在base_test中的实例化:
env = my_env::type_id::create("env", this);
但是,假如不按照上面的写法,向parent参数传递一个null会如何呢?
env = my_env::type_id::create("env", null);
如果一个component在实例化时,其parent被设置为null,那么这个component的parent将会被系统设置为系统中唯一的uvm_root的实例uvm_top,如下图所示。
uvm_root的存在可以保证整个验证平台中只有一棵树,所有结点都是uvm_top的子结点。
在验证平台中,有时候需要得到uvm_top,由于uvm_top是一个全局变量,可以直接使用uvm_top。除此之外,还可以使用如 下的方式得到它的指针:
uvm_root top;
top=uvm_root::get();
(3)层次结构相关函数
UVM提供了一系列的接口函数用于访问UVM树中的结点。这其中最主要的是以下几个
(3.1)get_parent函数
get_parent函数,用于得到当前实例的parent,其函数原型为:
extern virtual function uvm_component get_parent ();
(3.2)get_child函数
与get_parent相对的就是get_child函数:
extern function uvm_component get_child (string name);
与get_parent不同的是,get_child需要一个string类型的参数name,表示此child实例在实例化时指定的名字。因为一个component 只有一个parent,所以get_parent不需要指定参数;而可能有多个child,所以必须指定name参数。
(3.3)get_children函数
为了得到所有的child,可以使用get_children函数:
extern function void get_children(ref uvm_component children[$]);
它的使用方式为:
uvm_component array[$];
my_comp.get_children(array);
foreach(array[i])
do_something(array[i]);
(3.4)get_first_child和get_next_child
除了一次性得到所有的child外,还可以使用get_first_child和get_next_child的组合依次得到所有的child:
string name;
uvm_component child;
if (comp.get_first_child(name))
do begin
child = comp.get_child(name);
child.print();
end while (comp.get_next_child(name));
这两个函数的使用依赖于一个string类型的name。在这两个函数的原型中,name是作为ref类型传递的:
extern function int get_first_child (ref string name);
extern function int get_next_child (ref string name);
name只是用于get_first_child和get_next_child之间及不同次调用get_next_child时互相之间传递信息。无需为name赋任何初始 值,也没有必要在使用这两个函数过程中对其做任何赋值操作。
(3.5)get_num_children函数
get_num_children函数用于返回当前component所拥有的child的数量:
extern function int get_num_children ();