ビジョンライブラリの画像処理結果をディスプレイ表示【(2)実装】

ビジョンライブラリ
スポンサーリンク

本記事の概要

概要

Vitisビジョンライブラリから高位合成したIPをエクスポートし、画像処理した結果をディスプレイに表示するZynqアプリケーションを紹介。
用例のcustomconvに含まれるFilter2D_accelを例にラプラシアンフィルタをZynqに実装する。

ビジョンライブラリを利用して高位合成した画像処理IPをZynqに実装しようと思います。
前回の記事では、画像処理IPをVivadoにインポートし、ブロックデザインを設計する方法を紹介しました。

今回はその続きで、作成したアプリケーションプロジェクトのソースコードについて解説し、実際にディスプレイに表示されるか確認してみようと思います。

この記事の対象読者
  • Vitisビジョンライブラリを初めて扱う
  • ビジョンライブラリで高位合成したIPのアプリケーションプロジェクトでの設定方法や使い方を知りたい

Vitisプロジェクト

プロジェクトの構成

では、Vitisでプロジェクトを作成していきましょう。

Vitisのプロジェクトは2つのプロジェクトで構成されています。プラットフォームプロジェクトがデバイスとアプリケーションを繋ぐインターフェースの役割をしています。

Vitis上に作成されるプロジェクト
  • プラットフォームプロジェクト
    • ハードウェアデザイン(XSAファイル)
    • ドメイン… BSP (Board Support Package)やOSなどのハードウェアを動作させるための基本ソフトウェアのグループ
  • システムプロジェクト
    • アプリケーションプロジェクト…ドメイン上で動作する、”Hello World”のような各アプリケーションソフトウェアのグループ

プラットフォームプロジェクトはVivadoで作成したXSAファイルをもとに、Vitisが自動で生成します。XSAファイルからプラットフォームプロジェクトを作成する方法は以下の記事をご覧ください。

本記事では、このプラットフォームプロジェクト上で動作するアプリケーションプロジェクトのソースコードを紹介し、どういう指針でソースコードを組んだかをまとめようと思います。

アプリケーションプロジェクトのソースコード

構成

アプリケーションプロジェクトのソースコードの構成は次の通りとしました。

アプリケーションプロジェクトの”src”フォルダ内のファイルの構成

テスト画像testimg.hは次のサンプルビットマップ画像(解像度160×120)をヘッダファイルに変換したものです。

作成したソースコード

今回、新しく作成したvideo_filter2d_out.c, video_filter2d_out.h, testimg.hのダウンロードリンクはこちらです。

“Filter2D ディスプレイ表示デモのVitisソースコード” をダウンロード

video_filter2d_out.zip – 6947 回のダウンロード – 86.25 KB

『ダウンロードせずに見たい!』という方は、video_filter2d_out.c, video_filter2d_out.hに限り、次の+マークをクリックして、中をご覧いただければと思います。

ソースコード
video_filter2d_out.hファイル
#ifndef VIDEO_FILTER2D_OUT_H_
#define VIDEO_FILTER2D_OUT_H_

/* ------------------------------------------------------------ */
/*				Include File Definitions						*/
/* ------------------------------------------------------------ */
#include "xil_types.h"
#include "xil_cache.h"
#include "vga_modes.h"
#include "./dynclk/dynclk.h"
#include "xaxivdma.h"
#include "xfilter2d_accel.h"
#include "xvtc.h"
#include "testimg.h"

/* ------------------------------------------------------------ */
/*					Miscellaneous Declarations					*/
/* ------------------------------------------------------------ */
#define DYNCLK_BASEADDR     XPAR_AXI_DYNCLK_0_BASEADDR
#define VGA_VDMA_ID         XPAR_AXIVDMA_0_DEVICE_ID
#define DISP_VTC_ID         XPAR_VTC_0_DEVICE_ID
#define FILTER_ID           XPAR_XFILTER2D_ACCEL_0_DEVICE_ID

#define VGA_PIXEL_BYTE      (4)
#define IMG_PIXEL_BYTE      (3)

#define FILTER_SIZE         (3 * 3)
#define SHIFT               (8)

#define VRAM    ((volatile unsigned char *)  0x10000000)
#define img_in  ((volatile unsigned char *)  0x10200000)
#define img_out ((volatile unsigned char *)  0x10210000)
#define filter  ((volatile unsigned short *) 0x10220000)

#define DISPLAY_NUM_FRAMES  3

/* ------------------------------------------------------------ */
/*                グローバル変数                                 */
/* ------------------------------------------------------------ */
XAxiVdma            vdma_inst;
XVtc                vtc_disp;
XFilter2d_accel     filter2d;

u8      *pFrames[DISPLAY_NUM_FRAMES];

/* ------------------------------------------------------------ */
/*                    グローバル関数                             */
/* ------------------------------------------------------------ */
int dynclk_setting(u32 dynClkAddr, VideoMode vMode);
int VTC_send_setting(XVtc *vtcptr, VideoMode vMode);
int VDMA_send_setting(XAxiVdma *vdmaptr, u8 *framePtr[DISPLAY_NUM_FRAMES], u32 stride, VideoMode vMode);
int filter2d_setting(XFilter2d_accel *filter2d_inst, u32 imgin_adr, u32 filter_adr, u32 imgout_adr);

/************************************************************************/

#endif /* VIDEO_FILTER2D_OUT_H_ */
video_filter2d_out.cファイル

#include "video_filter2d_out.h"

/* ------------------------------------------------------------ */
/*                Procedure Definitions                         */
/* ------------------------------------------------------------ */
int main(void)
{
    int Status;

    //-----------------------------------------------------
    /* S. 送信側*/
    //-----------------------------------------------------
    //-----------------------------------------------------
    /* S.1 Filter2dの設定*/
    //-----------------------------------------------------
    // S.1.0 初期化
    Status = XFilter2d_accel_Initialize(&filter2d, FILTER_ID);
    if (Status != XST_SUCCESS)
    {
        xil_printf("Filter 2d Initialization failed %d\r\n", Status);
        return XST_FAILURE;
    }

    // S.1.1 Filter2Dの設定
    Status = filter2d_setting(&filter2d, (u32)(img_in), (u32)(filter), (u32)(img_out));
    if (Status != XST_SUCCESS)
    {
        xil_printf("Filtering Setting failed %d\r\n", Status);
        return XST_FAILURE;
    }

    //-----------------------------------------------------
    /* S.2 dynclk の設定*/
    //-----------------------------------------------------
    Status = dynclk_setting(DYNCLK_BASEADDR, VMODE_640x480);
    if (Status != XST_SUCCESS)
    {
        xil_printf("Dynamic Clock Setting failed %d\r\n", Status);
        return XST_FAILURE;
    }

    //-----------------------------------------------------
    /* S.3 VDMAの読み出し側の設定 */
    //-----------------------------------------------------
    // S.3.0 初期化
    XAxiVdma_Config *vdmaConfig;
    vdmaConfig = XAxiVdma_LookupConfig(VGA_VDMA_ID);
    if (!vdmaConfig)
    {
        xil_printf("No video DMA found for ID \n");
        return XST_FAILURE;
    }
    
    // S.2 VDMAの読み出し設定
    // 画像を格納するフレームバッファのポインタを取得
    for (int i = 0; i < DISPLAY_NUM_FRAMES; i++)
    {
        pFrames[i] = (u8*)VRAM;
    }

    Status = XAxiVdma_CfgInitialize(&vdma_inst, vdmaConfig, vdmaConfig->BaseAddress);
    if (Status != XST_SUCCESS)
    {
        xil_printf("VDMA initialization failed %d\r\n", Status);
        return XST_FAILURE;
    }

    // S.3 VDMAの読み出し設定
    Status = VDMA_send_setting(&vdma_inst, pFrames, VMODE_640x480.width * VGA_PIXEL_BYTE, VMODE_640x480);
    if (Status != XST_SUCCESS)
    {
        xil_printf("VDMA Setting failed %d\r\n", Status);
        return XST_FAILURE;
    }

    //-----------------------------------------------------
    /* S.4 送信側VTCの設定 */
    //-----------------------------------------------------
    // S.4.0 初期化
    XVtc_Config *vtcConfig;
    vtcConfig = XVtc_LookupConfig(DISP_VTC_ID);
    Status = XVtc_CfgInitialize(&vtc_disp, vtcConfig, vtcConfig->BaseAddress);
    if (Status != XST_SUCCESS)
    {
        return XST_FAILURE;
    }
    
    // S.4 送信側VTCのタイミング生成の設定
    Status = VTC_send_setting(&vtc_disp, VMODE_640x480);
    if (Status != XST_SUCCESS)
    {
        return XST_FAILURE;
    }

    //-----------------------------------------------------
    /* S.5 フレームバッファへの入力 */
    //-----------------------------------------------------
    // S.5.0 img_inへの入力
    u32 iPixelAddr3;
    u32 xcoi, ycoi;
    for(xcoi = 0; xcoi < X_BMP_SIZE; xcoi++)
    {
        
        for(ycoi = 0; ycoi < Y_BMP_SIZE; ycoi++)
        {
            iPixelAddr3 = (xcoi + ycoi * X_BMP_SIZE) * IMG_PIXEL_BYTE ;
            img_in[iPixelAddr3]       = bmp_file_array[ycoi][xcoi][0];   //blue
            img_in[iPixelAddr3 + 1]   = bmp_file_array[ycoi][xcoi][1];   //green
            img_in[iPixelAddr3 + 2]   = bmp_file_array[ycoi][xcoi][2];   //red
        }
    }

    // S.5.1 filterへの入力
    filter[0] = 0  << SHIFT;
    filter[1] = 1  << SHIFT;
    filter[2] = 0  << SHIFT;
    filter[3] = 1  << SHIFT;
    filter[4] = -4 << SHIFT;
    filter[5] = 1  << SHIFT;
    filter[6] = 0  << SHIFT;
    filter[7] = 1  << SHIFT;
    filter[8] = 0  << SHIFT;

    // S.5.2 キャッシュ更新
    Xil_DCacheFlush();

    //-----------------------------------------------------
    /* S.6 Filter2D 実行 */
    //-----------------------------------------------------
    XFilter2d_accel_Start(&filter2d);
    while(XFilter2d_accel_IsDone(&filter2d) != 1){};

    //-----------------------------------------------------
    /* S.7 img_outをVRAMに格納 */
    //-----------------------------------------------------
    // S.7.0 VRAMへの入力
    u32 iPixelAddr4;
    u32 xred, yred;
    for(ycoi = 0; ycoi < VMODE_640x480.height; ycoi++)
    {
        for(xcoi = 0; xcoi < VMODE_640x480.width; xcoi++)
        {
            xred = (int)(xcoi * X_BMP_SIZE / VMODE_640x480.width );
            yred = (int)(ycoi * Y_BMP_SIZE / VMODE_640x480.height);
            iPixelAddr4 = (xcoi + ycoi * VMODE_640x480.width) * VGA_PIXEL_BYTE;
            iPixelAddr3 = (xred + yred * X_BMP_SIZE         ) * IMG_PIXEL_BYTE;

            VRAM[iPixelAddr4]       = img_out[iPixelAddr3]    ;   //blue
            VRAM[iPixelAddr4 + 1]   = img_out[iPixelAddr3 + 1];   //green
            VRAM[iPixelAddr4 + 2]   = img_out[iPixelAddr3 + 2];   //red
        }
    }
    // S.7.1 キャッシュ更新
    Xil_DCacheFlush();

    while(1){};

    return 0;
}

//-----------------------------------------------------
/* S.1 filter2d の設定*/
//-----------------------------------------------------
int filter2d_setting(XFilter2d_accel *filter2d_inst, u32 imgin_adr, u32 filter_adr, u32 imgout_adr)
{
    // S.1.0 Filter2dのコントロールレジスタへの入力
    //control_s_axi (parameter)
    XFilter2d_accel_Set_shift(filter2d_inst, SHIFT);
    XFilter2d_accel_Set_rows(filter2d_inst, Y_BMP_SIZE);
    XFilter2d_accel_Set_cols(filter2d_inst, X_BMP_SIZE);

    // S.1.1 Filter2dの配列入力
    //control_r_s_axi (pointer)
    XFilter2d_accel_Set_img_in(filter2d_inst, imgin_adr);
    XFilter2d_accel_Set_filter(filter2d_inst, filter_adr);
    XFilter2d_accel_Set_img_out(filter2d_inst, imgout_adr);

    return  0;
}

//-----------------------------------------------------
/* S.2 dynclk の設定*/
//-----------------------------------------------------
int dynclk_setting(u32 dynClkAddr, VideoMode vMode)
{
    ClkConfig clkReg;
    ClkMode clkMode;

    ClkFindParams(vMode.freq, &clkMode);

    if (!ClkFindReg(&clkReg, &clkMode))
    {
        xdbg_printf(XDBG_DEBUG_GENERAL, "Error calculating CLK register values\n\r");
        return XST_FAILURE;
    }
    
    ClkWriteReg(&clkReg, dynClkAddr);
    ClkStart(dynClkAddr);

    return XST_SUCCESS;
}

//-----------------------------------------------------
/* S.3 送信側VDMAの設定 */
//-----------------------------------------------------
int VDMA_send_setting(XAxiVdma *vdmaptr, u8 *framePtr[DISPLAY_NUM_FRAMES], u32 stride, VideoMode vMode)
{
    int i;
    int Status;
    XAxiVdma_DmaSetup   vdmasetup;

    // S.3.1 フレームを読み書きするのに必要な情報を設定
    vdmasetup.FrameDelay                = 0;
    vdmasetup.EnableCircularBuf         = 1;
    vdmasetup.EnableSync                = 0;
    vdmasetup.PointNum                  = 0;
    vdmasetup.EnableFrameCounter        = 0;
    vdmasetup.VertSizeInput             = vMode.height;
    vdmasetup.HoriSizeInput             = (vMode.width) * 4;
    vdmasetup.FixedFrameStoreAddr       = 0;
    vdmasetup.Stride                    = stride;
    for (i = 0; i < DISPLAY_NUM_FRAMES; i++)
    {
        vdmasetup.FrameStoreStartAddr[i]  = (u32)  framePtr[i];
    }

    // S.3.2 VideoDMA読み出し開始
    Status = XAxiVdma_StartReadFrame(vdmaptr, &vdmasetup);
    if (Status != XST_SUCCESS)
    {
        xil_printf("VDMA Start Read Frame failed %d\r\n", Status);
        return XST_FAILURE;
    }

    // S.3.3 送受信するフレームを特定のフレーム(0)に固定
    Status = XAxiVdma_StartParking(vdmaptr, 0, XAXIVDMA_READ);
    if (Status != XST_SUCCESS)
    {
        xil_printf("VDMA Start Parking failed %d\r\n", Status);
        return XST_FAILURE;
    }

    return  XST_SUCCESS;
}

//-----------------------------------------------------
/* S.4 送信側VTCの設定 */
//-----------------------------------------------------
int VTC_send_setting(XVtc *vtcptr, VideoMode vMode)
{
    XVtc_Timing vtcTiming;
    XVtc_SourceSelect SourceSelect;

    // S.4.1 タイミングを制御するのに必要な情報を設定
    vtcTiming.HActiveVideo  = vMode.width                     ;    /**< Horizontal Active Video Size */
    vtcTiming.HFrontPorch   = vMode.hps    - vMode.width      ;    /**< Horizontal Front Porch Size */
    vtcTiming.HSyncWidth    = vMode.hpe    - vMode.hps        ;    /**< Horizontal Sync Width */
    vtcTiming.HBackPorch    = vMode.hmax   - vMode.hpe + 1    ;    /**< Horizontal Back Porch Size */
    vtcTiming.HSyncPolarity = vMode.hpol                      ;    /**< Horizontal Sync Polarity */
    vtcTiming.VActiveVideo  = vMode.height                    ;    /**< Vertical Active Video Size */
    vtcTiming.V0FrontPorch  = vMode.vps    - vMode.height     ;    /**< Vertical Front Porch Size */
    vtcTiming.V0SyncWidth   = vMode.vpe    - vMode.vps        ;    /**< Vertical Sync Width */
    vtcTiming.V0BackPorch   = vMode.vmax   - vMode.vpe + 1    ;    /**< Horizontal Back Porch Size */
    vtcTiming.V1FrontPorch  = vMode.vps    - vMode.height     ;    /**< Vertical Front Porch Size */
    vtcTiming.V1SyncWidth   = vMode.vpe    - vMode.vps        ;    /**< Vertical Sync Width */
    vtcTiming.V1BackPorch   = vMode.vmax   - vMode.vpe + 1    ;    /**< Horizontal Back Porch Size */
    vtcTiming.VSyncPolarity = vMode.vpol                      ;    /**< Vertical Sync Polarity */
    vtcTiming.Interlaced    = 0                               ;    /**< Interlaced / Progressive video */

    memset((void *)&SourceSelect, 0, sizeof(SourceSelect));
    SourceSelect.VBlankPolSrc       = 1;
    SourceSelect.VSyncPolSrc        = 1;
    SourceSelect.HBlankPolSrc       = 1;
    SourceSelect.HSyncPolSrc        = 1;
    SourceSelect.ActiveVideoPolSrc  = 1;
    SourceSelect.ActiveChromaPolSrc = 1;
    SourceSelect.VChromaSrc         = 1;
    SourceSelect.VActiveSrc         = 1;
    SourceSelect.VBackPorchSrc      = 1;
    SourceSelect.VSyncSrc           = 1;
    SourceSelect.VFrontPorchSrc     = 1;
    SourceSelect.VTotalSrc          = 1;
    SourceSelect.HActiveSrc         = 1;
    SourceSelect.HBackPorchSrc      = 1;
    SourceSelect.HSyncSrc           = 1;
    SourceSelect.HFrontPorchSrc     = 1;
    SourceSelect.HTotalSrc          = 1;

    // S.4.2 VTCトランザクションの開始
    XVtc_SelfTest(vtcptr);
    XVtc_RegUpdateEnable(vtcptr);
    XVtc_SetGeneratorTiming(vtcptr, &vtcTiming);
    XVtc_SetSource(vtcptr, &SourceSelect);
    XVtc_EnableGenerator(vtcptr);

    return XST_SUCCESS;
}

ソースコードの解説

フローチャート

まずは、ソースコード全体を概観できるようにフローチャートで流れを説明します。

ブロックデザインに配置した各IPの初期設定を最初に行い、次にメモリ内のフレームバッファへ画像と空間フィルタを入力しました。メモリへの格納が完了したら、Filter2D処理を実行します。Filter2D処理の完了を待ち、完了後にFilter2D_accel IPからの出力バッファをVRAMに格納します。

VDMA処理では、VRAMからディスプレイに常に静止画を出力し続けているので、無限ループ中に空間フィルタリング処理後の画像がディスプレイに表示され続けます。

S.1~S.4 初期設定

アプリケーションプロジェクト内のmain関数では、ブロックデザイン上に配置したIPの初期設定を実施しています。

AXI4-Liteを介して初期設定が必要なIP
  1. Filter2d_accel IP
  2. dynclk IP
  3. VDMA IP
  4. VTC IP

このうち、dynclk IP, VDMA IP, VTC IPのビデオ出力系の3つのIPは以前の記事『【外部映像送受信(2)】Zynq上で外部映像を送受信するアプリケーション(送信系の作成)』で解説しています。

これらのIPの設定もそれはそれで複雑で難しいのですが、今回の記事は新たに追加したFilter2d_accel IPに焦点を絞って、その使い方を解説します。

Filter2d_accel IPの設定

Filter2d_accel IPでは、control_s_axicontrol_r_s_axiの2つのバスから制御信号を入力します。

各バスで設定すべきFilter2d_accel IPに必要な初期設定は

  • コントロールレジスタの設定 (control_s_axiバス経由)
    • 空間フィルタのビットシフト量:SHIFT
    • 入出力画像の行数:ROWS
    • 入出力画像の列数:COLS
  • 各配列のポインタ (control_r_s_axiバス経由)
    • 入力画像 img_inのポインタ
    • 出力画像 img_outのポインタ
    • 空間フィルタ filterのポインタ

です。

この必要な初期設定を、filter2d_settingという関数を使って実行しました。
XSAファイルを読み込んで生成されるドライバの”xfilter2d_accel.h”では、すでにXFilter2d_accel_Set_**というFilter2d_accel IPを操作するためのAPI関数が、用意されているのでどんどん活用していきましょう。

//-----------------------------------------------------
/* S.1 filter2d の設定*/
//-----------------------------------------------------
int filter2d_setting(XFilter2d_accel *filter2d_inst, u32 imgin_adr, u32 filter_adr, u32 imgout_adr)
{
    // S.1.0 Filter2dのコントロールレジスタへの入力
    //control_s_axi (parameter)
    XFilter2d_accel_Set_shift(filter2d_inst, SHIFT);
    XFilter2d_accel_Set_rows(filter2d_inst, Y_BMP_SIZE);
    XFilter2d_accel_Set_cols(filter2d_inst, X_BMP_SIZE);

    // S.1.1 Filter2dの配列入力
    //control_r_s_axi (pointer)
    XFilter2d_accel_Set_img_in(filter2d_inst, imgin_adr);
    XFilter2d_accel_Set_filter(filter2d_inst, filter_adr);
    XFilter2d_accel_Set_img_out(filter2d_inst, imgout_adr);

    return  0;
}

main関数では、Filter2d_accel IPをインスタンス化した後、この関数を呼び出して初期設定を完了します。

    //-----------------------------------------------------
    /* S.1 filter2dの設定*/
    //-----------------------------------------------------
    // S.1.0 初期化
    Status = XFilter2d_accel_Initialize(&filter2d, FILTER_ID);
    if (Status != XST_SUCCESS)
    {
        xil_printf("Filter 2d Initialization failed %d\r\n", Status);
        return XST_FAILURE;
    }

    // S.1.1 Filter2Dの設定
    Status = filter2d_setting(&filter2d, (u32)(img_in), (u32)(filter), (u32)(img_out));
    if (Status != XST_SUCCESS)
    {
        xil_printf("Filtering Setting failed %d\r\n", Status);
        return XST_FAILURE;
    }

S.5 フレームバッファへの入力

次に、ビットマップ画像の画素値をimg_in配列に格納、ラプラシアンフィルタをfilter配列に格納します。

メモリ空間の設計

img_in, img_out, filter, VRAMがどのようにメモリ空間の領域を占めているかを述べておきましょう。

まず、ヘッダファイルで、メモリ上で各配列を格納している領域のベースアドレスを、次のように宣言しています。

#define VRAM    ((volatile unsigned char *)  0x10000000)
#define img_in  ((volatile unsigned char *)  0x10200000)
#define img_out ((volatile unsigned char *)  0x10210000)
#define filter  ((volatile unsigned short *) 0x10220000)

メモリ空間が次の図のような構成となるようにベースアドレスを選んでいます。

S.5.0 配列img_inへの入力

メモリ空間の構成を踏まえて、配列img_inに画像を入力していきます。
bmp_file_arrayは入力画像をヘッダーファイル化した配列です。bmp_file_arrayからimg_inに画像を渡します。

    // S.5.0 img_inへの入力
    u32 iPixelAddr3;
    u32 xcoi, ycoi;
    for(xcoi = 0; xcoi < X_BMP_SIZE; xcoi++)
    {
        
        for(ycoi = 0; ycoi < Y_BMP_SIZE; ycoi++)
        {
            iPixelAddr3 = (xcoi + ycoi * X_BMP_SIZE) * IMG_PIXEL_BYTE ;
            img_in[iPixelAddr3]       = bmp_file_array[ycoi][xcoi][0];   //blue
            img_in[iPixelAddr3 + 1]   = bmp_file_array[ycoi][xcoi][1];   //green
            img_in[iPixelAddr3 + 2]   = bmp_file_array[ycoi][xcoi][2];   //red
        }
    }

Filter2D_accel IPはメモリから入力画像の画素値を順にAXI4経由で読み出していきますが、前回の記事で紹介したようなメモリマップに従ってあらかじめ画素値を詰めておく必要があります。そこで、メモリマップに従い、図のようにメモリの1バイトごとに画像のBGR値が対応していることを踏まえ、Vitisアプリケーションのプログラムにおいてもimg_in配列に値を入力します。

S.5.1 配列filterへの入力

同様に、空間フィルタfilterにも値を入力します。Filter2D_accel IPで空間フィルタを読み出すときに、2byteで一要素を読み出していたことに注意します。そこで、ヘッダーファイル上で、filter配列のデータ型を指定するときは、unsigned charではなくunsigned shortを指定します。

#define filter  ((volatile unsigned short *) 0x10220000)

この点に注意しておき、filter配列に値を代入します。値は高位合成のCsimで指定したものと同じ4近傍のラプラシアンフィルタを代入しました。SHIFT値はヘッダー上で8ビットとしています。

    // S.5.1 filterへの入力
    filter[0] = 0  << SHIFT;
    filter[1] = 1  << SHIFT;
    filter[2] = 0  << SHIFT;
    filter[3] = 1  << SHIFT;
    filter[4] = -4 << SHIFT;
    filter[5] = 1  << SHIFT;
    filter[6] = 0  << SHIFT;
    filter[7] = 1  << SHIFT;
    filter[8] = 0  << SHIFT;

この配列とメモリの構成を図にするとこのようになります。

最後に、配列に値を詰め終えたらキャッシュを更新し、キャッシュコヒーレンスを保ちます。

    // S.5.2 キャッシュ更新
    Xil_DCacheFlush();

S.6 Filter2D 実行

前準備が整ったので、いよいよFilter2Dを実行します。

    //-----------------------------------------------------
    /* S.6 Filter2D 実行 */
    //-----------------------------------------------------
    XFilter2d_accel_Start(&filter2d);
    while(XFilter2d_accel_IsDone(&filter2d) != 1){};

Filter2d_accelのAPI関数XFilter2d_accel_Startを呼び出し、空間フィルタリングを開始します。フィルタリング処理が完了すると、XFilter2d_accel_IsDoneが1になるので、それまで処理をwhileループで待ちます。

どちらの関数もFilter2d_accel IPにおけるアドレス0x00のap_ctrlのステータスを示すコントロールレジスタを参照しています。このレジスタに値を書き込んだり読み出したりして、Filter2d_accel IPを操作・監視しています。このap_ctrlに関連するレジスタの説明は、以前のC/RTL協調シミュレーションの記事に記載しています。

S.7 img_outをVRAMに格納

Filter2d処理が完了し、空間フィルタリング処理後の画像がメモリのimg_out配列に格納されているはずです。
img_out配列をそのままVDMA伝送しディスプレイ表示してもよかったのですが、画像とディスプレイの解像度やチャネル数が異なっていたので、VRAMという配列に格納しなおしています。

その移し替えの処理を次の通り実行しました。

    //-----------------------------------------------------
    /* S.7 img_outをVRAMに格納 */
    //-----------------------------------------------------
    // S.7.0 VRAMへの入力
    u32 iPixelAddr4;
    u32 xred, yred;
    for(ycoi = 0; ycoi < VMODE_640x480.height; ycoi++)
    {
        for(xcoi = 0; xcoi < VMODE_640x480.width; xcoi++)
        {
            xred = (int)(xcoi * X_BMP_SIZE / VMODE_640x480.width );
            yred = (int)(ycoi * Y_BMP_SIZE / VMODE_640x480.height);
            iPixelAddr4 = (xcoi + ycoi * VMODE_640x480.width) * VGA_PIXEL_BYTE;
            iPixelAddr3 = (xred + yred * X_BMP_SIZE         ) * IMG_PIXEL_BYTE;

            VRAM[iPixelAddr4]       = img_out[iPixelAddr3]    ;   //blue
            VRAM[iPixelAddr4 + 1]   = img_out[iPixelAddr3 + 1];   //green
            VRAM[iPixelAddr4 + 2]   = img_out[iPixelAddr3 + 2];   //red
        }
    }
    // S.7.1 キャッシュ更新
    Xil_DCacheFlush();

解像度が640×480(ディスプレイ)と160×120(画像)とで4×4倍異なるので、それをNearest-Neighborで補間しています。配列VRAMとimg_outの対応関係がややこしく、もう少しすっきりとディスプレイ表示したいところです。

最後に、メモリ(VRAM)を更新したので、またキャッシュコヒーレンスを保ちます。

以上で、処理は終了です。

動作確認

実装

ダウンロードや作成した一連のファイルをworkspaceのsrcフォルダに移動させたらビルドし、実装まで行いました。
実装までの手順の詳細を改めて[+]マーク以下に記載しておきます。

①プロジェクトのビルド
エクスプローラ内のプラットフォームプロジェクト(hdmi_out_system_wrapper)を選択し、右クリックします。[Build Project]を選択すると、ビルドが始まります。

次に、エクスプローラ内のシステムプロジェクト(hdmi_out_application_system)を選択し、右クリックします。同様に[Build Project]を選択すると、ビルドが始まります。

②コンフィグレーション
回路基板をホストPCにつなぎます。
つないで電源を入れたら、PC側からFPGAへのbitstreamファイルの書き込み(コンフィグレーション)とアプリケーションソフトウェアのDDRへの書き込みを実行します。

“hdmi_out_application_system”を右クリックし、[Run as]>[Launch Hardware]を選択すると、コンフィグレーションとソフトウェアの書き込みが実施されます。

VGA出力結果

評価ボードZyboにソフトウェアの書き込みを行い、VGA端子をディスプレイに接続します。

VGA端子からディスプレイに画像を出力してみました。

問題なく、ラプラシアンフィルタで空間フィルタリング処理した、次の画像がVGA出力されていることを確認できました!

まとめ

本記事では、高位合成したFilter2D_accel IPを実際にアプリケーションプロジェクト上で実行するためのプログラムを紹介しました。静止画の出力なので、そこまで難しいタイミング制御を考えなくてよかったので楽に実装を進めることができました。
もし動画やカメラ出力を表示しようとすると、もう少し複雑になるように思います。動画の出力にもチャレンジしてみたいですね。

最後までご覧いただきありがとうございました!

スポンサーリンク


参考:開発環境

環境
  • 開発用PC: Windows 10, 64bit
    • Vitis コア開発キット – 2021.2
    • Vivado – 2020.2

コメント