类库地图
SV的核心特性包括面向对象、随机约束、线程通信、功能覆盖率收集等,这些特性也为建立一个验证环境提供了足够多的便利。(uvm提供了框架,从而可以更多聚焦于测试用例)
root--test--env
在可以看到对验证环境的共同需求是:组件的创建和访问、环境的结构创建、组件之间的连接和运行、不同阶段的顺序安排、激励的生成、传递和控制、测试的报告机制
UVM类库地图按照UVM的核心机制将地图进行了分块:·
- 核心基类
- 工厂(factory)类
- 事务(transaction)和序列(sequence)类
- 结构创建(structure creation)类
- 环境组件(environment component)类
- 通信管道(channel)类
- 信息报告(message report)类
- 寄存器模型(register model)
- 线程同步(thread synchronization)类
- 事务接口(transaction interface)类 在环境组件中例化,组件通过端口连接通信
- 由于软件环境中对象的生成是动态的,验证环境中的组件也需要UVM提供底层功能来完成对象的创建和访问。
- 在组件创建之外,UVM也需要提供环境上下层次中创建、连接和运行组件的顺序控制方法,只有在底层机制上有效地保证这一点,才会避免可能发生的句柄悬空问题。
- 在组件通信中,UVM也提供了功能更丰富的TLM (TransactionLevel Model)接口,这可以保证相邻组件的通信不再通过显式句柄引用,而是独立于组件的通信方式。
- 对于测试序列(sequence)的生成和传输也是利用了TLM传输在sequence和driver之间完成。而对于不同sequence的发送顺序控制,也类似于SV测试MCDF子系统的要求,需要实sequence之间的灵活调度。
- 为了便于验证环境的调试,UVM的报告机制可以将来自于不同组件、不同级别的信息并且加以过滤,最终生成测试报告。
工厂机制
- UVM工厂的存在就是为了更方便地替换验证环境中的实例或者注册了的类型,同时工厂的注册机制也带来了配置的灵活性。
- 这里的实例或者类型替代,在UVM中称作覆盖(override) ,而被用来替换的对象或者类型,应该满足注册(registration)和多态(polymorphism)的要求。(实现多态,有二种方式,覆盖,重载。覆盖,是指子类重新定义父类的虚函数的做法。重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。)
- (73条消息) 继承与多态的区别_l_漫漫的博客-CSDN博客_继承和多态的区别
- UVM的验证环境构成可以分为两部分,一部分构成了环境的层次这部分代码是通过uvm_component类完成,另外一部分构成了环境的属性(例如配置)和数据传输,这一部分通过uvm_object类完成。
- 这两种类的集成关系从UVM类库地图可以看到,uvm_component类继承于uvm_object类,而这两种类也是进出工厂的主要模具和生产对象。之所以称为模具,是因为通过注册,可以利用工厂完成对象创建
- 而之所以对象由工厂生产,也是利用了工厂生产模具可灵活替代的好处,这使得在不修改原有验证环境层次和验证包的同时,实现了对环境内部组件类型或者对象的覆盖。
uvm_{component,object}的例化
- 每一个uvm_{component, object}在例化的时候都应该给予一个名字(string)。
- “full name”指的是component所处的完整层次结构。在每个层次中例化的组件名称,应该独一无二 (unique) 。创建component或者object的方法如下:
创建uvm_component对象时,
comp_type: :type_id::create(string name,uvm_component parent);
//type_id:注册到工厂里的类型,create表示方法,create后台会调用new。
创建uvm_object对象时,
object type: : type_id: : create (string name) ;
class comp1 extends uvm_component;
'uvm_component_utils(comp1)
function new(string name="comp1",uvm_component parent=null);
class obj1 extends uvm_object;
function new(string name="obj1");
c2=create::type_id::create("c2",null);
o2=create::type_id::create("o2");
- c2和o2的例化方式也是最后通过调用new()函数实现的。毕竟对于任何对象的例化,最终都要通过new()构建函数来实现的。
- 一般来说运用factory的步骤可分为:
- 将类注册到工厂
- 在例化前设置覆盖对象和类型(可选的)
- 对象创建
在两种类comp1和obj1的注册中,分别使用了UVM宏uvm_component_utils和~uvm_object_utilso·什么是宏(macro呢?
·为什么需要宏呢?
- 这两个宏做的事情就是将类注册到factory中。在解释注册函数之前,我们需要懂得在整个仿真中,factory是独有的,即有且只有一个,这保证了所有类的注册都在一个“机构”中。
uvm_coreservice_t类
- 该类内置了UVM世界核心的组件和方法,它们主要包括
- 唯一的uvm_factory,该组件用来注册、覆盖和例化
- 全局的report_server,该组件用来做消息统筹和报告
- 全局的tr_database,该组件用来记录transaction记录.
- get_root()方法用来返回当前UVM环境的结构顶层对象(root(uvm)而不是test(sv))
- 而在UVM-1.2中,明显的变化是通过uvm_coreservice_t将最重要的机制(也是必须做统一例化处理的组件)都放置在了uvm_coreserice_t类中。
- 该类并不是uvm_component或者uvm_object,它也并没有例化在UVM环境中,而是独立于UVM环境之外的。
- uvm_coreservice_ t只会被UVM系统在仿真开始时例化一次。用户无需,也不应该自行再额外例化该核心服务组件。该类里的4个类型都不是component或者object,比如factory,就是用来例化uvm_componcent/object的,故不属于uvm_component/object。
- 这个核心组件如同一个随时待命的仆人,做好服务的准备。
- 理论上,用户可以获取核心服务类中的任何一-个对象,例如uvm_ _default_ _factory对象, 继而直接利用factory来实现创建和覆盖。当然,创建和覆盖也可以由其它方式完成。
注册宏`uvm_{component,object}_utils
- `uvm_ component_utils用来注册组件类uvm_component
- `uvm_ object_utils用来注册核心基类uvm_object
- 在宏调用的过程中,实现类型定义typedef uvm_ component_ registry #(T,` "S`") type_ id
- uvm_factory::register()来注册type_ id并且得到实例。
- 一旦发生注册,type_ id:create()函数就可以最终通过uvm_ factory::create_ component_ by_ _type()来实现。
- 无论对于uvm_ component或者uvm_ object, 在UVM世界中,请养成习惯使用注册宏uvm_ {component, object}_utils
- 对于注册,并不是真正地将一个抽象的类型(空壳)放置在什么地方,而是通过例化该类的对象来完成。
- 由于一种类型在通过宏调用时只注册一次,那么在不考虑覆盖的情况下uvm_default_factory就将每一个类对应的对象都放置到了factory的字典当中。
- uvm_default_factory::create_component_by_type()经过代码简化,读者可以看到关键语句,它们首先检查处在该层次路径中需要被例化的对象,是否受到了“类型覆盖”或者“实例覆盖”的影响,进而将最终类型对应的对象句柄(正确的产品模板)交给工厂。
- 有了正确的产品模板,接下来就可以通过uvm_component_registry::create_component()来完成例化。
注册后的对象创建(component或者object)
- 创建对象时,需要结合工厂的注册和覆盖机制来决定,应该使用哪一个类型来创建
- uvm_ component和uvm_ object在创建时虽然都需要调用create()函数,但最终创建出来的uvm_ component是会表示在UVM层次结构中的,而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的成员变量。
component/object与工厂有关的方法
- 配合工厂的注册、创建和覆盖的相关方法(属于uvm_coponent/object):●create() ●create_ component() ●get() ●get_ type_ name() ●set_ inst_ _override() ●set_ type_ override()
- 每一个uvm_component的类在注册时,会定义一个新的uvm_component_ registry类,其如同一个外壳,一个包装模板的纸箱,在factory中注册时, 该纸箱中容纳的是被注册类的“图纸”并没有一个“实例”
- 除了使用component/object来创建实例,也可以利用factory(属于uvm_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中去。这就使得通过,上面的任何一种方法在创建对象时,不会受困于类型与类型名不同的苦恼。
覆盖方法
工厂提供的便利--覆盖(override)
- 覆盖机制可以将其原来所属的类型替换为另外一个新的类型。在覆盖之后,原本用来创建原属类型的请求,将由工厂来创建新的替换类型。(比如替换vip里的driver)
- 无需再修改原始代码,继而保证了原有代码的封装性。
- 新的替换类型必须与被替换类型相兼容,否则稍后的句柄赋值将失败,所以使用继承。
- 允许灵活的配置,例如可使用子类来覆盖原本的父类
- 可使用不同的对象来修改其代码行为
- 要想实现覆盖特性,原有类型和新类型均需要注册。
- 当使用create()来创建对象时:
- 工厂会检查,是否原有类型被覆盖。
- 如果是,那么它会创建一个新类型的对象。
- 如果不是,那么它会创建一个原有类型的对象。
- 类型覆盖指,UVM层次结构下的所有原有类型都被覆盖类型所替换。
- 实例覆盖指,在某些位置中的原有类型会被覆盖类型所替换。
set_inst_override()
static function void set_inst_override (uvm_object_wrapper override_type,string inst_ path,uvm_component parent=null);.
- string inst_path指向的是组件结构的路径字符串
- uvm_component parent=null 如果缺省,表示使用inst_path内容为绝对路径 如果有值传递,则使用{parent.get_full_name(), '.', inst_path}来作为目标路径。(''root.test.env.checker"这种带双引号的字符串索引路径 不带双引号的层次化也可以实现)这里用字符串是告诉实例化的名称是什么
orig_type::type_id::set_inst_override(new_type::get_type(),"orig_inst _path")
typedef 静态函数 静态函数
set_type_override()
static function void set_type_override (uvm_object_wrapper override_type, bit replace=1) ;
- uvm_object_wrapper override_type 这是什么?并不是某一个具体实例的句柄,实际上是注册过后的某一个类在工厂中注册时的句柄。怎么找到它呢?就使用new_typeget_type()。
- bit replace=1 1:如果已经有覆盖存在,那么新的覆盖会替代旧的覆盖。0:如果已经有覆盖存在,那么该覆盖将不会生效。
- set_type_override是一个静态函数(静态函数意味在任何一个地方都可以调用这个函数) 参照此覆盖方式
orig_type::type_id::set_type_override (new_type::get_type())
{ typedef } { 静态函数 } { 静态函数 }
这里用的type_id也是为了调用另一个静态函数set_type_override,之前是为了调用create创建对象
如何使用覆盖相关函数
- 首先需要知道,有不止一个类提供与覆盖有关的函数,然而名称与参数列表可能各不相同:
- uvmcomponent: :set_{ type, inst}_override{_by_type}
- uvm component _registry : :set_{ type, inst}_override
- uvm_object _registry : :set_{type, inst}_override
- uvm_factory: :set_{ type, inst}__override
- 因此,想要实现类型替换,也有不止一种方式。包括上述给的例子中通过orig type : :type_ id来调用覆盖函数,还可以在uvm_component的域中直接调用,或者使用uvm_factory来做覆盖。
'include "uvm_macros.svh"
class comp1 extends uvm_component;
'uvm_component_utils(comp1)
function new(string name="comp1",uvm_component parent=null);
$display($formatf("comp1::%s is created",name));
virutal function void hello(string name);
$display($formatf("comp1::%s said hello!",name));
class comp2 extends comp1;
'uvm_component_utils(comp2)
function new(string name="comp2",uvm_component parent=null);
$display($formatf("comp2::%s is created",name));
function void hello(string name);
$display($formatf("comp2::%s said hello!",name));
comp1::type_id::set_type_override(comp2::get_type());
c2=comp1::type_id::create("c2",null);
覆盖实例
-
comp2覆盖了comp1类型comp1: :type_id: :set_type_override (comp2: :get_type());
-
紧接着对c1和c2对象进行了创建,可以从输出结果看到,cl的所属类型仍然是compl,c2的所属类型则变为了comp2。这说明了factory的覆盖机制只会影响通过factory注册并且创建的对象。
-
所以通过type_id::create()和factory的类型覆盖可以实现对象所属类型在例化时的灵活替换。
-
在例化c2之前,首先应该用comp2来替换compl的类型。只有先完成了类型替换,才可以在后面的例化时由factory选择正确的类型。
-
在后面发生了类型替换以后,如果原有的代码不做更新,那么c2句柄的类型仍然为comp1,但却指向了comp2类型的对象。这就要求,comp2应该是comp1的子类,只有这样,句柄指向才是安全合法的。
-
c2在调用hello()方法时,由于首先是comp1类型,那么会查看compl::hello(),又由于该方法在定义时被指定为虚函数,这就通过了多态性的方法调用,转而调用了comp2::hello()函数。因此,显示的结果也是“comp2::c2 said hello!”。
确保正确覆盖的代码要求
- 将UVM环境中所有的类都注册到工厂中,并通过工厂来创建对象。
- 在使用某些类的时候,确保该类已经被导入(import)到当前域(scope)中。
- 通过工厂创建对象时,句柄名称应该同传递到create()方法中的字符串名称相同。无论是通过层次路径名称来覆盖还是配置,将例化组件的句柄名称同创建时create()方法中的字符串名称保持一致。
- 由于覆盖是采用parent wins模式(层次越高,配置的优先级越高,比如test中的override优先级高于env中的override),因此要注意在同一个顶层build_phase()中覆盖方法应发生在对象创建之前。
- 为了尽量保证运行时覆盖类可以替换原始类,覆盖类最好是原始类的子类,而调用成员方法也应当声明为虚方法。
- 另外—种确保运行时覆盖类型句柄正确使用的方式,需要通过$cast()讲行动态类型转换。
总结
将浏览完UVM工厂后,你有没有觉得它就像一个大大的乐高世界。
一旦把那些组件装载盒子注册之后,接下来的UVM环境搭建就变得更加容易、更方便日后维护了。
整个UVM世界的构建,离不开factory的三个核心要素:注册、创建和覆盖。
- `uvm_{component, object}_utils
- uvm_{component, object}type_id:create().
- set_{type, inst}_override{,_by_type}()
UVM学习中的一大阻碍就是,实现某—种效果的方法有很多种,但是对于初学者,你只需要掌握最常用的一种实现方式,就足够了!因为我们最终需要掌握UVM世界的全貌,而不是研究全部的用法,毕竟我们时间非常有限!