標準水平たたみ込み

たたみ込みは、K 個のデータ サンプルと K 個のたたみ込み係数を使用して実行されます。上の図では K の値は 5 ですが、この値はコードで定義されます。たたみ込みを実行するには、K 個以上のデータ サンプルが必要です。たたみ込みウィンドウは、画像外にあるピクセルを含む必要があるため、最初のピクセルでは開始できません。
対称たたみ込みを実行すると、src 入力からの最初の K 個のデータ サンプルが水平係数でたたみ込まれ、最初の出力が計算されます。2 つ目の出力を計算するには、次の K 個のデータ サンプルが使用されます。この計算は、最後の出力が書き込まれるまで各行に対して実行されます。
この操作を実行する C コードは次のとおりです。
const int conv_size = K;
const int border_width = int(conv_size / 2);
#ifndef __SYNTHESIS__
T * const local = new T[MAX_IMG_ROWS*MAX_IMG_COLS];
#else // Static storage allocation for HLS, dynamic otherwise
T local[MAX_IMG_ROWS*MAX_IMG_COLS];
#endif
Clear_Local:for(int i = 0; i < height * width; i++){
local[i]=0;
}
// Horizontal convolution
HconvH:for(int col = 0; col < height; col++){
HconvWfor(int row = border_width; row < width - border_width; row++){
int pixel = col * width + row;
Hconv:for(int i = - border_width; i <= border_width; i++){
local[pixel] += src[pixel + i] * hcoeff[i + border_width];
}
}
}
このコードは簡単でわかりやすいものですが、ハードウェア結果の質に悪影響を及ぼす問題がいくつかあります。
1 つ目の問題は、C コンパイル中に大型ストレージが必要であるということです。アルゴリズムの中間結果は内部 local 配列に格納されます。HEIGHT*WIDTH の配列が必要で、1920*1080 の標準ビデオ画像では 2,073,600 の値が保持されます。
- Zynq®-7000 All Programmable SoC または Zynq UltraScale+™ MPSoC をターゲットとするクロス コンパイラおよび多くのホスト システムでは、この量のローカル ストレージのためランタイムでスタック オーバーフローが発生します (ターゲット デバイス上での実行、Vivado HLS 内での協調シミュレーションの実行など)。local 配列のデータはスタックに配置され、OS で管理されるヒープには含まれません。
arm-linux-gnueabihf-g++でクロス コンパイルする際は、-Wl,"-z stacksize=4194304"リンカー オプションを使用して十分なスタック空間を割り当てます。このオプションの構文は、リンカーによって異なります。関数がハードウェアでのみ実行される場合は、__SYNTHESIS__ マクロを使用するとこのような問題を回避できます。このマクロは、ハードウェア関数がハードウェアに合成されるときにシステム コンパイラにより自動的に定義されます。上記のコードでは、C シミュレーション中にダイナミック メモリ割り当てを使用してコンパイルの問題を回避しており、合成中はスタティック ストレージのみが使用されます。このマクロを使用する短所は、C シミュレーションで検証されたコードが合成されるコードとは異なるものになることです。この例の場合はコードは複雑ではないので、動作は同じになります。 - この local 配列の主な問題は、FPGA インプリメンテーションの質です。これは配列なので、内部 FPGA ブロック RAM を使用してインプリメントされます。これは、FPGA 内にインプリメントするにはかなり大きいメモリであり、より大型でコストのかかる FPGA デバイスが必要となる可能性があります。DATAFLOW 最適化を使用して、小型で効率的な FIFO を介してデータをストリーミングすると、ブロック RAM の使用を最小限に抑えることはできますが、データがストリーミングでシーケンシャルに使用されるようにする必要があります。現在のところ、そのような要件はありません。
2 つ目の問題は、パフォーマンスと local 配列の初期化に関するものです。Clear_Local ループは local 配列の値を 0 に設定するために使用されます。高パフォーマンスで実行するためにこのループをハードウェアでパイプライン処理しても、この操作をインプリメントするのに約 2 百万クロック サイクル必要です。このメモリの初期化中、システムで画像処理を実行することはできません。同じデータの初期化は、HConv ループ内の一時的な変数を使用して、書き出し前に累積を初期化することにより実行できます。
最後の問題は、データのスループット、つまりシステム パフォーマンスがデータ アクセス パターンにより制限されることです。
- 最初のたたみ込み出力を作成するため、最初の K 個の値が入力から読み出されます。
- 2 つ目の出力の計算には、新しい値が読み出され、その後同じ K-1 個の値が再度読み出されます。
高パフォーマンスの FPGA を得るには、PS へのアクセスを最小限に抑えることが重要です。前に既にフェッチされたデータに再度アクセスすることは、システムのパフォーマンスに悪影響を与えます。FPGA では多数の計算を同時に実行して高パフォーマンスを達成することが可能ですが、データのフローが値を再読み出しするために頻繁に中断されると高パフォーマンスは得られません。
上記のコードでは、データを何度も読み出す必要があるので、DMA 操作を使用してプロセッサから直接ストリーミングできません。