FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Vivado-HLSで書いた回路とプログラムで速度比較(2. directive挿入による性能改善)

Vivado-HLSで行列積演算回路を作成したが、この性能はあまり良いものでは無かった。

msyksphinz.hatenablog.com

その後いろいろ調査を行った結果、以下の資料を発見した。 以下の資料は、その名の通り浮動小数点行列積をVivado-HLSで実装したときの最適化ガイドだ。これを見れば、何らかの有益な情報が掴めるかもしれない。

http://japan.xilinx.com/support/documentation/application_notes/j_xapp1170-zynq-hls.pdf

最適化ガイドのソースコードには、パイプライン挿入のディレクティブに加え、見慣れないディレクティブが挿入されていた。これは何だろう?

set_directive_array_partition -type block -factor 16 -dim 2 "mmult_hw" A
set_directive_array_partition -type block -factor 16 -dim 1 "mmult_hw" B

set_directive_pipeline -II 1 "matrix_mul/L2"

調査してみると、このディレクティブは該当するポートに対して並列にアクセスすることを許可するらしい。なるほど、これで入出力ポートの並列化を行っていくわけか。

  • Vivado Design Suiteユーザー ガイド 高位合成

http://www.xilinx.com/support/documentation/sw_manuals_j/xilinx2012_3/ug902-vivado-high-level-synthesis.pdf

配列をより小さな配列または個々のエレメントに分割します。結果として RTLには、1つの大きなメモリではなく、複数の小型メモリまたは複数のレジスタインプリメントされます。ストレージの読み出しおよび書き込みポートの数が増加し、デザインのスループットが改善される可能性がありますが、より多くのメモリインスタンスレジスタが必要となります。

なるほど。また、-dimは分割対象の次元を指定するものらしい。上記の例だと、1次元目と2次元目の両方を分解、Bは1次元目を分解するという訳か?

f:id:msyksphinz:20161004022131p:plain

行列積は、赤矢印の方向に演算を進めていくのだが、これを上記の通りに分解すると、

f:id:msyksphinz:20161004022204p:plain

上記は、それぞれ接続されている要素がまとまっており、離れている要素は同時にアクセスできる構成になっているとする。そうすると、2番目の行列の演算が全て並列に行なわれるということなのか。

複数種類の構成を作って性能を検証する

そこで、以下の行列演算プログラムに対していくつかの最適化を実施し、どのようになるのかを実験した。

void matrix_mul (float matrix_a[MATRIX_LENGTH][MATRIX_LENGTH],
         float matrix_b[MATRIX_LENGTH][MATRIX_LENGTH],
         float matrix_c[MATRIX_LENGTH][MATRIX_LENGTH])
{
  L2:for (int j = 0; j < MATRIX_LENGTH; j++) {
    L1:for (int i = 0; i < MATRIX_LENGTH; i++) {
      matrix_c[j][i] = 0;
      L0:for (int k = 0; k < MATRIX_LENGTH; k++) {
        matrix_c[j][i] += matrix_a[j][k] * matrix_b[k][i];
      }
    }
  }
  return;
}
  1. 最適化無し (matrix_hls_normal)

  2. 最内ループの最適化 + 並列アクセス挿入 (matrix_hls_L0)

set_directive_pipeline -II 1 "matrix_mul/L0"
  1. 2番目のループの最適化 + 並列アクセス挿入 (matrix_hls_L1)
set_directive_pipeline -II 1 "matrix_mul/L1"
  1. 3番目のループの最適化 + 並列アクセス挿入 (matrix_hls_L2)
set_directive_pipeline -II 1 "matrix_mul/L1"

それぞれの速度および面積比較を行うと、以下のようになった。

f:id:msyksphinz:20161004023159p:plain

normal pipeline_L0 pipeline_L1 pipeline_L2
Clock Latency 8.41 8.09 8.09 9.80
Speed
Exec Latency 1110625 573697 7156 7218
Area
BRAM_18K 0 24 72 72
DSP48E 5 5 80 80
FF 488 821 11467 126153
LUT 846 1215 17882 59039

おお、昨日の結果よりもずいぶんと高速化されたな。

しかし、L2までパイプライン化すれば良いというものでは無さそうだ。性能は出るが、パイプライン化され過ぎていて、面積がかなり大きくなってしまっている。ここは、pipeline_L1のデザインが最適と言えるだろう。

VivadoにIPとしてインポートし、Zynq ZedBoardで動作させる

早速pipeline_L1デザインをVivadoにインポートし、プロジェクトを作って合成した。

f:id:msyksphinz:20161004023404p:plain

新しい行列演算回路を挿入した図。まああまり見た目は変わっていないけど。

早速昨日と同じプログラムを実行して、サイクル数を測定した。

サイクル数
Vivado-HLSで生成した回路 23059
ARM Cortex-A9で実行したプログラム 179650

おお、随分と速くなった!