Verilogのテストベンチでfor~nextを使う
- 2020.09.13
- 回路設計

本ブログに来ていただく方はVerilogを扱う方が多いようです。そして、テストベンチの項目を読んでいただいているようです。
テストするのはPWMでLEDを駆動する回路です。詳細は「FPGAでLEDの調光」で解説していますので、今回はVerilogのテストベンチ構築を解説いたします。
テストするVerilogファイルは以下のような階層構造で作っています。トップファイルがLED_PWM.vです。その下位層にSawwave.vとperiod.vがあります。

これらのソースコードを示します。
// TITLE: "LED_pwm.v" // MODULE NAME: // PROJECT CODE: // AUTHOR: (****@nakaharagiken.com) // CREATION DATE: 2020.7.16 // SOURCE: // LICENSE: Copyright (c) 2020 nakaharagiken All rights reserved. // DESCRIPTION: // NOTES TO USER: LEDをPWMで調光 // SOURCE CONTROL: // REVISION HISTORY: v0.1 2020.7.16 First edition // // // // 入力クロック 10MHz = 100nsec // // // `timescale 1ns/1ps module LED_pwm ( clock, // in system clock 10MHz nreset, // in asynchronous reset ____reset___|~~~normal~~~ ledonoff, // in LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ dimmerdat, // in [7:0] 明るさデータ dark = 00h bright = ffh dimmerset, // in 明るさデータセット ________|~|______________ LEDcathode // out LED 駆動信号 ); input clock; // in system clock 10MHz input nreset; // in asynchronous reset ____reset___|~~~normal~~~ input ledonoff; // in LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ input [7:0] dimmerdat; // in [7:0] 明るさデータ dark = 00h bright = ffh input dimmerset; // in 明るさデータセット ________|~|______________ output LEDcathode; // out LED 駆動信号 /////////////////////////////////////////////////////////////// // SIGNALS /////////////////////////////////////////////////////////////// reg [7:0] rpwmreg; // 明るさレジスタ reg rLEDcathode; // 出力用レジスタ wire [7:0] wSawwave; // ノコギリ波 wire wcomparator; // コンパレータ出力 wire wtimerout; // カウントパルス parameter [7:0] cINIT = 8'hff; // LED明るさ初期値 ////////////////////////////////////////// // カウントパルス // v0.0 51.2usecカウンタ ////////////////////////////////////////// period inst_period ( .clock (clock), // in system clock 10MHz .nreset (nreset), // in asynchronous reset ____reset___|~~~normal~~~ .timerout (wtimerout) // out タイマー信号 _______|~|_______________ ); ////////////////////////////////////////// // ノコギリ波生成 ////////////////////////////////////////// Sawwave inst_Sawwave ( .clock (clock), // in system clock 10MHz .nreset (nreset), // in asynchronous reset ____reset___|~~~normal~~~ .enable (wtimerout), // in イネーブル _______|~|_______________ .Sawwave (wSawwave) // out [7:0] ノコギリ波 ); ////////////////////////////////////////// // 明るさレジスタ ////////////////////////////////////////// always @ (posedge clock, negedge nreset) begin if (!nreset) begin rpwmreg <= cINIT; end else begin if (dimmerset == 1'b1) begin rpwmreg <= dimmerdat; end end end ////////////////////////////////////////// // コンパレータ ////////////////////////////////////////// always @ (posedge clock, negedge nreset) begin if (!nreset) begin rLEDcathode <= 1'b1; // 初期値:消灯 end else begin if (ledonoff == 1'b0) begin rLEDcathode <= 1'b1; // LED OFF end else begin if (wSawwave < rpwmreg) begin // コンパレータ rLEDcathode <= 1'b0; // LED ON end else begin rLEDcathode <= 1'b1; // LED OFF end end end end assign LEDcathode = rLEDcathode; endmodule
// TITLE: "Sawwave.v" // MODULE NAME: // PROJECT CODE: // AUTHOR: (****@nakaharagiken.com) // CREATION DATE: 2020.7.16 // SOURCE: // LICENSE: Copyright (c) 2020 nakaharagiken All rights reserved. // DESCRIPTION: // NOTES TO USER: ノコギリ波生成 // SOURCE CONTROL: // REVISION HISTORY: v0.1 2020.7.16 First edition // // // // 入力クロック 10MHz = 100nsec // // // `timescale 1ns/1ps module Sawwave ( clock, // in system clock 10MHz nreset, // in asynchronous reset ____reset___|~~~normal~~~ enable, // in イネーブル _______|~|_______________ Sawwave // out [7:0] ノコギリ波 ); input clock; // in system clock 10MHz input nreset; // in asynchronous reset ____reset___|~~~normal~~~ input enable; // in イネーブル _______|~|_______________ output [7:0] Sawwave; // out [7:0] ノコギリ波 /////////////////////////////////////////////////////////////// // SIGNALS /////////////////////////////////////////////////////////////// reg [7:0] rcounter; // カウンター ////////////////////////////////////////// // カウンタ ////////////////////////////////////////// always @ (posedge clock, negedge nreset) begin if (!nreset) begin rcounter <= 8'h00; end else begin if (enable == 1'b1) begin rcounter <= rcounter + 1'b1; end end end assign Sawwave = rcounter; endmodule
// TITLE: "period.v" // MODULE NAME: // PROJECT CODE: // AUTHOR: (****@nakaharagiken.com) // CREATION DATE: 2020.7.16 // SOURCE: // LICENSE: Copyright (c) 2020 nakaharagiken All rights reserved. // DESCRIPTION: // NOTES TO USER: 入力10MHzでxxusecのカウントパルス生成 // SOURCE CONTROL: // REVISION HISTORY: v0.1 2020.7.16 First edition // // // // 入力クロック 10MHz = 100nsec // // // 100nsec(10MHz) × 200(hex) = 100nsec × 512(dec) = 51.2usec // v0.0 51.2usecカウンタ // `timescale 1ns/1ps module period ( clock, // in system clock 10MHz nreset, // in asynchronous reset ____reset___|~~~normal~~~ timerout // out タイマー信号 _______|~|_______________ ); input clock; // in system clock 10MHz input nreset; // in asynchronous reset ____reset___|~~~normal~~~ output timerout; // out タイマー信号 _______|~|_______________ /////////////////////////////////////////////////////////////// // SIGNALS /////////////////////////////////////////////////////////////// reg [15:0] rcounter; // カウンター reg rPls_out; // 出力用レジスタ //parameter [15:0] cCOUNT = 16'h0200; // 100nsec(10MHz) × 200(hex) = 100nsec × 512(dec) = 51.2usec // 16'h0200 = 0000001000000000 (bin) // 1111110000000000 // 5432109876543210 // ////////////////////////////////////////// // カウンタ ////////////////////////////////////////// always @ (posedge clock, negedge nreset) begin if (!nreset) begin rcounter <= 16'h0000; rPls_out <= 1'b0; end else begin if (rcounter[9] == 1'b1) begin rPls_out <= 1'b1; rcounter <= 0; // reset counter end else begin rPls_out <= 1'b0; rcounter <= rcounter + 1; end end end assign timerout = rPls_out; endmodule
Verilogのモジュール呼び出しは、上記のLED_PWM.vとSawwave.vやperiod.vの関係を見るとわかると思いますが、私は機械的にインスタンスに記載しています。並べて書いてみるとわかりやすいと思います。

テストベンチの記載も同じ事です。
このLED_PWM.vをテストする為のテストベンチはさらに親の立場になりますので、以下のような階層構造になります。

親はTB_LED_pwm.vというファイル名にしてあります。VHDLでもVerilogでもテストベンチのファイルだという事を明確に区別するために、ファイル名でテストベンチの区別をするのが便利です。
これで階層が3段になった訳ですが、下位階層は勝手につながってくれます。これはテストベンチだけではなく、通常の階層構造でも同じ事です。
// TITLE: "TB_LED_pwm.v" // MODULE NAME: TB_LED_pwm // PROJECT CODE: // AUTHOR: (****@nakaharagiken.com) // CREATION DATE: 2020.7.16 // SOURCE: // LICENSE: Copyright (c) 2020 nakaharagiken All rights reserved. // DESCRIPTION: LED_pwm.vのテストベンチ // NOTES TO USER: // SOURCE CONTROL: // REVISION HISTORY: v0.1 2020.7.16 First edition // v0.2 2020.9.13 i_ledonoff制御分けた // // `timescale 1ns/100ps module TB_LED_pwm; parameter SYSCLK_PERIOD = 100; // 10MHz reg i_clock; // system clock reg i_nreset; // asynchronous reset ____reset___|~~~normal~~~ reg i_ledonoff; // LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ reg [7:0] i_dimmerdat; // 明るさデータ dark = 00h bright = ffh reg i_dimmerset; // 明るさデータセット ________|~|______________ wire o_output; // LED 駆動信号 // for文内で使用する変数の宣言 integer i; ////////////////////////////////////////////////////////////////////// // Clock Driver ////////////////////////////////////////////////////////////////////// always @(i_clock) #(SYSCLK_PERIOD / 2.0) i_clock <= !i_clock; ////////////////////////////////////////////////////////////////////// // Main ////////////////////////////////////////////////////////////////////// initial begin i_clock <= 0; i_nreset <= 0; // 初期値 i_ledonoff <= 0; // LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ i_dimmerdat <= 0; // 明るさデータ dark = 00h bright = ffh i_dimmerset <= 0; // 明るさデータセット ________|~|______________ repeat(50) @(posedge i_clock); i_nreset <= 1; repeat(100) @(posedge i_clock); i_ledonoff <= 1; // LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ repeat(100) @(posedge i_clock); i_ledonoff <= 1; // LED 制御ON for (i = 0; i <= 255; i = i+10) begin i_dimmerdat <= i; // 明るさデータ dark = 00h bright = ffh repeat(10) @(posedge i_clock); i_dimmerset <= 1; // 明るさデータセット ________|~|______________ repeat(1) @(posedge i_clock); i_dimmerset <= 0; // 明るさデータセット ________|~|______________ repeat(500000) @(posedge i_clock); end repeat(500000) @(posedge i_clock); for (i = 0; i <= 255; i = i+10) begin i_dimmerdat <= i; // 明るさデータ dark = 00h bright = ffh i_ledonoff <= 1; // LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ repeat(10) @(posedge i_clock); i_dimmerset <= 1; // 明るさデータセット ________|~|______________ repeat(1) @(posedge i_clock); i_dimmerset <= 0; // 明るさデータセット ________|~|______________ repeat(400000) @(posedge i_clock); i_ledonoff <= 0; // LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ repeat(100000) @(posedge i_clock); end end LED_pwm inst_LED_pwm ( .clock (i_clock), // in system clock 10MHz .nreset (i_nreset), // in asynchronous reset ____reset___|~~~normal~~~ .ledonoff (i_ledonoff), // in LED ON(1) OFF(0) ___off______|~~~on~~~~~~~ .dimmerdat (i_dimmerdat), // in [7:0] 明るさデータ dark = 00h bright = ffh .dimmerset (i_dimmerset), // in 明るさデータセット ________|~|______________ .LEDcathode (o_output) // out LED 駆動信号 ); endmodule
テストベンチファイルの15行目はテストベンチ特有の記述です。
`timescale 1ns/100ps
テストベンチの時間単位と精度を示しています。FPGAで扱うのは数MHz~数百MHzなので単位としては1nsec程度だと思います。精度も100psecとしていますが、これよりも細かくても良いです。1psecでも最近のパソコンであれば全く問題なくシミュレートできます。
17行目がテストベンチのモジュール宣言ですが、通常であれば入力信号と出力信号があるのでその宣言があります。しかし、テストベンチには入出力信号がありませんので、モジュール名の宣言だけを行います。ここも並べて比較してみましょう。

19行目にはparameter宣言でクロックの周期を設定しています。これは個人の趣味なので無くても問題ありません。次のようにクロックの待ち時間にSYSCLK_PERIODを使用しています。
35行目がクロックの宣言です。クロックのように常にHi/Lowを繰り返すような信号はalwaysで記述します。
always @(i_clock)
#(SYSCLK_PERIOD / 2.0) i_clock <= !i_clock;
というように1行の記述にしていますが、2行以上になる場合にはbegin/endを使用します。
always begin
i_clock = 1’b1;
#(SYSCLK_PERIOD / 2.0);
i_clock = 1’b0;
#(SYSCLK_PERIOD / 2.0);
end
上記は同じ動作になります。クロックの他に定期的に繰り返す信号はalwaysで記載します。
22行目~27行目はレジスタとワイヤーの宣言ですが、ここは通常のモジュールと違います。宣言しているのはregとwireです。ここをテストベンチの場合にはreg=input wire =outputを置き換えて考えてください。
つまりテストするモジュールへの入力信号はregで宣言します。出力信号はwireで宣言します。ちょっとわかりにくいかも知れませんので、実際のシミュレータの画像と見比べると理解できると思います。
宣言は以下のようになっています。
reg i_clock;
reg i_nreset;
reg i_ledonoff;
reg [7:0] i_dimmerdat;
reg i_dimmerset;
wire o_output;
これに対し、テストベンチの出力波形は以下のようになります。

このようにテストベンチでregとwireで宣言した信号がテストベンチ波形として表示・解析されます。ここで、これらの信号が入力も出力も区別されずに表示されていることに気が付くでしょうか? 今回はModelSimを使用しています。
ですがら、22行目~27行目で宣言する際にregの頭にi_を付け、wireの頭にo_を付けています。そうすることで、上の波形のように入力と出力がわかるようにしています。
30行目がタイトルにもあるfor~nextで使う変数宣言です。for~next構文はテストベンチ以外でも使うことができます。ただし、for~nextの構文はプログラミンうとは少し違います。その違いを理解しておかなくてはなりません。
その良い例として、テストベンチで使うfor~nextがあると思います。
49行目からinitial構文で記載していますが、これは定期的ではなく非定期の信号を記載します。大抵はテストベンチの本体部になります。
initial分の場合、初期値を記載しますので、52行目から初期値を与えています。この初期値が無くてもシミュレータは動作します。その場合、未定でシミュレートが進みます。
58行目で初期値を入力した後の時間待ちが入っています。 repeat(50)で@(posedge i_clock)を繰り返していますが、これはVHDLに似せて記述しているのでこのような記述になっています。一般的にはwait for (SYSCLK_PERIOD * 50);などのように記載するのですが、クロック同期で何クロックの待ち時間なのかが分かりにくいので、クロックの数で記載するようにしています。
66行目からふぁfor~nextの構文です。VHDLやVerilogにおけるfor~nextは変数の数だけ構文記述を簡素化しています。iを変数とした値を条件分岐に使用しているのえはありません。
for (i = 0; i <= 255; i = i+10) begin は255まで10ステップでiをインクリメントしています。Verilogの場合にはiをレジスタに代入するのは簡単です。VHDLの場合には型変換が必要になります。
i_dimmerdat <= i; でfor (i = 0; i <= 255; i = i+10)のループをi_dimmerdatに代入しています。LEDの明るさ用設定レジスタを設定しています。
シミュレーション結果を以下に示します。

i_dimmerdatが変化するとo_outputのデューティーが変化しているのがわかります。
このようにしてfor~nextを使う事によってi_dimmerdatの変化を簡素化して記述できます。テストベンチではなるべく多くの入力を変化させて実証することが重要なのでfor~nextを積極的に使用するようにします。
-
前の記事
FPGAのピン配置を決める手順 2020.09.12
-
次の記事
レジスタって何? 2020.09.19

コメントを書く