FPGA開発日記

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

Vivado-HLSとAXI4-DMAの試行(1. Vivado-HLSデザインをAXI-Streamインタフェースに対応させる)

前回行列乗算回路を最適化することで、ソフトウェアと比較して大幅に性能を向上させることに成功した。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

その時に参照していたのが以下の資料だが、そこにはAXI4-DMAを使ったときの方法が書いてある。

http://www.xilinx.com/support/documentation/application_notes/xapp1170-zynq-hls.pdf

AXI4-DMAを使ってデータ転送を高速化すれば、さらにパフォーマンスを上げることができると踏んだ。この方法について調査してみることにする。

AXI DMAの概要

以下の資料に簡単な説明がある。

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

  • AXI Memory Map Read(MM2S) : データを読み出し、AXI Stream(スレーブ ペリフェラル)への転送
  • AXI Memory Map Write(S2MM) : マスター ペリフェラルからデータを読み出し、データ書き込み

と理解した。

f:id:msyksphinz:20161006012527p:plain

では、AXI4 DMAを使うためにVivado-HLSで作成した行列積乗算回路をどのように変更すれば良いのだろうか。

浮動小数点行列積回路をAXI4-Streamに対応させる

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

AXI4 DMAを使うためには、上の図にもあるとおり、インタフェースをAXI4 Streamにしなければならない。 このため、AXI4-Streamに対応した浮動小数点行列積回路を設計しなければならない。

このために、前回設計したmatrix_hls回路に対してWrapperを用意する。

void hls_matrix_accel(AXI_VAL INPUT_STREAM[2048], AXI_VAL OUTPUT_STREAM[1024])
{
  //Map ports to Vivado HLS interfaces
#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
#pragma HLS INTERFACE axis port=INPUT_STREAM
#pragma HLS INTERFACE axis port=OUTPUT_STREAM

  wrapped_mmult_hw<float,32,32*32,4,5,5>(INPUT_STREAM, OUTPUT_STREAM);
}

本体はこちら。

template <typename T, int DIM, int SIZE, int U, int TI, int TD>
void wrapped_mmult_hw (AXI_VAL in_stream[2*SIZE], AXI_VAL out_stream[SIZE])
{
  T A[DIM][DIM], B[DIM][DIM], C[DIM][DIM];
  assert (sizeof(T)*8 == 32);
  // stream in the 2 input matrices
  for (int i = 0; i < DIM; i++) {
    for (int j = 0; j < DIM; j++) {
      #pragma HLS PIPELINE II=1
      int k=i*DIM+j;
      A[i][j] = pop_stream<T,U,TI,TD>(in_stream[k]);
    }
  }
  for (int i = 0; i < DIM; i++) {
    for (int j = 0; j < DIM; j++) {
      #pragma HLS PIPELINE II=1
      int k=i*DIM+j+SIZE;
      B[i][j] = pop_stream<T,U,TI,TD>(in_stream[k]);
    }
  }

  // do multiplication
  matrix_mul<T,DIM>(A,B,C);

  for (int i = 0; i < DIM; i++) {
    for (int j = 0; j < DIM; j++) {
      #pragma HLS PIPELINE II=1
      int k=i*DIM+j;
      out_stream[k] = push_stream<T,U,TI,TD>(C[i][j], k==1023);
    }
  }
}

パイプライン識別子と、push_stream(), pop_stream() を使ってデータの転送を行っている。

上記のpush_stream(), pop_stream()とは何だろうと思っていろいろ調べていたのだが、どうやらそのような実装があるようだ。

http://xilinx.eetrend.com/files-eetrend-xilinx/download/201307/4240-8636-xapp1170-zynq-hls.pdf

template <typename T, int U, int TI, int TD>
inline ap_axiu <sizeof(T)*8,U,TI,TD> push_stream(T const &v, bool last = false)
{
  ap_axiu<sizeof(T)*8,U,TI,TD> e;
  assert(sizeof(T) == sizeof(int));
  union
  {
    int oval;
    T ival;
  } converter;
  converter.ival = v;
  e.data = converter.oval;
  // set it to sizeof(T) ones
  e.strb = -1;
  e.keep = 15; //e.strb;
  e.user = 0;
  e.last = last ? 1 : 0;
  e.id = 0;
  e.dest = 0;
  return e;
}


template <typename T, int U, int TI, int TD>
inline T pop_stream(ap_axiu <sizeof(T)*8,U,TI,TD> const &e)
{
  assert(sizeof(T) == sizeof(int));
  union
  {
    int ival;
    T oval;
  } converter;
  converter.ival = e.data;
  T ret = converter.oval;
  volatile ap_uint<sizeof(T)> strb = e.strb;
  volatile ap_uint<sizeof(T)> keep = e.keep;
  volatile ap_uint<U> user = e.user;
  volatile ap_uint<1> last = e.last;
  volatile ap_uint<TI> id = e.id;
  volatile ap_uint<TD> dest = e.dest;
  return ret;
}

上記のインタフェースをVivado-HLSで合成した。合成結果は以下のようになった。

f:id:msyksphinz:20161006013753p:plain

今回は演算対象の行列は32×32とした。前回48×48で比較したので、一概に比較は出来ないけど、まあスケールするとして十分に速いだろう。

追記: 48×48の行列積でも同様に合成してみた

あれ、前回最速で7000サイクルくらいだったからちょっと遅いなあ。。。

f:id:msyksphinz:20161006014138p:plain

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

今度は、これをVivadoでインプリメントしてみよう。