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

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

FPGAの部屋

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

Vivado HLS 2019.2 で普通に C ソースコードを書いて Sobel フィルタを実装する2

Vivado HLS 2019.2 で普通に C ソースコードを書いて Sobel フィルタを実装する1”の続き。

前回は、ivado HLS 2019.2 で普通に C ソースコードを書いて Sobel フィルタを実装するためのソースコードとテストベンチを貼った。今回は、C シミュレーション、 C コードの合成、 C/RTL 協調シミュレーション、 Export RTL を行う。

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

sobel_filter_axis3/solution/csim/build ディレクトリを示す。
sobel_filter_axis3_3_200227.png

Sobel フィルタ処理結果の sobel.jpg を示す。綺麗なエッジが検出できている。
sobel_filter_axis3_4_200227.jpg

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

Latency は 480016 クロックで、総ピクセル数よりも 16 クロック多いだけである。リソース使用量も少ない。

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

Latency は 480041 クロックだった。優秀だ。

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

outs_TVALID も ins_TREADY もほとんど 1 にアサートされたままなので、スループットが高い。

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

リソース使用量も少なく、 CP achieved post-implementation が 4.152 ns で大丈夫そうだ。
  1. 2020年02月28日 04:46 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で普通に C ソースコードを書いて Sobel フィルタを実装する1

xfOpenCV 、 HLS Video Library で Sobel フィルタを実装してきたが、 C のソースコードで Sobel フィルタを実装してみよう。
なお、この Sobel フィルタの実装は、HDLab さんで、”FPGAの部屋 プレゼンツ 「 HLSハンズオンセミナー基礎編」”をやっているが、その次のセミナとして Vivado HLS のチューニング方法を詳しく解説する応用編で使用しようと思っている。ただし、ここに載せたソースコードは少し変更してある。
この Sobel フィルタの実装は、横方向と縦方向の Sobel フィルタを同時に行って、その結果を二乗和平方根を取っている。その平方根を計算する部分には、”square root を Vivado HLS で実装する3”のソースコードを使用する。平方根のコードは Sobel フィルタで使用するため開発を行った。

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

// sobel_filter_axis3.cpp
// 2020/02/27 by marsee

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

#define HORIZONTAL  0
#define VERTICAL    1

ap_int<32> sobel_fil(ap_int<32> h_or_v, ap_int<32> x0y0, ap_int<32> x1y0, ap_int<32> x2y0, ap_int<32> x0y1,
        ap_int<32> x1y1, ap_int<32> x2y1, ap_int<32> x0y2, ap_int<32> x1y2, ap_int<32> x2y2);
ap_int<32> conv_rgb2y(ap_int<32> rgb);
ap_int<32> square_root8(ap_int<32> val);

#define DISPLAY_WIDTH 800
#define DISPLAY_HIGHT 600

int sobel_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs){
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> sobel;
    ap_int<32> sobel_val, sobel_h_val, sobel_v_val;

    ap_int<32> line_buf[2][DISPLAY_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    ap_int<32> pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

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

    LOOP_Y: for(int y=0; y<DISPLAY_HIGHT; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=48 avg=48
        LOOP_X: for(int x=0; x<DISPLAY_WIDTH; x++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT min=64 max=64 avg=64
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix; // AXI4-Stream からの入力

            LOOP_PIX_MAT_K: for(int k=0; k<3; k++){
                LOOP_PIX_MAT_M: 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];
            ap_int<32> 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;

            sobel_h_val = sobel_fil(HORIZONTAL, 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]);
            sobel_v_val = sobel_fil(VERTICAL,   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]);
            sobel_val = square_root8(sobel_h_val*sobel_h_val + sobel_v_val*sobel_v_val);
            sobel.data = (sobel_val<<16)+(sobel_val<<8)+sobel_val;

            if(x==0 && y==0) // 最初のピクセル
                sobel.user = 1;
            else
                sobel.user = 0;
            if(x == (DISPLAY_WIDTH-1)) // 行の最後
                sobel.last = 1;
            else
                sobel.last = 0;

            if(x<2 || y<2)
                sobel.data = 0;

            outs << sobel;
        }
    }
    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 にした
ap_int<32> conv_rgb2y(ap_int<32> rgb){
    ap_int<32> r, g, b, y_f;
    ap_int<32> 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);
}

// sobel filter
// HORZONTAL
// x0y0 x1y0 x2y0  1  2  1
// x0y1 x1y1 x2y1  0  0  0
// x0y2 x1y2 x2y2 -1 -2 -1
// VERTICAL
// x0y0 x1y0 x2y0  1  0 -1
// x0y1 x1y1 x2y1  2  0 -2
// x0y2 x1y2 x2y2  1  0 -1
ap_int<32> sobel_fil(ap_int<32> h_or_v, ap_int<32> x0y0, ap_int<32> x1y0, ap_int<32> x2y0, ap_int<32> x0y1,
        ap_int<32> x1y1, ap_int<32> x2y1, ap_int<32> x0y2, ap_int<32> x1y2, ap_int<32> x2y2){
    ap_int<32> y;

    if(h_or_v == HORIZONTAL){
        y = x0y0 + 2*x1y0 + x2y0 - x0y2 - 2*x1y2 - x2y2;
    } else {
        y = x0y0 - x2y0 + 2*x0y1 - 2*x2y1 + x0y2 - x2y2;
    }
    if(y<0)
        y = -y;
        //y = 0;
    else if(y>255)
        y = 255;
    return(y);
}

// square_root8
// 8bit幅のsquare_rootを求める
ap_int<32> square_root8(ap_int<32> val){
    ap_int<32> temp = 0;
    ap_int<32> square;

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

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

    return(temp);
}


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

// sobel_filter_axis3_tb.cpp
// 2020/02/27 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>

int sobel_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs);
int sobel_filter_soft(int32_t *cam_fb, int32_t *sobel_fb,
        int32_t x_size, int32_t y_size);
int32_t square_root8_soft(int32_t val);

const char INPUT_BMP_FILE[] = "test2.jpg";
const char OUTPUT_BMP_FILE[] = "sobel.jpg";

int main(){
    hls::stream<ap_axis<32,1,1,1> > ins;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals;

    // 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_sobel(sizeof(int32_t)*img.cols*img.rows);
    std::vector<int32_t> sw_sobel(sizeof(int32_t)*img.cols*img.rows);

    // 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 i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        ins << pix;
    }

    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 (j==0 && i==0)   // 最初のデータの時に TUSER を 1 にする
                pix.user = 1;
            else
                pix.user = 0;

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

            ins << pix;
        }
    }

    sobel_filter_axis(ins, outs);   // ハードウェアのソーベルフィルタ
    sobel_filter_soft(rd_bmp.data(), sw_sobel.data(), 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_sobel[y*img.cols+x] = (int32_t)val;
            if (val != sw_sobel[y*img.cols+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n",
                        x, y, val, sw_sobel[y*img.cols+x]);
                return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    const int sobel_row = img.rows;
    const int sobel_cols = img.cols;
    cv::Mat wbmpf(sobel_row, sobel_cols, CV_8UC3);
    // wbmpf にsobel フィルタ処理後の画像を入力
    cv::Mat_<cv::Vec3b> sob_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 = sob_vec3b(y,x);
            int32_t rgb = hw_sobel[y*wbmpf.cols+x];
            pixel[0] = (rgb & 0xff); // blue
            pixel[1] = (rgb & 0xff00) >> 8; // green
            pixel[2] = (rgb & 0xff0000) >> 16; // red
            sob_vec3b(y,x) = pixel;
        }
    }

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

    return(0);
}

#define HORIZONTAL  0
#define VERTICAL    1

int32_t sobel_fil_soft(int32_t h_or_v, 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);

int sobel_filter_soft(int32_t *cam_fb, int32_t *sobel_fb,
    int32_t x_size, int32_t y_size){
    int32_t sobel_val, sobel_h_val, sobel_v_val;
    int32_t pix[3][3];

    for(int y=0; y<y_size; y++){
        for(int x=0; x<x_size; x++){
            for(int i=2; i>=0; --i){
                for(int j=2; j>=0; --j){
                    if(x>=2 && y>=2)
                        pix[i][j] = conv_rgb2y_soft(cam_fb[(y-i)*x_size+(x-j)]);
                    else
                        pix[i][j] = 0;
                }
            }
            sobel_h_val = sobel_fil_soft(HORIZONTAL,pix[0][0], pix[0][1], pix[0][2],
                                                    pix[1][0], pix[1][1], pix[1][2],
                                                    pix[2][0], pix[2][1], pix[2][2]);
            sobel_v_val = sobel_fil_soft(VERTICAL,  pix[0][0], pix[0][1], pix[0][2],
                                                    pix[1][0], pix[1][1], pix[1][2],
                                                    pix[2][0], pix[2][1], pix[2][2]);
            sobel_val = square_root8_soft(sobel_h_val*sobel_h_val + sobel_v_val*sobel_v_val);
            sobel_fb[y*x_size+x] = (sobel_val<<16)+(sobel_val<<8)+sobel_val;
        }
    }

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

// sobel filter
// HORZONTAL
// x0y0 x1y0 x2y0  1  2  1
// x0y1 x1y1 x2y1  0  0  0
// x0y2 x1y2 x2y2 -1 -2 -1
// VERTICAL
// x0y0 x1y0 x2y0  1  0 -1
// x0y1 x1y1 x2y1  2  0 -2
// x0y2 x1y2 x2y2  1  0 -1
int32_t sobel_fil_soft(int32_t h_or_v, 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;

    if(h_or_v == HORIZONTAL){
        y = x0y0 + 2*x1y0 + x2y0 - x0y2 - 2*x1y2 - x2y2;
    } else {
        y = x0y0 - x2y0 + 2*x0y1 - 2*x2y1 + x0y2 - x2y2;
    }
    if(y<0)
        y = -y;
        //y = 0;
    else if(y>255)
        y = 255;
    return(y);
}

// square_root8_soft
// 8bit幅のsquare_rootを求める
int32_t square_root8_soft(int32_t val){
    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);
        }
    }

    return(temp);
}


Vivado HLS 2019.2 の sobel_filter_axis3 プロジェクトを作成した。
sobel_filter_axis3_1_200227.png
  1. 2020年02月27日 06:35 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で HLS Video Library を使用した Sobel フィルタを作る2

Vivado HLS 2019.2 で HLS Video Library を使用した Sobel フィルタを作る1”の続き。

前回は、 HLS Video Library で実装した Sobel フィルタのソースコードを貼って、Vivado HLS 2019.2 の sobel_filter プロジェクトを示した。今回は、 sobel_filter プロジェクトで、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

C シミュレーションの結果を示す。
HLS_Video_Library_2_200225.png

7 個ほど OpenCV の結果を合わないとエラーがでているが偏差も 1 または 2 の範囲に収まっている。

sobel_filter/solution1/csim/build ディレクトリを見ると元画像の test2.jpg と HLS Video Library による Sobel フィルタ結果の test2_result.jpg と OpenCV による Sobel フィルタ結果の test2_result_cv.jpg がある。
HLS_Video_Library_3_200225.png

元画像の test2.jpg
HLS_Video_Library_4_200225.jpg

HLS Video Library による Sobel フィルタ結果の test2_result.jpg
HLS_Video_Library_5_200225.jpg

OpenCV による Sobel フィルタ結果の test2_result_cv.jpg
HLS_Video_Library_6_200225.jpg

画像を見る限りでは、 test2_result.jpg も test2_result_cv.jpg も同じようである。

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

Latency は 485824 クロックで 485824 クロック / 480000 ピクセル 約 1.01 クロック / ピクセルだった。問題無さそうだ。
BRAM_18K の使用量も 3 個で、その他のリソースも使用量が xfOpenCV よりも圧倒的に少ない。

HLS Video Library では Sobel フィルタの実装を Filter2D でやっているようだ。その合成レポートを示す。
HLS_Video_Library_8_200225.png

Analysis 画面を示す。
HLS_Video_Library_9_200225.png

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

Latency は 485860 クロックだった。

C/RTL 協調シミュレーションの波形を見てみよう。
HLS_Video_Library_11_200225.png

拡大してみた。
HLS_Video_Library_12_200225.png

OUTPUT_STREAM_TVALID が 0 に落ちているところの周期は 4.035 us だった。800 クロックで 4 us なので 1 行分のようだ。

OUTPUT_STREAM_TVALID が 0 に落ちているところを拡大した。
HLS_Video_Library_13_200225.png

OUTPUT_STREAM_TVALID が 0 に落ちている時間は 35 ns 、つまり 7 クロック分だった。
xfOpenCV に比べて優秀だ。

Export RTL を行った。
HLS_Video_Library_14_200225.png

リソース使用量も少ないし、CP achieved post-implementation も 3.433 ns と問題ない。
  1. 2020年02月26日 05:01 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で HLS Video Library を使用した Sobel フィルタを作る1

前回まで、xfOpenCV で Sobel フィルタを実装してきたが、Vivado HLS には旧 OpenCV のスキームがまだ、 2019.2 でも備わっている。それが HLS Video Library だ。Xilinx Wiki に HLS Video Library の資料がある
HLS Video Library の hls::Sobel() を使って、Sobel フィルタを実装してみよう。
実は”FPGAの部屋”のブログで 1 度 HLS Video Library で Sobel フィルタを実装してある。下に示す。
Vivado HLS 2015.4 で OpenCV を使ってみた3(Sobelフィルタを試した1)
Vivado HLS 2015.4 で OpenCV を使ってみた4(Sobelフィルタを試した2)
Vivado HLS 2015.4 で OpenCV を使ってみた5(Sobelフィルタを試した3)
Vivado HLS 2015.4 で OpenCV を使ってみた6(Sobelフィルタを試した4)

今回は、”Vivado HLS 2015.4 で OpenCV を使ってみた3(Sobelフィルタを試した1)”のソースコードを少し変更してやってみよう。

最初に、sobel_filter_hvl.h を示す。

// sobel_filter_hvl.h
// 2020/02/25 by marsee

#ifndef __SOBEL_FILTER_HVL_H__
#define __SOBEL_FILTER_HVL_H__

#include "ap_axi_sdata.h"
#include "hls_video.h"

#define MAX_HEIGHT    600
#define MAX_WIDTH    800

typedef hls::stream<ap_axiu<32,1,1,1> > AXI_STREAM;
typedef hls::Scalar<3, unsigned char> RGB_PIXEL;
typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC3> RGB_IMAGE;
typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC1> GRAY_IMAGE;
#endif



ソースコードの sobel_filter_hvl.cpp を示す。横方向の Sobel フィルタとなっている。

// sobel_filter_hvl.cpp
// 2020/02/25 by marsee

// 2016/04/03 : グレー変換あり Sobel フィルタ

#include "sobel_filter_hvl.h"

void sobel_filter(AXI_STREAM& INPUT_STREAM, AXI_STREAM& OUTPUT_STREAM, int rows, int
cols) {
#pragma HLS DATAFLOW
#pragma HLS INTERFACE ap_stable port=cols
#pragma HLS INTERFACE ap_stable port=rows
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=OUTPUT_STREAM
#pragma HLS INTERFACE axis register both port=INPUT_STREAM
#pragma HLS INTERFACE s_axilite port=cols
#pragma HLS INTERFACE s_axilite port=rows

    RGB_IMAGE img_0(rows, cols);
    GRAY_IMAGE img_1g(rows, cols);
    GRAY_IMAGE img_2g(rows, cols);
    RGB_IMAGE img_3(rows, cols);

    hls::AXIvideo2Mat(INPUT_STREAM, img_0);
    hls::CvtColor<HLS_BGR2GRAY>(img_0, img_1g);
    hls::Sobel<1,0,3>(img_1g, img_2g);
    hls::CvtColor<HLS_GRAY2BGR>(img_2g, img_3);
    hls::Mat2AXIvideo(img_3, OUTPUT_STREAM);
}


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

// sobel_filter_hvl_tb.cpp
// 2020/02/25 by marsee

// OpenCV 2 の Mat を使用したバージョン
// 2016/04/03 : グレー変換あり Sobel フィルタ

#include <iostream>
#include "hls_opencv.h"
#include "sobel_filter_hvl.h"

using namespace cv;

#define INPUT_IMAGE        "test2.jpg"
#define OUTPUT_IMAGE    "test2_result.jpg"
#define OUTPUT_IMAGE_CV    "test2_result_cv.jpg"

void sobel_filter(AXI_STREAM& INPUT_STREAM, AXI_STREAM& OUTPUT_STREAM, int rows, int
cols);
void opencv_sobel_filter(Mat& src, Mat& dst);

int main (int argc, char** argv) {
    // OpenCV で 画像を読み込む
    Mat src = imread(INPUT_IMAGE);
    AXI_STREAM src_axi, dst_axi;

    // Mat フォーマットから AXI4 Stream へ変換
    cvMat2AXIvideo(src, src_axi);

    // image_filter() 関数をコール
    sobel_filter(src_axi, dst_axi, src.rows, src.cols);

    // AXI4 Stream から Mat フォーマットへ変換
    // dst は宣言時にサイズとカラー・フォーマットを定義する必要がある
    Mat dst(src.rows, src.cols, CV_8UC3);
    AXIvideo2cvMat(dst_axi, dst);

    // Mat フォーマットからファイルに書き込み
    imwrite(OUTPUT_IMAGE, dst);

    // opencv_image_filter() をコール
    Mat dst_cv(src.rows, src.cols, CV_8UC3);
    opencv_sobel_filter(src, dst_cv);
    imwrite(OUTPUT_IMAGE_CV, dst_cv);

    // dst と dst_cv が同じ画像かどうか?比較する
    for (int y=0; y<src.rows; y++){
        Vec3b* dst_ptr = dst.ptr<Vec3b>(y);
        Vec3b* dst_cv_ptr = dst_cv.ptr<Vec3b>(y);
        for (int x=0; x<src.cols; x++){
            Vec3b dst_bgr = dst_ptr[x];
            Vec3b dst_cv_bgr = dst_cv_ptr[x];

            // bgr のどれかが間違っていたらエラー
            if (dst_bgr[0] != dst_cv_bgr[0] || dst_bgr[1] != dst_cv_bgr[1] || dst_bgr[2] != dst_cv_bgr[2]){
                printf("x = %d, y = %d,  Error dst=%d,%d,%d dst_cv=%d,%d,%d\n", x, y,
                        dst_bgr[0], dst_bgr[1], dst_bgr[0], dst_cv_bgr[0], dst_cv_bgr[1], dst_cv_bgr[2]);
                //return 1;
            }
        }
    }
    printf("Test with 0 errors.\n");

    return 0;
}

void opencv_sobel_filter(Mat& src, Mat& dst){
    Mat gray(src.rows, src.cols, CV_8UC1);
    Mat img0g(src.rows, src.cols, CV_8UC1);

    cvtColor(src, gray, CV_BGR2GRAY);
    Sobel(gray, img0g, IPL_DEPTH_16S, 1, 0, 3);
    cvtColor(img0g, dst, CV_GRAY2BGR);

}


Vivado HLS 2019.2 の sobel_filter プロジェクトを示す。このプロジェクトは、Ultra96 用のクロック周期が 5 ns のプロジェクトだ。
HLS_Video_Library_1_200225.png
  1. 2020年02月25日 05:05 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で xfOpenCV を使用する5(sobel filter 3)

Vivado HLS 2019.2 で xfOpenCV を使用する4(sobel filter 2)(xfOpenCV を使用する時のVivado HLSの設定方法)”の続き。

前回は、sobel_filter プロジェクトを使用して、 xfOpenCV を使用する時の GUI 上での Vivado HLSの設定方法を紹介した。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

まずは、C シミュレーションからやってみよう。
xfOpenCV_30_200223.png

sobel_filter/solution1/csim/build ディレクトリの内容を示す。
xfOpenCV_31_200223.png

hls.jpg を示す。
xfOpenCV_32_200223.jpg

左端のエッジが表示されているようだ。

out_ocv.jpg を示す。
xfOpenCV_33_200223.jpg

out_error.jpg を示す。やはり、左端の線が表示されている。
xfOpenCV_34_200223.jpg

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

BRAM_18K が 239 個使用されている。これは、Mat のメモリを実装してしまっているのだろうか?
analysis 画面を見ても大量のBRAM が実装されている。何で?
xfOpenCV_36_200223.png

HLS ビデオライブラリで Sobel フィルタを実装した際は BRAM の使用量は 3 個だった。ラインバッファ使っているので、これくらいだと思うのだが、素直に Mat をメモリとして実装しているとしか考えられない?

C/RTL 協調シミュレーションを行った。
Dump Trace は all に変更した。
xfOpenCV_37_200223.png

結果を示す。
xfOpenCV_38_200223.png

1447426 クロックかかっている。総ピクセル数は 480000 個なので、約 3.02 クロック/ピクセルになっている。

C/RTL 協調シミュレーションの波形を示す。
出力と入力がきれいに分かれていて、出力期間は入力期間の約 1/2 になっている。3倍かかるはずだ。。。DATAFLOW 指示子を入れてあるのにシリアライズされているようだ。これではバッファも総ピクセル数分は必要となるだろう?
xfOpenCV_39_200223.png

入力部分を拡大してみた。 2 クロックに 1 回データ転送している。
xfOpenCV_40_200223.png

今度は出力部分を拡大した。TVALID が 0 に下がる部分までの周期は 4.02 us だった。
TVALID が 0 の部分は 20 ns なので、 4000 ns がデータ転送期間だ。 4000 ns / 5 ns(クロック周期) = 800 なので、1行のピクセル数になる。出力だけ取ってみれば、まともな出力であると言える。
xfOpenCV_41_200223.png

xfOpenCV_42_200223.png

Export RTL を行った。
Vivado synthesis, place and route にチェックを入れて OK ボタンをクリックした。
xfOpenCV_43_200223.png

RAMB18 と RAMB36/FIFO が足りないというエラーになった。メモリ食いすぎ。。。
xfOpenCV_44_200223.png

エラー内容を示す。

ERROR: [DRC UTLZ-1] Resource utilization: RAMB18 and RAMB36/FIFO over-utilized in Top Level Design (This design requires more RAMB18 and RAMB36/FIFO cells than are available in the target device. This design requires 470 of such cell types but only 432 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device.)
ERROR: [DRC UTLZ-1] Resource utilization: RAMB36/FIFO over-utilized in Top Level Design (This design requires more RAMB36/FIFO cells than are available in the target device. This design requires 235 of such cell types but only 216 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device.)
ERROR: [DRC UTLZ-1] Resource utilization: RAMB36E2 over-utilized in Top Level Design (This design requires more RAMB36E2 cells than are available in the target device. This design requires 235 of such cell types but only 216 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device.)
INFO: [Vivado_Tcl 4-198] DRC finished with 3 Errors


少なくとも xfOpenCV の Sobel フィルタは現時点では使い物にならないということが分かった。HLS ビデオライブラリか 自分でコードを書いたほうがよほど良いと思う。

(2020/02/29:追記)
Vivado HLS 2019.2 で xfOpenCV を使用する2(dilation 2)”で教えてもらった方法で HLS stream プラグマで depth=1 を depth=16 にしたらレイテンシが 1/2 になった。

ソースコードの xf_sobel.cpp の HLS stream プラグマを示す。

#pragma HLS stream variable=imgInput1.data dim=1 depth=16
#pragma HLS stream variable=imgOutput1.data dim=1 depth=16


C コードの合成を行った。以前の結果と並べてみた。左が以前の結果だ。
xfOpenCV_35_200223.pngxfOpenCV_49_200228.png

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

968026 クリックかかっている。480000 ピクセルなので、約倍のクロックかかっている。前よりも少ない。

波形を示す。入力と出力がパイプラインされていない。画面分のバッファが必要だ。
xfOpenCV_51_200228.png

Export RTL は同様にRAMB18 と RAMB36/FIFO が足りないというエラーになった。やはり、メモリ食いすぎ。。。

xfOpenCV の sobel フィルタはやはり使えないようだ。
  1. 2020年02月24日 05:07 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:0

なひたふさんの「Vivadoのプロジェクトをgitで管理する最小限は何か」を参考にしてVivado のプロジェクトをTCLファイルで復元した2

なひたふさんの「Vivadoのプロジェクトをgitで管理する最小限は何か」を参考にしてVivado のプロジェクトをTCLファイルで復元した”の続き。

前回、Vivado プロジェクトを送る時に最小限のファイルで送れればとっても良い。そこで、なひたふさんの「Vivadoのプロジェクトをgitで管理する最小限は何か」を参考にして最小限のファイルでVivado プロジェクトを復元してみたのだが、手続きが多すぎた。そこで、もっと簡単にできる方法ということで、tcl ファイルを編集してみた。

これが今回、移動する Vivado 2019.2 のプロジェクトだ。
Vivado 2019.2 の sums_ex3 プロジェクトで、同じ階層に Vivado HLS 2019.2 の s_squares_axim プロジェクトがあって、s_squares_axim\solution1\impl\ip の IP をブロックデザインの IP として使用している。
Vivado_2019_2_1_200223.png

File メニューから Project -> Write Tcl... を選択した。
Vivado_2019_2_2_200223.png

Write Project to Tcl ダイアログが表示された。Copy sources to new project と Recreate Block Designs using Tcl にチェックを入れて、OK ボタンをクリックした。
Vivado_2019_2_3_200223.png

sums_ex3.tcl が出力された。書き換える行は origin_dir のある行だ。(set origin_dir ".")
150 行目と 159 行目となる。
Vivado_2019_2_4_200223.png

150 行目の set_property "ip_repo_paths"は Vivado HLS の IP へのパスなので、 "$origin_dir/s_squares_axim/solution1/impl/ip" とした。
159 行目はインポートするブロックデザインのラッパー HDL ファイルへのパスなので、"${origin_dir}/Vivado/sums_bd_wrapper.v"(Vivado フォルダの下にある)
Vivado_2019_2_5_200223.png

これで、修正は終了し、セーブした。

Vivado プロジェクトを作成するフォルダに sums_ex3.tcl をコピーした。Vivado HLS 2019.2 の s_squares_axim フォルダをコピーした。
Vivado_2019_2_6_200223.png

Vivado フォルダの下には、sums_bd_warpper.v をコピーした。
Vivado_2019_2_7_200223.png

スタートメニューから Vivado 2018.3 Tcl Shell を開く。
cd で希望のフォルダに行った。
sums_ex3.tcl を起動した。
source sums_ex3.tcl
Vivado_2019_2_8_200223.png

Vivado_2019_2_9_200223.png

sums_ex3 フォルダが生成され、フォルダの中に sums_ex3.xpr という名前の Vivado 2019.2 プロジェクト・ファイルが生成されていた。sums_ex3.xpr を Vivado 2019.2 で読み込んで、ブロックデザインを表示した。
Vivado_2019_2_11_200223.png

ビットファイルも問題なく生成できた。
Vivado_2019_2_12_200223.png
  1. 2020年02月23日 15:13 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で xfOpenCV を使用する4(sobel filter 2)(xfOpenCV を使用する時のVivado HLSの設定方法)

Vivado HLS 2019.2 で xfOpenCV を使用する3(sobel filter 1)”の続き。

前回は、xfOpenCV の xfopencv/examples/sobelfilter をやってみようと思う。ただし、この soblefilter は xf::Mat のインターフェースなので、 AXI4-Stream インターフェースに変更した。更に現状の解像度では、Ultra96 のリソースに入らないので、画像のサイズを縮小した。今回は、sobel_filter プロジェクトを使用して、 xfOpenCV を使用する時の GUI 上での Vivado HLSの設定方法を紹介する。

最初に Vivado HLS 2019.2 で sobel_filter プロジェクトを作成した。
xfOpenCV_22_200223.png

xfOpenCV 用の設定を行う。
Vivado HLS の Project メニューから Project Settings... を選択する。
xfOpenCV_23_200223.png

左のペインから Simulation を選択し、Simulation Settings の TestBench Files から xf_sobel_tb.cpp を選択する。
右の Edit CFLAGS... ボタンをクリックする。
xfOpenCV_24_200223.png

Edit CFLAG Dialog が表示される。
そこに、

-D__SDSVHLS__ -I/home/masaaki/xfopencv/include --std=c++0x

を入力した。
-I の後ろは xfOpenCV の下の include ファイルへのパスを指定する。
xfOpenCV_25_200223.png

Input Argumets に test2.jpg 入力して、OK ボタンをクリックする。(他の Synthesis とかをクリックすると OK ボタンがハイドされて押せなくなるようだ)
xfOpenCV_26_200223.png

もう一度、Vivado HLS の Project メニューから Project Settings... を選択する。
xfOpenCV_23_200223.png

左のペインから Synthesis を選択する。
xf_sobel.cpp を選択し、右の Edit CFLAGS... ボタンをクリックする。
Edit CFLAG Dialog が表示される。
そこに、

-D__SDSVHLS__ -I/home/masaaki/xfopencv/include --std=c++0x

を入力した。

次に xf_sobel_accel.cpp を選択し、右の Edit CFLAGS... ボタンをクリックする。
Edit CFLAG Dialog が表示される。
そこに、

-D__SDSVHLS__ -I/home/masaaki/xfopencv/include --std=c++0x

を入力した。

Top Function を指定する。
Browse... ボタンをクリックして、 xf_sobel を選択した。
xfOpenCV_29_200223.png

これで設定は終了だ。
  1. 2020年02月23日 05:47 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で xfOpenCV を使用する3(sobel filter 1)

Vivado HLS 2019.2 で xfOpenCV を使用する2(dillation 2)”の続き。

前回は、xfOpenCV の dillation をやったが、今回は、xfOpenCV の xfopencv/examples/sobelfilter をやってみようと思う。ただし、この soblefilter は xf::Mat のインターフェースなので、 AXI4-Stream インターフェースに変更した。更に現状の解像度では、Ultra96 のリソースに入らないので、画像のサイズを縮小した。

変更したソースコードの xf_soble.cpp を示す。

// xf_sobel.cpp
// 2020/02/13 by marsee

// xfopencv/HLS_Use_Model/Standalone_HLS_AXI_Example/xf_ip_accel_app.cpp のコードを引用している
// https://github.com/Xilinx/xfopencv/blob/master/HLS_Use_Model/Standalone_HLS_AXI_Example/xf_ip_accel_app.cpp

#include "xf_sobel_config.h"
#include "common/xf_infra.h"

void xf_sobel(hls::stream< ap_axiu<8,1,1,1> >& _src,hls::stream< ap_axiu<8,1,1,1> >& _dst,int height,int width){
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both  port=_src
#pragma HLS INTERFACE axis register both  port=_dst

     xf::Mat<XF_8UC1, HEIGHT, WIDTH, NPC1> imgInput1(height,width);
     xf::Mat<XF_8UC1, HEIGHT, WIDTH, NPC1> dstgx(height,width);
     xf::Mat<XF_8UC1, HEIGHT, WIDTH, NPC1> dstgy(height,width);

#pragma HLS stream variable=imgInput1.data dim=1 depth=1
#pragma HLS stream variable=imgOutput1.data dim=1 depth=1
#pragma HLS dataflow

    xf::AXIvideo2xfMat(_src, imgInput1);

    sobel_accel(imgInput1,dstgx, dstgy);

    xf::xfMat2AXIvideo(dstgx, _dst);
}


テストベンチ xf_sobel_tb.cpp のコードを示す。

// xf_sobel_tb.cpp
// 2020/02/13 by marsee

// xfopencv/HLS_Use_Model/Standalone_HLS_AXI_Example/xf_dilation_tb.cpp のコードを引用している
// https://github.com/Xilinx/xfopencv/blob/master/HLS_Use_Model/Standalone_HLS_AXI_Example/xf_dilation_tb.cpp

#include "xf_headers.h"
#include "xf_sobel_config.h"
#include "common/xf_infra.h"
#include "common/xf_axi.h"

void xf_sobel(hls::stream< ap_axiu<8,1,1,1> >& _src,hls::stream< ap_axiu<8,1,1,1> >& _dst,int height,int width);

int main(int argc, char** argv)
{

    if(argc != 2)
    {
        fprintf(stderr,"Invalid Number of Arguments!\nUsage:\n");
        fprintf(stderr,"<Executable Name> <input image path> \n");
        return -1;
    }

    cv::Mat out_img,ocv_ref;
    cv::Mat in_img,in_img1,diff;

    // reading in the color image
    in_img = cv::imread(argv[1], 0);
    if (in_img.data == NULL)
    {
        fprintf(stderr,"Cannot open image at %s\n", argv[1]);
        return 0;
    }
    // create memory for output images
    ocv_ref.create(in_img.rows,in_img.cols,CV_8UC1);
    diff.create(in_img.rows,in_img.cols,CV_8UC1);
    in_img1.create(in_img.rows,in_img.cols,CV_8UC1);

    uint16_t height = in_img.rows;
    uint16_t width = in_img.cols;

    /////////////////   Opencv  Reference  ////////////////////////
    cv::Sobel(in_img, ocv_ref, CV_8UC1, 1, 0, 3);
    cv::imwrite("out_ocv.jpg", ocv_ref);

    hls::stream< ap_axiu<8,1,1,1> > _src,_dst;

    cvMat2AXIvideoxf<NPC1>(in_img, _src);
    xf_sobel(_src, _dst, height, width);
    AXIvideo2cvMatxf<NPC1>(_dst, in_img1);

    cv::imwrite("hls.jpg", in_img1);
    //////////////////  Compute Absolute Difference ////////////////////

    cv::absdiff(ocv_ref, in_img1, diff);
    cv::imwrite("out_error.jpg", diff);

    // Find minimum and maximum differences.
        double minval=256,maxval=0;
    int cnt = 0;
    for (int i=0; i<in_img.rows; i++)
    {
        for(int j=0; j<in_img.cols; j++)
        {
            uchar v = diff.at<uchar>(i,j);
            if (v>0)
                cnt++;
            if (minval > v)
                minval = v;
            if (maxval < v)
                maxval = v;
        }
    }

    float err_per = 100.0*(float)cnt/(in_img.rows*in_img.cols);
    fprintf(stderr,"Minimum error in intensity = %f\n Maximum error in intensity = %f\n Percentage of pixels above error threshold = %f\n",minval,maxval,err_per);

    if(err_per > 0.3f)
        return 1;


    return 0;

}


Vivado HLS 2019.2 で Ultra96-V2 用の sobel_filter プロジェクトを作成して、そこに xfopencv/examples/sobelfilter ディレクトリから、その他、 xf_config_params.h, xf_headers.h, xf_sobel_accel.cpp, xf_sobel_config.h を持ってきてある。
xfOpenCV_20_200222.png

ただし、 xf_sobel_config.h は Ultra96-V2 に入らないので、WIDTH を 800 に HEIGHT を 600 に変更してある。
xfOpenCV_21_200222.png
  1. 2020年02月22日 09:52 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で xfOpenCV を使用する2(dilation 2)

Vivado HLS 2019.2 で xfOpenCV を使用する1(dilation 1)”の続き。

前回は、Vivado HLS 2019.2 で xfOpenCV を使ってみることにしたということで、xfopencv/HLS_Use_Model/Standalone_HLS_AXI_Example のスクリプトを実行して、 dilation_project を作成し、C シミュレーションの結果を表示した。今回は、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

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

Latency の max は 2087198 クロックだった。画像は、1920 x 1080 = 2073600 ピクセルだから、約 1 クロック/ピクセルだった。

次に、dilation_accel の合成結果を見てみよう。
xfOpenCV_15_200220.png

その下の、grp_dilate_fu_82 の合成結果を見る。

xfOpenCV_16_200220.png

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

Latency は 4168112 クロックだった。C コードの合成の見積もりの倍のクロックが必要とのレポートが出ている。
どういうこと?ということで波形を見てみよう。
拡大した波形を示す。
xfOpenCV_18_200220.png

_src の TREADY と _dst の TVALID が 1クロック毎に 1 → 0 を繰り返しているので、スループットは 1/2 になっている。
約 2 クロック/ピクセルとなっていしまっている。なぜ C コードの合成のレポートがでは、レイテンシが正確ではないのだろうか?

Export RTL を行った。結果を示す。
xfOpenCV_19_200220.png

(2020/02/29:追記)

C/RTL協調シミュレーションにおいてスループットが1/2となってしまっているのは、xf::Matのメンバであるhls::streamに対して、depth=1と指定されているのが原因のようです。

と教えていただたいので、depth=16 にしてもう一度やってみた。教えていただいた方も depth=16 にされていたようだが、私も 16 にした理由は、LUT を使用した分散RAM の depth が 16 だからである。教えていただいてありがとうございました。

ソースコードの xf_accel_app.cpp の HLS stream プラグマを示す。

#pragma HLS stream variable=imgInput1.data dim=1 depth=16
#pragma HLS stream variable=imgOutput1.data dim=1 depth=16


これで、C シミュレーションは変わらないので、 C コードの合成を行った。以前の結果と並べてみた。左が以前の結果だ。
xfOpenCV_14_200220.pngxfOpenCV_45_200228.png

Latency は同じだが、Utilization Estimates の FIFO の項目が以前に比べて、FF が 4 個、 LUT が 8 個増えている。

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

Latency は 2087199 クロックだった。以前は、 4168112 クロックだったので、約半分になった。
1920 ピクセル x 1080 行 = 2073600 ピクセルなので、2087199 / 2073600 ≒ 1.01 クロック / ピクセルとなり、ほとんど 1 クロック / ピクセルだったので、性能は問題なくなった。

C/RTL 協調シミュレーションの波形を示す。拡大してみたところ、 p_dst_TVALID も p_src_TREADY もほとんど 1 だった。
xfOpenCV_47_200228.png

Export RTL を行った。
リソース使用量も少なく、 CP achieved post-implementation が 3.320 ns で大丈夫そうだ。
xfOpenCV_48_200228.png

dilation は使い物になりそうだ。
  1. 2020年02月21日 03:42 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:2

Vivado HLS 2019.2 で xfOpenCV を使用する1(dilation 1)

Vivado HLS 2019.2 で xfOpenCV を使ってみることにした。どのくらい使えるのだろうか?
前回、Vivado HLS 2018.3 でも同様に dilation をやっている。
Vivado HLS で xfOpenCV を使用する1
Vivado HLS で xfOpenCV を使用する2(Vivado HLS 2018.3 のGUI を使用する)

ザイリンクス OpenCV ユーザ ー ガイド UG1233 (v2019.1) 2019 年 6 月 5 日”の 46 ページの”第4章 HLS の使用”によると以下の制限があるそうです。引用します。

Vivado HLS 2019.1 で使用モデルが正しく機能するようにするため、次の変更を加える必要があります。
1. 適切なコンパイル時間オプションの使用: HLS で xfOpenCV 関数を使用する際、コンパイル時に -D__SDSVHLS__
および -std=c++0x オプションを使用する必要があります。
2. インターフェイス レベル引数へのインターフェイス プラグマの指定: 最上位インターフェイス引数を (1 つ以上の
読み出し/書き込みアクセスを持つ) ポインターとして含む関数には、m_axi インターフェイス プラグマを指定す
る必要があります。次に例を示します。
void lut_accel(xf::Mat<TYPE, HEIGHT, WIDTH, NPC1> &imgInput,
xf::Mat<TYPE, HEIGHT, WIDTH, NPC1> &imgOutput, unsigned char *lut_ptr)
{
#pragma HLS INTERFACE m_axi depth=256 port=lut_ptr offset=direct bundle=lut_ptr
 xf::LUT< TYPE, HEIGHT, WIDTH, NPC1> (imgInput,imgOutput,lut_ptr);
}


早速、xfOpenCV を git clone した。
git clone https://github.com/Xilinx/xfopencv.git
xfOpenCV_1_200219.png

今回は、xfopencv/HLS_Use_Model/Standalone_HLS_AXI_Example をやってみよう。
xfOpenCV_2_200220.png

これをなぜやるかと言うと、AXI4-Stream 入力、出力になっているからである。実際の回路で使用する時に沿った入出力になっているからだ。ソースコードトップの xf_ip_accel_app.cpp を引用する。
xfOpenCV_3_200220.png

dilation_accel() を挟んで、xf::AXIvideo2xfMat() と xf::xfMat2AXIvideo() が使用されている。

それでは、この Standalone_HLS_AXI_Example ディレクトリを Vivado_HLS/xfOpenCV ディレクトリの下にコピーした。
xfOpenCV_4_200220.png

そして、 script.tcl を起動する前に、インクルード・ディレクトリを変更した。ディレクトリをコピーしてあるので、相対パスではなく絶対パスに変更した。
元の script.tcl を示す。
xfOpenCV_5_200220.png

変更後の script.tcl を示す。
xfOpenCV_6_200220.png

-cflags "-D__SDSVHLS__ -I../../include --std=c++0x"

-cflags "-D__SDSVHLS__ -I/home/masaaki/xfopencv/include --std=c++0x"

に変更した。

script.tcl を実行した。
vivado_hls -f script.tcl
xfOpenCV_7_200220.png

Vivado HLS の GUI を立ち上げて、いま作成した dillation_project を読み込んだ。
xfOpenCV_8_200220.png

持っていないFPGA が指定されていた。
Ultra96-V2 の ZYNQ UltraScale+ MPSoC に変更しよう。
Solution メニューから Solution Settings... を選択した。
Solution Settings (solution1) で Synthesis を選択して、Part Selection を xczu3eg-sbva481-1-e に変更した。
xfOpenCV_9_200220.png

これで FPGA が置き換わったが、C シミュレーションは変更なしなはずなので、見ていこう。
csim のレポートを見た。成功している。
xfOpenCV_10_200220.png

dillation_project/solution1/csim/build ディレクトリを見た。
xfOpenCV_11_200220.png

元画像の tsetcase55.jpg を示す。
xfOpenCV_12_200220.jpg

ハードウェアで Dilation 処理しているはずの hls.jpg を示す。
なお、Dilation は膨張という意味で、白部分を拡張する機能らしい。”モルフォロジー変換”参照。
xfOpenCV_13_200220.jpg

白黒になっている以外の違いが良く分からない?
多少、白い点々が大きくなっているか?
  1. 2020年02月20日 04:57 |
  2. reVISION, xfOpenCV
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクトでラプラシアン・フィルタAXI4-Streamバージョンのカーネルを複数インスタンス

今回は、Vitis 2019.2 アプリケーション・プロジェクトでラプラシアン・フィルタAXI4-Streamバージョンのカーネルを複数インスタンスしてみよう。

Vitis 統合ソフトウェア プラットフォームの資料 アプリケーション アクセラレーション開発 UG1393 (v2019.2) 2019 年 11 月 11 日”の”第 55 章 アドバンス トピック: 複数の計算ユニットおよびカーネルのストリーミング”の”複数の計算ユニット”によると、

Vitis 環境では、v++ コマンドで --nk オプションを使用してカーネル (計算ユニット) のインスタンス数を指定します。--nk オプションは、xlcbin ファイルにインスタンシエートするカーネルの数と名前マップを指定します。

ということなので、やってみよう。

Vitis 2019.2 の streaming_lap_filter4 プロジェクトを作成した。(もうすでにビルドしてあるが)
streaming_lap_filter_92_200219.png

Assistant ウインドウの streaming_lap_filter4_system -> streaming_lap_filter4 を右クリックして右クリックメニューから Settings... を選択する。

Project Settings ダイアログが表示される。
V++ linker options: に

--config ../src/krnl_stream_dmar_lap_dmaw.ini

を入力した。
streaming_lap_filter_93_200219.png

krnl_stream_dmar_lap_dmaw.ini の内容を示す。

[connectivity]
nk=dma_read:1:dma_read_2
nk=krnl_lap_filter_dmaw:1:krnl_lap_filter_dmaw_2

stream_connect=dma_read_2.outs:krnl_lap_filter_dmaw_2.ins
stream_connect=dma_read_1.outs:krnl_lap_filter_dmaw_1.ins


これで Hardware をビルドした。

Vivado のブロックデザインを見てみよう。
streaming_lap_filter_94_200219.png

dma_read_1, dma_read_2, krnl_lap_filter_dmaw_1, krnl_lap_filter_dmaw_2 が実装されているのが分かる。2 組のストリーミング接続のカーネルが実装された。

アドレス・エディタの画面を示す。
streaming_lap_filter_95_200219.png

ホスト・プログラムも作ったが、まだうまく動いていない。とりあえず、他に書きたい内容が目白押しなので、後でデバックしようと思う。

(追記)
Vitis_Accel_Examples/host/mult_compute_units/ が複数の計算ユニットを使った簡単な例だそうだ。ホスト・プログラムを作る際には、Vitis_Accel_Examples/host/mult_compute_units/ を参考にしよう。
  1. 2020年02月19日 04:07 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Ultra96 USB-to-JTAG/UART Pod がWindows 10 で動作しなくなった2

Ultra96 USB-to-JTAG/UART Pod がWindows 10 で動作しなくなった”の続き。

Ultra96 USB-to-JTAG/UART Pod Ver.1 を Ultra96-V2 に取り付けたのに、USB ケーブルをつないだ Windows 10 でシリアル変換の COM ポートが生成されなくて困っていたが COM ポートを生成させることができたのでブログに書いておく。

最初に、本当にイダさんにお世話になりました。ありがとうございました。トラブルシューティング方法を教えていただきました。

デバイスマネージャーの画面を示す。
Ultra96_JTAG_3_200218.png

USB Serial Port(COM6) が生成されているのが分かる。

やり方を示す。
USB Serial Converter B を右クリックし右クリックメニューからプロパティを選択する。
USB Serial Converter B のプロパティが開いたら詳細設定タブをクリックして、セットアップの” VCP をロードする”にチェックを入れる。(これが入っていなかった)
Ultra96_JTAG_4_200218.png

そうすると、COM6 が出てきた。
COM6 はTera Term からも選択できた。
Ultra96_JTAG_5_200218.png

最後にドライバの使用しているファイルを示す。
Ultra96_JTAG_2_200218.png

本当に良かった。。。うれしい。。。
  1. 2020年02月18日 05:00 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96 USB-to-JTAG/UART Pod がWindows 10 で動作しなくなった

TMMF2020 でフィルタ処理の様子を展示しようとして、 Ultra96-V2 を Ultra96 USB-to-JTAG/UART Pod ( AES-ACC-U96-JTAG ) を使用して、Windows 10 のノートパソコンに接続しようとしたら、Tera Term のシリアル接続に候補が出てこなかった。
イダさんに Ultra96 用 USB シリアル変換基板を持ってきていただいて事なきを得た。
Ultra96 USB-to-JTAG/UART Pod を落としてしまったので、壊れたのか?と思って、新しい Ultra96 USB-to-JTAG/UART Pod を購入してしまったのだが、家に帰ってきて、Ubuntu 18.04 に Ultra96 USB-to-JTAG/UART Pod を接続したら普通に動作する。
これはおかしいということで、もう一度、Windows 10 のノートパソコンに接続してデバイスマネージャーを見たら、表示メニューから”非表示のデバイスの表示”を選択すると、COM3 と COM4 が見えている。
Ultra96_JTAG_1_200217.png

ドライバがおかしいのか?と思って、https://www.ftdichip.com/Drivers/VCP.htm に行って、ドライバをもう一度インストールしてみたが、同様だった。

Windows 10 のバージョンは 1909 のためかもしれないとツィターで教えてもらったが、皆さん、このような症状が出ている方はいますでしょうか?
よろしければコメント欄でお知らせください。動作している、動作していない、その際のWindows 10 のバージョンもお知らせください。
よろしくお願いいたします。

RTLを語る会16”でデモしたときには、同じノートパソコンで Ultra96 USB-to-JTAG/UART Pod を使ってデモしたんですよ。それが 2019/11/09 で、Windows 10 の 1909 出たのが、2019/11/12 なので、時期的にはおかしくないんです。

なお、私の使用している Ultra96 USB-to-JTAG/UART Pod はコネクタが 1 ピン少ない Ultra96-V1 の時に買った Ultra96 USB-to-JTAG/UART Pod です。

(追記)
職場のWindows 10 1909バージョンでは、Tera TermでUltra96のUltra96 USB-to-JTAG/UART Pod でシリアル接続できたので、Windows 10 1909バージョンの問題ではないようです。

(2020/02/18:追記)
いろいろな方からレポートいただきました。ありがとうございます。
やはり、皆さん、うまく行っているのので、自分のノートパソコン固有の問題のようです。新しい Ultra96 USB-to-JTAG/UART Pod を購入したので、それが来たら確かめてみようと思います。
  1. 2020年02月17日 04:46 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

TMMF2020 に出展しました2

昨日でTMMF2020 の出展も終わりました。
プレゼンも思いがけなくたくさんの人に聞いていただいて嬉しかったです。ありがとうございました。
私のブースはFPGA のことを効いてくる人がほとんどでしたが、FPGA って知らないけど使ってみたい、興味あるという若い方に来ていただいたのが良かったです。そういう場合には、小林さんのFPGA大全が良い本ですから、それ見てやってみてくださいとご案内しました。
隣がミクニンさんで、とても素晴らしいM5StackV で学習がすぐできるディープラーニングのアプリを展示されていました。後で自分でもやってみたいです。ミクニンさん、いろいろとありがとうございました。
また、何か作品作って出てみたいですね。。。
TMMF2020_10_200217.jpeg

TMMF2020_11_200217.jpeg

TMMF2020_12_200217.jpeg
  1. 2020年02月17日 04:26 |
  2. Make出展
  3. | トラックバック:0
  4. | コメント:0

TMMF2020 に出展しました

TMMF2020 に出展しました。
隣のブースはミクニンさんでした。偶然。。。

朝、ストライズの練習会で 12.8 km 洞峰公園を走ってから、午前 9 時 30 分にカビオに行って準備を始めました。
準備しているときの様子を 2 階から取った写真です。
TMMF2020_6_200216.jpeg

TMMF2020_7_200216.jpeg

TMMF2020_8_200216.jpeg

私のブースの写真です。
TMMF2020_9_200216.jpeg

Ultra96 の JTAG ボードを落として、動かなくなって焦りました。イダさんに Ultra96 のシリアル-USB 変換をいただいて無事にフィルタ結果を表示することができました。イダさんありがとうございました。
家に帰ってきて、早速、AES-ACC-U96-JTAG を注文しました。

FPGAの部屋のブログを見ていただいている方にも来ていただいて、嬉しかったです。今日も展示していますので、よろしくお願いいたします。
  1. 2020年02月16日 04:50 |
  2. Make出展
  3. | トラックバック:0
  4. | コメント:0

Tsukuba Mini Maker Faire 2020 に出展します

今日、明日と Tsukuba Mini Maker Faire 2020 に出展します。
皆さん、よろしければ見に来てください。

展示物は、Ultra96-V2 のPMOD 拡張基板すべてと、Ultra96-V2 と PMOD 拡張基板+OV5642 カメラで自分の家の前の道をガボールフィルタ、ラプラシアンフィルタでどう見えるかをディスプレイに表示します。
ガボールフィルタとラプラシアンフィルタを使ってUltra96-V2でカメラ画像をフィルタする3(完成)”を展示します。

家の前の道
TMMF2020_4_200215.jpeg

ラプラシアンフィルタ処理
TMMF2020_3_200215.jpeg

ガボールフィルタ左白線用
TMMF2020_1_200215.jpeg

ガボールフィルタ右白線用
TMMF2020_2_200215.jpeg

Zybot を静的展示して、走っている様子の動画を流します。
TMMF2020_5_200215.jpeg

後は、過去に作ったアクリルサインを持っていこうと思っています。
Make_2013_3_131104.jpg

プレゼンテーションもします。日曜日のオオトリです。
FPGAでリアルタイムに画像をフィルタしよう”という題です。

どうぞよろしくお願いします。

時節柄、マスク姿でいると思いますので、ご理解ください。
  1. 2020年02月15日 04:53 |
  2. Make出展
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン5

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す10(streaming_lap_filter3 のプロファイル)”で、カーネル間のストリーミング接続は、カーネルを起動するレイテンシがかかっていることが分かった。ここでは、2 個のカーネルを連続して起動していた。それでは、カーネルが 1 個の時はどうなのだろうか? 同じ、ラプラシアン・フィルタの実装で確かめてみよう。もうすでに、Vitis のプロジェクトは作ってあって、ブログも書いてある。
Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2
Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン3
Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン4”参照

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン3”でプロファイル無しの状態での平均実行時間は、430 us だった。
Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン4”では、Appliction Timeline を表示したが、カーネルのプロパティは変更していなかったので、ソフトウェアだけのプロファイルを取っているようだった。この時の実行時間は 712 us だった。

今回は、カーネルのプロパティの Data Transfer を Counter + Trace に変更し、Stall Profiling にチェックを入れて、Appliction Timeline を表示してみよう。

Vitis 2019.2 の lap_filter_axis_dma プロジェクトを示す。
streaming_lap_filter_77_200213.png

Assistant ウインドウの lap_filter_axis_dma_system -> lap_filter_axis_dma -> Hardware -> lap_filter_axis_dma -> lap_filter_axis_dma を右クリックし、右クリックメニューから Settings... を選択する。
すると、Hardware Function Settings ダイアログが立ち上がる。そこで、、Data Transfer を Counter + Trace に変更し、Stall Profiling にチェックを入れた。
streaming_lap_filter_78_200213.png

ビルドを行って終了した。成功だ。
streaming_lap_filter_79_200213.png

Vivado のプロジェクトを見ると、”Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す10(streaming_lap_filter3 のプロファイル)”と同様に System DPA が追加されている。
streaming_lap_filter_80_200213.png

ビルドが成功したので、BOOT.BIN をUltra96-V2 のPetaLinux の /rum/media/mmcblk0p1 ディレクトリに転送した。つまり、MicroSD カードの第 1 パーティションに転送した。
/home/masaaki/Vitis_Work/2019.2/lap_filter_axis_dma/Hardware/sd_card に移動する。
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1

Ultra96-V2 の PetaLinux をリブートして、Ultra96-V2 のPetaLinux で zocl ドライバをロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

Assistant ウインドウの Hardware を右クリックして、右クリックメニューから Run -> Run Configurations... を選択する。
Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン4”ですでに設定は終了しているので、Run ボタンをクリックして、起動した。
streaming_lap_filter_81_200213.png

実行時間は 917 us だった。
streaming_lap_filter_82_200213.png

Assistant ウインドウの lap_filter_axis_dma_system -> lap_filter_axis_dma -> Hardware の下に、Debugger_lap_filter_axis_dma -> Run Summary(xclbin) をダブルクリックして Viits Analyzer を起動した。
Appliction Timeline をクリックして表示した。
streaming_lap_filter_83_200213.png

clEnqueueTask からカーネルの lap_filter_axis_dma が起動するまでの時間を計測した。 636 us だった。長い。。。
streaming_lap_filter_84_200213.png

clEnqueueTask から clFinish が終了するまでの時間は、1.830 ms だった。実行時間と合わない。
streaming_lap_filter_85_200213.png

これは、時間計測に OpenCL の event.getProfilingInfo() を使用しているからだろうか? ”Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す10(streaming_lap_filter3 のプロファイル)”では、gettimeofday() を使用している。以前測った時は、どちらでも値は変わらなかったのだが。。。

m_axi_gmem-DDR (inm|outm) などのトランザクションを拡大してみた。
streaming_lap_filter_86_200213.png

更に拡大すると、AXIインターフェースのトランザクションが見える。
streaming_lap_filter_87_200213.png

最後に Profile Summary を示す。
streaming_lap_filter_88_200213.png

streaming_lap_filter_89_200213.png

streaming_lap_filter_90_200213.png

streaming_lap_filter_91_200213.png

やはり、2 個のカーネルをカーネル間のストリーミング接続するよりも、ハードウェアで接続したほうが速い。
  1. 2020年02月14日 04:44 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す10(streaming_lap_filter3 のプロファイル)

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す9(streaming_lap_filter3 プロジェクト2)”の続き。

前回は、カーネルのストリーミング接続の動作を実機で確かめたところ成功した。しかし、動作のレイテンシが遅かった。今回は、プロファイルを取得してみよう。なお、今回のプロファイルの設定は、Fixstars Tech Blog の”Zybo+VitisでSDSoC相当の高位合成やってみた”を参考にさせていただいている。

Assistant ウインドウで Streaming_lap_filter3_system -> streaming_lap_filter3 -> Hardware -> streaming_lap_filter3 の dma_read と krnl_lap_filter_dmaw のプロパティを変更する。
streaming_lap_filter_65_200212.png

dma_read を右クリックし、右クリックメニューから Settings... を選択する。
すると、Hardware Function Settings ダイアログが立ち上がる。そこで、、Data Transfer を Counter + Trace に変更し、Stall Profiling にチェックを入れた。
streaming_lap_filter_63_200212.png

同様に krnl_lap_filter_dmaw をを右クリックし、右クリックメニューから Settings... を選択する。
すると、Hardware Function Settings ダイアログが立ち上がる。そこで、、Data Transfer を Counter + Trace に変更し、Stall Profiling にチェックを入れた。
streaming_lap_filter_64_200212.png

これで、Hardware をビルドして、成功した。
streaming_lap_filter_66_200212.png

ビルドが成功したので、BOOT.BIN をUltra96-V2 のPetaLinux の /rum/media/mmcblk0p1 ディレクトリに転送した。つまり、MicroSD カードの第 1 パーティションに転送した。
/home/masaaki/Vitis_Work/2019.2/streaming_lap_filter3/Hardware/sd_card に移動する。
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1
streaming_lap_filter_69_200212.png

Ultra96-V2 のPetaLinux で zocl ドライバをロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
streaming_lap_filter_67_200212.png

Assistant ウインドウの Hardware を右クリックして、右クリックメニューから Run -> Run Configurations... を選択する。
Debugger_streaming_lap_filter3 を作成した。
Enable profiling をクリックする。
Generate timeline trace report を Yes に変更する。
Collect Data Transfer Trace を Fine にする。
Collect Stall Trace を All にする。
Apply ボタンをクリックした。
Run ボタンをクリックして、動作させた。
streaming_lap_filter_68_200212.png

streaming_lap_filter_70_200212.png

動作させたところ、実行時間は 2.631 ms だった。
これは、”Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す9(streaming_lap_filter3 プロジェクト2)"での平均実行時間の 1.649 ms からすると、約 1 ms 程度、実行時間が長くなっている。プロファイルを取得するのに、それだけ余計な時間を取られるのだろう?
今回の実行時間を前回の平均実行時間にタイムスケールを合わせると、1.649 / 2.631 ≒ 0.627 倍すれば良いということになる。

Assistant ウインドウの streaming_lap_filter3_system -> streaming_lap_filter3 -> Hardware の下に、Debugger_streaming_lap_filter3 -> Run Summary(xclbin) をダブルクリックして、Vitis Analyzer を起動する。

Vitis Analyzer が立ち上がった。Application Timeline をクリックした。
streaming_lap_filter_71_200212.png

これを見ると、clCreateProgramBinary が長い。これは、ビットストリームをロードしている部分かな?
後ろの方にちょこっとデータ転送の部分が見えるので、拡大する。
streaming_lap_filter_72_200212.png

1 つ目の clEnqueueTask と 2 つ目の clEnqueueTask との間隔は、1.037 ms だった、これを 0.627 倍して補正すると 650 us となる。結構長い時間かかっている。よって、krnl_lap_filter_dmaw がすぐに起動されないので、dma_read の完了が伸びている。

更に拡大すると、dma_read や krnl_lap_filter_dmaw の AXI4 Master のトランザクションも見ることができる。これは、”Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す9(streaming_lap_filter3 プロジェクト2)"で見た ChipScope のAXI4 Master のトランザクション間隔とぴったり一致する。
streaming_lap_filter_73_200212.png

1 つ目の clEnqueueTask から 2 つ目の clFinish まで時間計測すると、2.602 ms となった。これは実行時間にほぼ等しい。
streaming_lap_filter_74_200212.png

1 つ目の clEnqueueTask から dma_read 起動までの時間は、406 us となった。これもプロファイル取得なしとすると、255 us となる。
streaming_lap_filter_75_200212.png

これだけ詳しくプロファイルを取得できるとなると、ハードウェアに追加されているはずだ。
Vivado のブロックデザインを見てみよう。
streaming_lap_filter_76_200212.png

System DPA というユニットが追加されている。
更に、dma_read と krnl_lap_filter_dmaw IP から出ている配線が追加されていた。

う〜ん。カーネルの起動が遅い気がする。もっと 2 つ目のカーネルを素早く起動できないものだろうか?
データ転送部分の占める割合が少ない気がする。

最後に Profile Summary を示す。
streaming_lap_filter_580_200213.png

streaming_lap_filter_581_200213.png

streaming_lap_filter_582_200213.png

streaming_lap_filter_583_200213.png
  1. 2020年02月13日 04:37 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す9(streaming_lap_filter3 プロジェクト2)

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す8(streaming_lap_filter3 プロジェクト)”の続き。

前回は、Vitis 2019.2 で krnl_filter_dmaw.cpp を使用した streaming_lap_filter3 プロジェクトを作成した。ホスト・アプリケーションソフトの krnl_streaming_lap_filter_host3.cpp を貼った。今回は、カーネルのストリーミング接続の動作を確かめてみよう。

最初に、動作を確かめた時には、2 つのカーネルが完走したが、つまりホスト・アプリケーションソフトが終了するようになったが、DMA Write のトランザクションが全く出ていなかった。そこで、 krnl_filter_dmaw.cpp の volatile をすべて消して、やってみたらうまく行った。これは、volatile が良くないのか?と思ったが、他のパソコンで volatile 付きで動作がうまく行ったのと、もう一度、同じパソコンで volatile 付きでやってみたら動作したので、単に最初のビルドが正常でなかったようだ。

さて、動作を確認してみよう。
Vitis で生成されたBOOT.BIN をUltra96-V2 の MicroSD カードの第 1 パーティションに転送する。
/home/masaaki/Vitis_Work/2019.2/streaming_lap_filter3/Hardware/sd_card ディレクトリに移動し、 sudo su で root になって scp コマンドで SFTP する。
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1
streaming_lap_filter_51_200210.png

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

Vitis の Assistant ウインドウの streaming_lap_filter3_system の streaming_lap_filter3 -> Hardware を右クリックし、右クリックメニューから Run -> Run Configurations... を選択して、 streaming_lap_filter3 の Run Configuration を作成した。
streaming_lap_filter_53_200210.png

Run Configuration ダイアログで Run ボタンをクリックすると動作を確認することができた。
streaming_lap_filter_54_200210.png

実行時間は 1.651 ms だった。
後、 2 回ほど実行してみた。
streaming_lap_filter_58_200210.png

streaming_lap_filter_59_200210.png

それぞれ実行時間は 1.772 ms と 1.525 ms だった。これら 3 つの実行時間を平均すると、1.649 ms となった。
ラプラシアンフィルタを 1 つのカーネルとして実装した例はすでにやってある。”Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン3”がそうだ。そこに出てきた数値の 560 us と 430 us を平均すると 495 us となる。今回のカーネル間のストリーミング接続による方法の方が、約3.33 倍遅いという結果が出た。やはり、今回のChipScope の結果をみても、dma_read をスタートしてから、krnl_lap_filter_dmaw をスタートするまでが長いのではないだろうか?

ChipScope の波形を示す。krnl_lap_filter_dmaw の DMA Write の AWVALID でトリガをかけている。
streaming_lap_filter_55_200210.png
streaming_lap_filter_57_200210.png

生成された temp_lap.bmp ファイルをパソコンに SFTP して確かめてみた。
streaming_lap_filter_61_200210.png

temp_lap.bmp ファイルを表示すると、エッジが表示されているのが分かる。
streaming_lap_filter_62_200210.png

ラプラシアンフィルタを 1 つのカーネルとして実装した例よりも、2 つのカーネルに分けてストリーミング接続をした方が 3.33 倍遅いという結果が出たが、それは、たぶん、2 つ目のカーネルの起動が遅いのだと思う。ChipScope 画面を見るとスループットは十分取れていると言える。
計算してみよう。
dma_read のRead スピードを計算してみると、ChipScope 波形のクロックを数えると、64 クロックのRead トランザクションについて、次のトランザクションとの間隔は 204 クロックだった。よって、スループットは
64 クロック / 204 クロック * 200 MHz ≒ 62.7 MWord / sec (Word = 32 ビット)
62.7451 MWord / sec * 4 バイト ≒ 251 MB / sec
3072 ピクセル / 62.7 MWord / sec = 49.0 us

ChipScope のスループットだと 3072 ピクセルのラプラシアンフィルタ処理を 49.0 us で終了できるはずなので、設定側が相当遅いということが考えられる。ということは、ピクセル数が多くなるほど、相対的に性能が向上する可能性があるはずだ。
今は 1 つのカーネルでのラプラシアンフィルタ処理に比べて、 2 つのカーネルのストリーミング接続の方が 3.33 倍遅いが、ピクセル数が多くなると、双方の性能差が縮んでくることが予想される。
  1. 2020年02月11日 04:14 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す8(streaming_lap_filter3 プロジェクト)

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す7(krnl_lap_filter_dmaw.cpp)”の続き。

前回は、カーネルを 2 個にするために krnl_lap_filter と dma_write カーネルを一緒にした krnl_lap_filter_dmaw を作成し、Vivado HLS 2019.2 のプロジェクトを作成してC シミュレーション、C コードの合成、C/RTL 協調シミュレーションを行った。今回は、Vitis 2019.2 で krnl_filter_dmaw.cpp を使用した streaming_lap_filter3 プロジェクトを作成した。ホスト・アプリケーションソフトを作成して動作を確かめてみる。

Vitis 2019.2 で streaming_lap_filter3 プロジェクトを作成した。
ホスト・アプリケーションソフトの krnl_streaming_lap_filter_host3.cpp を示す。

(2020/02/11:修正) printf(); fflush(); を削除した。

// krnl_streaming_lap_host3.cpp
// 2020/02/08 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 <sys/time.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 "bmp_header.h"

int 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);
int32_t lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t width, int32_t height); // software

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[])
{
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int32_t blue, green, red;
    const char* xclbinFilename;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals_soft;

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

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

    // ピクセルを入れるメモリをアロケートする
    std::vector<int32_t,aligned_allocator<int32_t>> rd_bmp(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int32_t,aligned_allocator<int32_t>> hw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    size_t size_in_bytes = (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int32_t);

    // 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);

    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 |
            CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_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_dma_read(program,"dma_read");
    cl::Kernel krnl_lap_filter_dmaw(program,"krnl_lap_filter_dmaw");

    // 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 rd_bmp_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, rd_bmp.data());
    cl::Buffer hw_lapd_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, hw_lapd.data());

    //set the kernel Arguments
    krnl_dma_read.setArg(0,rd_bmp_buf);
    krnl_dma_read.setArg(2,bmpihr.biWidth);
    krnl_dma_read.setArg(3,bmpihr.biHeight);

    krnl_lap_filter_dmaw.setArg(1,hw_lapd_buf);
    krnl_lap_filter_dmaw.setArg(2,bmpihr.biWidth);
    krnl_lap_filter_dmaw.setArg(3,bmpihr.biHeight);

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

    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    //Launch the Kernel
    q.enqueueTask(krnl_dma_read);
    q.enqueueTask(krnl_lap_filter_dmaw);

    q.finish();

    // 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({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // 時間計測
    gettimeofday(&end_time, NULL);

    if (end_time.tv_usec < start_time.tv_usec) {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec - 1, 1000000 + end_time.tv_usec - start_time.tv_usec);
    }
    else {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec, end_time.tv_usec - start_time.tv_usec);
    }

    // ソフトウェアとハードウェアのチェック
    // ins_soft に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
       pix.user = 0;
        pix.data = int32_t(i);
        ins_soft << pix;
    }

    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            pix.data = rd_bmp[(j*bmpihr.biWidth)+i];

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

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

            ins_soft << pix;
        }
    }
    lap_filter_axis_soft(ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight); // ソフトウェアのラプラシアン・フィルタ

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            outs_soft >> vals_soft;
            if (hw_lapd[y*bmpihr.biWidth+x] != vals_soft.data){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, int(hw_lapd[y*bmpihr.biWidth+x]), int(vals_soft.data));
                //return(1);
            }
        }
    }
    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);

    return(0);
}

int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t width, int32_t height){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;
    int32_t **line_buf;
    int32_t pix_mat[3][3];
    int32_t lap_fil_val;
    int32_t i;

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

    // メモリをアロケートする
    for (i=0; i<2; 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);
        }
    }

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

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

            for (int k=0; k<3; k++){
                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==0 && y==0) // 最初のデータでは、TUSERをアサートする
                lap.user = 1;
            else
                lap.user = 0;

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

            outs << lap;    // AXI4-Stream へ出力
        }
    }

    for (i=0; i<2; i++)
        free(line_buf[i]);
    free(line_buf);

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


Hardware をビルドして成功した。
streaming_lap_filter_50_200210.png

Vivado のブロックデザインを示す。
streaming_lap_filter_56_200210.png

V++ linker options を示す。

--config ../src/krnl_stream_dmar_lap_dmaw.ini --dk chipscope:dma_read_1 --dk chipscope:krnl_lap_filter_dmaw_1:ins --dk chipscope:krnl_lap_filter_dmaw_1:outm --dk chipscope:krnl_lap_filter_dmaw_1:x_size


krnl_stream_dmar_lap_dmaw.ini の内容を示す。

[connectivity]
stream_connect=dma_read_1.outs:krnl_lap_filter_dmaw_1.ins

  1. 2020年02月10日 05:03 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す7(krnl_lap_filter_dmaw.cpp)

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す6(AXI4 StreamにFIFOを追加)”の続き。

前回は、同時に動作できるカーネルが 2 個である場合は、途中に画像全部以上の FIFO があれば、最初の dma_read カーネルが終了し、dma_write カーネルが起動できて、つまり、全てのカーネルが動作するのではないか?という仮説に基づいて krnl_lap_filter カーネルに 4096 深度の FIFO を追加したが、動作しなかった。今回は、カーネルを 2 個にするために krnl_lap_filter と dma_write カーネルを一緒にした krnl_lap_filter_dmaw を作成する。

(2020/02/10:修正) INTERFACE m_axi 指示子に offset=slave を忘れていたので、追加して、図を変更しました。

作成した krnl_lap_filter_dmaw.cpp を貼っておく。

// krnl_lap_filter_dmaw.cpp
// 2020/02/08 by marsee

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

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){

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

void dma_write(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++){
#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;
        }
    }
}

//extern "C" {
void krnl_lap_filter_dmaw(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        int32_t x_size, int32_t y_size){
#pragma HLS DATAFLOW
#pragma HLS INTERFACE m_axi depth=3072 port=outm offset=slave 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

    hls::stream<ap_axiu<32,0,0,0> > lap_stream;

    krnl_lap_filter(ins, lap_stream, x_size, y_size);
    dma_write(lap_stream, outm, x_size, y_size);
}
//}


extern "C" { } はシミュレーションを通すためにコメントアウトしてある。

次に、テストベンチの krnl_lap_filter_dmaw_tb.cpp を示す。

// krnl_lap_filter_dmaw_tb.cpp
// 2020/02/08 by marsee

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

void krnl_lap_filter_dmaw(hls::stream<ap_axiu<32,0,0,0> >& ins, volatile int32_t *outm,
        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_soft;

    ap_axiu<32,0,0,0> pix;
    ap_axiu<32,0,0,0> 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)*(img.rows));
    std::vector<int32_t> sw_lap(sizeof(int32_t)*(img.cols)*(img.rows));

    // 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_dmaw(ins, hw_lap.data(), 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++){
            ap_int<32> val = hw_lap[y*img.cols+x];
            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;
    };
}


VIvado HLS 2019.2 で krnl_lap_filter_dmaw プロジェクトを作成した。
streaming_lap_filter_44_200209.png

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

lap.bmp ファイルが生成されている。
streaming_lap_filter_46_200209.png

C コードの合成を行った。問題無さそうだ。
streaming_lap_filter_47_200209.png

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

C/RTL 協調シミュレーションの波形を示す。
streaming_lap_filter_49_200209.png
  1. 2020年02月09日 04:32 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す6(AXI4 StreamにFIFOを追加)

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す5(ChipScope で波形を確認する2)”の続き。

前回は、設定を行ってChipScope 波形を観察した結果、dma_write カーネルが動作していないようだということが分かった。今回は、同時に動作できるカーネルが 2 個である場合は、途中に画像全部以上の FIFO があれば、最初の dma_read カーネルが終了し、dma_write カーネルが起動できて、つまり、全てのカーネルが動作するのではないか?という仮説に基づいて krnl_lap_filter カーネルに 4096 深度の FIFO を追加してみよう。

最初に、今の krnl_lap_filter.cpp を合成した結果を示す。
streaming_lap_filter_40_200207.png

FIFO を入れた krnl_lap_filter2.cpp のソースコードを示す。

// krnl_lap_filter2.cpp
// 2020/02/07 by marsee

#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 DATAFLOW
#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

    hls::stream<ap_axiu<32,0,0,0> > buf;
#pragma HLS STREAM variable=buf depth=4096 dim=1

    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_X1 : for (int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_Y1 : for (int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            ins >> pix;
            buf << pix;
        }
    }

    LOOP_X2 : for (int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_Y2 : for (int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            buf >> 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;
    }; */
}
}


これを C コードの合成した結果を示す。
streaming_lap_filter_41_200207.png

BlockRAM が 2 個から 13 個に増えている。

Vitis 2019.2 の GUI で streaming_lap_filter2 を作成して、krnl_lap_filter.cpp だけを作成した krnl_lap_filter2.cpp のコードに入れ替えてビルドした。ビルドできた。
streaming_lap_filter_42_200208.png

Ultra96-V2 を起動して、zcol ドライバをロードlした。

Vitis GUI で Run Configuration を作って、ホスト・アプリケーションを起動したが、同様に途中で止まってしまった。
streaming_lap_filter_43_200208.png

波形を示す。
streaming_lap_filter_44_200208.png

これでも動作しないとすると、違う原因なのか?
カーネルを 2 個にまとめて、やってみよう。
  1. 2020年02月08日 04:28 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す5(ChipScope で波形を確認する2)

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す4(ChipScope で波形を確認する1)”の続き。

前回は、Vitis 2019.2 の streaming_lap_filter プロジェクトの Run Configuration を作成して、実機動作を行ったが、成功しなかった原因を探るために、ChipScope を入れて波形を確認しようということで、--dk オプションを使用した ILA IP コアの挿入を行った。今回は引き続き、設定を行ってChipScope 波形を観察してみよう。

前回までで、ビルドは終了している。ChipScope とVitis の制御がかち合ってしまうとまずいと思ったので、gtkterm から作業した。しかし、今やってみたが、Vitis からやっても何ら問題なかった。今度からVitis GUI でやろうと思う。
さて、gtkterm からの作業ということで、scp コマンドで BOOT.BIN, streaming_lap_filter.exe, streaming_lap_filter.xclbin を SFTP で Ultra96-V2 の PetaLinux にアップロードした。
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1
scp streaming_lap_filter.exe 192.168.3.23:/mnt
scp streaming_lap_filter.xclbin 192.168.3.23:/mnt

streaming_lap_filter_24_200206.png

streaming_lap_filter_25_200206.png

Ultra96-V2 の PetaLinux 上で zocl ドライバをロードして、XRT のパスを設定して、/mnt ディレクトリに入った。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
export XILINX_XRT=/usr
cd /mnt

streaming_lap_filter_26_200206.png

Vivado を立ち上げて、Tasks から Open Hardware Manager をクリックした。
streaming_lap_filter_27_200206.png

HARDWARE MANAGER が立ち上がる。
Open Target をクリックして、Auto Connect を選択した。
streaming_lap_filter_28_200206.png

hw_ila_1 が開いた。まだ波形が表示されていない。
Trigger_Setup ウインドウにある Specify the probe file and refresh the device をクリックする。
streaming_lap_filter_29_200206.png

streaming_lap_filter/Hardware/streaming_lap_filter.ltx ファイルを選択して Refresh ボタンをクリックする。
streaming_lap_filter_30_200206.png

すると波形が表示された。
streaming_lap_filter_31_200206.png

波形ウインドウをフロートして、設定を行う。
Settings ウインドウの Trigger position in window を 10 に設定した。
Trigger Setup ウインドウでは、dma_read の ARVALID = 1 でトリガをかけるように設定した。
streaming_lap_filter_32_200206.png

gtkterm で streaming_lap_filter.exe を実行した。
./streaming_lap_filter.exe streaming_lap_filter.xclbin
streaming_lap_filter_33_200206.png

すると、ChipScope のトリガがかかった。
streaming_lap_filter_34_200206.png

dma_read の AXI4 Master のトランザクションが 4 個見える。
16 バースト 4 個なので、64 ピクセルをRead していることになる。ちょうど 1 行分のピクセルだ。
AXI4 Stream インターフェースも少し動作しているのが見える。krnl_lap_filter の入力はかなり入ってるが、出力は少ししか出て行っていない。
streaming_lap_filter_35_200206.png

次に、AXI4 Lite インターフェースを見てみると、dma_read と krnl_lap_filter は AXI4 Lite の Read トランザクションが見えるが、dma_write はRead トランザクションが見えない。
streaming_lap_filter_36_200206.png

dma_read のトランザクションはアドレス・オフセット 0 番地をRead しているので、終了を判定していると思われる。
streaming_lap_filter_37_200206.png

krnl_lap_filter の Read トランザクションも同様だ。
streaming_lap_filter_38_200206.png

dma_write の AXI4 Lite インターフェースのARVALID でトリガかけてみたが、引っかからない。
streaming_lap_filter_39_200207.png

dma_write が起動していないんじゃないか?少なくともケアされていない疑惑が生じている。
なお、ひでみさんに起動できるカーネルは 2 個じゃないか?ということを聞いたので、カーネルを 2 個にして確かめてみよう。

ChipScope を使う際にエラーが出る場合はケーブルドライバをインストールしてあるかどうか?を確認したほうが良い。
ケーブルドライバのインストール方法はここを参照ください。

ホスト・アプリケーションソフトを一時停止するための関数として wait_for_enter() 関数が”UG1393 (v2019.2) 2019 年 11 月 11 日Vitis アプリケーション アクセラレーション開発”の 238 ページの”ハードウェア デバッグでの ILA トリガーのイネーブル”、”カーネル開始前に ILA トリガーを追加”に書かれている。
  1. 2020年02月07日 04:56 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す4(ChipScope で波形を確認する1)

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す3”の続き。

前回は、Vitis 2019.2 の streaming_lap_filter プロジェクトの Run Configuration を作成して、実機動作を行ったが、成功しなかった。今回は、その原因を探るために、ChipScope を入れて波形を確認してみよう。

参考にしたのは、”Vitis 統合ソフトウェア プラットフォームの資料 アプリケーション アクセラレーション開発 UG1393 (v2019.2) 2019 年 11 月 11 日”の”第 20 章 アプリケーションおよびカーネルのデバッグ”の 235 ページの”ハードウェア実行中のデバッグ”だ。
236 ページの”ChipScope を使用したデバッグでのカーネルのイネーブル”がには、--dk オプションを使用した ILA IP コアの挿入方法について書かれている。早速やってみよう。

Vitis GUI でやる方法を示す。
Vitis GUI の Assistant ウインドウで Streaming_lap_filter_system -> streaming_lap_filter を右クリックし右クリックメニューから settings... を選択する。
Project Settings ダイアログが開く。
V++ linker options に以下のオプションを追加した。

--config ../src/krnl_stream_dmar_lap_dmaw.ini --dk chipscope:dma_read_1 --dk chipscope:dma_write_1 --dk chipscope:krnl_lap_filter_1:ins --dk chipscope:krnl_lap_filter_1:outs --dk chipscope:krnl_lap_filter_1:x_size


streaming_lap_filter_21_200205.png

なお、--dk chipscope:<カーネルのインスタンス名>で、AXI4 Master 、AXI4 Lite インターフェースはそのままILA でプローブされたが、AXI4 Stream はプローブされなかった。そこで、krnl_lap_filter だけは各ポートを指定して、--dk オプションを入れている。

streaming_lap_fiter プロジェクトのHardware を clean してもう一度ビルドした。
その際に、 cl::CommandQueue() に CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE プロパティを設定した。これは、設定すると、コマンドキュー内のコマンドは順不同で実行されるそうだ。
このプロパティは、streaming_k2k_mm の host.cpp で使用されている。
streaming_lap_filter_22_200206.png

ビルドが成功した。
Vivado プロジェクトのブロックデザインを確認すると、AXI4 Master, AXI4 Lite, AXI4 Stream の各ポートに ILA のプローブが接続されているのが分かる。
streaming_lap_filter_23_200206.png
  1. 2020年02月06日 05:37 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す3

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す2”の続き。

前回は、Vitis 2019.2 の streaming_lap_filter プロジェクトにソースコードを入れてプロジェクトを完成させたあとで、ビルドに成功した。今回は、Run Configuration を作成して、実機動作を行ったが、成功しなかった。

最初にUltra96-V2 のPetaLinux を起動して、BOOT.BIN をSFTP する。
/home/masaaki/Vitis_Work/2019.2/streaming_lap_filter/Hardware/sd_card に移動して、scp コマンドでUltra96-V2 のPetaLinux の第 1 パーティションの /run/media/mmcblk0p1 に BOOT.bin をコピーする。
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1
streaming_lap_filter_14_200205.png

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

streaming_lap_filter の Run Configuration を作成し、Run ボタンをクリックして起動した。
streaming_lap_filter_13_200202.png

streaming_lap_filter_16_200205.png

ログを示す。
f まで表示されている。
streaming_lap_filter_17_200205.png

f まで表示されているということは、enqueueTask(); はしたけど、q.finish(); から抜けていないということだ。
streaming_lap_filter_18_200205.png

Vivado のブロックデザインを見てみたが、正常に接続されている。
streaming_lap_filter_19_200205.png

Address Editor 画面を示す。
streaming_lap_filter_20_200205.png
  1. 2020年02月05日 04:27 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す2

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す1”の続き。

前回は、今まで作ってきた krnl_dma_read , krnl_lap_filter , krnl_dma_write をストーミング接続した Vitis 2019.2 プロジェクトを作成して、動作を確認することにした、ということで、Vitis 2019.2 の streaming_lap_filter プロジェクトを作成した。今回は、ソースコードを入れていってプロジェクトを完成させる。

krnl_dma_read.cpp , krnl_lap_filter.cpp , krnl_dma_write.cpp を Streaming_lap_filter_system -> streaming_lap_filter/src にインポートした。そして、Hardware Functions に指定した。
xclbin ファイルの名前は krnl_lap_filter とした。
streaming_lap_filter_6_200202.png

カーネル間のストーミング接続を記述したファイルを生成する。
Vitis GUI で Streaming_lap_filter_system -> streaming_lap_filter/src を右クリックして、右クリックし、右クリックメニューから New -> File を選択した。
New File ダイアログで File name に krnl_stream_dmar_lap_dmaw.ini を生成した。
streaming_lap_filter_7_200202.png

krnl_stream_dmar_lap_dmaw.ini が生成されて、dma_read から krnl_lap_filter へのストーミング接続と krnl_lap_filter から dma_write までのストーミング接続について記述した。

[connectivity]
stream_connect=dma_read_1.outs:krnl_lap_filter_1.ins
stream_connect=krnl_lap_filter_1.outs:dma_write_1.ins


streaming_lap_filter_8_200202.png

次にホスト・アプリケーションを作成しよう。
Vitis GUI で Streaming_lap_filter_system -> streaming_lap_filter/src を右クリックして、右クリックし、右クリックメニューから New -> File を選択した。
New File ダイアログで File name に krnl_streaming_lap_host.cpp を入力した。
streaming_lap_filter_9_200202.png

krnl_streaming_lap_host.cpp が新規作成されたので、ソースコードを入力した。
streaming_lap_filter_10_200202.png

krnl_stream_dmar_lap_dmaw.ini をストーミング接続の記述ファイルとして Vitis に指示する。
Assistant ウインドウで Streaming_lap_filter_system -> streaming_lap_filter を右クリックし右クリックメニューから settings... を選択する。
Project Settings ダイアログが開く。
V++ linker options に

--config ../src/krnl_stream_dmar_lap_dmaw.ini

を入力して、Apply and Close ボタンをクリックする。
streaming_lap_filter_11_200202.png

Assistant ウインドウで Streaming_lap_filter_system -> streaming_lap_filter -> Hardware をビルドした。2回ビルドすると、成功の緑のチェックマークになった。
streaming_lap_filter_12_200202.png

現在の、krnl_streaming_lap_host.cpp を示す。なお、デバック中なので、printf(); と fflush(); が埋め込んである。

// krnl_streaming_lap_host.cpp
// 2020/02/02 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 <sys/time.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 "bmp_header.h"

int 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);
int32_t lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t width, int32_t height); // software

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[])
{
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int32_t blue, green, red;
    const char* xclbinFilename;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals_soft;

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

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

    // ピクセルを入れるメモリをアロケートする
    std::vector<int32_t,aligned_allocator<int32_t>> rd_bmp(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int32_t,aligned_allocator<int32_t>> hw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    size_t size_in_bytes = (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int32_t);

    // 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);

    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
    printf("a"); fflush(stdout);
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    printf("b"); fflush(stdout);

    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    printf("b"); fflush(stdout);
    bin_file.seekg (0, bin_file.end);
    printf("b"); fflush(stdout);
    unsigned nb = bin_file.tellg();
    printf("b"); fflush(stdout);
    bin_file.seekg (0, bin_file.beg);
    printf("b"); fflush(stdout);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    printf("b"); fflush(stdout);

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

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_dma_read(program,"dma_read");
    cl::Kernel krnl_lap_filter(program,"krnl_lap_filter");
    cl::Kernel krnl_dma_write(program,"dma_write");
    printf("c"); fflush(stdout);

    // 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 rd_bmp_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, rd_bmp.data());
    cl::Buffer hw_lapd_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, hw_lapd.data());

    //set the kernel Arguments
    krnl_dma_read.setArg(0,rd_bmp_buf);
    krnl_dma_read.setArg(2,bmpihr.biWidth);
    krnl_dma_read.setArg(3,bmpihr.biHeight);

    krnl_lap_filter.setArg(2,bmpihr.biWidth);
    krnl_lap_filter.setArg(3,bmpihr.biHeight);

    krnl_dma_write.setArg(1,hw_lapd_buf);
    krnl_dma_write.setArg(2,bmpihr.biWidth);
    krnl_dma_write.setArg(3,bmpihr.biHeight);
    printf("e"); fflush(stdout);

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

    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    //Launch the Kernel
    q.enqueueTask(krnl_dma_read);
    q.enqueueTask(krnl_lap_filter);
    q.enqueueTask(krnl_dma_write);
    printf("f"); fflush(stdout);

    q.finish();
    printf("h"); fflush(stdout);

    q.finish();
    printf("h"); fflush(stdout);


    // 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({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);
    printf("g"); fflush(stdout);

    q.finish();
    printf("h"); fflush(stdout);

    // 時間計測
    gettimeofday(&end_time, NULL);
    printf("i"); fflush(stdout);

    if (end_time.tv_usec < start_time.tv_usec) {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec - 1, 1000000 + end_time.tv_usec - start_time.tv_usec);
    }
    else {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec, end_time.tv_usec - start_time.tv_usec);
    }

    // ソフトウェアとハードウェアのチェック
    // ins_soft に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
       pix.user = 0;
        pix.data = int32_t(i);
        ins_soft << pix;
    }

    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            pix.data = rd_bmp[(j*bmpihr.biWidth)+i];

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

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

            ins_soft << pix;
        }
    }
    lap_filter_axis_soft(ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight); // ソフトウェアのラプラシアン・フィルタ

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            outs_soft >> vals_soft;
            if (hw_lapd[y*bmpihr.biWidth+x] != vals_soft.data){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, int(hw_lapd[y*bmpihr.biWidth+x]), int(vals_soft.data));
                //return(1);
            }
        }
    }
    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);

    return(0);
}

int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t width, int32_t height){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;
    int32_t **line_buf;
    int32_t pix_mat[3][3];
    int32_t lap_fil_val;
    int32_t i;

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

    // メモリをアロケートする
    for (i=0; i<2; 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);
        }
    }

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

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

            for (int k=0; k<3; k++){
                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==0 && y==0) // 最初のデータでは、TUSERをアサートする
                lap.user = 1;
            else
                lap.user = 0;

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

            outs << lap;    // AXI4-Stream へ出力
        }
    }

    for (i=0; i<2; i++)
        free(line_buf[i]);
    free(line_buf);

    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 = 0;
    else if (y>255)
        y = 255;
    return(y);
}

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

Vitis 2019.2 で自作カーネルを使用してストーミング接続を試す1

今まで作ってきた krnl_dma_read , krnl_lap_filter , krnl_dma_write をストーミング接続した Vitis 2019.2 プロジェクトを作成して、動作を確認することにした。プロジェクト名は streaming_lap_filter だ。

Vitis 2019.2 で streaming_lap_filter アプリケーション・プロジェクトを作成する。
File メニューの New -> Application Project... を選択した。

New Application Project が立ち上がった。
Project name に streaming_lap_filter を入れた。
streaming_lap_filter_1_200202.png

platform では、ultra96_min2 プラットフォームを選択した。
streaming_lap_filter_2_200202.png

Domain はそのままとした。
streaming_lap_filter_3_200202.png

Templates は Empty Application を選択した。
streaming_lap_filter_4_200202.png

Streaming_lap_filter と Streaming_lap_filter_system が生成された。
streaming_lap_filter_5_200202.png

  1. 2020年02月03日 05:22 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

映画「AI崩壊」を見てきました

昨日、映画「AI崩壊」を奥さんと見てきました。
いろいろとツッコミどころが満載の映画でした。逆に面白いような?
私の言いたいことは、ここに代弁されています。
もう1つ気になったのは、AIはデータで学習させるので、プログラムで作るというものじゃない、というのが分かってないんじゃないかな?ということでした。
更に、AIに映像としてプログラムを読み込ませるところで、アセンブラのニーモニックを投影しているんですが、そんなもの投影して読み込ませられるんでしょうか?まだ、16進数表示したほうがマシのような気がしますが、そういう仕様なんでしょうか?
  1. 2020年02月02日 05:17 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_dma_read を作成する3(DMA Read サイズを固定する)

Vivado HLS 2019.2 で krnl_dma_read を作成する2(IP 化)”の続き。

Vivado HLS 2019.2 で krnl_dma_write を作成する3”でDMA Write のサイズを固定したところ、劇的にリソース使用量が減少した。そうしたら、krnl_dma_read も同様ではないか?と思って、DMA Read サイズを固定したバージョンを作成してみることにした。

まずは、 krnl_dma_read2.cpp を示す。

// krnl_dma_read2.cpp
// 2020/01/31 by marsee

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

#define X_SIZE  64
#define Y_SIZE  48

//extern "C" {
void dma_read2(volatile int32_t *inm, hls::stream<ap_axiu<32,0,0,0> >& outs){
#pragma HLS INTERFACE s_axilite port=return 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++){
        LOOP_DRX: for(int x=0; x<X_SIZE; x++){
#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_read2_tb.cpp を示す。

// krnl_dma_read2_tb.cpp
// 2020/01/31 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_read2(volatile int32_t *inm, hls::stream<ap_axiu<32,0,0,0> >& outs);

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_read2(rd_bmp, outs);

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


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

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

dma_read.bmp が生成されていた。
streaming_kernel_75_200131.png

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

FF は 724 個、LUT は 1007 個使用している。DMA Read サイズを可変にしている時は、FF が 10754 個、LUT が 8515 個使用しているので、FF は 10 倍以上リソース使用量が多い。
Latency も 3082 クロックで、ほとんど 1 クロック/ピクセルに近い。

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

3123 クロックだった。

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

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

拡大してみると RVALID の 0 の部分は、クロックの立ち上がっている部分のみなので、0 と判定されていないことが分かった。よって、RVALID はずーと 1 と同じということが分かった。
TVALID もほとんど 1 になっているので、スループットが高いことが分かる。

Export RTL を行った。
streaming_kernel_81_200131.png

LUT も FF も DMA Read サイズを可変にした場合よりも劇的少なくなっているのが分かる。

dma_read2.xo ファイルが生成された。
streaming_kernel_82_200131.png
  1. 2020年02月01日 05:08 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0