当前位置:   article > 正文

如何编写一个高效的Testbench?_testbench编写

testbench编写

文章目录

写在前面

概述

介绍

构建testbenches

产生时钟信号

提供激励

显示结果        

简单的testbenches

自动化验证

编写testbenches的指导方针

高级testbenches技术

用任务(Tasks)分解测试激励

控制仿真中的双向信号

有用的Verilog概念

force和release

assign和deassign

timescale

读取Memory初始化文件

编码风格

缩进

文件命令

信号命名

注释

设计架构

结论


写在前面

        在FPGA的开发过程中,验证是一个必不可少的环节,其所占时间有时候甚至还要大于RTL级的开发时间。对于大型设计,设计公司通常都有一整套完整且规范的设计流程。而对于小型设计,我们设计者则可以自己编写一个testbench对设计进行一个简单的功能验证。本文将告诉你:对于小型设计,要如何编写一个高效率的testbench。

        本文主要翻译自Xilinx《XAPP199,Writing Efficient Testbenches》,浅蓝色字体为本人理解


概述

        本文是为刚接触HDL验证流程和没有丰富的Testbenches编写经验的逻辑设计师编写的。Testbenches是验证HDL设计的主要手段。本文提供了布局和构建高效Testbenches的指导大纲。它还提供了一种算法来为设计开发一个自检的Testbenches。


介绍

        由于设计规模和复杂性的增加,数字化设计验证已经成为一项越来越困难和艰巨的任务。为了解决这一挑战,验证工程师使用了一些验证工具和方法。对于大型的、数百万门电路级的设计,工程师通常使用一套正式的验证工具。然而,对于较小的设计,设计工程师通常发现带有testbenches的HDL仿真工具是一种最有效的测试模式。

        Testbenches已经成为验证HLL(High-Level Language,高级语言)设计的标准方法。通常,Testbenches会实现以下任务:

  • 实例化被测模块(DUT)
  • 通过对模型应用测试向量来激励DUT
  • 输出结果到终端或波形窗口进行目视检查
  • 可以选择将实际结果与预期结果进行比较

       通常,testbenches是用行业标准的VHDL或Verilog硬件描述语言编写的。Testbenches调用被测模块,然后编写测试激励来激励它以观察期输出。复杂的testbenches会实现一些额外的功能----例如,它们包含用于确定设计的适当设计刺激或将实际结果与预期结果进行比较的逻辑。

        本文描述了一个高效的testbenches架构,并提供了一个自检testbenches的示例----它自动地比较实际输出的结果和预期输出的结果。

        图1显示了遵循上述步骤的标准HDL验证流程。

         由于testbenches是用VHDL或Verilog编写的,因此testbenches验证过程可以跨平台和工具进行移植。此外,由于VHDL和Verilog是标准的非专有语言,所以在未来的设计中,用VHDL或Verilog编写的验证套件可以轻松地被复用。

        testbenches通常包含一下部分:被测模块DUT,测试激励,测试结果比较。DUT是我们编写的RTL代码,也就是我们的功能模块;测试激励是要对DUT进行的测试输入,比如你的功能模块是一个加法器,那么你总得写个测试激励----要给2个加数赋值吧?结果比较模块的实现比较多样化:你可以直接观察波形,看看是不是自己想要的结果;也可以将结果与预期正确的结果自动做比较,如果无误就输出低电平,如果有误就输出高电平;也可以把所有的结果都保存下来,再用脚本自动比对,等等。


构建testbenches

        testbenches可以用VHDL或Verilog编写。由于testbenches仅用于仿真,因此它们不受可综合过程中使用的RTL语言子集的语义约束的限制。因此,可以更通用地编写测试testbenches,这会使得它们更具可维护性。

        所有的testbenches都包含表1中所示的基本部分。如上所述,testbenches通常还包含额外的功能,例如在终端上显示结果和错误检测功能。

        下面的例子展示了一些在testbenches中经常使用的结构。

        上面的结构是一个testbenches一定会具备的基本结构,testbenches的编写比较灵活,因为其内容不需要综合,也就是说不需要被映射到具体的电路实现,说白了,就是设计者随心所欲的写激励来作为输入,观察被测的功能模块能否在对应测试激励下,实现符合预期条件的输出。


产生时钟信号

        使用系统时钟来实现时序逻辑的设计必须生成一个时钟。时钟可以很容易地在Verilog源代码中实现。以下是时钟生成的例子:

  1. // Declare a clock period constant.
  2. Parameter ClockPeriod = 10;
  3. // Clock Generation method 1:
  4. initial begin
  5. forever Clock = #(ClockPeriod / 2) ~ Clock;
  6. end
  7. // Clock Generation method 2:
  8. initial begin
  9. always #(ClockPeriod / 2) Clock = ~Clock;
  10. end

        时钟信号一般使用always和Foever这两个循环来构建,方法很简单:每半个时钟周期,时钟信号翻转一次,这样就能实现周期性的占空比为50%的方波。 


提供激励

        为了获得testbenches的验证结果,必须向被测模块提供测试刺激。在testbenches上使用并行块来提供必要的测试激励。可以采用两种方法:绝对时间激励和相对时间激励。在第一种方法中,仿真数值相对于仿真时间零指定。相比之下,相对时间激励提供初始值,然后等待事件再重新触发激励。根据设计人员的需要,这两种方法可以在一个testbenches中实现。

        下面分别提供了绝对时间激励和相对时间激励的例子:

(1)绝对时间激励

  1. initial begin
  2. Reset = 1;
  3. Load = 0;
  4. Count_UpDn = 0;
  5. #100 Reset = 0;
  6. #20 Load = 1;
  7. #20 Count_UpDn = 1;
  8. end

        绝对时间激励使用#+时间来定义,比如#20就代表从仿真开始20个时间单位。

(2)相对时间激励

  1. always @ (posedge clock)
  2. TB_Count <= TB_Count + 1;
  3. initial begin
  4. if (TB_Count <= 5)
  5. begin
  6. Reset = 1;
  7. Load = 0;
  8. Count _UpDn = 0;
  9. end
  10. else
  11. begin
  12. Reset = 0;
  13. Load = 1;
  14. Count_UpDn = 1;
  15. end
  16. end
  17. initial begin
  18. if (Count == 1100) begin
  19. Count_UpDn <= 0;
  20. $display("Terminal Count
  21. Reached, now counting down.");
  22. end
  23. end

        相对时间激励则使用某些时间来进行控制,比如时钟的上升沿啦,某个信号条变成具体的指定的值的时间啦,等等。

        Verilog中的所有initial块是一起并发执行的。但是,在每个initial块中,事件是按照写入的顺序执行的。这意味着测试激励在每个并发块中会从仿真的零时刻开始执行。设计者应该使用多个initial块将复杂的测试激励分解成多个简单的部分,从而使代码更具可读性和可维护性。


显示结果        

        在Verilog中,通过$display和$monitor关键字可以方便地显示结果。

        以下是在终端屏幕上显示仿真结果的例子:

  1. // pipes the ASCII results to the terminal or text editor
  2. initial begin
  3. $timeformat(-9,1,"ns",12);
  4. $display(" Time Clk Rst Ld SftRg Data Sel");
  5. $monitor("%t %b %b %b %b %b %b", $realtime,
  6. clock, reset, load, shiftreg, data, sel);
  7. end

        $timeformat关键字指定时间变量的格式。$display关键字将带引号的文本("…")输出到终端窗口。$monitor关键字的工作方式不同,因为它的输出是事件驱动的。在本例中,$realtime变量(由用户分配给当前仿真时间)用于触发信号列表中值的显示。信号列表以$realtime变量开始,后面跟着要显示其值的其他信号的名称(时钟、重置、加载等)。开头的“%”关键字包含一个格式说明符列表,用于控制如何格式化信号列表中的每个信号值以便显示。格式列表是位置对应的——每个格式说明符顺序地与信号列表中的一个连续的信号名称相关联。例如,%t指示符将显示的$realtime值格式化为时间格式,而第一个%指定符将时钟值格式化为二进制格式。Verilog提供了额外的格式说明符,例如,%h用于十六进制格式,%d用于十进制格式,%o用于八进制格式。

        格式化的显示结果如图2所示。

         $display是直接显示括号里的一句话,而$monitor则是对括号内的变量进行监控,每当监控变量中的任意一个发生改变,就会在终端打印所有的变量值。


简单的testbenches

        简单的testbenches例化被测模块,然后为其提供测试激励。Testbench输出以图形方式显示在仿真工具的波形窗口上,或者作为文本发送到用户的终端或文件。下面是一个表示移位寄存器的简单Verilog设计:

  1. module shift_reg (clock, reset, load, sel, data, shiftreg);
  2. input clock;
  3. input reset;
  4. input load;
  5. input [1:0] sel;
  6. input [4:0] data;
  7. output [4:0] shiftreg;
  8. reg [4:0] shiftreg;
  9. always @ (posedge clock)
  10. begin
  11. if (reset)
  12. shiftreg = 0;
  13. else if (load)
  14. shiftreg = data;
  15. else
  16. case (sel)
  17. 2'b00 : shiftreg = shiftreg;
  18. 2'b01 : shiftreg = shiftreg << 1;
  19. 2'b10 : shiftreg = shiftreg >> 1;
  20. default : shiftreg = shiftreg;
  21. endcase
  22. end
  23. endmodule

        以下的testbenches例化了上面的移位寄存器模块:

  1. module testbench; // declare testbench name
  2. reg clock;
  3. reg load;
  4. reg reset; // declaration of signals
  5. wire [4:0] shiftreg;
  6. reg [4:0] data;
  7. reg [1:0] sel;
  8. // instantiation of the shift_reg design below
  9. shift_reg dut(.clock (clock),
  10. .load (load),
  11. .reset (reset),
  12. .shiftreg (shiftreg),
  13. .data (data),
  14. .sel (sel));
  15. //this process block sets up the free running clock
  16. initial begin
  17. clock = 0;
  18. forever #50 clock = ~clock;
  19. end
  20. initial begin// this process block specifies the stimulus.
  21. reset = 1;
  22. data = 5'b00000;
  23. load = 0;
  24. sel = 2'b00;
  25. #200
  26. reset = 0;
  27. load = 1;
  28. #200
  29. data = 5'b00001;
  30. #100
  31. sel = 2'b01;
  32. load = 0;
  33. #200
  34. sel = 2'b10;
  35. #1000 $stop;
  36. end
  37. initial begin// this process block pipes the ASCII results to the
  38. //terminal or text editor
  39. $timeformat(-9,1,"ns",12);
  40. $display(" Time Clk Rst Ld SftRg Data Sel");
  41. $monitor("%t %b %b %b %b %b %b", $realtime,
  42. clock, reset, load, shiftreg, data, sel);
  43. end
  44. endmodule

        上面的testbenches例化了被测设计,设置了时钟,并提供了测试刺激。所有过程块从仿真的零时开始,并且是并发的。井号(#)指定应用下一个刺激之前的仿真时间延迟。$stop命令指示仿真工具停止testbenches仿真(所有testbenches都应该包含一个停止命令)。最后,$monitor语句将ASCII格式的结果回显给屏幕或文本文件。

        上面这个testbench算是一个比较经典且简单的测试结构,被测模块、测试激励、时钟和结果监控都有了,可以作为一个testbench的一般模板来使用。


自动化验证

        建议自动化验证测试结果,特别是对于较大型的设计。自动化验证减少了检查设计正确性所需的时间,并将人为出错的可能性降至最低。自动化testbenches验证常用的方法有:

  • 数据库比较。首先,创建一个包含预期输出的数据库文件。然后,获取仿真输出,并与文件中的参考值与实际结果进行比较(可借助脚本工具)。但是,由于没有提供从输出到输入文件的指针,这种方法的缺点是很难跟踪错误来源。
  • 波形的比较。波形比较可以自动进行,也可以手动进行。该方法使用testbenches比较器将预期波形与testbenches的输出波形进行比较。Xilinx HDL bench工具可以实现这一自动化功能。
  • 自检Testbenches。自检Testbenches在运行时,将实际结果和预期结果进行实时比对。因为可以在测试台中构建有用的错误跟踪信息来显示设计失败的地方,所以调试时间被显著缩短。

        自检Testbenches是通过在测试台文件中放置一系列期望的向量来实现的。这些向量在定义的运行时间隔与实际模拟结果进行比较。如果实际结果与预期结果匹配,则仿真成功。如果结果没有匹配预期,则Testbenches会报告差异的地方。

        实现自检Testbenches对于同步设计来说更简单,因为预期和实际结果可以在一个时钟边缘或每“n”个时钟周期之后进行比较。比较方法也取决于设计的性质。例如,内存I/O的Testbenches应该在每次向内存位置写入新数据或从内存位置读取新数据时检查结果。如果一个设计使用了大量的组合块,在指定预期结果时则必须考虑组合延迟。

        在自检Testbenches中,预期输出将与定期运行时的实际输出进行比较,以提供自动错误检查。这种技术适用于中小型设计。然而,由于可能的输出组合随着设计的复杂性呈指数级增长,为大型设计编写一个自检Testbenches就变得更加困难和耗时。

        下面是用Verilog编写的简单的自检Testbenches的例子:

        在例化被测模块后,将设定预期的结果。在后面的代码中,将预期结果和实际结果进行比较,并将结果反馈给终端。如果每一次结构都匹配,就会显示“end of good simulation”消息。如果发生不匹配,则报告一个错误以及不匹配的期望值和实际值。

  1. `timescale 1 ns / 1 ps
  2. module test_sc;
  3. reg tbreset, tbstrtstop;
  4. reg tbclk;
  5. wire [6:0] onesout, tensout;
  6. wire [9:0] tbtenthsout;
  7. parameter cycles = 25;
  8. reg [9:0] Data_in_t [0:cycles];
  9. // /
  10. // Instantiation of the Design
  11. // /
  12. stopwatch UUT (.CLK (tbclk), .RESET (tbreset), .STRTSTOP (tbstrtstop),
  13. .ONESOUT (onesout), .TENSOUT (tensout), .TENTHSOUT (tbtenthsout));
  14. wire [4:0] tbonesout, tbtensout;
  15. assign tbtensout = led2hex(tensout);
  16. assign tbonesout = led2hex(onesout);
  17. ///
  18. //EXPECTED RESULTS
  19. ///
  20. initial begin
  21. Data_in_t[1] =10'b1111111110;
  22. Data_in_t[2] =10'b1111111101;
  23. Data_in_t[3] =10'b1111111011;
  24. Data_in_t[4] =10'b1111110111;
  25. Data_in_t[5] =10'b1111101111;
  26. Data_in_t[6] =10'b1111011111;
  27. Data_in_t[7] =10'b1110111111;
  28. Data_in_t[8] =10'b1101111111;
  29. Data_in_t[9] =10'b1011111111;
  30. Data_in_t[10]=10'b0111111111;
  31. Data_in_t[11]=10'b1111111110;
  32. Data_in_t[12]=10'b1111111110;
  33. Data_in_t[13]=10'b1111111101;
  34. Data_in_t[14]=10'b1111111011;
  35. Data_in_t[15]=10'b1111110111;
  36. Data_in_t[16]=10'b1111101111;
  37. Data_in_t[17]=10'b1111011111;
  38. Data_in_t[18]=10'b1110111111;
  39. Data_in_t[19]=10'b1101111111;
  40. Data_in_t[20]=10'b1011111111;
  41. Data_in_t[21]=10'b0111111111;
  42. Data_in_t[22]=10'b1111111110;
  43. Data_in_t[23]=10'b1111111110;
  44. Data_in_t[24]=10'b1111111101;
  45. Data_in_t[25]=10'b1111111011;
  46. end
  47. reg GSR;
  48. assign glbl.GSR = GSR;
  49. initial begin
  50. GSR = 1;
  51. // ///
  52. // Wait till Global Reset Finished
  53. // ///
  54. #100 GSR = 0;
  55. end
  56. //
  57. // Create the clock
  58. //
  59. initial begin
  60. tbclk = 0;
  61. // Wait till Global Reset Finished, then cycle clock
  62. #100 forever #60 tbclk = ~tbclk;
  63. end
  64. initial begin
  65. // //
  66. // Initialize All Input Ports
  67. // //
  68. tbreset = 1;
  69. tbstrtstop = 1;
  70. // /
  71. // Apply Design Stimulus
  72. // /
  73. #240 tbreset = 0;
  74. tbstrtstop = 0;
  75. #5000 tbstrtstop = 1;
  76. #8125 tbstrtstop = 0;
  77. #500 tbstrtstop = 1;
  78. #875 tbreset = 1;
  79. #375 tbreset = 0;
  80. #700 tbstrtstop = 0;
  81. #550 tbstrtstop = 1;
  82. // /
  83. // simulation must be halted inside an initial statement
  84. // /
  85. // #100000 $stop;
  86. end
  87. integer i,errors;
  88. ///
  89. ///
  90. // Block below compares the expected vs. actual results
  91. // at every negative clock edge.
  92. ///
  93. ///
  94. always @ (posedge tbclk)
  95. begin
  96. if (tbstrtstop)
  97. begin
  98. i = 0;
  99. errors = 0;
  100. end
  101. else
  102. begin
  103. for (i = 1; i <= cycles; i = i + 1)
  104. begin
  105. @(negedge tbclk)
  106. // check result at negedge
  107. $display("Time%d ns; TBSTRTSTOP=%b; Reset=%h; Expected
  108. TenthsOut=%b; Actual TenthsOut=%b", $stime, tbstrtstop, tbreset,
  109. Data_in_t[i], tbtenthsout);
  110. if ( tbtenthsout !== Data_in_t[i] )
  111. begin
  112. $display(" ------ERROR. A mismatch has occurred-----");
  113. errors = errors + 1;
  114. end
  115. end
  116. if (errors == 0)
  117. $display("Simulation finished Successfully.");
  118. else if (errors > 1)
  119. $display("%0d ERROR! See log above for details.",errors);
  120. else
  121. $display("ERROR! See log above for details.");
  122. #100 $stop;
  123. end
  124. end
  125. endmodule

        这种简单的自检Testbenches设计可以移植到任何测试用例中——当然,预期输出值和信号名称必须修改才能重用。如果不需要在每个时钟边缘进行检查,则可以根据需要修改for循环。

        如果仿真成功,终端屏幕会显示如下信息:

        自动化验证听起来挺唬人的,实际上简单的很:就是我先把所有预期的结果穷举出来(如果有必要的话,比如其没有规律性),然后再把得到的测试结果与其一一进行比较,如果比较结果是对的,那么我就输出高电平或者低电平;如果不对,那么我就输出相反的电平。这样,我们只需要监控这个信号,就可以知道我们的设计是否是正确的。 


编写testbenches的指导方针

        本节提供了编写测试工作台的指导方针。正如规划一个电路设计的布局有助于实现更好的电路性能一样,规划一个testbenches的布局同样可以改善仿真验证过程。

  • (1)在编写testbenches前多了解仿真工具

        尽管常用的仿真工具符合HDL行业标准,但这些标准没有解决几个重要的特定仿真问题。不同的仿真工具具有不同的特性、性能和性能特征,则会导致其产生不同的仿真结果。

        ····基于事件VS基于循环的仿真

                基于事件的仿真在输入、信号或门电路改变值时调度一个仿真事件。在基于事件的仿真中,延迟值可以与门电路和线网相关联,以实现最佳的定时仿真。

                基于循环的仿真以同步设计为目标。它们优化组合逻辑,并在时钟周期分析结果。这个特性使得基于循环的仿真比基于事件的仿真更快,内存效率更高。

        ----调度事件

                基于事件的仿真工具供应商使用不同的算法来调度仿真事件。因此,根据仿真工具使用的调度算法,在相同的仿真时间发生的事件可以以不同的顺序被调度(在每个事件之间插入                  delta延迟)。为了避免算法依赖和确保正确的结果,事件驱动的testbenches应该指定一个显式的刺激序列。

(2)避免使用无限循环

        当事件添加到基于事件的仿真工具时,CPU和内存使用量会增加,处理会变慢。除非对testbenches至关重要,否则不应该使用无限循环来提供测试刺激。通常,时钟是在一个无限循环中指定的(例如,Verilog中的“forever”循环),而不是其他信号事件。

(3)将测试激励分解成不同的逻辑块

        所有初始块(Verilog)都并行运行。如果不相关的激励被划分成单独的块,testbenches激励序列将变得更容易执行和检查。由于每个并发块都是相对于零仿真时间运行的,因此使用单独的块传递激励更容易。使用单独的测试激励块会产生更容易创建、维护和升级的testbenches。

(4)不要显示不重要的数据

        大型设计的testbenches可能包含超过100,000个事件和大量信号。显示大量的仿真数据会大大降低仿真速度。最好每“n”个时钟周期只采样相关信号,以保证足够的仿真速度。


高级testbenches技术

用任务(Tasks)分解测试激励

        当创建大型testbenches时,测试激励应该被划分成不同的子模块,以使得代码清晰和方便修改。任务(task)可用于对信号进行划分。在下面的例子中,测试台模拟了SDRAM控制器的设计。该设计包括重复激励块,因此testbench通过声明单独的任务对激励进行划分,这些任务稍后在testbench中调用,以实现单独的设计功能。

  1. task addr_wr;
  2. input [31 : 0] address;
  3. begin
  4. data_addr_n = 0;
  5. we_rn = 1;
  6. ad = address;
  7. end
  8. endtask
  9. task data_wr;
  10. input [31 : 0] data_in;
  11. begin
  12. data_addr_n = 1;
  13. we_rn = 1;
  14. ad = data_in;
  15. end
  16. endtask
  17. task addr_rd;
  18. input [31 : 0] address;
  19. begin
  20. data_addr_n = 0;
  21. we_rn = 0;
  22. ad = address;
  23. end
  24. endtask
  25. task data_rd;
  26. input [31 : 0] data_in;
  27. begin
  28. data_addr_n = 1;
  29. we_rn = 0;
  30. ad = data_in;
  31. end
  32. endtask
  33. task nop;
  34. begin
  35. data_addr_n = 1;
  36. we_rn = 0;
  37. ad = hi_z;
  38. end
  39. endtask

        这些任务指定了设计功能的某些独立单元—地址读和地址写、数据读和数据写或nop(无操作)。一旦指定,这些任务可以在激励过程中被调用。如下:

  1. Initial begin
  2. nop ; // Nop
  3. #( 86* `CYCLE +1); addr_wr (32'h20340400); // Precharge, load
  4. Controller MR
  5. #(`CYCLE); data_wr (32'h0704a076); // value for Controller MR
  6. #(`CYCLE); nop ; // Nop
  7. #(5 * `CYCLE); addr_wr (32'h38000000); // Auto Refresh
  8. #(`CYCLE); data_wr (32'h00000000); //
  9. #(`CYCLE); nop ; // Nop
  10. end

        将测试激励分解成不同的任务以使激励更容易实现,并使代码更具可读性。

        这一方法类似于C语言的子函数封装,我们可以把测试过程中要实现的激励封装到不同的任务里,这样在编写测试激励时,只需要调用对应的task即可,这样的代码看起来简洁、直观,而且这一方法实际上能有效的节约开发时间。


控制仿真中的双向信号

        大多数设计使用了双向信号,这使得在testbenches中必须与单向信号区别对待。

        下面是使用了双向信号的设计:

  1. module bidir_infer (DATA, READ_WRITE);
  2. input READ_WRITE ;
  3. inout [1:0] DATA ;
  4. reg [1:0] LATCH_OUT ;
  5. always @ (READ_WRITE or DATA)
  6. begin
  7. if (READ_WRITE == 1)
  8. LATCH_OUT <= DATA;
  9. end
  10. assign DATA = (READ_WRITE == 1) ? 2'bZ : LATCH_OUT;
  11. endmodule

        对应的testbenches:

  1. module test_bidir_ver;
  2. reg read_writet;
  3. reg [1:0] data_in;
  4. wire [1:0] datat, data_out;
  5. bidir_infer uut (datat, read_writet);
  6. assign datat = (read_writet == 1) ? data_in : 2'bZ;
  7. assign data_out = (read_writet == 0) ? datat : 2'bZ;
  8. initial begin
  9. read_writet = 1;
  10. data_in = 11;
  11. #50 read_writet = 0;
  12. end
  13. endmodule

        在上面的testbenches中,data_in信号为设计中的双向信号DATA提供激励,而data_out信号则读取DATA的返回值。

        关于双向信号,建议参考:如何规范地使用双向(inout)信号?


有用的Verilog概念

        有用的Verilog语言概念,如$monitor、$display和$time,在上面的Verilog测试示例中已经进行了讨论。本节将讨论可以在测试平台中使用的其他Verilog概念。

force和release

        force和release语句可用于重写对寄存器或网进行的过程赋值。这些概念通常用于强制特定的设计行为。一旦释放强制值,该信号将保持其状态,直到通过过程赋值传递新值。下面是force和release语句的用法示例:

  1. module testbench;
  2. ..
  3. ..
  4. initial begin
  5. reset = 1;
  6. force DataOut = 101;
  7. #25 reset = 0;
  8. #25 release DataOut;
  9. ..
  10. ..
  11. end
  12. endmodule

assign和deassign

        assign和deassign语句类似于force和release语句,但是assign和deassign仅适用于设计中的寄存器。它们通常用于设置输入值。与强制语句类似,assign语句覆盖过程语句传递的值。下面是assign和deassign语句的使用示例:

  1. module testbench;
  2. ..
  3. ..
  4. initial begin
  5. reset = 1;
  6. DataOut = 101;
  7. #25 reset = 0;
  8. release DataOut;
  9. ..
  10. ..
  11. end
  12. initial begin
  13. #20 assign reset = 1;// this assign statement overrides the earlier
  14. statement #25 reset = 0;
  15. #50 release reset;
  16. endmodule

timescale

        timescale指令用于指定testbenches的单位时间步长。这也会影响仿真工具的精度。这个指令的语法是:`timescale reference_time/precision

        Reference_time是仿真的单位时间。Precision则决定了仿真时间的精度。下面是一个使用“timescale”的例子:

  1. `timescale 1 ns / 1 ps
  2. // this sets the reference time to 1 ns and precision to 1 ps.
  3. module testbench;
  4. ..
  5. ..
  6. initial begin
  7. #5 reset = 1; // 5 unit time steps correspond to 5 * 1ns = 5ns in
  8. simulation time
  9. #10 reset = 0;
  10. ..
  11. end
  12. initial begin
  13. $display (“%d , Reset = %b”, $time, reset); // this display
  14. // statement will get executed
  15. // on every simulator step, ie, 1 ps.
  16. end
  17. endmodule

        如果仿真使用时间延迟值,则模拟必须运行在比最小延迟更大的精度。例如,如果在仿真库中使用9ps延迟,仿真的精度必须为1ps以适应9ps延迟。

        关于`timescale指令,建议参考:你真的会用`timescale吗?


读取Memory初始化文件

        Verilog提供了 $readmemb 和 $readmemh 命令来读取一个ASCIl文件,以便初始化Memory 。语法如下:$readmemb (“<design.mif>”, design_instance);

        MIF是创建的Memory初始化文件。用户可以指定MIF文件中的内容。


编码风格

        下面是一些有助于创建更好可阅读性和可维护性RTL的编码风格。

缩进

        缩进代码以使其更易读。建议缩进宽度为三到四个空格。缩进宽度为5个或5个以上的空格通常在右边边缘留下的空间很小,而缩进宽度小于3个空格会导致缩进过小。

文件命令

        保持".v“(Verilog)或”.vhd" (VHDL)的扩展文件名。如果更改了这些标准扩展名,一些编辑器可能无法识别源文件。

信号命名

        所有用户信号使用相同的小写字母。Verilog是大小写敏感的,错误的大写可能导致设计无法进行综合和模拟。此外,使用一致的信号名称格式样式会使信号名称更容易在源文件中定位。使用简短、描述性的信号名称。简短的名称更容易输入,描述性的名称有助于识别信号功能。

注释

        大量编写注释。注释对于接手和重用代码的其他人来说是无价的。大量注释代码,填充了重要的细节,大大提高了源代码的清晰度和可重用性。

设计架构

        一个文件对弈一个module。单独的模块对应单独的文件会使设计更容易维护。


结论

        testbenches为工程师提供了一个便携的、可升级的验证流程。随着混合语言仿真器的使用,设计人员可以自由地使用他们选择的HDL语言来验证VHDL和Verilog设计。高级行为语言有助于创建使用简单构造的testbenches,并且只需要最少的源代码。在仿真过程中自动验证正确设计结果的自检testbenches有助于RTL的设计开发。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/661336
推荐阅读
  

闽ICP备14008679号