XilinxソフトIP AXI VDMA(Video Direct Memory Access)の使い方 (2)ドライバの使用方法

IP
スポンサーリンク

本記事の概要

概要

Xilinx ソフトIPコアAXI VDMAのドライバについて解説

以下の手順で、初期化とDMAのトランザクションを実行する:

  1. ハードウェア設定の定義:XAxiVdma_LookupConfig()
  2. ドライバとデバイスの初期化:XAxiVdma_CfgInitialize()
  3. DMAの設定:XAxiVdma_DmaConfig()
  4. DMAで読み出す or 書き込むバッファアドレスの指定:XAxiVdma_DmaSetBufferAddr()
  5. DMA動作の開始:XAxiVdma_DmaStart()

※3-5の関数は、XAxiVdma_StartReadFrame()XAxiVdma_StartWriteFrame()で代替可能。

AXI Video Direct Memory Access (VDMA)コアは、ビデオ生成部における中核をなすIPとなっていますが、使える機能が多様な分、初心者には敷居の高いIPと感じます。

前回の記事では、XilinxソフトIP AXI VDMA(Video Direct Memory Access)の基本機能について解説しました。

AXI VDMAのDMA動作を実行させるには、外部からAXI4-LITEを通じて、制御レジスタを変更する必要があります。

制御レジスタの変更方法は2つあります。

  1. AXI Traffic Generator IPを利用して、直接レジスタにパラメータを書き込む
  2. ドライバを活用して、プロセッサ(MicroblazeやZynqのARMコアなど)からレジスタを変更する(プロセッサのあるシステムのみ)

今回は、プロセッサのあるシステム向けに、AXI VDMAを外部から制御するためのドライバの使用方法について解説していきたいと思います。

この記事の対象読者
  • FPGAの開発に興味のある学生
  • Vitisを用いたアプリケーション開発は未経験のエンジニア
  • FPGAでの動画像処理を行いたいエンジニア
ひがし
ひがし

それでは、興味のある方はぜひ最後までご覧ください!

AXI VDMA(Video Direct Memory Access)の基本機能

AXI VDMAは、メモリとビデオインターフェースとの間に配置して、メモリとの画像の送受信をDMA方式で実行するためのIPコアです。

DMAの制御レジスタの変更は、図のようにAXI4-LITEポートを通じて実行します。

冒頭に述べたとおり、AXI VDMAのレジスタの変更方法には、主に2つあり、今回はプロセッサを通じて制御信号を送受信する方法について解説したいと思います。

プロセッサでデバイス(今回はAXI VDMA)を動かすためのドライバは、ザイリンクスSDK (Software Development Kit)開発環境やVitis™ 統合ソフトウェアプラットフォームで配布されています。
また、githubにも公開されています。ライブラリ内のヘッダファイル”xaxivdma.h“を読み込むことによって、ドライバをSDK上で扱うことができるようになります。

コード

まず、最初にコードから。

VDMA_settingという関数を作成し、この関数の中で必要な設定をすべて行うようにしています。

#include "xaxivdma.h"
#include "vga_modes.h" //Digilient社より提供。VideoModeの定義。

#define VGA_VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID
#define DEMO_MAX_FRAME (1920*1080*4)
#define DEMO_STRIDE (1920 * 4)

/* 構造体の定義 */
XAxiVdma vdma;
XAxiVdma_DmaSetup   vdmasetup;

/* メモリの定義 */
u8  frameBuf[DEMO_MAX_FRAME] __attribute__((aligned(0x20)));

int main(void){
    int Status;
    /* 他のIPの設定*/
    /* 略 */

    /* VDMAの設定 */
    Status = VDMA_setting(VGA_VDMA_ID, &vdma, &vdmasetup, frameBuf, DEMO_STRIDE, VMODE_640x480);

    /* frameBufへの書き込み */
    /* 略 */
    return 0;
}

int VDMA_setting(u16 VDMA_Id, XAxiVdma *vdma, XAxiVdma_DmaSetup *vdmasetup, u8 *framePtr, u32 stride, VideoMode vMode)
{
    /*---------------------------------------------------------------*/
    /* 初期化*/
    /*---------------------------------------------------------------*/
    /* ハードウェア設定を格納した構造体の定義 */
    XAxiVdma_Config *vdmaConfig;
    vdmaConfig = XAxiVdma_LookupConfig(VDMA_ID);
    
    /* ドライバとデバイスの初期化 */
    int Status;
    Status = XAxiVdma_CfgInitialize(vdma, vdmaConfig, vdmaConfig->BaseAddress);

    /*---------------------------------------------------------------*/
    /* DMA トランザクション*/
    /*---------------------------------------------------------------*/
    /* フレームを読み書きするのに必要な情報を設定 */
    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;
    vdmasetup->FrameStoreStartAddr[0]   = (u32) framePtr;

    /* DMAの設定 */
    Status = XAxiVdma_DmaConfig(vdma, XAXIVDMA_READ, vdmasetup);

    /* DMAで読み出すバッファの設定 */
    Status = XAxiVdma_DmaSetBufferAddr(vdma, XAXIVDMA_READ, vdmasetup->FrameStoreStartAddr);

    /* DMA動作の開始 */
    Status = XAxiVdma_DmaStart(vdma, XAXIVDMA_READ);
    
    /* 送受信するフレームを特定のフレームに固定 */
    Status = XAxiVdma_StartParking(vdma, 0, XAXIVDMA_READ);
    
    return  XST_SUCCESS;

以下では、各コードについて解説していきます。

AXI VDMAのドライバの使用方法

xaxivdma.h“のマニュアルはドキュメンテーションから参照することができます。
今回はドキュメンテーションの内容をできる限りわかりやすく解説していきたいと思います。

実行フロー例

想定環境

今回は、割り込みは考えず、メモリからRGB画像(画素数640×480;階調8bit)を読み出す場合を例に挙げて実行フローを示したいと思います。なお、各動画モードは、Digilent社から提供されるvga_modes.hを読み込むことによって、楽に設定できるので、includeしておきましょう。

画像のイメージ図をここに記します。
1ピクセルあたりRGBが8bitずつで、合計24bitになります。
なお、AXI VDMAではデータを32bitの倍数ずつ読み出したり、書き出したりしているので、空のメモリ(No data)を8bit最後に付け加え、1ピクセル合計で32bitとします。

ひがし
ひがし

Xilinxのビデオ関連のIPでは、AXI-StreamではRGBではなく、なぜか”RBG”の順の配列になっています。今回はXilinxのルールに合わせて、メモリの配列をRBGの順にしています。

画像を格納するメモリは以下のように定義しました:

/* メモリの定義 */
u8  frameBuf[DEMO_MAX_FRAME] __attribute__((aligned(0x20)));

初期化からDMA動作までのフローチャートは以下の通りです。

初期化

まず、AXI VDMAのIPコアとドライバの初期化までを行います。
このとき、使用する関数は次の2つです。

/* ハードウェア設定を格納した構造体の定義 */
XAxiVdma_Config *vdmaConfig;
vdmaConfig = XAxiVdma_LookupConfig(VDMA_ID);

/* ドライバとデバイスの初期化 */
int Status;
XAxiVdma vdma;
Status = XAxiVdma_CfgInitialize(vdma, vdmaConfig, vdmaConfig->BaseAddress);
ハードウェア設定を格納した構造体の定義(XAxiVdma_LookupConfig)

まず、XAxiVdma_LookupConfigという関数により、デバイスIDを引数にハードウェア設定を返り値として出力します。
このハードウェア設定へのポインタをvdmaConfigとしました。

XAxiVdma_Config構造体には、AXI VDMAのハードウェア設定がまとめて格納されています。
例えば、Vivado上で設定したAXI VDMAの各バスのデータ幅やバッファの深さなど、ハードウェア設定に関する情報を設定ファイルからまとめて一つの構造体にしてくれます。

ひがし
ひがし

ザイリンクスSDK開発環境やVitis™ 統合ソフトウェアプラットフォームでは、最初にVivadoで作ったハードウェアの設定を読み込んだと思います。
このとき読み込んだ設定は、自動でヘッダファイルとしてまとめられています。XAxiVdma_LookupConfigは、このヘッダファイルから、「AXI VDMAの動作に必要な設定を参照して、返り値として出力する」、そのような関数になります。

XAxiVdma_LookupConfig()

機能
 デバイスのハードウェア設定を返り値として出力する。
パラメータ
 ・AXI VDMAのデバイスID (システム内に複数のAXI VDMAコアが存在する場合に識別)
返り値
 ・デバイスのハードウェア設定が格納された構造体へのポインタ。
 もし、デバイスIDが見つからなければ、Nullポインタ。

ドライバとデバイスの初期化(XAxiVdma_CfgInitialize)

次に、ハードウェア設定に基づいて、デバイスとドライバを初期化していきます。

ひがし
ひがし

事前にXAxiVdmaという構造体を新たに定義していますが、ドライバを動かすときに必要なソフトウェア側の設定を格納している構造体のようです。
ドキュメンテーションでは、ドライバインスタンスと呼ばれています。

インスタンスの概念については、以下の記事を参照ください。

関数XAxiVdma_CfgInitialize()を実行すると、ドライバインスタンスに、ドライバを動かすのに必要な各種設定が格納されます。

XAxiVdma_CfgInitialize()

機能:
 デバイスとドライバを初期化する。
パラメータ:
 ・ドライバインスタンスへのポインタ
 ・ハードウェア設定が格納された構造体へのポインタ
 ・デバイスのベースアドレス
返り値:
 ・XST_SUCCESS ; ハードウェアの初期化にすべて成功した場合
 ・XST_FAILURE;ハードウェアの初期化の過程で失敗した場合

DMAトランザクション

初期化が完了したので、DMA動作を実行していきます。
ソースコード例を記載します。

/* フレームを読み書きするのに必要な情報を設定 */
    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;
    vdmasetup->FrameStoreStartAddr[0]   = (u32) framePtr;

    /* DMAの設定 */
    Status = XAxiVdma_DmaConfig(vdma, XAXIVDMA_READ, vdmasetup);

    /* DMAで読み出すバッファの設定 */
    Status = XAxiVdma_DmaSetBufferAddr(vdma, XAXIVDMA_READ, vdmasetup->FrameStoreStartAddr);
    
    /* DMA動作の開始 */
    Status = XAxiVdma_DmaStart(vdma, XAXIVDMA_READ);
    
    /* 送受信するフレームを特定のフレームに固定 */
    Status = XAxiVdma_StartParking(vdma, 0, XAXIVDMA_READ);
フレームの読み書きに必要な情報を設定

まず、フレームを読み出すのに必要な情報を設定していきます。
XAxiVdma_DmaSetupという構造体には、画像の画素数やストライド(横1列分のデータサイズ。今回の例では、640(横ピクセル数)×4(RBG+空)×1byte)などの情報が格納されています。

また、画像のフレームが格納されているメモリのアドレス(pFrames)も指定し、さっき定義した構造体の変数に代入します。

以下では、2つの関数XAxiVdma_DmaConfigXAxiVdma_DmaSetBufferAddrとを用いて、このXAxiVdma_DmaSetupという構造体をAXI4-LITE経由でAXI VDMAのIPコアに送信していきます。

DMAの設定

まず、さきほど定義した構造体XAxiVdma_DmaSetupをAXI4-LITE経由でAXI VDMAのIPコアに送信し、DMAとIPコアとの間のチャネルの設定をコンフィグレーションします。

使う関数はXAxiVdma_DmaConfig()というものです。

XAxiVdma_DmaConfig()

機能:
 設定ファイルを用いてDMAのチャネルをコンフィグレーションする。
パラメータ:
 ・ドライバインスタンスへのポインタ
 ・DMAのチャンネルのRead or Write方向
 ・セットアップファイルを格納した構造体へのポインタ
返り値:
 ・XST_SUCCESS ; ハードウェアの初期化にすべて成功した場合
 ・XST_DEVICE_BUSY ; DMAチャネルが使用中
 ・XST_INVAID_PARAM ; パラメータ設定が正しくない (例:バッファアドレスなど)
 ・XST_DEVICE_NOT_FOUND ; チャネルが正しくない

DMAで読み出すフレームバッファのアドレスを指定する

次に、どのアドレスのメモリから画像データを読み出したり書き出すのかを、AXI VDMA IPコアに伝えます。
このとき、使う関数がXAxiVdma_DmaSetBufferAddrというものです。

XAxiVdma_DmaSetBufferAddr()

機能:
 DMAで読み出すバッファアドレスを指定する。
パラメータ:
 ・ドライバインスタンスへのポインタ
 ・DMAのチャンネルのRead or Write方向
 ・送受信するメモリのアドレス
返り値:
 ・XST_SUCCESS ; ハードウェアの初期化にすべて成功した場合
 ・XST_DEVICE_BUSY ; DMAチャネルが使用中
 ・XST_INVAID_PARAM ; パラメータ設定が正しくない (例:バッファアドレスなど)
 ・XST_DEVICE_NOT_FOUND ; チャネルが正しくない

DMA動作の開始

以上までが設定ファイルの読み込みです。

ここまで完了したら、いよいよDMA動作を開始します。
関数XAxiVdma_DmaStart()を実行すれば、メモリから画像が随時読み出されていきます。

先程DMAに送信したバッファアドレスのメモリに画像データを書き込めば、AXI VDMA IPコアから画像がAXI4-Stream形式で出力されます。

XAxiVdma_DmaStart()

機能:
 DMA動作の実行。
パラメータ:
 ・ドライバインスタンスへのポインタ
 ・DMAのチャンネルのRead or Write方向
返り値:
 ・XST_SUCCESS ; ハードウェアの初期化にすべて成功した場合
 ・XST_DEVICE_BUSY ; DMAチャネルが使用中
 ・XST_INVAID_PARAM ; パラメータ設定が正しくない (例:バッファアドレスなど)
 ・XST_DEVICE_NOT_FOUND ; チャネルが正しくない

DMAの設定から開始までを一気に行うには

なお、DMAの設定とバッファアドレスの指定、DMA動作の開始までの一連の関数を一つにまとめた関数も用意されています。
XAxiVdma_StartReadFrame()XAxiVdma_StartWriteFrame()という関数を1つ使うだけで処理を終わらせることができるので使い勝手は良さそうです。

2021/4/3追記

以下のコードのように、XAxiVdma_DmaConfigとXAxiVdma_DmaSetBufferAddrとXAxiVdma_DmaStartの3つの関数をXAxiVdma_StartReadFrame()に置き換えて、静止画をディスプレイに出力できるかどうか実験してみました。結果は、問題なくVDMA動作が行われました。

    ///* DMAの設定 */
    //Status = XAxiVdma_DmaConfig(vdma, XAXIVDMA_READ, vdmasetup);
    //
    ///* DMAで読み出すバッファの設定 */
    //Status = XAxiVdma_DmaSetBufferAddr(vdma, XAXIVDMA_READ, vdmasetup->FrameStoreStartAddr);
    //
    ///* DMA動作の開始 */
    //Status = XAxiVdma_DmaStart(vdma, XAXIVDMA_READ);
    
    Status = XAxiVdma_StartReadFrame(vdma, vdmasetup);

参考文献

Xilinx社のAPIドキュメンテーションを参考にしました。

axivdma: Main Page

コメント