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

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

FPGAの部屋

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

Vitis Unified IDE 2023.2 でエッジ強調フィルタの edge_enhancement_axis_RGB24 を実装する1

Vitis Unified IDE 2023.2 でエッジ強調フィルタの edge_enhancement_axis_RGB24 を実装してみよう。
エッジ強調フィルタは画像の輪郭を強調して、画像をはっきりさせるためのフィルタだ。(”前処理フィルタについて”を参照した)

Vitis Unified IDE 2023.2 で edge_enhancement_axis_RGB24 プロジェクトを作成する。

Vitis Unified IDE 2023.2 で File メニューから New Component -> HLS を選択して、ZUBoard 1CG 用の edge_enhancement_axis_RGB24 プロジェクトを作成した。

ソースコード、テストベンチのファイルは edge_enhancement_axis_RGB24 プロジェクトのファイルを追加した。

最初に、VITIS COMPONENT -> edge_enhancement_axis_RGB24 -> Settings -> hls_config.cfg をクリックし、設定を行った。
C Sythesis sourcestopedge_enhancement_axis_RGB24 を設定した。これは、C コードの合成時にハードウェアとする関数を指定する。
Testbench sources CFLAGS-I/usr/local/include を指定した。
C Simulation ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。

ソースコードに edge_enhancement_axis_RGB24.h と edge_enhancement_axis_RGB24.cpp を追加した。
テストベンチに edge_enhancement_axis_RGB24_tb.cpp と test2.jpg を追加した。
zub1cg_i7filters_112_231129.png

ヘッダ・ファイルの edge_enhancement_axis_RGB24.h を示す。

// edge_enhancement_axis_RGB24.h
// 2023/11/29 by marsee
//

#ifndef __EDGE_ENHANCEMENT_AXIS_RGB24_H__
#define __EDGE_ENHANCEMENT_AXIS_RGB24_H__

#define ORG_IMGwAxiVdma 0
#define EDGE_ENHANCEMENTwAxiVdma 1
#define ORG_IMGwAxiDma 2
#define EDGE_ENHANCEMENTwAxiDma 3

#endif


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

// edge_enhancement_axis_RGB24.cpp
// 2023/11/29 by marsee
//

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

#include "edge_enhancement_axis_RGB24.h"

constexpr int size = 3;

ap_uint<24> edge_enhancement_fil(ap_uint<24> (&xy)[size][size]);
ap_uint<8> edge_enhancement_file_calc(ap_int<32> (&xy)[size][size]);
ap_uint<32> separate_rgb(ap_uint<24> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b);

int edge_enhancement_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size){
#pragma HLS INTERFACE mode=s_axilite port=col_size
#pragma HLS INTERFACE mode=s_axilite port=row_size
#pragma HLS INTERFACE mode=s_axilite port=function
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register
#pragma HLS INTERFACE mode=s_axilite port=return

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> edge_enhancement;

    ap_uint<24> line_buf[size-1][1920]; // Up to HD resolution
#pragma HLS array_partition variable=line_buf block factor=2 dim=1

    ap_uint<24> pix_mat[size][size];
#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;
        if(function==ORG_IMGwAxiDma || function==EDGE_ENHANCEMENTwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
#pragma HLS PIPELINE II=1
            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];
            pix_mat[2][2] = pix.data;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = pix.data;

            edge_enhancement.data = edge_enhancement_fil(pix_mat);

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

            if(function==ORG_IMGwAxiVdma || function == EDGE_ENHANCEMENTwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    edge_enhancement.user = 1;
                else
                    edge_enhancement.user = 0;
                if(x == (col_size-1)) // 行の最後
                    edge_enhancement.last = 1;
                else
                    edge_enhancement.last = 0;
            }else{
                edge_enhancement.user = 0;
                edge_enhancement.last = pix.last;
            }
            edge_enhancement.keep = 0x7;
            edge_enhancement.strb = 0x7;
            if(function==EDGE_ENHANCEMENTwAxiVdma || function==EDGE_ENHANCEMENTwAxiDma)
                outs << edge_enhancement;
            else
                outs << pix;
        }
    }
    return(0);
}

// edge_enhancement filter
ap_uint<24> edge_enhancement_fil(ap_uint<24> (&xy)[size][size]){
    ap_int<32> pix_1d_r[size][size], pix_1d_b[size][size], pix_1d_g[size][size];
    ap_uint<8> y_r, y_b, y_g; 
    ap_uint<24> y;

    for(int i=0; i<size*size; i++){
        separate_rgb(xy[i/3][i%3], pix_1d_r[i/3][i%3], pix_1d_g[i/3][i%3], pix_1d_b[i/3][i%3]);
    }
    
    y_r = edge_enhancement_file_calc(pix_1d_r);
    y_g = edge_enhancement_file_calc(pix_1d_g);
    y_b = edge_enhancement_file_calc(pix_1d_b);

    return(((ap_uint<24>)y_r << 16) + ((ap_uint<24>)y_g << 8) + (ap_uint<24>)y_b);
}

// edge_enhancement filter
//
//  0 -1  0
// -1  5 -1
//  0 -1  0
ap_uint<8> edge_enhancement_file_calc(ap_int<32> (&xy)[size][size]){
    ap_int<32> y;

    y =           -xy[0][1] 
        -xy[1][0] +(5 * xy[1][1]) -xy[1][2]
                  -xy[2][1];
        
    if(y<0)
        y = -y;
        //y = 0;
    else if(y>255) // 8 bits
        y = 255;
    return((ap_uint<8>)y);
}

// separate_rgb
// RGBを分離する
// RBGのフォーマットは、{R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
//
ap_uint<32> separate_rgb(ap_uint<24> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b){
    b = (ap_int<32>)(rbg & 0xff);
    g = (ap_int<32>)((rbg>>8) & 0xff);
    r = (ap_int<32>)((rbg>>16) & 0xff);
    return(0);
}


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

// edge_enhancement_axis_RGB24_tb.cpp
// 2023/11/29 by marsee
// EDGE_ENHANCEMENTwXilinxVideoStandard を define すると axi_vdma 用となり、コメントアウトすると axi_dma 用になる
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "edge_enhancement_axis_RGB24.h"

#define EDGE_ENHANCEMENTwXilinxVideoStandard

constexpr int size = 3;

int edge_enhancement_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size);
int edge_enhancement_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size);
ap_uint<24> edge_enhancement_fil_soft(ap_uint<24> (&xy)[size][size]);
ap_uint<8> edge_enhancement_file_calc_soft(ap_int<32> (&xy)[size][size]);
ap_uint<32> separate_rgb_soft(ap_uint<24> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b);

const char INPUT_JPG_FILE[] = "test2.jpg";
const char OUTPUT_JPG_FILE[] = "edge_enhancement.jpg";
const char ORG_OUT_JPG_FILE[] = "org_image.jpg";

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins, ins2;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft;
    hls::stream<ap_axiu<24,1,1,1> > outs, outs2;
    hls::stream<ap_axiu<24,1,1,1> > outs_soft;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals, vals_soft;

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

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

    // rd_bmp にJPGのピクセルを代入
    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); // RGB 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

#ifdef EDGE_ENHANCEMENTwXilinxVideoStandard
    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        pix.last = 0;
        pix.user = 0;
        pix.keep = 0x7;
        pix.strb = 0x7;

        ins << pix;
        ins2 << pix;
        ins_soft << pix;
    }
#endif

    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];
#ifdef EDGE_ENHANCEMENTwXilinxVideoStandard
            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;
#else
            if(j==img.rows-1 && i==img.cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            pix.user = 0;
#endif
            pix.keep = 0x7;
            pix.strb = 0x7;

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

#ifdef EDGE_ENHANCEMENTwXilinxVideoStandard
    edge_enhancement_axis_RGB24(ins, outs, EDGE_ENHANCEMENTwAxiVdma, img.rows, img.cols); // ハードウェアのエッジ強調フィルタ
    edge_enhancement_axis_RGB24_soft(ins_soft, outs_soft, EDGE_ENHANCEMENTwAxiVdma, img.rows, img.cols); // ソフトウェアのエッジ強調フィルタ
#else
    edge_enhancement_axis_RGB24(ins, outs, EDGE_ENHANCEMENTwAxiDma, img.rows, img.cols); // ハードウェアのエッジ強調フィルタ
    edge_enhancement_axis_RGB24_soft(ins_soft, outs_soft, EDGE_ENHANCEMENTwAxiDma, img.rows, img.cols); // ソフトウェアのエッジ強調フィルタ
#endif

    // ハードウェアとソフトウェアのエッジ強調フィルタの値のチェック
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            ap_uint<32> val = vals.data;
            hw_edge_enhancement[y*img.cols+x] = (int32_t)val;
            outs_soft >> vals_soft;
            ap_uint<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 edge_enhancement_row = img.rows;
    const int edge_enhancement_cols = img.cols;
    cv::Mat wbmpf(edge_enhancement_row, edge_enhancement_cols, CV_8UC3);
    // wbmpf にedge_enhancement フィルタ処理後の画像を入力
    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 rbg = hw_edge_enhancement[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのエッジ強調フィルタの結果を jpg ファイルへ出力する
    cv::imwrite(OUTPUT_JPG_FILE, wbmpf);
#ifdef EDGE_ENHANCEMENTwXilinxVideoStandard
    edge_enhancement_axis_RGB24(ins2, outs2, ORG_IMGwAxiVdma, img.rows, img.cols); // 元画像出力
#else
    edge_enhancement_axis_RGB24(ins2, outs2, ORG_IMGwAxiDma, img.rows, img.cols); // 元画像出力
#endif

    cv::Mat wbmpf2(edge_enhancement_row, edge_enhancement_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            outs2 >> vals;
            int32_t val = vals.data;
            pixel[0] = (val & 0xff); // blue
            pixel[1] = ((val >> 8) & 0xff); // green
            pixel[2] = ((val >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を jpg ファイルへ出力する
    cv::imwrite(ORG_OUT_JPG_FILE, wbmpf2);

    return(0);
}

int edge_enhancement_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size){

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> edge_enhancement;

    ap_uint<24> line_buf[size-1][1920]; // Up to HD resolution
    ap_uint<24> pix_mat[size][size];

    do {   // user が 1になった時にフレームがスタートする

        ins >> pix;
        if(function==ORG_IMGwAxiDma || function==EDGE_ENHANCEMENTwAxiDma)
            break;
    } while(pix.user == 0);

    for(int y=0; y<row_size; y++){
        for(int x=0; x<col_size; 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];
            pix_mat[2][2] = pix.data;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = pix.data;

            edge_enhancement.data = edge_enhancement_fil_soft(pix_mat);

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

            if(function==ORG_IMGwAxiVdma || function == EDGE_ENHANCEMENTwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    edge_enhancement.user = 1;
                else
                    edge_enhancement.user = 0;
                if(x == (col_size-1)) // 行の最後
                    edge_enhancement.last = 1;
                else
                    edge_enhancement.last = 0;
            }else{
                edge_enhancement.user = 0;
                edge_enhancement.last = pix.last;
            }
            edge_enhancement.keep = 0x7;
            edge_enhancement.strb = 0x7;
            if(function==EDGE_ENHANCEMENTwAxiVdma || function==EDGE_ENHANCEMENTwAxiDma)
                outs << edge_enhancement;
            else
                outs << pix;
        }
    }
    return(0);
}

// edge_enhancement filter
ap_uint<24> edge_enhancement_fil_soft(ap_uint<24> (&xy)[size][size]){
    ap_int<32> pix_1d_r[size][size], pix_1d_b[size][size], pix_1d_g[size][size];
    ap_uint<8> y_r, y_b, y_g; 
    ap_uint<24> y;

    for(int i=0; i<size*size; i++){
        separate_rgb_soft(xy[i/3][i%3], pix_1d_r[i/3][i%3], pix_1d_g[i/3][i%3], pix_1d_b[i/3][i%3]);
    }
    
    y_r = edge_enhancement_file_calc_soft(pix_1d_r);
    y_g = edge_enhancement_file_calc_soft(pix_1d_g);
    y_b = edge_enhancement_file_calc_soft(pix_1d_b);

    return(((ap_uint<24>)y_r << 16) + ((ap_uint<24>)y_g << 8) + (ap_uint<24>)y_b);
}

// edge_enhancement filter
//
//  0 -1  0
// -1  5 -1
//  0 -1  0
ap_uint<8> edge_enhancement_file_calc_soft(ap_int<32> (&xy)[size][size]){
    ap_int<32> y;

    y =           -xy[0][1] 
        -xy[1][0] +(5 * xy[1][1]) -xy[1][2]
                  -xy[2][1];
        
    if(y<0)
        y = -y;
        //y = 0;
    else if(y>255) // 8 bits
        y = 255;
    return((ap_uint<8>)y);
}

// separate_rgb
// RGBを分離する
// RBGのフォーマットは、{R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
//
ap_uint<32> separate_rgb_soft(ap_uint<24> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b){
    b = (ap_int<32>)(rbg & 0xff);
    g = (ap_int<32>)((rbg>>8) & 0xff);
    r = (ap_int<32>)((rbg>>16) & 0xff);
    return(0);
}


画像ソフトの Pinta で 2.0 のガウスぼかしを掛けた test2.jpg を示す。
zub1cg_i7filters_83_231125.jpg
  1. 2023年11月30日 03:46 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 でラプラシアン・フィルタの laplacian_axis_RGB24 を実装する2

Vitis Unified IDE 2023.2 でラプラシアン・フィルタの laplacian_axis_RGB24 を実装する1”の続き。

ラプラシアン・フィルタもたくさん実装しているが、改めて実装してみるということで、前回は、Vitis Unified IDE 2023.2 で ZUBoard 1CG 用のラプラシアン・フィルタの laplacian_axis_RGB24 プロジェクトと作成し、ソースコードとテストベンチを書いた。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Package、Implementation を行った。

FLOW の C SIMULATION -> Run をクリックすると C Simulation が実行され、成功した。
zub1cg_i7filters_98_231129.png

laplacian_axis_RGB24 プロジェクトのディレクトリの下に laplacian_axis_RGB24 ディレクトリが生成され、その下の hls ディレクトリの下に csim ディレクトリが生成されている。
csim/build ディレクトリを見ると、laplacian.jpg と org_image.jpg が生成されていた。
zub1cg_i7filters_99_231129.png

laplacian.jpg を示す。
zub1cg_i7filters_100_231129.jpg

FLOW の C SYNTHESIS -> Run をクリックして、C コードの合成を行って成功した。
zub1cg_i7filters_101_231129.png

FLOW の C SYNTHESIS -> REPORTS -> Synthesis を表示した。
zub1cg_i7filters_102_231129.png
zub1cg_i7filters_103_231129.png

レイテンシは 2073616 クロックで問題無さそうだ。

C/RTL 協調シミュレーションを行う。
VITIS COMPONETENTS -> laplacian_axis_RGB24 -> Settings -> hls_config.cfg をクリックして、設定画面を表示した。
C/RTL Cosimulationldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
C/RTL Cosimulation の trace_levelport にした。
C/RTL Cosimulation の wave_debug にチェックを入れた。

FLOW -> C/RTL COSIMULATION -> Run をクリックして、C/RTL 協調シミュレーションを行った。
Vivado が起動して、RTL シミュレーションを行って終了した。
zub1cg_i7filters_104_231129.png

laplacian_axis_RGB24.wcfg タブをクリックし、波形を表示した。
全体波形を示す。
zub1cg_i7filters_105_231129.png

C/RTL 協調シミュレーションが終了し、成功した。
zub1cg_i7filters_106_231129.png

Cosimulation をクリックした。
レイテンシは 480017 クロックだった。問題無さそうだ。
zub1cg_i7filters_107_231129.png

Package を行う。
FLOW -> PACKAGE -> Run をクリックして、Package を行って成功した。
zub1cg_i7filters_108_231129.png

laplacian_axis_RGB24/laplacian_axis_RGB24/hls/impl/ip が生成されて、その中に ZIP ファイルとして、xilinx_com_hls_laplacian_axis_RGB24_1_0.zip が生成された。
zub1cg_i7filters_109_231129.png

Implementation を行う。
FLOW -> IMPLEMENTATION -> Run をクリックした。
Implementation が終了し、成功した。
zub1cg_i7filters_110_231129.png

Place and Route をクリックした。
CP achieved post-implementation も 5.551 ns で問題無さそうだ。
zub1cg_i7filters_111_231129.png
  1. 2023年11月29日 04:38 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 でラプラシアン・フィルタの laplacian_axis_RGB24 を実装する1

Vitis Unified IDE 2023.2 でラプラシアン・フィルタの laplacian_axis_RGB24 を実装してみよう。

Vitis Unified IDE 2023.2 で laplacian_axis_RGB24 プロジェクトを作成する。

Vitis Unified IDE 2023.2 で File メニューから New Component -> HLS を選択して、ZUBoard 1CG 用の laplacian_axis_RGB24 プロジェクトを作成した。

ソースコード、テストベンチのファイルは laplacian_axis_RGB24 プロジェクトのファイルを追加した。

最初に、VITIS COMPONENT -> laplacian_axis_RGB24 -> Settings -> hls_config.cfg をクリックし、設定を行った。
C Sythesis sourcestoplaplacian_axis_RGB24 を設定した。これは、C コードの合成時にハードウェアとする関数を指定する。
Testbench sourcesCFLAGS-I/usr/local/include を指定した。
C Simulationldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。

ソースコードに laplacian_axis_RGB24.h と laplacian_axis_RGB24.cpp を追加した。
テストベンチに laplacian_axis_RGB24_tb.cpp と test2.jpg を追加した。
zub1cg_i7filters_96_231128.png

ヘッダ・ファイルの laplacian_axis_RGB24.h を示す。

// laplacian_axis_RGB24.h
// 2023/11/27 by marsee
//

#ifndef __LAPLACIAN_AXIS_RGB24_H__
#define __LAPLACIAN_AXIS_RGB24_H__

#define ORG_IMGwAxiVdma 0
#define LAPLACIANwAxiVdma 1
#define ORG_IMGwAxiDma 2
#define LAPLACIANwAxiDma 3

#endif


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

// laplacian_axis_RGB24.cpp
// 2023/11/27 by marsee
//

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

#include "laplacian_axis_RGB24.h"

ap_int<32> laplacian_fil(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_rbg2y(ap_int<32> rbg);

int laplacian_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size){
#pragma HLS INTERFACE mode=s_axilite port=col_size
#pragma HLS INTERFACE mode=s_axilite port=row_size
#pragma HLS INTERFACE mode=s_axilite port=function
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register
#pragma HLS INTERFACE mode=s_axilite port=return

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> laplacian;
    ap_int<32> laplacian_val;

    ap_int<32> line_buf[2][1920]; // Up to HD resolution
#pragma HLS array_partition variable=line_buf block factor=2 dim=1

    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;
        if(function==ORG_IMGwAxiDma || function==LAPLACIANwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
#pragma HLS PIPELINE II=1
            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_rbg2y(pix.data);
            pix_mat[2][2] = y_val;

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

            laplacian_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]);
            laplacian.data = (laplacian_val<<16)+(laplacian_val<<8)+laplacian_val;

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

            if(function==ORG_IMGwAxiVdma || function == LAPLACIANwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    laplacian.user = 1;
                else
                    laplacian.user = 0;
                if(x == (col_size-1)) // 行の最後
                    laplacian.last = 1;
                else
                    laplacian.last = 0;
            }else{
                laplacian.user = 0;
                laplacian.last = pix.last;
            }
            laplacian.keep = 0x7;
            laplacian.strb = 0x7;
            if(function==LAPLACIANwAxiVdma || function==LAPLACIANwAxiDma)
                outs << laplacian;
            else
                outs << pix;
        }
    }
    return(0);
}

// RBGからYへの変換
// RBGのフォーマットは、{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_rbg2y(ap_int<32> rbg){
    ap_int<32> r, g, b, y_f;
    ap_int<32> y;

    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>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);
}

// laplacian filter
//
// x0y0 x1y0 x2y0  1  1  1
// x0y1 x1y1 x2y1  1 -8  1
// x0y2 x1y2 x2y2  1  1  1
ap_int<32> laplacian_fil(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;

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


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

// laplacian_axis_RGB24_tb.cpp
// 2023/11/27 by marsee
// LAPLACIANwXilinxVideoStandard を define すると axi_vdma 用となり、コメントアウトすると axi_dma 用になる
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "laplacian_axis_RGB24.h"

#define LAPLACIANwXilinxVideoStandard

int laplacian_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size);
int laplacian_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size);
ap_int<32> laplacian_fil_soft(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_rbg2y_soft(ap_int<32> rbg);

const char INPUT_JPG_FILE[] = "test2.jpg";
const char OUTPUT_JPG_FILE[] = "laplacian.jpg";
const char ORG_OUT_JPG_FILE[] = "org.jpg";

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins, ins2;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft;
    hls::stream<ap_axiu<24,1,1,1> > outs, outs2;
    hls::stream<ap_axiu<24,1,1,1> > outs_soft;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals, vals_soft;

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

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

    // rd_bmp にJPGのピクセルを代入
    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); // RGB 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

#ifdef LAPLACIANwXilinxVideoStandard
    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        pix.last = 0;
        pix.user = 0;
        pix.keep = 0x7;
        pix.strb = 0x7;

        ins << pix;
        ins2 << pix;
        ins_soft << pix;
    }
#endif

    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];
#ifdef LAPLACIANwXilinxVideoStandard
            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;
#else
            if(j==img.rows-1 && i==img.cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            pix.user = 0;
#endif
            pix.keep = 0x7;
            pix.strb = 0x7;

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

#ifdef LAPLACIANwXilinxVideoStandard
    laplacian_axis_RGB24(ins, outs, LAPLACIANwAxiVdma, img.rows, img.cols); // ハードウェアのラプラシアンフィルタ
    laplacian_axis_RGB24_soft(ins_soft, outs_soft, LAPLACIANwAxiVdma, img.rows, img.cols); // ソフトウェアのラプラシアンフィルタ
#else
    laplacian_axis_RGB24(ins, outs, LAPLACIANwAxiDma, img.rows, img.cols); // ハードウェアのラプラシアンフィルタ
    laplacian_axis_RGB24_soft(ins_soft, outs_soft, LAPLACIANwAxiDma, img.rows, img.cols); // ソフトウェアのラプラシアンフィルタ
#endif

    // ハードウェアとソフトウェアのラプラシアンフィルタの値のチェック
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            ap_uint<32> val = vals.data;
            hw_laplacian[y*img.cols+x] = (int32_t)val;
            outs_soft >> vals_soft;
            ap_uint<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 laplacian_row = img.rows;
    const int laplacian_cols = img.cols;
    cv::Mat wbmpf(laplacian_row, laplacian_cols, CV_8UC3);
    // wbmpf にlaplacian フィルタ処理後の画像を入力
    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 rbg = hw_laplacian[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのラプラシアンフィルタの結果を jpg ファイルへ出力する
    cv::imwrite(OUTPUT_JPG_FILE, wbmpf);
#ifdef LAPLACIANwXilinxVideoStandard
    laplacian_axis_RGB24(ins2, outs2, ORG_IMGwAxiVdma, img.rows, img.cols); // 元画像出力
#else
    laplacian_axis_RGB24(ins2, outs2, ORG_IMGwAxiDma, img.rows, img.cols); // 元画像出力
#endif

    cv::Mat wbmpf2(laplacian_row, laplacian_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            outs2 >> vals;
            int32_t val = vals.data;
            pixel[0] = (val & 0xff); // blue
            pixel[1] = ((val >> 8) & 0xff); // green
            pixel[2] = ((val >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を jpg ファイルへ出力する
    cv::imwrite(ORG_OUT_JPG_FILE, wbmpf2);

    return(0);
}

int laplacian_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
        int32_t row_size, int32_t col_size){

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> laplacian;
    ap_int<32> laplacian_val;

    ap_int<32> line_buf[2][1920]; // Up to HD resolution

    ap_int<32> pix_mat[3][3];

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

    for(int y=0; y<row_size; y++){
        for(int x=0; x<col_size; x++){
            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_rbg2y_soft(pix.data);
            pix_mat[2][2] = y_val;

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

            laplacian_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]);
            laplacian.data = (laplacian_val<<16)+(laplacian_val<<8)+laplacian_val;

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

            if(function==ORG_IMGwAxiVdma || function == LAPLACIANwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    laplacian.user = 1;
                else
                    laplacian.user = 0;
                if(x == (col_size-1)) // 行の最後
                    laplacian.last = 1;
                else
                    laplacian.last = 0;
            }else{
                laplacian.user = 0;
                laplacian.last = pix.last;
            }
            laplacian.keep = 0x7;
            laplacian.strb = 0x7;
            if(function==LAPLACIANwAxiVdma || function==LAPLACIANwAxiDma)
                outs << laplacian;
            else
                outs << pix;
        }
    }
    return(0);
}

// RBGからYへの変換
// RBGのフォーマットは、{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_rbg2y_soft(ap_int<32> rbg){
    ap_int<32> r, g, b, y_f;
    ap_int<32> y;

    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>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);
}

// laplacian filter
//
// x0y0 x1y0 x2y0  1  1  1
// x0y1 x1y1 x2y1  1 -8  1
// x0y2 x1y2 x2y2  1  1  1
ap_int<32> laplacian_fil_soft(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;

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


test2.jpg を示す。
zub1cg_i7filters_97_231128.jpg
  1. 2023年11月28日 04:19 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 でアンシャープマスクキング・フィルタの unsharp_masking_axis_RGB24 を実装する2

Vitis Unified IDE 2023.2 でアンシャープマスクキング・フィルタの unsharp_masking_axis_RGB24 を実装する1”の続き。

以前にもアンシャープマスキング・フィルタを実装したことがあったが、実装し直すことにしたということで、前回は、Vitis Unified IDE 2023.2 で ZUBoard 1CG 用のアンシャープマスクキング・フィルタの unsharp_masking_axis_RGB24 プロジェクトと作成し、ソースコードとテストベンチを書いた。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Package、Implementation を行った。

FLOW の C SIMULATION -> Run をクリックすると C Simulation が実行され、成功した。
zub1cg_i7filters_84_231125.png

unsharp_masking_axis_RGB24 プロジェクトのディレクトリの下に unsharp_masking_axis_RGB24 ディレクトリが生成され、その下の hls ディレクトリの下に csim ディレクトリが生成されている。
csim/build ディレクトリを見ると、unsharp_masking.jpg と org_image.jpg が生成されていた。
zub1cg_i7filters_85_231125.png

原画像の org_image.jpg に対して、unsharp_masking.jpg は画像がシャープになっているのが分かる。
zub1cg_i7filters_86_231125.jpg

FLOW の C SYNTHESIS -> Run をクリックして、C コードの合成を行って成功した。
zub1cg_i7filters_87_231125.png

FLOW の C SYNTHESIS -> REPORTS -> Synthesis を表示した。
zub1cg_i7filters_88_231125.png
zub1cg_i7filters_89_231125.png

レイテンシは 2073615 クロックで問題無さそうだ。

C/RTL 協調シミュレーションを行う。
VITIS COMPONETENTS -> unsharp_masking_axis_RGB24 -> Settings -> hls_config.cfg をクリックして、設定画面を表示した。
C/RTL Cosimulationldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
trace_level を port にした。

FLOW -> C/RTL COSIMULATION -> Run をクリックして、C/RTL 協調シミュレーションを行ったところ終了しなかった。
強制終了して、設定を変更した。
hls_config.cfg の C/RTL Cosimulation の wave_debug にチェックを入れて、もう一度 C/RTL COSIMULATION -> Run をクリックした。
Vivado が起動して、RTL シミュレーションを行って終了した。
zub1cg_i7filters_90_231125.png

unsharp_masking_axis_RGB24.wcfg タブをクリックし、波形を表示した。
全体波形を示す。
zub1cg_i7filters_91_231125.png

C/RTL 協調シミュレーションが終了し、成功した。
zub1cg_i7filters_92_231125.png

Cosimulation をクリックした。
レイテンシは 480011 クロックだった。問題無さそうだ。
zub1cg_i7filters_93_231125.png

Package を行う。
FLOW -> PACKAGE -> Run をクリックして、Package を行って成功した。
zub1cg_i7filters_94_231125.png

Implementation を行う。
FLOW -> IMPLEMENTATION -> Run をクリックした。
Implementation が終了し、成功した。

Place and Route をクリックした。
CP achieved post-implementation も 7.001 ns で問題無さそうだ。
zub1cg_i7filters_95_231125.png
  1. 2023年11月27日 03:44 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 でアンシャープマスクキング・フィルタの unsharp_masking_axis_RGB24 を実装する1

以前にもアンシャープマスキング・フィルタを実装したことがあったが、実装し直すことにして、Vitis Unified IDE 2023.2 でアンシャープマスクキング・フィルタの unsharp_masking_axis_RGB24 を実装した。
アンシャープマスキング・フィルタについては、”アンシャープマスキング(鮮鋭化フィルタ)”を参照した。

Vitis Unified IDE 2023.2 で unsharp_masking_axis_RGB24 プロジェクトを作成する。

Vitis Unified IDE 2023.2 で File メニューから New Component -> HLS を選択して、ZUBoard 1CG 用の unsharp_masking_axis_RGB24 プロジェクトを作成した。

ソースコード、テストベンチのファイルは unsharp_masking_axis_RGB24 プロジェクトのファイルを追加した。

最初に、VITIS COMPONENT -> unsharp_masking_axis_RGB24 -> Settings -> hls_config.cfg をクリックし、設定を行った。
C Sythesis sources の top に unsharp_masking_axis_RGB24 を設定した。これは、C コードの合成時にハードウェアとする関数を指定する。
Testbench sources の CFLAGS-I/usr/local/include を指定した。
C Simulation の ldflags -L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。

ソースコードに unsharp_masking_axis_RGB24.h と unsharp_masking_axis_RGB24.cpp を追加した。
テストベンチに unsharp_masking_axis_RGB24_tb.cpp と画像ソフトの Pinta で 2.0 のガウスぼかしを掛けた test2.jpg を追加した。
zub1cg_i7filters_82_231125.png

ヘッダ・ファイルの unsharp_masking_axis_RGB24.h を示す。

// unsharp_masking_axis_RGB24.h
// 2023/11/25 by marsee
//

#ifndef __UNSHARP_MASKING_FILTER_AXIS_RGB24_H__
#define __UNSHARP_MASKING_FILTER_AXIS_RGB24_H__

#define HORIZONTAL 0
#define VERTICAL 1

#define ORG_IMGwAxiVdma 0
#define UNSHARP_MASKINGwAxiVdma   1
#define ORG_IMGwAxiDma  2
#define UNSHARP_MASKINGwAxiDma    3

#define UM_FIXED ap_fixed<24, 16, AP_TRN_ZERO, AP_SAT>
#endif


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

// unsharp_masking_axis_RGB24.cpp
// 2023/08/18 by marsee
// アンシャープマスキング・フィルタのアルゴリズムは”アンシャープマスキング(鮮鋭化フィルタ)”
// (https://imagingsolution.net/imaging/unsharpmasking/)を参照した
//

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

#include "unsharp_masking_axis_RGB24.h"

constexpr int size = 3;

void unsharp_masking_fil(ap_int<32> (&pix_mat)[size][size], UM_FIXED k, ap_uint<24> &result);
ap_int<32> unsharp_masking_fil_calc(ap_int<32> *pixd, UM_FIXED k);
ap_int<32> separate_rgb(ap_int<32> rgb, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b);

int unsharp_masking_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size, UM_FIXED k){
#pragma HLS INTERFACE mode=s_axilite port=k
#pragma HLS INTERFACE mode=s_axilite port=col_size
#pragma HLS INTERFACE mode=s_axilite port=row_size
#pragma HLS INTERFACE mode=s_axilite port=function
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register
#pragma HLS INTERFACE mode=s_axilite port=return

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> unsharp_masking;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1

    ap_int<32> pix_mat[size][size];
#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;
        if(function==ORG_IMGwAxiDma || function==UNSHARP_MASKINGwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
#pragma HLS PIPELINE II=1
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            unsharp_masking_fil(pix_mat, k, val);
            unsharp_masking.data = val;
            if(x<2 || y<2)
                unsharp_masking.data = 0;

            if(function==ORG_IMGwAxiVdma || function == UNSHARP_MASKINGwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    unsharp_masking.user = 1;
                else
                    unsharp_masking.user = 0;
                if(x == (col_size-1)) // 行の最後
                    unsharp_masking.last = 1;
                else
                    unsharp_masking.last = 0;
            }else{
                unsharp_masking.user = 0;
                unsharp_masking.last = pix.last;
            }
            unsharp_masking.keep = 0x7;
            unsharp_masking.strb = 0x7;
            if(function==UNSHARP_MASKINGwAxiVdma || function==UNSHARP_MASKINGwAxiDma)
                outs << unsharp_masking;
            else
                outs << pix;
        }
    }
    return(0);
}

// アンシャープマスキング・フィルタ
// x0y0 x1y0 x2y0 -k   -j  -k
// x0y1 x1y1 x2y1 -k  9+8k -k x 1/9
// x0y2 x1y2 x2y2 -k   -k  -k
//
// k : 鮮鋭化の強さ(固定小数点) , k != 0
// num_adec_k : Kの小数点の位置
// 2015/09/27 : 演算の小数部は num_adec_k*2 ビットとする。
//
void unsharp_masking_fil(ap_int<32> (&pix_mat)[size][size], UM_FIXED k, ap_uint<24> &result){
    ap_int<32> pix_1d_r[9], pix_1d_b[9], pix_1d_g[9];
    ap_int<32> y_r, y_b, y_g, y;

    for(int i=0; i<9; i++){
        separate_rgb(pix_mat[i/3][i%3], pix_1d_r[i], pix_1d_g[i], pix_1d_b[i]);
    }

    y_r = unsharp_masking_fil_calc(pix_1d_r, k);
    y_b = unsharp_masking_fil_calc(pix_1d_b, k);
    y_g = unsharp_masking_fil_calc(pix_1d_g, k);

    result = (y_r << 16) + (y_g << 8) + y_b;
}

// unsharp_masking_fil_calc
// アルゴリズムは”アンシャープマスキング(鮮鋭化フィルタ)” https://imagingsolution.net/imaging/unsharpmasking/ を参照している
ap_int<32> unsharp_masking_fil_calc(ap_int<32> *pixd, UM_FIXED k){
    UM_FIXED y_fixed;
    ap_int<32> y;

    y_fixed = (-(UM_FIXED)pixd[0] - (UM_FIXED)pixd[1] - (UM_FIXED)pixd[2] -(UM_FIXED)pixd[3]
        - (UM_FIXED)pixd[5] - (UM_FIXED)pixd[6] - (UM_FIXED)pixd[7] - (UM_FIXED)pixd[8]) * k;
    y_fixed += ((UM_FIXED)9.0 + (UM_FIXED)8.0 * k) * pixd[4];
    y_fixed /= (UM_FIXED)9.0;
    y = (ap_int<32>)(y_fixed + (UM_FIXED)0.5); // 四捨五入          

    if(y<0)
        //y = -y;
        y = 0;
    else if(y>255) // 8 bits
        y = 255;
    return(y);
}

// separate_rgb
// RGBを分離する
// RBGのフォーマットは、{R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
//
ap_int<32> separate_rgb(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b){
    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>16) & 0xff;
    return(0);
}


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

// unsharp_masking_axis_RGB24_tb.cpp
// 2023/08/18 by marsee
// UNSHARP_MASKINGwXilinxVideoStandard を define すると axi_vdma 用となり、コメントアウトすると axi_dma 用になる
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "unsharp_masking_axis_RGB24.h"

//#define UNSHARP_MASKINGwXilinxVideoStandard

constexpr int size = 3;

int unsharp_masking_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size, UM_FIXED k);
int unsharp_masking_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size, float k);
void unsharp_masking_fil_soft(ap_int<32> (&pix_mat)[size][size], float k, ap_uint<24> &result);
ap_int<32> unsharp_masking_fil_calc_soft(ap_int<32> *pixd, float k);
ap_int<32> separate_rgb_soft(ap_int<32> rgb, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b);

const char INPUT_JPG_FILE[] = "test2.jpg";
const char OUTPUT_JPG_FILE[] = "unsharp_masking.jpg";
const char ORG_OUT_JPG_FILE[] = "org_image.jpg";

#define SHARPENING_STRENGTH 4.0
#define SQUARE_ERROR_LIMIT    0 // 2乗誤差のエラー限界、この数より上はエラーとする

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins, ins2;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft;
    hls::stream<ap_axiu<24,1,1,1> > outs, outs2;
    hls::stream<ap_axiu<24,1,1,1> > outs_soft;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals, vals_soft;
    UM_FIXED k;
    float fk;

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

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

    // rd_bmp にJPGのピクセルを代入
    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); // RGB 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

#ifdef UNSHARP_MASKINGwXilinxVideoStandard
    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        pix.last = 0;
        pix.user = 0;
        pix.keep = 0x7;
        pix.strb = 0x7;
        ins << pix;
    }
#endif

    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (int32_t)rd_bmp[(j*img.cols)+i];
#ifdef UNSHARP_MASKINGwXilinxVideoStandard
            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;
#else
            if(j==img.rows-1 && i==img.cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            pix.user = 0;
#endif
            pix.keep = 0x7;
            pix.strb = 0x7;

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

    k = (UM_FIXED)SHARPENING_STRENGTH;
    fk = (float)SHARPENING_STRENGTH;
#ifdef UNSHARP_MASKINGwXilinxVideoStandard
    unsharp_masking_axis_RGB24(ins, outs, UNSHARP_MASKINGwAxiVdma, img.rows, img.cols, k); // ハードウェアのメディアンフィルタ
    unsharp_masking_axis_RGB24_soft(ins_soft, outs_soft, UNSHARP_MASKINGwAxiVdma, img.rows, img.cols, fk);  // ソフトウェアのメディアンフィルタ
#else
    unsharp_masking_axis_RGB24(ins, outs, UNSHARP_MASKINGwAxiDma, img.rows, img.cols, k); // ハードウェアのメディアンフィルタ
    unsharp_masking_axis_RGB24_soft(ins_soft, outs_soft, UNSHARP_MASKINGwAxiDma, img.rows, img.cols, fk);  // ソフトウェアのメディアンフィルタ
#endif

    // ハードウェアとソフトウェアのメディアンフィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_uint<32> val = vals.data;
            ap_uint<32> val_soft = vals_soft.data;
            hw_unsharp_masking[y*img.cols+x] = (int32_t)val;
            if ((double)pow((double)(val&0xff)-(val_soft&0xff),(double)2) > SQUARE_ERROR_LIMIT || // v の2乗誤差が4よりも大きい
                (double)pow((double)((val>>8)&0xff)-((val_soft>>8)&0xff),(double)2) > SQUARE_ERROR_LIMIT || // s の2乗誤差が4よりも大きい
                (double)pow((double)((val>>16)&0x1ff)-((val_soft>>16)&0x1ff),(double)2) > SQUARE_ERROR_LIMIT){ // h の2乗誤差が4よりも大きい
                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 unsharp_masking_row = img.rows;
    const int unsharp_masking_cols = img.cols;
    cv::Mat wbmpf(unsharp_masking_row, unsharp_masking_cols, CV_8UC3);
    // wbmpf にunsharp_masking フィルタ処理後の画像を入力
    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 rbg = hw_unsharp_masking[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのメディアンフィルタの結果を jpg ファイルへ出力する
    cv::imwrite(OUTPUT_JPG_FILE, wbmpf);

#ifdef UNSHARP_MASKINGwXilinxVideoStandard
    unsharp_masking_axis_RGB24(ins2, outs2, ORG_IMGwAxiVdma, img.rows, img.cols, k); // ハードウェアのメディアンフィルタ
#else
    unsharp_masking_axis_RGB24(ins2, outs2, ORG_IMGwAxiDma, img.rows, img.cols, k); // ハードウェアのメディアンフィルタ
#endif

    cv::Mat wbmpf2(unsharp_masking_row, unsharp_masking_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            outs2 >> vals;
            int32_t val = vals.data;
            pixel[0] = (val & 0xff); // blue
            pixel[1] = ((val >> 8) & 0xff); // green
            pixel[2] = ((val >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を jpg ファイルへ出力する
    cv::imwrite(ORG_OUT_JPG_FILE, wbmpf2);

    return(0);
}

// アンシャープマスキング・フィルタのアルゴリズムは”アンシャープマスキング(鮮鋭化フィルタ)”
// (https://imagingsolution.net/imaging/unsharpmasking/)を参照した
//
int unsharp_masking_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size, float k){

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> unsharp_masking;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1

    ap_int<32> pix_mat[size][size];
#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;
        if(function==ORG_IMGwAxiDma || function==UNSHARP_MASKINGwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
#pragma HLS PIPELINE II=1
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            unsharp_masking_fil_soft(pix_mat, k, val);
            unsharp_masking.data = val;
            if(x<2 || y<2)
                unsharp_masking.data = 0;

            if(function==ORG_IMGwAxiVdma || function == UNSHARP_MASKINGwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    unsharp_masking.user = 1;
                else
                    unsharp_masking.user = 0;
                if(x == (col_size-1)) // 行の最後
                    unsharp_masking.last = 1;
                else
                    unsharp_masking.last = 0;
            }else{
                unsharp_masking.user = 0;
                unsharp_masking.last = pix.last;
            }
            unsharp_masking.keep = 0x7;
            unsharp_masking.strb = 0x7;
            if(function==UNSHARP_MASKINGwAxiVdma || function==UNSHARP_MASKINGwAxiDma)
                outs << unsharp_masking;
            else
                outs << pix;
        }
    }
    return(0);
}

// アンシャープマスキング・フィルタ
// x0y0 x1y0 x2y0 -k   -j  -k
// x0y1 x1y1 x2y1 -k  9+8k -k x 1/9
// x0y2 x1y2 x2y2 -k   -k  -k
//
// k : 鮮鋭化の強さ(固定小数点) , k != 0
// num_adec_k : Kの小数点の位置
// 2015/09/27 : 演算の小数部は num_adec_k*2 ビットとする。
//
void unsharp_masking_fil_soft(ap_int<32> (&pix_mat)[size][size], float k, ap_uint<24> &result){
    ap_int<32> pix_1d_r[9], pix_1d_b[9], pix_1d_g[9];
    ap_int<32> y_r, y_b, y_g, y;

    for(int i=0; i<9; i++){
        separate_rgb_soft(pix_mat[i/3][i%3], pix_1d_r[i], pix_1d_g[i], pix_1d_b[i]);
    }

    y_r = unsharp_masking_fil_calc_soft(pix_1d_r, k);
    y_b = unsharp_masking_fil_calc_soft(pix_1d_b, k);
    y_g = unsharp_masking_fil_calc_soft(pix_1d_g, k);

    result = (y_r << 16) + (y_g << 8) + y_b;
}

// unsharp_masking_fil_calc
// アルゴリズムは”アンシャープマスキング(鮮鋭化フィルタ)” https://imagingsolution.net/imaging/unsharpmasking/ を参照している
ap_int<32> unsharp_masking_fil_calc_soft(ap_int<32> *pixd, float k){
    float y_fixed;
    ap_int<32> y;

    y_fixed = (-(float)pixd[0] - (float)pixd[1] - (float)pixd[2] -(float)pixd[3]
        - (float)pixd[5] - (float)pixd[6] - (float)pixd[7] - (float)pixd[8]) * k;
    y_fixed += ((float)9.0 + (float)8.0 * k) * pixd[4];
    y_fixed /= (float)9.0;
    y = (ap_int<32>)(y_fixed + (float)0.5); // 四捨五入          

    if(y<0)
        //y = -y;
        y = 0;
    else if(y>255) // 8 bits
        y = 255;
    return(y);
}

// separate_rgb
// RGBを分離する
// RBGのフォーマットは、{R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
//
ap_int<32> separate_rgb_soft(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b){
    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>16) & 0xff;
    return(0);
}


画像ソフトの Pinta で 2.0 のガウスぼかしを掛けた test2.jpg を示す。
zub1cg_i7filters_83_231125.jpg
  1. 2023年11月25日 07:58 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

FPGAの部屋のまとめサイトの更新(2023年11月24日)

FPGAの部屋のまとめサイト”を更新しました。
2023年11月23日までの記事をまとめました。
  1. 2023年11月24日 06:44 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 でメディアンフィルタの median_axis_RGB24 を実装する2

Vitis Unified IDE 2023.2 でメディアンフィルタの median_axis_RGB24 を実装する1”の続き。

メディアンフィルタの median_axis_RGB24 がバグっていたので作り直すということで、前回は、Vitis Unified IDE 2023.2 で median_axis_RGB24 プロジェクトを作成し、設定を行った。そして、ソースコードとテストベンチを示した。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Package、Implementation を行った。

FLOW の C SIMULATION -> Run をクリックすると C Simulation が実行され、成功した。
”MEDIAN: Success HW and SW results match”が表示されたが、表示されるはずの”"ORG: Success HW and SW results match”が表示されない。しかし、max.jpg ファイルは出力されているので、ソフトウェアは走ってはいるようだ。
zub1cg_i7filters_69_231123.png

median_axis_RGB24 プロジェクトのディレクトリの下に median_axis_RGB24 ディレクトリが生成され、その下の hls ディレクトリの下に csim ディレクトリが生成されている。
csim/build ディレクトリを見ると、median.jpg と org_image.jpg が生成されていた。
zub1cg_i7filters_70_231123.png

median.jpg ではノイズがきれいに除去されている。
zub1cg_i7filters_71_231123.jpg

FLOW の C SYNTHESIS -> Run をクリックして、C コードの合成を行って成功した。
zub1cg_i7filters_72_231123.png

FLOW の C SYNTHESIS -> REPORTS -> Synthesis を表示した。
zub1cg_i7filters_73_231123.png
zub1cg_i7filters_74_231123.png

レイテンシは 2073624 クロックだった。画素数は 1920 x 1080 = 2073600 ピクセルなので、良好だと言える。

C/RTL Cosimulation を行う。
VITIS COMPONETENTS -> median_axis_RGB24 -> Settings -> hls_config.cfg をクリックして、設定画面を表示した。
C/RTL Cosimulation の ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
trace_levelport にした。

FLOW -> C/RTL COSIMULATION -> Run をクリックして、C/RTL 協調シミュレーションを行ったところ、成功した。
zub1cg_i7filters_75_231123.png

Cosimulation をクリックした。
レイテンシは 480020 クロックだった。問題無さそうだ。
zub1cg_i7filters_76_231123.png

Wave Viewer をクリックした。
Vivado が起動して、波形を表示した。全体波形を示す。
zub1cg_i7filters_77_231123.png

Package を行う。
FLOW -> PACKAGE -> Run をクリックして、Package を行って成功した。
zub1cg_i7filters_78_231123.png

median_axis_RGB24/median_axis_RGB24/hls/impl/ip が生成されて、その中に ZIP ファイルとして、xilinx_com_hls_median_axis_RGB24_1_0.zip が生成された。
zub1cg_i7filters_79_231123.png

Implementation を行う。
FLOW -> IMPLEMENTATION -> Run をクリックした。
Implementation が終了し、成功した。
zub1cg_i7filters_80_231123.png

Place and Route をクリックした。
CP achieved post-implementation も 6.809 ns で問題無さそうだ。
zub1cg_i7filters_81_231123.png
  1. 2023年11月23日 05:21 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 でメディアンフィルタの median_axis_RGB24 を実装する1

RGB 24 ビット・データ入出力対応のメディアン・フィルタを Vitis HLS 2021.2 で作成する1”のメディアンフィルタは 3 x 3 ピクセルの領域から、RGB それぞれの中央値を見つけるソースコードだったが、それでは、ピクセルを合成していることになってしまう。各ピクセルを輝度値に直して、中央値になるピクセルを見つけ、そのピクセルをメディアンフィルタの値とする必要があるので、実装し直すことにした。

Vitis Unified IDE 2023.2 で median_axis_RGB24 プロジェクトを作成する。

Vitis Unified IDE 2023.2 で File メニューから New Component -> HLS を選択して、ZUBoard 1CG 用の median_axis_RGB24 プロジェクトを作成した。

ソースコード、テストベンチのファイルは median_axis_RGB24 プロジェクトのファイルを追加した。

最初に、VITIS COMPONENT -> median_axis_RGB24 -> Settings -> hls_config.cfg をクリックし、設定を行った。
C Sythesis sources の top に median_axis_RGB24 を設定した。これは、C コードの合成時にハードウェアとする関数を指定する。
Testbench sources の CFLAGS に -I/usr/local/include を指定した。
C Simulation の ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。

ソースコードに median_axis_RGB24.h と median_axis_RGB24.cpp を追加した。
テストベンチに median_axis_RGB24_tb.cpp とノイズ入りの画像 test2.jpg を追加した。
zub1cg_i7filters_68_231122.png

ヘッダ・ファイルの median_axis_RGB24.h を貼っておく。

// min_max_axis_RGB24.h
// 2023/11/20 by marsee
//

#ifndef __MIN_MAX_AXIS_RGB24_H__
#define __MIN_MAX_AXIS_RGB24_H__

#include <stdint.h>

#define ORG_IMGwAxiVdma 0
#define MEDIANwAxiVdma  1
#define ORG_IMGwAxiDma  2
#define MEDIANwAxiDma   3

struct pix_st{
    uint32_t pix;
    uint32_t index;
};

#endif


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

// median_axis_RGB24.cpp
// 2023/11/20 by marsee
//

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

#include "median_axis_RGB24.h"

constexpr int size = 3;

void median_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result);
void pixel_sort(pix_st *y);
ap_uint<32> conv_rbg2y(ap_uint<32> rbg);

int median_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size){
#pragma HLS INTERFACE mode=s_axilite port=col_size
#pragma HLS INTERFACE mode=s_axilite port=row_size
#pragma HLS INTERFACE mode=s_axilite port=function
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register
#pragma HLS INTERFACE mode=s_axilite port=return

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> median;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1

    ap_int<32> pix_mat[size][size];
#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;
        if(function==ORG_IMGwAxiDma || function==MEDIANwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
#pragma HLS PIPELINE II=1
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            median_fil(pix_mat, val);
            median.data = val;
            if(x<2 || y<2)
                median.data = 0;

            if(function==ORG_IMGwAxiVdma || function==MEDIANwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    median.user = 1;
                else
                    median.user = 0;
                if(x == (col_size-1)) // 行の最後
                    median.last = 1;
                else
                    median.last = 0;
            }else{
                median.user = 0;
                median.last = pix.last;
            }
            median.keep = 0x7;
            median.strb = 0x7;
            if(function==MEDIANwAxiDma || function==MEDIANwAxiVdma)
                outs << median;
            else
                outs << pix;
        }
    }
    return(0);
}

// median filter
//
// x0y0 x1y0 x2y0
// x0y1 x1y1 x2y1
// x0y2 x1y2 x2y2
//
void median_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result){
    pix_st pixst[size*size];
    uint32_t index;

    for(int i=0; i<size*size; i++){
        pixst[i].pix = (uint32_t)conv_rbg2y(pix_mat[i/size][i%size]);
        pixst[i].index = i;
    }

    pixel_sort(pixst);
    
    index = pixst[4].index; // 中央値
    result = (ap_uint<24>)pix_mat[index/size][index%size];
}

// pixel_sort()
// bubble sort
// ”メジアン(中央値)、範囲(レンジ)、ヒストグラムを求める”参照
// https://cgengo.sakura.ne.jp/arg04.html
void pixel_sort(pix_st *y){
#pragma HLS ARRAY_PARTITION variable=y dim=1 complete
    pix_st tmp;

    for(int i=1; i<size*size; i++){
        for(int j=0; j<size*size-i; j++){
            if(y[j].pix < y[j+1].pix){
                tmp = y[j];
                y[j] = y[j+1];
                y[j+1] = tmp;
            }
        }
    }
}

// RBGからYへの変換
// RBGのフォーマットは、{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_uint<32> conv_rbg2y(ap_uint<32> rbg){
    ap_uint<32> r, g, b, y_f;
    ap_uint<32> y;

    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>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);
}


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

// median_axis_RGB24_tb.cpp
// 2023/11/20 by marsee
// MEDIANwXilinxVideoStandard を define すると axi_vdma 用となり、コメントアウトすると axi_dma 用になる
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "median_axis_RGB24.h"

//#define MEDIANwXilinxVideoStandard
//#define ORGINAL_IMAGE_TEST

constexpr int size = 3;

int median_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size);
int median_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size);
void median_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result);
void pixel_sort_soft(pix_st *y);
ap_uint<32> conv_rbg2y_soft(ap_uint<32> rbg);

const char INPUT_JPG_FILE[] = "test2.jpg";
const char MEDIAN_JPG_FILE[] = "median.jpg";
const char ORG_IMAGE_FILE[] = "org_image.jpg";

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins, ins2;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft, ins_soft2;
    hls::stream<ap_axiu<24,1,1,1> > outs, outs2;
    hls::stream<ap_axiu<24,1,1,1> > outs_soft, outs_soft2;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals, vals_soft;

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

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

    // rd_bmp にJPGのピクセルを代入
    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); // RGB 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

#ifdef MEDIANwXilinxVideoStandard
    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        pix.last = 0;
        pix.user = 0;
        pix.keep = 0x7;
        pix.strb = 0x7;
        ins << pix;
    }
#endif

    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (int32_t)rd_bmp[(j*img.cols)+i];
#ifdef MEDIANwXilinxVideoStandard
            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;
#else
            if(j==img.rows-1 && i==img.cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            pix.user = 0;
#endif
            pix.keep = 0x7;
            pix.strb = 0x7;

            ins << pix;
            ins2 << pix;
            ins_soft << pix;
            ins_soft2 << pix;
        }
    }

#ifdef MEDIANwXilinxVideoStandard
    median_axis_RGB24(ins, outs, MEDIANwAxiVdma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
    median_axis_RGB24_soft(ins_soft, outs_soft, MEDIANwAxiVdma, img.rows, img.cols);  // ソフトウェアのメディアンフィルタ
#else
    median_axis_RGB24(ins, outs, MEDIANwAxiDma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
    median_axis_RGB24_soft(ins_soft, outs_soft, MEDIANwAxiDma, img.rows, img.cols);  // ソフトウェアのメディアンフィルタ
#endif

    // ハードウェアとソフトウェアのメディアンフィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_uint<32> val = vals.data;
            hw_median[y*img.cols+x] = (int32_t)val;
            if (val != vals_soft.data){
                printf("ERROR MEDIAN: HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, val, vals_soft.data);
                return(1);
            }
        }
    }
    printf("MEDIAN: Success HW and SW results match\n");

    const int median_row = img.rows;
    const int median_cols = img.cols;
    cv::Mat wbmpf(median_row, median_cols, CV_8UC3);
    // wbmpf にmedian フィルタ処理後の画像を入力
    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 rbg = hw_median[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのメディアンフィルタの結果を jpg ファイルへ出力する
    cv::imwrite(MEDIAN_JPG_FILE, wbmpf);

    // オリジナル画像テスト
    #ifdef MEDIANwXilinxVideoStandard
        median_axis_RGB24(ins2, outs2, ORG_IMGwAxiVdma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
        median_axis_RGB24_soft(ins_soft2, outs_soft2, ORG_IMGwAxiVdma, img.rows, img.cols);  // ソフトウェアのメディアンフィルタ
    #else
        median_axis_RGB24(ins2, outs2, ORG_IMGwAxiDma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
        median_axis_RGB24_soft(ins_soft2, outs_soft2, ORG_IMGwAxiDma, img.rows, img.cols);  // ソフトウェアのメディアンフィルタ
    #endif

    // ハードウェアとソフトウェアのメディアンフィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs2 >> vals;
            outs_soft2 >> vals_soft;
            ap_uint<32> val = vals.data;
            hw_median[y*img.cols+x] = (int32_t)val;
            if (val != vals_soft.data){
                printf("ERROR ORG: HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, val, vals_soft.data);
                return(1);
            }
        }
    }
    printf("ORG: Success HW and SW results match\n");

    cv::Mat wbmpf2(median_row, median_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    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 rbg = hw_median[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を jpg ファイルへ出力する
    cv::imwrite(ORG_IMAGE_FILE, wbmpf2);

    return(0);
}

int median_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size){

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> median;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];

    ap_int<32> pix_mat[size][size];

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

    for(int y=0; y<row_size; y++){
        for(int x=0; x<col_size; x++){
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            median_fil_soft(pix_mat, val);
            median.data = val;
            if(x<2 || y<2)
                median.data = 0;

            if(function==ORG_IMGwAxiVdma || function==MEDIANwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    median.user = 1;
                else
                    median.user = 0;
                if(x == (col_size-1)) // 行の最後
                    median.last = 1;
                else
                    median.last = 0;
            }else{
                median.user = 0;
                median.last = pix.last;
            }
            median.keep = 0x7;
            median.strb = 0x7;
            if(function==MEDIANwAxiDma || function==MEDIANwAxiVdma)
                outs << median;
            else
                outs << pix;
        }
    }
    return(0);
}

// median filter
//
// x0y0 x1y0 x2y0
// x0y1 x1y1 x2y1
// x0y2 x1y2 x2y2
//
void median_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result){
    pix_st pixst[size*size];
    uint32_t index;

    for(int i=0; i<size*size; i++){
        pixst[i].pix = (uint32_t)conv_rbg2y_soft(pix_mat[i/size][i%size]);
        pixst[i].index = i;
    }

    pixel_sort_soft(pixst);
    
    index = pixst[4].index; // 中央値
    result = (ap_uint<24>)pix_mat[index/size][index%size];
}

// pixel_sort()
// bubble sort
// ”メジアン(中央値)、範囲(レンジ)、ヒストグラムを求める”参照
// https://cgengo.sakura.ne.jp/arg04.html
void pixel_sort_soft(pix_st *y){
    pix_st tmp;

    for(int i=1; i<size*size; i++){
        for(int j=0; j<size*size-i; j++){
            if(y[j].pix < y[j+1].pix){
                tmp = y[j];
                y[j] = y[j+1];
                y[j+1] = tmp;
            }
        }
    }
}

// RBGからYへの変換
// RBGのフォーマットは、{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_uint<32> conv_rbg2y_soft(ap_uint<32> rbg){
    ap_uint<32> r, g, b, y_f;
    ap_uint<32> y;

    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>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);
}

  1. 2023年11月22日 05:09 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタの min_max_axis_RGB24 を実装する3

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタの min_max_axis_RGB24 を実装する2”の続き。

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタを作成するということで、前回は、C シミュレーションと C コードの合成を行った。今回は C/RTL 協調シミュレーション、Package、Implementation を行った。

設定を行ってから、C/RTL 協調シミュレーションを行った。
VITIS COMPONETENTS -> min_max_axis_RGB24 -> Settings -> hls_config.cfg をクリックして、設定画面を表示した。
C/RTL Cosimulation の ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
trace_levelport にした。

FLOW -> C/RTL COSIMULATION -> Run をクリックして、C/RTL 協調シミュレーションを行ったところ、初めて成功した。
zub1cg_i7filters_58_231120.png

左の FLOW ウインドウの C/RTL COSIMULATION -> REPORTS の内容を見ていこう。
Summary をクリックした。
zub1cg_i7filters_59_231120.png

Cosimulation をクリックした。
レイテンシは 480021 クロックだった。問題無さそうだ。
zub1cg_i7filters_60_231120.png

Timing Trace をクリックした。
zub1cg_i7filters_61_231120.png

Wave Viewer をクリックした。
Vivado が起動して、波形を表示した。全体波形を示す。
zub1cg_i7filters_62_231120.png

Function Call Graph をクリックして、表示した。
zub1cg_i7filters_63_231120.png

Package を行う。
FLOW -> PACKAGE -> Run をクリックして、Package を行って成功した。
Summary をクリックした。
zub1cg_i7filters_64_231120.png

min_max_axis_RGB24/min_max_axis_RGB24/hls/impl/ip が生成されて、その中に ZIP ファイルとして、xilinx_com_hls_mini_max_axis_RGB24_1_0.zip が生成された。
zub1cg_i7filters_67_231120.png

Implementation を行う。
FLOW -> IMPLEMENTATION -> Run をクリックした。
Implementation が終了し、成功した。
Summary をクリックした。
zub1cg_i7filters_65_231120.png

Place and Route をクリックした。
CP achieved post-implementation も 7.232 ns で問題無さそうだ。
zub1cg_i7filters_66_231120.png
  1. 2023年11月21日 04:44 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタの min_max_axis_RGB24 を実装する2

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタの min_max_axis_RGB24 を実装する1”の続き。

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタを作成するということで、前回は、Vitis Unified IDE 2023.2 で min_max_axis_RGB24 プロジェクトを作成し、ソースコードとテストベンチを示した。今回は、C シミュレーションと C コードの合成を行った。

FLOW の C SIMULATION -> Run をクリックすると C Simulation が実行され、成功した。
”MIN: Success HW and SW results match”が表示されたが、表示されるはずの”"MAX: Success HW and SW results match”が表示されない。しかし、max.jpg ファイルは出力されているので、ソフトウェアは走ってはいるようだ。
zub1cg_i7filters_47_231119.png

min_max_axis_RGB24 プロジェクトのディレクトリの下に min_max_axis_RGB24 ディレクトリが生成され、その下の hls ディレクトリの下に csim ディレクトリが生成されている。
csim/build ディレクトリを見ると、min.jpg と max.jpg が生成されていた。
zub1cg_i7filters_54_231119.png

元画像の test2.jpg を示す。
zub1cg_i7filters_55_231119.jpg

最小値フィルタを掛けた、min.jpg を示す。
zub1cg_i7filters_56_231119.jpg

最大値フィルタを掛けた max.jpg を示す。
zub1cg_i7filters_57_231119.jpg

FLOW の C SYNTHESIS -> Run をクリックして、C コードの合成を行って成功した。
FLOW の C SYNTHESIS -> REPORTS -> Summary を表示した。
zub1cg_i7filters_48_231119.png

FLOW の C SYNTHESIS -> REPORTS -> Synthesis を表示した。
zub1cg_i7filters_49_231119.png
zub1cg_i7filters_50_231119.png

LOOP_Y_LOOP_X の INTERVAL は 1 クロックで、スループットは出ているので、問題無さそうだ。

FLOW の C SYNTHESIS -> REPORTS -> Function Call Graph をクリックして、表示した。
zub1cg_i7filters_51_231119.png

FLOW の C SYNTHESIS -> REPORTS -> Schedule Viewer を表示した。
zub1cg_i7filters_52_231119.png

FLOW の C SYNTHESIS -> REPORTS -> Kernel Guidance を表示した。チェックボックスにチェックを入れてある。
zub1cg_i7filters_53_231119.png
  1. 2023年11月20日 03:24 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタの min_max_axis_RGB24 を実装する1

Vitis Unified IDE 2023.2 で最小値フィルタ、最大値フィルタの min_max_axis_RGB24 を実装してみよう。
最小値フィルタ、最大値フィルタについては、”画像解析におけるフィルタ処理 - ノイズ軽減フィルタのまとめ”を参照した。

最小値フィルタ、最大値フィルタは、3 x 3 の画素で最小値のピクセルと最大値のピクセルを選択するフィルタのようだ。これは、3 x 3 の画像で中央値を求めるメディアン・フィルタと同様の構造にすれば良いということだ。(メディアン・フィルタに付いては、RGB それぞれについて 3 x 3 領域での中央値を求めてしまったので、後でソースコードを修正する)

Vitis Unified IDE 2023.2 で min_max_axis_RGB24 プロジェクトを作成する。

Vitis Unified IDE 2023.2 で File メニューから New Component -> HLS を選択して、ZUBoard 1CG 用の min_max_axis_RGB24 プロジェクトを作成した。

ソースコード、テストベンチのファイルは min_max_axis_RGB24 プロジェクトのファイルを追加した。

最初に、VITIS COMPONENT -> min_max_axis_RGB24 -> Settings -> hls_config.cfg をクリックし、設定を行った。
C Sythesis sources の top に min_max_axis_RGB24 を設定した。これは、C コードの合成時にハードウェアとする関数を指定する。
Testbench sourcesCFLAGS-I/usr/local/include を指定した。
C Simulation ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
zub1cg_i7filters_45_231119.png

ソースコードに min_max_axis_RGB24.h と min_max_axis_RGB24.cpp を追加した。
テストベンチに min_max_axis_RGB24_tb.cpp とノイズ入りの画像 test2.jpg を追加した。
zub1cg_i7filters_46_231119.png

ヘッダ・ファイルの min_max_axis_RGB24.h を貼っておく。
値を切り替えることで、最小値フィルタと最大値フィルタを切り替える。

// min_max_axis_RGB24.h
// 2023/11/19 by marsee
//

#ifndef __MIN_MAX_AXIS_RGB24_H__
#define __MIN_MAX_AXIS_RGB24_H__

#include <stdint.h>

#define ORG_IMGwAxiVdma 0
#define MINwAxiVdma     1
#define MAXwAxiVdma     2
#define ORG_IMGwAxiDma  3
#define MINwAxiDma      4
#define MAXwAxiDma      5

struct pix_st{
    uint32_t pix;
    uint32_t index;
};

#endif


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

// min_max_axis_RGB24.cpp
// 2023/11/19 by marsee
//

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

#include "min_max_axis_RGB24.h"

constexpr int size = 3;

void min_max_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result,
    int32_t function);
void pixel_sort(pix_st *y);
ap_uint<32> conv_rbg2y(ap_uint<32> rbg);

int min_max_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size){
#pragma HLS INTERFACE mode=s_axilite port=col_size
#pragma HLS INTERFACE mode=s_axilite port=row_size
#pragma HLS INTERFACE mode=s_axilite port=function
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register
#pragma HLS INTERFACE mode=s_axilite port=return

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> min_max;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1

    ap_int<32> pix_mat[size][size];
#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;
        if(function==ORG_IMGwAxiDma || function==MINwAxiDma || function==MAXwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
#pragma HLS PIPELINE II=1
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            min_max_fil(pix_mat, val, function);
            min_max.data = val;
            if(x<2 || y<2)
                min_max.data = 0;

            if(function==ORG_IMGwAxiVdma || function==MINwAxiVdma || function==MAXwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    min_max.user = 1;
                else
                    min_max.user = 0;
                if(x == (col_size-1)) // 行の最後
                    min_max.last = 1;
                else
                    min_max.last = 0;
            }else{
                min_max.user = 0;
                min_max.last = pix.last;
            }
            min_max.keep = 0x7;
            min_max.strb = 0x7;
            if(function==MINwAxiDma || function==MAXwAxiDma || 
                function==MINwAxiVdma || function==MAXwAxiVdma)
                outs << min_max;
            else
                outs << pix;
        }
    }
    return(0);
}

// min_max filter
//
// x0y0 x1y0 x2y0
// x0y1 x1y1 x2y1
// x0y2 x1y2 x2y2
//
void min_max_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result,
    int32_t function){
    pix_st pixst[size*size];
    uint32_t index;

    for(int i=0; i<size*size; i++){
        pixst[i].pix = (uint32_t)conv_rbg2y(pix_mat[i/size][i%size]);
        pixst[i].index = i;
    }

    pixel_sort(pixst);
    
    if(function == MINwAxiVdma || function == MINwAxiDma)
        index = pixst[size*size-1].index;
    else // function == MAXwAxiVdma || function == MAXwAxiDma
        index = pixst[0].index;
        
    result = (ap_uint<24>)pix_mat[index/size][index%size];
}

// pixel_sort()
// bubble sort
// ”メジアン(中央値)、範囲(レンジ)、ヒストグラムを求める”参照
// https://cgengo.sakura.ne.jp/arg04.html
void pixel_sort(pix_st *y){
#pragma HLS ARRAY_PARTITION variable=y dim=1 complete
    pix_st tmp;

    for(int i=1; i<size*size; i++){
        for(int j=0; j<size*size-i; j++){
            if(y[j].pix < y[j+1].pix){
                tmp = y[j];
                y[j] = y[j+1];
                y[j+1] = tmp;
            }
        }
    }
}

// RBGからYへの変換
// RBGのフォーマットは、{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_uint<32> conv_rbg2y(ap_uint<32> rbg){
    ap_uint<32> r, g, b, y_f;
    ap_uint<32> y;

    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>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);
}


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

// min_max_axis_RGB24_tb.cpp
// 2023/11/19 by marsee
// MIN_MAXwXilinxVideoStandard を define すると axi_vdma 用となり、コメントアウトすると axi_dma 用になる
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "min_max_axis_RGB24.h"

//#define MIN_MAXwXilinxVideoStandard
//#define ORGINAL_IMAGE_TEST

constexpr int size = 3;

int min_max_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size);
int min_max_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size);
void min_max_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result,
    int32_t function);
void pixel_sort_soft(pix_st *y);
ap_uint<32> conv_rbg2y_soft(ap_uint<32> rbg);

const char INPUT_JPG_FILE[] = "test2.jpg";
const char MIN_JPG_FILE[] = "min.jpg";
const char MAX_JPG_FILE[] = "max.jpg";

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins, ins2;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft, ins_soft2;
    hls::stream<ap_axiu<24,1,1,1> > outs, outs2;
    hls::stream<ap_axiu<24,1,1,1> > outs_soft, outs_soft2;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals, vals_soft;

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

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

    // rd_bmp にJPGのピクセルを代入
    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); // RGB 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

#ifdef MIN_MAXwXilinxVideoStandard
    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        pix.last = 0;
        pix.user = 0;
        pix.keep = 0x7;
        pix.strb = 0x7;
        ins << pix;
    }
#endif

    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (int32_t)rd_bmp[(j*img.cols)+i];
#ifdef MIN_MAXwXilinxVideoStandard
            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;
#else
            if(j==img.rows-1 && i==img.cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            pix.user = 0;
#endif
            pix.keep = 0x7;
            pix.strb = 0x7;

            ins << pix;
            ins2 << pix;
            ins_soft << pix;
            ins_soft2 << pix;
        }
    }

// MIN
#ifdef MIN_MAXwXilinxVideoStandard
    min_max_axis_RGB24(ins, outs, MINwAxiVdma, img.rows, img.cols); // ハードウェアの最小値、最大値フィルタ
    min_max_axis_RGB24_soft(ins_soft, outs_soft, MINwAxiVdma, img.rows, img.cols);  // ソフトウェアの最小値、最大値フィルタ
#else
    min_max_axis_RGB24(ins, outs, MINwAxiDma, img.rows, img.cols); // ハードウェアの最小値、最大値フィルタ
    min_max_axis_RGB24_soft(ins_soft, outs_soft, MINwAxiDma, img.rows, img.cols);  // ソフトウェアの最小値、最大値フィルタ
#endif

    // ハードウェアとソフトウェアの最小値、最大値フィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_uint<32> val = vals.data;
            hw_min_max[y*img.cols+x] = (int32_t)val;
            if (val != vals_soft.data){
                printf("ERROR MIN: HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, val, vals_soft.data);
                return(1);
            }
        }
    }
    printf("MIN: Success HW and SW results match\n");

    const int min_max_row = img.rows;
    const int min_max_cols = img.cols;
    cv::Mat wbmpf(min_max_row, min_max_cols, CV_8UC3);
    // wbmpf にmin_max フィルタ処理後の画像を入力
    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 rbg = hw_min_max[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアの最小値、最大値フィルタの結果を jpg ファイルへ出力する
    cv::imwrite(MIN_JPG_FILE, wbmpf);

// MAX
#ifndef ORGINAL_IMAGE_TEST
    #ifdef MIN_MAXwXilinxVideoStandard
        min_max_axis_RGB24(ins2, outs2, MAXwAxiVdma, img.rows, img.cols); // ハードウェアの最小値、最大値フィルタ
        min_max_axis_RGB24_soft(ins_soft2, outs_soft2, MAXwAxiVdma, img.rows, img.cols);  // ソフトウェアの最小値、最大値フィルタ
    #else
        min_max_axis_RGB24(ins2, outs2, MAXwAxiDma, img.rows, img.cols); // ハードウェアの最小値、最大値フィルタ
        min_max_axis_RGB24_soft(ins_soft2, outs_soft2, MAXwAxiDma, img.rows, img.cols);  // ソフトウェアの最小値、最大値フィルタ
    #endif
#else
    // オリジナル画像テスト
    #ifdef MIN_MAXwXilinxVideoStandard
        min_max_axis_RGB24(ins2, outs2, ORG_IMGwAxiVdma, img.rows, img.cols); // ハードウェアの最小値、最大値フィルタ
        min_max_axis_RGB24_soft(ins_soft2, outs_soft2, ORG_IMGwAxiVdma, img.rows, img.cols);  // ソフトウェアの最小値、最大値フィルタ
    #else
        min_max_axis_RGB24(ins2, outs2, ORG_IMGwAxiDma, img.rows, img.cols); // ハードウェアの最小値、最大値フィルタ
        min_max_axis_RGB24_soft(ins_soft2, outs_soft2, ORG_IMGwAxiDma, img.rows, img.cols);  // ソフトウェアの最小値、最大値フィルタ
    #endif
#endif

    // ハードウェアとソフトウェアの最小値、最大値フィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs2 >> vals;
            outs_soft2 >> vals_soft;
            ap_uint<32> val = vals.data;
            hw_min_max[y*img.cols+x] = (int32_t)val;
            if (val != vals_soft.data){
                printf("ERROR MAX: HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, val, vals_soft.data);
                return(1);
            }
        }
    }
    printf("MAX: Success HW and SW results match\n");

    cv::Mat wbmpf2(min_max_row, min_max_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    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 rbg = hw_min_max[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を jpg ファイルへ出力する
    cv::imwrite(MAX_JPG_FILE, wbmpf2);

    return(0);
}

int min_max_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size){

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> min_max;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];

    ap_int<32> pix_mat[size][size];

    LOOP_WAIT_USER : do {   // user が 1になった時にフレームがスタートする
        ins >> pix;
        if(function==ORG_IMGwAxiDma || function==MINwAxiDma || function==MAXwAxiDma)
            break;
    } while(pix.user == 0);

    for(int y=0; y<row_size; y++){
        for(int x=0; x<col_size; x++){
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            min_max_fil_soft(pix_mat, val, function);
            min_max.data = val;
            if(x<2 || y<2)
                min_max.data = 0;

            if(function==ORG_IMGwAxiVdma || function==MINwAxiVdma || function==MAXwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    min_max.user = 1;
                else
                    min_max.user = 0;
                if(x == (col_size-1)) // 行の最後
                    min_max.last = 1;
                else
                    min_max.last = 0;
            }else{
                min_max.user = 0;
                min_max.last = pix.last;
            }
            min_max.keep = 0x7;
            min_max.strb = 0x7;
            if(function==MINwAxiDma || function==MAXwAxiDma || 
                function==MINwAxiVdma || function==MAXwAxiVdma)
                outs << min_max;
            else
                outs << pix;
        }
    }
    return(0);
}

// min_max filter
//
// x0y0 x1y0 x2y0
// x0y1 x1y1 x2y1
// x0y2 x1y2 x2y2
//
void min_max_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result,
    int32_t function){
    pix_st pixst[size*size];
    uint32_t index;

    for(int i=0; i<size*size; i++){
        pixst[i].pix = (uint32_t)conv_rbg2y_soft(pix_mat[i/size][i%size]);
        pixst[i].index = i;
    }

    pixel_sort_soft(pixst);
    
    if(function == MINwAxiVdma || function == MINwAxiDma)
        index = pixst[size*size-1].index;
    else // function == MAXwAxiVdma || function == MAXwAxiDma
        index = pixst[0].index;
        
    result = (ap_uint<24>)pix_mat[index/size][index%size];
}

// pixel_sort()
// bubble sort
// ”メジアン(中央値)、範囲(レンジ)、ヒストグラムを求める”参照
// https://cgengo.sakura.ne.jp/arg04.html
void pixel_sort_soft(pix_st *y){
    pix_st tmp;

    for(int i=1; i<size*size; i++){
        for(int j=0; j<size*size-i; j++){
            if(y[j].pix < y[j+1].pix){
                tmp = y[j];
                y[j] = y[j+1];
                y[j+1] = tmp;
            }
        }
    }
}

// RBGからYへの変換
// RBGのフォーマットは、{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_uint<32> conv_rbg2y_soft(ap_uint<32> rbg){
    ap_uint<32> r, g, b, y_f;
    ap_uint<32> y;

    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>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);
}


  1. 2023年11月19日 14:39 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 で平均化フィルタの average_axis_RGB24 を実装する4

Vitis Unified IDE 2023.2 で平均化フィルタの average_axis_RGB24 を実装する3”の続き。

Vitis Unified IDE 2023.2 で画像フィルタの一種である平均化フィルタを実装するということで、前回は、C/RTL 協調シミュレーションを行ったところ、エラーになった。Package と Implementation を行った。今回は、C/RTL 協調シミュレーションするために HLS プロジェクトを新規作成して、C/RTL Cosimulation を行ったところ成功した。

C/RTL Cosimulation の使用するファイルのデータベースが更新されないようだった。仕方ないので、C/RTL 協調シミュレーションをするために、新しく HLS の average_axis_RGB24_2 プロジェクトを作成する。

Vitis Unified IDE 2023.2 で File メニューから New Component -> HLS を選択して、ZUBoard 1CG 用の average_axis_RGB24_2 プロジェクトを作成した。

ソースコード、テストベンチのファイルは average_axis_RGB24 プロジェクトのファイルを追加した。

最初に、VITIS COMPONENT -> average_axis_RGB24_2 -> Settings -> hls_config.cfg をクリックし、設定を行った。
C Sythesis sources の top に average_axis_RGB24 を設定した。これは、C コードの合成時にハードウェアとする関数を指定する。
Testbench sources の CFLAGS に -I/usr/local/include を指定した。
C Simulation の ldflags に -L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。

C/RTL Cosimulation の ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
trace_levelport にした。

C/RTL 協調シミュレーションを行うには、C コードを合成する必要があるので、C コードの合成を行った。
FLOW の C SYNTHESIS -> Run をクリックして、C コードの合成を行って成功した。
zub1cg_i7filters_35_231116.png

FLOW -> C/RTL COSIMULATION -> Run をクリックして、C/RTL 協調シミュレーションを行ったが、2時間経っても終了しない。
zub1cg_i7filters_36_231116.png

一旦、C/RTL Cosimulation を止めた。
もう一度、VITIS COMPONENT -> average_axis_RGB24_2 -> Settings -> hls_config.cfg をクリックし、wave_debug にチェックを入れた。
zub1cg_i7filters_37_231116.png

もう一度、FLOW -> C/RTL COSIMULATION -> Run をクリックして、C/RTL Cosimulationを行った。
C/RTL Cosimulation を行うと、Vivado が起動して、RTL シミュレーションが行われた。
zub1cg_i7filters_38_231116.png

波形を見ることができた。
zub1cg_i7filters_39_231116.png

TVALID も TREADY もほぼ 1 で問題無さそうだ。

Vivado を終了すると、C/RTL Cosimulation が終了した。
zub1cg_i7filters_40_231116.png

左の FLOW ウインドウの C/RTL COSIMULATION -> REPORTS の内容を見ていこう。
Summary をクリックした。
zub1cg_i7filters_41_231116.png

Cosimulation をクリックした。ここでは、Cosimulation の結果が表示された。
C/RTL Cosimulation のレイテンシは 480045 クロックだった。
zub1cg_i7filters_42_231116.png

Timing Trace をクリックした。
zub1cg_i7filters_43_231116.png

Function Call Graph をクリックして、表示した。
zub1cg_i7filters_44_231116.png
  1. 2023年11月17日 04:35 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 で平均化フィルタの average_axis_RGB24 を実装する3

Vitis Unified IDE 2023.2 で平均化フィルタの average_axis_RGB24 を実装する2”の続き。

Vitis Unified IDE 2023.2 で画像フィルタの一種である平均化フィルタを実装するということで、前回は、C シミュレーションと C コードの合成を行った。今回は、C/RTL 協調シミュレーションを行ったところ、エラーになった。Package と Implementation を行った。

C/RTL 協調シミュレーションを行う。
左の VITIS COMPONENT ウインドウの HSVConverter -> Settings -> hls_config.cfg をクリックし、C/RTL Cosimulation の ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
zub1cg_i7filters_26_231116.png

trace_levelport にした。
FLOW -> C/RTL COSIMULATION -> Run をクリックして、C/RTL 協調シミュレーションを行った。
zub1cg_i7filters_27_231116.png

エラーで終了した。
エラーの内容は、

[ERROR] ERROR: [COSIM 212-41] ../../average_axis_RGB24_tbcpp is not found.

だったので、テストベンチのファイル名が違っているということだが、間違っていない。
最初にテストベンチのファイルを作るときに、間違って average_axis_RGB24_tbcpp というファイル名でテストベンチ・ファイルを作成してしまったので、それがデータベースに残っているのかもしれないな?
でも、C シミュレーションは通ったのに、C/RTL 協調シミュレーションでエラーとは?データベースが違うのだろうか?
zub1cg_i7filters_34_231116.png

Package を行う。
output.format はデフォルトで Generate Vivado IP and .zip archive に設定されていた。
FLOW -> PACKAGE -> Run をクリックして、Package を行って成功した。
zub1cg_i7filters_28_231116.png

average_axis_RGB24/average_axis_RGB24/hls/impl/ip が生成されて、その中に ZIP ファイルとして、xilinx_com_hls_average_axis_RGB24_1_0.zip が生成された。
zub1cg_i7filters_33_231116.png

Implementation を行う。
hls_config.cfg をクリックし、Implementation の flow はデフォルトで Run full implementation (RTL synthesis, place and routte) に設定されていた。
FLOW -> IMPLEMENTATION -> Run をクリックした。
zub1cg_i7filters_29_231116.png

Implementation が終了し、成功した。
zub1cg_i7filters_30_231116.png

FLOW -> IMPLEMENTATION -> Summary をクリックした。
zub1cg_i7filters_31_231116.png

FLOW -> IMPLEMENTATION -> Place and Route をクリックした。
zub1cg_i7filters_32_231116.png

CP achieved post-implementation の値は、5.704 ns で問題ない。
  1. 2023年11月16日 05:07 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 で平均化フィルタの average_axis_RGB24 を実装する2

Vitis Unified IDE 2023.2 で平均化フィルタの average_axis_RGB24 を実装する1”の続き。

Vitis Unified IDE 2023.2 で画像フィルタの一種である平均化フィルタを実装するということで、前回は、Vitis Unified IDE 2023.2 で ZUBoard 1CG 用の average_axis_RGB24 プロジェクトを作成し、OpenCV を使用できる設定を行った。また、ヘッダ・ファイル、ソースコード、テストベンチのコードを示した。今回は、C シミュレーションと C コードの合成を行った。

FLOW の C SIMULATION -> Run をクリックすると C Simulation が実行され、成功した。
”Success HW and SW results match”が表示された。
zub1cg_i7filters_14_231115.png

Summary を表示した。
zub1cg_i7filters_15_231115.png

average_axis_RGB24 プロジェクトのディレクトリの下に average_axis_RGB24 ディレクトリが生成され、その下の hls ディレクトリの下に csim ディレクトリが生成されている。
csim/build ディレクトリを見ると、org.jpg と average.jpg が生成されていた。
zub1cg_i7filters_16_231115.png

平均化フィルタ後の結果の average.jpg を示す。
zub1cg_i7filters_17_231115.jpg

ガウシアン・フィルタ結果の gaussian.jpg と比較してみたが、このサンプルでは大きな差は無いようだ。
zub1cg_i7filters_18_231115.jpg

FLOW の C SYNTHESIS -> Run をクリックして、C コードの合成を行って成功した。
zub1cg_i7filters_19_231115.png

FLOW の C SYNTHESIS -> REPORTS -> Summary を表示した。
zub1cg_i7filters_20_231115.png

FLOW の C SYNTHESIS -> REPORTS -> Synthesis を表示した。
zub1cg_i7filters_21_231115.png
zub1cg_i7filters_22_231115.png
以下省略

FLOW の C SYNTHESIS -> REPORTS -> Function Call Graph をクリックして、表示した。”Vitis Unified IDE 2023.2 の HLS Development を試す4”で Vitis HLS のフリー・ライセンスを取得済みなので、表示された。
zub1cg_i7filters_23_231115.png

FLOW の C SYNTHESIS -> REPORTS -> Schedule Viewer を表示した。
zub1cg_i7filters_24_231115.png

FLOW の C SYNTHESIS -> REPORTS -> Kernel Guidance を表示した。チェックボックスにチェックを入れてある。
zub1cg_i7filters_25_231115.png
  1. 2023年11月15日 04:33 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 で平均化フィルタの average_axis_RGB24 を実装する1

Vitis Unified IDE 2023.2 で画像フィルタの一種である平均化フィルタを実装してみよう。使用する FPGA ボードは AVNET 社の ZUBoard 1CG とする。

前に Vitis Unified IDE 2023.2 で HLS Development の HSVConverter プロジェクトを作成したので、同じワークスペースに HLS の average_axis_RGB24 プロジェクトを作成する。

Vitis Unified IDE 2023.2 で File メニューから New Component -> HLS を選択した。
zub1cg_i7filters_1_231113.jpg

Create HLS Component - Empty HLS Component ダイアログが表示された。
Name and Location 画面で、Component name に average_axis_RGB24 を入力し、Component location を入力した。
zub1cg_i7filters_2_231113.png

Configuration File 画面
デフォルトのままとした。
zub1cg_i7filters_3_231113.png

Source Files 画面
ここもデフォルトのままとした。
zub1cg_i7filters_4_231113.png

Hardware 画面
Part で xzu1cg-sva484-1-e を選択した。
zub1cg_i7filters_5_231113.png

Settings 画面
clock に 10ns を入力した。
zub1cg_i7filters_6_231113.png

Summary 画面
Finish ボタンをクリックした。
zub1cg_i7filters_7_231113.png

average_axis_RGB24 プロジェクトが作成された。
zub1cg_i7filters_8_231113.png

Welcome 画面から HLS のプロジェクトを作成した”Vitis Unified IDE 2023.2 の HLS Development を試す1”と違って、File メニューから New Component -> HLS を選択すると、Settings 画面で clock_uncertainty に入力しなくてもプロジェクトが作れてしまった? この辺りは再現性があるとするとバグなのかもしれない?

average_axis_RGB24.h とソースコードの average_axis_RGB24.cpp、テストベンチの average_axis_RGB24_tb.cpp を作成しプロジェクトに追加した。
ガウシアン・フィルタのプロジェクトからノイズが混じった test2.jpg を average_axis_RGB24 の Test Bench に追加した。
なお、すでに C シミュレーションと C コードの合成は通してある状態だ。
zub1cg_i7filters_12_231113.png

hls_config.cfg を表示して、設定を行う。
今回のプロジェクトでは OpenCV を使用する。

Vitis Unified IDE 2023.2 で、左の VITIS COMPONENTS の HSVConverter -> Settings -> hls_config.cfg をクリックした。
C Sythesis sources の top に average_axis_RGB24 を設定した。これは、C コードの合成時にハードウェアとする関数を指定する。
zub1cg_i7filters_9_231113.png

Testbench sources の CFLAGS に -I/usr/local/include を指定した。
zub1cg_i7filters_10_231113.png

C Simulation の ldflags に -L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定した。
zub1cg_i7filters_11_231113.png

average_axis_RGB24.h を貼っておく。

// average_axis_RGB24.h
// 2023/11/13 by marsee
//

#ifndef __AVERAGE_FILTER_AXIS_RGB24_H__
#define __AVERAGE_FILTER_AXIS_RGB24_H__

#define HORIZONTAL 0
#define VERTICAL 1

#define ORG_IMGwAxiVdma 0
#define AVERAGEwAxiVdma   1
#define ORG_IMGwAxiDma  2
#define AVERAGEwAxiDma    3

#endif


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

// average_axis_RGB24.cpp
// 2023/11/13 by marsee
//

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

#include "average_axis_RGB24.h"

constexpr int size = 3;

void average_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result);
ap_int<32> average_fil_calc(ap_int<32> *pixd);
ap_int<32> separate_rgb(ap_int<32> rgb, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b);

int average_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size){
#pragma HLS INTERFACE mode=s_axilite port=col_size
#pragma HLS INTERFACE mode=s_axilite port=row_size
#pragma HLS INTERFACE mode=s_axilite port=function
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register
#pragma HLS INTERFACE mode=s_axilite port=return

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> average;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1

    ap_int<32> pix_mat[size][size];
#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;
        if(function==ORG_IMGwAxiDma || function==AVERAGEwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
#pragma HLS PIPELINE II=1
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            average_fil(pix_mat, val);
            average.data = val;
            if(x<2 || y<2)
                average.data = 0;

            if(function==ORG_IMGwAxiVdma || function == AVERAGEwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    average.user = 1;
                else
                    average.user = 0;
                if(x == (col_size-1)) // 行の最後
                    average.last = 1;
                else
                    average.last = 0;
            }else{
                average.user = 0;
                average.last = pix.last;
            }
            average.keep = 0x7;
            average.strb = 0x7;
            if(function==AVERAGEwAxiVdma || function==AVERAGEwAxiDma)
                outs << average;
            else
                outs << pix;
        }
    }
    return(0);
}

// average filter
//
// x0y0/9 + x1y0/9 + x2y0/9 + x0y1/9 + x1y1/9 + x2y1/9 + x0y2/9 + x1y2/9 + x2/y2/9
//
void average_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result){
    ap_int<32> pix_1d_r[9], pix_1d_b[9], pix_1d_g[9];
    ap_int<32> y_r, y_b, y_g, y;

    for(int i=0; i<9; i++){
        separate_rgb(pix_mat[i/3][i%3], pix_1d_r[i], pix_1d_g[i], pix_1d_b[i]);
    }

    y_r = average_fil_calc(pix_1d_r);
    y_b = average_fil_calc(pix_1d_b);
    y_g = average_fil_calc(pix_1d_g);

    result = (y_r << 16) + (y_g << 8) + y_b;
}

// average_fil_calc
ap_int<32> average_fil_calc(ap_int<32> *pixd){
    ap_int<32> y;
    ap_fixed<24, 16, AP_TRN_ZERO, AP_SAT> pixdf[9];

    for(int i=0; i<9; i++){
        pixdf[i] = (ap_fixed<24, 16, AP_TRN_ZERO, AP_SAT>)pixd[i];
    }
    y = (ap_int<32>)(pixd[0]/9 + pixd[1]/9 + pixd[2]/9 + pixd[3]/9 + pixd[4]/9 + 
        pixd[5]/9 + pixd[6]/9 + pixd[7]/9 + pixd[8]/9 +
        (ap_fixed<24, 16, AP_TRN_ZERO, AP_SAT>)0.5);

    if(y<0)
        y = -y;
        //y = 0;
    else if(y>255) // 8 bits
        y = 255;
    return(y);
}

// separate_rgb
// RGBを分離する
// RBGのフォーマットは、{R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
//
ap_int<32> separate_rgb(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b){
    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>16) & 0xff;
    return(0);
}


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

// average_axis_RGB24_tb.cpp
// 2023/11/14 by marsee
// AVERAGEwXilinxVideoStandard を define すると axi_vdma 用となり、コメントアウトすると axi_dma 用になる
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "average_axis_RGB24.h"

//#define AVERAGEwXilinxVideoStandard

constexpr int size = 3;

int average_axis_RGB24(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size);
int average_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size);
void average_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result);
ap_int<32> average_fil_calc_soft(ap_int<32> *pixd);
ap_int<32> separate_rgb_soft(ap_int<32> rgb, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b);

const char INPUT_JPG_FILE[] = "test2.jpg";
const char OUTPUT_JPG_FILE[] = "average.jpg";
const char ORG_OUT_JPG_FILE[] = "org.jpg";

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins, ins2;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft;
    hls::stream<ap_axiu<24,1,1,1> > outs, outs2;
    hls::stream<ap_axiu<24,1,1,1> > outs_soft;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals, vals_soft;

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

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

    // rd_bmp にJPGのピクセルを代入
    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); // RGB 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

#ifdef AVERAGEwXilinxVideoStandard
    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        pix.last = 0;
        pix.user = 0;
        pix.keep = 0x7;
        pix.strb = 0x7;
        ins << pix;
    }
#endif

    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (int32_t)rd_bmp[(j*img.cols)+i];
#ifdef AVERAGEwXilinxVideoStandard
            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;
#else
            if(j==img.rows-1 && i==img.cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            pix.user = 0;
#endif
            pix.keep = 0x7;
            pix.strb = 0x7;

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

#ifdef AVERAGEwXilinxVideoStandard
    average_axis_RGB24(ins, outs, AVERAGEwAxiVdma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
    average_axis_RGB24_soft(ins_soft, outs_soft, AVERAGEwAxiVdma, img.rows, img.cols);  // ソフトウェアのメディアンフィルタ
#else
    average_axis_RGB24(ins, outs, AVERAGEwAxiDma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
    average_axis_RGB24_soft(ins_soft, outs_soft, AVERAGEwAxiDma, img.rows, img.cols);  // ソフトウェアのメディアンフィルタ
#endif

    // ハードウェアとソフトウェアのメディアンフィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_uint<32> val = vals.data;
            hw_average[y*img.cols+x] = (int32_t)val;
            if (val != vals_soft.data){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, val, vals_soft.data);
                return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    const int average_row = img.rows;
    const int average_cols = img.cols;
    cv::Mat wbmpf(average_row, average_cols, CV_8UC3);
    // wbmpf にaverage フィルタ処理後の画像を入力
    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 rbg = hw_average[y*wbmpf.cols+x];
            pixel[0] = (rbg & 0xff); // blue
            pixel[1] = ((rbg >> 8) & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのメディアンフィルタの結果を jpg ファイルへ出力する
    cv::imwrite(OUTPUT_JPG_FILE, wbmpf);

#ifdef AVERAGEwXilinxVideoStandard
    average_axis_RGB24(ins2, outs2, ORG_IMGwAxiVdma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
#else
    average_axis_RGB24(ins2, outs2, ORG_IMGwAxiDma, img.rows, img.cols); // ハードウェアのメディアンフィルタ
#endif

    cv::Mat wbmpf2(average_row, average_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            outs2 >> vals;
            int32_t val = vals.data;
            pixel[0] = (val & 0xff); // blue
            pixel[1] = ((val >> 8) & 0xff); // green
            pixel[2] = ((val >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を jpg ファイルへ出力する
    cv::imwrite(ORG_OUT_JPG_FILE, wbmpf2);

    return(0);
}

int average_axis_RGB24_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int32_t function,
         int32_t row_size, int32_t col_size){

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> average;
    ap_uint<24> val;

    ap_int<32> line_buf[2][1920];

    ap_int<32> pix_mat[size][size];

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

    for(int y=0; y<row_size; y++){
        for(int x=0; x<col_size; x++){
            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 = pix.data;
            pix_mat[2][2] = y_val;

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

            average_fil_soft(pix_mat, val);
            average.data = val;
            if(x<2 || y<2)
                average.data = 0;

            if(function==ORG_IMGwAxiVdma || function == AVERAGEwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    average.user = 1;
                else
                    average.user = 0;
                if(x == (col_size-1)) // 行の最後
                    average.last = 1;
                else
                    average.last = 0;
            }else{
                average.user = 0;
                average.last = pix.last;
            }
            average.keep = 0x7;
            average.strb = 0x7;
            if(function==AVERAGEwAxiVdma || function==AVERAGEwAxiDma)
                outs << average;
            else
                outs << pix;
        }
    }
    return(0);
}

// average filter
//
// x0y0/9 + x1y0/9 + x2y0/9 + x0y1/9 + x1y1/9 + x2y1/9 + x0y2/9 + x1y2/9 + x2/y2/9
//
void average_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result){
    ap_int<32> pix_1d_r[9], pix_1d_g[9], pix_1d_b[9];
    ap_int<32> y_r, y_b, y_g, y;

    for(int i=0; i<9; i++){
        separate_rgb_soft(pix_mat[i/3][i%3], pix_1d_r[i], pix_1d_g[i], pix_1d_b[i]);
    }

    y_r = average_fil_calc_soft(pix_1d_r);
    y_b = average_fil_calc_soft(pix_1d_b);
    y_g = average_fil_calc_soft(pix_1d_g);

    result = (y_r << 16) + (y_g << 8) + y_b;
}

// average_fil_calc
ap_int<32> average_fil_calc_soft(ap_int<32> *pixd){
    ap_int<32> y;
    ap_fixed<24, 16, AP_TRN_ZERO, AP_SAT> pixdf[9];

    for(int i=0; i<9; i++){
        pixdf[i] = (ap_fixed<24, 16, AP_TRN_ZERO, AP_SAT>)pixd[i];
    }
    y = (ap_int<32>)(pixd[0]/9 + pixd[1]/9 + pixd[2]/9 + pixd[3]/9 + pixd[4]/9 + 
        pixd[5]/9 + pixd[6]/9 + pixd[7]/9 + pixd[8]/9 +
        (ap_fixed<24, 16, AP_TRN_ZERO, AP_SAT>)0.5);

    if(y<0)
        y = -y;
        //y = 0;
    else if(y>255) // 8 bits
        y = 255;
    return(y);
}

// separate_rgb
// RGBを分離する
// RBGのフォーマットは、{R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
//
ap_int<32> separate_rgb_soft(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &g, ap_int<32> &b){
    b = rbg & 0xff;
    g = (rbg>>8) & 0xff;
    r = (rbg>>16) & 0xff;
    return(0);
}


test2.jpg を示す。ノイズ入りの画像となっている。
zub1cg_i7filters_13_231113.jpg
  1. 2023年11月14日 04:48 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる5

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる4”の続き。

RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を加えた 5 個のフィルタを動作させたいということで、前回は、System ILA を追加して、論理合成、インプリメンテーション、ビットストリームの生成を行って、再度、bit ファイルと hwh ファイルを ZUBoard 1CG の PYNQ にアップロードして、ILA ダッシュボードを起動して、波形を確認したところ、問題ないことがわかった。画像も正常に表示されていた。結局、昨日の 1 回のみバグっただけで、今日は何回やっても画像が表示された。今回は、バグが無かったので、正常に HSVConverter を PYNQ 上で試したところ、うまく行った。

i5filters.ipynb を実行した。
なお Python コードは”ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる3”を参照のこと。
Vitis Unified IDE 2023.2 の HLS Development で試したと同様に、HSVConverter のパラメータを下に示す様に変更した。

hsvconv.register_map.h_add = 180
hsvconv.register_map.s_mult = 0x0200 # 2.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT>
hsvconv.register_map.s_add = 0
hsvconv.register_map.v_mult = 0x0200 # 2.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT>
hsvconv.register_map.v_add = 0


色相を 180 度変換して、彩度と明度を 2 倍している。
zub1cg_i5filters_99_231113.png
zub1cg_i5filters_100_231113.jpg
zub1cg_i5filters_101_231113.jpg
zub1cg_i5filters_102_231113.jpg
zub1cg_i5filters_103_231113.jpg

Vitis Unified IDE 2023.2 の HLS Development の結果と同様な画像が得られた。

次に、HSVConverter のパラメータを以下の様に変更した。

hsvconv.register_map.h_add = 0
hsvconv.register_map.s_mult = 0x0100 # 1.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT>
hsvconv.register_map.s_add = 32
hsvconv.register_map.v_mult = 0x0100 # 1.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT>
hsvconv.register_map.v_add = 32


色相はそのままで、彩度と明度に 32 を加えている。
その結果を示す。
zub1cg_i5filters_104_231113.jpg
zub1cg_i5filters_105_231113.jpg

こちらも、Vitis Unified IDE 2023.2 の HLS Development の結果と同様な画像が得られた。
  1. 2023年11月13日 04:28 |
  2. ZUBoard
  3. | トラックバック:0
  4. | コメント:0

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる4

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる3”の続き。

RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を加えた 5 個のフィルタを動作させたいということで、前回は、ZUBoard 1CG の PYNQ を立ち上げて、upyter Notebook 上で i5filters フォルダを作成し、Jupyter Notebook で前回作成したビット・ファイルと hwh ファイルをアップグレードした。画像ファイルを用意して、i5filters.ipynb ファイルを作成し、実行したところ、真っ黒な画像が得られた。デバッグの必要がある。今回は、System ILA を追加して、論理合成、インプリメンテーション、ビットストリームの生成を行って、再度、bit ファイルと hwh ファイルを ZUBoard 1CG の PYNQ にアップロードして、ILA ダッシュボードを起動して、波形を確認したところ、問題ないことがわかった。画像も正常に表示されていた。結局、昨日の 1 回のみバグっただけで、今日は何回やっても画像が表示された。なんだったんだろうか?

median_axis_RGB24_0、RGB24toHSV_0、HSVConverter_0 の AXI4-Stream 出力に Debug を指定して、System ILA のポートを追加した。
zub1cg_i5filters_93_231111.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary を示す。
zub1cg_i5filters_94_231111.png

ZUBoard 1CG の PYNQ の Jupyter Notebook の i5fitlers フォルダで、i5filters.hwh と i5filters.bit ファイルを削除した。
再度、i5filters フォルダに i5filters.hwh と i5filters_wrapper.bit をアップロードし、i5filters_wrapper.bit の名前を i5filters.bit に変更した。

i5filters.ipynb を起動して、ビット・ファイルのロードまで進めた。
Vivado 2023.2 の Flow Navigator -> PROGRAM AND DEBUG -> Open Hardware Manager -> Open Target をクリックし、Auto Connect を選択した。
ILA ダッシュボードが表示された。
ILA ダッシュボードのトリガを、最後の IP である slot2: axis_dwidth_converter_1_M_AXIS : TVALID の立ち上がりに設定した。
トリガを掛けて、i5filters.ipynb を実行していく。”run_kernel()”を実行すると、ILA ダッシュボードでトリガが掛かった。
zub1cg_i5filters_95_231111.png

AXI4-Stream のトランザクションはすべて実行されているようだ。
i5filters.ipynb を見ると、画像が表示されていた。
zub1cg_i5filters_96_231111.png

メディアン・フィルタだけ動作させているので、ノイズが除去された画像が表示されていれば OK のはずなので、問題無さそうだ。
昨日の結果はなんだったんだろうか?と思うが、その後、何回かやっても同様の結果だったので、良いことにする。

最後に波形を示す。
axi dma の MM2S に近い slot3 : axis_subset_converter_0_M_AXIS : TDATA はデータが流れているが、axi dma の S2MM に行く slot2 : axis_dwidth_converter_1_M_AXIS : TDATA は 01010101 で固定されている。これの TDATA 本当はオール 0 のはずなのだが、RGB24toHSV - HSVConverter - HSVtoRGB24 の計算誤差が生じている。
zub1cg_i5filters_97_231111.png

それでは、なぜオール 0 なのか?という話では、3 x 3 の median_axis_RGB24 IP が動作中なので、データが集まらない、1 行目と 2 行目はオール 0 にしているからだ。1 行目と 2 行目をどうしよう?というのは、悩むところなのだが、データをそのまま通すか?もしくはオール 0 にするかの 2 択だとおもう。エッジ・フィルタはオール 0 で問題無さそうだが、メディアン・フィルタではどちらにするか悩むところだ。
zub1cg_i5filters_98_231111.png
  1. 2023年11月12日 04:54 |
  2. ZUBoard
  3. | トラックバック:0
  4. | コメント:0

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる3

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる2”の続き。

RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を加えた 5 個のフィルタを動作させたいということで、前回は、RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を加えたところ、HSVtoRGB24 IP に col_size の入力ポートが付いていたが、これはバグだ。正しくは、AXI4-Liteインターフェースのレジスタになるはずだった。そこで、Vitis HLS 2023.1 を起動して、バグを修正し、もう一度、HSVtoRGB24 IP をブロック・デザインに追加した。論理合成、インプリメンテーション、ビットストリームの生成を行って、成功した。今回は、ZUBoard 1CG の PYNQ を立ち上げて、upyter Notebook 上で i5filters フォルダを作成し、Jupyter Notebook で前回作成したビット・ファイルと hwh ファイルをアップグレードした。画像ファイルを用意して、i5filters.ipynb ファイルを作成し、実行したところ、真っ黒な画像が得られた。デバッグの必要がある。

まずは、前回、作成できたビット・ファイルと hwh ファイルを見る。
i5filters/i5filters.runs/impl_1/i5filters_wrapper.bit がビット・ファイルだ。
zub1cg_i5filters_86_231110.png

i5filters/i5filters.gen/sources_1/bd/i5filters/hw_handoff/i5filters.hwh が hwh ファイルだ。
zub1cg_i5filters_85_231110.png

ZUBoard 1CG の PYNQ を起動して、Jupyter Notebook で、i5filters フォルダを作成した。
i5filters フォルダに i5filters.hwh と i5filters_wrapper.bit をアップロードし、i5filters_wrapper.bit の名前を i5filters.bit に変更した。
i4filters フォルダから test2.jpg をコピーした。
Python3 の i5filters.ipynb ファイルを新規作成した。
zub1cg_i5filters_87_231110.png

i5filters.ipynb の Python3 コードを書いた。

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')
from pynq import allocate, Overlay

i5filters = Overlay("./i5filters.bit")

dma = i5filters.axi_dma_0
median = i5filters.median_axis_RGB24_0
sobel = i5filters.sobel_axis_RGB24_0
gaussian = i5filters.gaussian_axis_RGB24_0
color_conv = i5filters.color_converter_RGB24_0
rgb2hsv = i5filters.RGB24toHSV_0
hsvconv = i5filters.HSVConverter_0
hsv2rgb = i5filters.HSVtoRGB24_0

image_path = "./test2.jpg"
#image_path = "./blue.bmp"
#image_path = "./green.bmp"
#image_path = "./red.bmp"
original_image = Image.open(image_path)

canvas = plt.gcf()
size = canvas.get_size_inches()
canvas.set_size_inches(size*2)

width, height = original_image.size
print("Image size: {}x{} pixels.".format(width, height))
plt.figure(figsize=(12, 10));
_ = plt.imshow(original_image)

in_buffer = allocate(shape=(height, width, 3), 
                           dtype=np.uint8, cacheable=1)
out_buffer = allocate(shape=(height, width, 3), 
                            dtype=np.uint8, cacheable=1)
in_buffer[:] = np.array(original_image)

def run_kernel():
    dma.sendchannel.transfer(in_buffer)
    dma.recvchannel.transfer(out_buffer) 
    gaussian.write(0x00,0x01) # start
    median.write(0x00,0x01) # start
    rgb2hsv.write(0x00,0x01) # start
    hsvconv.write(0x00,0x01) #start
    hsv2rgb.write(0x00,0x01) #start
    color_conv.write(0x00,0x01) #start
    sobel.write(0x00,0x01) # start
    dma.sendchannel.wait()
    dma.recvchannel.wait()

print(height)
print(width)

gaussian.register_map.row_size = height
gaussian.register_map.col_size = width
gaussian.register_map.function_r = 2 # ORG_IMGwAxiDma
#gaussian.register_map.function_r = 3 # GAUSSIANwAxiDma

median.register_map.row_size = height
median.register_map.col_size = width
#median.register_map.function_r = 2 # ORG_IMGwAxiDma
median.register_map.function_r = 3 # MEDIANwAxiDma

rgb2hsv.register_map.row_size = height
rgb2hsv.register_map.col_size = width
rgb2hsv.register_map.function_r = 1 # ForAXIDma

hsvconv.register_map.row_size = height
hsvconv.register_map.col_size = width
#hsvconv.register_map.function_r = 2 # ORG_IMGwAxiDma
hsvconv.register_map.function_r = 3 # HSV_CONVwAxiDma
hsvconv.register_map.h_add = 1
hsvconv.register_map.s_mult = 0x0100 # 1.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT> 
hsvconv.register_map.s_add = 1
hsvconv.register_map.v_mult = 0x0100 # 1.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT> 
hsvconv.register_map.v_add = 1

hsv2rgb.register_map.row_size = height
hsv2rgb.register_map.col_size = width
hsv2rgb.register_map.function_r = 1 # ForAXIDma

color_conv.register_map.row_size = height
color_conv.register_map.col_size = width
color_conv.register_map.function_r = 2 # ORG_IMGwAxiDma
#color_conv.register_map.function_r = 3 # COLOR_CONVwAxiDma
color_conv.register_map.red_mag = 0x0100 # 1.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT>
color_conv.register_map.green_mag = 0x0100 # 1.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT> 
color_conv.register_map.blue_mag = 0x2000 # 32.0, ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT> 

sobel.register_map.row_size = height
sobel.register_map.col_size = width
sobel.register_map.function_r = 2 # ORG_IMGwAxiDma
#sobel.register_map.function_r = 3 # SOBELwAxiDma

run_kernel()
sobel_image = Image.fromarray(out_buffer)

print("Image size: {}x{} pixels.".format(width, height))
plt.figure(figsize=(12, 10));
_ = plt.imshow(sobel_image)

del in_buffer
del out_buffer


i5filters.ipynb を実行したが、得られた画像は真っ黒だった。デバッグが必要だ。
zub1cg_i5filters_88_231110.jpg
zub1cg_i5filters_89_231110.jpg
zub1cg_i5filters_90_231110.jpg
zub1cg_i5filters_91_231110.jpg
zub1cg_i5filters_92_231110.jpg
  1. 2023年11月11日 04:16 |
  2. ZUBoard
  3. | トラックバック:0
  4. | コメント:0

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる2

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる1”の続き。

RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を加えた 5 個のフィルタを動作させたいということで、前回は、Vivado 2023.2 で i5filters プロジェクトを作成し、i4filters プロジェクトでブロック・デザインを出力した i4filters.tcl を i5filters.tcl に改名し、少々手直したあとで、i5filters プロジェクトで動作させてブロック・デザインを作成した。今回は、RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を加えたところ、HSVtoRGB24 IP に col_size の入力ポートが付いていたが、これはバグだ。正しくは、AXI4-Liteインターフェースのレジスタになるはずだった。そこで、Vitis HLS 2023.1 を起動して、バグを修正し、もう一度、HSVtoRGB24 IP をブロック・デザインに追加した。論理合成、インプリメンテーション、ビットストリームの生成を行って、成功した。

RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を i5fitlers ブロック・デザインに Add IP した。
zub1cg_i5filters_73_231108.png

HSVtoRGB24 IP に col_size の入力ポートが付いているが、これはバグだ。正しくは、AXI4-Liteインターフェースのレジスタになるはずだった。修正する必要がある。
Vitis HLS 2023.1 を起動して、HSVtoRGB24 プロジェクトを開いた。
col_size が axis になっている。ここでミスったようだ。
zub1cg_i5filters_74_231108.png

solution3 を新規作成した。
col_size を s_axilite に変更した。
zub1cg_i5filters_75_231108.png

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

Export RTL を行った。Implementation も一緒に行われた。結果を示す。
zub1cg_i5filters_78_231108.png

問題無さそうだ。

Vivado 2023.2 に戻って、i5filters ブロック・デザインから HSVtoRGB24 IP を削除した。
zub1cg_i5filters_79_231108.png

i5filters プロジェクトの HSVtoRGB24 ディレクトリのファイルを削除して、先程合成した Vitis HLS 2023.1 の HSVtoRGB24 プロジェクトの solution3/impl/export.zip を展開して、i5filters プロジェクトの HSVtoRGB24 ディレクトリに入れた。
Vivado 2023.2 の i5filters プロジェクトで、Refresh IP Catalog が表示されたので、クリックした。
もう一度、Refresh IP Catalog が表示されたので、クリックした。
zub1cg_i5filters_80_231108.png

再度、i5filters ブロック・デザインに HSVtoRGB24 IP を Add IP した。
zub1cg_i5filters_81_231108.png

Address Editor 画面を示す。
zub1cg_i5filters_82_231108.png

Sources 画面をクリックし、i5filters_i ブロック・デザインのインスタンスを右クリックし、右クリックメニューから Create HDL Wrapper... を選択して、i5filters_wrapper.v ファイルを生成した。
zub1cg_i5filters_83_231108.png

Flow Navigator -> PROGRAM AND DEBUG -> Generate Bitstream をクリックし、論理合成、インプリメンテーション、ビットストリームの生成を行って、成功した。
Project Summary を示す。
zub1cg_i5filters_84_231108.png
  1. 2023年11月09日 05:17 |
  2. ZUBoard
  3. | トラックバック:0
  4. | コメント:0

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させる1

ZUBoard 1CG の PYNQ v3.0.1 で自作の 5 個のフィルタを動作させよう。

ZUBoard 1CG の PYNQ v3.0.1 で自作の 4 個のフィルタは動作させることができているので、次は RGB24toHSV - HSVConverter - HSVtoRGB24 の各 IP を加えた 5 個のフィルタを動作させたい。今回は、Vivado 2023.2 で i5filters プロジェクトを作成し、i4filters プロジェクトでブロック・デザインを出力した i4filters.tcl を i5filters.tcl に改名し、少々手直したあとで、i5filters プロジェクトで動作させてブロック・デザインを作成した。

"ZUBoard 1CG の PYNQ v3.0.1 で自作の 4 個のフィルタを動作させる1”の Vivado 2023.1 の i4filters プロジェクトで File メニューから Export -> Export Hardware... を選択して、i4filters.tcl を生成した。i4filters.tcl はブロック・デザインを生成する tcl スクリプトだ。

Vivado 2023.2 で ZUBoard 1CG 用の i5filters プロジェクトを作成した。
zub1cg_i5filters_66_231108.png

"ZUBoard 1CG の PYNQ v3.0.1 で自作の 4 個のフィルタを動作させる1”の Vivado 2023.1 の i4filters プロジェクトの gaussian_axis_RGB24, median_axis_RGB24, sobel_axis_RGB24, i4filters.tcl を i5filters プロジェクトのディレクトリにコピーした。

RGB24toHSV, HSVConverter, HSV24RGB24 ディレクトリを作成し、RGB24toHSV, HSVConverter, HSV24RGB24 の各 IP をコピーした。

i4filters.tcl を i5filters.tcl に改名した。
zub1cg_i5filters_68_231108.png

i5filters.tcl の scripts_vivado_version を 2023.2 に、design_name を i5filters に変更した。
zub1cg_i5filters_67_231108.png

Vivado 2023.2 の Flow Navigator -> PROJECT MANAGER -> IP Catalog をクリックし、IP Catlog を表示した。
IP Catlog 内で右クリックし右クリックメニューから Add Repository を選択した。
gaussian_axis_RGB24, median_axis_RGB24, sobel_axis_RGB24, RGB24toHSV, HSVConverter, HSV24RGB24 を選択して、IP Catlog に追加した。
zub1cg_i5filters_69_231108.png

IP Catlog に追加された。
zub1cg_i5filters_70_231108.png

TCL Console タブをクリックし、次のコマンドを入力した。

cd /media/masaaki/Ubuntu_Disk/HDL/2023.2/zub1cg/i5filters/
source i5filters.tcl


zub1cg_i5filters_71_231108.png

i5filtes ブロック・デザインが生成された。
zub1cg_i5filters_72_231108.png
  1. 2023年11月08日 06:50 |
  2. ZUBoard
  3. | トラックバック:0
  4. | コメント:0

"MicroZed Chronicles: Introducing Vitis Unified IDE"をやってみる4

"MicroZed Chronicles: Introducing Vitis Unified IDE"をやってみる3”の続き。

Vitis Unified IDE 2023.2 の HLS Development を試したが、Vitis Unified IDE 2023.2 の通常のアプリケーション・プロジェクトも試してみよう。ちょうどAdam Talyer さんの"MicroZed Chronicles: Introducing Vitis Unified IDE"が公開されたので、これをやってみよう。

前回は、examples の Hello World を利用して、アプリケーション・プロジェクトを作成し、ZUBoard 1CG で実行した。今回は、"MicroZed Chronicles: Introducing Vitis Unified IDE"からソースコードを引用して、ZUBoard 1CG の RGB LED を点灯することができた。

"MicroZed Chronicles: Introducing Vitis Unified IDE"から RGB LED を点灯するソースコードを引用する。

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"

XGpio LEDS;

int main()
{
    init_platform();
    print("Vitis Unified IDE Example\n\r");
    XGpio_Initialize(&LEDS, XPAR_XGPIO_0_BASEADDR);


    XGpio_SetDataDirection(&LEDS, 1, 0x0 );
    XGpio_SetDataDirection(&LEDS, 2, 0x0 );   

    while(1){
        XGpio_DiscreteWrite(&LEDS, 1, 0x01);
        XGpio_DiscreteWrite(&LEDS, 2, 0x04);
        usleep(500000);
        XGpio_DiscreteWrite(&LEDS, 1, 0x02);
        XGpio_DiscreteWrite(&LEDS, 2, 0x01);
        usleep(500000);
        XGpio_DiscreteWrite(&LEDS, 1, 0x04);
        XGpio_DiscreteWrite(&LEDS, 2, 0x02);
        usleep(500000);
    } 



    cleanup_platform();
    return 0;
}


このソースコードを helloworld.c と入れ替えた。
Vivado_2023_2_95_231107.png

FLOW -> Build をクリックして、ビルドを行った。
Vivado_2023_2_96_231107.png

CTRL キーを押しながら XGpio_Initialize() 関数の上にマウスカーソルを置くと、定義が表示された。
やはり第 2 引数は、ベース・アドレスになったようだ。
Vivado_2023_2_97_231107.png

xgpio.h の XGpio_Initialize() 関数の定義を引用する。

/*
 * Initialization functions in xgpio_sinit.c
 */
#ifndef SDT
int XGpio_Initialize(XGpio *InstancePtr, u16 DeviceId);
XGpio_Config *XGpio_LookupConfig(u16 DeviceId);
#else
int XGpio_Initialize(XGpio *InstancePtr, UINTPTR BaseAddress);
XGpio_Config *XGpio_LookupConfig(UINTPTR BaseAddress);
#endif


FLOW -> Run をクリックして、ZUBoard 1CG 上でソフトウェアを走らせると、RGB LED 1 は青ー緑ー赤と点灯し、RGB LED2 は赤ー青ー緑と点灯した。
Vivado_2023_2_98_231107.png

XGpio_Initialize() 関数の第 2 引数がベース・アドレスになったということで、気になったのが、Vitis Unified IDE 2023.2 の HLS Development の場合だ。
Vitis Unified IDE 2023.2 の HLS Development を試す6”で IP を生成したので、そのドライバのうちの HSVConverter/HSVConverter/hls/impl/ip/drivers/HSVConverter_v1_0/src/xhsvconverter_sinit.c を見ると、SDT が定義されている場合は、XHsvconverter_Initialize() の第 2 引数はベース・アドレスで、定義されていない場合の第 2 引数はデバイス ID になっている。
Vivado_2023_2_99_231107.png

Vivado_2023_2_100_231107.png
  1. 2023年11月07日 05:11 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

"MicroZed Chronicles: Introducing Vitis Unified IDE"をやってみる3

"MicroZed Chronicles: Introducing Vitis Unified IDE"をやってみる2”の続き。

Vitis Unified IDE 2023.2 の HLS Development を試したが、Vitis Unified IDE 2023.2 の通常のアプリケーション・プロジェクトも試してみよう。ちょうどAdam Talyer さんの"MicroZed Chronicles: Introducing Vitis Unified IDE"が公開されたので、これをやってみよう。

前回は、Vitis Unified IDE 2023.2 を起動して、zub_rgb_led_pf プラットフォームを作成した。今回は、examples の Hello World を利用して、アプリケーション・プロジェクトを作成し、ZUBoard 1CG で実行した。

Welcome ウインドウをクリックした。
Examples をクリックする。
Vivado_2023_2_81_231105.png

EXAMPLES ウインドウが左に開いた。その、Embedded Software Examples を開いて、Hello World をクリックする。
右画面に Hello World が表示された。
Create Application Component Template ボタンをクリックした。
Vivado_2023_2_82_231105.png

Create Application Component - Hello World ダイアログが表示された。
Name and Location 画面
デフォルトのままにした。
Vivado_2023_2_83_231105.png

Select Platform 画面
作成しておいた zub_rgb_led_pf を選択した。
Vivado_2023_2_84_231105.png

Select Domain 画面
standaloen_psu_cortexa53_0 (デフォルト)を選択した。
Vivado_2023_2_85_231105.png

Summary 画面
Finish ボタンをクリックした。
Vivado_2023_2_86_231105.png

VITIS_WORK に hello_world アプリケーション・プロジェクトが生成された。
Vivado_2023_2_87_231105.png

VITIS_WORK -> hello_world -> Settings -> launch.json をクリックして、表示した。
launch.json は Vitis Unified Software Platform Documentation: Application Acceleration Development (UG1393) によると、プロジェクトを実行またはデバッグするための環境セットアップが保存されているそうだ。
Vivado_2023_2_88_231105.png

VITIS_WORK -> hello_world -> Settings -> UserConfig.cmake をクリックして、表示した。
Vivado_2023_2_89_231105.png

helloworld.c をクリックして表示した。
FLOW ウインドウの Build をクリックして、ビルドを行った。
Platform Build ダイアログが表示された。OK ボタンをクリックした。
Vivado_2023_2_90_231105.png

ビルドが成功した。
Vivado_2023_2_91_231105.png

ZUBoard 1CG ボードの SW2 をすべて ON して JTAG モードにして、電源を ON した。
FLOW ウインドウの Debug をクリックして、デバッグ・モードを表示した。
Vivado_2023_2_92_231105.png

printf() まで Step Over で実行した。
Vivado_2023_2_93_231105.png

ターミナルに printf() の表示が見えた。
Vivado_2023_2_94_231105.png
  1. 2023年11月06日 04:44 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

"MicroZed Chronicles: Introducing Vitis Unified IDE"をやってみる2

"MicroZed Chronicles: Introducing Vitis Unified IDE"をやってみる1”の続き。

Vitis Unified IDE 2023.2 の HLS Development を試したが、Vitis Unified IDE 2023.2 の通常のアプリケーション・プロジェクトも試してみよう。ちょうどAdam Talyer さんの"MicroZed Chronicles: Introducing Vitis Unified IDE"が公開されたので、これをやってみよう。

前回は、Vivado 2023.2 で zuboard_rgb_led プロジェクトを作成した。zub_rgb_led ブロック・デザインを作成し、2 つの RGB LED を制御する AXI GPIO を追加した。回路を完成させ、論理合成、インプリメンテーション、ビットストリームの生成を行った。その後、ハードウェアをエクスポートした。今回は、Vitis Unified IDE 2023.2 を起動して、zub_rgb_led_pf プラットフォームを作成した。

Vivado 2023.2 で Tools メニューから Launch Vitis IDE を選択して、Vitis Unified IDE 2023.2 を起動した。
Vivado_2023_2_71_231105.png

左のウインドウから Open Workspace を起動した。
Open Folder ダイアログが開いた。
zuboard_rgb_led プロジェクトのディレクトリの下に vitis_work ディレクトリを作成し、ワークスペースに指定した。
Vivado_2023_2_72_231105.png

左のウインドウから Create Platform Component をクリックし、プラットフォームを作成する。(残念ながらキャプチャした画面を消してしまったようだ)
Create Platform Component ダイアログが開いた。
Name and Location 画面
Component name に zub_rgb_led_pf と入力した。
Component location はデフォルトのままで、Next ボタンをクリックした。
Vivado_2023_2_74_231105.png

Select Platform Creation Flow 画面
デフォルトで Hardware Desgin が選択されていた。
Hardware Desgin (XSA) に Viavdo で生成された XSA ファイルを指定した。
Vivado_2023_2_75_231105.png

Select Operating System and Processor 画面
デフォルトのままとした。
Vivado_2023_2_76_231105.png

Summary 画面
Finish ボタンをクリックした。
Vivado_2023_2_77_231105.png

vitis_work に zub_rgb_led_pf プラットフォームが生成された。
左のVITIS_WORK -> zub_rgb_led_pf -> vitis-comp.json をクリックした。
その脇の zub_rgb_led_pf -> standalone_psu_cortexa53_0 -> Board Support Package を表示した。
いままでと同様に、ライブラリを選択する画面がある。
Vivado_2023_2_78_231105.png

左の VITIS_WORK -> Sources -> psu_cortexa53_0 -> standalone_pse_cortexa53_0 -> bsp -> include -> xparameter.h を見た。
"MicroZed Chronicles: Introducing Vitis Unified IDE"によるとデバイス ID が無くなって、ベース・アドレスを使うことになったそうだ。
Vivado_2023_2_79_231105.png

左の VITIS_WORK -> Sources -> hw -> sdt にデバイス・ツリーがあった。
sytem-top.dts を見てみた。
Vivado_2023_2_80_231105.png
  1. 2023年11月05日 04:43 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

"MicroZed Chronicles: Introducing Vitis Unified IDE"をやってみる1

Vitis Unified IDE 2023.2 の HLS Development を試したが、Vitis Unified IDE 2023.2 の通常のアプリケーション・プロジェクトも試してみよう。ちょうどAdam Talyer さんの"MicroZed Chronicles: Introducing Vitis Unified IDE"が公開されたので、これをやってみよう。

今回は、Vivado 2023.2 で zuboard_rgb_led プロジェクトを作成した。zub_rgb_led ブロック・デザインを作成し、2 つの RGB LED を制御する AXI GPIO を追加した。回路を完成させ、論理合成、インプリメンテーション、ビットストリームの生成を行った。その後、ハードウェアをエクスポートした。

Vivado 2023.2 で ZUBoard 1CG 用の zuboard_rgb_led プロジェクトを作成した。

zub_rgb_led ブロック・デザインを作成した。
Zynq UltraScale + MPSoC と AXI GPIO を Add IP して、Run Connection Automation で必要な IP が自動的に追加されて配線が行われた。
Vivado_2023_2_60_231104.png

上のブロック・デザインでは、Zynq UltraScale + MPSoC をダブル・クリックして設定を行った。
PS-PL Configuration の PS-PL Interface -> AXI HPM1 FPD のチェックを外した。
Vivado_2023_2_61_231104.png

Run Connection Automation で GPIO を接続する。
GPIO に rgb1_led_3bits を割り当てた。
Vivado_2023_2_62_231104.png

GPIO2 に rgb2_led_3bits を割り当てた。
Vivado_2023_2_63_231104.png

ブロック・デザインが完成した。
Vivado_2023_2_64_231104.png

AXI GPIO の設定を示す。
Vivado_2023_2_65_231104.png

Address Editor を示す。
Vivado_2023_2_66_231104.png

Vivado 2023.2 の Sources ウインドウで右クリックし、右クリックメニューから Create HDL Wrapper... を選択し、 zub_rgb_led_rapper.v を作成した。
Vivado_2023_2_67_231104.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary を示す。
Vivado_2023_2_68_231104.png

File メニューから Export -> Export Hardware... を選択して、zub_rgb_led_wrapper.xsa を出力した。
その際に、Output 画面で Include bitstream のラジオ・ボタンにチェックを入れた。
Vivado_2023_2_69_231104.png

zub_rgb_led_wrapper.xsa が出力された。
Vivado_2023_2_70_231104.png
  1. 2023年11月04日 18:03 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 の HLS Development を試す6

Vitis Unified IDE 2023.2 の HLS Development を試す5”の続き。

Vitis Unified IDE 2023.2 の HLS Development を試してみようということで、前回は、C/RTL 協調シミュレーションを行った。最初に trace_level を port にしたところ、C/RTL 協調シミュレーションが終了しなかった。trace_level を none にしたら、終了した。もう一度、trace_level を port にして、wave_debug にチェックを入れたところ、波形を確認することができた。今回は、Export RTL と Implementation を行った。

FLOW -> PACKAGE -> Run をクリックして、Package を行った。
Summary を示す。
Vivado_2023_2_49_231101.png

HSVConverter/HSVConverter/hls/impl/ip が生成された。そのディレクトリに xilinx_com_hls_HSVConverter_1_0.zip が生成されている。
Vivado_2023_2_50_231101.png

hls_config.cfg をクリックし、Implementation の flow はデフォルトで Run full implementation (RTL synthesis, place and routte) に設定されていた。これで問題無い。
FLOW -> IMPLEMENTATION -> Run をクリックした。
Vivado_2023_2_51_231101.png

Implementation が終了し、成功した。
Vivado_2023_2_52_231101.png

FLOW -> IMPLEMENTATION -> Summary をクリックした。
Vivado_2023_2_53_231101.png

FLOW -> IMPLEMENTATION -> RTL Synthesis をクリックした。
Vivado_2023_2_54_231101.png

FLOW -> IMPLEMENTATION -> Place and Route をクリックした。
Vivado_2023_2_55_231101.png
Vivado_2023_2_56_231101.png

CP achieved post-implementation の値は、4.695 ns で Vitis HLS 2023.1 の結果の 4.552 ns とほとんど変化がない。
Vitis Unified IDE 2023.2 の HLS Development の C SYNTHESIS の ESTIMATED の 4.833 ns とあまり差が無い。この結果だけで判断するのは良くないが、C SYNTHESIS の ESTIMATED の推測値の精度が良くなっているのかもしれない?
  1. 2023年11月03日 09:35 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis Unified IDE 2023.2 の HLS Development を試す5

Vitis Unified IDE 2023.2 の HLS Development を試す4”の続き。

Vitis Unified IDE 2023.2 の HLS Development を試してみようということで、前回は、C コードの合成を行った。Function Call Graph を表示しようとしたらフリー・ライセンスが必要ということなので、ライセンスを取得したら、Function Call Graph、Schedule Viewer を表示することができた。今回は、C/RTL 協調シミュレーションを行った。最初に trace_level を port にしたところ、C/RTL 協調シミュレーションが終了しなかった。trace_level を none にしたら、終了した。もう一度、trace_level を port にして、wave_debug にチェックを入れたところ、波形を確認することができた。

C/RTL 協調シミュレーションを行う。
左の VITIS COMPONENT ウインドウの HSVConverter -> Settings -> hls_config.cfg をクリックし、C/RTL Cosimulation の ldflags-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc を指定する必要があった。これを指定しないとエラーになった。
Vivado_2023_2_41_231101.png

trace_levelport にしたところ、C/RTL 協調シミュレーションが終了しなかった。
これは、以前のバージョンの Vitis HLS でもよくあった現象だ。
Vivado_2023_2_42_231101.png

trace_level を none にしたら、C/RTL 協調シミュレーションが終了した。
Vivado_2023_2_43_231101.png

左の FLOW ウインドウの C/RTL COSIMULATION -> REPORTS の内容を見ていこう。
Summary をクリックした。
Vivado_2023_2_44_231101.png

Cosimulation をクリックした。ここでは、Cosimulation の結果が表示された。
C/RTL 協調シミュレーションのレイテンシは 480045 クロックだった。
Vitis HLS 2023.1 で C/RTL 協調シミュレーションを行ったときのレイテンシは 480048 クロックだった。
Vivado_2023_2_45_231101.png

Timing Trace をクリックした。これは、コードの動作が可視化できそうだ。
Vivado_2023_2_46_231101.png

Function Call Graph をクリックして、表示した。
Vivado_2023_2_47_231101.png

C/RTL 協調シミュレーションの波形を表示したい。
波形を記録するモードで C/RTL 協調シミュレーションが終了しない場合は、以前の Vitis HLS では、 Vivado ウインドウで RTL シミュレーションをすれば、波形を表示することができたので、それを試してみよう。

trace_levelport にして、wave_debug にチェックを入れた。
Vivado_2023_2_57_231101.png

C/RTL Cosimulation を行うと、Vivado が起動して、RTL シミュレーションが行われた。
Vivado_2023_2_58_231101.png

HSVConverter.wcfg タブをクリックすると波形が表示された。
TVALID も TREADY も 1 のままで問題無さそうだ。
Vivado_2023_2_59_231101.png
  1. 2023年11月02日 06:41 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0