USBのコントローラーに比べてお手軽にUSBデバイスを作り上げることができるFTDI社のFT601の使い方を紹介します。FT601+FPGAでUSBデバイスを作り、Windows PCからUSB3.0で通信を行います。通信といっても4KBのデータをループバックさせて送信データと受信データの一致を確認するだけのシステムの設計例です。大変少ないコード量で実現できることがわかります。応用例がすぐ頭に浮かぶのではないでしょうか。

FTDI
FTDI(Future Technology Devices International Limited)は、USB テクノロジーを専門とするスコットランドのファブレス半導体デバイス会社で、最新のコンピュータでレガシー デバイスをサポートするために、RS-232 または TTL シリアル伝送と USB 信号間の変換を行うデバイスであるFT232が有名です。このFT232で同社を知った方が多いのではと思います。PCからRS232Cポートが消えていく中でRS232Cを制御しなければならない場面で、USB技術を全く知らなくても扱える、USBの恩恵に与ることができるFT232に衝撃を受けたことを覚えています。技術者はほぼ何もしなくてもRS232Cを使った資産をUSBに置き換えできるのですから。私はFT232でFTDI社を知った後、同社のFT245も扱いました。FT245は8bitパラレルデータの送受信がUSBで行えるチップで、こちらも驚くほど簡単に扱える、USBの技術をを知らなくてもUSBの恩恵に与ることができるチップでUSBデバイスの開発を楽にしてくれました。

FT600FT601
FT600 および FT601 SuperSpeed USB3.0 IC は、高速かつ大容量のデータ転送を可能にし、広範囲のデータ集約型イメージングおよびデータ収集アプリケーションを USBを使った通信システムで構築できることをコンセプトに開発されています。どちらのチップも最大 8 つのエンドポイントと 2 つの追加通信エンドポイントをサポートし、最大 4 つのデータ IN チャネルと 4 つのデータ OUT チャネルを、IN で最大 4k バイト、OUT で 4k バイトの長さのダブル バッファリングで作成できます。このシリーズは接続と制御が簡単で、追加のファームウェア開発は必要ありません。
USB3.0 SuperSpeed (5Gbps)、USB High Speed (480Mbps)、および USB 2.0 Full Speed (12Mbps) 転送のサポートし、FT600は16 ビット幅のFIFOインターフェイス、FT601は32 ビット幅のパラレル FIFO インターフェイスで利用が可能です。最大 8 つの構成可能なエンドポイント (パイプ)と内蔵16kB FIFOデータバッファRAMを持っています。

開発環境
FT600やFT601を試してみたい場合に、これらの評価ボード(UMFT60xx)を手に入れて、FMC又はHSMCコネクタのいずれかを備えたFPGAのボードと接続するのがもっともお手軽でしょう。FT600やFT601の評価ボード(UMFT60xx)は同社のWEBDigikeyRSコンポーネンツ等で購入できます。FPGAのボードは以前より所有しているCyclone Vスターターキットを用いました。こちらもDigikeymarutsuで購入することができます。一方PC側は、USB3.0のポートを備えているPCであれば何でも良いようで。なぜならば必要なデバイスドライバやライブラリはWindows、Linux、Mac用に用意されているためです。ここで紹介する開発環境は以下のとおりです。

  • Cyclone Vスターターキット : FPGA評価ボード
  • UMTF601A-B :FT601評価ボード
  • Quartus Prime Little Edition 20.1.1 :FPGA開発ツール
  • PyCharm 2022.1(Commyniti Edition) / Python 3.9.18
  • D3XX Drivers:FT600やFT601と通信するためのデバイスドライバー(Windows64bit用を使用)
  • サンプルプログラムや各種ライブラリ:FT600/FT601を扱うためのPythonのモジュールもここから

FT601の動き
FT601には、マルチチャンネルFIFOモードと245同期FIFOモードがありますが、ここではシンプルでUSB3.0ならではの高速伝送を実感できる245同期FIFOモードの使い方を示します。

(FT600/FT601の技術資料を見ると、何の説明も無く245 Syncronousという言葉が出てきます。これは、FT245がパラレル8bitポートをUSB通信に置き換えるデバイスで、FT600やFT601はこのFT245の技術やコンセプトを継承し、より高速化したチップであるからです。FT245FT600やFT601に比べて発売日が古いため長らく世界中で愛され日本でもユーザーが多いため、技術解説した日本語の記事や本が多く見つけることができます。そのためFT600やFT601を使うに先立ちFT245で情報収集し使い方の理解を深めておくのが良いかもしれません。)

このモードの間、1 つの IN と 1 つの OUT FIFO チャンネルを使用します。(例:EP2のINとOUTのパイプ)CLK はバス・マスター(つまり、FT600やFT601を制御するFPGA)に出力されるクロックで、66MHz または 100MHz に設定できます。バスマスタであるFPGAの回路はデータの読み書きをこのクロックに同期して実行することになります。

TXE_Nは、送信FIFOエンプティの出力信号です。アクティブ・ローであり、アクティブの時、チップ内蔵のRAM(送信FIFO)に空きがあることを示します。送信FIFOに空きがあり、FIFOマスタからデータを受信する準備ができていることを示す。この信号をトリガーにFPGAはデータをチップに書き込めば良いです。
RXF_N は出力信号で、Receive FIFO Full です。アクティブLowで、アクティブのとき、受信FIFOにデータがあり、FIFOマスタ(FPGA)からデータを受信する準備ができていることを示します。このときチップ内蔵RAM(受信FIFO)に読み出すべきデータがあり、読み出す準備ができていることを示します。
OE_N は入力信号で、出力イネーブルです。アクティブLowで、チップはバス・マスタによってLowに駆動されると、スレーブはデータとバイトを駆動します。
WR_N は入力信号で、ライト・イネーブル。アクティブLowで、バス・マスタによってLow駆動されると、マスタは書き込みサイクルにアクセスできます。
RD_N は入力信号(FPGAからFT600/FT601に与える)で、リード・イネーブルです。アクティブLowで、バス・マスタであるFPGAによってLow駆動することで、バス・マスタ(FPGA)は読み出しサイクルを実行できます。
BE3:0はバイトイネーブル信号です。バイト単位でアクセスせずにFT600なら16bit幅、FT601なら32bit幅のみでアクセスすいるのならこのBE信号はすべてのbitをHiにしたままにします。

リードサイクルとライトサイクルを示します。

バスマスターであるFPGAでは、TXT_NやRXF_Nのアサートをトリガーに、FT600/FT601へのリードサイクルやライトサイクルを実行する回路を設計することになります。その際に、読み出し信号であるRD_Nや書き込み信号であるWR_NをイネーブルにすることでCLKに同期したデータ読み出しや書き込みサイクルを実行させます。

ループバックテスト システム
本題です。下図で示すようにPCからUSBデバイス(FPGA+FT601)にデータを送信し、受信してデータの一致を確認しするシステムを作ることとします。FPGAではFPGA内部にFIFOを備え、FT601がPCから受信したデータをFT601から読み出してFPGA内のFIFOに蓄える回路を作ります。PCからの受信リクエストに応じてFT601がデータ送信可能となるのでFPGAはFPGA内のFIFOのデータをFT601に書き込みます。FT601はそのデータをホストPCへ送信します。

FPGAの設計
主に、245同期FIFOモードでFT601を制御するモジュールと、Alteraのメガファンクションで提供されるFIFOのIPからなる構成としました。

245同期FIFOモードでFT601を制御するモジュール(上図のFT602_245SYNCFIFO)は、FT601の出力信号TXT_NやRXF_N、つまり、FT601内蔵RAMの送信(FT601からホストPC)用FIFOの状態(空き)や受信(ホストPCからの受信)用FIFOの状態(空き、満タン)を見て、リードサイクルを実行する、またはライトサイクルを実行するステートマシンとなっています。ここでFT601の状態だけを見て、ライトサイクルやリードサイクルの開始を決めるのでは無く、FPGA内に作成したFIFO(上図のFIFO)の状態(空か満タンか)を見ることにしました。この回路はFT601からのクロック(100MHz)をそのまま使っています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity FT601_245SYNCFIFO is

   port(
		RST_N		:  in std_logic;  
   -- interface with FT601
    	CLK_IN   :  in std_logic;                     
    	TXE_N    :  in std_logic;                     
    	WR_N     :  out std_logic;                    
    	BE       :  inout std_logic_vector(3 downto 0);
    	RXF_N    :  in std_logic;
    	OE_N     :  out std_logic;
    	RD_N     :  out std_logic;
    	DATA     : inout std_logic_vector(31 downto 0);
		
   -- interface with user cercuit
    	Tx_DATA                 : in std_logic_vector(31 downto 0);
    	Rx_DATA                 : out std_logic_vector(31 downto 0);
    	RX_Ready                : in std_logic;
    	TX_Ready                : in std_logic;
    	DATA_SendRequest        : out std_logic;
    	DATA_ReceiveRequest     : out std_logic;
    	FIFO_DATA_Received      : out std_logic;
    	FIFO_DATA_Transmitted   : out std_logic
 	);
end entity;

architecture RTL of FT601_245SYNCFIFO is

   type state_type is (idle,r1,r2,r3,r4,r5,r6,r7,w1,w2,w3,w4);
   signal i_STATE :  state_type;
   signal i_TXE_N :  std_logic;
   signal i_RXF_N :  std_logic;
   signal i_WR_N  :  std_logic;
   signal i_RD_N  :  std_logic;
   signal i_OE_N  :  std_logic;
   signal i_DATA_Received_length : std_logic_vector(15 downto 0);
   signal i_DATA_Transmited_length : std_logic_vector(15 downto 0);

begin

   OE_N  <= i_OE_N;
   RD_N  <= i_RD_N;
   WR_N  <= i_WR_N;
	
	process(CLK_IN) begin
		 if (CLK_IN'event and CLK_IN = '1') then
		   i_RXF_N <= RXF_N;
			i_TXE_N <= TXE_N;
			  case i_STATE is
					when idle =>
						DATA  <= (others=>'Z');
						BE  <= (others=>'Z');
						DATA_SendRequest <= '0';        
						DATA_ReceiveRequest <= '0';     
						FIFO_DATA_Received <= '0';
						FIFO_DATA_Transmitted <= '0';
						Rx_DATA <= X"00000000";
						i_DATA_Received_length <= X"0000";
						i_DATA_Transmited_length <= X"0000";
						if((i_RXF_N = '0') and (RX_Ready = '1')) then
							BE  <= (others=>'Z');
							i_STATE <= r1;
						elsif((i_TXE_N = '0') and (TX_Ready = '1')) then
							BE  <= (others=>'1');
							i_STATE <= w1;
						else
							i_OE_N <= '1';
							i_RD_N <= '1';
							i_WR_N <= '1';
							i_STATE <= idle;
						end if;
					-- read sequence from FT601
					when r1 =>
						i_STATE <= r2;
					when r2 =>                     
						i_OE_N <= '0';                -- Output Enable to FT601
						i_STATE <= r3;
					when r3 =>
						i_RD_N <= '0';                -- Read Enable to FT601
						
						i_STATE <= r4; 
					when r4 =>
						DATA_ReceiveRequest <= '1';   -- to parent circuit
						if(i_RXF_N = '0') then
							Rx_Data  <= DATA;    		-- Read data from FT601
							i_DATA_Received_length <= i_DATA_Received_length + '1';
							i_STATE <= r4;
						else
							i_OE_N <= '1';
							i_RD_N <= '1';
							DATA_ReceiveRequest <= '0';   -- to parent circuit						
							i_STATE <= r5;
						end if;
					when r5 =>
						FIFO_DATA_Received <= '1';
						i_STATE <= r6;
					when r6 =>
						i_STATE <= r7;
					when r7 =>
						i_STATE <= idle;
					-- write sequence to FT601
					when w1 =>
						DATA_SendRequest <='1';
						i_STATE <= w2;
					when w2 =>
						DATA <= Tx_DATA;
						i_WR_N <= '0';       -- Write Enable
						if(i_TXE_N = '0') then
							i_DATA_Transmited_length <= i_DATA_Transmited_length + '1';
							i_STATE <= w2;
						else
							i_WR_N <= '1'; 
							DATA_SendRequest <='0';
							i_STATE <= w3;
						end if;
					when w3 =>
						DATA  <= (others=>'Z');
						FIFO_DATA_Transmitted <= '1';	
						i_STATE <= w4;
					when w4 =>
						i_STATE <= idle;
					when others =>
						i_STATE <= idle;
			  end case;
		 end if;
	end process;

end RTL;

FIFOは、Intel(Altera)のQuartus Primeに無償で提供されているメガファンクションのFIFOーIPを用いています。データ幅は32bit、深さ1024(=4KBの大きさのメモリ)のシングルクロック動作のFIFOに設定しました。クロックはFT601からのクロックを反転させたものにしました。つまり、FT601と上述のFT602_245SYNCFIFOはFT601のクロックの立ち上がり基準で動作し、FIFOはこのクロックの立ち下がりでデータの出し入れを制御することになります。

トップのエンティテイのコードはこんな感じです。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity TOP is

port(
	-- interface with FT601
	RST_N		: 	in std_logic;				-- CYclone5 starter kit CPU_Reset Sw is connected AB24: FPGA Pin number
	CLK_IN   :  in std_logic;                    -- from FT601
	TXE_N    :  in std_logic;                    -- from FT601, It means that Transmit FIFO is empty 
	WR_N     :  out std_logic;                    
	BE       :  inout std_logic_vector(3 downto 0);
	RXF_N    :  in std_logic;					-- from FT601, It means that Receive FIFO is empty
	OE_N     :  out std_logic;
	RD_N     :  out std_logic;
	DATA     : inout std_logic_vector(31 downto 0) ;

	 
	-- for debugging
	db_RXF_N_LED 			:  out std_logic; 	-- LEDG0  is connected L7:Cyclone5 GX Pin number
	db_TXE_N_LED			:  out std_logic; 	-- LEDG1  is connected K6:Cyclone5 GX Pin number
	db_MyFIFO_EMPTY_LED	:  out std_logic; 		-- LEDR0  is connected F7:Cyclone5 GX Pin number
	db_MyFIFO_FULL_LED	:  out std_logic 		-- LEDR1  is connected F3:Cyclone5 GX Pin number
		
	);
end entity;

architecture RTL of TOP is

	component FT601_245SYNCFIFO port (
	  RST_N		:  in std_logic;
	-- interface with FT601
	  CLK_IN   :  in std_logic;                     			-- from FT601
	  TXE_N    :  in std_logic;                     			-- from FT601, It means that Transmit FIFO is empty
	  WR_N     :  out std_logic;                    
	  BE       :  inout std_logic_vector(3 downto 0);
	  RXF_N    :  in std_logic;									-- from FT601, It means that Receive FIFO is Full
	  OE_N     :  out std_logic;
	  RD_N     :  out std_logic;
	  DATA     : inout std_logic_vector(31 downto 0);  

	-- interface with user cercuit
	  Tx_DATA        		: in std_logic_vector(31 downto 0); -- write data to FT601
	  Rx_DATA        		: out std_logic_vector(31 downto 0);-- read data from FT601
	  RX_Ready       		: in std_logic;
	  TX_Ready       		: in std_logic;
	  DATA_SendRequest      : out std_logic;
     DATA_ReceiveRequest   : out std_logic;
     FIFO_DATA_Received    : out std_logic;
     FIFO_DATA_Transmitted : out std_logic
	);
	end component;
	
	-- Mega function FIFO IP ( 32 bits x 1024 words single port FIFO )
	component FIFO
	PORT
	(
		aclr		: IN STD_LOGIC ;
		clock		: IN STD_LOGIC ;
		data		: IN STD_LOGIC_VECTOR (31 DOWNTO 0);
		rdreq		: IN STD_LOGIC ;
		wrreq		: IN STD_LOGIC ;
		empty		: OUT STD_LOGIC ;
		full		: OUT STD_LOGIC ;
		q		: OUT STD_LOGIC_VECTOR (31 DOWNTO 0)
	);
	end component;
	
	-- internal signal or buffer
	signal i_Tx_DATA			:	std_logic_vector(31 downto 0);
	signal i_Tx_DATA_Shifted	:	std_logic_vector(31 downto 0);
	signal i_Rx_DATA			:	std_logic_vector(31 downto 0);
	signal i_RX_Ready			:	std_logic;
	signal i_TX_Ready			:	std_logic;
	signal i_FIFO_DATA_Received	: 	std_logic;
	signal i_FIFO_DATA_Transmitted	:	std_logic;
	signal i_TXE_N				:	std_logic;
	signal i_RXF_N				:	std_logic;
	signal i_CLK_IN				:	std_logic;
	signal i_MyDATA_RD_REQUEST	:	std_logic;
	signal i_MyDATA_WR_REQUEST	:	std_logic;
	signal i_CLEAR_FIFO			:	std_logic;
	signal i_MyFIFO_EMPTY		:	std_logic;
	signal i_MyFIFO_FULL		:	std_logic;
	
	begin
	i_CLK_IN 		<= not CLK_IN;
	i_CLEAR_FIFO	<= not RST_N;
	i_RXF_N 			<= RXF_N;
	i_TXE_N 			<= TXE_N;
	
		
	-- for debugging
	db_RXF_N_LED 				<= not i_RXF_N; 
	db_TXE_N_LED 				<= not i_TXE_N;
	db_MyFIFO_EMPTY_LED		<= i_MyFIFO_EMPTY;
	db_MyFIFO_FULL_LED		<= i_MyFIFO_FULL;
	--
	
	
	FT601_0:FT601_245SYNCFIFO port map (
		RST_N				 =>	RST_N,	
		-- connected to outside (FT601)
		CLK_IN          =>  CLK_IN,                      
		TXE_N           =>  TXE_N,              
		WR_N            =>  WR_N,    		
		BE              =>  BE, 
		RXF_N           =>  RXF_N,
		OE_N            =>  OE_N,  
		RD_N            =>  RD_N,  
		DATA            =>  DATA,
	
		-- connectd to inside
		Tx_DATA         =>  i_Tx_DATA,     
		Rx_DATA         =>  i_Rx_DATA,        
		RX_Ready        => 	not i_MyFIFO_FULL, 
		TX_Ready        =>  i_MyFIFO_FULL,

		DATA_SendRequest		=>	i_MyDATA_RD_REQUEST,
		DATA_ReceiveRequest		=>	i_MyDATA_WR_REQUEST, 
		FIFO_DATA_Received      =>	i_FIFO_DATA_Received,
		FIFO_DATA_Transmitted   =>	i_FIFO_DATA_Transmitted  

	);
	
	FIFO_inst : FIFO PORT MAP (
		aclr	=> i_CLEAR_FIFO,
		clock	=> i_CLK_IN,					-- 
		data	=> i_Rx_DATA,					--	Write data from FT601 as i_RX_DATA
		rdreq	=> i_MyDATA_RD_REQUEST,	
		wrreq	=> i_MyDATA_WR_REQUEST,	
		empty	=> i_MyFIFO_EMPTY,				
		full	=> i_MyFIFO_FULL,				
		q		=> i_Tx_DATA					--	Read data from this FIFO, Write it to FT601's FIFO
	);


		
end RTL;

デバイスドライバ
PCから利用するにはこのチップのデバイスドライバのインストールが必要です。こちらか自身のPC/OS環境にあったデバイスドライバーをダウンロードしてPCにインストールします。正しくインストルされればFT601が搭載されたボードとUSBケーブルでPCに接続したとき、以下のように確認できます。(FTDI FT60 USB3.0 Bridge Device)

FT601の動作モード
モード設定用の信号ピンでコンフィグレーションできますが、USB通信を介してソフトウェアからの設定も可能です。FTDI社のサイトに、AN_370 FT60X ChipConfiguration Programmer User Guide や、FT600/FT601 Configration Utilityがあるので確認、利用しましょう。FPGAの動作以前に購入したUMTF601A-Bの状態確認やデバイスドライバのインストールが正しく行えているかの確認にも有用です。このUtilityでFT601の動作モードを変更(保存)することができます。 
FIFO MODEは、上述で245(ソフトウェアの定義ファイルの表現ではCONFIGURATION_FIFO_MODE_245)だと伝えましたが、もう一つデータ転送使用上重要なものにチャンネルコンフィグなるものがあります。こちらをどうするか、やりたいことをを踏まえて決定します。チャンネルコンフィグには以下のとおり、5種ありますが、ここでは、CONFIGURATION_CHANNEL_CONFIG_1を設定します。(これだとEP2にIN用のパイプ、OUT用のパイプが作られ、FT601の内蔵RAM上にそれぞれ4KBのFIFOが割り当てられます。

  • CONFIGURATION_CHANNEL_CONFIG_4
  • CONFIGURATION_CHANNEL_CONFIG_2
  • CONFIGURATION_CHANNEL_CONFIG_1
  • CONFIGURATION_CHANNEL_CONFIG_1_OUTPIPE
  • CONFIGURATION_CHANNEL_CONFIG_1_INPIPE

ソフトウェア
FT60X Software Examplesに行けば、C++、C#、Pythonで作られたサンプルプログラムが豊富に用意されています。必要なDLLやライブラリもあります。FPGA側のサンプルコードも用意されています。どの部分を自作したいか、経験値によりけりですが、技術資料を読むだけではつかみきれない仕様の詳細を、これらの豊富なサンプルコードを読むことで理解が深まるので、ぜひじっくり参照しましょう。

PythonでFT601とやりとりしてみる
PythonでFT601を扱うには、以下のようにPythonの開発環境にftd3xx Library for Python をインストールします。

python -m pip install ftd3xx

FTD3XX.dllをPythonの実行環境にコピーしました。(これのファイルはどこに置くべきかはちゃんとわかっていません。とりあえず、Pythonの実行ファイルをあるフォルダに置きました。)そして、Pythonのコード内で、ftd3xxをインポートすればftd3xxで用意されているメソッドを利用することがでできます。

import ftd3xx

今回の例では、デバイスドライバーをオープンしてハンドルを取得し、コンフィグレーション情報の取得と、データのライト/リードするメソッドだけ利用しました。4KBのランダムデータまたはインクリメンタルデータを作成して、FT601+FPGAのUSBデバイスにデータを送信し、そして受信します。送信データを受信データを比較して一致するかどうかを確認しました。

ソースコードを掲載します。

import ftd3xx
import sys
import numpy as np
from random import randint
import logging
import time

FT_OPEN_BY_SERIAL_NUMBER = 0x00000001
FT_OPEN_BY_DESCRIPTION = 0x00000002
FT_OPEN_BY_LOCATION = 0x00000004
FT_OPEN_BY_GUID = 0x00000008
FT_OPEN_BY_INDEX = 0x00000010


def information_fifo_mode(num):
    # The following defines refer to "FTD3XX.h"
    fifo_mode_list = ["CONFIGURATION_FIFO_MODE_245", "CONFIGURATION_FIFO_MODE_600",
                      "CONFIGURATION_MODE_COUNT"]
    return fifo_mode_list[num]


def information_channel_config(num):
    # The following defines refer to "FTD3XX.h"
    channel_config_list = ["CONFIGURATION_CHANNEL_CONFIG_4", "CONFIGURATION_CHANNEL_CONFIG_2",
                           "CONFIGURATION_CHANNEL_CONFIG_1", "CONFIGURATION_CHANNEL_CONFIG_1_OUTPIPE",
                           "CONFIGURATION_CHANNEL_CONFIG_1_INPIPE", "CONFIGURATION_CHANNEL_CONFIG_COUNT"]
    return channel_config_list[num]


def open_ft_usb_device(device_type, device_name):
    ascii_device_name = bytes(device_name, encoding="ASCII")
    for device_id in range(16):
        usb_object = ftd3xx.create(device_id, FT_OPEN_BY_INDEX)
        if sys.platform == 'win32' and usb_object.getDriverVersion() < 0x01020006:
            usb_object.close()
            return None, 'D3XX driver version is old. Please update driver!'
        if usb_object.getDeviceInfo()['Description'] != ascii_device_name:
            usb_object.close()
            continue
        return usb_object, 'Successfully opened %s USB device: %s' % (device_type, device_name)
    return None, 'open usb device failed!'


def close_ft_usb_device(usb_object):
    usb_object.close()


def get_random_data(data_length):
    data_array = [randint(0, 255) for i in range(data_length)]
    return bytes(data_array), len(data_array)


def get_incremental_data(data_length):
    # repeat = int(data_length / 256)
    data_array = []
    # for i in range(repeat):
    #     for j in range(256):
    #         data_array.append(j)
    # data_array = [j for i in range(repeat) for j in range(256)]
    # return bytes(data_array), len(data_array)
    size_of_int_data = int(data_length / 4)
    data_array = np.arange(0, size_of_int_data, dtype=np.int32)
    return bytes(data_array), len(data_array)


def WritePipe(D3XX, pipe, buffer, size):
    return D3XX.writePipe(pipe, buffer, size)


def ReadPipe(D3XX, pipe, size):
    received = D3XX.readPipeEx(pipe, size, True)
    return received['bytesTransferred'], received['bytes']


def upstream_test(usb_handle):
    send_data_length = 4096
    send_data, length = get_incremental_data(send_data_length)
    transferred_value = WritePipe(usb_handle, 0x02, send_data, send_data_length)
    if transferred_value != send_data_length:
        # close_ft_usb_device(usb)
        print("WritePile failed")
        return
    print("WritePile Successful")
    return


def downstream_test(usb_handle):
    receive_data_length = 4096
    value, receive_data = ReadPipe(usb_handle, 0x82, receive_data_length)
    if value == receive_data_length:
        f_receive = open("receive_data.dat", "wb")
        f_receive.write(receive_data)
        print("ReadPile Successful, receive data saved")
    return


def loop_back_test(usb_handle, option):
    send_data_length = 4096
    receive_data_length = 4096
    flg = False
    if option == 1:
        data_type = "random"
        send_data, length = get_random_data(send_data_length)
    else:
        data_type = "incremental"
        send_data, length = get_incremental_data(send_data_length)
    # start_write = time.perf_counter()
    transferred_value = WritePipe(usb_handle, 0x02, send_data, send_data_length)
    # end_write = time.perf_counter() - start_write
    # print(f'{end_write}秒!')
    if transferred_value != send_data_length:
        close_ft_usb_device(usb)
        print("WritePile failed")
        return
    else:
        # start_read = time.perf_counter()
        value, receive_data = ReadPipe(usb_handle, 0x82, receive_data_length)
        # end_read = time.perf_counter() - start_read
        # print(f'{end_read}秒!')
        if value == receive_data_length:
            flg = True
        if send_data != receive_data:
            print(" ** send_data and receive_data mismatch")
            return
        else:
            print(" send_data and receive_data match, loopback test passed **")

    if flg:
        f_send = open("send_data_" + data_type + ".dat", "wb")
        f_send.write(send_data)
        f_receive = open("receive_data_" + data_type + ".dat", "wb")
        f_receive.write(receive_data)


def main(handle):
    while True:
        try:
            print('***** 1: loopback_test_random')
            print('***** 2: loopback_test_incremental')
            print('***** 3: upstream_test_incremental')
            print('***** 4: downstream_test')
            print('***** 5: end test')
            cmd = input('command input >> ')
        except KeyboardInterrupt:
            break

        cmd = int(cmd)
        if cmd == 5:
            close_ft_usb_device(handle)
            break
        elif cmd == 1 or cmd == 2:
            loop_back_test(handle, cmd)
        elif cmd == 3:
            upstream_test(handle)
        elif cmd == 4:
            downstream_test(handle)
        else:
            print("invalid command")


if __name__ == '__main__':
    usb, message = open_ft_usb_device("FT60X", "FTDI SuperSpeed-FIFO Bridge")
    print(message)
    print("VendorID : 0x%X" % usb.getChipConfiguration().VendorID)
    print("ProductID : 0x%X" % usb.getChipConfiguration().ProductID)
    print("ChannelConfig : %s" % information_channel_config(usb.getChipConfiguration().ChannelConfig))
    print("FIFOMode : %s" % information_fifo_mode(usb.getChipConfiguration().FIFOMode))
    print("--------------------------------------------------------------------------")
    print("------------------------  Loop back test    ------------------------------")
    main(usb)
 

これでFPGA側のコードと、ホストPC側のコードを示しています。メインのコードは非常の少ないですよね。今回紹介した設計例はシンプルなループバックを行うシステムですが、最後まで読んだ方はもう応用例が浮かんでいますよね。