XILINXのXDMAコアの使い方を調べています。
XDMAはディスクリプタベースのスキャッタギャザーDMAをサポートしているということです。
これだけだと何のことかさっぱりわからないと思いますが、ディスクリプタというのはDMAの転送元アドレスや転送先アドレスを書いたリストのことで、スキャッタギャザーというのは細かいDMAを大量に発行することを意味します。
さらに詳しく説明しますと、ユーザ空間でmallocやnewで確保したメモリというのは、一般的には物理メモリ上では細かく断片化しています。
イメージ的には下の図のような感じになります。
断片化の大きさや数、長さは常に変わっているので上の図のアドレスやサイズはあくまでもイメージです。数十キロバイトサイズの大きな連続したブロックに割り当てられていることもありますし、64バイトしかないこともあります。
とにかく、ユーザのバッファは物理(論理)アドレス上では断片化しているので、何番地~何番地までのメモリの内容を一度にFPGAに転送することはできません。
そこで、この断片化されたリストを送って、細かいDMAをたくさん発行するのがスキャッタギャザーです。スキャッタギャザーとは、散乱したものを集めるという意味です。
この断片化したパソコン内の物理メモリのアドレスとFPGAの転送元/先アドレスの情報を記したものがディスクリプタです。XILINX XDMAのディスクリプタは8ワード長で、形式は以下のとおりです。
オフセット0の上位16bitはMagicワードで、ここには0xad4bを指定します。下位8bitに0x13を書いておくと、転送終了後にSTOP、COMPLETE、EOPというイベントを発行します。
Lenにはバイト単位で長さを指定します。
src_addrはDMA転送元のアドレスです。DMA WR(Host to Card)の場合はPC上のメモリの物理アドレス(論理アドレス)を書いておきます。
dst_addrはFPGAのAXIに発行されるメモリのアドレスです。
nxt_addrは次のディスクリプタが格納されたPC上のメモリの物理アドレス(論理アドレス)です。スキャッタギャザーなので、複数のディスクリプタをリストにして使えるのですが、今回は1個のDMAを発行するのでnextポインタには0を指定しておきます。
このディスクリプタをパソコン内のメモリに格納しておいて、DMAが開始すると、FPGAは最初のディスクリプタをDMAで読み出して、そのディスクリプタの内容に従ってDMAを発行します。
さて、ディスクリプタはパソコンのメモリの中に書いておく必要がありますが、どこに書けばよいのでしょうか?
基本的に、mallocなどで確保したメモリはページアウトされている可能性があるのと、不連続なので、ディスクリプタには不向きです。
そこで、断片化していない連続した非ページメモリが最適です。そういうメモリは、AllocateCommonBufferという関数で確保します。AllocateCommonBufferは単純な関数ではなく、DMAアダプタを作ったあとでその中に関数ポインタとして存在しています。つまり、DMAのバスのタイプによって実装が変わる関数なので、単純ではありません。
コモンバッファ(共有バッファ)というのは、仮想アドレスと、物理アドレス(論理アドレス)の両方を知ることができるバッファです。2種類のアドレスでアクセスできるのでコモンバッファといわれているようです。
そういうわけで、AllocateCommonBufferで確保したコモンバッファにディスクリプタを作ります。
ULONG *desc = (ULONG *)dx->combuf; // 作ったコモンバッファの仮想アドレス
desc[0] = 0xad4b0013;
desc[1] = 100; // bytes
desc[2] = dx->combufPhysAddr.LowPart; // src addr low
desc[3] = dx->combufPhysAddr.HighPart; // src addr high
desc[4] = 0; // dest addr low
desc[5] = 0; // dest addr high
desc[6] = 0; // next addr low
desc[7] = 0; // next addr high
// BAR1にDMAコントロールレジスタがある
ULONG *DmaReg = (PULONG)dx->barVirtualAddress[1];
// DMAデスクリプタのアドレスを指定
WRITE_REGISTER_ULONG(&DmaReg[0x4080/4], dx->combufPhysAddr.LowPart); // デスクリプタのアドレスを書き込む
WRITE_REGISTER_ULONG(&DmaReg[0x4084/4], dx->combufPhysAddr.HighPart);
WRITE_REGISTER_ULONG(&DmaReg[0x4088/4], 0); // extra
WRITE_REGISTER_ULONG(&DmaReg[0x0004/4], 0xfffe7f); // DMA開始
そして、BAR1の0x4080,0x4084にディスクリプタのあるPC上の物理アドレスを指定し、BAR1の0x0004にDMA開始のコマンドを書き込みます。本当は0x000001でいいのですが、エラーやその他の停止条件もトリガするため0xfffe7fを書いています。
このようにするとXDMAのコアが動き出します。実際に100バイトの書き込みを行ってみた時の波形は以下のようになりました。
AXIの転送サイクルとしては2回発行されていて、最初に4クロック(16バイト×4)で64バイトが転送され、次に32バイトと4バイトが転送されています。
データバスは128bitなので、wstrbが0xffff→0x000fと変化することで4バイトの書き込みに対応します。
次の波形は16kByteの転送を行ったときのものです。
小さな転送がたくさん行われていて、全体が終わるまでに約9.5μ秒かかっています。ここだけみれば1700MB/secの速度で転送できていることになります。
なお、1つ1つの転送は256nsの時間で行われているので、32クロック=512バイトです。
偶然によっては、転送先アドレスが0で始まらない場合もあって、wstrbがFFFFとならず1クロック余計にかかることもあります。
これでDMAの発行ができるようになりました。
次はスキャッタギャザーに本格対応させるために、デバイスドライバの開発環境を整えることにします。
最近のコメント