FC2カウンター FPGAの部屋 AXI4-Stream インターフェースの畳み込み層1(C コードの合成)

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

FPGAの部屋

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

AXI4-Stream インターフェースの畳み込み層1(C コードの合成)

Vivado HLSでのAXI4-Stream のテンプレートを作成する2”でAXI4-Stream インターフェースのテンプレートが決定できたので、いよいよ畳み込み層を作ってみよう。畳み込み層は今まで作ってきた画像フィルタと何ら変わるところはない。

畳み込み層のVivado HLS 2017.4 プロジェクトの conv_layer プロジェクトを示す。
だけど、まだテストベンチが完成していないので、ハードウェア化する conv_layter() 関数が正しいかどうか?はまだ分からない?
そして、conv_layer.cpp は 2 つの方法で作成した。1 つは、2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装で、もう1つはVivado HLS のHLS ビデオライブラリの hls::LineBuffer と hls::Window を使用した実装だ。この2つの畳み込み層の実装を比べてみよう。2つともそれぞれ、ラインバッファと入力データ・ウインドウ以外は同じ実装になっている。
まずは、2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装から見てみる。
conv_layer_1_180214.png

共通に使用する conv_layer.h から示す。

// conv_layter.h
// 2018/02/06 by marsee
//

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

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed1_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
        } 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 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 HORIZONTAL_PIXEL_WIDTH  56
#define VERTICAL_PIXEL_WIDTH    10

#define ARRAY_SIZE                5

#define NUMBER_OF_KERNEL        2

typedef ap_ufixed<80, AP_TRN, AP_WRAP> in_type;
typedef ap_fixed<226, AP_TRN, AP_WRAP> val_type;
typedef ap_fixed<166, AP_TRN, AP_WRAP> out_type;

#endif


2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装の conv_layer.cpp を示す。

// conv_layer.cpp (line_buf, pix_mat)
// 2018/02/06 by marsee
//

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

#include "conv_layer.h"
#include "conv1_weight.h"
#include "conv1_bias.h"

int conv_layer(hls::stream<ap_axiu<32,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs){
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axiu<32,1,1,1> pix;
    ap_fixed2_axis<16,6,1,1,1> conv_out;

    in_type line_buf[ARRAY_SIZE-1][HORIZONTAL_PIXEL_WIDTH];
#pragma HLS ARRAY_PARTITION variable=line_buf block factor=4 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    in_type pix_mat[ARRAY_SIZE][ARRAY_SIZE];
#pragma HLS array_partition variable=pix_mat complete

    in_type ap_uf_pix;
    val_type val;

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

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

            ap_uf_pix = (in_type)((ap_ufixed<168, AP_TRN, AP_WRAP>)(pix.data & 0xff) / 256);

            // 2次元配列のデータを左シフト
            Loop4 : for (int k=0; k<ARRAY_SIZE; k++){
                Loop5 : for (int m=0; m<ARRAY_SIZE-1; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }

            Loop6: for (int i=0; i<ARRAY_SIZE-1; i++){ // 以前の行のデータを line_buf から入力
                pix_mat[i][ARRAY_SIZE-1] = line_buf[i][x];
            }
            pix_mat[ARRAY_SIZE-1][ARRAY_SIZE-1] = ap_uf_pix; // pix_mat の最後に新しいデータを入力

            Loop7: for (int i=0; i<ARRAY_SIZE-2; i++){ // 行の入れ替え
                line_buf[i][x] = line_buf[i+1][x];
            }
            line_buf[ARRAY_SIZE-2][x] = ap_uf_pix;

            // conv_layer の演算
            for (int k=0; k<NUMBER_OF_KERNEL; k++){
                val = 0.0;
                for (int j=0; j<ARRAY_SIZE; j++){
                    for (int i=0; i<ARRAY_SIZE; i++){
                        val += (val_type)pix_mat[j][i] * (val_type)conv1_weight[k][0][j][i];
                    }
                }
                val += (val_type)conv1_bias[k];
                if(k==0)
                    conv_out.data.data0 = val;
                else
                    conv_out.data.data1 = val;
            }


            // 最初のARRAY_SIZE-1行とその他の行の最初のARRAY_SIZE-1列は無効データなので出力しない
            if (x<(ARRAY_SIZE-1) || y<(ARRAY_SIZE-1))
                continue;
            else { // 有効なデータの時
                if (x==(ARRAY_SIZE-1) && y==(ARRAY_SIZE-1)){ // 最初のデータでは、TUSERをアサートする
                    conv_out.user = 1;
                } else {
                    conv_out.user = 0;
                }

                if (x == (HORIZONTAL_PIXEL_WIDTH-1)){    // 行の最後で TLAST をアサートする
                    conv_out.last = 1;
                } else {
                    conv_out.last = 0;
                }

                outs << conv_out;
            }
         }
     }
     return(0);
}


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

Latency は 577 クロックだった。処理する画像は 560 ピクセルなので、約 1.02 クロック/ピクセルとなった。性能的には問題ないと思う。
リソース使用量は DSP48E を 23 個、FF を 2745 個、LUT を 2786 個使用している。問題ないリソース使用量だと思う。

次に、Vivado HLS のHLS ビデオライブラリの hls::LineBuffer と hls::Window を使用した実装の conv_layer.cpp を示す。
conv_layer_3_180214.png

// conv_layer.cpp (hls::LineBuffer, hls::Window)
// 2018/02/06 by marsee
//

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

#include "conv_layer.h"
#include "conv1_weight.h"
#include "conv1_bias.h"

int conv_layer(hls::stream<ap_axiu<32,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs){
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axiu<32,1,1,1> pix;
    ap_fixed2_axis<16,6,1,1,1> conv_out;

    hls::LineBuffer<ARRAY_SIZE-1, HORIZONTAL_PIXEL_WIDTH, in_type> linebuf;
    hls::Window<ARRAY_SIZE, ARRAY_SIZE, in_type> mbuf;

    in_type ap_uf_pix;
    val_type val;

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

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

            ap_uf_pix = (in_type)((ap_ufixed<168, AP_TRN, AP_WRAP>)(pix.data & 0xff) / 256);

            mbuf.shift_pixels_left();    // mbuf の列を1ビット左シフト
            for(int i=0; i<ARRAY_SIZE-1; i++){
                mbuf.insert_pixel(linebuf.getval(i,x), i, ARRAY_SIZE-1);
            }
            mbuf.insert_pixel(ap_uf_pix, ARRAY_SIZE-1, ARRAY_SIZE-1);

            // LineBuffer の更新
            linebuf.shift_pixels_up(x);
            linebuf.insert_bottom_row(ap_uf_pix, x);

            // conv_layer の演算
            for (int k=0; k<NUMBER_OF_KERNEL; k++){
                val=0.0;
                for (int j=0; j<ARRAY_SIZE; j++){
                    for (int i=0; i<ARRAY_SIZE; i++){
                        val += (val_type)mbuf.getval(j,i) * (val_type)conv1_weight[k][0][j][i];
                    }
                }
                val += (val_type)conv1_bias[k];
                if(k==0)
                    conv_out.data.data0 = val;
                else
                    conv_out.data.data1 = val;
            }


            // 最初のARRAY_SIZE-1行とその他の行の最初のARRAY_SIZE-1列は無効データなので出力しない
            if (x<(ARRAY_SIZE-1) || y<(ARRAY_SIZE-1))
                continue;
            else { // 有効なデータの時
                if (x==(ARRAY_SIZE-1) && y==(ARRAY_SIZE-1)){ // 最初のデータでは、TUSERをアサートする
                    conv_out.user = 1;
                } else {
                    conv_out.user = 0;
                }

                if (x == (HORIZONTAL_PIXEL_WIDTH-1)){    // 行の最後で TLAST をアサートする
                    conv_out.last = 1;
                } else {
                    conv_out.last = 0;
                }

                outs << conv_out;
            }
         }
     }
     return(0);
}



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

なんと、前の2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装と全く一緒の実装だった。。。
  1. 2018年02月14日 06:49 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


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

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