UVM入门与进阶学习笔记1——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: new
	function 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"); //注意没有parent
    	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");  //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));
    endfunction
    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
    function void hello(string name);
      $display($sformatf("comp2:: %s said hello!", name));
    endfunction
  endclass
  comp1 c1, c2;
  initial begin
    comp1::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()进行动态类型转换。
  • 24
    点赞
  • 162
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值