文章目录
1.参考
1.验证思路
1.V Verification
- 例化1个深度为8,位宽为8的异步FIFO;
- 读时钟是写时钟的2倍,即读快写慢
- 先对FIFO进行写操作,直到其写满,写入的数据为随机数据
- 然后对FIFO进行读操作,直到其读空 然后对FIFO写入4个随机数据后,同时对其进行读写操作
2.UVM Verification:
- top_tb.sv 是整个验证平台的顶层模块(测试用具)。在它里面连接了待测设计和验证平台。
- my_if.sv 定义了一个接口,用来连接待测设计和验证组件。
- my_transaction.sv 定义了在验证平台中传递信息的事务。它里面包含一些变量。
- my_driver.sv 发送激励。
- in_moniotor.sv 在信号输入端口监测。
- out_monitor.sv 在信号输出端口监测。
- my_sequencer.sv 是 UVM 搭建的验证平台中不可或缺的部件,负责中转由 sequence 传来的transaction。
- i_agt.sv 是一个容器类,里面实例化了 my_driver、my_sequencer 和 in_monitor。
- o_agt.sv 也是一个容器类,里面实例化了 out_monitor。
- my_model 是参考模型,由于 fifo 实现的功能较为简单,此处的参考模型仅仅是把输入的数据复制一遍然后输出。
- my_scoreboard.sv 是计分板,用于比较待测硬件的输出和参考模型的输出是否一致。
- my_env.sv 是一个容器,它里面实例化了 i_agt、o_agt、my_model、my_scoreboard 等部件。
- base_test.sv 它里面实例化 my_env,同时也会规定信息打印的规则。
- my_case0 和 my_case1 是两个测试用例。my_case0 可以运行成功,my_case1 运行不成功,运行不成功的原因是 uvm-1.2 版本不支持 default_sequence 的使用或者是 uvm-1.2 版本支持,但是我还不会用。
- fifo_rst_mon.sv 和 fifo_chk_rst.sv,前者用来检测复位信号,后者用来检测复位后寄存器的复位值是否正确。由于这个是我第一次用 UVM 搭建的验证平台,所以许多地方还不成熟,比如 my_model 仅仅是为了验证平台的完整而添加的,实际上并不需要。
3.http://t.csdn.cn/hfKe4


2.实践
1.搭建环境组件
component大致结构
class A extends uvm_ ; //类定义
`uvm_component_utils( A ) //宏注册
function new (string name=" A ",uvm_component parent =null); //构建函数
super.new(name,parent);
endfunction
//例化组件abc a1b1c1
//component的phase机制
extern virtual function void build_phase( )
extern virtual function void connect_phase( )
endclass
//————————————————————————————————————————
function void A::build_phase( )
super.build_phase( );
a1=a::type_id::create("a1",this); //创建组件abc对象
endfunction
//————————————————————————————————————————
//在agent/env组件中用到connect_phase
function void A::connect_phase( )
super.connect_phase( );
.connect();
endfunction
2.根据白皮书思路更新组件
1.只有driver的验证平台(task main)
2.对driver实例化,构建顶层验证平台top_tb
3.给driver加入factory、objection机制(后期objection迁移至sqr)
4.加入virtual interface
定义interface ,目的连接dut和driver,dut-interface-driver
在顶层模块top_tb 例化interface(fifo_if1)和dut,用(if.)实现连接
driver作为一个类不能例化interface,所以使用vif
virtual interface fifo_if fifo_if2;
利用config_db机制连接top_tb中的if和driver中的vif
在top_tb里set
initial begin
uvm_config_db#(virtual fifo_if)::set(null,"uvm_test_top.env.i_agent1.dri","vif",fifo_if1);
end
//向"uvm_test_top.env.i_agentt1.dri"的"vif"变量传递一个virtual fifo_if类型的数据,数据为fifo_if1
//树根固定是uvm_test_top
在driver的build_phase里get,未成功完成config报错fatal
if(!uvm_config_db#(virtual fifo_if)::get(this,"","vif",fifo_if2))
`uvm_fatal("my_driver","virtual interface must be set for vif!!!")
//路径为this
5.加入transaction,object类,自动化域机制
object类大致结构
class B extends uvm_ ; //类定义
`uvm_object_utils_begin( B ) //宏注册
`uvm_field_int(x,UVM_ALL_ON) //自动化域机制
...
`uvm_object_utils_end
function new (string name=" B " ); //构建函数
super.new(name);
endfunction
一个transaction就是一个包
物理协议中的数据交换以帧/包为单位,一个帧/包里要定义好各项参数
以太网协议,每个包的大小至少是64byte,包中包含了源地址,目的地址,包的类型,整个包的crc校验数据等
class my_transaction extends uvm_sequence_item;
rand bit[47 : 0] dmac; //48bit的以太网目的地址
rand bit[47 : 0] smac; //以太网源地址
rand bit[15 : 0] ether_type; //以太网类型
rand byte pload[]; //携带的数据大小
rand bit[31 : 0] crc; //前面所有数据的校验值
constraint pload_cons { //携带的数据大小被限制在1-1500byte
pload.size >= 1;
pload.size <= 1500;
}
function bit[31 : 0] calc_crc(); //crc的计算方法,网上可查
return 32'h0;
endfunction
function void post_randomize();
crc = calc_crc;
endfunction
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(dmac,UVM_ALL_ON)
`uvm_field_int(smac,UVM_ALL_ON)
`uvm_field_int(ether_type,UVM_ALL_ON)
`uvm_field_array_int(pload,UVM_ALL_ON)
`uvm_field_int(crc,UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
6.在driver中实现对transaction的驱动
task main_phase里调用task drive_one_pkt
task drive_one_pkt(fifo_transaction tr)里例化transaction
7.加入env
8.加入mon
输入monitor检测wrdata,输出monitor检测rddata。考虑到检测端口不一致,写了两个monitor
与driver行为相对,driver负责把transaction的数据转变成dut数据,monitor用于收集dut数据并转换成transaction交到后续组件处理
monitor也需要vif
需要时刻收集数据,永不停歇,所以main_phase使用while(1)
9.封装成agent
不同agent代表不同的协议
//枚举类型变量 is_active 有两个值
typedef enum bit{UVM_PASSIVE=0,UVM_ACTIVE=1} uvm_active_passive_enum;
uvm_active_passive_enum is_active=UVM_ACTIVE; //uvm_agent中默认值,需要例化dri、sqr
uvm_active_passive_enum is_active=UVM_PASSIVE; //只需要例化mon
在上一层env中的build_phase中指定agent.is_active值
10.加入ref model
//fifo仅仅是复制一遍transaction
在env中例化ref model
11.transaction从agent传到ref model,再传给scoreboard
组件间的transaction传输采用TLM,TLM包括了ap的write和port的get/put
异步fifo中通信的组件包括了 in_mon→ref model→scoreboard←out_mon
在这四个组件中例化TLM接口
在上层env中例化fifo后连接四个组件的端口
实现in_mon→【write】(fifo)【get】→ref model→【write】(fifo)【get】→scoreboard←【get】(fifo)【write】←out_mon
(1)in_mon中
声明ap接口和例化,调用ap的广播功能write
uvm_analysis_port #(fifo_transaction) ap; //声明ap接口
function void in_monitor::build_phase(uvm_phase phase); //在build中例化ap接口
ap = new("ap",this);
task in_monitor::main_phase(uvm_phase phase); //在main中收集完transaction调用写入ap接口
collect_one_pkt(tr);
ap.write(tr);
在i_agent中例化ap端口,与in_mon的ap相连
uvm_analysis_port #(fifo_transaction) ap; //声明ap接口
//connect_phase中
ap = mon.ap;
(2)ref model中
面向in_mon 使用uvm_blocking_get_port,声明例化,调用get功能
面向scoreboard 声明ap接口和例化,调用ap的广播功能write
uvm_blocking_get_port#(fifo_transaction) port;
uvm_analysis_port#(fifo_transaction) ap;
function void fifo_refmodel::build_phase(uvm_phase phase);
port = new("port",this);
ap = new("ap",this);
task fifo_refmodel::main_phase(uvm_phase phase);
port.get(tr);
ap.write(new_tr);
(3)env中
定义fifo将两个端口相连,声明例化相连
//声明fifo
uvm_tlm_analysis_fifo#(fifo_transaction) iagt_refmodl_fifo;
uvm_tlm_analysis_fifo#(fifo_transaction) refmodl_scb_fifo;
uvm_tlm_analysis_fifo#(fifo_transaction) scb_oagt_fifo;
//在build中例化
function void fifo_env::build_phase(uvm_phase phase);
iagt_refmodl_fifo=new("iagt_refmodl_fifo",this);
refmodl_scb_fifo=new("refmodl_scb_fifo",this);
scb_oagt_fifo=new("scb_oagt_fifo",this);
//在connect中连接
function void fifo_env::connect_phase( );
super.connect_phase();
i_agent1.ap.connect(iagt_refmodl_fifo.analysis_export);
refmodl.port.connect(iagt_refmodl_fifo.blocking_get_export);
refmodl.ap.connect(refmodl_scb_fifo.analysis_export);
scb.exp_port.connect(refmodl_scb_fifo.blocking_get_export);
scb.act_port.connect(scb_oagt_fifo.blocking_get_export);
o_agent1.ap.connect(scb_oagt_fifo.analysis_export);
endfunction
12.加入scoreboard
13.加入sequencer
14.sequence机制
sequence激励 对应于测试用例case
//case0.sv中包含
class case0_sequence extends uvm_sequence #(my_transaction);
class my_case0 extends base_test;
如何启动测试用例?启动测试用例是通过设置default_sequence来启动sequence
整个验证环节有多个测试用例/case,在互不影响的条件下启动sequence,最理想的方法是在命令行中指定参数
//top_tb
initial begin
run_test();
end
+UVM_TESTNAME=case0
因此在class my_case0 extends base_test的build_phase中设置default_sequence
//default_sequence
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agent1.sqr.main_phase",
"default_sequence",
case0_sequence::type_id::get());
//第二个参数是相对第一个参数的相对路径
//第二个参数main_phase使sequencer知道在哪个phase启动default_sequence这个sequence
//第三个第四个参数以及uvm_config_db#(uvm_object_wrapper)是uvm的规定
15.加入basetest
16.基于basetest扩展case0
//case0.sv中包含
//
class case0_sequence extends uvm_sequence #(my_transaction);
例化transaction
在task body()里do transaction 创建transaction
//
class my_case0 extends base_test;
在build_phase里设置default_sequence
在run_phase里
例化case0_sequence ,挂起objection,创建case0_sequence ,调用case0_sequence.start启动case0_sequence的task body同时指定sqr,落下objection
task my_case0::run_phase(uvm_phase phase);
case0_sequence seq; //例化case0_sequence
phase.raise_objection(this); //挂起objection
#4;
seq = case0_sequence::type_id::create("seq"); //创建case0_sequence
`uvm_info("case0_sequence","case0_sequence begin",UVM_LOW)
seq.start(env.i_agt1.sqr); //调用case0_sequence.start启动case0_sequence的task body,指定了sqr
#3000;
phase.drop_objection(this); //落下objection
endtask



3.代码详解
top_tb
`include "uvm_macros.svh" //把uvm_macros.svh文件添加进来,是UVM中包含众多宏定义的文件,只需包含一次
import uvm_pkg::*; //将整个uvm_pkg导入验证平台,只有导入这个库,编译器才会认识类名
`ifndef MY_TRANSACTION__SV
`define MY_TRANSACTION__SV
//my_codes
`endif
// 首尾加入这三句话后,在这中间的代码如果被编译过一次,那么哪怕在多个文件都include了它,也只会编译一次,就不会出现重复定义的情况了。
if
interface my_if(input wclk, input rclk, input wrst_n, input rrst_n);
logic winc,rinc;
logic [7 : 0] wdata;
wire wfull,rempty;
logic [7 : 0] rdata;
clocking ckw @(posedge wclk); //时钟块ckw声明了块中的信号wfull、winc、wdata在时钟的上升沿有效,信号的方向相对于modport DRV
input wfull;
inout winc;
inout wdata;
endclocking
clocking ckim @(posedge wclk);
input wfull;
inout winc;
input wdata;
endclocking
clocking ckom @(posedge rclk);
input rempty;
inout rinc;
input rdata;
endclocking
modport DUT(
input winc,
input rinc,
input wdata,
output rdata,
output wfull,
output rempty
);
modport DRV(
clocking ckw,
input rinc,
input rdata,
input rempty
);
modport OMON(
clocking ckom,
input wfull,
input winc,
input wdata
);
endinterface
driver
task my_driver::main_phase(uvm_phase phase);
`uvm_info("my_driver","begin!",UVM_LOW)
while(1) begin
seq_item_port.get_next_item(req); //从seq_item_port端口向sqr去get下一个transaction(req)
drive_one_pkt(req); //把transaction打包
seq_item_port.item_done();
end
endtask
//在drive_one_pkt中,将从sqr获取的tr所有数据压入队列data_q,data_q相当于一个byte流,之后再将data_q驱动到vif-dut
task my_driver::drive_one_pkt(my_transaction tr);
byte unsigned data_q[];
int data_size,j;
data_size = tr.pack_bytes(data_q)/8; //一个byte=8bit,用自动化域机制的pack_bytes函数把tr变成byte流放入data_q
`uvm_info("my_driver","begin to drive one pkt",UVM_LOW)
for(int i = 0; i < data_size; i++) begin
@fifo_if2.ckw;
if((!fifo_if2.ckw.wfull) && (fifo_if2.ckw.winc == 1)) begin //未写满且写允许的条件下,写数据=data_q
fifo_if2.ckw.wdata <= data_q[i];
`uvm_info("my_driver",$sformatf("%0d number is sent,number is %0h",j++,fifo_if2.ckw.wdata),UVM_LOW)
end else if((!fifo_if2.ckw.wfull) && (fifo_if2.ckw.winc == 0)) begin //未写满但不允许写
fifo_if2.ckw.winc <= 1; //将写信号允许
i--; //将i复位一次
end else begin //写满的条件下
fifo_if2.ckw.winc <= 0; //写信号不允许
i--; //将i复位一次
end
end
endtask
i_agent
function void i_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(is_active==UVM_ACTIVE)begin
dri.seq_item_port.connect(sqr.seq_item_export); //是sqr的export接口
end
ap=mon.ap;
endfunction