UVM验证—第一课:方法学&类库&工厂
目录
1. 验证方法学
2. 类库地图
3. 工厂机制
3.1 概述
3.2 工厂的意义
3.3 工厂提供的便利
3.3.1 创建(create)
3.3.2 uvm_coreservice_t类
3.3.3 创建component/object实例方法
3.4 覆盖override()方法
3.4.1 set_type_override()
3.4.1 set_inst_override()
3.4.2 代码
1. 验证方法学
有了SystemVerilog之后,是不是足以搭建一个验证平台呢?这个问题的答案是肯定的,只是很难。就像汉语是很优秀的语言一样,自古以来,无数的名人基于它创作出很多优秀的篇章。有很多篇章经过后人的浓缩,变成了一个又一个的成语和典故。在这些篇章的基础上,作家写作的时候稍微引用几句就会让作品增色不少。而如果一个成语都不用,一点语句都不引用,也能写出优秀的文章,但是相对来说比较困难。这些优秀的作品就是汉语的库。同样,SystemVerilog是一门优秀的语言,但是如果仅仅使用SystemVerilog来进行验证显然不够,有很多直接的问题需要考虑,比如:
- 验证平台中都有哪些基本的组件,每个组件的行为有哪些?
- 验证平台中各个组件之间是如何通信的?
- 验证中要组建很多测试用例,这些测试用例如何建立、组织的?
- 在建立测试用例的过程中,哪些组件是变的,哪些组件是不变的?
同时,也有一些更高层次的问题需要考虑:
- 验证平台中数据流与控制流如何分离?
- 验证平台中的寄存器方案如何解决?
2. 类库地图
- 在SV模块中,验证环境整体的构建,是从底层模块的验证组件搭建到通信和激励生成。
- 这些元素无论是软件对象的创建、访问、修改、配置,还是组件之间的通信等都是通过用户自定义的方式来实现的。
- UVM验证方法学作为之前所有方法学的融合版本,从自身初衷而言,就是将验证过程中可以重用和标准化的部分都规定在其方法学的类库当中,通过标准化的方式减轻了验证人员构建环境的负担。
验证环境的共同需求
- 组件的创建和访问
- 环境的结构创建、组件之间的连接和运行
- 不同阶段的顺序安排
- 激励的生成、传递和控制
- 测试的报告机制
UVM类库地图按照UVM的核心机制将地图进行了分块:
- 核心基类
- 工厂(factory)类
- 事务(transaction)和序列(sequence)类
- 结构创建(structurecreati on)类
- 环境组件(environmentcomponent)类
- 通信管道(channel)类
- 信息报告(messagereport)类
- 寄存器模型(registermodel)类
- 线程同步(threadsynchronization)类
- 事务接口(transactioninterface)类
3. 工厂机制
3.1 概述
- 工厂(factory)机制是UVM的真正魅力所在。
- 工厂机制也是软件的一种典型设计模式(design pattern)
3.2 工厂的意义
- UVM工厂的存在就是为了更方便地替换验证环境中的实例或者注册了的类型,同时工厂的注册机制也带来了配置的灵活性。
- 这里的实例或者类型替代,在UVM中称作覆盖(override),而被用来替换的对象或者类型,应该满足注册(registration)和多态(polymorphism)的要求。
- UVM的验证环境构成可以分为两部分,一部分构成了环境的层次,这部分代码是通过uvm_component类完成,另外一部分构成了环境的属性(例如配置)和数据传输,这一部分通过uvm_object类完成。
- 这两种类的集成关系从UVM类库地图可以看到,uvm_component类继承于uvm_object类,而这两种类也是进出工厂的主要模具和生产对象。
- 之所以称为模具,是因为通过注册,可以利用工厂完成对象创建。
- 而之所以对象由工厂生产,也是利用了工厂生产模具可灵活替代的好处,这使得在不修改原有验证环境层次和验证包的同时,实现了对环境内部组件类型或者对象的覆盖。
3.3 工厂提供的便利
3.3.1 创建(create)
module object_create; // 定义一个模块 object_create,用于测试对象创建import uvm_pkg::*; // 导入 UVM 标准库中的所有类和方法`include "uvm_macros.svh" // 包含 UVM 宏定义文件,尤其是 `uvm_component_utils 和 `uvm_object_utils 等宏class comp1 extends uvm_component; // 定义一个继承自 uvm_component 的类 comp1`uvm_component_utils(comp1) // 使用宏注册 comp1 类,使其可以使用 factory 创建机制function new(string name="comp1", uvm_component parent=null); // 构造函数super.new(name, parent); // 调用父类 uvm_component 的构造函数$display($sformatf("%s is created", name)); // 打印组件创建信息endfunction: newfunction void build_phase(uvm_phase phase); // build_phase 方法super.build_phase(phase); // 调用父类的 build_phaseendfunction: build_phaseendclassclass obj1 extends uvm_object; // 定义一个继承自 uvm_object 的类 obj1`uvm_object_utils(obj1) // 使用宏注册 obj1 类,使其可以通过 factory 创建function new(string name="obj1"); // 构造函数super.new(name); // 调用父类 uvm_object 的构造函数$display($sformatf("%s is created", name)); // 打印对象创建信息endfunction: newendclasscomp1 c1, c2; // 声明两个 comp1 类型的变量 c1 和 c2obj1 o1, o2; // 声明两个 obj1 类型的变量 o1 和 o2initial begin // 初始块,仿真开始时执行一次c1 = new("c1"); // 直接调用构造函数创建 c1 组件,name 为 "c1"o1 = new("o1"); // 直接调用构造函数创建 o1 对象,name 为 "o1"c2 = comp1::type_id::create("c2", null); // 使用 UVM factory 机制创建 c2 组件,parent 为 nullo2 = obj1::type_id::create("o2"); // 使用 UVM factory 机制创建 o2 对象,无 parent 参数endendmodule // 模块结束
(1) 运用factory的步骤:
- 将类注册到工厂
- 在例化前设置覆盖对象和类型(可选)
- 对象创建
(2) 在两种类comp1和obj1的注册中,分别使用了UVM宏uvm_component_utils和uvm_object_utils
- 什么是宏(macro)呢?
- 为什么需要宏呢?
这两个宏做的事情就是将类注册到actory中。在解释注册函数之前,我们需要懂得在整个仿真中,factory是独有的,即有且只有一个,这保证了所有类的注册都在一个“机构”中。
(3) uvm_{component,object}的例化
- 每一个uvm_{component,object}在例化的时候都应该给予一个名字(string)。
- “full name”指的是component所处的完整层次结构。
- 在每个层次中例化的组件名称,应该独一无二(unique)。
创建component或者object的方法如下:
(1) 创建uvm_component对象时,
- comp_type::type_id::create(string name, uvm_component parent);
(2) 创建uvm_object对象时,
- object_type::type_id::create(string name);
3.3.2 uvm_coreservice_t类
(1) 该类内置了UVM世界核心的组件和方法,它们主要包括:
- 唯一的uvm_factory,该组件用来注册、覆盖和例化
- 全局的report_server,该组件用来做消息统筹和报告
- 全局的tr_database,该组件用来记录transaction记录
- get_root()方法用来返回当前UVM环境的结构顶层对象
(2) 而在UVM-1.2中,明显的变化是通过uvm_coreservice_t将最重要的机制(也是必须做统一例化处理的组件)都放置在了uvm_coreserice_t类中。
(3) 该类并不是uvm_component或uvm_object,它也并没有例化在UVM环境中,而是独立于UVM环境之外的。
(4) uvm_coreservice_t只会被uvm系统在仿真开始时例化一次。用户无需,也不应该自行再额外例化该核心服务组件。
(5) 这个核心组件如同一个随时待命的仆人,做好服务的准备。
(6) 理论上,用户可以获取核心服务类中的任何一个对象,例如uvm_default_factory对象,继而直接利用factory来实现创建和覆盖。当然,创建和覆盖也可以由其它方式完成。
3.3.3 创建component/object实例方法
(1) 创建对象时,需要结合工厂的注册和覆盖机制来决定,应该使用哪一个类型来创建。
(2) 配合工厂的注册、创建和覆盖的相关方法:(uvm_component/uvm_object创建实例方法①)
- create()
- create_component()
- get()
- get_type_name()
- set_inst_override()
- set_type_override()
(3) 每一个uvm_component的类在注册时,会定义一个新的uvm_component_registry类
- 当你使用宏
uvm_component_utils(my_class)
注册一个类(如my_comp
)时:- UVM 会自动生成一个名为
my_comp_registry
的类。 - 这个类继承自
uvm_component_registry
。 - 它的作用是:作为这个类在 factory 系统中的“注册信息”载体。
- UVM 会自动生成一个名为
typedef class my_comp;class my_comp_registry extends uvm_component_registry #(my_comp, "my_comp");// 单例模式的实现等
endclass
其如同一个外壳,一个包装模板的纸箱,在factory中注册时,该纸箱中容纳的是被注册类的“图纸”,并没有一个“实例”。
包装模板的纸箱 | uvm_component_registry 类 |
图纸 | 类型信息、构造函数指针、名字等元数据 |
实例 | 调用 create() 或 new() 后才生成的对象 |
(4)除了使用component/object来创建实例,也可以利用factory来创建:(工厂创建component/object实例的方法②)
- 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中去。这就使得通过上面的任何一种方法在创建对象时,不会受困于类型与类型名不同的苦恼。
只需记得上面代码中的创建即可!!
3.4 覆盖override()方法
- 覆盖机制可以将其原来所属的类型替换为另外一个新的类型。
- 在覆盖之后,原本用来创建原属类型的请求,将由工厂来创建新的替换类型。
- 无需再修改原始代码,继而保证了原有代码的封装性。
- 新的替换类型必须与被替换类型相兼容,否则稍后的句柄赋值将失败,所以使用继承。
- 做顶层修改时,非常方便!
- 允许灵活的配置,例如可使用子类来覆盖原本的父类
- 可使用不同的对象来修改其代码行为
- 要想实现覆盖特性,原有类型和新类型均需要注册。
- 当使用create()来创建对象时:
- 工厂会检查,是否原有类型被覆盖。
- 如果是,那么它会创建一个新类型的对象。
- 如果不是,那么它会创建一个原有类型的对象。
- 覆盖发生时,可以使用“类型覆盖”或者“实例覆盖”
- 类型覆盖指,UVM层次结构下的所有原有类型都被覆盖类型所替换。
- 实例覆盖指,在某些位置中的原有类型会被覆盖类型所替换。
3.4.1 set_type_override()
static function void set_type_override(uvm_object_wrapper override_type, bit replace=1);
- uvm_object_wrapper override_type
这是什么?并不是某一个具体实例的句柄,实际上是注册过后的某一个类在工厂中注册时的句柄。怎么找到它呢?就使用new_type::get_type()。
- bit replace=1
1:如果已经有覆盖存在,那么新的覆盖会替代旧的覆盖。
0:如果已经有覆盖存在,那么该覆盖将不会生效。
3.4.1 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}来作为目标路径。
3.4.2 代码
module factory_override; // 定义名为 factory_override 的模块import uvm_pkg::*; // 导入 UVM 库`include "uvm_macros.svh" // 包含 UVM 宏定义class comp1 extends uvm_component; // 定义基类 comp1,继承自 UVM 组件`uvm_component_utils(comp1) // 注册 comp1 到 UVM 工厂function new(string name="comp1", uvm_component parent=null); // 构造函数super.new(name, parent); // 调用父类构造函数$display($sformatf("comp1:: %s is created", name)); // 打印创建信息endfunctionvirtual function void hello(string name); // 声明虚方法$display($sformatf("comp1:: %s said hello!", name)); // 基类实现endfunctionendclassclass comp2 extends comp1; // 定义派生类 comp2`uvm_component_utils(comp2) // 注册 comp2 到 UVM 工厂function new(string name="comp2", uvm_component parent=null);super.new(name, parent); // 调用基类构造函数$display($sformatf("comp2:: %s is created", name)); // 添加额外信息endfunctionfunction void hello(string name); // 重写 hello 方法$display($sformatf("comp2:: %s said hello!", name)); // 派生类实现endfunctionendclasscomp1 c1, c2; // 声明两个 comp1 类型句柄initial begin // 初始块comp1::type_id::set_type_override(comp2::get_type()); // 设置工厂覆盖:comp1->comp2-->当你要创建 comp1 时,请创建 comp2。c1 = new("c1"); // 直接实例化 comp1(不受工厂影响)c2 = comp1::type_id::create("c2", null); // 通过工厂创建对象(应用覆盖)所有通过 create() 方法创建 comp1 的地方,都会得到一个 comp2 的实例-->由于 comp2 继承自 comp1,当创建 comp2 对象时,SystemVerilog 会自动调用构造函数链-->(super.new() → comp1::new()→comp2::new() )c1.hello("c1"); // 调用 comp1 的 hello 方法c2.hello("c2"); // 调用实际类型 comp2 的 hello 方法,调用重写后的 hello-->由于`hello`方法在基类`comp1`中声明为`virtual`,因此具有多态性。调用时会根据实际对象的类型(`comp2`)来决定调用哪个方法。所以,这里调用的是`comp2`中的`hello`方法。end
endmodule
关键点解析:
-
工厂覆盖机制:
-
set_type_override
将 comp1 映射到 comp2 -
create()
方法应用覆盖,new()
直接构造不应用覆盖
-
-
输出结果: comp1:: c1 is created // c1 直接构造(comp1) comp1:: c2 is created // 工厂创建时先调用基类构造函数(comp1部分) comp2:: c2 is created // 再调用派生类构造函数(comp2部分) comp1:: c1 said hello! // c1 调用 comp1 的 hello comp2:: c2 said hello! // c2 实际是 comp2 类型(**父类new方法是虚方法,调用子类;父类new方法不是虚方法,调用父类**)
-
多态行为:
-
c2
声明为 comp1 类型但实际指向 comp2 对象 -
虚方法调用 (
hello()
) 根据实际对象类型执行-
hello
在基类中声明为virtual
-
确保方法调用基于实际对象类型而非句柄类型
-
-
c2 的特殊性
-
句柄类型:
comp1
(基类) -
方法调用时,系统自动查找实际对象的方法表
-
实际对象:
comp2
(派生类)
-
-