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

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

FPGAの部屋

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

AXI4-Stream インターフェースの全結合層1(C コードや指示子による性能差1)

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

最初に、C ソースコードを貼るのだが、全結合層はどのC ソースコードや指示子がリソース使用量とレイテンシの両面から良いのか良く分からないので、とりあえずのたたき台としてのコードであるということを断っておく。
まずは、affine_layer1.h から貼っておく。

// affine_layer1.h
// 2018/02/25 by marsee
//

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

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed2_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data1;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int U, int TI, int TD>
    struct float2_axis{
        struct data {
            float data0;
            float data1;
        } data;
        ap_uint<1> keep;
        ap_uint<1> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

#define NUMBER_OF_MIDDLE_LAYER    100

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

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

typedef ap_fixed<19,7,AP_TRN,AP_WRAP> affine_type;

#define V_PRE_LAYER_HIGHT    3
#define H_PRE_LAYER_WIDTH    26

#endif


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

// affine_layer1.cpp
// 2018/02/26 by marsee
//

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

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

int affine_layer1(hls::stream<ap_fixed2_axis<16,6,1,1,1> >& ins,
        mdata_type & outd){
//#pragma HLS ARRAY_PARTITION variable=af1_weight complete dim=1
#pragma HLS INTERFACE ap_hs register port=outd
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS DATA_PACK variable=outd

    ap_fixed2_axis<16,6,1,1,1> stdata;
    affine_type dot[100];
//#pragma HLS ARRAY_PARTITION variable=dot complete dim=1

    Loop1: do {
#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<100; col++){
//#pragma HLS PIPELINE II=1
                if (x==0 && y==0// 最初は 0 にクリアする
                    dot[col] = 0;

                dot[col] += stdata.data.data0 * af1_weight[y*H_PRE_LAYER_WIDTH+x][col];
                dot[col] += stdata.data.data1 * af1_weight[V_PRE_LAYER_HIGHT*H_PRE_LAYER_WIDTH+y*H_PRE_LAYER_WIDTH+x][col];

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

    Loop5: for (int col=0; col<100; col++){ // 出力にコピー
#pragma HLS UNROLL
        outd.data[col] = dot[col];
    }

    return(0);
}


出力は、ap_fixed<19,7,AP_TRN,AP_WRAP> data [NUMBER_OF_MIDDLE_LAYER] なのだが、配列でそのままだとRAM インターフェースになるので、とりあえず全部の出力線を確保するために、DATA_PACK指示子を使用している。

#pragma HLS DATA_PACK variable=outd

そしてその、IOレベルのインターフェースは ap_hs にしている。

#pragma HLS INTERFACE ap_hs register port=outd


PIPELINE指示子とARRAY_PARTITION指示子は後で入れるが、とりあえずはコメントアウトしてある。
このままC コードの合成を行う。
affine_layer_21_180228.png

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

Estmated は 9.63 ns で制約を満たしている。
Latency は 39371 クロックから 47171 クロックだった。100 MHz クロックとすると、393.71 us から 471.71 us である。
リソース使用量は、BRAM_18K が 13 個、DSP48E が 3 個、FF が 4,266 個、LUT が 1,354 個だった。

Analysis 画面を示す。
affine_layer_23_180228.png

53 ステートまである。dot 配列もBRAM に割り当てられているので、2 ポートずつ 2 クロックに渡ってアクセスしているのが分かる。

Resource 画面。
af1_weight と dot がBRAM にアサインされているのが分かる。
affine_layer_24_180228.png


次に、重みの af1_weight[][] に ARRAY_PARTITIONの complete オプション付きの指示子を付けてみよう。
これは、Loop3 の下のPIPELINE指示子を生かすときには、中間層の 100 個 X 畳み込みフィルタのカーネル数の 2 の 200 並列になるので、その場合にはBRAMではポートが足りなくなるため必要になるのだが、現在は要らないけど入れてみよう。
affine_layer_8_180228.png

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

Latency は 54,971 クロックから 62,771 クロックで、前より遅くなってしまった。。。
BRAM_18K は、3 個で前より 10 個減少した。DSP48E も 2 個で 1 個減少した。FF は 5,418 個で増えた。LUT も 3,949 個で 3 倍程度に増えている。

Analysis 画面を示す。
affine_layer_10_180228.png

Resource 画面を示す。
dot_V がBRAM_18K なので、read が並んでいる。
affine_layer_11_180228.png
  1. 2018年02月28日 21:47 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS のエラー内容がGUI に表示されていない時の対処方法

Vivado HLS のC コードの合成で、GUI にエラー内容が表示されずに、何のエラーで終了したのかが分からない時があって困っていた。
例えば、全結合層の実装しているときのエラーを示す。
affine_layer_1_180227.png

エラー内容を貼っておく。

Failed checking during preprocessing.
    while executing
"source C:/Users/Masaaki/Documents/VIvado_HLS/ZYBO_Z7-20/test/affine_layer1/solution1/csynth.tcl"
    invoked from within
"hls::main C:/Users/Masaaki/Documents/VIvado_HLS/ZYBO_Z7-20/test/affine_layer1/solution1/csynth.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 synthesis.


どういうエラーだかが全く分からない。。。

そういう時は、solution1 の solution1.log (solution2 でやっているときはsolution2 と読み替えること)に書いてあった。
affine_layer_2_180227.png

solution1.log を示す。
affine_layer_3_180227.png

affine_layer1/affine_layer1.cpp:38:24: error: no viable overloaded '='
              dot[col] = 0;

affine_layer1/affine_layer1.cpp:40:23: error: no viable overloaded '+='
             dot[col] += stdata.data.data0 * af1_weight[y*26 +x][col];


エラー内容が分かった。キャストなどのコードを追加しよう。

と思ったのだが、エラーの原因は配列のdot[100] を間違って、mdata_type で宣言してしまったことだった。
mdata_type は次の様に宣言してある。

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


よってC++ のコードが間違っている。この mdata_type を使うのは間違いなので、次の affine_type を使うように書き直した。

typedef ap_fixed<19,7,AP_TRN,AP_WRAP> affine_type;


これでエラーが無くなった。

しかし、普通にVivado HLS のGUI 上にエラー内容を表示してほしいものである。。。
  1. 2018年02月27日 04:16 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

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

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

(2018/04/21 :修正、バグフィックス)
(2018/04/25 : 修正、バグフィックス)

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

まずは、C/RTL 協調シミュレーションを行った。結果を示す。
Max_Pooling_5_180225.png

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

outs_TVALID を見ると 1 行ごとに 2 クロックに 1 回 1 にアサートされているのが分かる。これはMax Pooling がストラド2 の 2 x 2 の領域に行われているからだ。

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

Start ビットをセットしてから、ポーリングで終了を検出している。

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

合成レポートでは、678 個だったFF は 399 個、LUT は 1042 個が 328 個に減少した。
  1. 2018年02月26日 06:01 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

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

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

(2018/04/21 :修正、バグフィックス)
(2018/04/25 : 修正、バグフィックス)

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

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

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

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

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

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

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

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

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



Analysis 画面を示す。
Max_Pooling_3_180225.png

C 4 ステートまである。

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

「空海-KU-KAI- 美しき王妃の謎」(映画)を見てきました

久しぶりに映画を見てきました。「空海-KU-KAI- 美しき王妃の謎」です。
初めは、〇〇ねこの映画なのか?と思っていました。後のほうになると面白くなってきましたが、最初はどうなることかと思ってました。
まあまあかな?
  1. 2018年02月24日 21:20 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

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

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

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

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

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

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

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

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed2_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data1;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int U, int TI, int TD>
    struct float2_axis{
        struct data {
            float data0;
            float data1;
        } data;
        ap_uint<1> keep;
        ap_uint<1> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

#define H_PIXEL_WIDTH_IN    52
#define V_PIXEL_WIDTH_IN    6
#define H_PIXEL_WIDTH_OUT    26
#define V_PIXEL_WIDTH_OUT    3

#define ARRAY_SIZE                2

#define NUMBER_OF_KERNEL        2

#define X_STRIDE                2
#define Y_STRIDE                2

typedef ap_fixed<166, AP_TRN, AP_WRAP> conv_type;

#endif


max_pooling.cpp を貼っておく。
(2018/04/21 : 修正、バグフィックス)
(2018/04/25 : 修正 Loop10 バグフィックス

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

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

#include "max_pooling.h"

int max_pooling(hls::stream<ap_fixed2_axis<16,6,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs){
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_fixed2_axis<16,6,1,1,1> pix;
    ap_fixed2_axis<16,6,1,1,1> mp_out;

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

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

    conv_type val[NUMBER_OF_KERNEL], conv_data;

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

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

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

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

                Loop7: for (int i=0; i<ARRAY_SIZE-1; i++){ // 以前の行のデータを line_buf から入力
                    pix_mat[n][i][ARRAY_SIZE-1] = line_buf[n][i][x];
                }
                pix_mat[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];
                        }
                    }
                }
                if (n == 0)
                    mp_out.data.data0 = val[0];
                else
                    mp_out.data.data1 = val[1];

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

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


max_pooling_tb.cpp を貼っておく。

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

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

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

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

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

int main(){
    using namespace std;

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

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

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

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

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

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

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

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

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

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

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

    // 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] << ", "
                    << mp_fout[H_PIXEL_WIDTH_OUT*y+x][1] << "}";
            if (y==V_PIXEL_WIDTH_OUT-1 && x==H_PIXEL_WIDTH_OUT-1)
                OH << endl;
            else
                OH << "," << endl;
        }
    }
    OH << "};" << endl << endl;

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

    return(0);
}    


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

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

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

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

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

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

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

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

            outs << fpix;
        }
    }

    return(0);
}

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

「Amazon EC2 F1インスタンス入門ワークショップ」に参加しました

昨日は、「Amazon EC2 F1インスタンス入門ワークショップ」に参加しました。

Xilinx社の”Amazon EC2 F1インスタンス入門ワークショップ”に参加します」であらかじめAmazon AWS のアカウントを作ってF1 インスタンスを使えるようにしておきました。

ハンズオンの項目は次の4つでした。

F1起動前準備&EC2インスタンスの起動
FPGAアクセラレーション体験
SDAccelによるF1アプリケーション開発
後⽚付けとまとめ


結構厚いF1 インスタンスのハンズオンの資料を頂きました。できれば資料のページ番号を付けてもらえれば完璧だったと思いました。
資料は最初に説明があって、次にやるべきことを丁寧に解説してあります。これがあれば自分でも一通りやってみることができます。ただミスるとずっと課金が継続されるかもしれないので、ハンズオンの参加して体験するのが良いと思います。
F1インスタンスでは、SDAccel やVivado などが使えて、実際のFPGA にビット・ファイルを流し込んで、(これはPCI Express やDDR SDRAMコントローラなどの周辺は変更しないので、パーシャルリコンフィグで流し込むそうです)アクセラレーションできるそうです。ですが、C4, M4, R4 などのFPGA 実機が使えないインスタンスでも、Amazon マシンイメージ(AMI)にFPGA Developer AMI を使用すれば、FPGA実機は使えないけど、SDAccel やVivado などが使えて開発できるそうです。ただし扱えるFPGA は、F1インスタンスで使えるUltraScale のFPGA のみだそうです。C4 でFPGA Developer AMI を使えば開発し放題かな?と内心期待しながら話を聞いていたんですが、UltraScale のFPGA のみということを聞いて、ちょっとがく。。。としました。

F1起動前準備&EC2インスタンスの起動

Security Group を作成して、インバウンドで SSH と RDP を通すように設定しました。
Key Pair を作成しました。
FPGA Developer AMI を使用し、f1.2xlarge インスタンスタイプで、作成したKey Pair を使って、バージニア北部にEC2インスタンスを作成しました。
インスタンスができたら秘密鍵を使ってSSH ログインします。Tera Term を使用しました。
SSHログイン後にスクリプトで初期設定処理を行います。
Windows のリモートデスクトップでログインします。CentOSが見えました。
Hello World を実行しました。Hello World といってもFPGAで実行した結果が見えますが、42 という文字がたくさん出てくるものでした。

FPGAアクセラレーション体験

このハンズオンではFPGAのビット・ファイルを作成するところは数時間かかるそうでやっていないのが残念ですが、FPGA にビット・ファイルをダウンロードしてCPU との性能比較を行いました。
ffmpeg を使用したHEVC encoding をCPU とFPGA アクセラレーションで性能を比較します。スピード差は約 2.08 倍だそうです。自分でやったときの値は少し違った気がしますが、近い値でした。

SDAccelによるF1アプリケーション開発

SDAccel のGUI を上げて、プロジェクトが確認できました。ただしコンパイルは時間がかかるので無しで、CPUとハードウェアのエミュレーションだけでした。プロジェクトはIDCT プロジェクトです。
あらかじめ出来上がっているビット・ファイルを使用して、性能を確認しました。

後⽚付けとまとめ
インスタンスを停止しただけではストレージ容量の課金があるそうなので、削除しないと無料にはならないようです。その方法を教えて頂きました。

バージニア北部のストレージの課金は1月当り 0.1$ / GB だそうです。100 GB使用していたので、10$ / month です。(課金されるときは秒換算で課金だそうです)
FPGA Developer AMI を使用するとやはり、ツールがあるので、最低 70 GB は使うそうです。
f1.2xlarge インスタンスは1.数ドル / hour です。でも C4 インスタンスとかは 0.4 $ / hour とかがあるので、1日 4 時間使うとなると 1.6 $ / day で、30日とすると、48 $ 。それに、200 GB 使っているとすると、20 $ / month で合計 68 $。それを 4 年間使うとなると、68 $ X 12 X 4 = 3,264 $ で 110 円 / $ とすると約 36 万円かあ~。やはり、Linux マシン買うより高いですね。これだとLinux マシン買う方が良いかな。。。
(ここでは、C4 インスタンスにSDxをインストールして、そこで、SDSoC や Vivado , Vivado HLS をリモートで使用することを考えています。つまり、Linux の Xilinx FPGA 環境をすべてAmazon AWS でやろうと考えていました。FPGAのコンフィグレーションはTCP 接続でローカル・マシンを使います)

最後にAmazon AWS の $ 25 のクーポンを頂きました。
アマゾンのサーバーに完全移行するには費用が少し高いですが、「Amazon EC2 F1インスタンス入門ワークショップ」に参加して損は無いと思います。
あと、自分で持っていない大きなUltraScale FPGA が使えるので、大きなDNNの推論エンジンを作ったときに実際にFPGA に入れて確かめられますね。これは後でやってみようかな?
  1. 2018年02月23日 06:23 |
  2. AWS-FPGA
  3. | トラックバック:0
  4. | コメント:0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Analysis 画面を示す。
relu_6_180221.png

C5 まである。

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

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

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

Analysis 画面を示す。
relu_9_180221.png

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

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

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

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

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

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

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

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

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

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed2_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data1;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int U, int TI, int TD>
    struct float2_axis{
        struct data {
            float data0;
            float data1;
        } data;
        ap_uint<1> keep;
        ap_uint<1> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

#define HORIZONTAL_PIXEL_WIDTH  52
#define VERTICAL_PIXEL_WIDTH    6

#define ARRAY_SIZE                2

#define NUMBER_OF_KERNEL        2

typedef ap_fixed<166, AP_TRN, AP_WRAP> conv_type;

#endif


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

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

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

#include "relu.h"

int relu(hls::stream<ap_fixed2_axis<16,6,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs){
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

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

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

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

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

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

            outs << pix;
        }
    }

    return(0);
}


relu_tb.cpp を貼っておく。

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

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

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

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

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

int main(){
    using namespace std;

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

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

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

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

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

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

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

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

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

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

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

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

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

    return(0);
}    


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

    float2_axis<1,1,1> fpix;

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

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

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

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

            outs << fpix;
        }
    }

    return(0);
}

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

Xilinx社の”Amazon EC2 F1インスタンス入門ワークショップ”に参加します

Connpass で募集されていたXilinx社の”Amazon EC2 F1インスタンス入門ワークショップ”に申し込みました。

今まで、やってみたかったけど、数十万円とか請求が来たらどうしよう?という懸念から申し込みをためらっていたAmazon EC2 F1インスタンスですが、入門するワークショップがあるということで、やってみることにしました。

Amazon EC2 F1インスタンスの解説方法は”Amazon EC2 F1インスタンス⼊⾨ワークショップー事前準備ー”を見ながらやっています。

アカウント作成は”AWS アカウント作成の流れ”を参照しています。
AWS のサポートプランの選択でベーシックプランの無料に申し込めました。
Amazon_AWS_F1_1_180220.png

10分チュートリアルもあるので、やってみたいです。
Amazon_AWS_F1_2_180220.png

F1 インスタンスの起動制限数は 0 だったので、制限緩和のリクエストをしました。
Amazon_AWS_F1_3_180220.png

こんな感じで制限緩和のリクエストをしたのですが、これで良いのでしょうか?
Amazon_AWS_F1_4_180220.png

どうやって制限緩和を申請するのか?も”Amazon EC2 F1インスタンス⼊⾨ワークショップー事前準備ー”のマニュアルに書いておいて欲しいです。
  1. 2018年02月20日 04:42 |
  2. AWS-FPGA
  3. | トラックバック:0
  4. | コメント:0

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

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

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

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

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

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

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

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

conv_layer_tb.c を貼っておく。

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

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

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

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

#define BMP_FILE_NAME   "straight_RED_rect0_00_rgb.bmp"

int main(){
    using namespace std;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

    return(0);
}

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

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

    float ap_uf_pix;
    float val;

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

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

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

            mbuf.shift_pixels_left();    // mbuf の列を1ビット左シフト
            for(int i=0; i<ARRAY_SIZE-1; i++){
                mbuf.insert_pixel(linebuf.getval(i,x), i, ARRAY_SIZE-1);
            }
            mbuf.insert_pixel(ap_uf_pix, ARRAY_SIZE-1, ARRAY_SIZE-1);

            // LineBuffer の更新
            linebuf.shift_pixels_up(x);
            linebuf.insert_bottom_row(ap_uf_pix, x);

            // conv_layer の演算
            for (int k=0; k<NUMBER_OF_KERNEL; k++){
                val=0.0;
                for (int j=0; j<ARRAY_SIZE; j++){
                    for (int i=0; i<ARRAY_SIZE; i++){
                        val += mbuf.getval(j,i) * conv1_fweight[k][0][j][i];
                    }
                }
                val += conv1_fbias[k];
                if(k==0)
                    conv_out.data.data0 = val;
                else
                    conv_out.data.data1 = val;
            }


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

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

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

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

金曜日にインターフェース・オフ会、土曜日にFPGAXに行ってきました

金曜日には、時間休を取って、Interface2月号特集「知っ得 世界のAI技術」のオフ会に行ってきました。
Vengineer さんの「ディープラーニングでは、エコシステムが大切よ!」と中森章さんのAIチップの話が聞けた。中森さんよくAIチップの情報をまとめたと思う。

土曜日のFPGX とても楽しめた。
特に興味を持ったのが、Halide で記述をアルゴリズムとスケジューリングに分けるというのは面白い。いつも一緒にVivado HLS で書いていたので、アルゴリズムだけは同じで、スケジューリングを書き換えて性能向上できるというのは良いね。Vivado HLS を使ってIP を吐ける(ですよね?)。というのでやってみたいと思った。聞いてみたのだが、Vivado HLS の柔軟性つまり、性能が要らないところには小さい回路でリソース使用量を抑えるというのをうまくできる仕組みがあるかどうか分からなかった。やってみたいと思った。

パソナテック 夏谷さんは、組込向けDeep Learning最新技術の紹介(量子化テクニックとDorefaNetについて)ということで、DNNのいろいろなテクニックの紹介とDorefaNet についてだった。興味深く聞いた。分かりやすかった。スライドが出たら復習したい。

An Introduction of DNN Compression Technology and Hardware Acceleration on FPGA(LeapMind 山田 貴登さん)は夏谷さんと発表がかぶっていて、慌てて直したそうだ。これもDNNのハードウェア実装についてで、興味深く聞けた。良かったと思う。

Intel HLS CompilerでSHA256アクセラレータを作ったら微妙に失敗した(日本アルテラ 竹村さん)は失敗といっていたが、成功していたみたいだ。Intel HLS CompilerのGUI は自分で適当に見繕って使うとのことだった。そうそう、Visual Studio 2010 インストールさせるのは止めてほしいと要望を伝えた。

宴会にも参加して、久しぶりに佐藤さんや、いろいろな方とお話しできて楽しかった。

私も今日はAXI4-StreamインターフェースのCNNを実装を再開したいね。現在、次のReLU を実装しようとしている。ReLU 自体の実装は簡単で畳み込み層と一緒にしたほうが本当は良いのだが、活性化関数を取り換えられるというメリットから別のIP とすることにした。現在は、畳み込み層のテストベンチを修正して、ReLU に入力を与えられるようにデータを記述した C のヘッダファイルを出力するように修正している。

「FPGAエクストリーム・コンピューティング 第10回」の資料へのリンクです。
  1. 2018年02月18日 05:57 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

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

AXI4-Stream インターフェースの畳み込み層3(C ソースコード)”の続き。

前回は、すべてのC ソースコードとBMP 画像ファイルを貼ったので、手元でも確認できる様になったと思う。今回は、畳み込み層のC/RTL 協調シミュレーションとExport RTLを行う。

さて、早速 C/RTL 協調シミュレーションを行った。結果を示す。
conv_layer_13_180215.png
Latency は 604 クロックだった。

C/RTL 協調シミュレーション波形を示す。
まずは全体波形から。
conv_layer_14_180215.png

入力はins_TVALID はずっと 1 のままで、ins_TREADY は最初に少し 0 になるときもあるが、その後は 1 のままとなっていて、スループットが取れていることが分かる。
out_TVALID が時々 0 になるのは、行の初めで出力できない、列のインデックスが 3 以下の時だと思う。その後は、ずっと 1 なので問題ないだろう。

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

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

C コードの合成結果と比べると、DSP48E の 23 個は変化が無いが、FF は合成時に 2745 個だったのが、Export RTL では 962 個に減少した。LUT は 2786 個だったのが、635 個に減少した。
  1. 2018年02月16日 05:14 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースの畳み込み層3(C ソースコード)

AXI4-Stream インターフェースの畳み込み層2(C シミュレーション)”の続き。

前回はAXI4-Stream 版の畳み込み層のC シミュレーションを行った。今回は、そのソースコードを貼っておく。

まずは、畳み込み層のカーネルの値を記述した conv1_weight.h を貼っておく。

// conv1_weight.h
// 2017/12/06 10:54:11 by marsee

const float conv1_fweight[2][1][5][5] = 
{
    {
        {
            {0.764403421227,0.658424746889,0.595604201652,0.554044871161,0.367767232883},
            {0.582414155838,0.413274869036,0.31659268154,0.3508390519,0.331194144626},
            {0.589182274309,0.462105790282,-0.241299390378,-0.10093021104,0.233291757594},
            {0.792411286764,0.315893121865,0.0397628864727,0.356726636694,0.426826537165},
            {0.634481192118,0.651475977113,0.688949928547,0.707285991358,0.681420943406}
        }
    }
    ,
    {
        {
            {0.00564732125401,-0.012955272371,-0.0231571581103,-0.00289983746176,0.0281080593816},
            {-0.0115360072012,0.00253310449813,-0.00860163957467,0.00112793810127,-0.01455040341},
            {-0.00881717612899,-0.00902248113722,0.0004194288468,0.00110240651437,-0.0140454059394},
            {0.00271556513713,-0.00307791921855,0.000117170379207,-0.00891721414879,0.0173026634286},
            {0.000808453898046,0.000116327205532,-0.00275343050716,-0.00683461392689,-0.0169130858704}
        }
    }
};

const ap_fixed<91, AP_TRN, AP_WRAP> conv1_weight[2][1][5][5] =
{
    {
        {
            {0.765625,0.66015625,0.59375,0.5546875,0.3671875},
            {0.58203125,0.4140625,0.31640625,0.3515625,0.33203125},
            {0.58984375,0.4609375,-0.23828125,-0.09765625,0.234375},
            {0.79296875,0.31640625,0.0390625,0.35546875,0.42578125},
            {0.6328125,0.65234375,0.6875,0.70703125,0.6796875}
        }
    }
    ,
    {
        {
            {0.00390625,-0.0078125,-0.01953125,0.0,0.02734375},
            {-0.0078125,0.00390625,-0.00390625,0.0,-0.01171875},
            {-0.00390625,-0.00390625,0.0,0.0,-0.01171875},
            {0.00390625,0.0,0.0,-0.00390625,0.015625},
            {0.0,0.0,0.0,-0.00390625,-0.01171875}
        }
    }
};


次に、畳み込み層のバイアス値を記述した conv1_bias.h を示す。

// conv1_bias.h
// 2017/12/06 10:54:20 by marsee

const float conv1_fbias[2] = {
    -2.37814890843, -0.00283377712987
};

const ap_fixed<91, AP_TRN, AP_WRAP> conv1_bias[2] = {
    -1.00.0
};


BMP 画像のヘッダの構造を記述した bmp_header.h は”Vivado HLSで stdint.h を使用する”に貼ってあるので、そちらを参照のこと。

最後にテストベンチの conv_layer_tb.cpp を貼っておく。
(2018/02/19 : 修正)
(2018/04/17 : 修正)

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

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

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

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

#define BMP_FILE_NAME   "straight_RED_rect0_00_rgb.bmp"

int main(){
    using namespace std;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

    return(0);
}

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

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

    float ap_uf_pix;
    float val;

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

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

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

            mbuf.shift_pixels_left();    // mbuf の列を1ビット左シフト
            for(int i=0; i<ARRAY_SIZE-1; i++){
                mbuf.insert_pixel(linebuf.getval(i,x), i, ARRAY_SIZE-1);
            }
            mbuf.insert_pixel(ap_uf_pix, ARRAY_SIZE-1, ARRAY_SIZE-1);

            // LineBuffer の更新
            linebuf.shift_pixels_up(x);
            linebuf.insert_bottom_row(ap_uf_pix, x);

            // conv_layer の演算
            for (int k=0; k<NUMBER_OF_KERNEL; k++){
                val=0.0;
                for (int j=0; j<ARRAY_SIZE; j++){
                    for (int i=0; i<ARRAY_SIZE; i++){
                        val += mbuf.getval(j,i) * conv1_fweight[k][0][j][i];
                    }
                }
                val += conv1_fbias[k];
                if(k==0)
                    conv_out.data.data0 = val;
                else
                    conv_out.data.data1 = val;
            }


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

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

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


最後にPNG にする必要があったが、straight_RED_rect0_00_rgb.bmp を貼っておく。
conv_layer_17_180215.png
  1. 2018年02月15日 04:29 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースの畳み込み層2(C シミュレーション)

AXI4-Stream インターフェースの畳み込み層1(C コードの合成)”の続き。

前回は、2 つの実装の畳み込み層のC ソースコードを書いて、C コードの合成を行った。今回は、テストベンチを書いたので、C シミュレーションを行った。

まずは、ハードウェア化する C ソースコードとして、hls::LineBuffer, hls::Window を使用した conv_layer.cpp を使用した。これは意味はないが、たまたまこのコードだっただけの話だ。
さて、テストベンチは、straight_RED_rect0_00_rgb.bmp という白線の白黒画像を読み込んで、畳み込みを行う。straight_RED_rect0_00_rgb.bmp は straight_RED_rect0_00.bmp のグレースケールのBMP 画像をRGB に変更した画像で、サイズは 56 x 10 ピクセルだ。
straight_RED_rect0_00_rgb.bmp を示す。
conv_layer_11_180215.png

テストベンチは、ハードウェア化する畳み込みを行う関数とソフトウェアで float 型で演算する畳み込みを行う関数を 2 つ呼び出して、その2 つの結果を比較し、その結果を出力する。そして、ある一定以上の違いがあったらエラーを出す。
次に、ハードウェアとソフトウェアの畳み込みを行う2 つの関数の出力を適当に処理して、BMP 画像にする。ハードウェアの畳み込みを行う関数のデータから出力したBMP 画像が temp_conv0.bmp と temp_conv1.bmp だ。そして、ソフトウェアの畳み込みを行う関数のデータから出力したBMP 画像が temp_conv_float0.bmp と temp_conv_float1.bmp になる。どちらも、 2 個のカーネルがあるため、2 つの画像を出力している。個の画像のサイズは、5x5 のカーネルを使用して、パッデング 0 、ストライド 1 なので、縦横とも 4 ピクセル減少して、52 x 6 ピクセルになっている。
さて、現在のVivado HLS 2017.4 の conv_layer プロジェクトを示す。
conv_layer_5_180215.png

これで C シミュレーションを行った。結果を示す。
conv_layer_6_180215.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../conv_layer_tb.cpp in debug mode
Compiling ../../../conv_layer.cpp in debug mode
Generating csim.exe

outs
HW and SW results i = 0, j = 0, HW = 4.335938, -0.017578, SW = 2.956438, -0.043737
HW and SW results i = 1, j = 0, HW = 4.266602, -0.016602, SW = 2.887825, -0.041697
HW and SW results i = 2, j = 0, HW = 4.193359, -0.016602, SW = 2.813908, -0.042108
HW and SW results i = 3, j = 0, HW = 4.140625, -0.016602, SW = 2.762030, -0.041928
HW and SW results i = 4, j = 0, HW = 4.064453, -0.015625, SW = 2.685595, -0.039677
HW and SW results i = 5, j = 0, HW = 4.011719, -0.014648, SW = 2.633010, -0.038917
HW and SW results i = 6, j = 0, HW = 3.991211, -0.014648, SW = 2.612745, -0.038658
HW and SW results i = 7, j = 0, HW = 4.005859, -0.013672, SW = 2.628183, -0.038179
HW and SW results i = 8, j = 0, HW = 4.014648, -0.013672, SW = 2.636920, -0.037909
HW and SW results i = 9, j = 0, HW = 4.036133, -0.013672, SW = 2.658426, -0.037599
HW and SW results i = 10, j = 0, HW = 4.024414, -0.013672, SW = 2.647083, -0.037274
HW and SW results i = 11, j = 0, HW = 4.015625, -0.013672, SW = 2.637882, -0.037213
HW and SW results i = 12, j = 0, HW = 4.033203, -0.012695, SW = 2.655350, -0.036282
HW and SW results i = 13, j = 0, HW = 4.222656, -0.016602, SW = 2.845468, -0.041951
HW and SW results i = 14, j = 0, HW = 4.448242, -0.017578, SW = 3.070912, -0.042820
HW and SW results i = 15, j = 0, HW = 4.807617, -0.013672, SW = 3.429559, -0.040354
HW and SW results i = 16, j = 0, HW = 5.202148, -0.019531, SW = 3.824572, -0.048534
HW and SW results i = 17, j = 0, HW = 5.367188, -0.020508, SW = 3.988451, -0.048833
HW and SW results i = 18, j = 0, HW = 5.325195, -0.026367, SW = 3.945544, -0.056148
HW and SW results i = 19, j = 0, HW = 5.275391, -0.022461, SW = 3.894094, -0.050489
HW and SW results i = 20, j = 0, HW = 5.178711, -0.021484, SW = 3.798736, -0.050703
HW and SW results i = 21, j = 0, HW = 5.010742, -0.020508, SW = 3.631046, -0.050160
HW and SW results i = 22, j = 0, HW = 4.715820, -0.019531, SW = 3.336445, -0.048055
HW and SW results i = 23, j = 0, HW = 4.457031, -0.019531, SW = 3.077446, -0.046769
HW and SW results i = 24, j = 0, HW = 4.337891, -0.018555, SW = 2.958363, -0.044824
HW and SW results i = 25, j = 0, HW = 4.344727, -0.019531, SW = 2.965589, -0.045536
HW and SW results i = 26, j = 0, HW = 4.338867, -0.018555, SW = 2.959619, -0.044858
HW and SW results i = 27, j = 0, HW = 4.303711, -0.018555, SW = 2.924359, -0.044479
HW and SW results i = 28, j = 0, HW = 4.276367, -0.018555, SW = 2.897588, -0.044321
HW and SW results i = 29, j = 0, HW = 4.258789, -0.017578, SW = 2.879802, -0.043693
HW and SW results i = 30, j = 0, HW = 4.239258, -0.018555, SW = 2.860393, -0.044097
HW and SW results i = 31, j = 0, HW = 4.186523, -0.018555, SW = 2.806980, -0.043727
HW and SW results i = 32, j = 0, HW = 4.125000, -0.018555, SW = 2.745974, -0.043355
HW and SW results i = 33, j = 0, HW = 4.058594, -0.017578, SW = 2.679494, -0.042717
HW and SW results i = 34, j = 0, HW = 3.978516, -0.018555, SW = 2.599331, -0.042859
HW and SW results i = 35, j = 0, HW = 3.731445, -0.018555, SW = 2.351973, -0.041944
HW and SW results i = 36, j = 0, HW = 3.646484, -0.018555, SW = 2.267337, -0.041979
HW and SW results i = 37, j = 0, HW = 3.750977, -0.018555, SW = 2.371555, -0.041249
HW and SW results i = 38, j = 0, HW = 3.694336, -0.015625, SW = 2.314963, -0.038042
HW and SW results i = 39, j = 0, HW = 3.620117, -0.015625, SW = 2.241181, -0.040116
HW and SW results i = 40, j = 0, HW = 4.099609, -0.017578, SW = 2.721145, -0.044066
HW and SW results i = 41, j = 0, HW = 4.369141, -0.022461, SW = 2.991379, -0.051047
HW and SW results i = 42, j = 0, HW = 4.442383, -0.018555, SW = 3.064429, -0.045425
HW and SW results i = 43, j = 0, HW = 4.265625, -0.014648, SW = 2.887242, -0.039155
HW and SW results i = 44, j = 0, HW = 3.979492, -0.012695, SW = 2.601302, -0.034550
HW and SW results i = 45, j = 0, HW = 3.593750, -0.013672, SW = 2.215396, -0.034609
HW and SW results i = 46, j = 0, HW = 3.423828, -0.013672, SW = 2.046252, -0.034175
HW and SW results i = 47, j = 0, HW = 3.249023, -0.013672, SW = 1.870888, -0.033923
HW and SW results i = 48, j = 0, HW = 3.207031, -0.013672, SW = 1.828757, -0.033767
HW and SW results i = 49, j = 0, HW = 3.158203, -0.012695, SW = 1.780369, -0.033276
HW and SW results i = 50, j = 0, HW = 3.140625, -0.012695, SW = 1.762675, -0.032938
HW and SW results i = 51, j = 0, HW = 3.123047, -0.012695, SW = 1.745412, -0.033239
HW and SW results i = 0, j = 1, HW = 4.738281, -0.021484, SW = 3.359004, -0.048814
HW and SW results i = 1, j = 1, HW = 4.651367, -0.021484, SW = 3.271967, -0.049351
HW and SW results i = 2, j = 1, HW = 4.590820, -0.021484, SW = 3.210930, -0.049440
HW and SW results i = 3, j = 1, HW = 4.591797, -0.021484, SW = 3.212094, -0.048609

中略

Success HW and SW results match

INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


i = 0, j = 0 の時の1 個目のカーネルのHW の数値は 4.335938 で、SW の数値は 2.956438 なので、だいぶ違っているという見方もあるだろう?それは、バイアス値のためだということが分かった。バイアス値を下に示す。
conv_layer_12_180215.png

ソフトウェアで使用する covn1_fbias[0] の値の -2.37814890843 に比べて、ハードウェアで使用する量子化された conv1_bias[0] が -1 であるのが分かると思う。それは、量子化のビット数が全体で 9 ビット、整数部が 1 ビットだからである。つまり整数部の 1 ビットは符号であるので、実質 -1 が最小の数となるためである。この量子化でも畳み込みニューラルネットワークの精度にはさほどの影響を及ぼさないという結論になったためだ。
上の i = 0, j = 0 の時の1 個目のカーネルのHW の数値は 4.335938 だったが、これにソフトウェアで使用する covn1_fbias[0] の値の -2.37814890843 とハードウェアで使用する量子化された conv1_bias[0] の -1 の差分の 1.37814890843 を引いてみると、2.95778909157 となって、SW の数値の 2.956438 とほぼ等しくなることが分かる。

さて、次に、 temp_conv0.bmp と temp_conv1.bmp 、 temp_conv_float0.bmp と temp_conv_float1.bmp を見ていこう。
temp_conv0.bmp を示す。
(2018/02/19 : 修正)
conv_layer_23_180219.png 

temp_conv1.bmp を示す。
(2018/02/19 : 修正)
conv_layer_24_180219.png 

temp_conv_float0.bmp を示す。
(2018/02/19 : 修正)
conv_layer_25_180219.png 

temp_conv_float1.bmp を示す。
(2018/02/19 : 修正)
conv_layer_26_180219.png 

ほとんどハードウェアとソフトウェアの違いは良く分からない?データの処理方法を失敗したかもしれない?

テストベンチのソースコードはこの記事が長くなったので、次の記事に貼っておく。
  1. 2018年02月15日 04:18 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェースの畳み込み層1(C コードの合成)

Vivado HLSでのAXI4-Stream のテンプレートを作成する2”でAXI4-Stream インターフェースのテンプレートが決定できたので、いよいよ畳み込み層を作ってみよう。畳み込み層は今まで作ってきた画像フィルタと何ら変わるところはない。

畳み込み層のVivado HLS 2017.4 プロジェクトの conv_layer プロジェクトを示す。
だけど、まだテストベンチが完成していないので、ハードウェア化する conv_layter() 関数が正しいかどうか?はまだ分からない?
そして、conv_layer.cpp は 2 つの方法で作成した。1 つは、2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装で、もう1つはVivado HLS のHLS ビデオライブラリの hls::LineBuffer と hls::Window を使用した実装だ。この2つの畳み込み層の実装を比べてみよう。2つともそれぞれ、ラインバッファと入力データ・ウインドウ以外は同じ実装になっている。
まずは、2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装から見てみる。
conv_layer_1_180214.png

共通に使用する conv_layer.h から示す。

// conv_layter.h
// 2018/02/06 by marsee
//

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

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed1_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed2_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data1;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int U, int TI, int TD>
    struct float2_axis{
        struct data {
            float data0;
            float data1;
        } data;
        ap_uint<1> keep;
        ap_uint<1> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

#define HORIZONTAL_PIXEL_WIDTH  56
#define VERTICAL_PIXEL_WIDTH    10

#define ARRAY_SIZE                5

#define NUMBER_OF_KERNEL        2

typedef ap_ufixed<80, AP_TRN, AP_WRAP> in_type;
typedef ap_fixed<226, AP_TRN, AP_WRAP> val_type;
typedef ap_fixed<166, AP_TRN, AP_WRAP> out_type;

#endif


2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装の conv_layer.cpp を示す。

// conv_layer.cpp (line_buf, pix_mat)
// 2018/02/06 by marsee
//

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

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

int conv_layer(hls::stream<ap_axiu<32,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs){
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axiu<32,1,1,1> pix;
    ap_fixed2_axis<16,6,1,1,1> conv_out;

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

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

    in_type ap_uf_pix;
    val_type val;

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

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

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

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

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

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

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


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

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

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


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

Latency は 577 クロックだった。処理する画像は 560 ピクセルなので、約 1.02 クロック/ピクセルとなった。性能的には問題ないと思う。
リソース使用量は DSP48E を 23 個、FF を 2745 個、LUT を 2786 個使用している。問題ないリソース使用量だと思う。

次に、Vivado HLS のHLS ビデオライブラリの hls::LineBuffer と hls::Window を使用した実装の conv_layer.cpp を示す。
conv_layer_3_180214.png

// conv_layer.cpp (hls::LineBuffer, hls::Window)
// 2018/02/06 by marsee
//

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

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

int conv_layer(hls::stream<ap_axiu<32,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs){
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axiu<32,1,1,1> pix;
    ap_fixed2_axis<16,6,1,1,1> conv_out;

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

    in_type ap_uf_pix;
    val_type val;

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

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

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

            mbuf.shift_pixels_left();    // mbuf の列を1ビット左シフト
            for(int i=0; i<ARRAY_SIZE-1; i++){
                mbuf.insert_pixel(linebuf.getval(i,x), i, ARRAY_SIZE-1);
            }
            mbuf.insert_pixel(ap_uf_pix, ARRAY_SIZE-1, ARRAY_SIZE-1);

            // LineBuffer の更新
            linebuf.shift_pixels_up(x);
            linebuf.insert_bottom_row(ap_uf_pix, x);

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


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

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

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



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

なんと、前の2次元配列のラインバッファと2次元配列の入力データ・ウインドウを使用した実装と全く一緒の実装だった。。。
  1. 2018年02月14日 06:49 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

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

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

前回は、Vivado HLSでAXI4-Stream のデータ幅を拡張するテンプレートを作成して合成してみたが、テンプレートのデータに配列を使ったので、合成できなかった。今回は、データを1次元配列にしてみよう。

(2018/02/12:書き換え)ブログを struct を使用した記述に書き換えました。@ciniml さん、教えて頂いてありがとうございました。

struct で 2 つ要素をまとめました。struct の中を本当は配列にしたかったのですが、配列だとAXI4-Stream でエラーになりました。修正したテンプレートの定義を含んだ stream_test.h を示す。

// stream_test.h
// 2018/02/11 by marsee
//

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

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed1_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed2_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data1;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int W, int I, int U, int TI, int TD>
    struct ap_fixed4_axis{
        struct data {
            ap_fixed<W,I,AP_TRN,AP_WRAP> data0;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data1;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data2;
            ap_fixed<W,I,AP_TRN,AP_WRAP> data3;
        } data;
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

#endif


修正した stream_test.cpp を示す。

// stream_test.cpp
// 2018/02/11 by marsee
//

#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_fixed1_axis<16,6,1,1,1> >& ins,
        hls::stream<ap_fixed2_axis<16,6,1,1,1> >& outs){
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=return
    ap_fixed1_axis<16,6,1,1,1> ins_t;
    ap_fixed2_axis<16,6,1,1,1> outs_t;

    for(int y=0; y<10; y++){
        for(int x=0; x<56; x++){
            ins >> ins_t;
            outs_t.data.data0 = ins_t.data.data0 * (ap_fixed<166, AP_TRN, AP_WRAP>)2.0;
            outs_t.data.data1 = ins_t.data.data0 * (ap_fixed<166, AP_TRN, AP_WRAP>)(-3.0);

            outs_t.user = 1;
            outs_t.last = 0;

            outs << outs_t;
        }
    }

    return(0);
}


今回、テストベンチを作成した。テストベンチの multi_test_tb.cpp を示す。

// stream_test_tb.cpp
// 2018/02/11 by marsee
//

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

#include "stream_test.h"

#define DATASIZE    560

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

int main(){
    using namespace std;

    ap_fixed1_axis<16,6,1,1,1> in_ts;
    ap_fixed2_axis<16,6,1,1,1> out_ts;

    hls::stream<ap_fixed1_axis<16,6,1,1,1> > instream;
    hls::stream<ap_fixed2_axis<16,6,1,1,1> > outstream;

    for(int i=0; i<DATASIZE; i++){
        in_ts.data.data0 = i % 11;
        instream << in_ts;
    }

    stream_test(instream, outstream);

    for(int i=0; i<DATASIZE; i++){
        outstream >> out_ts;
        printf("i = %d, data1 = %f, data0 = %f\n", i, (float)out_ts.data.data1, (float)out_ts.data.data0);
    }

    return(0);
}


テストベンチができたので、シミュレーションをすることができる。
早速、C シミュレーションを行った。
stream_test_3_180212.png

2 つのデータが、うまく分離できているようだ。

さて、C コードの合成を行った。今度は成功。良かった。
stream_test_4_180212.png

FF と LUT も使われていて大丈夫そうだ。

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

エラーで停止してしまった。エラー内容を示す。

F:/Xilinx/Vivado/2017.4/include/ap_stream.h:70:2: warning: #warning AP_STREAM macros are deprecated. Please use hls::stream<> from "hls_stream.h" instead. [-Wcpp]
apatb_stream_test.cpp: In function 'int AESL_WRAP_stream_test(hls::stream<ap_fixed1_axis<16, 6, 1, 1, 1> >&, hls::stream<ap_fixed2_axis<16, 6, 1, 1, 1> >&)':
apatb_stream_test.cpp:425:36: error: 'data' has no member named 'data1'
apatb_stream_test.cpp:478:36: error: 'data' has no member named 'data1'
apatb_stream_test.cpp:480:34: error: 'data' has no member named 'data1'
apatb_stream_test.cpp:2330:34: error: 'data' has no member named 'data1'
apatb_stream_test.cpp:2333:63: error: 'data' has no member named 'data1'
make: *** [obj/apatb_stream_test.o] Error 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 C:/Users/Masaaki/Documents/VIvado_HLS/ZYBO_Z7-20/test/stream_test/solution1/cosim.tcl"
    invoked from within
"hls::main C:/Users/Masaaki/Documents/VIvado_HLS/ZYBO_Z7-20/test/stream_test/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

.
通常のAXI4-Stream のテンプレートを使っていなかったためか?でも、修正前は動作したのだが。。。

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

ちゃんと、FF も LUT もあるので、問題ないだろう?
  1. 2018年02月12日 05:16 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

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

これから作る畳み込みニューラルネットワークについての目標2”で構想したAXI4-Stream で接続する畳み込みニューラルネットワークを作成するためには、ストリームのデータ幅を拡張する必要がある。そのため、Vivado HLSでAXI4-Stream のデータ幅を拡張するテンプレートを作成してC コードの合成を行ってみよう。

Vivado HLS 2017.4 で stream_test プロジェクトを作成した。
stream_test_1_180211.png

stream_test.h を示す。

// stream_test.h
// 2018/02/11 by marsee
//

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

template<int W, int I, int N, int U, int TI, int TD>
    struct ap_fixed_axis{
        ap_fixed<W, I, AP_TRN, AP_WRAP> data[N];
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

template<int W, int I, int N, int U, int TI, int TD>
    struct ap_ufixed_axis{
        ap_ufixed<W, I, AP_TRN, AP_WRAP> data[N];
        ap_uint<(W+7)/8> keep;
        ap_uint<(W+7)/8> strb;
        ap_uint<U>       user;
        ap_uint<1>       last;
        ap_uint<TI>      id;
        ap_uint<TD>      dest;
    };

#endif


データの配列 data[N] を定義して、畳み込みフィルタ数分のデータ幅のストリームを行うつもりだ。データの配列は任意精度固定小数点データ型とした。

stream_test.cpp を示す。

// stream_test.cpp
// 2018/02/11 by marsee
//

#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){
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=return
    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++){
            ins >> ins_t;
            outs_t.data[0] = ins_t.data[0] * (ap_fixed<166, AP_TRN, AP_WRAP>)2.0;
            outs_t.data[1] = ins_t.data[1] * (ap_fixed<166, AP_TRN, AP_WRAP>)3.0;

            outs_t.user = 1;
            outs_t.last = 0;

            outs << outs_t;
        }
    }

    return(0);
}



これで、C コードの合成を行ったところエラーが発生した。
stream_test_2_180211.png

ERROR: [XFORM 203-103] Cannot partition array 'ins.V.data.V' (stream_test/stream_test.cpp:12): different array partition directive on the same group of AXI-Stream ports.
ERROR: [HLS 200-70] Pre-synthesis failed.
command 'ap_source' returned error code
    while executing
"source C:/Users/Masaaki/Documents/VIvado_HLS/ZYBO_Z7-20/test/stream_test/solution1/csynth.tcl"
    invoked from within
"hls::main C:/Users/Masaaki/Documents/VIvado_HLS/ZYBO_Z7-20/test/stream_test/solution1/csynth.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 synthesis.


原因を検索してみると、Xilinx フォーラムの”AXI-Stream interface with data array side-channel does not build”が見つかった。
それによると配列じゃなくてフラットに 1 次元で書けということだ。

任意精度固定小数点データ型なので、どうやって 1 次元で書くかという問題はあるが、やり方はあると思う。次はそれを探っていこう。
  1. 2018年02月11日 08:57 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLSで関数内のBRAMを関数外から制御する2(C++のクラスを使って書いてみた)

Vivado HLSで関数内のBRAMを関数外から制御する1”の続き。

前回は、Vivado HLS 勉強会で一緒だった学生さんからVivado HLSの関数内で宣言したBRAM を外から読み書きしたいのだけど、どう書いたら良いか?という質問があったので、C 言語でサンプルコードを書いてみた。今回は、C++ のクラスを使って、サンプルコードを書いてみた。

まずは、C++ のクラスを使用して書いた、Vivado HLSの関数内で宣言したBRAM を外から読み書きするサンプルコードの bram_test2.cpp を示す。

// bram_test2.cpp
// 2018/02/06 by marsee
//

class bram {
    int array[1024];
public:
    void bram_write(int &index, int &data);
    void bram_read(int &index, int &data);
};

void bram::bram_write(int &index, int &data){
    array[index] = data;
}

void bram::bram_read(int &index, int &data){
    data = array[index];
}

int bram_test(int &index, int &wr, int &data){
#pragma HLS INTERFACE s_axilite port=data
#pragma HLS INTERFACE s_axilite port=wr
#pragma HLS INTERFACE s_axilite port=index
#pragma HLS INTERFACE s_axilite port=return

    static bram bram_inst;

    if(wr == 0){ // Read
        bram_inst.bram_read(index, data);
    }else// Write
        bram_inst.bram_write(index, data);
    }

    return(0);
}


なお、テストベンチは”Vivado HLSで関数内のBRAMを関数外から制御する1”の bram_test1_tb.cpp のままだ。

Vivado HLS 2017.4 で bram_test2 プロジェクトを作成した。
bram_test_15_180207.png

C シミュレーションを行った。結果を示す。前回同様だ。
bram_test_16_180207.png

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

前回と比較すると LUT が 15 個多い。その他は同じだ。

C/RTL 協調シミュレーションを行ったが、前回同様に終了しない。強制的にストップした。

C/RTL 協調シミュレーションの波形を示す。やはり、WDATAなどが表示されない。
bram_test_18_180207.png

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

IP 化はできている。

ここで前回土曜に指示子をコメントアウトして、デフォルトのI/Oプロトコルとブロックレベルのプロトコルにしてみよう。
bram_test_20_180208.png

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

このリソース使用量は前回と一致している。

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

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

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

これも前回と同様のリソース使用量となった。
  1. 2018年02月10日 21:01 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

第19回分子科学研究所技術研究会に行ってきました

第19回分子科学研究所技術研究会で発表と討論をやってきました。
その際の発表資料を公開します。
FPGAでの非同期信号の扱い方とVivadoによるサポート(公開用)」です。
ご意見待っています。意味わかんない。。。とか言って叩かないでください。。。w

2018/02/10:修正 リファレンスを修正して再度公開しました。
  1. 2018年02月09日 19:29 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

これから作る畳み込みニューラルネットワークについての目標2

これから作る畳み込みニューラルネットワークについての目標”の続き。

AXI4-Stream 対応のラプラシアンフィルタの様に畳み込みニューラルネットワークを作る予定だ。
畳み込み層はそのままフィルタなので、ラプラシアンフィルタなどの構造を元に作ることができると思う。
それに各層、畳み込み層やReLU、マックス・プーリング層、全結合層などをAXI4-Stream インターフェースで接続されるIPとしてVivado のIP インテグレータで接続すればよいのではないだろうか?もし、1 個のFPGA でロジックが足りなければ、AXI4-Stream インターフェースを外に出して他のFPGA に接続すれば良いのでは?と思う。

つまりこんなイメージだ。
AXI4-Stream_CNN_1_180207.png

ストリーム・データをIP を接続して処理させるのはFPGAの得意とするところだし、一番利点が生きる構成だ。これを使わない手は無いと思う。当然複雑なCNN はFPGAのリソースが足りなくてできないのだが、今度はストリームされる画像データ分だけの演算器があれば良いし、(1 クロックで処理できるとしてだが。。。)何とかなるだろう?

2つ目の全結合層では、1つ目の全結合層の演算がすべて終わるまで 1 個のデータも入ってこない。そこがボトルネックだが仕方がない。そので1フレームずれるのは仕方がないか。。。
  1. 2018年02月07日 21:07 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

Vivado HLSで関数内のBRAMを関数外から制御する1

この前、Vivado HLS 勉強会で一緒だった学生さんからVivado HLSの関数内で宣言したBRAM を外から読み書きしたいのだけど、どう書いたら良いか?という質問があったので、サンプルコードを書いてみた。

bram_test1.cpp を示す。

// bram_test1.cpp
// 2018/02/06 by marsee
//

int bram_test(int &index, int &wr, int &data){
#pragma HLS INTERFACE s_axilite port=data
#pragma HLS INTERFACE s_axilite port=wr
#pragma HLS INTERFACE s_axilite port=index
#pragma HLS INTERFACE s_axilite port=return
    int array[1024];

    if(wr == 0){ // Read
        data = array[index];
    }else// Write
        array[index] = data;
    }

    return(0);
}


テストベンチの bram_test1_tb.cpp を示す。Read してWrite してRead というだけの単純なテストベンチだ。

// bram_test1_tb.cpp
// 2018/02/06 by marsee
//

#include <stdio.h>

int bram_test(int &index, int &wr, int &data);

int main(){
    int index, wr, data;

    wr = 0; index = 0;
    bram_test(index, wr, data);
    printf("data = %x\n", data);

    wr = 1; index = 0; data = 0x1;
    bram_test(index, wr, data);

    wr = 0; index = 0;
    bram_test(index, wr, data);
    printf("data = %x\n", data);

    wr = 0; index = 1;
    bram_test(index, wr, data);
    printf("data = %x\n", data);

    wr = 1; index = 1; data = 0x2;
    bram_test(index, wr, data);

    wr = 0; index = 1;
    bram_test(index, wr, data);
    printf("data = %x\n", data);

    return(0);
}


Vivado HLS 2017.4 で bram_test1 というプロジェクトを作成した。ターゲットはZYBO Z7-20 だ。
bram_test_1_180207.png

C シミュレーションを行った。配列のイニシャライズはされていないが、書き込んだ後は同じ値が読めている。
bram_test_2_180207.png

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

BRAM_18K の使用量は 0 個だった。BRAM が実装されていない。
やはり、これは、C 言語では、関数内の宣言したローカル変数の配列はスタック領域にマップされて、関数から戻るときはクリアされる仕様が問題なんじゃないかな?ということで、static を配列の宣言に追加することにした。こうすれば、関数を抜けても配列は保持される。
bram_test_4_180207.png

// bram_test1.cpp
// 2018/02/06 by marsee
//

int bram_test(int &index, int &wr, int &data){
#pragma HLS INTERFACE s_axilite port=data
#pragma HLS INTERFACE s_axilite port=wr
#pragma HLS INTERFACE s_axilite port=index
#pragma HLS INTERFACE s_axilite port=return
    static int array[1024];

    if(wr == 0){ // Read
        data = array[index];
    }else// Write
        array[index] = data;
    }

    return(0);
}


これで、C シミュレーションを行った。結果を示す。
bram_test_5_180207.png

今度は配列がイニシャライズされていた。Read、Write も問題ない。

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

BRAM_18K が 2 個実装されている。良さそうだ。

C/RTL 協調シミュレーションを行うと、終わらなかった。
なので、テストベンチをRead、Write 1 個のみにして、C/RTL 協調シミュレーションを行った。
bram_test_7_180207.png

でもやはり、終わらない。
bram_test_8_180207.png

途中で止めて波形を見てみよう。C/RTL 協調シミュレーションの波形を拡大してみてみよう。まずは、WVALID などのWrite の信号が表示されていない。これは、途中でC/RTL 協調シミュレーションを止めてしまったからなのか?
bram_test_14_180207.png

それじゃ、AXI4 Lite Slave インターフェースの予定だが、デフォルトでやってみよう。AXI4 Lite のインターフェースの指示子を取ってしまった。
bram_test_9_180207.png

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

FF、LUT は使用量が少なくなったが、BRAM_18K は 2 個使用されていて問題ないようだ。

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

正常終了している。やはり、AXI4 Lite インターフェースの時だけおかしいのかな?

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

正常にアクセスできているようだ。

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

BRAM も 2 個使われていて、問題なさそうだ。
  1. 2018年02月07日 05:06 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討9(畳み込み演算6)

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討8(畳み込み演算5)”の続き。

前回は、2 つの畳み込み演算を並列に行うようにC ソースコードを書き換えたのだが、積和演算はLUT だけを使用した演算だった。さて、なぜLUT を使った演算にしてみたかというと、畳み込みニューラルネットワークを1クロックで演算できるようにするためにはDSP48E を使用すると足りなくなるという推測に基づいていた。もう 1 クロック動作はあきらめているので、DSP48E を使用しても問題は無さそうだ。ということで、DSP48E を使用する通常の積和演算をやってみた。

multi_test4.cpp で

#pragma HLS RESOURCE variable=out_temp core=AddSub

をコメントアウトした。
multi_test_66_180206.png

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

Estimated は 10.68 ns で Target の 10 ns を超えてしまっているが、Latency は 2 クロックで、これは短い。
リソース使用量は、DSP48E を使用され、14 個、FF は 350 個、LUT は 1,304 個使用している。
Latency が 2 クロックは短いな。。。

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

やはり Latency は 2 クロックだった。

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

2 つの畳み込み演算器がデータを受け取ってから、演算を行い、出力の値をその先の回路が受け取るまでに 2 クロックだった。

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

LUT は 392 個、FF が 179 個、何故か?DSP が 23 個だった?
インプリメント後の遅延時間は 9.404 ns でギリギリだが、Vivado で合成してもうまく行くかもしれない?

さて、Estimated は 10.68 ns なので、10 ns 以下にしようとして、Uncertainty を 3 ns にした。
その合成結果を示す。
multi_test_71_180206.png

Latency は 3 クロックに増加した。
リソース使用量は、DSP48E が 14 個で変わらなかった。FF は 600 個で 350 個から増えている。LUT は1,304 個で変わらなかった。

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

LUT は 578 個で、392 個から増えていた。FF は 256 個で、これも179 個から増えた。DSP48E は 14 個でこれは合成の時と同じ数だ。DSP48E は本来合成の時と同じ数のはずだ。なぜ、前は増えたのだろうか?
  1. 2018年02月06日 05:03 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討8(畳み込み演算5)

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討7(畳み込み演算4)”の続き。

前回は、2 個目の畳み込み演算の重みの配列を使用して違いを確認した。今回は、2 つの畳み込み演算を並列にやってみよう。

2 つの畳み込み演算を並列に行うためにC ソースコードを少し変更した。プロジェクトも新しく作成した。
multi_test4 プロジェクトを示す。
multi_test_60_180205.png

multi_test4.cpp を示す。

// multi_test4.cpp
// 2018/02/04 by marsee
//

#include <ap_fixed.h>
#include "multi_test4.h"
#include "conv1_weight.h"

int multi_test4(ap_ufixed_in in[25], ap_fixed_add &out0, ap_fixed_add &out1){
#pragma HLS ARRAY_PARTITION variable=in complete dim=1

    ap_fixed_madd out_temp = 0.0;

#pragma HLS RESOURCE variable=out_temp core=AddSub
#pragma HLS PIPELINE II=1

    conv0: for(int k=0; k<2; k++){
        conv1: for(int m=0; m<5; m++){
            conv2: for(int n=0; n<5; n++){
                out_temp += in[m*5+n] * conv1_weight[k][0][m][n];
            }
        }
        if(k==0)
            out0 = out_temp;
        else
            out1 = out_temp;
        out_temp = 0.0;
    }

    return(0);
}


これは前と同じだが、multi_test4.h を示す。

// multi_test4.h
// 2018/02/04 by marsee
//

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

typedef ap_ufixed<80, AP_TRN, AP_WRAP> ap_ufixed_in;
typedef ap_fixed<91, AP_TRN, AP_WRAP> ap_fixed_weight;
typedef ap_fixed<226, AP_TRN, AP_WRAP> ap_fixed_madd;
typedef ap_fixed<166, AP_TRN_ZERO, AP_SAT> ap_fixed_add;

#endif


muti_test4_tb.cpp を示す。

// multi_test4_tb.h
// 2018/02/04 by marsee
//

#include "multi_test4.h"

int multi_test4(ap_ufixed_in in[25], ap_fixed_add &out0, ap_fixed_add &out1);

int main(void){
    ap_ufixed_in in[25];
    ap_fixed_add out0, out1;
    ap_ufixed_in v = 0.5;

    for(int i=0; i<25; i=i++){
        in[i] = (ap_ufixed_in)v;
        v += (ap_ufixed_in)0.00390625;
        printf("in[%d] = %f\n", i, (float)v);
    }

    multi_test4(in, out0, out1);

    printf("out0 = %f\n", (float)out0);
    printf("out1 = %f\n", (float)out1);

    return(0);
}


これで C シミュレーションを行った。結果を示す。
multi_test_61_180205.png

出力値は正常だ。

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

Latency は 7 クロックだった。2 つの畳み込み演算を並列にできているようだ。
なお、conv0 の for ループに UNROLL 指示子を入れても、入れなくても結果は変わらなかった。
BRAM_18K や DSP48E は使用していない。FF は 1,424 個、LUT は 2,767 個使用している。

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

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

2 つの畳み込み演算器がデータを受け取ってから、演算を行い、出力の値をその先の回路が受け取るまでに 7 クロックだった。

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

こちらは、1 個目の畳み込み演算器の影響を受けるだろうから、この値になっていると思う。少し、危なさそうな数値だ。
  1. 2018年02月05日 05:13 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討7(畳み込み演算4)

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討6(畳み込み演算3)”の続き。

前回は、従来通りの畳み込み演算の重みの配列を使用して、LUT にマップされた畳み込み演算を行うことができた。今回は、畳み込みフィルタは 2 個ある。前回はそのうちの 1 個目を使用していたので、今回は、2 個目の畳み込み演算の重みの配列を使用して違いを見てみよう。

まずは、2 個目の畳み込み演算の重みの配列を使用するために、19 行目の

conv1_weight[0][0][m][n]

conv1_weight[1][0][m][n]

に変更して、2 個目の畳み込み演算の重みの配列を使用する。
multi_test_54_180204.png

これで C シミュレーションを行った。結果を示す。
multi_test_55_180204.png

出力 out = -0.18555 だった。これだとマイナスの値なので、ReLU で 0 になるはずだ。

C コードの合成を行った。左に今回の 2 個目の畳み込み演算の重みの配列を使用した場合、右に前回の 1 個目の畳み込み演算の重みの配列を使用した場合の合成結果を示す。
multi_test_56_180204.pngmulti_test_50_180203.png

Latency は前回は 7 クロックだったが、今回は 4 クロックに減っている。Interval は変わらずに 1 クロックのままだ。
リソース使用量の FF も前回は、1,286 個だったが、今回は 478 個で約 37 % に減っている。LUT も前回は、2,106 個だったが、今回は805 個で、約 38 % に減っていた。
2 個目の畳み込み演算の重みの配列を見ると、0 が多く入っていた。そして他の重みもたぶんリソース使用量が少なくなる値だったんだと思う。
このように、DSP48E を使用せずに片方が定数の場合は演算を簡単化することができるのだが、重みの値によって圧縮率が異なる。2つに畳み込みフィルタを使用する場合に、2つ独立にインスタンスするとLatency が異なると結果が出てくるクロックが異なるので、Latency を合わせる必要がある。

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

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

畳み込み演算器がデータを受け取ってから、演算を行い、出力の値をその先の回路が受け取るまでに 4 クロックだった。

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

LUT は247 個、SRL は0 個なので、LUT は247 個使用する。こちらのFinal Timing は問題なさそうだ。
  1. 2018年02月04日 04:55 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討6(畳み込み演算3)

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討5(畳み込み演算2)”の続き。

前回は、乗算記号の * を使用して、畳み込みニューラルネットワークの畳み込み演算の重みを定数と置いたときの乗算で途中の演算の小数点以下の精度を下げてやってみた。今回は、従来通りの畳み込み演算の重みの配列を使用して、LUT にマップした演算ができないかどうか?を確かめてみた。

さて、Vivado HLS 2017.4 で multi_test3 プロジェクトを作成した。
multi_test_48_180203.png

multi_test3.cpp を貼っておく。

// multi_test3.cpp
// 2018/01/30 by marsee
//

#include <ap_fixed.h>
#include "multi_test3.h"
#include "conv1_weight.h"

int multi_test3(ap_ufixed_in in[25], ap_fixed_add &out){
#pragma HLS ARRAY_PARTITION variable=in complete dim=1
#pragma HLS PIPELINE II=1

    ap_fixed_madd out_temp = 0.0;
#pragma HLS RESOURCE variable=out_temp core=AddSub

    conv1: for(int m=0; m<5; m++){
#pragma HLS UNROLL
        conv2: for(int n=0; n<5; n++){
            out_temp += in[m*5+n] * conv1_weight[0][0][m][n];
        }
    }

    out = out_temp;

    return(0);
}


ご覧の様に、今までやってたように for ループを使用しているが、unroll 指示子でループを展開して、RESOURCE 指示子で AddSub を指定すれば、LUT を使用してくれるようだ。

次に、multi_test3.h を示す。

// multi_test3.h
// 2018/01/30 by marsee
//

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

typedef ap_ufixed<80, AP_TRN, AP_WRAP> ap_ufixed_in;
typedef ap_fixed<91, AP_TRN, AP_WRAP> ap_fixed_weight;
typedef ap_fixed<226, AP_TRN, AP_WRAP> ap_fixed_madd;
typedef ap_fixed<166, AP_TRN_ZERO, AP_SAT> ap_fixed_add;

#endif


conv_weight.h を示す。

// conv1_weight.h
// 2017/12/06 10:54:11 by marsee

const float conv1_fweight[2][1][5][5] = 
{
    {
        {
            {0.764403421227,0.658424746889,0.595604201652,0.554044871161,0.367767232883},
            {0.582414155838,0.413274869036,0.31659268154,0.3508390519,0.331194144626},
            {0.589182274309,0.462105790282,-0.241299390378,-0.10093021104,0.233291757594},
            {0.792411286764,0.315893121865,0.0397628864727,0.356726636694,0.426826537165},
            {0.634481192118,0.651475977113,0.688949928547,0.707285991358,0.681420943406}
        }
    }
    ,
    {
        {
            {0.00564732125401,-0.012955272371,-0.0231571581103,-0.00289983746176,0.0281080593816},
            {-0.0115360072012,0.00253310449813,-0.00860163957467,0.00112793810127,-0.01455040341},
            {-0.00881717612899,-0.00902248113722,0.0004194288468,0.00110240651437,-0.0140454059394},
            {0.00271556513713,-0.00307791921855,0.000117170379207,-0.00891721414879,0.0173026634286},
            {0.000808453898046,0.000116327205532,-0.00275343050716,-0.00683461392689,-0.0169130858704}
        }
    }
};

const ap_fixed<91, AP_TRN, AP_WRAP> conv1_weight[2][1][5][5] =
{
    {
        {
            {0.765625,0.66015625,0.59375,0.5546875,0.3671875},
            {0.58203125,0.4140625,0.31640625,0.3515625,0.33203125},
            {0.58984375,0.4609375,-0.23828125,-0.09765625,0.234375},
            {0.79296875,0.31640625,0.0390625,0.35546875,0.42578125},
            {0.6328125,0.65234375,0.6875,0.70703125,0.6796875}
        }
    }
    ,
    {
        {
            {0.00390625,-0.0078125,-0.01953125,0.0,0.02734375},
            {-0.0078125,0.00390625,-0.00390625,0.0,-0.01171875},
            {-0.00390625,-0.00390625,0.0,0.0,-0.01171875},
            {0.00390625,0.0,0.0,-0.00390625,0.015625},
            {0.0,0.0,0.0,-0.00390625,-0.01171875}
        }
    }
};


multi_test3_tb.cpp を示す。

// multi_test3_tb.h
// 2018/01/30 by marsee
//

#include "multi_test3.h"

int multi_test3(ap_ufixed_in in[25], ap_fixed_add &out);

int main(void){
    ap_ufixed_in in[25];
    ap_fixed_add out;
    ap_ufixed_in v = 0.5;

    for(int i=0; i<25; i=i++){
        in[i] = (ap_ufixed_in)v;
        v += (ap_ufixed_in)0.00390625;
        printf("in[%d] = %f\n", i, (float)v);
    }

    multi_test3(in, out);

    printf("out = %f\n", (float)out);

    return(0);
}


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

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

Latency は 7 クロックだった。Interval は 1 で 1 クロックごとに次のデータを入れることができる。
リソース使用量は、BRAM_18K が 0 個、DSP48E も 0 個で、演算を LUT にマップできていることが分かった。FF は 1,286 個、LUT は 2,106 個使用している。うまく行っている。

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

Latency は 7 クロックだった。

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

畳み込み演算器がデータを受け取ってから、演算を行い、出力の値をその先の回路が受け取るまでに 7 クロックだった。

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

LUT 使用数は 852 個だった。でもSRL の 36 もLUTとしての数に含めるべきかもしれない?
そうすると、852 + 36 = 888 個だった。
  1. 2018年02月03日 05:44 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討5(畳み込み演算2)

”Vivado HLS 2017.4 で片方が定数の場合の乗算の検討4(畳み込み演算1)”の続き。

前回は、乗算記号の * を使用して、畳み込みニューラルネットワークの畳み込み演算の重みを定数と置いたときの乗算を検討した。今回は、同様に演算をするのだが、途中の演算の小数点以下の精度を下げてみようと思う。

multi_test2.cpp、multi_test2_tb.cpp はそのままで、multi_test2.h だけ変更した。

typedef ap_fixed<9, 1, AP_TRN, AP_WRAP> ap_fixed_multi;


multi_test_42_180202.png

つまり、17 ビットだった乗算のビット幅を 9 ビットに変更した。減らした 8 ビットはすべて小数部のビット数となる。これで、飽和は関係なく、量子化モードだけが効くことになる。
これで C シミュレーションを行った。結果を示す。
multi_test_43_180202.png
出力は、out = 6.070313 となった。前回の結果が out = 6.113281 だったため、減ってしまっている。

C コードの合成を行った。結果を示す。
multi_test_44_180202.pngmulti_test_39_180131.png
左が今回の結果、右が前回の結果だ。
Latency が 8 クロックだったのが、6 クロックになっている。FF が 1305 個から 1193 個に減少した。 LUT は 2251 個から 2022 個に減少した。

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

C シミュレーションの波形を示す。
multi_test_46_180202.png

Export RTL を行った。その際に、Vivado synthesis, place and route にチェックを入れた。
結果を示す。
multi_test_47_180202.png

実際にVivado で論理合成、インプリメントしたときのリソース使用量は、だいぶ少なくなっている。FF は 1193 個だったのが、639 個に、LUT は 2022 個だったのが、853 個に減少した。
Final Timing を見ると、これでVivado にIP として持っていくとタイミングエラーが出そうだが、とりあえずの仮のIP 化なので、そのままとする。
  1. 2018年02月02日 05:22 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

これから作る畳み込みニューラルネットワークについての目標

2018/02/02 : ”Vivado HLS 2017.4 で片方が定数の場合の乗算の検討5(畳み込み演算2)”のExport RTL 時の結果を受けて、記事の数値を入れ替えました。

今まで、1クロックごとに1判定出力できる畳み込みニューラルネットワークを Vivado HLS で作ろうと思って、以下の記事でテストしていた。
Vivado HLS 2017.4 で片方が定数の場合の乗算の検討1(C シミュレーション)
Vivado HLS 2017.4 で片方が定数の場合の乗算の検討2(C コードの合成1)
”Vivado HLS 2017.4 で片方が定数の場合の乗算の検討3(C コードの合成2)
Vivado HLS 2017.4 で片方が定数の場合の乗算の検討4(畳み込み演算1)

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討4(畳み込み演算1)”が”畳み込み演算1”と書いてあることから分かる通りにまだ後の記事がある。
しかし、劇的にロジックは減らない。
ここで計算してみると、5 x 5 の畳み込み演算に 853 LUT 使用するので、白線間走行用畳み込みニューラルネットワークで使用してる画像サイズの Row x Column = 10 x 56 ピクセルの画像は、ストライド 1 だと畳み込み演算器は 6 x 52 = 312 個必要だ。フィルタ数は 2 個なので、その倍、つまり、624 個必要となる。
つまり、624 x 853 LUT = 532,272 LUT 必要となる。ZYBO Z7-20 やPYNQ に実装しようとすると総LUT数は53,200 個なので全く足りない。よって 1 クロックで 1 出力の実装は無理だということが分かった。

目標を変更することにしようと思う。
1クロックで1判定出力でなく、画像がストリームで 1 クロックで 1 ピクセル送られてくるときに画像総ピクセル数のクロック+レイテンシのクロック数で変換するような畳み込みニューラルネットワークを Vivado HLS で作ろうと思う。これならば、畳み込み演算部は今までやってきたラプラシアンフィルタの回路を多少修正して適用すれば良い。つまり、白線間走行用畳み込みニューラルネットワークの場合は畳み込みのフィルタ数は 2 個なので、 853 x 2 = 1,706 LUT、程度演算部では使用すれば良いことになる。
後は、全結合層の演算がうまく画像総ピクセル数のクロックで行けるように C ソースコードを書いてみようと思う。

白線間走行用畳み込みニューラルネットワークだと 10 x 56 = 560 クロック+レイテンシで判定出力できるように作ってみよう。
  1. 2018年02月01日 21:28 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

@ikwzmさんのUltraZed 向け Debian GNU/Linux の構築をやってみる15(udmabuf を使用する)

@ikwzmさんのUltraZed 向け Debian GNU/Linux の構築をやってみる14(led_on.py を C で書いてみる)”の続き。

前回は、デバイスツリーをロードして、LEDを点灯させるアプリケーションソフトは @ikwzm さんの作成された led_on.py の代わりにじぶんで書いた C 言語のアプリケーションソフトを使用した。今回は、Vivado HLS で作ったプロジェクトで udmabuf を使用してみよう。

今回、参照するのは@ikwzm さんの書かれた、”UltraZed 向け Debian GNU/Linux で Vivado-HLS を使って合成した回路を動かす”まずは、この通りにやってみよう。

fpga ユーザーの examples ディレクトリに移動して、git clone でGitHub の ikwzm/ZynqMP-FPGA-Linux-Example-2-UltraZed を negative という名前でダウンロードして、negative ディレクトリに入る。なお、Vivado HLS の回路は in を AXI4 Master で読んできて、マイナスにして、out へ書くという回路だ。
cd negative
git clone https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-UltraZed negative
cd negative

UltraZed-EG_StKit_Linux_202_180130.png

Python スクリプトでビットファイルをバイナリ・ファイルへ変換し、/lib/firmware へコピー。
python3 fpga-bit-to-bin.py -f negative.bit negative.bin
sudo cp negative.bin /lib/firmware

UltraZed-EG_StKit_Linux_203_180130.png

/lib/firmware ディレクトリを見ると、negative.bin があるのが分かる。
UltraZed-EG_StKit_Linux_204_180130.png

fpga-load.dts をコンパイルして、fpga-load.dtb を生成する。
dtc -I dts -O dtb -o fpga-load.dtb fpga-load.dts
UltraZed-EG_StKit_Linux_205_180130.png

fpga-load.dtb を fpga に登録
sudo mkdir /config/device-tree/overlays/fpga
sudo cp fpga-load.dtb /config/device-tree/overlays/fpga/dtbo

UltraZed-EG_StKit_Linux_206_180130.png

COMポートのTrea Term ウインドウにメッセージが表示された。
UltraZed-EG_StKit_Linux_207_180130.png

FPGA のクロックを設定するための fclk0-zynqmp.dts をコンパイル
dtc -I dts -O dtb -o fclk0-zynqmp.dtb fclk0-zynqmp.dts
UltraZed-EG_StKit_Linux_208_180130.png

fclk0-zynqmp.dts を fclk0 に登録
sudo mkdir /config/device-tree/overlays/fclk0
sudo cp fclk0-zynqmp.dtb /config/device-tree/overlays/fclk0/dtbo

UltraZed-EG_StKit_Linux_209_180130.png

COMポートのTrea Term ウインドウにメッセージが表示された。
UltraZed-EG_StKit_Linux_210_180130.png

UIO と udmabuf のデバイスツリー negative.dts
UltraZed-EG_StKit_Linux_211_180130.png

negative.dts をコンパイル
dtc -I dts -O dtb -o negative.dtb negative.dts
UltraZed-EG_StKit_Linux_212_180130.png

negative.dtb を negative に登録
sudo mkdir /config/device-tree/overlays/negative
sudo cp negative.dtb /config/device-tree/overlays/negative/dtbo
ls -l /dev/uio*
ls -l /dev/udmabuf*

UltraZed-EG_StKit_Linux_213_180130.png

COMポートのTrea Term ウインドウにメッセージが表示された。
UltraZed-EG_StKit_Linux_214_180130.png

negative.py を動作させる
sudo python3 negative.py
UltraZed-EG_StKit_Linux_215_180130.png

今回はスループットが 292 MByte/sec 程度だったが、2回目やると次に示すようにスループットが減っていた。何か?奇数回の方が偶数会よりもスループットが高い気がする。
2 回目の結果を示す。
UltraZed-EG_StKit_Linux_216_180130.png

スループットは、148 MByte/sec 程度と 1 回目よりも少なくなっている。

後始末
デバイスツリーをディレクトリごと削除する。
sudo rmdir /config/device-tree/overlays/netagive
sudo rmdir /config/device-tree/overlays/fclk0
sudo rmdir /config/device-tree/overlays/fpga

UltraZed-EG_StKit_Linux_217_180130.png

COMポートのTrea Term ウインドウに表示されたメッセージを示す。
UltraZed-EG_StKit_Linux_218_180130.png
  1. 2018年02月01日 04:32 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:0