典型的UVM验证平台
==phase:==UVM由phase来管理验证平台的运行,这些phase统一以xxx_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数。
objection 机制:uvm中通过objection机制来控制验证平台的关闭。在每个phase中,uvm会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真,如果没有,则马上结束当前phase。
raise_objection必须在main_phase中第一个消耗仿真时间的语句之前
config_db机制:在config_db机制中,分为set和get两步操作。set可以理解为寄信,get相当于收信。config_db的set和get函数都有四个参数,
(1)set函数的第四个参数表示要传递哪一个interface。get函数的第四个参数表示要把得到的interface传递给哪一个变量。
(2)set函数的第二个参数表示的时路径索引,uvm通过run_test创建的实例名字为uvm_test_top,路径索引的名字为uvm_test_top
(3)第三个参数为一个字符串,为定义好的标签,发送和接收端应该保持一致
简单的DUT
module dut(clk,rst_n,rxd,rx_dv,txd,tx_en);
input clk;
input rst_n;
input [7:0] rxd;
input rx_dv;
output reg [7:0] txd;
output reg tx_en;
always @(posedge clk) begin
if (!rst_n)begin
txd <= 8'b0;
tx_en <= 1'b0;
end
else begin
txd <= rxd;
tx_en <= rx_dv;
end
end
endmodule
简单的接口
interface my_if(input clk, input rst_n);
logic [7:0] data;
logic valid;
endinterface
顶层top_tb的搭建
通过include 将uvm_macros.svh文件包含进来,里面包含了众多宏定义。
通过import语句将整个uvm_pkg导入到验证平台,只有导入这个库编译器在编译my_driver.sv文件时才会认识其中的uvm_driver等类名。
`timescale 1ps/1ps
`include "uvm_macros.svh" // include the definations of the pakages in uvm
import uvm_pkg::*; // including uvm environment
`include "my_transaction.sv"
`include "driver.sv"
`include "my_monitor.sv"
`include "my_scoreboard.sv"
`include "my_sequence.sv"
`include "my_sequencer.sv"
`include "my_agent.sv"
`include "my_model.sv"
`include "my_env.sv"
`include "base_test.sv"
module top_tb;
reg clk;
reg rst_n;
my_if input_if(clk,rst_n);
my_if output_if(clk,rst_n);
dut my_dut(
.clk(clk),
.rst_n(rst_n),
.rxd(input_if.data),
.rx_dv(input_if.valid),
.txd(output_if.data),
.tx_en(output_if.valid)
);
// three threads in parallel
initial begin
run_test("base_test");
end
initial begin
uvm_config_db #(virtual my_if)::set(null,"uvm_test_top.env.i_agt.drv","vif",input_if); // connect the input_if
uvm_config_db #(virtual my_if)::set(null,"uvm_test_top.env.i_agt.mon","vif",input_if);
uvm_config_db #(virtual my_if)::set(null,"uvm_test_top.env.o_agt.mon","vif",output_if);
end
initial begin
clk = 0;
forever begin
# 100 clk = ~clk;
end
end
initial begin
rst_n =1'b0;
#1000;
rst_n = 1'b1;
end
endmodule
run_test 创建一个名字为uvm_test_top的实例。只有使用了uvm_component_utils宏注册过之后才能使用该功能。
uvm_config_db :用来连接接口
(1)transaction
transaction实际上就是一个包,包含许多参数和变量。
post_randomize是system verilog中提供的一个函数,当某个类的实例的randomize函数被调用后,post_randomize会紧随其后无条件被调用。
`ifndef MY_TRANSACTION_SV
`define MY_TRANSACTION_SV
class my_transaction extends uvm_sequence_item;
rand bit [47:0] dmac ; // mac destination address
rand bit [47:0] smac ; // mac source address
rand bit [15:0] ether_type ; // the type of mac
rand byte pload[]; // the data it carry
rand bit [31:0] crc ;
// `uvm_object_utils(my_transaction)
//field_automation
`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
//constraint
constraint pload_cons{
pload.size >= 46;
pload.size <= 1500;
}
function bit [31:0] calc_crc();
return 32'h0;
endfunction
function void post_randomize(); // after the randomize
crc = calc_crc;
endfunction
function new(string name = "my_transaction ");
super.new(name);
endfunction
endclass
`endif
transaction 的基类是uvm_sequence_item,应该使用uvm_object_utils来实现factory机制。
field_automation机制: 使用该机制,可以直接调用transaction的copy、compare、print函数,无需自己定义。可以先使用uvm_object_utils_begin 和uvm_object_utils_end来实现my_transaction的factory注册。在这两个宏中间,使用uvm_field宏注册所有字段。pack_bytes可以将transaction中所有的字段变成byte流。使用unpack_bytes函数可以将byte流转换为tr中的各个字段。
(2)driver类的搭建
uvm中的driver应该派生自uvm_driver。uvm_driver的类的new函数有两个参数,一个是string 类型的name,一个是uvm_component类型的parent。
每一个派生自uvm_component或其派生类的类在其new函数中要指明两个参数name和parent。
driver所作的事情几乎都在main_phase中完成。
`ifndef MY_DRIVER_SV
`define MY_DRIVER_SV
class my_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(my_driver)
virtual my_if vif;
function new(string name = "my_driver",uvm_component parent = null); //what is the function of the uvm_parent
super.new(name,parent);
`uvm_info("my_driver","new is called ",UVM_LOW)
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("my_driver","build_phase is called",UVM_LOW);
if(!uvm_config_db#(virtual my_if)::get(this,"","vif",vif)) //interface connect
`uvm_fatal("my_driver","virtual interface must be set for vif!!!")
endfunction
extern virtual task main_phase(uvm_phase phase);
extern virtual task drive_one_pkt(my_transaction tr);
endclass
task my_driver::main_phase(uvm_phase phase);
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1)begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
task my_driver::drive_one_pkt(my_transaction tr);
byte unsigned data_q[];
int data_size;
data_size = tr.pack_bytes(data_q) /8;
`uvm_info("my_driver","begin to drive one pkt",UVM_LOW);
repeat(3) @(posedge vif.clk);
for(int i =0;i< data_size;i++)begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q[i];
end
@(posedge vif.clk);
vif.valid <=1'b0;
`uvm_info("my_driver","end drive one pkt",UVM_LOW)
endtask
`endif
`uvm_info宏的功能与display语句的功能类似。有三个参数,第一个是字符串,用于把打印的信息归类。第二个参数也是字符串,是具体需要打印的信息。第三个参数则是冗余级别,信息非常重要则设置为UVM_LOW,信息可有可无则设置为UVM_HIGH,介于两者中间则为UVM_MEDIUM。
get_full_name函数可以获得路径索引。
factory机制的实现被集成在了一个宏中:uvm_component_utils。该宏所做的事情非常多,其一就是my_driver登记在UVM内部的一张表。
所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册。
build_phase也是UVM中内建的一个phase,其在new函数之后main_phase之前执行。在build_phase中主要通过config_db的set和get操作来传递一些数据以及实例化成员变量等。(build_phase的执行顺序遵照从树根到树叶的顺序,当把整棵树的build_phase都执行完毕后,再执行后面的phase)
`uvm_fatal宏是一个类似于uvm_info的宏,只有两个参数,与uvm_info宏的前两个参数的意义一样。
(3)monitor
所有的monitor类应该派生自uvm_monitor,其应该使用uvm_component_utils宏注册。
`ifndef MY_MONITOR_SV
`define MY_MONITOR_SV
class my_monitor extends uvm_monitor;
virtual my_if vif;
uvm_analysis_port #(my_transaction) ap;
`uvm_component_utils(my_monitor)
function new(string name = "my_monitor",uvm_component parent = null);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual my_if)::get(this,"","vif",vif))
`uvm_fatal("my_monitor","virtual interface must be set for vif!!!")
ap = new("ap",this);
endfunction
extern virtual task main_phase(uvm_phase phase);
extern task collect_one_pkt(my_transaction tr);
endclass
task my_monitor:: main_phase(uvm_phase phase);
my_transaction tr;
while(1) begin
tr = new("tr");
collect_one_pkt(tr);
ap.write(tr);
end
endtask
task my_monitor:: collect_one_pkt(my_transaction tr);
byte unsigned data_q[$];
byte unsigned data_array[];
logic [7:0] data;
logic valid = 0 ;
int data_size;
while(1)begin
@(posedge vif.clk);
if(vif.valid)break;
end
`uvm_info("my_monitor","begin to collect one pkt",UVM_LOW)
while(vif.valid)begin
data_q.push_back(vif.data);
@(posedge vif.clk);
end
data_size = data_q.size();
data_array = new[data_size];
for (int i =0;i<data_size;i++)begin
data_array[i] = data_q[i];
end
tr.pload = new[data_size - 18];
data_size = tr.unpack_bytes(data_array)/8;
`uvm_info("my_monitor","end collect one pkt,print it:",UVM_LOW)
tr.print();
endtask
`endif
(4)agent
所有的agent都要派生自uvm_agent类,且其本身是一个component,应该使用uvm_component_utils宏来实现factory注册。
is_active是uvm_agent的一个成员变量,其有两个值UVM_PASSIVE和UVM_ACTIV。默认为UVM_ACTIVE。UVM_PASSIVE表示不需要驱动信号。
`ifndef MY_AGENT_SV
`define MY_AGENT_SV
class my_agent extends uvm_agent; // encapsulated driver and monitor to represent a protocol
my_driver drv;
my_monitor mon;
my_sequencer sqr;
uvm_analysis_port #(my_transaction) ap;
function new(string name,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);
`uvm_component_utils(my_agent)
endclass
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
mon = my_monitor::type_id::create("mon",this);
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);
end
ap = mon.ap;
endfunction
`endif
在agent中主要任务是在build_phase实例化各个组件,并且在connect将driver和sequencer联系起来。
(5)reference model
在UVM中,通常使用TLM(Transaction Level Modeling)实现component 之间transaction级别的通信。
思考两点,数据是如何发送的?数据是如何接收的?参考systemverilog中的信箱
数据的发送:使用uvm_analysis_port ,这是一个参数化的类,其参数就是需要传递的数据的类型。在声明完之后,还需要在build_phase中将其实例化。write是uvm_analysis_port的一个内建函数,可以使用write函数将数据写入到实例中。
数据的接收:使用uvm_blocking_get_port,这也是一个参数化的类。在声明完成之后也需要在build_phase中进行实例化。通过get任务可以接收传来的transaction。
`ifndef MY_MODEL_SV
`define MY_MODEL_SV
class my_model extends uvm_component;
uvm_blocking_get_port #(my_transaction) port;
uvm_analysis_port #(my_transaction) ap;
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);
`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;
my_transaction new_tr;
super.main_phase(phase);
while (1) begin
port.get(tr);
new_tr = new("new_tr");
new_tr.copy(tr);
`uvm_info("my_model","get one transaction, copy and print it:",UVM_LOW)
new_tr.print();
ap.write(new_tr);
end
endtask
`endif
在定义好各自端口之后,还需要再my_env中使用FIFO将两个端口联系在一起。
FIFO的类型是uvm_tlm_analysis_fifo,其本身也是一个参数化的类。之后,再connect_phase中将FIFO分别与my_monitor中的analysis_port和my_model中的blocking_get_port相连。
(6)env
所有的env应该派生自uvm_env,使用uvm_component_utils宏来实现factory的注册。
在组件中的实例化都应该使用factory机制,即使用type_name::type_id::create方式。
`ifndef MY_ENV_SV
`define MY_ENV_SV
class my_env extends uvm_env;
my_agent i_agt;
my_agent o_agt;
my_model mdl;
my_scoreboard scb;
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;
uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;
`uvm_component_utils(my_env)
function new (string name = "my_env",uvm_component parent);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
agt_mdl_fifo = new("agt_mdl_fifo",this);
mdl_scb_fifo = new("mdl_scb_fifo",this);
agt_scb_fifo = new("agt_scb_fifo",this);
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);
endfunction
function void 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
endclass
`endif
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;定义一个FIFO,之后在connect_phase中,将各个组件的通信端口进行连接。
(7)Scoreboard
所有的scoreboard类应该派生自uvm_scoreboard类。
`ifndef MY_SCOREBOARD_SV
`define MY_SCOREBOARD_SV
class my_scoreboard extends uvm_scoreboard;
my_transaction expect_queue[$];
uvm_blocking_get_port #(my_transaction) exp_port; //expected value
uvm_blocking_get_port #(my_transaction) act_port; // actual value
`uvm_component_utils(my_scoreboard)
extern function new(string name,uvm_component parent = null);
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
`uvm_info("my_scoreboard","Compare Successfully",UVM_LOW)
end
else begin
`uvm_info("my_scoreboard","compare failed",UVM_LOW)
$display("the expect pkt is");
tmp_tran.print();
$display("the actual pkt is");
get_actual.print();
end
end
else begin
`uvm_error("my_scoreboard","received from DUT ,while expect que us is empty");
$display("the unexpected pkt is");
get_actual.print();
end
end
join
endtask
`endif
(8)sequence机制
sequence机制用于产生激励,在一个规范化的UVM验证平台中,driver只负责驱动transaction,而不负责产生Transaction。sequence机制有两大组成部分,一是sequence,二是sequencer。
sequencer派生自uvm_sequencer,并且使用uvm_component_utils宏来注册到factory中。
sequence不属于验证平台的任何一部分,但其与sequencer之间有密切的联系。只有在sequencer的帮助下,sequence产生的transaction才能最终送给driver。
每一个sequence都派生自uvm_sequence。每一个sequence都有一个body任务,当一个sequence启动时,会自动执行body中的代码。
`uvm_do宏,有如下作用(1)创建一个my_transaction实例my_trans (2)将其随机化 (3)最终将其送个sequencer
如何连接driver和sequencer之间的联系通道?uvm_driver中有成员变量seq_item_port。uvm_sequencer中有成员变量,seq_item_export。在agent中使用connect函数将其联系在一起。之后,在driver中可以通过get_next_item任务向sequencer申请新的transaction。
以default_sequence的方式启动sequence:在某个component的build_phase中使用uvm_config_db::set进行设置。
uvm_config_db #(uvm_object_wrapper)::set(this,"i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get());
//该函数的第一个和第二个参数是启动sequence的路径,后面两个参数是已经规定好的
在命令行加上+UVM_TEST_NAME= my_case1可以选择仿真的测试案例。
`ifndef MY_SEUQENCE_SV
`define MY_SEUQENCE_SV
class my_sequence extends uvm_sequence #(my_transaction);
my_transaction my_trans;
function new(string name = "my_sequence");
super.new(name);
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat(10)begin
`uvm_do(my_trans);
end
#1000;
if(starting_phase != null);
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(my_sequence)
endclass
`endif
`ifndef MY_SEQUENCER_SV
`define MY_SEQUENCER_SV
class my_sequencer extends uvm_sequencer #(my_transaction);
function new (string name,uvm_component parent);
super.new(name,parent);
endfunction
`uvm_component_utils(my_sequencer)
endclass
`endif
(9)base_test
`ifndef BASE_TEST_SV
`define BASE_TEST_SV
class base_test extends uvm_test;
my_env env;
function new(string name = "base_test",uvm_component parent = null );
super.new(name,parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void report_phase(uvm_phase phase);
`uvm_component_utils(base_test)
endclass
function void base_test::build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env",this);
uvm_config_db #(uvm_object_wrapper)::set(this,"env.i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get());//start sqeuence
endfunction
function void base_test::report_phase(uvm_phase phase);
uvm_report_server server;
int err_num;
super.report_phase(phase);
server = get_report_server();
err_num = server.get_severity_count(UVM_ERROR);
if(err_num !=0 )begin
$display("test case failed");
end
else begin
$display("test case passed");
end
endfunction
`endif
Makefile
#Makefile for lab1
RTL = ../rtl/dut.v
SVTB = ./top_tb.sv ./my_if.sv
SEED = 1
# code coverage command
CM = -cm line+cond+fsm+branch+tgl
CM_NAME = -cm_name simv
CM_DIR = -cm_dir ./covdir.vdb
#-cm < coveragetype > :打开对应类型覆盖率,例如 -cm cond+tgl+lin+fsm+path为统计所有覆盖率。
#-cm_name:统计覆盖率文件名字。
#-cm_dir:指定生成.vdb文件目录。
#-cm_log+filename.log:记录仿真过程中log信息。
#-cm_nocasedef: 在统计case语句的条件覆盖率时,不考虑default条件未达到的情况。
#-cm_hier xxx.cfg:通过.cfg文件选择要查看覆盖率的模块或文件。
test: compile run
run:
./simv -l simv.log +ntb_random_seed =$(SEED) ${CM} ${CM_NAME} ${CM_DIR}
compile:
vcs -full64 -cpp g++-4.8 -cc gcc-4.8 -LDFLAGS -Wl,--no-as-needed -ntb_opts uvm -timescale=1ns/100ps -sverilog -debug_all $(SVTB) $(RTL) ${CM} ${CM_NAME} ${CM_DIR}
dve:
dve &
debug:
./simv -l simv.log -gui -tbug +ntb_random_seed =$(SEED)
clean:
rm -rf simv* csrc* *.tmp *.vpd *.key *.log DVE* covdir* vc_*
nuke:clean
rm -rf *.v* *.sv include .*.lock .*.old DVE* *.tcl *.h
cov:
dve -full64 -covdir *.vdb &
urg:
urg -dir covdir.vdb -report both