FC2カウンター FPGAの部屋 DNN

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

FPGAの部屋

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

今まで作ってきた層のテンプレートをMNIST のCNN に適用する3(Export RTL まで)

今まで作ってきた層のテンプレートをMNIST のCNN に適用する2(C コードの合成2)”の続き。

前回は、PYNQボードのZynq に入るようにパラメータを設定しC コードの合成を行った。今回は、C シミュレーション、C/RTL 協調シミュレーション、Export RTL を行う。

まずは、C シミュレーションを行った。結果を示す。
mnist_conv_nn10_hlss_15_180519.png

ハードウェアの間違いが 1 個、ソフトウェアでの間違いが 1 個だった。

C/RTL 協調シミュレーションを実行するに当たって、NUM_ITERATIONS を 100 から 2 に変更した。多すぎると時間がかかるからだ。
mnist_conv_nn10_hlss_16_180519.png

C/RTL 協調シミュレーションを実行した。
mnist_conv_nn10_hlss_17_180520.png

Latency は、min 118630 クロックで、avg が 118643 クロック、max が 118657 クロックだった。

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

2 回目のCNN の実行時間を測ってみると、1,186.28 us だった。ただし、クロックは 100 MHz となっている。
mnist_conv_nn10_hlss_19_180520.png

Export RTL を行った。実行中にエラーが発生した。このエラー内容を示す。
mnist_conv_nn10_hlss_20_180520.png

Phase 1.2 IO Placement/ Clock Placement/ Build Placer Device
ERROR: [Place 30-640] Place Check : This design requires more RAMB36/FIFO cells than are available in the target device. This design requires 144 of such cell types but only 140 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device.
ERROR: [Place 30-640] Place Check : This design requires more RAMB18 and RAMB36/FIFO cells than are available in the target device. This design requires 290 of such cell types but only 280 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device.
ERROR: [Place 30-640] Place Check : This design requires more RAMB36E1 cells than are available in the target device. This design requires 144 of such cell types but only 140 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device.
INFO: [Timing 38-35] Done setting XDC timing constraints.
Phase 1.2 IO Placement/ Clock Placement/ Build Placer Device | Checksum: a2718320


Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 2408.320 ; gain = 0.000 ; free physical = 1463 ; free virtual = 9892
Phase 1 Placer Initialization | Checksum: a2718320


Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 2408.320 ; gain = 0.000 ; free physical = 1463 ; free virtual = 9892
ERROR: [Place 30-99] Placer failed with error: 'Implementation Feasibility check failed, Please see the previously displayed individual error or warning messages for more details.'
Please review all ERROR, CRITICAL WARNING, and WARNING messages during placement to understand the cause for failure.
Ending Placer Task | Checksum: a2718320


Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 2408.320 ; gain = 0.000 ; free physical = 1464 ; free virtual = 9893
INFO: [Common 17-83] Releasing license: Implementation
46 Infos, 0 Warnings, 0 Critical Warnings and 5 Errors encountered.
place_design failed
ERROR: [Common 17-69] Command failed: Placer could not place all instances
INFO: [Common 17-206] Exiting Vivado at Sun May 20 05:23:04 2018...
[Sun May 20 05:23:04 2018] impl_1 finished
wait_on_run: Time (s): cpu = 00:01:18 ; elapsed = 00:02:03 . Memory (MB): peak = 2078.047 ; gain = 8.000 ; free physical = 2423 ; free virtual = 10851
ERROR: [Common 17-69] Command failed: Run 'impl_1' failed. Unable to open
INFO: [Common 17-206] Exiting Vivado at Sun May 20 05:23:04 2018...
Finished export RTL.


つまり、BRAM_18K が 290 個必要で、280 個しか無いから実装できないよということのようだ。前回のC コードの合成では、BRAM_18K は 146 個、 52 % で合成できると結果が出ていたが、その倍程度ということらしい。これは、Vivado HLS ツールのバグじゃないだろうか?

PYNQ ボード(ZYBO Z7-20 でも動作する)で動作するために、affine_layer1 の OUTPUT_PIPELINE_II を 16 に変更した。
mnist_conv_nn10_hlss_21_180520.png

これで、C コードの合成を行った。
mnist_conv_nn10_hlss_22_180520.png
mnist_conv_nn10_hlss_23_180520.png

Estimated は 9.40 ns で、Latency は、min が 202936 クロック、max が 204376 クロックだった。
リソース使用量は、BRAM_18K が 146 個、DSP48E が 180 個、FF が 8890 個、LUT が 16424 個だった。

Export RTL を行った。今回はエラー無く実行することができた。
なお、Vivado synthesis, place and route にチェックを入れている。
mnist_conv_nn10_hlss_24_180520.png

LUT は 5737 個、これは C コードの合成の 1/3 の量だ。FF は 5575 個、DSP が 180 個、BRAM が 146 個だった。
CP achieved post-implementation は 9.622 ns で、ちょっと危ない気もするが、とりあえずは良いだろう。

さて、以前の実装の”「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化6(再度Vivado HLS )”との性能を比較してみよう。
以前の実装のLatency の max は 2745569 クロックなので、 性能は 2745569 / 204376 ≒ 13.4 倍になった。
  1. 2018年05月21日 04:34 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

今まで作ってきた層のテンプレートをMNIST のCNN に適用する2(C コードの合成2)

今まで作ってきた層のテンプレートをMNIST のCNN に適用する1(C コードの合成1)”の続き。

前回は、MNISTのCNNを以前実装したときに比べて性能は84 倍になったが、リソース使用量がPYNQボードのZynq のPL 容量をオーバーしてしまった。今回は、PYNQボードのZynq に入るようにパラメータを設定しC コードの合成を行う。
その際に使用するのが、”全結合層のテンプレートの変更”で全結合層のテンプレートに追加した OUTPUT_PIPELINE_II だ。この値を変更することにより、リソース使用量を変更することができる。

というのも、BRAM_18K を使用しているのは全部、全結合層の第1層目だからだ。Analysis 画面を見てみよう。
mnist_conv_nn10_hlss_4_180519.png

左上の Module Hierarchy の affine_layer1 を見ると、434 ですべてのBRAM_18K を使っているのが分かる。
affine_layer1 の現在の OUTPUT_PIPELINE_II の値は、affine_layer_template_1 の Loop4 のInitiation Interval の archived を見れば分かる。
mnist_conv_nn10_hlss_10_180519.png

Loop4 のInitiation Interval の archived の値は 2 だった。現在のPIPELINE指示子のLoop4 の II は 2 ということになる。
さて、affine_layer1 の現在の OUTPUT_PIPELINE_II の値を 4 に設定してみよう。
mnist_conv_nn10_hlss_5_180519.png

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

Latency は min が 59656 クロック、max が 61096 クロックになった。
BRAM_18K は 103 % でまだオーバーしている。

affine_layer_template_1 の Loop4 のInitiation Interval の archived は 4 だった。設定通りだ。
mnist_conv_nn10_hlss_8_180519.png
mnist_conv_nn10_hlss_9_180519.png

次に、、affine_layer1 の現在の OUTPUT_PIPELINE_II の値を 8 に設定してみよう。
mnist_conv_nn10_hlss_11_180519.png

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

Latency の min は 116680 クロックで、 max は 118120 クロックだった。
BRAM_18K は 146 個、 52 % となった。これならばPYNQボードのZynq に入りそうだ。
この場合の”「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化6(再度Vivado HLS )”との性能向上を計算してみよう。
「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化6(再度Vivado HLS )”の場合は、Latency の max が 2475569 なので、2475569 / 118120 ≒ 21 倍に性能が向上した。
  1. 2018年05月20日 08:12 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

今まで作ってきた層のテンプレートをMNIST のCNN に適用する1(C コードの合成1)

全結合層のテンプレートの変更”で全結合層のテンプレートを変更したが、今まで作ってきた畳み込みニューラルネットワーク(CNN) の層のテンプレートをMNIST のCNN に適用して、お手軽に素早くCNN をFPGAに実装できるかどうか?を確かめてみた。

まずは、C コードの合成を行って、どのくらいの性能になるのか?をチェックする。

HDL に合成されるソースコードはテンプレートを使って、層の実体を作り、以前のMNISTのCNN のパラメータを入れていくだけの簡単な作業なので、直ぐ実装することができた。今回の層構成は今まで作ってきた白線間走行用CNN と同じなので、パラメータを変更するだけだった。

全体を結合するトップレベルのファイルの mnist_conv_nn10_hlss.cpp と mnist_conv_nn10_hlss.h を貼っておく。
mnist_conv_nn10_hlss.h から貼っておく。

// mnist_conv_nn10_hlss.h
// 2018/05/18 by marsee
//

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

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;
    };

template<int U, int TI, int TD>
    struct float1_axis{
        struct data {
            float data0;
        } 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_OUTPUT_LAYER    10

typedef ap_uint<4> output_type;

typedef ap_fixed<12,7,AP_TRN,AP_WRAP> out_affine_type;
#endif


mnist_conv_nn10_hlss.cpp を貼っておく。

// mnist_conv_nn10_hlss.cpp
// 2018/05/18 by marsee
//

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

#include "layer_general.h"
#include "mnist_conv_nn10_hlss.h"

int input_layer(hls::stream<ap_axiu<8,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<9,1,1,1> >&outs);

int conv_layer1(hls::stream<ap_fixed_axis<9,1,1,1> >& ins,
    hls::stream<ap_fixed_axis<10,3,10,1> >& outs);

int relu_conv1(hls::stream<ap_fixed_axis<10,3,10,1> >& ins,
    hls::stream<ap_fixed_axis<10,3,10,1> >& outs);

int max_pooling(hls::stream<ap_fixed_axis<10,3,10,1> >& ins,
    hls::stream<ap_fixed_axis<10,3,10,1> >& outs);

int affine_layer1(hls::stream<ap_fixed_axis<10,3,10,1> >& ins,
    hls::stream<ap_fixed_axis<13,7,1,1> >& outs);

int relu_affine1(hls::stream<ap_fixed_axis<13,7,1,1> >& ins,
    hls::stream<ap_fixed_axis<13,7,1,1> >& outs);

int affine_layer2(hls::stream<ap_fixed_axis<13,7,1,1> >& ins,
    hls::stream<ap_fixed_axis<12,7,1,1> >& outs);

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

int all_layers(hls::stream<ap_axiu<8,1,1,1> >& ins, output_type& output,
    out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER]){
#pragma HLS INTERFACE s_axilite port=output
#pragma HLS INTERFACE s_axilite port=dot2
#pragma HLS ARRAY_PARTITION variable=dot2 complete dim=1
#pragma HLS DATAFLOW
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=ins

    hls::stream<ap_fixed_axis<9,1,1,1> > outs_input_layer;
// #pragma HLS STREAM variable=outs_input_layer depth=560 dim=1
    hls::stream<ap_fixed_axis<10,3,10,1> > outs_conv_layer;
// #pragma HLS STREAM variable=outs_conv_layer depth=312 dim=1
    hls::stream<ap_fixed_axis<10,3,10,1> > outs_relu_conv1;
// #pragma HLS STREAM variable=outs_relu depth=312 dim=1
    hls::stream<ap_fixed_axis<10,3,10,1> > outs_max_pooling;
// #pragma HLS STREAM variable=outs_max_pooling depth=78 dim=1
    hls::stream<ap_fixed_axis<13,7,1,1> > outs_affine_layer1;
// #pragma HLS STREAM variable=outs_affine_layer1 depth=100 dim=1
    hls::stream<ap_fixed_axis<13,7,1,1> > outs_relu_affine1;
// #pragma HLS STREAM variable=outs_relu_affine1 depth=100 dim=1
    hls::stream<ap_fixed_axis<12,7,1,1> > outs_affine_layer2;
// #pragma HLS STREAM variable=outs_affine_layer2 depth=3 dim=1

    input_layer(ins, outs_input_layer);
    conv_layer1(outs_input_layer, outs_conv_layer);
    relu_conv1(outs_conv_layer, outs_relu_conv1);
    max_pooling(outs_relu_conv1, outs_max_pooling);
    affine_layer1(outs_max_pooling, outs_affine_layer1);
    relu_affine1(outs_affine_layer1, outs_relu_affine1);
    affine_layer2(outs_relu_affine1, outs_affine_layer2);
    output_layer(outs_affine_layer2, output, dot2);

    return(0);
}



Vivado HLS 2017.3 の mnist_conv_nn10_hlss プロジェクトを示す。
mnist_conv_nn10_hlss_1_180519.png

さて、早速、C コードの合成を行った。結果を示す。
mnist_conv_nn10_hlss_2_180519.png
mnist_conv_nn10_hlss_3_180519.png

Estmated は 9.40 ns だったが、例によってUncertainty は 3.00 ns に設定している。
Latency は、min で 31144 クロック、max で 32584 クロックだった。Interval は min は 31113 クロック、max は 32553 クロックだった。
リソース使用量はBRAM_18K が 434 個、155 % でZYBO Z7-20 のZynq の量を超えてしまっているので、実装できない。DSP48E は、180 個、FF は 8923 個、LUT は 16481 個使用してる。

以前の実装は、”「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化6(再度Vivado HLS )”だった。その合成結果を示す。
nn_fpga_ch7_24_170620.png

以前の実装のLatency は 2745569 クロックなので、今回の実装での実行時間は、約 1/84 になっている。つまり性能は 84 倍になったということだ。ただし、リソース使用量は増えている。これは演算を並列動作させているので仕方がない。
次は、PYNQボードのZynq に入るように指示子を変更してみよう。
  1. 2018年05月19日 06:49 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

全結合層のテンプレートの変更

この前から畳み込みニューラルネットワーク(CNN)の層のテンプレートを作ってきた。今回、MNISTのCNN を層のテンプレートで実装したのだが、BRAMのリソース使用量をオーバーしてしまったので、BRAMを多量に使用している第1層目のチューニングを変更する必要が出てきた。そのため、全結合層のテンプレートを変更することにした。

変更点は、出力の演算をする for ループのPIPELINE指示子のII を指定できるようにしたことだ。具体的には

const size_t OUTPUT_PIPELINE_II

をテンプレートの変数に追加して、出力の演算をする for ループのPIPELINE指示子のII を指定できるようにした。

新しい affine_layers_template.h を貼っておく。

// affine_layer_template.h
// 2018/05/02 by marsee
// テンプレートを使用して汎用化した affine_layer
// #define LOOP3_PIPELINE_ENABLE を書くとLoop3にPIPELINE指示子が入る
// 2018/05/19:テンプレートにOUTPUT_PIPELINE_IIを追加
//

#ifndef __AFFINE_LAYER_TEMPLATE_H__
#define __AFFINE_LAYER_TEMPLATE_H__

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

#include "layer_general.h"

#define TO_LITERAL(x) #x
#define PRAGMA_HLS(tok) _Pragma(TO_LITERAL(HLS tok)) // @hiyuhさんから

template<
    const size_t IN_W,     // 入力のビット幅
    const size_t IN_I,     // 入力の小数点位置
    const size_t OUT_W, // 出力のビット長
    const size_t OUT_I, // 出力の小数点位置
    const size_t WB_W,     // 重みとバイアスのビット長
    const size_t WB_I,     // 重みとバイアスの小数点位置
    const size_t V_PRE_LAYER_HIGHT,
    const size_t H_PRE_LAYER_WIDTH,
    const size_t NUMBER_OF_CHANNELS,
    const size_t NUMBER_OF_OUTPUT,
    const size_t HORIZ_PIPELINE_II,
    const size_t OUTPUT_PIPELINE_II
>int affine_layer_template(hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_CHANNELS,1> >& ins,
        hls::stream<ap_fixed_axis<OUT_W,OUT_I,1,1> >& outs,
        const ap_fixed<WB_W,WB_I,AP_TRN,AP_WRAP> af_weight[V_PRE_LAYER_HIGHT*H_PRE_LAYER_WIDTH*NUMBER_OF_CHANNELS][NUMBER_OF_OUTPUT],
        const ap_fixed<WB_W,WB_I,AP_TRN,AP_WRAP> af_bias[NUMBER_OF_OUTPUT]
){
//#pragma HLS ARRAY_PARTITION variable=af_weight complete dim=1

    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_CHANNELS,1> stdata;
    ap_fixed<OUT_W,OUT_I,AP_TRN,AP_WRAP> dot[NUMBER_OF_OUTPUT];
//#pragma HLS ARRAY_PARTITION variable=dot complete dim=1
    ap_fixed_axis<OUT_W,OUT_I,1,1> outd;

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

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

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

                ap_fixed<OUT_W,OUT_I,AP_TRN,AP_WRAP> dot_temp = ap_fixed<OUT_W,OUT_I,AP_TRN,AP_WRAP>(0);
                for (int i=0; i<NUMBER_OF_CHANNELS; i++){
                    dot_temp += stdata.data[i] * af_weight[V_PRE_LAYER_HIGHT*H_PRE_LAYER_WIDTH*i+y*H_PRE_LAYER_WIDTH+x][col];
                }
                dot[col] += dot_temp;

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

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

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

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

                    outs << outd;
                }
            }
        }
    }

    return(0);
}

#endif


MNIST 用の新しい affine_layer1.cpp を貼っておく。

// affine_layer1.cpp
// 2018/05/02 by marsee
// affine layer1 by template
// 2018/05/19:テンプレートにOUTPUT_PIPELINE_IIを追加
//

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

int affine_layer1(hls::stream<ap_fixed_axis<10,3,10,1> >& ins,
        hls::stream<ap_fixed_axis<13,7,1,1> >& outs){
//#pragma HLS DATA_PACK variable=outs
//#pragma HLS DATA_PACK variable=ins
    return(affine_layer_template<10,3,13,7,9,1,12,12,10,100,0,1>(ins, outs, af1_weight, af1_bias));
}


新しい affine_layer2.cpp を貼っておく。

// affine_layer2.cpp
// 2018/05/03 by marsee
// affine layer2 by template
// 2018/05/19:テンプレートにOUTPUT_PIPELINE_IIを追加
//

#define LOOP3_PIPELINE_ENABLE

#include "affine_layer_template.h"
#include "af2_weight.h"
#include "af2_bias.h"

int affine_layer2(hls::stream<ap_fixed_axis<13,7,1,1> >& ins,
        hls::stream<ap_fixed_axis<12,7,1,1> >& outs){
//#pragma HLS DATA_PACK variable=outs
//#pragma HLS DATA_PACK variable=ins
    return(affine_layer_template<13,7,12,7,9,1,1,100,1,10,3,1>(ins, outs, af2_weight, af2_bias));
}

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

テンプレートで書いた畳み込みニューラルネットワーク2(C/RTL協調シミュレーションとExport RTL)

テンプレートで書いた畳み込みニューラルネットワーク2(C シミュレーションとC コードの合成)”の続き。

前回は、テンプレートで書いた各層をつないで、畳み込みニューラルネットワーク(CNN)を構築した。そして、Vivado HLSのプロジェクト all_layers_template を作成して、C シミュレーションと C コードの合成を行った。今回は、C/RTL協調シミュレーションとExport RTL を行う。

まず、C コードの合成によって生成されたVHDL コードのトップファイル all_layers.vhd のentity を貼っておく。
template_cnn_42_180515.png

レジスタ設定、結果取得用のAXI4 Lite Slave インターフェースと画像データを入力するためのAXI4-Stream インターフェースがあるのが分かる。


最初に、C/RTL 協調シミュレーションを行った。

#include "curve_data_0_100.h"

を有効にして、

#define NUM_ITERATIONS 300 // C Simulation

をコメントアウトし、

#define NUM_ITERATIONS 2 // C/RTL CoSimulation

のコメントアウトを外した。
template_cnn_38_180515.png

これで、C/RTL 協調シミュレーションを行った。結果を示す。
template_cnn_39_180515.png

Latency の min は 9259 クロック、avg は 9278 クロック、max は 9297 クロックで、Interval はすべて 9222 クロックだった。C コードの合成と差があるのは何故だろうか?
template_cnn_39_180515.png

C/RTL 協調シミュレーション波形を示す。
全体の波形を示す。CNN の実行2つ分だ。
template_cnn_40_180515.png

2 つ目の CNN の実行時間を計測してみよう。
ap_start から ap_done が出て、終了するまでの時間を計測する。
template_cnn_41_180515.png

92.57 us だった。この前の実装の2番目の処理の実行時間は89.54 us だったので、約 3.03 us ほど遅くなっている。しかし、計測条件が異なるかも?前の計測では、ap_start から ap_done 終了時間ではなかった気がする。
今同条件で測定しなおしたら、以前の all_layers プロジェクトのC/RTL 協調シミュレーションの 2 番目の波形の ap_start から ap_done 終了時間は、90.14 us だった。よってその差は 2.43 us だった。

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

LUT は 1944 個、FF が 2545 個、DSP が 29 個、BRAM が 15 個使用している。
CP achieved post-implementation は 9.364 ns で、ちょっと危ない気もするが、とりあえずは良いだろう。
  1. 2018年05月17日 04:28 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワーク2(C シミュレーションとC コードの合成)

テンプレートで書いた畳み込みニューラルネットワーク1(ソースコード)”の続き。

前回は、テンプレートで書いた各層をつないで、畳み込みニューラルネットワーク(CNN)を構築するためのソースコードを貼った。今回は、そのソースコードを使用して、Vivado HLSのプロジェクト all_layers_template を作成して、C シミュレーションと C コードの合成を行う。

やはり、C シミュレーションがうまく行かないので、OS をWindows 10 から Ubuntu 16.04 に変更して、Vivado HLS 2017.3 (Ubuntu 16.04 のマシンにVivado 2017.4 がインストールされていなかったので) のプロジェクト all_layers_template を作成した。なお、float で演算する soft の関数は、以前と同じだ。
template_cnn_31_180515.png

テストベンチのファイルを示す。
template_cnn_32_180515.png

次に C シミュレーションを行うが、同じ精度のCNN のC シミュレーションは”AXI4-Stream インターフェースの畳み込みニューラルネットワーク3(シミュレーション)”でやっているので、これを参考にする。

最初に#include

"curve_data_0_100.h"

つまり、0 番目から 300 番目の白線画像でやってみた。
template_cnn_33_180515.png

hw_err_cnt = 8 sw_err_cnt = 20

の内の hw_err_cnt がハードウェア化関数でのエラーの数を表す。sw_err_cnt が float で実装したソフトウェアのエラーの数を示す。今回はハードウェアのエラーが 8 個で、ソフトウェアのエラーが 20 個だった。これは、”AXI4-Stream インターフェースの畳み込みニューラルネットワーク3(シミュレーション)”でのエラー数と同じだ。

次に、

#include "curve_data_2500_2600.h"

の 2500 番目から 2800 番目の白線画像でやってみた。結果を示す。
template_cnn_34_180515.png

hw_err_cnt = 14 sw_err_cnt = 11

で、ハードウェアが 14 個、ソフトウェアが 11 個間違った。これも”AXI4-Stream インターフェースの畳み込みニューラルネットワーク3(シミュレーション)”でのエラー数と同じだ。

最後に、

#include "curve_data_5000_5100.h"

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

hw_err_cnt = 46 sw_err_cnt = 15

で、エラー数はハードウェアが 37 個、ソフトウェアが 15 個だった。やはり、ハードウェアのエラー数が多くなっている。これも”AXI4-Stream インターフェースの畳み込みニューラルネットワーク3(シミュレーション)”でのエラー数と同じで、すべての C シミュレーションにおいてエラー数が同一なので、同じCNN と言えると思う。

AXI4-Stream インターフェースの畳み込みニューラルネットワーク3(シミュレーション)”と同様だが精度の計算を載せておく。
まずは、ハードウェアで、300 + 300 + 300 = 900 個の画像をテストして、8 + 14 + 46 = 68 個エラーがあったので、
(900 - 68) / 900 * 100 ≒ 92.4 % となった。

ソフトウェアは、300 + 300 + 300 = 900 個の画像をテストして、20 + 11 + 15 = 46 個エラーがあったので、
(900 - 46) / 900 * 100 ≒ 94.9 % となった。

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

Estmated は9.40 ns だが、Uncertainty を 3 ns に指定している。
Latency は min が 8538 クロックで、max が 8696 クロックだった。Interval は min が 8507 クロックで、max が 8663 クロックだった。
リソース使用量は、BRAM_18K が 15 個、DSP48E が 29 個、FF が 4813 個、LUT が 7306 個だった。

AXI4-Stream インターフェースの畳み込みニューラルネットワーク2(C コードの合成、Export RTL)”の最後の合成結果を示す。
all_layers_11_180316.png

今回の C コードの合成結果と比較してみよう。
Latency は 9627 クロックなので、今回の結果の方が短かった。一方、Interval は 8427 クロックで、今回よりも少ないクロックとなっている。
リソース使用量は、いずれも今回よりも少ないリソース使用量となっている。
  1. 2018年05月16日 04:30 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワーク1(ソースコード)

今まで作ってきたテンプレートで書いた各層をつないで、畳み込みニューラルネットワーク(CNN)を構築する。層がテンプレートで書かれているので、パラメータを入れれば簡単に?いろいろなCNN をFPGA で構成することができるはず。。。

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

// all_layers_templapte.h
// 2018/03/13 by marsee
//

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

#define NUMBER_OF_OUTPUT_LAYER    3

typedef ap_uint<2> output_type;

typedef ap_fixed<12,7,AP_TRN,AP_WRAP> out_affine_type;
#endif


all_layers_template.cpp を貼っておく。

// all_layers_template.cpp
// 2018/05/10 by marsee
//

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

#include "layer_general.h"
#include "all_layers_template.h"

int input_layer(hls::stream<ap_axiu<32,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<9,1,1,1> >&outs);

int conv_layer1(hls::stream<ap_fixed_axis<9,1,1,1> >& ins,
    hls::stream<ap_fixed_axis<16,6,2,1> >& outs);

int relu_conv1(hls::stream<ap_fixed_axis<16,6,2,1> >& ins,
    hls::stream<ap_fixed_axis<16,6,2,1> >& outs);

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

int affine_layer1(hls::stream<ap_fixed_axis<16,6,2,1> >& ins,
    hls::stream<ap_fixed_axis<19,7,1,1> >& outs);

int relu_affine1(hls::stream<ap_fixed_axis<19,7,1,1> >& ins,
    hls::stream<ap_fixed_axis<19,7,1,1> >& outs);

int affine_layer2(hls::stream<ap_fixed_axis<19,7,1,1> >& ins,
    hls::stream<ap_fixed_axis<12,7,1,1> >& outs);

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

int all_layers(hls::stream<ap_axiu<32,1,1,1> >& ins, output_type& output,
    out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER]){
#pragma HLS INTERFACE s_axilite port=output
#pragma HLS INTERFACE s_axilite port=dot2
#pragma HLS ARRAY_PARTITION variable=dot2 complete dim=1
#pragma HLS DATAFLOW
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=ins

    hls::stream<ap_fixed_axis<9,1,1,1> > outs_input_layer;
//#pragma HLS STREAM variable=outs_input_layer depth=560 dim=1
    hls::stream<ap_fixed_axis<16,6,2,1> > outs_conv_layer;
//#pragma HLS STREAM variable=outs_conv_layer depth=312 dim=1
    hls::stream<ap_fixed_axis<16,6,2,1> > outs_relu_conv1;
//#pragma HLS STREAM variable=outs_relu depth=312 dim=1
    hls::stream<ap_fixed_axis<16,6,2,1> > outs_max_pooling;
//#pragma HLS STREAM variable=outs_max_pooling depth=78 dim=1
    hls::stream<ap_fixed_axis<19,7,1,1> > outs_affine_layer1;
//#pragma HLS STREAM variable=outs_affine_layer1 depth=100 dim=1
    hls::stream<ap_fixed_axis<19,7,1,1> > outs_relu_affine1;
//#pragma HLS STREAM variable=outs_relu_affine1 depth=100 dim=1
    hls::stream<ap_fixed_axis<12,7,1,1> > outs_affine_layer2;
//#pragma HLS STREAM variable=outs_affine_layer2 depth=3 dim=1

    input_layer(ins, outs_input_layer);
    conv_layer1(outs_input_layer, outs_conv_layer);
    relu_conv1(outs_conv_layer, outs_relu_conv1);
    max_pooling(outs_relu_conv1, outs_max_pooling);
    affine_layer1(outs_max_pooling, outs_affine_layer1);
    relu_affine1(outs_affine_layer1, outs_relu_affine1);
    affine_layer2(outs_relu_affine1, outs_affine_layer2);
    output_layer(outs_affine_layer2, output, dot2);

    return(0);
}


なお各層の入力ポートと出力ポートのINTERFACE指示子はコメントアウトしてある。
all_layers_soft.cpp を貼っておく。

// all_layers_soft.cpp
// 2018/03/14 by marsee
//

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

#include "all_layers.h"

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

int relu_soft(hls::stream<float2_axis<1,1,1> >& ins,
        hls::stream<float2_axis<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 affine_layer1_soft(hls::stream<float2_axis<1,1,1> >& ins,
        hls::stream<float1_axis<1,1,1> >& outs);

int relu_affine1_soft(hls::stream<float1_axis<1,1,1> >& ins,
        hls::stream<float1_axis<1,1,1> >& outs);

int affine_layer2_soft(hls::stream<float1_axis<1,1,1> >& ins,
        hls::stream<float1_axis<1,1,1> >& outs);

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

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

    hls::stream<float2_axis<1,1,1> > outs_conv_layer_soft;
    hls::stream<float2_axis<1,1,1> > outs_relu_soft;
    hls::stream<float2_axis<1,1,1> > outs_max_pooling_soft;
    hls::stream<float1_axis<1,1,1> > outs_affine_layer1_soft;
    hls::stream<float1_axis<1,1,1> > outs_relu_affine1_soft;
    hls::stream<float1_axis<1,1,1> > outs_affine_layer2_soft;

    conv_layer_soft(ins, outs_conv_layer_soft);
    relu_soft(outs_conv_layer_soft, outs_relu_soft);
    max_pooling_soft(outs_relu_soft, outs_max_pooling_soft);
    affine_layer1_soft(outs_max_pooling_soft, outs_affine_layer1_soft);
    relu_affine1_soft(outs_affine_layer1_soft, outs_relu_affine1_soft);
    affine_layer2_soft(outs_relu_affine1_soft, outs_affine_layer2_soft);
    output_layer_soft(outs_affine_layer2_soft, output, dot2);

    return(0);
}


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

// all_layers_template_tb.cpp
// 2018/05/12 by marsee
//

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

#include "layer_general.h"
#include "all_layers_template.h"

#include "curve_data_0_100.h"
//#include "curve_data_2500_2600.h"
//#include "curve_data_5000_5100.h"

#define ALL_DATA_NUM   300
#define NUM_OF_KERNELS 2
#define COULMN_PIXELS 56
#define ROW_PIXELS 10
#define ALL_PIXELS 560
#define NUM_OF_OUTPUT 3

#define NUM_ITERATIONS    300 // C Simulation
//#define NUM_ITERATIONS    2 // C/RTL CoSimulation

int all_layers(hls::stream<ap_axiu<32,1,1,1> >& ins, output_type& output,
        out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER]);

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

int main(){
    using namespace std;

    hls::stream<ap_axiu<32,1,1,1> > ins;
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    output_type output, output_soft;
    out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER];
    float dot2_soft[NUMBER_OF_OUTPUT_LAYER];
    ap_axiu<32,1,1,1> pix;
    int hw_err_cnt = 0;
    int sw_err_cnt = 0;

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

        for(int y=0; y<ROW_PIXELS; y++){
            for(int x=0; x<COULMN_PIXELS; x++){
                // 1 画面分のデータを ins、ins_soft に入力する
                pix.data = ap_uint<32>(t_train_256[i][y*COULMN_PIXELS+x]);

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

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

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

        all_layers(ins, output, dot2);
        all_layers_soft(ins_soft, output_soft, dot2_soft);

        int t_test_num = 0;
        for(int m=0; m<NUMBER_OF_OUTPUT_LAYER; m++){
            if(t_test[i][m] == 1.0f){
                t_test_num = m;
                break;
            }
        }
        // out と out_soft を比較する
        /* cout << "output" << " = " << int(output) << " output_soft = " << int(output_soft) << endl;        for(int j=0; j<NUMBER_OF_OUTPUT_LAYER; j++){            cout << "dot2[" << j << "] = " << float(dot2[j]) << " dot2_soft[" << j << "] = " << dot2_soft[j] << endl;        } */
        if(int(output) != t_test_num){
            cout << "hw_error: i = " << i << " output = " << int(output) << " t_test_num = " << t_test_num << endl;
            hw_err_cnt++;
            //return(1);
        }
        if(int(output_soft) != t_test_num){
            cout << "sw_error: i = "<< i << " output_soft = " << int(output_soft) << " t_test_num" " = " << t_test_num << endl;
            sw_err_cnt++;
            //return(1);
        }
        if(int(output) != t_test_num || int(output_soft) != t_test_num){
            for(int j=0; j<NUMBER_OF_OUTPUT_LAYER; j++){
                cout << "dot2[" << j << "] = " << fixed << setprecision(8) << float(dot2[j]) << "    dot2_soft[" << j << "] = " << dot2_soft[j] << endl;
            }
            cout << endl;
        }
    }
    cout << "hw_err_cnt = " << hw_err_cnt << " sw_err_cnt = " << sw_err_cnt << endl;

    return(0);
}

  1. 2018年05月15日 04:47 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0
»