Modelsim搭建只有driver的UVM验证平台

记录一下《UVM实战(卷I)》学习笔记。

1 验证平台的组成

1.1 简单验证平台

在这里插入图片描述

  • driver:模拟DUT的真实使用情况,给DUT施加各种激励,包括正常异常激励,不同模式激励。
  • scoreboard:也被称为 checker 。根据DUT的输出判断DUT行为是否与预期符合。
  • monitor:收集DUT的输出并将它们传递给scoreboard。
  • reference model:给出预期的结果。

1.2 典型UVM验证平台

在这里插入图片描述

引入 agent 和 sequence 概念。

2 按照书本例程使用 Modelsim 搭建仿真环境

2.1 最简单的验证平台

参考文章:UVM学习-仿真环境的搭建
在上述参考文章的基础上,我们自己要去学习具体的原理,首先要了解Modelsim仿真流程及相关命令

首先按照课本编写好相关代码:
待测设计DUT:这里按照个人代码风格抄了一遍。

//file name: dut.v
module dut(
    input               I_sys_clk,
    input               I_reset_n,
    input       [7:0]   I_rxd    ,
    input               I_rx_dv  ,
    output reg  [7:0]   O_txd    ,
    output reg          O_tx_en
);

always @(posedge I_sys_clk or negedge I_reset_n)
begin
    if(~I_reset_n) 
    begin
        O_txd   <= 8'b0;
        O_tx_en <= 1'b0;
    end 
    else
    begin
        O_txd   <= I_rxd;
        O_tx_en <= I_rx_dv;
    end
end

endmodule

UVM是一个库,库中所有东西都是使用类(class)实现。验证平台中所有组件应该派生自UVM中的类。
类有函数(function)和任务(task),通过函数和任务可以完成driver输出激励,monitor监测,参考模型计算,scoreboard比较功能。
类有成员变量,通过成员变量可以控制类的行为。

添加driver类:

//file name: my_driver.sv
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV

class my_driver extends uvm_driver;
    function new(string name = "my_driver", uvm_component parent = null);
        super.new(name, parent);
    endfunction //new()
    extern virtual task main_phase(uvm_phase phase);
endclass //my_driver extends uvm_driver

task my_driver::main_phase(uvm_phase phase);
    top_tb.I_rxd <= 8'd0;
    top_tb.I_rx_dv <= 1'b0;
    while(!top_tb.I_reset_n)
        @(posedge top_tb.I_sys_clk);
    for (int i = 0; i < 256; i++) begin
        @(posedge top_tb.I_sys_clk)
        top_tb.I_rxd <= $urandom_range(0, 255);
        top_tb.I_rx_dv <= 1'b1;
        `uvm_info("my_driver", "data is drived", UVM_LOW);
    end
    @(posedge top_tb.I_sys_clk);
    top_tb.I_rx_dv <= 1'b0;

endtask

`endif 

uvm_info宏的功能与verilog的display类似,但比display更强大。
第一个字符串参数把打印的信息归类,第二个字符串参数是需要打印的信息,第三个参数是冗余级别,有UVM_LOW、UVM_MEDIUM、UVM_HIGH。UVM默认只显示UVM_MEDIUM或者UVM_LOW的信息。

添加testbench的顶层:

//file name:top_tb.sv
`timescale 1ns/1ps
`include "uvm_macros.svh"

import uvm_pkg::*;

`include "my_driver.sv" 

module top_tb();

parameter T = 10;

reg         I_sys_clk;
reg         I_reset_n;
reg [7:0]   I_rxd    ;
reg         I_rx_dv  ;
wire[7:0]   O_txd    ;
wire        O_tx_en  ;

dut my_dut(
    .I_sys_clk(I_sys_clk),
    .I_reset_n(I_reset_n),
    .I_rxd    (I_rxd    ),
    .I_rx_dv  (I_rx_dv  ),
    .O_txd    (O_txd    ),
    .O_tx_en  (O_tx_en  )
);

initial begin
    my_driver drv;
    drv = new("drv", null);
    drv.main_phase(null);
    $finish();
end

initial begin
    I_sys_clk <= 1'b1;
    I_reset_n <= 1'b0;
    #(T*10);
    I_reset_n <= 1'b1;
end

always #(T/2) I_sys_clk <= ~I_sys_clk;

endmodule

按以往仿真FPGA设计的流程, 在modelsim新建工程然后添加这3个文件。

在这里插入图片描述

直接全部编译,是会报错的:

# Compile of top_tb.sv was successful.
# Compile of dut.v was successful.
# Compile of my_driver.sv failed with 2 errors.
# 3 compiles, 1 failed with 2 errors.

使用命令对my_driver.sv单独编译打印报错信息:vlog ./demo0/my_driver.sv

在这里插入图片描述

报这个错的原因是 uvm_driver 类的定义没有包含进来,而头文件的包含是在 top_tb.sv 中完成的,所以我们需要指定头文件的路径才能包含进来。vlog 中的 +incdir+<directory> 指令指定include文件路径。关于这个选项官方文档见《modelsim_se_reg.pdf》p817:
注:moselsim文档路径:[Modelsim安装路径]/docs/pdfdocs/
在这里插入图片描述

vlog +incdir+C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src/uvm_pkg.sv ./demo0/dut.v ./demo0/top_tb.sv

当然,绝对路径比较长,可以先将路径设为变量

set UVM_PATH C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src
set SRC_PATH ./demo0 
vlog +incdir+$UVM_PATH $UVM_PATH/uvm_pkg.sv $SRC_PATH/dut.v $SRC_PATH/top_tb.sv 

接下来添加wave信号,运行仿真命令或者在work库右击top_tb→simulate without optimization,进行仿真:
注:如果优化直接把DUT优化掉了,因为输出空载。

vsim -novopt -sv_lib C:/software/FPGA/modeltech64_10.5/uvm-1.1d/win64/uvm_dpi work.top_tb

在这里插入图片描述

仿真结果:
在这里插入图片描述

将上述过程汇总在一个.do文件中,方便后续更新调用
文件名:demo0.do

cd D:/prj/uvm_prj/demo0  
vlib work  
set UVM_PATH C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src
set SRC_PATH . 
vlog +incdir+$UVM_PATH $UVM_PATH/uvm_pkg.sv $SRC_PATH/dut.v $SRC_PATH/top_tb.sv 
vsim -novopt -sv_lib C:/software/FPGA/modeltech64_10.5/uvm-1.1d/win64/uvm_dpi work.top_tb  
add wave -position insertpoint sim:/top_tb/*
run 2us

因为do文件放在源文件相同路径,所以路径稍微改了一下。

将调用do文件的操作用批处理命令实现:
文件名:demo0_do.bat

vsim -do demo0.do

2.2 加入factory机制

引入 UVM 的 factory 机制 自动创建一个类的实例并调用其中的函数和任务。

my_driver.sv:

`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV

class my_driver extends uvm_driver;
    `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 //new()
    extern virtual task main_phase(uvm_phase phase);
endclass //my_driver extends uvm_driver

task my_driver::main_phase(uvm_phase phase);
    `uvm_info("my_driver", "main_phase is called", UVM_LOW);
    top_tb.I_rxd <= 8'd0;
    top_tb.I_rx_dv <= 1'b0;
    while(!top_tb.I_reset_n)
        @(posedge top_tb.I_sys_clk);
    for (int i = 0; i < 256; i++) begin
        @(posedge top_tb.I_sys_clk)
        top_tb.I_rxd <= $urandom_range(0, 255);
        top_tb.I_rx_dv <= 1'b1;
        `uvm_info("my_driver", "data is drived", UVM_LOW);
    end
    @(posedge top_tb.I_sys_clk);
    top_tb.I_rx_dv <= 1'b0;

endtask

`endif 

top_tb.sv:

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

import uvm_pkg::*;

`include "my_driver.sv" 

module top_tb();

parameter T = 10;

reg         I_sys_clk;
reg         I_reset_n;
reg [7:0]   I_rxd    ;
reg         I_rx_dv  ;
wire[7:0]   O_txd    ;
wire        O_tx_en  ;

dut my_dut(
    .I_sys_clk(I_sys_clk),
    .I_reset_n(I_reset_n),
    .I_rxd    (I_rxd    ),
    .I_rx_dv  (I_rx_dv  ),
    .O_txd    (O_txd    ),
    .O_tx_en  (O_tx_en  )
);

initial begin
    // my_driver drv;
    // drv = new("drv", null);
    // drv.main_phase(null);
    run_test("my_driver");
    // $finish();
end

initial begin
    I_sys_clk <= 1'b1;
    I_reset_n <= 1'b0;
    #(T*10);
    I_reset_n <= 1'b1;
end

always #(T/2) I_sys_clk <= ~I_sys_clk;

endmodule

demo0.do的路径改一下,如果改了do文件名字则批处理文件也要对应,改好之后执行,结果如下:

在这里插入图片描述

可以看到,run_test(“my_driver”); 语句完成了 my_driver 的实例化并调用了 main_phase task。但是没有驱动数据,这与作者说的符合。

笔记:

  • factory机制的实现被集成在了一个宏中:uvm_component_utils。这个宏将 my_driver 类登记在 UVM 的一张表中,这张表是实现 factory 功能的基础。
  • 所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册。
  • run_test(“my_driver”); 语句创建实例并调用 main_phase 。

2.3 加入 Objection 机制

UVM中通过objection机制来控制验证平台的关闭。
在每个phase中,UVM会检查是否有 objection 被提起(raise_objection),如果有,那么等待这 objection 被撤销(drop_objection)后停止仿真;如果没有,则马上结束当前phase。
所以虽然 top_tb 没有 $finish ,但是没有 raise_objection, 所以会马上结束当前 phase,还没来得及驱动信号输出。

加入 objection 机制,my_driver.sv:

`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV

class my_driver extends uvm_driver;
    `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 //new()
    extern virtual task main_phase(uvm_phase phase);
endclass //my_driver extends uvm_driver

task my_driver::main_phase(uvm_phase phase);
    phase.raise_objection(this);
    `uvm_info("my_driver", "main_phase is called", UVM_LOW);
    top_tb.I_rxd <= 8'd0;
    top_tb.I_rx_dv <= 1'b0;
    while(!top_tb.I_reset_n)
        @(posedge top_tb.I_sys_clk);
    for (int i = 0; i < 256; i++) begin
        @(posedge top_tb.I_sys_clk)
        top_tb.I_rxd <= $urandom_range(0, 255);
        top_tb.I_rx_dv <= 1'b1;
        `uvm_info("my_driver", "data is drived", UVM_LOW);
    end
    @(posedge top_tb.I_sys_clk);
    top_tb.I_rx_dv <= 1'b0;
    phase.drop_objection(this);

endtask

`endif 

修改do文件运行批处理程序,仿真结果:
在这里插入图片描述

笔记:

  • raise_objection和drop_objection总是成对出现。
  • raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前。 如$display语句是不消耗仿真时间的, 这些语句可以放在raise_objection之前, 但是类似@( posedge top.clk) 等语句是要消耗仿真时间的。

2.4 加入 virtual interface

上诉代码信号驱动使用的是绝对路径,大大降低平台可移植性。

避免绝对路径的两个方法:

  1. 使用宏定义。修改路径只需要修改宏定义。
  2. 使用interface。

可以在 top_tb 定义 interface 然后实例化,但是 my_driver 是一个类,不可以在类中使用这种方式声明 interface。在类中使用的是 virtual interface。

my_driver.sv:

`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV

class my_driver extends uvm_driver;

    virtual my_if vif;

    `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 //new() 

    virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase); // 因为在其父类的build_phase中执行了一些必要的操
作, 这里必须显式地调用并执行它。
      `uvm_info("my_driver", "build_phase is called", UVM_LOW);
      if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif)) // config_db
         `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
   endfunction

    extern virtual task main_phase(uvm_phase phase);
endclass //my_driver extends uvm_driver

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 

top_tb.sv 增加:

// 模块内增加  
initial begin
   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end

// 模块外增肌 
interface my_if(input clk, input rst_n);
    logic [7:0] data ;
    logic       valid;
endinterface 
  • build_phase
    与main_phase一样, build_phase也是UVM中内建的一个phase。
    在build_phase中主要通过config_db的set和get操作来传递一些数据,以及实例化成员变量等。
    build_phase与main_phase不同的一点在于,build_phase是一个函数phase,而main_phase是一个任务phase,build_phase是不消耗仿真时间的。build_phase总是在仿真时间($time函数打印出的时间)为0时执行。
  • config_db
    set和get函数都有四个参数, 这两个函数的第三个参数必须完全一致。 set函数的第四个参数表示要将哪个interface 通过config_db传递给my_driver, get函数的第四个参数表示把得到的interface传递给哪个my_driver的成员变量。
    • set:可以简单地理解成是“寄信”
    • get: 可以简单地理解成是“收信”

仿真结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lu-ming.xyz

觉得有用的话点个赞吧 :)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值