基于SV简单的数字IC验证框架搭建

2024-03-02 16:59

本文主要是介绍基于SV简单的数字IC验证框架搭建,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基于SV简单的数字IC验证框架搭建

  • 简介
  • 一、DUT
  • 二、Interface
  • 三、Transaction
  • 四、Generator
  • 五、Driver
  • 六、Monitor
  • 七、Reference_model
  • 八、Scoreboard
  • 九、Environment
  • 十、Test
  • 十一、Testbench
  • 十二、输出结果


简介

    本文基于systemverilog搭建一个简单的验证框架(框架图如下所示),对于ic验证小白的入门指导。
    为什么要搭建这样一个验证平台,而不是对于DUT写个testbench就好了,对于这个问题,刚入门的我也有些疑惑。一般来说,我们的tsetbench只会设计DUT输入激励,关于DUT的输出相应我们一般都会直接通过仿真波形来人为查看结果是否正确,不正确再去改激励或者RTL设计,然后不断循环往复,直到所有结果与我们所预期那样,但这对于数字IC来说不切实际。为什么呢?因为一般来说数字ic所需要验证的端口会很庞大,你不可能人为的去一个一个设计激励并验证它是否正确,这将消耗大量时间。验证平台相当于创建一个随机激励并自动检测的机器,去检测你想检测的任何情况,并验证dut输出与你所预期是否相同。这或许在一些小型设计当中并不出众,一但设计规模大了之后,验证平台的效率会直线上升。
systemverilog_verification_structure


一、DUT

    DUT全称为Design Under Test,本设计由于是刚入门验证小白搭建验证平台,所以DUT设计只是一个全加器,目的是为了解整个验证框。架设计RTL代码如下所示。其中a与b为两个加数,cin为进位输入,cout为进位输出,sum为加法和。真值表如下图所示:
请添加图片描述

module adder(a,b,cin,sum,cout);input  a,b,cin;
output sum,cout;assign {cout,sum} = a + b + cin;endmodule

二、Interface

    大家在设计多层RTL代码时可以发现,有些信号可能需要流经几个设计层次,它必须一遍又一遍地被声明和连接。最糟糕是如果想添加一个新的信号,又需要在多个文件中定义与连接。这会大大增加连线出错几率,并且使得跟踪、调试和维护变得更加繁琐。
    而System Verilog使用接口(interface)为块之间的通信建模,接口可以看作一捆智能的连线,它可以包含任务、函数、参数、变量、功能覆盖率和断言,使我们在模块内通过接口监控和记录事务。由于该信息被封装在接口中,因此无论它具有多少端口,连接到设计也变得更加容易。
    在模块中(module)声明interface是可以的,但是在类中(class)直接声明会报错,在类中声明interface应该在前面加上virtual,如果不使用关键字 “virtual” 那么在多次调用该接口时,因为在其中的一个实例中对接口中某一信号的修改会影响其他实例接口,如果使用了 “virtual” 关键字,那么每个实例是独立的。所以我们要习惯在除了模块中的其他地方声明都应使用virtual。在本验证框架中,为了直观看出效果,定义了两个接口,一个是用于DUT输入(in_intf),还有一个用于DUT输出(out_intf)。两个接口定义如下:

interface in_intf();logic a;logic b;logic cin;
endinterface
interface out_intf();logic sum;logic cout;
endinterface

三、Transaction

    OOP的核心概念就是把数据和相关的方法封装成一个类,事务(transaction)就是基于这样的思想建立起来的。一个简单的DUT监视器可能只在接口上采样几个数值,如果将他们简单地保存在整数变量中然后传递给下一级,这样可能在一开始节省一点时间,但最终你还是需要将这些数值组合到一起以构成一个完整的事务。这些事务中的几个可能需要被组合成更高级别的事务,例如DMA事务。所以,应该立刻将这些接口数值封装成一个事务类,这样,你就可以在保存数据的同时保存相关的信息(端口号、接收时间),然后将该对象传递给测试平台的其他部分。
    一般来说,物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,每个包的大小不一样。很少会有协议是以bit或者byte为单位来进行数据交换的。以以太网为例,每个包的大小至少是64byte。这个包中要包括源地址、目的地址、包的类型、整个包的CRC校验数据等。transaction就是用于模拟这种实际情况,一笔transaction就是一个包。
    本验证平台在事务类中定义了一个随机变量cin,这将在Generator随机化产生输入激励,变量sumcout不需要赋随机值,值传递DUT输出相应。事务中还封装了两个display函数,分别显示激励输入与相应输出。其实,transaction中还可以设置约束(constraint),不过本文中没有约束条件。

class transaction;//packet class//simulus are declared with rand keywordrand bit a;rand bit b;rand bit cin;bit sum;bit cout;function void display_in(string name);$display("-------------------------");$display("[%0t]ns %s ",$time,name);//$display("-------------------------");$display("a = %0d,   b = %0d,   cin = %0d",a,b,cin);$display("-------------------------");  endfunction  function void display_out(string name);$display("-------------------------");$display("[%0t]ns %s ",$time,name);//$display("-------------------------");$display("sum = %0d,   cout = %0d",sum,cout);$display("-------------------------");endfunction   
endclass

四、Generator

    当RTL 设计越来越大时,要产生一个完整的激励集来测试的设计功能也变得越来越困难了。可以编写一个定向测试集来检查某些功能项,但当一个项目的功能项成倍增加时,编写足够多的定向测试集就不可能了。解决办法就是采用受约束的随机测试法(CRT)自动产生测试集。定向测试集能找到你所认为可能存在的Bug,CRT方法通过随机激励,可以找到你都无法确定的Bug。可以通过约束来选择测试方案,只产生有效的激励,以及测试感兴趣功能项,本验证平台Generator就是用来产生受约束的随机激励。
    Generator为DUT产生受约束随机激励类,代码如下所示,首先创建generator的new函数,并声明传入参数类型信箱(mailbox),mailbox为线程间的通信,除此之外还有事件(event)和旗语(semaphore),通过 this.gen2drv = gen2drv; 将该类中变量赋值为传入参数,信箱通过put与get将事务(transaction)send与recive。随后创建main任务,方便在env类中直接调用此任务。main任务中首先声明transaction句柄,然后创建一个transaction对象(trans = new();),然后将事务随机化(trans.randomize();),并用信箱发送随机激励后的transaction(gen2drv.put(trans);)。

class generator;transaction trans ;//Handle of Transaction classmailbox gen2drv; //mailbox declarationfunction new(mailbox gen2drv); // creation of mailbox and constructorthis.gen2drv = gen2drv;endfunctiontask main;repeat(1)begintrans = new();              //object for transaction classtrans.randomize();          //randomization of transactiontrans.display_in("Generator"); //checking purposew gen2drv.put(trans);          //putting data into mailboxendendtask
endclass

五、Driver

    Driver为驱动模块,目的是将Generator产生的激励传输到DUT中。当时我有个疑惑,为什么不将Generator模块与Driver放到一起,这样看起来不更简洁美观吗。但是验证测试平台是基于system verilog的面向对象编程,也就是说将重复造作的行为放到一个类中,而Driver只是驱动事务到输入接口(in_intf)上、Generator只是产生随机激励,如果想改约束直接改Generator中设计就行,不用动Driver模块,如果放到一起会大大增加工作量,以上仅是我个人理解。
    Driver代码如下所示,在new函数内参数i_vif为驱动到DUT端口的激励,gen2drv为接受generator传输过来的transaction。main任务中还有个#1;的延时,为了还原真实传输路径,实现从激励产生模块到接受模块数据传输延时。实际上也可以设计event在generator与driver模块中,当generator模块生成激励并put到信箱中,触发event,driver中检测到generator的触发event,开始接收信箱中数据,并继续下一步传输与处理。这里用延时代表driver已经接收到generator生成激励信号。之后将激励传输到DUT 接口中(i_vif.a <= trans.a; i_vif.b <= trans.b; i_vif.cin <= trans.cin;)。

class driver;virtual in_intf i_vif;  //vif is a handle of virtual interfacemailbox gen2drv;  //handle of mailboxfunction new(virtual in_intf i_vif,mailbox gen2drv);this.i_vif=i_vif;this.gen2drv=gen2drv;endfunctiontask main;repeat(1)begin       transaction trans;  //handle of transaction class,to get the mailbox data#1;gen2drv.get(trans);//getting trans data from mainboxi_vif.a   <= trans.a;i_vif.b   <= trans.b;i_vif.cin <= trans.cin;trans.display_in("Driver");endendtaskendclass

六、Monitor

    验证平台必须时刻监测DUT的行为,只有知道DUT的输入与输出信号变化之后,才能根据这些信号变化来判定DUT的行为是否正确。
    Monitor为验证平台的监控模块,即监控DUT的输入与输出,这里为了好区别,分别设计DUT输入监控(i_monitor)与DUT输出监控(o_monitor)。输出监控很好理解,那为什么还要多设计一个输入监控,为什么不直接将driver里的事务驱动到后面的reference_model模块呢?书上是这样说的:第一,在一个大型的项目中,driver根据某一协议发送数据,而monitor根据这种协议收集数据,如果driver和monitor由不同的人员实现,那么可以大大减少其中任何一方对协议理解的错误;第二,在实现代码重用时,使用monitor是非常有必要的。
    下面的代码中,在i_monitor传入DUT输入接口(i_vif),并将从接口上检测到的数据,封装到transaction中,通过mailbox发送给reference_model模块;在o_monitor传入DUT输出接口(o_vif),并将从接口上检测到的数据,封装到transaction中,通过mailbox发送给scoreboard模块。

class i_monitor;virtual in_intf i_vif; //virtual interface declarationmailbox mon2rml;  //declaration of mailboxfunction new(virtual in_intf i_vif,mailbox mon2rml);this.i_vif=i_vif;this.mon2rml=mon2rml;endfunctiontask main;repeat(1)begintransaction trans;  //handle of transaction classtrans = new();      //consturctor or creating object for trans#2;trans.a     = i_vif.a  ;//sampling of data in monitortrans.b     = i_vif.b  ;trans.cin   = i_vif.cin;mon2rml.put(trans);trans.display_in("input_monitor");endendtaskendclass
class o_monitor;virtual out_intf o_vif; //virtual interface declarationmailbox mon2scb;  //declaration of mailboxfunction new(virtual out_intf o_vif,mailbox mon2scb);this.o_vif=o_vif;this.mon2scb=mon2scb;endfunctiontask main;repeat(1)begintransaction trans;  //handle of transaction classtrans = new();      //consturctor or creating object for trans#2;trans.sum   = o_vif.sum;trans.cout  = o_vif.cout;mon2scb.put(trans);trans.display_out("out_monitor");//trans.display("output_Monitor");endendtaskendclass

七、Reference_model

    Reference_model模块用于完成和DUT相同的功能。reference_model的输出被scoreboard接收,用于和DUT的输出相比较。DUT如果很复杂,那么reference_model也会相当复杂。DUT是用Verilog写成的时序电路,而reference model则可以直接使用systemverilog高级语言的特性,同时还可以通过DPI等接口调用其他语言来完成与DUT相同的功能。虽然本文的DUT比较简单,reference_model也很简单,但对于ip级与系统级的reference_model来说,整个验证模块应该算reference_model是最难的,毕竟要完全复现DUT的完全功能。
    从下面代码中可以看出,定义了两个信箱mon2rmlrml2scb,一个用于接收i_monitor发送来的事务,一个用于给最终的scoreboard模块。这里接收driver所驱动的随机数a、b、cin,这里参考模型是根据真值表画出卡罗图、算出sum与cout与其关系建立起来的,然后将通过参考模型计算后的数值发送给scoreboard进行与DUT输出比对,查看DUT功能是否真确。

class reference_model;mailbox mon2rml;  mailbox rml2scb;function new(mailbox mon2rml,mailbox rml2scb);this.mon2rml = mon2rml;this.rml2scb = rml2scb;endfunctiontask main;transaction r_trans;  //recv transaction from i_monitortransaction s_trans;  //send transaction to scoreboard      repeat(1)begin#3;mon2rml.get(r_trans);   // getting info from mailboxs_trans = new();//reference_models_trans.sum   = r_trans.a ^ r_trans.b ^ r_trans.cin;s_trans.cout = (r_trans.a&r_trans.b)|(r_trans.b&r_trans.cin)|(r_trans.a&r_trans.cin);rml2scb.put(s_trans);r_trans.display_in("reference_model");s_trans.display_out("reference_model");end  endtaskendclass

八、Scoreboard

    计分板(Scoreboard)是用于比较参考模型的数值与DUT运算后的值是否相同。一般来说来自参考模型的数值会比来自DUT输出的值要快,所以在不断循环产生约束随机激励的验证平台,来自参考模型的数值会不断累积,这时就需要在计分板中定义一个队列,用于存储先带到来的参考模型数值,一旦检测到DUT有一个输出到达,立即将队列中数值弹出进行比对,如此循环。本验证平台所设计的时等所有模块都执行完毕,在开启下一次的随机测试,所以没有设计队列由于缓存。
    由下面的代码可以看出,scoreboard模块只是设置了两个mailbox(rml2scbmon2scb),一个用于接收来自reference_model,一个用于接收o_monitor。然后比对两transaction中的sum与cout输出是否相同,并打印结果。

class scoreboard;mailbox rml2scb;  mailbox mon2scb; function new(mailbox rml2scb,mailbox mon2scb);this.rml2scb = rml2scb;this.mon2scb = mon2scb;endfunctiontask main;repeat(1)begintransaction rml_tr; //handle of transaction classtransaction o_mon_tr;        #4;rml2scb.get(rml_tr);   // getting info from mailboxmon2scb.get(o_mon_tr);rml_tr.display_out("scoreboard_reference");o_mon_tr.display_out("scoreboard_actrue");if(rml_tr.sum==o_mon_tr.sum | rml_tr.cout==o_mon_tr.cout)  //reference model$display("Result is as Expected");else$display("Error Result!");$display("---------------------------------------------------------------");endendtaskendclass

九、Environment

    Environment类将前面所有的类包含到其类中,统一创建接口与信箱,为前面所有模块中的数据传输建立连接,至此,所有模块才算真正紧密联系。然后定义个run任务,将前面所有类中的main任务并行执行。这里run任务只循环执行一次,为了突出效果,可以将repeat(1)中参数改高点可以看出效果。

`include "transaction.sv"
`include "generator.sv"
`include "driver.sv"
`include "i_monitor.sv"
`include "o_monitor.sv"
`include "reference_model.sv"
`include "scoreboard.sv"class environment;generator       gen  ;driver          driv ;i_monitor       i_mon;o_monitor       o_mon;reference_model rml  ;scoreboard      scb  ;mailbox gen2drv;//gen to drvmailbox mon2rml;//i_mon to refe_modelmailbox rml2scb;//refe_model to scoreboardmailbox mon2scb;//o_mon to scoreboardvirtual in_intf  i_intf;virtual out_intf o_intf;function new(virtual in_intf  i_intf,virtual out_intf o_intf);this.i_intf = i_intf;this.o_intf = o_intf;gen2drv = new();mon2rml = new();rml2scb = new();mon2scb = new();gen   = new(gen2drv);driv  = new(i_intf,gen2drv);i_mon = new(i_intf,mon2rml);rml   = new(mon2rml,rml2scb);o_mon = new(o_intf,mon2scb);scb   = new(rml2scb,mon2scb);endfunctiontask test();forkgen.main();driv.main();i_mon.main();rml.main(); o_mon.main();	scb.main();joinendtasktask run;repeat(1)begintest();#5;end$finish;endtaskendclass

十、Test

    Test如下所示只定义了一个程序块(program),声明并创建了一个env对象,并启动env里的run任务。

`include "environment.sv"
program test(in_intf  i_intf,out_intf o_intf);environment env;initial beginenv = new(i_intf,o_intf);env.run();endendprogram  

十一、Testbench

    Testbench例化了addr模块,并将接口连接到模块端口,且例化了test程序,启动自动执行env中run任务。

`include "input_interface.sv"
`include "output_interface.sv"
`include "test.sv"module tbench_top;in_intf  i_intf();out_intf o_intf();test t1(i_intf,o_intf);adder h1(.a(i_intf.a),.b(i_intf.b),.cin(i_intf.cin),.sum(out_intf.sum),.cout(out_intf.cout));endmodule

十二、输出结果

    本验证平台可以在EDA playground上搭建验证,EDA playground上可以在线编译仿真SV、UVM等数字IC所需验证环境,而且方便快捷,真是学习数字IC验证的神仙网站!本验证例子EDA playground网址为systemveriog_verification。最后编译仿真结果如下图所示:
simulation
    由上图可以看出,generator产生随机激励为 a = 0, b = 1, cin = 1,然后DUT输出的结果是 sum = 0, cout = 1,reference_model算出来的结果也是 sum = 0, cout = 1,这就证实DUT本次验证例子正确,但还不能证明DUT功能设计正确,只有随机约束随机出所有可能的值且都验证正确时,才能算DUT功能设计正确。

这篇关于基于SV简单的数字IC验证框架搭建的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/766828

相关文章

Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)

《Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)》本文介绍了如何使用Python和Selenium结合ddddocr库实现图片验证码的识别和点击功能,感兴趣的朋友一起看... 目录1.获取图片2.目标识别3.背景坐标识别3.1 ddddocr3.2 打码平台4.坐标点击5.图

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

Java数字转换工具类NumberUtil的使用

《Java数字转换工具类NumberUtil的使用》NumberUtil是一个功能强大的Java工具类,用于处理数字的各种操作,包括数值运算、格式化、随机数生成和数值判断,下面就来介绍一下Number... 目录一、NumberUtil类概述二、主要功能介绍1. 数值运算2. 格式化3. 数值判断4. 随机

修改若依框架Token的过期时间问题

《修改若依框架Token的过期时间问题》本文介绍了如何修改若依框架中Token的过期时间,通过修改`application.yml`文件中的配置来实现,默认单位为分钟,希望此经验对大家有所帮助,也欢迎... 目录修改若依框架Token的过期时间修改Token的过期时间关闭Token的过期时js间总结修改若依

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

本地搭建DeepSeek-R1、WebUI的完整过程及访问

《本地搭建DeepSeek-R1、WebUI的完整过程及访问》:本文主要介绍本地搭建DeepSeek-R1、WebUI的完整过程及访问的相关资料,DeepSeek-R1是一个开源的人工智能平台,主... 目录背景       搭建准备基础概念搭建过程访问对话测试总结背景       最近几年,人工智能技术

5分钟获取deepseek api并搭建简易问答应用

《5分钟获取deepseekapi并搭建简易问答应用》本文主要介绍了5分钟获取deepseekapi并搭建简易问答应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1、获取api2、获取base_url和chat_model3、配置模型参数方法一:终端中临时将加

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.