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
得到的仿真结果相同。