電気回路/HDL/Verilogメモ の履歴(No.2)
更新内部信号線を pullup / pulldown できないのでその代替案†
以下は Xilinx ISE 14.2 で試した結果です。
pullup/pulldown の基本†
Verilog ではバス信号線などを pullup / pulldown で引っ張ることにより、すべてのドライバが highZ の時の値を 1 または 0 に決められることになっています。
通常そのようなトライステートなバスは FPGA 外部に出て行く信号線を記述するときに使います。
外部ピンをトライステートにする場合とは対照的に、
FPGA 内部のネットを疑似トライステート的に記述した場合には、
その回路は論理合成時に マルチプレクサ/and/or などの論理回路に置き換えられて、
「結果的にトライステートの場合と同様の動作をするよう」取りはからってもらえます。
たとえばこんな回路。
LANG:verilog wire oe1, oe2, oe3; wire [BUS_BITS-1:0] data1, data2, data3; wire [BUS_BITS-1:0] bus; assign bus = oe1 ? data1 : {BUS_BITS{1'bz}}; assign bus = oe2 ? data2 : {BUS_BITS{1'bz}}; assign bus = oe3 ? data3 : {BUS_BITS{1'bz}};
これは次のような警告を出しつつ問題なく論理合成できて、
LANG:console WARNING:Xst:2042 - Unit main: 8 internal tristates are replaced by logic (pull-up yes): bus<0>, bus<2>, bus<3>, bus<4>, bus<5>, bus<6>, bus<7>.
デフォルトでは pullup されているかのごとく highZ 時の値は 1 になります。
これを明示的に pulldown するには、Verilog 文法的には
LANG:verilog // bus 信号線を pulldown して、何も出力されないときは 0 とする generate genvar bus_pull_i; for(bus_pull_i=0; bus_pull_i<BUS_BITS; bus_pull_i=bus_pull_i+1) begin: bus_pull pulldown(bus[bus_pull_i]); end endgenerate
を付け足せば良いことになるのですが・・・実は後述の通りこのコードは論理合成できません。
もちろん 3 つの assign と pulldown をまとめて
LANG:verilog assign bus = oe1 ? data1 : oe2 ? data2 : oe3 ? data3 : {BUS_BITS{1'b0}};
と書いてしまえば論理合成可能で、得られる結果も同じなのですが、
data1, data2, data3 をバスに繋ぐコードは通常 あちこちのモジュールに分散しているので、 pulldown で「それ以外」の場合を一度に書けたらうれしいと。
内部信号線に付けた pullup/pulldown は論理合成できない†
上記のコードを論理合成しようとすると、 Synthesize でエラーになります。
LANG:console ERROR:Xst:850 - "main.v" line 40: Unsupported gate instantiation.
Verilog のゲートプリミティブ記述 pulldown の代わりに PULLDOWN モジュールを使って
LANG:verilog generate genvar bus_pull_i; for(bus_pull_i=0; bus_pull_i<BUS_BITS; bus_pull_i=bus_pull_i+1) begin: bus_pull PULLDOWN bus_pd(.O(bus[bus_pull_i])); end endgenerate
のように PULLDOWN してやると、Synthesize は通るようになります。
でも Translate で
LANG:console ERROR:Xst:2617 - Data Corruption(Timing): Arrival time traversal failed on Ugate element
となってやはり論理合成できない。
どうやら PULLDOWN/PULLUP モジュールは IOB に対するプルアップ・プルダウン制約に 特化した記述なようで、内部信号線には使えないみたいです。
そこで代替案†
pullup/pulldown の代わりに wor/wand を使うと、同じ結果を論理合成可能な形で得られます。
LANG:verilog wire oe1, oe2, oe3; wire [BUS_BITS-1:0] data1, data2, data3; wor [BUS_BITS-1:0] bus_pulled_down = {BUS_BITS{1'b0}}; assign bus_pulled_down = oe1 ? data1 : {BUS_BITS{1'bz}}; assign bus_pulled_down = oe2 ? data2 : {BUS_BITS{1'bz}}; assign bus_pulled_down = oe3 ? data3 : {BUS_BITS{1'bz}}; wand [BUS_BITS-1:0] bus_pulled_up = {BUS_BITS{1'b1}}; assign bus_pulled_up = oe1 ? data1 : {BUS_BITS{1'bz}}; assign bus_pulled_up = oe2 ? data2 : {BUS_BITS{1'bz}}; assign bus_pulled_up = oe3 ? data3 : {BUS_BITS{1'bz}};
wor に 0 を繋いでおけば、他から 1 が繋がれれば 1 を出力しますが、 何も繋がなければゼロのままになります。これは pulldown と同じ結果です。
同様に、
wand に 1 を繋いでおけば、他から 0 が繋がれれば 0 を出力しますが、 何も繋がなければ 1 のままになります。これは pullup と同じ結果です。
デバッグ時の注意点†
ただしこのようにしてしまうと回路のバグで複数のドライバが同時に 値を出力した場合にもシミュレーション時の結果が x にならず、 バグの発見が遅れてしまう可能性があります。
仕方がないので、現在のところ シミュレーション時には pullup/pulldown を使い、 論理合成時には wand/wor を使うようなコードを書いてみています。
LANG:verilog `ifdef XILINX_ISIM wire [BUS_BITS-1:0] bus; generate genvar bus_pull_i; for(bus_pull_i=0; bus_pull_i<BUS_BITS; bus_pull_i=bus_pull_i+1) begin: bus_pull pulldown(bus[bus_pull_i]); end endgenerate `else wor [BUS_BITS-1:0] bus = {BUS_BITS{1'b0}}; `endif // XILINX_ISIM
どなたか、普通ならこうするよ、とか、もっと良い方法があるよ、とかあれば教えて下さい。
この記事を書いてから発見した記述†
VHDL ではデフォルトの pullup を pulldown に上書きするために attribute を指定することが可能と書かれていました。
この記述がシミュレーションでも効果を発揮するのかどうか未確認です。 少なくともデフォルトの pullup はシミュレーションでは効かないので、 pullup/down を指定しない内部トライステートバスはシミュレーションと 実装後とで異なる動作をすることを確認済みです。
LANG:VHDL -- Data Bus Pull Down (virtual) attribute tristate2logic_pullup: string; attribute tristate2logic_pullup of DATABUS: signal is "no"; -- 0 in case the bus is not driven
別の人は、内部トライステートを使わずすべてちゃんとロジックで記述すべきだと書いていますね。
上の wand/wor を使った回路は一応 「ロジックで記述」 したことになっているので、 そちらがむしろ正攻法なのかもしれませんね。
そうだとすれば、シミュレーション時にトライステートバスを使うことで、 出力がぶつからないことを容易に確認するという手法の方がむしろ「メモ」に値するのかも。