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判定测试结果,缺少参考模型。


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

相关文章

发表新评论