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
这个dut很简单,通过八位的rxd接收数据,再通过txd传输出去,rx_dv是输入信号也就是接收数据rxd有效指示位,tx_en是发送数据有效指示位。
UVM如何搭建driver?
首先要搞清楚一个概念:uvm是一个库,在这个库中,几乎所有东西都是由类来实现的,包括但不限于driver,monitor,scoreboard等等,他们各自有各自的功能,当要实现这些功能时,首先要想到的是从UVM的某个类中派生出来一个新的类,在这个新的类中去实现这些功能,所以,使用UVM第一条原则就是:验证平台中的 所有组件应该派生自UVM中的类。
uvm验证平台中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);
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
这个driver的功能很简单,就是随机生成256个数据,向rxd发送,同时将rx_dv拉高,数据发送完成后将rx_dv拉低。有两点需注意:
- 所有派生自uvm_driver类的new函数有两个参数:1.string类型的name,2.uvm_component类型的parent。name就是名字,parent这里暂时没有做过多的解释,一步步来吧~ 实质上,这两个参数不是由uvm_driver本身要求的,而是uvm_component要求的,因为uvm_driver又是派生自uvm_component的类,所以必然得带有这两个参数。
- driver所干的所有事情基本都在main_phase中完成了,UVM本身是由phase来管理验证平台的运行的,这些phase统一以xxx_phase命名(如这里的main_phase),且都有一个类型为uvm_phase,名字为phase的参数。main_phase是uvm_driver类中预先定义好的task。可以简单的认为,实现一个driver就是在实现其中的main_phase。
上面的代码中还出现了uvm_info这个宏,这个宏的参数由三部分组成:
- 第一个参数:字符串“my_driver”,用于把打印的信息归类。
- 第二个参数:字符串“data is drived”,这是具体要打印的信息。
- 第三个参数:冗余级别 UVM_LOW,在验证平台中,信息的重要程度可以分为三个等级:关键,一般,可有可无,而他们依次对应的冗余级别的表示方法是UVM_LOW,UVM_MEDIUM,UVM_HIGH,UVM一般默认只会显示UVM_MEDIUM或者UVM_LOW的信息。至于UVM_HIGH信息的显示放在后面3.4.1来讲。
此uvm_info宏最终打印的结果:
UVM_INFO my_driver.sv(20) @ 48500000: drv [my_driver] data is drived
在此打印结果中有如下几项:
- UVM_INFO关键字:表示这是个uvm_info宏的打印结果,除了这个还有uvm_error宏,uvm_warning宏。
- my_driver.sv(20):指明这条打印信息的来源,其中包括uvm_info在文件中的行号(20)。
- 48500000:表明此条信息打印的时刻。
- drv:这是driver在UVM树种的路径索引,可使用get_full_name()函数来获取。
- [my_driver]:方括号中调用的是uvm_info宏中的第一个参数,作为一个信息归类,表明是driver这个组件打印出来的信息。
- data is drived:调用的是uvm_info宏中的第二个参数,也就是最终需要打印的信息。
以上就定义好了一个属于自己的my_driver的类,之后如果要使用它的话我们只需将其例化,如:
my_driver drv;
drv = new("drv",null);
类的定义只是说先定好规定,而类的实例化在于通过new()函数来通知仿真器创建一个my_driver这个类的实例,仿真器接到new命令后,就会在内存中划分一块空间,在划分前,会首先检查是否已经预先定义过这个类,在已经定义过得情况下,按照定义中的标准来分配空间,并把这块空间的指针返还给drv,之后就可以通过drv来查看类中的各个成员变量,函数,任务等等。对于大部分的类来说,只定义不实例化是没有意义的;而只例化不定义,仿真器会报错的。
对my_driver实例化并且搭建验证平台,代码如下:
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;
`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
第2行uvm_macros.svh是个UVM的文件,里面有很多宏定义;第4行通过import语句把uvm_pkg引入进来,只有导入这个库,编译下面my_driver.sv的时候才能认识里面的uvm_driver等类名。
24-27行主要是在定义一个my_driver的实例drv,并将其通过new函数实例化,前面uvm_info打印的drv路径索引就是从这里传入的参数,另一个参数是null,这里先不管,一般正常的验证平台这里传入的参数不是null。然后显示的调用了drv.main_phase,这里的参数也暂时为null。27行结束仿真。
然后下面两个块主要是生成一些时钟和复位信号,不做阐述了。
最后运行这个例子输出结果为256个“data is drived”。