Verilog 自習帳>記述のコツ

目次


always 中での代入(しょうもなくてハマったこと、その1)

△目次

なんせ、初心者が独学で勉強しようとすると、とんでもなく、しょうもないことでハマります。 みなさんには、何をくだらんこと書いてるか、と思われるでしょうが、とりあえず、晒しときます。

always の中で出力に代入する。

例えば、カウンタなどを書くとき、always の中で、出力線に代入したいですよね。最初は次のように書いてました。

module hoge(CLK, RSTn, out);
	input CLK, RSTn;
	output	[7:0]out;
	always @(posedge CLK or negedge RSTn) begin
		if (!RSTn)
			out<=0;
		else
			out <= out + 1;
	end
endmodule  // hoge

当然、out には <=で代入できない、って怒られます。次にしたのは、こんな感じです。

module hoge(CLK, RSTn, out);
	input CLK, RSTn;
	output	[7:0]out;
	reg [7:0]outreg;
	always @(posedge CLK or negedge RSTn) begin
		if (!RSTn)
			outreg<=0;
		else
			outreg <= outreg + 1;
	end
	assign out = outreg;
endmodule  // hoge

あまりにも冗長でムダですよね。今は次のように書いています。 なんか、参考書を読み飛ばしていたみたいですね。しかし、間違いを指摘 してもらえるのは、コンパイラのワケわからんエラーメッセージのみなのです。正解は、

IO線を、同じ名前で、reg として再宣言するのでした。
module hoge(CLK, RSTn, out);
	input CLK, RSTn;
	output	[7:0]out;
	reg [7:0]out;
	always @(posedge CLK or negedge RSTn) begin
		if (!RSTn)
			out<=0;
		else
			out <= out + 1;
	end
endmodule  // hoge
△目次

CPLDでの同期回路

△目次

CPLDに同期回路を実装するときに、回路記述に次の制約を設けるのが良いようです。

always のセンシティブリスト(@ の中身)は、posedge clk or negedge reset

CPLDには、クロックピン(GCLK)とリセットピン(RSTn)が出ていて、 always の posedge clk と negedge reset に優先的に割り当てられ、 クロックに同期した回路、負論理の非同期リセットが作りやすいようになっています。 逆に、それ以外の回路は作りにくいです。

よって、モジュールのパターンとしては、次のようになります。なんとかこのパターンに合わせこみます。

module hoge(CLK, RSTn, ...);
	input CLK, RSTn;
	reg register;
	always @(posedge CLK or negedge RSTn) begin
		if (!RSTn)
			register<=0;	// 全てレジスタのリセット
		else begin
			// クロックが入ったときの動作
		end
	end
endmodule  // hoge

では、次のようなことをしたいときはどうするのでしょう?

RSTn ピン以外のピンでリセットしたいとき

全体のリセットではなく、外部からのシグナルで特定のレジスタを リセットしたいときはどうするのでしょう?外部のリセットピンを 正論理で、RST_EXT としたときに、

	always @(posedge RST_EXT)
とすると、RSTn に割り当てられない、とコンパイラに 文句を言われることがあります。そんな時は、同期リセットにしてしまいましょう(同期リセットで良いなら)。
	always @(posedge CLK) begin
		if (RST_EXT)
			// リセット
		else
			// それ以外
	end

分周クロックを使いたいとき

シリアル信号のサンプリングなど、しかも速度を可変したいとき、 チップの GCLK(Global Clock)とは異なるスピードで動作させたいとき、 always @(posedge local_clk)としてしまいがちですが、 あくまでも、always @(posedge GCLK)したいと思います。

そんな時は、ローカルサンプリングクロック local_clk は、 レジスタのイネーブルに突っ込んでしまいたいと思います。

しかし、これでは、local_clk の幅が、GCLK の2倍以上あるときは、動作しない気がする。デューティ比最小のディバイダを使かいましょう。

	always @(posedge GCLK or negedge nRST) begin
		if (!nRST)
			count <= 0;
		else if (local_clk)
			if (count == 'd76)
				count <= 0;
			else
				count <= count + 1;
	end	// always
△目次

分周器はダウンカウンタがお得?

△目次

分周器が必要になることがよくあるのですが、アップカウンタとダウンカウンタとどっちが得なんでしょうかね。

アップカウンタダウンカウンタ
Verilog 記述
reg [7:0]count;
always @(posedge CLK) begin
	if (count == 'd100)
		count <= 0;
	else
		count <= count + 1;
end
always @(posedge CLK) begin
	if (count == 8'b0)
		count <= 'd100;
	else
		count <= count - 1;
	end
end
Xilinx WebPack ISE 5.2, XC9500 Fitting
Design Statistics
# IOs                              : 9

Macro Statistics :
# Xors                             : 7
#      1-bit xor2                  : 7

Cell Usage :
# BELS                             : 39
#      AND2                        : 11
#      AND3                        : 2
#      AND4                        : 2
#      AND5                        : 1
#      AND6                        : 1
#      AND7                        : 1
#      INV                         : 14
#      XOR2                        : 7
# FlipFlops/Latches                : 8
#      FD                          : 8
# IO Buffers                       : 9
#      IBUF                        : 1
#      OBUF                        : 8
Design Statistics
# IOs                              : 9

Macro Statistics :
# Xors                             : 7
#      1-bit xor2                  : 7

Cell Usage :
# BELS                             : 88
#      AND2                        : 11
#      AND8                        : 4
#      INV                         : 63
#      OR2                         : 3
#      XOR2                        : 7
# FlipFlops/Latches                : 8
#      FD                          : 8
# IO Buffers                       : 9
#      IBUF                        : 1
#      OBUF                        : 8

100 進カウンタの場合、ダウンカウンタは INV を大量に消費しているようです。直感的にはダウンカウンタの方が得に見えたのですが、実際合成してみると違うもんですね。

△目次

同期ラッチをかける

△目次

パラ-シリ変換なんかで、パラレルデータが準備できたら、ラッチをかけます。私は次のように書いてみました。トランスペアレントしてもよいなら(ツツヌケ時は処理しないようにしないといけない)、使える方法だと思います。

  1. ラッチトリガ(RE)を、いったん内部の信号(re_i)に変換します。
  2. re_i が L の時は、パラデータ(RD)は内部バッファ(rd_i)にツツヌケです。(トランスペアレントラッチ)
  3. RE が H になったら、RD を rd_i にラッチします。
  4. RE が L になっても、ラッチが開放しないように、内部の処理中の信号で、re_i を H (保持状態)にキープしておきます。
  5. re_i が、内部処理中フラグとしても兼用できてしまいます。

よくわからないうちは、ラッチトリガを、シフトレジスタでエッジサンプリングしたりなどしてしまいました。エッジサンプリングは、外部入力に対して行うものですよね。

module sender(GCLK, nRST, RD, RE, RBSY);
	input	GCLK, nRST;
	input	[7:0]RD;	// send data read from port
	input	RE;		// send data enable
	output	RBSY;		// data read busy
	reg	re_i;
	reg [7:0]rd_i;
	always @(posedge GCLK or negedge nRST) begin
		if (!re_i)			// …(3)
			rd_i  <= RD;		// …(2)
	end // always

	always @(posedge GCLK or negedge nRST) begin
		if (count > 7'b1001_000)	// 処理終了
			re_i <= 0;
		else				// 処理中
			re_i <= re_i | RE;	// …(1), (3), (4)
	end // always
	assign	RBSY = re_i;			// …(5)
endmodule	// sender
△目次

記述スタイル

△目次

私の使っているスタイルを紹介します。あんまり一般的ではないかもしれないですが、プログラマー出身の人なら、一部納得していただける部分もあると思います。

// モジュールの説明をコメントする。
module hogehoge(GCLK, nRST, in_1, in_2, out_1, out_2);
	input	GCLK, nRST;	// 外部インターフェイスになる信号は大文字、nXXX は負論理
	input	in_1;		// モジュール信号線は、原則 1行1宣言。ここに、信号の説明をコメントする。

	// clk_i		←代入漏れなどをおこさないようにalways の中で代入されるレジスタを先頭に書く
	always @(posedge GCLK or negedge nRST) begin // とにかく、posedge GCLK or negedge nRST)
		if (!nRST)			// 最初は、非同期リセット時の動作。
			clk_i <= 0;		// 全レジスタをリセットする
		else				// GCLK 時の動作
			clk_i <= {clk_i[0], clk};
	end // always(clk_i)	←どのalways の終わりかを書く(代入されるレジスタで識別)
endmodule  // hogehoge  ←どの module の終わりかを明示する
△目次

深い if はダメ

△目次

inout の使い方

△目次

メモリやバスアクセスなど、双方向入出力では、inout を使います。

module hogehoge(BUS, rw);
input rw;		// write = 1, read = 0;
inout [15:0]BUS;
reg   [15:0]BUS_i;
assign BUS = (rw)?BUS_i:16'bz;
endmodule
△目次

大分周デバイダ

△目次

AD変換ボードのサンプリングクロックみたいに、10MHzの基準クロックから、10MHz, 5MHz, 2MHz, 1MHz,,,,,5Hz, 2Hz, 1Hzのサンプリングクロックを作りたいときはどうすれば良いでしょう?

案1:
TTL感覚で 10分周ごとに1つのモジュールを連結する
案2:
とにかく同期式だから、10MHzの基準クロックを一つのカウンタでぜーんぶ数える。

やってみました。

案1: 10分周モジュールを使う

概算回路規模:
1つの10分周に 5個のレジスタ→1Hzまで分周するのに、7*5=35個のレジスタ
動作速度:
1段ごとに1クロック遅れるけどそれを気にしなければ。

案1: 一つのカウンタで

概算回路規模:
log2(10^7)=24ビットのカウンタでOK
動作速度:
遅延24段でめっちゃ苦しい。
△目次

ビット幅は省略しない

△目次

Verilog では、定数のことを、ビット幅を指定して、3'b110のように書きます。これも、文法リファレンスに載っていることですが、ビット幅を省略して'b110のように書くこともでき、たいていの場合動いてしまいます。

ところが、この意味は、32ビット幅なんですね。比較したり、レジスタにロードしたりする場合 はうまく動いても、シフトレジスタに突っ込むときに、ビット幅を忘れていて、ハマッたことがあるのでメモしておきます。

	// 正解
	always @(posedge OCLK)
		if (CLK_M)
			CLK_MPX[7:0] <= 8'b0000_0001;
		else
			CLK_MPX[7:0] <= {CLK_MPX[6:0], 1'b0};
	// ダメ
	always @(posedge OCLK)
		if (CLK_M)
			CLK_MPX[7:0] <= 8'b0000_0001;
		else
			CLK_MPX[7:0] <= {CLK_MPX[6:0], 0};
△目次

エッジ検出

△目次

読者より、フィードバックフォームでリクエストを頂きました。

通常の非同期回路だと、フリップフロップのクロックがエッジ検出になっているの ですが、同期式回路だと、フリップフロップのクロックは、システムクロックになっ ていて、入力の立ち上がり・立ち下がりに反応させることができません。私も最初 は悩みました。

答えは、何段かのシフトレジスタで入力をサンプリングしてエッジを検出する、で す。

2007/04/02 誤記修正。ご指摘感謝。

module counter(CLK, RST, A, COUNT);
    input CLK;
    input RST;
    input A;
    output [7:0] COUNT;

    reg [1:0] reg_a;
    reg [7:0] COUNT;

    always @(posedge CLK) begin
        if (RST) begin      // 同期リセット
	    reg_a <= 2'b00;
	end else begin
	    reg_a <= {reg_a[0], A};   // 入力 A をシフトレジスタに入れる
	end
    end

    always @(posedge CLK) begin
        if (RST) begin      // 同期リセット
	    COUNT <= 8'b0;
	end else begin
	    if (reg_a == 2'b01) begin   // 立ち上がり!!!
	        COUNT <= COUNT + 1;
	    end
	end
    end
endmodule

この方法は、エンコーダの A/B 相判別、チャタリング/ノイズフィルタ、 RS-232C の入力、などにも応用できます。

△目次

ムーア型マシンにすべし

△目次

レジスタと組み合わせ論理を使ったステートマシンでは、定番の書き方があります。

ここで、モジュールから値を出力するときに、(*1)のようなレジスタの出力がモ ジュールの出力になっているものと、(*2)のようにレジスタの出力の後ろに組み合 わせ論理をつけた出力の2通りの方法を取ることができます。(*3)のように、入力 もデコードして出力を生成するのが、ミーリィマシンといいますがハザードやメタ ステーブルの問題が出やすく、ほとんど使われません。

レジスタ出力
組み合わせ論理出力
ミーリィマシン
module hoge(CLK, nRESET, IN, OUT1, OUT2);
    input CLK;
    input RESET;
    input IN;
    output OUT1;
    output OUT2;

    reg OUT1;
    reg [1:0] reg_state;

    always @(posedge CLK or negedge nRESET) begin
        if (!nRESET) begin     // 非同期リセット(Lレベル)
	    OUT2 <= 0;
	end else begin
	    case(reg_state)
	        4'b00: begin
		    reg_state <= 4'b01;
		    end;
	        4'b01: begin
		    reg_state <= 4'b11;
		    OUT1 <= 1'b1;   // (*1) レジスタから直接出力している
		    end;
	        4'b11: begin
		    reg_state <= 4'b10;
		    end;
	        4'b10: begin
		    reg_state <= 4'b00;
		    end;
		default: begin   // デフォルトを付けとく。or full case
		    reg_state <= 4'b00;
		    end;
	    endcase
	end
    end

    assign OUT2 = reg_state[0] & OUT1;
           // (*2) レジスタの出力の後ろに組み合わせ論理を通して出力している
    assign OUT3 = OUT1 & IN;
           // (*3) 入力がレジスタを通らず、組み合わせ論理を通って出力
endmodule

私としては、FPGAやCPLDに回路を実装するというときは、(*1)のレジスタ出力型を おすすめします。よっぽど気合いをいれる場合は(*2)を使うかも。

△目次

順序回路と組み合わせ回路とブロッキング代入とノンブロッキング代入

△目次

フィードバックフォームより質問を受けました。私のやりかたを紹介します。

回路には大きく分けて、順序回路(レジスタを含み、状態を有するもの)と組み合わせ論理があります。順序回路は、クロックにしたがって動作します。クロック以外の信号が変わっても動作しないので、always@(posedge CLK) が基本です。非同期リセットをするときは always@(posedge CLK and negedge nRST) です。組み合わせ回路は、全ての入力をセンシティブリストに列挙した always文を使っても書けますが、私は、組み合わせ回路=function 文や assign 文で書きます。always=順序回路、assign=組み合わせ回路、のほうがわかりやすいと思っているし、組み合わせ回路を作るつもりで、always のセンシティブリストに入力を列挙しわすれたりしたりするからです。

代入は、always の中での代入は、クロック以外で変化してもらっては困る場合、reg に代入します。その場合は、ノンブロッキング代入 <= を使います。ブロックしてもらっては、クロックで一斉に変化する、というのが順序回路だからです。

順序回路の場合は、wire への assign になるので、ブロッキング代入 = を使います。a = b; c = a; という場合でも、信号伝搬の速度で、この順序に代入されるので、ブロッキング代入です。

順序回路組み合わせ回路
信号線regwire
構文always@(posedge CLK {or negedge nRST})assign a = b;
function b...
if 文、case 文
代入<==
△目次

雛型

△目次△目次


近藤靖浩