UVM学习笔记之代码解析
本学习笔记内容和代码均出自《UVM》实战一书。
上篇文章我们大致介绍了UVM中的一些基本组件和常用的一些任务函数,本篇文章我们着重看一下实例代码的解析。
这里所有代码都可以通过这个链接找到,并在线运行。
DUT代码
module dut(
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的功能非常简单,就是将输入的数据和数据有效信号按照时钟输出,DUT虽然简单,但是我们测试的时候往往需要测试各种数据,比如输入数据就有256中,按照传统的测试方法可能比较麻烦,对于这种情况用UVM的测试方法就比较简单。下面我们来看看driver
的设计:
Driver
class my_driver extends uvm_driver; //新类继承自UVM已有的类
// register class to uvm
//将新类注册到UVM中维护的一个类表
//这样在调用的时候可以通过类是run_test的方式调用
`uvm_component_utils(my_driver)
// add interface
// 定义Driver的接口,方便类的移植
virtual my_if vif;
// 定义New方法,并显式调用父类方法
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
`uvm_info("my_driver", "new is called", UVM_LOW);
endfunction
// 在build_phase初始化接口,接口初始化通过uvm_config_db的set和get完成
// 现在的理解config_db里面有一个字典,其他地方可以设置比如vif的值,这里
// 将设置的值取出并赋给当前类中的元素。
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))
`uvm_fatal("my_driver", "virtual interface must be set for vif!");
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
// Driver中所有的操作基本都是在main_phase中完成的
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
接口定义
interface my_if(input clk, input rst_n); // 时钟和复位信号输入
logic [7:0] data; // 数据寄存器
logic valid; // 数据有效寄存器
endinterface
TestBench
`timescale 1ns/1ps
// 引入必要的头文件,需要说明的是因为my_if.sv和my_driver.sv``都是在后面引入的
// 所以在这两个文件中并不需要引入uvm相关的头文件。但如果引入文件顺序不对的
// 话,可能会是有问题的。
`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);
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传递接口变量
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
initial begin
/* old class call method */
// 两种不同的运行方式
//my_driver drv;
//drv = new("drv", null);
//drv.main_phase(null);
//$finish();
/* new class call method using factory */
run_test("my_driver");
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
问题
目前对于上述代码还是有一些问题需要着重看一下
[] 接口设计上如何定义输入信号有哪些,寄存器变量有哪些
[] 这个例程仍不完整,缺少scoreboard判定测试结果,缺少参考模型。
最后更新于 2018-12-24 23:24:03 并被添加「UVM」标签,已有 4160 位童鞋阅读过。
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。