FC2カウンター FPGAの部屋 2018年04月

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

FPGAの部屋

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

HLSストリームの最終出力層(C ソースコード)

今回は、HLSストリームの最終出力層をやってみることにする。最終出力層は、CNN の 3 つの出力の内で、最大の値を出力した出力ポートのID を出力する。また、3 つの出力も出力する。

それでは、output_layer.h から貼っておく。

// output_layer.h
// 2018/03/11 by marsee
// 2018/03/12 : 出力フォーマットを変更
// 2018/04/30 : HLS Streamに変更
//

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

static const size_t NUMBER_OF_OUTPUT_LAYER = 3;

static const size_t W = 12;
static const size_t I = 7;

typedef struct {
    ap_fixed<W,I,AP_TRN,AP_WRAP> data [NUMBER_OF_OUTPUT_LAYER];
} mdata_type;

typedef struct {
    float data [NUMBER_OF_OUTPUT_LAYER];
} fmdata_type;

typedef ap_fixed<W,I,AP_TRN,AP_WRAP> out_affine_type;

typedef ap_uint<2> output_type;
#endif


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

// output_layer.cpp
// 2018/03/11 by marsee
// 2018/03/12 : 出力フォーマットを変更
// 2018/04/30 : HLS Streamに変更
//

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

#include "layer_general.h"
#include "output_layer.h"

int output_layer(hls::stream<ap_fixed_axis<W,I,1,1> >& ins, output_type& output,
        out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER]){
#pragma HLS ARRAY_PARTITION variable=dot2 complete dim=1
#pragma HLS DATA_PACK variable=ins

    ap_fixed_axis<W,I,1,1> stdata;
    mdata_type af2;
    int max_num;
    out_affine_type max_val;

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

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

        af2.data[i] = stdata.data[0];
    }

    max_val = 0;
    Loop3: for(int i=0; i<NUMBER_OF_OUTPUT_LAYER; i++){
#pragma HLS UNROLL
        dot2[i] = af2.data[i];
        if(i == 0){
            max_val = af2.data[0];
            max_num = 0;
        } else if (max_val < af2.data[i]){
            max_val = af2.data[i];
            max_num = i;
        }
    }

    output = output_type(max_num);

    return(0);
}


output_layer_tb.cpp を貼っておく。

// output_layer_tb.cpp
// 2018/03/12 by marsee
// 2018/03/12 : 出力フォーマットを変更
// 2018/04/30 : HLS Streamに変更
//

#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 "output_layer.h"
#include "affine_layer2_output.h"

int output_layer(hls::stream<ap_fixed_axis<W,I,1,1> >& ins, output_type& output,
        out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER]);

int output_layer_soft(hls::stream<float_axis<1,1> >& ins, output_type& output,
        float dot2[NUMBER_OF_OUTPUT_LAYER]);

int main(){
    using namespace std;

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

    ap_fixed_axis<W,I,1,1> pix;
    float_axis<1,1> fpix;
    output_type out, out_soft;
    out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER];
    float fdot2[NUMBER_OF_OUTPUT_LAYER];

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

        fpix.user = 0;
        fpix.data[0] = (float)i;
        ins_soft << fpix;
    }

    // 1 画面分のデータを ins、ins_soft に入力する
    for(int i=0; i < NUMBER_OF_OUTPUT_LAYER; i++){
        pix.data[0] = affine2_out[i];
        fpix.data[0] = affine2_fout[i];

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

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

        ins << pix;
        ins_soft << fpix;
    }

    output_layer(ins, out, dot2);
    output_layer_soft(ins_soft, out_soft, fdot2);

    // out と out_soft を比較する
    cout << "out" << " = " << int(out) << " out_soft" " = " << int(out_soft) << endl;
    for(int i=0; i<NUMBER_OF_OUTPUT_LAYER; i++){
        cout << "dot2[" << i << "] = " << float(dot2[i]) << " fdot2[" << i << "] = " << fdot2[i] << endl;
    }
    if(out != out_soft){
        cout << "error: out" << " = " << int(out) << " out_soft" " = " << int(out_soft) << endl;
        //return(1);
    }

    return(0);
}

int output_layer_soft(hls::stream<float_axis<1,1> >& ins, output_type& output,
        float dot2[NUMBER_OF_OUTPUT_LAYER]){

    float_axis<1,1> stdata;
    fmdata_type af2;
    int max_num;
    float max_val;

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

    Loop2: for(int i=0; i<NUMBER_OF_OUTPUT_LAYER; i++){
        if(i != 0)    // 最初の入力はすでに入力されている
            ins >> stdata;    // AXI4-Stream からの入力

        af2.data[i] = stdata.data[0];
    }

    max_val = 0;
    Loop3: for(int i=0; i<NUMBER_OF_OUTPUT_LAYER; i++){
        dot2[i] = af2.data[i];
        if(i == 0){
            max_val = af2.data[0];
            max_num = 0;
        } else if (max_val < af2.data[i]){
            max_val = af2.data[i];
            max_num = i;
        }
    }

    output = output_type(max_num);

    return(0);
}


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

ゴールデンウイークの2日目につくば市の宝篋山に登ってきました

今日はゴールデンウイーク2日目だったので、つくば市の宝篋山に登ってきました。

今日は小田休憩所から登ってみようということで、午前8時ころ小田休憩所についたのですが、2つの駐車場共に満車でした。。。orz
そこで、小田の街中の市営筑波山麓小田駐車場に行ってみたら、車が少なかったので止められました。
車を降りて、小田休憩所に戻りました。今日は、極楽寺コースを行くことにしました。(コース図(PDFです)

極楽寺コース、最初は田んぼ道でしたが、途中から急な山道になりました。結構急です。途中に小さいですが滝とかもありますね。小川が流れています。
houkyousan_1_180429.jpg

本当に山道です。結構きついです。。。脈拍が155くらいに上がると虫の息なので、休憩して135位でもう一度出発というのを繰り返していました。水分はポカリスエット500ml 1本持って行きました。登りの時に3回くらい飲んだかな?
上はファイントラックの下着の上に長袖のランキング用シャツでしたがビショビショの状態です。リュクサックもしょっていました。

1時間くらいかかって頂上に着きました。
houkyousan_2_180429.jpg

houkyousan_3_180429.jpg

やはり春だからか?霞かかってましたね。でも、良い景色です。

帰りは、距離が長いから緩やかだろうということで、常願寺コースを行きました。最初はなだらかで良い気持ちで下りを走っていたのですが、途中は急でした。。。結構水も流れていて、滑って転びそうになってしまいました。
やはり、足は太ももががくがくになってしまいましたね。下りは効きますね。。。

合計2時間くらいで小田休憩所に到着しました。

時間: 2:06:44
距離:約 8 km
高度上昇: 446 m
  1. 2018年04月29日 18:10 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

HLSストリームの全結合層2(Export RTLまで)

HLSストリームの全結合層1(C ソースコード)”の続き。

前回は、HLSストリームの全結合層のC ソースコードを貼ったので、今回は、HLSストリームの全結合層1層目のC シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

最初にC シミュレーションを行った。結果を示す。
hls_affine1_1_180428.png

Error Count = 0 なので、2 つの任意精度固定小数点データ型の全結合層の計算が合った。

C コードの合成を行った。
hls_affine1_2_180428.png

Estmated は 8.55 ns で問題なさそうだ。Latency の min は 8505 クロックで max は 8817 クロックだった。
BRAM_18K は 12 個、DSP48E は 3 個、FF は593 個、LUT は 1077 個使用している。

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

Latency は 8818 クロックだった。

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

ins_V_read がまばらに 1 になっているが、1 個のデータが来たら、中間層の 100 回演算を直列にしてから次のデータを read するためそうなっている。outs_V_write は、最後のデータが来るまで、出力値は確定しないので、最後だけ 1 にアサートされているのが分かると思う。

最初の一部分を拡大してみた。
hls_affine1_5_180428.png

ins_V_read の間隔は1.13 us だった。

最後を拡大してみた。
hls_affine1_6_180428.png

最後に中間層 100 個分のデータが出力されている。

Export RTL を行った。
なお、Vivado synthesis, place and route にチェックを入れている。
hls_affine1_7_180428.png

CP achieved post-implementation は 7.524 ns で問題なさそうだ。
  1. 2018年04月29日 17:39 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

HLSストリームの全結合層1(C ソースコード)

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

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

// affine_layer1.h
// 2018/04/27 by marsee (HLS stream)
//

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

#include "af1_weight.h"
#include "af1_bias.h"

static const size_t V_PRE_LAYER_HIGHT = 3;
static const size_t H_PRE_LAYER_WIDTH = 26;
static const size_t NUMBER_OF_MIDDLE_LAYER = 100;

static const size_t NUMBER_OF_KERNEL = 2;
static const size_t ARRAY_SIZE = 5;
static const size_t CW = 16;
static const size_t CI = 6;
static const size_t NW = 19;
static const size_t NI = 7;

typedef struct {
    ap_fixed<NW,NI,AP_TRN,AP_WRAP> data [NUMBER_OF_MIDDLE_LAYER];
} mdata_type;

typedef struct {
    float data [NUMBER_OF_MIDDLE_LAYER];
} fmdata_type;

typedef ap_fixed<NW,NI,AP_TRN,AP_WRAP> affine_type;
typedef ap_fixed<CW,CI,AP_TRN,AP_WRAP> conv_type;


#endif


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

// affine_layer1.cpp
// 2018/04/27 by marsee (HLS stream)
//

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

#include "layer_general.h"
#include "affine_layer1.h"

int affine_layer1(hls::stream<ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<NW,NI,1,1> >& outs){
//#pragma HLS ARRAY_PARTITION variable=af1_weight complete dim=1
#pragma HLS DATA_PACK variable=outs
#pragma HLS DATA_PACK variable=ins

    ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> stdata;
    affine_type dot[NUMBER_OF_MIDDLE_LAYER];
//#pragma HLS ARRAY_PARTITION variable=dot complete dim=1
    ap_fixed_axis<NW,NI,1,1> outd;

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

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

            Loop4: for (int col=0; col<NUMBER_OF_MIDDLE_LAYER; col++){
#pragma HLS PIPELINE II=1
                if (x==0 && y==0// 最初は 0 にクリアする
                    dot[col] = 0;

                affine_type dot_temp = (affine_type)0;
                for (int i=0; i<NUMBER_OF_KERNEL; i++){
                    dot_temp += stdata.data[i] * af1_weight[V_PRE_LAYER_HIGHT*H_PRE_LAYER_WIDTH*i+y*H_PRE_LAYER_WIDTH+x][col];
                }
                dot[col] += dot_temp;

                if (y==V_PRE_LAYER_HIGHT-1 && x==H_PRE_LAYER_WIDTH-1){ // 最後はバイアスを加算する
                    dot[col] += af1_bias[col];

                    outd.data[0] = dot[col];

                    if(col == 0)
                        outd.user = 1;
                    else
                        outd.user = 0;

                    if(col == NUMBER_OF_MIDDLE_LAYER-1)
                        outd.last = 1;
                    else
                        outd.last = 0;

                    outs << outd;
                }
            }
        }
    }

    return(0);
}


テストベンチの affine_layer1_tb.cpp を貼っておく。

// affine_layer1_tb.cpp
// 2018/04/27 by marsee (HLS stream)
//

#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 "affine_layer1.h"
#include "max_pooling_output.h"

int affine_layer1(hls::stream<ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<NW,NI,1,1> >& outs);

int affine_layer1_2(hls::stream<ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<NW,NI,1,1> >& outs);

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

int main(){
    using namespace std;

    hls::stream<ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> > ins;
    hls::stream<ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> > ins2;
    hls::stream<ap_fixed_axis<NW,NI,1,1> > outs;
    hls::stream<ap_fixed_axis<NW,NI,1,1> > outs2;
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> > ins_soft;
    hls::stream<float_axis<1,1> > outs_soft;

    mdata_type dot;
    mdata_type dot2;
    fmdata_type fdot;

    ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> pix;
    ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> pix2;
    float_axis<NUMBER_OF_KERNEL,1> fpix;
    ap_fixed_axis<NW,NI,1,1> pdata;
    ap_fixed_axis<NW,NI,1,1> pdata2;
    float_axis<1,1> fpdata;

    // 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] = (affine_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 に入力する
    for(int j=0; j < V_PRE_LAYER_HIGHT; j++){
        for(int i=0; i < H_PRE_LAYER_WIDTH; i++){
            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                pix.data[k] = mp_out[j*H_PRE_LAYER_WIDTH+i][k];
                fpix.data[k] = mp_fout[j*H_PRE_LAYER_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 == H_PRE_LAYER_WIDTH-1){ // 行の最後でTLASTをアサートする
                pix.last = 1;
                fpix.last = 1;
            } else {
                pix.last = 0;
                fpix.last = 0;
            }

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

    affine_layer1(ins, outs);
    affine_layer1_2(ins2, outs2);
    affine_layer1_soft(ins_soft, outs_soft);

    // outs, outs2 を dot[] と dot2[] に代入して比較する
    int errcnt = 0;
    for(int i=0; i<NUMBER_OF_MIDDLE_LAYER; i++){
        outs >> pdata;
        outs2 >> pdata2;
        outs_soft >> fpdata;

        dot.data[i] = pdata.data[0];
        dot2.data[i] = pdata2.data[0];
        fdot.data[i] = fpdata.data[0];

        printf("i = %d, HW = %f, HW2 = %f, SW = %f\n", i, (float)dot.data[i], (float)dot2.data[i], fdot.data[i]);
        if(dot.data[i] != dot2.data[i]){ // 2つの実装の値が合わない
            printf("ERROR HW and SW results mismatch i = %d, HW = %f, HW2 = %f, SW = %f\n", i, (float)dot.data[i], (float)dot2.data[i], fdot.data[i]);
            errcnt++;
            //return(1);
        }
    }
    cout << "Error Count = " << errcnt << endl;
    cout << endl;

    // max_pooling の結果をヘッダファイルに出力
    ofstream OH("affine_layer1_output.h");
    OH << "// affine_layer1_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 __AFFINE_LAYER1_OUTPUT_H__" << endl;
    OH << "#define __AFFINE_LAYER1_OUTPUT_H__" << endl;
    OH << endl;
    OH << "const float affine1_fout[" << NUMBER_OF_MIDDLE_LAYER  << "] = {" << endl;
    for (int i=0; i<NUMBER_OF_MIDDLE_LAYER ; i++){
        OH << "    " << fixed << setprecision(14) << fdot.data[i];
        if (i == NUMBER_OF_MIDDLE_LAYER-1)
            OH << endl;
        else
            OH << "," << endl;
    }
    OH << "};" << endl << endl;

    OH << "const ap_fixed<19,7,AP_TRN,AP_WRAP> affine1_out[" << NUMBER_OF_MIDDLE_LAYER << "] = {" << endl;
    for (int i=0; i<NUMBER_OF_MIDDLE_LAYER ; i++){
        OH << "    " << fixed << setprecision(14) << (float)dot.data[i];
        if (i == NUMBER_OF_MIDDLE_LAYER-1)
            OH << endl;
        else
            OH << "," << endl;
    }
    OH << "};" << endl << endl;
    OH << "#endif" << endl;

    return(0);
}

int affine_layer1_soft(hls::stream<float_axis<NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<float_axis<1,1> >& outs){

    float_axis<NUMBER_OF_KERNEL,1> stdata;
    float dot[100];
    float_axis<1,1> outd;

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

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

            Loop4: for (int col=0; col<100; col++){
                if (x==0 && y==0// 最初は 0 にクリアする
                    dot[col] = 0;

                for (int i=0; i<NUMBER_OF_KERNEL; i++){
                    dot[col] += stdata.data[i] * af1_fweight[V_PRE_LAYER_HIGHT*H_PRE_LAYER_WIDTH*i+y*H_PRE_LAYER_WIDTH+x][col];
                }

                if (y==V_PRE_LAYER_HIGHT-1 && x==H_PRE_LAYER_WIDTH-1){ // 最後はバイアスを加算する
                    dot[col] += af1_fbias[col];

                    outd.data[0] = dot[col];

                    if(col == 0)
                        outd.user = 1;
                    else
                        outd.user = 0;

                    if(col == NUMBER_OF_MIDDLE_LAYER-1)
                        outd.last = 1;
                    else
                        outd.last = 0;

                    outs << outd;
                }
            }
        }
    }

    return(0);
}

// 検証用 affine_layer1_2()
// 検証用に affine_layer1() とは異なる実装でコーディング
int affine_layer1_2(hls::stream<ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<NW,NI,1,1> >& outs){

    ap_fixed_axis<CW,CI,NUMBER_OF_KERNEL,1> stdata;
    conv_type aff_in[NUMBER_OF_KERNEL][V_PRE_LAYER_HIGHT][H_PRE_LAYER_WIDTH];
    affine_type dot1[NUMBER_OF_MIDDLE_LAYER];
    ap_fixed_axis<NW,NI,1,1> outd;

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

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

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

    for(int col=0; col<NUMBER_OF_MIDDLE_LAYER; col++){
        dot1[col] = 0;
        for(int i=0; i<NUMBER_OF_KERNEL; i++){
            for(int j=0; j<V_PRE_LAYER_HIGHT; j++){
                for(int k=0; k<H_PRE_LAYER_WIDTH; k++){
                    dot1[col] += aff_in[i][j][k]*af1_weight[i*V_PRE_LAYER_HIGHT*H_PRE_LAYER_WIDTH+j*H_PRE_LAYER_WIDTH+k][col];
                }
            }
        }
        dot1[col] += af1_bias[col];

        outd.data[0] = dot1[col];
        if(col == 0)
            outd.user = 1;
        else
            outd.user = 0;

        if(col == NUMBER_OF_MIDDLE_LAYER-1)
            outd.last = 1;
        else
            outd.last = 0;

        outs << outd;
    }

    return(0);
}


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

HLSストリームのマックス・プーリング層2(Export RTLまで)

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

前回はマックス・プーリング層の C ソースコードを公開した。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

まずは、C シミュレーションからやってみよう。
hls_max_pooling_1_180427.png

Error Count = 0 なので、2 つの任意精度固定小数点データ型のマックス・プーリングの計算が合った。

マックス・プーリング層の出力の max_pooling_output.h の浮動小数点数の結果の一部を示す。
hls_max_pooling_2_180427.png

マックス・プーリング層の出力の max_pooling_output.h の任意精度固定小数点数の結果の一部を示す。
hls_max_pooling_3_180427.png

C コードの合成を行った。
hls_max_pooling_4_180427.png

Estmated は 7.60 ns で問題ない。Latency は 318 クロックだった。入力数は、52 x 6 = 312 なので、ほぼ 1 クロックで 1 データを処理できていることが分かる。
リソース使用量は、FF が 509 個で、LUT は、614 個だった。

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

Latency は 323 クロックだった。

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

ins_V_read がほぼ 1 になっていて、スループットが高いのが分かる。
出力の outs_V_write は 3 つの塊が合って、1 クロックごとに 1 と 0 を繰り返している。これは、2 x 2 のマックス・プーリングで 2 データに 1 回しか結果が出ないからだ。

Export RTL を行った。
なお、Vivado synthesis, place and route にチェックを入れている。
hls_max_pooling_7_180427.png

CP achieved post-implementation は 7.398 ns で問題なさそうだ。
  1. 2018年04月27日 04:46 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

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

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

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

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

// max_pooling.h
// 2018/04/19 by marsee (HLS stream)
//

#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<W, I, AP_TRN, AP_WRAP> conv_type;

#endif


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

// max_pooling.cpp
// 2018/04/19 by marsee (HLS stream)
// 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 (HLS stream)
//

#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<NUMBER_OF_KERNEL,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 (HLS stream)
// 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 (HLS stream)
//

#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<W, I, AP_TRN, AP_WRAP> conv_type;

#endif


relu.cpp を示す。

// relu.cpp
// 2018/04/15 by marsee (HLS stream)
//

#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 (HLS stream)
//

#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

HLSストリーム・インターフェースの畳み込み層3(Windows 10 のVivado HLS 使用)

HLSストリーム・インターフェースの畳み込み層2(C シミュレーション、C/RTL 協調シミュレーション)”の続き。

Linux 版の Vivado HLS 2017.4 では(正確にいうと SDx 2017.4 のVivado HLS )では、HLSストレーム・インターフェース版畳み込み層のC/RTL 協調シミュレーションがエラーになってしまった。そこで、Windows 10 のVivado HLS 2017.4 (こちらも SDx がインストールしたVivado HLS)で試してみようと思う。なお、ソースコードは同一のコードを使用している。

一通りやることにして、最初は C シミュレーションからやってみよう。
hlsw_conv_layer_5_180418.png

C シミュレーションはLinux との違いは無いようだ。

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

Latency もリソース使用量もLinux と同一だった。

次に問題のC/RTL 協調シミュレーションを行った。成功した。Windows 10 だとうまく行くようだ。
hlsw_conv_layer_2_180418.png

Latency は 581 クロックだった。
C/RTL 協調シミュレーションの波形を示す。
hlsw_conv_layer_3_180418.png

ins_TRADY と ins_TVALID がほとんど 1 でスループットが最大になっているのが分かる。
outs_V_write を見ると、後ろの方に 6 個の 1 の部分があって、その 1 の部分が平らになっている。
問題無いようだ。

最後に、Vivado synthesis, place and route にチェックを入れて Export RTL を行った。結果を示す。なお、左が Windows 10 で右hがLinux の Export RTL の結果だ。
hlsw_conv_layer_4_180418.pnghls_conv_layer_4_180413.png

Resource Usage の SLICE がWindows 10 版と Linux 版で違っているのが分かる。Windows 10 版が 284 個で Linux 版が 268 個だった。その他の Resource Usage の値は一緒だった。
タイミングでも、CP achieved post-synthesis の値は一緒だったが、CP achieved post-implementation の値が違っている。Windows 10 版は 9.173 ns で Linux 版は 8.703 ns だった。

C/RTL 協調シミュレーションが Linux ではうまく行かないので、Windows 10 で開発を進めていこう。
  1. 2018年04月18日 05:08 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

darknet で画像認識をやってみる2(Tiny Darknet)

darknet で画像認識をやってみる1(ImageNet Classification)”の続き。

前回の extraction は 27 層だったが、今回は、更に層数の少ない 22 層の Tiny Darknet をやってみることにした。

darknet の Tiny Darknet をやってみる。
まずは、重み(tiny.weights)をダウンロードしよう。
wget https://pjreddie.com/media/files/tiny.weights
YOLOv3_32_180416.png

実行してみよう。
./darknet classify cfg/tiny.cfg tiny.weights data/dog.jpg
YOLOv3_38_180417.jpg
YOLOv3_39_180417.jpg

結果をテキストで示す。

masaaki@masaaki-H110M4-M01:~/DNN/darknet$ ./darknet classify cfg/tiny.cfg tiny.weights data/dog.jpg
layer     filters    size              input                output
    0 conv     16  3 x 3 / 1   224 x 224 x   3   ->   224 x 224 x  16  0.043 BFLOPs
    1 max          2 x 2 / 2   224 x 224 x  16   ->   112 x 112 x  16
    2 conv     32  3 x 3 / 1   112 x 112 x  16   ->   112 x 112 x  32  0.116 BFLOPs
    3 max          2 x 2 / 2   112 x 112 x  32   ->    56 x  56 x  32
    4 conv     16  1 x 1 / 1    56 x  56 x  32   ->    56 x  56 x  16  0.003 BFLOPs
    5 conv    128  3 x 3 / 1    56 x  56 x  16   ->    56 x  56 x 128  0.116 BFLOPs
    6 conv     16  1 x 1 / 1    56 x  56 x 128   ->    56 x  56 x  16  0.013 BFLOPs
    7 conv    128  3 x 3 / 1    56 x  56 x  16   ->    56 x  56 x 128  0.116 BFLOPs
    8 max          2 x 2 / 2    56 x  56 x 128   ->    28 x  28 x 128
    9 conv     32  1 x 1 / 1    28 x  28 x 128   ->    28 x  28 x  32  0.006 BFLOPs
   10 conv    256  3 x 3 / 1    28 x  28 x  32   ->    28 x  28 x 256  0.116 BFLOPs
   11 conv     32  1 x 1 / 1    28 x  28 x 256   ->    28 x  28 x  32  0.013 BFLOPs
   12 conv    256  3 x 3 / 1    28 x  28 x  32   ->    28 x  28 x 256  0.116 BFLOPs
   13 max          2 x 2 / 2    28 x  28 x 256   ->    14 x  14 x 256
   14 conv     64  1 x 1 / 1    14 x  14 x 256   ->    14 x  14 x  64  0.006 BFLOPs
   15 conv    512  3 x 3 / 1    14 x  14 x  64   ->    14 x  14 x 512  0.116 BFLOPs
   16 conv     64  1 x 1 / 1    14 x  14 x 512   ->    14 x  14 x  64  0.013 BFLOPs
   17 conv    512  3 x 3 / 1    14 x  14 x  64   ->    14 x  14 x 512  0.116 BFLOPs
   18 conv    128  1 x 1 / 1    14 x  14 x 512   ->    14 x  14 x 128  0.026 BFLOPs
   19 conv   1000  1 x 1 / 1    14 x  14 x 128   ->    14 x  14 x1000  0.050 BFLOPs
   20 avg                       14 x  14 x1000   ->  1000
   21 softmax                                        1000
   22 cost                                           1000
Loading weights from tiny.weights...Done!
data/dog.jpg: Predicted in 0.388883 seconds.
14.51%: malamute
 6.09%: Newfoundland
 5.59%: dogsled
 4.55%: standard schnauzer
 4.05%: Eskimo dog



やはり、前回と一緒で、一番確率の高いのは malamute のようだ。しかも 14.51% で、前回の 12.77% よりも確率は上がっている。
darknet から dog.jpg の画像を引用する。
YOLOv3_35_180416.jpg

次に data/eagle.jpg をやってみよう。
./darknet classify cfg/tiny.cfg tiny.weights data/eagle.jpg
YOLOv3_40_180417.jpg

data/eagle.jpg: Predicted in 0.384116 seconds.
54.11%: bald eagle
12.01%: ruddy turnstone
11.61%: kite
8.80%: hen
4.15%: vulture


やはり、一番確率の高いのは、bald eagle でハクトウワシだそうだ。こちらは、前回の 61.74% よりも 54.11% で確率は下がっている。
eagle.jpg をdarknet から引用する。
YOLOv3_36_180416.jpg

最後に giraffe.jpg をやってみる。
giraffe.jpg を darknet から引用する。
YOLOv3_37_180416.jpg

キリンとシマウマが写っている画像だ。
./darknet classify cfg/tiny.cfg tiny.weights data/giraffe.jpg
YOLOv3_41_180417.jpg

data/giraffe.jpg: Predicted in 0.378156 seconds.
29.71%: zebra
8.75%: tiger cat
7.81%: great grey owl
6.33%: prairie chicken
4.63%: bustard


やはり、シマウマが認識されているがキリンは項目が見当たらない。zebra の認識率は、前回は 28.03% で、今回は 29.71% だった。
  1. 2018年04月17日 04:06 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

darknet で画像認識をやってみる1(ImageNet Classification)

4月7日の”組込みDL 体験コース”は面白かった。パソナテックさんありがとうございました。層構成のレポートがわかるようになったし、とても参考になった。
しかし、テキストがYOLOv3 以前のdarknet で書かれているので、サイトが変わってしまったようだ。重みをダウンロードしようとしたらできなかった。
今回は、darknet の Classification(画像認識)をやってみようと思う。

darknet の ImageNet Classification をやってみよう。
まずは、重み(extraction.weights)をダウンロードしよう。
wget https://pjreddie.com/media/files/extraction.weights
YOLOv3_30_180416.png

コアダンプされたが、extraction.weights はダウンロードできているようだ。
実行してみよう。実行するときに detect ではなく、classifier を指定すれば良いようだ。
./darknet classifier predict cfg/imagenet1k.data cfg/extraction.cfg extraction.weights data/dog.jpg
YOLOv3_31_180416.png

masaaki@masaaki-H110M4-M01:~/DNN/darknet$ ./darknet classifier predict cfg/imagenet1k.data cfg/extraction.cfg extraction.weights data/dog.jpg
layer     filters    size              input                output
    0 conv     64  7 x 7 / 2   224 x 224 x   3   ->   112 x 112 x  64  0.236 BFLOPs
    1 max          2 x 2 / 2   112 x 112 x  64   ->    56 x  56 x  64
    2 conv    192  3 x 3 / 1    56 x  56 x  64   ->    56 x  56 x 192  0.694 BFLOPs
    3 max          2 x 2 / 2    56 x  56 x 192   ->    28 x  28 x 192
    4 conv    128  1 x 1 / 1    28 x  28 x 192   ->    28 x  28 x 128  0.039 BFLOPs
    5 conv    256  3 x 3 / 1    28 x  28 x 128   ->    28 x  28 x 256  0.462 BFLOPs
    6 conv    256  1 x 1 / 1    28 x  28 x 256   ->    28 x  28 x 256  0.103 BFLOPs
    7 conv    512  3 x 3 / 1    28 x  28 x 256   ->    28 x  28 x 512  1.850 BFLOPs
    8 max          2 x 2 / 2    28 x  28 x 512   ->    14 x  14 x 512
    9 conv    256  1 x 1 / 1    14 x  14 x 512   ->    14 x  14 x 256  0.051 BFLOPs
   10 conv    512  3 x 3 / 1    14 x  14 x 256   ->    14 x  14 x 512  0.462 BFLOPs
   11 conv    256  1 x 1 / 1    14 x  14 x 512   ->    14 x  14 x 256  0.051 BFLOPs
   12 conv    512  3 x 3 / 1    14 x  14 x 256   ->    14 x  14 x 512  0.462 BFLOPs
   13 conv    256  1 x 1 / 1    14 x  14 x 512   ->    14 x  14 x 256  0.051 BFLOPs
   14 conv    512  3 x 3 / 1    14 x  14 x 256   ->    14 x  14 x 512  0.462 BFLOPs
   15 conv    256  1 x 1 / 1    14 x  14 x 512   ->    14 x  14 x 256  0.051 BFLOPs
   16 conv    512  3 x 3 / 1    14 x  14 x 256   ->    14 x  14 x 512  0.462 BFLOPs
   17 conv    512  1 x 1 / 1    14 x  14 x 512   ->    14 x  14 x 512  0.103 BFLOPs
   18 conv   1024  3 x 3 / 1    14 x  14 x 512   ->    14 x  14 x1024  1.850 BFLOPs
   19 max          2 x 2 / 2    14 x  14 x1024   ->     7 x   7 x1024
   20 conv    512  1 x 1 / 1     7 x   7 x1024   ->     7 x   7 x 512  0.051 BFLOPs
   21 conv   1024  3 x 3 / 1     7 x   7 x 512   ->     7 x   7 x1024  0.462 BFLOPs
   22 conv    512  1 x 1 / 1     7 x   7 x1024   ->     7 x   7 x 512  0.051 BFLOPs
   23 conv   1024  3 x 3 / 1     7 x   7 x 512   ->     7 x   7 x1024  0.462 BFLOPs
   24 conv   1000  1 x 1 / 1     7 x   7 x1024   ->     7 x   7 x1000  0.100 BFLOPs
   25 avg                        7 x   7 x1000   ->  1000
   26 softmax                                        1000
   27 cost                                           1000
Loading weights from extraction.weights...Done!
data/dog.jpg: Predicted in 2.876122 seconds.
12.77%: malamute
10.03%: Siberian husky
 7.23%: Eskimo dog
 4.91%: miniature schnauzer
 4.88%: Afghan hound


一番確率の高いのは malamute で犬の一種のようだ。
darknet から dog.jpg の画像を引用する。
YOLOv3_35_180416.jpg

次に data/eagle.jpg をやってみよう。
./darknet classifier predict cfg/imagenet1k.data cfg/extraction.cfg extraction.weights data/eagle.jpg
YOLOv3_33_180416.png

Loading weights from extraction.weights...Done!
data/eagle.jpg: Predicted in 2.890507 seconds.
61.74%: bald eagle
36.86%: kite
0.48%: vulture
0.19%: ptarmigan
0.14%: hen


一番確率の高いのは、bald eagle でハクトウワシだそうだ。
eagle.jpg をdarknet から引用する。
YOLOv3_36_180416.jpg

最後に giraffe.jpg をやってみる。
giraffe.jpg を darknet から引用する。
YOLOv3_37_180416.jpg

キリンとシマウマが1頭ずついるので、何を認識するか興味のあるところだ。
./darknet classifier predict cfg/imagenet1k.data cfg/extraction.cfg extraction.weights data/giraffe.jpg
YOLOv3_34_180416.png

Loading weights from extraction.weights...Done!
data/giraffe.jpg: Predicted in 2.897918 seconds.
28.03%: zebra
14.40%: bustard
11.90%: gazelle
6.38%: cheetah
5.97%: impala


シマウマが認識されているがキリンは項目が見当たらない。
  1. 2018年04月16日 05:36 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

HLSストリーム・インターフェースの畳み込み層2(C シミュレーション、C/RTL 協調シミュレーション)

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

前回は、畳み込みニューラルネットワークの畳み込み層をHLS ストリームで記述することで、特徴マップを配列にしてHLS ストリームで転送するように記述することができた。それで、C コードの合成、Export RTL を行った。今回は、畳み込み層の C シミュレーション、C/RTL 協調シミュレーションを行う。

最初に、前回忘れていたので、合成後の VHDL の entity 部分を貼っておく。
なぜ、VHDL か?というと、ビット幅までentity 部分に記述されているからだ。

entity conv_layer is
port (
    ap_clk : IN STD_LOGIC;
    ap_rst_n : IN STD_LOGIC;
    ap_start : IN STD_LOGIC;
    ap_done : OUT STD_LOGIC;
    ap_idle : OUT STD_LOGIC;
    ap_ready : OUT STD_LOGIC;
    ins_TDATA : IN STD_LOGIC_VECTOR (31 downto 0);
    ins_TVALID : IN STD_LOGIC;
    ins_TREADY : OUT STD_LOGIC;
    ins_TKEEP : IN STD_LOGIC_VECTOR (3 downto 0);
    ins_TSTRB : IN STD_LOGIC_VECTOR (3 downto 0);
    ins_TUSER : IN STD_LOGIC_VECTOR (0 downto 0);
    ins_TLAST : IN STD_LOGIC_VECTOR (0 downto 0);
    ins_TID : IN STD_LOGIC_VECTOR (0 downto 0);
    ins_TDEST : IN STD_LOGIC_VECTOR (0 downto 0);
    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;


ブロックレベルのインターフェースとAXI4-Stream インターフェース、それに、DATA_PACK されたHLS ストリーム・インターフェースがあるのがわかる。

C シミュレーションの結果を示す。
hls_conv_layer_5_180414.png

2 以上離れたハードウェアの結果とソフトウェアの結果はない。
値も、”AXI4-Stream インターフェースの畳み込み層2(C シミュレーション)”と同じだ。

conv_layer_output.h も値も、”AXI4-Stream インターフェースの畳み込み層5(出力値のヘッダファイルを出力)”と同じだ。
hls_conv_layer_6_180414.png

次に、C/RTL 強調シミュレーションを行ったが、エラーになってしまった。
hls_conv_layer_7_180414.png

エラー内容を示す。

/opt/Xilinx/Vivado/2017.4/include/floating_point_v7_0_bitacc_cmodel.h:245:45: error: ‘mpfr_srcptr’ has not been declared
/opt/Xilinx/Vivado/2017.4/include/floating_point_v7_0_bitacc_cmodel.h:246:53: error: ‘mpfr_srcptr’ has not been declared
make: *** [obj/conv_layer.cpp_pre.cpp.tb.o] エラー 1
ERROR: [COSIM 212-317] C++ compile error.
ERROR: [COSIM 212-321] EXE file generate failed.
ERROR: [COSIM 212-321] EXE file generate failed.
ERROR: [COSIM 212-331] Aborting co-simulation: C simulation failed, compilation errors.
ERROR: [COSIM 212-4] *** C/RTL co-simulation finished: FAIL ***
command 'ap_source' returned error code
    while executing
"source /home/masaaki/Vivado_HLS/ZYBO-Z7-20/hlss_cnn/conv_layer/solution1/cosim.tcl"
    invoked from within
"hls::main /home/masaaki/Vivado_HLS/ZYBO-Z7-20/hlss_cnn/conv_layer/solution1/cosim.tcl"
    ("uplevel" body line 1)
    invoked from within
"uplevel 1 hls::main {*}$args"
    (procedure "hls_proc" line 5)
    invoked from within
"hls_proc $argv"
Finished C/RTL cosimulation.


テストベンチの 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 "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);
}

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

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

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

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

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

// layer_general.h
// 2018/04/12 by marsee (HLS stream)
//

#ifndef __LAYER_GENERAL_H__
#define __LAYER_GENERAL_H__

#include <ap_fixed.h>

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

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

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

#endif


conv_layer.h を示す。

// conv_layer.h
// 2018/04/12 by marsee (HLS stream)

#ifndef __CONV_LAYER_H__
#define __CONV_LAYER_H__

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

#define HORIZONTAL_PIXEL_WIDTH  56
#define VERTICAL_PIXEL_WIDTH    10

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

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

#endif

conv_layer.cpp を示す。

// conv_layer.cpp
// 2018/04/12 by marsee (HLS stream)
//

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

#include "layer_general.h"

#include <ap_axi_sdata.h>

#include "conv_layer.h"

int conv_layer(hls::stream<ap_axiu<32,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >&outs){
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS DATA_PACK variable=outs

    ap_axiu<32,1,1,1> pix;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> conv_out;

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

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

    in_type ap_uf_pix;
    val_type val;

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

結果を示す。
hls_conv_layer_4_180413.png

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

Vivado HLSでのAXI4-Stream のテンプレートを作成する3

Vivado HLSでのAXI4-Stream のテンプレートを作成する2”の続き。

Vivado HLSでのAXI4-Stream のテンプレートを作成する1”で、畳み込み層の特徴マップの個数に対応するために、データを配列にしたのだが、AXI4-Stream にするという制約ではうまくかなかった。よって、”Vivado HLSでのAXI4-Stream のテンプレートを作成する2”でデータを展開して書いたのだけれど、これでは不満が残った。たくさん特徴マップがあった時に書くのが大変だからだ。それに、特徴マップの数を変更することが難しい。配列で書けば配列の数を書き換えると、すぐに特徴マップの数を変更することができる。
その後、AXI4-Stream で各層をつなぐのではなく、HLS ストリームで接続することにしたので、データを配列にできると思う。今日はそのテストをしてみようと思う。

Vivado HLSでのAXI4-Stream のテンプレートを作成する1”の stream_test.cpp を書き換えた。stream_test.cpp を示す。

// stream_test.cpp
// 2018/02/11 by marsee
// 2018/04/12 : DATA_PACK指示子を追加
//

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

#include "stream_test.h"

int stream_test(hls::stream<ap_fixed_axis<16,6,2,1,1,1> >& ins,
        hls::stream<ap_fixed_axis<16,6,2,1,1,1> >& outs, int num){
#pragma HLS DATA_PACK variable=outs
#pragma HLS DATA_PACK variable=ins

    ap_fixed_axis<16,6,2,1,1,1> ins_t;
    ap_fixed_axis<16,6,2,1,1,1> outs_t;

    for(int y=0; y<10; y++){
        for(int x=0; x<56; x++){
#pragma HLS PIPELINE II=1
            ins >> ins_t;
            for(int i=0; i<num; i++){
                outs_t.data[i] = ins_t.data[i] * (ap_fixed<16, 6, AP_TRN, AP_WRAP>)(i+2);
            }
            outs_t.user = 1;
            outs_t.last = 0;

            outs << outs_t;
        }
    }

    return(0);
}

int stream_top(hls::stream<ap_fixed_axis<16,6,2,1,1,1> >& ins,
        hls::stream<ap_fixed_axis<16,6,2,1,1,1> >& outs){
#pragma HLS DATA_PACK variable=outs
#pragma HLS DATA_PACK variable=ins
#pragma HLS DATAFLOW

    hls::stream<ap_fixed_axis<16,6,2,1,1,1> > temp_st;

    stream_test(ins, temp_st, 2);
    stream_test(temp_st, outs, 2);

    return(0);
}


新たに stream_top() 関数を作成して stream_test() を 2 回読んでいる。これは、HLS ストリームの層を複数呼んだことになる。
stream_top() の入力ストリーム、出力ストリームに data_pack 指示子を付けたら、呼ばれた stream_test() の方にも入力ストリーム、出力ストリームに DATA_PACK 指示子を付けないとエラーになった。(stream_top() にだけ DATA_PACK 指示子を付けたらエラーになった)

stream_top.h は ”Vivado HLSでのAXI4-Stream のテンプレートを作成する1”のコードをそのまま使用している。

stream_test プロジェクトを示す。
stream_test2_1_180412.png

C コードの合成結果を示す。
stream_test2_2_180412.png

Estmated は 6.01 ns で十分な性能が出ている。
Latency は 565 クロックで、総ピクセル数が 560 ピクセルなので、ほぼ 1 クロックで 1 ピクセルの処理ができている。

DATA_PACK 指示子を付けた状態でのVHDL の entity 宣言部分を示す。

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


hls::stream<ap_fixed_axis<16,6,2,1,1,1> > がパックされているのがわかる。

DATA_PACK 指示子を取り除いた状態でのVHDL の entity 宣言部分を示す。

entity stream_top is
port (
    ins_V_data_0_V_dout : IN STD_LOGIC_VECTOR (15 downto 0);
    ins_V_data_0_V_empty_n : IN STD_LOGIC;
    ins_V_data_0_V_read : OUT STD_LOGIC;
    ins_V_data_1_V_dout : IN STD_LOGIC_VECTOR (15 downto 0);
    ins_V_data_1_V_empty_n : IN STD_LOGIC;
    ins_V_data_1_V_read : OUT STD_LOGIC;
    ins_V_keep_V_dout : IN STD_LOGIC_VECTOR (1 downto 0);
    ins_V_keep_V_empty_n : IN STD_LOGIC;
    ins_V_keep_V_read : OUT STD_LOGIC;
    ins_V_strb_V_dout : IN STD_LOGIC_VECTOR (1 downto 0);
    ins_V_strb_V_empty_n : IN STD_LOGIC;
    ins_V_strb_V_read : OUT STD_LOGIC;
    ins_V_user_V_dout : IN STD_LOGIC_VECTOR (0 downto 0);
    ins_V_user_V_empty_n : IN STD_LOGIC;
    ins_V_user_V_read : OUT STD_LOGIC;
    ins_V_last_V_dout : IN STD_LOGIC_VECTOR (0 downto 0);
    ins_V_last_V_empty_n : IN STD_LOGIC;
    ins_V_last_V_read : OUT STD_LOGIC;
    ins_V_id_V_dout : IN STD_LOGIC_VECTOR (0 downto 0);
    ins_V_id_V_empty_n : IN STD_LOGIC;
    ins_V_id_V_read : OUT STD_LOGIC;
    ins_V_dest_V_dout : IN STD_LOGIC_VECTOR (0 downto 0);
    ins_V_dest_V_empty_n : IN STD_LOGIC;
    ins_V_dest_V_read : OUT STD_LOGIC;
    outs_V_data_0_V_din : OUT STD_LOGIC_VECTOR (15 downto 0);
    outs_V_data_0_V_full_n : IN STD_LOGIC;
    outs_V_data_0_V_write : OUT STD_LOGIC;
    outs_V_data_1_V_din : OUT STD_LOGIC_VECTOR (15 downto 0);
    outs_V_data_1_V_full_n : IN STD_LOGIC;
    outs_V_data_1_V_write : OUT STD_LOGIC;
    outs_V_keep_V_din : OUT STD_LOGIC_VECTOR (1 downto 0);
    outs_V_keep_V_full_n : IN STD_LOGIC;
    outs_V_keep_V_write : OUT STD_LOGIC;
    outs_V_strb_V_din : OUT STD_LOGIC_VECTOR (1 downto 0);
    outs_V_strb_V_full_n : IN STD_LOGIC;
    outs_V_strb_V_write : OUT STD_LOGIC;
    outs_V_user_V_din : OUT STD_LOGIC_VECTOR (0 downto 0);
    outs_V_user_V_full_n : IN STD_LOGIC;
    outs_V_user_V_write : OUT STD_LOGIC;
    outs_V_last_V_din : OUT STD_LOGIC_VECTOR (0 downto 0);
    outs_V_last_V_full_n : IN STD_LOGIC;
    outs_V_last_V_write : OUT STD_LOGIC;
    outs_V_id_V_din : OUT STD_LOGIC_VECTOR (0 downto 0);
    outs_V_id_V_full_n : IN STD_LOGIC;
    outs_V_id_V_write : OUT STD_LOGIC;
    outs_V_dest_V_din : OUT STD_LOGIC_VECTOR (0 downto 0);
    outs_V_dest_V_full_n : IN STD_LOGIC;
    outs_V_dest_V_write : OUT STD_LOGIC;
    ap_clk : IN STD_LOGIC;
    ap_rst : IN STD_LOGIC;
    ap_done : OUT STD_LOGIC;
    ap_start : IN STD_LOGIC;
    ap_ready : OUT STD_LOGIC;
    ap_idle : OUT STD_LOGIC;
    ap_return : OUT STD_LOGIC_VECTOR (31 downto 0) );
end;


入力ポートごとに empty_n, read ポートがついて、出力ポートごとに full_n, write ポートが生成されている。これは無駄だ。

これから、内部で使用する HLS ストリームのポートに関しては、DATA_PACK 指示子を付けたほうが良さそうだ。付けなくても問題ないかもしれないが。。。
  1. 2018年04月12日 04:45 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

reVISION-Zybo-Z7-20をやってみた11(WarpTransform)

reVISION-Zybo-Z7-20をやってみた10(stereo)”の続き。

前回は、reVISION-Zybo-Z7-20 の File I/O の example の stereo をやってみた。今回は、reVISION-Zybo-Z7-20 の File I/O の example の最後の WarpTransform をやってみた。

なお、今回も @ikwzm さんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”のプラットフォームを使用して、SDSoC が出力した実行ファイルを実行している。

SDSoC の WarpTransform プロジェクトを示す。
reVISION-Zybo-Z7-20_127_120409.png

Debug でビルドを行って成功した。
Debug/sd_card を ZYBO-Z7-20 の Debian 上の ~/sdsoc_test/warptransform/ に SFTP した。

これからは、 ZYBO-Z7-20 の Debian 上での作業になる。
stereo から _p0_.dts と xlnk.dts を ~/sdsoc_test/stereo/ 上にコピーした。
ビットファイルをダウンロードして、Xilinx APF Accelerator driver を有功にする。
sd_card ディレクトリに行く。
cp ../stereo/*.dts .
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlink.dts
cd sd_card/

reVISION-Zybo-Z7-20_128_120409.png

warptransform.elf を実行した。
sudo warptransform.elf im0.jpg
reVISION-Zybo-Z7-20_129_120409.png

レポートを示す。

Transformation Matrix
1.01332 0.0172398 -19.5289
0.00700084 1.01997 -16.4525
0 0 0
elapsed time 14995626

Max_err: 4 Min_err: 1 Num_errs: 342 Num_errs > 1: 342


input.png, hls_out.jpg, opencv_output.png, diff_image.png が生成された。
reVISION-Zybo-Z7-20_130_120409.png

reVISION-Zybo-Z7-20 の WarpTransform example の im0.jpg を引用する。
reVISION-Zybo-Z7-20_131_120409.jpg

input.png を示す。
reVISION-Zybo-Z7-20_132_120409.jpg

opencv_output.png を示す。少し平行四辺形に歪んでいる?
reVISION-Zybo-Z7-20_133_120409.jpg

hls_out.jpg を示す。
reVISION-Zybo-Z7-20_134_120409.jpg

diff_image.png を示す。これは、opencv_output.png と hls_out.jpg の差分なのだろう?
reVISION-Zybo-Z7-20_135_120409.jpg

これで、reVISION-Zybo-Z7-20 の File I/O をすべて、@ikwzm さんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”のプラットフォームを使用して、SDSoC が出力した実行ファイルを実行することができた。
  1. 2018年04月11日 05:25 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:0

reVISION-Zybo-Z7-20をやってみた10(stereo)

reVISION-Zybo-Z7-20をやってみた9(再びDense Non-Pyramidal Optical Flow)”の続き。

前回は、CMA領域を 64MB に増やして、Dense Non-Pyramidal Optical Flowをやってみたところエラーになることもなく正常に実行できた。今回は、reVISION-Zybo-Z7-20 の File I/O の example の残りの stereo をやってみよう。

なお、今回も @ikwzm さんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”のプラットフォームを使用して、SDSoC が出力した実行ファイルを実行している。

SDSoC の stereo プロジェクトを示す。
reVISION-Zybo-Z7-20_126_120409.png

Debug でビルドを行って成功した。
Debug/sd_card を ZYBO-Z7-20 の Debian 上の ~/sdsoc_test/stereo/ に SFTP した。

これからは、 ZYBO-Z7-20 の Debian 上での作業になる。
dnp_of から _p0_.dts と xlnk.dts を ~/sdsoc_test/stereo/ 上にコピーした。
前回の dnp_of をやったままだったので、デバイスツリーを削除した。
cp ../dnp_of/*.dts .
sudo dtbocfg.rb --remove _p0_
sudo dtbocfg.rb --remove xlink

reVISION-Zybo-Z7-20_117_120408.png

ビットファイルをダウンロードして、Xilinx APF Accelerator driver を有功にする。
sd_card ディレクトリに行って、stereo.elf を実行する。
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlink.dts
cd sd_card/
sudo .stereo.elf left.png right.png

reVISION-Zybo-Z7-20_118_120408.png

sd_card ディレクトリに ocv_output.png, hls_output.jpg が生成された。
reVISION-Zybo-Z7-20_119_120408.png

まずは、reVISION-Zybo-Z7-20 の stereo example の left.png と right.png を引用する。
reVISION-Zybo-Z7-20_122_120408.jpg

reVISION-Zybo-Z7-20_123_120408.jpg

ocv_output.png を示す。
reVISION-Zybo-Z7-20_121_120408.jpg

hls_output.jpg を示す。
reVISION-Zybo-Z7-20_120_120408.jpg

白のほうが手前で、黒のほうが奥なんだろうと思う。
hls_output.jpg の方が、ocv_output.png よりも白っぽい感じがする。

今回も @ikwzm さんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”のプラットフォームを使用して、stereo example が実行できた。
  1. 2018年04月10日 05:06 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:0

reVISION-Zybo-Z7-20をやってみた9(再びDense Non-Pyramidal Optical Flow)

reVISION-Zybo-Z7-20をやってみた8(Harrisコーナー検出)”の続きではあるのだが、”@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる4”でエラーになった Dense Non-Pyramidal Optical Flow の対処方法を @ikwzm さんに教えて頂いたので、もう一度やってみようと思う。@ikwzm さん、お世話になり、ありがとうございました。

まずは、Linux 上では、CMA (Contiguous Memory Allocator) 領域という領域を SDSoC は使用している。CMA 領域は連続なメモリ領域を取ることができる。通常のメモリ領域を malloc() などで確保した場合はソフトウェア側から連続に見えても MMU (Memory Management Unit) がページごとに物理メモリを割り振っていて、ページを超えると物理アドレスが連続していないということが良くあり得る。その場合には、FPGAがメモリにDMA する場合は、ページごとにDMA の再起動が必要となる(ちなみにAXI4 インターフェースは 4Kバイト境界でバーストをやめる必要があります)。ページごとにアドレスを入れ替えるために Scatter Gather DMA がある。だが、連続領域にDMA するほうが絶対に効率が良い。そこで、物理メモリ的に連続領域を確保できる CMA 領域の出番がある。

Dense Non-Pyramidal Optical Flow のエラーは、その CMA 領域を確保しようとしたがメモリが足りないよというエラーだった。
よってCMA 領域を増やせば良いということになる。現在は 16 MB を確保している。それを 64 MB に増やすことにした。
増やし方は、ZYBO-Z7-20 の Micro SD カードの第1パーティションの uEnv.txt を書き換えることだ。なお、この書き方は、@ikwzm さんに教えて頂いた。

ZYBO-Z7-20 の Debian 上では /boot にマウントされている ディレクトリの uEnv.txt を書き換える。

linux_boot_cmd=setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait uio_pdrv_genirq.of_id=generic-uio && bootz 0x03000000 - 0x02A00000



linux_boot_cmd=setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait uio_pdrv_genirq.of_id=generic-uio cma=64M && bootz 0x03000000 - 0x02A00000


にした。つまり bootargs に cma=64M を追加した。
reVISION-Zybo-Z7-20_124_120409.png

リブートして、dmesg を見ると、CMA 領域が 64MB になった。
reVISION-Zybo-Z7-20_125_120409.png

それでは、もう一度、Dense Non-Pyramidal Optical Flow を@ikwzm さんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”の環境で動作させてみよう。

cd ~/sdsoc_test/dnp_of
sudo cp sd_card/_sds/_p0_.bin /lib/firmware/
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlnk.dts
cd sd_card/
sudo ./dnp_of.elf im0.jpg im1.jpg

今度はエラー無しに実行できた。
reVISION-Zybo-Z7-20_113_120408.png

~/sdsoc_test/dng_of/ ディレクトリを示す。
reVISION-Zybo-Z7-20_114_120408.png

~/sdsoc_test/dng_of/sd_card/ ディレクトリを示す。
reVISION-Zybo-Z7-20_115_120408.png

out_0.jpg ができている。開いてみると、
reVISION-Zybo-Z7-20_116_120408.jpg

うまく行っている。成功だ。
  1. 2018年04月09日 04:13 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

reVISION-Zybo-Z7-20をやってみた8(Harrisコーナー検出)

reVISION-Zybo-Z7-20をやってみた8(Dense Non-Pyramidal Optical Flow)”の続き。

前回は、reVISION-Zybo-Z7-20 の Dense Non-Pyramidal Optical Flow をやってみた。今回は、Harrisコーナー検出をやってみることにしよう。ただし、使用するプラットフォームは”@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる4”で使用した @ikwzm さんのプラットフォームとする。

それでは、SYSROOT を設定して、sdx を立ち上げ、File メニューから New -> SDx Project... を選択して新規プロジェクトを作成しよう。
なお、SYSROOT は .bashrc で設定した。
reVISION-Zybo-Z7-20_111_120408.png

Project name: は harris にした。
reVISION-Zybo-Z7-20_101_120407.png

zybo_z7_20 のプラットフォームを指定する。
reVISION-Zybo-Z7-20_102_120407.png

テンプレートに Harris - File I/O を指定する。
reVISION-Zybo-Z7-20_103_120407.png

harris プロジェクトが作成された。
reVISION-Zybo-Z7-20_104_120407.png

トンカチ・ボタンをクリックして、Debug モードでビルドし、成功した。
reVISION-Zybo-Z7-20_112_120408.png

~/sdx_workspaces/revisio_zybo_z7_20_ws/harris/Debug/sd_card ディレクトリを”@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる4”のZYBO-Z7-20 で走る Debian の ~/sdsoc_test/harris/ ディレクトリにコピーした。

~/sdsoc_test/harris/ ディレクトリに入って、dnp_of の _p0_.dts と xlink.dts をコピーした。
cd ~/sdsoc_test/harris/
cp ../dnp_of/*.dts .


デバイスツリーをオーバーロードして、harris.elf を実行する。
sudo cp sd_card/_sds/_p0_.bin /lib/firmware/
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlnk.dts
cd sd_card/
sudo ./harris.elf im0.jpg

成功だった。
reVISION-Zybo-Z7-20_105_120408.png

レポートを示す。

elapsed time 9409130
ocv corner count = 98, Hls corner count = 122
Commmon = 98 Success = 80.327866 Loss = 0.000000 Gain = 19.672131



harris.elf 実行後の ~/sdsoc_test/harris/sd_card/ ディレクトリの内容を示す。
reVISION-Zybo-Z7-20_106_120408.png

元画像の im0.jpg を示す。
reVISION-Zybo-Z7-20_107_120408.png

ハードウェアのHarris の出力が hls_out.jpg である。
reVISION-Zybo-Z7-20_108_120408.png

ハードウェアのHarris の出力がある点に、元画像 im0.jpg のところに点を描画したのが、output_hls.png である。
reVISION-Zybo-Z7-20_109_120408.png

OpenCV で出力があるところに原画像に点を売ったのが、output_ocv.png である。
reVISION-Zybo-Z7-20_110_120408.png

Harris コーナー検出は”@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる4”で使用した @ikwzm さんのプラットフォームで動作した。
  1. 2018年04月08日 05:38 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:0

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる4

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる3”の続き。

前回は、実行ファイルを実行した後で、一旦ドライバを取り外して異なるマスタの回路をロードして、マスタ用の実行ファイルを実行することができた。今回は、”ZYBO-Z7-20のDebianにOpenCV 3.1.0をインストール”で ZYBO-Z7-20 の Debian に OpenCV 3.1.0 をインストールすることができたので、”reVISION-Zybo-Z7-20をやってみた6(Zybo-Z7-20 で確認)”でやったバイラテラル・フィルタや、”reVISION-Zybo-Z7-20をやってみた8(Dense Non-Pyramidal Optical Flow)”でやった Dense Non-Pyramidal Optical Flow を @ikwzm さんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”でやってみることにする。

まずはバイラテラル・フィルタからやってみよう。
まずは、ZYBO-Z7-20 の Debian の ~/sdsoc_test ディレクトリに bilateral_ex と dnp_of の 2 つのディレクトリを作成した。
~/sdsoc_test/bilateral_ex ディレクトリには、_p0_.dts と xlink.dts と reVISION-Zybo-Z7-20 で生成したバイラテラル・フィルタの Debug/sd_card ディレクトリをSFTP してコピーした sd_card ディレクトリがある。なお、_p0_.dts と xlink.dts については、”@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる2”を参照のこと。

sd_card/_sds/_p0_.bin を /lib/firmware/ にコピーして、 _p0_.dts をロードした。更に xlink.dts をロードした。
./sd_card/bilateral_ex.elf を実行したところ libv4l2subdev.so.0 共有ライブラリが無いというエラーになってしまった。
sudo cp sd_card/_sds/_p0_.bin /lib/firmware/
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlnk.dts
sudo ./sd_card/bilateral_ex.elf

SDSoC_ikwzm_34_180406.png

エラー内容を示す。

./sd_card/bilateral_ex.elf: error while loading shared libraries: libv4l2subdev.so.0: cannot open shared object file: No such file or directory


reVISION-Zybo-Z7-20 の Micro SD カードのROOTFS パーテションを libv4l2subdev.so.0 で検索すると /usr/lib/ ディレクトリで発見した。
SDSoC_ikwzm_35_180406.png

”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”の Micro SD カードの元から /usr/lib/に入ってたファイルを作成した old_lib にコピーした。
SDSoC_ikwzm_36_180406.png

reVISION-Zybo-Z7-20 の Micro SD カードのROOTFS パーテションの libv4l2subdev.so.0 を”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”の Micro SD カードの /usr/lib/ にコピーした。(異なるターミナルでコピーを行った)
その後もう一度、バイラテラル・フィルタの実行ファイルを実行した。
sudo ./sd_card/bilateral_ex.elf
すると、libmediactl.so.0 が足りないと言われた。エラー内容を示す。

./sd_card/bilateral_ex.elf: error while loading shared libraries: libmediactl.so.0: cannot open shared object file: No such file or directory


同様に、reVISION-Zybo-Z7-20 の Micro SD カードのROOTFS パーテションの libmediactl.so.0 を”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”の Micro SD カードの /usr/lib/ にコピーした。(異なるターミナルでコピーを行った)
そして、
sudo ./sd_card/bilateral_ex.elf
今度は動作したが、im0.jpg を指定するのを忘れていた。
sudo ./sd_card/bilateral_ex.elf ./sd_card/im0.jpg
今度は成功した。実行時のレポートを示す。

sigma_color: 7.72211 sigma_space: 0.901059
elapsed time 14358244
Minimum error in intensity = 0
Maximum error in intensity = 1
Percentage of pixels above error threshold = 0.00236304 Count: 4


SDSoC_ikwzm_37_180406.png

SDSoC_ikwzm_38_180406.png

ZYBO-Z7-20 の Debian の ~/sdsoc_test/bilateral/ex/ ディレクトリを nautilus で見たところだ、error.png, hls_out.jpg, output_ocv.png が生成されている。
SDSoC_ikwzm_40_180406.png

error.png を示す。
SDSoC_ikwzm_41_180406.jpg

hls_out.jpg を示す。
SDSoC_ikwzm_42_180406.jpg

output_ocv.png を示す。
SDSoC_ikwzm_43_180406.jpg

次に、ドライバを削除して Dense Non-Pyramidal Optical Flow をやってみよう。
cd ~/sdsoc_test/dnp_of
sudo dtbocfg.rb --remove _p0_
sudo dtbocfg.rb --remove xlnk
sudo cp sd_card/_sds/_p0_.bin /lib/firmware/
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlnk.dts

SDSoC_ikwzm_44_180406.png

cd sd_card/
sudo ./dnp_of.elf im0.jpg im1.jpg

dnp_of.elf を実行するとエラーになってしまった。
SDSoC_ikwzm_45_180406.png

エラー内容を示す。

Failed to allocate memory

Failed to allocate memory

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.414190] Internal error: Oops: 17 [#1] PREEMPT SMP ARM

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.505378] Process dnp_of.elf (pid: 4117, stack limit = 0xe25b4218)

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.511714] Stack: (0xe25b5e00 to 0xe25b6000)

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.516061] 5e00: 00000000 c0113bc4 00000000 ee477600 000007e8 00000000 00000002 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.524220] 5e20: 001fa400 00000001 00000005 e32b007c 600f0013 00000000 00000014 ee7ed840

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.532378] 5e40: 83c00743 00000000 bebdf230 00000000 c0045808 bebdf230 e25b4000 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.540538] 5e60: 00000000 bf0376d0 00000000 e25b5ed0 00000000 00000001 e25b5ea4 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.548697] 5e80: e297adb8 83c00703 00000000 00000000 00000000 ffefe70c ef29a734 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.556856] 5ea0: efaac420 ee477300 d5480010 00000000 00000000 00000000 007e9000 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.565015] 5ec0: 00000000 00000000 00000002 00000000 00000000 b659a000 00032030 0000002c

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.573175] 5ee0: bebdf2b4 00000000 00000001 001fa400 00000000 0002f258 00032030 0000002c

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.581334] 5f00: 0016b790 bebdf230 ee6cc508 ee4f7780 c0045808 bebdf230 e25b4000 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.589492] 5f20: 00000000 c01f89fc bebdf230 c01f9288 00000000 00000000 ef29a700 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.597653] 5f40: ef085de0 ae43e000 e3324300 ef085de8 ef29a704 c01d0804 00000009 c02033ac

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.605811] 5f60: 00000009 0002f258 007e9000 c0045808 00000009 ee4f7781 ee4f7780 c0045808

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.613971] 5f80: bebdf230 e25b4000 00000000 c01f93cc 00000001 0002f258 007e9000 00000036

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.622130] 5fa0: c0107ac4 c01078e0 00000001 0002f258 00000009 c0045808 bebdf230 0002ff48

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.630288] 5fc0: 00000001 0002f258 007e9000 00000036 00000002 00000000 00000000 00000000

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.638448] 5fe0: 0002f2e4 bebdf22c 0001b66c b653f716 000f0030 00000009 04050607 00020103

Message from syslogd@debian-fpga at Apr 6 05:03:36 ...
kernel:[ 4499.686451] Code: eb44a1dc e15b0006 0a000011 e7990106 (e5903014)

^CApplication Terminated by User


”Failed to allocate memory”が 2 回出ているが malloc() はソース上で 3 回実行されている。1 回めは通っても 2 回め、3 回めは通らなかったのだろうか?

reVISION-Zybo-Z7-20 のバイラテラル・フィルタは実行できたが、Dense Non-Pyramidal Optical Flow は”Failed to allocate memory”で実行できなかった。
  1. 2018年04月07日 04:19 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

ZYBO-Z7-20のDebianにOpenCV 3.1.0をインストール

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる1”で作りなおしたZYBO-Z7-20のDebianにOpenCV3.1.0をインストールした。

なお、何でOpenCV 3.1.0 かというと、reVISION-Zybo-Z7-20 で作成したMicro SD カードでOpenCVのバージョンを調べるために、
pkg-config --modversion opencv
を実行したところ、3.1.0 だったためだ。

さて、OpenCV 3.1.0 をインストールしていこう。
最初に”Ubuntu 16.04にOpenCV 3.1をインストールする手順”を見ながら下準備をした。
しかし、無いモジュールもあった。コマンドを示す。
sudo apt-get install build-essential cmake git
debian_opencv310_1_180404.png

sudo apt-get install ffmpeg libopencv-dev libgtk-3-dev python-numpy python3-numpy libdc1394-22 libdc1394-22-dev libjpeg-dev libtiff5-dev libavcodec-dev libavformat-dev libswscale-dev libxine2-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libv4l-dev libtbb-dev qtbase5-dev libfaac-dev libmp3lame-dev libopencore-amrnb-dev libopencore-amrwb-dev libtheora-dev libvorbis-dev libxvidcore-dev x264 v4l-utils unzip
debian_opencv310_2_180404.png

OpenCV 3.1.0 をダウンロードする。
wget https://github.com/Itseez/opencv/archive/3.1.0.zip
debian_opencv310_3_180404.png

OpenCV 3.1.0 を解凍する。
unzip 3.1.0.zip
debian_opencv310_4_180405.png

cd opencv-3.1.0/
mkdir build
cd build

debian_opencv310_5_180405.png

この辺りで、Raspberry Pi のOpenCVインストール記事に乗り換える。”Raspberry Pi 2 ★ OpenCV 3.1.0 のインストール 〜 C++ & Python サンプルでのカメラ動作確認まで”を参考にした。その記事によると、cmake-gui を使うとのことなので、
sudo apt install cmake-gui
で cmake-gui をインストールした。
debian_opencv310_6_180405.png

cmake-gui
Where is the source code: に /home/fpga/opencv-3.1.0
Where is build the binaries: に /home/fpga/opencv-3.1.0/build
を設定した。
Add Entry ボタンをクリックして以下のエントリを追加し、チェックを付けた。
ENABLE_NEON
ENABLE_OMIT_FRAME_POINTER
ENABLE_PRECOMPILED_HEADERS (このエントリを追加してチェックをつけたことにより、後でエラーになってしまった。チェック無しをお勧めする)
ENABLE_VFPV3
debian_opencv310_7_180405.png

Configure ボタンをクリックする。(下の図はクリックする前)
debian_opencv310_8_180405.png

CMakeSetup ダイアログが開いた。Specify the generator for this project をUnix Makefile のままとして、Finish ボタンをクリックした。
debian_opencv310_9_180405.png

Configure が進んでいるところを示す。
debian_opencv310_10_180405.png 

Configure が終了し、エントリが増えた。
Generate ボタンをクリックした。
debian_opencv310_11_180405.png

Generate 終了。cmake-gui を閉じた。
debian_opencv310_12_180405.png

make
make が始まった。
debian_opencv310_13_180405.png

19% 付近でエラー発生。なんと stdlib.h がないと言われている。。。
debian_opencv310_14_180405.png

検索してみると、”opencv2系をgcc6でコンパイルしたらエラーになった"がヒット。それによると、

-D ENABLE_PRECOMPILED_HEADERS=OFF

にする必要があるということだ。
もう一度
cmake-gui
して、ENABLE_PRECOMPILED_HEADERS のチェックを外した。
debian_opencv310_15_180405.png

Configure して、Generate した。
debian_opencv310_16_180405.png

そして、もう一度
make
した。
debian_opencv310_17_180405.png

make が成功した。やった〜〜〜。

sudo make install
sudo ldconfig

debian_opencv310_18_180405.png

python のサンプルを実行する。
cd ../samples/python/
ls

debian_opencv310_19_180405.png

./video.py
debian_opencv310_20_180405.png

./video.py を実行すると以下のウインドウが表示された。
debian_opencv310_21_180405.png

次は C++ のサンプルをやってみる。
cd ~/opencv-3.1.0/samples/cpp/example_cmake/
make

debian_opencv310_22_180405.png

opencv_example ができた。

./opencv_example
で実行すると、

Gtk-Message: Failed to load module "canberra-gtk-module"

が出ている。
debian_opencv310_23_180405.png

./opencv_example を実行すると下のようなウインドウが表示された。
debian_opencv310_24_180405.png

”Gtk-Message: Failed to load”表示が気になるので、検索したところ” [SOLVED] Gtk-Message: Failed to load module "canberra-gtk-module"”が見つかった。
それによると、
sudo apt-get install libcanberra-gtk3-module
すればよいそうなので、やってみた。
もう一度、
./opencv_example
すると、”Gtk-Message: Failed to load”表示は解消された。
debian_opencv310_25_180405.png

しかし表示されたウインドウに変化はなかった。
debian_opencv310_26_180405.png

example.cpp を見てみると、VideoCapture とかを使用していて、カメラ画像をキャプチャするサンプルのようだ。これは無理のようだ。

ともかく、OpenCV 3.1.0 をインストールすることができた。
  1. 2018年04月06日 03:59 |
  2. OpenCV
  3. | トラックバック:0
  4. | コメント:0

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる3

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる2”の続き。

この記事は、”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”を全面的に参照しています。

前回は、Xilinx APF Accelerator driver を使用してSDSoC で作成した実行ファイルを実行することができた。今回は、前回の実行ファイルを実行した後で、一旦ドライバを取り外して異なるマスタの回路をロードして、マスタ用の実行ファイルを実行してみよう。

まずは、前回の multi_apuint をもう一度やってみよう。
ZYBO-Z7-20 の debian 上で
cd sdsoc_test/multi_apuint/
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlnk.dts
sudo ./sd_card/multi_apuint.elf

すでに、sudo cp sd_card/_sds/_p0_.bin /lib/firmware/ は昨日やってあるので省略。
SDSoC_ikwzm_22_180403.png

”multi_out = 100”が表示された。問題ない。
その時のシリアル接続のターミナルの表示を示す。
SDSoC_ikwzm_23_180403.png

そのまま、ドライバを削除する。
sudo dtbocfg.rb --remove _p0_
sudo dtbocfg.rb --remove xlnk


dma_pow をZYBO-Z7-20 の debian 上で動作させるのだが、最初にSDSoCの画面を示す。
SDSoC_ikwzm_25_180403.png

Vivado HLS の合成結果を示す。
SDSoC_ikwzm_26_180403.png

次にソースコードを示そう。
まずは、DMA_pow.cpp から。

#pragma SDS data zero_copy(in[0:10])
#pragma SDS data zero_copy(out[0:10])

int DMA_pow2(int *in, int *out){

    int i;

    for (i=0; i<10; i++){
     int temp = *in++;
        *out++ =temp * temp;
    }

    return(0);
}


Vivado HLS で使用した”FPGA+SoC+Linux実践勉強会での課題をやってみた1(Vivado HLS編)”に示すコードではエラーになってしまったので変更した。

DMA_pow_tb.cpp を示す。

#include <stdio.h>
#include <stdlib.h>
#include "sds_lib.h"

int DMA_pow2(int *in, int *out);

int main(){
    int *data;
    int *result;
    int i;

    if((data=(int *)sds_alloc(sizeof(int)*10)) == NULL){
     fprintf(stderr, "Can't allocate data[10]\n");
     exit(1);
    }

    if((result=(int *)sds_alloc(sizeof(int)*10)) == NULL){
     fprintf(stderr, "Can't allocate result[10]\n");
     exit(1);
    }

    for(int i=0; i<10; i++){
     data[i] = i;
    }

    DMA_pow2(data, result);

    for(i=0; i<10; i++){
        printf("data[%d] = %d, result[%d] = %d\n", i, data[i], i, result[i]);
    }
}


テストベンチは、sds_alloc() を使用して、連続領域にメモリをアサインしていて、DMA_pow.cpp の方がゼロコピーでDMA するようにしてある。最も、このメモリ量だと 4k バイトの1ページに届かないので、連続領域にメモリをアサインしなくも問題ないかもしれない?

ZYBO-Z7-20 の debian 上で dma_pow ディレクトリを作成した。
cd ~/sdsoc_test
mkdir dma_pow
cd dma_pow

SDSoC_ikwzm_27_180403.png

SFTP アプリケーションソフトを PC 上で立ち上げて、PC 上の sdx_workspaces/revisio_zybo_z7_20_ws/DMA_pow/Debug/sd_card ディレクトリを ZYBO-Z7-20 の debian 上の /home/fpga/sdsoc_test/dma_pow/ ディレクトリ上にコピーした。
SDSoC_ikwzm_28_180403.png

tree コマンドで確かめた。そして、multi_apuint で作成した _p0_.dts と xlnk.dts はそのまま使用できるので、コピーした。
tree sd_card
cp ../multi_apuint/*.dts

SDSoC_ikwzm_29_180403.png

sudo cp sd_card/_sds/_p0_.bin /lib/firmware/
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
sudo dtbocfg.rb --install xlnk --dts xlnk.dts

SDSoC_ikwzm_30_180403.png

sudo ./sd_card/DMA_pow.elf
SDSoC_ikwzm_31_180403.png

結果が問題なく表示された。成功だ。。。

その時のシリアル接続のターミナルの表示を示す。
SDSoC_ikwzm_32_180403.png

これで、SDSoC で作ったビットファイルとアプリケーションソフトを自分の環境で実行することができた。しかも、ZYBO-Z7-20 の debian を起動しながら入れ替えることができた。今までは、出来上がった sd_card ディレクトリの内容を SD カードに書いて、ボードに入れ、電源ON だったのだが、今回は、起動中のボードのLinux に SFTP してファイルを渡せば、ボードのLinux が動作したまま、いくらでも SDSoC の結果を試せるということになる。作って頂いた @ikwzm さん、ありがとうございました。
  1. 2018年04月04日 04:59 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる2

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる1”の続き。

この記事は、”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”を全面的に参照しています。

前回は、Xilinx APF Accelerator driver のインストールを行った。今回は、Xilinx APF Accelerator driver を使用してSDSoC で作成した実行ファイルを実行してみよう。

reVISION-Zybo-Z7-20 プラットフォームでmulti_apuint プロジェクトを作成した。
ソースコードは”Vivado HLS 勉強会1(基礎編)を公開しました”にあるので参照のこと。
SDSoC_ikwzm_8_180402.png

SDSoC の Vivado HLS のレポートを示す。
SDSoC_ikwzm_13_180403.png

sdx_workspaces/revisio_zybo_z7_20_ws/multi_apuint/Debug/sd_card ディレクトリを示す。
SDSoC_ikwzm_9_180402.png

ZYBO-Z7-20 の Debian にログインして sdsoc_test ディレクトリを作成し、その下に multi_apuint ディレクトリを作成した。
mkdir sdsoc_test
cd sdsoc_test
mkdir multi_apuint
cd multi_apuint

SDSoC_ikwzm_10_180402.png

sdx_workspaces/revisio_zybo_z7_20_ws/multi_apuint/Debug/sd_card ディレクトリをSFTP で ZYBO-Z7-20 の Debian の /home/fpga/sdsoc_test/multi_apuint ディレクトリの下にコピーした。
SDSoC_ikwzm_11_180402.png

/home/fpga/sdsoc_test/multi_apuint ディレクトリの下のディレクトリ構造を示す。
SDSoC_ikwzm_12_180402.png

/home/fpga/sdsoc_test/multi_apuint ディレクトリに _p0_.dts を作成した。
SDSoC_ikwzm_14_180403.png

_p0_.bin を /lib/firmware/ ディレクトリにコピーして、_sds/_p0.bin をFPGA にダウンロードする。
sudo cp sd_card/_sds/_p0_.bin /lib/firmware/
sudo dtbocfg.rb --install _p0_ --dts _p0_.dts

SDSoC_ikwzm_15_180403.png

ZYBO-Z7-20 の DONE LED が点灯してコンフィギュレーションできたことが分かった。
このターミナルは ssh ログインしてるターミナルなので、シリアル接続のターミナルにメッセージが出いている。
SDSoC_ikwzm_16_180403.png

Xilinx APF Accelerator driver を有功にする。
ZYBO-Z7-20 の Debian の /home/fpga/sdsoc_test/multi_apuint ディレクトリに xlnk.dts を作成した。
SDSoC_ikwzm_17_180403.png

sudo dtbocfg.rb --install xlnk --dts xlnk.dts
sudo ./sd_card/multi_apuint.elf

xlnk が開けないというエラーだった。
SDSoC_ikwzm_18_180403.png

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる1”にも追記で書いたが、コマンドが足りなかったようだ。最初それがわからなかったので、dpkg でインストールすることにした。
cd ~/xlnk-kernel-module/
dpkg -i xlnk-kernel-module-4.14.21-armv7-fpga_0.0.1-1_arm.deb

SDSoC_ikwzm_19_180403.png

もう一度、
sudo dtbocfg.rb --install xlnk --dts xlnk.dts
sudo ./sd_card/multi_apuint.elf

今度はうまく行って、”multi_out = 100”が表示された。
SDSoC_ikwzm_20_180403.png

その時のシリアル接続のターミナルの表示を示す。
SDSoC_ikwzm_21_180403.png
  1. 2018年04月03日 20:31 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

@ikwzmさんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみる1

@ikwzmさんの作ってくれた”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”をやってみることにした。
SDSoC は、プロジェクトをビルドしてSD カードに書いて、基板を起動しても RAM イメージのLinux が立ち上がってアプリケーションソフトを起動して機能を実現する方式だ。しかしそれに不満があった。SDSoC は、Root File System も指定できるようだが、それでも SDSoC 用に1つMicro SD カードを用意することには代わり無い。
私の希望は、今まで使っている育て上げてきたLinux 上で SDSoC から出力された実行ファイルが実行できるということだ。それを @ikwzmさんが”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”で実現してくれたので、使ってみない手は無い。。。
しかも、FPGA+SoC+Linux+Device Tree Overlay+FPGA Region を使用すれば、Linux をリブートすること無しに、FPGA のコンフィギュレーション、デバイス・ツリーのロード、PS のクロック設定ができてしまう。つまり、Linux のリブート無しに SDSoC の実行ファイルを実行できるのだ。これは使わない手は無い。。。 @ikwzm さん、ありがとうございました。

さて早速、やってみよう。
本当は、@ikwzm さんの”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(ブートイメージの提供)”の通りに Debian の Micro SD カードを作る必要があるのだが、”FPGA+SoC+Linux実践勉強会での課題をやってみた1(Vivado HLS編)”からのブログにあるように、2017年の12月にその Micro SD カードを作成してあるので、それを使うことにした。

FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)”の https://github.com/ikwzm/xlnk-kernel-module の @ikwzm が用意してくれたパッケージを使用することにした。
git clone git://github.com/ikwzm/xlnk-kernel-module
cd xlnk-kernel-module
git checkout 4.14.21-armv7-fpga
make

SDSoC_ikwzm_2_180331.png

make をしたがエラーになってしまった。 /lib/modules/4.14.21-armv7-fpga/build が無いとの事だった。

/lib/modules/ ディレクトリにあるのは、4.14.14-armv7-fpga だった。
SDSoC_ikwzm_3_180331.png

これはまずい。Makefile を 4.12.14-armv7-fpga に書き換えてみたのだが、ダメなので、もう一度、一から”FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(ブートイメージの提供)”の通りに Micro SD カードを作りなおした。
ただし、ヘッダファイルもインストールした。
sudo dpkg -i linux-headers-4.14.21-armv7-fpga_4.14.21-armv7-fpga-2_armhf.deb

これで、Micro SD カードの Debian はまっさらになったので、 apt update から設定を行った。
sudo apt update
sudo apt upgrade


そして、ファイル・マネージャーの nautilus をインストール。
sudo apt install nautilus
でも、ディスプレイが設定されていないということで起動できなかった。

そこで、ikwzm さんに教えて頂いた xbase-clients と xterm をインストールした。
sudo apt-get install xbase-clients xterm
これで、”ZYBO-Z7-20のDebian上のXクライアントをパソコンのUbuntu上に表示する”の様に nautilus のウインドウを表示することができた。でもまだ文字がトーフ状態だった。(なお、リブートする必要があった)

検索してみるとロケールを日本語にしているのに、日本語フォントが入っていないのが問題ということだった。
対処方法として、梅フォントをインストールした。
sudo apt-get install fonts-vlgothic
sudo apt-get install fonts-horai-umefont
sudo apt-get install fonts-umeplus


これで、ZYBO-Z7-20 に日本語フォントが入った。
nautilus &
で起動するときちんと日本語が見えた。
SDSoC_ikwzm_7_180402.png

これで設定はOK だろう。
さて、さっきの続きだ。
git clone git://github.com/ikwzm/xlnk-kernel-module
cd xlnk-kernel-module
git checkout 4.14.21-armv7-fpga
make

SDSoC_ikwzm_4_180401.png
SDSoC_ikwzm_5_180401.png

make は成功した。良かった。。。

sudo make install
SDSoC_ikwzm_6_180401.png

これも成功した。

(2018/04/03 :追記)
sudo make install の後の
sudo depmod
sudo systemctl start xilinx-apf-driver.service
sudo systemctl enable xilinx-apf-driver.service

を忘れてしまって、うまくインストールできていなかったようだ。
  1. 2018年04月02日 05:32 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

ZYBO-Z7-20のDebian上のXクライアントをパソコンのUbuntu上に表示する

ZYBO-Z7-20 の Debian 上の Xクライアントをパソコンの Ubuntu 上に表示したいということで、やってみた。

参考にしたのは、”Xクライアントを別のPCで表示する

Xクライアントを別のPCで表示する”によると

ssh CentOSのIPアドレス -X

で ZYBO-Z7-20 の Debian に入って、Xクライアントのコマンドを入力すれば良いようだ。

ssh 192.168.3.39 -X -l fpga
で、ZYBO-Z7-20 の Debian にログインして nautilus を起動した。
nautilus &
X_window_1_180401.png

ZYBO-Z7-20 の Debian 上の nautilus がパソコンの Ubuntu 上に表示された。
X_window_2_180401.png
  1. 2018年04月01日 06:01 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:0

Ubuntu 16.04.4 LTS に git-lfs をインストール

Ubuntu 16.04.4 LTS に git-lfs をインストールのに苦労したのでインストール方法を書いておく。

最初に、linuxbrew をインストールして git-lfs をインストールしようとした。
Git LFSの導入方法”辺りを参考にして、次のインストール方法を試してみた。

sudo apt linuxbrew-wapper

brew update
brew install git-lfs
git lfs install

git lfs install コマンドを実行した時に決まって、”git: 'lfs' はgitコマンドではありません。”と言われてしまった。
git-lfs_1_180401.png

いくらやっても同じなので、”Git LFSが1.0になってGitHubで使えるようになったので試してみた”を参考にした。

git-lfs.github.com から git-lfs-linux-amd64-2.4.0.tar.gz をダウンロードした。

git-lfs-linux-amd64-2.4.0.tar.gz を解凍したら、git-lfs-2.4.0 ディレクトリができた。

git-lfs-2.4.0 ディレクトリに入って、install.sh を実行した。
cd git-lfs-2.4.0
sudo bash install.sh

git lfs init は unknown command "init" for "git-lfs" になってしまったが、これが正常に解釈されるということは、git-lfs がインストールできたということだ。良かった。。。
git-lfs_2_180401.png
  1. 2018年04月01日 05:37 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:0