在编写testbench做简单的模块功能验证时,initial初始化的过程块和模块的实例化是常见的两个块,这两者的执行顺序一般来说认为是并行执行的,但是笔者在实际仿真过程中发现并不是这样的,现举例记录和说明如下。
实例
功能仿真环境
- Modelsim SE-64 2020.4
仿真代码
如下所示,待验证模块的功能为一个输入尺寸为384,单个输入数据的位宽为7的循环左移器,通过其他高级语言生成对应的输入输出数据存入txt文件中,在testbench中读入以进行验证,为纯组合逻辑。
`timescale 1 ns / 1ns
module barrel_shifter_tb;
parameter LIFT_SIZE = 384;
parameter STAGE_NUM = $clog2(LIFT_SIZE) + 1; // ceil(log2(384)) = 9, 9 + 1 = 10
parameter SHIFT_WIDTH = STAGE_NUM - 1; // 9
parameter MSG_WIDTH = 7;
parameter DATA_WIDTH = LIFT_SIZE * MSG_WIDTH; // 384 * 7 = 2688
parameter MAX_APP_VALUE = 2 ** MSG_WIDTH - 1;
parameter MAX_TEST_CASES = 20;
reg [DATA_WIDTH - 1 : 0] app_msg_in;
reg [SHIFT_WIDTH - 1 : 0] shift_factor;
wire [DATA_WIDTH - 1 : 0] app_msg_out;
reg [DATA_WIDTH - 1 : 0] app_msg_out_valid;
reg [DATA_WIDTH - 1 : 0] dataInMem[0:(MAX_TEST_CASES - 1)];
reg [DATA_WIDTH - 1 : 0] dataOutMem[0:(MAX_TEST_CASES - 1)];
reg [SHIFT_WIDTH - 1 : 0] shiftFacMem[0:(MAX_TEST_CASES - 1)];
integer i;
integer errCnt;
barrel_shifter u_barrel_shifter(
.app_msg_in(app_msg_in),
.shift_factor(shift_factor),
.app_msg_out(app_msg_out)
);
initial begin
errCnt = 0;
$readmemb("./sim/dataIn.txt", dataInMem);
$readmemb("./sim/dataOut.txt", dataOutMem);
$readmemb("./sim/shiftFactors.txt", shiftFacMem);
for (i = 0; i < MAX_TEST_CASES; i = i + 1) begin
app_msg_in = dataInMem[i];
shift_factor = shiftFacMem[i];
app_msg_out_valid = dataOutMem[i];
checkOutputs;
#10;
end
if (errCnt) begin
$display("********** TOTAL ERRORS: %d **********\n", errCnt);
end
else begin
$display("********** NO ERRORS! **********\n");
end
$stop;
end // inital
task checkOutputs;
begin
if (app_msg_out != app_msg_out_valid) begin
error;
end
end
endtask
task error;
begin
errCnt = errCnt + 1;
$display("ERROR AT %d: expected -> %h, get -> %h", i, app_msg_out_valid, app_msg_out);
end
endtask
endmodule
仿真结果
功能仿真的波形结果如下所示。不难看出,波形的结果是与预期相符合的。
但是,从上图中的红色方框中可以看出,testbench中是检测到了错误存在的,并且观察打印的模块输出和预期输出,会发现存在“错位”的现象,如检查第3个输出时,预期输出是读取到的第三个输出,但是testbench中比较的模块输出却是第二个输出,从结果上就像是纯组合逻辑的循环移位器的输出有“一级寄存器”,但这显然是不可能的,这是怎么一回事儿呢?
询问一些大佬之后,最终得到的结论(猜测)是,initial块和模块实例化在EDA工具仿真执行时其实还是存在一些逻辑上的先后顺序的,不能完完全全地当作并行来看。在这一假设的前提下,前面的现象就可以解释了,即相当于initial块中的输入用存储器中的数据初始化后,实例化的模块“经过一段时间”后才产生输出,而不是initial块中的输入一改变实例化模块的输出就立马发生变化并传递到了initial块中,因此在实例化模块产生输出“迟滞”的这一段时间内,initial块进行了输出检查,自然就是检查的实例化模块上一次的输出结果了,但根据initial块中顺序执行的规则,这时候的预期结果是本次的预期输出结果,这细微的差异就导致了仿真结果的异常。
解决方案
由于这是EDA仿真原理的问题,因此需要咱们在平时仿真时自行注意并采取对应的救急方案,笔者这里给出一种解决方案,即类似于寄存器的保持时间和建立时间,既然输出有“迟滞”,那就等一会儿,如下所示,就可以得到预期结果了~
……
for (i = 0; i < MAX_TEST_CASES; i = i + 1) begin
app_msg_in = dataInMem[i];
shift_factor = shiftFacMem[i];
app_msg_out_valid = dataOutMem[i];
#5 checkOutputs;
#10;
end
……