跳到主要内容

测试模块语法

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

注意事项

  1. `timescale 语法必须放在模块定义之前
  2. time_precision 不能大于 time_unit
  3. 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)
  4. 如果没有timescale说明将使用缺省值,一般是ns

2 测试模块

2.1 测试模块的三要素

  1. 产生激励(输入信号波形)
  2. 将输入激励加入到测试模块并收集其输出响应
  3. 将响应输出与期望值进行比较

2.2 施加激励的方法

产生激励并加到设计有很多种方法。一些常用的方法有:

  • 从一个 initial 块中施加线性激励
  • 从一个循环或 always 块施加激励
  • 从一个向量或整数数组施加激励
  • 记录一个仿真过程,然后在另一个仿真中回放施加激励

通常需要两类波形:

  • 一类是具有重复模式的波形,例如时钟波形
  • 另一类是一组确定值的波形,如输入的图像数据

2.3 确定值序列的产生方法

产生确定值序列的最佳方法是使用 initial 语句,initial 内使用阻塞赋值语句外延时,例如:

initial begin
Reset = 0;
#100 Reset = 1;
#80 Reset = 0;
#30 Reset = 1;
end

产生的波形如下图所示:

1

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
    如果需要套读取存储器中的某几位数据,需要先将存储器单元赋值给寄存器变量,然后对该寄存器变量采用部分选择或者位选择操作。