はじめに
Verilog などの RTL 記述言語の多くは、文法上 0/1 以外に X(不定値) や Z(high-Z) を含めた4値を扱えます。
不定値の取り扱いにはおそらく「これが正解」といった絶対的なものはなく、ケースバイケースな気がしております。
コーディングルールを考えるときの悩みの一つではあったりするので、少し整理してみたいと思います。
シミュレーションにおける不定値
例えば、AXI バスの valid 信号が 0 の時、他の信号線は仕様上は don't care です。
このとき他の信号に X が入っていれば、波形ビューワ―上での don't care 区間はビジュアル的に把握できますし、「うっかり valid が下がった後のタイミングでラッチしてしまうコード」を書いてしまっても、不定値が伝搬していきますのですぐに気が付くことができます。
なので、積極的に 不定値X を埋めるコードを書いておけば、検証がやりやすくなる面はあるかと思います。
一方で、
if ( a ) begin // 何か処理 end
のようなときに、a に x が入っていても、単に 偽 になるだけなので 0 が入っているのと同じ動作をしてしまい、テストを通過してバグを取りこぼすなんてこともあります。
ここはシミュレータにもいくつか機能があって、例えば Verilator などは内部的に2値しか使わない関係もあり、不定値を乱数にしたり固定値にしてくれます。 もちろん乱数は乱数ですので偶然テストが通ることはあるのですが、バグの早期発見の確率を上げてくれる要素ではあります。
なので私はよく 4値の扱える Vivado Simulator(xsim) と 2値でシミュレーションする Verilator の両方でテストを流したりもします。そうすると片方では通るのに片方で通らないというようなことが割と発生し、ラピッドプロトタイピングで「まずは大雑把にデバッグして動くようにしてしまいたい」時などに重宝しています。
論理合成時における不定値
論理合成時に不定値を含めるのはまずそのあり方から賛否がありあるかと思います。不定値を含めると当然出てきたネットリストの不定値部分の動作はシンセサイザ次第となってしまいます。これは後工程で出力されたネットリストを取り扱いにくくなることを意味します。 私はそもそもFPGA開発しかやったことないですが、 LSI開発されてる方々だと例えば、後からネットリストレベルでバグ修正して ECO(Engineering Change Order) を出したりされることもあるそうですので、その修正が RTL に戻せなくなったりとか、いろいろと課題がありそうです。
一方で、FPGA 開発なんかの場合、「めんどうなことはシンセサイザに任せてしまえ」というのはありかと思います。
人間が手動で論理圧縮をする場合、例えばカルノー図なんかを書いてみて、論理がシンプルになるようにするには X 部分を 1 に倒すのがいいか、0に倒すのがいいか検討するようなこともできます。
とはいえ、Xのままシンセサイザに合成してもらい、自動での論理圧縮に丸投げするのも、FPGAのように簡単にリコンパイルできる世界だと全然ありな気がします。
シンセサイザは X をどう扱っても良いわけですから、0 にする /1 にする/値保持する、などの中から一番都合のいいものを探す努力をしてくれます。
ちなみに私はよく
always_ff @(posedge aclk) begin if ( ~aresetn ) begin axi4s_tdata <= 'x; axi4s_tvalid <= 1'b0; else if ( aclken ) begin axi4s_tdata <= ほげほげ; axi4s_tvalid <= ふがふが; end end
のようなコードを書きます(賛否あるのですが)。
FPGAのシンセサイザは X を如何様に扱っても合法ですので、多くの場合もっとも論理圧縮の効く「リセットしない」に合成してくれます。
この書き方のメリットは tdata と tvalid のようなまとまったものをセットで書けるという点です。
もちろんお堅い LSI 開発だと、 「always文1つに変数一個まで」とか、そもそも「 always文自体禁止」とかのコーディングルールも聞きますので、FPGAでのラピッドプロト以外では許されないかなり荒っぽい書き方なのかもしれません。
このように、不定値の扱いはなかなか賛否両論ありそうに思う分野です。
それはバグなのか?
ここで、例として最初に書いた「AXIバスで、うっかり valid が下がった後のタイミングでdataをラッチしてしまうコード」を書いて商品を販売してしまった場合を考えてみます。
ですが、ここでたまたま「valid が下がっても data はキープされる実装になっていたのですべての場合で正常に動作する」ことが判明したとしましょう。
これは少なくとも製品をリコールする必要はないかと思います。
そういう意味でこれをバグと呼ぶかどうかの議論もあると思いますし、不定値をフェールセーフな値に倒せないかの議論にもなります。
フェールセーフな書き方
そしてここまでの話は don't care 部分(不定値)の扱いを
- 論理圧縮が効くように扱うべきなのか
- フェールセーフな方向に定義するべきなのか
という議論にもつながると思います。
手元にある定本 ASICの論理回路設計のジョンソンカウンタでマイナーループからの脱出について技法が書かれていますが、ソフトエラー(宇宙線でのbit化け)などのバグ以外の要因も含めて、万一よからぬ状態になった場合に復帰するように不定値部分をあえて手動で定義することは有効なケースは大いにありそうな気がします。
この際、何が安全かはシンセサイザは知る由がありませんので、プログラマが明示する必要が出てきます。この点でもまた高品質なLSIが作りたければ「合成記述に不定値は使うべきではない」という話の合理性にも繋がってきます。
(FPGAの場合、ダウンロードしている回路自体にソフトエラーが起こりうるので、こういう設計をする効果が如何ほどあるかは 謎ですが)。
終わりに
取り扱っているものの特性や、立ち位置によって、不定値にどう立ち向かうべきか千差万別な気がしております。
ラピッドプロトタイピングでは自動化する部分を最大化して早く安く検証を行いたいでしょうし、ASIC開発などでは長い製品ライフタイムの中でトータルコストを落としたければ設計時に低レイヤまで手動で管理した方が良いということになるかと思います。
私はFPGAなラピッドプロトがメインですが、それ前提にあまり乱暴なコード例ばかりを書いていると、「変な事を広めるな」と、マサカリが飛んできそうな気もしますし、悩ましいところです。
皆さまは不定値ってどう使われていますか? 私はかなり我流でやっているので、世間ではどういう取り扱いがなされているのかとても興味があります。 よろしければコメントお寄せください