Verilog的常用testbench模板分享

  楼主在初学verilog的时候就一直对testbench该怎么写感到困惑,之后的学习过程中也陆陆续续地看过一些testbench文件,其中有一些其实相当于就在testbench里重写了一下要验证地模块,个人感觉这有点”鸡生蛋“和”蛋生鸡“那味儿了。虽说在testbench里写不用考虑能否综合的问题,可以用一些更为方便的写法,但是终归还是用的Verilog体系内的语法,描述待测试模块预期的功能时很有可能会犯类似的错误,因此楼主觉得这种testbench的写法应付比较简单的模块可能还行,比较简单直接,但是应对复杂点的模块则出错的可能性偏大,不太可取。
  除了前述testbench的写法外,遍历穷举应该是常见的testbench写法之一,即借由高级语言穷举生成所有的输入和对应的输出,来比对模块的输出以达到验证的目的,因此本文就分享一个适用于这种穷举遍历思路的testbench模板,下面就借由一个原码输入原码输出的带饱和处理的减法器模块进行这一模板的写法说明。

原码减法器

  选取被减数 A A A和减数 B B B的数据位宽为 4 4 4,输出结果 r e s res res的数据位宽也为4,设计框图如下
在这里插入图片描述

设计说明如下:

  1. 输入为原码(Sign-magnitude,SM)表示,而加减法一般习惯用二的补码(2’s compliment,2’s),因此先将 A A A B B B转换为补码形式,其中 B B B为被减数,可以考虑直接对(-B)进行补码转换,即上图中的红色减号,此时后续”减法“则转为加法进行;
  2. 二的补码加法比较简单,直接相加即可,本设计并不需要用到进位,因此可以只在模块内声明一下作为连线即可;
  3. 最后再进行溢出时的饱和处理,即溢出时符号位不变,幅值取最大值。具体到溢出的判断,本设计就不考虑最高位进位和次高位进位的溢出判断了,而是直接用输入和输出的符号位作为判断依据,比较直观。

Verilog模块

  代码如下所示

/**
 * @function:
 *   compute res = A - B and get sign-magnitude representation result
 * @input:
 *   A, B -> sign-magnitude representation
 * @output:
 *   res  -> sign-magnitude representation
**/
module SmSub #(
  parameter WIDTH = 4
) (
  input  [(WIDTH - 1) : 0] A,
  input  [(WIDTH - 1) : 0] B,
  output [(WIDTH - 1) : 0] res
);
  

  wire [(WIDTH - 1) : 0] tmpA;         // magnitude part of A
  wire [(WIDTH - 1) : 0] complA;       // 2's complement representation of A
  wire [(WIDTH - 1) : 0] complB;       // 2's complement representation of B
  wire [(WIDTH - 1) : 0] complRes;     // 2's complement representation of res
  reg  [(WIDTH - 1) : 0] tmpRes;       // for negative result
  wire                   carry;
  reg  [(WIDTH - 1) : 0] satcomplRes;  // saturated complRes
  wire [(WIDTH - 1) : 0] negMax;       // maximum negative value using 2's complement representation


  // sign-magnitude -> 2's complement for A
  assign tmpA   = {1'b0, A[(WIDTH - 2):0]};
  assign complA = (1'b1 == A[(WIDTH - 1)]) ? ((~tmpA[(WIDTH - 1):0]) + 1) : A[(WIDTH - 1):0];

  // sign-magnitude -> 2's complement for (-B)
  assign complB = (1'b1 == B[(WIDTH - 1)]) ? {1'b0, B[(WIDTH - 2):0]} : ((~B[(WIDTH - 1):0]) + 1);

  // get result
  assign {carry, complRes} = complA + complB;

  // overflow process
  always @(*) begin
    case ({complA[(WIDTH - 1)], complB[(WIDTH - 1)], complRes[(WIDTH - 1)]})
      // postive spillover
      3'b001: begin
        satcomplRes = {1'b0, {(WIDTH - 1){1'b1}}};
      end
      // negtive spillover
      3'b110: begin
        satcomplRes = {1'b1, {(WIDTH - 1){1'b1}}};
      end
      // no spillover
      default: begin
        // 2's complement -> sign-magnitude for complRes
        tmpRes      = (~complRes[(WIDTH - 1):0]) + 1;
        satcomplRes = (1'b1 == complRes[(WIDTH - 1)]) ? {1'b1, tmpRes[(WIDTH - 2):0]} : complRes[(WIDTH - 1):0];
      end
    endcase
  end

  // handle special case N'b1000…000
  assign negMax = {1'b1, {(WIDTH - 1){1'b0}}};
  assign res    = (negMax[(WIDTH - 1):0] == satcomplRes[(WIDTH - 1):0]) ? {1'b1, {(WIDTH - 1){1'b1}}} : satcomplRes[(WIDTH - 1):0];

endmodule

matlab生成数据

  matlab代码如下所示,即穷举所有输入的情况,一共 2 4 × 2 4 = 256 2^4\times 2^4=256 24×24=256种,算出对应的输出,借由dec2bin()函数转为二进制形式写入到result.txt文件中,以供testbench读入。

注:下列代码整体风格其实与C很像,之所以不用C而是用matlab只是图matlab里有现成的十进制转二进制函数罢了……

dataWidth = 4;
fileName = "../result.txt";
fileID = fopen(fileName, "w");

dataNum = power(2, dataWidth);
magnitudeMax = power(2, dataWidth - 1) - 1;
result = zeros(dataNum, dataNum);
for i = 1 : dataNum
    for j = 1 : dataNum
        % convert to sign-magnitude representation
        if (i <= power(2, dataWidth - 1)) 
            tmpI = i - 1;
        else
            tmpI = power(2, dataWidth - 1) + 1 - i;
        end
        if (j <= power(2, dataWidth - 1)) 
            tmpJ = j - 1;
        else
            tmpJ = power(2, dataWidth - 1) + 1 - j;
        end
        tmpRes = tmpI - tmpJ;

        % saturated the results and convert to binary representation
        if tmpRes < 0
            if abs(tmpRes) >= magnitudeMax      % saturated
                fprintf(fileID, "%s\n", dec2bin(magnitudeMax + power(2, dataWidth - 1), dataWidth));        % sign-magnitude representation
            else
                fprintf(fileID, "%s\n", dec2bin(abs(tmpRes) + power(2, dataWidth - 1), dataWidth));
            end
        else
            if tmpRes >= magnitudeMax           % saturated
                fprintf(fileID, "%s\n", dec2bin(magnitudeMax, dataWidth));
            else
                fprintf(fileID, "%s\n", dec2bin(tmpRes, dataWidth));
            end
        end
    end
end

fclose(fileID);

testbench编写

  本文的重头戏来了,先放代码

`timescale 1 ns/ 10 ps
`define period	10

module SmSub_tb();
/********** 1. ports and signal declaration **********/
  reg  [3:0] A;
  reg  [3:0] B;
  wire [3:0] res;

  reg [3:0] refResMem[0:(16 * 16 - 1)];

  integer i;
  integer j;
  integer tmpIdx;
  integer errCnt;
  
/********** 2. module instantiation and assignment **********/
  SmSub #( 
    .WIDTH(4) 
  ) uut0 (
     .A(A),
     .B(B),
     .res(res)
    );
	
/********** 3. Initialization **********/
  initial begin
    errCnt = 0;
    $readmemb("./sim/result.txt", refResMem);

    for (i = 0; i < 16; i = i + 1) begin
      for (j = 0; j < 16; j = j + 1) begin
        A = i;  B = j;
        #(`period / 2) checkOutputs;
        #(`period / 2);
      end
    end

    if (errCnt) begin
      $display("***************** Total Errors: %d *****************\n", errCnt);
    end
    else begin
      $display("***************** No Errors! *****************\n");
    end
    #(`period*10) $stop;
	end           

  /********** 4. check part **********/
  task checkOutputs;
    begin
      tmpIdx = (i * 16) + j;
      if (res != refResMem[tmpIdx]) begin
        error;
      end
    end
  endtask

  task error;
    begin
      errCnt = errCnt + 1;
      $display("ERROR AT %d: expected -> %h, get -> %h", tmpIdx, refResMem[tmpIdx], res);
    end
  endtask

endmodule

说明如下:

  • 第一部分就是testbench都有的端口和接下来要用的信号声明;
  • 第二部分是测试模块的实例化,较为复杂点的模块可能还需要在testbench里额外添加一些连线;
  • 第三部分为初始化,先将错误计数变量初始化为0,再用系统函数$readmemb/$readmemh将前一步用matlab生成的参考输出读入,而考虑到本模块中的输入比较有规律,所以输入用整型变量ij产生,若数据量较大或不规律,可以参照前一步,用高级语言生成对应的输入文件读入。再之后,每产生一个输入,就调用对应的checkOutputs任务进行输出对比,如果存在错误则调用error任务进行错误处理即打印出错信息到终端,并对错误进行计数。最后则是检查错误个数,打印对应的输出信息,并调用$stop中止仿真。
  • 第四部分即输出对比函数和错误处理函数,本设计并不复杂,所以只是简单的比对仅有的一个输出,错误处理也只是单纯地打印,并不算复杂;

总结

  这一模板是楼主学Verilog以来感觉比较好用的一个,当然了,这种遍历穷举的验证方式在实际工程中还是有不小局限性的,输入比特数较大时往往就只能随机生成众多输入情况的一部分进行验证了,最终还是要借助于系统的验证方法学。尽管如此,个人感觉这一模板还是对初学Verilog的人来说还是比较友好的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值