reference:https://www.cnblogs.com/bettty/p/5285785.html

Abstract

本文介绍UVM框架,并以crc7为例进行UVM的验证,最后指出常见的UVM验证开发有哪些坑,以及怎么避免。

Introduction

本例使用环境:ModelSim 10.2c,UVM-1.1d,Quartus II 13.1(64 bit),器件库MAX V

1. UVM介绍

对UVM结构熟悉的读者可跳过本节。

叫UVM“框架”可能并不确切(只是便于理解,可类比软件界的“框架”)。UVM全称为通用验证方法论。在硬件开发过程中,验证是十分重要的环节。可以说,左手开发,右手验证。在历史上,为了实现通用化的验证,前人摸爬滚打,创造出了UVM这一套框架。UVM前身是OVM,两者都是Accellera提出,UVM在OVM的基础上有所改进。

本文旨在用一种简单的方式介绍UVM的结构。期望读者能够读完本文后,成功搭建一个完整的UVM验证系统。

Part 1:

UVM的功能

请看下图,一个典型的testbench验证过程如图所示。即,我们写testbench,将激励信号传入DUT(待验证模块),然后观察输出波形,或者查看输出结果,是否和预期的一致。通过这样的过程,我们判断我们编写的Verilog是否正确。

请看下图,UVM如同一个管家,将“输入激励”和“观察波形”的动作管理了起来。基于UVM进行开发,UVM提供了很多机制,也能够快速的产生我们想要输入的激励。

问题是,我们完全可以使用testbench解决问题,为什么还要使用UVM呢?

UVM是一个通用验证平台,基于它,我们可以产生复杂、大量、可定制化的随机激励,并可以提高大型验证工程的协作性和扩展性。举个例子,UVM框架就像软件开发的分层结构,定义好了统一的接口,那么,各个层次就可以交给各个团队来开发。验证项目也是如此,产生激励的工程如果有改动,并不会影响“观察波形”(实质是观察结果)的团队。实际上,UVM分的更细,它将各个流程都拆分开来,包括transaction、driver、sequence、sequencer、monitor、agent、test、env、top等部分。此外,UVM提供了优秀的factory机制、objection机制、reg机制,为我们简化开发过程。比如,reg机制就封装了我们在硬件开发中读写寄存器的一些操作。我们调用UVM的函数,就能够迅速的开发读写reg的过程。

Part 2:

UVM的结构

如前所述,UVM包括transaction、interface、driver、sequence、sequencer、monitor、reference model、agent、test、env、top等部分,其相互关系极其复杂。不如说,UVM牺牲简洁性换来“通用”性。

借用Pedro Araujo的结构图。

1) 正如这个图片所展示的,UVM是除了DUT(待验证模块)的其他所有部分。其中,sequencer产生sequence(图上没画),sequence产生transaction。

transaction,类似于软件中的一个package。在硬件中,以一个transaction为单位进行传输,一个完整的transaction传输结束,才拉高或拉低电平。

2)通过UVM的专门的类型——port把数据给driver。driver通过interface把产生的激励(也就是transaction)输入DUT。同时,DUT的输出也是和interface相连接的。一个monitor(monitor after)监测driver吐给DUT的输入,一个monitor(monitor before)监测DUT吐出来的输出。

3) 这里,看到一个agent把整个monitor、sequencer、driver都装起来了。这个agent实现的功能是转换。因为整个UVM都是systemverilog的,并且理论上是仿真的,都不是“硬件”。DUT在这里是真正的“硬件”。两者之间不能直接通信,只能通过一个agent,来对协议进行转换。不过,我们可以不用管agent怎么实现的。在开发的时候,只要把相关的模块连接好就行了。

4)这个图还少了一个reference model。因为reference model的工作在这个例子中,实际是在monitor after里面实现的。- -不过没关系。reference model完成的工作是,把DUT做的事完全的再做一遍。reference model接入monitor采到的输入激励,按照DUT的逻辑,产生一个结果。

5)同样通过port,把reference model产生的结果同monitor before采到的数据都丢到scoreboard上。在scoreboard上,我们会对两个结果进行比较。如果两个结果一致,则为正确。如果不一致,则为错误。

UVM本身用700页来写也完全可以。因为UVM极其复杂。本文不在此赘述。

2. 以crc7为例进行UVM的验证

Part 1:

搭建环境。

本文使用的Quartus II 13.1(64 bit),器件库MAX V。写了一个Verilog的简单的crc7。

仿真环境是ModelSim 10.2c。虽说自带UVM库。但是,没找到Modelsim自带的uvm_dpi.dll,于是,还重新编译了一番。

本文在win 10下。下载uvm-1.1d(现在最新版本有1.2d了),放好。安装好ModelSim 10.2c后,在命令提示符>后,输入

编辑环境变量

1
2
set UVM_HOME c:/tool/uvm-1.1d
set MODEL_TECH c:/modeltech_10.2c/win32

编译UCM_DPI动态链接库。编好一次就不用再编了。

1
c:/modeltech_10.2c/gcc-4.2.1-mingw32vc9/bin/g++.exe -g -DQUESTA -W -shared -Bsymbolic -I $MODEL_TECH/../include $UVM_HOME/src/dpi/uvm_dpi.cc -o $UVM_HOME/lib/uvm_dpi.dll $MODEL_TECH/mtipli.dll -lregex

Part 2:

编写待验证模块。

module crc7(clk,
rst,
data,
crc);
input wire clk;
input wire rst;
input wire data;
output reg[6:0] crc; reg g0;
assign g0 = data ^ crc[6]; always @(posedge rst or negedge clk)
if (rst)
begin
crc <= 7'b0000000;
end
else
begin
crc[6] <= crc[5];
crc[5] <= crc[4];
crc[4] <= crc[3];
crc[3] <= crc[2] ^ g0;
crc[2] <= crc[1];
crc[1] <= crc[0];
crc[0] <= g0;
end endmodule

在Quartus中编译通过。

Part 3:

编写验证代码。

在Modelsim中新建一个项目,新建如图所示多个.sv文件。

  crc7_tb_top.sv如下所示

 1 `include "uvm_pkg.sv"
2 `include "crc7_pkg.sv"
3 `include "crc7.v"
4 `include "crc7_if.sv"
5
6 module crc7_tb_top;
7 import uvm_pkg::*;
8 import crc7_pkg::*;
9
10 //interface declaration
11 crc7_if vif();
12
13 //connect the interface to the DUT
14 crc7 dut(vif.sig_clk,
15 vif.sig_rst,
16 vif.sig_data,
17 vif.sig_crc);
18
19 initial begin
20 uvm_resource_db#(virtual crc7_if)::set
21 (.scope("ifs"), .name("crc7_if"), .val(vif));
22
23 run_test("crc7_test");
24 end
25
26 initial begin
27 vif.sig_clk <= 1'b1;
28 end
29
30 always
31 #5 vif.sig_clk =~ vif.sig_clk;
32 endmodule
33
34
35

  crc7_monitor.sv如下所示

 1 class crc7_monitor_before extends uvm_monitor;
2 `uvm_component_utils(crc7_monitor_before)
3
4 uvm_analysis_port#(crc7_transaction) mon_ap_before;
5
6 virtual crc7_if vif;
7
8 function new(string name, uvm_component parent);
9 super.new(name, parent);
10 endfunction: new
11
12 function void build_phase(uvm_phase phase);
13 super.build_phase(phase);
14
15 void'(uvm_resource_db#(virtual crc7_if)::read_by_name(.scope("ifs"), .name("crc7_if"), .val(vif)));
16 mon_ap_before = new(.name("mon_ap_before"), .parent(this));
17 endfunction: build_phase
18
19 task run_phase(uvm_phase phase);
20 crc7_transaction c7_tx;
21 c7_tx = crc7_transaction::type_id::create(.name("c7_tx"), .contxt(get_full_name()));
22
23 forever begin
24 @(negedge vif.sig_clk)
25 begin
26 c7_tx.crc = vif.sig_crc;
27 `uvm_info("monitor_before",$sformatf("c7_tx.crc is '%b'", c7_tx.crc), UVM_LOW);
28 mon_ap_before.write(c7_tx);
29 end
30 end
31 endtask: run_phase
32 endclass: crc7_monitor_before
33
34 class crc7_monitor_after extends uvm_monitor;
35 `uvm_component_utils(crc7_monitor_after)
36
37 uvm_analysis_port#(crc7_transaction) mon_ap_after;
38
39 virtual crc7_if vif;
40
41 crc7_transaction c7_tx;
42 //For coverage
43 crc7_transaction c7_tx_cg;
44
45 //Define coverpoints
46 covergroup crc7_cg;
47 endgroup: crc7_cg
48
49 function new(string name, uvm_component parent);
50 super.new(name, parent);
51 crc7_cg = new;
52 endfunction: new
53
54 function void build_phase(uvm_phase phase);
55 super.build_phase(phase);
56
57 void'(uvm_resource_db#(virtual crc7_if)::read_by_name(.scope("ifs"), .name("crc7_if"), .val(vif)));
58 mon_ap_after = new(.name("mon_ap_after"), .parent(this));
59 endfunction: build_phase
60
61 task run_phase(uvm_phase phase);
62 integer count = 42, rst = 0;
63
64 c7_tx = crc7_transaction::type_id::create(.name("c7_tx"), .contxt(get_full_name()));
65
66 forever begin
67 @(negedge vif.sig_clk)
68 begin
69 rst = 0;
70 count = count - 1;
71 if(count == 0)
72 begin
73 rst = 1;
74 count = 42;
75 predictor();
76 `uvm_info("monitor_after",$sformatf("c7_tx.crc is '%b'", c7_tx.crc), UVM_LOW);
77 c7_tx_cg = c7_tx;
78
79 crc7_cg.sample();
80
81 mon_ap_after.write(c7_tx);
82 end
83 end
84 end
85
86 endtask: run_phase
87
88 virtual function void predictor();
89 c7_tx.crc = 7'b0101010;
90 endfunction: predictor
91 endclass: crc7_monitor_after

  crc7_sequencer.sv如下所示

 1 class crc7_transaction extends uvm_sequence_item;
2 bit[6:0] crc;
3
4 function new(string name = "");
5 super.new(name);
6 endfunction: new
7
8 `uvm_object_utils_begin(crc7_transaction)
9 `uvm_field_int(crc, UVM_ALL_ON)
10 `uvm_object_utils_end
11 endclass: crc7_transaction
12
13 class crc7_sequence extends uvm_sequence#(crc7_transaction);
14 `uvm_object_utils(crc7_sequence)
15
16 function new(string name = "");
17 super.new(name);
18 endfunction: new
19
20 task body();
21 crc7_transaction c7_tx;
22
23 repeat (1) begin
24 c7_tx = crc7_transaction::type_id::create(.name("c7_tx"), .contxt(get_full_name()));
25
26 start_item(c7_tx);
27 assert(c7_tx.randomize());
28 finish_item(c7_tx);
29 //c7_tx.end_event.wait_on();
30 end
31 endtask: body
32 endclass: crc7_sequence
33
34 typedef uvm_sequencer#(crc7_transaction) crc7_sequencer;
35
36

crc7_driver.sv如下所示

 1 class crc7_driver extends uvm_driver#(crc7_transaction);
2 `uvm_component_utils(crc7_driver)
3
4 virtual crc7_if vif;
5
6 function new(string name, uvm_component parent);
7 super.new(name, parent);
8 endfunction: new
9
10 function void build_phase(uvm_phase phase);
11 super.build_phase(phase);
12
13 void'(uvm_resource_db#(virtual crc7_if)::read_by_name(.scope("ifs"), .name("crc7_if"), .val(vif)));
14 endfunction: build_phase
15
16 task run_phase(uvm_phase phase);
17 drive();
18 endtask: run_phase
19
20 virtual task drive();
21 crc7_transaction c7_tx;
22 integer counter = 40;
23 bit[39:0] data;
24 data = 40'b0101000100000000000000000000000000000000;
25 vif.sig_data = 1'b0;
26 vif.sig_rst = 1'b0;
27
28
29 forever begin
30
31 //seq_item_port.get_next_item(c7_tx);
32
33 @(negedge vif.sig_clk)
34 begin
35 vif.sig_rst = 1'b0;
36 vif.sig_data = data[counter - 1];
37 counter = counter - 1;
38 if(counter == 0) begin
39 #28 counter = 40;
40 vif.sig_rst = 1'b1;
41 end
42 end
43 //seq_item_port.item_done();
44 end
45 endtask: drive
46 endclass: crc7_driver
47
48
49

(其余代码请在文章末尾下载。 )

其中,crc7_tb_top.sv第31行:

#5 vif.sig_clk =~ vif.sig_clk;

表示每5个时钟单位反向一次。即时钟周期为10ns。

crc7_monitor.sv中第1行和第34行,分别新建一个名为crc7_monitor_before和crc7_monitor_after的类。第26行,

c7_tx.crc = vif.sig_crc;

crc7_monitor_before类采集DUT输出的信号。本测试用例中只有一个输出信号,即计算出的crc。vif是interface类的实例。前文中说过,monitor是从interface上采集的。

第27行,

`uvm_info("monitor_before",$sformatf("c7_tx.crc is '%b'", c7_tx.crc), UVM_LOW);

使用UVM提供的宏打印格式化的数据。UVM提供的宏可类比软件中的库函数。就是无需声明,只管调用。

第28行,

mon_ap_before.write(c7_tx);

将采集到的数据写入ap中。ap是什么?ap是UVM中的port类型之一,叫analysis_port。简单说就像硬件的一个接口,协议UVM已经搞好了。我们只管读写就好了。

从第66行到第84行,都是本来应该在reference model里面实现的。就是说,从结构上来说,monitor采集到了driver发到interface上的信号,会丢给reference model来计算出一个结果(这是比较标准的结构)。本例中,由于reference model要做的事情太简单,就直接放在monitor里面做了。当monitor_after采集到了输入激励后,就在自己内部算了一把,然后把结果写出去。这就是66-84行所起的作用。由于crc7的运算结果可以查表。本例就直接查了表。也就是说,UVM完全可以实现输入激励的随机化。但是本例没用。本例使用了一对输入/输出数据。并且在第88行,直接把输出数据返回了。这对输入/输出数据为:40'b0101000100000000000000000000000000000000;7'b0101010;

同样,在第81行,

mon_ap_after.write(c7_tx);

把正确的结果写入ap。注意,ap是成对的。有写就有读。

crc7_sequencer.sv中,第26-28行,

26       start_item(c7_tx);
27 assert(c7_tx.randomize());
28 finish_item(c7_tx)

是规定动作。有开始就有结束。随机化也是必须写的,缺一不可。

crc7_driver.sv中第33行至42行,

33       @(negedge vif.sig_clk)
34 begin
35 vif.sig_rst = 1'b0;
36 vif.sig_data = data[counter - 1];
37 counter = counter - 1;
38 if(counter == 0) begin
39 #28 counter = 40;
40 vif.sig_rst = 1'b1;
41 end
42 end

完成的工作是,每一个时钟节拍打出data的1位。data见第24行。本来应该由sequence产生随机激励的,由于本例使用的是查表验证,因此,并没有使用UVM的随机功能。因此,要随机的数据没有在sequence里面写进去,要打入的数据在driver模块中写入了vif。这样完成了设定数据的输入。

Part 4:

编译仿真。

编译crc7_tb_top.sv,

vlog +incdir+C:/modeltech_10.2c/verilog_src/uvm-1.1d/src -L mtiAvm -L mtiOvm -L mtiUvm -L mtiUPF crc7_tb_top.sv

crc7_tb_top是整个工程的入口。

仿真crc7_tb_top,

vsim -ldflags "-lregex" -t 1ns -c -sv_lib c:/modeltech_10.2c/uvm-1.1d/win32/uvm_dpi work.crc7_tb_top

vsim的-ldflags参数是将后面的字符串“-lregex”作为参数传入到mingw里面。mingw会原封不动的传入给其调用的g++。至于为什么要这么做?因为按照命令,

vsim -c -sv_lib $UVM_HOME/lib/uvm_dpi work.hello_world_example  

编译的时候报错了,提示找不到uvm_dpi。明明编译了放在对的地方,可是就是找不到。于是,将路径写死在命令里。成功仿真。

添加波形,得到波形如图(截取部分):

查看打印结果。仿真结果和正确值相比,如图所示:

其中,每一个时钟周期打印的是,当前时刻移位后的crc7值。在40个bit结束后,移位结果为0101010,正确结果也是0101010,因此,compare OK。

3. UVM验证开发常见有哪些坑?怎样避免。

Q1:为什么状态机走入了未预料的分支?

在时钟边沿采集与时钟同样跳变的信号时,这一瞬间采集到的信号可能是高,也可能是低。此时,若做逻辑判断,可能出现无法预料的情况。

解决的方法是,加入其它逻辑判断条件,确保程序逻辑正确。

Q2:为什么reference model和DUT的结果计算时序不一致?

一般来说,一组数据经过DUT的处理,会有一定的时延。而reference model没有时延。我们希望在同一时刻,对reference model和DUT计算的结果在scoreboard中比较,则须考虑两者运行的时间差。

有两种方法解决:一是手动添加时延,比较简单,但是有可能出错;二是使用UVM的FIFO机制,把先到达的reference model输出的数据放入到一个队列中。确保仿真时间结束后,用来比较的两个结果都是基于同样的激励输入。

本文用例完整代码下载:uvm-crc-test.zip

基于UVM的verilog验证(转)的更多相关文章

  1. 基于UVM的verilog验证

    Abstract 本文介绍UVM框架,并以crc7为例进行UVM的验证,最后指出常见的UVM验证开发有哪些坑,以及怎么避免. Introduction 本例使用环境:ModelSim 10.2c,UV ...

  2. 基于 Token 的身份验证方法

    使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录.大概的流程是这样的: 客户端使用用户名跟密码请求登录 服务端收到请求,去验证用户名与密码 验证成功后,服务端会签发一个 Toke ...

  3. 基于Token的身份验证——JWT

    初次了解JWT,很基础,高手勿喷. 基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session. JWT是啥? JWT就是一个字符串,经过加密处理与校验处理的字符 ...

  4. Mininet实验 OpenFlow1.3协议基于Mininet部署与验证

    参照:OpenFlow1.3协议基于Mininet部署与验证 安装过程,参考原文. 实验 使用ifconfig查看本机IP地址:192.168.1.101 进入OpenDayLight界面,cd到bi ...

  5. sharepoint 2010 基于AD的Form验证

    一.新建web应用程序 1.验证部分选择“基于声明的身份验证” 2.设置端口 3.选择“启用基于窗体的身份验证(FBA)” “ASP.NET 成员身份提供程序名称”下面填写“LdapMember” “ ...

  6. Forms身份验证和基于Role的权限验证

    Forms身份验证和基于Role的权限验证 从Membership到SimpleMembership再到ASP.NET Identity,ASP.NET每一次更换身份验证的组件,都让我更失望.Memb ...

  7. (转)基于 Token 的身份验证

    原文:https://ninghao.net/blog/2834 最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,G ...

  8. 自己动手写CPU(基于FPGA与Verilog)

    大三上学期开展了数字系统设计的课程,下学期便要求自己写一个单周期CPU和一个多周期CPU,既然要学,就记录一下学习的过程. CPU--中央处理器,顾名思义,是计算机中最重要的一部分,功能就是周而复始地 ...

  9. [转载:Q1mi]Bootstrap和基于Bootstrap的登录验证示例

    转载自:Q1mi Bootstrap介绍 Bootstrap是Twitter开源的基于HTML.CSS.JavaScript的前端框架. 它是为实现快速开发Web应用程序而设计的一套前端工具包. 它支 ...

随机推荐

  1. Server SQL Modes

    The MySQL server can operate in different SQL modes, and can apply these modes differently for diffe ...

  2. 雷林鹏分享:C# 运算符

    C# 运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号.C# 有丰富的内置运算符,分类如下: 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 杂项运算符 本教程将逐一讲解算术运算 ...

  3. OnSen UI结合AngularJs打造”美团"APP"附近”页面 --Hybrid App

    1.页面效果图: 演示链接地址:http://www.nxl123.cn/bokeyuan/meiTuanDemo_near/ 2.核心代码 near.html: <ons-page id=&q ...

  4. FreeBDS之ipf防火墙

    FreeBSD使用手册https://www.freebsd.org/doc/zh_CN/books/handbook/index.html https://www.freebsd.org/doc/z ...

  5. BASE64图片转字符串

    Java代码图片字符串互转 /** * 将base64字符串转成图片 * TODO * @param imgStr base64图片字符串 * @param path 目标输出路径 * @return ...

  6. SPL之Iterator(迭代器)接口

    前言:SPL是用于解决典型问题(standard problems)的一组接口与类的集合. <?php /** * Class MyIterator * 在 PHP 中,通常情况下遍历数组使用 ...

  7. 1.1 从UNIX到Linux的发展历程

    MIT的CTSS:第一个分时操作系统 ◼ Multics系统(Multiplexed Information and Computing System) ⚫ 1965年AT&T,MIT和GE的 ...

  8. 【洛谷p2312】解方程

    (清明培训qwq,明天就要回学校了qwq拒绝) 行吧我洛谷都四天没碰了 解方程[传送门] 算法标签: (作为一个提高+省选-的题) 丁大佬真的很有幽默感emmm: #include <cstdi ...

  9. 『MXNet』第九弹_分类器以及迁移学习DEMO

    解压文件命令: with zipfile.ZipFile('../data/kaggle_cifar10/' + fin, 'r') as zin: zin.extractall('../data/k ...

  10. 百度地图API 自定义坐标点及图片

    var map = new BMap.Map("allmap");var point = new BMap.Point(105.955754,36.525109);map.cent ...