当作者完成模块的verilog代码编写并进行模块功能验证时,常面临两个问题:
(1)数据量不够:难以覆盖所有情况。
(2)手动比对复杂:耽误时间,且可能出错。
因此“自动产生大量的、随机的参考输入和参考输出,并与模块的输出进行对比”是一个非常有效的解决上述问题的方式。这里,作者以4级8比特流水线乘法器为例,展示如何进行模块的自动测试与功能验证。
整体流程如图1所示:包含参考数据的生成、待测模块(DUT)、Testbench的搭建和Makefile编写和最终运行。供各位读者参考,欢迎大家在评论区交流。
首先,在testbench中自动读取Golden Brick所生成的若干对参考输入和参考输出。接着,将参考输入送给DUT后产生输出。最后,对比DUT的输出和参考输出,从而判定整个模块的是否正常工作。工作流程由Makefile控制。
图1. 整体流程图
第一步:参考数据(Golden brick)的生成
生成Golden brick的方式多种多样。这里我们以C语言为例,goldenbrick.c代码如下所示。其中M为Golden brick的行数,即M对参考输入和参考输出:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include <string.h>
#define M 16
#define Nj 8
#define Ni 8
#define N (Ni*Nj)
#define eps (1e-7)
int main(int argc, char **argv)
{
unsigned int A, B, Ai, Bi, Ri;
unsigned int R, R_pipe0, R_pipe1, R_pipe2, R_pipe3;
int i,t;
srand48(time(NULL));
assert (Ni<(sizeof(int)*8));
assert (Nj<(sizeof(int)*8));
R = 0;
R_pipe0 = 0;
R_pipe1 = 0;
R_pipe2 = 0;
R_pipe3 = 0;
for(t=0; t<M; t++) {
/* generate uniformly random integers in correct range */
A = rand() % 256;
B = rand() % 256;
R_pipe3 = R_pipe2;
R_pipe2 = R_pipe1;
R_pipe1 = R_pipe0;
R_pipe0 = R;
R = A*B;
printf("A = ");
for(i=Ni-1; i>=0; i--) {
Ai = ((A&(1<<i))!=0)?1:0;
printf("%i",Ai);
}
printf(" B = ");
for(i=Ni-1; i>=0; i--) {
Bi = ((B&(1<<i))!=0)?1:0;
printf("%i",Bi);
}
printf(" RESULT = ");
for(i=Ni+Nj-1; i>=0; i--) {
Ri = ((R_pipe3&(1<<i))!=0)?1:0;
printf("%i",Ri);
}
printf("\n");
}
return 0;
}
用以下两条命令在终端编译并运行上述代码:
gcc -Wall -ggdb -o goldenbrick goldenbrick.c
goldenbrick > goldenbrick.txt
可得如下goldenbrick.txt文件,包含M=16行输入和输出:
A = 01100111 B = 11000110 RESULT = 0000000000000000
A = 01101001 B = 01110011 RESULT = 0000000000000000
A = 01010001 B = 11111111 RESULT = 0000000000000000
A = 01001010 B = 11101100 RESULT = 0000000000000000
A = 00101001 B = 11001101 RESULT = 0100111110101010
A = 10111010 B = 10101011 RESULT = 0010111100101011
A = 11110010 B = 11111011 RESULT = 0101000010101111
A = 11100011 B = 01000110 RESULT = 0100010000111000
A = 01111100 B = 11000010 RESULT = 0010000011010101
A = 01010100 B = 11111000 RESULT = 0111110000111110
A = 00011011 B = 11101000 RESULT = 1110110101000110
A = 11100111 B = 10001101 RESULT = 0011111000010010
A = 01110110 B = 01011010 RESULT = 0101110111111000
A = 00101110 B = 01100011 RESULT = 0101000101100000
A = 00110011 B = 10011111 RESULT = 0001100001111000
A = 11001001 B = 10011010 RESULT = 0111111100111011
第二步:待测模块(DUT)
整个4级8比特流水线乘法器的代码mult.v如下所示。输入为clk,reset,a 和 b,输出为result。这里我们采用了同步复位的方式,即reset仅在clk的上升沿起作用。
module mult(clk, reset, a, b, result);
input [7:0] a, b;
input clk, reset;
output [15:0] result;
reg [3:0] P00, P01, P02, P03, P10, P11, P12, P13, P20, P21, P22, P23, P30, P31, P32, P33;
reg [9:0] C00, C01, C02, C03;
reg [11:0] G0, G1;
reg [15:0] result;
always@(posedge clk)
begin
P00 <= (reset == 1'b1) ? a[1:0] * b[1:0] : 4'h0 ;
P01 <= (reset == 1'b1) ? a[1:0] * b[3:2] : 4'h0 ;
P02 <= (reset == 1'b1) ? a[1:0] * b[5:4] : 4'h0 ;
P03 <= (reset == 1'b1) ? a[1:0] * b[7:6] : 4'h0 ;
P10 <= (reset == 1'b1) ? a[3:2] * b[1:0] : 4'h0 ;
P11 <= (reset == 1'b1) ? a[3:2] * b[3:2] : 4'h0 ;
P12 <= (reset == 1'b1) ? a[3:2] * b[5:4] : 4'h0 ;
P13 <= (reset == 1'b1) ? a[3:2] * b[7:6] : 4'h0 ;
P20 <= (reset == 1'b1) ? a[5:4] * b[1:0] : 4'h0 ;
P21 <= (reset == 1'b1) ? a[5:4] * b[3:2] : 4'h0 ;
P22 <= (reset == 1'b1) ? a[5:4] * b[5:4] : 4'h0 ;
P23 <= (reset == 1'b1) ? a[5:4] * b[7:6] : 4'h0 ;
P30 <= (reset == 1'b1) ? a[7:6] * b[1:0] : 4'h0 ;
P31 <= (reset == 1'b1) ? a[7:6] * b[3:2] : 4'h0 ;
P32 <= (reset == 1'b1) ? a[7:6] * b[5:4] : 4'h0 ;
P33 <= (reset == 1'b1) ? a[7:6] * b[7:6] : 4'h0 ;
end
always @(posedge clk)
begin
C00 <= (reset == 1'b1) ? (P00 + {P01, 2'b0}) + {(P02 + {P03, 2'b0}), 4'h0} : 10'h000 ;
C01 <= (reset == 1'b1) ? (P10 + {P11, 2'b0}) + {(P12 + {P13, 2'b0}), 4'h0} : 10'h000 ;
C02 <= (reset == 1'b1) ? (P20 + {P21, 2'b0}) + {(P22 + {P23, 2'b0}), 4'h0} : 10'h000 ;
C03 <= (reset == 1'b1) ? (P30 + {P31, 2'b0}) + {(P32 + {P33, 2'b0}), 4'h0} : 10'h000 ;
end
always @(posedge clk)
begin
G0 <= (reset == 1'b1) ? {C01 + C00[9:2], C00[1:0]} : 12'h000 ;
G1 <= (reset == 1'b1) ? {C03 + C02[9:2], C02[1:0]} : 12'h000 ;
end
always @(posedge clk)
begin
result <= (reset == 1'b1) ? {G0[11:4] + G1, G0[3:0]} : 16'h0000;
end
endmodule
第三步:Testbench搭建
mult_testbench.v可以实现以下功能:(1)打开输入文件goldbenbrick.txt和输出文件testbench.txt(2)设置时钟和复位信号(3)读取输入参考信号,对比模块输出和参考输出,正确输出pass,错误输出fail(4)例化DUT
module mult_testbench();
reg [7:0] a,b;
reg [15:0] ref_result,result;
reg clk;
reg reset;
/////////////////////////////////////////
// Open Files
integer read_file;
integer write_file;
initial
begin
read_file = $fopen("goldenbrick.txt", "r");
write_file = $fopen("testbench.txt", "w");
/////////////////////////////////////////
// Set up signals
clk = 1'b0;
reset = 1'b0;
#10
reset = 1'b1;
end
/////////////////////////////////////////
// Do file IO
always @(posedge clk)
begin
$fscanf(read_file, "A = %b\t B = %b\t RESULT = %b", a, b, ref_result);
if (!$feof(read_file))
begin
if (ref_result == result)
$fdisplay(write_file, "A = %b\t B = %b\t RESULT = %b pass", a, b, result);
else
$fdisplay(write_file, "A = %b\t B = %b\t RESULT = %b fail", a, b, result);
end
else
begin
$fclose(read_file);
$fclose(write_file);
$finish;
end
end
/////////////////////////////////////////
// Set up DUT
mult mult0(.clk(clk), .a(a), .b(b), .result(result), .reset(reset));
/////////////////////////////////////////
// Run Simulation
always @(*)
begin
#5 clk <= ~ clk;
end
备注:
(1)在上述代码中可以引入right_data和total_data两个变量,从而计算正确率。
(2)$feof的位置需要注意,之后我会就这个问题写文章进行分析与讨论。
第四步:Makefile编写和文件结构
Makefile代码如下所示,分别执行goldenbrick.c和Synopsys VCS仿真。
TESTBENCH = mult_testbench.v
SIM_FILES = mult.v
VV = vcs
VVOPTS = -o $@ +v2k +vc -sverilog -timescale=1ns/1ps +vcs+lic+wait +multisource_int_delays \
+neg_tchk +incdir+$(VERIF) +plusarg_save +overlap +warn=noSDFCOM_UHICD,noSDFCOM_IWSBA,noSDFCOM_IANE,noSDFCOM_PONF -full64 -cc gcc +libext+.v+.vlib+.vh
all: c_compile sim
c_compile:
cd goldenbrick; gcc -Wall -ggdb -o goldenbrick goldenbrick.c
cd goldenbrick; ./goldenbrick > goldenbrick.txt
sim:
cp goldenbrick/goldenbrick.txt verilog/goldenbrick.txt
cd verilog; $(VV) $(VVOPTS) $(SIM_FILES) $(TESTBENCH); ./$@
如图2所示,Makefile放在主文件夹中,goldenbrick.v放在goldenbrick文件夹中,mult.v和mult_testbech.v放置在verilog文件中。
图2. 文件结构
在图2处运行
make
我们即可在/verilog中看到testbench.txt,所有结果均正确。这说明模块功能化运行正常。
A = 01100111 B = 11000110 RESULT = 0000000000000000 pass
A = 01101001 B = 01110011 RESULT = 0000000000000000 pass
A = 01010001 B = 11111111 RESULT = 0000000000000000 pass
A = 01001010 B = 11101100 RESULT = 0000000000000000 pass
A = 00101001 B = 11001101 RESULT = 0100111110101010 pass
A = 10111010 B = 10101011 RESULT = 0010111100101011 pass
A = 11110010 B = 11111011 RESULT = 0101000010101111 pass
A = 11100011 B = 01000110 RESULT = 0100010000111000 pass
A = 01111100 B = 11000010 RESULT = 0010000011010101 pass
A = 01010100 B = 11111000 RESULT = 0111110000111110 pass
A = 00011011 B = 11101000 RESULT = 1110110101000110 pass
A = 11100111 B = 10001101 RESULT = 0011111000010010 pass
A = 01110110 B = 01011010 RESULT = 0101110111111000 pass
A = 00101110 B = 01100011 RESULT = 0101000101100000 pass
A = 00110011 B = 10011111 RESULT = 0001100001111000 pass
A = 11001001 B = 10011010 RESULT = 0111111100111011 pass
我们也可在图2处分别运行:
make c_compile
或者
make sim
从而分别进行goldenbrick的产生和vcs仿真。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理