Testbench设计

   

      一个完整的设计,除了好的功能描述代码,对于程序的仿真验证是必不可少的。学
会如何去验证自己所写的程序, 即如何调试自己的程序是一件非常重要的事情。 而 RTL
逻辑设计中,学会根据硬件逻辑来写测试程序,即 Testbench 是尤其重要的。Verilog 测
试平台是一个例化的待测(MUT)模块,重要的是给它施加激励并观测其输出。逻辑
模块与其对应的测试平台共同组成仿真模型, 应用这个模型可以测试该模块能否符合自
己的设计要求。
编写 TESTBENCH 的目的是为了对使用硬件描述语言设计的电路进行仿真验证,测试
设计电路的功能、性能与设计的预期是否相符。通常,编写测试文件的过程如下:
• 产生模拟激励(波形);
• 将产生的激励加入到被测试模块中并观察其响应;
• 将输出响应与期望值相比较。
通常,一个完整的测试文件其结构为

module Test_bench();//通常无输入无输出
信号或变量声明定义
逻辑设计中输入对应 reg 型
逻辑设计中输出对应 wire 型
使用 initial 或 always 语句产生激励
例化待测试模块
监控和比较输出响应
endmodule

 

下面列举出一些常用的封装子程序,这些是常用的写法,在很多应用中都能用到。
/*----------------------------------------------------------------
时钟激励产生方法一:50%占空比时钟
----------------------------------------------------------------*/
parameter clockperiod = 10;

initial

   begin 

       clk_i = 0;

       forever

       #(clockperiod/2)clk_i = ~clk_i;

   end 

下面列举出一些常用的封装子程序,这些是常用的写法,在很多应用中都能用到。
/*----------------------------------------------------------------
时钟激励产生方法二:50%占空比时钟
----------------------------------------------------------------*/
parameter ClockPeriod=10;
   initial

      begin 

         clk_i = 0;

         always#(clockperiod/2)clk_i = ~clk_i;

      end

 

/*----------------------------------------------------------------
时钟激励产生方法三:产生固定数量的时钟脉冲
----------------------------------------------------------------*/

initial

   begin 

      clk_i = 0;

      repeat(6)

      #(clockperiod)clk_i = ~clk_i;

   end

 

/*----------------------------------------------------------------
时钟激励产生方法五:产生非占空比为 50%的时钟
----------------------------------------------------------------*/

initial

   begin 

      clk_i = 0;

      forever

         begin 

            #((clockperiod/2)-2) clk_i = 0;

            #((clockperiod/2)-2) clk_i = 1;

        end

    end

/*----------------------------------------------------------------
复位信号产生方法一:异步复位
----------------------------------------------------------------*/

initial

    begin 

       rst_n_i = 1;

       #100;

       rst_n_i = 0;

        #100;

       rst_n_i = 1;

    end 

/*----------------------------------------------------------------
复位信号产生方法二:同步复位
----------------------------------------------------------------*/

initial

   begin 

      rst_n_i = 1;

      @(negedge clk_i)

       rst_n_i = 0;

       #100                                                //固定复位时间

       repeat(10)@(negedge clk_i);               //固定周期数复位

       @(negedge clk_i)

       rst_n_i = 1;

   end

/*----------------------------------------------------------------
复位信号产生方法三:复位任务封装

----------------------------------------------------------------*/

task reset ;

     input[31:0]reset_time;

     RST_ING = 0;

     begin 

        rst_n = RST_ING;

        #reset_time;

        rst_n_i = ~RST_ING;

     end

endtask

 

/*----------------------------------------------------------------
特殊激励信号产生描述一:输入信号任务封装
----------------------------------------------------------------*/

task i_data;

input[7:0]dut_data;

   begin 

@(posedge data_en); send_data=0;
@(posedge data_en); send_data=dut_data[0];
@(posedge data_en); send_data=dut_data[1];
@(posedge data_en); send_data=dut_data[2];
@(posedge data_en); send_data=dut_data[3];
@(posedge data_en); send_data=dut_data[4];
@(posedge data_en); send_data=dut_data[5];
@(posedge data_en); send_data=dut_data[6];
@(posedge data_en); send_data=dut_data[7];
@(posedge data_en); send_data=1;

   end

endtask  

//调用方法:i_data(8'hXX)

/*----------------------------------------------------------------

特殊激励信号产生描述二:多输入信号任务封装
----------------------------------------------------------------*/

task more_input;

   input[7:0]a;

   input[7:0]b;

   input[31:0]times;

   output[8:0]c;

   begin 

      repreat(times)                   //等待times个时钟上升沿

         @(posedge clk_i)

               c = a + b;               //时钟上升沿a,b相加

   end

 

//调用方法:more_input(x,y,t,z); //按声明顺序
/*----------------------------------------------------------------
双向信号描述一:inout 在 testbench 中定义为 wire 型变量
----------------------------------------------------------------*/
//为双向端口设置中间变量 inout_reg 作为 inout 的输出寄存,其中 inout 变
//量定义为 wire 型,使用输出使能控制传输方向

//inout bir_port;

wire bir_port;

reg bir_port_reg;

reg bi_port_oe;

assign bi_port = bi_port_oe? bir_port_reg:1'bz;

 

/*----------------------------------------------------------------
双向信号描述二:强制 force
----------------------------------------------------------------*/
//当双向端口作为输出口时,不需要对其进行初始化,而只需开通三态门
//当双向端口作为输入时,只需要对其初始化并关闭三态门,初始化赋值需
//使用 wire 型数据,通过 force 命令来对双向端口进行输入赋值
//assign dinout=(!en) din :16'hz; 完成双向赋值

initial

   begin 

      force dinout = 20;

      #200

     force dinout = dinout - 1;

   end

 

/*----------------------------------------------------------------
特殊激励信号产生描述三:输入信号产生,一次 SRAM 写信号产生
----------------------------------------------------------------*/

initial

    begin 

       cs_n = 1;                      //片选无效

wr_n = 1;                      //写使能无效

rd_n = 1;                      //读使能无效

addr_8'hxx;                   //地址无效

data = 8'bzz;                 //数据无效

#100;                           

cs_n = 0;                     //片选无效

wr_n = 0;                     //写使能有效

addr_8'hzz;                   //写入地址

data = 8'h2c;               //写入数据

#100;                        

cs_n = 0;

wr_n = 0;

addr = 8'bF1;

data = 8'h2c;

#100;

cs_n = 1;

wr_n = 1;

#10;

addr = 8'hxx;

data = 8'hzz;

end

 

/*----------------------------------------------------------------
Testbench 中@与 wait

----------------------------------------------------------------*/

//@使用沿触发
//wait 语句都是使用电平触发

initial

    begin 

         start = 1'b1;

         sait(en = 1'b1);

          #10;

          start = 1'b0;

   end

 

/*----------------------------------------------------------------
仿真控制语句及系统任务描述
----------------------------------------------------------------*/
$stop //停止运行仿真,modelsim 中可继续仿真


$stop(n) //带参数系统任务,根据参数 0,1 或 2 不同,输出仿真信息


$finish //结束运行仿真,不可继续仿真


$finish(n) //带参数系统任务,根据参数 0,1 或 2 不同,输出仿真信息


//0:不输出任何信息
//1:输出当前仿真时刻和位置
//2:输出当前仿真时刻、 位置和仿真过程中用到的 memory 以及 CPU 时间的
统计
$random //产生随机数


$random % n //产生范围-n 到 n 之间的随机数


{$random} % n //产生范围 0 到 n 之间的随机数


/*----------------------------------------------------------------
仿真终端显示描述
----------------------------------------------------------------*/
$monitor //仿真打印输出,大印出仿真过程中的变量,使其终端显示
                   /*

                 $monitor($time,,,"clk=%d reset=%d out=%d",clk,reset,out);

             */

$display //终端打印字符串,显示仿真结果等
        /*
                $display(” Simulation start ! ");
                $display(”At time %t,input is %b%b%b,output is %b",$time,a,b,en,z);
        */
$time //返回 64 位整型时间
$stime //返回 32 位整型时间
$realtime //实行实型模拟时间

/*----------------------------------------------------------------
文本输入方式:$readmemb/$readmemh
----------------------------------------------------------------*/
//激励具有复杂的数据结构
//verilog 提供了读入文本的系统函数
$readmemb/$readmemh("<数据文件名>",<存储器名>);
$readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>);
$readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
$readmemb:/*读取二进制数据,读取文件内容只能包含:空白位置,注释行,二进制数
数据中不能包含位宽说明和格式说明,每个数字必须是二进制数字。*/
$readmemh:/*读取十六进制数据,读取文件内容只能包含:空白位置,注释行,十六进
制数
数据中不能包含位宽说明和格式说明,每个数字必须是十六进制数字。*/
            /*当地址出现在数据文件中,格式为@hh...h,地址与数字之间不允许空白位置,
               可出现多个地址*/

module

   reg[7:0]memory[0:3];

      integer i;

      initial

         begin 

              $readmemh("mem.data",memory);

             //显示此时存储器内容

             for(i = 0; i<4; i = i+1)

                $display("Memory[%d] = %h" ,i,memory[i]);

          end

endmodule

 

/*mem.dat 文件内容
@001
AB CD
@003
A1
*/
//仿真输出为
Memory[0] = xx;
Memory[1] =AB;
Memory[2] = CD;
Memory[3] =A1;

 

上面只例举了常用的 testbench 写法,在工程应用中基本能够满足我们需求,至于
其他更为复杂的 testbench 写法,大家可参考其他书籍或资料。
这里提出以下几点建议供大家参考:
• 封装有用且常用的 testbench,testbench 中可以使用 task 或 function 对代码
进行封装,下次利用时灵活调用即可;
• 如果待测试文件中存在双向信号(inout)需要注意,需要一个 reg 变量来表示输
入,一个 wire 变量表示输出;
• 单个 initial 语句不要太复杂, 可分开写成多个 initial 语句, 便于阅读和修改;
• Testbench 说到底是依赖 PC 软件平台,必须与自身设计的硬件功能想搭配。

 

posted @ 2016-05-14 12:16  lv_you  阅读(341)  评论(0)    收藏  举报