接下来根据上述模块编写测试激励文件。
3.1 声明仿真的单位和精度
`timescale时间刻度指令用来说明模块工作的时间单位和时间精度,基本语句如下:
`timescale 时间单位/时间精度
时间单位和时间精度可以以秒、毫秒、纳秒、皮秒或飞秒作为度量,具体数值可以选择1、10或100
,如:
`timescale 10ns/1ns
该语句定义了当前模块中的仿真时间单位是10ns
,仿真精度是1ns
,语法上要求时间精度必须小于等于时间单位,即前面的数值要大于等于后面的数值。
测试模块中经常使用 #号
延迟来生成信号,例如:
`timescale 10ns/1ns
...
initial
begin
A = 0;B = 0;//初始值
#4 A = 1;//4时间单位后,即40ns
#5 B = 1;//5时间单位后,即50ns
#6 A = 0;//6时间单位后,即60nd
#7 $stop;//7时间单位后,即70ns
end
又如:
`timescale 1ns/1ns
当代码中出现#10
时,代表的意思是延时10ns
,由于仿真的精度为1ns
,所以最低的延时精度只能到1ns
,如果想要延时10.001ns
,则需要更改仿真的精度(1ns=1000ps
),代码如下:
`timescale 1ns/1ps
#10.001 rst_n = 0;
为了测试上述led闪烁模块,定义如下时间单位和时间精度:
`timescale 1ns/1ns
3.2 宏定义
宏定义采用 `define 来进行指定,把某个指定的标识符用来代表一个字符串,整个标识符在整个文件中都表示所指代的字符串,语法如下:
`define 标识符 字符串 //注意句末无分号
对于上述led闪烁模块,需要产生一个时钟信号给它,为了方便进行全文的修改,我们对时钟信号的周期进行宏定义:
`define clock_period 20
如果不想让宏定义生效,可以使用 `undef 指令取消前面定义的宏:
`define WIDTH 16
`undef WIDTH //此条语句之后,WIDTH失效
reg [`WIDTH-1:0] data; //报错,因为宏定义已经取消
3.3 定义测试模块名
定义模块名的关键字为module
,同时测试模块以endmodule
结束,代码如下:
module led_flash_tb;
...
...
...
endmodule
模块名的命名方式一般在被测模块名后面加上 _tb
,或者在被测模块名前面加上tb_
,表示为哪个模块提供激励测试文件,通常激励文件不需要定义输入和输出端口。代码中定义的常量有时需要频繁的修改,为了方便修改,可以把常量定义成参数的形式,定义参数的关键字为 parameter
。
3.4 声明信号
在testbench中,信号常用的类型为reg类型
和wire类型
,reg类型
表示激励信号,wire类型
表示输出信号。
定义led闪烁测试模块中的输入和输出信号:
//时钟信号和复位信号均需要输入给待测模块
reg Clk;
reg Rst_n;
//led信号为待测模块的输出信号
wire led;
3.5 模块实例化
例化的设计模块是指被测模块,例化被测模块的目的是把被测模块和激励模块实例化起来,并且把被测模块的端口与激励模块的端口进行相应的连接,使得激励可以输入到被测模块。
如果被测模块是由多个模块组成的,激励模块中只需要例化多个模块的顶层模块。
将led闪烁模块实例化:
led_flash led\_flash0(
.Clk(Clk);
.Rst\_n(Rst_n);
.led(led)
);
在实例化模块中,左侧带“.”
的信号为 led 模块定义的端口信号,右侧括号内的信号为激励模块中定义的信号,其信号名可以和被测模块中的信号名一致,也可以不一致,命名一致的好处是便于理解激励模块和被测模块信号之间的对应关系。
3.6 使用initial或always产生激励信号
产生时钟激励信号:
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
产生led闪烁测试模块的其余激励信号:
initial begin
Rst_n = 0;
#200
Rst_n = 1;
#(`clock_period\*200)
$stop;
end
3.7 完整的testbench代码
led_flash_tb.v:
//------<测试代码>------
`timescale 1ns/1ns
`define clock_period 20
module led_flash_tb;
reg Clk;
reg Rst_n;
wire led;
led_flash led\_flash0(
.Clk(Clk);
.Rst\_n(Rst_n);
.led(led)
);
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 0;
#200
Rst_n = 1;
#(`clock_period\*200)
$stop;
end
endmodule
四、补充语法
repeat循环和forever循环语句只适用于testbench的编写。
4.1 repeat循环
repeat循环
的功能是把循环体语句执行某些次数,基本格式:
repeat(次数)
begin
循环体语句
end
该语句的基本使用案例链接:【FPGA零基础学习之旅#7】BCD计数器设计。
测试代码:
`timescale 1ns/1ns
`define clock_period 20
module BCD_Counter_tb;
reg Clk;
reg Rst_n;
reg Cin;
wire Cout;
wire [3:0]q;
BCD_Counter BCD\_Counter0(
.Clk(Clk), //系统时钟信号
.Rst\_n(Rst_n), //系统复位信号
.Cin(Cin), //进位输入信号
.Cout(Cout), //进位输出信号
.q(q) //计数器值
);
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
Cin = 1'b0;
#(`clock_period\*20);
Rst_n = 1;
#(`clock_period\*20);
repeat(30)begin
Cin = 1'b1;
#(`clock_period\*1);
Cin = 1'b0;
#(`clock_period\*5);
end
#(`clock_period\*20);
$stop;
end
endmodule
其中,该部分代码表示执行循环体中的语句30次
,且循环体的语句表示生成一个占空比为1:5
的脉冲信号:
repeat(30)begin
Cin = 1'b1;
#(`clock_period\*1);
Cin = 1'b0;
#(`clock_period\*5);
end
脉冲信号是一种离散信号,形状多种多样,与普通模拟信号(如正弦波)相比,波形之间在Y轴不连续(波形与波形之间有明显的间隔),但具有一定的周期性是它的特点。最常见的脉冲波是矩形波(也就是方波)。脉冲信号可以用来表示信息,也可以用来作为载波,比如脉冲调制中的脉冲编码调制(PCM)、脉冲宽度调制(PWM)等等,还可以作为各种数字电路、高性能芯片的时钟信号。
4.2 forever循环
forever循环
表示永远循环,直到仿真结束为止,相当于判断条件永远为真。forever循环
语句中需要添加时序控制,否则就会陷入死循环。
可以用forever循环
语句来编写文章【FPGA零基础学习之旅#9】状态机基础知识中Hello例程
的testbench:
reg [7:0]ASCII;
Hello Hello0(
.Clk(Clk),
.Rst\_n(Rst_n),
.data(ASCII),
.led(led)
);
initial begin
Rst_n = 0;
ASCII = 0;
#(`clock_period\*20);
Rst_n = 1;
#(`clock_period\*20 + 1);
forever begin
ASCII = "I";
#(`clock_period);
ASCII = "A";
#(`clock_period);
ASCII = "M";
#(`clock_period);
ASCII = "X";
#(`clock_period);
ASCII = "i";
#(`clock_period);
ASCII = "a";
#(`clock_period);
ASCII = "o";
#(`clock_period);
ASCII = "M";
#(`clock_period);
ASCII = "e";
#(`clock_period);
ASCII = "i";
#(`clock_period);
ASCII = "g";
#(`clock_period);
ASCII = "e";
#(`clock_period);
ASCII = "H";
#(`clock_period);
ASCII = "E";
#(`clock_period);
ASCII = "M";
#(`clock_period);
ASCII = "l";
#(`clock_period);
ASCII = "H";
#(`clock_period);
ASCII = "E";
#(`clock_period);
ASCII = "L";
#(`clock_period);
ASCII = "L";
#(`clock_period);
ASCII = "O";
#(`clock_period);
ASCII = "H";
#(`clock_period);
ASCII = "e";
#(`clock_period);
ASCII = "l";
#(`clock_period);
ASCII = "l";
#(`clock_period);
ASCII = "o";
#(`clock_period);
ASCII = "l";
end
end
比较常见的还有使用forever循环
生成时钟信号,如:
//---<forever循环生成时钟信号>---
initial begin
Clk = 1;
forever #10 Clk = ~Clk;
end
同时,使用always块
生成时钟信号的代码:
//---<always块生成时钟信号>---
initial Clk = 1;
always #10 Clk = ~Clk;
上述的两种写法都是生成了周期为20个时间单位
的Clk
信号,效果是一样的。两种写法均可以在测试模块中使用,并无优劣之分。
4.3 仿真控制任务$stop
$stop
的功能是停止当前仿真,注意是停止,而不是退出,仿真器会把仿真到该语句的仿真运行完,然后停止仿真(任务$finish
的功能则是停止仿真并退出仿真器),等待下一步命令,此时依然停留在仿真器的仿真界面中,一些仿真窗口(例如波形窗口等)依然保留着,仿真的结果也是保留的。
设计者在自己的计算机上完成仿真而且代码规模较小时,一般都是使用$stop
任务作为仿真结束的标志语句,然后根据仿真窗口来查看仿真结果。
需要注意的是,如果使用了forever循环
语句产生持续不断的信号时,不需要在仿真中使用$stop
,例如上述中Hello例程
的完整testbench:
Hello_tb.v.:
`timescale 1ns/1ns
![img](https://img-blog.csdnimg.cn/img_convert/80f5123670b8f719b467978791b411c6.png)
![img](https://img-blog.csdnimg.cn/img_convert/2970b8185511f54fef2922dfd058a666.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
然后根据仿真窗口来查看仿真结果。
需要注意的是,如果使用了`forever循环`语句产生持续不断的信号时,不需要在仿真中使用`$stop`,例如上述中`Hello例程`的完整testbench:
>
> Hello\_tb.v.:
>
>
>
`timescale 1ns/1ns
[外链图片转存中…(img-3POzRGlG-1714171615618)]
[外链图片转存中…(img-AIJjS0L4-1714171615619)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!