FC2カウンター FPGAの部屋 DNN

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

FPGAの部屋

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

AXI4-Stream インターフェースのMax Pooling 2(Cシミュレーションと合成)

AXI4-Stream インターフェースのMax Pooling 1(ソースコード)”の続き。

前回は、AXI4-Stream インターフェースの Max Pooling のソースコードを貼った。今回はVivado HLS 2017.4 で max_pooling プロジェクトを作成して、C シミュレーションと C コードの合成を行う。

まずは、Vivado HLS 2017.4 で max_pooling プロジェクトを作成した。

次に、C シミュレーションを行った。結果を示す。
Max_Pooling_1_180225.png

Max Pooling の出力を C のヘッダにまとめた max_pooling_output.h を出力している。その一部を示す。
const float mp_fout[78][2]
Max_Pooling_9_180225.png

const ap_fixed<16, 6, AP_TRN, AP_WRAP> mp_out[78][2]
Max_Pooling_10_180225.png

次に、C コードの合成を行った。結果を示す。
Max_Pooling_2_180225.png

Estmated は 6.31 ns で十分だ。Latency は 316 クロックだった。データは 312 個なので、十分な性能と言えよう。
FF は 422 個、LUT は 815 個を使用している。BRAM_18K は使用されていない。

合成結果のAXI4 Lite Slave インターフェースのアドレスマップを示す。

//------------------------Address Info-------------------
// 0x00 : Control signals
//        bit 0  - ap_start (Read/Write/COH)
//        bit 1  - ap_done (Read/COR)
//        bit 2  - ap_idle (Read)
//        bit 3  - ap_ready (Read)
//        bit 7  - auto_restart (Read/Write)
//        others - reserved
// 0x04 : Global Interrupt Enable Register
//        bit 0  - Global Interrupt Enable (Read/Write)
//        others - reserved
// 0x08 : IP Interrupt Enable Register (Read/Write)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x0c : IP Interrupt Status Register (Read/TOW)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x10 : Data signal of ap_return
//        bit 31~0 - ap_return[31:0] (Read)
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)



Analysis 画面を示す。
Max_Pooling_3_180225.png

C 4 ステートまである。

Max_Pooling_4_180225.png
  1. 2018年02月25日 03:59 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースのMax Pooling 1(ソースコード)

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

52 x 6 を入力して 2 x 2 のウインドウでMax Pooling を行う。ストライドは 2 だ。よって、出力は半分の 26 x 3 となる。

C++ のソースコードを貼っていく。

まずは、max_pooling.h から貼っておく。

// max_pooling.h
// 2018/02/19 by marsee
//

#ifndef __MAX_POOLING_H__
#define __MAX_POOLING_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 H_PIXEL_WIDTH_IN    52
#define V_PIXEL_WIDTH_IN    6
#define H_PIXEL_WIDTH_OUT    26
#define V_PIXEL_WIDTH_OUT    3

#define ARRAY_SIZE                2

#define NUMBER_OF_KERNEL        2

#define X_STRIDE                2
#define Y_STRIDE                2

typedef ap_fixed<166, AP_TRN, AP_WRAP> conv_type;

#endif


max_pooling.cpp を貼っておく。

// max_pooling.cpp
// 2018/02/20 by marsee
//

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

#include "max_pooling.h"

int max_pooling(hls::stream<ap_fixed2_axis<16,6,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_fixed2_axis<16,6,1,1,1> pix;
    ap_fixed2_axis<16,6,1,1,1> mp_out;

    conv_type line_buf[NUMBER_OF_KERNEL][ARRAY_SIZE-1][H_PIXEL_WIDTH_IN];
#pragma HLS ARRAY_PARTITION variable=line_buf block factor=2 dim=1
#pragma HLS ARRAY_PARTITION variable=line_buf block factor=1 dim=2

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

    conv_type val[NUMBER_OF_KERNEL], conv_data;

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

            Loop4: for (int n=0; n<NUMBER_OF_KERNEL; n++){
#pragma HLS UNROLL
                if (n == 0)
                    conv_data = pix.data.data0;
                else
                    conv_data = pix.data.data1;

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

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

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

                // max pooling の検索
                Loop9 : for (int k=0; k<ARRAY_SIZE; k++){
#pragma HLS UNROLL
                    Loop10 : for (int m=0; m<ARRAY_SIZE-1; m++){
                        if (k==0 && m==0){
                            val[n] = pix_mat[n][k][m];
                        } else if (val[n] < pix_mat[n][k][m]){
                            val[n] = pix_mat[n][k][m];
                        }
                    }
                }
                if (n == 0)
                    mp_out.data.data0 = val[0];
                else
                    mp_out.data.data1 = val[1];

                if (x==X_STRIDE-1 && y==Y_STRIDE-1){ // 最初のデータでは、TUSERをアサートする
                    mp_out.user = 1;
                } else {
                    mp_out.user = 0;
                }

                if (x == H_PIXEL_WIDTH_IN-1){    // 行の最後で TLAST をアサートする
                    mp_out.last = 1;
                } else {
                    mp_out.last = 0;
                }
            }
            if (x%X_STRIDE==X_STRIDE-1 && y%Y_STRIDE==Y_STRIDE-1){ // ストライド
                outs << mp_out;
            }
        }
    }
    return(0);
}


max_pooling_tb.cpp を貼っておく。

// max_pooling_tb.cpp
// 2018/02/23 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <math.h>
#include <ap_axi_sdata.h>
#include <hls_video.h>

#include "max_pooling.h"
#include "relu_output.h"

int max_pooling(hls::stream<ap_fixed2_axis<16,6,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs);

int max_pooling_soft(hls::stream<float2_axis<1,1,1> >& ins,
        hls::stream<float2_axis<1,1,1> >& outs);

int main(){
    using namespace std;

    hls::stream<ap_fixed2_axis<16,6,1,1,1> > ins;
    hls::stream<float2_axis<1,1,1> > ins_soft;
    hls::stream<ap_fixed2_axis<16,6,1,1,1> > outs;
    hls::stream<float2_axis<1,1,1> > outs_soft;

    float mp_fout[H_PIXEL_WIDTH_OUT*V_PIXEL_WIDTH_OUT][2];
    conv_type mp_out[H_PIXEL_WIDTH_OUT*V_PIXEL_WIDTH_OUT][2];

    ap_fixed2_axis<16,6,1,1,1> pix;
    float2_axis<1,1,1> fpix;

    // ins に入力データを用意する
    for(int i=0; i<5; i++){    // dummy data
        pix.user = 0;
        pix.data.data0 = (conv_type)i;
        pix.data.data1 = (conv_type)i;
        ins << pix;
        fpix.user = 0;
        fpix.data.data0 = (float)i;
        fpix.data.data1 = (float)i;
        ins_soft << fpix;
    }

    // 1 画面分のデータを ins、ins_soft に入力する
    for(int j=0; j < V_PIXEL_WIDTH_IN; j++){
        for(int i=0; i < H_PIXEL_WIDTH_IN; i++){
            pix.data.data0 = relu_out[j*H_PIXEL_WIDTH_IN+i][0];
            pix.data.data1 = relu_out[j*H_PIXEL_WIDTH_IN+i][1];
            fpix.data.data0 = relu_fout[j*H_PIXEL_WIDTH_IN+i][0];
            fpix.data.data1 = relu_fout[j*H_PIXEL_WIDTH_IN+i][1];

            if (j==0 && i==0){    // 最初のデータの時に TUSER を 1 にする
                pix.user = 1;
                fpix.user = 1;
            } else {
                pix.user = 0;
                fpix.user = 0;
            }

            if (i == H_PIXEL_WIDTH_IN-1){ // 行の最後でTLASTをアサートする
                pix.last = 1;
                fpix.last = 1;
            } else {
                pix.last = 0;
                fpix.last = 0;
            }

            ins << pix;
            ins_soft << fpix;
        }
    }

    max_pooling(ins, outs);
    max_pooling_soft(ins_soft, outs_soft);

        // outs, outs_soft を mp_out[][], relu_fout[][] に出力する
    for(int j=0; j < V_PIXEL_WIDTH_OUT; j++){
        for(int i=0; i < H_PIXEL_WIDTH_OUT; i++){
            outs >> pix;
            outs_soft >> fpix;

            mp_out[j*H_PIXEL_WIDTH_OUT+i][0] = pix.data.data0;
            mp_out[j*H_PIXEL_WIDTH_OUT+i][1] = pix.data.data1;
            mp_fout[j*H_PIXEL_WIDTH_OUT+i][0] = fpix.data.data0;
            mp_fout[j*H_PIXEL_WIDTH_OUT+i][1] = fpix.data.data1;
            printf("%d, %d, data0 = %f, data1 = %f, fdata0 = %f, fdata1 = %f\n", j, i, (float)pix.data.data0, (float)pix.data.data1, fpix.data.data0, fpix.data.data1);
            if ((double)pow((double)pix.data.data0-(double)fpix.data.data0,(double)2) > 4 ||
                    (double)pow((double)pix.data.data1-(double)fpix.data.data1,(double)2) > 4){ // 2乗誤差が4よりも大きい
                printf("ERROR HW and SW results mismatch i = %ld, j = %ld, HW = %f, %f, SW = %f, %f\n", i, j, (float)pix.data.data0, (float)pix.data.data1, fpix.data.data0, fpix.data.data1);
                //return(1);
            }
        }
    }
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    // ReLU の結果をヘッダファイルに出力
    ofstream OH("max_pooling_output.h");
    OH << "// max_pooling_output.h" << endl;
    time_t now = time(0);
    struct tm* localNow = localtime(&now);
    OH << "// " << localNow->tm_year+1900 << "/" << localNow->tm_mon+1 << "/" << localNow->tm_mday;
    OH << " " << setw(2) << setfill('0') << localNow->tm_hour << ":" << localNow->tm_min << ":" << localNow->tm_sec << " by marsee" << endl;
    OH << "//" << endl;
    OH << endl;
    OH << "#ifndef __MAX_POOLING_OUTPUT_H__" << endl;
    OH << "#define __MAX_POOLING_OUTPUT_H__" << endl;
    OH << endl;
    OH << "const float mp_fout[" << V_PIXEL_WIDTH_OUT*H_PIXEL_WIDTH_OUT  << "][" << NUMBER_OF_KERNEL << "] = {" << endl;
    for (int y=0; y<V_PIXEL_WIDTH_OUT ; y++){
        for (int x=0; x<H_PIXEL_WIDTH_OUT ; x++){
            OH << "    {" << fixed << setprecision(12) << mp_fout[V_PIXEL_WIDTH_OUT*y+x][0] << ", "
                    << mp_fout[V_PIXEL_WIDTH_OUT*y+x][1] << "}";
            if (y==V_PIXEL_WIDTH_OUT-1 && x==H_PIXEL_WIDTH_OUT-1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;

    OH << "const ap_fixed<16, 6, AP_TRN, AP_WRAP> mp_out[" << V_PIXEL_WIDTH_OUT*H_PIXEL_WIDTH_OUT  << "][" << NUMBER_OF_KERNEL << "] = {" << endl;
    for (int y=0; y<V_PIXEL_WIDTH_OUT ; y++){
        for (int x=0; x<H_PIXEL_WIDTH_OUT ; x++){
            OH << "    {" << fixed << setprecision(12) << (float)mp_out[V_PIXEL_WIDTH_OUT*y+x][0] << ", "
                    <<  (float)mp_out[V_PIXEL_WIDTH_OUT*y+x][1] << "}";
            if (y==V_PIXEL_WIDTH_OUT -1 && x==H_PIXEL_WIDTH_OUT -1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;
    OH << "#endif" << endl;

    return(0);
}    


int max_pooling_soft(hls::stream<float2_axis<1,1,1> >& ins,
        hls::stream<float2_axis<1,1,1> >& outs){

    float2_axis<1,1,1> fpix;
    float fpixd_ary[NUMBER_OF_KERNEL][V_PIXEL_WIDTH_IN][H_PIXEL_WIDTH_IN];
    float fval[NUMBER_OF_KERNEL];

    do {
    // user が 1になった時にフレームがスタートする
        ins >> fpix;
    } while(fpix.user == 0);

    for (int y=0; y<V_PIXEL_WIDTH_IN; y++){
        for (int x=0; x<H_PIXEL_WIDTH_IN; x++){
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> fpix;

            fpixd_ary[0][y][x] = fpix.data.data0;
            fpixd_ary[1][y][x] = fpix.data.data1;
        }
    }

    for (int y=0; y<V_PIXEL_WIDTH_IN-1; y+=2){
        for (int x=0; x<H_PIXEL_WIDTH_IN-1; x+=2){
            for(int p=0; p<2; p++){
                for(int m=0; m<2; m++){
                    for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            fval[p] = fpixd_ary[p][y][x];
                        } else if(fval[p] < fpixd_ary[p][y+m][x+n]){
                            fval[p] = fpixd_ary[p][y+m][x+n];
                        }
                    }
                }
            }
            fpix.data.data0 = fval[0];
            fpix.data.data1 = fval[1];

            if(x==0 && y==0)
                fpix.user = 1;
            else
                fpix.user = 0;

            if(x==V_PIXEL_WIDTH_OUT-2)
                fpix.last = 1;
            else
                fpix.last = 0;

            outs << fpix;
        }
    }

    return(0);
}

  1. 2018年02月24日 21:08 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースのReLU 3(C/RTL協調シミュレーションとExport RTL )

AXI4-Stream インターフェースのReLU 2(CシミュレーションとCコードの合成)”の続き。

前回は、ReLU プロジェクトのC シミュレーションとC コードの合成を行った。今回は、ReLU プロジェクトのC/RTL 協調シミュレーションとExport RTLを行う。

C/RTL 協調シミュレーションを行った。結果を示す。
relu_12_180222.png

Latency は 345 クロックだった。C コードの合成の時は、317 クロックだったので、こんなものだと思う。AXI4 Lite Slave インターフェースでStart を制御したり、終了を確認したりするので、どうしても余計に時間がかかる。

C/RTL 協調シミュレーションの波形を示す。
relu_13_180222.png

ins_TVALID, ins_TREADY, outs_TVALID, outs_TREADY 共にほとんど直線でスループット的にも問題ないというか、そのような波形だろうということは、Latency の値を見たときに推測済みなので、確認できた。

AXI4 Lite Slave インターフェースの波形を示す。
relu_14_180222.png

レジスタの start ビットを立ててから終了の監視を行っている。

Export RTL を行った。結果を示す。
なお、Vivado synthesis, place and route にチェックを入れてある。
relu_15_180222.png

LUT は 902 だが、SRL が 2 あるので、実質的には、904 個かかな?これは、C コードの合成の時の577 個よりも増えている。増えることもあるんだね。。。
FF は787 個で、C コードの合成の 1227 個よりも減っている。
CP achieved post-implementation の値が 9.359 ns で微妙になっているが、このままとしよう。
  1. 2018年02月22日 04:51 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースのReLU 2(CシミュレーションとCコードの合成)

AXI4-Stream インターフェースのReLU 1(C++ ソースコード)”の続き。

前回は、C++ ソースコードを貼った。今回は、Vivado HLS 2017.4 の relu プロジェクトで C シミュレーションと C コードの合成を行う。

まずは、Vivado HLS 2017.4 の relu プロジェクトを示す。
relu_1_180221.png

C シミュレーションを行った。ここでは、ReLU を実行した結果の relu_output.h を出力することを目的としている。
relu_2_180221.png

これが、relu_output.h だ。次のマックス・プーリング層への入力となる。
これが、float で演算したときの relu_fout[312][2] だ。ちなみに 312 は畳み込み後の結果の 52 x 6 ピクセルで、2 は畳み込みフィルタの数だ。
relu_3_180221.png

2つ目の畳み込みフィルタの出力が 0 になっている。1 個のフィルタでも行けるのかもしれない?
次に、同じ relu_output.h の const ap_fixed<16, 6, AP_TRN, AP_WRAP> relu_out[312][2] を示す。
relu_4_180221.png

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

Estimated は 6.47 ns で目標の 10 ns をクリアしている。Latency は 317 クロックで、6 x 52 = 312 から 5 クロックしか増えていない。
リソース使用量は FF が 577 個で、LUT が 1227 個だった。

Analysis 画面を示す。
relu_6_180221.png

C5 まである。

これで、C コードの合成は終わりなのだが、最初、relu.cpp の記述が下の様になっていた。
固定小数点数でも 0 は 0.0 と書いたほうが良いかな?と思ってソースコードを書いていた。
relu_7_180221.png

これで C コードの合成を行うと、Estimated がオーバーしてしまう。
relu_8_180221.png

Latency は、1566 で約 5 倍のクロックがかかっている。その証拠に、Initiation achieved が 5 クロックになっている。
リソース使用量も増えている。

Analysis 画面を示す。
relu_9_180221.png

C10 までステートが増えている。

この原因は、relu_ap_dcm_0_no_dsp_64_ip.tcl を開くと分かるが、0.0 と記述したのが、浮動小数点数と認識されているようだ。
relu_10_180221.png

下手に 0.0 と書かないで、0 と書いておこう。コンパイラに怒られたらキャストするか、0.0 と書く方が良さそうだ。
  1. 2018年02月21日 05:13 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースのReLU 1(C++ ソースコード)

AXI4-Stream インターフェースの畳み込み層に続いて、AXI4-Stream インターフェースのReLU IP を作成しよう。

ReLU は 0 以下の数を 0 に変更し、0 以上はそのままとする簡単な関数だ。

C++ のソースコードから貼っていく。
まずは、relu.h から貼っていく。

// relu.h
// 2018/02/20 by marsee
//

#ifndef __RELU_H__
#define __RELU_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 HORIZONTAL_PIXEL_WIDTH  52
#define VERTICAL_PIXEL_WIDTH    6

#define ARRAY_SIZE                2

#define NUMBER_OF_KERNEL        2

typedef ap_fixed<166, AP_TRN, AP_WRAP> conv_type;

#endif


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

// relu.cpp
// 2018/02/20 by marsee
// 2018/02/23 : 0 を conv_type(0.0) に変更
//

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

#include "relu.h"

int relu(hls::stream<ap_fixed2_axis<16,6,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_fixed2_axis<16,6,1,1,1> pix;

    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 からの入力

            if (pix.data.data0 < conv_type(0.0)// データが 0 以下だったら 0 にする
                pix.data.data0 = conv_type(0.0);

            if (pix.data.data1 < conv_type(0.0)// データが 0 以下だったら 0 にする
                pix.data.data1 = conv_type(0.0);

            outs << pix;
        }
    }

    return(0);
}


relu_tb.cpp を貼っておく。

// relu_tb.cpp
// 2018/02/20 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <math.h>
#include <ap_axi_sdata.h>
#include <hls_video.h>

#include "relu.h"
#include "conv_layer_output.h"

int relu(hls::stream<ap_fixed2_axis<16,6,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs);

int relu_soft(hls::stream<float2_axis<1,1,1> >& ins,
        hls::stream<float2_axis<1,1,1> >& outs);

int main(){
    using namespace std;

    hls::stream<ap_fixed2_axis<16,6,1,1,1> > ins;
    hls::stream<float2_axis<1,1,1> > ins_soft;
    hls::stream<ap_fixed2_axis<16,6,1,1,1> > outs;
    hls::stream<float2_axis<1,1,1> > outs_soft;

    float relu_fout[312][2];
    conv_type relu_out[312][2];

    ap_fixed2_axis<16,6,1,1,1> pix;
    float2_axis<1,1,1> fpix;

    // ins に入力データを用意する
    for(int i=0; i<5; i++){    // dummy data
        pix.user = 0;
        pix.data.data0 = (conv_type)i;
        pix.data.data1 = (conv_type)i;
        ins << pix;
        fpix.user = 0;
        fpix.data.data0 = (float)i;
        fpix.data.data1 = (float)i;
        ins_soft << fpix;
    }

    // 1 画面分のデータを ins、ins_soft に入力する
    for(int j=0; j < VERTICAL_PIXEL_WIDTH; j++){
        for(int i=0; i < HORIZONTAL_PIXEL_WIDTH; i++){
            pix.data.data0 = conv_layer_out[j*HORIZONTAL_PIXEL_WIDTH+i][0];
            pix.data.data1 = conv_layer_out[j*HORIZONTAL_PIXEL_WIDTH+i][1];
            fpix.data.data0 = conv_layer_fout[j*HORIZONTAL_PIXEL_WIDTH+i][0];
            fpix.data.data1 = conv_layer_fout[j*HORIZONTAL_PIXEL_WIDTH+i][1];

            if (j==0 && i==0){    // 最初のデータの時に TUSER を 1 にする
                pix.user = 1;
                fpix.user = 1;
            } else {
                pix.user = 0;
                fpix.user = 0;
            }

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

            ins << pix;
            ins_soft << fpix;
        }
    }

    relu(ins, outs);
    relu_soft(ins_soft, outs_soft);

    // outs, outs_soft を relu_out[][], relu_fout[][] に出力する
    for(int j=0; j < VERTICAL_PIXEL_WIDTH; j++){
        for(int i=0; i < HORIZONTAL_PIXEL_WIDTH; i++){
            outs >> pix;
            outs_soft >> fpix;

            relu_out[j*HORIZONTAL_PIXEL_WIDTH+i][0] = pix.data.data0;
            relu_out[j*HORIZONTAL_PIXEL_WIDTH+i][1] = pix.data.data1;
            relu_fout[j*HORIZONTAL_PIXEL_WIDTH+i][0] = fpix.data.data0;
            relu_fout[j*HORIZONTAL_PIXEL_WIDTH+i][1] = fpix.data.data1;
            if ((double)pow((double)pix.data.data0-(double)fpix.data.data0,(double)2) > 4 ||
                    (double)pow((double)pix.data.data1-(double)fpix.data.data1,(double)2) > 4){ // 2乗誤差が4よりも大きい
                printf("ERROR HW and SW results mismatch i = %ld, j = %ld, HW = %f, %f, SW = %f, %f\n", i, j, (float)pix.data.data0, (float)pix.data.data1, fpix.data.data0, fpix.data.data1);
                return(1);
            }
        }
    }
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    // ReLU の結果をヘッダファイルに出力
    ofstream OH("relu_output.h");
    OH << "// relu_output.h" << endl;
    time_t now = time(0);
    struct tm* localNow = localtime(&now);
    OH << "// " << localNow->tm_year+1900 << "/" << localNow->tm_mon+1 << "/" << localNow->tm_mday;
    OH << " " << setw(2) << setfill('0') << localNow->tm_hour << ":" << localNow->tm_min << ":" << localNow->tm_sec << " by marsee" << endl;
    OH << "//" << endl;
    OH << endl;
    OH << "#ifndef __RELU_OUTPUT_H__" << endl;
    OH << "#define __RELU_OUTPUT_H__" << endl;
    OH << endl;
    OH << "const float relu_fout[" << VERTICAL_PIXEL_WIDTH*HORIZONTAL_PIXEL_WIDTH  << "][" << NUMBER_OF_KERNEL << "] = {" << endl;
    for (int y=0; y<VERTICAL_PIXEL_WIDTH ; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH ; x++){
            OH << "    {" << fixed << setprecision(12) << relu_fout[VERTICAL_PIXEL_WIDTH*y+x][0] << ", "
                    << relu_fout[VERTICAL_PIXEL_WIDTH*y+x][1] << "}";
            if (y==VERTICAL_PIXEL_WIDTH-1 && x==HORIZONTAL_PIXEL_WIDTH-1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;

    OH << "const ap_fixed<16, 6, AP_TRN, AP_WRAP> relu_out[" << VERTICAL_PIXEL_WIDTH*HORIZONTAL_PIXEL_WIDTH  << "][" << NUMBER_OF_KERNEL << "] = {" << endl;
    for (int y=0; y<VERTICAL_PIXEL_WIDTH ; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH ; x++){
            OH << "    {" << fixed << setprecision(12) << (float)relu_out[VERTICAL_PIXEL_WIDTH*y+x][0] << ", "
                    <<  (float)relu_out[VERTICAL_PIXEL_WIDTH*y+x][1] << "}";
            if (y==VERTICAL_PIXEL_WIDTH -1 && x==HORIZONTAL_PIXEL_WIDTH -1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;
    OH << "#endif" << endl;

    return(0);
}    


int relu_soft(hls::stream<float2_axis<1,1,1> >& ins,
        hls::stream<float2_axis<1,1,1> >& outs){

    float2_axis<1,1,1> fpix;

    do {
    // user が 1になった時にフレームがスタートする
        ins >> fpix;
    } while(fpix.user == 0);

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

            if (fpix.data.data0 < 0.0// データが 0 以下だったら 0 にする
                fpix.data.data0 = 0.0;

            if (fpix.data.data1 < 0.0// データが 0 以下だったら 0 にする
                fpix.data.data1 = 0.0;

            outs << fpix;
        }
    }

    return(0);
}

  1. 2018年02月21日 04:48 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースの畳み込み層5(出力値のヘッダファイルを出力)

AXI4-Stream インターフェースの畳み込み層4(C/RTL 協調シミュレーションとExport RTL)”の続き。

前回は、C/RTL 協調シミュレーションとExport RTL を行った。今回は、次の層のデータとして、畳み込み層の出力値のC のヘッダファイルを作成するようにテストベンチを変更した。

畳み込み層の出力値のC のヘッダファイルを作成するように修正を行った Vivado HLS 2017.4 の conv_layer プロジェクトの conv_layer_tb.cpp のヘッダファイル出力部分の一部を示す。
conv_layer_19_180219.png

これで C シミュレーションを行うと、conv_layer_output.h が出力された。
conv_layer_20_180219.png

conv_layer_output.h を見ると、const float conv_layer_fout[312][2] があるのが見える。
配列の 1 次元目は畳み込みフィルタをかけた出力のサイズで縦 6 ピクセル X 横 52 ピクセル = 312 となっている。2 次元目の 2 はピクセルごとに 2 つの畳み込みフィルタの出力値を表している。
また、conv_layer_fout は float 型で float による畳み込みフィルタの演算結果が収納されている。なお、小数点以下の桁数は 12 桁にしてある。こうすると、float 型と任意精度固定小数点データ型の値の差が良く分かるだろう。任意精度固定小数点データ型は小数部の11、12桁目は 0 になっているのが分かると思う。
conv_layer_21_180219.png

conv_layer_output.h を見ると、次に、const ap_fixed<16, 6, AP_TRN, AP_WRAP> conv_layer_out[312][2] がある。
これは、量子化し、ハードウェア化する任意精度固定小数点データ型での畳み込みフィルタの演算結果が収納されている。次元の意味については conv_layer_fout と同様だ。
conv_layer_22_180219.png 

これで、次のReLU 層を作っても入力データの心配をする必要が無くなった。

conv_layer_tb.c を貼っておく。

// conv_layer_tb.cpp
// 2018/02/13 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <math.h>
#include <ap_axi_sdata.h>
#include <hls_video.h>

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

int conv_layer(hls::stream<ap_axiu<32,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs);
int conv_layer_soft(hls::stream<ap_axiu<32,1,1,1> >& ins,
    hls::stream<float2_axis<1,1,1> >& outs);

#define BMP_FILE_NAME   "straight_RED_rect0_00_rgb.bmp"

int main(){
    using namespace std;

    hls::stream<ap_axiu<32,1,1,1> > ins;
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    hls::stream<ap_fixed2_axis<16,6,1,1,1> > outs;
    hls::stream<float2_axis<1,1,1> > outs_soft;
    ap_axiu<32,1,1,1> pix;
    ap_fixed2_axis<16,6,1,1,1> vals;
    float2_axis<1,1,1> vals_soft;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw, *fbmpwf;
    int *rd_bmp, *hw_conv, *sw_conv;
    float *hw_convf;
    float *sw_convf;
    int blue, green, red;
    ap_uint<2> r_l;
    char fhname[100];
    char fsname[100];

    if ((fbmpr = fopen(BMP_FILE_NAME, "rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open straight_RED_rect0_00.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_conv =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight * NUMBER_OF_KERNEL))) == NULL){
        fprintf(stderr, "Can't allocate hw_conv0 memory\n");
        exit(1);
    }
    if ((sw_conv =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight * NUMBER_OF_KERNEL))) == NULL){
        fprintf(stderr, "Can't allocate sw_conv0 memory\n");
        exit(1);
    }

    if ((hw_convf =(float *)malloc(sizeof(float) * (bmpihr.biWidth * bmpihr.biHeight * NUMBER_OF_KERNEL))) == NULL){
        fprintf(stderr, "Can't allocate hw_conv0 memory\n");
        exit(1);
    }
    if ((sw_convf =(float *)malloc(sizeof(float) * (bmpihr.biWidth * bmpihr.biHeight * NUMBER_OF_KERNEL))) == NULL){
        fprintf(stderr, "Can't allocate sw_conv0 memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    // ins に入力データを用意する
    for(int i=0; i<5; i++){    // dummy data
        pix.user = 0;
        pix.data = i;
        ins << pix;
    }

    // 1 画面分のデータを ins、ins_soft に入力する
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            pix.data = (ap_uint<32>)rd_bmp[(j*bmpihr.biWidth)+i];

            if (j==0 && i==0)    // 最初のデータの時に TUSER を 1 にする
                pix.user = 1;
            else
                pix.user = 0;

            if (i == bmpihr.biWidth-1// 行の最後でTLASTをアサートする
                pix.last = 1;
            else
                pix.last = 0;

            ins << pix;
            ins_soft << pix;
        }
    }

    conv_layer(ins, outs);
    conv_layer_soft(ins_soft, outs_soft);

    // 画像サイズの縮小(畳み込みをすると行、列共に -4
    bmpfhr.bfSize = (HORIZONTAL_PIXEL_WIDTH-4) * (VERTICAL_PIXEL_WIDTH-4) * 3 + 54;
    bmpihr.biHeight = VERTICAL_PIXEL_WIDTH - 4;
    bmpihr.biWidth = HORIZONTAL_PIXEL_WIDTH - 4;

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    cout << endl;
    cout << "outs" << endl;
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            outs_soft >> vals_soft;
            out_type val0 = vals.data.data0;
            out_type val1 = vals.data.data1;
            float val_soft0 = vals_soft.data.data0;
            float val_soft1 = vals_soft.data.data1;

            hw_conv[(j*bmpihr.biWidth)+i] = ((int)val0+32)*4// 32を足して負の符号を排除し、整数部6ビットなので、2ビット分補正する
            hw_conv[(bmpihr.biWidth * bmpihr.biHeight)+(j*bmpihr.biWidth)+i] = ((int)val1+32)*4;
            sw_conv[(j*bmpihr.biWidth)+i] = ((int)val_soft0+32)*4;
            sw_conv[(bmpihr.biWidth * bmpihr.biHeight)+(j*bmpihr.biWidth)+i] = ((int)val_soft1+32)*4;

            hw_convf[(j*bmpihr.biWidth)+i] = (float)val0; // 32を足して負の符号を排除し、整数部6ビットなので、2ビット分補正する
            hw_convf[(bmpihr.biWidth * bmpihr.biHeight)+(j*bmpihr.biWidth)+i] = (float)val1;
            sw_convf[(j*bmpihr.biWidth)+i] = val_soft0;
            sw_convf[(bmpihr.biWidth * bmpihr.biHeight)+(j*bmpihr.biWidth)+i] = val_soft1;


            if ((double)pow((double)val0-(double)val_soft0,(double)2) > 4 || (double)pow((double)val1-(double)val_soft1,(double)2) > 4){ // 2乗誤差が4よりも大きい
                printf("ERROR HW and SW results mismatch i = %ld, j = %ld, HW = %f, %f, SW = %f, %f\n", i, j, (float)val0, (float)val1, val_soft0, val_soft1);
                //return(1);
            }
            printf("HW and SW results i = %ld, j = %ld, HW = %f, %f, SW = %f, %f\n", i, j, (float)val0, (float)val1, val_soft0, val_soft1);
            //if (vals.last)
                //cout << "AXI-Stream is end" << endl;
        }
    }
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    // ハードウェアの畳み込み演算の結果を temp_conv0.bmp, temp_conv1.bmp に出力する
    for (int k=0; k<2; k++){
        if (k==0){
            if ((fbmpw=fopen("temp_conv0.bmp""wb")) == NULL){
                fprintf(stderr, "Can't open temp_conv0.bmp by binary write mode\n");
                exit(1);
            }
        } else {
            if ((fbmpw=fopen("temp_conv1.bmp""wb")) == NULL){
                fprintf(stderr, "Can't open temp_conv1.bmp by binary write mode\n");
                exit(1);
            }
        }

        // BMPファイルヘッダの書き込み
        fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
        fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
        fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
        fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);
        // RGB データの書き込み、逆順にする
        for (int y=0; y<bmpihr.biHeight; y++){
            for (int x=0; x<bmpihr.biWidth; x++){
                if (k == 0){
                    blue = hw_conv[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                    green = blue;
                    red = blue;
                } else {
                    blue = hw_conv[(bmpihr.biWidth * bmpihr.biHeight)+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                    green = blue;
                    red = blue;
                }

                fputc(blue, fbmpw);
                fputc(green, fbmpw);
                fputc(red, fbmpw);
            }
        }
        fclose(fbmpw);
    }

    // ソフトウェアの畳み込み演算の結果を temp_conv_float0.bmp, temp_conv_float1.bmp に出力する
    for(int k=0; k<2; k++){
        if (k == 0){
            if ((fbmpwf=fopen("temp_conv_float0.bmp""wb")) == NULL){
                fprintf(stderr, "Can't open temp_conv_float0.bmp by binary write mode\n");
                exit(1);
            }
        } else {
            if ((fbmpwf=fopen("temp_conv_float1.bmp""wb")) == NULL){
                fprintf(stderr, "Can't open temp_conv_float1.bmp by binary write mode\n");
                exit(1);
            }
        }

        // BMPファイルヘッダの書き込み
        fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpwf);
        fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpwf);
        fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpwf);
        fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpwf);
        fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpwf);
        fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpwf);
        // RGB データの書き込み、逆順にする
        for (int y=0; y<bmpihr.biHeight; y++){
            for (int x=0; x<bmpihr.biWidth; x++){
                if (k == 0){
                    blue = sw_conv[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                    green = blue;
                    red = blue;
                } else {
                    blue = sw_conv[(bmpihr.biWidth * bmpihr.biHeight)+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                    green = blue;
                    red = blue;
                }

                fputc(blue, fbmpwf);
                fputc(green, fbmpwf);
                fputc(red, fbmpwf);
            }
        }
        fclose(fbmpwf);
    }

    // ヘッダ出力
    ofstream OH("conv_layer_output.h");
    OH << "// conv_layer_output.h" << endl;
    time_t now = time(0);
    struct tm* localNow = localtime(&now);
    OH << "// " << localNow->tm_year+1900 << "/" << localNow->tm_mon+1 << "/" << localNow->tm_mday;
    OH << " " << setw(2) << setfill('0') << localNow->tm_hour << ":" << localNow->tm_min << ":" << localNow->tm_sec << " by marsee" << endl;
    OH << "//" << endl;
    OH << endl;
    OH << "#ifndef __CONV_LAYER_OUTPUT_H__" << endl;
    OH << "#define __CONV_LAYER_OUTPUT_H__" << endl;
    OH << endl;
    OH << "const float conv_layer_fout[" << bmpihr.biHeight*bmpihr.biWidth << "][" << NUMBER_OF_KERNEL << "] = {" << endl;
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            OH << "    {" << fixed << setprecision(12) << sw_convf[bmpihr.biHeight*y+x] << ", "
                    << sw_convf[bmpihr.biHeight*bmpihr.biWidth+bmpihr.biHeight*y+x] << "}";
            if (y==bmpihr.biHeight-1 && x==bmpihr.biWidth-1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;

    OH << "const ap_fixed<16, 6, AP_TRN, AP_WRAP> conv_layer_out[" << bmpihr.biHeight*bmpihr.biWidth << "][" << NUMBER_OF_KERNEL << "] = {" << endl;
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            OH << "    {" << hw_convf[bmpihr.biHeight*y+x] << ", "
                    <<  hw_convf[bmpihr.biHeight*bmpihr.biWidth+bmpihr.biHeight*y+x] << "}";
            if (y==bmpihr.biHeight-1 && x==bmpihr.biWidth-1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;
    OH << "#endif" << endl;

    free(rd_bmp);
    free(hw_conv);
    free(sw_conv);
    free(hw_convf);
    free(sw_convf);

    return(0);
}

int conv_layer_soft(hls::stream<ap_axiu<32,1,1,1> >& ins,
    hls::stream<float2_axis<1,1,1> >& outs){
    ap_axiu<32,1,1,1> pix;
    float2_axis<1,1,1> conv_out;

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

    float ap_uf_pix;
    float val;

    do {
    // 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++){
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            ap_uf_pix = (float)(pix.data & 0xff) / 256.0;
            //printf("ap_uf_pix_soft = %f\n", ap_uf_pix);

            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 += mbuf.getval(j,i) * conv1_fweight[k][0][j][i];
                    }
                }
                val += conv1_fbias[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);
}

  1. 2018年02月19日 05:06 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

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
»