目录
- 简单的验证平台
- 典型的UVM验证平台
第一章 Systemverilog基础知识
Systemverilog是一种面向对象的编程语言,相对应的是非面向对象的语言(C语言),面向对象语言的重要特点是所有功能都要在类(class)里实现
A.1 结构体的使用
- 程序设计=算法+数据结构
- 声明结构体→定义结构变量
A.2 从结构体到类
- 类将结构体和它相应的函数集合在一起,成为新的数据组织形式。这种形式包括两种成分:
1、来自结构体的数据变量,在类中称为成员变量
2、来自与结构体相对应的函数,成为一个类的接口
- 类定义好后需要实例化才可以使用,实例化完成即可调用类中的函数
- new函数被称为“构造函数”
A.3 类的封装
- 类有三大特征:封装、继承、多态
私有变量local类型
- 私有变量(systemverilog中的local、其他编程语言的private)特点:
1、此变量只能在类的内部由类的函数/任务进行访问
2、在类的外部使用直接引用的方式进行访问会提示出错
A.4 类的继承
- 分析所要解决的问题并找出共性,这些共性构成一个“基类(或者父类)”。在此基础上,将问题分类,不同的分类具有各自的共性,使用这些分类的共性构建成一个“派生类(或者子类)”
class bird extends animal;
function void fly();
…
endfunction
endclass
- 当子类从父类派生后,子类天然地具有了父类所有的特征,父类的成员变量也是子类
的成员变量,父类的成员函数同时也是子类的成员函数。除了具有父类所有的特征外,子类还可以有自己额外的成员变量和成员函数 - 如果一个变量是local类型的,那么它是不能被外部直接访问的。如果父类中某成员变量是local类型,那么子类是否可以使用这些变量?答案是否定的
- 对于父类来说,子类算是"外人",只是算是比较特殊的“外人”而已。如果想访问父类中的成员变量,同时又不想让这些成员变量被外部访问,那么可以将这些变量声明为protected类型:
class animal;
string name;
protected int birthday;/*example:20030910*/
protected string category;/*example: bird, non_bird*/
protected int food_weight;
protected int is_healthy;
endclass
this、super
- this表示调用的成员是当前类的成员,super表示调用的成员是父类的成员
案例一
案例二
- 成员覆盖
-
A.5 类的多态
A.6 句柄的使用
- 句柄可以作为形式参数通过方法来完成对象指针的传递,从外部传入方法内部
- 句柄也可以在方法内部首先完成修改,而后再由外部完成使用
- 动态修改: 在执行程序时,可以在任何时刻为句柄创建新的对象,并将新的指针赋值给句柄
A.7 包(package)的使用
-
在多个module、interface、program之中共享parameter、data、type、task、function、class等,即利用package(包)的方式实现。
-
优点:将一簇相关的类组织在了单一的命名空间(namespace)下,使得分属于不同模块验证环境的类来自于不同的package,这样便可以通过package解决类的归属问题 。
-
重名的类可以归属到不同的package中编译。package是将命名空间分隔开了,如果要使用不同package的同名类,只需要注明使用哪个package
包与库的区分
包的命名规则
- 加入前缀
第二章 UVM验证平台
2.1 systemverilog与UVM的关系
- SystemVerilog 是一座提供了丰富材料和工具的建筑商店,而 UVM(Universal Verification Methodology)则像是一套建筑蓝图和指南:
如果你想建造一栋房子(即验证一个硬件设计),SystemVerilog 提供了所有必需的原材料:砖块(数据类型)、水泥(操作符)、木材(控制流语句)和工具(如类、接口等)。这些材料和工具允许你自由地设计和建造任何你想要的结构,不管它是简单的小屋还是复杂的摩天大楼。
UVM 则为你提供了一套详细的蓝图和建造指南,告诉你如何使用这些材料和工具来构建一个结构健全、设计合理、并且可以高效完成其功能(即验证任务)的“建筑”(即验证环境)。UVM 的蓝图确保了使用相同的设计模式、构造方法和实践,这样不同的建筑师(验证工程师)可以更容易地理解和协作,即使是在复杂的项目中。
- 总的来说,在这个类比中,SystemVerilog 是建筑的基础,提供了创建任何东西的可能性;而 UVM 则是完成特定项目(验证工作)的专业知识和指导,帮助你以一种高效和标准化的方式来使用这些基础材料和工具。
2.2 只有driver的验证平台(基于信号级的操作)
- 所有派生自uvm_driver的类的new函数有两个参数
- string类型的name
- uvm_component类型的parent
- driver所做的事情几乎都在main_phase中完成。
- UVM由phase来管理验证平台的运行,这些phase统一以XXX_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数。
- main_phase是uvm_driver中预先定义好的一个任务。因此可以简单认为一个driver=实现其main_driver
uvm_info宏
- 该宏功能与verilog中display类似,但比display功能强大。其包含:打印信息的物理文件来源、逻辑结点信息(在UVM树中的路径索引)、打印时间、对信息的分类组织及打印的信息。应尽量使用uvn_info宏取代display语句
- 它有三个参数:第一个参数是字符串,用于把打印的信息分类;第二个参数是字符串,是需要打印的信息;第三个参数是冗余级别。
- uvm_info的路径索引。在top_tb中通过run_test创建了一个my_driver的实例,那么这个实例的名字是什么呢?答案是uvm_test_top:UVM通过run_test语句创建一个名字为uvm_test_top的实例。读者可以通过把代码语句插入my_driver(build_phase或main_phase)中来验证。
$display("the full name of current component is: %s", get_full_name());
- 信息关键三级别:最重要—UVM_LOW、中等—UVM_MEDIUM、不重要—UVM_HIGH
- 类的定义与类的实例化
- 类的定义类似于在纸上写下一纸条文,然后把这些条纹通知给systemverilog的仿真器:验证平台可能会用到这样一个类,请做好准备工作。
classs A;
...
endclass
- 类的实例化在于通过new函数()来通知systemverilog的仿真器:请创建一个A的实例。仿真器接到new的指令后,就会在内存中划分一块空间(在划分前,会首先检查是否已经预先定义过这个类)在已经定义过的情况下按照定义中所指定的“条纹”分配空间,并且把这块空间的指针返回给a_inst。之后可以通过a_inst来查看类中各个成员变量,调用成员函数/任务等
开辟空间
进入new
初始化
返回
A a_inst;
a_inst = new();
- 注:对于大部分类,如果只定义而不实例化,是没有任何意义的;如果不定义就直接实例化,仿真器会报错。
2.2.2 加入factory机制
uvm_component_utils宏
- 所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册。
- factory机制被集成在这个宏中。这个宏的作用之一局势将my_driver登记在UVM这张表中,这张表是factory功能实现的基础。只要在定义一个新的类时使用这个宏,就相当与把这个类注册到了这张表中
- 只有在类定义时声明了这个宏,才能使用这个功能,这个宏起到了注册的作用,只有经过注册的类,才能使用这个功能。
- 在UVM验证平台中,只要一个类使用uvm_component_utils注册且此类被实例化了,那么这个类的main_phase就会自动被调用。这也就是为什么上一节中会强调实现一个driver等于实现其main_phase。所以,在driver中,最重要的就是实现main_phase
- 使用run_test语句替换原有的my_driver实例化及main_phase的显示调用。一个run_test语句会创建一个my_driver的实例,并且会自动调用my_driver的main_phase。仔细观察run_test语句,会发现传递给它的是一个字符串。UVM根据这个字符串创建了其所代表类的一个实例
- UVM通过run_test语句实例化了一个脱离了top_tb层次结构的实例,建立了一个新的层次结构。
module top_tb;
...
initial begin
run_test("my_driver");
end
endmodule
2.2.3 加入objection机制
- drop_objection语句当成是finish函数的替代者,只是在drop_objection语句之前必须先调用raise_objection语句,raise_objection和drop_objection总是成对出现。
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
top_tb.rxd <= 8'b0;
top_tb.rx_dv <= 1'b0;
while(!top_tb.rst_n)
@(posedge top_tb.clk);
for(int i = 0; i < 256; i++)begin
@(posedge top_tb.clk);
top_tb.rxd <= $urandom_range(0, 255);
top_tb.rx_dv <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW);
end
@(posedge top_tb.clk);
top_tb.rx_dv <= 1'b0;
phase.drop_objection(this);
endtask
- raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前。如$display语句是不消耗仿真时间的,这些语句可以放在raise_objection之前,但是类似@(posedge top.clk)等语句是要消耗仿真时间的。
- 按照如下的方式使用raise_objection是无法起到作用的:
task my_driver::main_phase(uvm_phase phase);
@(posedge top_tb.clk);
phase.raise_objection(this);
...
phase.drop_objection(this);
endtask
2.2.4 加入virtual interface
- 绝对路径的使用大大减弱了验证平台的可移植性,应该尽量杜绝在验证平台中使用绝对路径。
- 避免绝对路径的方法:
- 修改宏定义。但是仅限只调用一个宏文件的情况,若调用了两个宏文件的端口则不适用了
- 在SystemVerilog中使用interface来连接验证平台与DUT的端口。
步骤:1、定义interface
2、在top_tb中实例化DUT时,就可以直接使用
my_if input_if(clk, rst_n);
my_if output_if(clk, rst_n);
dut my_dut(.clk(clk),
.rst_n(rst_n),
.rxd(input_if.data),
.rx_dv(input_if.valid),
.txd(output_if.data),
.tx_en(output_if.valid));
3、在driver中使用interface。由于my_driver是一个类,故应先在类中声明vif,再在main_phase中用vif代替绝对路径驱动其中信号
class my_driver extends uvm_driver;
virtual my_if vif;//先在类中声明vif
...
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
vif.data <= 8'b0; //在main_phase中用vif代替绝对路径驱动其中信号
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
for(int i = 0; i < 256; i++)begin
@(posedge vif.clk);
vif.data <= $urandom_range(0, 255);
vif.valid <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW);
end
@(posedge vif.clk);
vif.valid <= 1'b0;
phase.drop_objection(this);
endtask
config_db机制
4、UVM通过run_test语句实例化了一个脱离了top_tb层次结构的实例,建立了一个新的层次结构。对于这种脱离了top_tb层次结构,同时又期望在top_tb中对其进行某些操作的实例,UVM引进了config_db机制。
- 在config_db机制中,分为set和get两步操作。set操作理解成是“寄信”,而get则相当于“收信”。
//在top_tb中执行set操作
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
//在my_driver中执行get操作
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("my_driver", "build_phase is called", UVM_LOW);
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
endfunction
- config_db的set和get函数都有四个参数:
1、set函数的第二个参数表示的是路径索引。
(无论传递给run_test的参数是什么,创建的实例的名字都为uvm_test_top。由于set操作的目标是my_driver,所以set函数的第二个参数就是uvm_test_top。)
2、这两个函数的第三个参数必须完全一致。
3、set函数的第四个参数表示要将哪个interface通过config_db传递给my_driver
4、get函数的第四个参数表示把得到的interface传递给哪个my_driver的成员变量
- 可以向my_driver中“寄”许多信。
这里引入了build_phase
UVM内建函数——build_phase
- 是UVM内建函数,UVM启动后自动执行。
- 执行顺序:new函数→build_phase→main_phase
- 主要通过config_db的set和get操作传递数据,以及实例化成员变量等
- 这里需要加入super.build_phase语句,因为在其父类的build phase中执行了一些必要的操作,这里必须显式地调用并执行它。
- build _phase与main_phase不同点:
build_phase是一个函数phase,而main_phase是一个任务phase
build_phase是不消耗仿真时间的。build_phase总是在仿真时间(Stime函数打印出的时间为0时执行。
uvm_fatal宏
- uvm_fatal宏是一个类似于uvm_info的宏,但是它只有两个参数,这两个参数与uvm_info宏的前两个参数的意义完全一样
1、与uvm_info宏不同的是,当它打印第二个参数所示的信息后,会直接调用Verilog的finish函数来结束仿真
2、uvm_fatal的出现表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查
3、对于uvm_fatal来说,uvm_info中出现的第三个参数的冗余度级别是完全没有意义的,只要是uvm_fatal打印的信息,就一定是非常关键的,所以无需设置第三个参数。
2.3 为验证平台加入各个组件
2.3.1 加入transaction
- 软件的“盒子”,对应的硬件–module
- 引入reference model、monitor、scoreboard等验证平台的其他组件。在这些组件之间,信息的传递是基于transaction的
- 物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,每个包的大小不一样。
以太网为例,每个包的大小至少是64byte。这个包中要包括源地址、目的地址、包的类型、整个包的CRC校验数据等。
- 一笔transaction就是一个包。在不同的验证平台中,会有不同的transaction。
post_randomize函数
- post_randomize是SystemVerilog中提供的一个函数,当某个类的实例的randomize函数被调用后,post_randomize会紧随其后无条件地被调用。
uvm_sequence_item基类
- 一是my_transaction的基类是uvm_sequence_item:
在UVM中,所有的transaction都要从uvm_sequence_item派生,只有从uvm_sequence_item派生的transaction才可以使用后文讲述的UVM中强大的sequence机制
- 二是这里没有使用uvm_component_utils宏来实现factory机制,而是使用了uvm_object_utils
1、从本质上来说,my_transaction与my_driver是有区别的,在整个仿真期间,my_driver是一直存在的,my_transaction不同,它有生命周期。
2、my_transaction的生命周期:它在仿真的某一时间产生,经过driver驱动,再经过reference model处理,最终由scoreboard比较完成后,其生命周期就结束了
- 这种类都是派生自uvm_object或者uvm_object的派生类,uvm_sequence_item的祖先就是uvm_object。UVM中具有这种特征的类都要使用uvm_object_utils宏来实现。
2.3.2 加入env
- 在验证平台中的什么位置例化reference model、scoreboard等?
- 解决方案:引入一个容器类,在这个容器类中实例化driver、monitor、reference model和scoreboard等。在调用run_test时,传递的参数不再是my_driver,而是这
个容器类,即让UVM自动创建这个容器类的实例。在UVM中,这个容器类称为uvm_env
class my_env extends uvm_env;
my_driver drv;
function new(string name = "my_env", uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv", this);
endfunction
`uvm_component_utils(my_env)
endclass
- 容器类在仿真中也是一直存在的
- 使用uvm_component_utils宏来实现factory的注册
- drv的实例化:只有使用factory机制注册过的类才能使用这种方式实例化
- 验证平台中的组件在实例化时都应该使用type_name::type_id::create的方式。
- 在drv实例化时,传递了两个参数,一个是名字drv,另外一个是this指针,表示my_env
- my_driver在uvm_env中实例化,所以my_driver的父结点(parent)就是my_env。通过parent的形式,UVM建立起了树形的组织结构。
1、这种树形的组织结构中,由run_test创建的实例是树根(这里是my_env),并且树根的名字是固定的,为uvm_test_top
2、在树根之后会生长出枝叶(这里只有my_driver),长出枝叶的过程需要在my_env的build_phase中手动实现
3、无论是树根还是树叶,都必须由uvm_component或者其派生类继承而来
- 当加入了my_env后,整个验证平台中存在两个build_phase,一个是my_env的,一个是my_driver的。那么这两个build_phase按照何种顺序执行呢?
在UVM的树形结构中,build_phase的执行遵照从树根到树叶的顺序,即先执行my_env的build_phase,再执行my_driver的build_phase。当把整棵树的build_phase都执行完毕后,再执行后面的phase。
- my_driver在验证平台中的层次结构发生了变化,它一跃从树根变成了树叶,所以在top_tb中使用config_db机制传递virtual my_if时,要改变相应的路径
2.3.3 加入monitor
- 功能:验证平台中实现监测DUT行为的组件是monitor。driver负责把transaction级别的数据转变成DUT的端口级别,并驱动给DUT,monitor的行为与其相对,用于收集DUT的端口数据,并将其转换成transaction交给后续的组件如reference model、scoreboard等处理。
需要注意的几点:
1、所有的monitor类应该派生自uvm_monitor
2、与driver类似,在my_monitor中也需要有一个virtual my_if
3、uvm_monitor在整个仿真中是一直存在的,所以它是一个component,要使用uvm_component_utils宏注册
4、由于monitor需要时刻收集数据,永不停歇,所以在main_phase中使用while(1)循环来实现这一目的
2.3.4 封装成agent
- driver和monitor两者处理同一种协议,在一套规则下做着不同的事情,UVM通常将两者封装在一起,成为一个agent。不同agent就代表不同协议
class my_agent extends uvm_agent ;
my_driver drv;
my_monitor mon;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_agent)
endclass
- 所有的agent都要派生自uvm_agent类,且其本身是一个component,应该使用uvm_
component_utils宏来实现factory注册。 - is_active是uvm_agent的一个成员变量
枚举类型变量uvm_active_passive_enum
- uvm_active_passive_enum是一个枚举类型变量,这个枚举变量仅有两个值:UVM_PASSIVE和UVM_ACTIVE。
1、在uvm_agent中,is_active的值默认为UVM_ACTIVE,在这种模式下,是需要实例化driver的
2、输出端口上不需要驱动任何信号,只需要监测信号。在这种情况下,端口上是只需要monitor的,所以driver可以不用实例化。
class my_env extends uvm_env;
my_agent i_agt;//声明i_agt和o_agt
my_agent o_agt;
...
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
i_agt = my_agent::type_id::create("i_agt", this);//在my_env的build_phase中对i_agt和o_agt进行实例化
o_agt = my_agent::type_id::create("o_agt", this);
i_agt.is_active = UVM_ACTIVE;//指定各自的工作模式是active模式还是passive模式
o_agt.is_active = UVM_PASSIVE;
endfunction
`uvm_component_utils(my_env)
endclass
- 上图需要注意的几点:
1、只有uvm_component才能作为树的结点,像my_transaction这种使用uvm_object_utils宏实现的类是不能作为UVM树的结点的
2、在my_env的build_phase中,创建i_agt和o_agt的实例是在build_ phase中;在agent中,创建driver和monitor的实例也是在build_phase中