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

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

FPGAの部屋

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

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 << " " << 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

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. その他のFPGAの話題
  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