本稿は、Xilinx の Vivado における部分再構成(Partial Reconfiguration)、別名 Dynamic Function eXchange(DFX)についての全 4 回の連載の最終回です。主に、部分再構成をリモートアップデート(Remote Update)のシナリオで使いたい方を対象にしています。この記事は、これまでの 3 回をお読みになったことを前提に書かれています。
はじめに
ここまで、部分再構成の一般論と、それに向けた Vivado の通常手順を説明してきました。今回は、この技法を FPGA ロジックのリモートアップデートに使うための基礎を固めます。
この利用シナリオの主な問題は、部分ビットストリーム(partial bitstream)が、場合によっては何年も前に生成された初期ビットストリームと互換性を持つ必要があることです。したがって、リコンフィギャラブルロジックを実装する際には、元のペアレント実装(Parent Implementation)が利用できる状態にあるか、あるいはまったく同じ結果——つまり完全に同じ配置配線(place and route)——を生成するように再実行できる必要があります。
Vivado プロジェクト全体を無傷で保持しておけば、ペアレント実装を再実行せずに済むかもしれません。また、その実装を繰り返せば、まったく同じ結果が得られる可能性もあります。しかし、どちらの方法に頼っても、将来にわたって部分ビットストリームをリリースできることを保証するのは困難です。
この問題には信頼できる解決策があります。ただし、その仕組みを理解するには、まず DCP と OOC に慣れておく必要があります。以下、両方のトピックを簡単に紹介します。
デザインチェックポイント(DCP)
Vivado は FPGA デザインの実装を、複数のデザインラン(design runs)によって実行します。通常、synth_1 や impl_1 という名前のほか、Out-of-Context モジュールラン(OOC)として分類される追加のランがあることを思い出してください。
こうしたランのそれぞれは Tcl スクリプト(script)の実行で構成されています。スクリプトは一時的な「インメモリプロジェクト(in-memory project)」を生成し、デザインファイルをロードし、プロパティや属性を設定し、合成、配置、配線、ビットストリーム生成などの処理を行う Tcl 関数を呼び出します。
このインメモリプロジェクトはディスク上にファイルを作成せず、GUI に表示されている Vivado プロジェクトとは無関係です。これはメモリ内に存在するオブジェクトであり、Tcl コマンドで多数の連続的操作を行えるようにするものです。
実装が進むにつれて、デザインチェックポイント(Design CheckPoint、DCP)ファイルがディスクに書き出されます(Tcl の write_checkpoint コマンドによります)。DCP ファイルの中身は、インメモリプロジェクトのスナップショットです。言い換えれば、ロードされたデザインファイルと、ロード後にプロジェクトに対して行われた操作を反映したデータベースです。
たとえば、合成ラン(通常は synth_1 という名前)は、すべての HDL ファイル、制約ファイル、IP ファイルをロードし、それから synth_design という Tcl コマンドを使って HDL ファイルの合成を実行します。その結果、これらのすべてのソース(IP を含む)から 1 つの大きなネットリストが得られます。ただし、ネットリストはインメモリプロジェクトの一部としてメモリ内に保持されることに注意してください。そのため、合成ランの Tcl スクリプトは write_checkpoint を呼び出し、合成の成果物である DCP ファイルを作成します。これで synth_1 ランは完了です。
実際のところ、synth_1 が生成する DCP は、通常 XDC ファイルからの制約も含んでいますが、ネットリスト DCP(netlist DCP)と呼ばれます。
その後に行われる実装ラン(通常 impl_1)は、新しいインメモリプロジェクトを作成し、このネットリスト DCP を(その他とともに)以降の処理の出発点として読み込みます。このランは、処理段階(たとえばデザイン最適化、配置、物理最適化、配線など)ごとのスナップショットを複数の DCP ファイルとして書き出します。
synth_1 を含むすべてのランは、DCP を自分のインメモリプロジェクトにロードできます。実際、通常はそうしています。
Out-of-Context(OOC)モジュールラン
Vivado では、IP は通常 GUI ツールによって構成されます。これが行われると、スクリプトがソースファイル(主に HDL ファイルと制約ファイル)を生成し、それらを合成します。その結果ネットリスト DCP が生成され、メインプロジェクトの合成ランと実装ランにロードされます。こうして IP のソースを再生成して合成を何度も実行する必要がなくなり、プロジェクト全体の実装完了までの時間が短縮されます。
IP の構成情報やいくつかの HDL ファイルなど、生の成果物を受け取り、それをネットリスト DCP に変換する Vivado のランは、Vivado 用語で Out-of-Context ランと呼ばれます。この用語は Vivado に関連してだけ使われ、私の知る限りそれ以外の用途はありません。実際には、あまり良い名前とは言えません。
このネットリスト DCP は、通常 read_ip という Tcl コマンドによって、合成ランと実装ランにロードされます。read_ip は、結局のところ OOC ランが生成した DCP をロードするだけのことがほとんどです。
Vivado では、メインプロジェクト内の HDL モジュールを選択し、その合成を OOC として実行することもできます(Project Manager のソースツリーでソースを右クリックし、"Set as Out-of-Context for Synthesis…" を選びます)。
OOC の欠点は、次の点にあります。デザイン全体を単一のプロジェクトとして扱うとき、シンセサイザ(synthesizer)はモジュールの境界を越えて一定の最適化を行えますが、OOC ではそれができません。したがって、OOC による個別合成は性能を低下させたり、リソースを無駄にしたりする可能性があります。
部分再構成における OOC と DCP
ソースファイルがリコンフィギャラブルモジュールのトップレベルとして選択されると、Vivado はそのモジュールとサブモジュールを合成するための OOC ランを作成します。このランは、該当する実装で使うネットリスト DCP を生成します。これはペアレント実装でもチャイルド実装(Child Implementation)でも同じで、リコンフィギャラブルモジュールは常に個別の DCP として表現されます。
ただし、このネットリスト DCP の使い方は、ペアレント実装とチャイルド実装で異なります。ペアレント実装に割り当てられたネットリスト DCP は、通常の実装で IP を扱うのと同様に synth_1 と impl_1 の両方にロードされます。部分再構成が関係していることは、主にフロアプランニングによって課される配置制約を通じてこのプロセスに影響します。
一方、チャイルド実装には専用の合成フェーズがありません。その代わり、リコンフィギャラブルモジュールのネットリスト DCP を、ペアレント実装の最終 DCP(つまり配置配線後の DCP)と混ぜ合わせます。より正確には、チャイルド実装はペアレント実装の最終 DCP を取り出し、リコンフィギャラブルロジックの部分を取り除いて、代わりに自分のリコンフィギャラブルモジュールのネットリスト DCP を挿入します。ズッキーニの詰め物料理を作るときに野菜の真ん中を取り除くのと少し似ています。
それでは、これを Tcl コマンドに分解してみましょう。
ペアレント・チャイルド実装の詳細
実装が内部でどう動くかを調べるには、プロジェクト名(に .tcl 拡張子を付けたもの)を持つ Tcl ファイルを見るのが一番です。このファイルは、実装のファイルが作成されるディレクトリと同じ場所にあります。
特に関心があるのは、チャイルド実装用の実装スクリプトです。関連するユーザーガイド、UG909 の第 3 章("Vivado Software Flow")が、直接そうとは言わなくても、このスクリプトを示して説明しているのは偶然ではありません。
先ほど述べたように、ペアレント実装には特別な点はほとんどありません。リコンフィギャラブルモジュールのネットリストが DCP に依存していることと、フロアプランニング制約が適用されることだけが通常と異なります。多かれ少なかれ、普通の階層設計と同じです。
しかし、その後にペアレント実装は、次のような Tcl コマンドによって 1 つではなく 2 つのビットストリームを書き出します。
write_bitstream -force -no_partial_bitfile theproject.bit write_bitstream -force -cell pr_block_ins pr_block_ins_lpf_partial.bit
そして、後でチャイルド実装が使う DCP を次のように作成します。
update_design -cell pr_block_ins -black_box lock_design -level routing write_checkpoint -force theproject_postroute_physopt_bb.dcp
この部分が実行されている間、インメモリプロジェクトが存在していることを思い出してください。それはネットリスト DCP のロードから始まり、配置配線やその他すべての最適化を経てきました。これらの 3 行の Tcl はビットストリームを書き出した後に実行されるため、インメモリプロジェクトは本当に最終段階にあります。
ここで穴を開け、ズッキーニの詰め物をする時が来ました。update_design コマンドはリコンフィギャラブルモジュールをブラックボックス(black box)に変えます。言い換えれば、そのロジックをすべて取り除き、別のロジックが入る余地を作ります。
次に、lock_design コマンドによってデザインの配置配線がロックされます。その後、プロジェクトのスナップショットが theproject_postroute_physopt_bb.dcp に書き出されます。もちろん "bb" は "Black Box" を意味します。
チャイルド実装のスクリプト内の該当部分は次のとおりです。
create_project -in_memory -part xc7k325tffg900-2 set_property design_mode GateLvl [current_fileset] add_files -quiet .../impl_1/theproject_postroute_physopt_bb.dcp add_files -quiet .../two_synth_1/pr_block.dcp set_property SCOPED_TO_CELLS pr_block_ins [get_files .../two_synth_1/pr_block.dcp] link_design -top theproject -part xc7k325tffg900-2 -reconfig_partitions pr_block_ins opt_design write_checkpoint -force theproject_opt.dcp [ ... ]
この後、配置配線などへと進みます。
上記の実装スクリプトが入力として使うソースは 2 つだけで、どちらも DCP であることに注意してください。
- theproject_postroute_physopt_bb.dcp — 配置、配線、ロック後のスタティックロジック。
- pr_block.dcp — リコンフィギャラブルモジュールのネットリスト(つまり合成のみが済んだ状態)。
実装が続くとき、最初の DCP がロックされていたおかげで、スタティックロジックの何も動かないことが保証されます。それでも、リコンフィギャラブルモジュールの配置配線は、ネットリスト DCP に基づいて通常どおり行われます。
ちなみに、リコンフィギャラブルモジュールに属する XCI の IP がある場合、上記の 2 つの add_files コマンドの間に、次のような行が IP ごとに追加されます。
read_ip -quiet .../theproject.srcs/sources_1/ip/blkmem/blkmem.xci
ただし、これは部分再構成に固有の話ではなく、どの実装でもこのように行われます。
2 つのビットストリームを書き出す直前に、チャイルド実装は、得られた配線済みデザインが、特に配置配線に関してペアレント実装のスタティックロジックと互換性があることを検証します。
これを行う Tcl コマンドは、たとえば次のとおりです。
pr_verify -full_check -initial /path/to/impl_1/theproject_postroute_physopt.dcp -additional /path/to/child_1_impl_1/theproject_routed.dcp -file child_1_impl_1_pr_verify.log
これはインメモリプロジェクトとは無関係に 2 つの DCP ファイルを比較していることに注意してください。ペアレント実装の配線済み DCP と、チャイルド実装の最終 DCP が比較されます。この比較の出力は *_pr_verify.log という名前のファイルに書き込まれます。
この比較により、ペアレントのビットストリームがすでに FPGA にロードされている状態で、部分ビットストリームをロードしても互換性があることが保証されます。この検証は、スタティックパーティションのロジック要素と配線の両方を調べます。
互換性がない場合、pr_verify は失敗ステータスを返します。これが起きると、Vivado のスクリプトでのビットストリーム生成は阻止されます。カスタムの実装スクリプトを書くときは、この手順を忘れないことが重要です。
この検証が失敗する理由は本来ありませんが、もし失敗すると、ビットストリーム生成は次のような大量のエラーとともに失敗します。"ERROR: [Constraints 18-891] HDPRVerify-08: design check point .../impl_1/theproject_postroute_physopt.dcp places instance ... at site SLICE_X118Y125, yet design check point .../impl_2/theproject_routed.dcp does not. Both check point must have the same static placement result" という具合です。
こうしたエラーメッセージは 100 件という上限に達し、その後は表示されなくなるでしょう。
リモートアップデートシナリオ向けの解決策
前述のとおり、課題は、リコンフィギャラブルロジックの実装をチャイルド実装として実行するときに、元のペアレント実装の結果を利用できなければならないことです。
力技の解決策は、Vivado プロジェクトディレクトリ全体を、それが依存するファイルと一緒にコピーしておくことです。新しい部分ビットストリームを生成する必要が生じたら、すべてのファイルを復元し、ペアレント実装を up-to-date 状態に強制して、チャイルド実装のビットストリームだけを作成します。この方法は技術的には問題ありませんが、長い目で見ると面倒になる可能性が高いです。この方法を選ぶ場合、元のペアレント実装の DCP ファイルに対して pr_verify を手動で実行してください。この方法では、ペアレント実装への意図しない変更を検出できないからです。
他に 2 つの選択肢があります。どちらも、ペアレント実装とチャイルド実装が前述の 2 つの DCP ファイルを通じて相互作用する、という知識に基づくものです。これら 2 つの選択肢の明らかな利点は、何をしているのかを理解したうえで作業できることです。
どちらの選択肢も原則は同じです。初期ビットストリームと一緒に生成された 2 つの DCP ファイル——以後「ゴールデン DCP(Golden DCPs)」と呼ぶことにします——を、部分ビットストリームがよりどころとするようにするのです。
最初の選択肢は、部分ビットストリームの実装を、非プロジェクトフロー(non-project flow)の Tcl スクリプトとして実行することです。具体的には、Vivado がチャイルド実装用に作成するスクリプトを実行しますが、ゴールデン DCP を使うように変更します。より正確には、link_design コマンドと pr_verify コマンドの引数を変更し、ゴールデン DCP に依存するようにします。
この選択肢の主な欠点は、非プロジェクトフローの Tcl スクリプトを実行すると Vivado の GUI との統合がうまくいかず、メッセージの処理や、レビューのための実装後デザインの表示などがかなり難しくなることです。
ゴールデン DCP をすり替える裏技
理想的には、Vivado がチャイルド実装ラン用に生成するスクリプトを自動的に変更し、ゴールデン DCP を参照するようにできればよいのですが、残念ながら信頼できる方法はなさそうです。
ただし、Vivado には実装の特定の段階の前後で実行する Tcl スクリプトを定義する機能があります。これらのスクリプトは実装ランのスクリプトから実行されるため、ランのスクリプト自体を書き換えるために使うことはできません。
それでも、この機能は少し乱暴な方法への道を開きます。それが 2 つ目の選択肢です。考え方は、ペアレント実装の DCP をゴールデン DCP で上書きしてしまうことです。そうすれば、チャイルド実装は通常どおり実行されますが、ペアレント実装がたまたま生成したものには関係なく、ゴールデン DCP に依存することになります。
この方法の利点は、Vivado での普段の作業習慣が変わらないことです。リコンフィギャラブルロジックに変更を加えると、Vivado はリコンフィギャラブルモジュールを合成する OOC を再実行し、次にチャイルド実装を実行して部分ビットストリームを生成します。スタティックロジックには変更がないので、Vivado が関連するランを実行する必要はありません。
このゴールデン DCP コピー方式を実装する Tcl スクリプトは、次のとおりです。
if { [catch {
set parentimpldir "[ file normalize "../impl_1"]"
set goldendir "[ file normalize "/path/to/golden"]"
file copy -force "[file normalize "$goldendir/theproject_postroute_physopt_bb.dcp"]" "$parentimpldir/"
file copy -force "[file normalize "$goldendir/theproject_postroute_physopt.dcp"]" "$parentimpldir/"
} errmsg ] } {
send_msg_id golden-reconfig-1 error "Failed to copy golden parent reconfiguration file(s): $errmsg"
return -code error
}
このスクリプトは、ペアレント実装がチャイルド実装のディレクトリに隣接する "impl_1" ディレクトリに置かれていること(おそらくそのとおりです)、および 2 つのゴールデン DCP がこのスクリプトの 3 行目で定義されているディレクトリに保存されていることを前提にしています。
Design Runs タブでチャイルドラン(たとえば child_0_impl_1)を右クリックし、"Change Run Settings…" を選んで、表示されたダイアログで Design Initialization(init_design)用の tcl.pre にそのスクリプトを設定します。あるいは、スクリプトを golden_pr.tcl として保存した場合は、Tcl で次のように指定します。
add_files -fileset utils_1 -norecurse /path/to/golden_pr.tcl set_property STEPS.INIT_DESIGN.TCL.PRE [ get_files /path/to/golden_pr.tcl -of [get_fileset utils_1] ] [get_runs child_0_impl_1]
このスクリプトを使うときに重要なのは、Vivado がペアレント実装に関して表示する情報や、ペアレント実装が生成したファイルをすべて無視することです。Vivado は実装後デザインの GUI を開いてレポートを表示するかもしれませんが、これらはまったく無関係である可能性が高いです。つまり、ここが混乱の入り込みやすい点です。
2 つ目に考えられる厄介な点は、プロジェクトの設定や制約ファイルの変更に応じて、Vivado が時々ペアレント実装を実行しようとすることです。これは Design Runs タブで該当行を右クリックし、"Force Up-to-Date" を選ぶことで回避できます。このメニューは、ランが Out-of-Date 状態、つまり完了しているが Vivado が更新が必要と判断した場合にのみ表示されます。対応する Tcl コマンドは、たとえば次のとおりです。
set_property needs_refresh false [get_runs synth_1]
ゴールデン DCP と一緒に保存すべきファイル
もちろん、将来にわたって互換性のあるビットストリームファイルを生成できるようにするため、ゴールデン DCP は安全な場所に保管しなければなりません。さらに、Vivado プロジェクト全体を .tar.gz / .zip ファイルに圧縮して保存し、すぐに再開できるようにしておくのも良い考えです。あるいは、その代わり、またはそれに加えて、File > Project > Archive… または次のようなコマンドでプロジェクトアーカイブを生成できます。
archive_project /path/to/theproject.xpr.zip -force -include_local_ip_cache -include_config_settings
ただし、Vivado プロジェクトのプロジェクトファイルやその他のファイルには絶対パスが含まれる場合があるため、プロジェクトを別のディレクトリや別のコンピュータに展開しても、期待どおりに動作しない可能性があります。アーカイブでも同じことが言えます。
使用した Vivado のバージョンは、あらゆるレポートファイルや .xpr プロジェクトファイルに書かれていますが、メモしておいても害はありません。ソフトウェアのアップグレードが影響する明確な理由はないとはいえ、そのバージョンの Vivado の実行可能コピーを保持しておくとよいでしょう。ある Vivado バージョンのゴールデン DCP と、別のバージョンのリコンフィギャラブルロジックのネットリスト DCP を混在させても、おそらく問題なく動作します。ただし、Vivado がそのような使い方を想定しているわけではありません。
まとめると、最小限のファイルセットは次のとおりです。
- ゴールデン DCP:theproject_postroute_physopt_bb.dcp と theproject_postroute_physopt.dcp
- 初期ビットストリームファイル(たとえば theproject.bit。FPGA のロードに使うので、保存しておくのは当然です)
- ペアレント実装を構築するためのソース
- UltraScale FPGA のみ(UltraScale+ は除く):初期ビットストリームに対応するクリアリングビットストリーム(clearing bitstream)
UltraScale FPGA でのクリアリングビットストリームの使い方は、FPGA にすでにあるロジックに関係していることに注意してください。だからこそ、初期ビットストリームに対応するクリアリングビットストリームを保存しなければなりません。また、初期ビットストリームが何らかのチャイルド実装の結果である場合は、その実装のクリアリングビットストリームも保存する必要があります。
ペアレント実装のソースを保存しておくことも重要です。スタティックロジックとリコンフィギャラブルロジックの間の接続を決定づけるからです。たとえば、ソースを編集してリコンフィギャラブルロジックにポートを追加し、そのポートがインスタンシエーション(instantiation)に現れると、パーティションピン(partition pins)の集合が変わります。その結果、リコンフィギャラブルロジックのネットリストには、元のスタティックデザインには存在しない外部ピンが現れることになります。
このような不一致が生じると、チャイルド実装は次のようなエラーメッセージで失敗します。"ERROR: [Netlist 29-77] Could not replace (cell 'pr_block_bb', library 'work_pr_block_ins_pr_block_ins_4', file 'NOFILE') with (cell 'pr_block', library 'work', file 'pr_block.edf') because of a port interface mismatch; in strict mode, no extra ports are allowed. 8 ports are missing on the original cell. 5 of the missing ports are: 'thingy[7]' 'thingy[6]' 'thingy[5]' 'thingy[1]' 'thingy[0]'".
このとき実際に失敗するのは、スタティックロジックと組み合わせロジック(combinational logic)を貼り合わせる link_design コマンド(上記参照)です。
この問題を避ける簡単で明白な方法は、デザインのスタティック部分を変更せず、リコンフィギャラブルロジックのポートリストも変更しないことです。
ただし、プロジェクトのスタティック部分に変更を加えても、実は問題ありません。ただ、リコンフィギャラブルモジュールのインスタンシエーションは同じままである必要があります。最後の検証を含め、実装がスムーズに進む限り、問題はありません。
まとめ
部分再構成をリモートアップデート用途で使うための、完全にスムーズな解決策はありません。それでも、この目標を達成するための戦略はいくつかあります。
重要な点は、既存プロジェクト向けの部分ビットストリームを構築して検証するには、原則として 2 つのゴールデン DCP があれば十分だということです。
また、ここで紹介した戦略がどんなに型破りに見えても、pr_verify による検証が、部分ビットストリームと、すでに配置されているスタティックロジックとの互換性を包括的にチェックするものであることを忘れないでください。正しいゴールデン DCP を使ってこの検証を行い、テストに合格すれば、他に心配することはありません。もちろん、チャイルド実装がタイミング制約(timing constraints)を満たすことも必要ですが、それはどのデザインの実装でも同じことです。