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

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

FPGAの部屋

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

KV260 の PYNQ で自作のソーベル・フィルタを動作させる6

KV260 の PYNQ で自作のソーベル・フィルタを動作させる5”の続き。

前回は、”KV260 の PYNQ で自作のソーベル・フィルタを動作させる1”で作成した img_filt ブロック・デザインの sobel_axis_RGB24 IP を入れ替えて、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビット・ファイルと hwh ファイルを生成した。それらのファイルを Jupyter Notebook にアップロードして、確かめた所、ソーベル・フィルタが動作した。今回は、ソーベル・フィルタは動作したが、せっかく System ILA を入れてあるので、波形を確認してみよう。

まずは、MM2S から見てみる。
slot0 : axi_dma_0_M_AXI_MM2S : ARVALID の立ち上がりでトリガを掛けた。
M_AXI_MM2S の波形を示す。
トリガの位置は 100 クロックなので、その後の 900 クロックでは、S2MM のトランザクションが始まることはない。
sobel_axis_RGB24_51_220330.png

M_AXI_MM2S の DMA Read が始まったので、slot2 : axi_dma_0_M_AXIS_MM2S と slot3 : axis_dwidth_converter_0_M_AXIS の AXI4-Stream もデータ転送が始まるが、ソーベル・フィルタ IP が動作していないので、バッファ分だけ動作して停止しているようだ。
sobel_axis_RGB24_52_220330.png

MM2S のトランザクション部分を拡大した。
sobel_axis_RGB24_53_220330.png

今度は、slot1 : axi_dma_0_M_AXI_S2MM : AWVALID の立ち上がりでトリガを掛けた。
M_AXI_MM2S も M_AXI_S2MM も盛大にトランザクションが発生している。
sobel_axis_RGB24_54_220330.png

AXI4-Stream インターフェースでも盛大にデータ転送が行われている。
sobel_axis_RGB24_55_220330.png
sobel_axis_RGB24_56_220331.png
  1. 2022年03月31日 05:11 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

KV260 の PYNQ で自作のソーベル・フィルタを動作させる5

RGB 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する4”の続きでもあり、
KV260 の PYNQ で自作のソーベル・フィルタを動作させる4”の続きでもある。

RGB 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する4”で、Vitis HLS 2021.2 で sobel_axis_RGB24 の C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL、Implementation を行った。今回は、”KV260 の PYNQ で自作のソーベル・フィルタを動作させる1”で作成した img_filt ブロック・デザインの sobel_axis_RGB24 IP を入れ替えて、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビット・ファイルと hwh ファイルを生成した。それらのファイルを Jupyter Notebook にアップロードして、確かめた所、ソーベル・フィルタが動作した。完成だ。

Vivado 2021.2 で作成済みの KV260 用の img_filt プロジェクトを示す。
sobel_axis_RGB24_13_220322.png

現在の img_filt ブロック・デザインを示す。
System_ILA が実装されている。
sobel_axis_RGB24_45_220329.png

sobel_axis_RGB24 IP を ”RGB 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する4”で作った sobel_axis_RGB24 IP に入れ替えた。

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

問題無さそうだ。

hwh ファイルとビットファイルを Jupyter Notebook 上から削除して、今回作成したファイルを KV260 の Jupyter Notebook 上に作成した sobel_axis_RGB24 ディレクトリにアップロードし、名前を sobel に変更した。
sobel.ipynb ファイルを示す。

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from pynq import allocate, Overlay

sobel_filter = Overlay("./sobel.bit")

dma = sobel_filter.axi_dma_0
sobel = sobel_filter.sobel_axis_RGB24_0

image_path = "./test2.jpg"
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=(6, 5));
_ = 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)    
    sobel.write(0x00,0x01) # start
    dma.sendchannel.wait()
    dma.recvchannel.wait()

print(height)
print(width)

sobel.register_map.row_size = height
sobel.register_map.col_size = width
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=(6, 5));
_ = plt.imshow(sobel_image)

del in_buffer
del out_buffer


sobel.register_map.function_r の値のみ変更した。

これで、sobel.ipynb ファイルを実行したところ、ソーベル・フィルタが動作した。成功だ。良かったよ。。。
sobel_axis_RGB24_47_220329.png
sobel_axis_RGB24_48_220329.png
sobel_axis_RGB24_49_220329.png

次に

sobel.register_map.function_r = 3 # SOBELwAxiDma

sobel.register_map.function_r = 2 # ORG_IMGwAxiDma

に変更して、動作させてみたところ、元画像が表示できた。これも成功だ。
sobel_axis_RGB24_50_220329.png
  1. 2022年03月30日 04:29 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

FPGAの部屋のまとめサイトの更新(2022年3月29日)

FPGAの部屋のまとめサイトを更新しました。
今回は、新しいカテゴリの追加はありません。記事をまとめました。
  1. 2022年03月29日 04:19 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

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

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

前回は、axi_vdma 用と axi_dma 用に動作を分けた sobel_axis_RGB24 のソースコードとテストベンチを貼った。今回は、sobel_axis_RGB24 の C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL、Implementation を行った。

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

solution3/csim/build ディレクトリを示す。問題無さそうだ。
sobel_axis_RGB24_38_220327.png

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

C/RTL 協調シミュレーションを行った。
Max II は 480077 クロックだった。問題無さそうだ。
sobel_axis_RGB24_41_220327.png

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

ins_TREADY と outs_TVALID がほとんど 1 なので、スループットが高いことが分かる。

Export RTL を行った。
solution3/impl ディレクトリに export.zip が出力されている。
sobel_axis_RGB24_43_220327.png

Implementation を行った。
CP achieved post-implementation が 7.398 ns で、良さそうだ。
sobel_axis_RGB24_44_220327.png
  1. 2022年03月27日 05:04 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

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

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

KV260 の PYNQ で自作のソーベル・フィルタを動作させる4”で sobel_axis_RGB24 IP が動作しなかったので、見直した所、”Xilinx の axi_dma IP を画像用に使用するための方法”に示すように、axi_vdma 用と axi_dma 用に動作を分けることにした。改めて、ソースコードとテストベンチを示す。

まずは、ヘッダ・ファイルから。
ORG_IMGwAxiVdma、SOBELwAxiVdma が axi_vdma 用の定義で、ORG_IMGwAxiDma、SOBELwAxiDma が axi_dma 用の定義となる。

sobel_axis_RGB24.h を示す。

// sobel_axis_RGB24.h
// 2022/03/20 by marsee
// 2022/03/26 : axi_vdma と axi dma 用に分けた
//

#ifndef __SOBEL_AXIS_RGB24_H__
#define __SOBEL_AXIS_RGB24_H__

#define HORIZONTAL 0
#define VERTICAL 1

#define ORG_IMGwAxiVdma 0
#define SOBELwAxiVdma   1
#define ORG_IMGwAxiDma  2
#define SOBELwAxiDma    3

#endif


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

// sobel_axis_RGB24.cpp
// 2022/03/20 by marsee
// Up to HD resolution
// 2022/03/23 : Added keep and strb
// 2022/03/26 : axi_vdma と axi dma 用に分けた
//

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

#include "sobel_axis_RGB24.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_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> sobel;
    ap_int<32> sobel_val, sobel_h_val, sobel_v_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==SOBELwAxiDma)
            break;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=600 min=600
        LOOP_X: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=800 min=800
#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(function==ORG_IMGwAxiVdma || function == SOBELwAxiVdma){
                if(x==0 && y==0) // 最初のピクセル
                    sobel.user = 1;
                else
                    sobel.user = 0;
                if(x == (col_size-1)) // 行の最後
                    sobel.last = 1;
                else
                    sobel.last = 0;
            }else{
                sobel.user = 0;
                sobel.last = pix.last;
            }
            sobel.keep = 0x7;
            sobel.strb = 0x7;
            if(function==SOBELwAxiVdma || function==SOBELwAxiDma)
                outs << sobel;
            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);
}

// 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_axis_RGB24_tb.cpp を示す。
SOBELwXilinxVideoStandard を define すると axi_vdma 用となり、コメントアウトすると axi_dma 用になる。

// sobel_axis_RGB24_tb.cpp
// 2022/03/20 by marsee
// 2022/03/23 : Added keep and strb
// 2022/03/26 : 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 "sobel_axis_RGB24.h"

#define SOBELwXilinxVideoStandard

int sobel_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 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[0] & 0xff) | ((pixel[1] & 0xff)<<8) | ((pixel[2] & 0xff)<<16); // RGB 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

#ifdef SOBELwXilinxVideoStandard
    // 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 = (ap_int<32>)rd_bmp[(j*img.cols)+i];
#ifdef SOBELwXilinxVideoStandard
            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;
        }
    }

#ifdef SOBELwXilinxVideoStandard
    sobel_axis_RGB24(ins, outs, SOBELwAxiVdma, img.rows, img.cols); // ハードウェアのソーベルフィルタ
#else
    sobel_axis_RGB24(ins, outs, SOBELwAxiDma, img.rows, img.cols); // ハードウェアのソーベルフィルタ
#endif
    sobel_filter_soft(rd_bmp.data(), sw_sobel.data(), img.cols, img.rows);  // ソフトウェアのソーベルフィルタ

    // ハードウェアとソフトウェアのソーベルフィルタの値のチェック
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            ap_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);
#ifdef SOBELwXilinxVideoStandard
    sobel_axis_RGB24(ins2, outs2, ORG_IMGwAxiVdma, img.rows, img.cols); // 元画像出力
#else
    sobel_axis_RGB24(ins2, outs2, ORG_IMGwAxiDma, img.rows, img.cols); // 元画像出力
#endif

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

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);
            if(x<2 || y<2)
                sobel_val = 0;
            sobel_fb[y*x_size+x] = (sobel_val<<16)+(sobel_val<<8)+sobel_val;
        }
    }
    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 にした
int32_t conv_rbg2y_soft(int32_t rbg){
    int32_t r, g, b, y_f;
    int32_t 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);
}

// 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.2 の sobel_axis_RGB24 プロジェクトを示す。KV260 用だ。solution3 を使用する。
sobel_axis_RGB24_36_220326.png

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

-I/usr/local/include


を設定した。
Linker Flags に

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


を設定した。
sobel_axis_RGB24_2_220320.png

更に、 Synthesis をクリックして、 Top Function に sobel_axis_RGB24 を指定した。
sobel_axis_RGB24_3_220320.png
  1. 2022年03月26日 04:33 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

Xilinx の axi_dma IP を画像用に使用するための方法

Xilinx IP の axi_dma IP を画像用に使用する場合について書いておく。

Xilinx 社の axi vdma などの画像用仕様では、画像フレームの最初のデータで AXI4-Stream の tuser を 1 にして、その後は 0 にする。画像の行の最後のデータで tlast を 1 にするという規則がある。
axi dma では、次の規則を守る必要がある。


tuser は使用しない。
データ転送の最後のデータの時に tlast を 1 にする。
tkeep, tstrb はオール1にする。


LogiCORE IP AXI DMA v7.1 製品ガイ ド Vivado Design Suite PG021 2014 年 4 月 2 日”が axi dma の日本語マニュアルだ。
45 ページの ”図 2‐29 : S2MM ステータス ス ト リームの場合のユーザー アプ リケーシ ョ ン フ ィ ール ド と タ イ ミ ングの例”を引用する。
sobel_axis_RGB24_35_220326.png
  1. 2022年03月26日 04:03 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

KV260 の PYNQ で自作のソーベル・フィルタを動作させる4

KV260 の PYNQ で自作のソーベル・フィルタを動作させる3”の続き。

前回は、axi_dma_0 の Width of Buffer Length Register が 14 ビットで足りないのじゃないか?という ikwzm さんのツィートでのご指摘があったので、26 ビットに変更して、Vivado プロジェクトで論理合成、インプリメンテーション、ビットストリームの生成を行って、Jupyter Notebook にアップロードして確かめたが、今度は、DMA が終了しなかった。更に、sobel_axis_RGB24 IP の TKEEP, TSTRB をオール 1 にしていないバグも修正したが、やはり、DMA が終了しなかった。今回は、Vivado のブロック・デザインに System_ILA を実装して、ILA ダッシュボードで波形を確認してみよう。

img_filt ブロック・デザインの全ての AXI4 インターフェースと AXI4-Stream インターフェースに System_ILA を実装した。
sobel_axis_RGB24_30_220324.png

これで、論理合成、インプリメンテーション、ビットストリームの生成をやり直した。
Project Summary を示す。
sobel_axis_RGB24_31_220324.png

Jupyter Notebook に新しいビット・ファイルと hwh ファイルをアップロードし、ビット・ファイルのロードまで、Jupyter Notebook を実行して、axi_dma_0_M_AXI_MM2S の AWVALID の立ち上がりでトリガーを掛けた。
なお、axi_dma_0_M_AXI_S2MM の AWVALID の立ち上がりではトリガーは掛からなかった。
sobel_axis_RGB24_32_220324.png

AXI DMA MM2S のAXI4 インターフェースは動作しているようだ。

同じ時点の AXI4-Stream を見た。
sobel_axis_RGB24_33_220324.png

axi_dma_0_M_AXIS_MM2S にはデータが出力されているが、axis_dwidth_converter_0_M_AXIS には 1 個のデータの受け渡しがあっただけだ。これは、まだソーベル・フィルタ IP がスタートしないのが原因だと思われる。
波形を拡大してデータを確認した。
sobel_axis_RGB24_34_220324.png

ここまでやってみて、気がついたことがある。
ソーベル・フィルタ IP は、AXI VDMA の仕様で作ってあって、AXI DMA の仕様じゃなかった。。。
ソーベル・フィルタ IP は、AXI DMA の仕様にする必要があると思う。
TUSER は使わないでスタートして、データの一番最後だけ TLAST を 1 にする必要があるんじゃないだろうか?
  1. 2022年03月25日 05:05 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

KV260 の PYNQ で自作のソーベル・フィルタを動作させる3

KV260 の PYNQ で自作のソーベル・フィルタを動作させる2”の続き。

前回は、ビットファイルと hwh ファイルを KV260 の Jupyter Notebook にアップロードし、sobel.ipynb を作成して、動作を確認したが、DMA buffer サイズが足りないというエラーになった。今回は、axi_dma_0 の Width of Buffer Length Register が 14 ビットで足りないのじゃないか?という ikwzm さんのツィートでのご指摘があったので、26 ビットに変更して、Vivado プロジェクトで論理合成、インプリメンテーション、ビットストリームの生成を行って、Jupyter Notebook にアップロードして確かめたが、今度は、DMA が終了しなかった。更に、sobel_axis_RGB24 IP の TKEEP, TSTRB をオール 1 にしていないバグも修正したが、やはり、DMA が終了しなかった。

img_filt の Vivado 2021.2 プロジェクトの img_filtl ブロック・デザイン中の axi_dma_0 の Width of Buffer Length Register が 14 ビットだったので、26 ビットに変更した。
sobel_axis_RGB24_27_220324.png

sobel_axis_RGB24 IP の TKEEP, TSTRB をオール 1 にしていないバグも修正し、sobel_axis_RGB24 IP を入れ替えた。
論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary を示す。
sobel_axis_RGB24_28_220324.png

hwh ファイルとビットファイルを KV260 の Jupyter Notebook 上に作成した sobel_axis_RGB24 ディレクトリにコピーした。なお、2つのファイルともに名前を sobel に変更した。

Jupter Notebook を実行した所、

dma.recvchannel.wait()

で止まっていた。
sobel_axis_RGB24_29_220324.png

なぜだろうか? System_ILA を挿入して、波形を観察してみよう。
  1. 2022年03月24日 04:42 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

KV260 の PYNQ で自作のソーベル・フィルタを動作させる2

KV260 の PYNQ で自作のソーベル・フィルタを動作させる1”の続き。

前回は、RGB 24 ビット・データ入出力対応のソーベル・フィルタ IP を使用して、Vivado 2021.2 で img_filtプロジェクトを作成し、img_filt ブロック・デザインを作成した。そして、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビットファイルと hwh ファイルを作成した。今回は、ビットファイルと hwh ファイルを KV260 の Jupyter Notebook にアップロードし、sobel.ipynb を作成して、動作を確認したが、DMA buffer サイズが足りないというエラーになった。

前回作成した hwh ファイルとビットファイルを KV260 の Jupyter Notebook 上に作成した sobel_axis_RGB24 ディレクトリにコピーした。なお、2つのファイルともに名前を sobel に変更した。
Jupyter Notebook で sobel.ipynb ファイルを作成した。
画像ファイルの test2.jpg をアップロードした。
sobel_axis_RGB24_24_220323.png

sobel.ipynb ファイルを貼っておく。

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from pynq import allocate, Overlay

sobel_filter = Overlay("./sobel.bit")

dma = sobel_filter.axi_dma_0
sobel = sobel_filter.sobel_axis_RGB24_0

image_path = "./test2.jpg"
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=(6, 5));
_ = 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)    
    sobel.write(0x00,0x01) # start
    dma.sendchannel.wait()
    dma.recvchannel.wait()

print(height)
print(width)

sobel.register_map.row_size = height
sobel.register_map.col_size = width
sobel.register_map.function_r = 1

run_kernel()
sobel_image = Image.fromarray(out_buffer)

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

del in_buffer
del out_buffer



sobel.ipynb を実行したが、DMA buffer サイズが足りないというエラーになった。
sobel_axis_RGB24_24_220323.png
sobel_axis_RGB24_25_220323.png
sobel_axis_RGB24_26_220323.png

どうしてだろうか? 同じようなコードで resizer_pl.ipynb は動いているのだけど、axi_dma の設定が違うのかな?
resizer_pl.ipynb で使っている Vivado のプロジェクトが見たい。何処かにあるかな?
  1. 2022年03月23日 04:56 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

KV260 の PYNQ で自作のソーベル・フィルタを動作させる1

(2022/03/24 :追記) TSTRB, TKEEP の処理を忘れたので、soble_axis_RGB24 IP のソースコードとテストベンチを書き換えたため、結果を修正した。

RGB 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する1
RGB 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する2
で作成した RGB 24 ビット・データ入出力対応のソーベル・フィルタ IP を使用して、Vivado 2021.2 で img_filtプロジェクトを作成し、img_filt ブロック・デザインを作成した。そして、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビットファイルと hwh ファイルを作成した。

Vivado 2021.2 で KV260 用の img_filt プロジェクトを作成した。
sobel_axis_RGB24_13_220322.png

img_filt ディレクトリの下に sobel_axis_RGB24 ディレクトリを作成し、”RGB 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.1 で作成する2”で生成された sobel_axis_RGB24/solution1/impl/export.zip を展開して sobel_axis_RGB24 ディレクトリにコピーした。
sobel_axis_RGB24_14_220322.png

IP Catalog に sobel_axis_RGB24 IP を追加した。
sobel_axis_RGB24_15_220322.png

img_filt ブロック・デザインを作成した。
sobel_axis_RGB24_16_220322.png

axi_dma_0 は Re-customize IP ダイアログで Enable Scatter Gather Engine のチェックを外した。
sobel_axis_RGB24_18_220322.png

axis_dwidth_converter_0 の Master interface TDATA width (bytes) を 3 に設定した。
sobel_axis_RGB24_19_220322.png

axis_dwidth_converter_1 の Master interface TDATA width (bytes) を 4 に設定した。
sobel_axis_RGB24_20_220322.png

Address Editor 画面を示す。
sobel_axis_RGB24_17_220322.png

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

img_filt/img_filt.gen/sources_1/bd/img_filt/hw_handoff/img_filt.hwh が生成された。
sobel_axis_RGB24_22_220322.png

img_filt/img_filt.runs/impl_1/img_filt_wrapper.bit が生成された。
sobel_axis_RGB24_23_220322.png
  1. 2022年03月22日 04:05 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

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

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

(2022/03/24 :追記) TSTRB, TKEEP の処理を忘れたので、ソースコードとテストベンチを書き換えた。

前回は、PYNQ で使用するためのソーベル・フィルタを Vitis HLS 2021.2 で作成するためのコードを貼って、sobel_axis_RGB24 プロジェクトを作成した。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL 、Implementation を行った。

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

sobel_axis_RGB24/solution1/csim/build ディレクトリに結果が生成されている。sobel.jpg がソーベル・フィルタの処理結果となる。
sobel_axis_RGB24_5_220321.png

C コードの合成を行った。
sobel_axis_RGB24_6_220321.png
sobel_axis_RGB24_7_220321.png
sobel_axis_RGB24_8_220321.png

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

最大レイテンシは 480026 クロックだった。画像サイズが 800 x 600 なので、余計なクロックは 23 クロックだけだった。

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

ins_TREADY, outs_TVALID 共にほとんど 1 なので、スループットが高いことが分かる。

Export RTL を行った。
sobel_axis_RGB24_11_220321.png

sobel_axis_RGB24/solution1/impl/export.zip が生成された。export.zip には sobel_axis_RGB24 IP が圧縮されている。

Implementation を行った。
sobel_axis_RGB24_12_220321.png

問題無さそうだ。
  1. 2022年03月21日 04:49 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

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

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する8”までの作業で画像を KV260 の PYNQ で処理する手順と RGB のフィールドは確認することができた。それじゃ実際の画像アプリケーションを KV260 の PYNQ で実装してみたいということで、ソーベル・フィルタを KV260 の PYNQ で実装してみることにした。
そういう訳で、RGB 24 ビット・データ入出力対応のソーベル・フィルタを Vitis HLS 2021.2 で作成する。今回は、ヘッダとソースコード、テストベンチを貼っておく。

(2022/03/24 :追記) TSTRB, TKEEP の処理を忘れたので、ソースコードとテストベンチを書き換えた。

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

// sobel_axis_RGB24.cpp
// 2022/03/20 by marsee
// Up to HD resolution
// 2022/03/23 : Added keep and strb
//

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

#include "sobel_axis_RGB24.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_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> sobel;
    ap_int<32> sobel_val, sobel_h_val, sobel_v_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;
    } while(pix.user == 0);

    LOOP_Y: for(int y=0; y<row_size; y++){
        LOOP_X: for(int x=0; x<col_size; 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 == (col_size-1)) // 行の最後
                sobel.last = 1;
            else
                sobel.last = 0;

            sobel.keep = 0x7;
            sobel.strb = 0x7;
            if(function == SOBEL_FILTER)
                outs << sobel;
            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);
}

// 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_axis_RGB24_tb.cpp を貼っておく。

// sobel_axis_RGB24_tb.cpp
// 2022/03/20 by marsee
// 2022/03/23 : Added keep and strb
//

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

int sobel_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 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[0] & 0xff) | ((pixel[1] & 0xff)<<8) | ((pixel[2] & 0xff)<<16); // RGB 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;
        pix.last = 0;
        pix.user = 0;
        pix.keep = 0x7;
        pix.strb = 0x7;

        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;

            pix.keep = 0x7;
            pix.strb = 0x7;

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

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

    // ハードウェアとソフトウェアのソーベルフィルタの値のチェック
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            ap_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_axis_RGB24(ins2, outs2, ORIGINAL_IMAGE, img.rows, img.cols); // 元画像出力

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

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);
            if(x<2 || y<2)
                sobel_val = 0;
            sobel_fb[y*x_size+x] = (sobel_val<<16)+(sobel_val<<8)+sobel_val;
        }
    }
    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 にした
int32_t conv_rbg2y_soft(int32_t rbg){
    int32_t r, g, b, y_f;
    int32_t 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);
}

// 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.2 の sobel_axis_RGB24 プロジェクトを示す。KV260 用だ。
sobel_axis_RGB24_1_220320.png

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

-I/usr/local/include


を設定した。
Linker Flags に

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


を設定した。
sobel_axis_RGB24_2_220320.png

更に、 Synthesis をクリックして、 Top Function に sobel_axis_RGB24 を指定した。
sobel_axis_RGB24_3_220320.png
  1. 2022年03月20日 05:43 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する8

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する7”の続き。

PL の FPGA で画像処理をアクセラレーションする時の、R, G, B のバイト・フィールドを確かめるために、前回は、データ幅を 24 ビットに変更した choose_RGB IP と AXI4-Stream Data Width Converter を使用して Vivado 2021.2 でブロック・デザインを修正して、論理合成、インプリメンテーション、ビットストリームの生成を行った。今回は、できあがったビットファイルと hwh ファイルを KV260 の Jupyter Notebook にアップロードし、動作をテストしたところ成功した。

前回、Vivado 2021.2 の check_RGB で作成した check_RGB.gen/sources_1/bd/check_RGB/hw_handoff/check_RGB.hwh と check_RGB/check_RGB.runs/impl_1/check_RGB_wrapper.bit を Jupyter Notebook の check_RGB ディレクトリにアップロードして check_RGB_wrapper.bit の名前を check_RGB.bit に変更した。

もう一度 Python3 のソースコードを示す。
ソースコードのみを示す。(なお、このソースコードは resizer_pl.ipynb を参考にさせていただいています)
変換後の画像を表示する行を追加した。

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from pynq import allocate, Overlay

check_RGBd = Overlay("./check_RGB.bit")

dma = check_RGBd.axi_dma_0
chRGB = check_RGBd.choose_RGB_0

image_path = "./RGB_test.png"
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=(6, 5));
_ = 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)    
    chRGB.write(0x00,0x01) # start
    dma.sendchannel.wait()
    dma.recvchannel.wait()

print(height)
print(width)

chRGB.register_map.row_size = height
chRGB.register_map.col_size = width
chRGB.register_map.choose_rgb_val = 0

run_kernel()
chRGB_image = Image.fromarray(out_buffer)

print("Image size: {}x{} pixels.".format(width, height))
plt.figure(figsize=(6, 5));
_ = plt.imshow(chRGB_image)

del in_buffer
del out_buffer


Jupyter Notebook の実行結果を示す。

chRGB.register_map.choose_rgb_val = 0

の場合。
image_format4PYNQ_47_220319.png
image_format4PYNQ_48_220319.png

ビット 7 〜 0 まで抽出すると赤が含まれているフィールドのみを表示できた。成功だ。

chRGB.register_map.choose_rgb_val = 1

の場合。
image_format4PYNQ_49_220319.png

ビット 15 〜 8 まで抽出すると緑が含まれているフィールドのみを表示できた。

chRGB.register_map.choose_rgb_val = 2

の場合。
image_format4PYNQ_50_220319.png

ビット 23 〜 16 まで抽出すると青が含まれているフィールドのみを表示できた。

chRGB.register_map.choose_rgb_val = 2

の場合の実際の波形を見てみよう。
Vivado 2021.2 で ILA ダッシュボードを起動した。
axi_dma_0_M_AXI_S2MM : AWVALID の立ち上がりでトリガーを掛けた。
image_format4PYNQ_51_220319.png

AXI4 Master インターフェースの axi_dma_0_M_AXI_S2MM のデータが変化している部分を表示した。
やはり、cv::xf::Mat 形式のようだ。
image_format4PYNQ_53_220319.png

Vivado 2021.2 の check_RGB ブロック・デザインをもう一度見る。
image_format4PYNQ_42_220317.png

slot2 が axi_dma_0 の MM2S の出力で、slot3 が AXI4-Stream Data Width Converter の出力になるので、観察すると、axi_dma_0 の MM2S の出力は cv::xf::Mat 形式だが、 AXI4-Stream Data Width Converter の出力は色でバイト・フィールドを決定されたフォーマットに変換されている。
image_format4PYNQ_52_220319.png

AXI4-Stream Data Width Converter を使用すると cv::xf::Mat 形式を色でバイト・フィールドを決定されたフォーマットに変換してくれるようだ。
  1. 2022年03月19日 04:48 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する7

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する6”の続き。

PL の FPGA で画像処理をアクセラレーションする時の、R, G, B のバイト・フィールドを確かめるために、前回は、Vitis HLS 2021.2 の choose_RGB IP を 24 ビット幅にして、Vivado 2021.2 のブロック・デザインで AXI4-Stream Data Width Converter を使用して、32 ビット幅から 24 ビット幅に変換してみよう。とりあえずは、Vitis HLS 2021.2 で choose_RGB IP を 24 ビット幅の AXI4-Stream に変更した。今回は、データ幅を 24 ビットに変更した choose_RGB IP と AXI4-Stream Data Width Converter を使用して Vivado 2021.2 でブロック・デザインを修正して、論理合成、インプリメンテーション、ビットストリームの生成を行った。

現在の check_RGB ブロック・デザインは”PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する3”で作成したブロック・デザインに System ILA を追加した”PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する5”の状態にある。
これに、データ幅を 24 ビットに変更した choose_RGB IP と AXI4-Stream Data Width Converter を追加する。
追加した check_RGB ブロック・デザインを示す。
image_format4PYNQ_42_220317.png

axi_dma_0 の M_AXIS_MM2S から接続される axis_dwidth_converter_0 の設定を示す。
image_format4PYNQ_45_220317.png

axi_dma_0 の M_AXIS_S2MM へ接続される axis_dwidth_converter_1 の設定を示す。
image_format4PYNQ_46_220317.png

Address Editor を示す。
image_format4PYNQ_43_220317.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary を示す。
image_format4PYNQ_44_220317.png
  1. 2022年03月18日 04:37 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する6

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する5”の続き。

PL の FPGA で画像処理をアクセラレーションする時の、R, G, B のバイト・フィールドを確かめるために、前回は、動作しない原因を確かめるために、AXI4 インターフェースと AXI4-Stream インターフェースに System ILA を挿入し、ILA ダッシュボードで見た。その結果、AXI4-Stream の axi_dma_0_M_AXIS_MM2S を見ると、cv::xf::Mat 形式のデータのようだった。cv::xf::Mat 形式のデータだと 3 バイトのデータを 4 バイト幅にエンコードしているので、ワード数が少なくなっているのが、実行が終了しない原因のようだ。今回は、Vitis HLS 2021.2 の choose_RGB IP を 24 ビット幅にして、Vivado 2021.2 のブロック・デザインで AXI4-Stream Data Width Converter を使用して、32 ビット幅から 24 ビット幅に変換してみよう。とりあえずは、Vitis HLS 2021.2 で choose_RGB IP を 24 ビット幅の AXI4-Stream に変更する。

Vitis HLS 2021.2 の choose_RGB プロジェクトで作業する。プロジェクトは”PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する1”を参照のこと。

AXI4-Stream を 24 ビット幅に変更した choose_RGB.cpp を示す。

// choose_RGB.cpp
// 2021/03/10 by marsee
// 2021/03/16 : Changed the bit width of AXI4-Stream from 32 bits to 24 bits.

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

int choose_RGB(hls::stream<ap_axiu<24,1,1,1> >& ins, hls::stream<ap_axiu<24,1,1,1> >& outs,
        int32_t row_size, int32_t col_size, int32_t choose_rgb_val){
#pragma HLS INTERFACE mode=s_axilite port=choose_rgb_val
#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=return
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register

    ap_axiu<24,1,1,1> dt;

    for(int row=0; row < row_size; row++){
#pragma HLS LOOP_TRIPCOUNT avg=4 max=4 min=4
        for(int col=0; col < col_size; col++){
#pragma HLS LOOP_TRIPCOUNT avg=32 max=32 min=32
            ins >> dt;
            switch(choose_rgb_val){
            case 0:
                dt.data &= (ap_uint<24>)0xff;
                break;
            case 1:
                dt.data &= (ap_uint<24>)0xff00;
                break;
            default: // 2
                dt.data &= (ap_uint<24>)0xff0000;
                break;
            }
            outs << dt;
        }
    }
    return(0);
}


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

// choose_RGB_tb.cpp
// 2022/03/10 by marsee
// 2021/03/16 : Changed the bit width of AXI4-Stream from 32 bits to 24 bits.

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

int choose_RGB(hls::stream<ap_axiu<24,1,1,1> >& ins, hls::stream<ap_axiu<24,1,1,1> >& outs,
        int32_t row_size, int32_t col_size, int32_t choose_rgb_val);

const char INPUT_IMG_FILE[] = "RGB_test.bmp";
const char OUTPUT_IMG_FILE[] = "output.bmp";
const int32_t choose_rgb_val = 2;

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins;
    hls::stream<ap_axiu<24,1,1,1> > outs;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals;

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

    // ピクセルを入れる領域の確保
    std::vector<ap_uint<24>> rd_buf(sizeof(uint32_t)*img.cols*img.rows);
    std::vector<ap_uint<24>> out_buf(sizeof(uint32_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_buf[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];
        }
    }
    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = rd_buf[(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;

            pix.keep = 0xf;
            pix.strb = 0xf;
            ins << pix;
        }
    }

    choose_RGB(ins, outs, img.rows, img.cols, choose_rgb_val);

    // out_buf に結果を代入
    for(int j=0; j<img.rows; j++){
        for(int i=0; i<img.cols; i++){
            outs >> pix;
            out_buf[j*img.cols+i] = pix.data;
        }
    }

    // wbmpf に結果を代入
    cv::Mat wbmpf(img.rows, img.cols, CV_8UC3);
    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);
            uint32_t rbg = (int32_t)out_buf[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;
        }
    }
    // 結果を画像ファイルへ出力する
    cv::imwrite(OUTPUT_IMG_FILE, wbmpf);
}


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

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

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

Export RTL を行って、IP を作成した。

Implementation を行った。問題無さそうだ。
image_format4PYNQ_41_220317.png
  1. 2022年03月17日 05:26 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する5

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する4”の続き。

PL の FPGA で画像処理をアクセラレーションする時の、R, G, B のバイト・フィールドを確かめるために、前回は、KV260 の Jupyter Notebook に生成されたビットファイルと hwh ファイル、画像ファイルをアップロードして、動作させたところ動作しなかった。今回は、動作しない原因を確かめるために、AXI4 インターフェースと AXI4-Stream インターフェースに System ILA を挿入し、ILA ダッシュボードで見てみよう。

Vivado 2021.2 の check_RGB プロジェクトで AXI4 インターフェースと AXI4-Stream インターフェースに System ILA を挿入した。
image_format4PYNQ_35_220316.png

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

KV260 の Jupyter Notebook の check_RGB ディレクトリに、check_RGB/check_RGB.runs/impl_1/check_RGB_wrapper.bit を再度アップロードして、名前を check_RGB.bit に変更した。
同様に、check_RGB.gen/sources_1/bd/check_RGB/hw_handoff/check_RGB.hwh を再度アップロードした。
check_RGB.ipynb ファイルの

check_RGBd = Overlay("./check_RGB.bit")

まで実行した。
image_format4PYNQ_28_220316.png

Vivado 2021.2 で ILA ダッシュボードを起動した。
axi_dma_0_M_AXI_S2MM : AWVALID の立ち上がりでトリガーを掛けた。
image_format4PYNQ_29_220316.png

check_RGB.ipynb ファイルの

run_kernel()

まで実行したところトリガーがかかった。
image_format4PYNQ_30_220316.png

image_format4PYNQ_31_220316.png

axi_dma_0_M_AXI_S2MM の波形がある部分を拡大した。
image_format4PYNQ_32_220316.png

更に拡大した。
image_format4PYNQ_33_220316.png

データがおかしい。まばらに 0x000000ff があるのは、おかしい。連続しているはず。

AXI4-Stream の axi_dma_0_M_AXIS_MM2S を見ると、これは、cv::xf::Mat 形式のデータのようだ。。。
cv::xf::Mat 形式は”Vitis Vision Library の AXI4 Master インターフェース版 medianblur をZYBO Z7-20 で使ってみる1(準備編)”で解析してある。
もしかして、AXI4 Stream Data Width Converter で変換できるのだろうか?
  1. 2022年03月16日 05:25 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する4

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する3”の続き。

PL の FPGA で画像処理をアクセラレーションする時の、R, G, B のバイト・フィールドを確かめるために、前回は、choose_RGB IP を使用して、Vivado 2021.2 で check_RGB プロジェクトを作成し、論理合成、インプリメンテーション、ビットストリームの生成を行った。今回は、KV260 の Jupyter Notebook に生成されたビットファイルと hwh ファイル、画像ファイルをアップロードして、動作させたところ動作しなかった。

KV260 の Jupyter Notebook で check_RGB ディレクトリを作成し、check_RGB/check_RGB.runs/impl_1/check_RGB_wrapper.bit をアップロードして、名前を check_RGB.bit に変更した。
同様に、check_RGB.gen/sources_1/bd/check_RGB/hw_handoff/check_RGB.hwh をアップロードした。
check_RGB.ipynb ファイルを新規作成して、Python3 のソースコードを書いた。
ソースコードのみを示す。(なお、このソースコードは resizer_pl.ipynb を参考にさせていただいています)

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from pynq import allocate, Overlay

check_RGBd = Overlay("./check_RGB.bit")

dma = check_RGBd.axi_dma_0
chRGB = check_RGBd.choose_RGB_0

image_path = "./RGB_test.png"
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=(6, 5));
_ = 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)    
    chRGB.write(0x00,0x01) # start
    dma.sendchannel.wait()
    dma.recvchannel.wait()

print(height)
print(width)

chRGB.register_map.row_size = height
chRGB.register_map.col_size = width
chRGB.register_map.choose_rgb_val = 0

run_kernel()
chRGB_image = Image.fromarray(out_buffer)

del in_buffer
del out_buffer


実行していくと、

in_buffer[:] = np.array(original_image)

で shape が違うと言われてしまった。。。
png ファイルを読み込むと次元が (4, 32, 4) になってしまうのだろうか?
image_format4PYNQ_23_220315.png
image_format4PYNQ_24_220315.png

png ファイルを使っているとダメそうなので、bmp ファイルに変換して、やってみることにした。
Pinta で RGB_test.png を RGB_test.bmp に変換して、KV260 の check_RGB ディレクトリにアップロードした。
ソースコードの

image_path = "./RGB_test.png"

image_path = "./RGB_test.bmp"

に変更した。
これでソースコードを実行した。
今度はエラーのあった行を無事に通過したが、

run_kernel()

に行ったきりで終了しなかった。
image_format4PYNQ_25_220315.png
image_format4PYNQ_26_220315.png
image_format4PYNQ_27_220315.png

  1. 2022年03月15日 04:50 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する3

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する2”の続き。

PL の FPGA で画像処理をアクセラレーションする時の、R, G, B のバイト・フィールドを確かめるために、前回は、choose_RGB プロジェクトで、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL 、Implementation を行った。今回は、前回作成した choose_RGB IP を使用して、Vivado 2021.2 で check_RGB プロジェクトを作成し、論理合成、インプリメンテーション、ビットストリームの生成を行った。

Vivado 2021.2 で KV260 用の check_RGB プロジェクトを作成した。
image_format4PYNQ_15_220314.png

check_RGB プロジェクトに choose_RGB ディレクトリを新規作成し、Vitis HLS 2021.2 の choose_RGB/solution1/impl/export.zip ファイルを展開して、コピーした。
choose_RGB ディレクトリを IP Catalog に追加した。
image_format4PYNQ_16_220314.png

choose_RGB IP を使用して、check_RGB ブロック・デザインを作成した。
image_format4PYNQ_17_220314.png

Zynq UltraScale+ MPSoC は S_AXI_HPC0_FPD と M_AXI_HPM0_FPD ポートを使用している。また、pl_clk0 の周波数はデフォルトの 100 MHz だ。

axi_dma_0 の設定を示す。
Enable Scatter Gather Engine のチェックボックスのチェックを外した。
image_format4PYNQ_18_220314.png

Address Editor 画面を示す。
image_format4PYNQ_19_220314.png

HDL Wrapper ファイルを作成し、論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary 画面を示す。
image_format4PYNQ_20_220314.png

check_RGB/check_RGB.gen/sources_1/bd/check_RGB/hw_handoff/check_RGB.hwh ファイルが生成された。
image_format4PYNQ_22_220314.png

check_RGB/check_RGB.runs/impl_1/check_RGB_wrapper.bit ファイルが生成された。
image_format4PYNQ_21_220314.png
  1. 2022年03月14日 04:16 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する2

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する1”の続き。

PL の FPGA で画像処理をアクセラレーションする時の、R, G, B のバイト・フィールドを確かめるために、前回は、Vitis HLS 2021.2 で choose_RGB プロジェクトを作成した。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL 、Implementation を行った。

C シミュレーションを 4 回行った。
image_format4PYNQ_5_220311.png

choose_RGB_tb.cpp の 20 行目の choose_rgb_val に 0 を設定した。

const int32_t choose_rgb_val = 0;


青の行と最後の行の青のみ抽出された。
image_format4PYNQ_6_220311.png

choose_RGB_tb.cpp の 20 行目の choose_rgb_val に 1 を設定した。

const int32_t choose_rgb_val = 1;


緑の行と最後の行の緑のみ抽出された。
image_format4PYNQ_7_220311.png

choose_RGB_tb.cpp の 20 行目の choose_rgb_val に 2 を設定した。

const int32_t choose_rgb_val = 2;


赤の行と最後の行の赤のみ抽出された。
image_format4PYNQ_8_220311.png

choose_RGB_tb.cpp の 20 行目の choose_rgb_val に 3 を設定した。

const int32_t choose_rgb_val = 3;


真っ黒だった。
image_format4PYNQ_9_220311.png

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

C/RTL 協調シミュレーションを行った。
レイテンシは 132 クロックだった。
総ピクセル数は 32 ピクセル x 4 行で 128 ピクセルだった。
image_format4PYNQ_12_220311.png

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

Export RTL を行って、IP を作成した。
choose_RGB/solution1/impl ディレクトリに export.zip が生成された。

Implementation を行った。
image_format4PYNQ_14_220311.png

全く問題ない様だ。
  1. 2022年03月13日 04:27 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

PYNQ の画像ファイル・フォーマットを調査するために choose_RGB IP を Vitis HLS 2021.2 で作成する1

自作した Vivado プロジェクトの DMA_pow2_test を PYNQ で実行する2(制御用 Python コードの書き方を調べる)”で引用した pynq-helloworld/resizer_pl.ipynb ファイルだが、このフレーム・ワークを自分で使用しようとした時に 32 ビット・フィールドの内の R, G, B の割当が分からない?ので、Vitis HLS 2021.2 を使って、確かめてみることにした。

choose_RGB.cpp を示す。

// choose_RGB.cpp
// 2021/03/10 by marsee
//

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

int choose_RGB(hls::stream<ap_axiu<32,1,1,1> >& ins, hls::stream<ap_axiu<32,1,1,1> >& outs,
        int32_t row_size, int32_t col_size, int32_t choose_rgb_val){
#pragma HLS INTERFACE mode=s_axilite port=choose_rgb_val
#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=return
#pragma HLS INTERFACE mode=axis register_mode=both port=outs register
#pragma HLS INTERFACE mode=axis register_mode=both port=ins register

    ap_axiu<32,1,1,1> dt;

    for(int row=0; row < row_size; row++){
#pragma HLS LOOP_TRIPCOUNT avg=4 max=4 min=4
        for(int col=0; col < col_size; col++){
#pragma HLS LOOP_TRIPCOUNT avg=32 max=32 min=32
            ins >> dt;
            switch(choose_rgb_val){
            case 0:
                dt.data &= 0xff;
                break;
            case 1:
                dt.data &= 0xff00;
                break;
            case 2:
                dt.data &= 0xff0000;
                break;
            default:
                dt.data &= 0xff000000;
            }
            outs << dt;
        }
    }
    return(0);
}


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

// choose_RGB_tb.cpp
// 2022/03/10 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"

int choose_RGB(hls::stream<ap_axiu<32,1,1,1> >& ins, hls::stream<ap_axiu<32,1,1,1> >& outs,
        int32_t row_size, int32_t col_size, int32_t choose_rgb_val);

const char INPUT_IMG_FILE[] = "RGB_test.png";
const char OUTPUT_IMG_FILE[] = "output.png";
const int32_t choose_rgb_val = 1;

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

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

    // ピクセルを入れる領域の確保
    std::vector<uint32_t> rd_buf(sizeof(uint32_t)*img.cols*img.rows);
    std::vector<uint32_t> out_buf(sizeof(uint32_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_buf[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];
        }
    }
    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (int32_t)rd_buf[(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;

            pix.keep = 0xf;
            pix.strb = 0xf;
            ins << pix;
        }
    }

    choose_RGB(ins, outs, img.rows, img.cols, choose_rgb_val);

    // out_buf に結果を代入
    for(int j=0; j<img.rows; j++){
        for(int i=0; i<img.cols; i++){
            outs >> pix;
            out_buf[j*img.cols+i] = pix.data;
        }
    }

    // wbmpf に結果を代入
    cv::Mat wbmpf(img.rows, img.cols, CV_8UC3);
    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);
            uint32_t rbg = out_buf[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;
        }
    }
    // 結果を画像ファイルへ出力する
    cv::imwrite(OUTPUT_IMG_FILE, wbmpf);
}


画像ファイルは、32 X 4 ピクセルの R, G, B, RGB 色の行を Pinta で作成した RGB_test.png を使用する。
image_format4PYNQ_1_220311.png

Vitis HLS 2021.2 の choose_RGB プロジェクトを示す。
image_format4PYNQ_2_220311.png

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

-I/usr/local/include


を設定した。
linker Flags に

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


を設定した。
image_format4PYNQ_3_220311.png

更に、 Synthesis をクリックして、 Top Function に choose_RGB を指定した。
image_format4PYNQ_4_220311.png
  1. 2022年03月11日 04:15 |
  2. PYNQ
  3. | トラックバック:0
  4. | コメント:0

閲覧中のウェブページ全体を単一のhtmlファイルとしてダウンロード可能な”SingleFile”を使ってみた

閲覧中のウェブページ全体を単一のhtmlファイルとしてダウンロード可能な”SingleFile”を使ってみた。

SingleFile を使って、KV260 の Jupyter Notebook の記述を 1 ファイルで保存しておきたい。
KV260 の Jupyter Notebook は KV260 を起動しているときしか見ることができなかったので、KV260 を起動していない時も見たいと思っていたが、HTML にすると複数ファイルになってしまうので、管理が面倒だったのだ。

Jupyter Notebook で右クリックし右クリックメニューから SingleFile -> SingleFile でページを保存を選択する。
SingleFile_1_220310.png

ダイアログがでるので、保存ディレクトリと名前を設定する。
SingleFile_2_220310.png

resizer_pl_jn.html ファイルができた。本当に単一のファイルになった。
SingleFile_3_220310.png

resizer_pl_jn.html ファイルを開くと Jupyter Notebook が再現できた。
SingleFile_4_220310.png
  1. 2022年03月11日 03:44 |
  2. パソコン関連
  3. | トラックバック:0
  4. | コメント:0

axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させる3(Jupyter Notebook で動作確認、波形を確認)

axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させる2(Vivado でビットファイルと hwh ファイルを生成)”の続き。

TKEEP と TSTRB は、入力された信号を出力するか、オール 1 にする必要がありそうだ。それを踏まえて、再度 axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させてみようということで、前回は、DMA_pow2_axis IP を使用して、Vivado 2021.2 の DMA_pow2_axis_i プロジェクトを再度、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビットファイルと hwh ファイルを生成した。今回は、Jupyter Notebook で動作を確認し、ILA ダッシュボードで波形を確認する。

Jupyter Notebook に DMA_pow2_wrapper.bit をアップロードして、名前を DMA_pow2.bit に変更した。
また、DMA_pow2.hwh もアップロードした。
これで Python3 コードを動作させたところ、動作した。。。良かった。
result には data の値を 2 乗した値が入っている。
Kria-PYNQ_153_220305.png
Kria-PYNQ_154_220305.png

python3 のコード部分のみを示す。

from pynq import allocate, Overlay

DMA_pow2_axis_i = Overlay("./DMA_pow2.bit")

dma = DMA_pow2_axis_i.axi_dma_0
pow2 = DMA_pow2_axis_i.DMA_pow2_axis_0

import numpy as np
data = allocate(shape=(10), dtype=np.uint32)
result = allocate(shape=(10), dtype=np.uint32)

for i in range(10):
    data[i] = i
print(data)
print(result)

def run_kernel():
    dma.sendchannel.transfer(data)
    dma.recvchannel.transfer(result)    
    pow2.write(0x00,0x01) # start
    dma.sendchannel.wait()
    dma.recvchannel.wait()
    
run_kernel()
print(result)

del data
del result


ILA ダッシュボードを起動して波形を観察する。
最初に、axi_dma_0 の MM2S の ARVALID の立ち上がりでトリガーを掛けた。
Kria-PYNQ_155_220305.png

10 バーストで 0 〜 9 まで DMA Read している。問題ない。

AXI4-Stream 部分を見たが、全く問題ない。うまく行っている。
Kria-PYNQ_156_220305.png

axi_dma_0 の S2MM の AWVALID の立ち上がりでトリガーを掛けた。
きちんと 10 バーストで DMA Write が終了している。これが正常の波形だ。
Kria-PYNQ_157_220305.png

結論として、Xilinx 社の DMA IP に AXI4-Stream で入力する時は、少なくとも TKEEP をケアする必要がある。入力した値を返すか、オール 1 にする。できれば、TSTRB も TKEEP と同様にケアしておいて損はない。
  1. 2022年03月09日 04:00 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させる2(Vivado でビットファイルと hwh ファイルを生成)

axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させる1(Vitis HLS で DMA_pow2_axis IP を作成)”の続き。

TKEEP と TSTRB は、入力された信号を出力するか、オール 1 にする必要がありそうだということで、それを踏まえて、再度 axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させてみようとうことで、前回は、TKEEP, TSTRB の処理を追加して、DMA_pow2_axis IP を再合成し、IP にした。今回は、その再合成された DMA_pow2_axis IP を使用して、Vivado 2021.2 の DMA_pow2_axis_i プロジェクトを再度、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビットファイルと hwh ファイルを生成する。

すでに””PYNQ を使って Python で手軽に FPGA を活用 (5)”をやってみる1(Vivado で DMA_pow2_axis_i プロジェクトを作成する)”で作成済みの DMA_pow2_axis_i プロジェクトがあるので、その DMA_pow2_axis ディレクトリの中身を前回作成した DMA_pow2_axis IP に入れ替えた。
Kria-PYNQ_149_220305.png

ブロック・デザインを示す。
Kria-PYNQ_150_220305.png

Address Editor 画面を示す。
Kria-PYNQ_151_220305.png

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

DMA_pow2_axis_i/DMA_pow2_axis_i.runs/impl_1 ディレクトリに DMA_pow2_wrapper.bit が生成された。
Kria-PYNQ_159_220308.png

DMA_pow2_axis_i/DMA_pow2_axis_i.gen/sources_1/bd/DMA_pow2/hw_handoff ディレクトリに DMA_pow2.hwh が生成された。
Kria-PYNQ_158_220308.png
  1. 2022年03月08日 04:05 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させる1(Vitis HLS で DMA_pow2_axis IP を作成)

AXI4-Stream におけるサイド・チャネル信号 TKEEP と TSTRB の扱いについて”と”AXI4-Stream の信号についてのまとめ”で AXI4-Stream の信号について、再度学習した。
TKEEP と TSTRB は、入力された信号を出力するか、オール 1 にする必要がありそうだということで、それを踏まえて、再度 axi_dma と DMA_pow2_axis を Kria-PYNQ で動作させてみようと思う。

まずは、Vitis HLS 2021.2 で DMA_pow2_axis を再度作成する。今回は、TKEEP と TSTRB もコードに書いて、入力された値を出力する。
DMA_pow2_axis.cpp を貼っておく。

// DMA_pow2_axis.cpp
// 2022/02/22 by marsee
// 2022/02/26 : Added tuser and tlast.
//

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

int DMA_pow2_axis(hls::stream<ap_axis<32,1,1,1> >& data,
        hls::stream<ap_axis<32,1,1,1> >& result){
#pragma HLS INTERFACE mode=s_axilite port=return
#pragma HLS INTERFACE mode=axis register_mode=both port=result register
#pragma HLS INTERFACE mode=axis register_mode=both port=data register
    ap_axis<32,1,1,1> dt, rlt;

    for(int i=0; i<10; i++){
        data >> dt;
        rlt.data = dt.data * dt.data;
        rlt.last = dt.last;
        rlt.user = dt.user;
        rlt.keep = dt.keep;
        rlt.strb = dt.strb;
        result << rlt;
    }
    return(0);
}


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

// DMA_pow2_axis_tb.cpp
// 2022/02/22 by marsee
// 2022/02/26 : Added tuser and tlast.
//

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

int DMA_pow2_axis(hls::stream<ap_axis<32,1,1,1> >& data,
        hls::stream<ap_axis<32,1,1,1> >& result);

int main(){
    using namespace std;
    hls::stream<ap_axis<32,1,1,1> > data;
    hls::stream<ap_axis<32,1,1,1> > result;
    ap_axis<32,1,1,1> dt;
    ap_axis<32,1,1,1> rlt;

    for(int i=0; i<10; i++){
        dt.data = i;
        if(i == 0)
            dt.user = 1;
        else
            dt.user = 0;
        if(i == 9)
            dt.last = 1;
        else
            dt.last = 0;
        dt.keep = 0xf;
        dt.strb = 0xf;
        data << dt;
    }

    DMA_pow2_axis(data, result);

    cout << endl;
    for(int i=0; i<10; i++){
        result >> rlt;
        cout << "data = " << i << " result = " << rlt.data << " user = " << rlt.user
                << " last = " << rlt.last << " keep = " << rlt.keep
                << " strb = " << rlt.strb << endl;
    }
    cout << endl;
    return(0);
}


Vitis HLS 2021.2 の DMA_pow2_axis プロジェクトを示す。
Kria-PYNQ_139_220304.png

C シミュレーションを行った。
Kria-PYNQ_140_220304.png

C コードの合成を行った。
Kria-PYNQ_141_220304.png

Latency は 13 クロックだった。良さそうだ。

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

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

Export RTL を行って、IP 化した。
  1. 2022年03月07日 03:47 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream の信号についてのまとめ

”AXI4-Stream におけるサイド・チャネル信号 TKEEP と TSTRB の扱いについて”を踏まえて、もう一度 AXI4-Stream 信号についてまとめておきたい。前回 AXI4-Stream について、まとめた資料は”AXI4-Stream のお勉強

参考資料は
AMBA®4 AXI4-Stream Protocol Version: 1.0 Specification
Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日
TKEEP and TSTRB combinations

AXI4-Stream の信号についての表を示す。
AXI4-Stream_1_220306.png

この表は、”AMBA®4 AXI4-Stream Protocol Version: 1.0 Specification”の”Table 2-1 Interface signals list”と、”Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日”の”表 A‐7 : AXI4‐Stream 信号のまとめ”を参考にした。

なお、TKEEP と TSTRB については、”TKEEP and TSTRB combinations”の表を翻訳してExcel 表を書いた。
AXI4-Stream_2_220306.png

Xilinx 社の DMA IP の S2MM に AXI4-Stream を入力するならば、TKEEPはオール1もしくは、入力をそのまま出力する。
上の表に合わせてTSTRBもオール1もしくは、入力をそのまま出力する。
  1. 2022年03月06日 15:27 |
  2. AXI4バス
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream におけるサイド・チャネル信号 TKEEP と TSTRB の扱いについて

今まで、TKEEP と TSTRB は”Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日”の 148 ページの”AXI4‐Stream 信号のま とめ”の”表 A-7”によるとオプションで Vitis HLS で IP コアを作成する時も C コードに処理を書かなかったのですが、特に DMA に入力する AXI4-Stream の TKEEP の処理については、入力をそのまま出力に返すか、オール 1 にしないと問題が起こる様だ。

Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日”の 148 ページの”AXI4‐Stream 信号のま とめ”の”表 A-7”とそこの TKEEP と TSTRB の記述を引用する。
TKEEP_TSTRB_4_220306.png

TKEEP : ザイリンクス IP では、 ヌル バイ ト の利用をパケット化されたストリームの末尾にある残りのバイ トのエンコードに限定しています。
ザイリンクスのエンドポイン ト IP では、 ストリーム中の先頭または途中のヌル バイ ト を示す目的で TKEEP を使用しません。
デフォルト: 1

TSTRB : 変更な し
通常、 TSTRB はスパース ストリーム のエンコードに使用 し ます。TSTRB は、 残りのパケットのエンコード だけを目的として使用すべきではありません。
デフォルト: TKEEP と同じ、 それ以外の場合は 1


TKEEP はストリームの末尾の残りバイト、つまり、32 ビット幅だったら、4 バイト単位の転送なので、例えば 43 バイトを転送する時は、最後のデータ転送ではリトル・エンディアンでは 0x7 になるということか?
TSTRB はバイト・イネーブルとして使用できるのだろう?

TKEEP と TSTRB はビデオ・ストリームでは有効でないということだ。
Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日”の 133 ページの ”7‐1 : ビデオ IP 分野における AXI4‐Stream 信号の使用”を引用する。
TKEEP_TSTRB_1_220306.png

DSP/ワイヤレス IP 分野でも TKEEP と TSTRB は有効でないようだ。
Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日”の 134 ページの”表 7‐2 : DSP/ワイヤレス IP 分野における AXI4‐Stream 信号の使用”を引用する。
TKEEP_TSTRB_2_220306.png

通信 IP 分野では、TKEEP のみオプションでサポートされる。
Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日”の 135 ページの”表 7‐3 : 通信 IP 分野における AXI4‐Stream 信号の使用”を引用する。
TKEEP_TSTRB_3_220306.png

AXI4‐Stream インフラストラクチャ IPのサブカテゴリとインフラストラクチャ IPで使用される信号を示す。殆どの信号がインフラストラクチャ IP では使用される様だ。
Vivado Design Suite AXI リファレンス ガイド UG1037 (v3.0) 2015 年 6 月 24 日”の 136 ページの”表 7‐4 : AXI4‐Stream インフラストラクチャ IP のサブ カテゴ リ”と”表 7‐5 : インフラストラクチャ IP 分野における AXI4‐Stream 信号の使用”を引用する。
TKEEP_TSTRB_5_220306.png
TKEEP_TSTRB_6_220306.png

つまり Xilinx 社の DMA では、TKEEP と TSTRB を使用するようだ。
TKEEP はケアが必要だが、”TKEEP and TSTRB combinations”によると、やはり TSTRB もケアしておいた方が良さそうだ。
どちらもオール 1 にするか? 入力された値をそのまま出力する。

”PYNQ を使って Python で手軽に FPGA を活用 (5)”をやってみる2(Jupyter Notebook で確認)”などで私が Vitis HLS で作成した DMA_pow2_axis IP はどうやら TKEEP と TSTRB を処理しなかったため動作しなかったようだ。
  1. 2022年03月06日 05:02 |
  2. AXI4バス
  3. | トラックバック:0
  4. | コメント:0

DMA_pow2 を Kria-PYNQ の jupyter Notebook で動作させる3(Jupyter Notebook で動作確認する)

DMA_pow2 を Kria-PYNQ の jupyter Notebook で動作させる2(Vivado でビットファイルと hwh ファイルを生成)”の続き。

前回は、Vivado 2021.2 で DMA_pow2_test32 プロジェクトを作成する。 DMA_pow2 IP を使用して DMA_pow2_test32 ブロック・デザインを作成し、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビットファイルと hwh ファイルを生成した。今回は、Jupyter Notebook にビットファイルと hwh ファイルをアップロードして、動作確認を行って成功した。また、System ILA を使って、波形を観察した。

Jupyter Notebook で my_notebook/DMA_pow2_test32 ディレクトリを作成し、DMA_pow2_test32.hwh と DMA_pow2_test32_wrapper.bit をアップロードした。
名前を DMApow2.hwh, DMApow2.bit に変更した。

DMApow2.ipynb ファイルを新規作成した。
コードを作って、実行したところ成功した。
Kria-PYNQ_133_220302.png
Kria-PYNQ_134_220302.png

Python3 コードのみを貼っておく。

from pynq import allocate, Overlay

DMApow2i = Overlay("DMApow2.bit")

DMA_pow2 = DMApow2i.DMA_pow2_0

import numpy as np
data = allocate(shape=(10), dtype=np.uint32, cacheable=1)
result = allocate(shape=(10), dtype=np.uint32, cacheable=1)

data[:] = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

print(data)
DMA_pow2.write(0x18,data.physical_address) # in_r
DMA_pow2.write(0x20,result.physical_address) # out_r

DMA_pow2.write(0x00,0x01) # start
while True:
    status = DMA_pow2.read(0x00)
    if status == 0xe:
        break
print(result)

del data
del result


allocate された data と result バッファの物理アドレスは、physical_address で参照できる。(Python productivity for Zynq (Pynq) v2.5 の Allocate を参照

System ILA を使用して、波形を観察した。
まずは、MM2S の DMA Read 波形を示す。
Kria-PYNQ_135_220302.png

ARLEN は 0x09 なので、10 バーストだった。

次に S2MM の DMA Write を示す。
こちらも AWLEN が 0x09 で 10 バーストで正常だ。
Kria-PYNQ_136_220302.png

データ転送部分を拡大した。データは正しい。
Kria-PYNQ_137_220302.png
  1. 2022年03月05日 04:43 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

DMA_pow2 を Kria-PYNQ の jupyter Notebook で動作させる2(Vivado でビットファイルと hwh ファイルを生成)

DMA_pow2 を Kria-PYNQ の jupyter Notebook で動作させる1(DMA pow2 IP を Vitis HLS で作成する)”の続き。

前回は、Vitis HLS 2021.2 で DMA_pow2 IP を作成した。今回は、 Vivado 2021.2 で DMA_pow2_test32 プロジェクトを作成する。 DMA_pow2 IP を使用して DMA_pow2_test32 ブロック・デザインを作成し、論理合成、インプリメンテーション、ビットストリームの生成を行って、ビットファイルと hwh ファイルを生成する。

Vivado 2021.2 の DMA_pow2_test32 プロジェクトを作成した。KV260 用だ。
Kria-PYNQ_138_220304.png

IP Catalog に Dma_pow2 IP を登録した。
Kria-PYNQ_118_220228.png

DMA_pow2_test32 ブロック・デザインを示す。
Kria-PYNQ_119_220228.png

Address Editor を示す。
Kria-PYNQ_120_220228.png

ラッパー Verilog HDL ファイルを作成し、論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary を示す。
Kria-PYNQ_122_220228.png

DMA_pow2_test32/DMA_pow2_test32.gen/sources_1/bd/DMA_pow2_test32/hw_handoff ディレクトリに DMA_pow2_test32.hwh が生成された。

DMA_pow2_test32/DMA_pow2_test32.runs/impl_1 ディレクトリに DMA_pow2_test32_wrapper.bit が生成された。
  1. 2022年03月04日 03:45 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

DMA_pow2 を Kria-PYNQ の jupyter Notebook で動作させる1(DMA pow2 IP を Vitis HLS で作成する)

”PYNQ を使って Python で手軽に FPGA を活用 (5)”を手順通りにやってみる”で、Vitis HLS 2021.2 で作成した DMA_pow2_axis IP を元々の stream_double.v に変更したら、Jupyter Notebook で動作した。今回は、Xilinx 社の AXI DMA IP を使用せずに、Vitis HLS で作成した AXI4 Master インターフェースの DMA_pow2 IP だとJupyter Notebook で動作するのか?を確認する。今回は、Vitis HLS 2021.2 で DMA_pow2 IP を作成する。

今までも作っていると思うが、Vitis HLS 2021.2 で DMA_pow2 プロジェクトを作成した。KV260 用だ。Part Selection の Part は xck26-sfvc784-2LV-c だ。
Kria-PYNQ_110_220228.png

64 ビットアドレスの DMA を禁止して、 32 ビットアドレスの DMA にする。
Vitis HLS の Solution メニューから Solution Settings... を選択する。
Solution Settings (solution1) ダイアログが開く。
config_interface を展開して、m_axi_addr64 の Value のチェックボックスのチェックを外した。
Kria-PYNQ_111_220228.png

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

// DMA_pow2.cpp
// 2021/11/17 by marsee

#include <stdint.h>

int DMA_pow2(int32_t *in, int32_t *out){
#pragma HLS INTERFACE mode=m_axi depth=10 port=out offset=slave
#pragma HLS INTERFACE mode=m_axi depth=10 port=in offset=slave
#pragma HLS INTERFACE mode=s_axilite port=return
    for(int i=0; i<10; i++){
        out[i] = in[i] * in[i];
    }
    return(0);
}


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

// DMA_pow2_tb.cpp
// 2021/11/17 by marsee
//

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

int DMA_pow2(int32_t *in, int32_t *out);

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

    DMA_pow2(data, result);

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


C シミュレーションを行った。
Kria-PYNQ_112_220228.png

C コードの合成を行った。
Kria-PYNQ_113_220228.png
Kria-PYNQ_114_220228.png

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

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

AXI4-Lite の波形を確認した。
終了条件は 0x00 番地を Read した時に 0x0e が読めたときのようだ。
Kria-PYNQ_117_220228.png

Export RTL を行った。
DMA_pow2/solution1/impl ディレクトリに export.zip が生成された。これに IP が圧縮されている。
  1. 2022年03月03日 04:09 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

”PYNQ を使って Python で手軽に FPGA を活用 (5)”を手順通りにやってみる

今までは、”PYNQ を使って Python で手軽に FPGA を活用 (5)”の”PL から PS に接続されたメモリを読み書きする”の stream_double.v を Vitis HLS 2021.2 で作った DMA_pow2_axis に置き換えて KV260 の PYNQ で実行して失敗していた。今回は、”PYNQ を使って Python で手軽に FPGA を活用 (5)”の”PL から PS に接続されたメモリを読み書きする”をそのままやってみる。つまり、DMA_pow2_axis の代わりに stream_double.v を使って、やってみよう。

stream_double.v を DMA_pow2_axis_i プロジェクトに追加した。プロジェクト名がおかしい気がするが、そこはスルーしてください。
Kria-PYNQ_123_220302.png

DMA_pow2 ブロック・デザインに stream_double.v をドラック&ドロップして、配線した。
Kria-PYNQ_124_220302.png

Address Editor 画面を示す。
Kria-PYNQ_125_220302.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。
Kria-PYNQ_126_220302.png

KV260 の PYNQ の Jupyter Notebook の DMA_pow2_axis_i ディレクトリに DMA_pow2_wrapper.bit と DMA_pow2.hwh ファイルをホスト・パソコンからアップロードした。DMA_pow2_wrapper.bit は DMA_pow2.bit と名前を変更した。
DMA_pow2.ipynb ファイルも DMA_pow2_axis_0 が無くなったので、修正した。
Python コードを実行すると成功した。やはり、stream_double.v だと大丈のようだ。
Kria-PYNQ_127_220302.png
Kria-PYNQ_128_220302.png

System ILA の波形を確認する。
最初に MM2S の AXI4 Master 波形から確認しよう。
Kria-PYNQ_129_220302.png

10 バースト Read で問題ないな。

MM2S と S2MM の AXI4-Stream のデータ転送も始まるが、バッファ数の分だけのようだ。4個分。
Kria-PYNQ_130_220302.png

S2MM の AXI4 Master の AWVALID でトリガを掛けた。800 クロック目がトリガ・ポイントに設定した。
その前に MM2S と S2MM の AXI4-Stream のトランザクションがある。
Kria-PYNQ_131_220302.png

S2MM の AXI4 Master インターフェースのアクセスを示す。
今回は、ちゃんと AWLEN の値が 0x09 で 10 バースト転送になっているのが分かる。
Kria-PYNQ_132_220302.png

Vitis HLS 2021.2 の DMA_pow2_axis IP は何処か AXI DMA IP に合わないところがあるのだろうか?
今度こそ DMA を Vitis HLS で書いて PYNQ での動作を確認してみよう。
  1. 2022年03月02日 04:40 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0
»