《The UVM Primer》——Chapter2: A Conventional Testbench for the TinyALU

在搭建UVM验证环境之前,我们先从SystemVerilog验证平台开始,随后一步一步地过渡到完整的UVM验证平台。

TinyALU对验证平台的需求如下:

  • 全面测试 TinyALU 的功能
  • 仿真RTL的每一行代码和通过这些行代码的路径

因此我们要创建需要覆盖的内容,随后创建验证平台去覆盖代码。并且创建自检的验证平台,这样跑回归时就不需要手动检查了。

TinyALU的功能覆盖率模型


我们使用 SystemVerilog 的 covergroup 来实现(具体可以参考绿皮书SystemVerilog),覆盖率目标如下:

  • 测试所有指令
  • 所有指令的全”0”输入仿真
  • 所有指令的全”1”输入仿真
  • 所有指令的复位后运行
  • 单周期指令之后运行乘法指令
  • 乘法指令之后运行一个单周期指令
  • 指令连续两次运行的仿真

所有的上述情况都验证并通过后, 我们就能确认这个TinyALU是可用的。是否达到100%代码覆盖率也是需要检查的。

testbench文件

将验证平台放在同一个仿真文件里,该文件包含三个部分: 激励自检覆盖率。在文件里例化 DUT,并为其施加激励使用自检覆盖采集的 always 模块来监控它。

testbench变量定义

用一种便于定义激励的形式来定义TinyALU的指令, 验证平台信号定义如下:

module top;
​
   typedef enum bit[2:0] {no_op  = 3'b000,
                          add_op = 3'b001, 
                          and_op = 3'b010,
                          xor_op = 3'b011,
                          mul_op = 3'b100,
                          rst_op = 3'b111} operation_t;
   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;
​
   assign op = op_set;
​
   tinyalu DUT (.A, .B, .clk, .op, .reset_n, .start, .done, .result);

使用 SystemVerilog 的枚举类型将 TinyALU 的指令定义为枚举变量。为了方便验证平台的使用,我们将DUT 中没有的rst_op指令加入指令枚举类型。assign语句用于将指令输入到 DUT 的指令总线上。

我们使用SystemVerilog的byte和bit来定义激励变量。最后,我们使用端口顺序映射的形式(这样就不用把信号名写两次,这是个很好的特性)来例化DUT。

covergroup模块

用covergroup来捕获功能覆盖。先定义covergroup,再例化并用其进行采集

covergroup op_cov;
​
      coverpoint op_set {
         bins single_cycle[] = {[add_op : xor_op], rst_op,no_op};
         bins multi_cycle = {mul_op};
​
         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]);
​
​
      }
​
   endgroup
​
   covergroup zeros_or_ones_on_ops;
​
      all_ops : coverpoint op_set {
         ignore_bins null_ops = {rst_op, no_op};}
​
      a_leg: coverpoint A {
         bins zeros = {'h00};
         bins others= {['h01:'hFE]};
         bins ones  = {'hFF};
      }
​
      b_leg: coverpoint B {
         bins zeros = {'h00};
         bins others= {['h01:'hFE]};
         bins ones  = {'hFF};
      }

op_cov 这个 covergroup 用来保证我们覆盖了所有的指令以及这些指令间可能的组合。zeros_or_ones_on_ops 这个 covergroup 用来检查我们是否在所有的指令上用全 0 和全 1 操作数进行测试。

定义完这些 covergroup 之后,我们需要将其声明例化采集

initial begin : coverage
   
      oc = new();
      c_00_FF = new();
   
      forever begin @(negedge clk);
         oc.sample();
         c_00_FF.sample();
      end
   end : coverage

这是个非常简单的覆盖率模型。我们在每个时钟的下降沿检查 TinyALU 的指令和数据输入并进行记录。要达到完全覆盖需要借助带约束随机的激励。

tester模块

可以为每个覆盖目标写一个直接测试用例来验证,对于小型的DUT是可行的。更加高效的办法是使用随机的激励,并且是带有约束的随机激励。这里使用get_op( )和get_data( )两个函数来实现带约束的随机激励:

initial begin : tester
      reset_n = 1'b0;
      @(negedge clk);
      @(negedge clk);
      reset_n = 1'b1;
      start = 1'b0;
      repeat (1000) begin
         @(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 个 transaction。每次循环我们从 get_op()里得到随机的指令,从 get_data( )里得到随机操作数,随后将他们驱动到 TinyALU,并且执行了 start 信号的协议。这两个函数保证了我们得到的是合法的指令和有效的数据。

scoreboard循环做自检

Scoreboard循环使用预期结果来对比检查TinyALU的结果。循环会监控 done 信号,每当 done 信号拉高,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

简单的验证平台

到这已经的完成了TinyALU的验证平台,传递功能覆盖率,检查所有的指令是否工作正常,很少的工作来产生激励,只用一个模块和文件就完成了。

但这验证平台不是一个好的例子,所有的行为都在一个文件里混到一起了。这

些使得这个验证平台难以复用。那要怎么改进这个验证平台呢?我们最先注意到的是 scoreboard 模块, coverage 模块和 tester 模块是在完全不同的功能之上定义的,它们没必要在同一个文件里。把他们分开后,我们就可以把这个验证平台中的模块复用到其他验证平台,方便于修改其他 Test 模块中的其他模块,比如激励。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值