<スピード測定ツール>

「カメレオンUSB FX2」のコンセプトは他の機器と接続して、高速にデータ転送をおこなうことにあります。このため、単体で何か役に立つアプリケーションというのが実は難しいところです。とはいえ、ある程度は実用的でなければ作っても意味がありません。そこで考えたのがこの、「スピード測定ツール(spd_chk)」です。

USB2.0 HiSpeed転送といえばスペック的には480Mbpsということになっていますが、実際のところどの程度のスピードが出るのかについては、PCのチップセット・OSの種類(Windows2000なのかVistaなのか)・OSに組み込んでいるデバイスなどによって変動します。もちろんUSBデバイスのマイコンや、マイコンで動作するファームウエアなども影響を与えます。

実際何かの要件があって、高速でデータ転送を行う必要が生じたとき、「カメレオンUSB FX2」と手持ちのPCを使って、スペック的にアプリケーションを作成することが可能なのかどうか? これは重要な問題です。実際に開発を進めたあげく、やっぱり速度が足りませんでしたでは悲しすぎます。

そこで、今回「カメレオンUSB FX2」をPCに接続して、いったい「現在の環境でどの程度までの速度でデータの転送が出来るのか」という「限界転送速度」を測定することにしました。PCで実行中のプログラムから、MAX2の内部までを、上り(MAX2 -> PC)下り(PC -> MAX2)それぞれ測定します。もちろん、ただデータを送ればよいというわけではなく、正しく送れているかも受け側でチェックするようにしています。

このサンプルアプリケーションは、自由に改変して使用していただいて構いません。ユーザ独自のアプリケーションを開発する際のフレームワークとしてお使いください。

ダウンロード スピード測定アプリ(spd_chk)ソース一式(ホストアプリ・ファーム・ロジック)

使用例とバイナリはこちら


<改造版 スピード測定ツール(FX2 -> PC 方向)> 2010.4.23追加

上記のツールはFIFOバッファの状況を見ながら転送を行い、最大効率の転送能力を測定しています。瞬間的には400Mbpsの速度が出ている時もあれば、OSが他の処理をしているため一時的に転送が待たされている時もあります。この2つを平均して上記の転送速度が表示されています。

液晶パネルの表示データをキャプチャしてPCに取り込むといった用途では、表示用のデータは常に生成されていますので、常にPCに転送し続ける必要があります。転送が待たされている場合は、「カメレオンUSB FX2」の2KbyteのFIFOバッファにデータが溜められます。 FIFOバッファの容量以上に待たされる場合はデータがドロップ(捨てられる)する状況が発生します。(MAX2にRAMを付ければ話は別です)

例えば320*240ドット、24bitColor、60FPSの液晶パネルのデータをキャプチャするには320*240*24*60=110Mbpsの転送速度が必要です。この場合spd_chk.exeの表示する平均の転送速度が120Mbpsあっても十分では無く、コンスタントに絶え間なく120Mbpsで転送できる必要があるのです。
コンスタントに転送出来ると言う事は、FIFOへの書き込みに待ち時間が発生しないということです。そこで上記のspd_chkを改造して、指示したスピードで転送を行った場合に、FIFOの待ちが発生するか調べるツールを作成しました。

指示した転送速度で転送を行う方法は、下記の方法で実装しています。FIFOへの書き込みは48MHzのスピードで1byte単位で行われています。待ちが無く書き込めれば48*8=384Mbpsの速度になります。そこで、連続する256サイクルの書き込みタイミングの中から一定のクロック分を間引いて書き込みます。例えば128クロック間引けば48*8*128/256=192Mbpsになります。spd_chk2は指定された速度に対して必要なクロック数を間引きます。

使用方法は引数にスピード(Mbps単位)を指定して起動します。120Mbpsで転送できるか調べるためには「spd_chk2 120」と起動します。転送が間に合わず、正しくないデータがPCに届いた場合は途中で中断されます。spd_chk2は永久にデータを転送し続けますので、適当なところでESCキーを押して終了させます。終了時にFIFOの書き込みに待ちが発生したかを調べて、待ちがない場合は「*** NO Drop data. ***」のメッセージが表示されます。

spd_chk2 ソース&バイナリ
(max2のディレクトにあるsvfを書き込んで使用します。)
(このツールではspd_chk.cppのBUF_LENとQUE_NUMの値を増やしています。アプリケーション実行時に確保される転送バッファが増加しますのでドロップ対策に有効です。)


<FX2LPのFIFOについて>

FX2LPはGPIFとFIFOと大きく2つのインターフェイスを提供しています。GPIFというのはFX2LP側が主導権をもって、外部機器をコントロールしてデータ転送を行うための仕組みで、IDEインターフェイスのハードディスクなどを直結して使用することが出来るようになっています。これに対してFIFOというのは、F2LPは単なるFIFO(先入れ先出し)メモリとして動作して、FX2LPの外部回路が主導権をもってデータ転送を行うことになります。

「カメレオンUSB FX2」の場合はMAX2という、汎用的かつ高速な外部回路が存在しますので、通常FX2LPはFIFOモードとして使用することになります。

FIFOメモリとして動作すると書きましたが、このFIFOメモリのアクセスの方法によって2種類のモードに分けることが出来ます。非同期アクセスモードと同期アクセスモードです。非同期アクセスモードは、ライト(リード)信号をFX2LPに与えて、このライト(リード)信号の立ち上がり(立下り)のタイミングでFIFOメモリーにデータを書き込む(読み込む)方法です。

これに対して同期アクセスモードは、ライト(リード)信号を有効にしている期間に、IFCLKと呼ばれるクロック信号の立ち上がり(立下り)に同期して毎回データを書き込む(読み込む)方法です。転送速度は非同期方法に比べて高速になりますが、その反面タイミングを取るのが難しくなります。

同じ同期アクセスモードでもこのIFCLKをFX2LPの内部(30/48MHz)で生成するか、外部から(Max 48MHz)取り込むかによっても動作タイミングが異なってきます。また、FIFOのデータバスの幅をエンドポイント毎(FIFOアドレスで選択する)に8bitか16bitに切り替えることが可能です。

速度的にはバス幅16bitで48MHzで転送するのが一番高速になりますが、480MbpsのUSB2.0 HiSpeedの速度を考慮すれば、8bit幅 48MHz(384Mbps)でも十分に使い切ることになります。

各転送モード時の信号のタイミングチャートははFX2LPのデータシートを参照していただく必要がありますが、高速なデータ転送を行うためには下記の点に留意する必要があります。


<spd_chkアプリの処理概要>

spd_chkアプリではエンドポイントは上り・下りでそれぞれ固定。8bit幅のデータバスで48MHzのFX2LP内部クロックを使った非同期転送で限界速度を測定します。下りのエンドポイントはEP2OUT(アドレス0x02)、上りのエンドポイントはEP6IN(0x86)を使用します。各エンドポイントの長さはバルクエンドポイントの最大長の512Byteでこれをクワッド(x4)バッファとして、2Kbyteを割り当てます。

測定方法は上りを測定して、下りを測定というようにそれぞれを分けて測定することで、測定中のエンドポイントの切り替えは行いません。

32bitのインクリメンタル数値を転送して、受信側で正しいことをチェックします。本来は不要な機能ですが、開始番号(シード)をホスト側からMAX2のレジスタに設定できる機能も、サンプルアプリケーションということで実装しました。

限界速度を測定するために、FIFOの状態信号(FLAGA/FLAGB)をMAX2がチェックして、FIFOに空きがある状態であればデータを書き込む(データがFIFOに到着していれば読み込む)方法を採用しています。MAX2はFIFOが一杯(FIFOにデータが無い)の状態の待ちクロック数(wait_cnt31)をカウントしています。転送にかかったクロック数(時間)は(待ちクロック数+転送バイト数)となり、実際の転送速度を計算することが出来ます。

待ちクロック数(wait_cnt31)とMAX2側でデータを検証したフラグは、PC側から読み出すことが出来ます。

(USB2.0 HiSpeed専用のアプリケーションです、USB1.1やFullSpeedでは動作しません)


<ファームウエア>

「カメレオンUSB FX2」のサンプルアプリケーションということで、なるべく汎用的に他のアプリケーションでもそのまま使用できるようなFWを目指しました。
FWとして実装されている機能は下記の通りです。

今回作成したFWはMAX2内部のレジスタの読み書きをサポートしています。この機能を実現するために、MAX2とFX2LPは2bitのMODE信号(FX2:PD6,PD7 MAX2:IO2,IO3)を使用してお互いのフェーズを合わせています。

名称  
MODE_IDLE 0 アイドル状態(初期・転送終了時など)
MODE_REGR 1 レジスター読出し(データバスはFIFOバスを共有)
MODE_REGW 2 レジスター書き込み(データバスはFIFOバスを共有)
MODE_START 3 FIFO開始状態

FWで実装されているコマンドは下記の表の通りです。
コマンドとパラメータはEP1OUTエンドポイントを使ってFX2LPに送信します。戻り値のあるコマンドを発行した場合は、戻り値をEP1INを使用して、必要なバイト数を読み出してください。

コマンド名称 パラメータ 戻り値 処理内容
CMD_EP6IN_START なし なし EP6IN転送開始
CMD_EP6IN_STOP なし なし EP6IN転送中断
CMD_EP2OUT_START なし なし EP2OUT転送開始
CMD_EP2OUT_STOP なし なし EP2OUT転送中断
CMD_PORT_CFG レジスタアドレスビット、
出力ポート指定
なし PDポートの設定
レジスタアドレスビット:MAX2のレジスタアドレスのポートを指定
出力ポート指定:出力に設定するポートを指定
CMD_REG_READ レジスタ番号 レジスタ値 MAX2レジスタの読出し
CMD_REG_WRITE レジスタ番号、値 なし MAX2レジスタの設定
CMD_PORT_READ なし ポート状態 FX2LPのPORTDの読出し
CMD_PORT_WRITE ポート状態 なし FX2LPのPORTDの書き込み
CMD_IFCONFIG なし FX2LPのIFCONFIGレジスタに書き込む(転送モード、IFCLK設定)
例)0xE3(同期、内部IFCLK48MHz使用)
0xA3(同期、内部IFCLK30MHz使用) 0xEB(非同期、IFCLK48MHz出力のみ)
CMD_MODE_IDLE なし なし アイドルモード(レジスタ書き込み、転送終了時に設定)

spd_chk.cppから抜粋 FWコマンドの使用例
  1. LONG cmd_len=0,ret_len=0;
  2. u8 cmd[64];
  3. u8 ret[64];
  4.  if(usb->fwload(t, fw_iic,(u8 *)"FX2_FIFO"))
  5.  {
  6.    inep = usb->get_endpoint(0x81);
  7.    outep = usb->get_endpoint(0x01);
  8.    //FX2 I/Oポート設定 & MAX2初期パラメータ設定 & MAX2リセット
  9.    cmd[cmd_len++]=CMD_PORT_CFG;
  10.    cmd[cmd_len++]=0x03; //ADDR[1:0](addr_mask)
  11.    cmd[cmd_len++]=0x0c; //RST, DIR(out_pins)
  12.    cmd[cmd_len++]=CMD_REG_WRITE;
  13.    cmd[cmd_len++]=0; //ADDR=0
  14.    cmd[cmd_len++]=(u8)(START_SEED32>>0); //init_val[7:0]
  15.    cmd[cmd_len++]=CMD_REG_WRITE;
  16.    cmd[cmd_len++]=1; //ADDR=1
  17.    cmd[cmd_len++]=(u8)(START_SEED32>>8); //init_val[15:8]
  18.    cmd[cmd_len++]=CMD_REG_WRITE;
  19.    cmd[cmd_len++]=2; //ADDR=2
  20.    cmd[cmd_len++]=(u8)(START_SEED32>>16); //init_val[23:16]
  21.    cmd[cmd_len++]=CMD_REG_WRITE;
  22.    cmd[cmd_len++]=3; //ADDR=3
  23.    cmd[cmd_len++]=(u8)(START_SEED32>>24); //init_val[31:24]
  24.    cmd[cmd_len++]=CMD_MODE_IDLE; //アイドルモードに戻しておく
  25.    cmd[cmd_len++]=CMD_PORT_WRITE;
  26.    cmd[cmd_len++]=RST_1; //RST=1
  27.    cmd[cmd_len++]=CMD_PORT_WRITE;
  28.    cmd[cmd_len++]=RST_0; //RST=0
  29.    cmd[cmd_len++]=CMD_IFCONFIG;
  30.    cmd[cmd_len++]=0xE3; //IFCLK=48MHz, IFCLK出力, 同期(Sync)モード
  31.    usb->xfer(outep, cmd, cmd_len);
  32.    cmd_len=0;
  33.    ......
1−3行目 コマンド転送バッファ、インデックス変数の宣言です。コマンド・戻り値ともに64byteまで同時に転送することが出来ます。

5行目 ファームウエアのロードを行っています。FWが持つ文字列"FX2_FIFO"を指定することで、既にFWがロード済みの場合は処理を早くすることが出来ます。

7−8行目 コマンド転送・戻り値用エンドポイントの確保。

11−13行目 PORT_CFGコマンドを使用して、FX2LPのポートDを設定します。spd_chkではMAX2のレジスタ選択アドレスにPD0、PD1を使用しています。PD2をRST信号、PD3をDIR信号に出力ポートとして使用。

14−25行目 MAX2のレジスタ(init_val)に値を書き込む

27行目 レジスタの書き込み終了後は、ゴミを書かないようにアイドルモードに戻す

28−31行目 ポートを制御してMAX2をリセット(書き込んだレジスタ内容を反映させる)

32−33行目 FX2LPのIFCONFIGレジスタに書き込む値を指定することで、転送モードやIFCLKを設定します。今回は同期モードで内部IFCLK48MHz動作にします。

34行目 設定したコマンドをoutepエンドポイントを使ってFWに転送します。

35行目 次回のコマンドのために、インデックス変数を初期化しておきます。

今回発行したコマンドには戻り値のあるコマンドはありませんでしたが、、戻り値のある場合はinepエンドコマンドから戻り値を読み込みます。

<MAX2デザイン>

spd_chk.v Verilogソースの解説
  1. `define DEBUG
  2. module spd_chk(FX2_SLOE, FX2_SLRD, FX2_SLWR, FX2_FD, FX2_FIFOADR, FX2_PKTEND,
  3.     FX2_FLAGA, FX2_FLAGB, FX2_FLAGC, FX2_IFCLK,
  4.     MODE, ADDR, RST, DIR,
  5. `ifdef DEBUG
  6.     TP
  7. `endif
  8. );
モジュール定義部分です。FX2_が先頭に付く信号は、「カメレオンUSB FX2」基板内部でFX2LPと接続されている信号ですので、全て記述しておきます。Qualtusツールの「Pin Planner」ツールでMAX2のI/Oピンに割り当てます。

1行目のDEBUGがデファインされている場合は、TP(テストポート)信号が有効になります。私の場合はTPをロジアナに接続してデバッグを行っています。
  1. output FX2_SLOE;
  2. output FX2_SLRD;
  3. output FX2_SLWR;
  4. inout [7:0] FX2_FD;
  5. output [1:0] FX2_FIFOADR;
  6. output FX2_PKTEND;
  7. input FX2_FLAGA;
  8. input FX2_FLAGB;
  9. input FX2_FLAGC;
  10. input FX2_IFCLK;
4行目 のFIFO用データバスは8bitで使用します。MAX2のレジスタのリードライトにもこのバスを使用しています。
  1. `ifdef DEBUG
  2.   output [11:0] TP;
  3.   assign TP[0] = FX2_SLRD;
  4.   assign TP[1] = FX2_SLWR;
  5.   assign TP[2] = FX2_FLAGA;
  6.   //assign TP[2] = cmp_err;
  7.   assign TP[3] = FX2_FLAGB;
  8.   assign TP[11:4] = FX2_FD;
  9. `endif
デバッグ用の信号をTPに割り振っています。DEBUG時のみ有効になります。
  1. `define MODE_IDLE   0
  2. `define MODE_REGR   1
  3. `define MODE_REGW   2
  4. `define MODE_START  3
  5. input [1:0] MODE;
  6. input [1:0] ADDR;
  7. input RST;
  8. `define DIR_FX2PC  0
  9. `define DIR_PC2FX  1
  10. input DIR;
1−5行目は 動作モードをFX2LPからMAX2に伝えるための信号の定義です。(FX2LPのPD7、PD6と接続)
7行目はMAX2のレジスタを選択するためのアドレス線として使用します。(FX2LPのPD0、PD1と接続)
8行目はMAX2のリセット用信号として使用しています。(FX2LPのPD2と接続)
10-12行目はFIFOの転送方向をMAX2に伝えるために使用します。(FX2LPのPD3と接続)
  1. reg [31:0] init_val;
  2. always @(posedge FX2_IFCLK)
  3. begin
  4.   if(MODE == `MODE_REGW) begin
  5.     if    (ADDR == 0) init_val[7:0]    <= FX2_FD;
  6.     else if(ADDR == 1) init_val[15:8]   <= FX2_FD;
  7.     else if(ADDR == 2) init_val[23:16]  <= FX2_FD;
  8.     else if(ADDR == 3) init_val[31:24]  <= FX2_FD;
  9.   end
  10. end
PCに送信するデータのシードを保持するレジスタinit_valの書き込み処理です。モードがMODE_REGWの時にFX2_FDバスからデータを取り込みます。FX2_FDバスは8bitですのでアドレス信号にて切り替えを行います。書き込みのタイミングにはIFCLKを使用しています。
  1. reg slwr,slrd;
  2. reg [31:0] cnt32;
  3. reg [30:0] wait_cnt31;
  4. reg [1:0] cnt2;
  5. reg [7:0] cnt32_8;
  6. reg cmp_err;
  7. reg first_skip; //first data is invalid
内部レジスタの定義です。
slwrはFIFO書き込み時に0になります。slrdはFIFO読み込み時に0になります。
cnt32は送信データ用の32bitカウンタです。
cnt2はcnt32を1byteずつ送信するためのカウンタです。
cnt32_8はcnt32をラッチする送信用の8bitバッファです。
cmp_errは受信データの正当性をチックし、正しくない場合に1になります。
first_skipは4同期転送時の受信タイミング調整用で、最初のクロックを無視するためのフラグです。
  1. always @(posedge FX2_IFCLK)
  2. begin
  3.   if(RST) begin
  4.     cnt32 <= init_val;
  5.     wait_cnt31 <= 0;
  6.     cnt2 <= 0;
  7.     slwr <= 1;
  8.     slrd <= 1;
  9.     cmp_err <= 0;
  10.     first_skip <= 1;
回路全体はIFCLKの立ち上がり信号による同期回路になります。
3−10行目でRST信号によるレジスタに初期値の設定を行います。
4行目はPCから書き込んだカウンタの種をカウンタにセットしています。
  1. end else if((((FX2_FLAGB == 1)&&(DIR == `DIR_FX2PC)) || 
  2.         ((FX2_FLAGA == 1)&&(DIR == `DIR_PC2FX))) &&
  3.         (MODE == `MODE_START)) begin
  4.     if(DIR == `DIR_FX2PC) begin
  5.       slwr <= 0;
  6.       if (cnt2 == 0) cnt32_8 <= cnt32[7:0];
  7.       else if(cnt2 == 1) cnt32_8 <= cnt32[15:8];
  8.       else if(cnt2 == 2) cnt32_8 <= cnt32[23:16];
  9.       else if(cnt2 == 3) cnt32_8 <= cnt32[31:24];
  10.       if(cnt2 == 3) cnt32 <= cnt32 + 1;
  11.     end
1−3行目 モードが転送モード(MODE_START)の場合に、FIFOの状況をFLAG信号でチェックして、FIFOに空き(あるいはデータが届いている)状態であれば、FIFOに書き込み(読み込み)を行います。

4−11行目 転送方向が(FX2 -> PC)の場合の処理。
slwrをアクティブにして、cnt2の値にしたがって、8bitレジスタcnt32_8にcnt32の内容をコピーします。4回コピーが終わったら、cnt32をインクリメント。
  1.     if(DIR == `DIR_PC2FX) begin
  2.       slrd <= 0;
  3.       if((cnt2 == 1) && (FX2_FD != cnt32[ 7: 0])) cmp_err <= 1;
  4.       if((cnt2 == 2) && (FX2_FD != cnt32[15: 8])) cmp_err <= 1;
  5.       if((cnt2 == 3) && (FX2_FD != cnt32[23:16])) cmp_err <= 1;
  6.       if(first_skip == 0) begin
  7.         if((cnt2 == 0) && (FX2_FD != cnt32[31:24])) cmp_err <= 1;
  8.         if(cnt2 == 0) cnt32 <= cnt32 + 1;
  9.       end
  10.       first_skip <= 0;
  11.     end
転送方向が(PC -> FX2)の場合の処理。
slrdをアクティブにして、FIFOバスの内容に正しい値が入っているかチェックする。エラー時はcmp_errに1をセット。
slrdをアクティブにした最初のデータは無効なので、これをネグルためにfirst_skipを使用。
  1.     cnt2 <= cnt2 + 1;
  2.   end else begin
  3.     if(MODE == `MODE_START) wait_cnt31 <= wait_cnt31 + 1;
  4.     slwr <= 1;
  5.     slrd <= 1;
  6.     cnt2 <= 0;
  7.     first_skip <= 1;
  8.   end
  9. end
1行目 はcnt2レジスタのインクリメント。
3行目 転送開始状態なのに、FIFOが一杯(空)のため待ちの場合は、wait_cnt31をインクリメント。
4−7行目 転送が行われないときの状態をセット。
  1. wire [7:0] reg_data;
  2. assign reg_data = (ADDR == 0) ? wait_cnt31[7:0] :
  3.            (ADDR == 1) ? wait_cnt31[15:8] :
  4.            (ADDR == 2) ? wait_cnt31[23:16] : { cmp_err, wait_cnt31[30:24] };
  5. wire [7:0] fifo_data;
  6. assign fifo_data = cnt32_8;
  7. assign FX2_SLOE = ((MODE == `MODE_START)&&(DIR == `DIR_PC2FX)) ? 0 : 1;
  8. assign FX2_FIFOADR = (DIR == `DIR_FX2PC) ? 2 : 0;
  9. assign FX2_PKTEND = 1;
  10. assign FX2_SLRD = slrd;
  11. assign FX2_SLWR = slwr;
  12. assign FX2_FD =
  13.   ((MODE == `MODE_REGW) || ((MODE == `MODE_START) && (DIR == `DIR_PC2FX))) 8'bzzzzzzzz :
  14.   (MODE == `MODE_REGR) ? reg_data : fifo_data;
  15. endmodule
1−4行目 MAX2レジスタ読出しのための定義、32bit目には検証結果cmp_errのフラグをセット。
9−16行目 モード状態、転送方向(DIR)の状態に応じて、FX2の制御信号をコントロールする。

(行番号が見にくくてすみません)
(必ずMAX2の未使用I/Oは「As input tri-stated with weak pull-up registor」に設定してください)


オプティマイズの開発環境紹介
私が使用している開発環境です。
手前に見える裏返っている基板は「カメレオンUSB+ロジアナ」です。MAX2のデバッグ用テストポートに直結されていて、Verilogソースを修正してTPに見たい信号を割り当てて即座に確認することが出来ます。

左にあるのが「Byte Blaster MV互換(JTAGライター)」ユニバーサルエリアにJTAGコネクタを付けて使用しています。
(リボンケーブルを付けて取り回しやすくしています。)
QualtusIIと連動してすぐにMAX2にデザインを書き込めるので、開発効率がアップします。

「カメレオンUSB FX2」基板からJTAG信号を引き出す方法はこちらのページを参照ください。