UVM实战[二]

本期将讲解UVM环境构成和启动方式。主要参考资料为

http://bbs.eetop.cn/thread-320165-1-1.html
http://rockeric.com/

环境构成

进行仿真验证的基本流程是

  1. 例化DUT
  2. 产生并发送激励
  3. 检测响应
  4. 检查响应是否正确

在验证环境中,产生并发送激励将会交给两个不同的类完成,即uvm_driver和uvm_sequence,检测响应通过uvm_monitor完成,而检查响应是否正确通过uvm_scoreboard。除了保证某项功能正确,我们还需要能够确保spec中的每一项功能都通过测试,而衡量验证完备性的指标之一就是功能覆盖率,在我们的验证环境中收集功能覆盖率的任务则交给了conv_coverage实现。

接下来将以数据从uvm_driver驱动到DUT,再从DUT到uvm_monitor,再到uvm_scoreboard的顺序讲解验证环境的构成。

接口定义

当我们需要进行仿真验证时,与DUT的交互是一个必要的内容,所我们首先分析DUT的接口,较为简单,一共有四组接口,一组寄存器配置接口,三组数据接口用于输入特征图、权重和偏置数据的读取,一组数据接口用于输出特征图的存储接口。

interface的定义在顶层的tb.sv中,三组输入数据几口可以使用同一类型的接口实现,下列代码中的具体内容省略了,详情请自行查看。最后一组接口用于检测寄存器的内容,当前版本没有使用寄存器模型,所以这个接口是必要的。

interface cfg_intf (input clk , input rst_n);
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
  endclocking
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
  endclocking
endinterface

interface mem_in_intf (input clk , input rst_n);
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
  endclocking
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
  endclocking
endinterface

interface mem_out_intf (input clk , input rst_n);
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
  endclocking
endinterface

interface conv_intf (input clk , input rst_n);
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
  endclocking
endinterface

注意在interface定义中,分别定义了两个时钟块,一组驱动用的时钟块,一组检测用的时钟块,目的就是为了模拟真实的建立保持时间,时钟块的具体用法可以参考绿皮书的第四章内容。

环境组件

有了接口定义以后,通过接口定义,我们便能够与DUT交互,那么进行交互我们需要做什么呢?

首先看整体结构,如果看不清,后台回复UVM结构图获取VISIO文件。按照接口进行分类,可以分成两大类,一类通过接口与DUT实现交互,另外一类构成了其他的组件,例如checker,通过其他组件收集到的数据进行数据比对,保证DUT的功能正确性。

验证环境一共由四个PKG组成,通过在顶层import导入:

import cfg_pkg::*;
import mem_in_pkg::*;
import mem_out_pkg::*;
import conv_pkg::*;

与DUT直接联系的组件

我们从DUT与验证环境的接口处开始说起,cfg_pkg包含了对于寄存器进行验证的组件。构建UVM环境基本的几个组件包括uvm_driver,uvm_sequencer,uvm_monitor,uvm_agent。而uvm_sequence_item和uvm_sequence则不属于环境的组件,他们是环境组件之间传递信息的载体。

package cfg_pkg;
  import uvm_pkg::*;
  `include "uvm_macros.svh"

  typedef enum {WR,RD,IDLE}cmd_t;

  class cfg_item extends uvm_sequence_item;
  endclass : cfg_item

  class cfg_driver extends uvm_driver #(cfg_item);
  endclass :cfg_driver

  class cfg_sequencer extends uvm_sequencer #(cfg_item);
  endclass: cfg_sequencer

  class cfg_base_sequence extends uvm_sequence #(cfg_item);
  endclass: cfg_conv_sequence

  class cfg_monitor extends uvm_monitor;
  endclass: cfg_monitor

  class cfg_agent extends uvm_agent;
    cfg_driver driver;
    cfg_monitor monitor;
    cfg_sequencer sequencer;
    local virtual cfg_intf vif;
  endclass:cfg_agent

endpackage

UVM的思想之一就是要降低组件之间的耦合度,让组件的功能更加单纯。

uvm_driver和uvm_monitor是距离DUT最近的两个组件,可以直接与DUT的接口进行互动。

uvm_monitor通过检测接口上的信号,转化为数据包,如实地发送给checker,只实现这一单纯的功能,而对于驱动DUT这一功能则交给uvm_driver。

uvm_driver通过从uvm_sequencer获取到的uvm_sequence_item解析出驱动数据,如实的将uvm_sequence_item的内容驱动到DUT的接口上,也只实现这一单纯的功能,至于具体的激励内容,则通过uvm_sequencer暴露接口给顶层环境,让验证人员通过uvm_sequencer发送激励。

而uvm_sequencer的功能就更加简单了,只需要实现传递uvm_sequence_item即可,不需要关注其他的工作。

而uvm_agent中则通常会例化四个组件,uvm_driver,uvm_sequencer,uvm_monitor和对应的interface。uvm_agent的功能也非常单一,仅仅只是将对于一组接口的相关组件进行一个打包,把他们整合起来,这样在顶层就只需要例化这一个组件即可。

mem_in_pkg和mem_out_pkg的内容整体上和cfg_pkg基本一致,不再赘述。

与DUT没有直接联系的组件

通过cfg_pkg的内容,我们实现了对DUT的驱动与检测,那么驱动的内容从何而来,而检测的数据包又要发送到哪里去呢?从前面图中我们可以看到,除了五个agent以外,我们还有其他的组件,包括conv_checker,conv_coverage和conv_virtual_sequencer。

  class conv_checker extends uvm_scoreboard;
  endclass:conv_checker

  class conv_coverage extends uvm_component;
  endclass:conv_coverage

  class conv_virtual_sequencer extends uvm_sequencer;
    cfg_sequencer    cfg_sqr;
    mem_in_sequencer fmi_sqr;
    mem_in_sequencer wt_sqr;
    mem_in_sequencer bias_sqr;
  endclass:conv_virtual_sequencer

conv_checker继承自uvm_scoreboard,他通过前面所述的五个agent中的monitor,获取DUT的信息,进行数据对比检查。在现在的DUT中,他所实现的功能是,在一次卷积运算结束后,使用软件算法直接进行卷积运算,然后与DUT的计算结果进行对比,确保DUT功能正确。

UVM并没有预置的类用于覆盖率收集,所以conv_coverage继承自uvm_component,成为最简单的UVM组件。由于除了寄存器的接口以外,其他都是简单得sram接口,所以只对寄存器进行覆盖率收集。覆盖率组件通过覆盖率的收集,量化功能验证的完备性,根据对应的功能点,设定对应的覆盖率,而功能验证的目的就是为了达到100%的功能覆盖率。

conv_virtual_sequencer继承自uvm_sequencer,其本身并没有什么功能,所以他的名字中带有virtual,他只是一个虚拟的sequencer。其内部包含了cfg_sqr,fmi_sqr,wt_sqr和bias_sqr,作用就是将他们整合在一起,sequencer就是一根数据线,uvm_sequence_item就是传输的数据,而virtual_sequencer就一个集线器或者说一个拓展坞,把很多条数据线绑在一起。

conv_env

数据的驱动由uvm_driver实现,检测由uvm_monitor实现,激励由uvm_sequencer传递,数据对比由conv_checker实现,覆盖率收集由conv_coverage实现。那么接下来就需要把这些组件全部整合在一起,成为一个验证环境,这便是conv_env。在这里,我们只需要完成各个组件的例化和他们之间的连接,不要关心其他工作。

  class conv_env extends uvm_env;
    cfg_agent cfg_agt;
    mem_in_agent fmi_agt;
    mem_in_agent wt_agt;
    mem_in_agent bias_agt;
    mem_out_agent fmo_agt;
    conv_checker chker;
    conv_coverage cvrg;
    conv_virtual_sequencer virt_sqr;
  endclass: conv_env

uvm_test

现在我们已经获得了一个针对卷积模块的DUT,那么如何开始仿真测试?回忆前面所提到的virtual_sequencer,我们只需要通过virtual_sequencer对每个DUT的接口进行驱动,就能让DUT运转起来。针对每一个测试,我们需要创建对应的uvm_test类,然后再uvm_test内通过virtual_sequencer进行激励发送即可。

  class conv_base_test extends uvm_test;
    conv_env env;

    task run_phase(uvm_phase phase);
      phase.raise_objection(this);
      this.run_top_virtual_sequence();
      phase.drop_objection(this);
    endtask

    virtual task run_top_virtual_sequence();
    endtask

  endclass: conv_base_test

上述代码中的run_phase的内容就是在构建环境后,整个仿真真正需要进行的测试内容。可以看到我们定义了一个run_top_virtual_sequence方法,用于运行virtual_sequence。

与virtual_sequencer对应的,virtual_sequence就是virtual_sequencer所需要传输的内容,它的内部会包括各式各样的sequence,针对每一个agent发送不同的激励。通过修改virtual_sequence的内容,我们就能够完成不同的测试用例。

启动方式

这里先不讨论整个环境的树状结构、连接方式和运行机制,这些将在后续的推送中实现。

在构建完整个环境和测试用例以后,我们就需要在顶层启动测试。

module tb ();
logic clk;
logic rst_n;
    conv i_conv ();//这里省略了端口连接,具体请参考实验代码
  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end
  // reset trigger
  initial begin
    #10 rst_n <= 0;
    repeat(10) @(posedge clk);
    rst_n <= 1;
  end
  import uvm_pkg::*;
  `include "uvm_macros.svh"
  import cfg_pkg::*;
  import mem_in_pkg::*;
  import mem_out_pkg::*;
  import conv_pkg::*;

cfg_intf cfg_if(.*);
mem_in_intf fmi_if(.*);
mem_in_intf wt_if(.*);
mem_in_intf bias_if(.*);
mem_out_intf fmo_if(.*);
conv_intf conv_if(.*);

assign conv_if.start              =i_conv.i_regfile.start              ;
assign conv_if.done               =i_conv.i_regfile.done               ;
assign conv_if.fmap_in_w          =i_conv.i_regfile.fmap_in_w          ;
assign conv_if.fmap_in_h          =i_conv.i_regfile.fmap_in_h          ;
assign conv_if.fmap_in_ch_div_32  =i_conv.i_regfile.fmap_in_ch_div_32  ;
assign conv_if.k_w                =i_conv.i_regfile.k_w                ;
assign conv_if.k_h                =i_conv.i_regfile.k_h                ;
assign conv_if.fmap_out_w         =i_conv.i_regfile.fmap_out_w         ;
assign conv_if.fmap_out_h         =i_conv.i_regfile.fmap_out_h         ;
assign conv_if.fmap_out_w_div_32  =i_conv.i_regfile.fmap_out_w_div_32  ;
assign conv_if.fmap_out_ch_div_32 =i_conv.i_regfile.fmap_out_ch_div_32 ;
assign conv_if.pooling_bypass     =i_conv.i_regfile.pooling_bypass     ;
assign conv_if.act_bypass         =i_conv.i_regfile.act_bypass         ;
assign conv_if.padding_cnt        =i_conv.i_regfile.padding_cnt        ;
assign conv_if.stripe             =i_conv.i_regfile.stripe             ;
assign conv_if.last_pixel         =i_conv.i_regfile.last_pixel         ;
assign conv_if.last_pixel_div_32  =i_conv.i_regfile.last_pixel_div_32  ;
assign conv_if.fmap_out_ch        =i_conv.i_regfile.fmap_out_ch        ;
assign bias_if.addr[15:8]='0;

  initial begin
    uvm_config_db#(virtual mem_in_intf)::set(uvm_root::get(), "uvm_test_top", "fmi_in_vif", fmi_if);
    uvm_config_db#(virtual mem_in_intf)::set(uvm_root::get(), "uvm_test_top", "wt_vif", wt_if);
    uvm_config_db#(virtual mem_in_intf)::set(uvm_root::get(), "uvm_test_top", "bias_vif", bias_if);
    uvm_config_db#(virtual cfg_intf)::set(uvm_root::get(), "uvm_test_top", "cfg_vif", cfg_if);
    uvm_config_db#(virtual mem_out_intf)::set(uvm_root::get(), "uvm_test_top", "fmo_vif", fmo_if);
    uvm_config_db#(virtual conv_intf)::set(uvm_root::get(), "uvm_test_top", "conv_vif", conv_if);
    // If no external configured via +UVM_TESTNAME=my_test, the default test is
    // std_test
    run_test("std_test");
  end

endmodule : tb

在tb的顶层模块中,我们要做5件事:

  1. 定义时钟与复位
  2. 例化dut
  3. 例化与连接各个interface
  4. 将每个interface句柄通过uvm_config_db传递到环境中去
  5. 通过run_test()方法启动测试

uvm_config_db是UVM所提供用于传递数据的静态方法,在后续的推送中将会展开讲解。这里值得注意的是,一定要在run_test()之前实现uvm_config_db传递,否则在run_test()开始后,环境内部将无法获取句柄,导致报错。

run_test()是UVM提供的测试启动方法,传递参数是一个字符串变量,该字符串将用于指定默认的testcase。如果在命令选项中,没有通过+UVM_TESTNAME指定具体的TESTNAME,将会运行默认的testcase。

总结

本次讲解了验证环境的基本组件和构成,以及在顶层启动的注意事项。

  1. 与DUT直接交互的组件为uvm_driver和uvm_monitor,传递激励信息的组件为uvm_sequencer,uvm_agent将三者组合起来。
  2. uvm_scoreboard获取各个uvm_monitor传递过来的数据,进行比对,保证DUT功能的正确性。
  3. conv_coverage用于收集覆盖率
  4. conv_virtual_sequencer将每个uvm_agent中的uvm_sequencer集中起来进行管理,起到集线器或者说路由器的效果
  5. conv_env将上述所有组件容纳起来,并且进行连接
  6. uvm_test通过编写conv_virtual_sequence,经由conv_virtual_sequencer发送激励实现不同的testcase
  7. 在顶层实现各项例化,并且在run_test()之前传递接口句柄
  8. 通过run_test()启动测试,并且指定默认testcase

更多内容请期待后续推送,实验代码在后台回复“UVM实验”即可获得资源。

原文地址:https://www.cnblogs.com/icparadigm/p/12268327.html

时间: 2024-10-16 14:41:59

UVM实战[二]的相关文章

Python爬虫实战二之爬取百度贴吧帖子

大家好,上次我们实验了爬取了糗事百科的段子,那么这次我们来尝试一下爬取百度贴吧的帖子.与上一篇不同的是,这次我们需要用到文件的相关操作. 前言 亲爱的们,教程比较旧了,百度贴吧页面可能改版,可能代码不好使,八成是正则表达式那儿匹配不到了,请更改一下正则,当然最主要的还是帮助大家理解思路. 2016/12/2 本篇目标 1.对百度贴吧的任意帖子进行抓取 2.指定是否只抓取楼主发帖内容 3.将抓取到的内容分析并保存到文件 1.URL格式的确定 首先,我们先观察一下百度贴吧的任意一个帖子. 比如:ht

FastDFS安装使用实战二(配置篇)

FastDFS安装使用实战二(配置篇) Keywords:FastDFS.分布式文件系统.Ubuntu Author:soartju 转载请注明出处:http://soartju.iteye.com/blog/803524 FastDFS的配置文件在%FastDFS%/conf目录下,其中包括 Client.conf    客户端上传配置文件 Storage.conf    文件存储服务器配置文件 Tracker.conf    负责均衡调度服务器配置文件 http.conf        ht

Python机器学习实战&lt;二&gt;:机器学习概述

1.机器学习的真实含义是利用数据来彰显数据背后的真实含义. 2.机器学习的一般用例:人脸识别.手写数字识别.垃圾邮件过滤.产品推荐等等. 3.机器学习的主要任务是分类,即将实例数据划分到合适的分类中.另一项任务是回归,主要用于预测数值型数据.分类和回归属于监督学习,之所以称为监督学习,是因为这类算法必须知道预测什么,即目标的分类信息.另一种机器学习方式是无监督学习,此时数据没有类别信息,也没有给定的目标.在无监督学习中,将数据集合分成由类似对象组成的多个类成为聚类,将寻找数据统计值的过程称为密度

转 Python爬虫实战二之爬取百度贴吧帖子

静觅 » Python爬虫实战二之爬取百度贴吧帖子 大家好,上次我们实验了爬取了糗事百科的段子,那么这次我们来尝试一下爬取百度贴吧的帖子.与上一篇不同的是,这次我们需要用到文件的相关操作. 本篇目标 1.对百度贴吧的任意帖子进行抓取 2.指定是否只抓取楼主发帖内容 3.将抓取到的内容分析并保存到文件

二 Flask Project 实战 二

5 templates {{ and }} is an expression{% and %} denotes a control flow statement like if and for blocks defined here that will be overridden in the other templates{% extends 'base.html' %} tells Jinja that this template should replace the blocks from

Docker最全教程之Python爬网实战(二十一)

原文:Docker最全教程之Python爬网实战(二十一) Python目前是流行度增长最快的主流编程语言,也是第二大最受开发者喜爱的语言(参考Stack Overflow 2019开发者调查报告发布).笔者建议.NET.Java开发人员可以将Python发展为第二语言,一方面Python在某些领域确实非常犀利(爬虫.算法.人工智能等等),另一方面,相信我,Python上手完全没有门槛,你甚至无需购买任何书籍! 由于近期在筹备4.21的长沙开发者大会,耽误了不少时间.不过这次邀请到了腾讯资深技术

数据-第16课-栈的应用实战二

第16课-栈的应用实战二 1. 问题的提出 计算机的本质工作就是数学运算,那计算机可以读入字符串”9 + (3 - 1) *5 +8/2”并且计算值吗? 2. 后缀表达式 波兰科学家在20世纪50年代提出了一种将运算符放在数字后面的后缀表达式. 对应的,我们平时用的数学表达式叫做中缀表达式. 实例 5 + 3 => 5 3 + 1 + 2 * 3 => 1 2 3 * + 9 + ( 3–1 ) * 5 => 9 3 1–5 * + 中缀表达式符合人类的阅读和思维习惯:后缀表达式符合计算

数据--第20课-递归的应用实战二

第20课-递归的应用实战二 1. 递归与回溯 (1)递归在程序设计中也常用于需要回溯算法的场合. (2)回溯算法的基本思想. ① 从问题的某一种状态出发,搜索可以到达的所有状态. ② 当某个状态到达后,可向前回退,并继续搜索其它可达状态 ,并继续搜索其它可达状态. ③ 当所有状态都到达后,回溯算法结束. (3)程序设计中可利用函数的活动对象保存回溯算法的状态数据,因此可以利用递归完成回溯算法 2. 八皇后问题 在一个8×8国际象棋盘上,有8个皇后,每个皇后占一格:要求皇后间不会出现相互“攻击”的

C# Redis实战(二)

二.Redis服务 在C# Redis实战(一)中我将所有文件拷贝到了D盘redis文件夹下,其中redis-server.exe即为其服务端程序,双击即开始运行,如图 可以将此服务设置为windows系统服务,下载Redis服务安装软件,安装即可. 安装完成在服务中找到此服务,将其设置为自动延迟启动即可. 再回到redis文件夹下,找到redis-cli.exe文件,它就是Redis客户端程序.打开,输入:set qiujialong 123 即在Redis中插入了一条key为qiujialo