TestBench编写基础知识学习


下面用一个移位寄存器的程序,讲解一些知识点

verilog程序

module shift_reg (clock, reset, load, sel, data, shiftreg);
input clock;
input reset;
input load;
input [1:0] sel;
input [4:0] data;
output [4:0] shiftreg;
reg [4:0] shiftreg;

always @ (posedge clock)
begin
    if (reset)
        shiftreg = 0;
    else if (load)
        shiftreg = data;
    else
        case (sel)
            2'b00 : shiftreg = shiftreg;
            2'b01 : shiftreg = shiftreg << 1;
            2'b10 : shiftreg = shiftreg >> 1;
            default : shiftreg = shiftreg;
        endcase
end
endmodule 

testbench程序

`timescale  1ns/1ns     

module testbench; // 申明TestBench名称
reg clock;
reg load;
reg reset; // 申明信号
wire [4:0] shiftreg;
reg [4:0] data;
reg [1:0] sel;

// 申明移位寄存器设计单元
shift_reg dut(.clock (clock),
    .load (load),
    .reset (reset),
    .shiftreg (shiftreg),
    .data (data),
    .sel (sel));

initial begin   // 建立时钟
    clock = 0;
    forever #50 clock = ~clock;
end

initial begin   // 提供激励
    reset = 1;
    data = 5'b00000;
    load = 0;
    sel = 2'b00;
    #200
    reset = 0;
    load = 1;
    #200
    data = 5'b00001;
    #100
    sel = 2'b01;
    load = 0;
    #200
    sel = 2'b10;
    #1000 $stop;
end

initial begin   // 打印结果到终端
    $timeformat(-9,1,"ns",12);
    $display(" Time Clk Rst Ld SftRg Data Sel");
    $monitor("%t %b %b %b %b %b %b", $realtime,
    clock, reset, load, shiftreg, data, sel);
end
endmodule 

`timescale的使用方法

`timescale 1ns/1ns ,前面是仿真单位,后面是仿真精度。在这种情况下#200表示延时200ns后进行操作,如果我写#200.1是无效的,因为精度只有1ns。

`timescale 1ns/10ps,在这种情况下输入#200.1是有效的,因为最小仿真单位是10ps,而#200.1仿真的最小是100ps,所以这种情况下输入有效。

timeformat和monitor的使用方法

语法:$timeformat( Units, Precision, “Suffix”, MinFieldWidth );

规则:
1.Unit 是 0 到-15 之间的整数值,表示打印的时间的单位:0 表示秒,-3 表示毫秒,-6 表示微秒。-9 表示毫微(纳)秒; -12 表示微微(皮)秒; -15 表示毫微微秒;中间值也可以使用:例如-10表示100ps单位。

2.Precision 是在小数点后面要打印的小数位数。

3.Suffix 是在时间值后面打印的一个字符串。

4.MinFieldWidth 是打印的时间的字符长度,包括前面的空格。如果要求更多字符,那么打印的字符更多。

5.如果没有指定变量,默认地使用下面的值:Units:仿真精度;Precision:0;Suffix:空字符串;MinFieldWidth:20 个字符。

    $timeformat(-9,1,"ns",12);
    $display(" Time Clk Rst Ld SftRg Data Sel");
    $monitor("%t %b %b %b %b %b %b", $realtime,
    clock, reset, load, shiftreg, data, sel);

这段代码的打印结果如下图所示:
在这里插入图片描述
打印的结果在ModelSim的Transcript窗口查看。display函数的作用只是打印了里面的字符串,没什么需要讲的,主要看后面的monitor函数。monitor函数前半段是要显示的数据的格式,后半段是要显示的信号是什么。当monitor里面的任一变量发生一次变化,monitor便会输出一次结果。

%t和$realtime
$realtime返回的时间数字是一个实型数。该数是以时间尺度为基准的。可以结合上面的对timeformat的讲解对照着看上面图中显示的时间那栏的信息。

$time可以返回一个64位的整数来表示当前仿真时刻值,该时刻也是以模块的仿真时间尺度位基准的。

%b和$reset等
%b表示数据以二进制来显示reset,其他同理。需要注意monitor里面的端口名称应该是testbench文件中定义的端口名,而不是在quartus程序中定义的。假如testbench中例化模块有这段程序 .sys_clk (clock),前面的sys_clk是quartus程序里面定义的端口名,后面的clock是testbench里面定义的端口,在写monitor里面的变量时应该用clock。

时钟模块的testbench程序编写

一段程序里面可以有多个

initial 
	begin
		...
	end

每个initial语句是并行执行,在仿真的起始时刻同时开始,所以为了让程序更加明了,可以把时钟的初始化单独放在一个initial语句,像下面一样,先给时钟一个初始状态,然后每T/2翻转一次。

initial begin   // 方法一
    clock = 0;
    forever #(T/2)  clock = ~clock;
end

initial begin   // 方法二
    sys_clk= 0;
end
always #(T/2) sys_clk = ~sys_clk;

虽然每个initial是并行的,但是就单个initial而言,它内部的程序是顺序执行的,在没有时延代码时,①和②虽然说执行有先后,但他们仿真时是同时完成赋值的。

initial begin   
    reset = 1;                //①
    data = 5'b00000;          //②
    #200
    reset = 0;
    #200
    data = 5'b00001;
    #1000 $stop;
end

可以看到程序中有仿真停止时间通过语句$stop完成,对于那些不知道仿真何时才会出现我们要观察的结果的程序中也可以没有停止语句,但这也会导致仿真时间轴很长,也很难找到要观察的波形,解决办法可以参考: Modelsim Se-64的一些使用技巧

在testbench中例化quartus模块

wire 	data_in   //input信号
...
assign data_in = A & B;

对于quartus程序中的input和output端口,在testbench中大多数情况定义为reg和wire型,但这并不绝对,假如我们在testbench中模拟输入,需要用到assign语句来完成输入型号的模拟,这个时候就要定义他为wire型。

shift_reg u_shift_reg 
	(
		 .clock (clk),
   		 .load  (ld ),
   		 .reset (rst)
   	);

例如这段testbench程序,shiftreg是quartus程序的顶层模块,在例化它时,clk,ld,rst这些参数要用testbench中定义的,而不是用quartus程序中定义的参数。clock,load,reset这些是quartus模块的端口名,不用改变。

parameter和`define的区别

`define    T1   20
parameter  T2 = 20;   

always #(`T1/2)  clk1 = ~clk1; 
always #(T2/2)   clk2 = ~clk2;  

1.对于`define定义的参数,在定义代码语句的最后没有“;”,但是parameter定义参数时,在语句的结尾要加“;” 。

2.在使用`define 定义的参数时,在参数名字前面要加左上角的点(键盘数字左边的那个键),在使用parameter 定义的参数时,直接写参数名就行了。

`define:           作用 -> 常用于定义常量可以跨模块、跨文件;
                    范围 -> 整个工程;
parameter:         作用 -> 常用于模块间参数传递;
                    范围 -> 本module内有效的定义;
localparam          作用 -> 常用于状态机的参数定义;
                    范围 -> 本module内有效的定义,不可用于参数传递;
localparam cannot be used within the module port parameter list.

defparam用于重定义参数的数,可以通过defparam 顶层.子模块名1.子模块名2=XXX。这样的语法格式更改,从而实现专门针对某一个子模块更改,这样有助于例化了多个相同的子模块,但我只更改一个子模块的问题。defparam目前所有综合工具都不支持综合,所以仅仅用于仿真
FPGA设计心得(8)Verilog中的编译预处理语句.这篇比较详细的讲了宏定义,可以参考

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页