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

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

FPGAの部屋

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

垂直方向、水平方向が反転している画像のDMA(vhflip_dma_write)

2019年最初の記事はやはりVivado HLS の記事を書こうと思う。

ZYBO Z7-20でのMNISTの実装にOV5642を使用する2”で垂直方向はフリップしたのだが、水平方向はミラー無しにした。これは、Vivado HLS でカメラ画像をDMA するIP を作成する際に性能を出すためだったのだが、最初に水平方向をミラーありにして、アドレスが減りながらDMA する場合を見ていこう。

Ultra96用PMOD拡張ボードでカメラ入力9(Vivado 2018.2のcam_test_182プロジェクト6)”のDMA_Write_sFB を少し改造して、vhflip_dma_write.cpp を作成した。

最初にvhflip_dma_write.h を示す。

// vhflip_dma_write.h
// 2019/01/01 by marsee
//

#ifndef __VHFLIP_DMA_WRITE_H__
#define __VHFLIP_DMA_WRITE_H__

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

#define MAX_WIDTH 800
#define MAX_HEIGHT 600

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

#endif


vhflip_dma_write.cpp を示す。

// vhflip_dma_write.cpp
// 2019/01/01 by marsee
//

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

#include "vhflip_dma_write.h"

int vhflip_dma_write(AXI_STREAM & ins,
    volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
    volatile ap_uint<2> &active_frame){
#pragma HLS INTERFACE s_axilite port=active_frame
#pragma HLS INTERFACE m_axi depth=480000 port=fb0 offset=slave
#pragma HLS INTERFACE m_axi depth=480000 port=fb1 offset=slave
#pragma HLS INTERFACE m_axi depth=480000 port=fb2 offset=slave
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=return

    AP_AXIU32 pix;
    int max_fb_chk;

    active_frame = 0;

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

    LOOP_Y0: for (int y=MAX_HEIGHT-1; y>=0; y--){ // vflip
        LOOP_X0: for (int x=MAX_WIDTH-1; x>=0; x--){ // hflip
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            fb0[(y*MAX_WIDTH)+x] = pix.data;
        }
    }

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

    LOOP_Y1: for (int y=MAX_HEIGHT-1; y>=0; y--){ // vflip
        LOOP_X1: for (int x=MAX_WIDTH-1; x>=0; x--){ // hflip
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            fb1[(y*MAX_WIDTH)+x] = pix.data;

        }
    }

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

    LOOP_Y2: for (int y=MAX_HEIGHT-1; y>=0; y--){ // vflip
        LOOP_X2: for (int x=MAX_WIDTH-1; x>=0; x--){ // hflip
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            fb2[(y*MAX_WIDTH)+x] = pix.data;
        }
    }
end:
    return(0);
}


vhflip_dma_write_tb.cpp を示す。

// vhflip_dma_write_tb_tb.cpp
// 2019/01/01 by marsee
//

#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include "hls_opencv.h"

#include "vhflip_dma_write.h"

int vhflip_dma_write(AXI_STREAM & ins,
    volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
    volatile ap_uint<2> &active_frame);

#define NUM_FRAME_BUFFER 3

int main()
{
    using namespace cv;

    AXI_STREAM ins;
    AP_AXIU32 pix;

    ap_uint<2> active_frame;
    int *frame_buffer;

    // OpenCV で 画像を読み込む
    Mat src = imread("bmp_file0_vhflip.bmp");
    AXI_STREAM src_axi, dst_axi;

    // Mat フォーマットから AXI4 Stream へ変換、3画面分
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        cvMat2AXIvideo(src, src_axi);
        for (int y=0; y<MAX_HEIGHT; y++){
            for (int x=0; x<MAX_WIDTH; x++){
                src_axi >> pix;
                ins << pix;
            }
        }
    }

    // frame buffer をアロケートする、3倍の領域を取ってそれを3つに分ける
    if ((frame_buffer =(int *)malloc(NUM_FRAME_BUFFER * sizeof(int) * (MAX_WIDTH * MAX_HEIGHT))) == NULL){
        fprintf(stderr, "Can't allocate frame_buffer0 ~ 2\n");
        exit(1);
    }

    vhflip_dma_write(ins, (volatile ap_int<32> *)frame_buffer,
        (volatile ap_int<32> *)&frame_buffer[MAX_WIDTH * MAX_HEIGHT],
        (volatile ap_int<32> *)&frame_buffer[2 * (MAX_WIDTH * MAX_HEIGHT)],
        active_frame);

    // AXI4 Stream から Mat フォーマットへ変換
    // dst は宣言時にサイズとカラー・フォーマットを定義する必要がある
    Mat dst[3];
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        dst[i] = Mat(src.rows, src.cols, CV_8UC3);
    }

    // dst[i] にframe_bufferから画像データをロード
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        Mat_<Vec3b> dst_vec3b = Mat_<Vec3b>(dst[i]);
        for(int y=0; y<dst[i].rows; y++){
            for(int x=0; x<dst[i].cols; x++){
                Vec3b pixel;
                int rgb = frame_buffer[i*(MAX_WIDTH * MAX_HEIGHT)+y*MAX_WIDTH+x];
                pixel[0] = (rgb & 0xff); // blue
                pixel[1] = (rgb & 0xff00) >> 8; // green
                pixel[2] = (rgb & 0xff0000) >> 16; // red
                dst_vec3b(y,x) = pixel;
            }
        }
    }

    // DMAされたデータをBMPフィルに書き込む
    char output_file[] = "dma_result0.bmp";
    for (int i=0; i<NUM_FRAME_BUFFER; i++){
        switch (i){
            case 0:
                strcpy(output_file,"dma_result0.bmp");
                break;
            case 1:
                strcpy(output_file,"dma_result1.bmp");
                break;
            case 2:
                strcpy(output_file,"dma_result2.bmp");
                break;
        }
        // Mat フォーマットからファイルに書き込み
        imwrite(output_file, dst[i]);
    }

    //free(frame_buffer);
    return 0;
}


Vivado HLS 2018.3 のvhflip_dma_write プロジェクトを示す。
vflip_dma_write_1_190102.png

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

C シミュレーションでは、垂直方向にフリップして、水平方向もミラーした画像の bmp_file0_vhflip.bmp を垂直方向、水平方向共に反転して3回DMA する。
bmp_file0_vhflip.bmp を示す。
vflip_dma_write_3_190102.jpg

csim ディレクトリを示す。
vflip_dma_write_5_190102.png

3つのDMA されたBMP ファイルの1つの dma_result0.bmp を示す。dma_result1.bmp 、dma_result2.bmp も同じ画像だった。
vflip_dma_write_4_190102.jpg

C コードの合成結果を示す。
vflip_dma_write_6_190102.png
vflip_dma_write_7_190102.png

合成結果は問題なく 1 クロックで 1 ピクセルをDMA できている。この結果は問題ない。

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

レイテンシは 3600102 クロックだった。これは、合成のときのレイテンシの約 2.5 倍となった。
その理由を探るためにC/RTL 協調シミュレーションの波形を見ていこう。
vflip_dma_write_9_190102.png

まずは全体の波形を示す。AWLEN が 00 で単一転送だということが分かる。バースト転送になっていない。これは、AXI インターフェースのバースト転送の際のアドレスは単調増加のみで単調減少のモードは無いので、バースト転送にならないということである。
波形を拡大してみよう。
vflip_dma_write_10_190102.png

アドレスの発行1つ当たり、1つのデータ転送が行われているのが分かる。非常に効率悪い。性能が出ないパターンだ。
この状況は避けたいということで、”ZYBO Z7-20でのMNISTの実装にOV5642を使用する2”で垂直方向はフリップしたのだが、水平方向はミラー無しにしたのだった。

次回は、垂直方向はフリップしたのだが、水平方向はミラー無しのときのVivado HLS の実装を見ていこう。
  1. 2019年01月02日 07:51 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0