FC2カウンター FPGAの部屋 AXI4-Stream インターフェースの全結合層1(C コードや指示子による性能差1)
FC2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

AXI4-Stream インターフェースの全結合層1(C コードや指示子による性能差1)

AXI4-Stream インターフェースの Max Pooling に続いて、AXI4-Stream インターフェースの全結合層を作っていこう。

最初に、C ソースコードを貼るのだが、全結合層はどのC ソースコードや指示子がリソース使用量とレイテンシの両面から良いのか良く分からないので、とりあえずのたたき台としてのコードであるということを断っておく。
まずは、affine_layer1.h から貼っておく。

// affine_layer1.h
// 2018/02/25 by marsee
//

#ifndef __AFFINE_LAYER1_H__
#define __AFFINE_LAYER1_H__
#include <ap_fixed.h>

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed2_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data1;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int U, int TI, int TD>
    struct float2_axis{
        struct data {
            float data0;
            float data1;
        } data;
        ap_uint<1> keep;
        ap_uint<1> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

#define NUMBER_OF_MIDDLE_LAYER    100

typedef struct {
    ap_fixed<19,7,AP_TRN,AP_WRAP> data [NUMBER_OF_MIDDLE_LAYER];
} mdata_type;

typedef struct {
    float data [NUMBER_OF_MIDDLE_LAYER];
} fmdata_type;

typedef ap_fixed<19,7,AP_TRN,AP_WRAP> affine_type;

#define V_PRE_LAYER_HIGHT    3
#define H_PRE_LAYER_WIDTH    26

#endif


次に、affine_layer1.cpp を貼っておく。

// affine_layer1.cpp
// 2018/02/26 by marsee
//

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <hls_video.h>

#include "affine_layer1.h"
#include "af1_weight.h"
#include "af1_bias.h"

int affine_layer1(hls::stream<ap_fixed2_axis<16,6,1,1,1> >& ins,
        mdata_type & outd){
//#pragma HLS ARRAY_PARTITION variable=af1_weight complete dim=1
#pragma HLS INTERFACE ap_hs register port=outd
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS DATA_PACK variable=outd

    ap_fixed2_axis<16,6,1,1,1> stdata;
    affine_type dot[100];
//#pragma HLS ARRAY_PARTITION variable=dot complete dim=1

    Loop1: do {
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
    // user が 1になった時にフレームがスタートする
        ins >> stdata;
    } while(stdata.user == 0);

    Loop2: for (int y=0; y<V_PRE_LAYER_HIGHT; y++){
        Loop3: for (int x=0; x<H_PRE_LAYER_WIDTH; x++){
//#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> stdata;    // AXI4-Stream からの入力

            Loop4: for (int col=0; col<100; col++){
//#pragma HLS PIPELINE II=1
                if (x==0 && y==0// 最初は 0 にクリアする
                    dot[col] = 0;

                dot[col] += stdata.data.data0 * af1_weight[y*H_PRE_LAYER_WIDTH+x][col];
                dot[col] += stdata.data.data1 * af1_weight[V_PRE_LAYER_HIGHT*H_PRE_LAYER_WIDTH+y*H_PRE_LAYER_WIDTH+x][col];

                if (y==V_PRE_LAYER_HIGHT-1 && x==H_PRE_LAYER_WIDTH-1){ // 最後はバイアスを加算する
                    dot[col] += af1_bias[col];
                    if(dot[col] < 0)    // ReLU
                        dot[col] = 0;
                }
            }
        }
    }

    Loop5: for (int col=0; col<100; col++){ // 出力にコピー
#pragma HLS UNROLL
        outd.data[col] = dot[col];
    }

    return(0);
}


出力は、ap_fixed<19,7,AP_TRN,AP_WRAP> data [NUMBER_OF_MIDDLE_LAYER] なのだが、配列でそのままだとRAM インターフェースになるので、とりあえず全部の出力線を確保するために、DATA_PACK指示子を使用している。

#pragma HLS DATA_PACK variable=outd

そしてその、IOレベルのインターフェースは ap_hs にしている。

#pragma HLS INTERFACE ap_hs register port=outd


PIPELINE指示子とARRAY_PARTITION指示子は後で入れるが、とりあえずはコメントアウトしてある。
このままC コードの合成を行う。
affine_layer_21_180228.png

C コードの合成結果を示す。
affine_layer_22_180228.png

Estmated は 9.63 ns で制約を満たしている。
Latency は 39371 クロックから 47171 クロックだった。100 MHz クロックとすると、393.71 us から 471.71 us である。
リソース使用量は、BRAM_18K が 13 個、DSP48E が 3 個、FF が 4,266 個、LUT が 1,354 個だった。

Analysis 画面を示す。
affine_layer_23_180228.png

53 ステートまである。dot 配列もBRAM に割り当てられているので、2 ポートずつ 2 クロックに渡ってアクセスしているのが分かる。

Resource 画面。
af1_weight と dot がBRAM にアサインされているのが分かる。
affine_layer_24_180228.png


次に、重みの af1_weight[][] に ARRAY_PARTITIONの complete オプション付きの指示子を付けてみよう。
これは、Loop3 の下のPIPELINE指示子を生かすときには、中間層の 100 個 X 畳み込みフィルタのカーネル数の 2 の 200 並列になるので、その場合にはBRAMではポートが足りなくなるため必要になるのだが、現在は要らないけど入れてみよう。
affine_layer_8_180228.png

C コードの合成を行った。結果を示す。
affine_layer_9_180228.png

Latency は 54,971 クロックから 62,771 クロックで、前より遅くなってしまった。。。
BRAM_18K は、3 個で前より 10 個減少した。DSP48E も 2 個で 1 個減少した。FF は 5,418 個で増えた。LUT も 3,949 個で 3 倍程度に増えている。

Analysis 画面を示す。
affine_layer_10_180228.png

Resource 画面を示す。
dot_V がBRAM_18K なので、read が並んでいる。
affine_layer_11_180228.png
  1. 2018年02月28日 21:47 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
http://marsee101.blog.fc2.com/tb.php/4093-5bde37ce
この記事にトラックバックする(FC2ブログユーザー)