前回行列乗算回路を最適化することで、ソフトウェアと比較して大幅に性能を向上させることに成功した。
その時に参照していたのが以下の資料だが、そこには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) : マスター ペリフェラルからデータを読み出し、データ書き込み
と理解した。
では、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で合成した。合成結果は以下のようになった。
今回は演算対象の行列は32×32とした。前回48×48で比較したので、一概に比較は出来ないけど、まあスケールするとして十分に速いだろう。
追記: 48×48の行列積でも同様に合成してみた
あれ、前回最速で7000サイクルくらいだったからちょっと遅いなあ。。。
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でインプリメントしてみよう。