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

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

FPGAの部屋

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

RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する3

RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する2”の続き。

MicroZed Chronicles: Kria & Raspberry Pi Camera”のブロック・デザインにソーベル・フィルタを追加するために RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成することにしたということで、前回は、C シミュレーションを行ったが、エラーになってしまった。 Kria KV260 ボードを指定したのだが、これを単体の FPGA チップにしたところ、エラーが解消した。今回は残りの C コードの合成、 C/RTL 協調シミュレーション、 Export RTL 、 Implementation を行った。

最初に C コードの合成を行った。結果を示す。
Kria_PCAM_87_211020.png
Kria_PCAM_88_211020.png
Kria_PCAM_89_211020.png

Function Call Graph を示す。相変わらず、見方が良く分からない。
Kria_PCAM_90_211020.png

C/RTL 協調シミュレーションを行った。結果を示す。
Kria_PCAM_91_211020.png

Avg II, Max II, Min II が 921663 クロックだった。
今の画像サイズは 1280 x 720 ピクセルなので、総ピクセル数は 921600 ピクセルとなるので、性能的には問題無さそうだ。

Open Wave Viewer ボタンをクリックして、 C/RTL 協調シミュレーションの波形を見てみよう。
Kria_PCAM_98_211020.png

outs_TVALID がずーと 1 であることが分かる。

C/RTL 協調シミュレーションの Function Call Graph を示す。
Kria_PCAM_92_211020.png

STALLING RATE が 0.00 % でストールしていないということのようだ。

Export RTL を行った。
sobel_filter_axis_RBG/solution1/impl ディレクトリに export.zip が生成された。
Kria_PCAM_99_211022.png

IMPLEMENTATION を行った。
Run Implementation ダイアログが開いた。
RTL Synthesis, Place & Route ラジオボタンをクリックして、選択した。
Kria_PCAM_94_211020.png

結果を示す。
Kria_PCAM_95_211020.png
Kria_PCAM_96_211020.png
Kria_PCAM_97_211020.png

CP achieved post-implementation は 8.126 ns で問題無さそうだ。
リソース使用量も少ない気がする。
  1. 2021年10月22日 04:10 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する2

”RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する1”の続き。

MicroZed Chronicles: Kria & Raspberry Pi Camera”のブロック・デザインにソーベル・フィルタを追加するために RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成することにしたということで、前回は、ソーベル・フィルタのソースコードを貼って、 Vitis HLS 2021.1 で sobel_filter_axis_RBG プロジェクトを作成した。今回は、 C シミュレーションを行ったが、エラーになってしまった。 Kria KV260 ボードを指定したのだが、これを単体の FPGA チップにしたところ、エラーが解消した。

まずは、右向き緑三角の右横の下向き黒三角をクリックし、C Simulation を選択して、 C シミュレーションを行った。
するとエラーになった。エラーの内容は

ERROR: [HLS 200-1023] Part 'xck26sfvc784-2LV-c' is not installed.


え〜〜。このエラーなんだ。どうしよう?
Kria_PCAM_80_211020.png

Web サイトを検索したけど検索できなかった。
いろいろと探したが、情報が無かったので、ボードじゃなくて、FPGA チップを選択したらどうなんだろう?ということで、 xck26sfvc784-2LV-c 単体を設定してみた。
Solution メニューから Solution Settings... を選択する。
Solution Settings (solution1) ダイアログが開く。
Part Selection の ... ボタンをクリックして、Device Selectin Dialog を表示した。
下の図のように選択し、 xck26sfvc784-2LV-c を選択した。
Kria_PCAM_81_211020.png

Solution Settings (solution1) ダイアログに戻った。
xck26sfvc784-2LV-c が選択されている。
Kria_PCAM_82_211020.png

この状態で、 C シミュレーションを行うと成功した。
Kria_PCAM_83_211020.png

sobel_filter_axis_RBG/solution1/csim/build を見てみよう。
C シミュレーションで生成された org.jpg と sobel.jpg があった。
Kria_PCAM_84_211020.png

org.jpg を示す。
この画像は、ソースコードの sobel_filter_axis() の第三引数の function に 0 を入力して生成された画像だ。この画像は元画像と同じになっている。これは、カメラ画像をスルーして表示するモードとなっている。
Kria_PCAM_85_211020.jpg

sobel.jpg を示す。
この画像は、ソースコードの sobel_filter_axis() の第三引数の function に 1 を入力して生成された画像だ。この画像はソーベル・フィルタの出力画像となっている。
Kria_PCAM_86_211020.jpg
  1. 2021年10月20日 05:08 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する1

MicroZed Chronicles: Kria & Raspberry Pi Camera”のブロック・デザインにソーベル・フィルタを追加するために RBG 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成することにした。今回は、ソーベル・フィルタのソースコードを貼って、 Vitis HLS 2021.1 で sobel_filter_axis_RBG プロジェクトを作成した。

まずは、ヘッダファイルの sobel_filter_axis_RBG.h を貼る。

// sobel_filter_axis_RBG.h
// 2021/10/18 by marsee
//

#ifndef __SOBEL_FILTER_AXIS_RBG10_H__
#define __SOBEL_FILTER_AXIS_RBG10_H__

#define HORIZONTAL 0
#define VERTICAL 1

#define HD_720

#ifdef HD_RES
#define DISPLAY_WIDTH 1920
#define DISPLAY_HIGHT 1080
#endif

#ifdef HD_720
#define DISPLAY_WIDTH 1280
#define DISPLAY_HIGHT 720
#endif

#ifdef SVGA_RES
#define DISPLAY_WIDTH 800
#define DISPLAY_HIGHT 600
#endif

#ifdef SMALL_RES
#define DISPLAY_WIDTH 64
#define DISPLAY_HIGHT 48
#endif

#define ALL_PIXEL_VALUE (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define ORIGINAL_IMAGE 0
#define SOBEL_FILTER 1

#endif


次に、ソースコードの sobel_filter_RBG10.cpp を貼る。

// sobel_filter_RBG10.cpp
// 2021/10/18 by marsee
//

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

#include "sobel_filter_axis_RBG.h"

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

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

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

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

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

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

    LOOP_Y: for(int y=0; y<DISPLAY_HIGHT; y++){
        LOOP_X: for(int x=0; x<DISPLAY_WIDTH; x++){
#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;

            sobel_h_val = sobel_fil(HORIZONTAL, pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                                pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                                pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            sobel_v_val = sobel_fil(VERTICAL,   pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                                pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                                pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            sobel_val = square_root8(sobel_h_val*sobel_h_val + sobel_v_val*sobel_v_val);
            sobel.data = (sobel_val<<16)+(sobel_val<<8)+sobel_val;

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

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

            if(function == SOBEL_FILTER)
                outs << sobel;
            else
                outs << pix;
        }
    }
    return(0);
}

// RBGからYへの変換
// RBGのフォーマットは、{R(8bits), B(8bits), G(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>>8) & 0xff;
    g = rbg & 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);
}

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

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

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

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

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

    return(temp);
}


最後にテストベンチの sobel_filter_axis_RBG_tb.cpp を貼る。

// sobel_filter_axis_RBG_tb.cpp
// 2021/10/19 by marsee
//

#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 "sobel_filter_axis_RBG.h"

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

const char INPUT_JPG_FILE[] = "test2.jpg";
const char OUTPUT_JPG_FILE[] = "sobel.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;

    // 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_sobel(sizeof(int32_t)*(img.cols)*(img.rows));
    std::vector<int32_t> sw_sobel(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[1] & 0xff) | ((pixel[0] & 0xff)<<8) | ((pixel[2] & 0xff)<<16); // RBG 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

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

    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (ap_int<32>)rd_bmp[(j*img.cols)+i];

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

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

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

    sobel_filter_axis(ins, outs, SOBEL_FILTER); // ハードウェアのソーベルフィルタ
    sobel_filter_soft(rd_bmp.data(), sw_sobel.data(), img.cols, img.rows);  // ソフトウェアのソーベルフィルタ

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

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

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

    sobel_filter_axis(ins2, outs2, ORIGINAL_IMAGE); // 元画像出力

    cv::Mat wbmpf2(sobel_row, sobel_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 >> 8) & 0xff); // blue
            pixel[1] = (val & 0xff); // green
            pixel[2] = ((val >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

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

    return(0);
}

int32_t sobel_fil_soft(int32_t h_or_v, int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1,
        int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2);
int32_t conv_rbg2y_soft(int32_t rbg);

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

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

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

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

// sobel filter
// HORZONTAL
// x0y0 x1y0 x2y0  1  2  1
// x0y1 x1y1 x2y1  0  0  0
// x0y2 x1y2 x2y2 -1 -2 -1
// VERTICAL
// x0y0 x1y0 x2y0  1  0 -1
// x0y1 x1y1 x2y1  2  0 -2
// x0y2 x1y2 x2y2  1  0 -1
int32_t sobel_fil_soft(int32_t h_or_v, int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1,
        int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2){
    int32_t y;

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

// square_root8_soft
// 8 bit幅のsquare_rootを求める
int32_t square_root8_soft(int32_t val){
    int32_t temp = 0;
    int32_t square;

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

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

    return(temp);
}


Vitis HLS 2021.1 で sobel_filter_axis_RBG プロジェクトを作成した。
Vitis HLS 2021.1 のプロジェクトを作る時に、 Part Selection では Board をクリックして、 Kria KV260 Vision Starter Kit を選択した。
Kria_PCAM_76_211018.png

ソースコードやヘッダファイル、テストベンチを入れた sobel_filter_axis_RBG プロジェクトを示す。
Kria_PCAM_77_211019.png

今回のテストベンチ・コードでは OpenCV ライブラリを使用している。
Vitis HLS 2021.1 には内蔵された OpenCV は無いので、別にインストールした OpenCV を指定する。
Vitis HLS の Project メニューから Project Settings... を選択して、Project Settings ダイアログを開いた。
Simulation タブを開いて、sobel_filter_axis_RBG_tb.cpp の CFLAGS に

-I/usr/local/include

を設定した。
Linker Flags に

-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc

を設定した。
Kria_PCAM_78_211019.png

更に、 Synthesis をクリックして、 Top Function に sobel_filter_axis を指定した。
Kria_PCAM_79_211019.png
  1. 2021年10月19日 05:17 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

”MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる7

”MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる6”の続き。

MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみようということで、前回は、ブロック・デザインでのデータの流れを確認した。今回は、ブロック・デザインの各部のストリーム・データを Vivado Analyzer で確認してみよう。

カメラから DDR メモリへのデータパスでは、すでに ILA が入っている。 ila 番号と入っている位置を示す。

ila_0 : mipi_csi2_rx_subsys_st_0 の video_out 出力
ila_1 : v_axi4s_vid_out_0 の vtg_ce, locked, overflow, underflow, fifo_read_level[12:0], status[31:0]
ila_2 : v_demosaic_0 の m_axis_video 出力
ila_3 : v_gamma_lut_0 の m_axis_video 出力


これらの ila のメモリ長を 1024 個から 2048 個に変更した。

DDR メモリから Display Port 出力へのデータパスに 1 つも ILA が入っていないので、 axi_vdma_0 の M_AXI_S2MM 出力に Debug を設定して、system_ila0 を実装した。
Kria_PCAM_75_211018.png

これで、論理合成、インプリメンテーション、ビットストリームの生成を行って成功した。
Project Summary を示す。相変わらず、 TIming の Slack はマイナスだ。
Kria_PCAM_65_211017.png

Kira_PCAM/Vivado/myporj ディレクトリに design_1_wrapper.xsa が生成された。
Kria_PCAM_66_211017.png

新しい design_1_wrapper.xsa ができたので、 kria_n プラットフォームをアップデートする。
Explorer ウインドウの kria_n プラットフォームを右クリックし右クリックメニューから Update Hardware Specification を選択する。
kria_n プラットフォームがアップデートされた。
Kria_PCAM_67_211017.png

Explorer ウインドウの display_port_appn アプリケーション・プロジェクトをクリックして、ビルド・ボタン(トンカチ・ボタン)をクリックして、ビルドを行った。成功した。
Kria_PCAM_68_211017.png

Assistant ウインドウの display_port_appn_system -> display_port_appn -> Debug を右クリックし右クリックメニューから Run -> Debugger_display_port_appn-Default を選択して、 FPGA をコンフィギュレーションし、アプリケーション・ソフトウェアを起動した。

アプリケーション・ソフトウェアを起動した後で、Vivado の Flow Navigator で PROGRAM AND DEBUG -> Generate Bitstream -> Open Hardware Manager -> Open Target をクリックして、 Auto Connect を選択した。
波形ビューアーが表示された。

ila の波形を表示する用意が整ったので、トリガ信号を決定して、波形を観測した。

ila_0 (mipi_csi2_rx_subsys_st_0 の video_out 出力)(波形ビューアーでは、 ila の番号が +1 されいているようだ)
Kria_PCAM_70_211017.png

ビデオのピクセル・クロックが 74.25 MHz で AXI4-Stream の動作周波数が 150 MHz なので、 TVALID がずっと 1 ではなく、0 がかなり見受けられる。
TUSER も画像の最初のフレームの最初のデータのときに 1 になっているようだ。 

ila_2 (v_demosaic_0 の m_axis_video 出力)
Kria_PCAM_71_211017.png

ここで、 24 ビット・データになっている。

ila_3 (v_gamma_lut_0 の m_axis_video 出力)
Kria_PCAM_72_211017.png

system_ila0 (axi_vdma_0 の M_AXI_S2MM 出力)
Kria_PCAM_73_211017.png

やはり、ビデオのピクセル・クロックが 74.25 MHz で AXI4-Stream の動作周波数が 150 MHz なので、 TREADY がずっと 1 ではなく、0 がかなり見受けられる。途中でずーと 0 になっていた。

波形を拡大してみた。
Kria_PCAM_74_211017.png

TREADY は 1 クロック毎に 1 と 0 を繰り返している。

画像フィルタは axi_vdma_0 の M_AXI_S2MM 出力に入れてみよう。
  1. 2021年10月18日 04:33 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

”MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる6

”MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる5”の続き。

MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみようということで、前回は、””MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる2”で自分でブロック・デザインを修正して作成した XSA ファイルでカメラ画像がディスプレイに表示されるか?を確かめたところ、問題なくカメラ画像をディスプレイに表示することができた。今回は、ブロック・デザインでのデータの流れを確認してみよう。

カメラから DDR メモリへの経路のデータの流れ方を説明する。

1. カメラからの入力を受ける mipi_csi2_rx_subsyst_0 は 16 ビット AXI4-Stream 出力となっている。
2. axis_subset_converter_1 に入って、 tdata[9:2] を抽出して、 8 ビット AXI4-Stream 出力として出力される。
3. v_domosaic_0 に入って、 8 ビット AXI4-Stream 入力が 24 ビット AXI4-Stream 出力として出力される。ここで、デモザイクされる。ベイヤー・パターンが RBG (Red, Blue, Green) パターンに変換される。
4. v_gmma_lut_0 では、 24 ビット AXI4-Stream 入出力となっていて、ガンマ補正が行われる。
5. axi_vdma_0 (S_AXIS_S2MM) で 24 ビット AXI4-Stream 入力データを DDR メモリに書いている。


DDR メモリから Display Port 出力のデータの流れを説明する。

1. DDR メモリからデータを読んで、 axi_vdma_0 (M_AXIS_MM2S) から 24 ビット AXI4-Stream が出力される。
2. axis_suset_converter_0 で 24 ビット AXI4-Stream を以下のように置き換えている。
tdata[15:8],tdata[23:16],tdata[7:0] つまり RBG を BRG に置き換えているようだ。これは、Zynq UltraScale+ MPSoC のDisplayPort のLiveVideo のピクセルデータの色のビットフィールドが BRG だからだろう?(”Zynq UltraScale+ MPSoC のDisplayPort のLiveVideo のピクセルデータ”参照)
3. v_axi4s_vid_out_0 で BRG に変換された 24 ビット AXI4-Stream を入力して、 vid_data[35:0] に変換している。その他、ビデオ用の信号を主力して、 Zynq UltraScale+ MPSoC の dp_live_video に入力している。


Kria_PCAM_5_211009.png
Kria_PCAM_6_211009.png
  1. 2021年10月17日 04:34 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

”MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる5

SD カード・ブートモードの Zynq UltraScale+ MPSoC で Vitis を使用してコンフィギュレーション、ソフトウェアを起動する”の続き。

前回は、SD カード・ブートモードでの KV260 での Vitis での FPGA コンフィギュレーション+アプリケーション・ソフトウェアの起動方法を探って、それを解明することができた。今回は、””MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる2”で自分でブロック・デザインを修正して作成した XSA ファイルでカメラ画像がディスプレイに表示されるか?を確かめよう。

まずは、新しく kria_n プラットフォームを作成する。
Vitis 2021.1 の File メニューから New -> Platform Project... を選択する。
New Platform Project ダイアログが表示された。最初の画面は Create new platform project だ。
Platform project name に kria_n と入力した。
Kria_PCAM_48_211015.png

”MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる2”で作成した design_1_wrapper.xsa ファイルを指定した。
Kria_PCAM_49_211015.png

Finish ボタンをクリックすると、 kria_n プラットフォーム・プロジェクトが生成された。
Kria_PCAM_50_211015.png

kria_n プラットフォームを元にしたアプリケーション・プロジェクトを作成する。
Vitis 2021.1 の File メニューから New -> Application Project... を選択する。
New Application Project ダイアログが表示された。最初の画面は、 Create a New Application Project だ。
Kria_PCAM_51_211015.png

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

Application Project Details 画面では、 Application project name に diaplay_port_appn を入力した。
現在の画面では、 Show all processors in the hardware specification のチェックボックスにチェックを入れてある。
Kria_PCAM_53_211015.png

Domain 画面。
デフォルトの standalone_domain のままとする。
Kria_PCAM_54_211015.png

Template 画面。
デフォルトの Hello World とした。
Finish ボタンをクリックする。
Kria_PCAM_55_211015.png

Vitis IDE 画面に display_port_appn アプリケーション・プロジェクトが生成された。
Kria_PCAM_56_211015.png

ソース・コードをインポートする。
Explorer の display_port_appn_system -> display_port_appn の src を右クリックし、右クリックメニューから Import Sources... を選択する。
Import Sources ダイアログが表示された。
Browse ボタンをクリックして、 display_port_app/src ディレクトリを選択する。
右のウインドウに display_port_app/src ディレクトリにあるファイルが全て表示されるので、全てのファイルにチェックを入れて、インポートする。
Kria_PCAM_57_211015.png

Explorer の display_port_appn_system -> display_port_appn -> src ディレクトリにソース・コードがインポートされた。
Kria_PCAM_58_211015.png

Explorer の display_port_appn_system -> display_port_appn を右クリックし、右クリックメニューから Properties を選択する。
Properties for display_port_appn ダイアログが開く。
左のウインドウで、 C/C++ Build -> Setiings をクリックする。
Tool Settings タブをクリックし、ARM v8 gcc linker -> Libraries をクリックする。
右の Libraries (-l) に m を追加する。
Kria_PCAM_59_211015.png

Explorer の display_port_appn_system をクリックして、ビルド・ボタンをクリックするとビルドが成功した。
Kria_PCAM_60_211015.png

Assistant ウインドウの display_port_appn_system -> display_port_appn を右クリックし、右クリックメニューから Run -> Run Configurations... を選択する。
Run Configurations ダイアログが表示された。
Main タブ。
Kria_PCAM_61_211015.png

Target Setup タブをクリックして、 Use FSBL flow for initialization のチェックボックスのチェックを外した。
Run ボタンをクリックした。
Kria_PCAM_62_211015.png

FPGA コンフィギュレーションとアプリケーション・ソフトウェアが起動して、カメラ画像がディスプレイに表示された。
成功だ。
  1. 2021年10月15日 04:38 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

SD カード・ブートモードの Zynq UltraScale+ MPSoC で Vitis を使用してコンフィギュレーション、ソフトウェアを起動する

”MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる4”で Adam Taylor さんが用意してくれた Viits 2021.1 の display_port_app アプリケーション・プロジェクトは動作した。しかし、自分で作成した Vitis のアプリケーション・プロジェクトを動かしてみたい。しかし、KV260 には PS_MODE の切り替えスイッチが無いので、JTAG モードに設定できない。そこで、 KV260 の Vitis での制御方法を探ってみた。

まずは、””MicroZed Chronicles: Kria & Raspberry Pi Camera”をやってみる4”の Vitis プロジェクトに display_port_app2 アプリケーション・プロジェクトを作成した。これは、 kira プラットフォームをベースとするアプリケーション・プロジェクトだ。つまり、既存の display_port_app と同じアプリケーション・プロジェクトを作成した。
display_port_app の src からソフトウェア・コードもそのまま Import Sources... してある。
Kria_PCAM_42_211014.png

display_port_app2 アプリケーション・プロジェクトをビルドすると、

undefined reference to `pow'

というエラーが出た。
Kria_PCAM_38_211013.png

このバグは、 math ライブラリを追加すると直るようだ。
Explorer の display_port_app2_system -> display_port_app2 を右クリックし、右クリックメニューから Properties を選択する。
Properties for display_port_app2 ダイアログが開く。
左のウインドウで、 C/C++ Build -> Setiings をクリックする。
Tool Settings タブをクリックし、ARM v8 gcc linker -> Libraries をクリックする。
右の Libraries (-l) に m を追加する。
Kria_PCAM_39_211013.png

もう一度、ビルドすると成功した。
Kria_PCAM_40_211013.png

さて、 KV260 から MicroSD カードを抜いて、Vitis の Assistant ウインドウの display_port_app2_system -> display_port_app2 を右クリックし、右クリックメニューから Run -> Launch Hardware (Single Application Debug) を選択して FPGA をコンフィギュレーションし、アプリケーション・ソフトウェアを起動する。

すると Overwrite Boot Mode のダイアログが表示された。
ターゲットが JTAG モードじゃないと言われている。ブートモードのレジスタにオーバーライトするのかと聞かれている。
Yes ボタンをクリックした。
Kria_PCAM_43_211014.png

カメラ画像はディスプレイに表示されなかった。

それじゃ何で、既存の display_port_app はカメラ画像が表示されるんだろう?
Run Configuration を比べてみると、違いがあった。それは、 Target Setup タブで Use FSBL flow for initialization にチェックが入っているかどうか?だった。
私の作った display_port_app2 は Use FSBL flow for initialization にチェックが入っている。
Kria_PCAM_44_211014.png

既存の display_port_app は Use FSBL flow for initialization のチェックが外れている。
Kria_PCAM_45_211014.png

display_port_app2 も Use FSBL flow for initialization のチェックを外した。
Kria_PCAM_47_211014.png

Use FSBL flow for initialization のチェックを外した後で、Apply ボタンをクリックして設定を反映し、 Run ボタンをクリックして、FPGA をコンフィギュレーションし、アプリケーション・ソフトウェアを起動したところ、 Overwrite Boot Mode のダイアログが表示されずにカメラ画像がディスプレイに表示された。
これだった。。。

考えてみれば FSBL が SD カード・モードの時は走って、 JTAG の時は走らないので、 SD カード・モードの時は FSBL を起動する必要が無いのではないだろうか?
それでオーバーライトと言われちゃったんだな。。。一つ勉強になった。 Adam Taylor さん、ありがとうございます。
  1. 2021年10月14日 04:39 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0
»