VHDL/Verilogの検証
FPGA/ASICなどのディジタル回路設計者はVHDL/Verilogで回路記述をした後、テストを行うのにテストベンチを作成されていると思います。
スティミュラスと期待値はExcelなどで計算して、そのデータをReadするVHDL/Verilogのテストベンチを書いている人が多いんじゃないでしょうか。
アサーションも使いたいという人は、System VerilogやPSLなどを使っているかもしれませんね。
この記事は、コード生成や高位合成なんて信用出来ないので、VHDL/Verilog回路記述は手書きするけど、テストベンチだけ自動生成で楽したいという方向けの内容となっています。
テストベンチは重要
テストベンチは言うまでもなく非常に重要で、これが間違っていると何をテストしているのやらわからなくなってしまいます。これがバグっていると、回路記述のバグを見つけることもできません。データのタイミングやビット精度も含めて、間違いなく記述する必要があります。
Wilson research Groupによる調査だと、FPGA設計プロジェクトにおいて、おおよそ半分の工数が検証に使われているようなので、それにかかる手間暇は馬鹿にならないですね。
手書きだと絶対に間違う
言うまでもなく手書きでコードを書くと絶対に間違いが生じます。書き間違いをしたことが無いなんて完璧人間がいたら、拝みに行って銅像立ててあげたいです。
間違えないためには・・・最近ローコードやノーコード開発なんてのも流行っているようですが、そんなものはFPGA設計には無いと思うので、自動生成してしまいましょう。
テストベンチを自動生成する
MathWorksのMATLAB/SimulinkのオプションHDL Coderはそもそも、回路記述を自動生成するツールなんですが、テストベンチも自動生成できちゃいます。
ただ、このHDL Coder、テストベンチ生成しようとすると、回路記述まで生成しようとして面倒なことになるので、テストベンチ「だけ」生成するには、HDL Coderをちょっとダマして使ってあげます。
サンプルとして、フィルタのシミュレーションをするモデルを作成しました。
入力信号はSin波のスウィープ信号で、可視化は時間軸応答。
テストベンチを生成するために設計対象部分をサブシステム化します。
サブシステム化後のモデルで、設計対象のVHDL/Verilogファイル名と同じになるように、サブシステム名を変更します。ここではDUTとしています。
ここで作成したサブシステムのHDLブロックプロパティを [No HDL] つまり、HDL生成しない設定にします。
生成されたテストベンチ
生成されたテストベンチ(▲をクリックで展開)
// -------------------------------------------------------------
//
// File Name: hdlsrc\sample_testbench\DUT_tb.v
// Created: 2023-02-28 18:05:25
//
// Generated by MATLAB 9.13 and HDL Coder 4.0
//
//
// -- -------------------------------------------------------------
// -- Rate and Clocking Details
// -- -------------------------------------------------------------
// Model base rate: 2.26757e-05
// Target subsystem base rate: 2.26757e-05
//
// -------------------------------------------------------------
// -------------------------------------------------------------
//
// Module: DUT_tb
// Source Path:
// Hierarchy Level: 0
//
// -------------------------------------------------------------
`timescale 1 ns / 1 ns
module DUT_tb;
reg clk;
reg reset;
wire enb;
wire Out1_done; // ufix1
wire rdEnb;
wire Out1_done_enb; // ufix1
reg [15:0] Out1_addr; // ufix16
wire Out1_active; // ufix1
reg check1_done; // ufix1
wire snkDonen;
wire resetn;
wire tb_enb;
reg tb_enb_delay;
wire ce_out;
wire Out1_enb; // ufix1
wire Out1_lastAddr; // ufix1
reg [15:0] Data_Type_Conversion_out1_addr; // ufix16
wire Data_Type_Conversion_out1_active; // ufix1
wire Data_Type_Conversion_out1_enb; // ufix1
wire [15:0] Data_Type_Conversion_out1_addr_delay_1; // ufix16
reg signed [31:0] fp_In1; // sfix32
reg signed [23:0] rawData_In1; // sfix24_En22
reg signed [31:0] status_In1; // sfix32
reg signed [23:0] holdData_In1; // sfix24_En22
reg signed [23:0] In1_offset; // sfix24_En22
wire signed [23:0] In1; // sfix24_En22
wire signed [23:0] Out1; // sfix24_En21
wire [15:0] Out1_addr_delay_1; // ufix16
reg signed [31:0] fp_Out1_expected; // sfix32
reg signed [23:0] Out1_expected; // sfix24_En21
reg signed [31:0] status_Out1_expected; // sfix32
wire signed [23:0] Out1_ref; // sfix24_En21
reg Out1_testFailure; // ufix1
wire testFailure; // ufix1
assign Out1_done_enb = Out1_done & rdEnb;
assign Out1_active = Out1_addr != 16'b1010110001000100;
assign #2 enb = rdEnb;
assign snkDonen = ~ check1_done;
always
begin : clk_gen
clk <= 1'b1;
# (5);
clk <= 1'b0;
# (5);
if (check1_done == 1'b1) begin
clk <= 1'b1;
# (5);
clk <= 1'b0;
# (5);
$stop;
end
end
initial
begin : reset_gen
reset <= 1'b1;
# (20);
@ (posedge clk)
# (2);
reset <= 1'b0;
end
assign resetn = ~ reset;
assign tb_enb = resetn & snkDonen;
// Delay inside enable generation: register depth 1
always @(posedge clk or posedge reset)
begin : u_enable_delay
if (reset) begin
tb_enb_delay <= 0;
end
else begin
tb_enb_delay <= tb_enb;
end
end
assign rdEnb = (check1_done == 1'b0 ? tb_enb_delay :
1'b0);
assign ce_out = enb & (rdEnb & tb_enb_delay);
assign Out1_enb = ce_out & Out1_active;
// Count limited, Unsigned Counter
// initial value = 0
// step value = 1
// count to value = 44100
always @(posedge clk or posedge reset)
begin : c_2_process
if (reset == 1'b1) begin
Out1_addr <= 16'b0000000000000000;
end
else begin
if (Out1_enb) begin
if (Out1_addr >= 16'b1010110001000100) begin
Out1_addr <= 16'b0000000000000000;
end
else begin
Out1_addr <= Out1_addr + 16'b0000000000000001;
end
end
end
end
assign Out1_lastAddr = Out1_addr >= 16'b1010110001000100;
assign Out1_done = Out1_lastAddr & resetn;
// Delay to allow last sim cycle to complete
always @(posedge clk or posedge reset)
begin : checkDone_1
if (reset) begin
check1_done <= 0;
end
else begin
if (Out1_done_enb) begin
check1_done <= Out1_done;
end
end
end
assign Data_Type_Conversion_out1_active = Data_Type_Conversion_out1_addr != 16'b1010110001000100;
assign Data_Type_Conversion_out1_enb = Data_Type_Conversion_out1_active & (rdEnb & tb_enb_delay);
// Count limited, Unsigned Counter
// initial value = 0
// step value = 1
// count to value = 44100
always @(posedge clk or posedge reset)
begin : DataTypeConversion_process
if (reset == 1'b1) begin
Data_Type_Conversion_out1_addr <= 16'b0000000000000000;
end
else begin
if (Data_Type_Conversion_out1_enb) begin
if (Data_Type_Conversion_out1_addr >= 16'b1010110001000100) begin
Data_Type_Conversion_out1_addr <= 16'b0000000000000000;
end
else begin
Data_Type_Conversion_out1_addr <= Data_Type_Conversion_out1_addr + 16'b0000000000000001;
end
end
end
end
assign #1 Data_Type_Conversion_out1_addr_delay_1 = Data_Type_Conversion_out1_addr;
// Data source for In1
initial
begin : In1_fileread
fp_In1 = $fopen("In1.dat", "r");
status_In1 = $rewind(fp_In1);
end
always @(Data_Type_Conversion_out1_addr_delay_1, rdEnb, tb_enb_delay)
begin
if (tb_enb_delay == 0) begin
rawData_In1 <= 24'bx;
end
else if (rdEnb == 1) begin
status_In1 = $fscanf(fp_In1, "%h", rawData_In1);
end
end
// holdData reg for Data_Type_Conversion_out1
always @(posedge clk or posedge reset)
begin : stimuli_Data_Type_Conversion_out1
if (reset) begin
holdData_In1 <= 24'bx;
end
else begin
holdData_In1 <= rawData_In1;
end
end
always @(rawData_In1 or rdEnb)
begin : stimuli_Data_Type_Conversion_out1_1
if (rdEnb == 1'b0) begin
In1_offset <= holdData_In1;
end
else begin
In1_offset <= rawData_In1;
end
end
assign #2 In1 = In1_offset;
DUT u_DUT (.In1(In1), // sfix24_En22
.Out1(Out1) // sfix24_En21
);
assign #1 Out1_addr_delay_1 = Out1_addr;
// Data source for Out1_expected
initial
begin : Out1_expected_fileread
fp_Out1_expected = $fopen("Out1_expected.dat", "r");
status_Out1_expected = $rewind(fp_Out1_expected);
end
always @(Out1_addr_delay_1, rdEnb, tb_enb_delay)
begin
if (tb_enb_delay == 0) begin
Out1_expected <= 24'bx;
end
else if (rdEnb == 1) begin
status_Out1_expected = $fscanf(fp_Out1_expected, "%h", Out1_expected);
end
end
assign Out1_ref = Out1_expected;
always @(posedge clk or posedge reset)
begin : Out1_checker
if (reset == 1'b1) begin
Out1_testFailure <= 1'b0;
end
else begin
if (ce_out == 1'b1 && Out1 !== Out1_ref) begin
Out1_testFailure <= 1'b1;
$display("ERROR in Out1 at time %t : Expected '%h' Actual '%h'", $time, Out1_ref, Out1);
end
end
end
assign testFailure = Out1_testFailure;
always @(posedge clk)
begin : completed_msg
if (check1_done == 1'b1) begin
if (testFailure == 1'b0) begin
$display("**************TEST COMPLETED (PASSED)**************");
end
else begin
$display("**************TEST COMPLETED (FAILED)**************");
end
end
end
endmodule // DUT_tb
スティミュラスデータ(▲をクリックで展開)
400000
3ffe54
3ff948
3ff0cf
3fe4de
3fd567
3fc25f
3fabb9
3f916a
3f7367
3f51a3
3f2c13
3f02ad
3ed566
3ea432
省略
期待値データ(▲をクリックで展開)
00263a
011270
0409e3
0a5d72
13fa13
1e548d
254b8f
261652
21d7a9
1d0c9a
1bd52a
1e7b48
21ae93
222a99
1feb70
1db5c9
1dcf26
1fab44
20eaeb
202fb5
省略
所要時間は記事書くのも含めて30分ぐらい
回路性能に影響しないテストベンチなんてシコシコ書いてないで自動生成して楽しましょう。