1.
SystemC是基于C++的,用户需要自己管理内存,容易发生内存泄漏[^内存泄漏指的是程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃]。
而SystemVerilog则不存在这个问题,而且与Verilog完全兼容,提供DPI接口,而且本身自带内存管理机制,不用担心内存泄漏问题。还支持系统函数调用,可以直接调用可执行程序,即外部已经用C写好的参考模型。
2.
验证平台大致包括如下几个部分
-
driver:实现激励。
-
scoreboard:用于将DUT与参考模型进行比较。
-
monitor:用于收集DUT的输出并传递给scoreboard。
3.
一个典型的UVM验证平台
4.
使用UVM的第一条原则是:验证平台中所有的组件应该派生自UVM中的类。
加入driver
1.
先列出我们下面要讲的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 [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
2.
这里的driver
类用于发送激励,派生自uvm_driver
,这里的构造new函数有两个参数,一个是string
类型的name
,即类名,另个是uvm_component
类型的parent
。这里uvm_driver
是一个派生自uvm_component
的类,而每一个派生自uvm_component
的类都要指明name
和parnent
这两个参数。
具体代码如下:
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);
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
3.
UVM用phase
来管理验证平台的运行,类型为uvm_phase
,而名称一般为xxxx_phase
,这里driver
所做的事情几乎都在main_phase
中完成。这里实现一个driver
就等于是实现其main_phase
。
4.
这里的代码还使用了uvm_info
宏,主要完成信息打印,但比$display
语句更强大,它有三个参数,第一个参数是字符串,用于把打印信息进行归类,第二个参数也是字符串,是具体需要打印的信息,第三个参数是冗余级别。对于一些关键的信息需要打印出来的,可以设置为UVM_LOW
,而对于一些可有可无的信息,则可以设置为UVM_HIGH
,默认情况下是UVM_MEDIUM
,因此默认情况下只显示UVM_MEDIUM
和UVM_LOW
的信息。
这里打印的结果如下所示:
UVM_INFO my_driver.sv(20)@48500000:drv[my_driver]data is drived
-
UVM_INFO关键字:表明这是一个
uvm_info
宏打印的结果。 -
my_driver.sv(20):指明此条打印信息的来源,其中括号里的数字表示原始的
uvm_info
打印语句在my_driver.sv中的行号。 -
48500000:表明此条信息的打印时间。
-
drv:这是driver在UVM树中的路径索引。
这里的路径索引还可以通过函数
get_full_name()
来获取,使用方式如下:$display("the full name of current component is: %s", get_full_name());
因此建议使用宏uvm_info
来代替以前的$display
语句。
5.
下面对my_driver实例化并且最终搭建的验证平台如下:
`timescale 1ns/1ps
`include "uvm_macros.svh"//UVM宏定义文件
import uvm_pkg::*;//UVM类库文件
`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;
drv = new("drv", null);
drv.main_phase(null);
$finish();
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
6.
接下来我们来为driver加入factory机制,即加入宏uvm_component_utils
,主要完成将my_driver登记注册的功能。
加入后的driver代码如下:
class my_driver extends uvm_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
同时还要对top_tb顶层做一些修改。
module top_tb;
...
initial begin
run_test("my_driver");
end
endmodule
修改完成后,原先的顶层代码的下面这部分就可以删掉了。即一句run_test(my_driver);
就完成了对my_driver类的例化和main_phase的自动调用。
initial begin
my_driver drv;
drv = new("drv", null);
drv.main_phase(null);
$finish();
end
因此这里的uvm_component_utils
其实完成了根据传递的字符串(类名)来创建了一个实例。
注意,所有派生自uvm_component
及其派生类的类都应该使用uvm_component_utils
宏注册。
initial begin
my_driver drv;
drv = new("drv", null);
drv.main_phase(null);
$finish();
end
但是注意这里的输出仅为下面这样:
new is called
main_phased is called
字符串循环data is drived
部分没有被输出。看起来仿真被中断了,因为在之前的顶层模块中我们使用$finish();
来结束仿真,但加入factory机制后的顶层模块这句被删除了,那么是如何自动结束仿真的呢。
7.
这里需要引入一个叫做objection
的机制。在每个phase中,UVM会检查是否有objection被提起(raise_objection
),如果有,那么等待这个objection被撤销(drop_objection
)后停止仿真;如果没有,则马上结束当前phase。
以下是加入objection机制的driver。可以看到增加了phase.raise_objection(this);
和phase.drop_objection(this);
这对语句用于控制仿真的raise和drop。此时,就可以正常输出字符串循环data is drived
了。
注意phase.raise_objection(this);
必须放在消耗仿真时间单位语句的前面,否则不起作用。
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
8.
下面我们再来加入virtual interface来使得代码更加整洁规范并便于修改移植。
首先来定义interface。
interface my_if(input clk, input rst_n);
logic [7:0] data;
logic valid;
endinterface
然后在顶层例化和使用interface。
...
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));
...
接着在my_driver里声明virtual interface,以便使用。
class my_