UVM世界观篇之三:工厂机制(上)

本文转自:http://www.eetop.cn/blog/html/28/1561828-2331496.html

如果读者作为一名观光游客来到UVM世界,那么我作为导游,首先应该给读者介绍的是UVM世界中各个类的角色扮演,它们如何搭建UVM的高层建筑,还有这些高层建筑的特色和应用场景是什么。所谓走马观花,先吊起游客的兴致,不外乎是个推销UVM应用的好手段。而不巧的是,路桑却是一个稍显严肃的工程实践者,如果用轻松的话先带着游客到UVM大观园浏览一圈,而后又经过少量的过渡来讲UVM的几种核心机制,那么另外来自于读者的另外一种抱怨“为什么才讲完1+1,就要开始100以内的加法”?与其先易后难,不如将本书从一开始就定义为一本题材严肃的案头书,陪伴诸君验证生涯,也是极好的。

在考虑先深入UVM的哪一种核心机制作为打开UVM世界大门的钥匙,路桑认为工厂机制是UVM的真正魅力所在,固先领诸君先一窥其内部机制。本书对于UVM的介绍也将偏重于应用,同时兼顾与其应用,目的在于使得读者在应用的同时知其所以然,这样在今后自己搭建测试平台的时候少一点同UVM基本调试的纠葛,多一些轻松的测试用例编写和功能覆盖率收集,毕竟这才是UVM要服务于verifier们的初衷啊。当然,我们会在本书的高级应用章节部分,独立出一章为读者介绍如何建立一套自动化测试平台构建工具的要素,而这一需求在愈加快速芯片验证的时代显得尤为凸显。如果有读者看到这里,愈发被吊起了胃口,那么请对路桑后期的一些平台构建工具的思想篇章保持关注。

在继续阅读接下来篇章的时候,这同之前读者阅读SV核心篇章的技术前提一致,UVM的这些篇章假设读者已经具备了UVM的一些基本概念,最好已经做过一些基本的实例,懂得UVM江湖的行话和简单的套路把式。只有这样,我们才能够好地将UVM适当地解构(适当到刚够用为止,这贴合实际工作的要求)。毕竟,路桑认为如果本书从ABC开始介绍,除了有点赚流量的嫌疑,还有会使得书本变得太厚,这对于读者恐怕不太友好,当然“浪费”的纸张对自然环境就更不好了。

言归正传,我们本篇关于工厂机制的介绍将从下面的几点一一展开:

  • 工厂的意义
  • 工厂提供的便利
  • 再看工厂设计模式
  • 覆盖方法
  • 确保正确覆盖的代码要求
  • 实际应用

工厂的意义

UVM工厂的存在就是更方便地替代验证环境中的实例或者注册了的类型,同时工厂的注册机制也带来了配置的灵活性。这里的实例或者类型替代,在UVM中称作覆盖(override),而被用来替换的对象或者类型之间,应该满足注册(registration)和多态(polymorphism)的要求,这些要求建议的coding方式我们也会在下面具体谈到。

UVM的验证环境构成可以分为两部分,一部分构成了环境的层次,这部分代码是通过uvm component完成,另外一部分构成了构成了环境的属性(例如配置)和数据传输,这一部分通过uvm object完成。这两种类的集成关系从上一篇《类库地图》可以看到,uvm component继承与uvm object,而这两种类的区别会在下一篇《核心基类》中重点探讨。这两种类也是进出工厂的主要模具和生产对象。

之所以称之为模具,是因为通过在工厂注册,可以通过工厂完成对象的创建;而之所以有生产对象从工厂生产,也是利用了工厂生产模具可灵活替代的便捷性,提供了在不修改原有验证环境层次和验证包的同时,实现了对环境内部组件(uvm_component)和对象(uvm_object)的覆盖。

工厂提供的便利

在介绍完工厂的意义之后,我们来通过实际代码看看工厂提供的一些常用方法和其背后的机制。

module object_create;
import uvm_pkg::*;
`include "uvm_macros.svh"

class comp1 extends uvm_component;
`uvm_component_utils(comp1)

function new(string name="comp1", uvm_component parent=null);
    super.new(name, parent);    
    $display($sformatf("%s is created", name));
endfunction: new

function void build_phase(uvm_phase phase);
    super.build_phase(phase);
endfunction: build_phase

endclass

class obj1 extends uvm_object;
`uvm_object_utils(obj1)

function new(string name="obj1");
    super.new(name);
    $display($sformatf("%s is created", name));
endfunction: new

endclass

comp1 c1, c2;
obj1 o1, o2;

initial begin
    c1 = new("c1");
    o1 = new("o1");
    c2 = comp1::type_id::create("c2", null);
    o2 = obj1::type_id::create("o2");
end

endmodule

上面的例码中分别定义了两个类comp1(component类)和obj1(object类)。在后面的c1和o1的例化中,通过new()函数进行;而c2和o2的例化,则通过了看起来较为复杂的方式来进行。这两种方式都实现了对象的例化,这一点可以从仿真输出印证:

c1 is created
o1 is created
c2 is created
obj1 is created

可以看到,c2和o2的例化方式也是最后通过调用new()函数实现的。没错,毕竟对于任何对象的例化,最终都要通过new()构建函数来实现的。那么,除此以外,这一串长长的句子还做了些什么呢?要探寻这一点,我们就需要回到“打开factory的正确步骤”了。一般来说,正确打开factory的步骤分为:

  1. 将类注册到工厂
  2. 在例化前设置覆盖对象和类型
  3. 对象创建

在上面的例码中为了首先说明对象的创建机制而暂时省略了“设置覆盖”的流程。那么我们先就着注册类来看看这一步骤吧。在上面两种类的comp1和obj1的注册中,分别使用了UVM的宏`uvm_component_utils和`uvm_object_utils。在要展开这两个宏开始解剖前,我想一些读者恐怕要对这些宏抱怨起来了,因为这些宏的厚实包装,不但隐藏了更多的细节、降低了仿真速度,竟然还影响了调试(断点在宏里面设置时有的仿真器无法支持)……好吧,关于UVM宏的优劣探讨我们也会专门在本章后面讨论。

嗯嗯,先忍住槽点,来展开这两个宏,将其主要的所作所为来分析看看:

`define uvm_component_utils(T) \
`m_uvm_component_registry_internal(T,T) \
`m_uvm_get_type_name_func(T) \
...

`define uvm_object_utils(T) \
`m_uvm_object_registry_internal(T,T) \
`m_uvm_object_create_func(T) \
`m_uvm_get_type_name_func(T) \
...

这两个宏主要做的事情就是将类注册到factory中。在解释注册函数之前,我们需要懂得在整个仿真中,factory是独有的,有且只有一个,这保证了所有的component的注册都在一个“机构”中。那么,在上面代码以及读者构写的代码中,并没有主动出现factory例化代码,这个factory何时被例化,而隐藏在哪里呢?

相比较于UVM-1.1,UVM-1.2将UVM的核心机制都浓缩在了一个类uvm_coreservice_t,该类内置了UVM世界核心机制的组件和方法,它们主要包括了:

  • 唯一的uvm_factory,该组件用来注册、覆盖和例化。
  • 全局的report_server,该组件用来做消息统筹和报告。
  • 全局的tr_database,该组件用来记录transaction记录。
  • get_root()方法用来返回当前UVM环境的结构顶层对象。

就之前的UVM-1.1,uvm_factory虽然也是唯一的,不过它的例化时独立进行的,而在UVM-1.2中,明显的变化时,通过uvm_coreservice_t将最重要的机制(也是必须做统一化例化处理的组件)放置在了类uvm_coreserice_t中。该类并不是uvm_component或者uvm_object,它在环境中也并没有例化在uvm验证环境中,而是独立于环境之外的。

`ifndef UVM_CORESERVICE_TYPE
`define UVM_CORESERVICE_TYPE uvm_default_coreservice_t
`endif

typedef class `UVM_CORESERVICE_TYPE;
virtual class uvm_coreservice_t;
local static `UVM_CORESERVICE_TYPE inst;
...

static function uvm_coreservice_t get();
    if(inst==null)
    inst=new;
    return inst;
endfunction // get
endclass

class uvm_default_coreservice_t extends uvm_coreservice_t;
local uvm_factory factory;
local uvm_tr_database tr_database;
local uvm_report_server report_server;
...

virtual function uvm_factory get_factory();
    if(factory==null) begin
    uvm_default_factory f;
    f=new;
    factory=f;
    end
    return factory;
endfunction
endclass

uvm-1.2/base/uvm_coreservice.svh

在UVM环境部分的组件中,很多的方法实现中都会调用函数uvm_coreservice_t::get(),进而会在验证环境的建立过程中创建出一个唯一的uvm_coreservie_t::inst,该对象是uvm_default_coreservice_t类型。同时,不单单uvm_default_coreservice_t只例化了一次,对于uvm_default_coreservice_t::factory,uvm_default_coreservice_t::tr_database和uvm_default_coreservice_t::report_server,通过其方法调用get_factory(),get_report_server()和get_default_tr_database()都只是例化了一次。

因此,如果在仿真器中寻找这个隐藏的核心服务组件对象,可以先搜寻类uvm_coreservice_t,再在其内部搜寻局部静态变量uvm_coreservice_t::inst。找到之后,进一步展开,就可以发现其其内部的几个主要组件实例factory、report_server、tr_database。无论是对于uvm_coreservice_t::inst,还是uvm_default_coreservice_t的成员变量factory、report_server等,都遵循一个原则,即如果被“需要”,那么会例化只且例化一次。

在上面的简单例码中,由于两个宏`define uvm_component_utils(T)和`define uvm_object_utils(T)需要uvm_coreservie_t::inst,所以在仿真开始后,可以从仿真器的对象浏览窗口中看到下面的对象关系图。该图从Synopsys VCS 'object'窗口中截取,而今后用来表示对象关系、UVM结构等层次关系的图时,我们也将从VCS工具中截取示例说明。需要额外补充一点的是,之前的SV核心篇章的实例仿真均是在MentorGraphics QuestaSim中进行的,而到了UVM核心篇章的实例仿真均在Synopsys VCS仿真器中进行。

在下面这个对象图中,有4列,从左至右分别是层次、类型、对象ID和对象创建时间。对于对象ID和对象创建时间,我们可以从下图的对象中看到,已经被创建的对象均是在0时刻创建的,而且都是它们对应类型的第一个实例(用@1表示,也应该是唯一一个)。

了解了UVM核心机制组件的建立,我们来继续分解上面的宏`define uvm_component_utils(T)和`define uvm_object_utils(T)。

这里,以uvm_component_utils(T)为例,其注册的机制发生在该宏的进一步拆解中:

`define m_uvm_component_registry_internal(T,S) \
typedef uvm_component_registry #(T,`"S`") type_id; \
static function type_id get_type(); \
return type_id::get(); \
endfunction \

virtual function uvm_object_wrapper get_object_type(); \
return type_id::get(); \
endfunction

通过typedef uvm_component_registry #(T,`"S`") type_id 来定义了一个新的类型:

class uvm_component_registry #(type T=uvm_component, string Tname="<unknown>")
extends uvm_object_wrapper;
typedef uvm_component_registry #(T,Tname) this_type;
const static string type_name = Tname;

virtual function string get_type_name();
    return type_name;
endfunction

local static this_type me = get();

static function this_type get();
    if (me == null) begin
    uvm_coreservice_t cs = uvm_coreservice_t::get();
    uvm_factory factory=cs.get_factory();
    me = new;
    factory.register(me);
    end
    return me;
endfunction

...

static function T create(string name, uvm_component parent, string contxt="");
    uvm_object obj;
    uvm_coreservice_t cs = uvm_coreservice_t::get();
    uvm_factory factory=cs.get_factory();
    if (contxt == "" && parent != null)    
    contxt = parent.get_full_name();    
    obj = factory.create_component_by_type(get(),contxt,name,parent);
    if (!$cast(create, obj)) begin
    ...
    
    end
endfunction

uvm-1.2/base/uvm_registry.svh

 

可以看到,在上面通过调用宏而发生的类型定义typedef uvm_component_registry #(T,`"S`") type_id 已经发生了注册的行为即type_id::me = type_id::get(),通过cs.factory来注册了type_id。

暂时撇开如何在factory中注册每一个类型先不谈,而一旦发生了注册,那么上面例码中的type_id::create()函数就可以最终通过factory.create_component_by_type()来实现。

解释完上面的component(或者object)通过UVM的宏来完成注册之后,我们继续深入uvm_factory了解注册方法register()的大致实现,以及上面创建component对象的方法create_component_by_type()。希望通过这两个方法的联系,读者可以大致了解如何将产品模板首先在工厂内注册,而后通过注册了的模板完成批量化的生成。
 

function void uvm_default_factory::register (uvm_object_wrapper obj);
    if (obj == null) 
    begin
        ...
    end
    if (obj.get_type_name() != "" && obj.get_type_name() != "<unknown>") 
    begin    
        if (m_type_names.exists(obj.get_type_name()))
        ...    
        else
        m_type_names[obj.get_type_name()] = obj;    
    end
    if (m_types.exists(obj)) 
    begin
        if (obj.get_type_name() != "" && obj.get_type_name() != "<unknown>")
        ...
    end
    else
    begin
        m_types[obj] = 1;
        ...
    end
endfunction

function uvm_component uvm_default_factory::create_component_by_type

(uvm_object_wrapper requested_type, tring parent_inst_path="",
    string name, uvm_component parent);
    string full_inst_path;
    ...
    requested_type = find_override_by_type(requested_type, full_inst_path);
    return requested_type.create_component(name, parent);
endfunction


uvm-1.2/base/uvm_factory.svh

从上面已经简化过的uvm_default_factory(默认UVM factory的实现类)两个方法来看,读者可以从这两个方法中解读出的重要信息已经加粗,它们分别代表的信息是:

  • register()方法在该类没有被注册过或者覆盖过时,会将该类例化过的对象句柄放置到factory内的类型字典(关联数组)中uvm_default_factory::m_type_names,同时将对象句柄作为uvm_default_factory::m_types字典的索引键,而键值设置为1。从这个方法的操作来看,我们所谈的“注册”并不是真正地将一个类型(空壳)抽象地放置在什么地方,而是首先通过例化该类的对象。由于一种类型在通过宏调用时,有且只注册一次,那么在不考虑覆盖的情况下,uvm_default_factory就将每一个类对应的对象都放置到了factory的字典当中。这里,我们将SV的关联数组称之为字典(借鉴Python的称谓),是为了更好地体现“注册”的含义。所以,今后读者看到字典的称谓,请对应到SV的关联数组。
  • creaet_component_by_type()经过了代码的简化,读者可以看到上面的两行关键语句,它们要做的首先检查处在该层次路径中需要被例化的对象,是否受到了“类型覆盖”或者“实例覆盖”的影响,进而最终类型对应的对象句柄(正确的产品模板)交给工厂。有了正确地产品模板,接下来就可以通过uvm_component_registry::create_component()来完成例化。实际上,uvm_component_registry::create_component()内部就是直接通过调用uvm_component的构建函数new(name, parent)来实现的。

讲到这里,工厂的注册和创建component的机制都讲解完了,而工厂创建object的机制也是与之类似的。那么为什么要将component和object区分开来呢?这里需要再次强调的是,component和object虽然在创建时都需要传递create(name, parent),但是最终创建出来的component是会表示在UVM层次结构中的,而object则不会显示在层次当中。这一点,也可以从uvm_component::new(name, parent)和uvm_object::new(name)中看得出来。之所以uvm_component::new(name, parent)保留两个参数,且缺一不可就是为了通过类似“钩子”的做法,一层层由底层勾住上一层,这样就能够将整个UVM结构串接起来了。而uvm_object::new(name)则没有parent参数,因此也不会显示在UVM层次中,也只能作为configuration或者transaction等用来做传递的配置结构体或者抽象数据传输的数据结构体,成为uvm_component的成员变量。

除了简化UVM源代码来提取factory精华,还有一种更直观的方式就是绘图。

虽然在实际的uvm_default_factory中,它用来注册所有uvm_component和uvm_object的词典只有一个,但是用来覆盖层次中类型的方式却有不止一种。在上面的图中我们将这些可能用来覆盖的类,抽象到一个词典中为了方便简化注册、创建和覆盖的关系。

 

在注册过程中,我们通过uvm_component_registry或者uvm_object_registry(均继承与uvm_object_wrapper)来分别注册uvm_component和uvm_object。上面的例子以uvm_component_registry来说明,对于这样一种专门用来注册的类而言,它们自身的方法是专门为配合factory的注册、创建和覆盖而生的,这些方法分别是:

  • create()
  • create_component()
  • get()
  • get_type_name()
  • set_inst_override()
  • set_type_override()

每一个uvm_component的类在注册时,均会定义一个新的uvm_compoent_registry类,该类的定义形式为:

typedef uvm_component_registry #(T,Tname) this_type;

这样在定义注册类this_type的同时,该类内部的局部成员静态变量也同一时间将自己例化出一个对象,将这个对象(即模板)注册到factory中去:

local static this_type me = get();

那么,uvm_component_registry之于uvm_component的关系像什么呢?它就像一个轻量化的外壳,一个包装模板的纸箱,只不过在工厂注册时,该纸箱是一个实例,因为必须实例才可以携带参数化的信息 #(T=comp1, Tname="comp1"),而其内部的模板,无论是uvm_component comp1还是模板的默认名称“comp1“,都还只是个空空的产品图纸,箱子里面只有”图纸“并没有一个产品实例。所以,通过一个可以用来注册、创建和覆盖的小型盒子就可以用来注册每一个uvm_component或者uvm_object。
那么,创建的过程用上面的图怎么来理解呢?结合代码,如果发现factory的覆盖词典中没有用来替换这个类型的“箱子”,那么我们依旧可以通过这个箱子来创建对象,而实际上创建的过程就是间接调用了uvm_component的构建函数

uvm_component::new(name, parent):
requested_type.create_component(name, parent);

细心的读者可以发现,我们在注册component或者object时,会将其类型T和类型名Tname同时传递给factory,也因此我们如果要通过factory来创建对象时,可以使用的方法有:

  • create_component_by_name()
  • create_component_by_type()
  • create_object_by_name()
  • create_object_by_type()

为了避免不必要的麻烦,我们在使用宏`uvm_component_utils和`uvm_object_utils注册类型时,宏内部就将类型T作为类型名Tname='T'注册到factory中去。这就使得通过上面的任何一种方法在创建对象时,不会受困于类型与类型名不同的苦恼。

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页