Verilogのテストベンチでfor~nextを使う

Verilogのテストベンチでfor~nextを使う

 本ブログに来ていただく方は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を積極的に使用するようにします。