一:验证平台代码
DUT是一个简单的存储器。就六个信号,时钟信号clk,复位信号reset(高有效),读使能信号rd_en,写使能信号wr_en,写数据信号wdata,读数据信号rdata。
对于写操作:
address, wr_en和wdata 在同一时钟进行驱动。
对于读操作:
address和rd_en在同一时钟进行驱动,系统在下一时钟出现反应。
/*
-----------------
| |
addr ---->| |
| |------> rdata
| Memory Model |
wdata ---->| |
| |
-----------------
^ ^
| |
wr_en rd_en
-------------------------------------------------------------------------- */
module memory
#( parameter ADDR_WIDTH = 2,
parameter DATA_WIDTH = 8 ) (
input clk,
input reset,
//control signals
input [ADDR_WIDTH-1:0] addr,
input wr_en,
input rd_en,
//data signals
input [DATA_WIDTH-1:0] wdata,
output reg [DATA_WIDTH-1:0] rdata
);
// reg [DATA_WIDTH-1:0] rdata;
//Memory
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH];
//Reset
always @(posedge reset)
for(int i=0;i<2**ADDR_WIDTH;i++) mem[i]=8'hFF;
// Write data to Memory
always @(posedge clk)
if (wr_en) mem[addr] <= wdata;
// Read data from memory
always @(posedge clk)
if (rd_en) rdata <= mem[addr];
endmodule
利用uvm平台搭建这个mem的验证平台。
验证平台结构图如下:
首先定义验证平台的interface。
`ifndef INTERFACE_SV
`define INTERFACE_SV
interface mem_if(input logic clk,reset);
//---------------------------------------
//declaring the signals
//---------------------------------------
logic [1:0] addr;
logic wr_en;
logic rd_en;
logic [7:0] wdata;
logic [7:0] rdata;
//---------------------------------------
//driver clocking block
//---------------------------------------
clocking driver_cb @(posedge clk);
default input #1 output #1;//采样在时钟前,驱动时候在时钟上升沿后。
output addr;
output wr_en;
output rd_en;
output wdata;
input rdata;
endclocking
//---------------------------------------
//monitor clocking block
//---------------------------------------
clocking monitor_cb @(posedge clk);
default input #1 output #1;
input addr;
input wr_en;
input rd_en;
input wdata;
input rdata;
endclocking
//---------------------------------------
//driver modport
//---------------------------------------
modport DRIVER (clocking driver_cb,input clk,reset);
//---------------------------------------
//monitor modport
//---------------------------------------
modport MONITOR (clocking monitor_cb,input clk,reset);
endinterface
`endif
1,产生seq_item。
`ifndef SEQ_ITEM_SV
`define SEQ_ITEM_SV
import uvm_pkg::*;
`include "uvm_macros.svh"
class mem_seq_item extends uvm_sequence_item;
//---------------------------------------
//data and control fields
//---------------------------------------
rand bit [1:0] addr;
rand bit wr_en;
rand bit rd_en;
rand bit [7:0] wdata;
bit [7:0] rdata;
//---------------------------------------
//Utility and Field macros
//---------------------------------------
`uvm_object_utils_begin(mem_seq_item)
`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_object_utils_end
//---------------------------------------
//Constructor
//---------------------------------------
function new(string name = "mem_seq_item");
super.new(name);
endfunction
//---------------------------------------
//constaint, to generate any one among write and read
//---------------------------------------
constraint wr_rd_c { wr_en != rd_en; };
endclass
`endif
seq_item类继承与uvm_sequence_item,在这个类中定义产生的sequence的类型,并且利用域的自动化(方便后边调用比较,拷贝函数),并且做了简单的随机会约束。
2,定义sequence类,此类继承与uvm_sequence。
//=========================================================================
// mem_sequence - random stimulus
//=========================================================================
`include "seq_item.sv"
import uvm_pkg::*;
`include "uvm_macros.svh"
class mem_sequence extends uvm_sequence#(mem_seq_item);
`uvm_object_utils(mem_sequence)
//---------------------------------------
//Constructor
//---------------------------------------
function new(string name = "mem_sequence");
super.new(name);
endfunction
//`uvm_declare_p_sequencer(mem_sequencer)
//---------------------------------------
// create, randomize and send the item to driver
//---------------------------------------
virtual task body();
repeat(2) begin
req = mem_seq_item::type_id::create("req");
wait_for_grant();
req.randomize();
send_request(req);
wait_for_item_done();
end
endtask
endclass
//=========================================================================
//=========================================================================
// write_sequence - "write" type
//=========================================================================
class write_sequence extends uvm_sequence#(mem_seq_item);
`uvm_object_utils(write_sequence)
//---------------------------------------
//Constructor
//---------------------------------------
function new(string name = "write_sequence");
super.new(name);
endfunction
virtual task body();
`uvm_do_with(req,{req.wr_en==1;})
endtask
endclass
//=========================================================================
//=========================================================================
// read_sequence - "read" type
//=========================================================================
class read_sequence extends uvm_sequence#(mem_seq_item);
`uvm_object_utils(read_sequence)
//---------------------------------------
//Constructor
//---------------------------------------
function new(string name = "read_sequence");
super.new(name);
endfunction
virtual task body();
`uvm_do_with(req,{req.rd_en==1;})
endtask
endclass
//=========================================================================
//=========================================================================
// write_read_sequence - "write" followed by "read"
//=========================================================================
class write_read_sequence extends uvm_sequence#(mem_seq_item);
`uvm_object_utils(write_read_sequence)
//---------------------------------------
//Constructor
//---------------------------------------
function new(string name = "write_read_sequence");
super.new(name);
endfunction
virtual task body();
`uvm_do_with(req,{req.wr_en==1;})
`uvm_do_with(req,{req.rd_en==1;})
endtask
endclass
//=========================================================================
//=========================================================================
// wr_rd_sequence - "write" followed by "read" (sequence's inside sequences)
//=========================================================================
class wr_rd_sequence extends uvm_sequence#(mem_seq_item);
//---------------------------------------
//Declaring sequences
//---------------------------------------
write_sequence wr_seq;
read_sequence rd_seq;
`uvm_object_utils(wr_rd_sequence)
//---------------------------------------
//Constructor
//---------------------------------------
function new(string name = "wr_rd_sequence");
super.new(name);
endfunction
virtual task body();
repeat(4)
`uvm_do(wr_seq)
repeat(4)
`uvm_do(rd_seq)
endtask
endclass
//=========================================================================
本案例产生了4个不同的sequence。产生sequence可以用两种不同的方法,第一个是利用
req = mem_seq_item::type_id::create(“req”);
wait_for_grant();
req.randomize();
send_request(req);
wait_for_item_done();
这种发生产生一个sequence并且交给sequencer发送出去。第二种方法就是利用sequence宏,`uvm_do和``uvm_do_with。直接产生。
其中sequence中的body函数会自动执行,需要在test中使用start,或者使用config机制,设置set.default_srqunence.。比如:
uvm_config_db#(uvm_object_wrapper)::set(this,“xxxx”,“default_sequence”,“xxxx”)
3,sequencer。一般来说sequence不需要做任何改动。
import uvm_pkg::*;
`include "seq_item.sv"
`include "uvm_macros.svh"
class mem_sequencer extends uvm_sequencer#(mem_seq_item);
`uvm_component_utils(mem_sequencer);
//---------------------------------------
//constructor
//---------------------------------------
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction
endclass
4,driver。sequencer将sequence发送传输到driver并且由driver发送到interface。driver和sequencer使用TLM通信使用端口进行传输数据。因为item不输入compontent类型不能使用端口。其中driver通过config机制get到interface。
import uvm_pkg::*;
`include "uvm_macros.svh"
`define DRIV_IF vif.DRIVER.driver_cb
`include "seq_item.sv"
class mem_driver extends uvm_driver #(mem_seq_item);
//---------------------------------------
// Virtual Interface
//---------------------------------------
virtual mem_if vif;
`uvm_component_utils(mem_driver)
//---------------------------------------
// Constructor
//---------------------------------------
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
//---------------------------------------
// build phase
//---------------------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
`uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
endfunction: build_phase
//---------------------------------------
// run phase
//---------------------------------------
virtual task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
drive();
seq_item_port.item_done();
end
endtask : run_phase
//---------------------------------------
// drive - transaction level to signal level
// drives the value's from seq_item to interface signals
//---------------------------------------
virtual task drive();
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
@(posedge vif.DRIVER.clk);
`DRIV_IF.addr <= req.addr;
if(req.wr_en) begin // write operation
`DRIV_IF.wr_en <= req.wr_en;
`DRIV_IF.wdata <= req.wdata;
@(posedge vif.DRIVER.clk);
end
else if(req.rd_en) begin //read operation
`DRIV_IF.rd_en <= req.rd_en;
@(posedge vif.DRIVER.clk);
`DRIV_IF.rd_en <= 0;
@(posedge vif.DRIVER.clk);
req.rdata = `DRIV_IF.rdata;
end
endtask : drive
endclass : mem_driver
在run_phase中通过
seq_item_port.get_next_item(req);
drive();
seq_item_port.item_done(); 一直从sequencer中获取sequence发送出去。
5,monitor。监视器通过接口获取interface中的数据,并将信号转换为事务级别。
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "seq_item.sv"
class mem_monitor extends uvm_monitor;
//---------------------------------------
// Virtual Interface
//---------------------------------------
virtual mem_if vif;
//---------------------------------------
// analysis port, to send the transaction to scoreboard
//---------------------------------------
uvm_analysis_port #(mem_seq_item) item_collected_port;
//---------------------------------------
// The following property holds the transaction information currently
// begin captured (by the collect_address_phase and data_phase methods).
//---------------------------------------
mem_seq_item trans_collected;
`uvm_component_utils(mem_monitor)
//---------------------------------------
// new - constructor
//---------------------------------------
function new (string name, uvm_component parent);
super.new(name, parent);
trans_collected = new();
item_collected_port = new("item_collected_port", this);
endfunction : new
//---------------------------------------
// build_phase - getting the interface handle
//---------------------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
endfunction: build_phase
//---------------------------------------
// run_phase - convert the signal level activity to transaction level.
// i.e, sample the values on interface signal ans assigns to transaction class fields
//---------------------------------------
virtual task run_phase(uvm_phase phase);
forever begin
@(posedge vif.MONITOR.clk);
wait(vif.monitor_cb.wr_en || vif.monitor_cb.rd_en);
trans_collected.addr = vif.monitor_cb.addr;
if(vif.monitor_cb.wr_en) begin
trans_collected.wr_en = vif.monitor_cb.wr_en;
trans_collected.wdata = vif.monitor_cb.wdata;
trans_collected.rd_en = 0;
@(posedge vif.MONITOR.clk);
end
if(vif.monitor_cb.rd_en) begin
trans_collected.rd_en = vif.monitor_cb.rd_en;
trans_collected.wr_en = 0;
@(posedge vif.MONITOR.clk);
@(posedge vif.MONITOR.clk);
trans_collected.rdata = vif.monitor_cb.rdata;
end
item_collected_port.write(trans_collected);
end
endtask : run_phase
endclass : mem_monitor
其中声明uvm_analysis_port #(mem_seq_item) item_collected_port;设置一个广播的port方便在scoreboard中接收monitor中获取的数据。run_phase中监视接口中的数据并且通过import中的write函数写入到scoreboard的队列中。
6,agent,将driver,sequencer和monitor包在一起。在agent中需要实例化driver,sequencer和monitor并且完成sequencer和driver之间的端口连接(此端口是uvm类中自己定义的,只需要连接即可)
`include "seq_item.sv"
`include "sequencer.sv"
`include "sequence.sv"
`include "driver.sv"
`include "monitor.sv"
class mem_agent extends uvm_agent;
//---------------------------------------
// component instances
//---------------------------------------
mem_driver driver;
mem_sequencer sequencer;
mem_monitor monitor;
`uvm_component_utils(mem_agent)
//---------------------------------------
// constructor
//---------------------------------------
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
//---------------------------------------
// build_phase
//---------------------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase);
monitor = mem_monitor::type_id::create("monitor", this);
//creating driver and sequencer only for ACTIVE agent
if(get_is_active() == UVM_ACTIVE) begin
driver = mem_driver::type_id::create("driver", this);
sequencer = mem_sequencer::type_id::create("sequencer", this);
end
endfunction : build_phase
//---------------------------------------
// connect_phase - connecting the driver and sequencer port
//---------------------------------------
function void connect_phase(uvm_phase phase);
if(get_is_active() == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction : connect_phase
endclass : mem_agent
7,scoreboard。scoreboard继承uvm_scoreboard。在scoreboard中需要声明import接收monitor中写入的事务,并且需要定义write函数。
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "seq_item.sv"
class mem_scoreboard extends uvm_scoreboard;
//---------------------------------------
// declaring pkt_qu to store the pkt's recived from monitor
//---------------------------------------
mem_seq_item pkt_qu[$];
//---------------------------------------
// sc_mem
//---------------------------------------
bit [7:0] sc_mem [4];
//---------------------------------------
//port to recive packets from monitor
//---------------------------------------
uvm_analysis_imp#(mem_seq_item, mem_scoreboard) item_collected_export;
`uvm_component_utils(mem_scoreboard)
//---------------------------------------
// new - constructor
//---------------------------------------
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
//---------------------------------------
// build_phase - create port and initialize local memory
//---------------------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase);
item_collected_export = new("item_collected_export", this);
foreach(sc_mem[i]) sc_mem[i] = 8'hFF;
endfunction: build_phase
//---------------------------------------
// write task - recives the pkt from monitor and pushes into queue
//---------------------------------------
virtual function void write(mem_seq_item pkt);
//pkt.print();
pkt_qu.push_back(pkt);
endfunction : write
//---------------------------------------
// run_phase - compare's the read data with the expected data(stored in local memory)
// local memory will be updated on the write operation.
//---------------------------------------
virtual task run_phase(uvm_phase phase);
mem_seq_item mem_pkt;
forever begin
wait(pkt_qu.size() > 0);
mem_pkt = pkt_qu.pop_front();
if(mem_pkt.wr_en) begin
sc_mem[mem_pkt.addr] = mem_pkt.wdata;
`uvm_info(get_type_name(),$sformatf("------ :: WRITE DATA :: ------"),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("Addr: %0h",mem_pkt.addr),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("Data: %0h",mem_pkt.wdata),UVM_LOW)
`uvm_info(get_type_name(),"------------------------------------",UVM_LOW)
end
else if(mem_pkt.rd_en) begin
if(sc_mem[mem_pkt.addr] == mem_pkt.rdata) begin
`uvm_info(get_type_name(),$sformatf("------ :: READ DATA Match :: ------"),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("Addr: %0h",mem_pkt.addr),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("Expected Data: %0h Actual Data: %0h",sc_mem[mem_pkt.addr],mem_pkt.rdata),UVM_LOW)
`uvm_info(get_type_name(),"------------------------------------",UVM_LOW)
end
else begin
`uvm_error(get_type_name(),"------ :: READ DATA MisMatch :: ------")
`uvm_info(get_type_name(),$sformatf("Addr: %0h",mem_pkt.addr),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("Expected Data: %0h Actual Data: %0h",sc_mem[mem_pkt.addr],mem_pkt.rdata),UVM_LOW)
`uvm_info(get_type_name(),"------------------------------------",UVM_LOW)
end
end
end
endtask : run_phase
endclass : mem_scoreboard
因为本例比较简单,所以没有chacker,直接在scoreboard中创建参考模型,然后做数据比对。如果数据比对错误,会产生uvm_error在test中会产生错误。
8,environment。将上面的agent和scoreboard包在一起。其中在env中需要创建agent和scoreboard对象,并且实现scoreboard中的import和angent中的monitor的port连接。
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "agent.sv"
`include "scoreboard.sv"
class mem_model_env extends uvm_env;
//---------------------------------------
// agent and scoreboard instance
//---------------------------------------
mem_agent mem_agnt;
mem_scoreboard mem_scb;
`uvm_component_utils(mem_model_env)
//---------------------------------------
// constructor
//---------------------------------------
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new
//---------------------------------------
// build_phase - crate the components
//---------------------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mem_agnt = mem_agent::type_id::create("mem_agnt", this);
mem_scb = mem_scoreboard::type_id::create("mem_scb", this);
endfunction : build_phase
//---------------------------------------
// connect_phase - connecting monitor and scoreboard port
//---------------------------------------
function void connect_phase(uvm_phase phase);
mem_agnt.monitor.item_collected_port.connect(mem_scb.item_collected_export);
endfunction : connect_phase
endclass : mem_model_env
9,test。test类继承uvm_test.在test类中需要创建env对象。
`ifndef TEST_SV
`define TEST_SV
`include "env.sv"
class mem_model_base_test extends uvm_test;
`uvm_component_utils(mem_model_base_test)
//---------------------------------------
// env instance
//---------------------------------------
mem_model_env env;
//---------------------------------------
// constructor
//---------------------------------------
function new(string name = "mem_model_base_test",uvm_component parent=null);
super.new(name,parent);
endfunction : new
//---------------------------------------
// build_phase
//---------------------------------------
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create the env
env = mem_model_env::type_id::create("env", this);
endfunction : build_phase
//---------------------------------------
// end_of_elobaration phase
//---------------------------------------
virtual function void end_of_elaboration();
//print's the topology
print();
endfunction
//---------------------------------------
// end_of_elobaration phase
//---------------------------------------
function void report_phase(uvm_phase phase);
uvm_report_server svr;
super.report_phase(phase);
svr = uvm_report_server::get_server();
if(svr.get_severity_count(UVM_FATAL)+svr.get_severity_count(UVM_ERROR)>0) begin
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE)
`uvm_info(get_type_name(), "---- TEST FAIL ----", UVM_NONE)
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE)
end
else begin
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE)
`uvm_info(get_type_name(), "---- TEST PASS ----", UVM_NONE)
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE)
end
endfunction
endclass : mem_model_base_test
`endif
并且在report_phase产生报告。
10,创建一个测试类继承test类分别创建出不同的测试任务。
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "seq_item.sv"
`include "test.sv"
class mem_wr_rd_test extends mem_model_base_test;
`uvm_component_utils(mem_wr_rd_test)
//---------------------------------------
// sequence instance
//---------------------------------------
wr_rd_sequence seq;
//定义sequence的种类方便调用不同的测试
//换用不同的sequence可以实现不同的测试激励
//---------------------------------------
// constructor
//---------------------------------------
function new(string name = "mem_wr_rd_test",uvm_component parent=null);
super.new(name,parent);
endfunction : new
//---------------------------------------
// build_phase
//---------------------------------------
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create the sequence
seq = wr_rd_sequence::type_id::create("seq");
endfunction : build_phase
//---------------------------------------
// run_phase - starting the test
//---------------------------------------
task run_phase(uvm_phase phase);
phase.raise_objection(this);
seq.start(env.mem_agnt.sequencer);//启动sequence
phase.drop_objection(this);
//set a drain-time for the environment if desired
phase.phase_done.set_drain_time(this, 50);
endtask : run_phase
endclass : mem_wr_rd_test
注:在test中要开启sequence,seq.start(env.mem_agnt.sequencer);并且把objection挂起,防止仿真的结束。(在任意一个run_pgase中挂起都行)
11,top顶层,在top中产生所用的clk,例化dut,利用config_db设置interface,方便driver和monitor调用。并且调用run_test(“mem_wr_rd_test”);函数。系统会自动的调用上诉的test。也可以通过使用脚本参数指定仿真的test。
vsim -novopt -classdebug +UVM_TESTNAME=mem_wr_rd_test work.tbench_top
//including interfcae and testcase files
`include "interface.sv"
`include "test.sv"
`include "wr_rd_test.sv"
//---------------------------------------------------------------
module tbench_top;
//---------------------------------------
//clock and reset signal declaration
//---------------------------------------
bit clk;
bit reset;
//---------------------------------------
//clock generation
//---------------------------------------
always #5 clk = ~clk;
//---------------------------------------
//reset Generation
//---------------------------------------
initial begin
reset = 1;
#5 reset =0;
end
//---------------------------------------
//interface instance
//---------------------------------------
mem_if intf(clk,reset);
//---------------------------------------
//DUT instance
//---------------------------------------
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)
);
//---------------------------------------
//passing the interface handle to lower heirarchy using set method
//and enabling the wave dump
//---------------------------------------
initial begin
uvm_config_db#(virtual mem_if)::set(uvm_root::get(),"*","vif",intf);
//enable wave dump
$dumpfile("dump.vcd");
$dumpvars;
end
//---------------------------------------
//calling test
//---------------------------------------
initial begin
run_test("mem_wr_rd_test");
end
endmodule
二,仿真
通过questasim,运行do脚本仿真结果如下: