Verilogで犯しがちな記述ミス のバックアップ(No.10)

更新


公開メモ

意図

インプリメント時のワーニングをうまく見る方法が分からず、 簡単な記述ミスのせいで2,3時間を無駄にすることがしばしばなので、 ありがちなミスやそれへの対処法をここに記述して、日頃から注意しようという算段です。

宣言されていない信号線が幅1の wire として解釈される

Verilog ではこれは言語仕様なので、警告も出ないのですよね。

このせいで、クロックが正しく繋がれていなかったり、 幅の広いバス線のはずが1ビット目しか繋がれていなかったり、 常に泣かされています。

宣言されていない信号線が使われたらエラーにするか、 最低でも警告を出すオプションがあればかなり開発が 順調に進むと思うのですが・・・

見つけられていないだけかもしれません?

対処法

(2010/06/03 追記)

marsee さんに対処法を教えていただきました。

ソースコードの最初に

LANG:verilog
`default_nettype none

を記述すれば良いそうで、定義していない信号をエラーにできます。 Verilog 2001 以降で使えるそうです。

以下の注意も一緒にいただきました。

注意点1

marsee さんより:

Xilinxのライブラリなどでは、1ビットのwireは定義していないこともあるので、

コンパイルの順番によっては、そこでエラーになることがあります。

そこで、最後に`default_nettype wireを書いておくと良いと思います。

つまり、

LANG:verilog

`default_nettype none

(Verilogの回路本体)

`default_nettype wire

です。

注意点2

で、もう一つ自分で見つけた注意点ですが、

http://japan.xilinx.com/support/answers/34811.htm

にあるとおり、`default_nettype none の副作用として、次のコードがコンパイルエラーになります。

LANG:verilog
`default_nettype none
module my_module (
  input    clk,
  input    reset,
  input    data_in ,
  output   data_out
);
  ...

endmodule

次のように書けばエラーになりません。

LANG:verilog
`default_nettype none
module my_module (
  input wire clk,
  input wire reset,
  input wire data_in ,
  output wire data_out
);
  ...

endmodule

現在の所、Implementation 時に上記コードがエラーになるのは Virtex-6 と Spartan-6 FPGA といった最新の FPGA に対してのみのようです。 Spartan 3A DSP ではエラーになりませんでした。

ただ、ISim では Spartan 3 でも最新のパーサーが使われるようで、 ほぼすべてのモジュールでエラーが出まくって驚きました。

今後のことも考えると、input / output で wire を省略するのはやめた方が良いようです。

・・・うーん、どこかで wire を入れるとエラーになることもあったような???

対処法1

そこそこ安全な対処法は `define を使う方法で、

LANG:verilog
`default_nettype none
(内容)
`default_nettype wire

の代わりに

LANG:verilog
`ifdef DEFAULT_NETTYPE_NONE
`default_nettype none
`endif
(内容)
`default_nettype wire

としておいて、コンパイラオプションで DEFAULT_NETTYPE_NONE を定義するという方法です。

DEFAULT_NETTYPE_WIRE が未定義の場合にも、問題なくコンパイルできるので、 コードをそのまま別のプロジェクトに持って行っても少しだけ無駄なコードがある というだけで実害は生じません。

対処法2

wire も reg も付いていないすべての input/output/inout に一括で wire を付けてしまおう、という方針であれば、

(^[ \t]*(input|output|inout)\>)(?! *(wire|reg))

という正規表現で検索して、

\1 wire

に置き換えれば、かなり手間が省けます。

まずいのは function の中身などで、

LANG:verilog
function some_function;
    input a;
    input b;

まで

LANG:verilog
function some_function;
    input wire a;
    input wire b;

にされてしまうため、その部分だけ手作業で元に戻すことになります。

演算子の優先順位

参考:http://homepage3.nifty.com/hdl_design/verilog_hdl2.htm

ビット演算子と等号

ビット論理演算子の & や | よりも等号・不等号の方が優先順位が 高いことをすぐに忘れてしまい、痛い目を見ます。

LANG:verilog
assign a = b == c & d;

これは、

LANG:verilog
assign a = ( b == c ) & d;

と解釈されますので、

LANG:verilog
assign a = b == ( c & d );

としたければ、括弧は必須です。

Pascal や Ruby ではビット論理演算が等号よりも強かったので、 今でも勘違いして原因が分からず途方に暮れます。

C++ や C#, Java もビット論理演算が等号より弱いので、 そちらでも間違えまくりです(泣

等号と3項演算子

LANG:verilog
assign a = b == c ? d : e

は、

LANG:verilog
assign a = ( b == c ) ? d : e

と解釈されるので、

LANG:verilog
assign a = b == ( c ? d : e )

としたければ括弧は必須です。

これも結構やらかします。

3項演算子 ? : はすべての演算子の中で最も優先順位が低い、と覚えておけばいいのですね。

比較や代入における信号のビット数の取り違い

ちょっとすぐに例が思い浮かばないのですが、

http://japan.xilinx.com/support/answers/33037.htm

にある話と似たような状況で、定数や、演算結果のビット数が、 思い浮かべたビット数と異なるために、回路が思ったように動作しないことがしばしばありました。

大抵は、{ 8'h00, some_signal } のように明示的に信号幅の拡張を行うことで解決するのですが、 一見正しそうで間違った式を書くことができてしまうようなので、注意が必要です。

失敗例を思い出したら追記します。

引き算での桁あふれ

marsee さんに教えてもらった例です。
http://marsee101.blog19.fc2.com/blog-entry-763.html

前提として、Verilog ではビット幅を指定しない整数は32ビット幅であると仮定されます。

そして、演算ではビット幅の小さい信号を大きい信号の幅に合わせてから計算が行われます。

したがって、次の例は rp == 8'h00, wp == 8'hff の時、誤動作します。

LANG:verilog
reg [7:0] wp, rp;
...
assign fifo_full = ( rp - 1 == wp ) ? 1 : 0;

誤動作の原因は、rp - 1 == wp の評価が以下のように行われるためです。

  • 1 は 32'h00000001 と解釈される
  • rp が 32'h00000001 に合わせて { 24'h000000, rp } の形で 32 ビット幅に拡張される
  • そこから 32'h00000001 が差し引かれる
  • wp がやはり 32 ビット幅に拡張されて { 24'h000000, wp } となる
  • 結果的に { 24'h000000, rp } - 32'h00000001 == { 24'h000000, wp } が評価される

rp == 8'h00 の時、左辺は 32'hffffffff となって、右辺の 32'h000000ff とは異なると評価されるのですが、これはコードを書いた marsee さんの意図する ところではなかったというお話です。

回避策は、

LANG:verilog
assign fifo_full = ( rp - 8'h01 == wp ) ? 1'b1 : 1'b0;

のように、各定数に正しく幅を指定することです。

http://marsee101.blog19.fc2.com/blog-entry-763.html のコメントでは「足し算なら・・・」 という話も出ているのですが、ビット幅を指定しないと足し算でもうまく行かないですよね。 今度は rp == 8'hff の時に rp + 1 == 32'h00000100 になってしまうと思います。

ちょっと書き方を迷ってしまうのがビット幅が parameter で可変の場合でしょうか。 見直してみると、自作の [[非同期 FIFO>電気回路/HDL/非同期信号を扱うための危ういVerilogライブラリ#非同期 FIFO]] では以下のような書き方をしてました。

LANG:verilog
parameter DEPTH_BITS = 8;

reg [DEPTH_BITS-1:0] wp, rp;
wire [DEPTH_BITS-1:0] one = 1;
...
assign fifo_full = ( rp - one == wp ) ? 1 : 0;

というか、自作のコードでは以下のように書いてあるのですが、 ここまでするのは正しいとはいえ逆に可読性が低下している気がしてきました。
#実際にはビット数を数え間違っていたのを慌てて直しました(汗

LANG:verilog
wire [DEPTH_BITS-1:0] one  = { {DEPTH_BITS-1{1'b0}}, 1'b1 };

signed と unsigned をシステムタスクで変換できるように、 何か言語上うまくビット幅をパラメータで書く方法があるといいのですが、、、

Veritak のチュートリアルページの解説

このあたりの細かい注意事項は、Veritak の verilog チュートリアル
http://japanese.sugawara-systems.com/tutorial/verilog/framepage7.htm
の8章あたりを読むと一通りのことを勉強できますね。

とてもためになります。

インプリメント時のワーニング欄を見やすくする工夫

ソフトウェアのプログラミングでもそうなのですが、 最近のコンパイラはかなり賢いので、 バグを含むコードを与えると高確率で的確な警告を出してくれるため、 ちゃんと警告欄に目を光らせていればデバッグの手間を大幅に省くことができます。

そして、コンパイラからの警告を有効に活用するため、 コンパイル時に出る警告は、それが無害なものであることが明らかでも、 コーディングを工夫することで上手に消すことが推奨されています。 したがって通常のソフトウェア開発では、 コードをコンパイルしても1つもコンパイル警告が出ないのが普通です。

しかし Xilinx ISE で Verilog を用いてコーディングしている限り、 コーディングの工夫だけでは警告を減らすことができず、 結果として警告欄が無害な警告であふれてしまって 本当に危険な警告に気づけない状況で開発を進めなければならないようです。

例えば PicoBlaze や CoreGenerator など、Xilinx のライブラリを組み込むだけで数百もの警告が出ますので、 もううんざり、、、なのです。

警告欄を有効活用するための方法をメモしたいと思います。

Message Filter を使う

  • Verilog 2001 では、コーディングの工夫だけではどうやっても消せない種類の警告があること、
  • Xilinx 公式のコードから尋常じゃない数の警告が出ること、

などから、 無害な警告はコーディングの工夫ではなく、コンパイラ出力を目で追う段階でフィルタする、 という方法が提供されているようです。

Google:Xilinx ISE Message Filter

Project Navigator でのメッセージ フィルタの設定
http://www.xilinx.com/itp/xilinx7j/help/iseguide/html/ise_filtering_messages_pn.htm

これ、フィルタの数が少ないうちはよいのですが、フィルタ管理の GUI が弱すぎるため、 何百ものフィルタを This Instance Only で追加していくと、 フィルタの削除や無効化をしたいときににっちもさっちもいかなくなります。

[Edit Message Filters] メニューを選ぶだけで数十秒の時間が掛かり、 なおかつフィルタの無効化・有効化や、削除には、 1つ1つのフィルタに対してマウスでの操作が必要になります。

まとめて削除できない GUI は、あまりに手を抜きすぎです。

ISE 11 まではこれらのフィルタはプロジェクトディレクトリの filter.filter (だったっけ?)というテキストファイルに格納されていたので、 最後の手段はこれを慎重にテキストエディタで編集することでした。

ISE 12 では格納場所が変わったようで iseconfig/filter.filter ですね。

いざとなったらこれを編集するソフトを自作しなければならないかも、 と思っているのですが、すでにどこかにあったりするのでしょうか?

未稿

以下、自分で書いたコードからなるべく警告を出さない工夫をメモろうとしたのですが、、、 まだあまり見つけられていません。

WARNING:Xst:2677 - Node <????????> of sequential type is unconnected in block <????>.
HDLCompilers:261 - "????????" line ???? Connection to output port '??????' does not match port size

あたりは、いくつかのピンを意図的に使っていないことを指定する構文が verilog の言語仕様にあれば良いのですが、たぶんできないのですよね。

コメント




宣言する名前に工夫する

[アプロ] (2010-06-04 (金) 15:51:24)

reg宣言, 〜_reg って付ける。F/Fを推定
reg [1:0] aaaa_reg ; /* コメント */

wire宣言, 〜_sig って付ける。組み合わせ回路や単なる接続信号
wire bbbb_sig ; /* コメント */

こんな感じにいつもしています。まあ、好みですけど(笑)

  • 経験不足で、reg と wire とを一目で見分けられるようにすることのメリットがまだ見えていません。信号源が reg 出力そのままであるか、組み合わせロジックの出力であるかを見分けることでハザードや遅延量を正しく期待する、とかでしょうか?よろしければ目的を教えていただけると勉強になります。 -- [武内(管理人)]
  • 内部でモジュールを呼び出して接続する際に、これなんだっけ? とか、モジュールの出力ピンとして、assign文を書く時に、F/Fだっけ? 組み合わせだっけ? すぐに判明します -- [アプロ]
  • ふむふむ、何となく分かってきました。どうもありがとうございます。 -- [武内(管理人)]

Verilog記述の時の失敗例

[marsee] (2010-06-04 (金) 13:16:24)

15深度のFIFOのfullをVerilogで書いた時の私の失敗例です。これがわかるまでに苦労しました。
http://marsee101.blog19.fc2.com/blog-entry-763.html

  • あ!これに近いことを私もやりました。本文に入れさせていただきます。 -- [武内(管理人)]

無題

[アプロ] (2010-06-04 (金) 12:25:12)

メリット:
・HDLチェックツールのエラー対策、RTLスタイルガイド準拠
 製品向けの対応の設計ルールですね
・正論理、負論理をはっきりさせることと、その部分でビット数が明確になります
 ソースを読む場合、いちいち、宣言部分まで戻って見ることをやりたくない
 なにしろ、300行以上はザラですからね

  • お返事ありがとうございます。正論理・負論理について、if(enable) や if(!not_enable) と書くのと、if(enable==1'b1) や if(not_enable==1'b0) と書くのとで後者の方が読みやすいという気がしないのですが、、、まだよく分かっていないみたいです。 -- [武内(管理人)]
  • ビット数の話はお陰さまで何となく分かりました。が、実感として得るには少し痛い目を見ないと骨身にしみないかもしれません(汗 -- [武内(管理人)]

()は必須です

[アプロ] (2010-06-03 (木) 07:57:45)

assign文による組み合わせ回路で、条件判定する場合は、() でくくる癖をつけた方がいいですよ

また、あとあと論理がわかるように
(c == 1'b1) ?
とか明示するとよいでしょう

  • 書き込みありがとうございます。予防のために括弧を付ける癖を付けるというのが正しい姿勢なのでしょうね。 -- [武内(管理人)]
  • 条件判定で c == 1'b1 と書くのは個人的にはまだ受け入れにくいです。C を始めとするプログラミング言語では c != 0 とか c != false は冗長であるというのが常識で、この書き方もそれに似ているように思えてしまいます。特にビット幅まで指定した場合に c == 1'b1 と c == 1'b0 を瞬時に判別できないため、c と !c で書き分けるのに比べてかえって視認性が悪くなってしまうのではと感じています。c == 1'b1 という書き方のメリットはどのあたりにあるのでしょう? -- [武内(管理人)]

宣言されていない信号線が幅1の wire として解釈されるの解決法

[marsee] (2010-06-03 (木) 07:09:46)

宣言されていない信号線が幅1の wire として解釈されるの解決法としては、`default_nettype noneを最初に記述するという方法があります。こうすると定義していない信号があるとエラーになります。ただ、これはVerilog2001の構文です。Xilinxのライブラリなどでは、1ビットのwireは定義していないこともあるので、コンパイルの順番によっては、そこでエラーになることがあります。そこで、最後に`default_nettype wireを書いておくと良いと思います。つまり、
`default_nettype none
Verilogの回路本体
`default_nettype wire
です。

  • 大変有用な情報をありがとうございます。昨日からどこが悪いか全く分からず苦労している回路にこれを付けたらバグが見つかりそうな予感です(汗 -- [武内(管理人)]
  • メモリモジュールのテストで ModelSim のステップ数制限にかかってしまったので、久しぶりに ISim を起動したら http://japan.xilinx.com/support/answers/34811.htm の影響でエラー出まくりでした。この `default_nettype none の副作用について上に追記しました。 -- [武内(管理人)]

Counter: 307941 (from 2010/06/03), today: 5, yesterday: 0