<カメレオンUSB SPA3アプリケーションの構成>

アプリケーション全体の構成は下記の図のようになります。

FX2とFPGAの間の通信は、「FIFO 16bit」あるいは「FIFO 8bit + ポート8bit」を選択します。
JTAGはFPGAのコンフィグレーションに使用しますが、コンフィグレーション終了後はアプリケーションのデータ転送に使用出来ます。

アプリケーション全体で見ると開発しなくてはならないのは

の3つが必要になります。

FX2のCPUである8051は8bitで、動作クロックも48MHzと貧弱です。このため、実際のデーター処理はPC側で行うことになります。
FX2が処理しなくてはならないのは、USBの制御、FPGAのコンフィグレーション、JTAGを使用したFPGAとの通信です。
このような処理は、どのアプリケーションでも必要な部分ですので、個別に開発しないで済ませたい部分です。

そこで今回、Spartan3Aに特化したFX2用のファームウエアを開発しました。
これにより、アプリケーション開発者はFX2のドキュメントを読んだり、FX2用のファームウエアの開発から解放されます。

また、PCアプリケーションから見れば、FPGAに対して高速にデータの受け渡しを行うことが重要です。
FX2を使用して高速にデータ転送を行うには、Cypressの提供するCyAPIライブラリを使用して、USBの同期転送を行います。
同期転送は非同期転送と比べて、扱いが面倒なのですが、オプティマイズの提供すライブラリを使用することで、簡単に扱うことが可能になります。


<SPA3ライブラリの構成>

カメレオンUSB SPA3ライブラリの構成は下記の図のようになっています。
SPA3クラスの関数を使用することで、FX2 FWのロード、FPGAコンフィグ、FIFO/エンドポイント制御、JTAG経由でのブロックRAM/ポートアクセスを行うことが出来ます。
具体的な使用方法は、サンプルやソースコードを参照してください。

PCアプリケーション
FIFOを使った
高速同期転送
JTAG
RAMアクセス
JTAG
ポート
FPGA
コンフィグ
FX2 FW
ロード
FIFO
制御
ポートD
制御
エンドポイント
制御
SPA3クラス
CyUSBコントロール
CUSB2クラス

<SPA3ライブラリの関数>

分類 関数 動作
初期化
FPGAコンフィグ
bool init(cusb2 *usb, u64 *dev_dna, u8 *bit_buf) dev_dnaで指定したカメSPA3にFWをロードして初期化する。
dev_dnaがゼロの場合は、最初に見つかったカメSPA3をターゲットにする。
bit_bufにはISEで作成したコンフィグ用イメージを指定する。
既にFPGAがコンフィグ済みの場合は、FPGA内部のbitイメージのタイムスタンプと比較して、異なればリコンフィグを行う。
JTAG
I/Oポート
void port(u8 bits, u8 *out, u8 *in) JTAG通信を行い、FPGA内部に構築したI/Oポートにアクセスする。
bitsはポートの総ビット数(IN/OUTポートの大きい方)、
outは出力ポートのビットイメージをセットする。
inには入力ポートの状態が戻り値としてセットされる。
JTAG
RAMアクセス
void mem_read(u16 addr, u16 word, u16 *buf) JTAG通信を行い、FPGA内部のブロックRAMを読み出す。
アドレス(addr)は13bit有効で、wordは読み出すワード数(16bit)を指定する。読み出した内容はbufに格納される。
void mem_write(u16 addr, u16 word, u16 *buf) JTAG通信を行い、FPGA内部のブロックRAMに書き込む。
アドレス(addr)は13bit有効で、wordは書き込むワード数(16bit)を指定する。書き込む内容はbufに格納しておく。
エンドポイント
制御
void ep6in_start() カメSPA3 -> PC への転送を開始する。
void ep6in_stop() カメSPA3 -> PC への転送を停止する。
void ep2out_start PC -> カメSPA3 への転送を開始する。
void ep2out_stop() PC -> カメSPA3 への転送を停止する。
ポートD制御 void portd_cfg(u8 dir) ポートDの各bitの入力・出力を設定する。(0:入力 1:出力)
u8 portd_read() ポートDを読み出す。
void portd_write(u8 val) ポートDに書き込む。
FIFO制御 void ifconfig(u8 val) FIFOの動作モード、クロックなどを設定する。
サンプルコード、FX2データーシート参照。
void set_fifo_8() FIFOを8bitモードに設定する。
void set_fifo_16() FIFOを16bitモードに設定する。

<SPA3 FX2 FW>

SPA3ライブラリはFX2にロードされたFW(ファームウエア)を使用して、Spartan3Aを制御します。
同時にこのFWはUSBに関する処理も行っています。

複数のカメレオンUSB SPA3がPCに接続された際に、どのような方法で個々のカメレオンUSB SPA3を識別するかは重要な問題です。
特に、カメレオンUSB SPA3はPCからダイナミックにコンフィグレーションを行いますので、意図しないカメレオンUSB SPA3に対して、コンフィグレーションを行ってしまうと、Spartan3Aデバイスの破損の可能性もあります。
しかしながら、カメレオンUSB SPA3のFX2にはEEPROMが実装されていません。このため「カメレオンUSB FX2」で使用したような、EEPROMにIDを書き込んでおくといった手法は使えません。
そこでSpartan3Aに書き込まれている固有のIDであるDevice DNAコードを使用します。FX2 FWはFX2で実行されると、まずSpartan3AからDevice DNAを読み出します。そして、Device DNAをUSBデバイスのシリアルナンバーとして使用して、再度認識(リナムレーション)されます。シリアルナンバーとはUSBのベンダーIDやプロダクトIDと同様に、USBの規格の中で定められています。

PCアプリケーションでは、Device DNA番号を指定することで、特定のカメレオンUSB SPA3を識別することが出来ます。Device DNA番号にゼロを指定すると、最初に見つけたカメレオンUSB SPA3を対象にすることが出来ます。

オプティマイズの別の製品「カメレオンUSB FX2」はFX2とアルテラのCPLD MAX2を実装した、カメレオンUSB SPA3と構成が似ています。このため、「カメレオンUSB FX2」と「カメレオンUSB SPA3」が混在した状態でも、識別の問題が発生します。
「カメレオンUSB FX2」は前述の通り、EEPROMにIDを書き込むことで識別しますが、「カメレオンUSB SPA3」もID ゼロ番として扱われてしまいます。ですから、「カメレオンUSB FX2」と「カメレオンUSB SPA3」が混在する環境では、必ず「カメレオンUSB FX2」にゼロ番以外のIDを書き込んで使用してください。


<サンプル I/Oポート&Device DNAによるセキュリティ>

実際にSPA3ライブラリを使用する簡単なコンソールアプリケーションを作ってみます。
このサンプルではUSBの高速転送については、一切行いません。USBの高速転送については、USB速度測定ツールを参照ください。

カメレオンUSB SPA3ではDevice DNAを識別のために使用していますが、本来Device DNAはFPGAロジックのセキュリティー(コピー防止)のために使用されます。

Device DNAはユニークな値ですので、FPGA内部でDNAからキーを生成させます。キーを生成するアルゴリズムはFPGA内部にありますので、簡単に解析することは出来ません。そしてPCアプリケーションからは、DNAに対応する認証コードをFPGAに書き込みます。認証コードはアプリケーション開発者がFPGA内部と同じアルゴリズムで生成します。FPGA内部では、書き込まれた認証コードとキーを比較して、一致しない場合は機能を制限させます。PCのアプリケーションをいくら解析やクラッキングをしたところで、FPGA内部のセキュリティーを突破することは出来ませんので、かなり強固なセキュリティーを構築できます。

今回のサンプルでは、FPGAロジック内部で読み出したDNAをJTAG入力ポートを介して、PCが読み取り、認証コードを生成して、JTAG出力ポートを介してFPGAに書き込みます。その後JTAG入力ポートを介して、FPGA内部の認証結果を読み出します。

ソースコード&バイナリ一式(io_dna_test.zip)ダウンロード

処理内容は単純で

  1. カメレオンUSB SPA3の認識、FWのロードFPGAのコンフィグ
  2. FPGAのロジックが読み出したDevice DNAをPCアプリで取得し、認証コードを生成してFPGAに返す
  3. FPGA内部のロジックが生成したコードと、PCから書き込まれたコードをFPGA内部で比較して、セキュリティー状態をPCに返す

PCアプリケーションのソースコードの解説です。

Visual C++ 2010 Express ソースコード
  1. #include "stdafx.h"
  2. //#define DEBUG_SPA3
  3. #ifdef DEBUG_SPA3
  4. u8 spa3_bit[1024*64];
  5. #else
  6. u8 spa3_bit[]=
  7. #include "spa3_bit.inc"
  8. #endif
3行目は、FPGAのデザインを開発中はコメントアウトします。
ISEが生成したbitファイルをダイレクトに反映させ、ロジックのデバッグが可能です。
FPGAのデザイン開発が終了したら、"spa3_bit.inc"ファイルに取り込み、プログラムに内蔵させます。
  1. class cusb2 *usb;
  2. class spa3 *spa3;
cusb2、spa3クラスはプログラム全体で、アクセス出来ると便利なので、グローバルポインタで宣言します。(今回のサンプルでは無意味です)
  1. #define PORT_BITS 58 //PC<->FPGA間のポートビット数(入力と出力は同じビット数)
  2. struct{//出力(PC -> FPGA)ポート
  3.   u64 key_code :32;
  4. } out_port;
  5. struct{//入力(FPGA -> PC)ポート
  6.   u64 dev_dna :57;
  7.   u64 security :1;
  8. } in_port;
1行目でポートのビット数を定義しています。この定義は後のport()関数で使用します。FPGAのデザインとこの値が一致している必要があります。

3-5行目で出力ポート(PC -> FPGA)の定義を行います。C言語のビットフィールドを使って定義します。このようにすることで、ポートを変数のように扱うことが出来ます。

7-10行目は入力ポート(FPGA -> PC)の定義です。合計で58bitです。出力ポートは32bitでしたので、今回のサンプルでは入力ポートの方がビット数が多いことになります。ビット数の多い方を、1行目の定義に使用します。
  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. #ifdef DEBUG_SPA3
  4.   FILE *fp_spa3;
  5.   fopen_s(&fp_spa3, "spa3\\io_dna.bit","rb");
  6.   fread(spa3_bit, 1, sizeof(spa3_bit), fp_spa3);
  7.   fclose(fp_spa3);
  8. #endif
main関数です。3-8行目はデバッグ中のbitファイル(ロジック)を読み込む処理です。
  1.   u64 dev_dna;
  2.   usb = new cusb2();
  3.   spa3 = new class spa3();
1行目はDevice DNAの変数定義です。Device DNAは57bitですので、u64を使用します。
2−3行目でcusb2とspa3を実体化しています。
  1.   dev_dna = 0;
  2.   if( spa3->init(usb, &dev_dna, spa3_bit) ) //Spartan3Aコンフィグレーション
1行目でdev_dnaにゼロを入れることで、最初に見つけたカメSPA3をターゲットにします。見つかったSPA3のDevice DNAの値がdev_dnaに戻ります。特定のDevice DNAをターゲットにする場合は、その値をdev_dnaに代入して呼び出します。

2行目で、指定したターゲットのFX2にFWをロードして、FPGAのコンフィグレーションを行います。正常終了するとTrueが返ります。
  1.     out_port.key_code = 0;
  2.     spa3->port(PORT_BITS, (u8 *)&out_port, (u8 *)&in_port);
  3.     printf("XC3S50A Device DNA = %8.8X%8.8X\n", (u32)(in_port.dev_dna >> 32), (u32)in_port.dev_dna);
  4.     printf("Security bit = %d\n", in_port.security);
1行目で出力ポートのkey_codeに代入しています。定義した変数に代入することで、設定すべき値が構造体にセットされます。
2行目でセットした構造体を、port()関数でFX2->FPGAに送信します。同時に入力ポートの値がin_port構造体にセットされて、戻ってきます。
3−4行目はin_portにセットされて戻ってきた入力ポートの値を表示します。
  1.     out_port.key_code = (u32)(in_port.dev_dna >> 32) ^ (u32)in_port.dev_dna;  //FPGAと同じアルゴリズムで生成
  2.     printf("Write key_code = %4.4X\n", out_port.key_code);
FPGAから読み出したDevice DNAを元に、認証コードを生成しています。本来この処理はアプリケーション提供者が行い、アプリケーションプログラムにアルゴリズムが組み込まれることはありません。
  1.     spa3->port(PORT_BITS, (u8 *)&out_port, (u8 *)&in_port); //in_portはキーを書き込む前の値のため捨てる
  2.     spa3->port(PORT_BITS, (u8 *)&out_port, (u8 *)&in_port); //この時点で書き込んだキーが反映される
  3.     printf("Security bit = %d\n", in_port.security);
1行目でkey_codeをFPGAに送信します。in_portに値が戻りますが、この値はkey_codeを書き込む前の状態です。
2行目でkey_codeが反映された状態のin_portを取得します。
3行目でFPGAが返したセキュリティ状態を表示します。
  1.     //次回のために不正なキーを書き込んでおく
  2.     out_port.key_code = 0;
  3.     spa3->port(PORT_BITS, (u8 *)&out_port, (u8 *)&in_port);
  4.     }
  5.   return 0;
  6. }
1−3行目 次回のために不正なキーを書き込んでおく

続いて、FPGAのロジック部分のソースコードです。Verilogで記述しています。

XILINX ISE Verilog ソースコード(io_dna.v)
  1. module io_dna(FX2_SLOE, FX2_SLRD, FX2_SLWR, FX2_FD, FX2_FIFOADR, FX2_PKTEND,
  2.     FX2_FLAGA, FX2_FLAGB, FX2_FLAGC, FX2_IFCLK, CLK12
  3.     );
  4.   output FX2_SLOE;
  5.   output FX2_SLRD;
  6.   output FX2_SLWR;
  7.   inout [15:0] FX2_FD;
  8.   output [1:0] FX2_FIFOADR;
  9.   output FX2_PKTEND;
  10.   input FX2_FLAGA;
  11.   input FX2_FLAGB;
  12.   input FX2_FLAGC;
  13.   input FX2_IFCLK;
  14.   input CLK12;
トップモジュールの定義です。カメSPA3はFX2と接続されていますので、使用しない信号も残しておきます。同時にucfファイルでピン番号も固定する必要があります。詳しくはこちらも参照
  1.   //fixed output pins
  2.   assign FX2_SLOE = 1;
  3.   assign FX2_SLRD = 1;
  4.   assign FX2_SLWR = 1;
  5.   assign FX2_FIFOADR = 3;
  6.   assign FX2_PKTEND = 1;
出力信号は未使用だとISEのエラーになるので、適当に設定しておく。
  1.   wire [57:0] in_port;
  2.   wire [57:0] out_port;
入出力ポートの信号名を定義。実体は下記のjtag_port内部で定義される。58bitは下記の、インスタンス化とPCアプリと一致する必要があります。
  1.   jtag_port #(58) jtag_port(.OUT_PORT(out_port), .IN_PORT(in_port));
jtag_portをインスタンス化します。jtag_portはjtag_port.vに記述されています。ビット数は可変になっていますので、入出力ポートの大きい方を設定します。jtag_port内部では、58*2個のレジスタが定義され使用されます。
今回はJTAG RAMアクセスを使用しませんが、JTAG RAMアクセスを使用する場合は、spd_chk16のサンプルに含まれるjtag_port.vを使用してください。
  1.   wire [56:0] dev_dna;
  2.   wire dna_ready;
  3.   dna dna(.dev_dna(dev_dna), .dna_ready(dna_ready), .clk(CLK12));
Device DNAを読み出すモジュール(dna.v)をインスタンス化。Device DNAの読み出しはシリアルに行われるため、FX2から常時出力される12MHzのクロックを使用した。
  1.   wire security;
  2.   wire [31:0] key_code;
  3.   assign key_code = out_port[31:0];
2−3行目に注目してください。信号key_codeを定義して、これをout_portにアサインします。この記述によって、PC -> FPGAへの出力ポートがkey_codeとして結びつけられます。
  1.   assign security = (key_code == ({7'b0000000, dev_dna[56:32]} ^ dev_dna[31:0])) ? 1 : 0;
Device DNAからキーを生成し、PCから書き込まれた値と比較します。比較結果をsecurity信号に代入します。
  1.   assign in_port = {security, dev_dna};
この行も重要です。FPGA -> PCへの入力ポート(in_port)をsecurityとdev_dnaに割り当てます。この割り当ては、PC側のアプリケーションでは構造体のビットフィールドとして定義して使用されます。
  1. endmodule