<スピード測定ツール>
USB2.0 HiSpeed転送といえばスペック的には480Mbpsということになっていますが、実際のところどの程度のスピードが出るのかについては、PCのチップセット・OSの種類・OSに組み込んでいるデバイスなどによって変動します。もちろんUSBデバイスのマイコンや、マイコンで動作するファームウエアなども影響を与えます。
実際何かの要件があって、高速でデータ転送を行う必要が生じたとき、「カメレオンUSB SPA3」と手持ちのPCを使って、スペック的にアプリケーションを作成することが可能なのかどうか? これは重要な問題です。実際に開発を進めたあげく、やっぱり速度が足りませんでしたでは悲しすぎます。
そこで、今回「カメレオンUSB SPA3」をPCに接続して、いったい「現在の環境でどの程度までの速度でデータの転送が出来るのか」という「限界転送速度」を測定することにしました。PCで実行中のプログラムから、上り(SPA3
-> PC)下り(PC -> SPA3)それぞれ測定します。もちろん、ただデータを送ればよいというわけではなく、正しく送れているかも受け側でチェックするようにしています。
このサンプルアプリケーションは、自由に改変して使用していただいて構いません。ユーザ独自のアプリケーションを開発する際のフレームワークとしてお使いください。
ダウンロード スピード測定アプリ(spd_chk16)ソース一式(ホストアプリ・ロジック)
<FX2のFIFOについて>
FX2はGPIFとFIFOと大きく2つのインターフェイスを提供しています。GPIFというのはFX2LP側が主導権をもって、外部機器をコントロールしてデータ転送を行うための仕組みで、IDEインターフェイスのハードディスクなどを直結して使用することが出来るようになっています。これに対してFIFOというのは、FX2は単なるFIFO(先入れ先出し)メモリとして動作して、FX2の外部回路が主導権をもってデータ転送を行うことになります。
「カメレオンUSB SPA3」の場合はSpartan3Aという、汎用的かつ高速な外部回路が存在しますので、通常FX2LPはFIFOモードとして使用することになります。
FIFOメモリとして動作すると書きましたが、このFIFOメモリのアクセスの方法によって2種類のモードに分けることが出来ます。非同期アクセスモードと同期アクセスモードです。非同期アクセスモードは、ライト(リード)信号をFX2に与えて、このライト(リード)信号の立ち上がり(立下り)のタイミングでFIFOメモリーにデータを書き込む(読み込む)方法です。
これに対して同期アクセスモードは、ライト(リード)信号を有効にしている期間に、IFCLKと呼ばれるクロック信号の立ち上がり(立下り)に同期して毎回データを書き込む(読み込む)方法です。転送速度は非同期方法に比べて高速になりますが、その反面タイミングを取るのが難しくなります。
同じ同期アクセスモードでもこのIFCLKをFX2の内部(30/48MHz)で生成するか、外部から(Max
48MHz)取り込むかによっても動作タイミングが異なってきます。また、FIFOのデータバスの幅をエンドポイント毎(FIFOアドレスで選択する)に8bitか16bitに切り替えることが可能です。
速度的にはバス幅16bitで48MHzで転送するのが一番高速になりますが、480MbpsのUSB2.0
HiSpeedの速度を考慮すれば、8bit幅 48MHz(384Mbps)、16bit幅 30MHz(480Mbps)でも十分に使い切ることになります。
各転送モード時の信号のタイミングチャートははFX2のデータシートを参照していただく必要がありますが、高速なデータ転送を行うためには下記の点に留意する必要があります。
<SPA3ライブラリを使った高速転送>
カメレオンUSB SPA3では、SPA3ライブラリがFX2のファームウエアを提供します。このファームウエアを使用することで、FIFOを使った高速転送を実現します。
下りのエンドポイントはEP2OUT(アドレス0x02)、上りのエンドポイントはEP6IN(0x86)を使用します。各エンドポイントの長さはバルクエンドポイントの最大長の512Byteでこれをクワッド(x4)バッファとして、2Kbyteを割り当てます。
提供される関数を使用してエンドポイント毎の開始・停止処理、FIFOの8/16bit切り替え、IFCLKの設定などを行います。
(関数の詳細はこちらのページをご覧ください)
今回のサンプルでは使用していませんが、Spartan3A内部にはブロックRAMが内蔵されています。FX2の2Kbyteのバッファでは不十分で、データの取りこぼしが生じる状況でも、ブロックRAMをバッファとして使用することで安定した転送が実現できます。
<spd_chk16アプリの処理概要>
spd_chk16アプリではエンドポイントは上り・下りでそれぞれ固定。16bit幅のFIFOで30MHzのFX2内部クロックを使った非同期転送で限界速度を測定します。
測定方法は上りを測定して、下りを測定というようにそれぞれを分けて測定することで、測定中のエンドポイントの切り替えは行いません。
32bitのインクリメンタル数値を転送して、受信側で正しいことをチェックします。本来は不要な機能ですが、開始番号(シード)をPC側からJTAGポート機能を使用して、FPGAのレジスタに設定しています。
限界速度を測定するために、FIFOの状態信号(FLAGA/FLAGB)をFPGAがチェックして、FIFOに空きがある状態であればデータを書き込む(データがFIFOに到着していれば読み込む)方法を採用しています。FPGAはFIFOが一杯(FIFOにデータが無い)の状態の待ちクロック数(wait_cnt31)をカウントしています。転送にかかったクロック数(時間)は(待ちクロック数+転送バイト数)となり、実際の転送速度を計算することが出来ます。
待ちクロック数(wait_cnt31)とFPGA側でデータを検証したフラグを、PC側から読み出します。
転送バッファの容量を見積もるために、FIFOバッファが一杯で、待たされている時間がどの程度続くかを調べることは有効です。
今回試験的に、Spartan3AのブロックRAMを使用して、一定以上の待たされている時間を記録する機能を実装しています。
(USB2.0 HiSpeed専用のアプリケーションです、USB1.1やFullSpeedでは動作しません)
<ソースコードの解説>
こちらのページでFPGAのコンフィグ、JTAGポートなどの解説を行っていますので、USBの高速転送に絞って説明します。
Visual C++ 2010 Express ソースコード(spd_chk.cpp) |
|
6行目のDEBUG_SPA3を有効にすると、ISEが生成したbitファイルを直接読み込んでFPGAのコンフィグを行います。 7行目のWAIT_DISPを有効にすると、ブロックRAMを使用して収集した待ち時間を表示します。 |
|
1−2行目はPCアプリからFPGAに転送中/停止中を伝えるための定義です。 4-5行目はPCアプリからFPGAに転送方向を伝えるための定義です。 7−8行目は同期転送用のスレッドを起動する際に、指定する転送バッファの長さと個数です。 |
|
6行目は同期転送用のスレッドを制御するためのタスクコントロールブロックへのポインタです。 |
|
JTAG ポートの定義です。 出力ポートでFPGAの動作モード、設定などを行い。 入力ポートでFPGA状態を取得します。 1行目は大きいほうの出力ポート35bitになっています。 |
|
同期転送スレッドから呼び出されるコールバック関数です。FX2 -> PCにデータが届いた際に呼び出されます。 ポインタbufにデータのポインタ、lenにデータの長さがセットされて呼ばれます。この例では単純にファイルに書きためています。コールバック関数は戻り値が重要で、引き続きデータを受信(送信)しつづける際にはtrueを返し、送受信を終了する際にはfalseを返すようにします。 16行目はSPA3ライブラリのep6in_stop()関数を呼び出し、FX2に対して転送を停止するように指示しています。 18-19行目はJTAGポート機能を使用して、FPGAに対して転送モードの終了を指示しています。 |
|
同期転送スレッドから呼び出されるコールバック関数です。PC -> FX2に転送するデータを作成するために、バッファに空きが出来た際に呼び出されます。 事前に送信用のデータをキューの数だけ作成することになります。ですのでこの関数が呼ばれる回数は、実際に送信したキューの数+作成したキューの数になる点にご注意ください。 他の処理は上のコールバック関数と同様です。 |
|
転送速度を表示する関数です。 2行目で同期転送スレッドを削除しています。 11−12行目はIFCLKの周波数、待ち時間などから実際の転送速度を計算しています。 |
|
ブロックRAMを使用して収集した待ち時間(Cycle)を表示する関数 |
|
メイン関数です。 15-16行目でカメレオンUSB、SPA3クラスを実体化させています。 23行目でdev_dnaにゼロを指定することで、最初に見つけたカメSPA3を対象にします。 24行目でFX2にSPA3ファームウエアのロード、FPGAのコンフィグを行います。 |
|
1行目はFIFOを16bitに設定しています。 3行目はFX2のifconfigレジスタを設定することで、IFCLK 30MHzに設定します。(詳細はFX2のユーザマニュアルを参照) 5−9行目でFPGAに初期値の設定を行います。 11-13でFPGAのリセット(RST)を 1 -> 0にセットしています。 |
|
3行目でFX2->PCへの同期転送スレッドを開始させます。0x86はエンドポイントEP6INのアドレスです。バッファ長、キューの数、コールバック関数を指定します。 5行目でFX2にEP6INの転送開始を指示します。 7−9行目でFPGAに対して、転送方向、転送開始を指示します。 12行目で転送終了を待ち、結果を表示します。 |
|
JTAG RAMアクセス関数を使用して、FPGA内部のブロックRAMを読み出します。 読み出した後は、次の転送に備えて、初期化の書き込みを行います。 |
|
1-6行目でRSTを 1 -> 0にして、FPGAのリセットを行います。 10行目はPC->FX2への同期転送スレッドを開始させます。0x02はEP2OUTのアドレスです。バッファ長、キューの数、コールバック関数を指定します。 残りの処理はFX2->PCとほぼ同じです。 |
引き続きFPGAのソースコードです。ポイントはFX2のFIFOの扱いになります。
FX2からのIFCLKをクロックに使い、FIFOの状態をFLAG信号で確認しながらリード/ライトを行います。
JTAG経由でブロックRAMへのアクセスも実装しています。
(spd_chkに含まれるjtag_port.vにはブロックRAMアクセスが含まれます。io_dna_testに含まれるjtag_port.vにはブロックRAMアクセスは含まれません。用途に応じて使い分けてください。)
XILINX ISE Verilog ソースコード(spd_chk.v) |
|
トップモジュールの定義です。カメSPA3はFX2と接続されていますので、使用しない信号も残しておきます。同時にucfファイルでピン番号も固定する必要があります。詳しくはこちらも参照。 |
|
1-3行目は転送中/停止中を示すモード信号 5行目はロジック全体をイニシャライズするためのリセット(RST)信号 7-9行目は転送方向を示すDIR信号 13行目は入力(FPGA -> PC)ポート 14行目は出力(PC -> FPGA)ポート 共に35bitでPCアプリケーションと同じ値にします。 |
|
16-27行目はCORE Generatorで生成したブロックRAMを使用したデュアルポートRAMのインスタンス化です。 12-14行目はjtag_port.vで記述される、JTAGポートとJTAG RAMアクセスのインスタンス化です。 JTAG RAMアクセスからの信号をそのまま、デュアルポートのBポートに接続しています。 このようにすることで、FPGAのロジックが書き込む裏で、同時にPC側からアクセスすることが出来ます。 |
|
デュアルポートRAMのAポートに、アドレス、クロックなどをアサインします。 |
|
内部レジスタの定義です。 slwrはFIFO書き込み時に0になります。slrdはFIFO読み込み時に0になります。 cnt32は送信データ用の32bitカウンタです。 cnt2はcnt32を2byteずつ送信するためのカウンタです。 cnt32_16はcnt32をラッチする送信用の16bitバッファです。 cmp_errは受信データの正当性をチックし、正しくない場合に1になります。 first_skipは4同期転送時の受信タイミング調整用で、最初のクロックを無視するためのフラグです。 wait16はFIFOが一杯で書き込めないクロック数をカウントするカウンタです。 |
|
回路全体はIFCLKの立ち上がり信号による同期回路になります。 3−10行目でRST信号によるレジスタに初期値の設定を行います。 4行目はJTAG出力ポート(PC -> FPGA)のカウンタの種をカウンタにセットしています。 |
|
1−2行目 モードが転送モード(MODE_START)の場合に、FIFOの状況をFLAG信号でチェックして、FIFOに空き(あるいはデータが届いている)状態であれば、FIFOに書き込み(読み込み)を行います。 4−8行目 転送方向が(FX2 -> PC)の場合の処理。 slwrをアクティブにして、cnt2の値にしたがって、16bitレジスタcnt32_16にcnt32の内容をコピーします。2回コピーが終わったら、cnt32をインクリメント。 |
|
転送方向が(PC -> FX2)の場合の処理。 slrdをアクティブにして、FIFOバスの内容に正しい値が入っているかチェックする。エラー時はcmp_errに1をセット。 slrdをアクティブにした最初のデータは無効なので、これをネグルためにfirst_skipを使用。 |
|
2-5行目 FIFOが一杯で2Cycles以上待たされた状態での書き込み開始の場合は、デュアルポートRAMのアドレスをインクリメントすることで、wait16の値を書き込む。転送が開始された状態なので、待たされたサイクルをカウントするレジスタwait16はクリアする。 7-10行目 転送開始モード(MODE_START)にも関わらず、FIFOの読み書きが出来ない状態だったので、wait_cnt31とwait16をインクリメントする。 11-14行目 転送が行われないときの状態をセット。 |
|
1行目 JTAGアウトポート(PC -> FPGA)のアサイン 2行目 JTAGインポート(FPGA -> PC)のアサイン 3行目 デュアルポートRAMのデータバスは常にwait16をアサインしておく。アドレスが変更されることで、自動的に書き込まれる。 5-10行目 モード状態、転送方向(DIR)の状態に応じて、FX2の制御信号をコントロールする。 |