FC2カウンター FPGAの部屋 HLSストレーム・インターフェースの畳み込み層1(Cソースコード、C コードの合成、Export RTL)

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

FPGAの部屋

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

HLSストレーム・インターフェースの畳み込み層1(Cソースコード、C コードの合成、Export RTL)

(2018/04/14 : 修正、C ソースコードが間違っていたので、ブログを書き直しました)

AXI4-Stream インターフェースの畳み込み層のブログ記事5つを下に示す。
AXI4-Stream インターフェースの畳み込み層1(C コードの合成)
AXI4-Stream インターフェースの畳み込み層2(C シミュレーション)
AXI4-Stream インターフェースの畳み込み層3(C ソースコード)
AXI4-Stream インターフェースの畳み込み層4(C/RTL 協調シミュレーションとExport RTL)
AXI4-Stream インターフェースの畳み込み層5(出力値のヘッダファイルを出力)
これらのブログ記事に示す畳み込み層は特徴マップの個数分のストリームのデータを持っているが、そこは配列でなく特徴マップごとに変数を作っていた。これは、AXI4-Streamのテンプレートを使用すると配列が書けないからだった。(”Vivado HLSでのAXI4-Stream のテンプレートを作成する1”参照)
Vivado HLSでのAXI4-Stream のテンプレートを作成する3”で検証した結果によると、HLS ストリームでテンプレートを作れば、特徴マップの個数分のデータを配列として、HLS ストリームに加えられることが分かったので、AXI4-Stream インターフェースの畳み込み層をHLS ストリームで書き換えてみることにした。

最初に、すべての層で使う予定のHLS ストリームのテンプレートを書いた layer_general.h を示す。

// layer_general.h
// 2018/04/12 by marsee
//

#ifndef __LAYER_GENERAL_H__
#define __LAYER_GENERAL_H__

#include <ap_fixed.h>

template<int W, int I, int N, int U>
    struct ap_fixed_axis{
        ap_fixed<W, I, AP_TRN, AP_WRAP> data[N];
        ap_uint<U>  user;
        ap_uint<1>  last;
    };

template<int W, int I, int N, int U>
    struct ap_ufixed_axis{
        ap_ufixed<W, I, AP_TRN, AP_WRAP> data[N];
        ap_uint<U>  user;
        ap_uint<1>  last;
    };

template<int N, int U>
    struct ap_float_axis{
        float data[N];
        ap_uint<U>  user;
        ap_uint<1>  last;
    };

#endif


conv_layer.h を示す。

// conv_layer.h
// 2018/04/12 by marsee

#ifndef __CONV_LAYER_H__
#define __CONV_LAYER_H__

#define HORIZONTAL_PIXEL_WIDTH  56
#define VERTICAL_PIXEL_WIDTH    10

static const size_t NUMBER_OF_KERNEL = 2;
static const size_t ARRAY_SIZE = 5;
static const size_t W = 16;
static const size_t I = 6;

typedef ap_ufixed<8, 0, AP_TRN, AP_WRAP> in_type;
typedef ap_fixed<22, 6, AP_TRN, AP_WRAP> val_type;
typedef ap_fixed<16, 6, AP_TRN, AP_WRAP> out_type;

#endif


conv_layer.cpp を示す。

// conv_layer.cpp
// 2018/04/12 by marsee
//

#include "ap_int.h"
#include "hls_stream.h"

#include "layer_general.h"

#include <ap_axi_sdata.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_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >&outs){
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS DATA_PACK variable=outs

    ap_axiu<32,1,1,1> pix;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,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<16, 8, 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];
                conv_out.data[k] = 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);
}


畳み込み層が最初の層なので、入力はAXI4-Stream インターフェースで、出力は HLS ストリーム・インターフェースとする。

conv_layer プロジェクトを示す。
hls_conv_layer_1_180413.png

まだ、シミュレーション用のテストベンチは作成していないが、C コードの合成を行った。
結果を示す。
hls_conv_layer_2_180413.png

Estimated は 9.40 ns で制約を満たしている。
Latency は 576 クロックで、総ピクセル数が 560 なので、576 / 560 ≒ 1.03 クロック/ピクセルだった。ほぼ 1 クロックで 1 ピクセルの処理が行えていると言えると思う。
リソースは DSP48E が 23 個、FF が 2,584 個、LUT が 2,454 個使用されている。

Export RTL を行った。
Vivado synthesis, place and route にチェックを入れた。
hls_conv_layer_3_180413.png

結果を示す。
hls_conv_layer_4_180413.png

リソース使用量は、SLICE が 268 個、LUT が 552 個、FF が 847 個、DSP が 23 個だった。
CP achieved post-implementation は 8.703 ns で、問題なかった。
  1. 2018年04月13日 04:46 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


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

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