草庐IT

Verilog基础知识(二) Testbench编写

Triumph++ 2023-04-25 原文

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

有关Verilog基础知识(二) Testbench编写的更多相关文章

  1. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  2. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  3. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  4. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  5. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  6. ruby-on-rails - 如何为空白字段编写 rspec? [Rails3.1] - 2

    我使用rails3.1+rspec和factorygirl。我对必填字段(validates_presence_of)的验证工作正常。我如何让测试将该事实用作“成功”而不是“失败”规范是:describe"Addanindustrywithnoname"docontext"Unabletocreatearecordwhenthenameisblank"dosubjectdoind=Factory.create(:industry_name_blank)endit{shouldbe_invalid}endend但是我失败了:Failures:1)Addanindustrywithnona

  7. ruby-on-rails - 尝试为 Rails 中的用户名验证编写 REGEX - 2

    我正在尝试用Ruby(Rails)编写一个正则表达式,以便用户名的字符仅包含数字和字母(也没有空格)。我有这个正则表达式,/^[a-zA-Z0-9]+$/,但它似乎没有用,我在Rails中收到一个错误,说“The如果正则表达式使用多行anchor(^或$),这可能会带来安全风险。您是要使用\A和\z,还是忘记添加:multiline=>true选项?"我的user.rb模型中此实现的完整代码是:classUser我做错了什么以及如何修复此正则表达式,使其仅对数字和字母有效而不对空格有效?谢谢。 最佳答案 简短回答:使用/\A[a-z

  8. ruby-on-rails - 如何编写跨模型、 Controller 和 View 的 Rails mixin - 2

    为了减少我的小Rails应用程序中的代码重复,我一直致力于将我的模型之间的通用代码放入它自己的单独模块中,到目前为止一切顺利。模型的东西相当简单,我只需要在开头包含模块,例如:classIso这工作正常,但是现在,我将有一些Controller和View代码,这些代码也将在这些模型之间通用,到目前为止,我有这个用于我的可发送内容:#Thisisamodulethatisusedforpages/formsthatarecanbe"sent"#eitherviafax,email,orprinted.moduleSendablemoduleModeldefself.included(kl

  9. ruby - 我怎样才能更好地了解/了解更多关于 Ruby 的知识? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?

  10. ruby - 在不添加 "end"的情况下编写 Ruby 的任何方法? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭11年前。Ruby是一种美丽的语言,但有一个我讨厌写很多次的关键词“结束”。有什么方法可以写出简洁的代码而不用每次都写“end”吗?

随机推荐