如何从零开始构建一个可用的UVM验证平台
前面大体说明了uvm的结构,类型关系以及启动过程。现在最关心的大概也就是uvm是如何把一串激励发送给被测对象的了。对于传统的基于verilog的验证平台,基本套路就是各种module中的task相互调用,最终把激励施加到顶层模块例化的被测对象中,但是具体步骤各个团队又有挺大差异。基于UVM的平台,本质其实也是这样,但是它把各种调用关系进行了更严格的限定,所以所有使用uvm的团队,实现的验证平台都更加相似。下面以之前的apb/spi接口为例具体讲解一下。这里把之前的图再贴过来一下。
之前已经说过有关sequencer,driver,monitor,env等的大体功能,这些可以被认为是验证平台的硬体,除此之外,还有一些在这之间流动的软体需要近一步说明一下。
uvm_sequence_item就是这种软体最基本的构造单元。比如可以定义apb端的sequence_item,其中包括读写信息,数据地址这些成员。
class apb_transfer extendsuvm_sequence_item;
rand bit[31:0] addr;
randapb_direction_enum direction;
rand bit[31:0] data;
rand intunsigned delay = 0;
constraint c_direction { direction inside {APB_READ, APB_WRITE }; }
constraint c_delay { delay <= 10=\"\" ;=\"\">
//这里需要将其中的变量注册一下,这些后边具体讲
`uvm_object_utils_begin(apb_transfer)
`uvm_field_int(addr, UVM_DEFAULT)
`uvm_field_enum(apb_direction_enum, direction, UVM_DEFAULT)
`uvm_field_int(data, UVM_DEFAULT)
`uvm_object_utils_end
function new (string name ='apb_transfer');
super.new(name);
endfunction
//对其他方法没有进行特别的设定
endclass : apb_transfer
比uvm_sequence_item稍微高一层极的信息单元是uvm_sequence,这个可以认为是一连串的uvm_sequence_item,可以表达一个完整的操作,下面是读fifo的一个例子。
class read_rx_fifo_seq extends uvm_sequence#(apb_pkg::apb_transfer);
//此类从uvm_sequence派生得到,uvm_sequence是一个参数化的类,这里类型赋值为apb_transfer,它被定义在apb_pkg这个包中
functionnew(string name='read_rx_fifo_seq');
super.new(name);
endfunction
// Registersequence with a sequencer
`uvm_object_utils(read_rx_fifo_seq)
rand bit[31:0] read_addr;
rand intunsigned del = 0;
rand intunsigned num_of_rd;
constraintnum_of_rd_ct { (num_of_rd <= 150);=\"\">
constraintdel_ct { (del <= 10);=\"\">
constraintaddr_ct {(read_addr[1:0] == 0); }
//一个sequence最重要的行为就是body,里边定义这个信息序列都发送什么东西
virtual taskbody();
`uvm_info(get_type_name(), UVM_LOW)
$sformatf('Starting Reads...',num_of_rd),
response_queue_error_report_disabled = 1;
for (int i = 0; i < num_of_rd;=\"\" i++)=\"\">
read_addr =`RX_FIFO_REG; //rx fifo address
`uvm_do_with(req,
{ req.addr == read_addr;
req.direction == APB_READ;
req.delay == del; } )
end
endtask
//uvm_do_with这个宏负责把各个最基本的sequence_item加上约束发送出去。
endclass : read_rx_fifo_seq
如何把这个sequence发送出去呢?这就需要在testcase里边把这个sequence通过sequencer发出去。
class read_rx_fifo_test extends uvm_test;
`uvm_component_utils(read_rx_fifo_test)
spi_apb_env spi_apb_env_0;
//test中例化env
function new(string name ='read_rx_fifo_test',
uvm_component parent=null);
super.new(name,parent);
endfunction : new
virtual function void build_phase(uvm_phasephase);
super.build_phase(phase);
spi_apb_env_0 = spi_apb_env::type_id::create('spi_apb_env_0',this);
endfunction : build_phase
virtual taskmain_phase(uvm_phase phase);
super.main_phase(phase);
//执行基类的任务
read_rx_fifo_seq_inst
=read_rx_fifo_seq::type_id::create('read_rx_fifo_seq_inst',this);
//产生一个seq
read_rx_fifo_seq_inst.start(spi_apb_env_0.apb_agent_0.apb_sqr);
//将seq通过sqr发送
endtask
endclass : read_rx_fifo_test
sequencer得到了这个序列,就要把它交给driver,然后driver把其中的信息元素放置到与dut连接的接口上。这样就将需要的激励施加给了被测对象。
class apb_master_driver extends uvm_driver #(apb_transfer);
// 连接driver与dut的虚接口.
virtual apb_if vif;
function new (string name, uvm_componentparent);
super.new(name, parent);
endfunction : new
// 在外部定义以下任务
extern virtual function voidbuild_phase(uvm_phase phase);
extern virtual function voidconnect_phase(uvm_phase phase);
extern virtual task run_phase(uvm_phasephase);
extern virtual protected taskget_and_drive();
extern virtual protected task drive_transfer(apb_transfer trans);
extern virtual protected taskdrive_address_phase (apb_transfer trans);
extern virtual protected task drive_data_phase(apb_transfer trans);
endclass : apb_master_driver
function void apb_master_driver::connect_phase(uvm_phasephase);
super.connect_phase(phase);
if (!uvm_config_db#(virtual apb_if)::get(this,'', 'vif', vif))
`uvm_error('NOVIF',{'virtual interface must be set for:',get_full_name(),'.vif'})
//将虚接口通过配置从顶层取出来,并赋值给vif
endfunction : connect_phase
task apb_master_driver::run_phase(uvm_phase phase);
get_and_drive();
endtask : run_phase
// 在执行时一直在从sqr取item并且赋值给vif
task apb_master_driver::get_and_drive();
while (1) begin
fork
begin
forever begin
@(posedge vif.pclock iff (vif.preset))
seq_item_port.get_next_item(req);
//从sqr取到item
drive_transfer(req);
//发送
seq_item_port.item_done(req);
//告知sqr使其产生下一个item
end
end
join_any
disable fork;
end
endtask : get_and_drive
//具体一个发送过程
task apb_master_driver::drive_transfer (apb_transfer trans);
drive_address_phase(trans);
//发送地址
drive_data_phase(trans);
//发送数据
endtask : drive_transfer
task apb_master_driver::drive_address_phase (apb_transfertrans);
int slave_indx;
slave_indx =cfg.get_slave_psel_by_addr(trans.addr);
vif.paddr <=>
vif.psel <=><>
vif.penable <=>
if (trans.direction == APB_READ) begin
vif.prwd<=>
end
else begin
vif.prwd<=>
vif.pwdata<=>
end
@(posedge vif.pclock);
endtask : drive_address_phase
task apb_master_driver::drive_data_phase (apb_transfertrans);
vif.penable <=>
@(posedge vif.pclock iff vif.pready);
if (trans.direction == APB_READ) begin
trans.data =vif.prdata;
end
vif.penable <=>
vif.psel <=>
endtask : drive_data_phase
再重复一下这个基本的步骤,在test的main_phase中,由基本的sequence_item组成的sequence被放到sequencer上执行,sequencer将它转交给driver,其后driver通过虚接口将具体信号施加到被测对象上,实现uvm中激励的产生传输与施加过程。
这里就产生了两个问题,一个是sequencer如何将sequence_item转交给driver,另一个是driver如何通过虚接口将具体信号施加到被测对象上。这里具体解释
对于第一个问题,我们在driver中有这么2句
seq_item_port.get_next_item(req);
seq_item_port.item_done(req);
这里的seq_item_port就是driver与sequencer通信的接口。
在uvm_driver中可以看到这个定义为
uvm_seq_item_pull_port #(REQ, RSP)seq_item_port;
这里的REQ,RSP都是对应uvm_driver的sequence_itemuvm_seq_item_pull_port是一个uvm专用的tlm口
同样,在uvm_sequencer中有
uvm_seq_item_pull_imp #(REQ, RSP, this_type)seq_item_export;
。而
而在上层的uvm_agent中有
virtual function voidconnect_phase(uvm_phase phase);
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
这里drv跟sqr分别是例化的uvm_driveruvm_sequencer的名字,可以看到他们两个实质是通过这个tlm接口来传递item的。
下面图大致说明了这个链接建立以及传送数据的过程。
首先在
build
后的
connect 时段,会确立
drv.seq_item_port
与
sqr.seq_item_export的链接,也就是检查port与export的一一对应关系,并将与port对应的export映射给m_if,而后每次执行driver中的 get_next_item与item_done实际就是执行了sequencer中的get_next_item与item_done。而这实际上就是往一个队列中不断存数与取数的过程。详细代码在uvm_sequencer中
接下来再看第2个问题,driver通过虚接口驱动信号的过程。
这里是apb接口的定义
interface apb_if (input pclock, input preset);
parameter PADDR_WIDTH = 32;
parameter PWDATA_WIDTH = 32;
parameter PRDATA_WIDTH = 32;
logic [PADDR_WIDTH-1:0] paddr;
logic prwd;
logic [PWDATA_WIDTH-1:0] pwdata;
logic penable;
logic[15:0] psel;
logic [PRDATA_WIDTH-1:0] prdata;
logic pslverr;
logic pready;
endinterface : apb_if
下面是顶层TB中的一段
reg clock;
reg reset;
apb_if apb_if_0(clock, reset);
//例化接口
dut_dummy dut( .apb_clock(clock),
.apb_reset(reset),
.apb_if(apb_if_0)
);
//例化被测对象,链接前面定义的接口
initial begin
uvm_config_db#(virtual apb_if)::set(null, '*.demo_tb0.apb0*','vif', apb_if_0);
//通过config_db将 apb_if_0这个实际的接口,传送到验证平台下层各组件的虚假口中
run_test();
//运行测试
end
这里的 uvm_config_db#(virtual apb_if)::set对应之前driver里边的uvm_config_db#(virtual apb_if)::get
也就是在顶层set,底层get,然后通过uvm_config_db这个类似数据库的玩意,实现从顶层module到底层class中接口的链接,从而driver中的信息流进dut里边。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- zrrp.cn 版权所有 赣ICP备2024042808号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务