一、验证平台简介
说明:
- uvm_sequencer:所有的sequencer都要派生自uvm_sequencer,用于组织管理sequence,driver申请数据时,把sequence生成的sequence_item发给driver。
- uvm_driver:所有的driver都要派生自uvm_driver,用于向sequencer申请sequence_item(数据包),并将包里的信息按照协议规定驱动到DUT的端口上。
- uvm_monitor:所有的的monitor都要派生自uvm_monitor,用于从DUT接收数据,并将其转成transaction级别的sequence_item,发送给scoreboard。
- uvm_agent:所有的agent要派生自uvm_agent,用于将sequencer、driver和monitor封装在一起,提高代码的可重用性,有UVM_ACTIVE/UVM_PASSIVE用于是否激活monitor
- uvm_scoreboard:一般的scoreboard都要派生自uvm_scoreboard,用于比较reference model和monitor分别发送来的数据,根据比较结果判断DUT是否正确工作。
- uvm_env:所有的env要派生自uvm_env,用于将平台上的component组件封装在一起,实现一个环境多个用例。
- uvm_test:所有的测试用例要派生自uvm_test或其派生类,任何一个派生的测试用例中,都要实例化env。当用例运行时,才能把数据发给DUT,并接收DUT的数据。
- reference model:UVM中并没有针对reference model定义一个类,reference model直接派生自uvm_component。用于模仿DUT,完成与DUT相同的功能。reference model可以直接使用SystemVerilog高级语言的特性,还可以通过DPI等接口调用其他语言来完成与DUT相同的功能。
二、DUT设计
该dut设计功能就是通过rxd接收数据,再通过txd发送出去,rx_dv是接收数据有效信号,tx_en是发送数据有效信号。
module dut(
clk,
rst_n,
rxd,
rx_dv,
txd,
tx_en
);
input clk;
input rst_n;
input[7:0] rxd;
input rx_dv;
output [7:0] txd;
output tx_en;
reg[7:0] txd;
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
三、driver验证平台
该验证平台只含driver,其中加入factory机制、objection机制、和virtual interface虚接口
my_driver.sv:
- 验证平台中所有的组件应该派生自UVM中的类
- UVM验证平台中的driver应该派生自uvm_driver
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class my_driver extends uvm_driver;
virtual my_if vif;
`uvm_component_utils(my_driver)
function new(string name = "my_driver", uvm_component parent = null);
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))
`uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
for(int i = 0; i < 256; i++)begin
@(posedge vif.clk);
vif.data <= $urandom_range(0, 255);
vif.valid <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW);
end
@(posedge vif.clk);
vif.valid <= 1'b0;
$display("the full name of current component is: %s", get_full_name());
phase.drop_objection(this);
endtask
`endif
说明1:
- 每一个派生自uvm_component或其派生类的类在其new函数中要指明两个参数:string类型的name和uvm_component类型的parent,name就是实例化对象传入的名字,parent指父结点(而非父类),my_driver这儿暂时把它设置成了空null。
- UVM由phase来管理验证平台的运行,phase统一以xxxx_phase来命名,且都有一个类型为uvm_phase名字为phase的参数。main_phase是uvm_driver中预先定义好的一个任务,在factory机制下是自动运行的,不需要去调用。
- uvm_info宏:有三个参数,第一个参数是字符串,用于把打印的信息归类;第二个参数也是字符串,是具体需要打印的信息;第三个参数则是冗余级别。关键的信息可以设置UVM_LOW,有些信息可有可无设置为UVM_HIGH,介于两者之间的就是UVM_MEDIUM
- get_full_name():打印当前结点的路径索引,结果为drv。
说明2:
- factory机制:采用子类替换uvm库中父类的办法实现自动运行而不需要自己重新写某些代码即可以自动创建一个类的实例并调用其中的函数(function)和任务(task)
- factory机制的实现被集成uvm_component_utils宏中。这个宏所做的事情非常多,其中之一就是将my_driver登记在UVM内部的一张表中,这张表是factory功能实现的基础。定义一个新的类时使用这个宏,就相当于把这个类注册到了这张表中
- 在使用factory机制时必须要先注册,再例化对象(该my_driver没有创建对象),所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册
说明3:
- UVM中通过objection机制来控制验证平台的关闭,在每个phase中,如果objection被提起(raise_objection)那么等待这个objection被撤销(drop_objection)后停止仿真;如果没有(即使没有使用$finish结束仿真),也会马上结束当前phase。
- raise_objection语句必须在main_phase中第一个消耗仿真时间语句之前。如$display语句是不消耗仿真时间的,这些语句可以放在raise_objection之前,类似@(posedge top.clk)等语句是要消耗仿真时间的
- 仿真时间是指$time函数打印出的时间,CPU时间(运行时间)指一个测试用例的运行时间
说明4:
- 对于脱离了top_tb层次结构,同时又期望在top_tb中对其进行某些操作的实例,UVM引进config_db机制
- UVM启动后,会自动执行build_phase。build_phase在new函数之后main_phase之前执行。在build_phase中主要通过config_db的set和get操作来传递一些数据,以及实例化成员变量等。super.build_phase表示在其父类的build_phase中执行了一些必要的操作,必须显式地调用并执行它。
- build_phase与main_phase不同的一点在于,build_phase是一个函数phase,而main_phase是一个任务phase,build_phase是不消耗仿真时间的。build_phase总是在仿真时间($time函数打印出的时间)为0时执行。
- uvm_fatal宏只有两个参数,这两个参数与uvm_info宏的前两个参数的意义完全一样。与uvm_info宏不同的是,当它打印第二个参数所示的信息后,会直接调用Verilog的finish函数来结束仿真。uvm_fatal的出现表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查。
-
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100);
- set函数是寄信,其中第一个和第二个参数联合起来组成目标路径,与此路径符合的目标才能收信。第一个参数必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。第三个参数表示一个记号,用以说明这个值是传给目标中的哪个成员的,第四个参数是要设置的值。
- 此处set函数的第一个参数为null。在这种情况下,UVM会自动把第一个参数替换为uvm_root::get(),即uvm_top。
-
uvm_config_db#(int)::get(this, "", "pre_num", pre_num);
- get函数是收信,get函数中的第一个参数和第二个参数联合起来组成路径。第一个参数也必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。一般的,如果第一个参数被设置为this,那么第二个参数可以是一个空的字符串。第三个参数就是set函数中的第三个参数,这两个参数必须严格匹配,第四个参数则是要设置的变量
my_if.sv:
`ifndef MY_IF__SV
`define MY_IF__SV
interface my_if(input clk, input rst_n);
logic [7:0] data;
logic valid;
endinterface
`endif
说明:
- top.rx_dv,top.clk这样的绝对路径信号当一个地方信号改变时相关代码也要做大量修改如clk信号的层次从top.clk变成了top.clk_inst.clk
- 避免绝对路径:使用宏或者使用interface,提高代码的可移植性和可重用性
- interface模块(module)才可以使用,在类中使用的是virtual interface
top_tb.sv:
`timescale 1ns/1ps
`include "uvm_macros.svh"//UVM中的宏文件包含了很多的宏定义,只需要包含一次
import uvm_pkg::*;//导入uvm验证平台的包
`include "my_if.sv"
`include "my_driver.sv"
module top_tb;
reg clk;
reg rst_n;
reg[7:0] rxd;
reg rx_dv;
wire[7:0] txd;
wire tx_en;
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));
initial begin
clk = 0;
forever begin
#100 clk = ~clk;
end
end
initial begin
rst_n = 1'b0;
#1000;
rst_n = 1'b1;
end
initial begin
run_test("my_driver");
end
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
endmodule
说明:
- 一个run_test语句会根据传入的字符串创建一个my_driver的实例,并且会自动调用my_driver的main_phase
- 根据类名创建一个类的实例,这是由于uvm_component_utils宏,工厂机制实现的
- 无论传递给run_test的参数是什么,创建的实例的名字都为uvm_test_top。由于set操作的目标是my_driver,所以set函数的第二个参数就是uvm_test_top。