メモ置き場

メモ置き場です.開発したものや調べたことについて書きます.

[tex: ]

ZYNQでAXI DMAを試してみる

ZYNQで超単純なAXI DMAを試してみる.

AXI DMAとはAXIバスを使ったDirect Memory Accessのことで,PSを介さずにPL部分からメモリ(ZYBO-Z7の場合はDDR3メモリ)にデータを転送する方法のことである.

XilinxからはAXI Direct Memory AccessというIPが提供されていて,これを使うとDMAを実装できるようになる.今回はこのIPを使ってAXI DMAを試してみた.
こちらのページを参考にしてみた.
Lauri's blog | AXI Direct Memory Access

AXI DMA IPについて

IPのリファレンスはこちら.
LogiCORE IP AXI DMA v7.1 製品ガイド Vivado Design Suite PG021 2014 年 4月2日
IPは図のような構成をしているらしい.各ポート/ブロックについてまとめておく.
f:id:okchan08:20190315132129p:plain
ブロック図を見てみると,読み込み/書き込みで非常に対象的な構造をしているIPであることに気づく.どうもAXI DataMoverというIPを読み込み/書き込みで2個くっつけて,Scatter/Gatherを追加したようなIPに見える.各ブロック図の機能についてまとめた.

名前 機能
DataMover データ転送ロジック.AXI4 Memory Mappedで読んだデータをAXI4-Streamに変換して出力する,あるいはAXI4 Streamで受けたものをAXI4 Memory Mappedでメモリに書き込む
MM2S(S2MM) Cntl/Sts Logic 読み出し(書き込み)の制御とステータス情報を保持している
Registers AXI DMAの設定に使用されるレジスタ.DMAでどのメモリアドレスから読み出すか,データの長さはどれくらいか,などを決めているはず
Scatter/Gather 使っていないのでよくわからない.断片化されたメモリからまとめて読み出す/書き込むといったときに使うみたい

次に各ブロックから出ている矢印についてまとめた.

名前 機能
AXI4 Memory Map Read メモリからデータを読み込む信号
AXI4 Stream Master(MM2S) Memory Mapで読み出したデータがStreamに変換され出力される.Memory-Mapped to Streamの略
AXI4 Control Steram(MM2S) Memory Map ReadのステータスをStreamとして出力
AXI4-Lite RegistersにAXI4-Liteで読み書きするためのもの
AXI4 Memory Map Write/Read Scatter/Gather(SG)でメモリに読み書きする場合の信号.SGの場合は別途ポートが準備されるみたい
AXI4 Stream(S2MM) Memory Map Writeの制御信号をAXI4-Stream形式で与える
AXI4-Stream Slave (S2MM) Stream形式のデータをPL中のロジックなどから受ける
AXI4 Memory Map Write メモリへデータを書き込む信号.AXI4-Stream Slave (S2MM)からのデータが移される

SG以外の機能はAXI DataMover IPとほとんど変わらないみたい.State情報やControlなどはDataMoverのリファレンスに詳しく書いてあった*1

VivadoでIPコアを使うには,IP Catalogを開いてAXI DMAなどと検索するとAXI Direct Memory AccessというIPが見つかるのでそれを使えばよい.今回は下のような設定でIPを使用する.
f:id:okchan08:20190315135355p:plain
SGを使うかどうかなどのチェックボックスがある.必要があればチェックを入れる.

  • Width of buffer length register

DMAで転送するデータの長さ.ここをn bitにすると2^n - (Data Width Byte)バイトまで転送できる.デフォルトでは14bitになっていて,32bit(=4Byte)で転送する場合は2^14 - 4 = 16380バイトまで転送できる.最大で8 MiB - 8 バイトまで行けるらしい.転送速度とかにはそんなに影響しないらしい.

  • Address Width

Memory Map Read/Writeする場合のアドレス長だと思う.

  • Memory Map Data Width

AXI4でデータを転送する際のデータバスの幅.64bitにしてZYNQ IPのHPかACPポートに接続するとスループットが良くなって早くなるらしい.

  • Stream Data Width

AXI4-Streamで他のIPに接続する場合のデータバスの幅.

  • Max Burst Size

バースト転送の最大サイズ.例えば16にすると,16個分のデータを1回のDMAで転送する.
IPコアから出ているポートについてまとめる.

ポート名 形式 用途
S_AXI_LITE AXI4_Lite AXI経由でDMAコアの制御や転送済みバイト数を読み出したりするポート.ブロック図だとRegisterから出てくるAXI4-Liteに相当
S_AXIS_S2MM AXI4-Stream AXI4-StreamでDMAしたいデータを入力する.AXI4-Stream Slave (S2MM)に相当
s_axi_lite_aclk S_AXI_LITE用のクロック
m_axi_mm2s_aclk M_AXI_MM2S用のクロック
m_axi_s2mm_aclk M_AXI_S2MM用のクロック
axi_resetn IP全体のリセット.LOWでリセットがかかる
M_AXI_MM2S AXI4 Memory Mapでメモリからデータを読み出す.AXI4 Memory Map Readに相当
M_AXI_S2MM AXI4 AXI4-Streamで受けたデータをMemory Mapに変換して出力.AXI4 Memory Map Writeに相当
M_AXIS_MM2S AXI4-Stream M_AXI_MM2Sで読み出したデータをStream形式に変換して出力.AXI4 Stream Master (MM2S)に相当

作成したデザイン

AXI DMAを使ったデザインを作ってみた.
f:id:okchan08:20190316192135p:plain

Zynqの設定について

AXI DMAを使うためにZynqの設定を行う.DMAのバスはZynqのHigh Performance(HP)ポートに接続して使うため,そのポートを有効にする.
f:id:okchan08:20190316210907p:plain
PS-PL ConfigurationのタブからHP Slave AXI Interfaceを開いてS AXI HP0 interfaceにチェックを入れる.HPポートは合計で4個まで使えるようだが,具体的にどう使うのかはわからない*2
ZynqのHPポートはAXI3バスになっていて,バースト転送の上限が16になっているなど,AXI4とは性能が異なるみたい.でもつなぐ際はバスの方でうまいことやってくれるみたいなので,ユーザー側からは気にしなくても良いんだと思う.
次にDMAに使うクロックを別途用意する.この操作は必ずしも必要でないと思うが,高速なデータ転送を行うにはそれなりに早いクロックを準備したほうがいいはず.
f:id:okchan08:20190316211237p:plain
Clock ConfigurationのタブからPL Fabric Clocksを開きFCLK_CLK1に新しくチェックを入れる.また周波数を今回は150 MHzに設定した.以上の設定が終わったらOKで変更を反映させる.

また今回は割り込みを使わない.DMA転送が完了したときに割り込みを発生させるといったこともできるらしいが追々やっていこうと思う.

DMAのパスについて

ZYBO-Z7用に作成した今回のデザインでは,

  • DDR3からDMA転送→PLへデータを送る→PL上でLoopback→転送データをDMAでDDR3に転送

のようにした.上記のblock designを見てもらうと,DMA IPコアのM_AXIS_MM2SとS_AXIS_S2MMが繋がっているのがわかると思う.この2つのポートをつなぐことでループバックを実現している.将来的にDMAを使った回路を作成する場合は,M_AXIS_MM2SとS_AXIS_S2MMに作成したIPを繋ぐといった拡張をすれば良い.

AXIバスのアドレス

特に設定してないが,ブロックデザインを作成した段階で数のように設定されていた.
f:id:okchan08:20190316212311p:plain
S_AXI_LITEのアドレス(ここでは0x4040_0000+オフセット)に適当な値を書き込むことで,DMAのコントロールを行うことができる.今回使うレジスタを以下に示す.

レジスタ オフセット 説明
MM2S_DMACR 0x00 MM2S DMA制御
MM2S_DMASR 0x04 MM2S DMAステータス
MM2S_SA 0x18 MM2S DMAで読み出すメモリ初めのアドレスを格納
MM2S_LENGTH 0x28 MM2Sの転送長さ(バイト)
S2MM_DMACR 0x30 S2MM DMA制御
S2MM_DMASR 0x34 S2MM DMAステータス
S2MM_SA 0x48 S2MM DMAで書き込むメモリの先頭アドレスを格納
S2MM_LENGTH 0x58 S2MMの転送長さ(バイト)

それぞれ32bitのレジスタである.制御レジスタやステータス・レジスタはそれぞれのビットに書き込んだり読み出したりすることでDMAの制御と状態を知ることができる.例としてMM2S_DMACRレジスタの割当を示す.
f:id:okchan08:20190316215211p:plain
詳細についてはDMAのリファレンスを参考のこと.

Linux側の設定

Linux側で使用するメモリ領域を制限したり
ZED BoardでPLを自作した場合のDMAのやりかた: なひたふJTAG日記
DMA用のデバイスドライバを準備したり
Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ - Qiita
といった準備が本当は必要らしい.今回は超単純なDMAをやってみるだけなので,/dev/memを使ってDMAを行うことにする.

Device Treeの設定

Vivadoで作成したプロジェクトをもとに,デバイスツリーを作成する.この記事を参考のこと.pl.dtsiをもとにOverlay用のデバイスツリーを作成する.

/dts-v1/;
/plugin/;
/ {
    fragment@0 {
        target-path = "/amba_pl";
        __overlay__ {
            axi_dma_0: dma@40400000 {
                #dma-cells = <1>;
                clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
                clocks = <&clkc 15>, <&clkc 16>, <&clkc 16>;
                //compatible = "xlnx,axi-dma-7.1", "xlnx,axi-dma-1.00.a";
                compatible = "generic-uio";
                reg = <0x40400000 0x10000>;
                xlnx,addrwidth = <0x20>;
                xlnx,sg-length-width = <0xe>;
                dma-channel@40400000 {
                    compatible = "xlnx,axi-dma-mm2s-channel";
                    dma-channels = <0x1>;
                    xlnx,datawidth = <0x20>;
                    xlnx,device-id = <0x0>;
                };
                dma-channel@40400030 {
                    compatible = "xlnx,axi-dma-s2mm-channel";
                    dma-channels = <0x1>;
                    xlnx,datawidth = <0x20>;
                    xlnx,device-id = <0x0>;
                };
            };
        };
    };  
};

このデバイスツリーを使ってOverlayしておく./proc/device-tree/amba_pl/にdma@40400000が見えるようになる.dmaノードの下には2つのdma-channelというノードがついている.それぞれDMA転送を行うチャンネルで,compatibleの値を見ればわかるように40400000がメモリ→PLの転送,40400030がPL→メモリの転送を行う.前者のアドレスの値はZynqで設定されたS_AXI_LITEのアドレスと一致している.後者はこのアドレスにS2MMのオフセット0x30を足したものに一致する.DMAの実行では,このアドレスを叩きに行って各種制御などを行う.ZynqのS_AXI_HP0のオフセットは,MM2S_SAで指定したメモリアドレスに対するオフセットという意味だと思う.つまり今の設定ではMM2S_SAで設定したアドレスがDMA転送されるが,オフセットを例えば0x50とかにすると設定したアドレス+0x50から読まれるとかになるんじゃないかな.ちゃんと調べないといけないが.痕多分アドレスは4の倍数とかになってないとダメな気がする.

DMAの実行

ソフトウェアを作って実際にDMAをやってみる.ここにあるCのコードをそのままZynq上に持っていきコンパイルする./dev/memに対して読み書きを行うので,実行はroot権限をつけて行う.

aho@zynq$ ls
dma_test.c
aho@zynq$ gcc dma_test.c
aho@zynq$ sudo ./a.out
Source memory block:      78563412 44332211 02000000 03000000 04000000 05000000 06000000 07000000 
Destination memory block: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
Resetting DMA
Stream to memory-mapped status (0x00000001@0x34): halted

Memory-mapped to stream status (0x00000001@0x04): halted
Halting DMA
Stream to memory-mapped status (0x00000001@0x34): halted
Memory-mapped to stream status (0x00000001@0x04): halted
Writing destination address
Stream to memory-mapped status (0x00000001@0x34): halted
Writing source address...
Memory-mapped to stream status (0x00000001@0x04): halted
Starting S2MM channel with all interrupts masked...
Stream to memory-mapped status (0x00000000@0x34): running
Starting MM2S channel with all interrupts masked...
Memory-mapped to stream status (0x00000000@0x04): running
Writing S2MM transfer length...
Stream to memory-mapped status (0x00000000@0x34): running
Writing MM2S transfer length...
Memory-mapped to stream status (0x00000000@0x04): running
Waiting for MM2S synchronization...
Waiting for S2MM sychronization...
Stream to memory-mapped status (0x00001002@0x34): running idle IOC_Irq
Memory-mapped to stream status (0x00001002@0x04): running idle IOC_Irq
Destination memory block: 78563412 44332211 02000000 03000000 04000000 05000000 06000000 07000000

初めSourceのメモリの値が78563412 44332211...,転送先のメモリが00000000 00000000...となっている.DMAを実行すると,まずSourceのメモリがPLに転送される.転送後はLoopbackにより,Destination memoryへとデータが転送される.最後に表示されたDesitinationの中身を見てみると,78563412 44332211...となりもとの値に一致していることがわかる.

今後やってみたいこと

上記デザインでは,DMA転送をする際にPS側から制御レジスタを叩く必要があった.これを改良して,PL側で制御用のAXI4-Streamから制御用の信号を渡して常にDMAが行われているといった回路にしてみたい.PLで何らかの処理をリアルタイムに行い,その結果をリアルタイムにメモリに送っていくといった回路を考えている.あと割り込みも試してみたい.

*1:https://www.xilinx.com/support/documentation/ip_documentation/axi_datamover/v5_1/pg022_axi_datamover.pdf

*2:例えばHP0とHP1から同時にDMAができるのか,とかそのための設定とか