= ステートマシンの書き方 Verilog HDL編 = ステートマシンの書き方と、それには直接関係ないけれど知っておくと特になることのまとめ。 === !`default_nettye none === Verilog HDLは、宣言なしで登場した名前を1bit幅のwireとして勝手に解釈しますが、moduleの前に!`default_nettype noneを書いておけばそれを禁止できます。 代入の左辺のbit幅が足りず桁あふれするせいでまともに動かない、という厄介なバグを、コンパイルの段階で検出できるありがたいおまじないです。 なお、XilinxのIPコアのソースなどは!`default_nettypeがwireであることを前提に作られているものがあるので、endmoduleの後に!`default_nettype wireを 書いておくのも忘れないようにしましょう(Verilog HDLのプリプロセッサ制御文は、ほかのソースファイルの記述にまで影響を与えます)。 === 命名規則 === 一例として、ここに載せてあるソースの命名規則を以下に示します。 ||種類||命名||備考|| || 入力ポート || I_~ ||ポートは全部大文字|| ||出力ポート||O_~|||| ||入出力ポート||IO_~|||| ||パラメータ||G_~||VHDLのGenericに由来|| ||localparam||C_~||VHDLのConstantに由来|| ||FFを推論するもの||r_~|||| ||組み合わせ出力を推論するもの||w_~||wireのみでなく、reg w_~というパターンもあり|| ||負論理||~_N|||| ||イネーブル信号||~_ENA||少し長いが、_ENだと負論理と区別しにくいので|| === ラッチが推論されないようにする === ステートマシンを書くとき、always @(*)節の中に少しでも書き忘れがあるとラッチが推論される回路になってしまいます。 組み合わせ回路になるようにするには、以下のようにする必要があります。 * beginのすぐあとに「なにも作用しない」ときの値を列挙する * 現在の状態を維持する(遷移しない)場合にもw_next_stへの代入をする * default文を書く ラッチが推論されていないことを確かめるには、ソースを眺めるよりもツールで合成をしてWarningを調べるほうが確実です。 以下に、各ツールでラッチが生成されたとき出るWarningをまとめます。 || ツール || メッセージ || || Xilinx XST || WARNING:Xst:737 - Found n-bit latch for signal <信号名> || || Xilinx Vivado || || || Altera QuartusII || || == ソース == [[Embed(statemachine_20151104.zip)]] {{{ code verilog /* I_KICK入力があってからG_COUNTクロック後にO_KICKを出すステートマシン。 */ `default_nettype none module DELAY_SM #( parameter G_COUNT = 10 ) ( input wire I_CLK, input wire I_RST, input wire I_KICK, output wire O_KICK ); localparam C_IDLE = 0; // ステート数が増えたときに付け直すのが面倒なので、 localparam C_WAIT = 1; // 4'd0のような幅をつける記述はしない。 // Verilog2005の$clog2の代わり function integer clog2; input integer value; begin value = value - 1; for (clog2 = 0; value > 0; clog2 = clog2 + 1) value = value>>1; end endfunction // ステート数が少ないため幅は4も必要ないが、ビット割付はシンセサイザが勝手に // やるので幅が大きすぎる分には問題ない。 reg [3:0] r_current_st; reg [3:0] w_next_st; reg w_start; wire w_finish; reg w_kick; reg r_kick; // clog2(value)は、valueの「要素数」を表現できる最小限のビット幅を返す。 // valueまでの「値」を表現したいなら+1した値を与えなければならない。 // たとえば0~16までを表現したいなら、clog2(16+1)ビットが必要になる。 reg [clog2(G_COUNT+1)-1:0] r_count; // ステートマシンの核はこれだけ。 always @(posedge I_CLK or posedge I_RST) begin if (I_RST) begin r_current_st <= C_IDLE; // リセットで初期状態へ遷移 end else begin r_current_st <= w_next_st; end end // このalways節の中はすべてブロッキング代入 // ノンブロッキング代入を使ってしまうと、同時刻に同一変数への代入が行われる // ため結果が処理系依存になってしまう。 always @(*) begin // へたにセンシティビティ・リストを書くより*がよい w_start = 1'b0; // ここらへんには「なにも作用しない」ときの値を w_kick = 1'b0; // 列挙する。記述がもれるとラッチが生成される。 case (r_current_st) C_IDLE : begin if (I_KICK) begin w_start = 1'b1; // こうするとミーリ型になる w_next_st = C_WAIT; end else begin // 遷移しない場合もw_next_stへの代入を書く // そうしないとラッチ生成 w_next_st = C_IDLE; end end C_WAIT : begin if (w_finish) begin w_kick = 1'b1; w_next_st = C_IDLE; end else begin w_next_st = C_WAIT; end end default : begin // defaultでw_next_stへの代入を忘れるとラッチ生成 w_next_st = C_IDLE; end endcase end // 組み合わせ出力からフリップフロップ出力に直す always @(posedge I_CLK or posedge I_RST) begin if (I_RST) begin r_kick <= 1'b0; end else begin r_kick <= w_kick; end end assign O_KICK = r_kick; // 値域は0~G_COUNT always @(posedge I_CLK or posedge I_RST) begin if (I_RST) begin r_count <= 0; end else begin if (w_start) begin r_count <= 1; end else if (r_count == 0) begin r_count <= r_count; end else if (r_count < G_COUNT) begin r_count <= r_count + 1'b1; end else begin r_count <= r_count; end end end assign w_finish = (r_count == G_COUNT-1) ? 1'b1 : 1'b0; endmodule `default_nettype wire }}}