MPSSE-JTAGを理解する
FTDI社のFT2232シリーズにはMPSSEという、汎用のUSBマルチプロトコルシリアル変換機能が搭載されています。MPSSEはもともとJTAG専用というわけではなく、SPI、I2Cなどで汎用のシリアル変換チップを作るためのもののようです。
今日はそのMPSSEの機能について調べてみました。
まずMPSSEを使うことができるデバイスは、FT2232D、FT2232H、FT232H、FT4232Hです。この中でFT2232D以外はHighSpeed対応です。
ここではFT2232Hを例に説明していきます。
FT2232Hを使う場合、TCK=bit0、TDI=bit1、TMS=bit2、TDO=bit3になります。FT2232HにはChannelAとChannelBがありますが、どちらを使うこともできます。ADBUSのbit4~bit7、ACBUSに属するbit8~bit15はGPIOにすることも可能です。TCKクロックの速度は60MHz÷分周比となりますが、分周比は2,4,6,8・・なので、30MHz、15MHz、10MHz、7.5MHz・・・への設定が可能です。
読むべきアプリケーションノートはAN108とAN129です。
https://www.ftdichip.com/Support/Documents/AppNotes/AN_129_FTDI_Hi_Speed_USB_To_JTAG_Example.pdf
AN129にはMPSSEを使うJTAGのサンプルプログラムが載っていて、AN108にはMPSSEのコマンドの解説が載っています。
MPSSEは、OUT FIFOに入っているコマンドを順番に実行し、結果をIN FIFOに入れていきます。そのコマンドは<コマンドコード>[<長さ>][<データ>]の形式で、FT2232Hはそれを解釈して実行していきます。
そのコマンドの説明がAN108に書かれています。
AN129には実際の手順が書かれています。まず以下のようにしてFT2232Hを初期化してMPSSEモードに入れます。
FT_Open(0, &ftHandle); // 0は最初に見つかったデバイス。FT2232HのChannelAが使われる。
FT_ResetDevice(ftHandle);
// 受信バッファのサイズを取得し残っている残っているデータを受信
FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
FT_Read(ftHandle, &byInputBuffer, dwNumBytesToRead, &dwNumBytesRead);// 受信バッファを最大値に
FT_SetUSBParameters(ftHandle, 65536, 65535);// イベントが発生しないようにする
FT_SetChars(ftHandle, false, 0, false, 0);
FT_SetTimeouts(ftHandle, 0, 5000);
FT_SetLatencyTimer(ftHandle, 16);
FT_SetBitMode(ftHandle, 0x0, 0x00);
FT_SetBitMode(ftHandle, 0x0, 0x02); // ここでMPSSEモードに切り替わる
Sleep(50);
ここまでがFT2232HのUSB機能自体の初期化です。
そうしたら次にMPSSEコマンドを与えて、JTAG機能の初期化を行うのですが、最初に0xAAという未定義のコマンドを与えてわざとエラーを起こさせます。するとFA AAというレスポンスが返るので、これを利用して受信バッファの同期を取ります。
次に、
- 0x8A:60MHzクロックを使う。FT2232Dは12MHzなので
- 0x97:アダプティブクロックを無効にする。ARMのデバッグはRTCKを使うが、ここでは使わない
- 0x8D:3phaseクロックを無効にする
- 0x80 0x08 0x0b:LowByteを TMS=1 TDO=IN TDI=0 TCK=0にする
- 0x82 0x00 0x00:HighByteをINにする
- 0x86 分周比 分周比:クロック速度の設定
- 0x85:ループバックをディゼーブルにする
と、非常に様々なオプションを初期設定します。
いよいよJTAGのTDIやTMSにパターンを出力していくわけなのですが、MPSSEにはTMSの与え方だけで6種類、TDIの与え方で12種類ものコマンドがあります。クロックの立下り or 立ち上がりでデータを有効にするとか、データを入力するかとか、長さをByteで与えるかbitにするかとかがあります。
原理的には0x00~0x7fまでの128種類のコマンドがあるのですが、AN_129で実際に使われているのは
- 0x4B・・TMS操作。TDOは入力しない。
- 0x3B・・TDI/TDO入出力。長さはbit単位。クロックの立下りでTDI出力し、クロックの立ち上がりでTDO入力。
- 0x6B・・TMS操作。TDOは入力する。
- 0x8F・・データなしでひたすらクロックを与える
の4種類のみでした。
このほか、実際のJTAGツールを作るには
- 0x39・・TDI/TDO入出力。長さはbyte単位。クロックの立下りでTDI出力し、クロックの立ち上がりでTDO入力。
も必要でしょう。0x3Bコマンドは8bitまでのシフトしかできないのでいささか効率が悪いようです。
JTAGの操作では、Shift_DRやShift_IRの最後でTMSを上げるという操作が必要になるのですが、MPSSEにはそういうコマンドはありません。そのため8bitのレジスタシフトであれば、7bitの0x3Bコマンドと1bitの0x6Bコマンドに分けなければなりません。そこが面倒なとろです。
0x4Bコマンドと0x6Bコマンドでは、bit7がTDIから出力され、bit[6:0]がTMSから出力されるので、データシフトの最後の1bitは0x6Bコマンドを使って出すというわけです。
これらのコマンドを駆使してデータ出力するのですが、例えば35bitのデータシフトをしたいのであれば、「0x39コマンドで4バイトを出し、0x3bコマンドで2bitを出し、0x6bコマンドで1bitを出しながらTMSを上げる」というふうに分解する必要が出てきます。面倒ですね。
そのほかいろいろ分かったことは、
- DigilentのUSB-JTAGは、MPSSEでもアクセスできる
- MPSSEコマンドとMPSSEコマンドの間は200nsくらい空く
- 0x3bコマンドは8bitまでしか送れない
- 0x39コマンドでバイト単位で送っても8bitごとに50nsくらい空く
- 3Bコマンド(TDO受信あり)も1Bコマンド(TDO受信なし)も速度は変わらない
- 受信したデータはMSBから詰まっていく。そのデータはコマンドごとにクリアされないので、TMSのときの7bit+1bitのように最後のデータを受信する場合には自然につながる(最後から2番目のバイトは捨てる)
これでMPSSEを使ったJTAGケーブルが作れそうですね。
ただ、これだけでは力不足です。XILINXのXVCプロトコルはTMSやTDIといった役割を無視した単純なGPIOを期待したものなので、TMSやTDIをそのまま出せるようでなければなりません。0x80によるポート出力と0x81によるポート入力で単純にカチカチやってみると、TCK=1.6MHzくらいとなりました。
遅いけれどもXVC対応のケーブルも作れるでしょう。
| 固定リンク
コメント