UVM入门与进阶学习笔记1——UVM概述、类库地图、工厂机制、覆盖方法
目录
- 验证方法学概述
- UVM的优势
- 类库地图
- UVM核心类库
- 工厂机制
- 覆盖方法
前言:个人学习UVM的在线笔记。学习的过程中借鉴一些大佬的笔记和路科验证学习资料,只做个人学习交流使用,感谢!
验证方法学概述
UVM的优势
- UVM融合其他验证方法学的积极意义在于,打通了各个EDA公司和IC设计公司的验证技能通道,便于验证技术交流和人才流动,也方便IC设计公司的技术及工具选择。用户不再受限于使用何种仿真器、使用哪一家的验证IP,而只需要将主要精力着眼于设计的功能验证,由此也提升了验证效率。
- SV核心特性包括面向对象、随机约束、线程通信、功能覆盖率收集等,这些特性也为建立一个验证环境提供足够多的便利。
- UVM验证方法学通过吸取eRM(Specman/e验证方法学),AVM,OVM,UVM等不同方法学的优点,可谓集众家之所长。其思想并非必须要与某种语言绑定。
- 所有验证方法学服务目的都在于提供一些可重用的类减轻在项目之间水平复用和垂直复用的工作量,同时对验证新人能提供一套可靠的框架,摆脱搭建房子构思图纸的苦恼。
- UVM面向所有数字设计,涵盖从模块级到芯片级,ASIC到FPGA,控制逻辑、数据通路到处理器验证的全部场景。
- UVM中Universal的含义代表该方法学可适用于大多数验证项目,而它自身提供的基础类库(basic class library)和基本验证结构可以让具有不同软件编程经验的验证人员能够快速构建起一个结构可靠的验证框架。
- UVM自定义的框架构建类和测试类能够减轻环境构建的负担,进而将更多的精力集中在如何制定验证计划和创建测试场景。
- UVM框架,包括UVM的类库和核心机制、核心UVM组件和层次构建方式、常见UVM组件间的通信方式、UVM测试场景的构成、UVM寄存器模型应用。
类库地图
SV模块中验证环境整体的构建,是从底层模块验证组件搭建到通信和激励生成,这些元素无论是软件对象的创建、访问、修改、配置,还是组件之间的通信等都通过用户自定义的方式实现的。
UVM的初衷是将验证过程中可复用和标准化的部分都规定在其方法学的类库中,通过标准化的方式减轻构建环境的负担。
对验证环境的共同需求:(划重点)
- 组件的创建和访问;
- 环境的结构创建、组件之间的连接和运行;
- 不同阶段的顺序安排;
- 激励的生成、传递和控制;
- 测试的报告机制。
UVM核心类库
- 由于软件环境中对象的生成是动态的,验证环境的组件也需要UVM提供底层功能完成对象的创建和访问。
- UVM需提供环境上下层次中创建、连接和运行组件的顺序控制方法,只有在底层机制上有效地保证这一点,才会避免可能发生的句柄悬空问题。
- 组件通信中,UVM提供功能更丰富的TLM(Transaction level model)接口,可以保证相邻组件的通信不再通过显式句柄引用,而是独立于组件的通信方式。
- 对于测试序列(sequence)的生成和传输也是利用了TLM传输在sequence和driver之间完成。对于不同sequence的发送顺序控制,需要实现sequence之间的灵活调度。
- 为了便于验证环境的调试,UVM的报告机制可将来自不同组件、不同级别的信息并且加以过滤,最终生成测试报告。
- 核心基类提供最底层的支持,包括一些基本方法例如拷贝、创建、比较和打印。在核心类之上发展了支持UVM特性的各个相关的类群。
- 工厂类提供注册环境组件、创建组件和覆盖组件类型的方法。
- 事务类和序列类用来规定在TLM(Transaction Level Model)传输管道中的数据类型和数据生成方式。
- 环境组件类是构成验证结构的主要部分,组件之间的嵌套关系通过层层例化和连接形成结构层次关系。
- 事务接口类和通信管道类共同实现组件之间的通信和存储。
- 线程同步类要比SV自身的同步方法更方便,同步时包含的信息更多。
- 信息报告类使得从UVM环境中报告的信息一致规范化,便于整体的控制和过滤。
- 寄存器模型类用来完成对寄存器和存储的建模、访问和验证。
工厂机制
三核心要素:注册uvm_{component, object}_utils
、创建uvm_{component, object}::type_id::create()
和覆盖set_{type,inst}_override{_by_type}
工厂的意义:
- 工厂机制也是软件的一种典型设计模式,是为了更方便替换验证环境中的实例或注册了的类型,同时工厂的注册机制也带来了配置的灵活性。
- 实例或类型替代,在UVM中称为覆盖,而被用来替换的对象或类型,应满足注册和多态的要求。
- UVM的验证环境构成可分为:一部分构成环境的层次,这部分代码通过uvm_component类完成;另一部分构成环境的属性和数据传输,通过uvm_object类完成。
- uvm_component类继承于uvm_object类,这两种类也是进出工厂的主要模具和生产对象。
- 可以利用工厂通过注册完成对象创建;之所以对象由工厂生产,是利用工厂生产模具可灵活替代的好处,使得在不修改原有验证环境层次和验证包的同时,实现对环境内部组件类型或对象的覆盖。
验证环境的不动产:generator、stimulator、monitor、agent、checker/reference model、environment、test,这些组件在uvm_component
的子类中均有对应的组件。
非固定资产即TLM transaction,从generator流向stimulator的数据包,这些类统一由uvm_object
表示。
uvm_{component, object}的例化:
- 每个uvm_{component, object}例化时都应给予一个名字(string);
- “full name”指的是component所处的完整层次结构;
- 在每个层次中例化的组件名称应独一无二。
创建component或object的方法:
comp_type: :type_id: :create(string name, uvm_component parent);
comp_type: :type_id: :create(string name);
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); //对父类new函数的继承$display($sformatf("%s is created", name));endfunction: newfunction void build_phase(uvm_phase phase); //phase机制super.build_phase(phase);endfunction: build_phase
endclass
//注意分清楚component和object类
class obj1 extends uvm_object;`uvm_object_utils(obj1) function new(string name="obj1"); //注意没有parentsuper.new(name);$display($sformatf("%s is created", name));endfunction: new
endclasscomp1 c1, c2;
obj1 o1, o2; //例化
initial beginc1 = new("c1");o1 = new("o1"); //SV创建方式c2 = comp1::type_id::create("c2", null);o2 = obj1::type_id::create("o2"); //UVM工厂创建方式
end
endmodule
输出结果:
c1 is created
o1 is created
c2 is created
obj1 is created
- 上面例码分别定义了两个类comp1(component类)和obj1(object类),两种方式都实现了对象的例化。
- c1和o1的例化通过new()函数进行;c2和o2的例化通过更复杂的方式进行。
- c2和o2的例化方式最后也是通过调用new()函数实现,毕竟对任何对象的例化,最终都要通过new()构建函数来实现。
- 在两种类comp1和obj1的注册中分别使用了UVM宏`uvm_component_utils和uvm_object_utils,这两个宏做的事情就是将类注册到factory中。
- factory是独有的,且只有一个,保证了所有类的注册都在一个“机构”中(红宝书P265页)。
运用factory的步骤:(划重点)
- 将类注册到工厂;
- 在例化前设置覆盖对象和类型(可选的);
- 对象创建。
uvm_coreservice_t类:内置了UVM世界核心的组件和方法,主要包括:
- 唯一的uvm_factory,用来注册、覆盖和例化;
- 全局的report_server,用来做消息统筹和报告;
- 全局的tr_database,用来记录transaction记录;
get_root()
方法用来返回当前UVM环境的结构顶层对象;- UVM-1.2明显的变化是通过uvm_correservice_t将最重要的机制(也是必须做统一例化处理的组件)都放置在其中;该类并非uvm_component或uvm_object,它并没有例化在UVM环境中,而是独立于UVM环境之外。
- uvm_correservice_t只会被UVM系统在仿真开始时例化一次,用户无需也不应自行再额外例化该核心服务组件;这个核心组件如同随时待命的仆人,做好服务的准备。
- 理论上用户可获取核心服务类中的任何一个对象,如uvm_default_factory对象,继而直接利用factory实现创建和覆盖。
- 宏调用的过程中,实现类型定义——
typedef uvm_component_registry #(T, "S") type_id
。 uvm_factory::register()
——注册type_id并得到实例。- 一旦发生注册,
type_id::create()
函数就可通过uvm_factory::create_component_by_type()
实现。 - 对于注册,并不是真正将一个抽象的类型(空壳)放置在什么地方,而是通过例化该类的对象完成。
- 由于一种类型在通过宏调用时只注册一次,不考虑覆盖的情况下,
uvm_default_factory
将每个类对应的对象都放置到factory的字典当中。 uvm_default_factory::create_component_by_type()
经过代码简化可以看到关键语句,它们首先检查处在该层次路径中需要被例化的对象,是否受到"类型覆盖”或“实例覆盖”的影响,进而将最终类型对应的对象句柄(正确模板)交给工厂。- 有正确模板后可通过
uvm_component_registry::create_component()
完成例化。
注册后的对象创建:
- 在创建时虽然都需调用create()函数,但最终创建出的
uvm_component
会表示在uvm层次结构中,而uvm_object
则不会显示在层次中;这一点可以从uvm_component::new(name,parent)
和uvm_object::new(name)
中看出。 uvm_component::new(name, parenet)
保留两个参数,是为了通过类似“钩子”的做法,一层层由底层勾住上一层,就能将整个UVM结构串接起来。uvm_object: new(name)
没有parent参数,故不会显示在UVM层次中,只能作为configuration或transaction等用来做传递的配置结构体或抽象数据传输的数据结构体,成为uvm_component的成员变量。
配合factory的注册、创建和覆盖的方法:
create()
create_component()
get()
get_type_name()
set_inst_override()
set_type_override()
每个uvm_component的类在注册时,会定义一个新的uvm_component_registry
类,其如同一个包装模块的纸箱,在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中去。使得通过上面任何一种方法创建对象时,不会受困于类型与类型名不同的苦恼。
覆盖方法
覆盖机制可将原来所属的类型替换为新类型,覆盖后原本用来创建原属类型的请求,将由工厂创建新的替换类型;要想实现覆盖特性,原有类型和新类型均需要注册。
- 无需再修改原始代码,继而保证原有代码的封装性;
- 新的替换类型必须与被替换类型相兼容,否则稍后的句柄赋值将失败,所以使用继承。
做顶层修改时非常方便:
- 允许灵活的配置,例如可使用子类覆盖原本的父类;
- 可使用不同对象修改其代码行为。
当使用create()创建对象时:
- 工厂会检查是否原有类型被覆盖;
- 如果是,那么它会创建一个新类型的对象;
- 如果不是,那么它会创建一个原有类型的对象。
覆盖发生时,可用“类型覆盖”或“实例覆盖”:
- 类型覆盖:UVM层次结构下的所有原有类型都被覆盖类型所替换;
- 实例覆盖:在某些位置中的原有类型会被覆盖类型所替换。
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/0
(1:如果已有覆盖存在,那么新的覆盖会替代旧的覆盖;0:如果已有覆盖存在,那么该覆盖将不会生效)。set_type_override
是一个静态函数;参照的覆盖方式:orig_type::type_id::set_type_override(new_type::get_type())
。
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}
作为目标路径。- set_type_override是一个静态函数;参照的覆盖方式:
orig_type::type_id::set_inst_override(new_type::get_type(),"orig_inst_path")
。
不止一个类提供与覆盖有关的函数,然而名称与参数列表可能各不相同:
uvm_component::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来做覆盖。
module factory_override;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("comp1:: %s is created", name));endfunctionvirtual function void hello(string name);$display($sformatf("comp1:: %s said hello!", name));endfunctionendclassclass 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));endfunctionfunction void hello(string name);$display($sformatf("comp2:: %s said hello!", name));endfunctionendclasscomp1 c1, c2;initial begincomp1::type_id::set_type_override(comp2::get_type()); //类型覆盖c1 = new("c1");c2 = comp1::type_id::create("c2", null);c1.hello("c1");c2.hello("c2");end
endmodule
输出结果:
comp1:: c1 is created
comp1:: c2 is created
comp2:: c2 is created
comp1:: c1 said hello!
comp2:: c2 said hello!
- comp2类型覆盖了comp1类型——
comp1::type_id::set_type_override(comp2::get_type());
。 - 紧接着对c1和c2对象进行创建,可以从输出结果看到的是c1的所属类型仍然是comp1,c2的类型则变为comp2。说明factory的覆盖机制只会影响通过factory方法创建的对象。所以,通过
type_id::create()
和factory的类型覆盖可以实现对象类型在例化时的灵活替换。 - 在例化c2之前应该用comp2替换comp1的类型。只有先完成类型替换,才可以在后面的例化时由factory选择正确的类型。
- 发生类型替换后,如果原有代码不做更新,那么c2句柄的类型仍然为comp1,却指向comp2类型的对象。这要求comp2应该是comp1的子类,只有这样,句柄指向才是合法安全的。
- c2在调用hello()方法时,由于首先是comp1类型,那么会查看
comp1::hello()
,又由于该方法在定义时被指定为虚函数,这就通过多态性的方法调用,转而调用comp2::hello()
函数。因此显示的结果也是“comp2::c2 said hello!
”。 - 有了注册类型词典和覆盖类型队列的信息后,如下图,当c2通过factory创建时,会查看被创建类型是否已经被覆盖,如果被覆盖则从
uvm_default_factory::m_type_overrides
中取得覆盖类型的信息。 - 例子中comp2类型已经覆盖comp1类型,因此最终创建的类型是comp2类型。
确保正确覆盖的代码要求:
- 将UVM环境中所有类都注册到工厂中,并通过工厂来创建对象。
- 在使用某些类时确保该类已经被导入到当前域中。
- 通过工厂创建对象时,句柄名称应同传递到create()方法中的字符串名称相同。无论是通过层次路径名称来覆盖还是配置,将例化组件的句柄名称同创建时create()方法中的字符串名称保持一致。
- 由于覆盖是采用parent wins模式,因此要注意在同个顶层build_phase()中覆盖方法应发生在对象创建之前。
- 为了尽量保证运行时覆盖类可以替换原始类,覆盖类最好是原始类的子类,而调用成员方法也应声明为虚方法。
- 另一种确保运行时覆盖类句柄类型正确的方式,则需通过
$cast()
进行动态类型转换。
UVM入门与进阶学习笔记1——UVM概述、类库地图、工厂机制、覆盖方法相关推荐
- UVM入门与进阶学习笔记4——UVM仿真的开始与结束
目录 UVM的编译和运行顺序 UVM仿真开始 UVM世界的"诞生" UVM仿真结束 UVM的编译和运行顺序 下图是UVM的编译运行顺序,非常重要的知识点! 在加载硬件模型调用仿真器 ...
- UVM入门与进阶学习笔记16——sequencer和sequence(2)
目录 sequence的层次化 Hierarchical Sequence Virtual Sequence Layering Sequence sequence的层次化 就水平复用而言,在MCDF各 ...
- UVM入门与进阶学习笔记17——寄存器模型(2)
目录 寄存器模型集成 总线UVC的实现 总线UVC解析 MCDF寄存器设计代码 Adapter的实现 Adapter的集成 访问方式 前门访问 后门访问 前门与后门的比较 前门与后门的混合应用 寄存器 ...
- UVM实战 卷I学习笔记10——UVM中的寄存器模型(3)
目录 后门访问与前门访问 *UVM中前门访问的实现 后门访问操作的定义 *使用interface进行后门访问操作 UVM中后门访问操作的实现:DPI+VPI *UVM中后门访问操作接口 后门访问与前门 ...
- UVM入门与进阶学习笔11——TLM通信(3)
目录 通信管道 TLM FIFO Analysis Port Analysis TLM FIFO Request & Response通信管道 通信管道 TLM通信的实现方式的共同点在于都是端 ...
- Python数据分析入门--NumPy进阶学习笔记
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 NumPy 进阶操作 1.NumPy dtype 层次结构 2.NumPy 高阶数组操作 2.1数组的重塑 2.2数组 ...
- UVM实战 卷I学习笔记8——UVM验证平台的运行(2)
目录 *build阶段出现UVM_ERROR停止仿真 *phase的跳转 phase机制的必要性 phase的调试 超时退出 *build阶段出现UVM_ERROR停止仿真 之前的代码中,如果使用co ...
- 古月居ros课件_古月居ROS入门21讲学习笔记(基础概述1-5课)
第一课 课程介绍 ROS: Robot Operating System 机器人操作系统,包括通讯机制+开发工具+应用功能+生态系统.ROS是机器人领域的普遍标准. 课程结构 第二课 linux操作系 ...
- opencv进阶学习笔记3:像素运算和图像亮度对比度调节
基础版传送门: python3+opencv学习笔记汇总目录(适合基础入门学习) 进阶版目录: python+opencv进阶版学习笔记目录(适合有一定基础) 像素运算 要求两张图大小,以及格式(np ...
- 尚硅谷JavaScript高级教程(javascript实战进阶)学习笔记
前言 这个是我学习过程中的笔记,分享给大家,希望对大家有用. 学习内容是尚硅谷JavaScript高级教程(javascript实战进阶),这里是视频链接. 我在前面有两篇对于web前端HTML和CS ...
最新文章
- Request Connection: Remote Server @ 192.229.145.200:80
- 如何从命令行重新加载.bash_profile?
- 锚杆拉拔试验弹性模量计算_土钉、锚杆拉拔试验检测方案 - 图文 -
- 千万级规模高性能、高并发的网络架构经验分享--转
- Azkaban通过API动态传递参数
- C#线程的使用和测试
- spotify 数据分析_我的Spotify流历史分析
- 如何及时还原被删除的活动目录对象
- vs下C# WinForm 解决方案里面生成的文件都是什么作用?干什么的?
- 封装一个FTPClient连接池工具类
- 力扣题目系列:1299. 将每个元素替换为右侧最大元素 -- 一道算法优化入门题
- Solidworks CAM入门教程,简单生成雕刻机刀路,经验分享
- 阿里云ECS后台CPU占用100%,top却找不到
- 生活中的逻辑谬误01.诉诸纯洁
- 机器人城阳_玄德机器人墙板码板机城阳性能优异
- 站群网站八大盈利模式浅析
- 汽车之家APP车型口碑--参数分析
- 日本亚马逊海淘转运公司好?日亚转运公司攻略
- Revi二次开发_3.筛选不合格厚度的叠合板
- [转]人生多磨难 句句皆精华