FC2カウンター FPGAの部屋 Vivado HLS
FC2ブログ

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

FPGAの部屋

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

Vivado HLS でディレクトリのファイル名を取得するソフトウェアを開発

学習画像をMNISTのデータ形式に変換するのに、今回は、ファイル名が連番ではないので、すべてのファイル名を取得する必要がある。
どうするか?考えたが、ググってみると、「コンピューター:C言語講座:ディレクトリ内容の読み出し」を見つけた。このコードを元にファイル名を取得するように書いてみた。

Vivado HLS 2018.2 で scan_file_name というプロジェクトを作った。
ファイルは scan_file_name.cpp と、この前撮影したZYBOt カメラから撮影したコースの写真だ。
scan_file_name_1_180924.png

C シミュレーションを行った。成功だ。ファイル名を取得することができた。
scan_file_name_2_180924.png

C シミュレーションの出力結果を示す。

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../scan_file_name.cpp in debug mode
Generating csim.exe
bmp_file5.bmp
bmp_file30.bmp
bmp_file7.bmp
bmp_file3.bmp
bmp_file15.bmp
bmp_file38.bmp
bmp_file8.bmp
bmp_file55.bmp
bmp_file57.bmp
bmp_file156.bmp
bmp_file175.bmp
bmp_file4.bmp
bmp_file32.bmp
bmp_file140.bmp
bmp_file58.bmp
bmp_file0.bmp
bmp_file51.bmp
bmp_file160.bmp
bmp_file139.bmp
bmp_file22.bmp
bmp_file129.bmp
bmp_file60.bmp
bmp_file181.bmp
bmp_file170.bmp
bmp_file37.bmp
bmp_file165.bmp
bmp_file177.bmp
bmp_file59.bmp
bmp_file147.bmp
bmp_file10.bmp
bmp_file25.bmp
bmp_file133.bmp
bmp_file169.bmp
bmp_file49.bmp
bmp_file161.bmp
bmp_file176.bmp
bmp_file28.bmp
bmp_file144.bmp
bmp_file42.bmp
bmp_file16.bmp
bmp_file135.bmp
bmp_file2.bmp
bmp_file21.bmp
bmp_file166.bmp
bmp_file1.bmp
bmp_file48.bmp
bmp_file183.bmp
bmp_file6.bmp
bmp_file150.bmp
bmp_file154.bmp
bmp_file128.bmp
bmp_file19.bmp
bmp_file145.bmp
bmp_file53.bmp
bmp_file64.bmp
bmp_file178.bmp
bmp_file127.bmp
bmp_file39.bmp
bmp_file164.bmp
bmp_file126.bmp
bmp_file47.bmp
bmp_file148.bmp
bmp_file172.bmp
bmp_file17.bmp
bmp_file158.bmp
bmp_file44.bmp
bmp_file151.bmp
bmp_file138.bmp
bmp_file56.bmp
bmp_file61.bmp
bmp_file180.bmp
bmp_file141.bmp
bmp_file174.bmp
bmp_file155.bmp
bmp_file168.bmp
bmp_file62.bmp
bmp_file33.bmp
bmp_file54.bmp
bmp_file35.bmp
bmp_file179.bmp
bmp_file11.bmp
bmp_file20.bmp
bmp_file159.bmp
bmp_file52.bmp
bmp_file24.bmp
bmp_file146.bmp
bmp_file43.bmp
bmp_file18.bmp
bmp_file182.bmp
bmp_file45.bmp
bmp_file29.bmp
bmp_file31.bmp
bmp_file14.bmp
bmp_file152.bmp
bmp_file136.bmp
bmp_file130.bmp
bmp_file40.bmp
bmp_file131.bmp
bmp_file13.bmp
bmp_file46.bmp
bmp_file134.bmp
bmp_file41.bmp
bmp_file9.bmp
bmp_file163.bmp
bmp_file12.bmp
bmp_file23.bmp
bmp_file34.bmp
bmp_file143.bmp
bmp_file63.bmp
bmp_file137.bmp
bmp_file142.bmp
bmp_file27.bmp
bmp_file149.bmp
bmp_file171.bmp
bmp_file26.bmp
bmp_file157.bmp
bmp_file50.bmp
bmp_file173.bmp
bmp_file36.bmp
bmp_file153.bmp
bmp_file132.bmp
bmp_file162.bmp
bmp_file167.bmp
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


train ディレクトリの下のleft, right, straight ディレクトリには、42 + 43 + 38 = 123 個のファイルがある。それらのファイル名をすべて表示することができた。
scan_file_name_3_180924.png

これで、学習画像をMNIST のデータ形式に変換することができる。

scan_file_name.cpp のソースコードを貼っておく。

// scan_file_name.cpp
// 2018/09/23 by marsee
//

#include <stdio.h>
#include <dirent.h>
#include "hls_opencv.h"

const char LEFT_PIC_DIR[] = "train/left";
const char RIGHT_PIC_DIR[] = "train/right";
const char STRAIGHT_PIC_DIR[] = "train/straight";
const char CUR_DIR[] = ".";
const char PAR_DIR[] = "..";

int main(){
    DIR *ldir, *rdir, *sdir;
    struct dirent *ldp, *rdp, *sdp;
    int lnum=0, rnum=0, snum=0;
    int max_num;
    int lcnt=0, rcnt=0, scnt=0;

    // Open folder of each file
    if((ldir=opendir(LEFT_PIC_DIR)) == NULL){
        fprintf(stderr, "LEFT_PIC_DIR Open Error\n");
        exit(1);
    }
    if((rdir=opendir(RIGHT_PIC_DIR)) == NULL){
        fprintf(stderr, "RIGHT_PIC_DIR Open Error\n");
        exit(1);
    }
    if((sdir=opendir(STRAIGHT_PIC_DIR)) == NULL){
        fprintf(stderr, "STRAIGHT Open Error\n");
        exit(1);
    }
    
    // Count the number of files in each folder
    for(ldp=readdir(ldir); ldp != NULL; ldp=readdir(ldir)){
        lnum++;
    }
    for(rdp=readdir(rdir); rdp != NULL; rdp=readdir(rdir)){
        rnum++;
    }
    for(sdp=readdir(sdir); sdp != NULL; sdp=readdir(sdir)){
        snum++;
    }
    
    // Find the maximum value
    if(lnum > rnum && lnum > snum)
        max_num = lnum;
    else if(rnum > lnum && rnum > snum)
        max_num = rnum;
    else
        max_num = snum;
    
    // Close
    closedir(ldir);
    closedir(rdir);
    closedir(sdir);
    
    // Reopen folder of each file
    if((ldir=opendir(LEFT_PIC_DIR)) == NULL){
        fprintf(stderr, "LEFT_PIC_DIR Open Error\n");
        exit(1);
    }
    if((rdir=opendir(RIGHT_PIC_DIR)) == NULL){
        fprintf(stderr, "RIGHT_PIC_DIR Open Error\n");
        exit(1);
    }
    if((sdir=opendir(STRAIGHT_PIC_DIR)) == NULL){
        fprintf(stderr, "STRAIGHT Open Error\n");
        exit(1);
    }
    
    // Display file name
    for(int i=1; i <= max_num; i++){
        if(i <= lnum){
            ldp = readdir(ldir);
            if(strcmp(CUR_DIR, ldp->d_name)!=0 && strcmp(PAR_DIR, ldp->d_name)!=0){
                std::cout << ldp->d_name << std::endl;
            }
        }
        if(i <= rnum){
            rdp = readdir(rdir);
            if(strcmp(CUR_DIR, rdp->d_name)!=0 && strcmp(PAR_DIR, rdp->d_name)!=0){
                std::cout << rdp->d_name << std::endl;
            }
        }
        if(i <= snum){
            sdp = readdir(sdir);
            if(strcmp(CUR_DIR, sdp->d_name)!=0 && strcmp(PAR_DIR, sdp->d_name)!=0){
                std::cout << sdp->d_name << std::endl;
            }
        }
    }
    
    return(0);
}

  1. 2018年09月24日 04:03 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Concatenate IP の作成2

Concatenate IP の作成1”の続き。

前回は、Concatenate IP のソースコードを貼った。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RLT を行う。

最初に concatenate プロジェクトを示す。
concatenate_1_180902.png

C シミュレーションを行った。
concatenate_2_180902.png

エラーは 0 だった。つまり、合流した 2 つのストリームは同じ値だった。

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

Estimated は 7.268 ns で問題ない。
Detail -> Instance の grp_concatenate_template_1_fu_48 をクリックして concatenate_template.h の合成結果を確認する。
concatenate_4_180903.png

Detail -> Instance の ins_hls_stream_templ_U0 をクリックした。
ins_hls_stream の合成結果を確認する。
concatenate_5_180903.png

Loop_y_Loop_x のInitiation Interval の archived は 1 となっていて、良いと思う。

次に concatenate_template_U0 をクリックしてみてみよう。
concatenate_6_180903.png

こちらも、Loop_y_Loop_x のInitiation Interval の archived は 1 となっていて、問題ない。

次に C/RTL 協調シミュレーションを行ったが、エラーで落ちてしまった。
concatenate_7_180903.png

mpfr_srcptr が定義されていないというエラーだった。”HLSストリーム・インターフェースの畳み込み層2(C シミュレーション、C/RTL 協調シミュレーション)”で同じエラーが出ていて、コメント欄で回避の仕方も教えてもらったが、今回は、ソースコードに

#include </opt/Xilinx/Vivado/2018.2/include/gmp.h>

を書いても、エラーの回避が出来なかった。Initiation Interval の archived は 1 になっているので、C/RTL 協調シミュレーション結果も問題ないものと思える。C/RTL 協調シミュレーションは今のところ Windows でやったほうが良さそうた。

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

LUT が 284 個、FF が 387 個が使用されている。
CP achieved post-implementation は 4.671 ns で問題ない。
  1. 2018年09月03日 04:40 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Concatenate IP の作成1

今回は Concatenate IP をVivado HLS 2018.2 で作成してみよう。
Concatenate IP は 2 つのHLS ストリームを 1 つのHLS ストリームにマージするIP で、SqueezeNet を実装するために必要となる。
Concatenate IP を作るために、”2つのHLSストリームを同時に入力して演算してHLSストリーム出力”と”2つのHLSストリームを同時に入力して演算してHLSストリーム出力2”でHLS ストリームを 2 つ必要とするときの並列動作については検証しておいた。よって、この成果を使用してConcatenate IP を作成しよう。

まずは、concatenate_template.h を示す。

// concatenate_template.h
// 2018/08/08 : by marsee
//

#ifndef __CONCATENATE_TEMPLATE_H___
#define __CONCATENATE_TEMPLATE_H___

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

#include "layer_general.h"

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

template<
    const size_t IN_W,  // 入力のビット幅、2つの入力のビット幅と小数点位置は合わせる
    const size_t IN_I,  // 入力の小数点位置
    const size_t NUMBER_OF_IN_CHANNELS,
    const size_t HORIZONTAL_PIXEL_WIDTH,
    const size_t VERTICAL_PIXEL_WIDTH
> int ins_hls_stream_template(hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> >&ins,
    hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> >&outs){
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> in_val;
#pragma HLS ARRAY_PARTITION variable=in_val.data complete dim=1
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> out_val;

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

    Loop_y : for(int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        Loop_x : for(int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE II=1
            if(!(x==0 && y==0)){
                ins >> in_val;
            }
            for(int k=0; k<NUMBER_OF_IN_CHANNELS; k++)
                out_val.data[k] = in_val.data[k];
            out_val.user = in_val.user;
            out_val.last = in_val.last;
            outs << out_val;
        }
    }
    return(0);
}

template<
    const size_t IN_W,  // 入力のビット幅、2つの入力のビット幅と小数点位置は合わせる
    const size_t IN_I,  // 入力の小数点位置
    const size_t NUMBER_OF_IN0_CHANNELS,
    const size_t NUMBER_OF_IN1_CHANNELS,
    const size_t HORIZONTAL_PIXEL_WIDTH,
    const size_t VERTICAL_PIXEL_WIDTH
>int concatenate_template(hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN0_CHANNELS,1> >&ins0,
    hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN1_CHANNELS,1> >&ins1,
    hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN0_CHANNELS+NUMBER_OF_IN1_CHANNELS,1> >&outs
){
#pragma HLS DATAFLOW
    hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN0_CHANNELS,1> > outs0;
    hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN1_CHANNELS,1> > outs1;
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN0_CHANNELS,1> in0;
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN0_CHANNELS,1> out0;
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN1_CHANNELS,1> in1;
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN1_CHANNELS,1> out1;
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN0_CHANNELS+NUMBER_OF_IN1_CHANNELS,1> out;

    ins_hls_stream_template<IN_W,IN_I,NUMBER_OF_IN0_CHANNELS,HORIZONTAL_PIXEL_WIDTH,VERTICAL_PIXEL_WIDTH>(ins0, outs0);
    ins_hls_stream_template<IN_W,IN_I,NUMBER_OF_IN1_CHANNELS,HORIZONTAL_PIXEL_WIDTH,VERTICAL_PIXEL_WIDTH>(ins1, outs1);

    Loop_y : for(int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        Loop_x : for(int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE II=1
            outs0 >> out0;
            outs1 >> out1;

            Loop_cp : for(int i=0; i<NUMBER_OF_IN0_CHANNELS+NUMBER_OF_IN1_CHANNELS; i++){
                if(i<NUMBER_OF_IN0_CHANNELS)
                    out.data[i] = out0.data[i];
                else
                    out.data[i] = out1.data[i-NUMBER_OF_IN0_CHANNELS];
            }

            out.user = out0.user;
            out.last = out0.last;

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

#endif


concatenate1.cppを示す。

// concatenate1.cpp
// 2018/08/09 by marsee
//

#include "concatenate_template.h"

int concatenate1(hls::stream<ap_fixed_axis<16,6,2,1> >& ins0,
    hls::stream<ap_fixed_axis<16,6,2,1> >& ins1,
    hls::stream<ap_fixed_axis<16,6,4,1> >& outs){
    return(concatenate_template<16,6,2,2,56-4,10-4>(ins0, ins1, outs));
}


最後にテストベンチの concatenate1_tb.cpp を示す。

// concatenate1_tb.cpp
// 2018/08/09 by marsee
//

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

#include "layer_general.h"
#include "conv_layer1.h"
#include "bmp_header.h"

int conv_layer1(hls::stream<ap_fixed_axis<9,1,1,1> >& ins,
        hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >& outs);
int conv_layer_soft(hls::stream<ap_axiu<32,1,1,1> >& ins,
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> >& outs);
int conv_layer2(hls::stream<ap_axiu<32,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >&outs);
int concatenate1(hls::stream<ap_fixed_axis<16,6,2,1> >& ins0,
    hls::stream<ap_fixed_axis<16,6,2,1> >& ins1,
    hls::stream<ap_fixed_axis<16,6,4,1> >& outs);

#define BMP_FILE_NAME   "straight_RED_rect0_00_rgb.bmp"

int main(){
    using namespace std;

    hls::stream<ap_fixed_axis<9,1,1,1> > ins0;
    hls::stream<ap_fixed_axis<9,1,1,1> > ins1;
    hls::stream<ap_axiu<32,1,1,1> > ins2;
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > con0;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > con1;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL+NUMBER_OF_KERNEL,1> > outs;
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> > outs2;
    hls::stream<float_axis<NUMBER_OF_KERNEL,1> > outs_soft;
    ap_axiu<32,1,1,1> pix;
    ap_fixed_axis<9,1,1,1> pixf;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL+NUMBER_OF_KERNEL,1> vals;
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> vals2;
    float_axis<NUMBER_OF_KERNEL,1> vals_soft;

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

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

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

    for(int i=0; i<NUMBER_OF_KERNEL; i++){
        if ((hw_convf[i] =(float *)malloc(sizeof(float) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate hw_convf[%d] memory\n", i);
            exit(1);
        }
        if ((sw_convf[i] =(float *)malloc(sizeof(float) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate sw_convf[%d] memory\n", i);
            exit(1);
        }
    }

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

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

        pix.data = i;
        pixf.data[0] = i;

        pix.last = 0;
        pixf.last = 0;

        ins0 << pixf;
        ins1 << pixf;
        ins2 << pix;
        ins_soft << pix;
    }

    // 1 画面分のデータを ins、ins_soft に入力する
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            pix.data = (ap_uint<32>)rd_bmp[(j*bmpihr.biWidth)+i];
            pixf.data[0] = (ap_fixed<9,1,AP_TRN,AP_WRAP>)((ap_ufixed<16,8,AP_TRN,AP_WRAP>)(pix.data & 0xff) / 256);

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

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

            ins0 << pixf;
            ins1 << pixf;
            ins2 << pix;
            ins_soft << pix;
        }
    }

    printf("bmpihr.biWidth = %d\n", bmpihr.biWidth);
    printf("bmpihr.biHeight = %d\n", bmpihr.biHeight);
    // 畳み込み演算
    conv_layer1(ins0, con0);
    conv_layer1(ins1, con1);
    concatenate1(con0, con1, outs);
    conv_layer2(ins2, outs2);
    conv_layer_soft(ins_soft, outs_soft);

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

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    out_type val[NUMBER_OF_KERNEL];
    out_type val1[NUMBER_OF_KERNEL];
    out_type val2[NUMBER_OF_KERNEL];
    float val_soft[NUMBER_OF_KERNEL];

    cout << endl;
    cout << "outs" << endl;
    int errcnt=0;
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            outs2 >> vals2;
            outs_soft >> vals_soft;

            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                val[k] = vals.data[k];
                val1[k] = vals.data[NUMBER_OF_KERNEL+k];

                val2[k] = vals2.data[k];
                val_soft[k] = vals_soft.data[k];

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

                float *hw_convfp = hw_convf[k];
                float *sw_convfp = sw_convf[k];
                hw_convfp[(j*bmpihr.biWidth)+i] = (float)val[k];
                sw_convfp[(j*bmpihr.biWidth)+i] = val_soft[k];
                if (val[k] != val2[k] || val[k] != val1[k]){
                    printf("ERROR val and val2 results mismatch i = %d, j = %d, val[%d] = %f, val1[%d] = %f, val2[%d] = %f\n", i, j, k, (float)val[k], k, (float)val1[k], k, (float)val2[k]);
                    errcnt++;
                    //return(1);
                }
                printf("HW and SW results i = %d, j = %d, HW[%d] = %f, HW1[%d] = %f, HW2[%d] = %f, SW[%d] = %f\n", i, j, k, (float)val[k], k, (float)val1[k], k, (float)val2[k], k, val_soft[k]);
            }
        }
    }
    cout << "Error Count = " << errcnt << endl;
    cout << "Success HW and SW results match" << endl;
    cout << endl;

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

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

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

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

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

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

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

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

    free(rd_bmp);
    for(int k=0; k<NUMBER_OF_KERNEL; k++){
        free(hw_conv[k]);
        free(sw_conv[k]);
        free(hw_convf[k]);
        free(sw_convf[k]);
    }

    return(0);
}

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

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

    float ap_uf_pix;
    float val;

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

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

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

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

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

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

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

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

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

// 検証用 conv_layer2()
// 検証用に conv_layer() とは異なる実装でコーディング
int conv_layer2(hls::stream<ap_axiu<32,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> >&outs){

    ap_axiu<32,1,1,1> pix;
    val_type conv_val[NUMBER_OF_KERNEL][VERTICAL_PIXEL_WIDTH][HORIZONTAL_PIXEL_WIDTH];
    in_type ap_uf_pix[VERTICAL_PIXEL_WIDTH][HORIZONTAL_PIXEL_WIDTH];
    ap_fixed_axis<W,I,NUMBER_OF_KERNEL,1> conv_out;

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

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

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

   for(int i=0; i<NUMBER_OF_KERNEL; i++){    // カーネルの個数
        for(int j=0; j<VERTICAL_PIXEL_WIDTH-(ARRAY_SIZE-1); j++){
            for(int k=0; k<HORIZONTAL_PIXEL_WIDTH-(ARRAY_SIZE-1); k++){
                conv_val[i][j][k] = 0;
                for(int m=0; m<ARRAY_SIZE; m++){
                    for(int n=0; n<ARRAY_SIZE; n++){
                        conv_val[i][j][k] += (val_type)ap_uf_pix[j+m][k+n] * (val_type)conv1_weight[i][0][m][n];
                    }
                }
                conv_val[i][j][k] += (val_type)conv1_bias[i];
            }
        }
    }

    for(int y=0; y<VERTICAL_PIXEL_WIDTH-(ARRAY_SIZE-1); y++){
        for(int x=0; x<HORIZONTAL_PIXEL_WIDTH-(ARRAY_SIZE-1); x++){
            for(int i=0; i<NUMBER_OF_KERNEL; i++){
                conv_out.data[i] = conv_val[i][y][x];
            }

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

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

            outs << conv_out;
        }
    }

    return(0);
}

  1. 2018年09月02日 10:14 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Duplicate IP の製作2(Vivado HLS プロジェクト)

Duplicate IP の製作1(ソースコード)”の続き。

前回は、1 つの HLS ストリームを 2 つにするDuplicate IP のソースコードを示した。今回は、そのソースコードを使用して、Vivado HLS 2018.2 のプロジェクトを作成して、IP 化まで行う。

Vivado HLS 2018.2 の duplicate プロジェクトを示す。
split_1_180829.png

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

No Error だった。2つのHLS ストリームが同一だった。

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

Estimated は 5.403 ns で十分だ。
Latency も 784 ピクセルなので、 790 クロックということは、ほとんど 1 ピクセル / クロックになっている。
リソース使用量は、FF が 106 個、LUT が 464 個だった。
次に、Detail -> Instance の grp_split_template_fu_186 をクリックして、split_template の結果を見てみよう。
split_4_180829.png

こちらのLatency は 789 クロックだった。
リソース使用量は、FF が 103 個、LUT が 341 個だった。

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

Latency は 795 クロックだった。

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

制御信号がほとんど一直線で良さそうだ。

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

CP achieved post-implementation も 4.348 ns で問題ない。
  1. 2018年08月30日 05:02 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Duplicate IP の製作1(ソースコード)

SqueezeNet4mnist をVivado HLSのテンプレートで実装しようと思ったときに、1つのHLSストリームを 2 つのHLSストリームに分ける Duplicate IP と 2 つのHLSストリームを 1 つのHLSストリームにする Concatenate IP が必要となる。
今回は、Duplicate IP を作ってみよう。

まずは、duplicate_template.h を貼っておく。

// duplicate_template.h
// 2018/08/23 by marsee
//

#ifndef __DUPLICATE_TEMPLATE_H___
#define __DUPLICATE_TEMPLATE_H___

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

#include "layer_general.h"

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

template<
    const size_t IN_W,     // 入力のビット幅、2つの入力のビット幅と小数点位置は合わせる
    const size_t IN_I,     // 入力の小数点位置
    const size_t NUMBER_OF_IN_CHANNELS,
    const size_t HORIZONTAL_PIXEL_WIDTH,
    const size_t VERTICAL_PIXEL_WIDTH
>int duplicate_template(hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> >&ins,
    hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> >&outs0,
    hls::stream<ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> >&outs1
){
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> in;
    ap_fixed_axis<IN_W,IN_I,NUMBER_OF_IN_CHANNELS,1> out;

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

    Loop_y : for(int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        Loop_x : for(int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE II=1
            if(!(x==0 && y==0)){
                ins >> in;
            }

            Loop_cp : for(int i=0; i<NUMBER_OF_IN_CHANNELS; i++){
                if(i<NUMBER_OF_IN_CHANNELS){
                    out.data[i] = in.data[i];
                }
            }

            out.user = in.user;
            out.last = in.last;

            outs0 << out;
            outs1 << out;
        }
    }
    return(0);
}

#endif


HLSストリームを 2 つのHLSストリームに分けるだけの記述になっている。

次に、duplicate_template.h をインスタンスする duplicate1.cpp を貼っておく。

// duplicate1.cpp
// 2018/08/23 by marsee
//

#include "duplicate_template.h"

int duplicate1(hls::stream<ap_fixed_axis<16,6,2,1> >& ins,
        hls::stream<ap_fixed_axis<16,6,2,1> >& outs0,
        hls::stream<ap_fixed_axis<16,6,2,1> >& outs1){
    return(duplicate_template<16,6,2,28,28>(ins, outs0, outs1));
}



最後にテストベンチの duplicate1_tb.cpp を貼っておく。

// duplicate1_tb.cpp
// 2018/08/23 by marsee
//

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

#include "layer_general.h"

static const size_t NW = 16;
static const size_t NI = 6;
static const size_t NUMBER_OF_KERNEL = 2;
static const size_t HORIZONTAL_PIXEL_WIDTH = 28;
static const size_t VERTICAL_PIXEL_WIDTH = 28;

int duplicate1(hls::stream<ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> >& ins,
        hls::stream<ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> >& outs0,
        hls::stream<ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> >& outs1);

int main(){
    using namespace std;

    hls::stream<ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> > ins;
    hls::stream<ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> > outs0;
    hls::stream<ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> > outs1;
    ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> in;
    ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> out0;
    ap_fixed_axis<NW,NI,NUMBER_OF_KERNEL,1> out1;

    typedef ap_fixed<NW,NI,AP_TRN,AP_WRAP> ap_fixed_type;

    // ins に入力データを用意する
    for(int i=0; i<5; i++){    // dummy data
        in.user = 0;
        for(int k=0; k<NUMBER_OF_KERNEL; k++){
            in.data[k] = ap_fixed_type(float(k+i)/100.0);
        }
        ins << in;
    }

    // 1 画面分のデータを insに入力する
    for(int j=0; j < VERTICAL_PIXEL_WIDTH; j++){
        for(int i=0; i < HORIZONTAL_PIXEL_WIDTH; i++){
            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                in.data[k] = ap_fixed_type((float)(k+HORIZONTAL_PIXEL_WIDTH*j+i)/100.0);
            }

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

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

    duplicate1(ins, outs0, outs1);

    // outs0 と outs1 を比較する
    int error = 0;
    for(int j=0; j < VERTICAL_PIXEL_WIDTH; j++){
        for(int i=0; i < HORIZONTAL_PIXEL_WIDTH; i++){
            outs0 >> out0;
            outs1 >> out1;
            for(int k=0; k<NUMBER_OF_KERNEL; k++){
                if(out0.data[k] != out1.data[k]){
                    printf("Error : out0.data[%d] = %f, out1.data[%d] = %f\n", k, float(out0.data[k]), k, float(out1.data[k]));
                    error = 1;
                }
            }

            if(j==0 && i==0){
                if(out0.user==1 && out1.user==1) ;
                else{
                    printf("j==0 && i==0 : out0.user = %d, out1.user = %d\n", out0.user, out1.user);
                    error = 1;
                }
            } else {
                if(out0.user==0 && out1.user==0) ;
                else{
                    printf("j!=0 || i!=0 : out0.user = %d, out1.user = %d\n", out0.user, out1.user);
                    error = 1;
                }
            }

            if(i == HORIZONTAL_PIXEL_WIDTH-1){
                if(out0.last==1 && out1.last==1) ;
                else{
                    printf("Horizontal last data : out0.last = %d, out1.last = %d\n", out0.last, out1.last);
                    error = 1;
                }
            }
        }
    }
    if(error == 0)
        printf("No Error\n");

    return(0);
}


テストベンチはHLSストリームのデータを用意して duplicate1 を呼び出して、出力された 2 つのHLSストリームの outs0, outs1 を比べて同一かどうか?をテストするテストベンチとなっている。
  1. 2018年08月29日 05:17 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

2つのHLSストリームを同時に入力して演算してHLSストリーム出力2

2つのHLSストリームを同時に入力して演算してHLSストリーム出力”の続き。

前回は、2つのHLSストリームを同時に入力して演算してHLSストリーム出力したいということで、コードを書いてみたが、スタート部分がシリアライズされていた。今回は、そこも並列動作させてみよう。

今回のソースコード two_hls_stream2.cpp を示す。

// two_hls_streams2.cpp
// 2018/08/07 by marsee
//

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

int ins_hls_stream(hls::stream<ap_axis<32,1,1,1> >& ins,
        hls::stream<ap_axis<32,1,1,1> >& outs);

int two_hls_streams(hls::stream<ap_axis<32,1,1,1> >& ins0,
        hls::stream<ap_axis<32,1,1,1> >& ins1,
        hls::stream<ap_axis<32,1,1,1> >& outs){
#pragma HLS DATAFLOW
    hls::stream<ap_axis<32,1,1,1> > out0;
    hls::stream<ap_axis<32,1,1,1> > out1;
    ap_axis<32,1,1,1> in_val0;
    ap_axis<32,1,1,1> in_val1;
    ap_axis<32,1,1,1> out_val;

    ins_hls_stream(ins0, out0);
    ins_hls_stream(ins1, out1);

    Loop1 : for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        out0 >> in_val0;
        out1 >> in_val1;

        out_val.data = in_val0.data * in_val1.data;
        out_val.user = in_val0.user;
        out_val.last = in_val0.last;
        outs << out_val;
    }
    return(0);
}

int ins_hls_stream(hls::stream<ap_axis<32,1,1,1> >& ins,
    hls::stream<ap_axis<32,1,1,1> >& outs){
    ap_axis<32,1,1,1> in_val;
    ap_axis<32,1,1,1> out_val;

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

    Loop2 : for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        if(i != 0){
            ins >> in_val;
        }
        out_val.data = in_val.data;
        out_val.user = in_val.user;
        out_val.last = in_val.last;
        outs << out_val;
    }
    return(0);

}


つまり、スタート部分を関数にして、2つの入力に対してそれぞれ関数をコールする。そして、その関数をDATAFLOW 指示子で並列動作されるというコンセプトだ。

まずは、この two_hls_stream2 プロジェクトを示す。
two_hls_streams_8_180808.png

これで C シミュレーションを行った。
two_hls_streams_9_180808.png

問題無い様だ。

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

Latency は 17 クロック、Interval は 15 クロックだった。前回のLatency は 16 クロックだった。
リソース使用量は、DSP48E は変わらないが、FF と LUT が増えている。特にLUT は 375 個だったところが、1185 個に増えた。

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

20 クロックだった。前回は 21 クロックだった。

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

スタート部分もシリアライズされていないで、並列動作しているようだ。
  1. 2018年08月09日 05:08 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

2つのHLSストリームを同時に入力して演算してHLSストリーム出力

2つのHLSストリームを同時に入力して演算してHLSストリーム出力したい。これは、2つの畳み込み演算を1つにまとめる機能を持ったIPをVivado HLSで作るために必要となる。
ただし、C やC++ は並列動作が想定されていないので、どうなるか分からない。

まずは、ソースファイルの two_hls_streams.cpp を示す。

// two_hls_streams.cpp
// 2018/08/07 by marsee
//

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

int two_hls_streams(hls::stream<ap_axis<32,1,1,1> >& ins0,
    hls::stream<ap_axis<32,1,1,1> >& ins1,
    hls::stream<ap_axis<32,1,1,1> >& outs){
    ap_axis<32,1,1,1> in_val0;
    ap_axis<32,1,1,1> in_val1;
    ap_axis<32,1,1,1> out_val;

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

    Loop2 : do {    // user が 1になった時にスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        ins1 >> in_val1;
    } while(in_val1.user == 0);

    Loop3 : for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        if(i != 0){
            ins0 >> in_val0;
            ins1 >> in_val1;
        }
        out_val.data = in_val0.data * in_val1.data;
        out_val.user = in_val0.user;
        out_val.last = in_val0.last;
        outs << out_val;
    }
    return(0);
}


ins0 のスタートを待った後に、ins1 のスタートを待って、ins0 と ins1 を乗算して、outs にHLSストリーム出力している。
次に、テストベンチの two_hls_streams_tb.cpp を示す。

// two_hls_streams_tb.cpp
// 2018/08/07 by marsee
//

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

int two_hls_streams(hls::stream<ap_axis<32,1,1,1> >& ins0,
    hls::stream<ap_axis<32,1,1,1> >& ins1,
    hls::stream<ap_axis<32,1,1,1> >& outs);

int main(){
    hls::stream<ap_axis<32,1,1,1> > ins0;
    hls::stream<ap_axis<32,1,1,1> > ins1;
    hls::stream<ap_axis<32,1,1,1> > outs;
    ap_axis<32,1,1,1> in_val0;
    ap_axis<32,1,1,1> in_val1;
    ap_axis<32,1,1,1> out_val;

    for(int i=0; i<2; i++){ // dummy
        in_val0.data = i;
        in_val0.user = 0;
        in_val0.last = 0;
        ins0 << in_val0;
    }

    for(int i=0; i<3; i++){ // dummy
        in_val1.data = i;
        in_val1.user = 0;
        in_val1.last = 0;
        ins1 << in_val1;
    }

    for(int i=0; i<10; i++){
        in_val0.data = i;
        in_val1.data = i+1;
        if(i == 0){
            in_val0.user = 1;
            in_val1.user = 1;
        }else{
            in_val0.user = 0;
            in_val1.user = 0;
        }
        if(i == 9){
            in_val0.last = 1;
            in_val1.last = 1;
        }else{
            in_val0.last = 0;
            in_val1.last = 0;
        }
        ins0 << in_val0;
        ins1 << in_val1;
    }

    two_hls_streams(ins0, ins1, outs);

    for(int i=0; i<10; i++){
        outs >> out_val;
        std::cout << "outs[" << i << "].data = " << out_val.data
                << " .user = " << out_val.user << " .last = " << out_val.last
                << std::endl;
    }

    return(0);
}


Vivado HLS 2018.2 の two_hls_streams プロジェクトを作成した。
two_hls_streams_1_180808.png

C シミュレーションを行った。
two_hls_streams_2_180808.png

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

Estimated は8.510 ns でLatency は 16 クロックだった。

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

Latency は 21 クロックだった。

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

ins0 のスタートを検出したら ins1 のスタートを検出している。つまり、シリアライズされているので、ここを並列動作させた方が良かな?
とりあえずは、演算本体部分はins0 と ins1 を同時に読んで演算を行っているようだ。
  1. 2018年08月08日 05:21 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0
»