赞
踩
编写Testbench的目的是把RTL代码在Modsim中进行仿真验证,通过查看仿真波形和打印信息验证代码逻辑是否正确。下面以3-8译码器说明Testbench代码结构。
Testbench代码的本质是通过模拟输入信号的变化来观察输出信号是否符合设计要求!因此,Testbench的核心在于如何模拟输入信号,并把模拟的输入信号输入到功能模块中产生输出信号,如上图所示。解决方案为:
Testbench代码可自定义,也可自动生成!
- module decoder3_8(
- input wire [2:0] in,
- output reg [7:0] out
- );
- // always/initial 模块中只能用 reg型变量
- always @(*) begin
- case(in)
- 3'h0: out = 8'h01;
- 3'h1: out = 8'h02;
- 3'h2: out = 8'h04;
- 3'h3: out = 8'h08;
- 3'h4: out = 8'h10;
- 3'h5: out = 8'h20;
- 3'h6: out = 8'h40;
- 3'h7: out = 8'h80;
- // 避免lanth
- default: out = 8'h00;
- endcase
- end
- endmodule
- `timescale 1ns/1ns // 时间单位及=精度设置
-
- module tb_decoder3_8();
-
- reg [2:0] in;
-
- wire [7:0] out;
- // 初始化
- initial begin
- in <= 3'h0;
- end
- // 实现输入信号电平自动变化
- always #10 in <= {$random} % 8;
- initial begin
- $timeformat(-9, 0, "ns", 6);
- $monitor("time:%t in:%b out:%b",$time,in,out);
- end
- // 通过实例化模块把模拟输入信号传入功能模块中
- decoder3_8 decoder3_8_inist(
- .in(in),
- .out(out)
- );
- endmodule
(1) 时间单位:时间尺度预编译指令 时间单位 / 时间精度
(2) 延时:#数字
(3) 测试模块的命名:tb_<功能模块名>
(4) 需要定义模拟的输入/输出信号:
(5) 输入信号初始化
(6) 用always 语句实现信号在仿真过程中的电平变化
(7) 通过实例化模块把模拟输入信号传入功能模块中
(8) 通过 $monitor实现变量实时监测
testbench文件中编写的系统函数要在initial语句块中!
使用格式:
$timeformat(time_unit, decimal_number, suffix_string, minimum_field_wdith);
- $display //打印信息,自动换行
- $write //打印信息
- $strobe //打印信息,自动换行,最后执行
- $monitor //监测变量
- $stop //暂停仿真
- $finish //结束仿真
- $time //时间函数
- $random //随机函数
- $readmemb //读文件函数
用法:
$<系统函数名>("格式控制语句", 变量1, 变量2, 变量3....);
相应的格式控制符有:
(1) 常用转义字符
转义字符 | 含义 |
\n | 换行符 |
\t | 横向制表符 |
\v | 纵向制表符 |
\\ | 反斜杠 / |
\'' | 引号 '' |
\a | 响铃 |
%% | 百分号 % |
(2) 常用数据格式
格式 | 说明 |
%b / %B | 二进制 |
%d / %D | 十进制 |
%o / %O | 八进制 |
%h / %H | 十六进制 |
%e / %E | 科学计数法显示十进制数 |
%c / %C | ASCII码 |
%t / %T | 时间 |
%s / %S | 字符串 |
%v / %V | 线网型信号强度 |
%m / %M | 层次名 |
更多打印格式参考:
System_Verilog打印格式_我不是悍跳狼丶的博客-CSDN博客_verilog格式化输出
用法:
$display("Add:%b+%b=%d",a, b, c); //格式“%b+%b=%d” 格式控制,未指定时默认十进制
Quartus可自动生成Testbench的基本框架,其中包含仿真所需参数的定义及rtl模块的实例化,生成后自己根据仿真需求对相应的变量进行初始化,或对输入信号进行模拟即可!可减少代码量,避免不必要的错误!
按下图依次点击即可:
Processing -> Start -> Start Test Bench Template Witer
在下方会显示生成的testbench文件路径:
注意:此时生成的文件为.vt文件,需要在生成的文件夹中改成.v文件,再根据需要进行改写即可!
基本语法:实例化的模块名.变量名
如:在RTL代码中定义了变量 state
- module rtl_module(
- port
- );
- // 定义状态寄存器
- reg [2:0]state;
-
- endmodule
在testbench中访问的方式为:
- `timescale 1 ns/ 1 ps
- module tb_rtl_module(
- port
- );
- // 获取rtl代码中的变量
- wire [2:0] state = rtl_module_int1.state;
-
- // 实例化的模块
- rtl_module rtl_module_int1 (
- .port(port)
- )
- endmodule
注意:testbench中接收的变量要定义为wire型!
如:在RTL代码中定义的输入信号in
- module rtl_module(
- input wire in
- );
- //
- endmodule
在testbench中对输入信号进行模拟的方式为:
- `timescale 1 ns/ 1 ps
- module tb_rtl_module();
-
- reg in;
-
- always @(posedge sys_clk or negedge sys_rst_n) begin
- if(sys_rst_n == 1'b0)
- in<= 1'b0;
- else
- in<= {$random} % 2;
- end
-
- rtl_module rtl_module_int1 (
- .in(in)
- )
- endmodule
注意事项:有严格时序要求时不能采用直接复制的方式,否则仿真图中会出现逻辑混乱的问题!
错误演示:
- initial
- begin
- sys_clk = 1'b1;
- sys_rst_n <= 1'b0;
- push_money <= 1'b0;
- #30
- sys_rst_n <= 1'b1;
- #28
- push_money <= 1'b1;
- #43
- push_money <= 1'b0;
- #59
- push_money <= 1'b1;
- #68
- push_money <= 1'b0;
- end
在实际仿真中,我们没有必要按照实际的计数器值进行仿真,这将给仿真调试带来不便,此时我们只需改仿真参数为较小的数,能方便的看出输入输出的关系即可:
即在仿真文件的顶层模块中给每个参数传入新的值!如下所示:
- rom_ip
- #(
- .CNT_200MS_MAX (199) ,
- .CNT_256_MAX (9) ,
- .CNT_KEYFILTER_MAX (9) ,
- .CUNT_SCAN_MAX (99) ,
- .CNT_SHIFT_MAX (21)
- )
- rom_ip_inst (
- port
- );
这种写法要求参数要从最顶层模块传到最底层模块,每一级都需写参数列表,当参数过多时会造成不便!
用defparam命令重定义每个子模块中的仿真参数,这样比较直观,且可以对任意子模块的参数进行设置,较为方便。
- // 重定义仿真参数的方法
- defparam rom_ip_inst.rom_rader_inst.CNT_200MS_MAX = 199;
- defparam rom_ip_inst.rom_rader_inst.CNT_256_MAX = 9;
- defparam rom_ip_inst.key1_filter_inst1.COUNTER_MAX = 9;
- defparam rom_ip_inst.key1_filter_inst2.COUNTER_MAX = 9;
- defparam rom_ip_inst.dynamic_seg_main_inst1.CUNT_SCAN_MAX = 99;
- defparam rom_ip_inst.dynamic_seg_main_inst1.CNT_SHIFT_MAX = 99;
语法:
defparam 顶层模块实例化名.子模块1实例化名.子模块1的子模块实例化名 = 值;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。