火曜日, 3月 19, 2013

JavaでJTAG通信

新しいターゲットボードDE0-Nanoですが、本体の機能だけでホストPCと通信する場合には、ダウンロードケーブルを利用したJTAG通信を利用することになります。しかし、このJTAG通信に関するまとまった情報がなかなか入手しづらく、DE0-Nano付属のデモアプリケーションでも明らかにホストPCと通信しているのですが、なぜかこれに関しても技術資料が一切ありません。で、こちらで独自に調査した結果をこれから述べたいと思います。

DE0-Nanoは本体にUSB Blaster互換の回路が載っているので、USB Blasterのドライバをそのまま使ってボードへのアクセスが可能になっています。この互換回路にはFTDI社のFT245BLが使われていますので、USB Blasterのドライバの正体は、実はFTDI社のD2XXドライバだと思われます。その証拠に、ドライバ付属のDLL(ftd2xx.dll)を使ってそのままボードにアクセスが可能だったりします。というわけで、ハードウェアアクセスのための資料はD2XXドライバのドキュメントという形で間接的に入手が可能です。

では、次にどういったデータを流せばいいのかというレベルの話になりますが、これに関してはUrJTAG関連で詳細なドキュメントがあります。かいつまんで言うと、USBのパケット長は最大64バイトなので、そこにデータを詰め込んで送信するわけですが、そのフォーマットはJTAGの全信号を制御できるbit bangingモードとデータを高速で送れるbyte shiftモードの2種類を使い分けることになります。

そして、JTAGを使ってどのようにしてホストPCとの通信を確立するのかというレベルの話になりますが、これはオンチップ・デバッグ用のVirtual JTAGメガファンクションを利用するのがおそらく最も簡単です。では、実際に回路を作ってみましょう。

jtag_led_bdf.png

これはQuartus IIの回路図エディタでsld_virtual_jtagメガファンクションに入出力ピンをつないだものです。とりあえず8ビットのデータを入出力しますので、パラメータsld_ir_widthを8にセットし、念のためsld_auto_instance_indexを”YES”にします。入力ピンkey[7..0]をir_outに接続し、ir_inを出力ピンled[7..0]に接続します。ledにはボード上のLEDのピン(A15, A13, B13, A11, D1, F3, B1, L3)を割り当てますが、keyは下位2ビットの押しボタン(E1, J15)以外は特に使わないので、適当にDIPスイッチ(M15, B9, T8, M1)やGPIO入力ピン(B8, A8)に割り当てます。

入力が面倒な方のために、回路図ファイル(jtag_led.bdf)と論理合成済みのsofファイル(jtag_led.sof)を用意してあります。ただし、回路図ファイルの方はピン番号とデバイス(EP4CE22F17C6)の指定が必要になります。

上の回路を予めDE0-Nanoに書き込んでおいた上で、JNAをインストールした環境上で以下のJavaプログラムを実行します。JNAのインストールやJavaのコンパイルが面倒な方は、JNA同梱済みのJarファイル(jtag_led.jar)を実行してください。

実行環境はWindows XPでのみ動作確認をしていますが、32ビット環境であればたぶん動くと思います。64ビット環境の方は未確認ですが、おそらくDLLの名前を変更すれば動くと思われます。(”usbblstr32” → “usbblstr64”)

import com.sun.jna.*;
import com.sun.jna.ptr.*;
public class JTAG_LED {
    static native int FT_OpenEx(String pArg1, int flags,
        PointerByReference pHandle);
    static native int FT_Close(Pointer ftHandle);
    static native int FT_Write(Pointer ftHandle, Pointer lpBuffer,
        int dwBytesToWrite, IntByReference lpdwBytesWritten);
    static native int FT_Read(Pointer ftHandle, Pointer lpBuffer,
        int dwBytesToRead, IntByReference lpdwBytesReturned);
    static {
        Native.register("usbblstr32");
    }
    Pointer ftHandle = null;
    public JTAG_LED(String description) {
        PointerByReference pHandle = new PointerByReference();
        if (FT_OpenEx(description, 2, pHandle) != 0) {
            throw new RuntimeException("FT_OpenEx failed.");
        }
        ftHandle = pHandle.getValue();
    }
    public void close() {
        FT_Close(ftHandle);
    }
    Memory memory = new Memory(64);
    IntByReference ref = new IntByReference();
    public static final short L = 0x2D2C;
    public static final short H = L | 0x1010;
    public static final short TMS = L | 0x0202;
    public static final short TMS_H = TMS | H;
    public static final short OFF = 0x0D0C;
    public void write(short... data) {
        memory.write(0, data, 0, data.length);
        FT_Write(ftHandle, memory, data.length << 1, ref);
    }
    public static final int WR = 0x81;
    public static final int RD = 0xC1;
    public static short byteshift(int data, int mode) {
        return (short) ((data << 8) | mode);
    }
    public byte read() {
        FT_Read(ftHandle, memory, 1, ref);
        return memory.getByte(0);
    }
    public static void main(String[] args) throws InterruptedException {
        JTAG_LED jtag = new JTAG_LED("USB-Blaster");
        jtag.write(TMS, TMS, TMS, TMS, TMS); // TEST_LOGIC/RESET
        jtag.write(L, TMS, TMS, L, L); // SHIFT_IR
        jtag.write(byteshift(0x0e, WR), L, TMS); // USER1 instruction
        jtag.write(TMS, TMS, L, L); // SHIFT_DR
        jtag.write(byteshift(0, WR), TMS_H, TMS, TMS, L, L); // Dummy write
        int lfsr = 1;
        for (int b = 1; (b & 1) != 0;) {
            jtag.write(byteshift(lfsr, RD), TMS_H, TMS, TMS, L, L);
            if (((lfsr <<= 1) & 256) != 0) lfsr ^= 0x171;
            b = jtag.read() & 255;
            System.out.println(Integer.toBinaryString(b));
            Thread.sleep(((b & 2) != 0) ? 10 : 100);
        }
        jtag.write(byteshift(0, WR), TMS_H, TMS, OFF);
        jtag.close();
    }
}

うまくいくと、写真では伝わりにくいのですが、LEDが激しく点滅するようになります。この点滅パターンはLFSRで発生しているので全くランダムというわけではなく、時々左に流れるように見えたりします。左側の押しボタンを押すと、点滅がゆっくりになります。そして、右側の押しボタンを押すと、プログラムが停止してLEDが消灯します。コンソールで実行すると入力ピンの状態を2進数表示しますので、押しボタンやDIPスイッチの状態を確認することができます。

これらの制御は全てホストPCによるものです。その証拠に、普段は点灯しないはずのLOAD LED(D4)が点灯しているのがわかると思います。

de0nano_jtag.jpg

このサンプルプログラムはsld_virtual_jtagメガファンクションの仮想命令レジスタ(VIR)をデータの送受信に利用したもので、実はかなり変則的な使い方になります。仮想命令レジスタは最大32ビットなので、データをその単位でやり取りするのであれば、この方法で十分です。本来は仮想命令レジスタでチャネルを選択した上で、仮想データレジスタ(VDR)を使ってデータの送受信を行いますが、この場合データ長の制限がない代わりに、自前でシフトレジスタを実装する必要があります。

仮想命令レジスタの書き込みは、実際にはJTAGの命令レジスタ(IR)にUSER1命令(0000001110)をセットし、データレジスタ(DR)に送信データとインスタンスIDをセットします。インスタンスIDはメガファンクションの生成数が1つだけであれば必ず1ビット長の”1”になりますので、送信データに”1”を付加して送出します。データの受信は送信と同時に行われますが、インスタンスIDによるチャネル選択が反映されるのは次の受信データからになります。したがって、最初に1回だけダミーデータを送出してチャネル選択を確定する必要があります。

このサンプルは最も単純な例なのでほとんどメガファンクション任せですが、実際にはFIFOフロー制御等、プロトコルレベルでの設計が必要になると思われます。JTAG系はクロックドメインが異なるのでメタスタビリティに配慮する必要がありますが、DCFIFOを使えばそれがクリアできる上にFIFOも実装できて一石二鳥です。