USBの使い方
USBを使うデザイン
本ボードには、USB2.0 HighSpeed対応のインタフェースが搭載されています。このインタフェースにはCypres社のEZ-USB FX2というデバイスを用いています。
このデバイスを動作させるためのファームウェアと、FPGAの中のインタフェース回路は、自力で開発するとかなりの工数がかかります。そのため、当社が開発したIPコアおよびDLL、ファームウェア等をパッケージとして提供しています。
提供物
- ファームウェア(出荷時にすでに基板上のチップに書き込み済み)
- デバイスドライバ(Cypress社のサンプルコード(ezusb.sys)をほぼそのまま利用したもの)
- DLL(ユーザ・アプリケーションからデバイスドライバを呼び出すための関数群)
- IPコア(FPGAの中でEZ-USB FX2とインタフェースするための回路)
このパッケージには、以下のような制限があります。
制約事項
- USB2.0 HighSpeedに対応しているパソコンを使うこと。
- 対応しているOSは、32bit版のMicroSoft Windows 2000以降。
64bit版のWindows Vistaや7は対応していない。対応しました。
このコアを使うメリット
このコアを使うと、ユーザアプリケーションからUSBが簡単に扱えるようになります。また、非同期FIFOによって、USBのクロックドメインとユーザ回路のクロックドメインを分離されるので、FPGA内のユーザ回路はUSBの48MHzクロックに合わせなくてよくなります。
実効転送速度は、OUT方向約30MBytes/sec、IN方向約40MBytes/secと、おそらく世界最速です。
使い方
このパッケージを使うには、以下のようにします。
- FPGA内にIPコア(ezusbfx2_ctrl.ngc)を埋め込む
- ユーザアプリケーションはtkusb.dllをインポートし、tkusb.dll内の関数を呼び出す。
IPコアの概要
特電EZ-USB FX2インタフェースコアVer2.1(以下、本コアと略す)は、Cypress Semiconductor社のEZ-USB FX2と、FPGA内のユーザロジックをインタフェースするためのコアです。このコアを使うことで、ユーザは、EZ-USB FX2の難しい設定や仕様を気にすることなく、簡単かつ高速にデータ通信を行うことができます。
図1 IPコアの概要
※本コアは当社のSpartan-6評価ボードでのみ動作します。他のボードでの動作は一切保障いたしませんのでご注意ください。(同様のボードを製作して移植しても動作しません)
IPコアのシンボル
まず最初に、本コアのシンボルを示します。
図2 本コアのシンボル
本コアには大きく分けて、EZ-USB FX2デバイスと接続するためのポートと、ユーザロジックと接続するためのポートがあります。
図2で、左側に描かれたポートの信号はEZ-USB FX2デバイスと接続するためのポートです。そのままFPGAの外に取り出し、EZ-USB FX2に直結してください。
図2で、右上に描かれたポートは、ユーザロジックを接続するためのポートです。ここにはユーザの作成したロジックを接続します。
図2で、右下に描かれたポートはオプション用の信号出力ポートです。
具体的には次のようにします。
① まず、FPGAのソースコードのトップモジュールで、EZ-USB FX2接続用のポートを宣言します。
リスト1 USBを使うための外部ポートの宣言
entity main is Port ( ・・・ USB_FLAGA_IP : in std_logic; USB_FLAGB_IP : in std_logic; USB_FLAGC_IP : in std_logic; USB_SLRD_OP : out std_logic; USB_SLWR_OP : out std_logic; USB_SLOE_OP : out std_logic; USB_FIFOADR_OP : out std_logic_vector(1 downto 0); USB_PKTEND_OP : out std_logic; USB_CLKOUT_IP : in std_logic; USB_IFCLK_OP : out std_logic; USB_RESET_BP : inout std_logic; ・・・ );
|
② 次に、このコアを宣言し、このコアから引き出すための信号を宣言します。
リスト2 IPコアのモジュールを使うための宣言
component ezusbfx2_ctrl is port ( -- ezusb fx2 port usb_clkout_ip : in std_logic; usb_ifclk_op : out std_logic; usb_fd_bp : inout std_logic_vector(15 downto 0); usb_flaga_ip : in std_logic; usb_flagb_ip : in std_logic; usb_flagc_ip : in std_logic; usb_sloe_op : out std_logic; usb_slrd_op : out std_logic; usb_slwr_op : out std_logic; usb_fifoaddr_op : out std_logic_vector(1 downto 0); usb_pktend_op : out std_logic; usb_reset_bp : inout std_logic;
-- user interface port uif_sysclk_ip : in std_logic; uif_reset_ip : in std_logic; uif_rd_data_op : out std_logic_vector(15 downto 0); uif_wr_data_ip : in std_logic_vector(15 downto 0); uif_rd_rdy_op : out std_logic; uif_rd_wait_op : out std_logic; uif_wr_req_op : out std_logic; uif_wr_wait_op : out std_logic; uif_rd_ip : in std_logic; uif_wr_ip : in std_logic;
-- following signals art option uif_usbclk_op : out std_logic; uif_length_op : out std_logic_vector(24 downto 0); uif_addr_op : out std_logic_vector(26 downto 0); uif_flag_op : out std_logic_vector(15 downto 0); uif_debug : inout std_logic_vector(15 downto 0) ); end component;
signal uif_rd : std_logic; signal uif_rd_data : std_logic_vector(15 downto 0); signal uif_rd_rdy : std_logic; signal uif_rd_wait : std_logic; signal uif_wr : std_logic; signal uif_wr_req : std_logic; signal uif_wr_data : std_logic_vector(15 downto 0); signal uif_wr_wait : std_logic; signal uif_wr_pre : std_logic; signal uif_flag : std_logic_vector(15 downto 0); signal uif_debug : std_logic_vector(15 downto 0); signal uif_length : std_logic_vector(24 downto 0); signal uif_lengthd : std_logic_vector(24 downto 0); signal uif_addr : std_logic_vector(26 downto 0);
|
③ 最後に、begin以降の部分で、コアをインスタンシエートします。
リスト3 IPコアのインスタンスエート(デザインの中に組み込むこと)
INST_USBCTRL : ezusbfx2_ctrl port map ( usb_clkout_ip => USB_CLKOUT_IP, usb_ifclk_op => USB_IFCLK_OP, usb_fd_bp => USB_FD_BP, usb_flaga_ip => USB_FLAGA_IP, usb_flagb_ip => USB_FLAGB_IP, usb_flagc_ip => USB_FLAGC_IP, usb_sloe_op => USB_SLOE_OP, usb_slrd_op => USB_SLRD_OP, usb_slwr_op => USB_SLWR_OP, usb_fifoaddr_op => USB_FIFOADR_OP, usb_pktend_op => USB_PKTEND_OP, usb_reset_bp => USB_RESET_BP,
uif_sysclk_ip => clk100m, uif_reset_ip => '0', uif_rd_data_op => uif_rd_data, uif_wr_data_ip => uif_wr_data, uif_rd_rdy_op => uif_rd_rdy, uif_rd_wait_op => uif_rd_wait, uif_wr_req_op => uif_wr_req, uif_wr_wait_op => uif_wr_wait, uif_rd_ip => uif_rd, uif_wr_ip => uif_wr,
uif_usbclk_op => USBCLK, uif_length_op => uif_length, uif_addr_op => uif_addr, uif_flag_op => uif_flag, uif_debug => uif_debug );
|
uif_で始まる信号名がユーザ回路用の信号、大文字のUSB_で始まる信号がEZ-USB FX2と接続するための信号です。
基本的な動作は、次のとおりです。
クロックとリセット
- uif_sysclk_ipポートに、FPGA内のユーザ回路が使用するクロックを入れます。
- uif_usbclk_opポートからUSBのクロック(48MHz)が出てきます。必要なければ使う必要はありません。
- uif_reset_ipを'1'にすると、USBデバイスにリセットがかかり、FPGA内のロジックもリセットされます。
データの受信(OUT転送)
- ホストからデータが送られてきたら、uif_rd_rdy_opが'1'になります。
- ただし、uif_rd_rdyが'1'になった時点では、uif_rd_waitが'1'になっているので、まだ読み出せません。
- uif_rd_waitが'0'になったら、データの読み出しが可能です。
- ユーザ回路はuif_rdを'1'にアサートして、ホストから送られてきたデータを読み出します。
データの送信(IN転送)
- ホストがデータを要求すると、uif_wr_reqが'1'になります。
- ユーザ回路はuif_wrを'1'にアサートして、ホストへ1ワード書き込みます。
コア組み込み後のプロジェクト
コアを組み込むと、プロジェクトツリーは次の図のようになります。
インスタンシエートの際に指定したラベル(INST_USBCTRL)と宣言したモジュール名がプロジェクトに追加されます。
ISE11.xでは、「?」で表示されます。プロジェクトのあるディレクトリに、ezusbfx2_ctrl.ngcとafifo.ngcを入れておいてください。論理合成時に自動的に組み込まれます。
※NGCはネットリストファイルです
図3 ネットリストを使う場合のプロジェクトマネージャの表示(ISE11)
ISE12.xでは、プロジェクトにNGCファイルを追加してください。「NG」と表示されます。
図4 ネットリストを使う場合のプロジェクトマネージャの表示(ISE12)
データの受信(OUT転送)の詳細
動作の説明
- PC上のソフトウェアでデータ書き込み関数がコールされると、本コア内のFIFOに先頭の数百バイトのデータが格納され、uif_rd_rdy_op出力が'1'になります。uif_rd_rdy_opはUSBからの読み出しデータが存在することを示します。この時点では、まだデータは出力されていません。
- FIFOに十分な量のデータが溜まると、uif_rd_wait_opが'0'になって、データが読み出し可能になります。FIFOの残量が十分でないとuif_rd_wait_opが'1'になって、取り出せるデータがないか少ないことを示します。
- ユーザロジックは、(uif_rd_rdy_op = '1' and uif_rd_wait_op = '0')という条件が満たされた場合に、uif_rd_ipを'1'にするようにしてください。
- ユーザ回路がデータを取り込んだら、uif_rd_ipを'1'にしてコアに伝えます。コアは、uif_sysclk_ipクロックの立ち上がりで次のデータを出力します。
- 最後のデータが読み出されたら、uif_rd_rdy_opは'0'に戻ります。
動作波形
次の図は、USBCLKとSYSCLKの速度がだいたい等しい場合の波形です。USBの1パケットに収まるサイズ(つまり512バイト以下)の読み出し時には、ほとんどWAITは発生しません。
図5 USBからのデータ読み出し波形(fUSBCLK ≒ fSYSCLK)
次の図は、USBCLKよりもSYSCLKのほうが速い場合の波形です。FIFOに溜まる速さよりも読み出す速さのほうが速いため、WAITが発生します。
図6 USBからのデータ読み出し波形(fUSBCLK << fSYSCLK)
サンプル回路
サンプルの回路記述を次に示します。
process(clk100m) begin if(clk100m'event and clk100m='1') then if(uif_rd_rdy = '1') then -- USBからの読み出し要求あり uif_rd <= '1'; -- USBへ読み出し要求を出す
if((uif_rd and (not uif_rd_wait)) = '1') then -- USBの読み出しが発行されたら rdcount <= rdcount + 1; -- 読み出しカウンタを増やす end if; else -- USBからの読み出し要求が終了したら rdcount <= (others => '0'); uif_rd <= '0'; -- uif_rdを戻す end if; end if; end process;
user_rd_valid <= uif_rd and (not uif_rd_wait);
|
この回路は、受信データが用意されてから1クロック後に読み出し信号(uif_rd)をアサートします。
有効な読み出しが行われたタイミングで、user_rd_validという信号がアサートされます。
有効な読み出しが行われると、別途作成しておいたuser_rd_countというカウンタを1つインクリメントさせます。このカウンタは、ブロックRAMの書き込みアドレスや、ユーザ設置レジスタのアドレスなどに使うことができます。
図7 USBからのデータ読み出しとユーザ回路の接続
データの送信(IN転送)の詳細
動作の説明
- PC上のソフトウェアでデータ読み出し関数がコールされると、uif_wr_req_op出力が'1'になります。この信号は、PC上のソフトウェアがFPGAにデータの送信を要求していることを示すフラグです。
- ユーザ回路は、uif_wr_data_ip[15:0]に書き込みたいデータを与え、uif_wr_ipを'1'にします。すると、uif_sysclk_ipの立ち上がりでサンプリングされ、FIFOに書き込まれます。
- uif_wr_ipを'0'にすることで、任意のタイミングで書き込みを一時停止することができます。
- FIFOの空き容量が十分でないとuif_wr_wait_opが'1'になります。その場合、ユーザロジックは 次のクロックのタイミングでuif_wr_ipを'0'にして、データの書き込みを一時的に停止してください。
- 最後のデータを書き込むと、uif_wr_req_opは'0'に戻ります。
動作波形
次の図は、USBCLKとSYSCLKの速度がだいたい等しい場合の波形です。USBの1パケットとFIFOに収まるサイズ(概ね2500バイト以下)の書き込み時には、WAITは発生しません。
図8 USBコアへのデータ書き込み波形(WAITなし)
USBCLKよりもSYSCLKが十分速く、また大量のデータを転送した場合にはWAITが発生します。その場合は次のような波形になります。
図9 USBコアへのデータ書き込み波形(WAITあり)
WAIT発生時は必ずuif_wr_ipを'0'にして転送を停止してください。usb_wr_wait_opはフラグが立ってからも2個程度の余裕があり、すぐにFIFOが溢れることはありませんが、できるだけ速やかに次の書き込みを停止してください。FIFOが溢れた場合の動作およびデータ内容は保障されません。
サンプル回路
サンプルの回路記述を次に示します。
process(clk100m) begin if(clk100m'event and clk100m='1') then -- uif_wr信号の生成 if(uif_wr_req = '1') then -- データ送信要求がある if(uif_wr_wait = '1') then -- HOST側のWAIT要求 uif_wr_pre <= '0'; -- 次の次のクロック時にwrを停止 else if(user_throttle = '1') then -- ユーザ都合で送信を停止 uif_wr_pre <= '0'; else uif_wr_pre <= '1'; end if; end if; else -- データ送信要求がない uif_wr_pre <= '0'; end if;
uif_wr <= uif_wr_pre; -- wr_preから1クロック遅れてuif_wrを作る
-- 書き込みカウンタの生成 if(uif_wr_req = '1') then -- データ送信要求がある if(uif_wr_pre = '1') then user_wr_count <= user_wr_count + 1; end if; else -- データ送信要求がない user_wr_count <= (others => '0'); -- カウンタリセット end if; end if; end process;
|
この回路は、送信要求がされてから2クロック後に書き込み信号(uif_wr)をアサートします。
なぜ2クロック後かというと、USBコアへ書き込むデータはBlockRAMの内容を送出する場合が多いためです。BlockRAMの読み出しには1クロックかかるため、書き込みは2クロック後にしたほうが都合がよいためです。
そのため、uif_wr_preという信号を作り、uif_wr_preから1クロック後にuif_wrを動かしています。
送信用FIFO内のデータ量が規定値以上になるとuif_wr_waitがアサートされます。その場合、uif_wr_preを立ち下げます。また、ユーザ回路がデータを用意できていない場合は、user_throttle信号を'1'にすることで、書き込みを一時停止させることができます。
uif_wr_preがアサートされると、別途作成しておいたuser_wr_countというカウンタを1つインクリメントさせます。このカウンタは、ブロックRAMの読み出しアドレスや、ユーザ設置レジスタのアドレスなどに使うことができます。
下の波形は、これらの信号のタイミングを表したものです。
uif_wr_data_ip[15:0]はBlockRAMの出力を、uif_wr_count[x:0]はBlockRAMのアドレスを想定しています。
図10 USBへのデータ書き込みとユーザ回路の接続
(実際にはこのようなタイミングでWAITが出ることはない)
オプション信号
本コアには、以下のオプション信号があります。
- uif_flag[15:0]
- uif_addr[26:0]
- uif_length[24:0]
- uif_debug[15:0]
ここでは、これらの信号の使い方を説明します。
uif_flag[15:0]
uif_flagは、ユーザが自由に使うことができる16bitのフラグです。この信号は、USBリード/ライト関数で指定されたflagという引数がそのまま出力されます。
この信号は、USBのデータ転送に、ターゲットやソースの情報を付加するために使います。
例えば、flagが0x00の場合はFPGA内のユーザ定義レジスタへ、0x01の場合はBlockRAMへ、0x03の場合はI/Oポートへ転送する、といった具合に使います。
user_rd_valid <= uif_rd and (not uif_rd_wait);
reg_wr <= user_rd_valid when (uif_flag = x"00") else '0'; bram_wr <= user_rd_valid when (uif_flag = x"01") else '0'; port_wr <= user_rd_valid when (uif_flag = x"03") else '0'; |
詳しい使い方は本ページに記載しているサンプル回路をご覧下さい。
uif_flag[26:0]
この信号は、APIのSDRAM リード/ライト関数で指定された開始アドレスが出力されます。
データ転送が開始される前にこの信号は有効になっていて、転送中には更新されません。
アドレスは27ビットで、128MBytesまでの空間を指定できます。
uif_length[24:0]
この信号は、転送されるペイロード(ユーザ用のデータ)の長さをワード(16bit)単位で示します。
データ転送が開始される前にこの信号は有効になっていて、転送中には更新されません。
例えば512バイトのデータを転送したい場合、0x0000100を示します。
当コアではuif_rd_rdy_opを見れば転送終了のタイミングはわかるので、この信号は使用する必要はありません。
uif_debug[15:0]
この信号は、使用できません。オープンにしておいてください。
ソフトウェアの説明
ファイルの説明
本IPコアを操作するソフトウェアは表1に示すファイルから構成されます。
表1 IPコアを操作するためのファイル
種類 |
ファイル名 |
デバイスドライバ |
tkdnsp6.sys |
ユーザ・インタフェース用DLL |
tkusb.dll |
DLL用インクルードファイル |
tkusb.h |
インポートライブラリ(Visual C++用) |
tkusb_vc.h |
インポートライブラリ(Borland C++用) |
tkusb_bcc.lib |
ヘッダファイルとインポートライブラリファイルは、ユーザプログラムのビルド時に使用します。
tkusb.dllは、実行するアプリケーション・ソフトウェアの実行ファイルと同じディレクトリに格納してください。
tkdnsp6.sysは、インストーラを実行した際に、Windowsのシステムディレクトリにインストールされます。
プログラムの作成
APIには様々な関数が用意されていますが、最低限必要な関数は、USBOpen、USBSetSmajMode、USBWriteData、USBReadData、USBCloseの4種類です。
USBOpenの後に、必ず、USBSetSmajModeを実行してください。
簡単なプログラムの例を次のリストに示します。
リスト4 USBを使うシンプルなソフトウェア
#include #include #include "tkusb.h"
int main() { unsigned char buf[256]; for(int i=0;i<256;i++) buf[i] = rand(); if(USBOpen() == FALSE) return 0; // FPGAボードなし printf("オープン成功");
USBSetSmajMode(FALSE); // アプリケーション通信ができるようにする
USBWriteData(buf,256,0x01); //BlockRAMへ256バイト転送 printf("書き込み完了");
USBReadData(buf,256,0x01); //IOポートから256バイト受信 printf("読み出し完了");
USBClose(); printf("クローズしました");
return 0; }
|
これをコンパイルするためには、次のようにします。
VisualCの場合
cl test.cpp tkusb_vc.lib
Boaland C++の場合
bcc32 test.cpp tkusb_bcc.lib
プログラムを実行する際にtkusb.dllをEXEと同じディレクトリに入れておく必要があります。
サンプルプログラムとサンプル回路
このサンプルは、上で説明したようなことをまとめたサンプルです。USBから送られてきたデータを、FPGA内のBlockRAMに格納したり読み出したりします。
図11 サンプルデザインの構成
FPGA内にはBlockRAMのバッファと、ユーザ定義の内蔵レジスタが用意されています。
内蔵レジスタはプッシュスイッチとLEDに接続されています。
OUT転送の際のフラグが0x01ならばBlockRAMに、フラグが0x00ならば内蔵レジスタに値を出力します。
IN転送の際のフラグが0x01ならばBlockRAMから、フラグが0x00ならば内蔵レジスタの値を読み出します。
BlockRAMは32kBytesのサイズ(16kword×16bit)を確保しています。このRAMのポートAはUSBとの通信用に使っていますが、ポートBはユーザ用に開放しています。
したがって、ユーザ回路をこのBlockRAMのポートBに接続することで、さまざまな応用回路を作ることができます。これを雛型にして、簡単なデータキャプチャ装置やデータ出力装置が作れるでしょう。
添付のプログラムusbapp¥test.exeを実効すると、LEDが■□■□■□■□ ↔ □■□■□■□■と点滅します。この点滅はパソコンのプログラムからUSBで制御されています。基板上の青色スイッチを押すと、プログラムが停止します。
この回路の動作を動画で紹介します。
既知の不具合
Version2.1 (平成22年1月11日リリース)
Version2.1コアの既知の問題
IPコア |
-3グレード(高速版)のFPGAで、周辺温度が低温(10℃以下)の場合、データが化ける可能性があります。 |
length_opが機能していません。 | |
ソフトウェア | USBからの応答がない場合に、USBReadData、USBWriteData関数がフリーズしてしまいます。この場合、USBポートを抜き差しするしか復帰の手段がありません。 |
Version2.2 (平成22年10月4日リリース)
Version2.2コアの既知の問題
IPコア | |
ソフトウェア |
タイムアウトした後に、通信が再開できないことがあります。その場合、EZ-USB FX2をリセットするしか復帰の手段がありません。 |