FC2カウンター FPGAの部屋

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

FPGAの部屋

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

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.biHeight*y+x] << ", "
                    << sw_convf[bmpihr.biHeight*bmpihr.biWidth+bmpihr.biHeight*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.biHeight*y+x] << ", "
                    <<  hw_convf[bmpihr.biHeight*bmpihr.biWidth+bmpihr.biHeight*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 : 修正)

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#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_conv0, *hw_conv1, *sw_conv0, *sw_conv1;
    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_conv0 =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_conv0 memory\n");
        exit(1);
    }
    if ((hw_conv1 =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_conv1 memory\n");
        exit(1);
    }
    if ((sw_conv0 =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_conv0 memory\n");
        exit(1);
    }
    if ((sw_conv1 =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_conv1 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);

    // 画像サイズの縮小
    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_conv0[(j*bmpihr.biWidth)+i] = ((int)val0+32)*4// 32を足して負の符号を排除し、整数部6ビットなので、2ビット分補正する
            hw_conv1[(j*bmpihr.biWidth)+i] = ((int)val1+32)*4;
            sw_conv0[(j*bmpihr.biWidth)+i] = ((int)val_soft0+32)*4;
            sw_conv1[(j*bmpihr.biWidth)+i] = ((int)val_soft1+32)*4;

            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_conv0[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                    green = blue;
                    red = blue;
                } else {
                    blue = hw_conv1[((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_conv0[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                    green = blue;
                    red = blue;
                } else {
                    blue = sw_conv1[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                    green = blue;
                    red = blue;
                }

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

    free(rd_bmp);
    free(hw_conv0);
    free(hw_conv1);
    free(sw_conv0);
    free(sw_conv1);

    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