首页资源分类FPGA/CPLD > 如何编写高效率的testbench

如何编写高效率的testbench

已有 445487个资源

下载专区

上传者其他资源

    FPGA/CPLD热门资源

    本周本月全部

    文档信息举报收藏

    标    签:如何编写高效率的testbench

    分    享:

    文档简介

    编写高效率的testbench

    文档预览

    编写高效率的 testbench 简介: 由于设计的规模越来越大也越来越复杂,数字设计的验证已经成为一个日益困难和繁琐 的任务。验证工程师们依靠一些验证工具和方法来应付这个挑战。对于几百万门的大型设计, 工程师们一般使用一套形式验证(formal verification)工具。然而对于一些小型的设计,设 计工程师常常发现用带有 testbench 的 HDL 仿真器就可以很好地进行验证。 Testbench 已经成为一个验证高级语言(HLL --High-Level Language) 设计的标准方法。 通常 testbench 完成如下的任务: 1. 实例化需要测试的设计(DUT); 2. 通过对 DUT 模型加载测试向量来仿真设计; 3. 将输出结果到终端或波形窗口中加以视觉检视; 4. 另外,将实际结果和预期结果进行比较。 通常 testbench 用工业标准的 VHDL 或 Verilog 硬件描述语言来编写。Testbench 调用功 能设计,然后进行仿真。复杂的 testbench 完成一些附加的功能—例如它们包含一些逻辑来 选择产生合适的设计激励或比较实际结果和预期结果。 后续的章节描述了一个仔细构建的 testbench 的结构,并且提供了一个自动比较实际结 果与预期结果的进行自我检查的 testbench 例子。 图 1 给出了一个如上所描述步骤的标准 HDL 验证流程。由于 testbench 使用 VHDL 或 Verilog 来描述,testbench 的验证过程可以根据不同的平台或不同的软件工具实现。由于 VHDL 或 Verilog 是公开的通用标准,使用 VHDL 或 Verilog 编写的 testbench 以后也可以毫 无困难地重用(reuse)。 图 1 使用 Testbench 的 HDL 验证流程 构建 Testbench Testbench 用 VHDL 或 Verilog 来编写。由于 testbench 只用来进行仿真,它们没有那些 适用于综合的 RTL 语言子集的语法约束限制,而是所有的行为结构都可以使用。因而 testbench 可以编写的更为通用,使得它们可以更容易维护。 所有 testbench 包含了如表 1 的基本程序段。正如上面所提到的,testbench 通常包含附 加功能,如在终端上可视的结果和内建的错误检测。 表 1 testbench 的基本程序段 下面的例子介绍 testbench 中经常使用的结构。 产生时钟信号 使用系统时钟的时序逻辑设计必须产生时钟。时钟信号在 VHDL 或 Verilog 中可以很容 易地实现。以下是 VHDL 和 Verilog 的时钟发生示例。 VHDL: -- Declare a clock period constant. Constant ClockPeriod : TIME := 10 ns; -- Clock Generation method 1: Clock <= not Clock after ClockPeriod / 2; -- Clock Generation method 2: GENERATE CLOCK: process begin wait for (ClockPeriod / 2) Clock <= ’1’; wait for (ClockPeriod / 2) Clock <= ’0’; end process; Verilog: // Declare a clock period constant. 声明时钟信号: Parameter ClockPeriod = 10; // Clock Generation method 1: 对时钟信号的驱动 initial begin 方法1 : Clock = 0; forever Clock = #(ClockPeriod / 2) ~ Clock; end 方法2 : // Clock Generation method 2: always #(ClockPeriod / 2) Clock = ~Clock; 提供激励信号 (输入信号的驱动) 为了获得 testbench 的验证结果,激励必须作用于 DUT。在 testbench 中使用的并行激励 块提供必要的激励。激励的产生可以采用两个方法:绝对时间激励和相对时间激励。在第一 个方法里,仿真变量相对于仿真时间零点进行详细描述。相对而言,相对时间激励提供初始 值,然后等待一个事件来重新触发激励。根据设计者的需要,两种方法可以在 testbench 中 同时使用。 下面的程序段是绝对时间激励的例子。 实例 以绝对时间刻度, 描述输入信号 : initial begin reset = 1; load = 0; cout_updn = 0; 持续100个单位时间,reset为L。 #100 reset = 0; #20 load = 1; #20 count_updn = 1; end 实例 下面的程序段是相对时间激励的例子。 以相对时间刻度, 描述输入信号 : always @(posedge clock) tb_count <= tb_count + 1; initial begin if(tb_count <= 5) begin reset = 1; load = 0; count_updn = 0; end else begin reset = 0; load = 1; count_updn = 1; end end initial begin if(count = 1100) begin count_updn <= 0; $display(“Terminal count Reached,now counting down”); end end initial语法特点 : Verilog 的 initial 块与文件中的其他 initial 块是同时执行。然而,在每一个 initial 块中, 事件是按照书写的顺序执行的。这说明在每一个并行块中的激励序列从序仿真时间零点开 始。为了代码有更好的可读性和更方便的可维护性,应采用多个块来分割复杂的测试激励。 显示结果 在 Verilog 中可以非常方便地使用系统函数$display()和$monitor()显示结果。VHDL 没有等效的显示指令,它提供了 std_textio 标准文本输入输出程序包。它允许文件的 i/o 重定 向到显示终端窗口(作为这个技术的示例,参看下面的自较验查验证设计) 下面是 verilog 示例,它将在终端屏幕上显示一些值。 // pipes the ASCII results to the terminal or text editor initial begin $timeformat(-9,1,"ns",12); $display(" Time Clk Rst Ld SftRg Data Sel"); $monitor("%t %b %b %b %b %b %b", $realtime, clock, reset, load, shiftreg, data, sel); end 系统函数$display 在终端屏幕上输出引用的附加说明文字(“。。。”)。系统函数$monitor 操作不同。因为它的输出是事件驱动的。例中的变量$realtime(由用户赋值到当前的仿真时 间)用于触发信号列表中值的显示。信号表由变量$realtime 开始,跟随其他将要显示的信号 名(clock, reset, load 等)。以%开始的关键字包含一个格式描述的表,用来控制如何格式化 显示信号列表中的每个信号的值。格式列表是位置确定的。每个格式说明有序地与信号列表 中的信号顺序相关。比如%t 说明规定了$realtime 的值是时间格式。并且第一个%b 说明符格 式化 clock 的值是二进制形式。Verilog 提供附加的格式说明,比如%h 用于说明十六进制, %d 说明十进制,%c 说明显示为八进制。图 2 说明格式显示结果 图 2 仿真结果 简单的 testbench 简单的 testbench 实例化用户设计,然后提供相应的激励。测试输出被图形化显示在仿 真器的波形窗口里或者作为文本发送到用户的终端或者是管道输出文本。 以下是一个简单的用 Verilog 实现的设计,它实现了一个移位寄存器的功能。 module shift_reg (clock, reset, load, sel, data, shiftreg); input clock; input reset; input load; input [1:0] sel; input [4:0] data; output [4:0] shiftreg; reg [4:0] shiftreg; always @ (posedge clock) begin if (reset) shiftreg = 0; else if (load) shiftreg = data; else case (sel) 2’b00 : shiftreg = shiftreg; 2’b01 : shiftreg = shiftreg << 1; 2’b10 : shiftreg = shiftreg >> 1; default : shiftreg = shiftreg; endcase end endmodule 以下是简单的 testbench,示例移位寄存器设计的例子 verilog 描述。 移位寄存器的testbench : module testbench; // declare testbench name reg clock; reg load; 时钟信号的驱动 : rst_n、idata、 iload、isel 输入信号的驱动 : reg reset; // declaration of signals wire [4:0] shiftreg; reg [4:0] data; reg [1:0] sel; // instantiation of the shift_reg design below shift_reg dut(.clock (clock), .load (load), .reset (reset), .shiftreg (shiftreg), .data (data), .sel (sel)); //this process block sets up the free running clock initial begin clock = 0; forever #50 clock = ~clock; end initial begin// this process block specifies the stimulus. reset = 1; data = 5’b00000; load = 0; sel = 2’b00; #200 reset = 0; load = 1; #200 输出信息定义 : data = 5’b00001; #100 sel = 2’b01; load = 0; #200 sel = 2’b10; #1000 $stop; end initial begin// this process block pipes the ASCII results to the //terminal or text editor $timeformat(-9,1,"ns",12); $display(" Time Clk Rst Ld SftRg Data Sel"); $monitor("%t %b %b %b %b %b %b", $realtime, clock, reset, load, shiftreg, data, sel); end endmodule 以上的 testbench 实例化设计,设置时钟,提供激励信号。所有的进程块在仿真时间零 点开始。英镑标记(#)说明下一个激励作用前的延迟。$stop 命令使仿真器停止测试仿真(所 有测试设计中都应该包含一个停止命令)。最后,$monitor 语句返回 ascII 格式的结果到屏幕 或者管道输出到一个文本编辑器。 自动验证 推荐自动实现测试结果的验证,尤其是对于较大的设计来说。自动化减少了检查设计是 否正确所要求的时间,也使人可能的犯错最少。 一般有以下几种常用的自动测试验证的方法: 1、数据库比较(database comparison)。首先,要创建一个包含预期输出的数据库文件。 然后,仿真输出被捕获并与预期输出数据库文件中参考的向量比较。然而,因为从输出到输 入文件指针没有提供,是这种方法的一个缺点,使得跟踪一个导致错误输出的原因比较困难。 2、波形比较(waveform comparison)。波形比较可以自动或是手动的运行。自动的方法 使用一个测试比较器来比较预期输出波形与测试输出波形。 3、自我检查测试平台(self-checking testbenches)。一个自我检查 testbench 检查预期的 结果与运行时间的实际结果,并不是在仿真结束以后。因为有用的错误跟踪信息可以内建在 一个测试设计中,用来说明哪些地方设计有误,调试时间可以非常明显地缩短。 自我检查 testbenches 自我检查 testbench 通过在一个测试文档中放置一系列的预期向量表来实现。运行时按 定义好的时间间隔将这些向量与实际仿真结果进行比较。如果实际结果与预期结果匹配,仿 真成功。如果结果不匹配,则报告两者的差异。 对于同步设计,实现自我检查 testbench 会更简单一些,因为与实现的结果相比较可以 在时钟沿或每个几个时钟进行。比较的方法基于设计本身的特性。比如一个存储器读写的 testbench 在每次数据写入后者读出时进行检查。 在自我检查的 testbench 中,预期输出与实际输出在一定的时间间隔比较以便提供自动 的错误检查。这个技术在小到中型的设计中非常好。但是,因为当设计复杂后,可能的输出 组合成指数倍的增长,为一个大型设计编写一个自我检查的 testbench 是非常困难和非常费 时的。 以下是一个用 Verilog 描述的简单的自我检查的 testbench 例子: 下述的设计实例中,预期的结果被详细说明。后面的代码,两种结果被比较,比较的结 果被返回终端。如果没有错误,一个“end of good simulation”消息会显示。如果失配发生, 根据期望与实际值的失配情况,错误会被相应报告。 定义时间刻度 : ‘timescale 1 ns / 1 ps 定义testbench模块名: module test_sc; 信号 & 参数定义 : reg tbreset, tbstrtstop; reg tbclk; wire [6:0] onesout, tensout; wire [9:0] tbtenthsout; parameter cycles = 25; reg [9:0] Data_in_t [0:cycles]; // ///////////////////////////// // Instantiation of the Design // ///////////////////////////// 将信号驱动给设计模块 : stopwatch UUT (.CLK (tbclk), .RESET (tbreset), .STRTSTOP (tbstrtstop), .ONESOUT (onesout), .TENSOUT (tensout), .TENTHSOUT (tbtenthsout)); wire [4:0] tbonesout, tbtensout; assign tbtensout = led2hex(tensout); //led2hex ????????? assign tbonesout = led2hex(onesout); /////////////////////////////////////////////////////////////// //EXPECTED RESULTS /////////////////////////////////////////////////////////////// 对输入信号 Data_in_t 初始化 : initial begin Data_in_t[1] =10’b1111111110; Data_in_t[2] =10’b1111111101; Data_in_t[3] =10’b1111111011; Data_in_t[4] =10’b1111110111; Data_in_t[5] =10’b1111101111; Data_in_t[6] =10’b1111011111; Data_in_t[7] =10’b1110111111; Data_in_t[8] =10’b1101111111; Data_in_t[9] =10’b1011111111; Data_in_t[10]=10’b0111111111; Data_in_t[11]=10’b1111111110; Data_in_t[12]=10’b1111111110; Data_in_t[13]=10’b1111111101; Data_in_t[14]=10’b1111111011; Data_in_t[15]=10’b1111110111; 全局复位信号 驱动 : 创建时钟信号 : 所以输入信号 驱动 : Data_in_t[16]=10’b1111101111; Data_in_t[17]=10’b1111011111; Data_in_t[18]=10’b1110111111; Data_in_t[19]=10’b1101111111; Data_in_t[20]=10’b1011111111; Data_in_t[21]=10’b0111111111; Data_in_t[22]=10’b1111111110; Data_in_t[23]=10’b1111111110; Data_in_t[24]=10’b1111111101; Data_in_t[25]=10’b1111111011; end reg GSR; initial begin GSR = 1; // /////////////////////////////// // Wait till Global Reset Finished // /////////////////////////////// #100 GSR = 0; end // //////////////// // Create the clock // //////////////// initial begin tbclk = 0; // Wait till Global Reset Finished, then cycle clock #100 forever #60 tbclk = ~tbclk; end initial begin // ////////////////////////// // Initialize All Input Ports // ////////////////////////// tbreset = 1; tbstrtstop = 1; // ///////////////////// // Apply Design Stimulus // ///////////////////// #240 tbreset = 0; tbstrtstop = 0; #5000 tbstrtstop = 1; #8125 tbstrtstop = 0; #500 tbstrtstop = 1; #875 tbreset = 1; #375 tbreset = 0; #700 tbstrtstop = 0; #550 tbstrtstop = 1; // ///////////////////////////////////////////////////// // simulation must be halted inside an initial statement // ///////////////////////////////////////////////////// // #100000 $stop; end /* end initial (结束 tbstrstop 信号 & tbrst 信号) */ integer i,errors; /////////////////////////////////////////////////////////////////// /////////////// // Block below compares the expected vs. actual results // at every negative clock edge. /////////////////////////////////////////////////////////////////// /////////////// 校验微模 : 每个↑进行 always @ (posedge tbclk) 预设结果&仿真结果的比较。 begin if (tbstrtstop) begin i = 0; errors = 0; end else begin for (i = 1; i <= cycles; i = i + 1) begin @(negedge tbclk) // check result at negedge $display("Time%d ns; TBSTRTSTOP=%b; Reset=%h; Expected TenthsOut=%b; Actual TenthsOut=%b", $stime, tbstrtstop, tbreset, Data_in_t[i], tbtenthsout); tbtenthsout 信号 : 待验证模块的输出信号。 if ( tbtenthsout !== Data_in_t[i] ) begin 验证用的比较信号。 其值已在初始化中完成。 $display(" ------ERROR. A mismatch has occurred-----"); errors = errors + 1; end end 对本次验证的结果, 做总结性输出 : if (errors == 0) $display("Simulation finished Successfully."); else if (errors > 1) $display("%0d ERROR! See log above for details.",errors); else $display("ERROR! See log above for details."); 100,表示什么????????? #100 $stop; end end /* 结束 验证用微模 */ endmodule 如果仿真成功,下图的信息就会在显示终端上显示: 图 3 verilog 示例验证 编写 testbench 的准则 本节罗列一些编写 testbench 的准则。正如规划一个电路设计可以帮助得到更好的电路 性能,规划好的 testbench 可以提高仿真验证的效率。 在编写 testbench 前要了解仿真器 虽然通用仿真工具兼容 HDL 工业标准,但标准并没有重点强调跟仿真相关的一些主题。 不同的仿真器有不同的特点、功能和执行效率。对我们而言要全面了解 Active-HDL 这个工 具。 --基于事件 vs 基于周期的仿真 仿真器使用基于事件或基于周期的仿真方法。基于事件的仿真器,当输入,信号,或是 门改变了值,来确定仿真器事件的时间。在一个基于事件的仿真器中,一个延时值可以附加 在门电路或是电路网络上来构建最优的时序仿真。基于周期的仿真器面向同步设计。这类工 具优化组合逻辑,在时钟沿分析结果。这个功能使得基于周期的仿真器比基于事件的仿真器 更快更有效。 --确定事件时间 基于事件的仿真器提供商使用不同的运算法则来确定仿真事件。所以,根据仿真器用来 确定的运算法则不同,同一个仿真时间的事件被确定为不同的次序(根据在每个事件之间插 入的 delta 延时)。为避免对运算法则的依赖和确保正确的结果,一个事件驱动测试应该详细 描述明确的激励顺序。 --避免使用无限循环 当一个事件添加到基于事件的仿真器,cpu 和内存的使用就增加了,仿真过程就会变慢。 除非是评价 testbench,无限循环不应该使用来作为设计的激励。一般地,只有时钟被描述成 一个无限循环(如'forever'循环)。 --细分激励到逻辑模块 在测试中,所有 initial 块(verilog)并行执行。如果无关的激励被分离到独立的块中, 测试激励的顺序会变得更容易实现和检查。因为每个并行的块相关于仿真时间的零点开始执 行,对于分离的块传递激励更容易。使用分离激励块使得 testbench 的建立,维护和升级更 加容易。 --避免显示并不重要的数据 大型设计的测试可能包含 10 万以上的事件或匿名信号。显示大量的仿真数据会相当地 降低仿真的速度。 高级测试技术 task微模的使用 : 根据任务和过程细分激励模块 在创建一个大的 testbench 时,激励将会被分割使得代码清晰而易于修改。Task 块可以 被用来分割信号。在下面例子中的 testbench 用于一个 SDRAM 控制器的测试。设计包括重 复的激励模块,testbench 通过不同的 task 来划分测试激励。这些 task 稍后被调用来进行独 立块的功能的测试仿真。 task addr_wr; input [31 : 0] address; 【注】: begin task微模, 允许对信号重复驱动! 即 多个task微模, 可以对同一个信号驱动。 data_addr_n = 0; we_rn = 1; ad = address; end task的定义 : endtask task data_wr; task + 名字 ; input [31 : 0] data_in; input 输入信号; begin 对信号驱动的内容; endtask data_addr_n = 1; we_rn = 1; ad = data_in; end endtask task addr_rd; input [31 : 0] address; begin data_addr_n = 0; we_rn = 0; ad = address; end endtask task data_rd; input [31 : 0] data_in; begin data_addr_n = 1; we_rn = 0; ad = data_in; end endtask task nop; begin data_addr_n = 1; we_rn = 0; ad = hi_z; end endtask 这些任务描述设计功能的独立单元:地址的读写,数据的读写,或者空操作。当这些 task 描述完成后,这些 task 可以在 testbench 中被调用。如下所示: task 调用 : #时间延时 ; task名字(输入量); Initial begin nop ; // Nop #( 86* ‘CYCLE +1); addr_wr (32’h20340400); // Precharge, load Controller MR #(‘CYCLE); data_wr (32’h0704a076); // value for Controller MR #(‘CYCLE); nop ; // Nop #(5 * ‘CYCLE); addr_wr (32’h38000000); // Auto Refresh #(‘CYCLE); data_wr (32’h00000000); // #(‘CYCLE); nop ; // Nop … … end 细分激励到独立的任务使得激励很容易实现,也使得代码的可读性更好。 在仿真时控制双向信号 多数设计使用双向信号,在 testbench 中必须区别对待双向信号和单向信号。 双向总线由 testbench 控制,双向总线的值可以通过数据顶层信号来访问。 以下是一个双向总线示例。 双向接口模块设计 : module bidir_infer (DATA, READ_WRITE); input READ_WRITE ; inout [1:0] DATA ; reg [1:0] LATCH_OUT ; always @ (READ_WRITE or DATA) begin if (READ_WRITE == 1) LATCH_OUT <= DATA; end assign DATA = (READ_WRITE == 1) ? 2’bZ : LATCH_OUT; endmodule 对设计的接口的 testbench : 对输入信号的驱动 : Verilog testbench 可以如下描述: module test_bidir_ver; reg read_writet; reg [1:0] data_in; wire [1:0] datat, data_out; bidir_infer uut (datat, read_writet); assign datat = (read_writet == 1) ? data_in : 2’bZ; assign data_out = (read_writet == 0) ? datat : 2’bZ; initial begin read_writet = 1; data_in = 11; #50 read_writet = 0; end endmodule 在这些测试设计中,data_in 信号提供激励到设计中的双向 DATA 数据信号,data_out 信号读取该 DATA 数据信号. Verilog 中有用的语法结构 其他有用的 Verilog 语法结构,如 $monitor, $display, 及$time,在前面的 verilog 测试示 例中论述过,这一节说明另外的可以在测试设计中使用的 verilog 语句结构。 force/release force/release 语句可以用来跨越进程对一个寄存器或一个 wire 网络的赋值。这个结构一 般用于强制特定的设计行为。一旦一个强制值释放,这个信号保持它的状态直到新的值被进 程赋值。以下是 force/release 语句的用法。 module testbench; .. initial begin reset = 1; force DataOut = 101; #25 reset = 0; #25 release DataOut; .. .. end endmodule assign/deassign assign/deassign 语句与 force/release 相类似,但是 assign/deassign 只用于设计中的寄存器。 他们一般用于设置输入值。就象一个 force 语句,assign 语句覆盖进程语句的赋值。以下是 一个 assign/deassign 语句的用法。 module testbench; .. .. initial begin reset = 1; DataOut = 101; #25 reset = 0; release DataOut; .. .. end initial begin #20 assign reset = 1;// this assign statement overrides the earlier statement #25 reset = 0; #50 release reset; endmodule timescales timescale 编译指令用于为 testbench 指定时间单位时间以及时间精度。它也影响仿真器 的精确度。表示符号为:‘timescale reference_time/precision Reference_time 是单位时间。Precision 决定延时应该达到的精度。以下是‘ timescale 的使用方法。 ‘timescale 1 ns / 1 ps // this sets the reference time to 1 ns and precision to 1 ps. module testbench; .. .. initial begin #5 reset = 1; // 5 unit time steps correspond to 5 * 1ns = 5ns in simulation time #10 reset = 0; .. end initial begin $display (“%d , Reset = %b”, $time, reset); // this display // statement will get executed // on every simulator step, ie, 1 ps. end endmodule 如果仿真使用时延值,仿真就必须运行在一个比最小时延还好的精确度以内。例如,如 果 9ps 延时在仿真库中使用,相应仿真的精确度就必须是在 1ps 到 9ps 之间可调的范围。 只读储器初始化文件 verilog 提供$readmemb 和 $readmemh 命令来读取 ascii 文件来初始化存储器的内容。这 个命令可以在仿真中用来初始化存储器。符号表达如下: $readmemb (“”, design_instance); 编码风格准则 编码风格参考部门的编码规范,以下稍作罗列: 缩进 缩进使代码易读。 文件名 源文件以".v"做文件扩展名。 信号命令 有含义的名称会帮助表明信号的功能。 注释 可以自由地注释 testbench 文件代码。注释能描述代码重要的设计细节,极大地增加了 源代码的清晰性和可重用性 结语 Testbenches 给工程师提供了一个可移动,可升级的验证流程。好的 testbench 可以模拟 实际的硬件环境,并模拟硬件环境对 DUT 进行更有实际意义的测试。当设计出现 bug 时可 以更加真实更加方便在仿真环境中将问题定位和解决。Testbench 实际是逻辑设计的一个重 要组成部分。

    Top_arrow
    回到顶部
    EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。