手动仿真在项目开发中是比较常用的,此时需要手动编写testbench文件。对于初学者来说,可能觉得编写testbench文件比较困难,但其实并没有想象的那么复杂,我们只需要按照testbench的结构,编写基本的激励文件还是比较容易的。当编写完testbench文件后,如果以后需要仿真其它模块的话,只需要在此基础上稍作修改即可。
编写testbench文件 的 主要目的 是为了对使用硬件描述语言(Verilog HDL或者VHDL)设计的电路进行仿真验证,测试设计电路的功能、部分性能是否与预期的目标相符。
Robei的仿真 相比于Modelsim和Vivado使用起来更简洁,更适合新手入门。在实验室,一节课2小时,光建立个工程就花掉半小时,这是非常繁琐的。Robei在仿真上,例化模块,以及代码编辑以及波形查看都非常方便。
基本的testbench结构如下:
`timescale 仿真单位/仿真精度
module test_bench();
//通常 testbench没有输入与输出端口
信号或变量定义声明
使用initial或always语句产生激励波形
例化设计模块
endmodule
激励文件的开头要声明仿真的单位和仿真的精度,声明的关键字为timescale,声明方法
如下:
`timescale 1ns/1ns
需要注意的是,timescale声明仿真单位和精度时,不需要以分号结尾。“/”之前的1ns表示仿真的单位是1ns,“/”之后的1ns表示仿真的精度是1ns。当代码中出现“#10”时,代表的意思是延时10ns,由于仿真的精度为1ns,所以最低的延时精度只能到1ns,如果想要延时10.001ns,则需要更改仿真的精度(1ns=1000ps),代码如下:
`timescale 1ns/1ps
#10.001 rst_n = 0;
需要注意的是,robei的仿真的设计精度以及单位需要填在前面板设计栏的Time_Scale一项中。
仿真的单位和精度声明完成后,接下来定义模块名,定义模块名的关键字为module,代码
如下:
module flow_led_tb();
模块名的命名方式一般在被测模块名后面加上“ _ tb”,或者在被测模块名前面加上“tb_”,表示为哪个模块提供激励测试文件,通常激励文件不需要定义输入和输出端口。
代码中定义的常量有时需要频繁的修改,为了方便修改,可以把常量定义成参数的形式,定义参数的关键字为parameter,代码如下:
parameter T = 20;
Verilog代码中,常用声明信号或变量的关键字为reg和wire,在initial语句或者always语句中使用的变量定义成reg类型,在assign语句或者用于连接被例化模块名的信号定义成wire类型,声明方法如下:
//reg define
reg sys_clk; // 时钟信号
reg sys_rst_n; // 复位信号
//wire define
wire [3:0] led;
使用initial或always语句产生激励波形
产生时钟激励的代码如下:
always #10 sys_clk = ~sys_clk;
上述代码表示每10ns(假设仿真单位是1ns),sys_clk的电平状态翻转一次,由于一个完整的时钟周期包括一个高电平和一个低电平,因此sys_clk的时钟周期为20ns,占空比为50%。如果要生成其它占空比时钟的话,代码如下:
always begin
#6 sys_clk = 0; #4 sys_clk = 1; end
需要注意的是,在always语句中设置了sys_clk的时钟周期,并没有设置初始值,因此sys_clk需要在initial语句中进行初始化。代码如下:
initial begin
sys_clk = 1'b0; // 时钟初始值
sys_rst_n = 1'b0; // 复位初始值
#20 sys_rst_n = 1'b1; // 在第21ns的时候复位信号信号拉高
end
例化的设计模块是指被测模块,例化被测模块的目的是把被测模块和激励模块实例化起来,并且把被测模块的端口与激励模块的端口进行相应的连接,使得激励可以输入到被测模块。如果被测模块是由多个模块组成的,激励模块中只需要例化多个模块的顶层模块,代码如下:
flow_led u0_flow_led (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.led (led )
);
在实例化模块中,左侧带“.”的信号为flow_led模块定义的端口信号,右侧括号内的信号为激励模块中定义的信号,其信号名可以和被测模块中的信号名一致,也可以不一致,命名一致的好处是便于理解激励模块和被测模块信号之间的对应关系。在实例化被测模块后,以endmodule结束。完整的testbench文件代码如下:
`timescale 1ns/1ns // 定义仿真时间单位1ns和仿真时间精度为1ns
module flow_led_tb(); // 测试模块
//parameter define
parameter T = 20; // 时钟周期为20ns
//reg define
reg sys_clk; // 时钟信号
reg sys_rst_n; // 复位信号
//wire define
wire [3:0] led;
//给输入信号初始值
20 initial begin
21 sys_clk = 1'b0;
22 sys_rst_n = 1'b0; // 复位
23 #(T+1) sys_rst_n = 1'b1; // 在第21ns的时候复位信号信号拉高
24 end
25
26 //50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
27 always #(T/2) sys_clk = ~sys_clk;
28
29 //例化flow_led模块
30 flow_led u0_flow_led (
31 .sys_clk (sys_clk ),
32 .sys_rst_n (sys_rst_n),
33 .led (led )
34 );
35
36 endmodule
在robei中,仿真例化是直接像平时程序的设计一样添加模块,连线,无需写代码,很方便。
事实上,用于仿真的关键字比较多,有些是可以综合的(能生成实际的电路),有些只能用于仿真,这里仅介绍testbench文件常用的关键字和基本的编写方法。