コード中に制約を書くときの注意点 のバックアップ(No.7)
更新コード中に制約を埋め込む †
次のコードは 非同期信号を扱うための危ういVerilogライブラリ で紹介している、非同期信号を同期化するためのコードの、以前のバージョンです。
LANG:verilog module double_ff #( parameter INIT = 1'b0 ) ( (* TIG="TRUE" *) input wire idata, input wire oclk, output wire odata ); wire temp; (* IOB="FALSE", RLOC="X0Y0", ASYNC_REG="TRUE" *) FDRSE #( .INIT(INIT) // Initial value of register (1'b0 or 1'b1) ) temp0 ( .Q(temp), // Data output .C(oclk), // Clock input .CE(1'b1), // Clock enable input .D(idata), // Data input .R(1'b0), // Synchronous reset input .S(1'b0) // Synchronous set input ); (* IOB="FALSE", RLOC="X0Y0" *) FDRSE #( .INIT(INIT) // Initial value of register (1'b0 or 1'b1) ) temp1 ( .Q(odata), // Data output .C(oclk), // Clock input .CE(1'b1), // Clock enable input .D(temp), // Data input .R(1'b0), // Synchronous reset input .S(1'b0) // Synchronous set input ); endmodule
このモジュールは1つのプロジェクト中で何十回、何百回も使うことになるため、 上記のように TIG や IOB, ASYNC_REG, RLOC といった制約をコード中に埋め込めれば 大きな助けになります。
そうでなければ、何百もの double_ff のインスタンスに対応する個別の制約を .ucf ファイルに別途記述しなければならないからです。
上記制約は次の意味を持っています。
- idata から temp0 へのパスは遅延解析が必要ないため idata に TIG を付けています
- temp0 や temp1 が IOB に入らないように IOB を付けています
- temp0 は非同期信号を受け取る事が分かっているので ASYNC_REG を付けています
- temp0 と temp1 とが同じスライスに入るように RLOC を付けています
この書き方は ISE コードエディタの Language Templates の記述
[Verilog]-[Synthesis Constructs]-[Coding Examples]-[Misc]-[Asynchronous Input Synchronization (Reduce Issues w/ Metastability]
をほぼ踏襲しているのですが、
そのまま使うにはいくつか問題がありそうなので、解決策を調べてみました。
ISE 11.5 での調査です。
上記コードに行き着くまでの考察は 非同期信号を扱うための危ういVerilogライブラリ を参照して下さい。
コード例 †
問題をはっきりさせるため、下記のあまり意味はないですがシンプルな例で見てみます。
LANG:verilog module main( input clk250MHz, input idata, output odata, output reg odata2 ); DCM_SP #( .CLKIN_PERIOD(4.0), // Specify period of input clock .CLK_FEEDBACK("1X") // Specify clock feedback of NONE, 1X or 2X ) DCM_SP_inst ( .CLK0(clk0), // 0 degree DCM CLK output .CLK90(clk90), // 90 degree DCM CLK output .CLKFB(clk0), // DCM clock feedback .CLKIN(clk250MHz) // Clock input (from IBUFG, BUFG or DCM) ); double_ff #(0) double_ff0 ( .idata(idata), .oclk(clk0), .odata(clk0_idata) ); double_ff #(0) double_ff90 ( .idata(clk0_idata), .oclk(clk90), .odata(clk90_idata) ); assign odata = clk90_idata; always @(posedge clk90) odata2 <= clk0_idata; endmodule
対応する ucf ファイル
LANG:xilinx_xcf NET "clk250MHz" TNM_NET = "clk250MHz"; TIMESPEC TS_clk250MHz = PERIOD "clk250MHz" 4 ns HIGH 50 %;
これは、idata という非同期入力を clk250MHz に同期化し、 さらにその信号を clk250MHz と 90 度ずれた位相を持つクロックに同期化して、 出力します。
入力クロック clk250MHz にタイミング制約が掛かっているので、 それが伝播して clk0 も clk90 も 125MHz で動作するよう制約が掛かります。 解説はDCM を使う場合のクロック周期制約。
PERIOD 制約について †
この記事の趣旨から言えば、クロックに対する制約も .ucf ファイルに書くのではなく、
LANG:verilog module main( (* PERIOD="250MHz" *) input clk250MHz,
のように書いてしまえばよいはずなのですが、これだと、インプリメント中のメッセージで
========================================================================= * HDL Analysis * ========================================================================= Analyzing top module <main>. Module <main> is correct for synthesis. ... Set user-defined property "PERIOD = 250MHz" for signal <clk250MHz> in unit <main>. ... WARNING:Xst:1577 - Converting constraint on signal clk250MHz: 'PERIOD=250MHz' to XCF style constraint NET clk250MHz PERIOD = 250.000000 MHz HIGH 50 %
となって、一見うまく行っているように見えるものの、.pcf に clk250MHz に対するクロック周期制約の記載は見つからず、まったく制約が働いていないように見えます。
上記メッセージが INFO でなく WARNING であるあたり、 何かうまく行っていないことを表しているのかもしれないのですが、 メッセージからは内容を読み取ることができません。
どうなっているのかまだ不明です。
main1 に対する .pcf ファイル †
上記コードをインプリメントすると、.pcf ファイルは次のようになりました。
LANG:xilinx_xcf SCHEMATIC START; PIN DCM_SP_inst_pins<3> = BEL "DCM_SP_inst" PINNAME CLKIN; TIMEGRP clk250MHz = PIN "DCM_SP_inst_pins<3>"; TIMEGRP clk0 = BEL "double_ff0/temp1" BEL "double_ff0/temp0"; TIMEGRP clk90 = BEL "odata2" BEL "double_ff90/temp1" BEL "double_ff90/temp0"; TS_clk250MHz = PERIOD TIMEGRP "clk250MHz" 4 ns HIGH 50%; TS_clk0 = PERIOD TIMEGRP "clk0" TS_clk250MHz HIGH 50%; TS_clk90 = PERIOD TIMEGRP "clk90" TS_clk250MHz PHASE 1 ns HIGH 50%; PIN idata_IBUF_pins<1> = BEL "idata_IBUF" PINNAME OUT; PIN double_ff0/temp1_pins<2> = BEL "double_ff0/temp1" PINNAME Q; PIN "idata_IBUF_pins<1>" TIG; PIN "double_ff0/temp1_pins<2>" TIG; SCHEMATIC END;
クロックに対する記述を除くと、
LANG:xilinx_xcf PIN idata_IBUF_pins<1> = BEL "idata_IBUF" PINNAME OUT; PIN double_ff0/temp1_pins<2> = BEL "double_ff0/temp1" PINNAME Q; PIN "idata_IBUF_pins<1>" TIG; PIN "double_ff0/temp1_pins<2>" TIG;
の部分が double_ff 関連の制約です。
TIG に関する問題点 †
TIG は出力ピンまで遡ってかかる †
.pcf の記述から、double_ff の入力ピンに記述した TIG 制約は、 そこに接続された上流の出力ピンまで遡って付いています。
これはかなり困ったことで、その出力ピンに double_ff
以外のモジュールが接続されていると、
そちらへのパスも遅延解析から除かれてしまうことになります。
本来であれば、下図のように TIG は temp0
への入力のみに掛かって欲しいのですが、、、
上記の例で言えば、double_ff90.idata に TIG が付いているせいで、 clk0_idata -> odata2 のパスにまで TIG 付いてしまっていることになります。
.ucf で一括して TIG 制約を掛ける †
制約ガイド などを色々調べたのですが、制約をコード中に埋め込む限り、 これに対する回避策はなく、やはり .ucf を使わなければならないようです。
ただ、double_ff のように1つのプロジェクト中で多数回インスタンス化されるモジュールに 制約を掛けるために、.ucf に対応する制約を1つ1つ手で書くのは気が遠くなります。
こういったときに楽をするには、 ワイルドカードをうまく使える形にインスタンス名やピン名を工夫すればよいようです。
- idata についていた TIG をはずしました
- FDRSE のインスタンスの名前が、他とダブる可能性のある temp0 や temp1 だったのを、
ほぼ間違いなくユニークな名前となる double_ff_register_first と double_ff_register_second に変更しました
その上で .ucf に、
LANG:xilinx_xcf INST "*/double_ff_register_first" TNM = "double_ff_register"; TIMESPEC TS_DOUBLE_FF = TO "double_ff_register" TIG;
と記述しました。
他のモジュールで double_ff_register_first という名のインスタンスを使ない限り、 これでうまくいきます。何かの拍子にこういう名前のインスタンスが現れるかも、 と心配なら、もっとありえない名前にしても良いかもしれません。
このようにインスタンス名をユニークにすることで、 ワイルドカードを使って制約を掛けられるようになります。
そして、.ucf を使えば TIG を TO で付けられるので、 他の信号線に影響を与えず必要な部分だけに制約を掛けられます。
出力される .pcf では、
LANG:xilinx_xcf TIMEGRP double_ff_register = BEL "double_ff90/double_ff_register_first" BEL "double_ff0/double_ff_register_first"; PATH TS_DOUBLE_FF_path = TO TIMEGRP "double_ff_register"; PATH "TS_DOUBLE_FF_path" TIG;
となっていて、必要な全ての BEL に正しく制約が掛かっていることを確認できます。
IOB および RLOC 制約について †
2つの FF を同一スライスに入れる †
double_ff では非同期信号を遅延の少ない2つの FF で受けるところに意味があるので、 2つの FF は同一スライスに入れて、間の遅延をできる限り小さくする必要があります。
上記の RLOC と IOB はこのために指定しているもので、
- IOB により FF を IOB に入れないことを、
- RLOC により2つの FF を同一スライスに入れることを、
それぞれ指示しています。
PAR して PlanAhead で見たところ、
のように、ちゃんと FF が2つずつペアとして単一スライスに入っていることを確認できました。
一番下のペインで Timing Results ページを見ると
TS_clk0 の中に Path1 というのがあって、
from double_ff0/double_ff_register_first to double_ff0/double_ff_register_second
の Setup には 1.356 ns という Delay が計算されていました。
250MHz では1クロック周期が 4ns なので、Slack は 2.644 となって、 そこそこ大きな値が取れていることが確認できます。
脱線しますが、同じパスの Hold が 0.974 として異なる値になっているのは、 skew などを加味した最大値と最小値が計算されているためなのでしょうね。
同じ理由で、局所的に同じ回路でも、
from double_ff90/double_ff_register_first to double_ff90/double_ff_register_second
の方は、Setup が 1.260 ns で、double_ff0 に比べて小さな値になってます。
この差はクロックスキューの違いが現れているのだと思います。
IOB について †
Language Templates では input ピンに IOB がかかっているのですが、 それだと上記 TIG と同様、上流にまで影響が及んでしまうと考えて、 IOB は FF にかけています。
ですが、この指定を取ってしまっても、RLOC の掛かっている FF が IOB に入ることは無いようなので、これは取ってしまっても構わないようです。
RLOC制約あり | RLOC制約なし | |
IOB制約あり | 同一スライス | 同一スライス |
IOB制約なし | 別スライス | 一部がIOB |
HBLKNM ではだめなので RLOC †
ISE の Language Templates にあるコードでは RLOC の代わりに HBLKNM で配置制約を掛けようとしているのですが、Spartan 3A DSP 向けに実際にコンパイルしてみたところ WARNING:ConstraintSystem:119 が出てうまく行きませんでした。 http://japan.xilinx.com/support/answers/34088.htm によれば、 「HBLKNM をネットに設定すると、パッドにしか伝搬されません」 とのことなので、ここでは HBLKNM ではなく RLOC で指定しています。
異なるパラメータでインスタンス化したときの問題 †
上記 double_ff はパラメータを1つ持っていて、これを使って FF の初期値を制御できます。
そこで main1 で double_ff をインスタンス化している部分を、
LANG:verilog double_ff #(0) double_ff0 ( .idata(idata), .oclk(clk0), .odata(clk0_idata) ); double_ff #(1) double_ff90 ( .idata(clk0_idata), .oclk(clk90), .odata(clk90_idata) );
としたところ、Map 時に以下のエラーが出ました。
ERROR:Pack:2811 - Directed packing was unable to obey the user design constraints (MACRONAME=hset, RLOC=X0Y0) which requires the combination of the symbols listed below to be packed into a single SLICE component. The directed pack was not possible because: There are more than two registers. The symbols involved are: FLOP symbol "double_ff0/double_ff_register_second" (Output Signal = clk0_idata) FLOP symbol "double_ff90/double_ff_register_first" (Output Signal = double_ff90/temp) FLOP symbol "double_ff0/double_ff_register_first" (Output Signal = double_ff0/temp) FLOP symbol "double_ff90/double_ff_register_second" (Output Signal = odata_OBUF)
Map は RLOC="X0Y0" のついた4つの FF をすべて1つのスライスに入れようとしているのですが、 1つのスライスに FF は2つしか入らないので困っているようです。
main1 ではなぜうまくいったのか †
「4つ入らない」というのは言われてみれば当たり前の話で、 そうなると逆にパラメータが等しいときにどうしてうまく行ったのかの方が不思議になるのですが、 この疑問は main1 のインプリメント時に出る次のメッセージで解明されます。
INFO:Xst:1730 - XST has found some RLOC properties in element double_ff90 which is instantiated several times. To handle this constraint XST will add the property "hu_set double_ff90" on each element with RLOC. INFO:Xst:1730 - XST has found some RLOC properties in element double_ff0 which is instantiated several times. To handle this constraint XST will add the property "hu_set double_ff0" on each element with RLOC.
全く同じモジュールが複数回インスタンス化されていると、 それぞれのインスタンスの中の RLOC が混ざってしまわないよう、 自動的にコンパイラが hu_set を付けてくれるようです。
ところが、本来同じモジュールでもパラメータが異なる場合には、 コンパイラからは異なるモジュールと見なされるため、この自動の hu_set 添付が働かず、 結果的に上記のエラーになったと言うことのようです。
double_ff #(0) や double_ff #(1) のどちらか一方でも複数回インスタンス化されていれば、 それらインスタンスの RLOC に hu_set が付くため、上記エラーは回避されることになります。
解決策 †
この問題を根本的に解決するには自分で hu_set を付ければ良いようです。
具体的には、(* RLOC="X0Y0" *) としていた部分を、 (* HU_SET="double_ff_registers", RLOC="X0Y0" *) などとします。
HU_SET は H_SET と同じく RLOC のグループに名前を付けるための制約ですが、 HU_SET では、制約で指定した文字列の前にコンパイラがインスタンスへのパスを付けてくれるので、 同じ制約が複数回インスタンス化されたとき、 それぞれの RLOC を異なるグループとして扱われるのだそうです。
これで、異なるパラメータでインスタンス化したときにも正しくインプリメントできました。
TIG 問題再び †
TIG 制約が上流に遡ってしまうという上記の問題に目をつぶり、 double_ff の idata に (* TIG="TRUE" *) を付けてあるコードに戻って、 ISE のちょっと気になる動作(恐らくバグ?)を確認します。
具体的には、double_ff の入力部分を
LANG:verilog (* TIG="TRUE" *) input wire idata,
とし、.ucf の "*/double_ff_register_first" に対する TIG 制約を取り除きました。
2つの double_ff を同じパラメータ(例えば #(0) ) でインスタンス化したときには、 (上流に遡ってしまうという副作用を除き)正しく TIG 制約が掛かるため、.pcf ファイルに対応する TIG の行が現れますし、
LANG:xilinx_xcf PIN idata_IBUF_pins<1> = BEL "idata_IBUF" PINNAME OUT; PIN "idata_IBUF_pins<1>" TIG; PIN double_ff0/temp1_pins<2> = BEL "double_ff0/temp1" PINNAME Q; PIN "double_ff0/temp1_pins<2>" TIG;
TimeAhead の画面でも TS_clk90 の解析結果には double_ff90/temp0 から double_ff90/temp1 へのパスしか現れません。
ところが、片方を #(0) で、もう片方を #(1) でインスタンス化すると、.pcf ファイルに TIG の行が現れなくなり、結果として TimeAhead の画面でも TS_clk90 の解析結果に double_ff0/temp1 から double_ff90/temp0 への、 クロックドメインをまたぐパスが含まれてしまいます。
すなわちこの場合、TIG がまったく無視されていることになります。
インプリメント時のメッセージ欄にもそれらしい情報は現れないため、 これ以上原因を探ることができてません。
Synthesis 時のメッセージでは、TIG が正しく解釈されていそうな様子が分かりますが、 その後 TIG に関するメッセージは現れません。
このような問題が生じるのが TIG 制約に対してのみなのか、 他にも同様に問題を生じる可能性があるのか、 非常に心配になるのですが、現時点では何とも調べようがありません。
これは、かなり根本的な部分のバグですよね。なぜこれが現時点で放置されているのか、、、 やはりコード中に TIG 制約を埋め込むこと自体が一般的ではないんですかね。
Xilinx の言語テンプレートや制約ガイドなどを見ても、 制約に関して、できること、と、できないこと、推奨される方法、 などがきちんと整理されていないように思います。
そのせいで個々のユーザーが無駄な努力をしなければならない現状は、 何とか改善して欲しいです。
現時点で使っているコード †
以下のようになっています。
LANG:verilog // include in ucf // INST "*/double_ff_register_first" TNM = "double_ff_register"; // TIMESPEC TS_DOUBLE_FF = TO "double_ff_register" TIG; module double_ff #( parameter INIT = 1'b0 ) ( input wire idata, input wire oclk, output wire odata ); wire temp; (* HU_SET="double_ff_registers", RLOC="X0Y0", ASYNC_REG="TRUE" *) FDRSE #( .INIT(INIT) // Initial value of register (1'b0 or 1'b1) ) double_ff_register_first ( .Q(temp), // Data output .C(oclk), // Clock input .CE(1'b1), // Clock enable input .D(idata), // Data input .R(1'b0), // Synchronous reset input .S(1'b0) // Synchronous set input ); (* HU_SET="double_ff_registers", RLOC="X0Y0" *) FDRSE #( .INIT(INIT) // Initial value of register (1'b0 or 1'b1) ) double_ff_register_second ( .Q(odata), // Data output .C(oclk), // Clock input .CE(1'b1), // Clock enable input .D(temp), // Data input .R(1'b0), // Synchronous reset input .S(1'b0) // Synchronous set input ); endmodule
.ucf ファイルに
LANG:xilinx_xcf INST "*/double_ff_register_first" TNM = "double_ff_register"; TIMESPEC TS_DOUBLE_FF = TO "double_ff_register" TIG;
ビット幅の拡張 †
上記モジュールを double_ff_1bit と言う名前に変えた上で、 double_ff を次のようにすると、複数ビットの同期化を簡単に行えます。
BITS パラメータを指定しなければ今まで通り1ビットを受け渡すので、 そのまま置き換えが可能と思います。
ただ、これを使う際には 複数の信号を一度に入力する場合 に書いたように、別途読み出しタイミングを計る機構が必要になります。
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
これを使う場合、.ucf ファイルは次のようにして大丈夫ですね。
LANG:xilinx_xcf INST "*bits[*].double_ff_1bit/double_ff_register_first" TNM = "double_ff_register"; TIMESPEC TS_DOUBLE_FF = TO "double_ff_register" TIG;
ここでの double_ff_1bit はモジュール名ではなく、 double_ff の generate の中に現れるインスタンス名です。
double_ff_1bit モジュールは double_ff 以外からは使わないものとします。