UVM Primer Ch2 A Conventional Testbench for the TinyALU

注意:个人学习笔记,参考书籍《The UVM Primer》,最好看英文版本。

在第一章的Introduction中,我们引入了TinyALU的案例,这一章将从从传统的SystemVerilog测试台开始,搭建一个简单的Testbench。

TinyALU Testbench 需要执行以下操作:

•完全测试TinyALU的功能

•模拟RTL的所有线路和通过这些线路的路径

这是一种“覆盖优先”的方法。我们定义了我们想要涵盖的内容,并创建了一个testbench去cover。并创建一个自检测试台(Testbench),这样可以运行回归,不必手动检查结果。

覆盖目标(Coverage goals):

• Test all operations        测试所有的操作
• Simulate all zeros on an input for all operations       测试输入全0
• Simulate all ones on an input for all operations        测试输入全1
• Execute all operations after a reset                          复位后执行所有操作运算     
• Run a multiply after a single cycle operation            单周期运算后执行乘法运算
• Run a single cycle operation after a multiply            乘法运算后执行单周期运算
• Simulate all operations twice in a row                       连续执行两次所有操作运算

如果我们已经测试了所有这些场景,并且没有收到任何错误,我们可以确定TinyALU正在工作。我们还将检查代码覆盖率是否达到100%。

将TinyALU测试台存储在一个Testbench文件中,该文件包含三个部分:激励,自检和覆盖。我们在文件中例化DUT,并用激励驱动其信号,同时用自检和覆盖块进行监控。

1、信号声明

首先,定义TinyALU的操作,方便后续能够轻松定义激励。然后,在测试台中对所有的信号进行声明:

module top;

   typedef enum bit[2:0] {no_op  = 3'b000,    //使用enum枚举类型对TinyAlu操作定义
                          add_op = 3'b001, 
                          and_op = 3'b010,
                          xor_op = 3'b011,
                          mul_op = 3'b100,
                          rst_op = 3'b111} operation_t;//DUT不使用111操作码,可添加为rst的枚举
   byte         unsigned        A;
   byte         unsigned        B;
   bit          clk;
   bit          reset_n;
   wire [2:0]   op;
   bit          start;
   wire         done;
   wire [15:0]  result;
   operation_t  op_set;
//采用byte和bit定义激励变量

   assign op = op_set;    //采用赋值语句将操作码应用于DUT的op总线

   tinyalu DUT (.A, .B, .clk, .op, .reset_n, .start, .done, .result);
//实例化DUT,该特性将端口与信号进行匹配,无需两次键入单个名称

2、coverage block

我们将使用覆盖组来捕获功能覆盖。我们声明覆盖组,实例化它们,并将它们用于采样。

covergroup op_cov;    //操作码覆盖
//op_cov覆盖组确保我们已经覆盖了所有操作以及它们之间可能的交互
      coverpoint op_set {
         bins single_cycle[] = {[add_op : xor_op], rst_op,no_op};//前边所定义的枚举类型bins
         bins multi_cycle = {mul_op};    //乘法的操作码bins

         bins opn_rst[] = ([add_op:mul_op] => rst_op);//执行完操作运算后复位
         bins rst_opn[] = (rst_op => [add_op:mul_op]);//复位后执行所有的操作运算

         bins sngl_mul[] = ([add_op:xor_op],no_op => mul_op);//单周期运算结束后执行乘法运算
         bins mul_sngl[] = (mul_op => [add_op:xor_op], no_op);//乘法运算后执行单周期运算

         bins twoops[] = ([add_op:mul_op] [* 2]);//所有操作运算连续执行两次
         bins manymult = (mul_op [* 3:5]);//乘法运算多次执行 3 4 5


      }

   endgroup

   covergroup zeros_or_ones_on_ops;
//zeros_or_ones_on_ops覆盖组检查数据端口上是否有全0和全1,以及是否使用这些组合测试了所有操作

      all_ops : coverpoint op_set {
         ignore_bins null_ops = {rst_op, no_op};} //忽略空操作运算

      a_leg: coverpoint A {
         bins zeros = {'h00};    //0
         bins others= {['h01:'hFE]};//0和1之间
         bins ones  = {'hFF};    //1
      }

      b_leg: coverpoint B {
         bins zeros = {'h00};
         bins others= {['h01:'hFE]};
         bins ones  = {'hFF};
      }
//交叉覆盖,binsof生成的结果可以进一步通过intersect关键字,选择关联值与所需值集相交的仓
      op_00_FF:  cross a_leg, b_leg, all_ops {
         bins add_00 = binsof (all_ops) intersect {add_op} &&
                       (binsof (a_leg.zeros) || binsof (b_leg.zeros));

         bins add_FF = binsof (all_ops) intersect {add_op} &&
                       (binsof (a_leg.ones) || binsof (b_leg.ones));

         bins and_00 = binsof (all_ops) intersect {and_op} &&
                       (binsof (a_leg.zeros) || binsof (b_leg.zeros));

         bins and_FF = binsof (all_ops) intersect {and_op} &&
                       (binsof (a_leg.ones) || binsof (b_leg.ones));

         bins xor_00 = binsof (all_ops) intersect {xor_op} &&
                       (binsof (a_leg.zeros) || binsof (b_leg.zeros));

         bins xor_FF = binsof (all_ops) intersect {xor_op} &&
                       (binsof (a_leg.ones) || binsof (b_leg.ones));

         bins mul_00 = binsof (all_ops) intersect {mul_op} &&
                       (binsof (a_leg.zeros) || binsof (b_leg.zeros));

         bins mul_FF = binsof (all_ops) intersect {mul_op} &&
                       (binsof (a_leg.ones) || binsof (b_leg.ones));

         bins mul_max = binsof (all_ops) intersect {mul_op} &&
                        (binsof (a_leg.ones) && binsof (b_leg.ones));

         ignore_bins others_only =
                                  binsof(a_leg.others) && binsof(b_leg.others);

      }

   endgroup

3、采样

一旦定义了覆盖组,我们就需要声明、例化和采样

initial begin
      clk = 0;
      forever begin
         #10;
         clk = ~clk;//#20一个周期
      end
   end
   
   op_cov oc;    //声明
   zeros_or_ones_on_ops c_00_FF;

   initial begin : coverage
   
      oc = new();    //例化
      c_00_FF = new();
   
      forever begin @(negedge clk);    //负边沿进行采样
         oc.sample();                  //操作码采样
         c_00_FF.sample();             //输入操作数采样
      end
   end : coverage
   

这是一个非常简单的覆盖模型。我们在每个时钟的负边缘检查TinyALU上的操作和数据输入,并记录我们看到的内容。

4、tester block

一般情况,我们会为每个覆盖目标编写一个定向测试。在TinyALU的情况下,这是可能的,但它需要更多的编码。相反,我们将创建随机激励。

我们将使用两个函数实现约束随机激励:get_op()和get_data():

  function operation_t get_op();    //随机操作码
      bit [2:0] op_choice;
      op_choice = $random;    //随机化
      case (op_choice)
        3'b000 : return no_op;
        3'b001 : return add_op;
        3'b010 : return and_op;
        3'b011 : return xor_op;
        3'b100 : return mul_op;
        3'b101 : return no_op;
        3'b110 : return rst_op;
        3'b111 : return rst_op;
      endcase // case (op_choice)
   endfunction : get_op

   function byte get_data();    //随机操作数
      bit [1:0] zero_ones;
      zero_ones = $random;     //随机化
      if (zero_ones == 2'b00)
        return 8'h00;
      else if (zero_ones == 2'b11)
        return 8'hFF;
      else
        return $random;
   endfunction : get_data
  initial begin : tester
      reset_n = 1'b0;
      @(negedge clk);
      @(negedge clk);    //较少毛刺
      reset_n = 1'b1;
      start = 1'b0;
      repeat (1000) begin    //重复1000次
         @(negedge clk);
         op_set = get_op();    //获取随机操作码
         A = get_data();       //获取随机操作数
         B = get_data();
         start = 1'b1;
         case (op_set) // handle the start signal
           no_op: begin 
              @(posedge clk);
              start = 1'b0;
           end
           rst_op: begin 
              reset_n = 1'b0;
              start = 1'b0;
              @(negedge clk);
              reset_n = 1'b1;
           end
           default: begin 
              wait(done);
              start = 1'b0;
           end
         endcase // case (op_set)
      end
      $stop;
   end : tester

此循环为TinyALU生成1000个事务。每次通过循环,我们都会从get_op()获得一个随机操作,从get_data()获得随机数据,将它们驱动到TinyALU,并处理启动信号的协议。

5、使用Scoreboard回路自检

Scoreboard循环检查实际TinyALU结果与预测结果。循环监视完成信号。当完成信号变高时,Scoreboard根据输入预测TinyALU输出,并检查输出是否正确:

 always @(posedge done) begin : scoreboard
      shortint predicted_result;    //定义预测输出信号
      #1;
      case (op_set)
        add_op: predicted_result = A + B;
        and_op: predicted_result = A & B;
        xor_op: predicted_result = A ^ B;
        mul_op: predicted_result = A * B;
      endcase // case (op_set)

      if ((op_set != no_op) && (op_set != rst_op))    //确保进行操作运算
        if (predicted_result != result)
          $error ("FAILED: A: %0h  B: %0h  op: %s result: %0h",
                  A, B, op_set.name(), result);    //错误输出打印

   end : scoreboard
   

覆盖结果

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值