Xilinx ISE におけるの制約の与え方 の履歴(No.13)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 電気回路/HDL/Xilinx ISE におけるの制約の与え方 へ行く。
Xilinx ISE を使った FPGA 開発における制約の書き方と満たし方を勉強する†
FPGA や CPLD の開発では、回路の動作を HDL 言語で正しく記述するだけではだめで、 その回路がきちんと要求されるタイミングで動くことが必要になります。
FPGAの部屋 の marsee さん曰く、 「XilinxのFPGAはHDLが書けても、まだ半分しかマスターできたことにならないと思っている。」 とのことで、実際後半戦では FPGA 内部の構造まで踏み込んだ理解が必要だったり、 初心者にとってはいろいろと苦労が多いです。。。
ということで、動作クロックを高めなければならなかったり、 高速な周辺機器とやりとりをしなければならなかったりするときに重要になる、 「制約」の使い方について勉強する羽目になりました。
FPGA 回路設計での「制約 (constraint)」は、
- 回路の動作速度やタイミングに対する要求をツールに伝える
- ツールが要求を満たすことができないときに、実装のヒント(配置の仕方など)を与える
の2つの目的で使われるようです。
以下では、私が初めて FPGA 開発をする際に使った種々の制約について、 考え方や、ツールの使い方をメモしたものです。
いかんせん初めてなので、間違いなども多いかもしれません。 お気づきの点があればコメントをお願いします。
というか、まだ実機で試してない状況で書いているので、
鵜呑みにしないように(?!)お願いします。。。
(現在徐々に試しつつ、加筆・修正を加えています)
情報源†
以下の内容は主に、FPGA の部屋 で学んだ内容に、私の経験をちょっとだけ加えたものです。
- FPGAの部屋 - UCFの書き方
http://marsee101.web.fc2.com/ucf_kakikata.html
貴重な情報を公開して下さっている著者の marsee さんに深く感謝します。
ピン配置および入出力タイプの制約†
すでに仕様の決まっている基板に対して IP 設計を行う場合には、 始めからピン配置が決まっているので、それをツールに伝えます。
一方で、基板の設計と IP 設計とを同時に行う場合には、 大まかな位置だけを指定しておき、後はツールに任すこともできるようです。
制約を与えるには、
- プロジェクトの最上位モジュール (Top Module) に必要な input / output / inout ポートを実装
- ISE Project Navigator の Hierarchy ペインで Top Module を選択
- Processes ペインで [User Constraints] - [I/O Pin Planning (PlanAhead) - Pre-Synthesis] をダブルクリック
として GUI で行っても良いのですが、
http://marsee101.blog19.fc2.com/blog-entry-25.html
に書かれているとおり、.ucf ファイルをテキストとして編集することで、
一度に複数のピンに対して IO レベルを指定するようなことも可能です。
テキストで制約を与えるのであれば、
- プロジェクトの最上位モジュール (Top Module) に必要な input / output / inout ポートを実装
- ISE Project Navigator の Hierarchy ペインで Top Module を右クリック
- [New Source] から Implementation Constraints File を選んで、適当な名前でファイルを作成
- できた .ucf を選択し、Processes ペインで [User Constraints] - [Edit Constraints (Text)]
という手順です。
もちろん、すでにプロジェクトに .ucf ファイルが存在すれば、1.〜3. は必要ありません。
制約の書き方としては、
NET "eth_txd[0]" LOC = J8; NET "eth_txen" LOC = D3;
というようなピン毎の設定の他、
NET "eth_*" SLEW = FAST;
のように、* や ? などのワイルドカードを使った一括指定が可能です。
「これらのピンの中から適当なものを選んでくれ」という制約を与えるには、 LOC の後ろに可能なピンをカンマ区切りで与えます。
NET "eth_txen" LOC = D3, D4, D5;
クロック周期の制約†
次に指定したくなるのがクロック周期に対する制約です。
http://marsee101.blog19.fc2.com/blog-entry-26.html で触れられているように、 Timing Constraints ツールから設定するのが楽なようです。
起動方法は、
- Hierarchy ペインで .ucf ファイルをダブルクリック
です。
画面左の Constraint Type から Clock Domains を選択すると、 Unconstrained Clocks の部分にクロックの一覧が出るので、 ダブルクリックして [Clock signal definition] - [Specify time] - [Time] に周期を入力します。
DCM を使う場合のクロック周期制約†
ここで注意が必要なこととして、 DCM を使って生成されるクロック信号に対しては クロック周期制約を掛けることができません。
えーと、できない、というのはちょっと語弊があって、
DCM に与えるクロックに制約を掛けておけば、
生成されるクロックに対しても自動的に制約がかかるため、
個別に制約を掛ける必要がない、というのが正しいです。
参照 >> http://marsee101.blog19.fc2.com/blog-entry-1335.html
というわけで、1つだけ制約を掛けておけば、 そこから生成されるすべてのクロックに正しい制約が掛かることになります。
クロックドメインをまたぐ信号を除外†
上記のように DCM で生成される複数のクロックに対して制約が掛かっている状況では、 ツールは関連するクロックドメイン間での信号の受け渡しについても、 律儀に遅延解析を行います。
通常、そのようなインタークロックな信号は、 受け渡しに特別な注意が必要になるため、 ロジック側でハンドシェイクその他の同期処理を入れて、 間違いのないデータの受け渡しを行うよう注意して HDL を書きます。
ところがツールはそのような高レベルの同期処理を理解できないため、 同期処理を入れなくても、遅延時間のみで正確な受け渡しのできる程度まで クロック周期を落とすよう指示してきます。 このため、インタークロックな信号に対して遅延解析が行われると、 非常識なほど低いクロック周波数が算出されてしまいます。
そこで、クロックドメインをまたぐ信号について、 「遅延解析を行わない (TIG : Timing Ignore a net)」という制約(?)を 付けることで、正しい周波数解析が行えるようにします。
一番間違いのない方法は、 インタークロックの同期処理を行っている HDL ソースに TIG 制約を埋め込む方法です。
非同期信号を扱うための危ういVerilogライブラリ にいくつか実例があります。
そもそもインタークロック信号の扱いには細心の注意を払う必要があるため、 そのような箇所には場当たり的な対処を行うのではなく実績のあるライブラリ (上記のような危うい物では無く?!)を用いるのが得策です。 そしてライブラリコード内に TIG 制約が記述されていれば、 個別のコードやプロジェクトに TIG を記述することはほとんど無くなります。
プロジェクトコードに TIG を書かなければならないときは、本当にそれが必要なのか、 また、それで正しく動くのか、をよく考えてからにすべきだと思います。
それでも、どうしても、すべてのインタークロック信号に1つ1つ TIG 制約を掛けるのが面倒、 という場合には、http://marsee101.blog19.fc2.com/blog-entry-27.html のように 一括で TIG 制約を掛ける方法もあります。
LANG:verilog BUFG clk_125MHz_bufg ( .I(clk_125MHz_pre), .O(clk_125MHz)); BUFG clk_100MHz_bufg ( .I(clk_100MHz_pre), .O(clk_100MHz)); IBUFG clk_125MHz_in_ibufg ( .I(clk_125MHz_in), .O(clk_125MHz_in1)); DCM_SP #( .CLKDV_DIVIDE(1.0), // Divide by: 1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5,6.0,6.5 // 7.0,7.5,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0 or 16.0 .CLKFX_DIVIDE(5), // Can be any integer from 1 to 32 .CLKFX_MULTIPLY(4), // Can be any integer from 2 to 32 .CLKIN_DIVIDE_BY_2("FALSE"), // TRUE/FALSE to enable CLKIN divide by two feature .CLKIN_PERIOD(8.0), // Specify period of input clock .CLKOUT_PHASE_SHIFT("NONE"), // Specify phase shift of NONE, FIXED or VARIABLE .CLK_FEEDBACK("1X"), // Specify clock feedback of NONE, 1X or 2X .DESKEW_ADJUST("SYSTEM_SYNCHRONOUS"), // SOURCE_SYNCHRONOUS, SYSTEM_SYNCHRONOUS or // an integer from 0 to 15 .DLL_FREQUENCY_MODE("HIGH"), // HIGH or LOW frequency mode for DLL .DUTY_CYCLE_CORRECTION("TRUE"), // Duty cycle correction, TRUE or FALSE .PHASE_SHIFT(0), // Amount of fixed phase shift from -255 to 255 .STARTUP_WAIT("FALSE") // Delay configuration DONE until DCM LOCK, TRUE/FALSE ) DCM_inst ( .CLK0(clk_125MHz_pre), // 0 degree DCM CLK output .CLKFX(clk_100MHz_pre), // DCM CLK synthesis out (M/D) .CLKFB(clk_125MHz), // DCM clock feedback .CLKIN(clk_125MHz_in1), // Clock input (from IBUFG, BUFG or DCM) .PSEN(1'b0), // Dynamic phase adjust enable input .RST(1'b0) // DCM asynchronous reset input );
のようにして生成したクロックに対して、.ucf に次のように書きます。
PIN "DCM_inst.CLK0" TNM = "CLK125MHz"; PIN "DCM_inst.CLKFX" TNM = "CLK100MHz"; TIMESPEC TS_IGNORE1 = FROM "CLK125MHz" TO "CLK100MHz" TIG ; TIMESPEC TS_IGNORE2 = FROM "CLK100MHz" TO "CLK125MHz" TIG ;
ここでは、DCM_inst の、CLK0 および CLKFX ピンにそれぞれ CLK125MHz、 CLK100MHz という名前をつけ、CLK125MHz から CLK100MHz、あるいは逆の CLK100MHz から CLK125MHz への信号すべてに TIG 制約を掛けています。
可能ではありますが、やはり個別に TIG を掛けるのが正しいスタンスだと感じています。
その他の信号への TIG 制約†
インタークロックな信号以外にも、数クロック程度の遅延を許容できる信号、 例えば割り込み信号線や、ステートマシンの起動用信号線等にも TIG 制約を掛けておくと フィッターが無理してその線を短くしようとしなくなるため、 他の部分の性能が向上することが期待できます。 しかし、そのような用途に TIG 制約を使うのは、やはりあまりお勧めできません。
というのも、そう言った目的にはあらかじめレジスタで遅延を入れておくことで 同じ効果が得られるからです。レジスタの挿入で解決した方が、 遅延量が予測可能になる、不定値が伝搬する問題を生じない、など、 ずっと良い結果が得られます。
そのような場合に挿入するレジスタには、性能向上のために REGISTER_DUPLICATION 制約や、REGISTER_BALANCING 制約を付けることも検討の余地がありそうです。
配置制約†
クロック周期に対する制約は、 個々のモジュール単位、あるいはそれらを数個繋いだ段階では制約を満たせているのに、 繋ぐモジュールの数が増えていくと、制約を満たせなくなってしまうことが良くあります。
この理由としては、モジュール間の配線が長くなって遅延時間が増加していることが多いようです。
どこがボトルネックになっているかは、Place & Route 後に PlanAhead を起動すれば見ることができ、 同じ画面でボトルネック解消のためのヒントを与えることもできます。
ここで始めに何点か注意:
Synthesize Report の Timing Summary†
Synthesize が出してくる Report にも Timing Summary があるのですが、 繋ぐモジュールの数が増えてくると、この値はあまり当てにはならないようです。
特に、上記のように DCM を使った回路では Synthesize が TIG を解釈しないので、 クロック周期などの見積もりがむちゃくちゃになってしまいます。
したがって、コンパイル時間が長く掛かってしまうのですが、 ちゃんとしたタイミング解析をするには Place & Route まで走らせなければなりません。
一方、モジュール単位のクロック周波数見積もりなどの用途では、 Synthesize Report の値も十分に参考になります。
そしてその参考にする方法ですが、Synthesize Report の出力する最高クロック周波数はあくまで概算なので、 実際にはこの値よりも高い周波数で動作するよう合成することも可能です。
ぎりぎりまでしぼってそれでも周波数があと少しだけ上がらない、 という状況であれば、配置の工夫で周波数を上げることができる可能性が高いので、 Synthesize の段階ではあまり気にせず Place & Route まで進んでみるのも良いようです。
Place & Route でエラーが出る†
あまり無茶な制約が掛かっていると、Place & Route がエラーで止まってしまいます。
ERROR:Par:228 - At least one timing constraint is impossible to meet because component delays alone exceed the constraint. A timing constraint summary below shows the failing constraints (preceded with an Asterisk (*)). Please use the Timing Analyzer (GUI) or TRCE (command line) with the Mapped NCD and PCF files to identify which constraints and paths are failing because of the component delays alone. If the failing path(s) is mapped to Xilinx components as expected, consider relaxing the constraint. If it is not mapped to components as expected, re-evaluate your HDL and how synthesis is optimizing the path. To allow the tools to bypass this error, set the environment variable XIL_TIMING_ALLOW_IMPOSSIBLE to 1.
信号線の遅延時間は(素子自体の遅延)+(信号伝達の遅延)で決まります。
このうち素子の配置を工夫して減らせるのは(信号伝達の遅延)の方だけですが、 あまりに制約が厳しいと(素子自体の遅延)だけで、 要求される遅延時間を超えてしまいます。
この場合、どんなに配置を工夫しても制約を満たすのは無理ですので、 HDL 記述のレベルで回路を見直す必要があります。 上記エラーはそのような状況を示しています。
で、どうやって見直すかというと、、、
上記のエラーメッセージの直後に、満たせなかった制約の一覧が出ますが、 それだけだと何が悪いか分かりません。
Processes ペインの [Implement Design] - [Map] - [Generate Post-Map Static Timing] - [Analyze Post-Map Static Timing] をダブルクリックして、Timing Analyzer を起動します。
エラーとなったパスに赤のチェックが付いていますので、 その内容から HDL レベルで回避策を講じることになります。
Xilinx の「スピード向上のためのストラテジ」:
http://www.xilinx.com/itp/xilinx8j/help/iseguide/html/ise_xst_speed_strategies.htm
のような情報もあるにはあるのですが、、、
実際にはツールに頼るだけだとコンパイル時間が長くなるだけであまり良い結果は得られないため、 クリティカルパスにレジスタを挿入するとか、レジスタ挿入の位置を工夫するといった、 回路に合わせた人為的な最適化をした方がうまく行くように感じられます。
Trace & Route が完了したが、いくつかの制約が満たされない†
Trace & Route が完了した後、 Design Summary/Report の [Design Overview] - [Summary] ページの 右上の Timing Constraints のところを見ると、 与えた制約がちゃんと満たされたかどうかが分かります。
うまく行っていれば All Constraints Met となりますが、
そうでなければ 2 Failing Constraints などとなります。
この状況は、「素子の配置を工夫すれば制約を満たせる可能性がある」ということで、 配置制約の出番になります。 ただ細かな配置制約を与えて、何とかタイミング制約を満たしたとしても、 少しの回路変更でまたうまく行かなくなったりするので、 もし HDL の見直しで改善できる余地があるなら、 配置をいじるよりもそちらを優先すべきです。
一方で、以下の例に挙げるような「大まかな配置制約」は、 細かな回路変更により破綻することも少ないので、 一考の余地があると思います。
PlanAhead の基本的な使い方†
Trace & Route でいくつかの制約が満たされないという結果になった時、
PlanAhead というツールでその原因を探り、
解決のヒントとして配置制約を与えることができます。
http://marsee101.web.fc2.com/planahead.html
起動方法は、Processes ペインの [Implement Design] - [Place & Route] - [Analyze Timing / Floorplan Design (PlanAhead)] をダブルクリック、です。
制約を満たさなかったパスは左下に赤文字で出ます。 それをクリックすると右上の領域に白い矢印で該当パスが表示されます。 ツールバーの緑で囲った部分を使って拡大すると、 1つ1つの素子の配置まで見ることができます。
遅延の大きいパスは大抵非常に長距離を走っているので、 この距離が小さくなるように素子の配置を変えてやるのが、 ここからの仕事です。
真ん中の青丸で囲ったところに "Create Site Constraint Mode" というのがあります。、 これをクリックしてから素子をドラッグ・ドロップすると、 特定の素子の位置を望みの箇所に固定することができます。
「素子を動かして矢印の長さを短くする」というだけなら簡単そうなんですが、 それらの素子には矢印で表示されたクリティカルパス以外にもいくつものパスが繋がっていて、 あちらを短くするとこちらが長くなる、というような状況になっていることが多く、 言葉で言うほど簡単ではないです。
一番下のタブで I/O ポート位置なども表示できるので、 それらを参考に全体を見渡しながらうまい配置を思い浮かべることになります。
ここではブロックメモリの位置を固定することで、 全体の配置を調整することを試みました。
ドラッグ中の素子には、その素子へ、 あるいはその素子からのパスがたくさん表示されます。 本来これらがどこに下ろすかの指標になるのですが、、、
今回は大まかな位置だけ決めて、 残りの細かい素子はおおきく自動で再配置してもらおうという算段なので、 線の長さはあまり気にしませんでした。
制約により固定された素子はオレンジで表示されています。
気の向くままにいじったら制約を保存して、 もう一度 Place & Route します。
上記の操作により ucf ファイルに追加された制約は次のようになりました。
INST "trimac/rx/rxmem/Mram_data2" LOC = RAMB16_X0Y17; INST "writer/rxfifo/rxfifo/mem" LOC = RAMB16_X1Y19; INST "trimac/rx/rxmem/Mram_data1" LOC = RAMB16_X0Y18; INST "trimac/control/Mrom__varindex0000" LOC = RAMB16_X0Y19; INST "reader/txfifo/txmem/mem" LOC = RAMB16_X0Y20; INST "reader/rxfifo/bmem/mem" LOC = RAMB16_X1Y18; INST "trimac/tx/Mram_mem" LOC = RAMB16_X0Y21;
上記制約を与えてフィッティングした結果です。
今回はメモリの配置で大まかな各モジュールの位置を指定するだけで、 残りの素子はツールによる自動配置に任せたまま全ての制約をクリアすることができました。
いつもそううまく行くわけではないのでしょうが、 基本としては最小限のプリミティブに制約を掛けておき、 残りの配置をツールに最適化させるのが賢い使い方だと思います。
配置指定をテキストベースで行う†
上記の方法では、要素の位置を完全に一カ所に決定する制約を与えましたが、 ucf ファイルをテキストベースで編集する場合には、
INST "trimac/rx/rxmem/Mram_data2" LOC = RAMB16_X0Y21,RAMB16_X0Y20,RAMB16_X0Y19;
のようにして、列挙された3カ所のどこか、という制約を与えたり、
INST "trimac/rx/rxmem/Mram_data2" LOC = RAMB16_X0Y16:RAMB16_X1Y21;
のようにして、左下と右上とで囲まれる四角形の領域の中、という制約を与えることもできます。
そのようなゆるい制約にしておけば、 その範囲内でのツールによる最適化を期待できるのですが、 次に紹介する点には注意が必要です。
気をつける点†
テキストベースで上記のような幅を持った制約を与えた場合、PlanAhead がその制約を解釈してくれないので、 PlanAhead で制約を追加や変更して上書きすると、テキストで与えた制約が失われてしまうようです?
回避策?†
ucf ファイルを2つ作って、ツールに任せるものと手で編集するものとに分けて使えば良いんですかね。
main.ucf と main_by_hand.ucf を作っておくと、 PlanAhead の起動時にどちらを読み込むか問い合わせてくるので、 main.ucf を選んでやれば main_by_hand.ucf の記述がいつの間にか消えてしまうという現象を避けられそうです。
入出力のタイミング制約†
参照> http://marsee101.blog19.fc2.com/blog-entry-26.html
入力信号のセットアップ&ホールド時間制約†
OFFSET = IN 2.5 ns VALID 3.0 ns BEFORE "eth_rxclk" RISING;
の形で、eth_rxclk に同期して入力される全てのピンに対して、 最低限 2.5 ns のセットアップ時間と 3.0 - 2.5 = 0.5 ns のホールド時間で動作するよう制約を掛けることができます。
出力信号のクロックエッジからの遅延時間制約†
OFFSET = OUT 6 ns AFTER "clk_125MHz_in" REFERENCE_PIN "eth_gtxclk" RISING;
の形で、clk_125MHz_in に同期して(clk_125MHz_in から DCM で生成されるいずれかのクロックに同期するものも含む) 出力されるすべての信号は eth_gtxclk の RISING EDGE から 6 ns 以内に値が決定しなければならない、という制約を与えます。
同じクロックに同期して出力される信号の中から特定のネットを指定して制約を掛けるには、
NET "eth_tx*" OFFSET = OUT 6 ns AFTER "clk_125MHz_in" REFERENCE_PIN "eth_gtxclk" RISING;
あるいは、
INST "eth_tx*" TNM = PADGROUP_ETH_TX; TIMEGRP "PADGROUP_ETH_TX" OFFSET = OUT 5 ns AFTER "clk_125MHz_in" REFERENCE_PIN "eth_gtxclk" RISING;
の形を使います。
NET を使った場合と TIMEGRP を使った場合との違いは、 NET を使った場合にはワイルドカードを展開した結果、 複数の制約として認識されるため、Timing Analyzer 上で非常に多くの制約が表示されてしまうのに対して、 TIMEGRP を使った方では1つの制約になることが挙げられます。
手動ではなく GUI の Constraints Editor を使う場合には NET 指定時のワイルドカードを認識してくれないので、
INST "eth_txd[0]" TNM = PADGROUP_ETH_TX; INST "eth_txd[1]" TNM = PADGROUP_ETH_TX; INST "eth_txd[2]" TNM = PADGROUP_ETH_TX; INST "eth_txd[3]" TNM = PADGROUP_ETH_TX; INST "eth_txd[4]" TNM = PADGROUP_ETH_TX; INST "eth_txd[5]" TNM = PADGROUP_ETH_TX; INST "eth_txd[6]" TNM = PADGROUP_ETH_TX; INST "eth_txd[7]" TNM = PADGROUP_ETH_TX; INST "eth_txen" TNM = PADGROUP_ETH_TX; INST "eth_txer" TNM = PADGROUP_ETH_TX; TIMEGRP "PADGROUP_ETH_TX" OFFSET = OUT 5 ns AFTER "clk_125MHz_in" REFERENCE_PIN "eth_gtxclk" RISING;
のようにすべてのネットを列挙する形で制約を作成することになるようです。
一応 PlanAhead とは違って、対応していない制約も表示だけは可能ですし、 上書き保存時に、ツールが非対応の制約が消えてしまうことも無いようです。
I/O バッファー制約†
入出力タイミング制約を満たすには、IBUF あるいは OBUF と呼ばれる入出力ピンに内蔵された(?) フリップフロップを活用するのが近道なようです。このための制約が IOB 制約と呼ばれます。
Verilog では、
LANG:verilog (* IOB="FORCE" *) reg some_register;
などと書くことで、特定のレジスタを IBUF あるいは OBUF として実装することができます。
IOB="TRUE" という書き方もできるのですが、"FORCE" としておけばレジスタを IOB に入れることができなかったときにエラーとなるので、IOB を使っているつもりでいたのに実は使えていなかった、というようなミスを防ぐことができます。
他にも遅延制約など、いろいろテクニックがありそうなのですが、 さしあたっては以上の方法でうまく行ってしまったようなので、 また必要になったときに書き足します。
コメント†
- こんにちは。紹介していただいてありがとうございます。私は、筑波大のシス情技術室所属です。 -- [marsee]
- どうもこんにちは。そうだったのですね。こちらは始めたばかりの初心者で、勉強々々の毎日です(汗 近くのよしみで(?)今後ともどうぞよろしくお願いいたします。 -- [武内(管理人)]
- こんにちは。こちらこそよろしくお願いいたします。よろしければ、そのうちに、直接お会いして、情報交換などは、いかがでしょうか? -- [marsee]
- こちらからは交換できるほどの情報はないのですが、せっかくのつながり、と思っていただけるのでしたらぜひお願いします( >そのうちに、直接 )左上の「連絡先」のメールアドレスにメールを、あるいは内線でも、お時間のあるときにご連絡下さい。 -- [武内(管理人)]