Testbench专题所谓testbench,即测试平台,详细的说就是给待验证的设计添加激励,同时观察输出响应是否符合设计要求。也许我们会把把程序开发出来算作一个工程项目的重大的比例,这在今天的FPGA设计中,并不是如此,往往在仿真验证上的工作量占到一半以上。试想这么一个测试,一个16位的输入总线,它可以有多少种组合?如果每次随机产生一种输入,用波形的去画一画,眼花!波形是最直观的测试手段,但不是唯一手段。一个完整的测试平台如下图所示,自动对比输出结果激励在终端打印或生成文本观察对比波形响应待验证设计测试平台它是分结构组成的,其中对设计测试结果的判断不仅可以通过观察对比波形得到,而且可以灵活使用脚本命令将有用的输出信息打印到终端或者产生文本进行观察,也可以写一段代码让它们自动比较输出结果。TB的设计是多种多样,可以使用灵活的VERILOG的验证脚本,但是它也是基于硬件语言但是又服务于软件测试的语言,有时并行有时顺序,只有掌握这些关键点,才能很好服务测试。技巧1Tb中的例化应该把INPUT转换成REG,因为待测设计的输入值是由TB决定的。相应的OUTPUT就应该转换成WIRE,因为待测设计的输出值不是由tb决定的。这里需要注意Inout端口,在例化中也是一个wire型。技巧2时钟产生第一种:parameterPERIOD=XX;InitialbeginClk=0;Forever#(PERIOD/2)clk=~clk;End第二种parameterPERIOD=XX;alwaysbegin#(PERIOD/2)clk=0;#(PERIOD/2)clk=1;End技巧3复位信号InitialbeginReset_task(XX);//注意时间尺度…….EndTaskreset_task;Input[15:0]reset_time;BeginReset=0;#reset_time;Reset=1;End技巧4变量的定义在编写testbench时,关于变量的定义常犯的错误就是将一个定义的全局变量应用到了两个不同的always块中(如例1所示),那么由于这两个always块独立并行的工作机制,很可能会导致意想不到的后果。例1:integeri;alwaysbeginfor(i=0;i32;i=i+1)begin………endendalwaysbeginfor(i=0;i32;i=i+1)begin……endend实际上,在verilog中(编写testbench时),如果在beginend之间定义了always的块名,那么你可以如例2一样申明变量。这样两个always块里的变量i就互不相关,也就不会产生不可预料的结果了。例2:alwaysbegin:block_lintegeri;for(i=0;i32;i=i+1)begin……endendalwaysbegin:block_2integeri;for(i=15;i=0;i=i-1)begin……endend除此以外,在verilog中的function和task也支持类似上面的局部变量定义。技巧5封装有用的子程序modueldisplay_report();//封装一些做测试时有用的报告显示//包括任务error,warning,fatal,terminate//显示warning报告,同时包含显示当前时间和警告内容(由用户输入)taskwarning;input[80*8:1]msg;begin$write(WARNINGat%t:%s,$time,msg);endendtask//显示error报告,同时包含显示当前时间和错误内容(由用户输入)taskerror;input[80*8:1]msg;begin$write(-ERROR-at%t:%s,$time,msg);endendtask//显示fatal报告,同时包含显示当前时间和致命内容(由用户输入)taskfatal;input[80*8:1]msg;begin$write(*FATAL*at%t:%s,$time,msg);terminate;endendtask//显示warning报告,同时包含显示当前时间和结束信息(该任务自动生成)taskterminate;begin$write(Simulationcompleted\n);$finish;endendtaskendmodule//使用上面封装好的taskmoduletestcase();//包含已经编写好的display_report.v,后面就可以调用其封装好的task了`includedisplay_report.v……initialbeginif(...)error(Unexpectedresponse\n);//调用error任务……terminate;//调用terminate任务end……endmodulemoduletestcase;initialbeginif(...)syslog.error(Unexpectedresponse);syslog.terminate;endendmodule这里提出的子程序封装的概念还是很受益的,这样封装好的task对于以后的testbench随时都可以拿来用,免去繁杂的重复劳动。技巧6防止同时调用taskTestben使用的是硬件语言,而其所依赖的环境却是基于PC的软件平台。这也就决定了其独特的代码风格。有时的的确确是以一个软件式的顺序方式在给待测试硬件代码做测试,但是写出来的testbench代码中却时常布满了并行执行的陷阱。这给硬件测试者带来了不少麻烦,既然我们选择了verilog,那么就得好好领会它在硬件测试环境下的特殊性。或者说,我们应该掌握一些常用的技巧来避免这些问题,让我们的testbench更高效的执行。下面就是一个task使用的常见冲突以及解决办法。taskwrite;input[7:0]wadd;input[7:0]wdat;beginad_dt=wadd;ale=1’bl;rw=1’bl;@(posedgerdy);ad_dt=wdat;ale=1’b0;@(negedgerdy);endendtaskinitialwrite(8’h5A,8’h00);initialwrite(8’hAD,8’h34);上面的task实现了往存储器的指定地址写入指定数据的功能。由于verilog中的always和initial在实际执行中都是并行工作的,也就很有可能出现上面两个initial同时进行task调用,同时需要写存储器的情况。那样会出现什么后果呢?可想而知,这是我们不希望看到的。那么如何解决这样的问题呢?看下面改进后的代码。taskwrite;input[7:0]wadd;input[7:0]wdat;regin_use;beginif(in_use===1’b1)$stop;in_use=1’b1;ad_dt=wadd;ale=1’b1;rw=1’b1;@(posedgerdy);ad_dt=wdat;ale=1’b0;@(negedgerdy);in_use=1’b0;endendtask粗体部分就是加入了检错机制,用in_use作为task已被调用的标志信号,从而避免其它的调用。技巧7结构化Testbench这是假设的待验证模块的顶层:moduleprj_top(clk,rst_n,dsp_addr,dsp_data,dsp_rw……);inputclk;inputrst_n;input[23:0]dsp_addr;inputdsp_rw;inout[15:0]dsp_data;………………endmodule这是testbench的顶层:moduletf_prj_top;/*这个例化适用于被例化文件(这里是print_task.v)不对待验证模块接口进行控制*///print_task.v里包含常用信息打印任务封装print_taskprint();/*这个例化适用于被例化文件需要对待验证模块接口进行控制,和通常RTL设计中例化方法时一样的*///sys_ctrl_task.v里包含系统时钟产生单元和系统复位任务sys_ctrl_tasksys_ctrl(.clk(clk),.rst_n(rst_n));//dsp_ctrl_task.v包含DSP读写控制模拟dsp_ctrl_taskdsp_ctrl(.dsp_rw(DSP_RW),.dsp_addr(dsp_addr),.dsp_data(dsp_data),……);/*这里的端口例化需要注意的时,原来被测试模块的output为reg,如果被底层的例化模块所控制,那么这个reg要改为wire类型进行定义,而底层模块要将其定义为reg*/wireclk;wirerst_n;wire[23:0]dsp_addr;wiredsp_rw;wire[15:0]dsp_data;……//例化待验证工程顶层prj_topuut(.clk(clk),.rst_n(rst_n),.dsp_addr(dsp_addr),.dsp_data(dsp_data),.dsp_rw(dsp_rw),……);/*注意下面调用底层模块的任务的方式,例如sys_ctrl表示上面例化的sys_ctrl_task.v,sys_reset是例化文件中的一个任务,用”.”做分割*/Initialbeginsys_ctrl.sys_reset(32’d1000);//系统复位1000ns#1000;dsp_ctrl.task_dsp_write(SELECT_STRB0,24'h000001,16’h00ff);//DSP写任务调用#1000;dsp_ctrl.task_dsp_read(SELECT_STRB0,24'h000008,dsp_rd_data);//DSP读任务调用……print.terminate;endendmodule//调用层1moduleprint_task;//----------------------------------------------------------------------////常用信息打印任务封装//----------------------------------------------------------------------////警告信息打印任务taskwarning;input[80*8:1]msg;begin$write(WARNINGat%t:%s,$time,msg);endendtask//错误信息打印任务taskerror;input[80*8:1]msg;begin$write(ERRORat%t:%s,$time,msg);endendtask//致命错误打印并停止仿真任务taskfatal;input[80*8:1]msg;begin$write(FATALat%t:%s,$time,msg);$write(Simulationfalse\n);$stop;endendtask//完成仿真任务taskterminate;begin$write(SimulationSuccessful\n);$stop;endendtaskendmodule//调用层2modulesys_ctrl_task(clk,rst_n);outputregclk;//时钟信号outputregrst_n;//复位信号parameterPERIOD=20;//时钟周期,单位nsparameterRST_ING=1'b0;//有效复位值,默认低电平复位//----------------------------------------------------------------------////系统时钟信号产生//----------------------------------------------------------------------//initial