测试模块语法
1 预备知识
1.1 模块实例化
module DFF(d, clk, rst, q, qb);
// 模块定义
endmodule
module REG4(d, clk, clr, q, qb);
output [3:0] q, qb;
input [3:0] d;
input clk, clr;
DFF d0(d[0], clk, clr, q[0], qb[0]); // 位置映射
DFF d1(.d(d[1]), .clk(clk), .rst(clr), .q(q[1]), .qb(qb[1])); // 名称映射
endmodule
- 位置映射中,实例中端口的次序与模块定义的次序相同;名称映射中,实例中端点的名称与模块定义的端点名称相同
- 模块实例化与调用程序不同。每个实例都是模块的一个完全的拷贝,相互独立、并行
1.2 延时 #n 语法
#n 表示延迟n个时间单位;
延迟赋值分为两种:正规延迟(写在赋值语句外)和内定延迟(写在赋值语句内);和三种赋值语句(连续赋值 assign,always 阻塞赋值,always 阻塞赋值)一共可以组成六种情况。
连续赋值 assign + 正规赋值
assign #10 C = A + B;
assign #(2, 3) C = A + B; // 门延迟
输入在 T 时刻发生变化,则先等待5个延迟,再计算 A + B 给 C(使用 T+5 时刻的 A 和 B 值)。若在等待期间(如T+1时刻)发生输入变化,则再次触发 assign,因而仿真器撤销先前的事件,重新执行该语句(先前的事件不再有效,即T+5时刻不会发生输出变化,在T+6时刻更新)
#(2, 3) 是一种门延迟(Gate Delay)或路径延迟(Path Delay)的表示方法,如果 C 要从 0 或 x 变为 1,需要等待 2 个时间单位。如果 C 要从 1 或 x 变为 0,需要等待 3 个时间单位。
always 阻塞/非阻塞赋值 + 正规赋值
always @(*) #5 C = A + B;
always @(*) #5 C <= A + B;
以上两个语句等价,输入在 T 时刻发生变化,则先等待5个延迟,再计算 A + B 给 C(使用 T + 5 时刻的 A 和 B 值);若 A + B 在 #5 延迟内发生新的变化,always 也不会重新执行
连续赋值 assign + 内定赋值
assign C = #5 A + B;
这是一个非法语句,若 A / B 在 T 时刻发生变化,则先计算 A + B,等待 #5 后将结果赋给 C(取决于T时刻的A、B值,具有记忆属性,与连续赋值原则不符)
always 阻塞赋值 + 内定赋值
always @(*) C = #5 A + B;
输入在 T 时刻发生变化,则先计算 A + B,再等待5个延迟将结果给C (使用 T 时刻的 A 和 B 值);若 A / B 在 # 5延迟内发生新的变化,always也不会重新执行(因为正在执行#5,所以敏感量被“挂起”)
always 非阻塞赋值 + 内定赋值
always @(*) C <= #5 A + B;
输入在 T 时刻发生变化,则先计算 A + B,再等待5个延迟将结果给C (使用 T 时刻的 A 和 B 值);若 A / B 在 # 5延迟内发生新的变化,则仿真器将新事件加入“队列”,依次完成刷新(在 T + 5 和 T + 6 时刻均刷新)。
对应电路的“传输线延迟”,常用
1.3 `timescale 语法
`timescale 说明仿真时间单位及精度,格式为 `timescale <time_unit>/<time_precision>,其中:
- time_unit:延时或时间的测量单位
- time_precision:延时值超出精度,要先舍入后使用
比如:
`timescale 1ns/100ps // 单位时间为 1ns,精度为 100ps
注意事项
`timescale语法必须放在模块定义之前- time_precision 不能大于 time_unit
- time_precision和time_unit的表示方法:
integer unit_string。integer : 可以是1, 10,100;unit_string: 可以是 s(second), ms(millisecond), us(microsecond), ns(nanosecond), ps(picosecond), fs(femtosecond) - 如果没有timescale说明将使用缺省值,一般是ns
2 测试模块
2.1 测试模块的三要素
- 产生激励(输入信号波形)
- 将输入激励加入到测试模块并收集其输出响应
- 将响应输出与期望值进行比较
2.2 施加激励的方法
产生激励并加到设计有很多种方法。一些常用的方法有:
- 从一个 initial 块中施加线性激励
- 从一个循环或 always 块施加激励
- 从一个向量或整数数组施加激励
- 记录一个仿真过程,然后在另一个仿真中回放施加激励
通常需要两类波形:
- 一类是具有重复模式的波形,例如时钟波形
- 另一类是一组确定值的波形,如输入的图像数据
2.3 确定值序列的产生方法
产生确定值序列的最佳方法是使用 initial 语句,initial 内使用阻塞赋值语句外延时,例如:
initial begin
Reset = 0;
#100 Reset = 1;
#80 Reset = 0;
#30 Reset = 1;
end
产生的波形如下图所示:
initial内采用阻塞赋值语句内延时,也能够按如上图像所示产生波形。
initial begin
Reset = 0;
Reset = #100 1;
Reset = #80 0;
Reset = #30 1;
end
2.4 重复值序列产生方法
重复值序列可以使用 always 语句或者 initial + forever 块来生成,例如常见的时钟信号产生:
reg clk_A, clk_B;
parameter CLK_PERIOD = 10;
// 时钟 A 生成
initial clk_A = 0;
always #(CLK_PERIOD/2) clk_A = ~clk_A;
// 时钟 B 生成
initial begin
clk_B = 0;
forever #(CLK_PERIOD/2) clk_B = ~clk_B;
end
上面的代码生成两个时钟信号,时钟 A 和时钟 B 完全相同,每 5 个时间单位翻转一次。
- 对于同一时刻的赋值,先执行阻塞赋值,再执行非阻塞赋值;
- 对于同一时刻、同一类型的赋值,按照代码顺序执行。
3 存储器
在实际芯片制造时,ROM、RAM都会采用特殊工艺,但是由于仿真目的的需要,有时候我们会定义存储器接口,这个时候就会使用存储器模块。
比如下面的代码,定义了一个32个8位寄存器组来模拟实际存储单元,其宽度为8,深度为32。
reg [7:0] Ram1 [31:0]; // 第一个元素为Ram1[31]
reg [7:0] Ram2 [0:31]; // 第1个元素为Ram2[0]
- 一个存储器赋值不能在一条赋值语句中完成,可以对存储器中的一个字赋值。
reg [31:0] Xrom [1:0];
Xrom = {{32'hA},{32'h8}}; // ERROR
Xrom[0] = 32'hA; // OK - 不允许对存储器变量值部分选择或位选择。
如果需要套读取存储器中的某几位数据,需要先将存储器单元赋值给寄存器变量,然后对该寄存器变量采用部分选择或者位选择操作。
reg [1:8] Ack, Dram [0:63];
Ack[0] = Dram[60]; // OK
Ack[2] = Dram[60][2]; // ERROR
Ack[2:0] = Dram[60][2:0]; // ERROR