UVM实战-2.2.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   

这个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”。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值