通用验证方法学UVM(Universal Verification Methodology)已经成为集成电路设计的验证标准。UVM类构建的库促进了测试用例的搭建,UVM测试用例的每一个元素都是从现有的UVM类派生出来的。
每个类都有仿真阶段,这些仿真阶段作为类的方法,按照一定的顺序执行。其中较为常用的几个UVM阶段如下:
为了在类和变量中实现一些重要的方法,UVM提供了UVM宏。最常见的UVM宏如下:
为了解释一个UVM环境的结构,在测试用例中,将使用一个简单的加法器作为测试设计(DUT)。UVM测试平台如图所示。DUT是与测试台交互以验证其功能的硬件实现。
为了实现DUT的仿真, sequencer产生一系列数据送入DUT。由于sequencer发送高抽象级别的数据包,并且DUT只接受来自接口的数据,所以,driver用来将来自sequencer的数据包,转换成信号送入DUT。
经过接口的数据需要被捕获,以便后续的仿真验证。由于driver只能实现数据包到信号的转换,所以,另外还需要一个模块实现与driver相反的功能,将信号转换成数据包。monitor就是这样的一个模块,收集driver与DUT之间通信的接口信号,并将其转换成数据包,最后送入参考模型中去进行比较。
一个agent通常包含三个组件:一个sequencer序列发生器,一个driver驱动,一个monitor监控。有两种类型的agent:Active Agent包含上述三个组件;Passive Agent只包含上述的monitor监控和driver驱动。agent包含build phase函数,用于去创建层次结果,以及connect phase用于连接模块。
参考模型(refmod)是在RTL实现之前,早期阶段构思的理想的模型。在抽象的高级别上模拟DUT。
comparator类主要用于比较参考模型和DUT之间的数据。参考模型和比较器组成了记分牌,用于检查DUT产生的传输是否正确。一个或者多个agent加上记分牌构成了env类。测试类负责执行测试,创建环境,并将序列连接到序列发生器上。最后,top中实现DUT和testbench的例化。
下面通过对一个加法器验证环境的搭建,来进一步理解UVM中的各个组件。加法器模块的功能验证平台分为以下的模块和类。
接口的构造是专门为封装块之间的通信信号而创建的,modport为模块端口提供方向信息,并控制特定模块内任务和功能的使用。
interface input_if(input clk, rst);
logic [31:0] A, B;
logic valid, ready;
modport port(input clk, rst, A, B, valid, output ready);
endinterface
interface output_if(input clk, rst);
logic [31:0] data;
logic valid, ready;
modport port(input clk, rst, output valid, data, ready);
endinterface
待测设计DUT(design under test)是需要被验证的设计代码。
module adder(input_if.port inter, output_if.port out_inter, output state);
enum logic [1:0] {
INITIAL,WAIT,SEND} state;
always_ff @(posedge inter.clk)
if(inter.rst) begin
inter.ready <= 0;
out_inter.data <= 'x;
out_inter.valid <= 0;
state <= INITIAL;
end
else case(state)
INITIAL: begin
inter.ready <= 1;
state <= WAIT;
end
WAIT: begin
if(inter.valid) begin
inter.ready <= 0;
out_inter.data <= inter.A + inter.B;
out_inter.valid <= 1;
state <= SEND;
end
end
SEND: begin
if(out_inter.ready) begin
out_inter.valid <= 0;
inter.ready <= 1;
state <= WAIT;
end
end
endcase
endmodule: adder
传输数据包的构造是专门为封装块之间通信的数据而创建的,其中的field机制能够将数据包中的数据注册到UVM工厂中,为数据提供copy()、compare()、print()、colne()等函数。
class packet_in extends uvm_sequence_item;
rand integer A;
rand integer B;
`uvm_object_utils_begin(packet_in)
`uvm_field_int(A, UVM_ALL_ON|UVM_HEX)
`uvm_field_int(B, UVM_ALL_ON|UVM_HEX)
`uvm_object_utils_end
function new(string name="packet_in");
super.new(name);
endfunction: new
endclass: packet_in
class packet_out extends uvm_sequence_item;
integer data;
`uvm_object_utils_begin(packet_out)
`uvm_field_int(data, UVM_ALL_ON|UVM_HEX)
`uvm_object_utils_end
function new(string name="packet_out");
super.new(name);
endfunction: new
endclass: packet_out
UVM序列是一个对象,它用于生成仿真的行为。UVM序列不是组件层次结构的一部分。每个UVM序列最终被绑定到一个UVM序列器。多个UVM序列实例可以绑定到同一个UVM序列器。
class sequence_in extends uvm_sequence #(packet_in);
`uvm_object_utils(sequence_in)
function new(string name="sequence_in");
super.new(name);
endfunction: new
task body;
packet_in tx;
forever begin
tx = packet_in::type_id::create("tx");
start_item(tx);
assert(tx.randomize());
finish_item(tx);
end
endtask: body
endclass: sequence_in
UVM序列器作为仲裁器,用于控制来自多个仿真序列的事务流。更具体地说,UVM序列器控制,由一个或多个UVM序列生成的事务流。
class sequencer extends uvm_sequencer #(packet_in);
`uvm_component_utils(sequencer)
function new (string name = "sequencer", uvm_component parent = null);
super.new(name, parent);
endfunction
endclass: sequencer
驱动器(driver)从序列器(sequencer),接收序列(sequence)产生的传输数据包(transaction),并在DUT接口上应用(驱动)它。因此,驱动器driver将传输级别的数据包仿真,转换成管脚信号级别的仿真。它还有一个TLM接口,用于接收来自序列器sequencer的传输数据包transaction,去驱动DUT上的接口信号。
typedef virtual input_if input_vif;
class driver extends uvm_driver #(packet_in);
`uvm_component_utils(driver)
input_vif vif;
event begin_record, end_record;
function new(string name = "driver", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif));
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
reset_signals();
get_and_drive(phase);
record_tr();
join
endtask
virtual protected task reset_signals();
wait (vif.rst === 1);
forever begin
vif.valid <= '0;
vif.A <= 'x;
vif.B <= 'x;
@(posedge vif.rst);
end
endtask
virtual protected task get_and_drive(uvm_phase phase);
wait(vif.rst === 1);
@(negedge vif.rst);
@(posedge vif.clk);
forever begin
seq_item_port.get(req);
-> begin_record;
drive_transfer(req);
end
endtask
virtual protected task drive_transfer(packet_in tr);
vif.A = tr.A;
vif.B = tr.B;
vif.valid = 1;
@(posedge vif.clk)
while(!vif.ready)
@(posedge vif.clk);
-> end_record;
@(posedge vif.clk); //hold time
vif.valid = 0;
@(posedge vif.clk);
endtask
virtual task record_tr();
forever begin
@(begin_record);
begin_tr(req, "driver");
@(end_record);
end_tr(req);
end
endtask
endclass
typedef virtual output_if output_vif;
class driver_out extends uvm_driver #(packet_out);
`uvm_component_utils(driver_out)
output_vif vif;
function new(string name = "driver_out", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif));
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
reset_signals();
drive(phase);
join
endtask
virtual protected task reset_signals();
wait (vif.rst === 1);
forever begin
vif.ready <= '0;
@(posedge vif.rst);
end
endtask
virtual protected task drive(uvm_phase phase);
wait(vif.rst === 1);
@(negedge vif.rst);
forever begin
@(posedge vif.clk);
vif.ready <= 1;
end
endtask
endclass
监视器(monitor)对DUT接口进行采样,并捕获事务中的信息,构成传输数据包(transaction),这些传输数据包被发送到UVM测试台以进行进一步分析。因此,与驱动器(driver)类似,但过程相反,它实现的是将管脚信号上的信号收集转换成传输数据包。
UVM监视器可以在内部对所产生的事务执行一些处理(例如覆盖率收集、检查、日志记录、记录等等)。
class monitor extends uvm_monitor;
input_vif vif;
event begin_record, end_record;
packet_in tr;
uvm_analysis_port #(packet_in) item_collected_port;
`uvm_component_utils(monitor)
function new(string name, uvm_component parent);
super.new(name, parent);
item_collected_port = new ("item_collected_port", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif));
tr = packet_in::type_id::create("tr", this);
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
collect_transactions(phase);
record_tr();
join
endtask
virtual task collect_transactions(uvm_phase phase);
wait(vif.rst === 1);
@(negedge vif.rst);
forever begin
do begin
@(posedge vif.clk);
end while (vif.valid === 0 || vif.ready === 0);
-> begin_record;
tr.A = vif.A;
tr.B = vif.B;
item_collected_port.write(tr);
@(posedge vif.clk);
-> end_record;
end
endtask
virtual task record_tr();
forever begin
@(begin_record);
begin_tr(tr, "monitor");
@(end_record);
end_tr(tr);
end
endtask
endclass
class monitor_out extends uvm_monitor;
`uvm_component_utils(monitor_out)
output_vif vif;
event begin_record, end_record;
packet_out tr;
uvm_analysis_port #(packet_out) item_collected_port;
function new(string name, uvm_component parent);
super.new(name, parent);
item_collected_port = new ("item_collected_port", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif));
tr = packet_out::type_id::create("tr", this);
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
collect_transactions(phase);
record_tr();
join
endtask
virtual task collect_transactions(uvm_phase phase);
wait(vif.rst === 1);
@(negedge vif.rst);
forever begin
do begin
@(posedge vif.clk);
end while (vif.valid === 0 || vif.ready === 0);
-> begin_record;
tr.data = vif.data;
item_collected_port.write(tr);
@(posedge vif.clk);
-> end_record;
end
endtask
virtual task record_tr();
forever begin
@(begin_record);
begin_tr(tr, "monitor_out");
@(end_record);
end_tr(tr);
end
endtask
endclass
代理(agent)是一个分层组件,它将处理特定DUT接口的其他验证组件组合在一起。一个典型的代理包括:一个sequencer,用于管理仿真流的UVM序列器;一个driver,用于在DUT接口上驱动仿真的驱动器;一个monitor,用于监视DUT的接口。代理可能包括其他组件,如覆盖率收集器、协议检查器、TLM模型等。
class agent extends uvm_agent;
sequencer sqr;
driver drv;
monitor mon;
uvm_analysis_port #(packet_in) item_collected_port;
`uvm_component_utils(agent)
function new(string name = "agent", uvm_component parent = null);
super.new(name, parent);
item_collected_port = new("item_collected_port", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = monitor::type_id::create("mon", this);
sqr = sequencer::type_id::create("sqr", this);
drv = driver::type_id::create("drv", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.item_collected_port.connect(item_collected_port);
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass: agent
class agent_out extends uvm_agent;
driver_out drv;
monitor_out mon;
uvm_analysis_port #(packet_out) item_collected_port;
`uvm_component_utils(agent_out)
function new(string name = "agent_out", uvm_component parent = null);
super.new(name, parent);
item_collected_port = new("item_collected_port", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = monitor_out::type_id::create("mon_out", this);
drv = driver_out::type_id::create("drv_out", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
mon.item_collected_port.connect(item_collected_port);
endfunction
endclass: agent_out
记分板(scoreboard)的主要功能是检查DUT的行为是否正确。记分板接收的实际数据包,来自代理(agent)送入DUT的输入输出,接收的期望数据包,来自参考模型,最后在记分板中比较实际的数据包和期望的数据包。
class comparator #(type T = packet_out) extends uvm_scoreboard;
typedef comparator #(T) this_type;
`uvm_component_param_utils(this_type)
const static string type_name = "comparator #(T)";
uvm_put_imp #(T, this_type) from_refmod;
uvm_analysis_imp #(T, this_type) from_dut;
typedef uvm_built_in_converter #( T ) convert;
int m_matches, m_mismatches;
T exp;
bit free;
event compared, end_of_simulation;
function new(string name, uvm_component parent);
super.new(name, parent);
from_refmod = new("from_refmod", this);
from_dut = new("from_dut", this);
m_matches = 0;
m_mismatches = 0;
exp = new("exp");
free = 1;
endfunction
virtual function string get_type_name();
return type_name;
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
@(end_of_simulation);
phase.drop_objection(this);
endtask
virtual task put(T t);
if(!free) @compared;
exp.copy(t);
free = 0;
@compared;
free = 1;
endtask
virtual function bit try_put(T t);
if(free) begin
exp.copy(t);
free = 0;
return 1;
end
else return 0;
endfunction
virtual function bit can_put();
return free;
endfunction
virtual function void write(T rec);
if (free)
uvm_report_fatal("No expect transaction to compare with", "");
if(!(exp.compare(rec))) begin
uvm_report_warning("Comparator Mismatch", "");
m_mismatches++;
end
else begin
uvm_report_info("Comparator Match", "");
m_matches++;
end
if(m_matches+m_mismatches > 100)
-> end_of_simulation;
-> compared;
endfunction
endclass
仿真环境env是一个分层组件,它将其他相互关联的验证组件组合在一起。通常在仿真环境env中实例化的典型组件有代理(agent)、记分牌(scoreboard),甚至其他仿真环境。
顶层仿真环境封装了所有针对DUT的验证组件。
class env extends uvm_env;
agent mst;
refmod rfm;
agent_out slv;
comparator #(packet_out) comp;
uvm_tlm_analysis_fifo #(packet_in) to_refmod;
`uvm_component_utils(env)
function new(string name, uvm_component parent = null);
super.new(name, parent);
to_refmod = new("to_refmod", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
mst = agent::type_id::create("mst", this);
slv = agent_out::type_id::create("slv", this);
rfm = refmod::type_id::create("rfm", this);
comp = comparator#(packet_out)::type_id::create("comp", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connect MST to FIFO
mst.item_collected_port.connect(to_refmod.analysis_export);
// Connect FIFO to REFMOD
rfm.in.connect(to_refmod.get_export);
//Connect scoreboard
rfm.out.connect(comp.from_refmod);
slv.item_collected_port.connect(comp.from_dut);
endfunction
virtual function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
endfunction
virtual function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info(get_type_name(), $sformatf("Reporting matched %0d", comp.m_matches), UVM_NONE)
if (comp.m_mismatches) begin
`uvm_error(get_type_name(), $sformatf("Saw %0d mismatched samples", comp.m_mismatches))
end
endfunction
endclass
测试用例是测试台中最顶层的组件。测试用例通常执行三个主要功能:实例化顶层环境、配置环境(通过工厂覆盖或配置数据库),以及通过在环境中调用序列来进行仿真。
通常,有一个带有UVM环境实例化和其他公共项的基本UVM测试。然后,其他单独的测试将扩展这个基本测试,并以不同的方式配置环境或选择不同的序列运行。
测试用例是在运行时动态实例化的,允许UVM测试台只编译一次,并与许多不同的测试一起运行。
class simple_test extends uvm_test;
env env_h;
sequence_in seq;
`uvm_component_utils(simple_test)
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env_h = env::type_id::create("env_h", this);
seq = sequence_in::type_id::create("seq", this);
endfunction
task run_phase(uvm_phase phase);
seq.start(env_h.mst.sqr);
endtask: run_phase
endclass
顶层主要实例化DUT模块和测试类,并配置它们之间的连接。
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "./input_if.sv"
`include "./output_if.sv"
`include "./adder.sv"
`include "./packet_in.sv"
`include "./packet_out.sv"
`include "./sequence_in.sv"
`include "./sequencer.sv"
`include "./driver.sv"
`include "./driver_out.sv"
`include "./monitor.sv"
`include "./monitor_out.sv"
`include "./agent.sv"
`include "./agent_out.sv"
`include "./refmod.sv"
`include "./comparator.sv"
`include "./env.sv"
`include "./simple_test.sv"
//Top
module top;
logic clk;
logic rst;
initial begin
clk = 0;
rst = 1;
#22 rst = 0;
end
always #5 clk = !clk;
logic [1:0] state;
input_if in(clk, rst);
output_if out(clk, rst);
adder sum(in, out, state);
initial begin
`ifdef INCA
$recordvars();
`endif
`ifdef VCS
//$vcdpluson;
$fsdbDumpfile("test.fsdb");
$fsdbDumpSVA();
$fsdbDumpvars();
$fsdbDumpMDA();
`endif
`ifdef QUESTA
$wlfdumpvars();
set_config_int("*", "recording_detail", 1);
`endif
uvm_config_db#(input_vif)::set(uvm_root::get(), "*.env_h.mst.*", "vif", in);
uvm_config_db#(output_vif)::set(uvm_root::get(), "*.env_h.slv.*", "vif", out);
run_test("simple_test");
end
endmodule
SystemVerilog直接编程接口(DPI)是SystemVerilog调用外部语言(如C、c++等)函数的接口。DPI由两个层组成:SystemVerilog层和外语层,它们彼此隔离。下面给出了refmod的代码,以说明DPI的用法。sum()函数在文件external.cpp中定义,一旦在refmod中调用它,就应该在sum()函数的定义之前添加关键字“external C”
#include
#include
extern "C" int sum(int a, int b){
return a+b;
}
import "DPI-C" context function int sum(int a, int b);
class refmod extends uvm_component;
`uvm_component_utils(refmod)
packet_in tr_in;
packet_out tr_out;
integer a, b;
uvm_get_port #(packet_in) in;
uvm_put_port #(packet_out) out;
function new(string name = "refmod", uvm_component parent);
super.new(name, parent);
in = new("in", this);
out = new("out", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
tr_out = packet_out::type_id::create("tr_out", this);
endfunction: build_phase
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
forever begin
in.get(tr_in);
tr_out.data = sum(tr_in.A, tr_in.B);
out.put(tr_out);
end
endtask: run_phase
endclass: refmod
comp: clean
g++ -c external.cpp -o external.o
vcs -full64 -sverilog top.sv -dpi -ntb_opts uvm -debug_pp -timescale=1ns/10ps external.o -debug_access+cbk -lca -cm tgl+line+fsm+cond+branch
sim:
./simv +UVM_TR_RECORD +UVM_VERBOSITY=HIGH +UVM_TESTNAME=simple_test -lca -cm tgl+line+fsm+cond+branch
all: comp sim
dbg: clean
g++ -c external.cpp -o external.o
vcs -full64 -sverilog top.sv -dpi -ntb_opts uvm -debug_pp -timescale=1ns/10ps external.o -debug_access+cbk -debug_access+all -kdb -lca
$ ./simv +UVM_TR_RECORD +UVM_VERBOSITY=HIGH +UVM_TESTNAME=simple_test -gui=verdi
clean:
rm -rf DVEfiles csrc simv simv.daidir ucli.key .vlogansetup.args .vlogansetup.env .vcs_lib_lock simv.vdb AN.DB vc_hdrs.h *.diag *.vpd *tar.gz external.o inter.fsdb novas.conf novas_dump.log novas.rc test.fsdb verdiLog
view_waves:
dve &
verdi:
verdi -sverilog -ntb_opts uvm-1.2 -timescale=1ns/10ps top.sv -f filelist.f +incdir+./ -ssf ./test.fsdb &
urg_gui:
urg -dir *.vdb -format both -report urgReportALL; \
firefox urgReportALL/tests.html &
comp函数先通过g++编译C程序,再通过VCS进行编译,其中-debug_access+cbk选项对静态网络、寄存器和变量启用基于PLI的回调。-cm tgl+line+fsm+cond+branch选项开启代码翻转覆盖率、行覆盖率、状态机覆盖率、条件覆盖率、分支覆盖率。
sim函数执行VCS仿真。
dbg函数用于VCS和verdi联调,编译里边加入了-debug_access+all -kdb –lca选项,仿真里边加入了-gui=verdi选项。
verdi函数用于启动verdi查看波形。
urg_gui函数用于查看代码覆盖率。
上述完整代码下载链接如下:
https://github.com/AFEI1100/easyUVM-master.git