本記事の概要
Xilinxの統合ソフトウェア開発環境では、Vitisビジョンライブラリ(以下、ビジョンライブラリ)をインクルードすることによって、画像処理機能を高速化(アクセラレーション)することが可能です。例えば、OpenCVのフィルタ処理などを、FPGAに実装可能なロジックへと高位合成することができます。
ビジョンライブラリのインクルードの方法は以下の記事にまとめています。
本サイトでは実際にVitisビジョンライブラリのFilter2D_accel関数を例に、ラプラシアンフィルタを行う画像処理IPのCシミュレーション、高位合成、C/RTL協調シミュレーション、そして実装を進めています。
実装は以前の記事で紹介したZynqで構成した静止画をディスプレイに表示するプロジェクトと組み合わせるつもりで、最終的にラプラシアンフィルタで処理した画像をディスプレイに表示させてみようと思います。
前回の記事で高位合成を行いました。
今回の記事ではC/RTL協調シミュレーションの方法を示した後、IPの入出力バス、そのタイミングチャートを確認できればと思います。
C/RTL協調シミュレーション
実行
前記事で示した通り高位合成の完了後、その結果をC/RTL協調シミュレーションで確認しました。
図の通り、[▶]をクリックし、[Cosimulation]を選択します。
新しく[Co-Simulation Dialog]ダイアログボックスが立ち上がりますので、協調シミュレーションの設定をして、[OK]をクリックします。
Input Argumentsには、入力画像のビットマップのパスを選択しました。ビットマップ画像は、Cシミュレーションと同様に、次の解像度160×120の画像としています。
また、[Dump Trace]では[all]を選択しました。[Dump Trace]で波形を保存する変数を選択でき、[all]とすれば下位階層も含めて信号を確認することができます。
以上の設定のもと、[OK]をクリックし協調シミュレーションを実行します。
結果
220秒(3-4分)ほどかかり、協調シミュレーションが完了しました。
Filter2d_accelのレイテンシーは、22,287クロック(約220us)でした。
高位合成したIPの入出力信号のタイミングチャート
[Open Wave Viewer…]をクリックして、波形を確認しました。Vivadoが立ち上がり、各タイミングを確認することができます。本記事では、このタイミングチャートのポイント、特に入出力変数のタイミングをそれぞれ図解しようと思います。
タイミングチャート全体
まずはタイミングチャート全体をざっと眺め一連の処理の大枠をつかみます。
波形の[Design Top Signal]を中心に確認しましょう。
RTLシミュレーションにおける変数とIPのバスの対応関係を踏まえて、全体のタイミングチャートをVivado Simulation の波形で確認しました。
まず、AXI4-Liteを通じて、(a)コントロールレジスタにデータを入力します。入力数によってレイテンシーは変わりますが、およそ数100nsくらいです。今回の協調シミュレーションでは320 nsかけて、データを入力し、その後コントロールレジスタのスタートビットに1を入力して、画像処理を開始します。
画像処理中はAXI4-Liteのcontrol_s_axiを通じてステータス(ap_ctrlの状態)を定期的に読み出し続けていました。それとは並列にまず(b)filter、次に(c)img_inをメモリから読み出し、(d)計算して得たimg_outをパイプライン処理でメモリに書き込んでいます。
160×120ピクセルの画像(19,200)を処理するのに、22,287クロックかかっていました。1ピクセルあたり約1.16クロックです。
この一連の処理の流れの各過程をもう少し細かく見てみます。
(a) コントロールレジスタの設定
コントロールレジスタはcontrol_s_axiモジュールとcontrol_r_s_axiモジュールの2つが存在し、その両方でレジスタを設定する必要があります。
まず、control_r_s_axiモジュールについて。
こちらでは、画像とフィルタを格納する配列のポインタを設定します。
control_r_s_axiに対応するimg_in__filter__img_outのグループ内の変数の波形を確認しましょう。
img_out, img_in, filterの順にフィルタを格納する配列のポインタを入力しているのがわかります。今回は、メモリとやり取りしていないので書き込む値は0となっていますが、Zynqに実装するときには実際の値を書き込まないといけませんね。
AXI4-Liteの書き込みや読み出しのプロトコルのイメージは以下の記事に記載しています。
次に、control_s_axiについて。こちらでは、次の3パラメータを設定した後、画像処理を開始するスタート信号を送信します。
波形を確認しましょう。何回か読み出しと書き込みが行われていました。
この各タイミングでの書き込み、読み出しをまとめると、以下の表のようになります。
タイミング | 書き込み or 読み出し | アドレス | 値 | 意味 |
① | 読み出し | 0x00 | 0x04 | ステータス読み出し。ap_idle(0x04)を確認 |
② | 書き込み | 0x10 | 0x08 | shiftに8を代入 |
③ | 書き込み | 0x18 | 0x78 | rowsに120を代入 |
④ | 書き込み | 0x20 | 0xa0 | colsに160を代入 |
⑤ | 読み出し | 0x00 | 0x04 | ステータス読み出し。ap_idle(0x04)を確認 |
⑥ | 書き込み | 0x00 | 0x01 | スタートビットに1を代入。 |
ちなみに、アドレス0x00のap_ctrlのステータスを示すコントロールレジスタは次の通りです。
今回は、bit2でidle状態になっていることを確認したうえでパラメータの書き込みを行い、bit0をHighにして画像処理の開始を指示しています。
(b) gmem1 ラプラシアンフィルタ
スタートビットをHigh状態にすると、IPコアが自動でメモリ内のラプラシアンフィルタの配列からIPコアへの値の読み出しを開始します。
前回の記事で示した通り、ラプラシアンフィルタの配列はshort int型で、値はこのように指定しました(SHIFTは8)。
filter_ptr[0] = 0; //0;
filter_ptr[1] = 1 << SHIFT;
filter_ptr[2] = 0; //0;
filter_ptr[3] = 1 << SHIFT;
filter_ptr[4] = -4 << SHIFT;
filter_ptr[5] = 1 << SHIFT;
filter_ptr[6] = 0; //0;
filter_ptr[7] = 1 << SHIFT;
filter_ptr[8] = 0; //0;
配列を絵にするとこんな感じです。この配列を1ワード(32bit)ずつリトルエンディアンで読み出しています。
また、バースト転送で5ワード、つまり配列全体を一気に読みだしています。
まず、バースト転送の方法を、ARチャネルを通じてIPコア(M側)からDDRメモリ(S側)に指示しています。今回、VitisHLSで生成された協調シミュレーションでは、ADDR=0x00、LEN=0x04、SIZE=010b、BURST=01bという風にARチャネルから信号を送っていました。信号の意味はこの通りです。
アドレス0x00からのデータを、転送回数5回(LEN=0x04)、バイト数4(SIZE=010b)、バースト転送タイプがインクリメント(BURST=01b)で読み出す
ここで、インクリメントはアドレス順に順次読み出すという意味になります。
このようにして、ラプラシアンフィルタを配列からバースト転送で一気に読み出します。
(c) gmem0 入力画像
次に、入力画像img_inを読み出しが開始されます。
前回の記事で見た通り、入力画像は4バイト(1ワード)ずつ配列に格納されています。
ラプラシアンフィルタと同様にバースト転送で複数ワードが連続で読み出されます。このとき、入力画像を転送するAXIバスでのARチャネルの設定は
- ARADDR : 0x0000, 0x0040, 0x0080, 0x00c0, …
- ARLEN : 0x0f (転送回数16回)
- ARSIZE : 010b (バイト数4 = 1ワード)
- ARBURST : 01b (インクリメント)
となっています。つまり、アドレスを順次インクリメントしながら4バイトずつ転送回数16回で、メモリの配列img_inから値を読み出すように指示しています。
図にすると16ワードのバースト転送を行い、バースト転送が完了したら、アドレスを0x40(64バイト=16ワード)だけ更新し次のバースト転送を実行しています。
(d) gmem2 出力画像
img_outはimg_inで行った読み出し操作と同じことを書き込み操作で行います。
AWチャネルの設定もimg_inの読み出しにおけるARチャネルの設定と全く同じで、
- AWADDR : 0x0000, 0x0040, 0x0080, 0x00c0, …
- AWLEN : 0x0f (転送回数16回)
- AWSIZE : 010b (バイト数4 = 1ワード)
- AWBURST : 01b (インクリメント)
となっています。
16ワードずつバースト転送でメモリに画像処理後の出力画像を配列img_outに書き込み、バースト転送が終わったらアドレスを0x40更新して、次のバースト転送を行います。
まとめ
本記事では、Filter2D_accel関数のC/RTL協調シミュレーションを行い、高位合成したIPのタイミングチャートを確認できました。事前に確認しておくと、実際にVitisでアプリケーションを構築するときに何をレジスタに書けばよいのか・読み出せばよいのかが理解できます。
また、今回のIPで使用しているAXI4バスの使い方は覚えることが多く最初は理解に時間がかかります。しかし、実際に作成されたIPの動作を例にして読み解いていけば、理解を深めて、扱いにも慣れていけると思います。
最後までご覧いただきありがとうございました!
コメント