金曜日, 12月 6, 2013

デモシステム更新(カメラ制御)

しばらく間が空いてしまいましたが、デモシステムを更新します。ダウンロードリンクは前回のデモと同じ(relm_demo.jar)になります。

基本的に前回のデモ内容は全て含まれていますが、回路の変更によりソフトウェアの互換性はありません。今後この新しい回路の仕様に基づいてリファレンスを公開していく予定ですので、その点ご容赦ください。

最も主要な追加デモはCMOSセンサカメラの制御ですが、その他にもSVGA解像度(800×600)への対応やウォームリセット機能が追加されました。また、JTAG通信仕様の変更により、画像の転送が少し速くなっています。

デモ用ソフトウェアは以下になります。

reset.rb
ウォームリセットを実行します。実際にはコア戦争のVampire(またはpit-trapper)のように、メモリ全体をジャンプ命令で埋め尽くして全てのスレッドの捕獲を試みます。もしうまく捕獲できなかった場合にはLEDが点灯したままになりますので、そのときは「Config FPGA」でコールドリセットを実行してください。

vga.rb
SDRAMの内容をVGA(640×480, 60Hz)出力します。外付けの出力回路は前回の記事を参考にしてください。「Send Image File…」または「Send Image URL…」で画像データをSDRAMに書き込むことができます。

svga.rb
SDRAMの内容をSVGA(800×600, 72Hz)出力します。モニタによっては対応してないかもしれません。解像度を切り替える場合は、一旦ウォームリセットを実行してください。

gsensor.rb
加速度センサで画面出力をスクロールします。予めvga.rbかsvga.rbのどちらかを実行しておく必要があります。メモリ上に2048×2048ピクセルの仮想画面があり、その中で表示位置を移動します。ボード上のボタン操作でスクロール方向を反転させることができます。(KEY1: 通常, KEY0: 反転)

led.rb
LEDをランダムに点滅させるプログラムです。DE0-Nano本体のみで実行が可能です。

camera.rb
CMOSセンサカメラの画像をリアルタイムで画面出力します。予めvga.rbかsvga.rbのどちらかを実行しておく必要があります。CMOSセンサのデータはYUV形式で受け取れますので、色変換とディザ表示のために、残る全てのスレッドを消費して強引に高速実行しています。残念ながら、DE0-NanoにはSDRAMが一つしか載ってないので、表示のための読み出しと書き込みが同時にはできません。そのため、昔のVRAMのようにメモリ書き込みの際に表示読み出しをしないので、黒いノイズが入ってしまいます。ボード上のKEY0を押すと、垂直ブランク期間中にのみ書き込みを行うモードとの切り替えを行いますが、ノイズが消える代わりに表示速度が低下します。KEY1は簡易肌色検出モードの切り替えになります。

IMG_3955.jpg

外付けのCMOSセンサは、aitendoのカメラモジュール(OV9655)を利用します。ピンアサインはこのモジュールに合わせてあるので、同じくaitendoのピンヘッダ変換基盤を利用すれば、あとはピンソケットを2つ付けるだけで面倒な配線は全く不要になります。カメラの向きの関係でピンソケットの片方をL型にする必要がありますが、どちらをL型にするかで違った形のカメラ装着が可能です。

IMG_3957.jpgcamera_circuit.png

このデモではカメラ入力を直接画面に表示していますが、例えばこのカメラをロボットの制御に応用する場合、画面表示の代わりに画像認識やモータ制御を行うことになります。画像認識にちょっとした顔検出アルゴリズムを組み込めば、顔の方に寄って来るロボットが作れたりします。こういった多数のセンサやアクチュエータを同時制御するロボティクス応用において、このプロセッサはまさに打って付けであるといえます。

月曜日, 8月 12, 2013

デモシステム公開

レジスタレスマルチプロセッサの実行可能なデモシステムを公開します。

このシステムは、市販のFPGAボードDE0-Nano上に16コアのレジスタレスマルチプロセッサを構築し、ホストPCからのプログラムのダウンロードおよび実行を可能にしたものです。

デモ内容は主に、ボード上のSDRAMを画像メモリとした画面出力と、加速度センサによる画面のスクロールです。レジスタレスマルチプロセッサではキャッシュアクセスによる命令実行タイミングの乱れがないため、厳密なタイミングのデバイス制御をソフトウェアで直接行えるという利点があります。特に、このシステムではその利点を生かして、複雑なSDRAMの制御をほぼソフトウェアのみで行っています。

デモの実行は、DE0-Nanoを接続したホストPC上で実行可能Jarファイル(relm_demo.jar)をダウンロードして実行するだけでOKです。ただし、Java実行環境とUSB-Blasterのドライバがインストールされている必要があります。おそらく、DE0-Nano付属のデモプログラムが動く環境であれば、問題ないと思われます。

できればDE0-Nano本体だけで実行できるデモにしたかったのですが、やはりボード上のLEDのみでは有効なアプリケーションを開発するのが難しいので、抵抗5本だけで出来る簡易VGA出力回路を増設することにしました。あくまでも簡易なので、DE0のVGA出力の様に中間調が出せませんが、それでもLEDに比べればかなりのことが表現出来ると思います。

vga_circuit.pngIMG_3378.JPG

上の回路では基板の空いた部分に圧電サウンダを載せていますが、今回のデモでは特に使用していません。音が不要な方や圧電素子によるFPGAの破壊が心配な方は、省略しても結構です。

relm_demo.jarを実行すると、JRuby環境が未ロードの場合にはインターネットからダウンロードを開始します。ダウンロードしたjruby-complete.jarがrelm_demo.jarと同じフォルダに保存されますので、次回からはダウンロードが不要になります。

jrubyloader.png

JRubyがダウンロード済みになると、デモシステムが起動します。

relm_demo.png

まず、ホストPCにDE0-Nanoを接続した上で、「Config FPGA」を押してマルチプロセッサ回路のコンフィギュレーションを実行します。コンフィギュレーション直後は16のスレッドのうち1つでプログラムローダを実行し、他のスレッドは停止状態となります。プログラムローダはホストPCからJTAG経由で送られてきたデータを基にメインメモリおよびSDRAMの書き換えを行います。これにより、再コンフィギュレーションすることなく、ソフトウェアの書き換えと起動が可能になります。

コンフィギュレーション完了後、デモプログラムの実行を行います。まずDE0-Nano本体のみで実行可能なデモとして、「led.rb」のタブを選択します。エディタにRubyのプログラムが表示されるので、「Run Program」を押して実行すると、DE0-Nano本体のLEDが点滅を始めます。デモプログラムはこのようにRubyのコードとして記述され、デモ環境で実行するとプログラムが転送されて、FPGA上で実行されるようになります。

ただし、現状ではRubyを利用したアセンブリ言語的なドメイン特化言語を構成しているので、比較的低レベルのプログラム記述でソフトウェアを構築することになります。残念ながら、RubyのコードがそのままFPGA上で実行できるわけではありません。

VGA出力回路を接続すると、より高度なデモが実行できます。「Config FPGA」でコンフィギュレーション終了後、「vga.rb」を選択して「Run Program」で実行します。VGA回路出力を適当なモニタに接続すると、通常はSDRAM初期状態のランダムパターンが映し出されます。下の写真では、左側がホストPCのモニタ、右側がFPGAのVGA出力になります。

IMG_3379.JPG

この状態で、「Send Image URL…」を押してWikipediaの富士山の記事に使われている画像のURL(http://upload.wikimedia.org/wikipedia/ja/3/3e/MtFuji_FujiCity.jpg)を入力します。(Ctrl+VでURL文字列の貼り付けが可能)

転送にしばらく時間が掛かりますが、VGA出力に富士山の画像が映し出されます。VGA出力が中間調を出せないため、画像は独自アルゴリズムによる誤差拡散ディザ表示となります。インターネット接続環境でない場合、「Send Image File…」でローカルファイルの画像を転送することも可能です。

IMG_3380.JPG

画像が表示されている状態で、「gsensor.rb」を選択して「Run Program」で実行すると、DE0-Nano本体の傾きに応じて画像全体がスクロールするようになります。

IMG_3381.JPG

この状態で別の画像を転送することが可能ですが、転送の最中でもDE0-Nano本体を動かすと画像のスクロールが実行されます。これは、加速度センサによる画面のスクロールの処理と、プログラムローダによる画像データのSDRAMへの書き込みが、別々のスレッドで並列実行していることを示しています。実際にはこの時点で、以下の4つのスレッドが実行していることになります。

  1. プログラムローダ
  2. VGA出力(vga.rb)
  3. 加速度センサ値読み取り(gsensor.rb)
  4. 加速度センサ値による画面スクロール(gsensor.rb)

これに加えて、LED点滅デモ(led.rb)を起動しても全く問題なく動作します。プロセッサ数の16個までのスレッドを同時に実行することが可能ですが、現状では個別のスレッドをホストPCから停止する機能はありません。ただし、「Config FPGA」を実行することで、プログラムローダのみの初期状態に戻すことができます。

エディタ上でプログラムを変更することで、ユーザ独自のプログラムを実行することができます。例えばled.rbの「_wait 1000000」の数値を変更して「Run Program」を実行すると、点滅速度を変えることができます。「New」を押すと空の「*Scratch*」タブが現れるので、最初からプログラムを書くことも可能です。ただし、変更内容を保存する機能はないため、本格的な開発をしたい場合にはEclipseでJarファイルをインポートするのがよろしいかと思います。

「Create mif files…」を押すと、関連するファイル(jtag.rb, asm.rb, de0nano.rb, mif.rb)を参照用に開いた上で、「Create mif files?」に「はい(Y)」と答えると論理合成時に必要となる初期メモリデータファイルを作成します。この初期メモリデータには、プログラムローダのコードが書き込まれます。実行プログラム記述については今後解説をしていく予定ですが、asm.rbおよびde0nano.rbの内容を解読することで理解することも一応可能です。

「Render VHDL」はプログラム実行には直接関係ありませんが、論理合成用のVHDLコードを出力することができます。このVHDLコードは実際にはRubyプログラムにより自動生成されますので、これらのRubyプログラムのコードも参照用にタブ表示されます。これも内容を解読することで、理論的には全ての技術内容を知ることが可能です。ちなみにVHDLコード出力をうっかり「Run Program」しても大丈夫なように、最初の行におまじないが入っています。

Javaのソースコードも含め、全ての技術的内容はJarファイル内に含まれていますが、全く解説なしに独自開発を進めるのは難しいと思われます。今後、このツールをベースに解説を進めていく予定です。

木曜日, 5月 23, 2013

命令セット

現在設計中のプロセッサは、命令語長4ビット、データ語長32ビットの合わせて36ビット幅で内部メモリへのアクセスを行います。

命令フェッチの際、4ビット長の命令と同時に読み出された32ビット長のデータを、命令実行の際のオペランドYとします。

 

 命令(4ビット)  オペランドY(32ビット)

 

レジスタとしては、プログラムカウンタPCの他に、32ビットのレジスタXに1ビットのキャリーフラグCを付加した33ビット長のアキュムレータAccを持ちます。

 

 プログラムカウンタPC

 

アキュムレータAcc(33ビット):

 キャリーフラグC  レジスタX(32ビット)

 

ちなみにDE0-Nano上のFPGA(EP4CE22)では36ビット×16Kワードの内部メモリが確保可能なので、プログラムカウンタPCは14ビットになります。

その他、各プロセッサ要素固有の内部状態として、排他制御用のロック状態フラグlockがあります。ロック状態フラグはLOCK(TRYLOCK)命令でセットされ、UNLOCK命令でリセットされます。ロック状態のセットは対象となるプロセッサでしか実行できませんが、リセットはオペランドでプロセッサを指定できるので、任意のプロセッサで実行可能です。

命令コードとその動作内容を、内部状態の変化として表したものが以下の表になります。ただし、これはあくまでも暫定的なもので、実装の都合により変更される可能性があります。(8/16変更しました)

 

コード Y[31..28] 命令 PC関連 Acc関連 その他動作内容
0000   GET, DATA PC := X X := Y  
0001 0000  JUMP PC := Y    
0001 0001 JUMPNZ if(X!=0) PC := Y
else PC := PC + 1
   
0001 0010 JUMPNP if(X<=0) PC := Y
else PC := PC + 1
   
0001 0011  JUMPM if(X<0) PC := Y
else PC := PC + 1
   
0001 0100 JUMPNM if(X>=0) PC := Y
else PC := PC + 1
   
0001 0101 JUMPP if(X>0) PC := Y
else PC := PC + 1
   
0001 0110 JUMPZ if(X==0) PC := Y
else PC := PC + 1
   
0001 0111 UNLOCK PC := PC + 1   unlock(Y)
0001 100- JUMPNC if(C==0) PC := Y
else PC := PC + 1
   
0001 101- JUMPC if(C==1) PC := Y
else PC := PC + 1
   
0001 110- TRYLOCK if(!lock) PC := Y
else PC := PC + 1
  lock := 1
0001 111- LOCK if(lock) PC := Y
else PC := PC + 1
 
0010 code PUT if(collision) PC := PC
else PC := PC + 1
  if(!collision) [Y] := code:X
0011   HALT PC := PC    
0100   MUL PC := PC + 1 X := Y * X  
0101   MULH X := (Y * X) >> 32  
0110   SHIFT if(Y<0) Acc := X>>Y
else Acc := X<<Y
 
0111   IO Acc := io_port(Y, Acc)  
1000   LOAD X := Y  
1001   OR X := Y or X  
1010   AND X := Y and X  
1011   XOR X := Y xor X  
1100   ADD Acc := Y + X  
1101   SUB Acc := Y — X  
1110   ADC Acc := Y + X + C  
1111   SBB Acc := Y — X — C  

 

基本的に4ビットの命令コードで命令実行内容が決定されますが、一部ジャンプ命令等はオペランドYの上位4ビットを利用して命令の拡張が行われています。

ジャンプ命令は条件に応じてPCの値を変化させるものですが、その後PCの下位ビットがメモリバンクと一致しない間は内部的にHALT命令を実行し、回転待ちを行います。

PUT命令によるメモリ書込みは、実際にはスレッドが目的のメモリバンクに到達した時点まで遅延されます。ここでもし、あるバンクへの書き込みが未完了の状態で、また同一バンクへの書き込みを実行しようとした場合(collision=1)、一旦PC := PCとして一周分の回転待ちの間に全ての遅延書き込みを完了させた上で、再び書き込み命令を実行します。二度目の書き込みでは全ての遅延書き込みが完了しているため、今度は必ず成功します。

PUT命令によるオペランド書き換えの際、実際にはYの上位4ビットの値で命令コードの書き換えも行われます。これは、命令コードとオペランドを同一メモリにした方が効率が良い(パリティビットが使える)のと、排他制御の際に同時書き換えができた方が都合が良いといった理由があります。ただ、オペランドだけ書き換える際にも命令コードを指定しなければならないので、ハンドアセンブルの場合は若干面倒です。基本的には、こういった作業をコンパイラ等で自動化することを想定した設計となっています。

ここで、設計上最も悩ましいのがランダムアクセス読み出しです。このプロセッサでは配列やテーブルの機能を実現するために最低限必要な命令として、DATA命令を実装しています。DATA命令はLOAD命令と同様にオペランドYの値をレジスタXに代入しますが、同時に代入実行前のレジスタXで指定された番地へのジャンプも実行します。基本的な使い方としては、レジスタXに戻り番地を入れて目的のDATA命令へのジャンプを実行することになりますが、このジャンプの飛び先を変化させることでランダムアクセスが可能になります。とはいえ、いちいちジャンプ命令のオペランドを書き換えていたのでは、あまりに効率が悪くて話になりません。

ところがなんと、不思議なことに同じ命令を利用することでこれが解決します。

例えば、1000番地にデータとしてオペランド値123を持つDATA命令を置いたとします。100番地からのコードで、まずLOAD 1000でレジスタXに1000を代入し、次に101番地で次の命令番地の102をオペランドとするDATA命令を実行します。するとまず、101番地のDATA命令の結果、レジスタXの値は102となり、1000番地にジャンプします。次に1000番地のDATA命令の実行により、レジスタXの値は123となり、102番地へのジャンプが実行されます。この結果、101番地のDATA命令は、表面的にはレジスタXで指定した番地のデータを取得(X := [X])し、ついでにオペランドで指定された番地へのジャンプ(PC := Y)を実行する命令として動作します。101番地と1000番地のDATA命令は実行内容は同じですが、プログラム内での意味合いは全く違ったものになりますので、101番地の方は同じ命令コードでGETという名前を割り当てることにします。

 

PC 命令 Y X
100 LOAD 1000 1000
101 DATA(GET) 102 102
1000 DATA 123 123
102 次の命令…    

 

GET命令によるランダムアクセス読み出しは、ハードウェア的には簡単な命令を一つ実装するだけで済むので非常に低コストです。しかし、実行時にはジャンプを2回実行しなくてはならないため、回転待ちのコストが掛かります。特に、上の例の様にGET命令の戻り番地を命令の次の番地にした場合、必ず1周分の回転待ちが発生します。これがもし、GET命令、DATA命令、戻り番地を隣接するメモリバンクに配置することが可能であれば、回転待ちは2命令分にまで減少します。実際には、同じDATA命令にアクセスするGET命令が複数あると考えられるので、そう単純にはいきませんが、命令やデータの配置を工夫することで、実行効率の改善が期待されます。こういった最適化の技法は、今後興味深い研究対象として発展する可能性があります。

水曜日, 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のコンフィギュレーションが可能になります。もし問題が無いようでしたら、今後はこの形態でコンフィギュレーションデータを公開していきたいと思います。