锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

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

时间:2023-06-10 09:37:00 to270集成电路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 keyword/span> rand 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 class
  mailbox gen2drv; //mailbox declaration
  
  function new(mailbox gen2drv); // creation of mailbox and constructor
    this.gen2drv = gen2drv;
  endfunction
  
  task main;
    repeat(1)begin
      trans = new();              //object for transaction class
      trans.randomize();          //randomization of transaction
      trans.display_in("Generator"); //checking purposew 
      gen2drv.put(trans);          //putting data into mailbox
    end
  endtask
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 interface
  
  mailbox gen2drv;  //handle of mailbox
  
  function new(virtual in_intf i_vif,mailbox gen2drv);
    this.i_vif=i_vif;
    this.gen2drv=gen2drv;
  endfunction
  
  task main;
    repeat(1)begin       
        transaction trans;  //handle of transaction class,to get the mailbox data
      	#1;
        gen2drv.get(trans);//getting trans data from mainbox
     
        i_vif.a   <= trans.a;
        i_vif.b   <= trans.b;
        i_vif.cin <= trans.cin;
        trans.display_in("Driver");
      end
  endtask
  
endclass

六、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 declaration
  
  mailbox mon2rml;  //declaration of mailbox
  
  function new(virtual in_intf i_vif,mailbox mon2rml);
    
    this.i_vif=i_vif;
    this.mon2rml=mon2rml;
       
  endfunction
  
  task main;
    repeat(1)begin
      transaction trans;  //handle of transaction class
      trans = new();      //consturctor or creating object for trans
      #2;
      trans.a     = i_vif.a  ;//sampling of data in monitor
      trans.b     = i_vif.b  ;
      trans.cin   = i_vif.cin;
      mon2rml.put(trans);
      trans.display_in("input_monitor");
    end
  endtask
  
endclass
class o_monitor;
  
  virtual out_intf o_vif; //virtual interface declaration
  
  mailbox mon2scb;  //declaration of mailbox
  
  function new(virtual out_intf o_vif,mailbox mon2scb);
    
    this.o_vif=o_vif;
    this.mon2scb=mon2scb;
       
  endfunction
  
  task main;
    repeat(1)begin

      transaction trans;  //handle of transaction class
      trans = 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");
    end
  endtask
  
endclass

七、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;
  endfunction
  
  task main;
    transaction r_trans;  //recv transaction from i_monitor
    transaction s_trans;  //send transaction to scoreboard 
    repeat(1)begin
        #3;
        mon2rml.get(r_trans);   // getting info from mailbox
        s_trans = new();
        
        //reference_model
        s_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  
  endtask
  
endclass

八、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;
  endfunction
  
  task main;
    repeat(1)begin
      transaction rml_tr; //handle of transaction class
      transaction o_mon_tr;        
      #4;
      rml2scb.get(rml_tr);   // getting info from mailbox
      mon2scb.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("---------------------------------------------------------------");
    end
  endtask
  
endclass

九、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 drv
  mailbox mon2rml;//i_mon to refe_model
  mailbox rml2scb;//refe_model to scoreboard
  mailbox mon2scb;//o_mon to scoreboard
  
  virtual 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);
    
  endfunction
  
  task test();
  	fork
      gen.main();
      driv.main();
      i_mon.main();
      rml.main(); 
      o_mon.main();	
      scb.main();
    join
  endtask
  
  task run;
    repeat(1)begin
    	test();
        #5;
    end
    $finish;
    
  endtask
  
endclass

十、Test

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

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

十一、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网址为https://www.edaplayground.com/x/mp8J。最后编译仿真结果如下图所示:

    由上图可以看出,generator产生随机激励为 a = 0, b = 1, cin = 1,然后DUT输出的结果是 sum = 0, cout = 1,reference_model算出来的结果也是 sum = 0, cout = 1,这就证实DUT本次验证例子正确,但还不能证明DUT功能设计正确,只有随机约束随机出所有可能的值且都验证正确时,才能算DUT功能设计正确。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章