水曜日, 5月 22, 2013

JRubyの導入

現在、DE0-Nano用のプロセッサ設計と平行して、JRuby関連の環境整備をしています。

JRubyはJava仮想マシン上で動くRuby環境で、Jarファイル一つでお手軽に導入可能な便利なものです。このRubyという言語は非常に自由度が高く、その上に別の言語(ドメイン特化言語)を構築するようなことも可能です。プロセッサの回路設計は論理合成のためにVHDLVerilog HDLといったハードウェア記述言語を利用しますが、マルチプロセッサの様な大規模な回路をCAD無しで直接記述するのは結構大変なので、ここではRubyを利用したより上位の記述をもとに、VHDLのコードを自動生成する方式を採ります。また、マルチプロセッサ用のソフトウェア開発においても、Rubyを利用したコンパイラを構築する予定です。

このJRubyですが、Windows環境ではDLL呼び出しの機能もあるので、JNAの代わりにそれ自体でJTAGのアクセスが可能です。というわけで、さっそくJRubyを利用したJTAGデモプログラム(jtag_demo.jar)を作ってみました。JRubyのJarファイル(jruby-complete.jar)は結構大きいので、今度は同梱せずにネットから最新版を自動的にダウンロードするようにしてあります。

jrubyloader.png

ダウンロードしたjruby-complete.jarをjtag_demo.jarと同じフォルダに置いておくか、Javaの拡張フォルダ(jre/lib/ext)に移動すれば、次からはダウンロード無しで実行が可能です。このやり方で、かなりコンパクトなファイルのみで回路データとコンフィギュレーションプログラムを配布することが可能になります。JavaとRubyのソースコード(Rubyの方は実行コード)はJarファイル内に同梱してありますので、同様の仕組みを利用したい方はご参照ください。

水曜日, 3月 27, 2013

JavaでJTAGコンフィギュレーション

どうせやるなら徹底的にということで、今度はJTAGによるFPGAのコンフィギュレーション(回路の書き換え)に挑戦してみたいと思います。JTAGコンフィギュレーションはQuartus IIが出力するSVF形式のファイルを利用します。SVFファイルは一種のスクリプト言語になっていて、これに記述される一連のJTAG操作を実行することにより、回路の再構成が可能となります。

まずは、DE0-Nanoを接続した状態で、Jarファイル(jtag_demo.jar)をダウンロードして実行してみてください。今度は一応64ビット環境も対応しているはずですが、こちらでは未確認です。

jtag_demo.png

上の画像は「Reset FPGA」「IDCODE」「Config FPGA」「Start Demo」の4つのコマンドを順次実行した結果の例です。「Reset FPGA」はFPGAのリコンフィギュレーションを強制的に実行するコマンドで、DE0-Nanoの場合はボード上のEPCS64の回路を読み込みます。「IDCODE」はデバイス(EP4CE22)の32ビットIDCODEを読み出すコマンドです。

「Config FPGA」は、SVFファイルを実行してFPGAのコンフィギュレーションを行います。SVFファイルはJarファイル内にリソースとして格納されていて、元々は1Mバイト超の巨大なファイルだったものが、30Kバイト程度に圧縮されています。実際、Jarファイルの容量の大部分は、同梱されているJNAのJarファイルです。SVF実行は必要最低限の機能しか実装していないので、例えばTDOをチェックするといった機能は省略されています。くれぐれも、DE0-Nano以外のデバイスでは実行しないでください。

最後の「Start Demo」は前回と同様のJTAG通信デモンストレーション(ただし、ボタン押下によるスピード変化や停止はなし)で、入出力データを観察することができます。実は、コンフィギュレーション前でも特にエラーもなく実行できてしまいますが、その場合は出力データがLEDに反映されることはありません。

今度はさすがにSVFの実行やGUIといったそれなりに複雑な処理がありますので、全て一本のJavaプログラムにまとめても読みにくいと思います。そこで、比較的汎用的な部分としてJTAGライブラリと基本的なGUI(JTAG.java)およびSVFの実行(SVFPlayer.java)、そしてデモアプリケーション(JTAG_Demo.java)といった構成に分割しました。

ライブラリを分離したおかげで、デモアプリケーション部分は非常にすっきりしています。JTAG.GUIクラスのインスタンスを生成すると、画面上にボタンが追加されます。実行内容はonClickメソッド内に記述しますが、SVF実行用にJTAG状態遷移メソッドstateと、SHIFT_DR/SHIFT_IR用にshiftメソッドを追加したので、生データを直接writeメソッドで書き込む必要はほとんどなくなりました。SVFPlayerクラスはJTAG.GUIの派生クラスで、生成パラメータにボタン名とSVFのリソース名を指定します。

import info.relm.JTAG;
import info.relm.SVFPlayer;
public class JTAG_Demo extends JTAG {
    public static void main(String[] args) {
        GUI.title("JTAG Demo for DE0-Nano");
        new GUI("Reset FPGA") {
            public void onClick() {
                reset();
                state("RESET", "IRSHIFT");
                shift(10, 0x1);
                state("IREXIT1", "IRUPDATE");
                text.append("Reconfiguration done.\n");
            }
        };
        new GUI("IDCODE") {
            public void onClick() {
                reset();
                state("RESET", "IRSHIFT");
                shift(10, 0x6);
                state("IREXIT1", "DRSHIFT");
                readBytes(4, 0);
                text.append("IDCODE: " +
                    Long.toBinaryString(0xffffffff00000000L |
                    read(4)[0]).substring(32) + '\n');
            }
        };
        new SVFPlayer("Config FPGA", "jtag_led.svf");
        new GUI("Start Demo", "Stop Demo") {
            public void onClick() {
                reset();
                state("RESET", "IRSHIFT");
                shift(10, 0xe);
                state("IREXIT1", "DRSHIFT");
                shift(9, 0x100);
                state("DREXIT1", "DRSHIFT");
                flush();
                try {
                    for (int lfsr = 1; ; Thread.sleep(10)) {
                        readBytes(1, lfsr);
                        shift(1, 1);
                        state("DREXIT1", "DRSHIFT");
                        text.append("out: " +
                            Integer.toBinaryString(lfsr | 256).substring(1) +
                            "\tin: " +
                            Integer.toBinaryString(read(1)[0] | 256).substring(1) +
                            '\n');
                        if (((lfsr <<= 1) & 256) != 0) lfsr ^= 0x171;
                    }
                } catch (InterruptedException e) {}
                shift(9, 0x100);
                state("DREXIT1", "DRUPDATE");
            }
        };
    }
}

これを使えば、わざわざQuartus IIを立ち上げなくてもFPGAのコンフィギュレーションが可能になります。もし問題が無いようでしたら、今後はこの形態でコンフィギュレーションデータを公開していきたいと思います。

火曜日, 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も実装できて一石二鳥です。

土曜日, 3月 16, 2013

ターゲットボード(DE0-Nano)

理論的な事ばかりをだらだらと書いていても仕方がないので、そろそろ実際の製作に入りたいと思います。

以前使っていたCQ出版のボードはさすがにもう設計が古く、パラレルポートが必要、初代Cycloneが最新の開発ツール(Quartus II Web Edition)のサポート対象外、といった問題があるので別のターゲットボードを探していたのですが、まさにうってつけのボードが見つかりました。

Terasic社のDE0-Nanoです。

この一万円前後の激安ボードに載っているCyclone IVはかなり大きな回路が入るので、8コアや16コア位はなんとかいけそうです。あと、A/Dコンバータや加速度センサ、SDRAMが付いているので、本体だけでも色々と遊べそうです。

ただ、惜しむらくは表示手段がボード上のLED 8個しかなく、またホストPCとの通信手段も基本的にJTAGで統合環境のみからのアクセスとなります。JTAG-UARTを使えば一応通信は可能だと思われますが、ゆくゆくはXilinxにも対応していきたいのでなるべくSOPC Builderは使いたくありません。Arduinoではダウンロードケーブルでそのままシリアル通信ができるのですが…。

マルチコアプロセッサの論理合成はそれなりに時間が掛かりますので、プログラム書き換えのたびに論理合成からやり直すのでは話になりません(無償のWeb Editionはインクリメンタル・コンパイル未対応)。ホストPCとの通信ができれば論理合成をせずにプログラムだけダウンロードすることも可能になるので、開発の効率が断然違います。また、ホストPCの自作プログラムとリアルタイムで通信できれば、さらに応用が広がることが期待されます。色々と調べてみると統合環境のコンソール入出力を無理やりリダイレクトするといった涙ぐましいものまであり、これは無理かなと一旦諦めかけたのですが、UrJTAGというフリーのJTAGツールからボードにアクセス可能なことがわかり、ここから調べていってようやくJavaからJNA経由でUSB Blasterを制御できるようになりました。(ただし、あくまでもDE0-Nano上の互換機能のみで、本物は未テスト)

こういった情報もこれから徐々に公開していきたいと思います。