UVM(Universal Verification Methodology)可以理解为形而上的东西,可以理解为是基于System verilog的一个库,提供一些API调用。
其实没必要把UVM抬的那么高,上升到形而上的层次。因为在实际的IC验证时,对某个复杂DUT的验证时,一般都会拆分成那几个模块。
要对DUT测试,肯定需要先产生测试用例,就是UVM中的sequencer和sequence概念。
产生测试用例之后,肯定需要把测试用例送到DUT端口上,作为激励,就是UVM中的driver的概念。
既然是验证,就是比较DUT是不是对的,既然要比较对与错,就需要DUT的行为模型,就是UVM中的reference model的概念。
既然要对比,可能对比算法比较复杂,一般需要单独出来,就是UVM中scoreboard的概念。
有了DUT的行为模型,要给行为模型激励啊,怎么给呢?直接采集DUT的端口信息就行了,这个工作也独立出来,就是UVM中monitor的概念。
monitor(软件部分)怎么采集DUT(RTL部分)的端口信息呢?用PLI,VPI,DPI,都可以。UVM将这个内容封装了一下,就是uvm_config_db。
这么多拆开独立的部分,他们之间肯定需要通信啊,怎么办呢?用消息队列,有名管道,socket等进行间通信方式都可以,UVM又封装了一下,美其名曰TLM。
从上面可以看出,UVM带给我们的不是形而上的高达上概念(当然,如果你之前在验证时,没有自觉的将不同功能模块分开的话,可能会感觉UVM很高达上,哈哈),而是实实在在的代码,提供了一组API,使我们在做verification时少敲了几行代码。用UVM也有不好的地方,个人感觉运行效率有点低,仿真时间加长。
总之,UVM是无数验证工程师经验的结晶,仿真工具也都支持,所以在做IC验证时尽量用UVM中的API,也可以自己实现TLM,config机制,实现起来也很简单(我实现过)。
之前我们熟悉了一下SVUNIT(http://blog.csdn.net/rill_zhen/article/details/45342999),本小节我们通过一个实验来熟悉一下UVM。
仔细体会本实验中的代码,领会其背后含义之后,UVM就入门了。
要进行验证,必须要有个靶子,就是DUT。本例DUT很简单:“左耳进,右耳出”,吃什么吐什么。
/*
* uvm test example
* Rill 2015-05-02
*/
module Mdut
#
(
parameter DATA_L = 8
)
(
input clk,
input rst_n,
IFrx.S rx_if,
output tx_dv,
output [DATA_L-1:0] txd
);
logic tx_dv_r;
logic [DATA_L-1:0] txd_r;
always_ff @(posedge clk)
if(~rst_n)
begin
tx_dv_r <= 1'b0;
txd_r <= DATA_L'b0;
end
else if(rx_if.vld)
begin
tx_dv_r <= rx_if.vld;
txd_r <= rx_if.data;
end
assign tx_dv = tx_dv_r;
assign txd = txd_r;
endmodule //Mdut
/*
* uvm test example
* Rill 2015-05-02
*/
interface IFrx
#(
parameter DATA_L=8
)
();
logic vld;
logic [DATA_L-1:0] data;
modport M
(
output vld,
output data
);
modport S
(
input vld,
input data
);
endinterface:IFrx
有了DUT,用UVM那一套东西怎么对DUT进行测试呢?
根据引言中的分析,UVM中的概念不是凭空产生的,一定要搞明白为什么会出现那个概念,是什么需求促使了那个概念的产生。
此外,下面我将直接将代码清单列出,要仔细体会,每一行代码的作用,为什么那么写,换个写法行不行。要做UVM的主人,千万不要被UVM的那些概念牵着鼻子走,否则会很被动。要理解TLM,config机制对RTL design人员可能会有点难度,可以请教公司的软件人员。UVM用多了之后,会发现那些概念都是水到渠成事。
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_TRANSACTION_SV
`define MY_TRANSACTION_SV
import uvm_pkg::*;
`define DATA_MAX 255
typedef struct packed
{
logic vld;
logic [7:0] data;
}trans_t;
class my_transaction extends uvm_sequence_item;
`uvm_object_utils(my_transaction)
rand bit vld;
rand bit [7:0] data;
constraint c_trans0
{
vld dist
{
0 :/ 50,
1 :/ 50
};
data dist
{
0 :/ 0,
[1:100] :/ 50,
[101:`DATA_MAX] :/ 50
};
}
constraint c_trans1
{
data >= 5;
}
extern function new(string name = "my_transaction");
extern function trans_t gen_tr;
endclass
function my_transaction::new(string name = "my_transaction");
super.new();
endfunction
function trans_t my_transaction::gen_tr;
trans_t tr;
tr.vld = vld;
tr.data = data;
return tr;
endfunction
`endif //MY_TRANSACTION_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_DRIVER_SV
`define MY_DRIVER_SV
`include "my_transaction.sv"
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver)
virtual IFrx.M rxif;
extern function new(string name = "my_driver",uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
extern task send_pkg(trans_t pkg);
endclass
function my_driver::new(string name = "my_driver",uvm_component parent);
super.new(nam,parent);
endfunction
function void my_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db #(virtual IFrx.M)::get(this,"","abcdefg",rxif);
endfunction
task my_driver::main_phase(uvm_phase phase);
int i = 0;
my_transaction tr;
trans_t trans;
phase.raise_objection(this);
tr = new("tr");
rxif.vld = 1'b0;
rxif.data = 8'b0;
while(!Ttb.rst_n)
@(posedge Ttb.clk);
repeat(10) @(posedge Ttb.clk);
for(i=0;i<10;i++)
begin
assert(tr.randomize());
trans = tr.gen_tr();
send_pkg(trans);
#0.1
if(rxif.vld)
begin
`uvm_info("my_driver",$psprintf("%t,data[%d]:0x%h is drived",$time,i,rxif.data),UVM_LOW);
end
end
repeat(10) @(posedge Ttb.clk);
rxif.vld = 1'b0;
repeat(10) @(posedge Ttb.clk);
phase.drop_objection(this);
endtask
task my_driver::send_pkg(trans_t pkg);
@(posedge Ttb.clk);
rxif.vld <= pkg.vld;
rxif.data <= pkg.data;
endtask
`endif //MY_DRIVER_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_MONITOR_I_SV
`define MY_MONITOR_I_SV
class my_monitor_i extends uvm_monitor;
`uvm_component_utils(my_monitor_i)
virtual IFrx.S rxif;
uvm_analysis_port #(trans_t) port_to_rm;//port to reference model
extern function new(string name = "my_monitor_i",uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
extern task collect_pkg(ref trans_t pkg);
endclass
function my_monitor_i::new(string name = "my_monitor_i",uvm_component parent);
super.new(name,parent);
endfunction
function void my_monitor_i::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db #(virtual IFrx.S)::get(this,"","1234567",rxif);
port_to_rm = new("port_to_rm",this);
endfunction
task my_monitor_i::main_phase(uvm_phase phase);
trans_t pkg;
while(1)
begin
collect_pkg(pkg);
if(pkg.vld)
begin
port_to_rm.write(pkg);
end
end
endtask
task my_monitor_i::collect_pkg(ref trans_t pkg);
@(posedge Ttb.clk);
#0.1
if(rxif.vld)
begin
pkg.vld = 1;
pkg.data = rxif.data;
`uvm_info("my_monitor_i",$psprintf("mon_i::0x%h",pkg.data),UVM_LOW);
end
endtask
`endif //MY_MONITOR_I_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_AGENT_I_SV
`define MY_AGENT_I_SV
`include "driver.sv"
`include "monitor_i.sv"
class my_agent_i extends uvm_agent;
`uvm_component_utils(my_agent_i)
my_driver drv;
my_monitor_i mon;
uvm_analysis_port #(trans_t) port_from_mon;
extern function new(string name = "my_agent_i",uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
endclass
function my_agent_i::new(string name = "my_agent_i",uvm_component parent);
super.new(name,parent);
endfunction
function void my_agent_i::build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv0",this);
mon = my_monitor_i::type_id::create("mon_i",this);
endfunction
function void my_agent_i::connect_phase(uvm_phase phase);
super.connect_phase(phase);
port_from_mon = mon.port_to_rm;
endfunction
`endif //MY_AGENT_I_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_MONITOR_O_SV
`define MY_MONITOR_O_SV
class my_monitor_o extends uvm_monitor;
`uvm_component_utils(my_monitor_o)
virtual IFrx.M txif;
uvm_analysis_port #(trans_t) port_to_sb;//port to scoreboard
extern function new(string name = "my_monitor_o",uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
extern task collect_pkg(ref trans_t pkg);
endclass
function my_monitor_o::new(string name = "my_monitor_o",uvm_component parent);
super.new(name,parent);
endfunction
function void my_monitor_o::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db #(virtual IFrx.M)::get(this,"","7654321",txif);
port_to_sb = new("port_to_sb",this);
endfunction
task my_monitor_o::main_phase(uvm_phase phase);
trans_t pkg;
while(1)
begin
collect_pkg(pkg);
if(pkg.vld)
begin
port_to_sb.write(pkg);
end
end
endtask
task my_monitor_o::collect_pkg(ref trans_t pkg);
@(posedge Ttb.clk);
#0.1
if(txif.vld)
begin
pkg.vld = 1;
pkg.data = txif.data;
`uvm_info("my_monitor_o",$psprintf("mon_o::0x%h",pkg.data),UVM_LOW);
end
endtask
`endif //MY_MONITOR_O_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_AGENT_O_SV
`define MY_AGENT_O_SV
`include "driver.sv"
`include "monitor_o.sv"
class my_agent_o extends uvm_agent;
`uvm_component_utils(my_agent_o)
my_monitor_o mon;
uvm_analysis_port #(trans_t) port_from_mon;
extern function new(string name = "my_agent_o",uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
endclass
function my_agent_o::new(string name = "my_agent_o",uvm_component parent);
super.new(name,parent);
endfunction
function void my_agent_i::build_phase(uvm_phase phase);
super.build_phase(phase);
mon = my_monitor_o::type_id::create("mon_o",this);
endfunction
function void my_agent_o::connect_phase(uvm_phase phase);
super.connect_phase(phase);
port_from_mon = mon.port_to_sb;
endfunction
`endif //MY_AGENT_O_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_REF_MODEL_SV
`define MY_REF_MODEL_SV
class my_ref_model extends uvm_component;
`uvm_component_utils(my_ref_model)
uvm_blocking_get_port #(trans_t) port_in; //from monitor_i
uvm_analysiss_port #(trans_t) port_out; //to monitor_o
extern function new(string name = "my_ref_model",uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
endclass
function my_ref_model::new(string name = "my_ref_model",uvm_component parent);
super.new(name,parent);
endfunction
function void my_ref_model::build_phase(uvm_phase phase);
super.build_phase(phase);
port_in = new("port_in",this);
port_out = new("port_out",this);
endfunction
task my_ref_model::main_phase(uvm_phase phase);
trans_t tr;
port_in.get(tr);
`uvm_info("ref_model",$psprintf("rm_from_mon_i:0x%h",tr.data),UVM_LOW);
port_out.write(tr);
endtask
`endif //MY_REF_MODEL_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_SCOREBOARD_SV
`define MY_SCOREBOARD_SV
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
uvm_blocking_get_port #(trans_t) port_dut;//from dut port
uvm_blocking_get_port #(trans_t) port_rm;//from reference model port
extern function new(string name = "my_scoreboard",uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
endclass
function my_scoreboard::new(string name = "my_scoreboard",uvm_component parent);
super.new(name,parent);
endfunction
function void my_scoreboard::build_phase(uvm_phase phase);
super.build_phase(phase);
port_dut = new("port_dut",this);
port_rm = new("port_rm",this);
endfunction
task my_ref_model::main_phase(uvm_phase phase);
trans_t pkg_dut;
trans_t pkg_rm;
fork
while(1)
begin
port_rm.get(pkg_rm);
`uvm_info("sb",%psprintf(sb from rm:0x%h",pkg_rm.data),UVM_LOW);
end
//==================//
while(1)
begin
port_dut.get(pkg_dut);
`uvm_info("sb",%psprintf(sb from dut:0x%h",pkg_dut.data),UVM_LOW);
end
join
endtask
`endif //MY_SCOREBOARD_SV
/*
* uvm test example
* Rill 2015-05-02
*/
`ifndef MY_ENV_SV
`define MY_ENV_SV
`include "agent_i.sv"
`include "agent_o.sv"
`include "ref_model.sv"
`include "scoreboard.sv"
import uvm_pkg::*;
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_agent_i agent_i;
my_agent_o agent_o;
my_ref_model ref_model;
my_scoreboard scoreboard;
uvm_tlm_analysis_fifo #(trans_t) agent_i_to_ref_model_fifo;
uvm_tlm_analysis_fifo #(trans_t) ref_model_to_scoreboard_fifo;
uvm_tlm_analysis_fifo #(trans_t) agent_o_to_scoreboard_fifo;
extern function new(string name = "my_env",uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern function void connect_phase(uvm_phase phase);
endclass
function my_env::new(string name = "my_env",uvm_component parent);
super.new(name,parent);
endfunction
function void my_env::build_phase(uvm_phase phase);
super.build_phase(phase);
agent_i = my_agent_i::type_id::create("agent_i0",this);
agent_o = my_agent_o::type_id::create("agent_o0",this);
scoreboard = my_scoreboard::type_id::create("scoreboard0",this);
ref_model = my_ref_model::type_id::create("ref_model",this);
agent_i.is_active = UVM_ACTIVE;
agent_o.is_active = UVM_PASSIVE;
agent_i_to_ref_model_fifo = new("agent_i_to_ref_model_fifo",this);
ref_model_to_scoreboard_fifo = new("ref_model_to_scoreboard_fifo",this);
agent_o_to_scoreboard_fifo = new("agent_o_to_scoreboard_fifo",this);
endfunction
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
agent_i.port_from_mon.connect(agent_i_to_ref_model_fifo.analysis_export);
ref_model.port_in.connect(agent_i_to_ref_model_fifo.blocking_get_export);
ref_model.port_out.connext(ref_model_to_scoreboard_fifo.analysis_export);
scoreboard.port_rm.connect(ref_model_to_scoreboard_fifo.blocking_get_export);
agent_o.port_from_mon.connect(agent_o_to_scoreboard_fifo.analysis_export);
scoreboard.port_dut.connect(agent_o_to_scoreboard_fifo.blocking_get_export);
endfunction
`endif
/*
* uvm test example
* Rill 2015-05-02
*/
`include "uvm_macros.svh"
import uvm_pkg::*;
module Ttb;
logic clk;
logic rst_n;
logic tx_dv;
logic [7:0] txd;
IFrx #(.DATA_L(8)) rxif();
IFrx #(.DATA_L(8)) txif();
Mdut dut0
(
.clk (clk),
.rst_n (rst_n),
.rx_if (rxif.S),
.tx_dv (txif.M.vld),
.txd (txif.M.data)
);
initial
begin
clk = 0;
rst_n = 0;
fork
forver #5 clk = ~clk;
join_none
repeat(10) @(posedge clk);
rst_n = 1'b1;
uvm_config_da #(virtual IFrx.M)::set(null,"uvm_test_top.agent_i0.drv0","abcdefg",rxif.M);
uvm_config_da #(virtual IFrx.S)::set(null,"uvm_test_top.agent_i0.mon_i","1234567",rxif.S);
uvm_config_da #(virtual IFrx.M)::set(null,"uvm_test_top.agent_o0.mon_o","7654321",txif.M);
run_test("my_env");
end
endmodule
Makefile:
/*
* uvm test example
* Rill 2015-05-02
*/
UVM_VERBOSITY = UVM_LOW
all:run
export UVM_HOME=/tools/home/INC13.10.001/tools/uvm/uvm_lib/uvm_sv
IUS = irun -cdslib ./cds.lib -hdlvar ./hdl.var -access rwc -uvm -uvmhome ${UVM_HOME} -f vflist -l irun.log -input run.tcl +UVM_NO_RELNOTES -timascale 1ns/10ps -quiet
run:
$(IUS)
clean:
rm -rf INCA* simv* *.log ucli.key
run.tcl:
database -open waves -into waves.shm -default;
probe -create -shm -all -variable -depth all;
run
exit
define myuvm_lib /home/openrisc/my_uvm
define work myuvm_lib
vflist:
-incdir ./src
./src/env.sv
./src/dut.sv
./src/tb.sv
./src/driver.sv
./src/transaction.sv
./src/interface.sv
./src/agent_i.sv
./sec/agent_o.sv
./src/monitor_i.sv
./src/monitor_o.sv
./src/ref_model.sv
./src/scoreboard.sv
一切准备就绪,执行make run,即可看到仿真结果,也可以看到仿真波形(simvision waves.shm/waves.trn &)。
本小节,我们熟悉了UVM的基本使用方法,在以后的使用中可能会逐渐有新的疑问,比如,怎么向driver发送不同类型的transaction?由于DUT行为太复杂(乱序执行),reference model难道也要搞成乱序吗?也要做成周期精确的吗?如果不是乱序的,monitor怎么收集DUT的信息呢?既然是乱序的,scoreboard怎么比较呢?有没有必要用sequencer呢?怎么产生覆盖率高的transaction呢?UVM的不同DUT之间怎么复用呢?怎么让不同transaction自动运行呢?等等这些问题都需要实际解决,那时你会发现,UVM做的工作有点少,还可能再需要增强一点。