FC2カウンター FPGAの部屋 2020年01月
fc2ブログ

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

FPGAの部屋

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

Vivado HLS 2019.2 で krnl_dma_write を作成する3

Vivado HLS 2019.2 で krnl_dma_write を作成する2”の続き。

Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続するためのDMA Write カーネルを作成したが、リソースを消費すぎていた。リソース消費を抑えようと for ループ内のPIPELINE 指示子を削除した。そうすると C コードの合成では、リソース使用量が減った様にレポートされたが、Export RTL したらリソース使用量が急激に増えてしまった。結局うまく行かなかった。その後の解析で、どうやら除算をしているようで、それがリソース使用量の大半を占めていることが分かった。
考えてみれば、DMA 数を設定できるということは、バーストの数を数えて行く必要がある、ということで除算を使用しているのだと思う。(私ならば減算していくが。。。)そこで、予めDMA 数をコンパイラが数えられるように定数にすれば、コンパイラが静的に解析できるはずだ。今回は X_SIZE と Y_SIZE を定数として与えてみよう。

krnl_dma_write2.cpp を示す。

// krnl_dma_write2.cpp
// 2020/01/30 by marsee

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

#define X_SIZE  64
#define Y_SIZE  48

extern "C" {
void dma_write2(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm){
#pragma HLS INTERFACE m_axi depth=3072 port=outm bundle=gmem
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=return bundle=control
    ap_axiu<32,0,0,0> pix;

    LOOP_DWY: for(int y=0; y<Y_SIZE; y++){
        LOOP_DWX: for(int x=0; x<X_SIZE; x++){
#pragma HLS PIPELINE II=1
            ins >> pix;
            outm[X_SIZE*y+x] = pix.data;
        }
    }
}
}


テストベンチの krnl_dma_write2_tb.cpp を示す。

// krnl_dma_write2_tb.cpp
// 2020/01/30 by marsee

#include <stdio.h>
#include <stdint.h>
#include "hls_opencv.h"
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

void dma_write2(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm);
void dma_write_soft(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        int32_t x_size, int32_t y_size);

const char INPUT_BMP_FILE[] = "test.bmp";
const char OUTPUT_BMP_FILE[] = "dma_write.bmp";

int main(){
    hls::stream<ap_axiu<32,0,0,0> > ins;
    hls::stream<ap_axiu<32,0,0,0> > ins_soft;
    ap_axiu<32,0,0,0> pix;

    // BMPファイルをMat に読み込む
    cv::Mat img = cv::imread(INPUT_BMP_FILE);

    // ピクセルを入れる領域の確保
    std::vector<int32_t> rd_bmp(sizeof(int32_t)*img.cols*img.rows);
    std::vector<int32_t> hw_dmaw(sizeof(int32_t)*(img.cols-2)*(img.rows-2));
    std::vector<int32_t> sw_dmaw(sizeof(int32_t)*(img.cols-2)*(img.rows-2));

    // rd_bmp にBMPのピクセルを代入
    cv::Mat_<cv::Vec3b> dst_vec3b = cv::Mat_<cv::Vec3b>(img);
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            cv::Vec3b pixel;
            pixel = dst_vec3b(y,x);
            rd_bmp[y*img.cols+x] = (pixel[0] & 0xff) | ((pixel[1] & 0xff)<<8) | ((pixel[2] & 0xff)<<16);
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

    // ins に入力データを用意する
    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (ap_int<32>)rd_bmp[(j*img.cols)+i];

            if ((i==img.cols-1) && (j==img.rows-1)) // フレームの最後で last をアサートする
                pix.last = 1;
            else
                pix.last = 0;

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

    dma_write2(ins, hw_dmaw.data());
    dma_write_soft(ins_soft, sw_dmaw.data(), img.cols, img.rows);

    // ハードウェアとソフトウェアの dma_write の値のチェック
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            if (hw_dmaw[y*img.cols+x] != sw_dmaw[y*img.cols+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, hw_dmaw[y*img.cols+x], sw_dmaw[y*img.cols+x]);
                return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    const int dmaw_rows = img.rows;
    const int dmaw_cols = img.cols;
    cv::Mat wbmpf(dmaw_rows, dmaw_cols, CV_8UC3);
    // wbmpf にラプラシアンフィルタ処理後の画像を入力
    cv::Mat_<cv::Vec3b> lap_vec3b = cv::Mat_<cv::Vec3b>(wbmpf);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = lap_vec3b(y,x);
            int32_t rgb = hw_dmaw[y*wbmpf.cols+x];
            pixel[0] = (rgb & 0xff); // blue
            pixel[1] = (rgb & 0xff00) >> 8; // green
            pixel[2] = (rgb & 0xff0000) >> 16; // red
            lap_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのラプラシアンフィルタの結果を bmp ファイルへ出力する
    cv::imwrite(OUTPUT_BMP_FILE, wbmpf);

    return(0);
}

void dma_write_soft(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        int32_t x_size, int32_t y_size){
    ap_axiu<32,0,0,0> pix;

    LOOP_DWY: for(int y=0; y<y_size; y++){
        LOOP_DWX: for(int x=0; x<x_size; x++){
            ins >> pix;
            outm[x_size*y+x] = pix.data;
        }
    }
}


Vivado HLS 2019.2 で krnl_dma_write2 プロジェクトを作成した。
streaming_kernel_67_200130.png

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

dma_write.bmp が生成されている。成功だ。
streaming_kernel_69_200130.png

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

リソース使用量は FF で 1 % 以下で、LUT で 1% だった。

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

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

WVALID も TREADY もほとんど 1 を保っている。スループットが高そうだ。

Export RTL を行った。
streaming_kernel_73_200130.png

LUT は 361 個, FF は 542 個と少ない。

dma_write2.xo ファイルも生成されている。
streaming_kernel_74_200130.png

AXI4 Master を使用する場合に DMA 数を動的に設定できると、設定された数がDMA バースト何個に相当するかを演算する必要がある。最後のバーストは数を変える必要があるかも知れない?それを動的にする必要があるため除算しているのだろう?(私は減算するが。。。)
一方、DMA 数をコードに記述してしまえば、コンパイラが解析できて、DMA 数をHDL コードの埋め込むことができるようになる。結果、回路が簡単になり、リソース使用量が減る。
Vivado HLS で AXI4 Master を使用する場合には、なるべく DMA 数を決め打ちにしておいたほうがリソース使用量が減って、スループットを上げやすくなる。DMA 数が変わったら、C ソースコードのDMA 数を変更してもう一度 Vivado HLS で合成すれば良いのである。。。

これが AXI4 Stream の場合は、データ転送数が可変でも、バーストという決まった数の転送を複数回する方法ではなく、バースト数の規定のない無限連続バーストであるため、データ転送数が可変でも問題ない。現にラプラシアン・フィルタはデータ転送数が可変だが、リソース使用量は少ない。
Vivado HLS 2019.2 で krnl_lap_filter を作成する1(ソースコードの表示)”、”Vivado HLS 2019.2 で krnl_lap_filter を作成する2(IP化)”を参照ください。
  1. 2020年01月31日 04:39 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_dma_write を作成する2

Vivado HLS 2019.2 で krnl_dma_write を作成する1”の続き。

Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続するために、前回は DMA Write カーネルを作成したのだが、リソースを消費すぎているのが分かった。今回は、リソース使用量を少なくするように努力してみよう。

PIPELINE 指示子を削除してみた。
streaming_kernel_50_200129.png

この状態で C コードの合成を行った。
streaming_kernel_51_200129.png

合成後の結果は、LUT が 1 % 程度と約 1/20 になった。

C/RTL 協調シミュレーションを行った。Latency は 3828 クロックだった。総ピクセル数は 3072 ピクセルなので、3828 / 3072 ≒ 1.25 クロック / ピクセルになる。あまり性能良くないな。。。
streaming_kernel_52_200129.png

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

AWLEN は 0f で 16 バーストであることが分かる。これは問題ない。WVALID を見ると、 0 に落ちている時間が目立つ。これが問題のようだ。
拡大してみよう。
streaming_kernel_54_200129.png

ストリーム入力も同様にTREADY が落ちているので、これで見てみよう。TREADY の周期は、395 ns となった。
streaming_kernel_55_200129.png

TREDY が 0 に落ちている間は、75 ns なので、395 / ( 395 - 75 ) ≒ 1.23 クロック / ピクセルということになった。先程の C/RTL 協調シミュレーションのクロック / ピクセルの値には初期の設定部分も入っているため、大体合っていると思う。

extern "C" { } を戻して、もう一度、C コードの合成を行って、Export RTL を行った。
streaming_kernel_56_200129.png

Export RTL の結果を示す。
streaming_kernel_57_200129.png

LUT が 11939 個で、FF が 10866 個になっていた。Vivado HLS の見積もりとは違いすぎる。

また、PIPELINE 指示子を有効にしてやってみた。
streaming_kernel_58_200129.png

C コードの合成を行って、C/RTL 協調シミュレーションを行った。
streaming_kernel_59_200129.png

こちらは、 3404 クロックだった。 1.11 クロック / ピクセルとなった。

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

WVALID や TREADY の落ち幅がこちらのほうが少ない。
波形を拡大してみよう。
streaming_kernel_61_200129.png

TREADY の周期は、340 ns だった。
streaming_kernel_62_200129.png

0 に落ちている幅は 20 ns で 4 クロックだった。 340 / (340 - 20) ≒ 1.06 クロック / ピクセルだった。

なぜにこんなにリソースを消費しているのか?だが。
Analysis 画面で見るとステートが 75 ステートあるのだ。
streaming_kernel_64_200130.png

そして、合成で生成された Verilog HDL ファイルの内の dma_write_urem_64bkb.v を見ると、何やら除算しているような?
32 回ループを回っているようだ。
streaming_kernel_65_200130.png

Analysis の Resource Profile を見ても、 dma_write_urem_64bkb が 2 個入っていて、殆どのリソースを消費している。
streaming_kernel_66_200130.png

でも何で除算なのだろう?引き算で行けるんじゃないだろうか?
dma_read も dme_write もラプラシアン・フィルタと違って、AXI4 Master があるので、任意のピクセル数となるとダイナミックに剰余を出したいのかも知れない?

次回は急遽、その解決方法を書いていきたい。
  1. 2020年01月30日 04:12 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_dma_write を作成する1

Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続してみよう。
今回は、DMA Write カーネルを作成しよう。

ストーミング接続用のhls::stream の定義は、hls::stream > にする必要がある。こうするとサイドチャネルの信号は last と keep , strb だけになる。この内の last を使用して、フレームの最後をマークしよう。
ストーミング接続用 DMA Write カーネルの krnl_dma_write.cpp を示す。
なお、C シミュレーションできないので、とりあえず extern "C" { } は外してある。

// krnl_dma_write.cpp
// 2020/01/28 by marsee

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

//extern "C" {
void dma_write(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        int32_t x_size, int32_t y_size){
#pragma HLS INTERFACE m_axi depth=3072 port=outm bundle=gmem
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=y_size bundle=control
#pragma HLS INTERFACE s_axilite port=x_size bundle=control
#pragma HLS INTERFACE s_axilite port=return bundle=control
    ap_axiu<32,0,0,0> pix;

    LOOP_DWY: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_DWX: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            ins >> pix;
            outm[x_size*y+x] = pix.data;
        }
    }
}
//}


テストベンチの krnl_dma_write_tb.cpp を示す。

// krnl_dma_write_tb.cpp
// 2020/01/28 by marsee

#include <stdio.h>
#include <stdint.h>
#include "hls_opencv.h"
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

void dma_write(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        int32_t x_size, int32_t y_size);
void dma_write_soft(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        int32_t x_size, int32_t y_size);

const char INPUT_BMP_FILE[] = "test.bmp";
const char OUTPUT_BMP_FILE[] = "dma_write.bmp";

int main(){
    hls::stream<ap_axiu<32,0,0,0> > ins;
    hls::stream<ap_axiu<32,0,0,0> > ins_soft;
    ap_axiu<32,0,0,0> pix;

    // BMPファイルをMat に読み込む
    cv::Mat img = cv::imread(INPUT_BMP_FILE);

    // ピクセルを入れる領域の確保
    std::vector<int32_t> rd_bmp(sizeof(int32_t)*img.cols*img.rows);
    std::vector<int32_t> hw_dmaw(sizeof(int32_t)*(img.cols-2)*(img.rows-2));
    std::vector<int32_t> sw_dmaw(sizeof(int32_t)*(img.cols-2)*(img.rows-2));

    // rd_bmp にBMPのピクセルを代入
    cv::Mat_<cv::Vec3b> dst_vec3b = cv::Mat_<cv::Vec3b>(img);
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            cv::Vec3b pixel;
            pixel = dst_vec3b(y,x);
            rd_bmp[y*img.cols+x] = (pixel[0] & 0xff) | ((pixel[1] & 0xff)<<8) | ((pixel[2] & 0xff)<<16);
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

    // ins に入力データを用意する
    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (ap_int<32>)rd_bmp[(j*img.cols)+i];

            if ((i==img.cols-1) && (j==img.rows-1)) // フレームの最後で last をアサートする
                pix.last = 1;
            else
                pix.last = 0;

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

    dma_write(ins, hw_dmaw.data(), img.cols, img.rows);
    dma_write_soft(ins_soft, sw_dmaw.data(), img.cols, img.rows);

    // ハードウェアとソフトウェアの dma_write の値のチェック
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            if (hw_dmaw[y*img.cols+x] != sw_dmaw[y*img.cols+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, hw_dmaw[y*img.cols+x], sw_dmaw[y*img.cols+x]);
                return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    const int dmaw_rows = img.rows;
    const int dmaw_cols = img.cols;
    cv::Mat wbmpf(dmaw_rows, dmaw_cols, CV_8UC3);
    // wbmpf にラプラシアンフィルタ処理後の画像を入力
    cv::Mat_<cv::Vec3b> lap_vec3b = cv::Mat_<cv::Vec3b>(wbmpf);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = lap_vec3b(y,x);
            int32_t rgb = hw_dmaw[y*wbmpf.cols+x];
            pixel[0] = (rgb & 0xff); // blue
            pixel[1] = (rgb & 0xff00) >> 8; // green
            pixel[2] = (rgb & 0xff0000) >> 16; // red
            lap_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのラプラシアンフィルタの結果を bmp ファイルへ出力する
    cv::imwrite(OUTPUT_BMP_FILE, wbmpf);

    return(0);
}

void dma_write_soft(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        int32_t x_size, int32_t y_size){
    ap_axiu<32,0,0,0> pix;

    LOOP_DWY: for(int y=0; y<y_size; y++){
        LOOP_DWX: for(int x=0; x<x_size; x++){
            ins >> pix;
            outm[x_size*y+x] = pix.data;
        }
    }
}


Vivado HLS 2019.2 で krnl_dma_write プロジェクトを作成した。
streaming_kernel_48_200129.png

C シミュレーションを実行した。
streaming_kernel_48_200129.png

dma_write.bmp が生成された。問題無さそうだ。
streaming_kernel_63_200129.png

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

LUT が 20 % も消費されている。リソースを消費すぎているのではないだろうか?
  1. 2020年01月29日 05:02 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_lap_filter を作成する2(IP化)

Vivado HLS 2019.2 で krnl_lap_filter を作成する1(ソースコードの表示)”の続き。

前回は、ラプラシアン・フィルタ・カーネル krnl_lap_filter を作成するということで、ソースコードを貼った。今回は、Vivado HLS 2019.2 のプロジェクトを作成して、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行っていこう。

Vivado HLS 2019.2 で krnl_lap_filter プロジェクトを作成した。
streaming_kernel_38_200127.png

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

C シミュレーション後には、lap.bmp が生成されていた。
streaming_kernel_40_200127.png

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

Latency の min は 3089 クロックだった。総ピクセル数が 3072 ピクセルなので、ほとんど 1 クロック/ピクセルとなっている。

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

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

outs_TVALID や ins_TREADY がほとんど 1 になっていて、スループットが高いことが分かる。

xo ファイルを生成するために extern "C" { } のコメントを外した。
streaming_kernel_44_200127.png

もう一度 C コードの合成を行ってから、Export RTL を行った。結果を示す。
streaming_kernel_45_200127.png

CP achieved post-implementation も 3.344 ns で問題無さそうだ。
krnl_lap_filter.xo ファイルが生成された。
streaming_kernel_46_200127.png
  1. 2020年01月28日 04:38 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_lap_filter を作成する1(ソースコードの表示)

Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続してみよう。
今回は、ラプラシアン・フィルタ・カーネル krnl_lap_filter を作成する。

ストーミング接続用のhls::stream の定義は、hls::stream > にする必要がある。こうするとサイドチャネルの信号は last と keep , strb だけになる。この内の last を使用して、フレームの最後をマークしよう。
ストーミング接続用ラプラシアン・フィルタ・カーネルの krnl_lap_filter.cpp を示す。
なお、今回から、絶対値を取って、マイナスのエッジも表示するようにソースコードを変更した。
前回までは、ラプラシアン・フィルタを書ける前にフレームに同期していたが、今回は user 信号が無くなっているため、ラプラシアン・フィルタ後に last 信号で同期している。こうすると、最初のラプラシアン・フィルタは不成功でも last で同期できるので、その次のフレームは成功するようになる。

// krnl_lap_filter.cpp
// 2020/01/26 by marsse

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

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int32_t conv_rgb2y(int32_t rgb){
    int32_t r, g, b, y_f;
    int32_t y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8; // 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int32_t laplacian_fil(int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1,
        int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2)
{
    int32_t y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = -y;
    else if (y>255)
        y = 255;
    return(y);
}

//extern "C" {
void krnl_lap_filter(hls::stream<ap_axiu<32,0,0,0> >& ins, hls::stream<ap_axiu<32,0,0,0> >& outs,
        int32_t x_size, int32_t y_size){
#pragma HLS INTERFACE s_axilite port=y_size bundle=control
#pragma HLS INTERFACE s_axilite port=x_size bundle=control
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=return bundle=control
    ap_axiu<32,0,0,0> pix;
    ap_axiu<32,0,0,0> lap;

    int32_t line_buf[2][1920]; // supported HD resolution
#pragma HLS array_partition variable=line_buf block factor=2 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    int32_t pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

    int32_t lap_fil_val;

    LOOP_X : for (int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_Y : for (int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            ins >> pix; // AXI4-Stream からの入力

            Loop4 : for (int k=0; k<3; k++){
                Loop5 : for (int m=0; m<2; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int32_t y_val = conv_rgb2y(pix.data);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                            pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                            pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap.data = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2) // 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap.data = 0;

            if (x==(x_size-1) && y==(y_size-1)) // フレームの最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            outs << lap;    // ストリームへ出力
        }
    }

    LOOP_WAIT_LAST: while(pix.last == 0) { // last が 1 になるまで待つ
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        ins >> pix;
    };
}
//}


次に、テストベンチの krnl_lap_filter_tb.cpp を示す。
今回は、OpenCV を使用している。Linux を使用している場合で Vivado HLS の C シミュレーションの時に warning: libjpeg.so.62 が出てしまう方は、”Ubuntu 16.04 上のVivado HLS 2017.2 でOpenCV を使用したプロジェクトでエラー発生”を参照ください。

// krnl_lap_filter_tb.cpp
// 2020/01/26 by marsee

#include <stdio.h>
#include <stdint.h>
#include "hls_opencv.h"
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

void krnl_lap_filter(hls::stream<ap_axiu<32,0,0,0> >& ins, hls::stream<ap_axiu<32,0,0,0> >& outs,
        int32_t x_size, int32_t y_size);

void krnl_lap_filter_soft(hls::stream<ap_axiu<32,0,0,0> >& ins, hls::stream<ap_axiu<32,0,0,0> >& outs,
        int32_t x_size, int32_t y_size);

const char INPUT_BMP_FILE[] = "test.bmp";
const char OUTPUT_BMP_FILE[] = "lap.bmp";

int main(){
    hls::stream<ap_axiu<32,0,0,0> > ins;
    hls::stream<ap_axiu<32,0,0,0> > ins_soft;
    hls::stream<ap_axiu<32,0,0,0> > outs;
    hls::stream<ap_axiu<32,0,0,0> > outs_soft;
    ap_axiu<32,0,0,0> pix;
    ap_axiu<32,0,0,0> vals, vals_soft;

    // BMPファイルをMat に読み込む
    cv::Mat img = cv::imread(INPUT_BMP_FILE);

    // ピクセルを入れる領域の確保
    std::vector<int32_t> rd_bmp(sizeof(int32_t)*img.cols*img.rows);
    std::vector<int32_t> hw_lap(sizeof(int32_t)*(img.cols-2)*(img.rows-2));
    std::vector<int32_t> sw_lap(sizeof(int32_t)*(img.cols-2)*(img.rows-2));

    // rd_bmp にBMPのピクセルを代入
    cv::Mat_<cv::Vec3b> dst_vec3b = cv::Mat_<cv::Vec3b>(img);
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            cv::Vec3b pixel;
            pixel = dst_vec3b(y,x);
            rd_bmp[y*img.cols+x] = (pixel[0] & 0xff) | ((pixel[1] & 0xff)<<8) | ((pixel[2] & 0xff)<<16);
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

    // ins に入力データを用意する
    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (ap_int<32>)rd_bmp[(j*img.cols)+i];

            if ((i==img.cols-1) && (j==img.rows-1)) // フレームの最後で last をアサートする
                pix.last = 1;
            else
                pix.last = 0;

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

    krnl_lap_filter(ins, outs,img.cols, img.rows);  // ハードウェアのラプラシアンフィルタ
    krnl_lap_filter_soft(ins_soft, outs_soft,img.cols, img.rows);   // ソフトウェアのラプラシアンフィルタ

    // ハードウェアとソフトウェアのラプラシアンフィルタの値のチェック
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            ap_int<32> val = vals.data;
            hw_lap[y*img.cols+x] = (int32_t)val;
            outs_soft >> vals_soft;
            ap_int<32> val_soft = vals_soft.data;
            if (val != val_soft){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, val, val_soft);
                return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    const int lap_rows = img.rows;
    const int lap_cols = img.cols;
    cv::Mat wbmpf(lap_rows, lap_cols, CV_8UC3);
    // wbmpf にラプラシアンフィルタ処理後の画像を入力
    cv::Mat_<cv::Vec3b> lap_vec3b = cv::Mat_<cv::Vec3b>(wbmpf);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = lap_vec3b(y,x);
            int32_t rgb = hw_lap[y*wbmpf.cols+x];
            pixel[0] = (rgb & 0xff); // blue
            pixel[1] = (rgb & 0xff00) >> 8; // green
            pixel[2] = (rgb & 0xff0000) >> 16; // red
            lap_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのラプラシアンフィルタの結果を bmp ファイルへ出力する
    cv::imwrite(OUTPUT_BMP_FILE, wbmpf);

    return(0);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int32_t conv_rgb2y_soft(int32_t rgb){
    int32_t r, g, b, y_f;
    int32_t y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8; // 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int32_t laplacian_fil_soft(int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1,
        int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2)
{
    int32_t y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = -y;
    else if (y>255)
        y = 255;
    return(y);
}

void krnl_lap_filter_soft(hls::stream<ap_axiu<32,0,0,0> >& ins, hls::stream<ap_axiu<32,0,0,0> >& outs,
        int32_t x_size, int32_t y_size){
    ap_axiu<32,0,0,0> pix;
    ap_axiu<32,0,0,0> lap;

    int32_t line_buf[2][1920]; // supported HD resolution

    int32_t pix_mat[3][3];
    int32_t lap_fil_val;

    LOOP_X : for (int y=0; y<y_size; y++){
        LOOP_Y : for (int x=0; x<x_size; x++){
            ins >> pix; // AXI4-Stream からの入力

            Loop4 : for (int k=0; k<3; k++){
                Loop5 : for (int m=0; m<2; m++){
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int32_t y_val = conv_rgb2y_soft(pix.data);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil_soft(   pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                                pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                                pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap.data = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2) // 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap.data = 0;

            if (x==(x_size-1) && y==(y_size-1)) // フレームの最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            outs << lap;    // ストリームへ出力
        }
    }

    LOOP_WAIT_LAST: while(pix.last == 0) { // last が 1 になるまで待つ
        ins >> pix;
    };
}

  1. 2020年01月27日 05:11 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

square root を Vivado HLS で実装する3

square root を Vivado HLS で実装する2”の続き。

前回は、square root を Vivado HLS で実装しようということで、逐次比較型ADコンバータのやり方を真似したsquare root を求めるソースコードを作って、C コードの合成とExport RTL を行った。しかし、使い物にならないということが分かった。前回までの square root ソースコードは、square root するビット長を可変にしていた。しかし、実際は、Sobel フィルタで使用するので、高々、結果が 8 ビット長なので、ビット長を 8 ビットに決め打ちにした。Vivado HLS もループ長を固定値にしたほうが最適化しやすいので、これでやってみよう。

ビット長を 8 ビットにした square_root8.cpp を示す。

// square_root8.cpp
// 2020/01/22 by marsee

#include <stdint.h>

int square_root8(int32_t val, int32_t *result){
#pragma HLS PIPELINE II=1
    int32_t temp = 0;
    int32_t square;

    for(int i=7; i>=0; --i){
        temp += (1 << i);
        square = temp * temp;

        if(square > val){
            temp -= (1 << i);
        }
    }
    *result = int32_t(temp);

    return(0);
}


テストベンチの square_root8_tb.cpp を示す。

// square_root8_tb.cpp
// 2020/01/22 by marsee

#include <stdio.h>
#include <stdint.h>

int square_root8(int32_t val, int32_t *result);

int main(){
    int64_t val;
    int32_t result;
    int32_t bit_len;

    for(int i=0; i<256; i++){
        square_root8(i, &result);
        printf("i = %d, result = %d\n", i, result);
    }

    int i = 255*255+1;
    square_root8(i, &result);
    printf("i = %d, result = %d\n", i, result);

    return(0);
}


Vivado HLS 2019.2 の square_root8 プロジェクトを作成した。
square_root_7_200124.png

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

問題ない。前回と同じだ。

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

Interval を見ると 1 クロックになっている。パイプラインはこうじゃないと。。。

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

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

result_ap_vld が途中からずっと 1 なのが良いですね。切れ目なくデータ出ている。

最初の部分を拡大してみたが、result の値も問題ない。C シミュレーションで確認しているが。。。
square_root_12_200124.png

Export RTL を行った。タイミング的にも問題なかった。
square_root_13_200124.png

このように、結果のビット長を 8 ビット決め打ちにすれば、問題なくなった。このように、Vivado HLS でソースコードを作る時にいろいろなパラメータを可変にしていると、Vivado HLS のコンパイラの制約が厳しくなって、性能が取れないことがある。その場合は、決め打ちにできるパラメータを決定してコンパイラの制約をゆるくしてみると結果が改善することがある。

パラメータを変更したくなったら、またVivado HLS でコンパイルすれば良いじゃない。。。

(追加)
ソースコードをもう一例書いてみましたが、結果は上のソースコードと全く一緒でした。

// square_root8.cpp
// 2020/01/22 by marsee

#include <stdint.h>

int square_root8(int32_t val, int32_t *result){
#pragma HLS PIPELINE II=1
    int32_t temp = 0;
    int32_t tempn = 0;
    int32_t square;

    for(int i=7; i>=0; --i){
        tempn = temp + (1 << i);
        square = tempn * tempn;

        if(square <= val){
            temp = tempn ;
        }
    }
    *result = int32_t(temp);

    return(0);
}

  1. 2020年01月25日 04:31 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

square root を Vivado HLS で実装する2

square root を Vivado HLS で実装する1”の続き。

square root を Vivado HLS で実装しようということで、逐次比較型ADコンバータのやり方を真似したsquare root を求めるソースコードを作って、前回貼った。そして、テストベンチのソースコードも貼って、C シミュレーションを行った。
今回は、C コードの合成を行って、Export RTL で Vivado のインプリメンテーションの結果を見てみよう。

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

Latency が大きいので、for 文に PIPELINE 指示子を入れてみよう。
square_root_4_200123.png

これでもう一度、C コードの合成を行った。結果をもう一度示す。
square_root_5_200123.png

PIPELINE 指示子を入れているのに Interval が min が 4 クロックで、max が 19 クロックだった。これでは 1 クロック毎にパイプラインしているとは言えない。。。
次回で何とか解決方法を考える。

Export RTL を行った。
square_root_6_200123.png

タイミングがメットしていない。
  1. 2020年01月24日 04:44 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

square root を Vivado HLS で実装する1

square root を Vivado HLS で実装しようと思う。厳密な square root ではなく、結果が 8 ビットの整数で表されればよい。つまり、 Sobel フィルタを実装したいので、x と y を 2 乗して足して、square Root したいので、RGB の値の範囲で square root できれば十分なのだ。

アルゴリズムを考えていたら、ちょうど逐次比較型ADコンバータが思い浮かんだ。これは、MSB からビットを立てながら、入力値と比較して、大きい場合はビットをクリアし、まだ小さい値ならばビットをそのままにする。そして、その下のビットを立てながら同じことをやっていく。
square root もそれが使えるんじゃないだろうか?ということで、逐次比較型ADコンバータと同様に square root を計算するように作ったソースコードの square_root.cpp を示す。

// square_root.cpp
// 2020/01/22 by marsee

#include <stdint.h>

int square_root(int64_t val, int32_t *result, int32_t bit_len){
    int64_t temp = 0;
    int64_t square;

    for(int i=(bit_len-1); i>=0; --i){
#pragma HLS LOOP_TRIPCOUNT min=1 max=16 avg=7
        temp += (1 << i);
        square = temp * temp;

        if(square > val){
            temp -= (1 << i);
        }
    }
    *result = int32_t(temp);

    return(0);
}


テストベンチの square_root_tb.cpp を示す。

// square_root_tb.cpp
// 2020/01/22 by marsee

#include <stdio.h>
#include <stdint.h>

int square_root(int64_t val, int32_t *result, int32_t bit_len);

int main(){
    int64_t val;
    int32_t result;
    int32_t bit_len;

    for(int i=0; i<256; i++){
        square_root(i, &result, 8);
        printf("i = %d, result = %d\n", i, result);
    }

    int i=255*255+1;
    square_root(i, &result, 8);
    printf("i = %d, result = %d\n", i, result);

    return(0);
}


Vivado HLS 2019.2 で ZYBO Z7-10 用に square_root プロジェクトを作成した。
square_root_1_200123.png

C シミュレーションを行った。うまく行っているようだ。
square_root_2_200123.png

すべての結果を示す。

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
   Compiling ../../../square_root_tb.cpp in debug mode
   Generating csim.exe
i = 0, result = 0
i = 1, result = 1
i = 2, result = 1
i = 3, result = 1
i = 4, result = 2
i = 5, result = 2
i = 6, result = 2
i = 7, result = 2
i = 8, result = 2
i = 9, result = 3
i = 10, result = 3
i = 11, result = 3
i = 12, result = 3
i = 13, result = 3
i = 14, result = 3
i = 15, result = 3
i = 16, result = 4
i = 17, result = 4
i = 18, result = 4
i = 19, result = 4
i = 20, result = 4
i = 21, result = 4
i = 22, result = 4
i = 23, result = 4
i = 24, result = 4
i = 25, result = 5
i = 26, result = 5
i = 27, result = 5
i = 28, result = 5
i = 29, result = 5
i = 30, result = 5
i = 31, result = 5
i = 32, result = 5
i = 33, result = 5
i = 34, result = 5
i = 35, result = 5
i = 36, result = 6
i = 37, result = 6
i = 38, result = 6
i = 39, result = 6
i = 40, result = 6
i = 41, result = 6
i = 42, result = 6
i = 43, result = 6
i = 44, result = 6
i = 45, result = 6
i = 46, result = 6
i = 47, result = 6
i = 48, result = 6
i = 49, result = 7
i = 50, result = 7
i = 51, result = 7
i = 52, result = 7
i = 53, result = 7
i = 54, result = 7
i = 55, result = 7
i = 56, result = 7
i = 57, result = 7
i = 58, result = 7
i = 59, result = 7
i = 60, result = 7
i = 61, result = 7
i = 62, result = 7
i = 63, result = 7
i = 64, result = 8
i = 65, result = 8
i = 66, result = 8
i = 67, result = 8
i = 68, result = 8
i = 69, result = 8
i = 70, result = 8
i = 71, result = 8
i = 72, result = 8
i = 73, result = 8
i = 74, result = 8
i = 75, result = 8
i = 76, result = 8
i = 77, result = 8
i = 78, result = 8
i = 79, result = 8
i = 80, result = 8
i = 81, result = 9
i = 82, result = 9
i = 83, result = 9
i = 84, result = 9
i = 85, result = 9
i = 86, result = 9
i = 87, result = 9
i = 88, result = 9
i = 89, result = 9
i = 90, result = 9
i = 91, result = 9
i = 92, result = 9
i = 93, result = 9
i = 94, result = 9
i = 95, result = 9
i = 96, result = 9
i = 97, result = 9
i = 98, result = 9
i = 99, result = 9
i = 100, result = 10
i = 101, result = 10
i = 102, result = 10
i = 103, result = 10
i = 104, result = 10
i = 105, result = 10
i = 106, result = 10
i = 107, result = 10
i = 108, result = 10
i = 109, result = 10
i = 110, result = 10
i = 111, result = 10
i = 112, result = 10
i = 113, result = 10
i = 114, result = 10
i = 115, result = 10
i = 116, result = 10
i = 117, result = 10
i = 118, result = 10
i = 119, result = 10
i = 120, result = 10
i = 121, result = 11
i = 122, result = 11
i = 123, result = 11
i = 124, result = 11
i = 125, result = 11
i = 126, result = 11
i = 127, result = 11
i = 128, result = 11
i = 129, result = 11
i = 130, result = 11
i = 131, result = 11
i = 132, result = 11
i = 133, result = 11
i = 134, result = 11
i = 135, result = 11
i = 136, result = 11
i = 137, result = 11
i = 138, result = 11
i = 139, result = 11
i = 140, result = 11
i = 141, result = 11
i = 142, result = 11
i = 143, result = 11
i = 144, result = 12
i = 145, result = 12
i = 146, result = 12
i = 147, result = 12
i = 148, result = 12
i = 149, result = 12
i = 150, result = 12
i = 151, result = 12
i = 152, result = 12
i = 153, result = 12
i = 154, result = 12
i = 155, result = 12
i = 156, result = 12
i = 157, result = 12
i = 158, result = 12
i = 159, result = 12
i = 160, result = 12
i = 161, result = 12
i = 162, result = 12
i = 163, result = 12
i = 164, result = 12
i = 165, result = 12
i = 166, result = 12
i = 167, result = 12
i = 168, result = 12
i = 169, result = 13
i = 170, result = 13
i = 171, result = 13
i = 172, result = 13
i = 173, result = 13
i = 174, result = 13
i = 175, result = 13
i = 176, result = 13
i = 177, result = 13
i = 178, result = 13
i = 179, result = 13
i = 180, result = 13
i = 181, result = 13
i = 182, result = 13
i = 183, result = 13
i = 184, result = 13
i = 185, result = 13
i = 186, result = 13
i = 187, result = 13
i = 188, result = 13
i = 189, result = 13
i = 190, result = 13
i = 191, result = 13
i = 192, result = 13
i = 193, result = 13
i = 194, result = 13
i = 195, result = 13
i = 196, result = 14
i = 197, result = 14
i = 198, result = 14
i = 199, result = 14
i = 200, result = 14
i = 201, result = 14
i = 202, result = 14
i = 203, result = 14
i = 204, result = 14
i = 205, result = 14
i = 206, result = 14
i = 207, result = 14
i = 208, result = 14
i = 209, result = 14
i = 210, result = 14
i = 211, result = 14
i = 212, result = 14
i = 213, result = 14
i = 214, result = 14
i = 215, result = 14
i = 216, result = 14
i = 217, result = 14
i = 218, result = 14
i = 219, result = 14
i = 220, result = 14
i = 221, result = 14
i = 222, result = 14
i = 223, result = 14
i = 224, result = 14
i = 225, result = 15
i = 226, result = 15
i = 227, result = 15
i = 228, result = 15
i = 229, result = 15
i = 230, result = 15
i = 231, result = 15
i = 232, result = 15
i = 233, result = 15
i = 234, result = 15
i = 235, result = 15
i = 236, result = 15
i = 237, result = 15
i = 238, result = 15
i = 239, result = 15
i = 240, result = 15
i = 241, result = 15
i = 242, result = 15
i = 243, result = 15
i = 244, result = 15
i = 245, result = 15
i = 246, result = 15
i = 247, result = 15
i = 248, result = 15
i = 249, result = 15
i = 250, result = 15
i = 251, result = 15
i = 252, result = 15
i = 253, result = 15
i = 254, result = 15
i = 255, result = 15
i = 65026, result = 255
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************

  1. 2020年01月23日 04:58 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_dma_read を作成する2(IP 化)

Vivado HLS 2019.2 で krnl_dma_read を作成する1(ソースコードの表示)”の続き。

前回は、Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続してみようということで、最初にDMA Read を作ることにした。そして、ソースコードを貼った。今回は、それらのソースコードを使用して、Vivado HLS 2019.2 で C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行った。

Vivado HLS 2019.2 で Krnl_dma_read プロジェクトを作成した。その際に New Vivado HLS Project のダイアログで Vitis Bottom Up Flow にチェックを入れた。
streaming_kernel_34_200122.png

Source に krnl_dma_read.cpp を入れて、Test Bench に bmp_header.h, krnl_dma_read_tb.cpp, test.bmp を入れた。
streaming_kernel_35_200122.png

最初に C シミュレーションを行った。
streaming_kernel_27_200121.png

dma_read.bmp が生成されていた。成功だ。
streaming_kernel_30_200121.png

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

Latency の min の 3153 クロック / 3072 ピクセル ≒ 1.03 クロック/ピクセルだった。

C/RTL 協調シミュレーションを行った。Latency は 3488 クロックだった。
streaming_kernel_29_200121.png

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

outs_TVALID の波形が途中で切れ気味だが、だいたいOKだろう。

ここで、krnl_dma_read.cpp の extern "C" { } のコメントアウトを外して C コードの合成を行った。
引き続きExport RTL を行った。結果を示す。
streaming_kernel_32_200121.png

dma_read.xo もできた。
streaming_kernel_36_200122.png

とりあえず、krnl_dma_read.cpp を使用して、Vitis のカーネルのストーミング接続をテストするが、その後で、 xo ファイルを使用して、Vitis のカーネルのストーミング接続をテストしてみよう。
  1. 2020年01月22日 05:01 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_dma_read を作成する1(ソースコードの表示)

Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続してみよう。
今回は、DMA Read カーネルを作成しよう。

ストーミング接続用のhls::stream の定義は、hls::stream<ap_axiu<32,0,0,0> > にする必要がある。こうするとサイドチャネルの信号は last と keep , strb だけになる。この内の last を使用して、フレームの最後をマークしよう。
ストーミング接続用 DMA Read カーネルの krnl_dma_read.cpp を示す。
なお、C シミュレーションできないので、とりあえず extern "C" { } は外してある。

// krnl_dma_read.cpp
// 2020/01/21 by marsee

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

//extern "C" {
void dma_read(volatile int32_t *inm, hls::stream<ap_axiu<32,0,0,0> >& outs, int32_t x_size, int32_t y_size){
#pragma HLS INTERFACE s_axilite port=return bundle=control
#pragma HLS INTERFACE s_axilite port=y_size bundle=control
#pragma HLS INTERFACE s_axilite port=x_size bundle=control
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE m_axi depth=3072 port=inm offset=slave bundle=gmem

    ap_axiu<32,0,0,0> pix;

    LOOP_DRY: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_DRX: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            pix.data = inm[x_size*y+x];

            if(x==(x_size-1) && y==(y_size-1))
                pix.last = 1;
            else
                pix.last = 0;
            outs << pix;
        }
    }
}
//}


テストベンチの krnl_dma_read_tb.cpp を示す。

// krnl_dma_read_tb.cpp
// 2020/01/21 by marsee

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <ap_axi_sdata.h>
#include "bmp_header.h"

void dma_read(volatile int32_t *inm, hls::stream<ap_axiu<32,0,0,0> >& outs, int32_t x_size, int32_t y_size);

int main(){
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int32_t *rd_bmp, *dmar;
    int32_t blue, green, red;
    hls::stream<ap_axiu<32,0,0,0> > outs;
    ap_axiu<32,0,0,0> vals;

    if ((fbmpr = fopen("test.bmp", "rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.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 =(int32_t *)malloc(sizeof(int32_t) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((dmar =(int32_t *)malloc(sizeof(int32_t) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd 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] = (int32_t)((blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16));
        }
    }
    fclose(fbmpr);

    dma_read(rd_bmp, outs, bmpihr.biWidth, bmpihr.biHeight);

    // DMAされた値のチェック
    for(int y=0; y<bmpihr.biHeight; y++){
        for(int x=0; x<bmpihr.biWidth; x++){
            outs >> vals;
            dmar[(y*bmpihr.biWidth)+x] = (int32_t)vals.data;
            if ((int32_t)vals.data != rd_bmp[(y*bmpihr.biWidth)+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, DMAR = %d, ORG = %d\n", x, y, (int)vals.data, (int)rd_bmp[(y*bmpihr.biWidth)+x]);
                return(1);
            }
        }
    }
    std::cout << "Success DMA READ results match" << std::endl;
    std::cout << std::endl;


    // DMA_Read の結果を dma_read.bmp へ出力する
    if ((fbmpw=fopen("dma_read.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.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++){
            blue = dmar[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (dmar[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (dmar[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    free(rd_bmp);
    free(dmar);

    return(0);
}


bmp_header.h を示す。

// bmp_header.h
// BMP ファイルフォーマットから引用させて頂きました
// http://www.kk.iij4u.or.jp/~kondo/bmp/
//
// 2017/05/04 : takseiさんのご指摘によりintX_tを使った宣言に変更。takseiさんありがとうございました
//              変数の型のサイズの違いによってLinuxの64ビット版では動作しなかったためです
//              http://marsee101.blog19.fc2.com/blog-entry-3354.html#comment2808
//

#include <stdio.h>
#include <stdint.h>

// BITMAPFILEHEADER 14bytes
typedef struct tagBITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} BITMAPFILEHEADER;

// BITMAPINFOHEADER 40bytes
typedef struct tagBITMAPINFOHEADER{
    uint32_t biSize;
    int32_t biWidth;
    int32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t biXPixPerMeter;
    int32_t biYPixPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImporant;
} BITMAPINFOHEADER;

typedef struct BMP24bitsFORMAT {
    uint8_t blue;
    uint8_t green;
    uint8_t red;
} BMP24FORMAT;

  1. 2020年01月21日 05:39 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた3

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた2”の続き。

前回は、カーネル間のストーミング接続の設定ファイルを確認して、自分で新しいプロジェクトを作成して、やってみたが、カーネル間のストーミング接続ファイルがVitis に見つけられなくてエラーになってしまった。今回は、エラーの解消を図った。

まずは、”Vitis のカーネル間のストリーミング接続について”を見ると、ストーミング接続ファイルの指定方法は、v++ のリンク時に --config オプションでファイルを指定する。よって、Vitis のGUI でリンク時のオプションを追加する方法でやってみよう。

Vitis のGUI でリンク時のオプションを追加する。
Assistant ウインドウの streaming_k2k_mm2_system -> streaming_k2k_mm2 を右クリックし右クリックメニューから Settings... を選択する。
streaming_kernel_19_200118.png

Project Settings ダイアログが開く。
V++ linker options に

--config ../src/krnl_stream_vadd_vmult.ini

を入力した。
streaming_kernel_20_200118.png

これでビルドしたところ、xcl2.hpp が無いというエラーが発生した。
streaming_kernel_21_200118.png

streaming_k2k_mm/libs から xcl2.cpp と xcl2.hpp をインポートした。
streaming_kernel_22_200118.png

これでもう一度ビルドしたところ、ビルドが成功したようだ。
streaming_kernel_23_200118.png

もう一度、ビルドすると、 streaming_k2k_mm2_system -> streaming_k2k_mm2 -> Hardware にみどりのチェックマークがついた。
streaming_kernel_24_200118.png

Ultra96-V2 の電源をON して、Linux をブートする。
BOOT.BIN の Ultra96-V2 の MicroSD カードの第 1 パーティションへのSFTP は前回と同じカーネルだから良いだろうということでやっていない。

Ultra96-V2 を reboot した。
Ultra96-V2 の Linux が起動したら、root でログインした。
zocl ドライバをロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

Vitis で Run Configuration を作成した。
RUN ボタンをクリックすると、TEST PASSED が表示された。成功だ
streaming_kernel_25_200118.png

シリアル・ターミナルの表示を示す。
streaming_kernel_26_200118.png

これで、自分書いたカーネルでカーネル間のストーミング接続ができるようになったと言えるだろう。
  1. 2020年01月20日 04:31 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた2

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた1”の続き。

前回は、Vitis のサンプル・プロジェクトに入っている streaming_k2k_mm をビルドして、実機確認し、成功した。今回は、カーネル間のストーミング接続の設定ファイルを確認して、自分で新しいプロジェクトを作成して、やってみようと思う。
カーネル間のストーミング記述ファイルは、何も指定しないとVitis ビルド時に見つからなくてエラーになってしまった。

まずは、カーネル間のストーミング接続を記述したファイルを見ていく。
カーネル間のストーミング接続を記述したファイルは streaming_k2k_mm/krnl_stream_vadd_vmult.ini のようだ。
streaming_kernel_12_200118.png

krnl_stream_vadd_vmult.ini を見ると、”Vitis のカーネル間のストリーミング接続について”で調べたように記述されていた。
streaming_kernel_13_200118.png

このファイルを使用すれば、カーネル間のストーミング接続を指定することができそうだ。

さて、自分で Vitis アクセラレーション・アプリケーション・プロジェクトを作成する。名前は streaming_k2k_mm2 とした。
streaming_kernel_14_200118.png

ultra96v2_min2 プラットフォームを使用した。
Templates では Empty Application を指定した。
streaming_kernel_15_200118.png

streaming_k2k_mm2 プロジェクトが作成された。
streaming_kernel_16_200118.png

streaming_k2k_mm2/src ディレクトリにホスト・アプリケーションとカーネル・アプリケーション 2 個、カーネル間のストーミング接続記述ファイルをインポートした。
streaming_kernel_17_200118.png

これで Hardware でビルドしたところエラーになった。
streaming_kernel_18_200118.png

ERROR: [CFGEN 83-2284] No stream resources found that can accomodate compute unit "krnl_stream_vadd_1.out"
ERROR: [SYSTEM_LINK 82-36] [20:11:30] cfgen failed
Time (s): cpu = 00:00:00.28 ; elapsed = 00:00:00.29 . Memory (MB): peak = 296.441 ; gain = 0.000 ; free physical = 14175 ; free virtual = 39277
ERROR: [SYSTEM_LINK 82-62] Error generating design file for /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm2/Hardware/krnl_stream_vadd_vmult.build/link/sys_link/cfgraph/cfgen_cfgraph.xml, command: /media/masaaki/Ubuntu_Disk/tools/Xilinx/Vitis/2019.2/bin/cfgen -nk krnl_stream_vadd:1 -nk krnl_stream_vmult:1 -dmclkid 0 -r /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm2/Hardware/krnl_stream_vadd_vmult.build/link/sys_link/_sysl/.cdb/xd_ip_db.xml -o /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm2/Hardware/krnl_stream_vadd_vmult.build/link/sys_link/cfgraph/cfgen_cfgraph.xml
ERROR: [SYSTEM_LINK 82-96] Error applying explicit connections to the system connectivity graph
ERROR: [SYSTEM_LINK 82-79] Unable to create system connectivity graph
INFO: [v++ 60-1442] [20:11:30] Run run_link: Step system_link: Failed
Time (s): cpu = 00:00:05 ; elapsed = 00:00:05 . Memory (MB): peak = 677.906 ; gain = 0.000 ; free physical = 14193 ; free virtual = 39295
ERROR: [v++ 60-661] v++ link run 'run_link' failed
ERROR: [v++ 60-626] Kernel link failed to complete
ERROR: [v++ 60-703] Failed to finish linking
makefile:94: recipe for target 'krnl_stream_vadd_vmult.xclbin' failed
make: *** [krnl_stream_vadd_vmult.xclbin] Error 1


結局、カーネル間のストーミング接続記述ファイルが認識されていないようだ。
  1. 2020年01月19日 04:54 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた1

昨日、新横浜から家に帰ってきました。

Vitis でメモリベースのIP を並べているとメモリ帯域の逼迫が心配なので、どうしてもカーネル間をストリーミング接続したいということで、”Vitis のストリーミング接続について”で調べたが、その内の、”Streaming Data Transfers Between Kernels (K2K)”のサンプルの streaming_k2k_mm についてやってみよう。

streaming_k2k_mm は、私のパソコンのVitis 上にサンプルとして読み込まれているので、Vitis GUI 上からサンプルとして実行することができる。
さて、Vitis のプロジェクトを作成して行こう。
Vitis GUI の File メニューから New -> Application Project... を選択する。
Create a New Application Project 画面で、Project Name に streaming_k2k_mm を入力した。
streaming_kernel_1_200118.png

Platform 画面では、ultra96v2_min2 プラットフォームを選択した。
streaming_kernel_2_200118.png

Domain 画面はそのまま。
streaming_kernel_3_200118.png

Template では、Stream Kernel to Kernel Memory Mapped を選択し、Finish ボタンをクリックした。
streaming_kernel_4_200118.png

krnl_steram_vadd カーネルと krnl_stream_vmult カーネル間のストーミング接続について図を書いてみた。下に示す。
streaming_kernel_5_200118.png

krnl_steram_vadd カーネルで in1 と in2 のメモリの値を足し算して、その値をストーミング接続で krnl_steram_vmult カーネルに送って、その値と krnl_steram_vmult カーネルの in1 のメモリの値を乗算して out のメモリにWrite する。

一度、Hardware をビルドしたが、ワーニングが出ている。
streaming_kernel_6_200118.png

もう一度、Hardware をビルドすると今度は成功。
streaming_kernel_7_200118.png

Ultra96-V2 の電源をON して、Linux をブートする。
/home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm/Hardware/sd_card ディレクトリに行って、BOOT.BIN を Ultra96-V2 の /run/media/mmcblk0p1/ に SFTP した。
cd /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm/Hardware/sd_card
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1


Ultra96-V2 を reboot した。
Ultra96-V2 の Linux が起動したら、root でログインした。
zocl ドライバをロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

Vitis で Run Configuration を作成した。
streaming_kernel_8_200118.png

streaming_kernel_9_200118.png

RUN ボタンをクリックすると、TEST PASSED が表示された。成功だ。
streaming_kernel_10_200118.png

シリアル・ターミナルの表示を示す。
streaming_kernel_11_200118.png
  1. 2020年01月18日 14:59 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続について

現在まだ新横浜プリンスホテルに宿泊しているが、Vitis のカーネル間のストリーミング接続について、覚書を書いておく。

Streaming Data Transfers Between Kernels (K2K)
Vitis のカーネル間のストリーミング接続については、まずは、Vitis Unified Software Development Platform DocumentationStreaming Data Transfers Between Kernels (K2K) に書かれている。
ストリームの形式は、hls::stream with the ap_axiu data type ということだ。hls::stream<ap_axiu<32,0,0,0> > aaa; という感じになるだろう。

Specify Streaming Connections Between Compute Units
Specify Streaming Connections Between Compute Units はコンピューティングユニット間のストリーミング接続を指定する方法について書かれている。
ストリーム間のストリーミング接続については、v++ のリンクの処理の中で、connectivity.stream_connect オプションを使用して、指定するらしい。
ファイルの書き方の例(Specify Streaming Connections Between Compute Units から引用する)

[connectivity]
#stream_connect=<cu_name>.<output_port>:<cu_name>.<input_port>
stream_connect=vadd_1.stream_out:vadd_2.stream_in


ファイルは、v++ の --config オプションにで指定する。(Specify Streaming Connections Between Compute Units から引用する)

v++ -l --config vadd_config.txt ...



Vitis Compiler General Options
--config オプションを見ている。
やはり --config オプションを使用して、カーネル間のストリーミング接続ファイルをリンカーに教えるようだ。

後は、v++ のオプションを GUI 上で指定できる方法を見つければストリーミング接続できるんじゃないだろうか?
とりあえず出先で Vitis の環境が無いので、家に帰ったら確かめてみよう。
  1. 2020年01月17日 05:12 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のストリーミング接続について

今日は新横浜プリンスホテルに宿泊しているので、大したことは書けないが Vitis のストリーミング接続について書いてみよう。

今まで実装してきたAXI4 Master アクセスによるカーネルは基本的にメモリを介して他のカーネルと通信するので、メモリ帯域を消費する。1 個や 2 個のカーネルだったら良いかもしれないが、3 , 4 個と接続するとメモリ帯域が足りなくなってしまうだろう。そこで、カーネルをストリーミング接続するとメモリ帯域を消費しないで何個でもカーネルを接続できるはずだ。

Vitis Unified Software Development Platform DocumentationStreaming Connections を見るとカーネルのストリーミング接続について書いてある。カーネルのストリーミング接続については 3 つあるようだ。

1. Streaming Data Between the Host and Kernel (H2K)
ホストからカーネルへのストリーミング接続で、残念ながら PCIe ベースの Alveo しか使用できないようだ。Ultra96V2 のプラットフォームでは使用できない。ここは、Ultra96V2 でやる場合には、カーネルにDMA を組み込んでストリーミングにすれば良いだろう。

2. Streaming Data Transfers Between Kernels (K2K)
カーネル間のストリーミング接続。
Alveo Data Centerアクセラレータカード用のQDMAプラットフォームなど、特定のターゲットプラットフォームでのみ使用できると書いてあるが、サンプルをやってみたところ Ultra96V2 でも動作するようだ。

3. Free-running Kernel
フリー・ランニング・カーネルなので、制御信号が無く、動作しっぱなしということらしい。これも Ultra96V2 でサンプルを動かしてみたが動作した。

今のところ、サンプルを動作させているだけで、自分で作成したカーネルをストリーミング接続することができていない。
それは、カーネル間のストリーミング接続が Vitis に認識されないためである。
カーネル間のストリーミング接続は、カーネル名.ini ファイルで Vitis に知らされるようなのだが、それを作って src に入れてもストリーミング接続が今のところ認識されずに困っている。
Vitis の GUI でのカーネルのストリーミング接続を有効にする方法を知っている方がいらしたら、教えてください。
  1. 2020年01月16日 04:34 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

牛久シティマラソンの 5km に出場しました

昨日は、奥さんと牛久シティマラソンの 5km に出場しました。

記録はネットタイムで 25 分 28 秒でした。私としては最高タイムです。やった〜。ですが、牛久シティマラソンは 40 mほど 5 km よりも短いのでは?疑惑があります。その場合には、12 秒ほどタイムに加える必要があります。順位としては、18位/106 人(エントリー数なので、これよりも少なくなっていると思います)でした。
コースは、高架道の下を 2 回ほどくぐる、つまり、往復 4 回くぐるので、アップダウンがあり、きついコースです。途中で足が上がらなくなってきました。
今までの最高タイムは 26 分 30 秒程度だったので、約 1 分速くなったことになります。
Ushiku_marathon_200114.jpg

奥さんは、私よりも 2 分 10 秒くらい速く、女子年代別 5 位入賞です。奥さん、おめでとう。

牛久シティマラソンの後は、自転車クラブSST の練習会で自転車練習 30 km でした。疲れましたが気持ちよかったです。皆さん、スピード速い。私もビンディング・ペダルにしたので、瞬発力はだいぶ上がっている気がしましたが、結局、心肺能力を鍛えないと持ちませんね。。。

次は、1月26日の勝田マラソンの 10 km に出場します。奥さんはマラソンです。
  1. 2020年01月14日 03:59 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する5(Vitis 編)

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する4(Vivado HLS 編 4)”の続き。

前回は、白線走行用畳み込みニューラルネットワークをVivado HLS 2019.2 で IP にした。今回は白線走行用畳み込みニューラルネットワークの xo ファイルを使用して Vitis 2019.2 でアプリケーション・プロジェクトを作成し、実機確認する。

最初に、ホスト・アプリケーションの all_layers_template_host.cpp を示す。

// all_layers_template_host.cpp
// 2019/12/25 by marsee
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "layer_general.h"

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

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

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

typedef ap_uint<2> output_type;
typedef ap_fixed<12,7,AP_TRN,AP_WRAP> out_affine_type;

void all_layers_dnn(volatile uint32_t *inm, volatile uint32_t *output,
        volatile int32_t *dot2, int32_t x_size, int32_t y_size);

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

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};

int main(int argc, char* argv[]){
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    output_type output_soft;
    float dot2_soft[NUM_OF_OUTPUT];
    ap_axiu<32,1,1,1> pix;
    int hw_err_cnt = 0;
    int sw_err_cnt = 0;
    const char* xclbinFilename;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    // t_train256[][]を入れるメモリをアロケート
    std::vector<int32_t,aligned_allocator<int32_t>> pixel(ROW_PIXELS*COULMN_PIXELS);
    size_t pixel_in_bytes = (ROW_PIXELS*COULMN_PIXELS) * sizeof(int32_t);

    std::vector<uint32_t,aligned_allocator<uint32_t>> output(1);
    size_t output_in_bytes = sizeof(uint32_t);

    std::vector<int32_t,aligned_allocator<int32_t>> dot2(NUM_OF_OUTPUT);
    size_t dot2_in_bytes = (NUM_OF_OUTPUT * sizeof(int32_t));

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);

    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_all_layers_dnn(program,"all_layers_dnn");
        
    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer pixel_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            pixel_in_bytes, pixel.data());
    cl::Buffer output_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            output_in_bytes, output.data());
    cl::Buffer dot2_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            dot2_in_bytes, dot2.data());

    //set the kernel Arguments
    krnl_all_layers_dnn.setArg(0,pixel_buf);
    krnl_all_layers_dnn.setArg(1,output_buf);
    krnl_all_layers_dnn.setArg(2,dot2_buf);
    krnl_all_layers_dnn.setArg(3,COULMN_PIXELS);
    krnl_all_layers_dnn.setArg(4,ROW_PIXELS);

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

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

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

                ins_soft << pix;

                pixel[y*COULMN_PIXELS+x] = uint32_t(t_train_256[i][y*COULMN_PIXELS+x]);
            }
        }

        // Data will be transferred from system memory over PCIe to the FPGA on-board
        // DDR memory.
        q.enqueueMigrateMemObjects({pixel_buf},0/* 0 means from host*/);

        //Launch the Kernel
        q.enqueueTask(krnl_all_layers_dnn);
        //q.enqueueTask(krnl_all_layers_dnn);

        // The result of the previous kernel execution will need to be retrieved in
        // order to view the results. This call will transfer the data from FPGA to
        // source_results vector

        q.enqueueMigrateMemObjects({output_buf, dot2_buf},CL_MIGRATE_MEM_OBJECT_HOST);

        all_layers_soft(ins_soft, output_soft, dot2_soft);

        int t_test_num = 0;
        for(int m=0; m<NUM_OF_OUTPUT; m++){
            if(t_test[i][m] == 1.0f){
                t_test_num = m;
                break;
            }
        }
        // out と out_soft を比較する
        /* cout << "output" << " = " << int(output) << " output_soft = " << int(output_soft) << endl;
        for(int j=0; j<NUM_OF_OUTPUT; j++){
            cout << "dot2[" << j << "] = " << float(dot2[j]) << " dot2_soft[" << j << "] = " << dot2_soft[j] << endl;
        } */

        if(int(output[0]) != t_test_num){
            std::cout << "hw_error: i = " << i << " output = " << int(output[0]) << " t_test_num = " << t_test_num << std::endl;
            hw_err_cnt++;
            //return(1);
        }
        if(int(output_soft) != t_test_num){
            std::cout << "sw_error: i = "<< i << " output_soft = " << int(output_soft) << " t_test_num" " = " << t_test_num << std::endl;
            sw_err_cnt++;
            //return(1);
        }
        if(int(output[0]) != t_test_num || int(output_soft) != t_test_num){
            for(int j=0; j<NUM_OF_OUTPUT; j++){
                std::cout << "dot2[" << j << "] = " << std::fixed << std::setprecision(8) << float(dot2[j])/float(256.0) << "   dot2_soft[" << j << "] = " << dot2_soft[j] << std::endl;
            }
            std::cout << std::endl;
        }
    }
    q.finish();

    std::cout << "hw_err_cnt = " << hw_err_cnt << " sw_err_cnt = " << sw_err_cnt << std::endl;

    return(0);
}


アクセラレーション用 all_layers_template アプリケーション・プロジェクトを作成した。

ultra96v2_min2 プラットフォームを使用している。

Hardware Functions に all_layers_template を指定した。

xclbin ファイル名を all_layters_dnn に変更した。

Assistant ウインドウで all_layers_template_system/all_layers_template/Hardware を右クリックし右クリックメニューからBuild を選択してビルドし、成功した。
template_cnn_2019_02_19_200113.png

Ultra96-V2 を電源ON して、Linux を起動した。
root でログインして、zocl ドライバを insmod した。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
template_cnn_2019_02_20_200113.png

Hardware を右クリックし右クリックメニューから Run -> Run Configurations... を選択した。
Single Application Debug をダブルクリックして、Debugger _all_layers_template を作成した。
template_cnn_2019_02_23_200113.png

template_cnn_2019_02_24_200113.png

Run ボタンをクリックして、アプリケーション・ソフトを起動したところ、アプリケーション・ソフトが実行されて結果が出力された。
どうも dot2[0] の値が dot2[2] の値と同じになっているようだ。output の値は問題ないようで、 hw_err_cnt = 8 になっている。
職場のパソコンで、同じコードでやってみたところ、hw_err_cnt = 200 となっていた。パソコンによって違うのだろうか?
template_cnn_2019_02_21_200113.png

シリアル・コンソールの様子を示す。
template_cnn_2019_02_22_200113.png
  1. 2020年01月14日 03:34 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

VSCode をインストールした

遅ればせながら、Ubuntu 18.04 に VSCode をインストールした。

インストール方法は”UbuntuにVSCodeをインストールする3つの方法”を参考にさせていただいた。
3 つの方法の内の最後の"3. debパッケージから直接インストール"でインストールした。
curl はインストール済みだったので、以下のコマンドを実行した。
curl -L https://go.microsoft.com/fwlink/?LinkID=760868 -o vscode.deb
sudo apt install ./vscode.deb

VSCode_1_200112.png

code --version
でバージョンを確認すると、

1.41.1
26076a4de974ead31f97692a0d32f90d735645c0
x64

だった。

code &
で VSCode を起動した。
VSCode_2_200112.png

立ち上がった VSCode にとりあえずインストールしたパッケージは

C/C++
Japanese Language Pack
C++ Intellisense
Python
svls-vscode
verilog HDL/SystemVerilog
VHDL


だった。
VSCode_3_200112.png

配色のテーマは Tomorrow Night Blue にした。
  1. 2020年01月12日 21:29 |
  2. VSCode
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する4(Vivado HLS 編 4)

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する3(Vivado HLS 編 3)”の続き。

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する1(Vivado HLS 編 1)”に Vitis のRTL カーネルを作成するためにパラメータを変更できる形に書いた畳み込みニューラルネットワークをVivado HLS 2019.2 でIP にするためのソースコードとテストベンチコードを書いたが、任意精度固定データ型をそのまま int32_t にキャストしてしまったので、小数点以下の桁が無くなってしまった。それを修正するために、任意精度固定データ型の整数ビット長を 8 ビット延長して 256 倍することにした。ということで、前回は、ソースコードとテストベンチ・コードを貼った。今回は、ソースコードとテストベンチ・コードをVivado HLS で C シミュレーション、C コードの合成、Export RTL を行った。

Vivado HLS 2019.2 の all_layers_template プロジェクトを示す。
template_cnn_2019_02_15_200111.png

C シミュレーションを行った。結果を示す。なお、C シミュレーションを行う時は、”extern "C" { }”を外してある。
今度は、ハードウエアのCNN 出力も小数点以下まで出力されている。

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
   Compiling ../../../all_layers_template_axim.cpp in debug mode
   Generating csim.exe
hw_error: i = 25 output = 2 t_test_num = 1
sw_error: i = 25 output_soft = 2 t_test_num = 1
dot2[0] = -5.59375000 dot2_soft[0] = -3.77501726
dot2[1] = 0.12500000 dot2_soft[1] = -0.13269189
dot2[2] = 0.25000000 dot2_soft[2] = 1.61074853

hw_error: i = 30 output = 2 t_test_num = 1
sw_error: i = 30 output_soft = 2 t_test_num = 1
dot2[0] = -6.53125000 dot2_soft[0] = -4.67336369
dot2[1] = 0.40625000 dot2_soft[1] = 0.12951475
dot2[2] = 0.43750000 dot2_soft[2] = 1.71587336

sw_error: i = 31 output_soft = 2 t_test_num = 1
dot2[0] = -7.31250000 dot2_soft[0] = -5.31440449
dot2[1] = 0.90625000 dot2_soft[1] = 0.69655895
dot2[2] = -0.25000000 dot2_soft[2] = 1.00723171

sw_error: i = 35 output_soft = 2 t_test_num = 1
dot2[0] = -7.12500000 dot2_soft[0] = -5.15462875
dot2[1] = 0.50000000 dot2_soft[1] = 0.19586089
dot2[2] = 0.43750000 dot2_soft[2] = 1.79063916

sw_error: i = 36 output_soft = 2 t_test_num = 1
dot2[0] = -7.68750000 dot2_soft[0] = -5.64889669
dot2[1] = 1.03125000 dot2_soft[1] = 0.69646239
dot2[2] = -0.25000000 dot2_soft[2] = 1.09402716

sw_error: i = 40 output_soft = 2 t_test_num = 1
dot2[0] = -7.21875000 dot2_soft[0] = -5.31394196
dot2[1] = 0.59375000 dot2_soft[1] = 0.30034199
dot2[2] = 0.09375000 dot2_soft[2] = 1.52586949

sw_error: i = 41 output_soft = 2 t_test_num = 1
dot2[0] = -8.12500000 dot2_soft[0] = -5.94443941
dot2[1] = 0.87500000 dot2_soft[1] = 0.61903512
dot2[2] = 0.06250000 dot2_soft[2] = 1.28180122

sw_error: i = 42 output_soft = 2 t_test_num = 1
dot2[0] = -10.21875000 dot2_soft[0] = -7.44187164
dot2[1] = 1.31250000 dot2_soft[1] = 1.10615981
dot2[2] = 0.37500000 dot2_soft[2] = 1.35738707

sw_error: i = 45 output_soft = 2 t_test_num = 1
dot2[0] = -8.21875000 dot2_soft[0] = -5.92508411
dot2[1] = 0.68750000 dot2_soft[1] = 0.44851223
dot2[2] = 0.18750000 dot2_soft[2] = 1.43742454

sw_error: i = 46 output_soft = 2 t_test_num = 1
dot2[0] = -10.37500000 dot2_soft[0] = -7.76649952
dot2[1] = 1.06250000 dot2_soft[1] = 0.82863915
dot2[2] = 0.90625000 dot2_soft[2] = 1.88942850

sw_error: i = 47 output_soft = 2 t_test_num = 1
dot2[0] = -12.12500000 dot2_soft[0] = -9.50911713
dot2[1] = 1.75000000 dot2_soft[1] = 1.48399019
dot2[2] = 0.75000000 dot2_soft[2] = 1.85759318

hw_error: i = 75 output = 2 t_test_num = 1
sw_error: i = 75 output_soft = 2 t_test_num = 1
dot2[0] = -5.96875000 dot2_soft[0] = -4.04238653
dot2[1] = -1.03125000 dot2_soft[1] = -1.22402656
dot2[2] = 2.12500000 dot2_soft[2] = 3.36929369

hw_error: i = 76 output = 2 t_test_num = 1
sw_error: i = 76 output_soft = 2 t_test_num = 1
dot2[0] = -6.18750000 dot2_soft[0] = -4.09871578
dot2[1] = -0.21875000 dot2_soft[1] = -0.46985394
dot2[2] = 0.40625000 dot2_soft[2] = 1.61257589

hw_error: i = 80 output = 2 t_test_num = 1
sw_error: i = 80 output_soft = 2 t_test_num = 1
dot2[0] = -6.37500000 dot2_soft[0] = -4.33292818
dot2[1] = -0.75000000 dot2_soft[1] = -0.96692348
dot2[2] = 1.78125000 dot2_soft[2] = 2.98383069

hw_error: i = 81 output = 2 t_test_num = 1
sw_error: i = 81 output_soft = 2 t_test_num = 1
dot2[0] = -6.46875000 dot2_soft[0] = -4.40864801
dot2[1] = 0.06250000 dot2_soft[1] = -0.15780880
dot2[2] = 0.09375000 dot2_soft[2] = 1.26864278

hw_error: i = 85 output = 2 t_test_num = 1
sw_error: i = 85 output_soft = 2 t_test_num = 1
dot2[0] = -6.15625000 dot2_soft[0] = -4.16326904
dot2[1] = -0.59375000 dot2_soft[1] = -0.84592772
dot2[2] = 1.21875000 dot2_soft[2] = 2.42255425

sw_error: i = 86 output_soft = 2 t_test_num = 1
dot2[0] = -6.50000000 dot2_soft[0] = -4.36515617
dot2[1] = 0.09375000 dot2_soft[1] = -0.08813666
dot2[2] = -0.28125000 dot2_soft[2] = 0.97706115

hw_error: i = 90 output = 2 t_test_num = 1
sw_error: i = 90 output_soft = 2 t_test_num = 1
dot2[0] = -5.81250000 dot2_soft[0] = -4.02276182
dot2[1] = -0.53125000 dot2_soft[1] = -0.66237617
dot2[2] = 0.46875000 dot2_soft[2] = 1.72938108

sw_error: i = 91 output_soft = 2 t_test_num = 1
dot2[0] = -5.71875000 dot2_soft[0] = -3.85103607
dot2[1] = 0.15625000 dot2_soft[1] = -0.09844255
dot2[2] = -0.78125000 dot2_soft[2] = 0.42963967

sw_error: i = 95 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000 dot2_soft[0] = -4.07760668
dot2[1] = -0.03125000 dot2_soft[1] = -0.30057180
dot2[2] = -0.43750000 dot2_soft[2] = 0.90393031

hw_err_cnt = 8 sw_err_cnt = 20
WARNING: Hls::stream 'hls::stream<ap_axiu<32, 1, 1, 1> >.1' contains leftover data, which may result in RTL simulation hanging.
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


”extern "C" { }”を入れて、C コードの合成を行った。
template_cnn_2019_02_16_200111.png

最大 46 us のレイテンシとなっている。
ロボットカーの白線走行用 CNN の大きさは 横 56 ピクセル X 縦 10 ピクセルで、白黒だ。層の構成は、畳み込み層-ReLU-マックス・プーリングー全結合層-ReLU-全結合層ー出力となる。畳込み層のフィーチャーマップは 2 個、5 x 5 ピクセルのストライド 1 だ。1層目の全結合層は 156 入力 100 出力、2層目の全結合層は 100 入力 3 出力となっている。3 つの出力は、それぞれ左折、直進、右折に対応している。白線走行用 CNN としては、46 us のレイテンシは相当なオーバースペックとなっている。

最後に Export RTL を行った。結果を示す。
template_cnn_2019_02_17_200111.png

all_layers_dnn.xo が生成された。
template_cnn_2019_02_18_200112.png
  1. 2020年01月12日 04:46 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する3(Vivado HLS 編 3)

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する2(Vivado HLS 編 2)”の続き。

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する1(Vivado HLS 編 1)”に Vitis のRTL カーネルを作成するためにパラメータを変更できる形に書いた畳み込みニューラルネットワークをVivado HLS 2019.2 でIP にするためのソースコードとテストベンチコードを書いたが、任意精度固定データ型をそのまま int32_t にキャストしてしまったので、小数点以下の桁が無くなってしまった。それを修正するために、任意精度固定データ型の整数ビット長を 8 ビット延長して 256 倍することにした。

ソースコードの all_layers_template_axim.cpp を示す。

// all_layers_template_axim.cpp
// 2018/05/10 by marsee
// 2019/12/28: VitisのRTLカーネルととして使用するためにall_layers_dnnを追加
//

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

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

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

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

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

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

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

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

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

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

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

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

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

    return(0);
}

extern "C" {
void all_layers_dnn(volatile uint32_t *inm, volatile uint32_t *output,
        volatile int32_t *dot2, int32_t x_size, int32_t y_size){
#pragma HLS INTERFACE m_axi port=inm offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port=output offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port=dot2 offset = slave bundle = gmem2
#pragma HLS INTERFACE s_axilite port = x_size bundle = control
#pragma HLS INTERFACE s_axilite port = y_size bundle = control
#pragma HLS INTERFACE s_axilite port = return bundle = control

#pragma HLS DATAFLOW
    hls::stream<ap_axiu<32, 1, 1, 1> > ins;
    ap_axiu<32,1,1,1> element;
    out_affine_type dot2_o[NUMBER_OF_OUTPUT_LAYER];
    output_type output_o;

    Loop_y: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10
        Loop_x: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=56 max=56 avg=56
#pragma HLS PIPELINE II=1
            element.data = ap_uint<32>(inm[x_size*y+x]);
            if(x==0 && y==0) // 最初のデータ
                element.user = 1;
            else
                element.user = 0;
            if(x==(x_size-1)) // 行の終了
                element.last = 1;
            else
                element.last = 0;

            ins << element;
        }
    }
    all_layers(ins, output_o, dot2_o);

    *output = uint32_t(output_o);

    Loop_dot2: for(int i=0; i<NUMBER_OF_OUTPUT_LAYER; i++){
#pragma HLS PIPELINE II=1
        ap_fixed<20,7,AP_TRN,AP_WRAP> dot2_t = dot2_o[i];
        dot2[i] = int32_t(dot2_t*256); // 8 bits shift left
    }
}
}


テストベンチの all_layers_template_axim_tb.cpp を示す。

// all_layers_template_axim_tb.cpp
// 2018/05/12 by marsee
// 2019/12/28: VitisのRTLカーネルととして使用するためにall_layers_dnnを追加
//

#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 <vector>
#include <stdint.h>

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

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

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

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

void all_layers_dnn(volatile uint32_t *inm, volatile uint32_t *output,
        volatile int32_t *dot2, int32_t x_size, int32_t y_size);

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

int main(){
    using namespace std;

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

    vector<uint32_t> pixel(ROW_PIXELS * COULMN_PIXELS);
    vector<int32_t> dot2(NUMBER_OF_OUTPUT_LAYER);

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

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

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

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

        all_layers_dnn(pixel.data(), &output, dot2.data(), COULMN_PIXELS, ROW_PIXELS);
        all_layers_soft(ins_soft, output_soft, dot2_soft);

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

    return(0);
}

  1. 2020年01月11日 04:20 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS の Vitis Bottom Up Flow を使用する3

Vivado HLS の Vitis Bottom Up Flow を使用する2”の続き。

前回は、Vitis 2019.2 のアクセラレーション・プロジェクト用のVivado HLS 2019.2 プロジェクトの square_cubed プロジェクトで xo ファイルを作成した。今回は、Vitis 2019.2 で square_cubed アクセラレーション・アプリケーション・プロジェクトを作成して実機で動作を確認しよう。

1. Vitis 2019.2 で ultra96v2_min2 プラットフォームを使用した square_cubed アプリケーション・プロジェクトを作成した。
2. ”Vivado HLS の Vitis Bottom Up Flow を使用する2”で作成した square_cubed.xo を square_cubed_system -> square_cubed -> src に追加した。
3. square_cubed_system -> square_cubed -> src に square_cubed_host.cpp を新規作成した。
4. xclbin ファイル名を squzre_cubed に変更した。
5. Assistant ウインドウで square_cubed_system -> square_cubed -> Hardware を右クリックし右クリックメニューからBuild を選択してビルドし、成功した。
square_cubed_19_200108.png

square_cubed_host.cpp を貼っておく。

// square_cubed_host.cpp
// 2020/01/06 by marsee
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};
// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用終了

#define DATA_SIZE 10

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを自分用に変更して引用します
int main(int argc, char* argv[])
{
    const char* xclbinFilename;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    std::vector<int32_t,aligned_allocator<int32_t>> in_data(DATA_SIZE);
    std::vector<int32_t,aligned_allocator<int32_t>> square_data(DATA_SIZE);
    std::vector<int32_t,aligned_allocator<int32_t>> cubed_data(DATA_SIZE);
    size_t size_in_bytes = (DATA_SIZE) * sizeof(int32_t);

    // input data
    for(int i=0; i<DATA_SIZE; i++){
        in_data[i] = i;
        square_data[i] = 0;
    }

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);


    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_squara_cubed(program,"square_cubed");

    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer ind_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, in_data.data());
    cl::Buffer squared_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, square_data.data());
    cl::Buffer cubed_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, cubed_data.data());

    // Data will be transferred from system memory over PCIe to the FPGA on-board
    // DDR memory.
    q.enqueueMigrateMemObjects({ind_buf},0/* 0 means from host*/);

    //set the kernel Arguments
    krnl_squara_cubed.setArg(0,ind_buf);
    krnl_squara_cubed.setArg(1,squared_buf);
    krnl_squara_cubed.setArg(2,cubed_buf);

    //Launch the Kernel
    q.enqueueTask(krnl_squara_cubed);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({squared_buf, cubed_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // Compare the results
    int error = 0;
    for(int i=0; i<DATA_SIZE; i++){
        if(square_data[i] != i*i || cubed_data[i] != i*i*i){
            std::cout << "Error: i = " << i << " i^2 = " << i*i << "  square_data = " << int(square_data[i]) <<
                    "  cubed_data = " << int(cubed_data[i]) << std::endl;
            error = 1;
        }else{
            //std::cout << "Error: i = " << i << " i^2 = " << i*i << "  square_data = " << int(square_data[i]) <<
                //"  cubed_data = " << int(cubed_data[i]) << std::endl;
        }
    }

    std::cout << "TEST " << (error ? "FAILED" : "PASSED") << std::endl;
    return (error ? EXIT_FAILURE : EXIT_SUCCESS);
}


まずは、Ultra96-V2 を起動して、scp で square_cubed の BOOT.BIN を Micro SD カードの第 1 パーティションにコピーした。
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1
square_cubed_13_200108.png

reboot した。
リブート後に zocl ドライバを起動した。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
square_cubed_16_200108.png

Assistant ウインドウで square_cubed_system -> square_cubed -> Hardware を右クリックし右クリックメニューから Run -> Run Configurations... を選択して、Debugger_square_cubed コンフィギュレーションを作成した。
square_cubed_14_200108.png

square_cubed_15_200108.png

Run ボタンをクリックしたところ、Vitis の Console に TEST PASSED が表示された。成功だ。
square_cubed_17_200108.png

Debugger_square_cubed コンフィギュレーションを起動した時のシリアル・コンソールの様子を示す。
square_cubed_18_200108.png
  1. 2020年01月09日 04:54 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis の アクセラレーション・アプリケーションの動作には、BOOT.BIN の入れ替えが必要

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する3(出力をグローバル・バッファからホストにコピーできない)”で、Vitis アクセラレーション・プラットフォームで出力、つまり、左に曲がるか、直進するか、右に曲がるか(CNN の利用用途は白線走行です)の判定の output と左折、直進、右折の判定スコアの dot2[ ] の 2 個の出力値を出力しているが、それが 0 になってしまって反映されていないという問題があった。いろいろとやってみたところ、Vitis で生成した sd_card ディレクトリの BOOT.BIN を入れ替えてブートしないと値がおかしくなるようだ。

現在の all_layers_template_host.cpp を示す。現在はテストのため、1 イテレーションだけの実行にしてある。

// all_layers_template_host.cpp
// 2019/12/25 by marsee
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "layer_general.h"

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

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

#define NUM_ITERATIONS  100 // C Simulation
//#define NUM_ITERATIONS    1 // C/RTL CoSimulation 2

typedef ap_uint<2> output_type;
typedef ap_fixed<12,7,AP_TRN,AP_WRAP> out_affine_type;

void all_layers_dnn(volatile uint32_t *inm, volatile uint32_t *output,
        volatile int32_t *dot2, int32_t x_size, int32_t y_size);

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

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};

int main(int argc, char* argv[]){
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    output_type output_soft;
    float dot2_soft[NUM_OF_OUTPUT];
    ap_axiu<32,1,1,1> pix;
    int hw_err_cnt = 0;
    int sw_err_cnt = 0;
    const char* xclbinFilename;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    // t_train256[][]を入れるメモリをアロケート
    std::vector<int32_t,aligned_allocator<int32_t>> pixel(ROW_PIXELS*COULMN_PIXELS);
    size_t pixel_in_bytes = (ROW_PIXELS*COULMN_PIXELS) * sizeof(int32_t);

    std::vector<uint32_t,aligned_allocator<uint32_t>> output(1);
    size_t output_in_bytes = sizeof(uint32_t);

    std::vector<int32_t,aligned_allocator<int32_t>> dot2(NUM_OF_OUTPUT);
    size_t dot2_in_bytes = (NUM_OF_OUTPUT * sizeof(int32_t));

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);

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

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

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

            ins_soft << pix;

            pixel[y*COULMN_PIXELS+x] = uint32_t(t_train_256[0][y*COULMN_PIXELS+x]);
        }
    }

    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_all_layers_dnn(program,"all_layers_dnn");
        
    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer pixel_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            pixel_in_bytes, pixel.data());
    cl::Buffer output_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            output_in_bytes, output.data());
    cl::Buffer dot2_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            dot2_in_bytes, dot2.data());

    // Data will be transferred from system memory over PCIe to the FPGA on-board
    // DDR memory.
    q.enqueueMigrateMemObjects({pixel_buf},0/* 0 means from host*/);

    //set the kernel Arguments
    krnl_all_layers_dnn.setArg(0,pixel_buf);
    krnl_all_layers_dnn.setArg(1,output_buf);
    krnl_all_layers_dnn.setArg(2,dot2_buf);
    krnl_all_layers_dnn.setArg(3,COULMN_PIXELS);
    krnl_all_layers_dnn.setArg(4,ROW_PIXELS);

    cl::Event event;
    uint64_t dnn_start, dnn_end;

    //Launch the Kernel
    q.enqueueTask(krnl_all_layers_dnn, NULL, &event);
    //q.enqueueTask(krnl_all_layers_dnn);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({output_buf, dot2_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();
    
    // 時間計測
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_START, &dnn_start);
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_END, &dnn_end);
    auto dnn_time = dnn_end - dnn_start;
    printf("all_layers_dnn : %lu ns\n", dnn_time);
    
    all_layers_soft(ins_soft, output_soft, dot2_soft);

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

    std::cout << "hw_err_cnt = " << hw_err_cnt << " sw_err_cnt = " << sw_err_cnt << std::endl;

    return(0);
}


Micro SD カードの第 1 パーティションに何らかの Vitis の sd_card ディレクトリをコピーし、Root FS を第 2 パーティションにコピーしたUltra96-V2 上の Linux をブートする。
Ubuntu 18.04 のホスト・パソコンで scp を使用して BOOT.BIN を MicroSD カードの第 1 パーティションにコピーした。
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1
template_cnn_2019_02_8_200108.png

Ultra96-V2 の Linux をリブートする。
Linux が起動したら、 root でログインする。
zocl ドライバを起動した。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
template_cnn_2019_02_9_200108.png

Vitis で Run Configuration を起動すると、output、dot2[ ] 出力ともに正常に出力された。
template_cnn_2019_02_10_200108.png
  1. 2020年01月08日 04:47 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS の Vitis Bottom Up Flow を使用する2

Vivado HLS の Vitis Bottom Up Flow を使用する1”の続き。

前回は、 2 個の出力バッファを使用している時に、ホスト・メモリにコピーできないのか?を確かめるために Vivado HLS プロジェクトの square_cubed プロジェクトを作成して確かめてみようということで、 Vivado HLS 2019.2 で square_cubed プロジェクトを作成した。(Vitis 2019.2 の RTL カーネルを作成するため)
今回は、Vitis 2019.2 のアクセラレーション・プロジェクト用のVivado HLS 2019.2 プロジェクトの square_cubed プロジェクトで xo ファイルを作成しよう。

最初に、C シミュレーションを行った。
square_cubed_5_200106.png

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

Initation Interval が 2 クロックになってしまっているが、とりあえずこのままとする。案外 DSP48E を使用している。やはり int32_t だからかな?

C コードの合成後に Solution Settings を見ると、

m_axi_addr64=ture

も入っていた。
square_cubed_7_200106.png

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

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

DMA Read はバーストだが、DMA Write はAXI4 インターフェースが 1 個なので、単発のトランザクションになってしまっているのが分かる。

square_cubed.cpp の

extern "C" { }

を元に戻した。
square_cubed_10_200106.png

再度C コードの合成を行ってから、Export RTL を行った。
square_cubed_11_200106.png

square_cubed.xo が生成された。
square_cubed_12_200107.png
  1. 2020年01月07日 04:28 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS の Vitis Bottom Up Flow を使用する1

”テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する3(出力をグローバル・バッファからホストにコピーできない)”で all_layers_dnn が 2 個の出力バッファを使用しているので、その場合は、ホスト・メモリにコピーできないのか?を確かめるために Vivado HLS プロジェクトの square_cubed プロジェクトを作成して確かめてみよう。ついでに、Vivado HLS の新規プロジェクトを作成する時に Vitis Bottom Up Flow というチェックボックスがあるのを覚えていたので、それを使用して、Vitis の RTL カーネルを生成してみようと思う。

Vivado HLS 2019.2 で square_cubed プロジェクトを作成した。これは、Ultra96 用のプロジェクトである。
square_cubed_1_200106.png

プロジェクト作成時の Solution Configuration の時に Vitis Bottom Up Flow にチェックを入れた。
square_cubed_2_200106.png

square_cubed プロジェクトが生成された。
ソース(square_cubed.cpp)とテストベンチ(square_cubed_tb.cpp)のファイルを作成した。
square_cubed_3_200106.png

プロジェクト作成時に Solution めにゅーから Solution Settings... を選択して見ると、

config_sdx -target xocc

が設定されていた。
square_cubed_4_200106.png

square_cubed.cpp を貼っておく。

// square_cubed.cpp
// 2020/01/05 by marsee
//

#include <stdint.h>

//extern "C" {
void square_cubed(volatile int32_t *in, volatile int32_t *square, volatile int32_t *cubed){
#pragma HLS INTERFACE m_axi depth=10 port=cubed offset=slave bundle=gmem
#pragma HLS INTERFACE m_axi depth=10 port=square offset=slave bundle=gmem
#pragma HLS INTERFACE m_axi depth=10 port=in offset=slave bundle=gmem
#pragma HLS INTERFACE s_axilite port=return bundle=control
    for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        int32_t in_t = in[i];
        square[i] = in_t * in_t;
        cubed[i] = in_t * in_t * in_t;
    }
}
//}


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

// square_cubed_tb.cpp
// 2020/01/06 by marsee
//

#include <iostream>
#include <stdint.h>

void square_cubed(volatile int32_t *in, volatile int32_t *square, volatile int32_t *cubed);

int main(){
    int32_t data[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int32_t square[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int32_t cubed[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    square_cubed(data, square, cubed);

    for(int i=0; i<10; i++){
        std::cout << "data[" << i << "]= " << data[i] << ", square[" << i << "]= " <<
                square[i] << ", cubed[" << i << "]= " << cubed[i] << std::endl;
    }
}

  1. 2020年01月06日 05:31 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する3(出力をグローバル・バッファからホストにコピーできない)

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する2(Vivado HLS 編 2)”の続き。

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する1(Vivado HLS 編 1)”のコードにはバグがあった。固定小数点を int32_t に変更しただけなので、小数点以下が切られてしまっている。これを現在は、256 倍してから int32_t に変更してある。
テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する2(Vivado HLS 編 2)”でエラーの表示を見ると、ハードウェアの出力値がほとんど 0 なのが分かると思う。このバグは 256 倍して出力して、出力側で 1/256 倍することで修正できた。しかし、Vitis アクセラレーション・プラットフォームで出力、つまり、左に曲がるか、直進するか、右に曲がるか(CNN の利用用途は白線走行です)の判定の output と左折、直進、右折の判定スコアの dot2[ ] の 2 個の出力値を出力しているが、それが 0 になってしまって反映されていない。

q.enqueueMigrateMemObjects({output_buf, dot2_buf},CL_MIGRATE_MEM_OBJECT_HOST);

q.enqueueMigrateMemObjects({output_buf},CL_MIGRATE_MEM_OBJECT_HOST);
q.enqueueMigrateMemObjects({dot2_buf},CL_MIGRATE_MEM_OBJECT_HOST);

の 2 つやってみたが、やはり output と dot2[ ] の値が 0 だ。
template_cnn_2019_02_7_200105.png

もしかすると、出力バッファは 1 個のみということなのか? 出力バッファを 1 個にして試してみよう。


そして、白線走行用途では複数回イテレーションを回す必要があるが、そのやり方は、Xilinx 社のGitHub の Xilinx/Vitis_Accel_Examples/cpp_kernels/kernel_chain/ にあるので参考にしたい。

Vitis_Accel_Examples/rtl_kernels/rtl_vadd_2kernels/src/host.cpp を見たところ、複数の出力バッファに出力できているように見えるが、別のカーネルのようだ。1 つのカーネルについて、グローバル・バッファからホストにコピーできるのは 1 個だけか?の結論は持ち越しのようだ。
  1. 2020年01月05日 06:29 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

ラプラシアン・フィルタをRTLカーネルとしてVitisで実装する3

ラプラシアン・フィルタをRTLカーネルとしてVitisで実装する2”の続き。

前回は、Vitis でアクセラレーション・アプリケーション・プロジェクトを作成し、ビルドした後で、Ultra96-V2 上で動作を確認することができた。今回は、lap_filter_aixs_dma のVitis アクセラレーション・アプリケーションを起動した時にできた profile_summary.csv を見てみよう。

前回のRun Configuration は Performance Analysis にも Enable profiling にもチェックが付いていない。
lap_fitler_75_200103.png

それでも、 profile_summary.csv と timeline_trace.csv があるのが分かる。
lap_fitler_80_200103.png

だが、今回の実行時に生成されたファイルであるか分からなかったので、 profile_summary.csv と timeline_trace.csv を削除して、もう一度、lap_filter_aixs_dma のVitis アクセラレーション・アプリケーションを起動した。
lap_fitler_81_200104.png

すると、 profile_summary.csv が作成された。

これを SFTP でホストマシンに引っ張ってきて確認しよう。
sudo su
scp 192.168.3.23:/mnt/profile_summary.csv /home/masaaki/temp

lap_fitler_82_200104.png

LibreOffice で確認した。
プロファイル情報が表示された。
Performance Analysis にも Enable profiling にもチェックがついてなくても情報が収集されるらしい?
lap_fitler_83_200104.png
  1. 2020年01月04日 05:34 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

ラプラシアン・フィルタをRTLカーネルとしてVitisで実装する2

ラプラシアン・フィルタをRTLカーネルとしてVitisで実装する1”の続き。

前回は、”Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2”で使用したラプラシアン・フィルタを RTL カーネルとして実装することにした。Vivado HLS プロジェクトを作成し、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行って、xo ファイルを作成した。
今回は、Vitis でアクセラレーション・アプリケーション・プロジェクトを作成し、ビルドした後で、Ultra96-V2 上で動作を確認した。

最初に Vitis 2019.2 で lap_filter_axis_dma アプリケーション・プロジェクトを作成した。

ラプラシアン・フィルタをRTLカーネルとしてVitisで実装する1”で作成した lap_filter_axis_dma.xo を lap_filter_axis_dma2/src に追加した。

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2”に貼ってあるソースコードを lap_filter_axis_dma2/src に追加した。

ultra96v2_min2 プラットフォームを使用している。

Hardware Functions に lap_filter_axis_dma を指定した。

xclbin ファイル名を lap_filter_axis_dma2 に変更した。

Assistant ウインドウで lap_filter_axis_dma2_system/lap_filter_axis_dma2/Hardware を右クリックし右クリックメニューからBuild を選択してビルドし、成功した。
lap_fitler_74_200102.png

Ultra96-V2 を電源ON して、Linux を起動した。
root でログインして、zocl ドライバを insmod した。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
lap_fitler_77_200103.png

Hardware を右クリックし右クリックメニューから Run -> Run Configurations... を選択した。
Single Application Debug をダブルクリックして、Debugger _lap_filter_axis_dma2 を作成した。
lap_fitler_75_200103.png

lap_fitler_76_200103.png

Run ボタンをクリックして、アプリケーション・ソフトを起動した。
lap_fitler_78_200103.png

問題なく実行できたようだ。
lap_fitler_79_200103.png

Ultra96-V2 上のLinux で /mnt ディレクトリに cd すると、ファイルが見えた。
lap_fitler_80_200103.png

これで、RTL カーネルでのラプラシアン・フィルタの動作が確認できた。
  1. 2020年01月03日 04:16 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

ラプラシアン・フィルタをRTLカーネルとしてVitisで実装する1

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装しようとしていたが、ソースコードとやり方が全くやったこと無いのはやり方が間違っているかコードが間違っているのか分からないので、トラブった時に大変だと考えた。そこで、”Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2”で使用したラプラシアン・フィルタだったら、カーネル・アプリケーションはできているので、これを RTL カーネルとして実装することにした。

新たに Vivado HLS 2019.2 の lap_filter_axis_dma プロジェクトを作成した。
lap_fitler_59_200102.png

lap_filter_axis_dma.cpp は”Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2”に貼ってあるソースコードそのままだ。

bmp_header.h は”Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン1”に貼ってある。

temp.bmp は”Vivado HLS勉強会5(AXI4 Stream)を公開しました”に貼ってある。

lap_filter_axis_dma_tb.cpp を貼っておく。

// lap_filter_axis_dma_tb.c
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
// m_axi offset=slave version
// 2019/12/31 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "bmp_header.h"

int32_t laplacian_fil_soft(int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1, int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2);
int32_t conv_rgb2y_soft(int32_t rgb);
void lap_filter_axis_dma(volatile int32_t *inm, volatile int32_t *outm, int32_t x_size, int32_t y_size);    // hardware
void laplacian_filter_soft(int32_t *cam_fb, int32_t *lap_fb, long width, long height); // software

int main()
{
    int32_t *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int32_t *rd_bmp, *hw_lapd, *sw_lapd;
    int32_t blue, green, red;
    char blue_c, green_c, red_c;

    if ((fbmpr = fopen("test.bmp", "rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.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 =(int32_t *)malloc(sizeof(int32_t) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int32_t *)malloc(sizeof(int32_t) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    if ((sw_lapd =(int32_t *)malloc(sizeof(int32_t) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (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);

    lap_filter_axis_dma((volatile int32_t *)rd_bmp, (volatile int32_t *)hw_lapd, (int32_t)bmpihr.biWidth, (int32_t)bmpihr.biHeight);    // ハードウェアのラプラシアン・フィルタ
    laplacian_filter_soft(rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n", x, y, *h, *s);
                return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.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 (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    free(rd_bmp);
    free(hw_lapd);
    free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(int32_t *cam_fb, int32_t *lap_fb, long width, long height)
{
    int32_t **line_buf;
    int32_t *lap_buf;
    int32_t x, y, i;
    int32_t lap_fil_val;
    int32_t a, b;
    int32_t fl, sl, tl;

    // line_buf の1次元目の配列をアロケートする
    if ((line_buf =(int32_t **)malloc(sizeof(int32_t *) * 3)) == NULL){
        fprintf(stderr, "Can't allocate line_buf[3][]\n");
        exit(1);
    }

    // メモリをアロケートする
    for (i=0; i<3; i++){
        if ((line_buf[i]=(int32_t *)malloc(sizeof(int32_t) * width)) == NULL){
            fprintf(stderr, "Can't allocate line_buf[%d]\n", i);
            exit(1);
        }
    }

    if ((lap_buf=(int32_t *)malloc(sizeof(int32_t) * (width))) == NULL){
        fprintf(stderr, "Can't allocate lap_buf memory\n");
        exit(1);
    }

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<height; y++){
        for (x=0; x<width; x++){
            line_buf[y%3][x] = conv_rgb2y_soft(cam_fb[y*width+x]);

            fl = (y+1)%3;       // 最初のライン, y%3=0 120, y%3=1 201, y=2 012, y=3 120
            sl = (y+2)%3;   // 2番めのライン
            tl = y%3;   // 3番目のライン

            // ラプラシアンフィルタ・データの書き込み
            if (y<2 || x<2){
                lap_fil_val = 0;
                lap_fb[(y*width)+x] = 0;
            } else {
                lap_fil_val = laplacian_fil_soft(   line_buf[fl][x-2], line_buf[fl][x-1], line_buf[fl][x],
                                                    line_buf[sl][x-2], line_buf[sl][x-1], line_buf[sl][x],
                                                    line_buf[tl][x-2], line_buf[tl][x-1], line_buf[tl][x]);
                lap_fb[(y*width)+x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            }
        }
    }
    free(lap_buf);
    for (i=0; i<3; i++)
        free(line_buf[i]);
    free(line_buf);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int32_t conv_rgb2y_soft(int32_t rgb){
    int32_t r, g, b, y_f;
    int32_t y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8; // 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int32_t laplacian_fil_soft(int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1, int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2)
{
    int32_t y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}



Configuration Settings は

config_interface -m_axi_addr64
config_sdx -target xocc


にしてある。
lap_fitler_60_200102.png

C シミュレーションを行ったがエラーになってしまった。
lap_fitler_61_200102.png

/home/masaaki/Vivado_HLS/Ultra96/test/lap_filter_axis_dma/solution1/csim/build/../../../lap_filter_axis_dma_tb.cpp:67: `lap_filter_axis_dma(int volatile*, int volatile*, int, int)' に対する定義されていない参照です
collect2: エラー: ld はステータス 1 で終了しました


調べた結果 extern "C" { } がまずいようだったので、コメントアウトした。
lap_fitler_62_200102.png

もう一度、C シミュレーションを行った。今度は成功した。
lap_fitler_63_200102.png

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

1 クロック毎に約 1 ピクセル処理できている。これが見たかった。

C/RTL 協調シミュレーションを行ったところ、再度エラー。
lap_fitler_65_200102.png
そういえば、AXI4 Master のINTERFACE 指示子の depth の設定がなかったので、追加した。

#pragma HLS INTERFACE m_axi depth=480000 port=outm offset=slave bundle=gmem
#pragma HLS INTERFACE m_axi depth=480000 port=inm offset=slave bundle=gmem


lap_fitler_66_200102.png

これでもう一度、C/RTL 協調シミュレーションを行った。
lap_fitler_67_200102.png

今度はLatency は 3538 クロックと表示されたが、エラーで終了している。
でも、波形表示のアイコンは表示されているので、波形は表示できそうだ。
Open Wave Viewer... アイコンをクリックして、波形を表示した。
lap_fitler_68_200102.png

波形が表示できた。
データを書き込むWrite トランザクションの m_axi_gmem_WVALID と m_axi_gmem_WREADY を見るとほとんど 1 になっていてスループットが取れていることが分かった。良さそうだ。

次に、本番用に lap_filter_axis_dma.ccp の extern "C" { } を戻した。
lap_fitler_69_200102.png

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

結果は extern "C" { } を外した場合と同じだった。

Exprot RTL を行った。
Vivado synthesis, place and route にチェックを入れた。
lap_fitler_71_200102.png

結果を示す。
lap_fitler_72_200102.png

LUT と FF は合成時よりも、だいぶ少なくなった。DSP は 1 個少なくなった。BRAM が 4 個少なくなったのはなぜだろうか?

lap_filter_axis_dma.xo が生成された。
lap_fitler_73_200102.png

やり方をまとめると

1. Vivado HLS プロジェクトを作成して、Soulition メニューからSolution Settings... のGeneral でconfig_interface -m_axi_addr64、 config_sdx -target xocc に設定する。
2. カーネル・アプリケーションのソースコードで、 extern "C" { } をコメントアウトして、C シミュレーション、C コードの合成、C/RTL 協調シミュレーションを行う。
3. C/RTL 協調シミュレーションはエラーになるが、波形は確認できる。
4. extern "C" { } を戻して、C コードの合成をして、Export RTL を行って、”.xo”ファイルを生成する。


という手順になる。

  1. 2020年01月02日 08:44 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

あけましておめでとうございます

あけましておめでとうございます。
昨年中は 403,978 アクセスいただきました。今年もよろしくお願いします。
FPGAs_room_191229.png

12月のアクセスが膨れたのは、ブログを https に変えた時に Google クローラーのアクセスが増えたからです。今もかなりのアクセスが来ています。何やっているんですかね?

今年も引き続き Vitis をやっていこうと思っています。RTL カーネルをやっていますが、Vitis AI もやってみたいですね。

自転車もトレーニングして大会に出てみたいです。ビンディング・ペダルに変えて、まだ乗っていないので、寒いけど乗りたいですね。

Tsukuba Mini Maker Faire 2020 が 2/15, /16 に開催されて、出展しますので、見に来てください。
  1. 2020年01月01日 04:07 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0