最近一个月在实习公司做回归测试,对公司的UVM平台用的比较熟练,就想着自己做一个DUT,然后搭建一个UVM验证平台。
首先,DUT是一个简单的32位的加法器,代码如下:alu.v
module adder32_cla(
input clk ,
input rst ,
input enable ,
input [31:0] a ,
input [31:0] b ,
input cin ,
output [31:0] sum_r ,
output cout_r
);
reg [31:0] sum_r = 32'h00000000 ;
reg cout_r = 1'h0 ;
always @(posedge clk or negedge rst)
begin
if (!rst)
begin
sum_r = 32'h00000000 ;
cout_r = 1'h0 ;
end
else if(enable)
begin
{cout_r,sum_r} <= a + b + cin;
end
else
begin
sum_r <= sum_r ;
cout_r <= cout_r ;
end
end
endmodule
View Code
UVM验证组件:
1、top.sv
`timescale 1ns/1ns
`include "pkg.sv"
`include "alu.v"
module top();
import uvm_pkg::*;
`include "uvm_macros.svh"
my_if my_my_if();
adder32_cla inst1
(
.clk (my_my_if.clk ),
.rst (my_my_if.rst ),
.enable (my_my_if.enable ),
.a (my_my_if.a ),
.b (my_my_if.b ),
.cin (my_my_if.cin ),
.sum_r (my_my_if.sum_r ),
.cout_r (my_my_if.cout_r )
);
initial
begin
my_my_if.cin = 1'b0 ;
my_my_if.a = 32'h00000000 ;
my_my_if.b = 32'h00000000 ;
my_my_if.enable = 1'b1 ;
my_my_if.rst = 1'b0 ;
#100 my_my_if.rst = 1'b1 ;
end
initial
begin
my_my_if.clk <= 1'b0 ;
#500 my_my_if.clk <= 1'b0 ;
#500 my_my_if.clk <= 1'b1 ;
forever
#50 my_my_if.clk = ~my_my_if.clk;
end
initial
begin
uvm_config_db#(virtual my_if)::set(null,"uvm_test_top.env.i_agt.drv","my_if",my_my_if);
uvm_config_db#(virtual my_if)::set(null,"uvm_test_top.env.o_agt.mon","my_if",my_my_if);
run_test();
end
endmodule
View Code
top.sv主要的作用是实例化DUT,和输入输出的interface,并且定义了时钟频率,传递了接口一连接TB,和run_test();用于启动UVM phase。
2、my_test.sv
`ifndef MY_TEST__SV
`define MY_TEST__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_env.sv"
class my_test extends uvm_test;
my_env env;
extern function new(string name="my_test",uvm_component parent=null);
extern virtual function void build_phase(uvm_phase phase);
`uvm_component_utils(my_test)
endclass
function my_test::new (string name="my_test",uvm_component parent=null);
super.new(name,parent);
endfunction
function void my_test::build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env");
endfunction
`endif
View Code
my_test.sv派生于uvm_test,属于基类。在项目中,主要采用基类构建框架,继承类实现具体动作的方式来增加灵活性。
3、my_env.sv
`ifndef MY_ENV__SV
`define MY_ENV__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_agent.sv"
`include "my_scoreboard.sv"
`include "my_model.sv"
class my_env extends uvm_env;
my_agent i_agt ; //my_driver functional
my_agent o_agt ; //my_monitor functional
my_model mdl ;
my_scoreboard scb ;
uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo; //my_monitor
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo; //my_driver
uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo; //my_model
extern function new (string name,uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_env);
endclass
function my_env::new (string name,uvm_component parent);
super.new(name,parent);
endfunction
function void my_env::build_phase(uvm_phase phase);
super.build_phase(phase);
i_agt = my_agent::type_id::create("i_agt",this);
o_agt = my_agent::type_id::create("o_agt",this);
i_agt.is_active = UVM_ACTIVE;
o_agt.is_active = UVM_PASSIVE;
mdl = my_model::type_id::create("mdl",this);
scb = my_scoreboard::type_id::create("scb",this);
agt_scb_fifo = new("agt_scb_fifo",this);
agt_mdl_fifo = new("agt_mdl_fifo",this);
mdl_scb_fifo = new(" mdl_scb_fifo",this);
endfunction
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
i_agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
mdl.ap.connect(mdl_scb_fifo.analysis_export);
scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
o_agt.ap.connect(agt_scb_fifo.analysis_export);
scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction
`endif
View Code
my_env.sv的主要是agent scoreboard reference_model模块的实例化,和三个tlm_analysis_fifo,然后build_phase中type_id::create()模块,配置i/oagent,scoreboard model和new三个FIFO,之后connect_phase中,connect analysis_export和blocking_get_export。
4、my_transaction.sv
要传送的transaction
import uvm_pkg::*;
`include "uvm_macros.svh"
`ifndef MY_TRANSACTION__SV
`define MY_TRANSACTION__SV
class my_transaction extends uvm_sequence_item;
rand bit cin;
rand bit [31:0] a,b;
bit [31:0] sum_r;
bit cout_r;
extern function new (string name="my_transaction");
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(cin,UVM_ALL_ON)
`uvm_field_int(a,UVM_ALL_ON)
`uvm_field_int(b,UVM_ALL_ON)
`uvm_field_int(sum_r,UVM_ALL_ON)
`uvm_field_int(cout_r,UVM_ALL_ON)
`uvm_object_utils_end
endclass
function my_transaction::new (string name="my_transaction");
super.new(name);
endfunction
`endif
View Code
主要包括各种内容以及uvm_object_utils,uvm_field_int的注册,参数约束和随机化。
5、my_sequencer.sv
发送transaction的sequencer
`ifndef MY_SEQUENCER__SV
`define MY_SEQUENCER__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
`include "my_sequence.sv"
class my_sequencer extends uvm_sequencer #(my_transaction);
extern function new (string name,uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
`uvm_component_utils(my_sequencer)
endclass
function my_sequencer::new (string name,uvm_component parent);
super.new(name,parent);
endfunction
function void my_sequencer::build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
`endif
View Code
负责把transaction类型的数据传送给sequence,里面内容很简单就一个注册和new函数。
6、my_sequence.sv
`ifndef MY_SEQUENCE__SV
`define MY_SEQUENCE__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
class my_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
`uvm_object_utils(my_sequence)
extern function new(string name="my_sequence");
virtual task body();
if(starting_phase!=null)
starting_phase.raise_objection(this);
repeat(10)
begin
`uvm_do(m_trans)
end
#10000;
if(starting_phase!=null)
starting_phase.drop_objection(this);
endtask
endclass
function my_sequence::new(string name="my_sequence");
super.new(name);
endfunction
`endif
View Code
sequence,用于产生激励,里面的objection机制用来控制验证平台的打开与关闭,需要在drop_objection之前先raise_objection。task_body中uvm_do(my_trans),在之前和之后要starting_phase.raise_objection(this)和starting_phase.drop_objection(this);
7、my_driver.sv
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
class my_driver extends uvm_driver #(my_transaction);
virtual my_if vif;//to DUT
uvm_analysis_port #(my_transaction) ap;//the data is to reference model
`uvm_component_utils(my_driver)
extern function new (string name,uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
extern virtual task drive_one_pkt(my_transaction req);
endclass
function my_driver::new (string name,uvm_component parent);
super.new(name,parent);
endfunction
function void my_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual my_if)::get(this,"","my_if",vif))
`uvm_fatal("my_driver","Error in Getting interface");
ap=new("ap",this);
endfunction
task my_driver::main_phase(uvm_phase phase);
my_transaction req;
super.main_phase(phase);
while(1)
begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
ap.write(req);
seq_item_port.item_done();
end
endtask
task my_driver::drive_one_pkt(my_transaction req);
@vif.drv_cb;
@vif.drv_cb
begin
vif.drv_cb.enable<=1'b1;
vif.drv_cb.cin<=req.cin;
vif.drv_cb.a<=req.a;
vif.drv_cb.b<=req.b;
end
@vif.drv_cb;
@vif.drv_cb vif.drv_cb.enable<=1'b0;
endtask
`endif
View Code
首先是virtual inf和build_phase中接受接口,否则fatal,然后main_phase中while(1)循环的get_next_item(req),调用发送函数发送req,返回item_done()。
8、my_monitor.sv
`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
class my_monitor extends uvm_monitor;
virtual my_if vif;
uvm_analysis_port #(my_transaction) ap; // to scoreboard
extern function new (string name,uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
extern virtual task receive_one_pkt(ref my_transaction get_pkt);
`uvm_component_utils(my_monitor)
endclass
function my_monitor::new (string name,uvm_component parent);
super.new(name,parent);
endfunction
function void my_monitor::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual my_if)::get(this,"","my_if",vif))
`uvm_fatal("my_monitor","Error in Getting interface");
ap=new("ap",this);
endfunction
task my_monitor::main_phase(uvm_phase phase);
my_transaction tr;
super.main_phase(phase);
while(1) begin
tr=new();
receive_one_pkt(tr);
ap.write(tr);
end
endtask
task my_monitor::receive_one_pkt(ref my_transaction get_pkt);
@(negedge vif.drv_cb.enable);
get_pkt.cout_r=vif.mon_cb.cout_r;
get_pkt.sum_r=vif.mon_cb.sum_r;
endtask
`endif
View Code
主要是virtual inf和analysis_port,在build_phase中接受接口,mian_phase中while(1)的调用接受函数和和ap.write(tr)。
9、my_agent.sv
`ifndef MY_AGENT__SV
`define MY_AGENT__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
`include "my_sequence.sv"
`include "my_sequencer.sv"
`include "my_driver.sv"
`include "my_monitor.sv"
class my_agent extends uvm_agent;
my_sequencer sqr;
my_driver drv;
my_monitor mon;
extern function new (string name,uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
uvm_analysis_port #(my_transaction) ap;
`uvm_component_utils_begin(my_agent)
`uvm_field_object(sqr,UVM_ALL_ON)
`uvm_field_object(drv,UVM_ALL_ON)
`uvm_field_object(mon,UVM_ALL_ON)
`uvm_component_utils_end
endclass
function my_agent::new (string name,uvm_component parent);
super.new(name,parent);
endfunction
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
if(is_active==UVM_ACTIVE )
begin
sqr=my_sequencer::type_id::create("sqr",this);
drv=my_driver::type_id::create("drv",this);
end
else begin
mon=my_monitor::type_id::create("mon",this);
end
endfunction
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);
this.ap=drv.ap;
end
else begin
this.ap=mon.ap;
end
endfunction
`endif
View Code
含有driver和monitor,在build_phase中如果UVM_ACTIVE,就例化drv和sqr,但都例化mon,在build_phase中如果UVM_ACTIVE就链接drv和sqr的接口。
10、interface.sv
`ifndef MY_INTERFACE__SV
`define MY_INTERFACE__SV
interface my_if;
logic clk,rst ;
logic cin ;
logic [31:0] a ;
logic [31:0] b ;
logic enable ;
wire cout_r ;
wire [31:0] sum_r ;
clocking drv_cb @(posedge clk);
output enable,cin,a,b;
endclocking
clocking mon_cb @(posedge clk);
input cout_r,sum_r;
endclocking
endinterface
`endif
View Code
interface里面就是接口,用于连接DUT和driver和monitor。
12、my_model.sv
就是参考模型(reference_model)
`ifndef MY_MODEL__SV
`define MY_MODEL__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
class my_model extends uvm_component;
uvm_blocking_get_port #(my_transaction) port; //from my_driver
uvm_analysis_port #(my_transaction) ap; //to scoreboard
extern function new (string name,uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
extern virtual task one_pkt(ref my_transaction pkt,ref my_transaction pkt2);
`uvm_component_utils(my_model)
endclass
function my_model::new (string name,uvm_component parent);
super.new(name,parent);
endfunction
function void my_model::build_phase(uvm_phase phase);
super.build_phase(phase);
port=new("port",this);
ap=new("ap",this);
endfunction
task my_model::main_phase(uvm_phase phase);
my_transaction tr,tr2;
super.main_phase(phase);
while(1) begin
tr2=new();
port.get(tr);
one_pkt(tr2,tr);
ap.write(tr2);
end
endtask
task my_model::one_pkt(ref my_transaction pkt,ref my_transaction pkt2);
bit [32:0] sum_total;
begin
sum_total=pkt2.a+pkt2.b+pkt2.cin;
pkt.sum_r=sum_total[31:0];
pkt.cout_r=sum_total[32];
end
endtask
`endif
View Code
需要进的blocking_get_port和出的analysis_port两个,在build_phase中连接他们,在main_phase中则while1的,如果get_port.get()收到,就ana_port.write()这个tr。
13、my_scoreboard.sv
`ifndef MY_SCOREBOARD__SV
`define MY_SCOREBOARD__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
class my_scoreboard extends uvm_scoreboard;
int pre_number=0;
my_transaction expect_queue[$];
uvm_blocking_get_port #(my_transaction) exp_port;//from my_reference
uvm_blocking_get_port #(my_transaction) act_port;//from my_monitor
`uvm_component_utils(my_scoreboard)
extern function new (string name,uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
endclass
function my_scoreboard::new (string name,uvm_component parent);
super.new(name,parent);
endfunction
function void my_scoreboard::build_phase(uvm_phase phase);
super.build_phase(phase);
exp_port=new("exp_port",this);
act_port=new("act_port",this);
endfunction
task my_scoreboard::main_phase(uvm_phase phase);
my_transaction get_expect,get_actual,tmp_tran;
bit result;
super.main_phase(phase);
fork
while (1)
begin
exp_port.get(get_expect);
expect_queue.push_back(get_expect);
end
while (1)
begin
act_port.get(get_actual);
if(expect_queue.size>0)begin
tmp_tran=expect_queue.pop_front();
result=get_actual.compare(tmp_tran);
if(result) begin
pre_number=pre_number+1;
$display("compare SUCCESSFULLy:%0d",pre_number);
end
else begin
$display("compare FAILED");
$display("the expect pkt is");
tmp_tran.print();
$display("the actual pkt is");
get_actual.print();
end
end
else begin
$display("ERROR::Received from DUT,while Expect Queue is empty");
get_actual.print();
end
end
join
endtask
`endif
View Code
是两个get_port,在build_phase中new它们,首先对于exp端,while(1)的,只要get一个tr,就要在tr_queue中push进去一个,同时对于act端,while(1)的。只要get一个tr,就要从tr_queue中pop一个出来,进行比较。这个两个是fork join的,互不影响。
14、my_case0.sv
`ifndef MY_CASE0__SV
`define MY_CASE0__SV
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_test.sv"
class my_case0 extends my_test;
`uvm_component_utils(my_case0)
extern function new(string name="my_case0",uvm_component parent=null);
extern virtual function void build_phase(uvm_phase phase);
endclass
function my_case0::new (string name="my_case0",uvm_component parent=null);
super.new(name,parent);
endfunction
function void my_case0::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(uvm_object_wrapper)::set(this,"env.i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get());
endfunction
`endif
View Code
my_case0.sv扩展于my_test.sv,在my_case0.sv的build_phase中传递sequence。
至此,本UVM验证平台的各个组件就已经完成了,接下来有时间把Makefile脚本完善好上传,并在装有vcs和Verdi的虚拟机去运行,查看波形和查看覆盖率报告。
目前还差的东西是在top.sv里面加上生成波形的语句。
附录:pkt.v 导入整个验证平台
`ifndef MY_INTERFACE__SV
`define MY_INTERFACE__SV
`include "interface.sv"
`include "my_env.sv"
`include "my_test.sv"
`include "my_case0.sv"
`endif