基于UVM的verilog验证

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后,在命令提示符>后,输入

编辑环境变量

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

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

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

时间: 2024-10-14 01:30:54

基于UVM的verilog验证的相关文章

基于Token的身份验证——JWT(转)

本文转自:http://www.cnblogs.com/zjutzz/p/5790180.html 感谢作者 初次了解JWT,很基础,高手勿喷.基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session. JWT是啥? JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为: A.B.C A由JWT头部信息header加密得到B由JWT用到的身份验证信息json数据加密得到C由A和B加密得到,是校验部分 怎样生成A? header格式为: { "typ

WebApi_基于Token的身份验证——JWT(z)

基于Token的身份验证——JWT JWT是啥? JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为: A.B.C A由JWT头部信息header加密得到B由JWT用到的身份验证信息json数据加密得到C由A和B加密得到,是校验部分 怎样生成A? header格式为: { "typ": "JWT", "alg": "HS256" } 它就是一个json串,两个字段是必须的,不能多也不能少.alg字段指定了生成C的算法

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

Forms身份验证和基于Role的权限验证 从Membership到SimpleMembership再到ASP.NET Identity,ASP.NET每一次更换身份验证的组件,都让我更失望.Membership的唯一作用就是你可以参考它的实现,它的数据库创建和扩展方面就真的让人实在无法使用了. 当大家欢呼着让ASP.NET开发走上ASP MVC的正确道路时,身份验证组件却走的更远了:SimpleMembership除了第三方验证的参考价值,它的主键和对领域模型的入侵让它成了摆设,而ASP.NE

基于 Token 的身份验证方法

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录.大概的流程是这样的: 客户端使用用户名跟密码请求登录 服务端收到请求,去验证用户名与密码 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端

(转)基于 Token 的身份验证

原文:https://ninghao.net/blog/2834 最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强,也更安全点,非常适合用在 Web 应用或者移动应用上.Token 的中文有人翻译成 "令牌",我觉得挺好,意思就是,你拿着这个令牌,才能过一些关卡. 传统身份验证的方法 HTTP 是一种没有状态的协议,也就是它并不知道是谁是

扩展一个boot的插件—tooltip&amp;做一个基于boot的表达验证

在线演示 本地下载 (代码太多请查看原文) 加班,加班加班,我爱加班··· 我已经疯了,哦也. 这次发一个刚接触boot的时候用boot做的表单验证,我们扩展一下tooltip的插件,让他可以换颜色. 其实挺简单的,主要是考究代码阅读的能力. boot的代码写的很简单,能省略“;”的地方就省略掉了,而且他的闭包也很有意思 +function($){ }(jQuery); 这种写法等同于 (function($){ })(jQuery); 少些一个符号,比较节俭. 他的对外接口写的就比较正常了:

sharepoint 2010 基于AD的Form验证

一.新建web应用程序 1.验证部分选择“基于声明的身份验证” 2.设置端口 3.选择“启用基于窗体的身份验证(FBA)” “ASP.NET 成员身份提供程序名称”下面填写“LdapMember” “ASP.NET 角色管理器名称”下面填写“LdapRole” 4.其他根据自己情况酌情修改 二.创建网站集 三.修改配置文件 1.应用程序配置文件 1 <roleManager enabled="true" defaultProvider="c" cacheRol

基于 Token 的身份验证

(参考:http://ninghao.net/blog/2834) 基于 Token 的身份验证 最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强,也更安全点,非常适合用在 Web 应用或者移动应用上.Token 的中文有人翻译成 “令牌”,我觉得挺好,意思就是,你拿着这个令牌,才能过一些关卡. 传统身份验证的方法 HTTP 是一种没有状态的协议,

JAVA中的Token 基于Token的身份验证

来源:转载 最近在做项目开始,涉及到服务器与安卓之间的接口开发,在此开发过程中发现了安卓与一般浏览器不同,安卓在每次发送请求的时候并不会带上上一次请求的SessionId,导致服务器每次接收安卓发送的请求访问时都新建一个Session进行处理,无法通过传统的绑定Session来进行保持登录状态和通讯状态. 基于传统方法无法判断安卓的每次请求访问状态,故查询资料了解到Token,特殊的身份证验证.以下是网上搜寻资料所得,作为学习总结资料. 令牌是一种能够控制站点占有媒体的特殊帧,以区别数据帧及其他