FC2カウンター FPGAの部屋

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

FPGAの部屋

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

HLSストリームのマックス・プーリング層(C ソースコード)

HLSストリームのマックス・プーリング層をやっていこう。今回は、C ソースコードを貼っておく。

検証を入れたことにより、max_pooling.cpp のバグが分かったので、フィックスした。検証した甲斐があったが、前のブログを修正する必要がある。。。orz

最初に、max_pooling.h を貼っておく。

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

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

static const size_t H_PIXEL_WIDTH_IN = 52;
static const size_t V_PIXEL_WIDTH_IN = 6;
static const size_t H_PIXEL_WIDTH_OUT = 26;
static const size_t V_PIXEL_WIDTH_OUT = 3;

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

static const size_t X_STRIDE = 2;
static const size_t Y_STRIDE = 2;

typedef ap_fixed<166, AP_TRN, AP_WRAP> conv_type;

#endif


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

// max_pooling.cpp
// 2018/04/19 by marsee
// 2018/04/20 : bug fix
// 2018/04/25 : Loop10 bug fix
//

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

#include "layer_general.h"
#include "max_pooling.h"

int max_pooling(hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs){
#pragma HLS DATA_PACK variable=outs
#pragma HLS DATA_PACK variable=ins

    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> pix;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,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 PIPELINE II=1
#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
                conv_data = pix.data[n];

                // 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[n][ARRAY_SIZE-1][ARRAY_SIZE-1] = 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; 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];
                        }
                    }
                }
                mp_out.data[n] = val[n];

                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/04/19 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 "layer_general.h"
#include "max_pooling.h"
#include "relu_output.h"

int max_pooling(hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs);

int max_pooling2(hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs);

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

int main(){
    using namespace std;

    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > ins;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > ins2;
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> > ins_soft;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > outs;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > outs2;
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> > outs_soft;

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

    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> pix;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> pix2;
    float_axis<NUMBER_OF_KERNEL,1> fpix;

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

        fpix.user = 0;
        for(int k=0; k<NUMBER_OF_KERNEL; k++){
            fpix.data[k] = (float)i;
        }
        ins_soft << fpix;
    }

    // 1 画面分のデータを ins、ins_soft に入力する
    ofstream OHX("relu_output_X0.csv");
    ofstream OHF("relu_output_F0.csv");
    for(int j=0; j < V_PIXEL_WIDTH_IN; j++){
        for(int i=0; i < H_PIXEL_WIDTH_IN; i++){
            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                pix.data[k] = relu_out[j*H_PIXEL_WIDTH_IN+i][k];
                fpix.data[k] = relu_fout[j*H_PIXEL_WIDTH_IN+i][k];
            }
            OHX << pix.data[0];
            if(i != H_PIXEL_WIDTH_IN-1)
                OHX << ",";
            else
                OHX << endl;
            OHF << fpix.data[0];
            if(i != H_PIXEL_WIDTH_IN-1)
                OHF << ",";
            else
                OHF << endl;

            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;
            ins2 << pix;
            ins_soft << fpix;
        }
    }

    max_pooling(ins, outs);
    max_pooling_soft(ins_soft, outs_soft);
    max_pooling2(ins2, outs2);

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

            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                mp_out[j*H_PIXEL_WIDTH_OUT+i][k] = pix.data[k];
                mp_fout[j*H_PIXEL_WIDTH_OUT+i][k] = fpix.data[k];

                printf("%d, %d, data[%d] = %f, fdata[%d] = %f\n", j, i, k, (float)pix.data[k], k, fpix.data[k]);
                if (pix.data[k] != pix2.data[k]){
                    printf("ERROR HW and SW results mismatch i = %ld, j = %ld, HW[%d] = %f, HW2[%d] = %f, SW[%d] = %f\n", i, j, k, (float)pix.data[k], k, (float)pix2.data[k], k,fpix.data[k]);
                    errcnt++;
                    //return(1);
                }
            }
        }
    }
    cout << "Error Count = " << errcnt << endl;
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    // max_pooling の結果をヘッダファイルに出力
    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[H_PIXEL_WIDTH_OUT*y+x][0];
            for (int i=1; i<NUMBER_OF_KERNEL; ++i)
            {
                OH << ", " << mp_fout[H_PIXEL_WIDTH_OUT*y+x][i];
            }
            OH << "}";
            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[H_PIXEL_WIDTH_OUT*y+x][0];
            for(int i=1; i<NUMBER_OF_KERNEL; i++){
                OH << ", " << (float)mp_out[H_PIXEL_WIDTH_OUT*y+x][i];
            }    
            OH << "}";
            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<float_axis<NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<float_axis<NUMBER_OF_KERNEL,1> >& outs){

    float_axis<2,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;

            for(int i=0; i<NUMBER_OF_KERNEL; i++){
                fpixd_ary[i][y][x] = fpix.data[i];
            }
        }
    }

    for (int y=0; y<V_PIXEL_WIDTH_IN-1; y+=Y_STRIDE){
        for (int x=0; x<H_PIXEL_WIDTH_IN-1; x+=X_STRIDE){
            for(int p=0; p<NUMBER_OF_KERNEL; p++){
                for(int m=0; m<Y_STRIDE; m++){
                    for(int n=0; n<X_STRIDE; 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];
                        }
                    }
                }
            }
            for(int i=0; i<NUMBER_OF_KERNEL; i++){
                fpix.data[i] = fval[i];
            }

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

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

            outs << fpix;
        }
    }

    return(0);
}

int max_pooling2(hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs){
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> pix;
    conv_type maxp_val[NUMBER_OF_KERNEL][V_PIXEL_WIDTH_IN][H_PIXEL_WIDTH_IN];
    conv_type pool_out[NUMBER_OF_KERNEL][V_PIXEL_WIDTH_OUT][H_PIXEL_WIDTH_OUT];
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> maxp_out;

    Loop1: do {
    // user が 1になった時にフレームがスタートする
        ins >> pix;
    } while(pix.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 >> pix;    // AXI4-Stream からの入力

            for (int i=0; i<NUMBER_OF_KERNEL; i++){
                maxp_val[i][y][x] = pix.data[i];
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<NUMBER_OF_KERNEL; i++){
        POOL2: for(int j=0; j<V_PIXEL_WIDTH_IN; j += Y_STRIDE){
            POOL3: for(int k=0; k<H_PIXEL_WIDTH_IN; k += X_STRIDE){
                POOL4: for(int m=0; m<Y_STRIDE; m++){
                    POOL5: for(int n=0; n<X_STRIDE; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/Y_STRIDE][k/X_STRIDE] = maxp_val[i][j][k];
                        } else if(pool_out[i][j/Y_STRIDE][k/X_STRIDE] < maxp_val[i][j+m][k+n]){
                            pool_out[i][j/Y_STRIDE][k/X_STRIDE] = maxp_val[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    for(int y=0; y<V_PIXEL_WIDTH_OUT; y++){
        for(int x=0; x<H_PIXEL_WIDTH_OUT; x++){
            for(int i=0; i<NUMBER_OF_KERNEL; i++){
                maxp_out.data[i] = pool_out[i][y][x];
            }

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

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

            outs << maxp_out;
        }
    }

    return(0);
}


なお、layer_general.h はここにある
  1. 2018年04月25日 05:39 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

HLSストレーム・インターフェースの畳み込み層4(検証用関数conv_layer2()を追加)

畳み込みニューラルネットワークのコードを書く時の検証方法”で、異なる実装の検証用の関数を用意することにしたので、早速、畳み込み層で検証用関数 conv_layer2() をテストベンチに実装してみた。

テストベンチの conv_layer_tb.cpp を示す。

// conv_layer_tb.cpp
// 2018/02/13 by marsee
// 2018/04/14 : HLS ストリーム対応
// 2018/04/24 : 検証用に異なる実装のconv_layer2()と比較
//

#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 "layer_general.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_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs);
int conv_layer_soft(hls::stream<ap_axiu<32,1,1,1> >& ins,
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> >& outs);
int conv_layer2(hls::stream<ap_axiu<32,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,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> > ins2;
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > outs;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > outs2;
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> > outs_soft;
    ap_axiu<32,1,1,1> pix;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> vals;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> vals2;
    float_axis<NUMBER_OF_KERNEL,1> vals_soft;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw, *fbmpwf;
    int *rd_bmp;
    int *hw_conv[NUMBER_OF_KERNEL];
    int *sw_conv[NUMBER_OF_KERNEL];
    float *hw_convf[NUMBER_OF_KERNEL];
    float *sw_convf[NUMBER_OF_KERNEL];
    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(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 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);
    }
    for(int i=0; i<NUMBER_OF_KERNEL; i++){
        if ((hw_conv[i] =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate hw_conv[%d] memory\n", i);
            exit(1);
        }
        if ((sw_conv[i] =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate sw_conv[%d] memory\n", i);
            exit(1);
        }
    }

    for(int i=0; i<NUMBER_OF_KERNEL; i++){
        if ((hw_convf[i] =(float *)malloc(sizeof(float) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate hw_convf[%d] memory\n", i);
            exit(1);
        }
        if ((sw_convf[i] =(float *)malloc(sizeof(float) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate sw_convf[%d] memory\n", i);
            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;
        ins2 << pix;
        ins_soft << 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;
            ins2 << pix;
            ins_soft << pix;
        }
    }

    // 畳み込み演算
    conv_layer(ins, outs);
    conv_layer2(ins2, outs2);
    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;

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    out_type val[NUMBER_OF_KERNEL];
    out_type val2[NUMBER_OF_KERNEL];
    float val_soft[NUMBER_OF_KERNEL];

    cout << endl;
    cout << "outs" << endl;
    int errcnt=0;
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            outs2 >> vals2;
            outs_soft >> vals_soft;

            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                val[k] = vals.data[k];
                val2[k] = vals2.data[k];
                val_soft[k] = vals_soft.data[k];

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

                float *hw_convfp = hw_convf[k];
                float *sw_convfp = sw_convf[k];
                hw_convfp[(j*bmpihr.biWidth)+i] = (float)val[k];
                sw_convfp[(j*bmpihr.biWidth)+i] = val_soft[k];
                if (val[k] != val2[k]){
                    printf("ERROR val and val2 results mismatch i = %d, j = %d, val[%d] = %f, val2[%d] = %f\n", i, j, k, (float)val[k], k, (float)val2[k]);
                    errcnt++;
                    //return(1);
                }
                printf("HW and SW results i = %d, j = %d, HW[%d] = %f, HW2[%d] = %f, SW[%d] = %f\n", i, j, k, (float)val[k], k, (float)val2[k], k, val_soft[k]);
            }
        }
    }
    cout << "Error Count = " << errcnt << endl;
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    // ハードウェアの畳み込み演算の結果を temp_conv0.bmp, temp_conv1.bmp に出力する
    for (int k=0; k<NUMBER_OF_KERNEL; 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(uint16_t), 1, fbmpw);
        fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
        fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 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++){
                int *hw_convp = hw_conv[k];
                blue = hw_convp[((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(uint16_t), 1, fbmpwf);
        fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpwf);
        fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpwf);
        fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpwf);
        fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 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++){
                int *sw_convp = sw_conv[k];
                blue = sw_convp[((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[0][bmpihr.biWidth*y+x];
            for(int i=1; i<NUMBER_OF_KERNEL; i++){
                OH << ", " << sw_convf[i][bmpihr.biWidth*y+x];
            }
            OH << "}";
            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[0][bmpihr.biWidth*y+x];
            for(int i=1; i<NUMBER_OF_KERNEL; i++){
                OH << ", " <<  hw_convf[i][bmpihr.biWidth*y+x];
            }
            OH << "}";
            if (y==bmpihr.biHeight-1 && x==bmpihr.biWidth-1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;
    OH << "#endif" << endl;

    free(rd_bmp);
    for(int k=0; k<NUMBER_OF_KERNEL; k++){
        free(hw_conv[k]);
        free(sw_conv[k]);
        free(hw_convf[k]);
        free(sw_convf[k]);
    }

    return(0);
}

int conv_layer_soft(hls::stream<ap_axiu<32,1,1,1> >& ins,
        hls::stream<float_axis<NUMBER_OF_KERNEL,1> >& outs){
    ap_axiu<32,1,1,1> pix;
    float_axis<NUMBER_OF_KERNEL,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];
                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);
}

// 検証用 conv_layer2()
// 検証用に conv_layer() とは異なる実装でコーディング
int conv_layer2(hls::stream<ap_axiu<32,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >&outs){

    ap_axiu<32,1,1,1> pix;
    val_type conv_val[NUMBER_OF_KERNEL][VERTICAL_PIXEL_WIDTH][HORIZONTAL_PIXEL_WIDTH];
    in_type ap_uf_pix[VERTICAL_PIXEL_WIDTH][HORIZONTAL_PIXEL_WIDTH];
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> conv_out;

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

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

   for(int i=0; i<NUMBER_OF_KERNEL; i++){    // カーネルの個数
        for(int j=0; j<VERTICAL_PIXEL_WIDTH-(ARRAY_SIZE-1); j++){
            for(int k=0; k<HORIZONTAL_PIXEL_WIDTH-(ARRAY_SIZE-1); k++){
                conv_val[i][j][k] = 0;
                for(int m=0; m<ARRAY_SIZE; m++){
                    for(int n=0; n<ARRAY_SIZE; n++){
                        conv_val[i][j][k] += (val_type)ap_uf_pix[j+m][k+n] * (val_type)conv1_weight[i][0][m][n];
                    }
                }
                conv_val[i][j][k] += (val_type)conv1_bias[i];
            }
        }
    }

    for(int y=0; y<VERTICAL_PIXEL_WIDTH-(ARRAY_SIZE-1); y++){
        for(int x=0; x<HORIZONTAL_PIXEL_WIDTH-(ARRAY_SIZE-1); x++){
            for(int i=0; i<NUMBER_OF_KERNEL; i++){
                conv_out.data[i] = conv_val[i][y][x];
            }

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

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

            outs << conv_out;
        }
    }

    return(0);
}


これのテストベンチを使用して、C シミュレーションを行った。
hlsw_conv_layer_6_180424.png

エラーは無かった。ハードウェアにする covn_layer() と検証用関数の cnov_layer2() は一致した。

C/RTL 協調シミュレーションを行った。
hlsw_conv_layer_7_180424.png

C/RTL 協調シミュレーションでも、ハードウェアにする covn_layer() と検証用関数の cnov_layer2() は一致した。
  1. 2018年04月24日 04:43 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

畳み込みニューラルネットワークのコードを書く時の検証方法

白線間走行用の畳み込みニューラルネットワーク(CNN)をVivado HLS で高位合成するために最適と思われるC ソースコードを書いたのだが、今回 Max Pooling のところでソースコードにバグがあった。
max_pooling.cpp が間違っていて、2 x 2 のウインドウでの最大値を取るはずが左上の値を取るコードになっていた。(”AXI4-Stream インターフェースのMax Pooling 1(ソースコード)”参照)
問題はどうやってソースコードが間違っているのか?を検証することだと思う。これほど間違っていても、最終的な出力では、最大 1.7 % 程度の精度の差があるだけである。(”AXI4-Stream インターフェースの畳み込みニューラルネットワーク5(モードの変更)”参照)

CNN をハードウェア化していなければ、TensorFlow やChairer などのフレームワークを使用していて、自分でプログラミングすることは無いと思われるので、コードを検証することは無いのだろうが、ハードウェアにする場合は、自分の書いたコードが正しいかどうかを常に確認する必要がある。特に、Vivado HLS に特化したソースコードを書くために複数の異なるコードを試すことはよくあるのだ。
TensorFlow やChairer などのフレームワークに重みとバイアスを変換して渡して推論させても、量子化出来なければ、値の比較はできない。
下の図で mp_fout[78][2] が量子化していない浮動小数点数の値で、mp_out[78][2] が量子化してある任意精度固定小数点データ型の値だ。2つの数には、1 以上の開きがある。これは、最初に畳み込み層の重みとバイアスを -1 ~ +1 までに量子化したためだ。
Max_Pooling_9_180225.pngMax_Pooling_10_180225.png

と言う訳で、任意精度で量子化できないと比較できない。(任意精度で量子化できるようだったら教えてください。特にTensorFlow はできるのかな?)

そこで、現在のソースコードはHLSストリームに最適化されているのだが、CNN を分割せずにソースコードを見たときに分かりやすいコードで書いたプログラムがあるので、それをHLSストリーム対応に変更し、各層に分割して比較してみようと思っている。(”カーブ、直線用白線間走行用畳み込みニューラルネットワーク13(AXI4 Stream版CNN IP 1”参照)

これで検証の方針は決まったのだが、CNN は多少コードを間違っていても、大した影響はない。ということは、ポイントを外さなければハードウェアに都合の良いように途中のアルゴリズムを変更しても、それほどの問題はないということかもしれない?それをおいおい確かめてみようと思う。

ちなみになぜ面倒に各層をHLSストリームでつないでいるのか?というと、自由に各層をつなげて、いろいろな層構成を書くことが簡単に行えるようにするためである。つまり、各層を部品として使えるようにするためだ。
  1. 2018年04月23日 05:06 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「レディ・プレーヤー1」(映画)を見てきました

今日は午後から奥さんと「レディ・プレーヤー1」(映画)を見てきました。
金田のバイクやデロリアン、メカゴジラ、ガンダムとかいろいろと出ていきました。見ていて楽しかったです。
  1. 2018年04月22日 21:01 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

「第45回 コンピュータビジョン勉強会@関東」で発表してきました

昨日、「第45回 コンピュータビジョン勉強会@関東」で「FPGAに実装したCNNを使用して白線間を走行するミニ・ロボットカーの製作」という題で発表してきました。

他の発表を聞いていたのですが、いろいろと面白いのがありました。ここにまとめサイトがあります。

2番目のお話は、ホットドッグ検出器でした。ホットドッグがあると白黒画面がホットドッグのところだけ色が付きます。資料にはないですが、会場で TensorFlow.js を使ってデモしていました。

Androidだけでもアリシアちゃんになれちゃうアプリを作った話@第45回 コンピュータビジョン勉強会」はモバイル端末上で動く画像のポーズを3Dキャラクタに追従させるアプリでした。資料 17 ページを見ると分かりますが、Chairer から ONNX を通して、TensorFlow にモデルを送っています。これは良いですね。。。

後、「SSDで道路の傷検出」とかいろいろとためになるお話が聞けました。楽しかったです。
  1. 2018年04月22日 05:07 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

HLSストレーム・インターフェースのReLU2(C ソースコード)

HLSストレーム・インターフェースのReLU1”の続き。

前回は、HLSストリーム・インターフェースでReLU を作成した。今回はその C ソースコードを貼っておく。

その前に、C コードの合成で生成された relu.vhd の entity 部分を貼っておく。

entity relu is
port (
    ap_clk : IN STD_LOGIC;
    ap_rst : IN STD_LOGIC;
    ap_start : IN STD_LOGIC;
    ap_done : OUT STD_LOGIC;
    ap_idle : OUT STD_LOGIC;
    ap_ready : OUT STD_LOGIC;
    ins_V_dout : IN STD_LOGIC_VECTOR (33 downto 0);
    ins_V_empty_n : IN STD_LOGIC;
    ins_V_read : OUT STD_LOGIC;
    outs_V_din : OUT STD_LOGIC_VECTOR (33 downto 0);
    outs_V_full_n : IN STD_LOGIC;
    outs_V_write : OUT STD_LOGIC;
    ap_return : OUT STD_LOGIC_VECTOR (31 downto 0) );
end;


すべての層で使う予定のHLS ストリームのテンプレートを書いた layer_general.h は、”HLSストレーム・インターフェースの畳み込み層1(Cソースコード、C コードの合成、Export RTL)”を参照のこと。

relu.h を示す。

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

#ifndef __RELU_H__
#define __RELU_H__

static const size_t HORIZONTAL_PIXEL_WIDTH = 52;
static const size_t VERTICAL_PIXEL_WIDTH = 6;
static const size_t ALL_PIXELS = HORIZONTAL_PIXEL_WIDTH * VERTICAL_PIXEL_WIDTH;

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

typedef ap_fixed<166, AP_TRN, AP_WRAP> conv_type;

#endif


relu.cpp を示す。

// relu.cpp
// 2018/04/15 by marsee
//

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

#include "layer_general.h"
#include "relu.h"

int relu(hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs){
#pragma HLS DATA_PACK variable=outs
#pragma HLS DATA_PACK variable=ins

    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> pix;

    do {
#pragma HLS PIPELINE II=1
#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 からの入力

            for(int i=0; i<NUMBER_OF_KERNEL; i++){                
                if (pix.data[i] < conv_type(0.0)) // データが 0 以下だったら 0 にする
                    pix.data[i] = 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 "layer_general.h"
#include "relu.h"
#include "conv_layer_output.h"

int relu(hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs);

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

int main(){
    using namespace std;

    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > ins;
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> > ins_soft;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > outs;
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> > outs_soft;

    float relu_fout[ALL_PIXELS][NUMBER_OF_KERNEL];
    conv_type relu_out[ALL_PIXELS][NUMBER_OF_KERNEL];

    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> pix;
    float_axis<NUMBER_OF_KERNEL,1> fpix;

    // ins に入力データを用意する
    for(int i=0; i<5; i++){    // dummy data
        pix.user = 0;
        for(int j=0; j<NUMBER_OF_KERNEL; j++){
            pix.data[j] = (conv_type)i;
        }
        ins << pix;
        fpix.user = 0;
        for(int j=0; j<NUMBER_OF_KERNEL; j++){
            fpix.data[j] = (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++){
            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                pix.data[k] = conv_layer_out[j*HORIZONTAL_PIXEL_WIDTH+i][k];
                fpix.data[k] = conv_layer_fout[j*HORIZONTAL_PIXEL_WIDTH+i][k];
            }

            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[][] に出力する
    int errcnt=0;
    for(int j=0; j < VERTICAL_PIXEL_WIDTH; j++){
        for(int i=0; i < HORIZONTAL_PIXEL_WIDTH; i++){
            outs >> pix;
            outs_soft >> fpix;

            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                relu_out[j*HORIZONTAL_PIXEL_WIDTH+i][k] = pix.data[k];
                relu_fout[j*HORIZONTAL_PIXEL_WIDTH+i][k] = fpix.data[k];
                if ((double)pow((double)pix.data[k]-(double)fpix.data[k],(double)2) > 4){ // 2乗誤差が4よりも大きい
                    printf("ERROR HW and SW results mismatch i = %d, j = %d, HW[%d] = %f, SW[%d] = %f\n", i, j, k, (float)pix.data[k], k, fpix.data[k]);
                    errcnt++;
                    return(1);
                }
                printf("HW and SW results i = %d, j = %d, HW[%d] = %f, SW[%d] = %f\n", i, j, k, (float)pix.data[k], k, fpix.data[k]);
            }
        }
    }
    cout << "Error Count = " << errcnt << endl;
    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];
            for(int i=1; i<NUMBER_OF_KERNEL; i++){
                OH << ", " << relu_fout[HORIZONTAL_PIXEL_WIDTH*y+x][i];
            }
            OH << "}";
            
            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 << "    {" << (float)relu_out[HORIZONTAL_PIXEL_WIDTH*y+x][0];
            for(int i=1; i<NUMBER_OF_KERNEL; i++){
                OH << ", " <<  (float)relu_out[HORIZONTAL_PIXEL_WIDTH*y+x][1];
            }
            OH << "}";

            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<float_axis<2,1> >& ins,
        hls::stream<float_axis<2,1> >& outs){

    float_axis<2,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 からの入力
            
            for(int i=0; i<NUMBER_OF_KERNEL; i++){
                if (fpix.data[i] < 0.0// データが 0 以下だったら 0 にする
                    fpix.data[i] = 0.0;
            }

            outs << fpix;
        }
    }

    return(0);
}


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

HLSストレーム・インターフェースのReLU1

HLSストリームの畳み込み層に続き、HLSストリームのReLU を実装して行こう。

まずは、relu プロジェクトを作成した。
hls_relu_1_180419.png

C シミュレーションを行った。結果を示す。
hls_relu_2_180419.png

C シミュレーションでの ReLU の出力結果は relu_output.h に記録された。
hls_relu_12_180419.png

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

Estmated は 8.72 ns で問題ない。
Latency は 321 クロックだった。これもほとんど 1 クロックで 1 ピクセルを処理できているので問題ないはず。。。
リソース使用量は、FF が 251 個、LUT が 791 個だった。

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

Latency は 351 クロックだった。

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

ins_V_empty_n の前の部分が間が空いているのが分かる。これは、user 信号が 1 にアサートされたときがスタートなのだが、それを待っている do while() 文のLatency が長いことが原因のようだ。
その部分を拡大してみよう。

hls_relu_6_180419.png

1 にアサートされる間隔は、60 ns で 6 クロック分に当たる。

解決策としては、その do while() 文にPIPELINE指示子を入れてみよう。
hls_relu_7_180419.png

これで、再度 C コードの合成を行った。結果を示す。
hls_relu_8_180419.png

Estmated は 6.77 ns となった。
Latency は、317 クロックと 321 クロックから 4 クロック改善している。
リソース使用量は、FF が 165 個で、LUT が 360 個だった。FF、LUT 共に少なくなっている。

もう一度、C/RTL 協調シミュレーションを行った。結果を示す。
hls_relu_9_180419.png

Latency は 322 クロックで、前回の 351 クロックより 29 クロック少なくなっている。

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

do while() 文と次の for() 文の間が 3 クロック間が空いているだけで、他はWait 無しに処理できているようだ。

Vivado synthesis, place and route にチェックを入れて Export RTL を行った。結果を示す。
hls_relu_11_180419.png

SLICE は 48 個使用している。
LUT は 119 個、FF は 132 個使用している。
CP achieved post-implementation は 5.008 ns で問題なさそうだ。
  1. 2018年04月19日 05:04 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0
»