Verilogで犯しがちな記述ミス の履歴(No.31)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 電気回路/HDL/Verilogで犯しがちな記述ミス へ行く。
意図†
インプリメント時のワーニングをうまく見る方法が分からず、 簡単な記述ミスのせいで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 をシステムタスクで変換できるように、 何か言語上うまくビット幅をパラメータで書く方法があるといいのですが、、、
(2010/06/12追記) 逆にビット幅の自動拡張をうまく利用することを考えると、
LANG:verilog assign fifo_full = rp - 1'b1 == wp;
が一番 Verilog っぽい書き方なのかもしれませんね。
Veritak のチュートリアルページの解説†
Verilog 演算子の細かい注意事項は、Veritak の verilog チュートリアル
http://japanese.sugawara-systems.com/tutorial/verilog/framepage7.htm
の8章を読むと一通りのことを勉強できますね。
とてもためになります。
式中のビット幅及び符号に関する基本的な考え方は、
- 「式中に現れる数値」と「結果を代入する先」を全て比較して、 最大のビット幅を持つ数値に合わせ、全ての数値をビット拡張してから演算を始める
- 演算に用いるすべての数値が符号付きであれば(代入先は関係ない)、ビット拡張は符号付きで行われ、演算も符号付きで行われる
- 1つでも符号なしが含まれていれば、すべてのビット拡張や演算が符号なしで行われる
- 代入先が演算結果より狭い場合、演算結果の上位ビットが切り捨てられる
- ビット幅が指定されていない定数は32ビット以上の幅を持つものとして扱われる
- デシマル表現(例 123, や 456)は signed だが、その他、 ベース表現 (例 4'd12)、ビットセレクト (例 a[4])、パートセレクト (例 a[4:2]) はすべて unsigned になる。
ということだそうですが・・・細かい例外的なことがてんこ盛りです。 詳細は上記リンク先へどうぞ。
培風館「RTL設計スタイルガイド」によると、これらに加えて
- if 文の条件式などは「結果を代入する先」が明示されないが、
内部的には 32bit が仮定されるため、
「式中に表れる数値」だけを見ていると予想できない大きなビット拡張が生じることがある。
(三項演算子 "? :" も同じかしら?)
unsigned 値と signed 値との和や差を正しく計算する方法†
特に上の 1.~ 3. の組み合わせは思っていない結果を生みやすいです。
例えば 8 ビットの 1 と -1 とを足す場合こんなことが起きます。
LANG:verilog module verilog_test(); reg [8-1:0] a = 1; reg signed [8-1:0] b = -1; initial begin $display(a+b); // 0 が表示される $display((a+b)/2); // 128 が表示される $display((a+b)/8'd2); // 0 が表示される $display((1+b)/2); // 0 が表示される $display(($signed(a)+b)/2); // 0 が表示される $display(($signed(a)+b)/'h0002); // 128 が表示される $display((a+b)>>1); // 0 が表示される $display($signed(a+b)/2); // 0 が表示される $display($unsigned(a+b)/2); // 0 が表示される $stop; end endmodule
初心者を地獄に突き落とすのに十分な程度にはひどい結果が得られている。
LANG:verilog $display(a+b); // 0 が表示される
これは良いとして、
LANG:verilog $display((a+b)/2); // 128 が表示される
では / 2 の部分の 2 が 32 ビット値なので、a と b とは演算の前に 32 ビットに拡張されますが、 a が unsigned なので b の符号拡張も unsigned で行われ、結果的に
LANG:verilog ( 32'h0000_0001 + 32'h0000_00ff ) / 32'd2
という計算の結果、128 が表示されます。
正しく計算するには、
LANG:verilog $display((a+b)/8'd2); // 0 が表示される
のように定数 2 にビット幅を指定して、そもそもビット幅拡張が起きないようにする方法が考えられます。
次の
LANG:verilog $display((1+b)/2); // 0 が表示される
がゼロになるのは、32 ビット定数である 1 や 2 が signed 扱いになっているためです。
ですので、
LANG:verilog $display(($signed(a)+b)/2);// 0 が表示される
とすればこちらも式に出てくる値がすべて signed になって、b が正しく符号拡張されるようになります。
当然、
LANG:verilog $display(($signed(a)+b)/'h0002); // 128 が表示される
だと 'h0002 が unsigned なので b が unsigned 拡張されてしまいます。
LANG:verilog reg [32-1:0] c = 2;
を使った場合にも同様になります。
LANG:verilog $display(($signed(a)+b)/c); // 128 が表示される
ここまで見てくると、
LANG:verilog $display((a+b)>>1); // 0 が表示される
は逆に想定外かもしれません。
ここで出てくる定数 1 は 32'd1 なので、「式中に表れる数値」の最大ビット幅は 32 となるはずですが、計算結果を見る限り b に対するビット幅拡張は行われていません。
計算のビット幅を決める「式中に現れる数値」にはビットシフト演算子の右辺は含まれないためです。 このように、全体のビット幅を決めるときに意味をなさない「数」としては、 3項演算子の条件部分、ビットシフト演算子の右辺、Reduction演算や比較演算、論理演算で結果が 1bit になる数式部分、があるようです。
最後の2つ、
LANG:verilog $display($signed(a+b)/2); // 0 が表示される $display($unsigned(a+b)/2);// 0 が表示される
が両方ともうまく行く理由は、ここでの a+b が $signed や $unsigned へ渡される引数であり、 2とは独立な式になっているためです。a, b はどちらも 8 ビット幅なので a+b も 8 ビットで計算され、 オーバーフローした分を除いた 8'x00 が $signed や $unsigned に渡されます。 どちらに渡しても結果はゼロなので、それを2で割った結果はやはりゼロになるわけです。
とてもややこしいですね・・・
シミュレーション時の RAM / ROM へのアクセス†
次のコードは FIFO の読み出し部分を想定したものですが、 特に、シミュレーションでは期待通りに動かない場合があるみたいです。
LANG:verilog(linenumber) 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 <mem> 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 に渡す
といった方法が考えられると思います。
演算結果のオーバーフロー†
次の式は思った通りに動きません。
たっくさんのページにあったのを見て、自分でもやらかしたのを思い出しました。
LANG:verilog reg [15:0] a, b, c; c = (a + b) >> 1;
これは、a + b が 16 ビット幅で演算されてから >> 1 されるため、 a + b でオーバーフローが起きた場合にはシフトの前に最上位ビットが失われてしまうためです。
LANG:verilog reg [15:0] a, b, c; c = ( {1'b0, a} + {1'b0, b} ) >> 1;
などとして、少なくとも一方を 17 ビット幅に拡張しておく必要があります。
上でも指摘しましたが、このとき定数 1 は32ビットですが、 シフト演算子の右辺の値はビット幅拡張の幅を決める際に参照されないため、 シフト演算子の左辺と、代入演算の左辺とだけで演算ビット数が決定されます。
$signed(1'b1) は -1†
これは考えてみれば当たり前なので、単なる恥ずかしい話なんですが、 Verilog の自動ビット幅拡張を利用した a + 1'b1 というコードに 後から「あれ、1'b1 は unsigned なので a が signed なら $signed で囲むべきじゃね?」 とか血迷ったことを考えて、
LANG:verilog a <= a + $signed(1'b1)
なんてコードができあがった際に、a が1ずつ「減っていく」のを見てしばらく固まりました。
1ビットの signed 値は $signed(1'b0) と $signed(1'b1) しかないわけですが、 この1ビットは最上位ビットなので signed を付けると符号ビットとして扱われ、 $signed(1'b0) == 0 はいいとして、$signed(1'b1) == -1 となるわけです。
よほど疲れてなければこんな血迷ったコードを書くことはないでしょうけれど、 疲れてたので面食らったという武勇伝でした。。。
32 ビット以上の parameter†
Verilog の parameter はマクロの置き換えとは異なり固有の信号線幅を持っています。
したがって parameter にはビット幅指定が可能なのですが、 多くのサンプルでは parameter や localparam にビット幅が指定されていません。
少なくとも Xilinx ISE で試す限り、ビット幅を明示しない parameter / localparam は 32 ビット幅になるため、パラメータとして指定された値が意図せず切り捨てられる可能性があります。
parameter に 32 ビット以上の値を入れる可能性がある場合には ビット幅指定を忘れないようにと覚えておきましょう。
LANG:verilog module some_module #( parameter PARAM1 = 64'h123456789abcdef ) ( ... ); // PARAM1 は 32 ビット幅に切り詰められてしまう endmodule
これを防ぐには
LANG:verilog module some_module #( parameter [63:0] PARAM1 = 64'h123456789abcdef ) ( ... ); // PARAM1 は 64 ビット幅として使える endmodule
などとしてビット幅を明示しなければなりません。
実例として、 複数ビットバージョンの double_ff の初期値を与える parameter に ビット幅指定を使った例を挙げておきます。
電気回路/HDL/コード中に制約を書くときの注意点#ビット幅の拡張
LANG:verilog module double_ff #( parameter BITS = 1, parameter [BITS-1:0] INIT = 1'b0 ) ( input wire [BITS-1:0] idata, input wire oclk, output wire [BITS-1:0] odata ); generate genvar bit_i; for ( bit_i = 0; bit_i < BITS; bit_i = bit_i + 1 ) begin: bits double_ff_1bit #( (INIT >> bit_i) & 1 ) double_ff_1bit ( .idata( idata[bit_i] ), .oclk( oclk ), .odata( odata[bit_i] ) ); end endgenerate endmodule
これと同様に、
LANG:verilog parameter PARAM1 = $signed(-1);
などとした場合、これは
LANG:verilog parameter [31:0] PARAM1 = 32'hffffffff;
と同じ意味で、値は default の unsigned になるため、本当に符号付きで扱いたい時には
LANG:verilog parameter signed [31:0] PARAM1 = -1;
としなければなりません。
このあたりは
LANG:verilog `define PARAM1 $signed(-1)
とした場合と大きく異なるため注意が必要です。
組み合わせ回路を always や function で書く際にいろいろミスりやすい†
培風館「RTL設計スタイルガイド」では、 組み合わせ回路を作成するには reg + always あるいは wire + assign + function を使うことが推奨されていました。
例えば、
LANG:verilog wire S, M; wire [3:0] A, B, C; wire [3:0] Y; always @(S or M or A or B or C) begin if ( S == 1'b0 ) begin if ( M == 1'b0 ) begin Y = A; end else begin Y = B; end end else begin Y = C; end end
とか、
LANG:verilog function [3:0] CalcY; input S, M; input [3:0] A, B, C; begin if ( S == 1'b0 ) begin if ( M == 1'b0 ) begin CalcY = A; end else begin CalcY = B; end end else begin CalcY = C; end end endfunction wire S, M; wire [3:0] A, B, C; wire [3:0] Y; assign Y = CalcY(S, M, A, B, C);
といった具合です。
これらの記述の欠点として、
- 前者について
- センシティビティリストでミスるとラッチを生成してしまう
- (reg 宣言してある Y が実はラッチを生成しないのが紛らわしい? ← 個人的な意見)
- 後者について
- function の引数や戻り値のビット幅を誤るミスが多発する
- 上記のような function の呼び出しでは引数の順番を間違えるミスもありがち
- 引数の宣言を忘れて function 外の信号を参照してしまってもエラーにならない
などのミスが生じやすいことが挙げられています。
これらと比較して「悪い例」として、
LANG:verilog wire S, M; wire [3:0] A, B, C; wire [3:0] Y; assign Y = (S==1'b0) ? (M==1'b0) ? A : B : C;
というのが挙げられていました。
曰く、「3項演算子が連続すると読みにくいので避けるべき」とのことでした。
ほんと?†
・・・いやいや、これって単にインデントや括弧を付ければ良い問題では???
個人的には上記の回路は、always や function を使ったのに比べても、
LANG:verilog wire S, M; wire [3:0] A, B, C; wire [3:0] Y; assign Y = !S ? ( !M ? A : B ) : C;
と書くのが読みやすく感じます。
あるいは、「条件文では例外的なものを先に処理する」という ソフトウェアでよく知られたスタイルガイドを適用するなら、 元の回路の並び順を変更して
LANG:verilog wire S, M; wire [3:0] A, B, C; wire [3:0] Y; assign Y = S ? C : M ? B : A ;
とすればさらに読みやすいですね。
こちらが読みやすいと思う理由としては、
- C や C++ に慣れていれば S==1'b0 や S==1'b1 は冗長で、それぞれ !S や S と書けば十分
- 3項演算子の連鎖は「ちゃんと括弧やインデントを付ければ」、if のようにも case のようにも使える
- 上記コードで心配された問題はすべてクリアされている
- 何より短く書ける分、一目で内容を理解できる
あたりです。私個人の独自規約としては、上記のような記述を多用することで、
- reg + always を順序回路の生成に
- wire + assign を組み合わせ回路の生成に
それぞれ限定して使うことにしています。
これで、順序回路と組み合わせ回路、どちらを意図しているか一目で分かって良い感じです。
当然、ややこしい計算部分は function にまとめることもありますが、 上記のように三項演算子で簡単に書けるものをわざわざ function にすることは コードにノイズを増やすだけなので避けるようにしています。
1点、
- C や C++ に慣れていれば S==1'b0 や S==1'b1 は冗長で、それぞれ !S や S と書けば十分
に関しては、コンパイラや lint にビット幅のミスマッチを検出させる目的で、 ビット幅の異なる代入や比較に対する警告が1つも出ないようなコーディングを 心がけるのであれば主張を取り下げます。 S==1'b0 や S==1'b1 の記述により、 自動的にすべての箇所におけるビット幅のチェックができるなら、 これらの記述が冗長とは思いません。
ただ私が Xilinx ISE 上でコーディングする限り、コンパイル時の警告をすべてつぶすような コーディングはほぼ不可能で、S==1'b0 や S==1'b1 の記述でコンパイラによるビット幅チェックが容易になることは望めませんでした。
そのような目的がない限り S==1'b0 などの記述は単に冗長だと思います。
上記のスタイルガイド本でも、わざわざそう書かなくても良いかもしれない、 と(あまり乗り気じゃない雰囲気で?)書いている章がありますね。
別の例†
同スタイルガイドでは、
LANG:verilog module DEC2TO4 ( input wire [1:0] AIN, input wire EN, output reg [3:0] DEC ); always @(AIN or EN) if (EN) case (AIN) 2'h0: DEC = 4'b0001; 2'h1: DEC = 4'b0010; 2'h2: DEC = 4'b0100; 2'h3: DEC = 4'b1000; default: DEC = 4'bxxxx; endcase else DEC = 4'b0000; end endmodule
とか、
LANG:verilog module DEC2TO4 ( input wire [1:0] AIN, input wire EN, output wire [3:0] DEC ); function [3:0] CalcDEC; input [1:0] AIN; input EN; begin if (EN) case (AIN) 2'h0: CalcDEC = 4'b0001; 2'h1: CalcDEC = 4'b0010; 2'h2: CalcDEC = 4'b0100; 2'h3: CalcDEC = 4'b1000; default: CalcDEC = 4'bxxxx; endcase else CalcDEC = 4'b0000; end endfunction assign DEC = CalcDEC(AIN, EN); endmodule
などというのも紹介されていましたが、これらも
LANG:verilog module DEC2TO4 ( input wire [1:0] AIN, input wire EN, output wire [3:0] DEC ); assign DEC = !EN ? 4'b0000 : AIN == 0 ? 4'b0001 : AIN == 1 ? 4'b0010 : AIN == 2 ? 4'b0100 : AIN == 3 ? 4'b1000 : 4'bxxxx ; endmodule
で十分だと思います。
コードにノイズが少ない分、何をやっているかが一目瞭然です。
まあこの例であればむしろ、
LANG:verilog module DEC2TO4 ( input wire [1:0] AIN, input wire EN, output wire [3:0] DEC ); assign DEC = { 3'b000, EN } << AIN; endmodule
と書くのが最も回路の意図を良く表わしていると思うのですが、 これだと AIN に不定ビットが含まれるときの動作が異なるので、 厳密にはルール違反でしょうか。
何にしても、回路の意図を最もシンプルに表せる記述を探すことを優先したいです。
たぶんこの記事には批判が多いと思います†
スタイルガイド本に紹介されているあたり、Verilog 界では ? : の連鎖を避けることや、 条件式では S==1'b1 などと比較するのが常識とされているのだと思います。
したがって、上記のような記事を書くとお叱りの声も多く聞こえてきそうです。
当然、個々のチーム内でのコーディング規約を修正してまで無理に勧める気はありませんが、 冷静に上記の回路のどれが一番読みやすいか、ミスが入りにくいかを考えてみると、 結論が変わることもあるのではと思ったため記事にしてみました。
プライオリティロジックの話†
Verilog では本来、 if 文で書いた部分については条件判別ロジックにプライオリティが付くため、 条件判別ロジックの順序をひっくり返すような最適化が抑制されるのだそうです? (Xilinx のツールで実際にどうなのかは未確認)
そういう意味でプライオリティを真に必要とする部分については if で書かれたコードを安易に ? : に書き換えるわけにはいかないですね。
上記の S, M, A, B, C から Y を計算する回路の書き換えは 書き換え後のコードで意図と異なる回路が生成される可能性があるので注意が必要です。
System Verilog では priority if と unique if とを使い分けることができるそうです。
逆に、コードに記述した通りのプライオリティに限定せず、 必要に応じて条件ロジックを最適化して欲しいときには if 文での記述は避けるべきなんですね。
インプリメント時のワーニング欄を見やすくする工夫†
ソフトウェアのプログラミングでもそうなのですが、 最近のコンパイラはかなり賢いので、 バグを含むコードを与えると高確率で的確な警告を出してくれるため、 ちゃんと警告欄に目を光らせていればデバッグの手間を大幅に省くことができます。
そして、コンパイラからの警告を有効に活用するため、 コンパイル時に出る警告は、それが無害なものであることが明らかでも、 コーディングを工夫することで上手に消すことが推奨されています。 したがって通常のソフトウェア開発では、 コードをコンパイルしても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 ですね。
いざとなったらこれを編集するソフトを自作しなければならないかも、 と思っているのですが、すでにどこかにあったりするのでしょうか?
(追記)しかたがないので、結局自分で作る羽目に(泣
電気回路/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 の言語仕様にあれば良いのですが、たぶんできないのですよね。
コメント†
case文の存在、全否定っすねw†
名無しの権兵衛 ()
assign DEC = !EN ? 4'b0000 : AIN == 0 ? 4'b0001 : AIN == 1 ? 4'b0010 : AIN == 2 ? 4'b0100 : AIN == 3 ? 4'b1000 : 4'bxxxx ;
は、やりすぎです。
C や C++ に慣れていれば、こんなコードを書いたら、みんなに嫌われることはご存知かと・・・
- はい、ちまたで3項演算子が毛嫌いされていることは存じています。でも個人的には、"cond1 ? value1 : cond2 ? value2 : value3" のように3項演算子を「3項目でネスト」する分には "if cond1 then value1 elseif cond2 then value2 else value3" と同様に読めば良いだけですので、どんどん使えば良いのにと思っています。「2項目でネスト」は可能な限り避けるべきだし、インデントに規約を設ける必要はありそうですが。 -- 武内(管理人)
- 先生が文章中で述べているRTL設計スタイルガイドの著作者です。?を使うと判りずらい、判りやすいというのはおっしゃるように価値観の違いですのでよろしいかと思いますが、 -- 長谷川裕恭
- この記述だと、AIN == 0 ? 4'b0001 : AIN == 1 ?と不必要なelse ifを記述していることになります。このような不必要なプライオリティ記述は、論理合成ツールで冗長な論理を生成してしまうことがあります。RTL記述のノウハウとしては如何に不必要な論理を(記述として)書かないことを推奨しています -- 長谷川裕恭
- 長谷川様、コメントありがとうございます。三項演算子のプライオリティが冗長な論理を生成しうるというご指摘はその通りであると思います。一方で、case 文についても parallel_case, full_case のような指定がなければプライオリティは付くのかと思っておりました。case 文で書けば冗長な論理の生成は起きないのでしょうか?例えばこんな記事もあるようでした: https://stackoverflow.com/questions/15418636/case-statement-in-verilog -- 武内(管理人)
マイナスになる値は、signedをつけましょう†
通りすがり ()
rp == 8'h00 の時、左辺は 32'hffffffff となって、右辺の 32'h000000ff とは異なると評価されるのですが、これはコードを書いた marsee さんの意図する ところではなかった
これって、そもそも論として、
reg signed [7:0] wp, rp;
と、符号付きの値である事を宣言しないのが、間違いでは?
- これはリングバッファの書き込み、読み出しポインタなので、負値を期待しているわけではなく、意図的にオーバーフローを起こして同じ範囲を繰り返すことを期待していますので、意味的には unsigned で正しいのだと思います。 -- 武内(管理人)
- なるほど・・・ でも、http://www.martinbroadhurst.com/cirque-in-c.html にあるように、unsigned int tail; /* 1 past the last element */ みたいにして、is_fullを等号判定できるようにしてなかったのが残念だった気がしますね・・・ -- 通りすがり
- is_full についてはおっしゃるとおり tail をそのように取るのが良いと思います。その場合にも、もし is_almost_full を実装するなら同様の注意が必要ですね。 -- 武内(管理人)
宣言する名前に工夫する†
[アプロ] (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だっけ? 組み合わせだっけ? すぐに判明します -- [アプロ]
- ふむふむ、何となく分かってきました。どうもありがとうございます。 -- [武内(管理人)]
- 書いたつもりで書いていなかったので、ものすごく古いメッセージへの返信になりますが、ソフトウェアの世界では変数の名前にその型を表わす記号付ける記法を wikipedia:ハンガリアン記法 と呼んで、90年代にはもてはやされていたのですが、最近ではあまり意味がないとして使用頻度が低下しています。 -- 武内(管理人)
- ああでも、Verilog では reg で宣言したにもかかわらず FF を生成しない使い方がありうるので、そういうときには何か記号を付けておいても良さそうですね。 -- 武内(管理人)
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 の副作用について上に追記しました。 -- [武内(管理人)]