driver 会生成DUT 所需要的激励信号,通过top_tb传递给DUT 的输入接口,在之前的代码里面都是直接在driver里面给top_tb的信号赋值,然后连接到DUT 的输入输出端口,本节给出规范的连接方法。

https://github.com/shirainbown/UVM-Step-by-Step/tree/master

在SystemVerilog中可以使用 interface类,用于连接验证平台和DUT 的端口,这样可以避免在端口发生改变时大量修改driver里面的端口信息,只需要修改interface的内容以及连接方式即可。

// 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

一个interface的定义代码如上所示,其中input表示这是外部的激励,例如这里的clk和rst_n都是在top_tb中产生的,声明之后就可以通过my_if访问到top_tb中产生的时钟信号。此时driver只需要驱动inf中的信号,而无需知道这个信号最终连接到哪个模块。

有了这个接口类,我们只需要在driver和top_tb里面分别声明,然后将对应的端口连接起来即可,代码如下:

`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class my_driver extends uvm_driver;

   virtual my_if vif;  // 声明一个接口,必须用 virtual类

   `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))  // 首先需要绑定到top_tb中的接口上
         `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;
   phase.drop_objection(this);
endtask
`endif

`timescale 1ns/1ps
`include "uvm_macros.svh"

import uvm_pkg::*;
`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);  // 这里不用加 virtual,但需要添加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
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);  
// 第1个参数表示作用域,null说明这个接口能够被所有组件所看到,把这个key和value存储在全局空间
// 第2个参数表示路径,uvm_test_top是内置的一个路径,默认是run_test('xx')中这个xx。表示xx路径下访问这个key,可以得到对应的value
// 如果是另一个xx2,访问这个key,可以是另一个value
// 第3-4个参数分别是key和value,可以理解为在它内部存放的一个字典中存放了"vif"这个名称,值为 input_if
uvm_config_db#(virtual my_if)::get(this, "", "vif", vif);
// this 表示当前组件查找,""表示组件自身的看得到的存储空间,也可以设置为子组件的路径,则表示去子组件的路径下查找这个key对应的value

上述代码用于连接在driver和top_tb中声明的两个interface,分别是driver中的vif和top_tb中的input_if,top_tb中的output_if暂时还没有与组件相连,先可以不管。

需要注意的是,uvm_config_db本身是一个参数化的类,需要指定接口类型,例如我们这里是virtual my_if 类型,因此使用uvm_config_db#(virtual my_if) ,然后需要分别在top_tb和driver里面调用这个类的两个成员函数,set和get。

第3个参数必须一致,但是可以随意取名,这里的"vif"也可以是"aaa",只需要保证要连接的两个模块都用同样的名称。

文件的结构如下,将my_if.sv添加到filelist.f中,Makefile脚本中的内容无需变动,直接运行make vcs。

├── 2.2.4
│   ├── filelist.f
│   ├── Makefile
│   ├── my_driver.sv
│   ├── my_if.sv
│   └── top_tb.sv
└── dut
    └── dut.sv

得到的仿真结果相同。