FC2カウンター FPGAの部屋 2018年02月21日
FC2ブログ

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

FPGAの部屋

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

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[HORIZONTAL_PIXEL_WIDTH*y+x][0] << ", "
                    << relu_fout[HORIZONTAL_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[HORIZONTAL_PIXEL_WIDTH*y+x][0] << ", "
                    <<  (float)relu_out[HORIZONTAL_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