SystemVerilog的一个简单验证demo

DUT:

    是一个简单的memory。就六个信号,时钟信号clk,复位信号reset(高有效),读使能信号rd_en,写使能信号wr_en,写数据信号wdata,读数据信号rdata。

    对于写操作:

        address, wr_en和wdata 在同一时钟进行驱动。

    对于读操作:

        address和rd_en在同一时钟进行驱动,系统在下一时钟出现反应。

 //Memory
  reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH];
  //Reset 
  always @(posedge reset) 
    for(int i=0;i<2**ADDR_WIDTH;i++) mem[i]=8'hFF;
   
  // Write data to Memory
  always @(posedge clk) 
    if (wr_en)    mem[addr] <= wdata; 
  // Read data from memory
  always @(posedge clk)
    if (rd_en) rdata <= mem[addr];

Testbench:

    对于验证来说,主要就是以下几个类组成:transaction,generator,driver,monitor,scoreboard,environment,top。

    对于transaction:

    主要是对生成激励中所需的字段进行声明,还可以用作DUT信号上监视器监视的活动的占位符。


  • 编写transaction的步骤:

  • 首先对域进行声明:

class transaction;

  //declaring the transaction items
  bit [1:0] addr;
  bit       wr_en;
  bit       rd_en;
  bit [7:0] wdata;
  bit [7:0] rdata;
  bit [1:0] cnt;
   
endclass
  • 对变量使用rand进行随机化
class transaction;
 
  //declaring the transaction items
  rand bit [1:0] addr;
  rand bit       wr_en;
  rand bit       rd_en;
  rand bit [7:0] wdata;
       bit [7:0] rdata;
       bit [1:0] cnt;
   
endclass
  • 对部分变量进行约束

 //constaint, to generate any one among write and read
  constraint wr_rd_c { wr_en != rd_en; };
  • generator类的编写

主要是对transaction随机化产生激励,并且把产生的类发送给driver。

  • 声明事务类句柄

class generator;  
  //declaring transaction class
  rand transaction trans;  
endclass
  • 随机化事务类

class generator;
   
  //declaring transaction class
  rand transaction trans; 
  //main task, generates(create and randomizes) the packets and puts into mailbox
  task main();
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
      gen2driv.put(trans);
  endtask
   
endclass
  • 利用信箱把随机化后的事务类发送给driver

class generator;
   
  //declaring transaction class
  rand transaction trans;
   
  //declaring mailbox
  mailbox gen2driv; 
 
  //constructor
  function new(mailbox gen2driv);
    //getting the mailbox handle from env
    this.gen2driv = gen2driv;
  endfunction
   
  //main task, generates(create and randomizes) the packets and puts into mailbox
  task main();
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
      gen2driv.put(trans);
  endtask
   
endclass
  • 增加变量去控制事务产生的数量

class generator;
   
  //declaring transaction class
  rand transaction trans;
   
  //declaring mailbox
  mailbox gen2driv;
   
  //repeat count, to specify number of items to generate
  int  repeat_count; 
 
  //constructor
  function new(mailbox gen2driv);
    //getting the mailbox handle from env
    this.gen2driv = gen2driv;
  endfunction
   
  //main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
  task main();
    repeat(repeat_count) begin
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
      gen2driv.put(trans);
    end
  endtask
   
endclass
  • 添加一个事件来指示生成过程的完成,该事件将在生成过程完成时触发。

class generator;
   
  //declaring transaction class
  rand transaction trans;
   
  //declaring mailbox
  mailbox gen2driv;
   
  //repeat count, to specify number of items to generate
  int  repeat_count; 
 
  //event
  event ended;
 
  //constructor
  function new(mailbox gen2driv,event ended);
    //getting the mailbox handle from env
    this.gen2driv = gen2driv;
    this.ended    = ended;
  endfunction
   
  //main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
  task main();
 
    repeat(repeat_count) begin
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");   
      gen2driv.put(trans);
    end
   -> ended;
  endtask 
endclass
  • interface的编写

接口将对信号进行分组,指定方向(Modport)并同步信号(时钟块)。

  • driver时钟块

//driver clocking block
clocking driver_cb @(posedge clk);
  default input #1 output #1;
  output addr;
  output wr_en;
  output rd_en;
  output wdata;
  input  rdata; 
endclocking
  • monitor时钟块

//monitor clocking block
clocking monitor_cb @(posedge clk);
  default input #1 output #1;
  input addr;
  input wr_en;
  input rd_en;
  input wdata;
  input rdata; 
endclocking
  • 指定方向

//driver modport
modport DRIVER  (clocking driver_cb,input clk,reset);
 
//monitor modport 
modport MONITOR (clocking monitor_cb,input clk,reset);
  • Driver类的编写

该类通过将事务类值分配给接口信号,接收从生成器生成的激励并驱动到DUT。

  • 声明接口和信箱,通过构造函数获取接口和信箱句柄

//creating virtual interface handle
  virtual mem_intf mem_vif;

  //creating mailbox handle
  mailbox gen2driv;

  //constructor
  function new(virtual mem_intf mem_vif,mailbox gen2driv);
    //getting the interface
    this.mem_vif = mem_vif;
    //getting the mailbox handle from  environment 
    this.gen2driv = gen2driv;
  endfunction
  • 添加复位任务,把接口信号初始化

//Reset task, Reset the Interface signals to default/initial values
  task reset;
    wait(mem_vif.reset);
    $display("--------- [DRIVER] Reset Started ---------");
    `DRIV_IF.wr_en <= 0;
    `DRIV_IF.rd_en <= 0;
    `DRIV_IF.addr  <= 0;
    `DRIV_IF.wdata <= 0;        
    wait(!mem_vif.reset);
    $display("--------- [DRIVER] Reset Ended ---------");
  endtask
  • 添加驱动任务,把generator产生的事务驱动给DUT

//drive the transaction items to interface signals
  task drive;
    forever begin
      transaction trans;
      `DRIV_IF.wr_en <= 0;
      `DRIV_IF.rd_en <= 0;
      gen2driv.get(trans);
      $display("--------- [DRIVER-TRANSFER: %0d] ---------",no_transactions);
      @(posedge mem_vif.DRIVER.clk);
        `DRIV_IF.addr <= trans.addr;
      if(trans.wr_en) begin
        `DRIV_IF.wr_en <= trans.wr_en;
        `DRIV_IF.wdata <= trans.wdata;
        $display("\tADDR = %0h \tWDATA = %0h",trans.addr,trans.wdata);
        @(posedge mem_vif.DRIVER.clk);
      end
      if(trans.rd_en) begin
        `DRIV_IF.rd_en <= trans.rd_en;
        @(posedge mem_vif.DRIVER.clk);
        `DRIV_IF.rd_en <= 0;
        @(posedge mem_vif.DRIVER.clk);
        trans.rdata = `DRIV_IF.rdata;
        $display("\tADDR = %0h \tRDATA = %0h",trans.addr,`DRIV_IF.rdata);
      end
      $display("-----------------------------------------");
      no_transactions++;
    end
  endtask
  • 添加本地变量,记录传输的次数

  //used to count the number of transactions
  int no_transactions;

  //drive the transaction items to interface signals
  task drive;
    ------
    ------
    no_transactions++;
  endtask
  • Monitor类的编写

  • 声明接口和信箱,通过构造函数获取接口和信箱句柄。

//creating virtual interface handle
virtual mem_intf mem_vif;
 
//creating mailbox handle
mailbox mon2scb;
 
//constructor
function new(virtual intf vif,mailbox mon2scb);
  //getting the interface
  this.vif = vif;
  //getting the mailbox handles from  environment
  this.mon2scb = mon2scb;
endfunction
  •  采样逻辑并将采样的事务发送到记分板

task main;
  forever begin
    transaction trans;
    trans = new();
 
    @(posedge mem_vif.MONITOR.clk);
    wait(`MON_IF.rd_en || `MON_IF.wr_en);
      trans.addr  = `MON_IF.addr;
      trans.wr_en = `MON_IF.wr_en;
      trans.wdata = `MON_IF.wdata;
      if(`MON_IF.rd_en) begin
        trans.rd_en = `MON_IF.rd_en;
        @(posedge mem_vif.MONITOR.clk);
        @(posedge mem_vif.MONITOR.clk);
        trans.rdata = `MON_IF.rdata;
      end     
      mon2scb.put(trans);
  end
endtask
  • Scoreboard类的编写

  • 声明信箱和变量以保持事务计数,通过构造函数连接句柄

//creating mailbox handle
mailbox mon2scb;
 
//used to count the number of transactions
int no_transactions;
 
//constructor
function new(mailbox mon2scb);
  //getting the mailbox handles from  environment
  this.mon2scb = mon2scb;
endfunction
  • 存储wdata并将rdata与存储的数据进行比较的逻辑

//stores wdata and compare rdata with stored data
task main;
  transaction trans;
  forever begin
    #50;
    mon2scb.get(trans);
    if(trans.rd_en) begin
      if(mem[trans.addr] != trans.rdata)
        $error("[SCB-FAIL] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
      else
        $display("[SCB-PASS] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
    end
    else if(trans.wr_en)
      mem[trans.addr] = trans.wdata;
 
    no_transactions++;
  end
endtask
  • Environment类的编写

  • 声明句柄

//generator and driver instance
generator gen;
driver    driv;
monitor    mon;   
scoreboard scb; 
//mailbox handle's
mailbox gen2driv;
mailbox mon2scb; 
//event for synchronization between generator and test
event gen_ended;
 
//virtual interface
virtual mem_intf mem_vif;
  • 在构造函数中创造信箱,driver,generator等

//constructor
function new(virtual mem_intf mem_vif);
  //get the interface from test
  this.mem_vif = mem_vif;
   
  //creating the mailbox (Same handle will be shared across generator and driver)
  gen2driv = new();
  mon2scb  = new();
   
  //creating generator and driver
  gen  = new(gen2driv,gen_ended);
  driv = new(mem_vif,gen2driv);
  mon  = new(mem_vif,mon2scb);
  scb  = new(mon2scb);
endfunction
  • 为了更好的访问,使用三级任务

task pre_test();
  driv.reset();
endtask
 
task test();
  fork
    gen.main();
    driv.main();
    mon.main();   //---NEW CODE---
    scb.main();   //---NEW CODE---
  join_any
endtask
 
task post_test();
  wait(gen.ended.triggered);
  wait(gen.repeat_count == driv.no_transactions);
  wait(gen.repeat_count == scb.no_transactions);   //---NEW CODE---
endtask
  • 添加一个运行任务以调用上述方法,在post_test()之后调用$ finish以结束模拟。

task run;
  pre_test();
  test();
  post_test();
  $finish;
endtask
  • 测试类的编写

测试负责,创造环境,配置测试平台,即设置要生成的事务的类型和数量,启动仿真

  • 声明并创建环境

//declaring environment instance
environment env;
 
initial begin
  //creating environment
  env = new(intf);
end
  • 配置要生成的事务数

//setting the repeat count of generator as 10, means to generate 10 packets
env.gen.repeat_count = 10;
  • 启动仿真

//calling run of env, it interns calls generator and driver main tasks.
env.run();
  • 顶层测试的编写

这是最顶层的文件,用于连接DUT和TestBench。由DUT,Test和Interface实例组成。连接DUT和TestBench。

  • 声明并生成时钟并设置

//clock and reset signal declaration
bit clk;
bit reset;
 
//clock generation
always #5 clk = ~clk;
 
//reset Generation
initial begin
  reset = 1;
  #5 reset =0;
end
  • 创建接口实例

//creatinng instance of interface, inorder to connect DUT and testcase
mem_intf intf(clk,reset);
  • 创建设计实例并连接接口信号

//DUT instance, interface signals are connected to the DUT ports
memory DUT (
  .clk(intf.clk),
  .reset(intf.reset),
  .addr(intf.addr),
  .wr_en(intf.wr_en),
  .rd_en(intf.rd_en),
  .wdata(intf.wdata),
  .rdata(intf.rdata)
 );
  • 创建一个测试实例并通过接口句柄

//Testcase instance, interface handle is passed to test as an argument
test t1(intf);





























全部评论
很奇特的思路,学习了
点赞 回复 分享
发布于 2022-08-01 00:03

相关推荐

牛客410815733号:这是什么电影查看图片
点赞 评论 收藏
分享
躺尸修仙中:因为很多92的也去卷中小厂,反正投递简历不要钱,面试不要钱,时间冲突就推,不冲突就面试积累经验
点赞 评论 收藏
分享
点赞 1 评论
分享
牛客网
牛客企业服务