テストの考え方
- 基本的に浮動小数点のテストの考え方と同じです。ただし、逐次期待を比較するのではなく結果をまとめて比較するパスを加えた3パス方式[1]です。
- 入力値はランダムな値[2]、期待値は左記のデータをDFTしたものとします。ところで、期待値は単精度計算なので必ず誤差が生じます。従って、結果と期待値の比較はSNRを確認することにします。共にC言語で作成します。
- 連続起動をテストするため、FFTを2回起動しパイプラインにバブルがないか性能も確認します。実は3回以上は起動できません。SRAM制御のブロック?を組み込んでいないので、3回目のFFTがSRAMの未使用部分に上書きをしてしまいます。逆にNGを確認します。
テストモジュールの概要
- 簡単なテストモジュールを示します。入力値はファイルから読み出し、結果はファイルに書き出すものとします。テスト名はtestでクロックとリセットを発生します。これに呼び出されるtopモジュールは、テストすべきモジュールと$readmemhによる入力値の読み出し、$fdisplayによる結果の書き込みを行います。
- 入力値はシリアルにFFTに入れられ、出力値はシリアルにファイルに書き込みます。つまり、verilogシミュレータでファイルの変換をするだけです。
テストの仕方
- トップモジュールとその他とLoadDataとStoreDataとBfCalcとCoefのコードを揃えます[3]。fmulとfaddも利用します。
- 下記のテストモジュールサンプルを同様に揃えます。
- 下記の入力データ作成ツールでランダムな入力ファイル"in.hex"を作成します。次に期待値データ作成ツールで"in.hex"から"exp.hex"を作成します。
> ./fmk 10 ←1024ポイントの入力値作成(fmkは適当につけたコマンド名)
> ./dft 10 ←1024ポイントの期待値作成(dftは適当につけたコマンド名)
- verilogシミュレータが読み込むファイル名"ins.hex"に変換します。FFTを2回連続してシミュレーションする場合は、"in.hex"と"exp.hex"を別名にし2セット作ります。
> ./fmk 10
> ./dft 10
> mv in.hex in0.hex
> mv exp.hex exp0.hex ←1回目
> ./fmk 10
> ./dft 10
> mv in.hex in1.hex
> mv exp.hex exp1.hex ←2回目
> cat in0.hex in1.hex >! ins.hex
> cat exp0.hex exp1.hex >! exps.hex
- シミュレータの前にtop.vのFFTを回す回数Nとポイント数の指数Rの値を確認します。共にparameter文で先頭に記述しています。
- verilogシミュレータを動かし、結果の"outs.hex"を作成します。
- 下記のデータ比較ツールで"exps.hex"と"outs.hex"の比較を行います。SNRで飛び出た値がないかチェックします。SNRはabs(期待値-出力値)/期待値で計算しています。
> ./fcmp 10 ←1024ポイントの結果と期待値比較(fcmpは適当につけたコマンド名)
- FFTの性質上、小さい値は途中大きい値のまるめ誤差の影響を受けて、SNRが極端に大きくなることがあります。従って、SNRが大きい場合、期待値が小さい値かどうかで判断します[4]。
- 波形で結果を確認してみます[5]。N=64でGapを取ると出力結果は間違ったものになりますが、見やすくするため、あえてGapを取ったFFTの結果の波形を示します。BfCalcが想定通りのサイクルで動作し、2回連続しても隙間なく計算を実行していることが分かります。
- 波形から分かるように、LoadDataとStoreDataにかかるサイクルがBfCalcよりも長くなることが想定できます(実際メモリに読み書きするとそう言った事態が生じます)。この状況を引き起こすテストも必要です。都合上割愛しますが、top.vのfftモジュール呼び出しで固定しているmRdAck, mWrAck信号を、数回に1回しかアサートしないようにすれば簡単に対応できます。
ツール(入力データ作成)[6]
- ランダムな入力データの作成。半精度浮動小数点表記の複素数列を"in.hex"名で出力。引数はポイント数の指数(指定なしの場合は10即ち1024)。
#include
#include
#include
main(int argc, char *argv[])
{
FILE *fp;
int i;
int radix;
int num;
int tmp_i;
float tmp_f;
int *tmp_p;
int z, s, e, m, x;
srand((unsigned)time(NULL));
if (argc > 1)
radix = atoi(argv[1]);
else
radix = 10;
num = 1 << radix;
if ((fp = fopen("in.hex", "w")) == NULL) {
printf("can't open file!\n");
exit(0);
}
for (i=0; i<num; i++) {
tmp_i = rand() & 0x0000ffff;
tmp_f = tmp_i / 65536.0;
tmp_p = &tmp_f;
s = (*tmp_p & 0x80000000) >> 31;
e = (((*tmp_p & 0x7f800000) >> 23) - 0x70) & 0x1f;
m = (*tmp_p & 0x7fffff) >> 13;
z = (m == 0) | (e == 0) | (e == 0x1f);
x = z ? 0 : (s << 15) | (e << 10) | m;
fprintf(fp, "%4x ", x);
tmp_i = rand() & 0x0000ffff;
tmp_f = tmp_i / 65536.0;
tmp_p = &tmp_f;
s = (*tmp_p & 0x80000000) >> 31;
e = (((*tmp_p & 0x7f800000) >> 23) - 0x70) & 0x1f;
m = (*tmp_p & 0x7fffff) >> 13;
z = (m == 0) | (e == 0) | (e == 0x1f);
x = z ? 0 : (s << 15) | (e << 10) | m;
fprintf(fp, "%4x\n", x);
}
}
ツール(期待値データ作成)[6]
- "in.hex"名のファイルを読み込み、単精度でDFTを計算。結果は半精度浮動小数点表記の複素数列を"exp.hex"名で出力。引数はポイント数の指数(指定なしの場合は10即ち1024)。
#include
#include
#include
float i2f(int i);
int f2i(float i);
main(int argc, char *argv[])
{
FILE *fpi;
FILE *fpo;
int tmp;
int radix;
int num;
float memr[4096], memi[4096];
int i, j;
float pai = 3.141592653589793;
float cr, ci;
float xr, xi;
float fr, fi;
if (argc > 1)
radix = atoi(argv[1]);
else
radix = 10;
num = 1 << radix;
if ((fpi = fopen("in.hex", "r")) == NULL) {
printf("can't open file!\n");
exit(0);
}
if ((fpo = fopen("exp.hex", "w")) == NULL) {
printf("can't open file!\n");
exit(0);
}
for (i=0; i<num; i++) {
fscanf(fpi, "%x", &tmp);
memr[i] = i2f(tmp);
fscanf(fpi, "%x", &tmp);
memi[i] = i2f(tmp);
}
for (i=0; i<num; i++) {
fr = 0;
fi = 0;
for (j=0; j<num; j++) {
cr = cos(-2 * pai * i / num * j);
ci = sin(-2 * pai * i / num * j);
xr = memr[j];
xi = memi[j];
fr += cr * xr - ci * xi;
fi += cr * xi + ci * xr;
}
fprintf(fpo, "%04x %04x\n", f2i(fr), f2i(fi));
}
}
float i2f(int i)
{
int z;
int s16, s32;
int e16, e32;
int m16, m32;
int x;
float *y;
z = (i & 0x7fff) ? 0 : 1;
s16 = i >> 15;
e16 = ((i & 0x7c00) >> 10);
m16 = i & 0x3ff;
s32 = s16;
e32 = z ? 0 : (e16 + 0x70) & 0xff;
m32 = z ? 0 : (m16 << 13);
x = (s32 << 31) | (e32 << 23) | m32;
y = &x;
return (*y);
}
int f2i(float i)
{
int *o;
int s, e, m;
int z, x;
o = &i;
s = (*o & 0x80000000) >> 31;
e = (((*o & 0x7f800000) >> 23) - 0x70) & 0x1f;
m = (*o & 0x7fffff) >> 13;
z = (m == 0) | (e == 0) | (e == 0x1f);
x = z ? 0 : (s << 15) | (e << 10) | m;
return(x);
}
ツール(データ比較)[6]
- "outs.hex"名のファイルと"exps.hex"名のファイルを読み込み、信号ごとのSNRを表示。引数はポイント数の指数(指定なしの場合は10即ち1024)。
- 実数のSNRの値 (実数の期待値) 虚数のSNRの値(虚数の期待値)の順で表示。最後は最大SNRを表示。
#include
#include
#include
float i2f(int i);
main(int argc, char *argv[])
{
FILE *fpe;
FILE *fpo;
int tmp;
int radix;
int num;
int i, j;
float er, ei;
float or, oi;
float ar, ai;
float max = 0;
if (argc > 1)
radix = atoi(argv[1]);
else
radix = 10;
num = 1 << radix;
if ((fpe = fopen("exps.hex", "r")) == NULL) {
printf("can't open file!\n");
exit(0);
}
if ((fpo = fopen("outs.hex", "r")) == NULL) {
printf("can't open file!\n");
exit(0);
}
for (i=0; i<num; i++) {
fscanf(fpe, "%x", &tmp);
er = i2f(tmp);
fscanf(fpe, "%x", &tmp);
ei = i2f(tmp);
fscanf(fpo, "%x", &tmp);
or = i2f(tmp);
fscanf(fpo, "%x", &tmp);
oi = i2f(tmp);
ar = fabs((er - or) / er);
ai = fabs((ei - oi) / ei);
if (ar > ai) {
if ((ar > max) & (er != 0.0))
max = ar;
}
else {
if ((ai > max) & (ei != 0.0))
max = ai;
}
printf("%14.10f(%14.10f) %14.10f(%14.10f)\n", ar, fabs(er), ai, fabs(ei));
}
printf("max: %14.10f\n", max);
}
float i2f(int i)
{
int z;
int s16, s32;
int e16, e32;
int m16, m32;
int x;
float *y;
z = (i & 0x7fff) ? 0 : 1;
s16 = i >> 15;
e16 = ((i & 0x7c00) >> 10);
m16 = i & 0x3ff;
s32 = s16;
e32 = z ? 0 : (e16 + 0x70) & 0xff;
m32 = z ? 0 : (m16 << 13);
x = (s32 << 31) | (e32 << 23) | m32;
y = &x;
return (*y);
}
テストモジュールサンプル(RTL)
/* **************************** MODULE PREAMBLE ********************************
Copyright (c) 2011, ArchiTek
This document constitutes confidential and proprietary information
of ArchiTek. All rights reserved.
*/
// ***************************** MODULE HEADER *********************************
`timescale 1ps / 1ps
module test;
// ************************ PARAMETER DECLARATIONS *****************************
parameter D = 1;
parameter DCLK = 5000;
parameter DCLK2 = DCLK/2;
// ************************** LOCAL DECLARATIONS *******************************
reg clk;
reg reset_n;
// ****************************** MODULE BODY **********************************
// -----------------------------------------------------------------------------
initial begin clk=0; forever begin #(DCLK2) clk=~clk; end end
// -----------------------------------------------------------------------------
initial begin reset_n=0; #D reset_n=0; #(DCLK*3.5) reset_n=1; end
// -----------------------------------------------------------------------------
top top_0 (
clk, reset_n
);
// ************************** FUNCTIONS and TASKS ******************************
endmodule
// *****************************************************************************
/* **************************** MODULE PREAMBLE ********************************
Copyright (c) 2011, ArchiTek
This document constitutes confidential and proprietary information
of ArchiTek. All rights reserved.
*/
// ***************************** MODULE HEADER *********************************
module top (
clk,
reset_n
);
// ***************************** I/O DECLARATIONS ******************************
input clk;
input reset_n;
// ************************* PARAMETER DECLARATIONS ****************************
parameter N = 1; // Number of starting
parameter R = 10; // Radix of FFT
// ************************** LOCAL DECLARATIONS *******************************
reg iVld;
wire iStall;
reg [7:0] iCnt;
wire [7:0] iCntD;
wire oVld;
reg oStall;
reg [7:0] oCnt;
wire mRdStrb;
wire [127:0] mRdData;
wire mWrStrb;
wire [127:0] mWrData;
reg [1:0] rst;
reg reset;
// ******************************** MODULE BODY ********************************
// -----------------------------------------------------------------------------
// Drive
always @(posedge clk)
if (reset)
iVld <= #1 1'b0;
else if (!iVld | !iStall)
iVld <= #1 iCntD < N;
always @(posedge clk)
if (reset)
iCnt <= #1 8'd0;
else
iCnt <= #1 iCntD;
always @(posedge clk)
if (reset)
oStall <= #1 1'b0;
else
oStall <= #1 $random;
always @(posedge clk)
if (reset)
oCnt <= #1 8'd0;
else
oCnt <= #1 oCnt + (oVld & !oStall);
always @(posedge clk)
if (oCnt >= N)
$finish;
assign iCntD = iCnt + (iVld & !iStall);
// -----------------------------------------------------------------------------
// Clock & Reset
always @(posedge clk or negedge reset_n)
if (!reset_n)
rst <= #1 2'b11;
else
rst <= #1 {rst[0], 1'b0};
always @(posedge clk or negedge reset_n)
if (!reset_n)
reset <= #1 1'b1;
else
reset <= #1 rst[1];
// -----------------------------------------------------------------------------
// FFT
fft #(10, R) fft_0 (
.iVld (iVld),
.iStall (iStall),
.oVld (oVld),
.oStall (oStall),
.mRdStrb (mRdStrb),
.mRdAck (1'b1),
.mRdData (mRdData),
.mWrStrb (mWrStrb),
.mWrAck (1'b1),
.mWrData (mWrData),
.reset (reset),
.clk (clk)
);
// -----------------------------------------------------------------------------
// Loader
loader loader_0 (
.strb (mRdStrb),
.data (mRdData),
.reset (reset),
.clk (clk)
);
// -----------------------------------------------------------------------------
// Store
store store_0 (
.strb (mWrStrb),
.data (mWrData),
.reset (reset),
.clk (clk)
);
// **************************** FUNCTIONS and TASKS ****************************
endmodule
// *****************************************************************************
/* **************************** MODULE PREAMBLE ********************************
Copyright (c) 2011, ArchiTek
This document constitutes confidential and proprietary information
of ArchiTek. All rights reserved.
*/
// ***************************** MODULE HEADER *********************************
module loader (
strb,
data,
reset,
clk
);
// ***************************** I/O DECLARATIONS ******************************
input strb;
output [127:0] data;
input reset;
input clk;
// ************************** LOCAL DECLARATIONS *******************************
reg [9:0] cnt;
reg [15:0] rom[0:8191];
// ******************************** MODULE BODY ********************************
always @(posedge clk)
if (reset)
cnt <= #1 10'd0;
else
cnt <= #1 cnt + strb;
assign data = {
rom[{cnt, 3'h7}],
rom[{cnt, 3'h6}],
rom[{cnt, 3'h5}],
rom[{cnt, 3'h4}],
rom[{cnt, 3'h3}],
rom[{cnt, 3'h2}],
rom[{cnt, 3'h1}],
rom[{cnt, 3'h0}]
};
// **************************** FUNCTIONS and TASKS ****************************
initial $readmemh("ins.hex", rom);
endmodule
// *****************************************************************************
/* **************************** MODULE PREAMBLE ********************************
Copyright (c) 2011, ArchiTek
This document constitutes confidential and proprietary information
of ArchiTek. All rights reserved.
*/
// ***************************** MODULE HEADER *********************************
module store (
strb,
data,
reset,
clk
);
// ***************************** I/O DECLARATIONS ******************************
input strb;
input [127:0] data;
input reset;
input clk;
// ************************** LOCAL DECLARATIONS *******************************
reg [9:0] cnt;
integer fp;
// ******************************** MODULE BODY ********************************
initial fp = $fopen("outs.hex");
always @(posedge clk)
if (reset)
cnt <= #1 10'd0;
else
cnt <= #1 cnt + strb;
always @(posedge clk)
if (strb) begin
$fdisplay(fp, "%4h %4h", data[15:0], data[31:16]);
$fdisplay(fp, "%4h %4h", data[47:32], data[63:48]);
$fdisplay(fp, "%4h %4h", data[79:64], data[95:80]);
$fdisplay(fp, "%4h %4h", data[111:96], data[127:112]);
end
// **************************** FUNCTIONS and TASKS ****************************
endmodule
// *****************************************************************************
回路デザイン > 設計例 [FFT] > テスト このページのTOP ▲