2012年5月5日土曜日

電気回路/HDL/Verilogで犯しがちな記述ミス - Takeuchi@ShigekawaLab


[このページの編集履歴] Top / 電気回路 / HDL / Verilogで犯しがちな記述ミス

公開メモ

意図 †

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

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

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

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

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

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

対処法 †

(2010/06/03 追記)

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

ソースコードの最初に

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

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

注意点1 †

marsee さんより:

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

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

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

つまり、

LANG:verilog

`default_nettype none

(Verilogの回路本体)

`default_nettype wire

です。

注意点2 †

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

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

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

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

`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 を使う方法で、

`default_nettype none  (内容)  `default_nettype wire

の代わりに

`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 の中身などで、

function some_function;      input a;      input b;

まで

function some_function;      input wire a;      input wire b;

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

演算子の優先順位 †

参考:

ビット演算子と等号 †

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

これは、

assign a = ( b == c ) & d;

と解釈されますので、

assign a = b == ( c & d );

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

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

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

等号と3項演算子 †

assign a = b == c ? d : e

は、

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

と解釈されるので、

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

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

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

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

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

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

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

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

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

引き算での桁あふれ †

marsee さんに教えてもらった例です。

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

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

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

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 さんの意図する ところではなかったというお話です。

回避策は、

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

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

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

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

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;

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

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

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

(2010/06/12追記) 逆にビット幅の自動拡張をうまく利用することを考えると、

assign fifo_full = rp - 1'b1 == wp;

が一番 Verilog っぽい書き方なのかもしれませんね。

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

Verilog 演算子の細かい注意事項は、Veritak の verilog チュートリアル

の8章を読むと一通りのことを勉強できますね。

とてもためになります。

式中のビット幅及び符号に関する基本的な考え方は、

  1. 「式中に現れる数値」と「結果を代入する先」を全て比較して、 最大のビット幅を持つ数値に合わせ、全ての数値をビット拡張してから演算を始める
  2. 演算に用いるすべての数値が符号付きであれば(代入先は関係ない)、ビット拡張は符号付きで行われ、演算も符号付きで行われる
  3. 1つでも符号なしが含まれていれば、すべてのビット拡張や演算が符号なしで行われる
  4. 代入先が演算結果より狭い場合、演算結果の上位ビットが切り捨てられる
  5. ビット幅が指定されていない定数は32ビット以上の幅を持つものとして扱われる

ということだそうです。

シミュレーション時の RAM / ROM へのアクセス †

次のコードは FIFO の読み出し部分を想定したものですが、 特に、シミュレーションでは期待通りに動かない場合があるみたいです。


聴力計を使用して、 AC電源の周波数を見つけるためにどのように
1:  2:  3:  4:  5:  6:  7:  8:  9:  10:  11:  12:  13:  14:  
parameter DATA_BITS = 8;  parameter DEPTH_BITS = 11;    reg [DATA_BITS-1:0] mem [0:2**DEPTH_BITS-1];  reg [DEPTH_BITS-1:0] rp;    always @(posedge clk) begin      if (rst) begin          rp <= 0;      end else begin          rp <= rp + 1;      end      odata <= mem[re ? rp+1 : rp];  end

問題があるのは 13 行目になります。

上記のとおり、rp が DEPTH_BITS ビット幅であっても rp + 1 は、 1 が 32'd1 と解釈されるため、演算結果は 32 ビット幅となります。

同じ式の出てくる 11 行目では、rp への代入時にこの 32 ビット幅の数値の LSB の DEPTH_BITS ビットが取り出されるため、そのように 32 ビットで演算されても実害はありません。

ところが mem[rp+1] ではそのような演算結果へのビット幅への制限が (少なくとも言語仕様上は)存在しないため、問題が発生します。

rp == {DEPTH_BITS{1'b1}} の時、rp + 1 は 32 ビット幅の 1 << DEPTH_BITS と等しい値となり、それがそのまま mem へのインデックス として使われます。すると、宣言されたレンジをオーバーしてしまうため シミュレータ上では X が出力されることになります。

上記コードが通常通り合成されれば RAM へのアドレス線が DEPTH_BITS 幅となるので、 アドレス値としては rp + 1 の LSB の DEPTH_BITS ビットが使われることになり、 オーバーフローした1ビットは切り捨てられ、 想定通りメモリアドレス0の値が出力されます。

つまり、上記の書き方による問題はシミュレーション時のみしか現れないのですが、 いずれにしても、上記コードには改善の余地があります。

後から気づいたのですが、この手の問題を未然に防ぐため、 配列のインデックスとしてビット幅の大きすぎる値をしていした部分に、 Synthesis 時に次の警告が出るようです。

Xst:1433 - Contents of array  may be accessed with an index that  exceeds the array size. This could cause simulation mismatch.

改善策としては rp + 1 を

  • rp + {{(DEPTH_BITS-1){1'b0}}, 1'b1} と書く
  • 少し手を抜いて rp + 1'b1 と書く
  • rp + 1 の値を一旦 DEPTH_BITS 幅の wire を通してから mem に渡す

といった方法が考えられると思います。

演算結果のオーバーフロー †

次の式は思った通りに動きません。

たっくさんのページにあったのを見て、自分でもやらかしたのを思い出しました。

reg [15:0] a, b, c;  c = (a + b) >> 1;

これは、a + b が 16 ビット幅で演算されてから >> 1 されるため、 a + b でオーバーフローが起きた場合にはシフトの前に最上位ビットが失われてしまうためです。

reg [15:0] a, b, c;  c = ( {1'b0, a} + {1'b0, b} ) >> 1;

などとして、少なくとも一方を 17 ビット幅に拡張しておく必要があります。

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

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


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

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

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

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

Message Filter を使う †

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

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

Google:Xilinx ISE Message Filter

Project Navigator でのメッセージ フィルタの設定

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

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

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

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

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

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

(追記)しかたがないので、結局自分で作る羽目に(泣
電気回路/HDL/Xilinx ISE のメッセージフィルタ#m858a0f5

未稿 †

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

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

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



These are our most popular posts:

C/C++とJava(第7章)

7.6.1 数値計算; (プログラム例 7.23 ) 連立線形方程式,逆行列(ガウス・ジョルダン) ; (プログラム例 7.28 ) 非線形方程式(二分法); (プログラム例 7.29 ) 非線形 .... 「%lf %d %s」の部分が,入力されたデータをどのように変換するかを指定する部分です. ... 桁数が 10 桁に満たない場合は,左側にスペースが挿入されます. ... 文字列が 見つかった場合は,最初に見つかった位置へのポインタを返し,見つからなかった場合 は,NULL を返します. ...... そのような場合は,変数のアドレスを渡すという方法で解決 できます. read more

プロジェクト‐ノート:数学 - Wikipedia

個人的には「インデントさせない」という解決法もあるとは考えているのですが、今の ところ、インデントさせる場合に的確な手法についての合意には至っていません。 .... 数学を少し嗜む者で,今は主に「結び目理論」と「常微分方程式」を研究しています. ... まあそれはともかくわたしが以前からずっと気になっているのは、「音声ブラウザ」などを も考慮したときいったいどう書くのが .... 一週間反対意見が見られない場合は、本項「 用語・表記の曖昧さ」節に「「線形」と書くことを推奨」といった意味の文言を加えたいと 思います。 read more

Q&A « innsi`s blog 東大院試合格への道!

過渡現象はラプラス変換、もしくは微分方程式の知識がないと厳しいので、あまり出な そうなら切り捨てるのも◎だと思います。 過渡現象自体は電気回路要論にの ... 変数 分離、同次形、一階線形、二階線形などはやったのですが、正直どのように勉強すれば いいかの指針もわかりません。よろしければ何か ..... この2つがしっかりしていなければ 、まずBrush upするも何も、書く内容が見つからないかと思います。 そこで、大学2年-3 年は ... read more

【数学の問題】二元一次方程式はどうやって解くのですか? - Yahoo!知恵袋

【数学の問題】二元一次方程式はどうやって解くのですか?まずこちらの二元一次 方程式の項を見てください。 ... ここから本文です. 解決済みの質問. 知恵コレに追加する ... なので、まず(x,y)=(3,4)という解が見つかります。 xを5増やし、yを3 ... 元が2つ(xとy) があるのに式が一つしかないから解けません(式が2つあればとける) ... read more

Related Posts



0 コメント:

コメントを投稿