PythonによるUSB3.0+FPGA開発
特電Artix-7ボードのUSB3.0 APIをPythonで操作する方法を説明します。
特電Artix-7ボードのサンプルデザインにはFX3IPコアが入っており、このIPコアを使うと毎秒300Mバイト以上の速度でデータを送受信できるようになります。
ダウンロード
まず、こちらのURLからPythonテストプログラムをダウンロードしてください。
- Pythonで書いたUSB3.0通信プログラム 2023/8/11 NEW
FPGAにはFX3IPコアを含んだデザインを書き込んでおいてください。
ファイルの説明
Pythonテストプログラムを解凍すると、中に3つのファイルが入っています。
- tkusbfx3.dll・・・特電Artix-7ボードのUSB3.0 DLL
- tkusbfx3.py・・・USB3.0 DLLをPythonで扱うためのラッパ
- test.py・・・Pythonのテストプログラム
ベンダIDとプロダクトID
このPythonライブラリが使用しているDLLは、VID=0x2129 PID=0x0640のデバイス(特電Aritx-7ボード、Cosmo-K、Cosmo-Z)に対して動作確認をしています。
Pythonの中でもDLLの中でも特にベンダID、プロダクトIDによる制限は行っていないので、CypressのEZ-USB FX3が動作するデバイスでCypressの標準的なデバイスドライバが読み込まれていれば動作する可能性はあります。
APIの説明
tkusbfx3.dllとtkusbfx3.pyを同じディレクトリに置き、 import tkusbfx3を行うと以下のAPIが使用できるようになります。
関数名 | 機能 |
DeviceCount | 接続されているTKDN FX3デバイスの数を返す |
Open | 番号を指定してUSBデバイスをオープンする |
Close | USBデバイスを閉じる |
Write | USBデバイスにデータを送信する |
Read | USBデバイスからデータを受信する |
SetTimeOut | USBから応答がない場合のタイムアウト時間を設定する。 (デフォルトは10秒) |
SetTransferSize |
1回の転送で何バイトを転送するか、最大転送サイズを設定 |
インポートする際にasを使うと名前を短くすることができます。以下のサンプルでは名前をtkusbとしています。
import tkusbfx3 as tkusb
DeviceCount
DeviceCountは接続されているFX3デバイスの数を返します。戻り値はintです。
この関数が0を返した場合は特電Artix-7ボード(またはCosmo-K)が接続されていません。2以上の数を返した場合は複数のボードが接続されています。
# 最初に何個のデバイスがあるかを数える devcount = tkusb.DeviceCount() if devcount == 0: print("No TKDUSBFX3 device found") exit(); print("%d devices found" % devcount)
Open
FX3デバイスをオープンします。引数は見つかったデバイスの番号です。0を指定すると最初に見つかったデバイスをオープンします。PCにArtix-7ボードが1枚しか接続されていない場合は0を指定します。
もし、DeviceCountで複数のデバイスが検出された場合は引数を0,1,・・と変えていくことで個々のデバイスをオープンできます。
# 最初に見つかったデバイスをオープンする result = tkusb.Open(0) print("Tokuden FX3 open status=%d VID=%s PID=%s DESCR=%s" % result) if(result[0] == False): # 見つからなければ終了 print("No TKUSBFX3 device found") exit()
Close
FX3デバイスをクローズします。プログラムの最後で呼び出してください。呼び出さなくてもプログラム終了と同時にクローズされるので特に問題はありません。
tkusb.Close()
Write
特電FX3IPコアにデータを書き込みます。この関数を使用するにはFPGA内にFX3IPコアが実装されている必要があります。
- 第1引数はアドレス(uint32)
- 第2引数は送信したいデータが格納されたバッファ(bytearray)
- 第3引数は送信したいデータサイズ(uint32)
- 第4引数はフラグ(uint16)
- 戻り値は送信したバイト数
です。送信したいバイト数は16以上を指定してください。
バッファのサイズと、datasizeの短いほうが実際に採用されます。そのためdatasizeがバッファより大きくても問題はありません。
フラグの値は、FPGA内のFX3IPコアに送信される16bitの値です。サンプルのFPGAデザインでは、フラグの値が0の場合はFPGA内のレジスタが、1の場合はBRAMが、2の場合はAXIバスに出力されるようになっています。
# 送信データを作る wbuf = bytearray(INOUT_BUFFER_SIZE) # ミュータブルなバイト配列 for i in range(length): wbuf[i] = int(random.random() * 256) sentbytes = tkusb.Write(addr, wbuf, datasize, flag)
Read
特電FX3IPコアからデータを読み出します。この関数を使用するにはFPGA内にFX3IPコアが実装されている必要があります。
- 第1引数はアドレス(uint32)
- 第2引数は受信データを格納するバッファ(bytearray)
- 第3引数は受信したいデータサイズ(uint32)
- 第4引数はフラグ(uint16)
- 戻り値は受信したバイト数
です。受信したいバイト数は16以上を指定してください。
バッファのサイズと、datasizeの短いほうが実際に採用されます。そのためdatasizeがバッファより大きくても問題はありません。
フラグの値は、FPGA内のFX3IPコアに送信される16bitの値です。サンプルのFPGAデザインでは、フラグの値が0の場合はFPGA内のレジスタが、1の場合はBRAMが、2の場合はAXIバスが選択されるようになっています。
# 受信バッファの用意 rbuf = bytearray(INOUT_BUFFER_SIZE) # ミュータブルなバイト配列 receivedbytes = tkusb.Read(addr , rbuf, datasize, flag)
SetTimeOut
送信受信のタイムアウトをms単位で指定します。デフォルトは10秒です。0xffffffffを指定した場合は無限に待ちます。
例えば300MByteのデータをUSB 2.0の速度で送信しようとした場合、だいたい10秒かかります。タイムアウトが短いと大きなデータを送信中なのにタイムアウトしてしまうので気を付けてください。
SetTransferSize
DLLでは大きなデータを小分けにして送信していますが、その小分けにするサイズを指定します。デフォルトでは1MByteです。このサイズが大きいとカーネルモードとユーザモードの遷移が少なくなるので効率よくなりますが、WindowsのデバイスドライバのDMAの制限により2MByteくらいにしておいたほうがよいでしょう。変更する必要はありません。
実行結果
サンプルのtest.pyはArtix-7ボード上のDDR3メモリに対して、256バイト、512バイト、1024バイト・・・16777216バイトの乱数データを送受信し、送受信した内容に相違点がないかどうかを調べます。
いわゆるUSB 3.0とDDR3のテストを行います。
実行結果を示します。
まとめ
- VID=2129、PID=0640のデバイスを開いて操作しますが、デバイスドライバのINFファイルを変更して署名を付け直してCypressのCYUSB3ドライバを読み込ませれば他のVID,PIDでも動作するはずです。
- FPGA内には特電FX3IPコアを実装しておく必要があります。
- import tkusbfx3でインポートし、Open,Close,Read,Writeの4つの関数で操作します。
- ReadとWriteのアドレスはFPGA内のAXIバスに出てくるアドレスとなります。第4引数のflagには2を指定します。
- Pythonでラッパすることによる速度の低下はほとんどありません。