Verilog の文法解説

Sun, 23 Oct 2016 19:08:51 JST (418d)

添付したソースの解説

adder.v

verilog のモジュールは

	module モジュール名(ポート・リスト);
	input 	 入力信号名;
	output 	 出力信号名;
	wire 	 内部信号名;
	reg 	 内部信号名;
		
		回路機能定義
	
	endmodule

と記述します.ここで,ポート・リストとは,このモジュールへの入出力線名を書きます.

次に,信号の定義方法を以下に示します.

	 wire inLeft; 			 // 1本の wire 型信号
	 wire inLeft, inRight; 		 // 2つの異なる名前の wire 型信号
	 wire [3:0] inLeft; 		 // 4本からなるバス状の wire 型信号
	 wire [3:0] inLeft, inRight; 	 // 4本のからなるバス状の wire 型信号が3組
	 wire [0:3] inLeft; 		 // レンジは逆に書いても良いが,システム全体で統一する必要がある

ここで示した定義は, input, output, reg すべてに共通しています.

また,定義した信号の参照方法は以下のようになります.

	 wire [15:0] data; 	 // と定義された信号線に対して
	
	 data[0] 		 // data の最下位ビット1本の信号線
	 data[5:2] 		 // data のビット番号5~2の4本の信号線
	 data 			 // 16ビットの信号全体

以上のことから,4ビットの入力が2つ,5ビットの出力が1つとなる全加算器のモジュールの骨格は

	 module adder(
	 inLeft,
	 inRight,
	 result
	 );
	 input [3:0] inLeft, inRight;
	 output [4:0] result;
	
	 回路機能定義
	
	 endmodule

と書くことができます.ただし,入出力信号に関しては,adder.v 内で記述した通り,ポート・リストの記述段階で詳細な定義をすることができます.

次に,組み合わせ回路を記述する assign 文について説明します.組み合わせ回路とは,出力がその時点の入力によってのみ決まる論理回路のことであり,論理演算などを行うことができます.HDL では,カルノー図などを描いて回路図などを作成する作業を論理式で記述するだけで行うことができます.例えば,全加算器を verilog によって記述する場合は,adder.v の29行目のように

	 29	 assign result = inLeft + inRight;

と記述するだけで,入力を inLeft,inRight とし,出力が result となる全加算器が記述できます.この代入文のことを assign 文(継続代入文)と呼び,assign 文には以下の特徴があります.

  • assign 文の左辺に用いることのできる信号は,wire または,ポート・リストで宣言した input,output のみである.
  • assign 文は右辺の式に変化があったときに式の値が評価され,左辺の値を更新する.
  • assign 文の実行順序は,記述の順序に左右されない.

top_module.v

次に,トップモジュールの説明をします.トップモジュールとは,モジュールの階層が最も上のモジュールのことであり,C言語で言うところの main 関数のようなものです.記述は普通の module と変わらず,vivado を使っている場合には自動でトップモジュールを判定してくれます.ただし,トップモジュールは,FPGA の入出力と接続されているため,ポート・リストには,.xdc ファイルで設定した名前を用いる必要があります.今回は,入力としてクロック(sysclock,今回は組み合わせ回路なので使用していない)とスイッチ(sw),出力として LED(led)を用いており,ポート・リストは24行目~26行目のようになります.

29行目~33行目ではモジュールのインスタンス化を行っています.インスタンス化とは,モジュールのなかに別のモジュールを呼び出して組み込むことを言います.インスタンス化は,

	 adder adder0(sw[3:0], sw[7:4], led[5:0]);					(1)

のように,モジュール名(adder)を書き,その後ろにインスタンス名(adder0,何でも良いが普通はモジュール名+α)を書き,( )内には,モジュールに接続する信号のリストを規定します.信号のリストの書き方には,(1) に示した方法と次のような方法があります.

	 adder adder0( .inLeft(sw[7:4]), .inRight(sw[3:0]), .result(led[5:0]) );	(2)

(1) の方法では,元のモジュールのポート・リストと順番を同じに書く必要がありますが,(2) の方法では,"."の後ろに元のモジュールの信号名を直接明記し,( )内に,接続する信号名を書けばよく,順番はそろえる必要がありません.

testbench.v

テストベンチは,シミュレーション用の verilog ファイルです.モジュールの記述と同様にテストベンチのモジュールを宣言しますが,ポート・リストは何も書きません.そして,シミュレーションした被テスト・モジュールをインスタンス化します.ここで,被テスト・モジュールへの入出力信号は input,output ではなく input は reg,output は wire で宣言します.

39~42行目や,61~73行目にある initial 文はシミュレーションの開始時に実行することを規定する文です.そして,シミュレーションの終了は $finish; によって指定します.initial 文内での時間の経過は # 後ろで指定します.例えば,#10 と書いた場合,10サイクル分の時間が経過します.initial 文内の代入では,<=(ノン・ブロッキング代入)を用いていますが,=(ブロッキング代入)を用いることもできます.両者の違いは,ブロッキング代入では記述の順番に代入を行っていきますが,ノン・ブロッキング代入では右辺の式を評価した後,複数あるノン・ブロッキング代入の左辺に,次のサイクルへ移る直前に一斉に値を代入します.これらの代入文の左辺には,reg 型の信号しか用いることができないので注意しましょう.

右辺の定数は次のように記述することができます.

	 (ビット長)'(s)(基数)(符号なし数値)

基数の前の s は,つけた場合符号あり,つけなかった場合符号なしになります.基数には,次のものがあります.

	  2 進数 … B または b
	  8 進数 … O または o
	 10 進数 … D または d
	 16 進数 … H または h

符号なし数値のところでは,数値の間に任意に "_" を挿入することができます.また,10進数に関しては,上記の方法以外に,通常の方法で記述することができますが,その場合には 32 ビット長の数値を意味することになります.16進数の記述において,10~15の値には A~F(a~f)を用います.

44~46行目にある always 文は

	 always #時間 begin
	 	処理
	 end

となっていますが,これは,#の後ろに書いたサイクルが経過する毎に処理を行うといったもので,シミュレーション専用の記述になっており,次に説明する always @() 文とは異なった用い方をします.

task 文はここでは解説しませんが,ここで用いているものは,指定したサイクル数を経過させるものなので,#時間 による記述と動作は変わりません.

vendingmachine.v

vendingmachine.v 内の43~88行目の always @() 文の説明をします.always @() 文は以下のように記述します.

	 always @(信号) begin
	 	 処理
	 end

上のように記述した場合,( ) 内の信号が変化する度に begin~end の処理を行います.また,以下の

	 posedge 信号
	 negedge 信号

を( ) 内に用いることもでき,posedge の場合は,信号のポジティブ・エッジで処理を行うことを表し,negedge では,ネガティブ・エッジで処理を行うことを表します.( ) 内で各信号は or でつなぐことができます.また,always @(*) とすることで,always 文内で参照している信号に変化があった場合に処理が実行されるようになります.

次に,always @()文内で用いることの多い if 文と case 文を説明します.まず,if文は以下のように記述します.

	 if(条件) begin
		 処理
	 end else if(条件) begin
		 処理
	 end else if(条件) begin
		 …
	 end else begin
		 処理
	 end

条件のところでは式のほかにも,信号を指定することができます.信号を指定した場合は,信号の値が非0のときに成立,0のときに不成立と判断されます.処理のところの代入では,assign 文ではなく,上で説明したブロッキング代入かノン・ブロッキング代入を用います.

case 文の構文は以下のようになります.

	 case(信号)
		 値1: begin
			 処理
		 end
		 値2: begin
			 処理
		 end
		 …
		 default: begin
			 処理
		 end
	 endcase

上のように記述することで信号の値によって処理を変えることができます.default では,値1,値2,…以外の場合に処理をします.

22~24行目にあるのは,define 文で,以下のように使います.

	 `define 名前 定数

こうすることで,定数に名前をつけて利用することができます.これは,C言語の #define と似ていますが,参照時は,名前の前に"`"をつける必要があります.ここで,"`"は,シングル・クォーテーションとは異なります.日本のキーボードならば,"shift + @"で打つことができます.

top_module2.v

top_module2.v は top_module.v とほとんど変わっていませが,入力として cpu_rstn を用いています.cpu_restn はリセットボタンを押したときに値が 0 となります.