这段时间将SystemVerilog及UVM复习了一遍,完成了一些练习,对验证平台的搭建也有了更深的掌握与理解。
下面将之前一个验证平台改造成基于UVM的验证平台。
代码
driver
对于driver,删掉原来用于与generator通讯的mailbox。因为UVM的driver类,提供了TLM通讯接口seq_item_port,与sequencer的seq_item_export相连,即可起到原本mailbox的作用。接口原本是通过构造函数传进来,这里采用uvm_config_db,后面在test中统一配置验证平台中的接口。
`define dinf vif.drvmod
class driver extends uvm_driver # (transaction);
virtual inf vif;
`uvm_component_utils(driver)
function new(string name="driver", uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
/*连接接口信号,原本是通过构造函数传进来*/
uvm_config_db#(virtual inf)::get(this,"","vif",vif);
if (vif==null) begin
`uvm_fatal($sformatf("%m"),"Virtual interface to driver must be set!");
end
endfunction
extern virtual task main_phase(uvm_phase phase);
extern virtual task send(transaction tr);
extern virtual task rst();
endclass
task driver::main_phase(uvm_phase phase);
int i=0;
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
/*复位操作*/
this.rst();
/*驱动信号至DUT*/
forever begin
`dinf.drvcb.wr_en <= 0;
`dinf.drvcb.rd_en <= 0;
seq_item_port.get_next_item(req);
send(req);
seq_item_port.item_done();
`uvm_info($sformatf("%m"),$sformatf("No.%2d Transaction drived!",i++),UVM_LOW);
`uvm_info($sformatf("%m"),{"\n",req.sprint()},UVM_LOW);
end
endtask
/*用于驱动到的任务*/
task driver::send(transaction tr);
@(`dinf.drvcb);
`dinf.drvcb.addr <= tr.addr;
if (tr.wr_en) begin
`dinf.drvcb.wr_en <= tr.wr_en;
`dinf.drvcb.wdata <= tr.wdata;
@(`dinf.drvcb);
end
if (tr.rd_en) begin
`dinf.drvcb.rd_en <= tr.rd_en;
@(`dinf.drvcb);
tr.rdata = `dinf.drvcb.rdata;
end
endtask
task driver::rst();
wait(`dinf.reset == 1);
`dinf.drvcb.wr_en <= 0;
`dinf.drvcb.rd_en <= 0;
`dinf.drvcb.addr <= 0;
`dinf.drvcb.wdata <= 0;
`uvm_info($sformatf("%m"),"Reset completed!",UVM_LOW);
wait(`dinf.reset == 0);
repeat(5) @(`dinf.drvcb);
endtask
transaction/sequence/sequencer
transaction类变化不大。sequence代替了原本generator的作用,sequencer则在sequence与driver之间起到调度作用。加入sequencer,就可以更方便地管理不同的sequence。sequencer这个组件很简单,这里之间用uvm_sequencer即可,无需派生。
class transaction extends uvm_sequence_item;
rand bit [1:0] addr;
rand bit wr_en;
rand bit rd_en;
rand bit [7:0] wdata;
bit [7:0] rdata;
static int cnt=0;
constraint wr_rd {wr_en != rd_en;};
`uvm_object_utils_begin(transaction)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(wr_en, UVM_ALL_ON)
`uvm_field_int(rd_en, UVM_ALL_ON)
`uvm_field_int(wdata, UVM_ALL_ON)
`uvm_field_int(rdata, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name="transaction");
super.new();
cnt++;
endfunction
endclass
class seqce extends uvm_sequence # (transaction);
`uvm_object_utils(seqce)
transaction tr;
int times;
function new(string name="seqce");
super.new(name);
endfunction
virtual task pre_body();
/*在生成transaction前确定生成的数目*/
uvm_config_db#(int)::get(null,get_full_name(),"times", times);
if (times==0)
`uvm_fatal($sformatf("%m"),"Times must be set!");
endtask
virtual task body();
if (starting_phase!=null) starting_phase.raise_objection(this);
repeat (this.times) begin
`uvm_do(tr);
end
#1000;
if (starting_phase!=null) starting_phase.drop_objection(this);
endtask;
endclass
typedef uvm_sequencer # (transaction) seqcer;
input monitor
原本的验证平台中,input/output的收集全部放在一个monitor中,这里将其分开,使得分工更加明确。input monitor收集写操作的事务,output monitor收集读操作的事务。两者都封装在各自的agent类之中。
`define minf vif.monmod
class imonitor extends uvm_monitor;
`uvm_component_utils(imonitor)
transaction tr;
virtual inf vif;
/*用于TLM通讯的端口,另一端是agent的端口*/
uvm_analysis_port #(transaction) ap;
function new(string name="imonitor", uvm_component parent);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
uvm_config_db#(virtual inf)::get(this,"","vif",vif);
if (vif==null) begin
`uvm_fatal($sformatf("%m"),"Virtual interface to driver must be set!");
end
ap = new("ap",this);
endfunction
extern virtual task main_phase(uvm_phase phase);
extern virtual task collect(transaction tr);
endclass
task imonitor::main_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
forever begin
tr = new();
/*收集事务*/
collect(tr);
/*发送事务*/
ap.write(tr);
end
endtask
task imonitor::collect(transaction tr);
wait((`minf.moncb.wr_en));
tr.wr_en = `minf.moncb.wr_en;
tr.addr = `minf.moncb.addr;
tr.wdata = `minf.moncb.wdata;
tr.rd_en = `minf.moncb.rd_en;
@(`minf.moncb);
endtask
output monitor
`define minf vif.monmod
class omonitor extends uvm_monitor;
`uvm_component_utils(omonitor)
transaction tr;
virtual inf vif;
uvm_analysis_port #(transaction) ap;
function new(string name="omonitor", uvm_component parent);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
uvm_config_db#(virtual inf)::get(this,"","vif",vif);
if (vif==null) begin
`uvm_fatal($sformatf("%m"),"Virtual interface to driver must be set!");
end
ap = new("ap",this);
endfunction
extern virtual task main_phase(uvm_phase phase);
extern virtual task collect(transaction tr);
endclass
task omonitor::main_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
forever begin
tr = new();
collect(tr);
ap.write(tr);
end
endtask
task omonitor::collect(transaction tr);
wait((`minf.moncb.rd_en));
tr.wr_en = `minf.moncb.wr_en;
tr.addr = `minf.moncb.addr;
tr.wdata = `minf.moncb.wdata;
tr.rd_en = `minf.moncb.rd_en;
@(`minf.moncb);
tr.rdata = `minf.moncb.rdata;
endtask
input agent / output agent
input agent中除了input monitor 还有sequencer以及driver;output agent中只有output monitor。同时agent作为更大的容器,含有与外界通讯的端口,一端连接monitor,一端连接TLM FIFO。
/*input agent*/
typedef uvm_sequencer # (transaction) seqcer;
class agent extends uvm_agent;
`uvm_component_utils(agent)
driver drv;
seqcer sqr;
imonitor mon;
/*TLM端口*/
uvm_analysis_port # (transaction) ap;
function new(string name="agent",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);
endclass
function void agent::build_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
super.build_phase(phase);
/*ACTIVE模式,多了driver及sequncer*/
if (is_active==UVM_ACTIVE) begin
drv = driver::type_id::create("drv",this);
sqr = seqcer::type_id::create("sqr",this);
end
mon = imonitor::type_id::create("mon",this);
ap = new("ap", this);
endfunction
function void agent::connect_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
if (is_active==UVM_ACTIVE) begin
drv.seq_item_port.connect(sqr.seq_item_export);
end
ap = mon.ap;
endfunction
/*output agent*/
class oagent extends uvm_agent;
`uvm_component_utils(oagent)
omonitor mon;
uvm_analysis_port # (transaction) ap;
function new(string name="oagent",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);
endclass
function void oagent::build_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
super.build_phase(phase);
mon = omonitor::type_id::create("mon",this);
ap = new("ap", this);
endfunction
function void oagent::connect_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
ap = mon.ap;
endfunction
scoreboard
scoreboard中用TLM FIFO代替了原本mailbox,通过两个端口,分别获得input monitor以及output monitor的数据,然后进行比较。同时设置了一个覆盖率检测,检查DUT中是否所有地址都被验证平台覆盖。
class scoreboard extends uvm_scoreboard;
`uvm_component_utils(scoreboard)
transaction act,exp;
bit [7:0] mem[4];
/*通讯端口*/
uvm_blocking_get_port # (transaction) ap_exp;
uvm_blocking_get_port # (transaction) ap;
/*覆盖率检查*/
covergroup addr_cov;
coverpoint act.addr;
endgroup
function new(string name="scoreboard",uvm_component parent);
super.new(name,parent);
addr_cov = new();
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
endclass
function void scoreboard::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
ap = new("ap",this);
ap_exp= new("ap_exp",this);
/*初始化用于储存真实值的memory*/
foreach (mem[i])
mem[i] = 8'hFF;
endfunction
task scoreboard::main_phase(uvm_phase phase);
int i=0;
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
forever begin
fork
forever begin
/*从input monitor中获取写事务,更新memory的值*/
ap_exp.get(exp);
mem[exp.addr] = exp.wdata;
i++;
end
join_none
/*从output monitor中获得读事务,检测正确性*/
ap.get(act);
$display("%m No.%2d Transaction got!",i);
if (mem[act.addr] == act.rdata) begin
`uvm_info($sformatf("%m"),$sformatf("SUCCESS No.%d \n ~~ ACT%d EXP%d",i++,act.rdata,mem[act.addr]),UVM_LOW);
end
else begin
`uvm_info($sformatf("%m"),$sformatf("FAIL No.%d \n ~~ ACT%d EXP%d",i++,act.rdata,mem[act.addr]),UVM_LOW);
end
/*采样覆盖率*/
addr_cov.sample();
end
endtask
environment
在environment中实例化agent,scoreboard,并做好端口连接。
`include "transaction.sv"
`include "seqce.sv"
`include "driver.sv"
`include "imonitor.sv"
`include "omonitor.sv"
`include "agent.sv"
`include "oagent.sv"
`include "scoreboard.sv"
class environment extends uvm_env;
`uvm_component_utils(environment)
agent iagt;
oagent oagt;
scoreboard sb;
/*用于monitor与scoreboard间通讯的TLM FIFO*/
uvm_tlm_analysis_fifo # (transaction) oagt2sb;
uvm_tlm_analysis_fifo # (transaction) iagt2sb;
function new(string name="environment", 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);
endclass
function void environment::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
iagt = agent::type_id::create("iagt",this);
oagt = oagent::type_id::create("oagt",this);
sb = scoreboard::type_id::create("sb",this);
iagt.is_active = UVM_ACTIVE;
oagt2sb = new("oagt2sb",this);
iagt2sb = new("iagt2sb",this);
/*启动sequence*/
uvm_config_db#(uvm_object_wrapper)::set(this,"iagt.sqr.main_phase","default_sequence",seqce::get_type());
endfunction
function void environment::connect_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
/*连接FIFO端口*/
oagt.ap.connect(oagt2sb.analysis_export);
iagt.ap.connect(iagt2sb.analysis_export);
sb.ap.connect(oagt2sb.blocking_get_export);
sb.ap_exp.connect(iagt2sb.blocking_get_export);
endfunction
test_base
基本测试类。实例化env,配置接口,测试次数,打印平台结构。
`include "environment.sv"
class test_base extends uvm_test;
`uvm_component_utils(test_base)
environment env;
rand int times;
virtual inf vif;
function new(string name="test_base",uvm_component parent);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
super.build_phase(phase);
env = environment::type_id::create("env",this);
/*配置接口*/
uvm_config_db#(virtual inf)::get(this,"","vif",vif);
uvm_config_db#(virtual inf)::set(this,"env.iagt.drv","vif",vif);
uvm_config_db#(virtual inf)::set(this,"env.oagt.mon","vif",vif);
uvm_config_db#(virtual inf)::set(this,"env.iagt.mon","vif",vif);
/*随机测试次数*/
times = $urandom_range(10,20);
uvm_config_db#(int)::set(this,"env.iagt.sqr","times", times);
endfunction
virtual function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
uvm_top.print_topology();
factory.print();
endfunction
endclass
top
顶层文件,连接DUT与验证平台。
`timescale 1ns/100ps;
module top;
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "interface.sv"
`include "test_base.sv"
`include "design.sv"
reg clk;
reg reset;
inf intf(clk,reset);
memory dut(
.clk(intf.clk),
.reset(intf.reset),
.addr(intf.addr),
.wr_en(intf.wr_en),
.rd_en(intf.rd_en),
.wdata(intf.wdata),
.rdata(intf.rdata)
);
initial begin
run_test();
end
initial begin
uvm_config_db#(virtual inf)::set(null,"uvm_test_top","vif",intf);
end
initial begin
clk = 0;
forever begin
#50 clk = ~clk;
end
end
initial begin
reset = 0;
#100;
reset = 1;
#100;
reset = 0;
end
initial begin
$dumpfile("dump.vcd");$dumpvars;
end
endmodule
总结
在改造的过程中,有一个感触,不用去费心思想各个组件的运行关系了。原本的验证平台中需要event控制仿真结束,而何时触发event,就又得加入其他的判断条件。而以哪个组件的运行终止作为最后判断标准又得斟酌。用UVM就非常方便了,直接编写在相应的phase中,UVM自动帮我调度。phase运行完成仿真自然结束了。仿真意外结束,看看运行到哪个phase,也更方便定位错误。