本节基于DUT完成UVM验证环境的基本框架搭建,实现对UVM理论知识点进行巩固练习,具体内容包括:如何创建激励、如何建立sequencer、如何连接sequencer和driver,如何集成agent、如何构建env等。
正式开始之前让我们再来回顾下搭建验证环境的过程:首先进行数据建模sequence_item(transaction),建好之后再将其放置到sequence,之后通过agent将sequence嵌入到sequencer中,然后将sequencer与driver连接起来,之后在env里面去集成agent,最后是编写testcase顶层。等到验证环境大体搭建完毕后,编写Makefile文件。
文章目录
编写程序注意点:
- 1、function new 这是固定的格式,没有任何返回值关键字
- 本篇的uvm_object(如:sequence_item或者sequence)的new写了缺省的name值,比如:
function new(string name = "packet_sequence");
- 本篇的uvm_component的new没写缺省的name和parent,但是写了也不会错,比如:
function new(string name, uvm_component parent);
- 本篇的uvm_object(如:sequence_item或者sequence)的new写了缺省的name值,比如:
- 2、除了new之外的其他function和task尽量都用virtual声明;function基本都是
virtual function void xxx
,task基本都是virtual task xxx
- 3、body没有super,其他的函数,尽量都继承一下,虽然对于uvm基类来说,没什么区别,养成习惯,以后对于派生类会很有用!【注意sequence的body无super继承、main_phase如果不想继承也可以】
- 4、sformatf是系统函数,别忘了加
$
。 - 5、sequence的sequence参数要和sequence_item class名字一样,此处是
packet
;sequence_item,依次需要通过packet_sequence
、packet_sequencer
、driver
,所以它们三个的类参数都需要定义成packet
- 6、env启动sequence时,需要定义并实例化sequence。可以在类中像agent一样在最上面定义句柄,在build_phase阶段实例化;更常见的是在main_phase阶段定义并实例化,注意,一定要放在main_phase第一句定义句柄(实例化与否无所谓,但是定义一定在开头)?(这个是为什么,有点像fork…join,我其实有点不理解)
1.1、数据建模(packet.sv)
注:sequence_item 别忘了我们的比喻,相当于手枪的“子弹”。
- 在注册数据包后,声明随机变量、同时可添加约束,也可对进行变量权限管理。
`ifndef PACKET_SV
`define PACKET_SV
class packet extends uvm_sequence_item;
//1.声明随机变量; $是队列
rand bit [3:0] sa;
rand bit [3:0] da;
rand bit [7:0] payload[$];
//2.注册; sequence_item 属于 object ,所以使用`uvm_object_utils注册
`uvm_object_utils_begin(packet) //UVM的域自动化机制(field automatic),可以直接调用预定义的print/copy/compare/... 详细见《UVM实战》P71
`uvm_field_int(sa, UVM_ALL_ON) //UVM_ALL_ON 表示 print/copy/compare/... 这些内置函数可以直接通过 packet 的实例化对象调用,本例是在driver中seq.print,将域自动化中的变量全部打印
`uvm_field_int(da, UVM_ALL_ON) //如果域自动化中的变量不想做某个操作,比如打印,可以这样写uvm_field_int(da, UVM_ALL_ON | UVM_NOPRINT)
`uvm_field_queue_int(payload, UVM_ALL_ON) //还需要注意的一点是,不同变量类型在使用域自动化机制注册时,对应调用的注册宏也会不同。详见《UVM实战》P69
`uvm_object_utils_end //常见的整数类型 uvm_field_int; 队列整数 uvm_field_queue_int; 动态数组整数 uvm_field_array_int; 静态数组整数 uvm_field_sarray_int
//3.添加约束
constraint c_payload{
payload.size() inside {[0:10]};
}
//4.new()构造函数
function new(string name = "packet");
super.new(name);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH) //信息归类、打印信息、冗余级别
endfunction
//$sformatf("%m") 解释
//它是一个格式化字符串函数,其中 %m 表示当前文件名和行号。
//因此,这段代码将会输出一个形如 "TRACE [文件名:行号]" 格式的信息。
//UVM_HIGH是信息的冗余度等级,主要用来过滤信息。详细介绍见《UVM实战》P76 UVM_NONE < UVM_LOW < UVM_MIDDLE < UVM_HIGH < UVM_FULL < UVM_DEBUG
//一般会在程序中每个需要打印的语句中都加入冗余度等级,Makefile中也会设置一个全局的冗余度等级
//当程序中的冗余度等级小于等于全局冗余度时,比如全局冗余度为UVM_MIDDLE,那么程序中的打印冗余度为UVM_NONE、UVM_LOW、UVM_MIDDLE的信息都可以显示,其他的会被过滤。
endclass
`endif
1.2、生产数据(packet_sequence.sv)
注:sequence 别忘了我们的比喻,相当于手枪的“弹夹”。
- 首先将上一步创建的数据包(packet)通过参数化的方式引入到sequence中,接着进行注册(注意sequence_item和sequence都是object注册),然后建立构造函数new,编写任务body(),设置发包数目,用以生产数据。【生产数据可以调用uvm_do()系列宏,也可以通过start_item和finish_item来实现(其实uvm_do本质上就是调用start_item和finish_item,建议直接用uvm_do即可)】
`ifndef PACKET_SEQUENCE_SV
`define PACKET_SEQUENCE_SV
class packet_sequence extends uvm_sequence#(packet); //1.通过参数化继承的方式把packet放进来
`uvm_object_utils(packet_sequence) //2.注册packet_sequence类,注意是object类型注册
function new(string name = "packet_sequence"); //3.new()构造函数,注意new函数没有返回值void,name的缺省值可以写,也可以不写
super.new(name);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
endfunction
virtual task body(); //4. 编写body函数,注意body不需要super继承
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
repeat(10) begin //5. 定义发包数目,调用uvm_do发送packet类型的req
`uvm_do(req); //req是packet_sequence父类uvm_sequence中定义并例化好的transaction对象(uvm中叫sequence_item),类型是我们上面传进去的packet类型
end
endtask
endclass
`endif
//上面直接调用uvm_do的方式是显式启动sequence,还需要在顶层模块,如env/testcase的main_phase中,调用start()函数启动sequence,示例如下:(本例子主要使用的就是这种方式)
/*
//env启动代码如下:
task my_env::main_phase(uvm_phase phase);
my_sequence seq = my_sequence::type_id::create("seq");
phase.raise_objection(this);
seq.start(i_agt.sqr);
phase.drop_objection(this);
endtask
*/
//除了显式启动,还有隐式启动的方式。是在sequence中除了调用uvm_do,还加上starting_phase,一个示例的body函数如下。启动是在顶层模块,如env的build_phase中,使用config_db调用default_sequence隐式启动sequence
/*
//sequence 的body函数如下
virtual task body(); //4. 编写body函数,注意body不需要super继承
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
if(starting_phase != null) //在env中使用default_sequence隐式启动seq时,采用starting_phase变量
starting_phase.raise_objection(this,"starting"); //挂起/落下objection,下文采用显式start调用,使用了phase变量
repeat(10) begin //5. 定义发包数目,调用uvm_do发送packet类型的req
`uvm_do(req); //req是packet_sequence父类uvm_sequence中定义并例化好的transaction对象(uvm中叫sequence_item),类型是我们上面传进去的packet类型
end
if(starting_phase != null)
starting_phase.drop_objection(this,"done"); //落下objection
endtask
//env启动代码如下
uvm_config_db#(uvm_object_wrapper)::set(this, "i_agt.sqr.main_phase", "default_sequence", my_sequence::type_id::get());
*/
知识点:
- 1、sequence使用参数化继承时,可以直接使用基类
uvm_sequence
中包含的req
,如下图:
-
2、sequence的启动方式有两种,分为显式启动和隐式启动,详细参考这里:https://blog.csdn.net/ReCclay/article/details/123026169
-
3、生产数据(实例化transaction并发送)的方式有两种,一种是直接调用uvm_do(最常用),另一种是使用start_item,finish_item。【uvm_do 本质就是调用 start_item,finish_item,uvm_do具体做了哪些事,详细可参考:https://blog.csdn.net/ReCclay/article/details/126722857】
1.3、驱动数据(driver.sv)
- 注册后,在run_phase阶段采用TLM端口
seq_item_port
(源码内置)接收sequencer发来的数据,并将其按照协议时序处理。
`ifndef DRIVER_SV
`define DRIVER_SV
class driver extends uvm_driver#(packet);
`uvm_component_utils(driver) //1.注册
function new(string name, uvm_component parent); //2.new()构造函数
super.new(name, parent);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction
virtual task main_phase(uvm_phase phase); //3.按照协议处理数据
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
forever begin
seq_item_port.get_next_item(req); //通过TLM端口申请数据
//req.print(); //数据按时序协议处理(如AHB协议),这里仅仅作为示例,打印了tr
seq_item_port.item_done(); //数据处理完毕,通知driver开始下一次数据传输
end
endtask
endclass
`endif
1.4、packet_sequencer.sv
`ifndef PACKET_SEQUENCER_SV
`define PACKET_SEQUENCER_SV
class packet_sequencer extends uvm_sequencer#(packet); //别忘了传入 sequence_item 类
`uvm_component_utils(packet_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction
endclass
`endif
1.5、代理(input_agent.sv)
- agent组件中包含了子组件,注册后应该先声明子组件,并在build_phase阶段实例化子组件,在connect_phase阶段建立子组件之间的连接。
`ifndef INPUT_AGENT_SV
`define INPUT_AGENT_SV
//typedef uvm_sequencer#(packet) packet_sequencer; //如果不想再单独定义sequencer,可以直接这样参数化sequencer类
class input_agent extends uvm_agent;
`uvm_component_utils(input_agent) //1.注册,声明子组件
packet_sequencer seqr;
driver drv;
function new(string name, uvm_component parent); //2.new()构造函数,确定父子关系
super.new(name, parent);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
endfunction
virtual function void build_phase(uvm_phase phase); //3.在build_phase阶段实例化子组件
super.build_phase(phase);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
seqr = packet_sequencer::type_id::create("seqr", this); //实例化子组件
drv = driver::type_id::create("drv", this);
endfunction
virtual function void connect_phase(uvm_phase phase); //4.在connect_phase阶段建立seqr与drv之间的连接
super.connect_phase(phase);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
drv.seq_item_port.connect(seqr.seq_item_export); //建立drv与seqr之间的连接
endfunction
endclass
`endif
1.6、环境层(router_env.sv)
- 在env中实例化agent子组件后,在main_phase阶段调用start()方法显式启动sequence,使用phase变量挂起和落下objection,控制发包。
`ifndef ROUTER_ENV_SV
`define ROUTER_ENV_SV
class router_env extends uvm_env;
`uvm_component_utils(router_env) //1.注册,声明子组件
input_agent i_agt;
function new(string name, uvm_component parent); //2.new()构造函数,确定父子关系
super.new(name, parent);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
endfunction
virtual function void build_phase(uvm_phase phase); //3.在build_phase阶段实例化子组件
super.build_phase(phase);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
i_agt = input_agent::type_id::create("i_agt", this); //实例化子组件
endfunction
virtual task main_phase(uvm_phase phase);
packet_sequence seq = packet_sequence::type_id::create("seq"); //注意,seqeunce是object,但是用的是type_id注册,并且无第二个参数
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
phase.raise_objection(this);
seq.start(i_agt.seqr); //显式调用start,启动seq发数
phase.drop_objection(this);
endtask
endclass
`endif
1.7、测试用例(test_base.sv)
- 实例化子组件env,打印拓扑结构;
`ifndef TEST_BASE_SV
`define TEST_BASE_SV
class test_base extends uvm_test;
`uvm_component_utils(test_base) //1.注册,声明子组件
router_env env;
function new(string name, uvm_component parent); //2.new()构造函数,确定父子关系
super.new(name, parent);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
endfunction
virtual function void build_phase(uvm_phase phase); //3.在build_phase阶段实例化子组件
super.build_phase(phase);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
env = router_env::type_id::create("env", this);
endfunction
virtual function void start_of_simulation_phase(uvm_phase phase); //4.打印拓扑结构
super.start_of_simulation_phase(phase);
`uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
//uvm_top.print_topology(); //打印uvm的拓扑结构
//factory.print(); //打印覆盖类型
endfunction
endclass
`endif
1.8、主代码,启动UVM(test.sv)
- 主程序代码,启动UVM平台,运行run_test()方法,在命令行中根据
+UVM_TESTNAME
指定的测试用例,实例化对象uvm_test_top
,然后开始自动自上而下执行各个phase,先build_phase,接着顺序执行其余phase,直到所有objection落下后结束仿真。
program automatic test;
import uvm_pkg::*;
`include "uvm_macros.svh"
initial begin
run_test();
end
endprogram
1.9、package包文件
`ifndef ROUTER_PKG_SV
`define ROUTER_PKG_SV
package router_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "packet.sv"
`include "packet_sequence.sv"
`include "driver.sv"
`include "packet_sequencer.sv"
`include "input_agent.sv"
`include "router_env.sv"
`include "test_base.sv"
endpackage
`endif
1.10、make编译(Makefile)
TB_TOP = ./test.sv
test = test_base
VFILES = ./router_pkg.sv
verbosity = UVM_HIGH
uvm_ver = uvm-1.1
seed = 1
defines = UVM_NO_DEPRECATED+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR
SOLVER = 2
all: compile run
compile:
vcs -full64 -sverilog -ntb_opts ${uvm_ver} -timescale=1ns/1ns -l comp.log -debug_acc+all +vcs+vcdpluson $(VFILES) ${TB_TOP} +define+${defines}
run:
./simv -l simv.log +ntb_random_seed=${seed} +UVM_TESTNAME=${test} +ntb_solver_mode=${SOLVER} +UVM_VERBOSITY=${verbosity}
clean:
rm -rf simv* csrc* *.tmp *.vpd *.key *.log *.h .vcs* DVE*
1.11、打印结果分析
1.11.1、phase运行先后分析
直接通过make命令编译,打印结果如下:
从上图可以大致看出各个phase的运行先后(不管new,new是哪里例化哪里调用!)
1.11.2、打印transaction
取消代码中driver.sv
的req.print();
注释,其打印信息如下:
上图可以看到在packet中(继承自sequence_item)除了我们自定义的sa/da/payload,还有一些分类的成员,如
begin_time
、depth
、parent sequence(name)
、parent sequence (full name)
、sequencer
成员信息。
1.11.3、打印拓扑结构
uvm_top.print_topology();
:打印拓扑结构,对应的部分如下:
1.11.4、打印工厂机制覆盖类型
factory.print();
:打印工厂机制覆盖类型,对应的部分如下:
1.12、小结
整个UVM验证环境的搭建,是先编写完成底层组件的代码,然后向上一级一级地编写上层组件代码。对于验证环境的搭建,只有自己动手做了,才能对语法有更透彻的理解。