2017.08.12

PCI Express 64bitでのDMA

PCI ExpressのBARを64bitにして、デバイスドライバで64bitのDmaAdapterをgetすれば、マップレジスタがエミュレーションされないのでデータのコピーが行われず、超高速になるのではないかと思い、実験してみました。

結果は大正解でした。

Speed_64

Read/Writeともに1.6GB/sほど出ています。PCI ExpressのBARが32bitだったときには1.0GB/s程度だったので、約1.6倍に高速化されました。この差は大きいですね。

sun

一つ一つの転送波形を見てみることにします。

まず、DMA WRの波形。

Dmawr_64

拡大してみてみると、256ns転送して32ns休むというサイクルになっています。

Dmawr_64_mag

256/(256+32) * 2GB = 1.7GB

なので、XDMAコアが出すAXIの最高速度はこのくらいです。

また、DMA RD(C2H)の波形ですが、2.4usくらいのサイクルの間に2.1usくらいの転送が行われているので、やはり1.7GBくらいとなります。

Dmard_64

Dmard_64_mag

PCI Expressの先にあるAXIの速度が1.7GBで、Windowsのアプリケーションで測った速度が1.6GBなので、ほぼフルの速度が出ていると考えてよいともいます。

sun

結論をまとめると、

  • PCI Expressを32bit BAR構成にすると、スキャッタギャザーDMAを実現するときにHALでデータコピーが発生するので遅くなる。約1.0GB/secしかでない。
  • 64bit BAR構成にすると、データコピーが発生しない。約1.6GB/secと、ほぼフルの速度が出る

また、64bit対応したPCI Expressカードならば、ドライバが巨大なコモンバッファを確保してくれるようになります。コモンバッファというのは物理アドレスで連続したページされないメモリ領域です。DMA転送などの用途にも使えます。

32bitだと8MBくらいしか取れませんが、64bit版だと4GBまで取れます。これだけでも、64bitのPCI Expressにする意義があるといえるでしょう。

XILINXのXDMAコアを使って、PCI Express Gen2 x4でバスの帯域2.0GB/sのところ、上り下りとも1.6GB/sまでできました。

| | コメント (0)

2017.08.10

PCI ExpressのDMA転送を改善する

前回の記事ではWindowsが用意するしくみでは、1MBや2MBといったサイズでDMAが制限されていることを書きました。

具体的には、ドライバのコードで以下のようなものを書きます。

DEVICE_DESCRIPTION DeviceDescription;
RtlZeroMemory(&DeviceDescription,sizeof(DeviceDescription));
DeviceDescription.Version = DEVICE_DESCRIPTION_VERSION;
DeviceDescription.Master = TRUE;
DeviceDescription.ScatterGather = TRUE;
DeviceDescription.Dma32BitAddresses = TRUE;
DeviceDescription.Dma64BitAddresses = FALSE;
DeviceDescription.InterfaceType = PCIBus;
DeviceDescription.MaximumLength = 0x10000000; // 256MByte
// DMAアダプタを作成
dx->dmaAdapter = IoGetDmaAdapter(dx->pdo, &DeviceDescription, &dx->NumOfMappedRegister);
KdPrint(("DmaAdapter: Num of Mapped registers =%d \n",dx->NumOfMappedRegister));

このコードは、DMAアダプタというものを獲得するコードなのですが、32bitのPCIバスを使って最大256MByteのDMA転送をするようなDMAアダプタを作ってくれ、と要求するものです。

ポイントは上の赤い字で記した、Dma64BitAddress=FALSEというところにあります。

64bitAddressesがFALSEだと、マップレジスタが256個しかとられません。

64bitAddressesをTRUEにしてみると、あら不思議、マップレジスタが65537個取れました。

Combuf_office_64

さくっと256MByte用のマップレジスタが取れたのでしょうか?

いや、PCI Expressも64bitでCPUも64bitなので、おそらくマップレジスタを使わずにDMA転送を行えるようになったのでしょう。

64bit版PCI Expressデバイスのすごいところはマップレジスタを使わないだけではなく、コモンバッファも巨大なサイズが取れるということです。

メインメモリ16GB積んだPCで、0xffffffffバイトのCommonBufferを要求したら、物理アドレス0x00000003-00000000から連続した4GBの長さのバッファが取れました。

Combuf_office_64_4gb

32bit版のPCIeとドライバでは4MBや8MBが限度だったので、4GBのバッファというのはすごいことです。

64bit版ドライバと64bit版デバイスはすごいですね。

sun

本当にPCI Expressを64bitにするには、64bit BARを有効にしなければなりません。XILINXのXDMAコアでは、以下のオプションを設定します。

Xdma_64bit

これで64bit版のBARが有効になります。

そして論理合成してBitファイルを書き込んだら、一度再起動します。32bitBARから64bitBARに切り替えるには、デバイスマネージャからの無効→有効するだけではうまくいかないようです。

| | コメント (0)

2017.08.09

WindowsのDMAとMapRegister

スキャッタギャザーDMAを発行しようとして試行錯誤していたのですが、どうしても1MByte以上の転送を行おうとするとブルースクリーンになってしまいました。

最初はMapTransferという関数を使っていたのですが、オブジェクトの開放の手順がよくわからなかったので、途中からGetScatterGatherListを使うようにしたのですが、1MByteの転送をしようとすると物理アドレス(論理アドレス)のリストを作るだけで、実際の転送をしなくてもブルースクリーンになってしまいます。

100回くらい青い画面を出して、ようやくわかってきました。

sun

スキャッタギャザーDMAでは、ユーザがmallocで確保したバッファが実際に割り当てられている物理メモリのアドレスを調べて、そこをめがけてFPGAからDMA転送するので、原理的にはデータのコピーが発生しません。

Sgtransfer

しかし、この方法には問題があります。

PCI Expressのアドレスはデフォルトでは32bitなので、4GBまでの転送先アドレスしか指定できません。パソコンのCPUのアドレスは4GBでは収まらないメモリ空間を持っています。それゆえ、メモリRD/WRを発行しても、メインメモリの先頭の4GBまでしかアクセスできないことになってしまいます。

そこで、Windowsでは、マップレジスタというものが用意されています。マップレジスタというのは、ハードウェアデバイス用の仮想メモリのようなもので、PCI Expressから指定された転送先アドレスを実際のDIMMの物理アドレスに変換する機能です。

つまり、PCI ExpressからメモリRD/WRで指定してくるアドレスは、パソコンのメモリの物理アドレスではなく、ハードウェア版のアドレス変換機構を通す前のアドレスなのです。これを論理アドレスといいます。

そして、物理アドレスと論理アドレスを変換する機能がマップレジスタなのです。

Mapregister_2

しかし、マップレジスタというのは実際に存在するわけではなく、そういう機能があったらいいな、というものをHALがエミュレートしているのです。

わけがわからないとは思いますが、つまりこういうことです。

  • MapTransferやGetScatterGatherListを実行すると、DMA転送先のアドレスとして、下位4GBを指すようなものが返される。
  • この下位4GBのバッファには、実際にユーザ用のバッファが割り当てられているわけではなく、DMA転送の前後で、カーネルがこっそりメモリコピーを行う

Mapregister2

Windowsはこんな仕組みでDMAを実行しているのです。

sun

ブルースクリーンになってしまう原因はマップレジスタの数でした。私が新しく買ったWindows10 64bitマシンでは、デバイスドライバの中で「このデバイスは0x10000000(256MByte)のDMAを行う」と申告しても、マップレジスタを256個しか割り当ててくれませんでした。

Mapreg_office_32bit

マップレジスタ1個は1つの物理ページ(=4096バイト)のアドレス変換テーブルなので、1MByte分のアドレス変換しかしてくれないのです。これ以上の長さのDMAをしようとしてもマップレジスタが無いので、NULLを返してしまい、それを開放しようとしてブルースクリーンになっていたのですね。

家のPCでやってみたところ512個のマップレジスタが取れました。

Mapreg2

これなら2MBまでのDMA転送ができます。

sun

実際に連続してスキャッタギャザーDMAをやってみたところ、アプリケーションレベルで測って毎秒1GB/secの速度で転送できました。

Dmardwr

しかし、1MBや2MBのサイズのDMA転送しかできないのですから、効率が悪いですね。

Windows7や8で32bit版ドライバを動かしていたときにはもっと確保できていたと思うのですが・・

重要なことは、スキャッタギャザーといってもユーザのバッファにDMA転送しているわけではなく、裏でカーネルがこっそりコピーしているということです。そのカーネルがこっそりコピーできるサイズが、1MBや2MBしかないのです。

| | コメント (0)

2017.08.08

特電の商品発送箱が変わります!

本日以降、特殊電子回路の商品発送に使用していた箱が変わります。

Beforeafter

いままで特電のSpartan-6やArtix-7の箱は上の写真の左側にあるような、白をベースとした箱に緑色のキラキラ印刷を施した箱で送っていました。箱の中には基板を納めるくぼみや、二階建ての引出があって、CD-ROMや付属品を入れるようになっていました。

しかし、この箱は組み立てるのが非常に大変で、引出の開け閉めもスムーズではなかったので、リニューアルしたいな・・とずっと思ってきました。

また、MITOUJTAGの梱包は、

Konpou8

のように白いダンボールにケーブルやCD-ROMなどを詰めて送っていましたが、これも、シンプルすぎて高級感がない。

実は、白い箱で送っているということで、何となく恥ずかしさを感じていました。

そこで、6つの面をフルカラーで印刷した新しい箱を作ることにしました。

Newbox1

どうですか?今までの特電ではないみたいでしょう。

側面にはバーコードが貼りつきます。

Newbox2

箱の中は硬質のスポンジで作った仕切りの中に、基板や付属品を納めることができるようになっています。

Newbox3

MITOUJTAGに関してもCD-ROMのケースと、Pocket JTAG Cableが気持ちよく納まるようにスポンジが加工されています。

Newbox4

これなら秋葉原の店頭に並んでいても恥ずかしくありませんね。

sun

さて、このデザイン箱ですが、佐川急便の伝票や「取り扱い注意」シール、それから納品書封筒などを貼り付けてしまうとせっかくのデザインが隠れてしまいます。

そこで、発送用にもう1段外側に白い無地のダンボールでくるむことにしました。

Newbox5

そして、外側の箱に佐川急便の伝票やシールを貼りつけて、出荷です!

Newbox6

現在、

  • Cosmo-Z
  • Cosmo-K
  • MITOUJTAG BASIC/Pro
  • Spartan-6/Artix-7

が新しい箱での出荷となる対象です。

8月8日の出荷分より新しい箱でお届けします。

| | コメント (0)

Windows10におけるXDMAコア割り込みの発生方法

DMAの完了を知るためには割り込みの使用が必須です。

XDMAコアはかなり複雑なのですが、割り込みを利用するには開始コマンドを発行する際に、以下のようなレジスタ設定をします。

// 割り込みマスクの設定。転送失敗も含め、あらゆる要因を受け付ける
WRITE_REGISTER_ULONG(&DmaReg[0x0090 / 4], 0xfffffe); 

// DMA割り込みの許可
WRITE_REGISTER_ULONG(&DmaReg[0x2010 / 4], 0xffffffff);

DbgPrint("DMA descriptor set at %08x-%08x\n", dx->combufPhysAddr.HighPart, dx->combufPhysAddr.LowPart);

// 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 adjは0
WRITE_REGISTER_ULONG(&DmaReg[0x0004 / 4], 0xfffe7f); // DMA開始

これでDMAの完了時に割り込みが発生します。

さて、FPGAがPCI Expressに割り込みを発生させたら、ドライバできちんと扱わなければなりません。そうしないと、割り込みが発生しっぱなしになって、CPUが割り込み確認→戻る→確認→戻るを繰り返してしまいます。

なお、Windowsのデバイスドライバでは、割り込み発生時に呼び出されるルーチン(ISR)は極めて高いIRQLで呼び出されます。

IRQLが高いというのは、優先順位が高いということなのですが、マイコンのようなものをイメージしてはいけません。

優先順位が高いルーチンというのは非常にプログラミングがしにくいのです。まず、使用できるメモリが非常に限られてきます。ページドメモリ(スワップ対象のメモリ)が使えないとか、いろいろと制約が多く、カーネルAPIの多くも使用禁止です。

以下のTPInterruptHanderという関数は実際のISRの例ですが、割り込みサービスルーチンでは、割り込み要因の確認と、割り込みフラグのクリアを行い、DPC(遅延プロシージャコール)を行って戻します。

BOOLEAN
TPInterruptHandler(
    IN PKINTERRUPT  Interupt,
    IN PVOID        ServiceContext
)
{
    BOOLEAN interruptRecognized = FALSE;
    PTP_DEVICE_EXTENSION dx = (PTP_DEVICE_EXTENSION)ServiceContext;
    ULONG *DmaReg = (PULONG)dx->barVirtualAddress[1]; // BAR1
    ULONG DmaReason = READ_REGISTER_ULONG(&DmaReg[0x2044 / 4]);
    if (DmaReason & 1) {
        // bit0 : H2C channel interrupt
        WRITE_REGISTER_ULONG(&DmaReg[0x2018 / 4], 1); // mask IRQ, W1Cなので書き込むとマスク
        dx->InterruptReason |= 0x80000000; // 要因を保存
        interruptRecognized = TRUE;                      // このハンドラで処理した
    }
    if(interruptRecognized)
    {
        IoRequestDpc(dx->fdo, NULL, dx);                 // 遅延ルーチン呼び出し
        InterlockedIncrement(&dx->InterruptCount);       // 割り込み回数をカウント
    }
    return interruptRecognized;
}
割り込みサービスルーチンでは、基本的にあまり多くのことをするべきではありません。

XDMAではDMA(H2C)完了の割り込みが発生すると、BAR1のオフセット0x2044のbit0が'1'になります。そのため、ここではフラグを確認して、割り込みをマスクしてDPCの呼び出しをセットするだけです。

DPCルーチンは、IRQLが低いので比較的なんでもできます。DbgPrintも気兼ねなく使うことができます。

LONG tempReason;
tempReason = InterlockedExchange((LONG *)&dx->InterruptReason,0);

//バスマスタ完了割込み処理。
if (tempReason & 0x80000000)
{
    KeSetEvent(&dx->DmaEvent, IO_NO_INCREMENT, FALSE); // DMAイベントをセット
    tempReason &= ~0x80000000;
}

当ドライバでは上のようにして、カーネルのイベントをセットします。

DMA発生元のルーチンでは、

Timeout.QuadPart = -30000000; // 3秒待ち
status = KeWaitForSingleObject(&dx->DmaEvent, Executive, KernelMode, FALSE, &Timeout);
if (status == STATUS_TIMEOUT) {
    DbgPrint("NO DMA interrupt.\n");
    *BytesReturned = 0;
    return STATUS_TIMEOUT;
}

ULONG *DmaReg = (PULONG)dx->barVirtualAddress[1];
ULONG DmaStatus;
ULONG DescCount;

WRITE_REGISTER_ULONG(&DmaReg[0x0004 / 4], 0); // DMA停止
DmaStatus = READ_REGISTER_ULONG(&DmaReg[0x0044 / 4]);
DescCount = READ_REGISTER_ULONG(&DmaReg[0x0048 / 4]);
DbgPrint("DMA done:DmaStatus=%08x, Complete descriptor count=%d\n", DmaStatus, DescCount);
WRITE_REGISTER_ULONG(&DmaReg[0x2014 / 4], 0xf); // 割り込みマスクを許可する

*BytesReturned = length;
return STATUS_SUCCESS;

として、KeWaitForSingleObject割り込みを待ち受けます。上のコードでは3秒でタイムアウトするようになっています。

割り込みが発生したら、ISR→DPC→イベント発生→元のプログラムが再開という流れになります。

そして、BAR1の0x0004に0を書いてDMAを停止させて、その後、DMAのステータスと完了したDescriptorの数を調べています。

このように、割り込みを使ってDMAの完了を知ることができるようになります。

| | コメント (0)

2017.08.07

Windows10時代のデバイスドライバ開発とデバッグ

DMAのデバイスドライバを開発するため、Windows10での開発環境を整える必要が出てきましたので調べてみました。

いくつかの参考になりそうな記事がありますので、リンクを貼っておきます。

「Windows 10 でサンプル ドライバーをビルドするまで」
https://blogs.msdn.microsoft.com/jpwdkblog/2015/08/21/windows-10/

「ユニバーサル Hello World ドライバー (KMDF) の作成」
https://msdn.microsoft.com/ja-jp/library/windows/hardware/hh439665(v=vs.85).aspx

最初の「Windows 10 でサンプル ドライバーをビルドするまで」に従ってインストールします。

●Visual Studio 2015 Expressのインストール

まずは、VisualStudio2015のExpressをインストールしなければなりませんが、これが大変です。ExpressはCommunityに変わり、2015は2017に変わったので、2015 Expressをダウンロードしようとしても2017 Communityに飛ばされてしまいます。なかなかたどり着けません。

Visual Studio 2015 Expressのダウンロード先はこちらです

日本語版 https://www.visualstudio.com/ja/post-download-vs/?sku=xdesk&clcid=0x411&telem=ga

英語版 https://www.visualstudio.com/ja/post-download-vs/?sku=xdesk&clcid=0x409&telem=ga

上のページを訪れると自動的にダウンロードの保存ダイアログが出るので、保存します。

Getvsexpress

私は日本語版をインストールしました。

●Windows 10 SDKのインストール

Windows 10 SDKは難しくありません。下記のページからEXEをダウンロードする、を押せば手に入ります。

https://developer.microsoft.com/ja-jp/windows/downloads/windows-10-sdk

Getsdk

●Windows 10 WDKのインストール

下記のページにあります。

https://developer.microsoft.com/en-us/windows/hardware/windows-driver-kit

Getwdk

●ユニバーサル Hello World ドライバー (KMDF) の作成

参考リンクの「ユニバーサル Hello World ドライバー (KMDF) の作成」をしてみようと思っても、同じようにはいきません。まず、ソリューションエクスプローラで「追加」→「新しい項目」とやっても、Package Manifestしか出ないのです。

Addnewitem

C++やHのファイルのテンプレートというものがないためなのですが、テンプレートは以下のフォルダの中にあります。

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\VCProjectItems

しかし、VCフォルダに存在するのはVCProjectItems_WDExpressです。そこで、。VCProjectItems_WDExpressをコピーしてVCProjectItemsに名前を変えます。

Copywdexpress

プロジェクトに新しいC++やCファイルを追加できるようになります。

Addc

これで、ユニバーサル Hello World ドライバー (KMDF) の作成が作れるようになります。

●配置とデバッグ

ただ、その先にある配置やデバッガのところがうまくいきません。リモートデバッグの方法も試してはみましたが、参考リンクのようにはいきません。

そもそも2台のPCをつないでデバッグする・・というのは理想的ではありますが、やはり1台のPCでやりたいものです。

私の方針は以下のとおりです。

  • セキュアブートを無効にする
  • 開発用兼実験用PCで、カーネルモード署名の強制を無効にする
  • Windowsをbcdeditでテストモードにする

カーネルモードでのデバッグはDbgPrintを使います。KdPrintは括弧を二重にしないといけなかったり、何かと使いにくいのです。DbgPrintは

DbgPrint("DmaAdapter address = %x",addr);

のようにprintf感覚で使えてとても便利です。DbgPrintの出力は、DbgView.exeで見ることができます。

Dbgview

DbgView.exeは以下のリンクから入手できます。

https://technet.microsoft.com/ja-jp/sysinternals/debugview.aspx

DbgView.exeを管理者モードで起動して、Captureのオプションを下記のように設定すれば、DbgPrintの出力を見ることができます。

Dbgprint2

●ドライバの日付について

基本的にビルドしてテストしての繰り返しなので、面倒なことはしたくありません。Visual Studioでビルドしたドライバには、自動的にinf2catやsigntoolで署名が施されます。

ただし、日付がUTCなので日本の午前0時を過ぎると翌日になってしまい、未来の日付は使えないとエラーになってしまいます。そこで、下記のオプションを設定する必要があります。

Inf2cat

●ドライバの署名について

Visual Studioでプロジェクトを作ると、オレオレ証明書ができているようです。

Debugフォルダの中に.cerというファイルがありますので、これを信頼されたルート証明機関にインストールします。

Sign2

これで、自分のPCだけで使えるAuthenticode署名ができるようなのですが、

Sign3

ドライバのインストールで失敗するようなので、私はWindowsをテストモードにして、署名の強制を無効にして使っています。

ざっと駆け足で説明しましたが、このような手順でWindowsのデバイスドライバを開発できるようになると思います。

| | コメント (0)

2017.08.06

XILINX XDMAの使い方と速度

XILINXのXDMAコアの使い方を調べています。

XDMAはディスクリプタベースのスキャッタギャザーDMAをサポートしているということです。

これだけだと何のことかさっぱりわからないと思いますが、ディスクリプタというのはDMAの転送元アドレスや転送先アドレスを書いたリストのことで、スキャッタギャザーというのは細かいDMAを大量に発行することを意味します。

さらに詳しく説明しますと、ユーザ空間でmallocやnewで確保したメモリというのは、一般的には物理メモリ上では細かく断片化しています。

イメージ的には下の図のような感じになります。

Sg

断片化の大きさや数、長さは常に変わっているので上の図のアドレスやサイズはあくまでもイメージです。数十キロバイトサイズの大きな連続したブロックに割り当てられていることもありますし、64バイトしかないこともあります。

とにかく、ユーザのバッファは物理(論理)アドレス上では断片化しているので、何番地~何番地までのメモリの内容を一度にFPGAに転送することはできません。

そこで、この断片化されたリストを送って、細かいDMAをたくさん発行するのがスキャッタギャザーです。スキャッタギャザーとは、散乱したものを集めるという意味です。

この断片化したパソコン内の物理メモリのアドレスとFPGAの転送元/先アドレスの情報を記したものがディスクリプタです。XILINX XDMAのディスクリプタは8ワード長で、形式は以下のとおりです。

Descr

オフセット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を指定しておきます。

sun

このディスクリプタをパソコン内のメモリに格納しておいて、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バイトの書き込みを行ってみた時の波形は以下のようになりました。

Dmawr100_2

AXIの転送サイクルとしては2回発行されていて、最初に4クロック(16バイト×4)で64バイトが転送され、次に32バイトと4バイトが転送されています。

データバスは128bitなので、wstrbが0xffff→0x000fと変化することで4バイトの書き込みに対応します。

次の波形は16kByteの転送を行ったときのものです。

Xdma_speed

小さな転送がたくさん行われていて、全体が終わるまでに約9.5μ秒かかっています。ここだけみれば1700MB/secの速度で転送できていることになります。

なお、1つ1つの転送は256nsの時間で行われているので、32クロック=512バイトです。

Dmawr_cont1

偶然によっては、転送先アドレスが0で始まらない場合もあって、wstrbがFFFFとならず1クロック余計にかかることもあります。

Dmawr_cont2

これでDMAの発行ができるようになりました。

次はスキャッタギャザーに本格対応させるために、デバイスドライバの開発環境を整えることにします。

| | コメント (0)

2017.08.05

Kintex-7のPCI ExpressコアのWrite速度

Kintex-7にAXIベースのPCI Expressのコアを入れて速度を測ってみました。

使ったのは、Cosmo-K+というボード。

1478579823_cosmok

PCI ExpressはGen2のx8なので、毎秒2GByteが理論上の最高速度なのですが、書き込み速度でさえ、65MB/s程度と、わずか3%の速度しか出ていません。

Pcie_rdwr

この回路で使っているのはXILINX XDMAコアですが、今回はDMAポートではなくAXI BYPASSポートを使っています。

Axi_bypass_2

AXI BYPASSのポートにBRAMをつないでいます。BRAMはすぐに応答を返すのでそこがボトルネックになっているというわけではありません。

そこで、XMDAコアからBlockRAMの間のAXIの配線を全部引き出して、MITOUJTAGで波形がみられるようにRTLのコアを横にいれました。

これでAXIの信号を見ることにします。

Pcie_dma

すると、こんな波形が得られました。

Pcie_wr

開始部分を拡大してみます。

Pcie_wr2

128bit幅で125MHzの転送なので、1回のバスサイクルで16バイトを並列に転送できます。ですが、この図を見てわかることは、WSTRBが000F 00F0 0F00 F000と、32bitずつ4回にわけて転送しています。

また、AWLENが00(=256)を指しているのに毎回の転送でLASTが送られてきてしまって、バースト転送が行われていません。

このため、32bitの転送で平均60nsくらいかかってしまいます。4MBytes/0.06us=66MBytes/secなので、計測結果とも一致します。

なぜこんなに遅いのかはわかりませんが、XILINXのAXIベースのPCI Expressコアは、PCI ExpressのWrite転送を小分けにしてしまうようです。

やっぱりDMAを使わないとだめでうね。

| | コメント (0)

«Windows10におけるデバイスドライバ