上文试图运行了第一个UVM平台,但实际上和真正的验证平台相差甚远,本文试图补充一些组件内容,同时解读一下书中的代码。
https://github.com/shirainbown/UVM-Step-by-Step/tree/master
我们从第一个实例可以发现,一个最基础的UVM 平台至少是需要有driver的,包含的内容有 top_tb + driver + DUT,结构层次为:
├── top_tb
├── drv
├── my_dut
修改代码得到:
// my_driver.sv
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver) // 改动1
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
`uvm_info("my_driver", "new is called", UVM_LOW);// 打印信息
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this); // 改动2
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
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;
phase.drop_objection(this); //改动3
endtask
`endif
有几个比较重要的改动,`uvm_component_utils 表示将my_driver注册到UVM 内部,这样my_driver可以使用一些内置的函数,例如在top_tb中的run_test,不用自己定义,较为方便。
改动2-3 是为了配合top_tb中的run_test,通过run_test执行driver的这个task必须要先raise再drop_objection。
`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
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
/*
my_driver drv;
drv = new("drv", null);
drv.main_phase(null); // main_phase 类似于主函数
$finish();
*/
endmodule
运行同样的脚本得到输出:
`uvm_info("my_driver", "main_phase is called", UVM_LOW);
// my_driver作为一个分类的名字,会统计这个标签出现的次数; "main_phase is called" 是需要输出的信息;UVM_LOW 冗余程度
下一篇文章将添加一个接口Interface,用于连接dut和driver,替代driver中直接访问top_tb的方式。