文章目录
构建验证环境的五大因素
- 组件的创建和访问
- 环境的结构创建、组件之间的连接和运行
- 不同阶段的顺序安排
- 激励的生成、传递和控制
- 测试的报告机制
类库地图
- 1、核心基类
- 2、工厂(factory)类
- 3、事务(transaction)和序列(sequence)类
- 4、结构创建(structure creation)类
- 5、环境组件(environment component)类·
- 6、通信管道(channel)类
- 7、信息报告(message report)类
- 8、寄存器模型(register model)类
- 9、线程同步(thread synchronization)类
- 10、事务接口(transaction interface)类
工厂机制
工厂的意义
- UVM工厂的存在就是为了更方便地替换验证环境中的实例或者注册了的类型,同时工厂的注册机制也带来了配置的灵活性。
- 这里的实例或者类型替代,在UVM中称作覆盖(override),而被用来替换的对象或者类型,应该满足注册(registration)和多态(polymorphism)的要求。
- UVM的验证环境构成可以分为两部分,一部分构成了环境的层次,这部分代码是通过uvm_component类完成,另外一部分构成了环境的属性(例如配置)和数据传输,这一部分通过uvm_object类完成。
- 这两种类的集成关系从UVM类库地图可以看到,uvm_component类继承于uvm_object类,而这两种类也是进出工厂的主要模具和生产对象。
- 之所以称为模具,是因为通过注册,可以利用工厂完成对象创建。而之所以对象由工厂生产,也是利用了工厂生产模具可灵活替代的好处,这使得在不修改原有验证环境层次和验证包的同时,实现了对环境内部组件类型或者对象的覆盖。
- 简而言之,我们利用工厂创建对象,首先需要一个模具,这个模具通过注册实现。
注册宏
uvm_component 类的注册:`uvm_component_utils ( comp1)
uvm_object类的注册:`uvm_object_utils ( comp1)
uvm_componen t 类的创建
class comp1 extends uvm_component; //继承于组件类型
`uvm_component_utils ( comp1) //首先要注册,固定范式
function new(string name="comp1", uvm_component parent=null); //当前的实例,谁例化了它,谁就是parent
super.new(name, parent) ;
$display($sformatf( "%s is created" , name)) ;
endfunction: new
function void build phase (uvm phase phase);
super. build phase (phase) ;
endfunction:
endclass
uvm_object类的创建
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") ; //SV的创建对象
o1 new ( "o1");
c2 =comp1 : :type_ id: :create ( "c2" , null); //UVM利用工厂创建对象
o2 =obj1: : type_id: : create ( "o2");
end
- 对于UVM真正注册,创建的过程在红宝书265页中有,目前理解起来比较困难
uvm_coreservice_t类
- 该类内置了UVM世界核心的组件和方法,它们主要包括
-
- 唯一的uvm_factory,该组件用来注册、覆盖和例化
-
- 全局的report_server,该组件用来做消息统筹和报告
-
- 全局的tr_database,该组件用来记录transaction记录
-
- get_root()方法用来返回当前UVM环境的结构顶层对象
- 而在UVM-1.2中,明显的变化是通过uvm_coreseervice_t将最重要的机制(也是必须做统一例化处理的组件)都放置在了uvm_coreserice_t类中。
- 该类并不是uvm_component或者uvm_object,它也并没有例化在UVM环境中,而是独立于UVM环境之外的。
- vm_coreservice_t会被UVM系统在仿真开始时例化一次。用户无需,也不应该自行再额外例化该核心服务组件。
- 这个核心组件如同一个随时待命的仆人,做好服务的准备。
- 理论上,用户可以获取核心服务类中的任何一个对象,例如uvm_default_factory对象,继而直接利用factory来实现创建和覆盖。当然,创建和覆盖也可以由其它方式完成。
注册后对象的创建
- 创建对象时,需要结合工厂的注册和覆盖机制来决定,应该使用哪一个类型来创建。
利用工厂做创建的方法
除了使用component/object来创建实例,也可以利用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中去。这就使得通过上面的任何一种方法在创建对象时,不会受困于类型与类型名不同的苦恼。
组件提供的创建方法
配合工厂的注册、创建和覆盖的相关方法:
- create()
- create_component()
- get()
- get_type_name()
- set_inst_override()
- set_type_override()
每一个uvm_component的类在注册时,会定义一个新的uvm_component_registry类,其如同一个外壳,一个包装模板的纸箱。在factory中注册时,该纸箱中容纳的是被注册类的图纸”,并没有一个“实例”
覆盖
- 覆盖机制可以将其原来所属的类型替换为另外一个新的类型
- 在覆盖之后,原本用来创建原属类型的请求,将由工厂来创建新的替换类型
无需再修改原始代码,继而保证了原有代码的封装性。
新的替换类型必须与被替换类型相兼容,否则稍后的句柄赋值将失败,所以使用继承。 - 做顶层修改时,非常方便
允许灵活的配置,例如可使用子类来覆盖原本的父类
可使用不同的对象来修改其代码行为
覆盖类型
- 覆盖发生时,可以使用“类型覆盖”或者“实例覆盖
类型覆盖指,UVM层次结构下的所有原有类型都被覆盖类型所替换。
实例覆盖指,在某些位置中的原有类型会被覆盖类型所替换。
覆盖方法
类型覆盖
static function void set_type_override(uvm_object_wrapper override_type, bit replace=1);
- uvm obiect wrapper override_type
这是什么?并不是某一个具体实例的句柄,实际上是注册过后的某一个类在工厂中注册时的句柄。怎么找到它呢?就使用new_type::get_type()。 - bit replace=1
1:如果已经有覆盖存在,那么新的覆盖会替代旧的覆盖。
0:如果已经有覆盖存在,那么该覆盖将不会生效。 - set_type_override是一个静态函数
orig_type : : type_id: :set_type_override(new_type: : get_type())
实例覆盖
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}来作为目标路径。 -
set type_override是一个静态函数
orig_type::type_id::set_inst_override (new_type ::get_type(), “orig_inst path”) -
什么叫做路径
UVM的顶层是root,root下是test,test下是env,env下是checker
那么checker的路径:“root.test.env.checker”
module factory_override ;
import uvm pkg : : * ;
'include "uvm macros.svh"
class comp1 extends uvm_component; //第一个类继承于component类
'uvm_component_utils (comp1)
function new(string name="comp1", uvm_component parent=null);
super.new (name, parent) ;
$display($sformatf ( "comp1 : : %s is created" , name))
endfunction: new
virtual function void hello (string name) ; //第一个类的身份信息
$display($sformatf ( "comp1 : : %s said hello ! " , name));
endfunction
endclass
class comp2 extends comp1 ; //第二个类继承于第一个类
`uvm component_utils (comp2)
function new(string name="comp2" , uvm_component parent=null);
super.new(name, parent) ;
$display($sformatf ( "comp2 : : %s is created" , name)) ;
endfunction: new
function void hello (string name) ; // 第二个类的身份信息
$display($sformatf ( "comp2 : : %s said hello! ", name)) ;
endfunction
endclass
comp1 c1,c2 ; //c1,c2都是comp1类型
initial begin
comp1 : : type_id: :set_type_override (comp2 : : get_type ()) ; //创建c1之前先做覆盖,将comp1覆盖类型类comp2
c1 = new ( "c1"'); //这地方还是comp1类型对象
c2 = comp1 : : type_id: :create ( "c2" , null) ; //利用工厂创建c2,类型会被覆盖为comp2
c1.hello ( "c1") ; //调用comp1的hello
c2.hello ( "c2") ;//调用comp2的hello
end
endmodule
- 覆盖必须是子类覆盖父类
- 调用的方法必须是虚方法,这地方是子类实例赋值给父类句柄
确保正确覆盖
- 将UVM环境中所有的类都注册到工厂中,并通过工厂来创建对象。
- 在使用某些类的时候,确保该类已经被导入(import)到当前域(scope)中。
- 通过工厂创建对象时,句柄名称应该同传递到create()方法中的字符串名称相同。无论是通过层次路径名称来覆盖还是配置,将例化组件的句柄名称同创建时create()方法中的字符串名称保持—致
- 由于覆盖是采用parent wins模式,因此要注意在同一个顶层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世界的全貌,而不是研究全部的用法,毕竟我们时间非常有限!