コード中に制約を書くときの注意点 の履歴(No.3)
更新コード中に制約を埋め込む†
次のコードは 非同期信号を扱うための危うい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 の記述をほぼ踏襲しているのですが、 そのまま使うにはいくつか問題がありそうで、解決策を調べてみました。
ISE 11.5 での調査です。
コード例†
問題をはっきりさせるため、下記のあまり意味はないですがシンプルな例で見てみます。
LANG:verilog module main1 ( input clk_125MHz_in, input idata, output odata ); DCM_SP #( .CLKIN_PERIOD(8.0), // Specify period of input clock .CLK_FEEDBACK("1X") // Specify clock feedback of NONE, 1X or 2X ) DCM_SP_inst ( .CLK0(clk_125MHz), // 0 degree DCM CLK output .CLK90(clk_125MHz90), // 90 degree DCM CLK output .CLKFB(clk_125MHz), // DCM clock feedback .CLKIN(clk_125MHz_in) // Clock input (from IBUFG, BUFG or DCM) ); double_ff double_ff0 ( .idata(idata), .oclk(clk_125MHz), .odata(clk0_idata) ); double_ff double_ff90 ( .idata(clk0_idata), .oclk(clk_125MHz90), .odata(clk90_idata) ); assign odata = clk90_idata; endmodule
対応する ucf ファイル
NET "clk_125MHz_in" TNM_NET = "clk_125MHz_in"; TIMESPEC TS_clk_125MHz_in = PERIOD "clk_125MHz_in" 8 ns HIGH 50 %;
これは、idata という非同期入力を clk_125MHz_in に同期化し、 さらにその信号を clk_125MHz_in と 90 度ずれた位相を持つクロックに同期化して、 出力します。
入力クロック clk_125MHz_in タイミング制約が掛かっているので、 clk_125MHz, clk_125MHz90 も 125MHz で動作するよう制約が掛かります。
PERIOD 制約について†
この記事の趣旨から言えば、クロックに対する制約も .ucf ファイルに書くのではなく、
LANG:verilog module main( (* PERIOD="125MHz" *) input clk_125MHz_in, input idata, output odata );
のように書いてしまえばよいはずなのですが、これだと、インプリメント中のメッセージで
WARNING:Xst:1577 - Converting constraint on signal clk_125MHz_in: 'PERIOD=125MHz' to XCF style constraint NET clk_125MHz_in PERIOD = 125.000000 MHz HIGH 50 %
となって、一見うまく行っているように見えるものの、
WARNING:ConstraintSystem:3 - Constraint <TIMESPEC TS_clk_125MHz = PERIOD "clk_125MHz" TS_clk_125MHz_in HIGH 50%>: This constraint will be ignored because the relative clock constraint named 'TS_clk_125MHz_in' was not found.
WARNING:ConstraintSystem:3 - Constraint <TIMESPEC TS_clk_125MHz90 = PERIOD "clk_125MHz90" TS_clk_125MHz_in PHASE 2 ns HIGH 50%>: This constraint will be ignored because the relative clock constraint named 'TS_clk_125MHz_in' was not found.
となって、正しく制約が掛かりませんでした。
実際 .pcf にはクロック周期制約の記載が入らないようで、どうなっているのかまだ不明です。
main1 に対する .pcf ファイル†
上記コードをインプリメントすると、.pcf ファイルは次のようになりました。
SCHEMATIC START; TIMEGRP clk_125MHz90 = BEL "double_ff90/double_ff_temp1" BEL "double_ff90/double_ff_temp0"; TIMEGRP clk_125MHz90_0 = BEL "double_ff90/double_ff_temp1" BEL "double_ff90/double_ff_temp0"; TIMEGRP clk_125MHz = BEL "double_ff0/double_ff_temp1" BEL "double_ff0/double_ff_temp0"; TIMEGRP clk_125MHz_0 = BEL "double_ff0/double_ff_temp1" BEL "double_ff0/double_ff_temp0"; PIN DCM_SP_inst_pins<3> = BEL "DCM_SP_inst" PINNAME CLKIN; TIMEGRP clk_125MHz_in = PIN "DCM_SP_inst_pins<3>"; TS_clk_125MHz_in = PERIOD TIMEGRP "clk_125MHz_in" 8 ns HIGH 50%; TS_clk_125MHz = PERIOD TIMEGRP "clk_125MHz" TS_clk_125MHz_in HIGH 50%; TS_clk_125MHz90 = PERIOD TIMEGRP "clk_125MHz90" TS_clk_125MHz_in PHASE 2 ns HIGH 50%; TS_clk_125MHz_0 = PERIOD TIMEGRP "clk_125MHz_0" TS_clk_125MHz_in HIGH 50%; TS_clk_125MHz90_0 = PERIOD TIMEGRP "clk_125MHz90_0" TS_clk_125MHz_in PHASE 2 ns HIGH 50%; PIN idata_IBUF_pins<1> = BEL "idata_IBUF" PINNAME OUT; PIN double_ff0/double_ff_temp1_pins<2> = BEL "double_ff0/double_ff_temp1" PINNAME Q; PIN "idata_IBUF_pins<1>" TIG; PIN "double_ff0/double_ff_temp1_pins<2>" TIG; SCHEMATIC END;
クロックに対する記述を除くと、
PIN idata_IBUF_pins<1> = BEL "idata_IBUF" PINNAME OUT; PIN double_ff0/double_ff_temp1_pins<2> = BEL "double_ff0/double_ff_temp1" PINNAME Q; PIN "idata_IBUF_pins<1>" TIG; PIN "double_ff0/double_ff_temp1_pins<2>" TIG;
の部分が double_ff 関連の制約です。
TIG に関する問題点†
TIG は出力ピンまで遡ってかかる†
.pcf の記述から、double_ff の入力ピンに記述した TIG 制約は、 そこに接続された出力ピンまで遡って付いています。
これはかなり困ったことで、その出力ピンに double_ff 以外のモジュールが接続されていると、
そちらへのパスも遅延解析から除かれてしまうことになります。
本来であれば、下図のように TIG は temp0
への入力のみに掛かって欲しいのですが、、、
.ucf で一括して TIG 制約を掛ける†
制約ガイドなどを色々調べたのですが、制約をコード中に埋め込む限り、 これに対する回避策はなく、やはり .ucf を使わなければならないようです。
で、double_ff のように1つのプロジェクト中で多数回インスタンス化されるモジュールに .ucf で制約を掛けるには、ワイルドカードをうまく使える形にすればよいようです。
- idata についていた TIG をはずしました
- FDRSE のインスタンスの名前を、他とダブる可能性のある temp0 や temp1 からユニークな名前として double_ff_temp0 と double_ff_temp1 としました
その上で .ucf に、
INST "*/double_ff_temp0" TNM = "double_ff_register"; TIMESPEC TS_DOUBLE_FF = TO "double_ff_register" TIG;
と記述しました。
インスタンス名をユニークにすることで、ワイルドカードを使って制約を掛けられるようになります。
また、TIG を TO で付けることにより、他の信号線に影響を与えず必要な部分だけに 制約を掛けられます。
出力される .pcf では、
TIMEGRP double_ff_register = BEL "double_ff90/double_ff_temp0" BEL "double_ff0/double_ff_temp0"; PATH TS_DOUBLE_FF_path = TO TIMEGRP "double_ff_register"; PATH "TS_DOUBLE_FF_path" TIG;
となっていて、正しく制約が掛かっていることを確認できます。
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_clk_125MHz の中に Path1 というのがあって、
from double_ff0/double_ff_temp0 to double_ff0/double_ff_temp1
の Setup には 1.448 ns という Delay が計算されていました。
125MHz では1クロック周期が 8ns なので、Slack は 6.552 と、 十分に大きな値が取れていることが確認できます。
脱線しますが、同じパスの Hold が 1.022 として異なる値になっているのは、 skew などを加味した最大値と最小値が計算されているためなのでしょうね。
局所的に同じ回路でも、
from double_ff90/double_ff_temp0 to double_ff90/double_ff_temp1
の方は、1.535 ns となっていて、クロックスキューのためなのか、
SliceM と SliceL との違いなのか、なかなか興味深いです。
IOB について†
Language Templates では input ピンに IOB がかかっているのですが、 それだと上記 TIG と同様、上流にまで影響が及んでしまうと考えて、 IOB は FF にかけています。
ですが、この指定を取ってしまっても、RLOC の掛かっている FF が 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(clk_125MHz), .odata(clk0_idata) ); double_ff #(1) double_ff90 ( .idata(clk0_idata), .oclk(clk_125MHz90), .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_temp0" (Output Signal = double_ff0/temp) FLOP symbol "double_ff0/double_ff_temp1" (Output Signal = clk0_idata) FLOP symbol "double_ff90/double_ff_temp0" (Output Signal = double_ff90/temp) FLOP symbol "double_ff90/double_ff_temp1" (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_temp0" に対する TIG 制約を取り除きました。
2つの double_ff を同じパラメータ(例えば #(0) ) でインスタンス化したときには、 (上流に遡ってしまうという副作用を除き)正しく TIG 制約が掛かるため、.pcf ファイルに対応する TIG の行が現れますし、
PIN double_ff0/double_ff_temp1_pins<2> = BEL "double_ff0/double_ff_temp1" PINNAME Q; PIN "double_ff0/double_ff_temp1_pins<2>" TIG; PIN idata_IBUF_pins<1> = BEL "idata_IBUF" PINNAME OUT; PIN "idata_IBUF_pins<1>" TIG;
TimeAhead の画面でも TS_clk_125MHz90 の解析結果には double_ff90/double_ff_temp0 から double_ff90/double_ff_temp1 へのパスしか現れません。
上述の通り、このパスは 1.525ns しかかからないため、Slack は 6.475ns 取れています。
ところが、片方を #(0) で、もう片方を #(1) でインスタンス化すると、.pcf ファイルに TIG の行が現れなくなり、結果として TimeAhead の画面でも TS_clk_125MHz90 の解析結果に double_ff0/double_ff_temp1 から double_ff90/double_ff_temp0 への、 クロックドメインをまたぐパスが含まれてしまいます。
Timing constraint: TS_clk_125MHz90 = PERIOD TIMEGRP "clk_125MHz90" TS_clk_125MHz_in PHASE 2 ns HIGH 50%; 2 paths analyzed, 2 endpoints analyzed, 0 failing endpoints 0 timing errors detected. (0 setup errors, 0 hold errors, 0 component switching limit errors) Minimum period is 5.720ns. -------------------------------------------------------------------------------- Slack (setup path): 0.570ns (requirement - (data path - clock path skew + uncertainty)) Source: double_ff0/double_ff_temp1 (FF) Destination: double_ff90/double_ff_temp0 (FF) Requirement: 2.000ns Data Path Delay: 1.468ns (Levels of Logic = 0) Clock Path Skew: 0.038ns (3.468 - 3.430) Source Clock: clk_125MHz rising at 0.000ns Destination Clock: clk_125MHz90 rising at 2.000ns Clock Uncertainty: 0.000ns Maximum Data Path: double_ff0/double_ff_temp1 to double_ff90/double_ff_temp0 Location Delay type Delay(ns) Physical Resource Logical Resource(s) ------------------------------------------------- ------------------- SLICE_X20Y97.XQ Tcko 0.631 clk0_idata double_ff0/double_ff_temp1 SLICE_X20Y96.BY net (fanout=1) 0.451 clk0_idata SLICE_X20Y96.CLK Tdick 0.386 odata_OBUF double_ff90/double_ff_temp0 ------------------------------------------------- --------------------------- Total 1.468ns (1.017ns logic, 0.451ns route)
すなわちこの場合、TIG がまったく無視されていることになります。
インプリメント時のメッセージ欄にもそれらしい情報は現れないため、 これ以上原因を探ることができてません。
Synthesis 時のメッセージでは、TIG が正しく解釈されていそうな様子が分かりますが、 その後 TIG に関するメッセージは現れません。
Analyzing module <double_ff.1> in library <work>. INIT = 32'sb00000000000000000000000000000000 Module <double_ff.1> is correct for synthesis. Set user-defined property "ASYNC_REG = TRUE" for instance <double_ff_temp0> in unit <double_ff.1>. Set user-defined property "HU_SET = double_ff_registers" for instance <double_ff_temp0> in unit <double_ff.1>. Set user-defined property "INIT = 0" for instance <double_ff_temp0> in unit <double_ff.1>. Set user-defined property "RLOC = X0Y0" for instance <double_ff_temp0> in unit <double_ff.1>. Set user-defined property "HU_SET = double_ff_registers" for instance <double_ff_temp1> in unit <double_ff.1>. Set user-defined property "INIT = 0" for instance <double_ff_temp1> in unit <double_ff.1>. Set user-defined property "RLOC = X0Y0" for instance <double_ff_temp1> in unit <double_ff.1>. Set user-defined property "TIG = TRUE" for unit <double_ff.1>. Analyzing module <double_ff.2> in library <work>. INIT = 32'sb00000000000000000000000000000001 Module <double_ff.2> is correct for synthesis. Set user-defined property "ASYNC_REG = TRUE" for instance <double_ff_temp0> in unit <double_ff.2>. Set user-defined property "HU_SET = double_ff_registers" for instance <double_ff_temp0> in unit <double_ff.2>. Set user-defined property "INIT = 1" for instance <double_ff_temp0> in unit <double_ff.2>. Set user-defined property "RLOC = X0Y0" for instance <double_ff_temp0> in unit <double_ff.2>. Set user-defined property "HU_SET = double_ff_registers" for instance <double_ff_temp1> in unit <double_ff.2>. Set user-defined property "INIT = 1" for instance <double_ff_temp1> in unit <double_ff.2>. Set user-defined property "RLOC = X0Y0" for instance <double_ff_temp1> in unit <double_ff.2>. Set user-defined property "TIG = TRUE" for unit <double_ff.2>.
このような問題が生じるのが TIG 制約に対してのみなのか、 他にも同様に問題を生じる可能性があるのか、 非常に心配になるのですが、現時点では何とも調べようがありません。
これは、かなり根本的な部分のバグですよね。なぜこれが現時点で放置されているのか、、、 やはりコード中に TIG 制約を埋め込むこと自体が一般的ではないんですかね。
Xilinx の言語テンプレートや制約ガイドなどを見ても、 制約に関して、できること、と、できないこと、推奨される方法、 などがきちんと整理されていないように思います。
そのせいで個々のユーザーが無駄な努力をしなければならない現状は、 何とか改善して欲しいです。
現時点で使っているコード†
以下のようになっています。
LANG:verilog 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_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 ); (* HU_SET="double_ff_registers", RLOC="X0Y0" *) FDRSE #( .INIT(INIT) // Initial value of register (1'b0 or 1'b1) ) double_ff_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
.ucf ファイルに
INST "*/double_ff_temp0" TNM = "double_ff_register"; TIMESPEC TS_DOUBLE_FF = TO "double_ff_register" TIG;