- 第一章 与UVM的第一次接触
■ SV具有所有面向对象语言的特性:封装、继承和多态;
■ SV还有还有独有的特性:约束(constraint)、功能覆盖率(function coverage);
■ SV提供内存管理机制,用户不用担心内存泄露的问题;
■ SV支持系统函数$system,可以直接调用外部的可执行程序,就像在Linux的shell下直接调用一样;
■ 从UVM的角度来看,方法学是一个库;
■ 无论任何抽象的概念,一个程序员要使用它,唯一的方法是把其用代码实现;
■ UVM中包括sequence机制、factory机制、callback机制、寄存器模型(register model)等;
- 第二章 一个简单的UVM验证平台
2.1 验证平台的组成
■ 验证平台要模拟DUT的各种真实使用情况,即要给DUT施加各种激励,有正常的激励,也有异常的激励,有各种模式的激励,激励的功能是有driver来实现的;
■ scoreboard:根据DUT的输出来判断DUT的行为是否与预期相符合,;
■ monitor:收集DUT的输出并把他们传递给scoreboard,;
■ refm:在验证平台中,实现和DUV一样的功能;
2.2 只有driver的验证平台
2.2.1 最简单的验证平台
■ driver是验证平台最基本的组件,是这个验证平台数据流的源泉;
■ UVM是一个库,所有的东西都是用类(class)来实现,driver、monitor、reference model、scoreboard等组成部分都是类(class);
■ 类(class)中有函数(function)、任务(task),通过这些函数和任务可以完成driver的输出激励功能,完成monitor的检测功能,完成参考模型refm的计算功能,完成scoreboard的比较功能;
■ 类(class)中有成员变量,通过这些成员变量可以控制类的行为,如driver的行为等;
■ 当要实现一个功能时,首先要想到是从UVM的某个类派生出一个新的类,在这个类中实现所期望的功能;
UVM的第一条原则:验证平台中所有的组件应该派生自UVM中的类;
■ UVM验证平台中的driver应该派生自uvm_driver;
eg: class PB_lbit_input_driver #(INPUT_DELAY = 1 , OUTPUT_DELAY = 1) extends uvm_dirver #(PB_lbit_input_item);
...
endclass : PB_1bit_input_driver
■ 每一个派生自uvm_component或其派生类的类在其new函数中,要说明两个参数:name和parent,这是uvm_component类的一大特征;
class my_driver extends uvm_driver;
function new(string name = “my driver”,uvm_component parent = nPBl);
super . new(name,parent);
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task my_dirver::main_phase(uvm phase phase);
...
endtask
■ UVM是由phase来管理验证平台的运行,这些phase统一以xxx_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数;
eg: // function declare
extern virtual function void build_phase( uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
function void PB_1bit_input_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
....
endfunction:build_phase
function void PB_lbit_input_agent::connect_phase(uvm_phase phase);
...
endfunction:connect_phase
■ main_phase是uvm_driver中预先定义好的一个任务,实现一个driver等于实现其main_phase;
■ `uvm_info (“my_driver”,“data is driverd”,“UVM_LOW”)中:
◆ 第一个参数是字符串,用于把打印信息归类;
◆ 第二个参数也是字符串,是具体要打印的信息;
◆ 第三个参数则是冗余级别;
◆ 本节uvm_info宏打印结果如下:
@ 45800000:drv[my_driver] data is drived;
◆ UVM采用树形结构,对于树中任何一个结点,都有一个与其相应的字符串类型的路径索引,路径索引可以通过get_fPBl_name函数来获取:
$display(“the fPBl name of current component is :%s” , get_fPBl_name( ) );
■ 类的定义与类的实例化区别:
◆ 类的定义
class A;
...
endclass
◆ 类的实例化:是指通过new创造出A的一个实例;
A a_inst;
a_inst = new( );
◆ 类的定义就是写一纸条文,然后告知SV的仿真器,验证平台可能用到这样一个类;
类的实例化在于通过new( )来告知SV的仿真器:请创建一个A的实例,仿真器接到new的指令后,就会在内存中划分一块空间, 在划分前,会首先检查是否已经预先定义过这个类,在已经定义过的情况下,按照定义中所指定的“条文”分配空间,并把这块空间的指针返回给a_inst,之后就可以通过a_inst来查看类中各个成员变量,调用成员函数/任务等;
(如果不定义,直接实例化,仿真器会报错;)
■ PB_tb_top.sv的文件编写
`ifndef PB_TB_TOP
`define PB_TB_TOP
//参数定义及模块路径声明
module PB_tb_top( );
pb123_top duv( );
endmodule
`endif // PB_TB_TOP
■ 要正常引用UVM中的库文件,需要在UVM平台中,导入库文件的相关的代码,这是在pb_top.svh文件`include进来的;
pb_top.svh的文件编写
`ifdef PB_TOP
`define PB_TOP
import uvm_pkg::*; //通过import语句将整个uvm_pkg导入验证平台中,只有导入了这个库,编译器在编写SV文件时,才会识别其中的uvm_driver等类名;
`include “uvm_macros.svh” //这是uvm中的一个文件,里面包含了众多的宏定义,只需要包含一次;
`include “vip_axi_pkg.svh”
import vip_axi_pkg::*;
`include “vip_public_pkg.svh”
import vip_public_pkg::*;
`include “vip_dif_pkg.svh”
import vip_dif_pkg::*;
`include “aip_public_pkg.svh::*;
import aip_public_pkg::*;
// pb_define
// reg_model
//scenario
//reg_cfg
pb_base_cfg.sv
pb_reg_cfg.sv
//agent
//refm
//scoreboard
//coverage
//sva
//reg_seq
//vir_seqr and vir_seq
//env_cfg and env
`endif
■ 通过调用$finish( )函数,可结束整个仿真,这是一个Verilog中提供的函数;
2.2.2 加入factory机制
■ 通过引入UVM的factory机制,可以自动创建一个类的实例,并调用其中的函数(function)和任务(task);
■ factory机制的实现被集成在一个宏中:uvm_component_utils,只要定义一个新的类class时使用这个宏,就相当于把这个类class注册到了这张表中;
//这张表是factory功能实现的基础;
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver)
function new(string name = “my_driver” , uvm_component parent = null) ;
super.new(name ,parent);
`uvm_info(“my_driver”,”new is calld” ,UVM_LOW);
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
■ 在driver中加入factory机制后,还需要对top_tb做一些改动,即在initial begin ...end语句中,用run_test()语句,替换掉my_driver的实例化及main_phase的显示调用;
// 一个run_test语句会创建一个my_driver的实例,并且会自动调用my_driver的main_phase;
old: initial begin
my_driver drv;
drv = new(“drv” , null);
drv.main_phase(null);
$finish( );
end
new: initial begin
run_test( “my_driver”); //传递的是一个类名字符串,UVM根据这个字符串类名创建了其所代表的类class的一个实例,并自动调用这个类中的main_phase;
end
■ 根据类名,创建一个类的实例,这是uvm_component_utils宏所带来的效果,只有在类class定义时声明了这个宏,才能使用这个功能,即这个宏起到了注册的作用;(只有经过注册的类class,才能使用这个功能)
■ 所有派生自uvm_component及其派生类的类,都应该使用uvm_component_utils宏注册;
■ 在UVM验证平台中,只要一个类使用uvm_component_utils注册且此类被实例化了,那么这个类的main_phase就会自动被调用;(eg中mark红色的代码,就是类的注册且实例化过程)
eg: class my_driver extends uvm_driver;
`uvm_component_utils(my_driver)
function new(string name = “my_driver” , uvm_component parent = null) ;
super.new(name ,parent);
`uvm_info(“my_driver”,”new is calld” ,UVM_LOW);
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
2.2.3 加入objection机制
■ UVM中通过objection机制来控制验证平台的关闭;
■ 在每个phase中,UVM会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真;如果没有,则马上结束当前phase;
■ 在drop_objection语句之前,必须先调用raise_objection语句,raise_objection和drop_object总是成对出现;
eg: task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info(“my_driver”, “main_phase is called” ,UVM_LOW);
...
phase.drop_objection(this);
endtask
■ raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前,如$display语句是不消耗仿真时间的,这些语句可以放在raise_objection之前,但类似@(posedge top.clk)等语句是要消耗仿真时间的; //否则raise_objection语句是无法起到作用
2.2.4 加入virtual interface
■ 应该尽量杜绝在验证平台中使用绝对路径,避免绝对路径的一个方法是使用宏;
(`define TOP top_tb)
■ 避免绝对路径的另外一种方式是使用interface;
eg: interface my_intf (input clk,input rst_n);
logic[7:0] data;
logic valid;
endinterface
module top_tb( );
my_if input_if(clk,rst_n);
my_if output_if(clk,rst_n); //在module模块中,可以这样声明intf
...
endmodule
■ 在类class中使用virtual interface进行接口的声明;
class my_driver extends uvm_driver;
virtual my_intf vif;
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
...
phase.drop_objection(this);
endtask
endclass
■ UVM通过run_test()语句实例化了一个脱离top_tb层次结构的实例,建立了一个新的层次结构;// 这个新的层次结构是什么样子?
■ 对于脱离了top_tb层次结构,同时又期望在top_tb中对其进行某些操作的实例,UVM引入了config_db机制;在config_db机制中,分给set和get两步操作;
//config_db通常都成对出现,在top_tb中通过set设置virtual interface,而在driver或monitor中通过get函数得到virtual interface;
eg:在top_tb中执行set操作:
module top_tb;
...
initial begin
uvm_config_db #(virtual my_if)::set(null,“uvm_test_top”,“vif”,input_if);
// 由于top_tb不是一个类,无法使用this指针,所以设置set的第一个参数为null,第二个参数使用绝对路径uvm_test_top.xxx;
end
endmodule
在my_driver中,执行get操作:
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);//显示地调用并执行父类中的build_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
■ 注意一下,在systemverilog中,往top_tb中传的是一个实际的物理接口,virtual interface是在program、class中定义的。
■ 当UVM启动之后,会自动执行build_phase,build_phase在new函数之后main_phase之前执行;
■ 在build_phase中主要通过config_db的set和get操作来传递一些数据,以及实例化成员变量等;
■ build_phase是一个函数phase,而main_phase值一个任务phase,build_phase是不消耗仿真时间,build_phase总是在仿真时间($time函数打印出的时间)为0时执行;
■ `uvm_fatal宏只有两个参数,当打印第二个参数所示的信息后,会直接调用Verilog的finish函数来结束仿真;
■ config_db的set和get函数都有四个参数,这两个函数的第三个参数必须完全一致;
具体的实现过程:
在top_tb中通过run_test创建一个my_driver的实例,实例的名字为uvm_test_top,无论传递给run_test的参数是什么,创建的实例的名字都为uvm_test_top;由于set的操作目标是my_driver,所以set函数的第二个参数是uvm_test_top;
uvm_config_db #(virtual my_if)则是一个参数化的类,其参数就是要寄信的类型;
■ 通过config_db可以传递两个不同类型的数据,也可以传递相同类型的不同数据;
2.3 为验证平台加入各个组件
2.3.1 加入transaction
■ 在refm、monitor、scb等验证平台的其它组件之间,信息的传递是基于transaction的;
■ 一般来说,物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,每个包的大小不一样,很少会有协议是以bit或者byte为单位来进行数据交换的;
■ 一笔transaction就是一个包;
class my_transaction extends uvm_sequence_item;
...
`uvm_object_utils(my_transaction)
...
endclass
■ post_randomize是SV中提供的一个函数,当某个类的实例的randomize函数被调用后,post_randomize会紧随其后无条件地被调用;
■ 在transaction定义中:一是my_transaction的基类是uvm_sequence_item,在UVM中,所有的transaction都要从uvm_sequence_item派生,只有从uvm_sequence_item派生的transaction才能使用UVM中强大的sequence机制;
二是,在这里 没有使用uvm_component_utils宏来实现factory机制,二是使用了uvm_object_utils;
■ my_transaction具有生命周期,它在仿真的某一时间产生,经过driver驱动,再经过refm处理,最终由scb比较完成后,其生命周期就结束了;
■ 这种派生自uvm_object或者uvm_object的派生类,uvm_sequence_item的祖先就是uvm_object,具有这种特征的类都要使用uvm_object_utils宏来实现;
eg:
task my_driver::main_phase(uvm_phase phase);
my_transaction tr;
...
for (int i = 0; i < 2 ;i++) begin
tr = new(“tr”);
assert(tr.randomize( ) with {pload.size == 200;});
driver_one_pkt(tr);
end
...
endtask
task my_driver::driver_one_pkt(my_transaction tr);
bit [47:0] tmp_data;
bit [7:0] data_q[$];
// push dmac to data_q
tmp_data = tr . dmac;
for(int i = 0 ; i < 6; i++) begin
data_q.push_back(tmp_data[7:0]); //将tr中的数据压入队列data_q中,这个过程类似于打包成一个byte流的过程;
tmp_data = (tmp_data >> 8);
end
...
while(data_q.size( ) > 0)begin
@(posedge vif .clk);
vif.valid <= 1’b1;
vif.data <= data_q . pop_front();//将data_q中所有的数据弹出并驱动
end
endtask
■ 在main_phase中定义class的method,因此只用调class中的main_phase函数,即可实现class中的相应method;
2.3.2 加入env
在验证平台的哪里对reference model、scoreboard进行实例化?
■ 一些不能进行实例化的地方:
◆ run_test,不行,它只能实例化一个实例;
◆ top_tb,run_test是在top_tb结构层次之外建立的一个新的结构层次,那在top_tb中引用就没有意义了
◆ driver,更不行了,哈哈哈,都不是一个东东。
结论:引入一个容器类,在这个类中实例化所有的driver、monitor、reference model和scoreboard等。在调用run_test时,传递的参数不是再是my_driver,而是这个容器类,即让UVM自动创建这个容器类(uvm_env)的实例;
class my_env entends 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::creat(“drv”,this);//只有使用过factory机制注册过的类才能使用这种方式进行实例化,只有使用这种实例化的实例,才能使用后文要讲述的factory机制中最为强大的重载功能, this指针表示my_env;
endfunction
‘uvm_component_utils(my_env); //注册factory机制
endclass
■ 验证平台中的组件在实例化时都应该使用type_name::type_id::create的方式;
回顾下my_driver的new函数:
function new (string name = “my_driver”, uvm_component parant = null);
super.new(name,parent);
endfunction
- new函数有两个参数,第一个参数是实例的名字,第二个则是parent;
- 由于my_driver在uvm_env中实例化,所以my_driver的父节点(parent)就是my_env;
- 通过parent的形式,UVM建立起了树形的组织结构,在这种树形的组织结构中,由run_test创建的实例是树根(这里是my_env),并且树根的名字是固定的为uvm_test_top;
■ 在UVM的树形结构中,build_phase的执行遵照从树根到树叶的顺序,当把整棵树的build_phase都执行外之后,再执行后面的phase;
2.3.3 加入monitor
■ 验证平台中实现检测DUT行为的组件是monitor;
■ driver负责把transaction级别的数据转变成DUT的端口级别,并驱动给DUT,monitor的行为与其相对,用于收集 DUT的端口数据,并将其转换成transaction交给后续的组件,如refm、scb等处理;
一个monitor的定义如下:
class my_monitor extends uvm_monitor;
virtual my_if vif;
`uvm_component_utils(my_monitor)
function new(string name = “my_monitor” , “uvm_component parent = null );
super.new (name ,parent);
endfucntion
...
task my_monitor::main_phase(uvm_phase phase);
my_transaction tr ;
while(1) begin //monitor需要时刻收集数据,永不停歇,因此用while(1)循环来实现这一目的
tr = new (“tr”) ;
collect_one_pkt(tr);
end
endtask
task my_monitor::collect_one_pkt(my_transaction tr);
...
endtask
■ 由于transaction是由driver产生并输出到DUT的端口上,所以driver可以直接将其交给后面的refm;
■ 在大型项目中,driver根据某一协议发送数据,而monitor根据这种协议收集数据;
■ 在env中实例化monitor之后,要在top_tb中使用config_db将input_if和output_if传递给两个monitor:
2.3.4 封装成agent
■ UVM通常将monitor和driver两者封装在一起,成为一个agent,不同的agent代表了不同的协议;
■ 把driver和monitor封装成agent后,在env中需要实例化agent,而不需要直接实例化driver和monitor了;
■ 只有uvm_component才能作为树的结点,像uvm_object_utils宏实现的类不能作为UVM树的结点;
◆在my_env的build_phase中, 创建i_agt和o_agt的实例是在build_phase中;
◆在agent中,创建driver和monitor的实例也是在build_phase中;
◆build_phase按照从树根到树叶的执行顺序;
◆ UVM要求UVM树最晚在build_phase时段完成;
◆ is_active的默认值是UVM_ACTIVE;
■ UVM只能在build_phase之前完后phase的build工作,在build_phase之后就不要再使用类似于my_agent::type_id::create(“i_agt”,this);来重建一个新的phase了;
◆还应当注意UVM中约定俗成的是在build_phase中完成实例化工作的,仅在build_phase中完成实例化好了;
■ 在JF的用例中,seq_cfg的例化是在new函数中完成的,在build_phase中完成的是configure_sequence和config_db的set操作;
eg:在pb_test_random用例中:
`ifndef PB_TEST_RANDOM
`define PB_TEST_RANDOM
`include “../../../testcase/pb_test_base.sv”
class pb_test_random extends pb_test_base;
pb_1bit_input_seq_cfg mo_alm_crm_seq_cfg;
...
vip_dif_ch_seq_cfg mo_dlpimc_in_seq_cfg[4];
// UVM Factory Register Macro
// public member register
`uvm_component_utils_begin(pb_test_random)
`uvm_field_object (mo_alm_crm_seq_cfg , UVM_ALL_ON)
...
`uvm_field_sarray_object(mo_dlpimc_in_seq_cfg , UVM_ALL_ON)
`uvm_component_utils_end
//methods
extern function new (string name = “pb_test_random” , uvm_component parent = null);
extern virtual function void build_phase (uvm_phase phase);
endclass:pb_test_random
function pb_test_random::new(string name = “pb_test_random” , uvm_component parent = null);
super.new(name.parent);
mo_alm_crm_seq_cfg=pb_1bit_input_seq_cfg::type id :create($sformatf(“mo_alm_crm_seq_cfg”));
foreach(mao_dlpimc_in_seq_cfg[i])
begin mao_dlpimc_in_seq_cfg[i]=vip_dif_ch_seq_cfg::type_id::create($sformatf(“mao_dlpimc_in_seq_cfg[%0d]”,i));
end
endfunction:new}}}}
function void pb_test_random::build_phase(uvm_phase phase);
super.build_phase(phase);
mo_env_cfg.mo_reg_cfg.gen_reg_cfg(mo_scenario);
mo_env_cfg.mo_reg_cfg.gen_base_cfg(mo_scenario);
mo_env_cfg.configure_env(mo_scenario,mo_env_cfg.mo_reg_cfg);
//seq_cfg
mo_alm_crm_seq_cfg.configure_sequence(mui_send_100us_time*73728/10000,1,10000,50000);
uvm_config_db#(pb_1bit_input_seq_cfg)::set(this,“mo_env.mo_alm_crm_agent.mo_seqr”,“mo_seq_cfg”,mo_alm_crm_seq_cfg);
...
foreach(mao_dlpimc_in_seq_cfg[i])
begin
mao_dlpimc_in_seq_cfg[i].configure_sequence(mui_send_100us_time*64,
73728/64,
{VIP_DIF_FDDLTE},
{0},
7372800,
1);
uvm_config_db#(vip_dif_ch_seq_cfg)::set(this, $sfortmaf(“mo_env.mao_dlpimc_in_agent[%0d].mo_seqr”,i),“mo_seq_cfg”,mao_dlpimc_in_seq_cfg[i]);
end
// set default sequence
uvm_config_db # (uvm_object_wrapper)::set(this , “mo_env.mo_vir_seqr.main_phase”,“default_sequence”,ul_ch_vir_seq::type_id::get( ));
//set scenario,used for vir_seq
uvm_config_db #(pb_scenario)::set(this , “*”,“mo_scenario”,mo_scenario);
agent_cfg;
scoreboard_cfg;
endfunction:build_phase
`endif
■ super.phase的内容://详见5.1.5章节
◆ build_phase,自动获取通过config_db::set 设置的参数。如果要关闭此功能,可以不用super.build_phase;
◆ 扩展自uvm_component的类,除了build_phase 外,其他phase几乎没做任何相关的事,其他phase完全可以不必加上super.xxx_phase 语句;
◆如果是扩展自用户自定义的类,如test_base类,且在其某个phase,如connect_phase中定义了一些重要内容,那么在具体测试用例的connect_phase中就不应该省略super.connect_phase;
2.3.5 加入reference model
■ 在UVM中,通常使用TLM(Transaction Level Modeling)实现component之间transaction级别的通信;
■ 要实现通信,需要考虑:第一,数据是如何发送的?第二,数据是如何接收的?
在UVM的transaction级别的通信中,数据的发送方式有多种方式,其中一种是使用uvm_analysis_port;
class my_model extends uvm_component;
uvm_blocking_get_port #(my_transaction) port; //获取my_transaction数据
uvm_analysis_port #(my_transaction) ap; //发送ap数据
...
endclass
◆ uvm_analysis_port是一个参数化的类,其参数就是这个analysis_port需要传递的数据的类型,在本节中是my_transaction;
◆ 发送ap数据是这样做的:
//定义一个参数化的类,并声明ap口
uvm_analysis_port #(my_transaction) ap;
//声明后需要实例化
ap = new(“ap”,this);
//收集完一个transaction之后,将其写入ap
ap.wirte(tr); // write是uvm_analysis_port的一个内建函数;
uvm_blocking_get_port是一种transaction级别数据的接收方式,也是一个参数化的类,其参数是要在其中传递的transaction的类型;
◆ 接收数据是这样做的:
uvm_blocking_get_port #(my_transaction),port;
port = new(“port”,this);
port.get(tr); //得到从i_agt的monitor中发出的transaction;
之后,在env的connect_phase中将fifo分别与my_monitor中的analysis_port和my_model中的blocking_get_port相连即可。
■ connect_phase在build_phase执行完之后马上执行,与build_phase不同,执行顺序是从树叶到树根---先执行driver和monitor的connect_phase,再执行agent的connect_phase,最后执行env的connect_phase; //当UVM启动之后,会自动执行build_phase,build_phase在new函数之后main_phase之前执行;
■ analysis_port是非阻塞性质的,ap.write函数调用完成后,马上返回,不会等待数据被接收;
2.3.6 加入scoreboard
■ 由于DUT处理数据需要延时,而reference model是基于高级语言的处理,一般不需要延时;
■ my_scoreboard要比较的数据一来自于reference model,另外一个来自于o_agt的monitor,前者通过exp_port获取,后者通过act_port来获取;
2.3.7 加入field_automatic机制
■ 对于不同的transaction来说,my_print()函数、my_copy( )函数、my_compare()函数都是需要逐字段地对transaction进行某些操作;
■ 通过UVM中的field_automatic机制,可以自动实现以上三个函数;
class my_transaction extends uvm_sequence_item;
rand bit [47:0] dmac;
rand bit [47:0] smac;
rand bit [15:0] ether_type;
rand byte pload[ ];
rand bit[31:0] crc;
...
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(dmac,UVM_ALL_ON)
`uvm_field_int(smac, UVM_ALL_ON)
...
`uvm_object_utils_end
...
endclass
■ 使用uvm_object_utils_begin和uvm_object_utils_end来实现my_transaction(类名)的factory注册,在这两个宏中间,使用uvm_field宏注册所有字段(声明的成员变量);
◆ 当使用上述宏注册之后,可以直接调用copy、compare、print函数,而无需自己定义,极大地地简化了验证平台的搭建,提高了效率;
2.4 UVM的终极大作:sequence
2.4.1 在验证平台中加入sequencer
■ sequence机制用于产生激励,是UVM最重要的机制之一;
■ driver只负责驱动transaction,而不负责产生transaction;
■ sequence机制有两大组成部分,一是sequence,二是sequencer;
■ sequencer产生transaction,而driver负责接收transaction;
class my_sequencer extends uvm_sequencer #(my_transaction); //是一个参数化的类
class my_agent extends uvm_agent;
my_sequencer sqr;
my_driver drv;
my_monitor mon;
`uvm_analysis_port #(my_transaction) ap;
...
endclass
...
2.4.2 sequence机制
■ sequence不属于验证平台的任何一部分,在sequencer的帮助下,sequence产生出的transaction才能最终送给driver;同样,sequencer只有在sequence出现的情况下才能体现其价值;
■ sequence就像一个弹夹,里面的子弹是transaction,而sequencer是一把枪;
■ sequencer是一个uvm_component,而sequence是一个uvm_object,一个sequence应该使用uvm_object_utils宏注册到factory中;
■ 与my_transaction一样,sequence也有其声明周期,其生命周期比my_transaction要更长一些,其内的transaction全部发送完毕,其声明周期也就结束了;
class my_sequence extends uvm_sequence #(my_transaction) ;
my_transaction m_trans; //实例
function new(string name = “my_sequence”);
super.new(name);
endfunction
virtual task body( ); //每一个sequence都有一个body任务,当一个sequence启动之后,会自动执行body中的代码;
repeat (10) begin
`uvm_do(m_trans)
end
#1000;
endtask
`uvm_object_utils(my_sequence)
endclass
■ uvm_do宏的作用:
- 创建一个my_transaction的实例m_trans;
- 将其随机化;
- 最终将其送给sequencer;
■ 如果不使用uvm_do宏,也可以直接使用start_item与finish_item的方式产生transaction,6.3.4节讲述;
■ 一个sequence在向sequencer发送transaction前,需要向sequencer发送一个请求,sequencer把这个请求放在一个仲裁队列中;作为sequencer,它需做两件事情:第一,检测仲裁队列里是否有某个sequence发送transaction的请求;第二,检测driver是否申请transaction;
- 如果仲裁队列例有发送请求,但是driver没有申请transaction,那么sequence将会一直处于等待driver的状态,直到driver申请新的transaction。此时,sequencer同意sequence的发送请求,sequence在得到sequencer的批准后,产生出一个transaction并交给sequencer,后者把这个transaction交给driver;
- 如果仲裁队列中没有发送请求,但是driver向sequencer申请新的transaction,那么sequence将会处于等待sequence的状态,一直到有sequence递交发送请求,sequencer马上同意这个请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction;
- 如果仲裁队列中有发送请求,同时driver也在向sequencer申请新的transaction,那么将会同意发送请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction;
■ 在uvm_driver中有成员变量seq_item_port,在uvm_sequencer中有成员变量seq_item_export,在agent中,使用connect函数把两者联系在一起:
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(is_active == UVM_ACTIVE)begin
drv.seq_item_port.connect(sqr.seq_item_export); //当两者连接好之后,就可以在driver中通过get_next_item任务向sequencer申请新的transaction;
end
ap = mon .ap; //monitor的analysis_port与agent的analysis_port相连
endfunction
eg: task my_driver::main_phase(uvm_phase phase);
vif.data <= 8`b0;
vif.valid <= 1`b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1) begin
seq_item_port.get_next_item(req); //通过get_next_item任务来得到一个新的req
driver_one_pkt(req); //驱动这个req
seq_item_port.item_done( ); //驱动完成,调用item_done告知sequencer
end
endtask
◆ 如上代码最显著的特征是使用了while(1)循环,因为driver只负责驱动transaction,而不负责产生,只要有transaction就驱动,所有必须做成一个无线循环的形式;
■ 在sequence中,向sequencer发送transaction的是uvm_do宏。uvm_do宏产生了一个transaction并交给sequencer,driver取走这个transaction后,uvm_do并不会立刻返回执行下一次uvm_do宏,直到driver返回item_done信号。此时uvm_do宏才算是执行完毕,返回并开始执行下一个uvm_do,并产生新的transaction。
■sequence如何向sequencer中送出transaction?只需要在某个component(如my_sequencer、my_env)的main_phase中启动这个sequence即可启动sequence向sequencer中送出transaction。
eg1:在env中启动sequence
task my_env::main_phase(uvm_phase phase);
my_sequence seq; //创建一个my_sequence的实例seq
phase.raise_objection(this);
seq = my_sequence::type_id::create(“seq”);
seq.start(i_agt.sqr); //调用start任务,参数是一个sequencer指针,指明sequence将产生的transaction交给哪个sequencer
phase.drop_objection(this);
endtask
◆ 在UVM中, objection一般伴随着sequence,通常只在sequence出现的地方才提起和撤销objection;
eg2:在sequencer中启动sequence
task my_sequencer::main_phase(uvm_phase phase);
my_sequence seq ;
phase.raise_objection(this);
seq = my_sequence::type_id::create(“seq”);
seq.start (this); //参数变成this
phase.drop_objection(this);
endtask
■ driver中申请:seq_item_port.get_next_item(req),除get_next_item之外,还可以选择使用try_next_item;
◆ get_next_item是阻塞的,它会一直等到新的transaction才会返回;
◆try_next_item则是非阻塞的,它尝试着询问sequencer是否有新的transaction,如果有,则得到此transaction,否则就直接返回;
◆ try_next_item的行为更接近真实driver的行为:当有数据时,就驱动数据,否则总线将一直处于空闲状态;
2.4.3 default_sequence的使用
■ 在实际应用中,更多使用default_sequence的方式启动sequence;
■ 使用default_sequence的方法:只需在某个component(如my_env)的build_phase中设置如下代码即可:
eg:my_env.sv
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
...
uvm_config_db #(uvm_object_wrapper)::set(this,“i_agt.sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ));
//此代码是在my_env中,而my_env本身已经是uvm_test_top,且第一个参数被设置为了this,所以第二个参数中就不需要uvm_test_top;
endfunction
◆ 这是除了在top_tb中通过config_db设置virtual interface后再一次用到config_db的功能;
◆ 第二个路径参数,出现了main_phase,这是UVM在设置default_sequence时的要求,必须指定是那个phase,从而使sequencer知道在那个phase启动这个sequence;
◆ 第三个和第四个参数,以及uvm_config_db#(uvm_object)是UVM的规定;
■ 除了在my_env的build_phase中设置default_sequence外,还可以在top_tb中设置:
eg:
module top_tb;
...
initial begin
uvm_config_db # (uvm_object_wrapper) :: set (null,“uvm_test_top.i_agt.sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ));
end
endmodule
■ 除了在my_env的build_phase中、top_tb中设置default_sequence外,还可以在其他的component里设置,如my_agent的build_phase里设置:
eg:
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
...
uvm_config_db#(uvm_object_wrapper)::set(this,“sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ));
endfunction
◆ 只需正确地设置set的第二个参数即可;
■ 无需在sequencer中手写一些get相关的代码,UVM已经做好了这些;
■ 在uvm_phase这个基类中,有个变量名为starting_phase,类型是uvm_phase,sequencer在启动default_sequence时,会自动做如下相关操作;
task my_sequencer::main_phase(uvm_phase phase);
...
seq.starting_phase == phase;
seq.start(this);
...
endtask
因此,可以在sequence中使用starting_phase进行提起和撤销objection:
class my_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
...
virtual task body( );
if (starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
`uvm_do (m_trans)
end
#1000;
if (starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(my_sequence)
endclass
从而objection完全与sequence关联在一起,在其它任何地方都不必再设置objection;
2.5 建造测试用例
2.5.1 加入base_test
■ 通常来说,树根是一个基于uvm_test派生的类;
■ 真正的测试用例都是基于base_test派生的一个类;
eg:
class base_test extends uvm_test;
...
function void base_test::build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create(“env”,this);
uvm_config_db#(uvm_object_wrapper)::set(this,“env.i_agt.sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ));
endfunction
...
endclass
◆ 在build_phase中实例化my_env,并设置sequencer的default_sequence,这里设置了default_sequence,其它地方就不需要再设置了;
◆ report_phase是UVM内建的一个phase,它在main_phase结束之后执行;
■ 通常在base_test中做如下的事情:第一,设置整个验证平台的超时退出时间;第二,通过config_db设置验证平台中某些参数的值;
2.5.2 UVM中测试用例的启动
■ 要测试一个DUT是否按照预期工作,需要对其施加不同的激励,这些激励被称为测试向量或pattern;一种激励作为一个测试用例,不同的激励就是不同的测试用例;
■ uvm_do_with宏,用于在随机化时提供某些字段的约束;
■ UVM提供对不加参数的run_test的支持,在这种情况下,UVM会利用UVM_TEST_NAME从命令行中寻找测试用例的名字,创建它的实例并运行;(+UVM_TEST_NAME = my_case1)
eg:top_tb.sv
initial begin
run_test( );//不加用例名等参数
end
■ 测试用例的启动及执行流程:
module top_tb
全局的run_test
启动验证平台
启动testcase
依次执行build_phase
顺序执行UVM树各结点的connect_phase、main_phase等
所有phase执行完毕,结束仿真