FC2カウンター FPGAの部屋 Ultra96実践勉強会で作ったVivado HLS 2018.2 のDMA Write IP
fc2ブログ

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

FPGAの部屋

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

Ultra96実践勉強会で作ったVivado HLS 2018.2 のDMA Write IP

11/3(土)にUltra96(UltraZed)実践勉強会に行ってきました。
運営のVengineer さん、一緒にグループになった皆さん、参加した皆さん、会場を提供していただいたAVNETの皆さん、ありがとうございました。
グループで行ったことは、自分のやりたかったことになりました。グループの皆さん、私のわがままに付き合っていただいて、ありがとうございました。
実際にやったことは、”Ultra96の低速拡張コネクタからカメラ入力”にまとめてあります。
Ultra96 の低速拡張コネクタをPMOD に変換する変換基板を作ったので、そこにMT9D111 カメラを接続してDisplayPort に出力したい。しかし、DisplayPort への出力方法が分からないので、とりあえずPSに接続されているDDR4 SDRAM にFrame Buffer として出力、PS のプロセッサでカメラの画像データを読み込みたいというものでした。
MT9D111 のインターフェースIP はすでに作ってあったので、それを使用して、Vivado HLS 2018.2 でカメラ・データのAXI4-Stream をDMA でFrame Buffer に書き込むDMA Write IP を作成しました。そして、それらを使用して、Vivado 2018.2 でカメラ・データを読み込んで、Frame Buffer に書くプロジェクトをコンパイルしたのですが、プリミティブ・エラーが出て終了になりました。これは、MT9D111 のインターフェースIP にZynq 用のDDR 出力のプリミティブが使用されていたためです。
なお、”UltraScale アーキテクチャ ライブラリ ガイド UG974 (v2018.1) 2018 年 4 月 4 日”の411 ページによるとODDRE1 を使えば良さそうです。

この Ultra96 勉強会でやったことをブログに書こうと思ったのですが、Windows 10 で開発したDMA Write IP をUbuntu 18.04 のVivado HLS 2018.2 でC シミュレーションしたところ動作しなかったです。これは、以前から知ってはいたのだが、デバックしてこなかったので、この際、テストベンチをOpenCV で書き換えようと思ってやっていたら、ハマってしまいました。
なかなかうまく行かないですね。
とりあえず途中経過を書いておきます。

(2018/11/21 : 追記)ここでのDMA_Write_sFB のソースコードは間違っています。正しく動作するコードは
Ultra96用PMOD拡張ボードでカメラ入力9(Vivado 2018.2のcam_test_182プロジェクト6)”と
Ultra96用PMOD拡張ボードでカメラ入力10(Vivado 2018.2のcam_test_182プロジェクト7)”をご覧ください。


Vivado HLS 2018.2 の DMA_Write_sFB プロジェクトです。
DMA_Write_sFB_1_181105.png

C シミュレーションをすると黒い画像ファイルが出力されちゃいます。
DMA_Write_sFB_2_181105.png

(2018/11/05:修正)ソースコードのバグフィックスをしました。
現在のDMA_Write_sFB_tb.cpp です。

// DMA_Write_sFB_tb.cpp
// 2018/11/03 by marsee
//

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

#include "DMA_Write_sFB.h"

//#define MAX_FRAME_NUMBER 3
#define MAX_FRAME_NUMBER 1 // for C/RTL CoSimulation

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

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.bmp");
    AXI_STREAM src_axi, dst_axi;

    // Mat フォーマットから AXI4 Stream へ変換、3画面分
    for(int i=0; i<MAX_FRAME_NUMBER; 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(MAX_FRAME_NUMBER * sizeof(int) * (MAX_WIDTH * MAX_HEIGHT))) == NULL){
        fprintf(stderr, "Can't allocate frame_buffer0 ~ 2\n");
        exit(1);
    }

    DMA_Wirte_sFB(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, MAX_FRAME_NUMBER);

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

    // dst[i] にframe_bufferから画像データをロード
    for(int i=0; i<MAX_FRAME_NUMBER; 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<MAX_FRAME_NUMBER; 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;
}


DMA_Write_sFB.h です。

// DMA_Write_sFB.h
// 2018/11/04 by marsee
//

#ifndef __DMA_WRITE_SFB_H__
#define __DMA_WRITE_SFB_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


DMA_Write_sFB.cpp を示す。

// DMA_Write_sFB.cpp
// DMA Write IP which can change the number of frame buffers
// 2018/11/02 by marsee
//

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

#include "DMA_Write_sFB.h"

int DMA_Wirte_sFB(AXI_STREAM & ins,
    volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
    volatile ap_uint<2> &active_frame, int max_fb){
#pragma HLS INTERFACE s_axilite port=max_fb
#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;
    volatile ap_int<32> *fb;
    int max_fb_chk;

    if (max_fb > 3)
        max_fb_chk = 3;
    else
        max_fb_chk = max_fb;

    LOOP_MAIN: for (int i=0; i<max_fb_chk; i++){
#pragma HLS LOOP_TRIPCOUNT min=1 max=3 avg=3
        switch (i){
        case 0:
            fb = fb0;
            break;
        case 1:
            fb = fb1;
            break;
        default: // i == 2
            fb = fb2;
            break;
        }
        active_frame = i;

        LOOP_WAIT: 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<MAX_HEIGHT; y++){
            LOOP_X: for (int x=0; x<MAX_WIDTH; x++){
#pragma HLS PIPELINE II=1
                if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                    ins >> pix;    // AXI4-Stream からの入力

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

  1. 2018年11月05日 05:43 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
https://marsee101.blog.fc2.com/tb.php/4346-5528a58a
この記事にトラックバックする(FC2ブログユーザー)