Verilog基础知识(二) Testbench编写

        编写Testbench的目的是把RTL代码在Modsim中进行仿真验证,通过查看仿真波形和打印信息验证代码逻辑是否正确。下面以3-8译码器说明Testbench代码结构。

 

        Testbench代码的本质是通过模拟输入信号的变化来观察输出信号是否符合设计要求!因此,Testbench的核心在于如何模拟输入信号,并把模拟的输入信号输入到功能模块中产生输出信号,如上图所示。解决方案为:

  • 通过随机数产生输入信号
  • 通过实例化模块把模拟输入信号传入功能模块中

Testbench代码可自定义,也可自动生成!

2 自定义3-8译码器Testbench代码

2.1  现有的功能模块为3-8译码器,其功能模块的RTL代码为:

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

2.2 对应Testbench代码为:

`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

2.3 代码说明

(1) 时间单位:时间尺度预编译指令 时间单位 / 时间精度

  •  定义时间单位: `timescale 1ns/1ns    表示时间单位为1ns,时间精度为1ns
  • 时间单位和时间精度由值 1、10、和 100 以及单位 s、ms、us、ns、ps 和 fs 组成
  • 时间单位不能比时间精度小
  • 仿真过程所有与时间相关量的单位(即1单位的时间)
  • 时间精度:决定时间相关量的精度及仿真显示的最小刻度   1ns/1ps  精度为.0.01ns

(2) 延时:#数字

  • #10      表示延时10个单位的时间,时间单位为1ns时,该语句表示延时10ns

(3) 测试模块的命名:tb_<功能模块名>

  • 功能模块名为:decoder3_8, 则对应测试模块名为:tb_decoder3_8

(4) 需要定义模拟的输入/输出信号:

  • 输入/输出信号与功能模块中定义的输入/输出信号保持一致
  • 输入信号一般定义为 reg 型信号,因为后面需要在always/initial语句块中被赋值
  • 输出信号一般为 wire型即可

(5) 输入信号初始化

  • initial 语句进行初始化,该语句中的代码块只执行一次
  •  根据需要初始化为0/1都可

(6) 用always 语句实现信号在仿真过程中的电平变化

  • always在仿真过程中将被多次执行
  • always #10 in <= {$random} % 8; 表示每隔10个时间单位in的电平变化一次
    {$random}%8   表示随机选取[0,7]之间的数
  • in <= {$random} % 8; 在赋值时会自动进行数据类型转换
  • always后面最好只有一条语句

(7) 通过实例化模块把模拟输入信号传入功能模块中

  •  即把模拟的输入信号传入到功能模块中即可

(8) 通过 $monitor实现变量实时监测

  • 实时打印输入输出信号的数值,以便于监测
  • $monitor等系统函数要在initial语句块中

3  常用系统函数

testbench文件中编写的系统函数要在initial语句块中!

3.1  $timeformat 设置显示时间的格式

使用格式:

$timeformat(time_unit, decimal_number, suffix_string, minimum_field_wdith);
  • time_unit:时间单位,为一个数字。0(s)、-3(ms)、-6(us)、-9(ns)、-12(ps)、
    -15(ps),也可使用中间值:-10表示100ps为单位
  •  decimal_number:打印时间值的小数位
  • suffix_string:跟在时间值后的字符串(后缀字符串),一般写对应的时间单位ns、ps等
  • minimum_field_wdith:是时间值字符串与后缀字符串合起来的这部分字符串的最小长度
  • 主要用途:更改$write、$display、$strobe、$monitor、$fwrite、$fdisplay、$fstrobe、$fmonitor等任务在%t格式下显示时间的方式

3.2 其他常用系统函数

$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  / %CASCII码
%t  / %T时间
%s / %S字符串
%v / %V线网型信号强度
%m / %M层次名

 更多打印格式参考:

System_Verilog打印格式_我不是悍跳狼丶的博客-CSDN博客_verilog格式化输出

 3.2.1 以 $display 用于输出、打印信息为例

用法:

$display("Add:%b+%b=%d",a, b, c); //格式“%b+%b=%d” 格式控制,未指定时默认十进制
  • %b:表示对应位置显示二进制数
  • %d:表示对应位置显示十进制数
  • a, b, c 为与格式控制语句中格式控制符顺序对应的需要打印的变量

4 自动生成Testbench代码

        Quartus可自动生成Testbench的基本框架,其中包含仿真所需参数的定义及rtl模块的实例化,生成后自己根据仿真需求对相应的变量进行初始化,或对输入信号进行模拟即可!可减少代码量,避免不必要的错误!

按下图依次点击即可:

Processing -> Start ->  Start Test Bench Template Witer

在下方会显示生成的testbench文件路径:

 注意:此时生成的文件为.vt文件,需要在生成的文件夹中改成.v文件,再根据需要进行改写即可!

5 testbench中调用RTL代码中寄存器变量的方法

基本语法:实例化的模块名.变量名

如:在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型

6 在testbench中对输入信号进行模拟要用always块进行封装,使其与系统时钟相关联

如:在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  

7 仿真参数重定义

在实际仿真中,我们没有必要按照实际的计数器值进行仿真,这将给仿真调试带来不便,此时我们只需改仿真参数为较小的数,能方便的看出输入输出的关系即可:

7.1 方式1参数传递的方式

即在仿真文件的顶层模块中给每个参数传入新的值!如下所示:

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
);

这种写法要求参数要从最顶层模块传到最底层模块,每一级都需写参数列表,当参数过多时会造成不便!

7.2 使用defparam命名

        用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的子模块实例化名  = 值;

  • 0
    点赞
  • 430
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
你好!关于Verilog testbench的问题,我可以给你一些基本的信息。Verilog testbench是用于验证Verilog硬件描述语言编写的电路设计的工具。它是一个模拟环境,用于测试和调试硬件设计的正确性和功能。 Verilog testbench通常由两个主要部分组成:被测设计(DUT)和测试环境。被测设计是你要验证的Verilog电路设计,而测试环境是用于模拟输入信号和检查输出信号的代码。 测试环境包括生成输入信号、应用输入信号到DUT、检查DUT输出信号以及报告测试结果的代码。你可以使用Verilog语言编写测试环境,以确保DUT按预期工作。 以下是一个简单的Verilog testbench示例: ```verilog module DUT(input wire a, input wire b, output wire c); // 这里是你的电路设计 // ... endmodule module testbench; // 生成输入信号 reg a, b; wire c; // 实例化被测设计 DUT dut(a, b, c); // 模拟输入信号 initial begin a = 0; b = 0; #10; // 等待10个时间单位 a = 1; #10; b = 1; #10; $finish; // 结束仿真 end // 检查输出信号 always @(c) begin $display("c = %b", c); end endmodule ``` 在这个示例中,DUT是被测设计模块,testbench是测试环境模块。testbench模块中的initial块生成了一系列的输入信号,并在每个输入信号变化后等待一段时间。always块用于检查输出信号并进行显示。 请注意,这只是一个简单的Verilog testbench示例,实际的测试环境可能更加复杂,具体取决于你的电路设计和测试需求。 希望这个回答能对你有所帮助!如果你有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Triumph++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值