个人《UVM实战卷I》学习随手笔记,此书作者:张强
一个验证平台要实现的基本功能:
简单验证平台框架:
UVM引入agent和sequence的概念,UVM验证平台的典型框架图:
DUT定义:通过rxd接收数据,由txd发送出去;rx_dv是接收的数据有效指示,tx_en是发送的数据有效指示。(此篇例子均基于该DUT设计)
1 module dut(clk,
2 rst_n,
3 rxd,
4 rx_dv,
5 txd,
6 tx_en);
7 input clk;
8 input rst_n;
9 input[7:0] rxd;
10 input rx_dv;
11 output [7:0] txd;
12 output tx_en;
13
14 reg[7:0] txd;
15 reg tx_en;
16
17 always @(posedge clk) begin
18 if(!rst_n) begin
19 txd <= 8'b0;
20 tx_en <= 1'b0;
21 end
22 else begin
23 txd <= rxd;
24 tx_en <= rx_dv;
25 end
26 end
27 endmodule
driver应派生自uvm_driver,简单的driver示例:
class my_driver extends uvm_driver;
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual task main_phase(uvm_phase phase); //extern常用于封装
endclass
task my_driver::main_phase(uvm_phase phase);
top_tb.rxd <= 8'b0;
top_tb.rx_dv <= 1'b0;
while(!top_tb.rst_n)
@(posedge top_tb.clk);
for(int i = 0; i < 256; i++)begin
@(posedge top_tb.clk);
top_tb.rxd <= $urandom_range(0, 255);
top_tb.rx_dv <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW)
end
@(posedge top_tb.clk);
top_tb.rx_dv <= 1'b0;
endtask
示例打印结果为:UVM_INFO my_driver.sv(20) @ 48500000: drv [my_driver] data is drived
uvm_info宏包含了打印信息的物理文件来源、 逻辑结点信息(UVM树中的路径索引)、 打印时间、 对信息的分类组织及打印的信息。 搭建验证平台时应尽量使用uvm_info宏取代display语句。
定义my_driver后需将其实例化,类的实例化指的是通过new创造出对应的实例。
A a_inst;
a_inst = new();
对my_driver实例化且最终搭建的验证平台如下:
`timescale 1ns/1ps
`include "uvm_macros.svh" //UVM文件,包含众多宏定义
import uvm_pkg::*;//导入该库,在编译my_driver.sv时才会识别其中的uvm_driver等类名
`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;
dut my_dut(.clk(clk),
.rst_n(rst_n),
.rxd(rxd),
.rx_dv(rx_dv),
.txd(txd),
.tx_en(tx_en));
initial begin
my_driver drv; //定义my_driver的实例并将其实例化
drv = new("drv", null); //new函数传入的名字参数drv,前面uvm_info打印时出现的drv在此传入
drv.main_phase(null); //显式调用my_driver的main_phase,括号里的参数phase不需用户理会
$finish();//结束仿真,是Verilog的函数
end
initial begin //产生时钟
clk = 0;
forever begin
#100 clk = ~clk;
end
initial begin //产生复位信号
rst_n = 1'b0;
#1000;
rst_n = 1'b1;
end
endmodule
下面示例:自动创建一个类的实例并调用其中的function和task,使用这个功能需要引入UVM的factory机制。
class my_driver extends uvm_driver;//与上面driver代码作比较
`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
extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
top_tb.rxd <= 8'b0;
top_tb.rx_dv <= 1'b0;
while(!top_tb.rst_n)
@(posedge top_tb.clk);
for(int i = 0; i < 256; i++)begin
@(posedge top_tb.clk);
top_tb.rxd <= $urandom_range(0, 255);
top_tb.rx_dv <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW);
end
@(posedge top_tb.clk);
top_tb.rx_dv <= 1'b0;
endtask
factory机制的实现被集成在了一个宏中:uvm_component_utils。 该宏做的事情非常多, 其中之一就是将my_driver登记在UVM内部的一张表中, 这张表是factory功能实现的基础。 只要在定义一个新的类时使用这个宏, 就相当于把这个类注册到了这张表中;
给driver加入factory机制后,还需要对top_tb做改动:
module top_tb;
…
initial begin
run_test("my_driver");
end //使用run_test语句替换了my_driver实例化及main_phase的显式调用
endmodule
输出结果:
new is called
main_phased is called
加入objection机制的driver如下:
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
top_tb.rxd <= 8'b0;
top_tb.rx_dv <= 1'b0;
while(!top_tb.rst_n)
@(posedge top_tb.clk);
for(int i = 0; i < 256; i++)begin
@(posedge top_tb.clk);
top_tb.rxd <= $urandom_range(0, 255);
top_tb.rx_dv <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW);
end
@(posedge top_tb.clk);
top_tb.rx_dv <= 1'b0;
phase.drop_objection(this);
endtask
避免绝对路径的一个方法是使用宏:当路径修改时只需修改宏的定义即可。但假如clk的路径变为top_tb.clk_inst.clk,而rst_n的路径变为top_tb.rst_inst.rst_n,那么修改宏定义是无法起作用的。
`define TOP top_tb
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
`TOP.rxd <= 8'b0;
`TOP.rx_dv <= 1'b0;
while(!`TOP.rst_n)
@(posedge `TOP.clk);
for(int i = 0; i < 256; i++)begin
@(posedge `TOP.clk);
`TOP.rxd <= $urandom_range(0, 255);
`TOP.rx_dv <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW);
end
@(posedge `TOP.clk);
`TOP.rx_dv <= 1'b0;
phase.drop_objection(this);
endtask
另一种方法是使用interface:在SV中使用interface来连接验证平台与DUT的端口.
interface my_if(input clk, input rst_n);
logic [7:0] data;
logic valid;
endinterface
//定义了interface后,在top_tb中实例化DUT时就可直接使用:
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));
接下来是如何在driver中使用interface的问题——第一种想法是在driver中声明如下语句,然后通过赋值的形式将top_tb中的input_if传递给它:
class my_driver extends uvm_driver;
my_if drv_if;
…
endclass
class my_driver extends uvm_driver;
virtual my_if vif;
endclass
在声明了vif后,就可以在main_phase中使用如下方式(vif.xxx)驱动其中的信号:
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;
phase.drop_objection(this);
endtask //代码中的绝对路径被消除,大大提高代码的可移植性和可重用性
最后,如何把top_tb中的input_if和my_driver中的vif对应起来?
在top_tb中执行set操作:
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
在top_tb中执行get操作:
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
上例中的build_phase与main_phase一样,build_phase也是UVM中内建的一个phase。当UVM启动后会自动执行build_phase。build_phase在new函数之后main_phase之前执行,主要通过config_db的set和get操作来传递一些数据,以及实例化成员变量等。
上例中加入了super.build_phase语句,因为在其父类的build_phase中执行了一些必要的操作,必须显式地调用并执行它。
在build_phase中出现了uvm_fatal宏,该宏只有两个参数,与uvm_info宏的前两个参数的意义完全一样,但当它打印第二个参数的信息后会直接调用finish函数结束仿真。uvm_fatal的出现表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查,所以不需要uvm_info的第三个参数(冗余度),uvm_fatal打印的信息是非常关键的。
config_db的set和get函数都有四个参数,它们的第三个参数必须完全一致。set函数的第四个参数表示要将哪个interface通过config_db传递给my_driver,get函数的第四个参数表示把得到的interface传递给哪个my_driver的成员变量。set函数的第二个参数表示的是路径索引。 在top_tb中通过run_test创建了一个my_driver的实例,它的名字是uvm_test_top——UVM通过run_test语句创建该实例。
无论传递给run_test的参数是什么,创建的实例名都为uvm_test_top。由于set操作的目标是my_driver,所以set函数的第二个参数就是uvm_test_top。
set与get的写法比较独特——使用双冒号是因为它们都是静态函数,而uvm_config_db#( virtual my_if)是一个参数化的类,该参数是要“寄信”的类型,这里是virtual my_if。假如要向my_driver的var变量传递一个int类型的数据,可用如下方式:
initial begin
uvm_config_db#(int)::set(null, "uvm_test_top", "var", 100);
end
而在my_driver中应使用如下方式:
class my_driver extends uvm_driver;
int var;
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!!!")
if(!uvm_config_db#(int)::get(this, "", "var", var))
`uvm_fatal("my_driver", "var must be set!!!")
endfunction
从这里可以看出,可以向my_driver中“寄”许多信。 上面两个例子是top_tb向my_driver传递了两个不同类型的数据,其实也可以传递相同类型的不同数据。 假如my_driver中需要两个my_if, 那么可以在top_tb中这么做:
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif2", output_if);
end
在my_driver中这么做:
virtual my_if vif;
virtual my_if vif2;
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!!!")
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif2", vif2))
`uvm_fatal("my_driver", "virtual interface must be set for vif2!!!")
endfunction