Writing Testbench——结构化Testbench
0赞特权同学为testbench的结构 化以及可重用性设计发愁许久,终于在《writing testbench》一书的ch6章节里找到了答案,可谓柳暗花明又一村。原来Testbench还是 可以做到可重用化的设计。下面以特权同学常用模块做一个结构化可重用的示例。
这是假设的待验证模块的顶层:
module prj_top(clk,rst_n,dsp_addr,dsp_data,dsp_rw……);
input clk;
input rst_n;
input[23:0] dsp_addr;
input dsp_rw;
inout[15:0] dsp_data;
……
……
……
endmodule
这是testbench的顶层:
module tf_prj_top;
/*这个例化适用于被例化文件(这里是print_task.v)不对待验证模块接口进行控制*/
//print_task.v里包含常用信息打印任务封装
print_task print();
/*这个例化适用于被例化文件需要对待验证模块接口进行控制,和通常RTL设计中例化方法时一样的*/
//sys_ctrl_task.v里包含系统时钟产生单元和系统复位任务
sys_ctrl_task sys_ctrl(
.clk(clk),
.rst_n(rst_n)
);
//dsp_ctrl_task.v包含DSP读写控制模拟
dsp_ctrl_task dsp_ctrl(
.dsp_rw(DSP_RW),
.dsp_addr(dsp_addr),
.dsp_data(dsp_data),
……
);
/*这里的端口例化需要注意的时,原来被测试模块的output为reg,如果被底层的例化模块所控制,那么这个reg要改为wire类型进行定义,而底层模块要将其定义为reg*/
wire clk;
wire rst_n;
wire[23:0] dsp_addr;
wire dsp_rw;
wire[15:0] dsp_data;
……
//例化待验证工程顶层
prj_top uut(
.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是例化文件中的一个任务,用”.”做分割*/
Initial begin
sys_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;
end
endmodule
//调用层1
module print_task;
//----------------------------------------------------------------------//
//常用信息打印任务封 装
//----------------------------------------------------------------------//
//警告信息打印任务
task warning;
input[80*8:1] msg;
begin
$write("WARNING at %t : %s",$time,msg);
end
endtask
//错误信息打印任务
task error;
input[80*8:1] msg;
begin
$write("ERROR at %t : %s",$time,msg);
end
endtask
//致命错误打印并停止 仿真任务
task fatal;
input[80*8:1] msg;
begin
$write("FATAL at %t : %s",$time,msg);
$write("Simulation false\n");
$stop;
end
endtask
//完成仿真任务
task terminate;
begin
$write("Simulation Successful\n");
$stop;
end
endtask
endmodule
//调用层2
module sys_ctrl_task(
clk,rst_n
);
output reg clk;//时钟信号
output reg rst_n; //复位信号
parameter PERIOD = 20; //时钟周期,单位ns
parameter RST_ING = 1'b0; //有效复位值,默认低电平复位
//----------------------------------------------------------------------//
//系统时钟信号产生
//----------------------------------------------------------------------//
initial begin
clk = 0;
forever
#(PERIOD/2) clk = ~clk;
end
//----------------------------------------------------------------------//
//系统复位任务封装
//----------------------------------------------------------------------//
task sys_reset;
input[31:0] reset_time; //复位时间输入,单位ns
begin
rst_n = RST_ING; //复位中
#reset_time; //复位时间
rst_n = ~RST_ING; //撤销复位
end
endtask
endmodule
//调用层3
module dsp_ctrl_task(
dsp_rw,dsp_strb0,dsp_strb1,dsp_iostrb,
dsp_addr,dsp_data
);
output reg dsp_rw; //DSP读写信号,低--写,高--读
output reg dsp_strb0; //DSP存储空间STRB0选通信号
output reg dsp_strb1; //DSP存储空间STRB1选通信号
output reg dsp_iostrb; //DSP存储空间IOSTRB选通信号
output reg [23:0] dsp_addr; //DSP地址总线
inout wire [15:0] dsp_data; //DSP数据总线
//print_task.v里包含常用信息打印任务封装
print_task print();
//----------------------------------------------------------------------//
//模拟DSP读写任务封装
//----------------------------------------------------------------------//
//DSP地址空间选择//
parameter SELECT_STRB0 = 2'd1,
SELECT_STRB1 = 2'd2,
SELECT_IOSTRB = 2'd3;
reg[15:0] dsp_data_reg;//DSP数据总线寄存器
assign dsp_data = dsp_rw ? 16'hzz : dsp_data_reg;
reg rd_flag; //任务忙标志位,用于防止同时调用该任务
reg wr_flag; //任务忙标志位,用于防止同时调用该任务
initial begin
rd_flag = 0; //DSP读 任务不忙
wr_flag = 0; //DSP写 任务不忙
//DSP信号接口初始化
dsp_rw = 1;
dsp_data_reg = 16'hzzzz;
dsp_addr = 24'hzzzzzz;
dsp_strb0 = 1;
dsp_strb1 = 1;
dsp_iostrb = 1;
end
reg h1; //DSP时钟模拟,h1为DSP指令周期
initial begin
h1 = 1'b0;
forever
#20 h1 = ~h1;
end
//模拟DSP读FPGA任务
task task_dsp_read;
input[1:0] tcs; //片选输入
input[23:0] taddr; //地址输入
output[15:0] tdata;//数据读出
begin
……
end
endtask
//模拟DSP写FPGA任务
task task_dsp_write;
input[1:0] tcs; //片选输入
input[23:0] taddr; //地址输入
input[15:0] tdata; //数据写入
begin
……
end
endtask
endmodule