NES on FPGA 21:48 2015/05/15

開発環境

 基本は無償ツールをベースに使用しています。
 主にSFL+で記述し、VerilogHDLやVHDLに変換して論理合成を行っています。 SFL+はSFLを個人的に記述しやすいように少し拡張したものです。
 VerilogHDLやVHDLへの変換はsfl2vl、sfl2vhコマンドや、 PARTHENONに付属するSFL2VHDL、SFL2Verilogコマンドを使用します。
 変換したHDLをAlteraやXilinxの統合開発環境において論理合成を行い、 生成された設計データをFPGAへ書き込みます。
 高速なソフトウェアシミュレーションのために、 VerilogHDLからC++へ変換可能な [Verilator] が役に立ちます。 テストベンチもC++で記述するため多彩な検証が可能です。
 NES本体よりも開発環境構築に時間がかかってるような…。

_
▼ SFL と PARTHENON

 SFL(Structured Function description Language) は1980年代初頭にNTT電気通信研究所で開発されたハードウェア記述言語です。 同期式回路を前提としているためクロック記述が不要であり、 C言語ライクな機能記述が可能です。 SFLシミュレーション環境としてPARTHENONが使用できます。
 詳細はこちら [PARTHENON SFL world] でSFL記述やシミュレーションモデルなどについての解説を閲覧できます。 またシミュレーション環境であるPARTHENONもダウンロード可能です。 SFL2VHDLが付属しています。

 追記: 最近はPARTHENONおよびSFL2VHDLは使用していません。 PARTHENONでのシミュレーションは実行速度が遅く、信号の観測性もあまりよくありません。 またSFL2VHDLに代わって、Shimizu氏が開発したsfl2vlを使用しています (コマンド名のため大文字と小文字が区別されます)。

 2015/5/15 追記: SFLの便利さを世に知らしめるためにチュートリアル的なものを書く予定でしたが、 なかなか時間が取れず…。 きしもと氏が詳細な解説を公開されていますので、 SFLを使ってみたい方はこちらをご参照ください。 [SFLチュートリアル(NSLにも対応)]

! NTTにある解説は難しいです。ちょっとした例があればいいんですが。

演習でやった方も思い出してください。

C++(C99でもいい)のような拡張が欲しいのう…。

_
▼ SFL+ 2008/04/17

 さすがに規模が大きくなってくると他言語の便利なところがうらやましくなります。 SFLをちょっと拡張(?)して個人的に使用しています。 バッチ処理にてSFLへ変換し、.sflpファイルから標準フォーマットの.sflと.hを生成します。

・制御信号の引数宣言

input io_A<4>, io_D_in<8>;
instrin io_write(io_A, io_D_in);

・配列宣言
 小規模なレジスタ宣言は通常memを使用しますが、 明示的にレジスタを指定することで場合によってはmemよりも回路規模削減できます。
rega sig[32]<8>;
sela sp[32];

・par文、any文、alt文の繰り返し
 超便利!
par(i=0;i<32;i++){ // 並列実行
	sig[i] := sig[i]<6:0> || 0b0;
}

any(i=0;i<32;i++){ // 条件処理
	sp[i] : sig[i] := sig[i]<6:0> || 0b0;
	else : sigm := 0b0000;
}

alt(i=0;i<32;i++){ // 優先度付き条件処理
	sp[i] : sig[i] := sig[i]<6:0> || 0b0;
	else : sigm := 0b0000;
}

・if else
 SFL仕様にはelseがないんですよ。
if(flag) ggg();
else fff();

・文字列定数
sel str<40>;
str = "think";
// str = 't'||'h'||'i'||'n'||'k'; に変換 
str = "ミカクニン"; // 半角カナもOK

・キャラクタ文字
reg csig<8>;
csig := 'N'; // 0x4E

・switch case文
 単一条件選択の時に便利!
switch(adrs){
	case 0b00: aaa();
	case 0b01: bbb();
	default: ccc();
}

・演算子(対象はレジスタのみ)
a += 0x2;
b -= 0x2;
c++;
d--;

・mem信号で16進数と10進数が使えるように。
mem gpr[8]<4>;

gpr[0xE] := 0x3;
gpr[5] := 0x2;

・などなど

_
▼ PARTHENONシミュレーション (最近は使用していません)

 記述したSFLとテストスクリプトをPARTHENONのSFLシミュレータであるsecondsに食わせます。
 構文エラーの場合はtermが起動してエラー個所を表示してくれます。 特にSFLに対してプリプロセッサ(上図のSFLPのようなもの)をかましていると、 行数のみのエラー表示では分かりにくいので重宝しています。 ただし、デフォルトのままではSFLファイルが強制的に更新されているようで、 エディタから「外部で更新しとるよ?」と怒られます。これは、 /usr/local/libexec/に存在するsfl_edit中に

 mv $1 $1.bak
 cp $1.bak $1
 xterm -title $1 -vb -e vi -c $2 $1 &

と記述されており、構文エラーなどの場合にこれが実行されるからです。 安全のためとはいえ、 エディタから怒られるわどうせプリプロセッサで生成するしバックアップも取ってるんで、 mvとcpはコメントアウトして使用しています。

_
▼ SFL2VHDL、SFL2Verilog (最近は使用していません)

 SFLを論理合成…といきたいところですが、 XilinxやAlteraなどの標準的なツールはSFLに対応していません(>_<)。 SFLファイルをそれらのツールで使用可能なVHDLやVerilogHDLファイルへ変換してプロジェクトに取り込みます。 SFL2VHDLやSFL2VerilogコマンドはPARTHENONに付属しています。

 % SFL2VHDL core.sfl core.vhd
にて変換します。 SFLファイルが複数ある場合はそれぞれについて変換し、 プロジェクトに追加します。
 VerilogHDLがイイという方は、同梱さてれいるSFL2Verilogも使用できます。

_
▼ sfl2vl、sfl2vh 2005/11/16

 SFL2VHDLを使う環境構築が大変?…という状況でしたが、 Naohiko Shimizu氏謹製のsfl2vlにより手軽にSFLを利用できるようになりました。 [sfl2vlサポートページ] で配布されており、「もうRTL記述には戻れない!」ひと増加中です。 個人利用でも、VerilogHDLやVHDLへの変換を容易に行うことができます。 SFL言語チュートリアルも大変に参考になります。
 変換時には最適化も施されます。 SFLで記述する場合、手作業での回路最適化は、SFLの高い可読性を損ないかねません。 sfl2vlでは、可読性を重視した記述さえも回路規模をだいぶ削れます。 使ってみました→SFL2VHDL,SFL2Verilogとsfl2vh,sfl2vlの比較
 聞くところによると、sfl2vlの方が実績があり、sfl2vhは順次最適化対応中とのこと。

_
▼ Verilatorと協調検証 2007/12/5

 高速検証環境をあなたに。
 verilatorでVerilogHDLをC++に変換することで、 遅延を考慮しないサイクルベースシミュレーションを行うことができます。  PPUの画像生成テストについては、 ModelSim Web Editionよりも6.5倍高速でModelSim SE並です。 (→図、ここではverilatorによるC++への変換とVC++でのコンパイル時間を入れた) 単純にシミュレーション速度だけでの比較ではWeb Editionよりverilatorが140倍高速でした。
 またCPUとソフトウェアの協調検証として、 CPUをC++に変換しエミュレータのCPUモデルと交換し実行することで、 CPUのみの高速なソフトウェア検証が可能になりました。

_
▼ Cygwin

 sfl2vlコマンドやverilatorコマンドなどを使用するためにはUNIX環境が必要です。 またmakeコマンドなども便利です。 Windows上でこれらコマンドを使用するためのUNIX環境としてCygwinを使用しています。

_
▼ トップモジュールについて

 トップモジュールについてはクロックやDLL、I/O、シリアルポート、 外部RAMなどの接続が必要なので、はじめからVHDLで記述しておきます。 SFLから変換したモジュールを、このトップモジュール直下のモジュールとして接続します。
 はじめXilinxをターゲットとして実装しましたが、 当然ながらAlteraのデバイスでも動かしたい要求もあります。 NESのコアモジュールはどちらの環境にも対応する汎用的な形で記述し、 デバイスのライブラリやプリミティブは使用しない方針です。 トップモジュールのみを取り替えることで、 どの環境でも実装できます。

_
▼ 演算子は便利

 SFLの単体モジュールとして、module構文とcircuit構文があります。 module構文では"+"などの演算子は使用できず、自前で演算器を接続する必要があります。 circuit構文では基本的な演算子が使用可能となっており、 可読性も向上するので主にこちらを使用します。
 それぞれの記述例は次のようになります(サイクル毎に可変な値を加算する、信号宣言などは省略)。

%i "slr.h"
%i "add.h"
module enzan {
	slr_16 base_slr;	// 自前のシフタ
	add_16 pitch_add;	// 自前の加算器
	par{
		base_slr.con(0x32, wins);	// 論理右シフト
		pitch_add.con(pitch, base_slr.dout);	// 加算
		pitch := pitch_add.dout;
	}
}
circuit構文だとシンプル。
circuit enzan {
	pitch := pitch + (0x32 >> wins);
}
SFL+ではこう
circuit enzan {
	pitch += (0x32 >> wins);
}

sfl2vlでは演算子サポートも強化されているため使わない手はありません。
 FPGA向けのXilinx ISEやAltera QuartusIIなどのツールもより便利になり、 演算子を使用すればビット数に応じて予め組み込んである高速な演算器を割り当ててくれます。 ただしFPGAの乗算器などリソースが足りない場合は自前で演算器を作成して、CLBやLEで演算器を構成する必要があります。

! moduleとcircuitの使い分けはPARTHENONシステムにおける論理合成可能かどうかを区別するためのものだったとか。

_
▼ デバッグ回路

 画面がまっくら…アドレスが範囲外…など、観測が不十分なときはよくデバッグ回路を仕込みます。 マスクしたり、想定外の入力があったときに停止したり。 しかしこの回路、不要なときは「削減」されるべきものです。 この「削減」とは、論理合成段階では回路を切り離し固定値出力にするわけですが、 記述段階ではコメントアウト、もしくは出力をゼロレベルと論理積 を取ることで論理合成時に削減されます。 つまりデバッグ回路を使用するかどうかは、 回路の励起信号、もしくは出力にスイッチを記述し切り替えることで対応します。

%d IR_DEBUG 0b1
/*%d IR_DEBUG 0b0*/

ir_def = IR_DEBUG;
if(ir_def) par{
   if(ir==0b00000010) par{
      halt := 0b1;
   }
}

_
▼ VerilogHDL、VHDLシミュレーション

 やっていません。Verilog-XLやModelSimなどを起動した記憶さえ無い! (初期バージョンまではね)
 HDLシミュレーションを行うなら、リセット信号、クロック信号を意識したテストベンチを 用意する必要があり、自動生成するにしても作業も管理も煩雑になりがちです。
 シミュレーションはPARTHENONで行っているので、 HDLへのコンバートさえ安全ならばとりあえず動きます。 おたまじゃくしがかえるになっても泳げるように、 何事もなしに実機動作しています。 これはいかにSFLによる完全同期設計が、 SFL2VHDLによるコンバートが安全であるかを示しているようです。 実機動作したからといってコンバートの安全性は保障できませんね。 そこは注意。

! 大工記述ですから!

というよりも単に波形を見たくなかっただけ。

完全非同期設計でSignalScan(波形ビューワ)とにらめっこするとなんだか変なわらいが込み上げて来る。

_
▼ VerilatorによるC++への変換とソフトウェアシミュレーション

 近年のPCスペックの向上と、適切なモデルによって、 HDLの検証は専用ツールによるRTLシミュレーションで行うよりも、 実行形式にして走らせるのがこれからのトレンド? という(VTOCは有料のようですが)ことで無償ツールのVerilatorを利用できます。
 VerilogHDLをC++へ変換してコンパイルし実行することで、 Verilog-XLなどのインタプリタ型HDL専用シミュレータよりも高速な検証が可能です。 一般的なHDLシミュレータとして、 verilog-XLは遅延情報に基づいたイベントドリブン型のシミュレーションを行っていると思います。 また、Synopsys社のVCSやCadence社のNC-Verilogはコンパイル型ですが、 やはりダイナミックシミュレータですので遅延情報が絡んできます (それでも高価なだけあってRTLシミュレーションでも高速みたいですが)。
 見たところVerilatorはイベント波及型シミュレータでしょうか? 要は遅延情報を含まないシミュレーションだと見ていますが。 使い方によっては、変換したC++コードのカーネルなどへの組み込みによる実機動作検証、 といったことも可能です(懐かしいね)。
 「C++コードやMakefileへの変換という手段のひとつですが、 VTOCの代わりにと思っている人はダウンロードしないでください」って。 もう使ってるがな。
 verilatorの検証をしていたところ、DMA転送のバグが判明… verilatorでスプライトがうまく表示されなかったため、 secondsでシミュレーションしたところ多重書き込みしまくってる。 そういやDMA関連はsecondsシミュなしのやっつけだったからなあ… このバグはModelSimでも再現できるかな? (というかアピールしてくれるだろうか?) verilatorはtristateやinoutはサポートしてないのか…。
 PARTHENONで20分かかっていたシミュレーション時間も5分程度に短縮できました。 しかしコンパイル時間も5分ほどかかるようで、 このへんverilatorがC++でなくCで出力できればもっと短縮できるのかな?なんでC++なんだよ。
 実装規模が増大し、さらにソフトも含めた検証では遅延モデルでのハードウエアシミュレーションではなく、 サイクル精度シミュレーションが有効になりそうです。 異なるクロックを使用する回路がある場合は、その部分をイベントドリブンで行う方法もあります。

 2013/8 追記: Verilatorのバージョンも上がって、さらにVisualStudio 2013 Expressなども使用できるようになりました。 PCの性能向上もあって、コンパイル時間、シミュレーション時間もストレスないレベルにまで短縮されました。 いやー便利、便利!

_
▼ FPGAボード

 はじめてのFPGAってことでまずはキットを入手しました。 20万ゲート(内部RAM含めて)以上で8万円程度、 といった基準があったようななかで3つほど候補があり、 一番安かったのがCQ出版社から市販されていたSpartan-IIE300評価キットです。 ゲーム機に適しているとかそんなことは考えもつきません。
 実装するうちに、クリスタルは66MHzより50MHzがよかったな(VGA出力がしやすい)とか、 拡張ピン位置がまとまっててカートリッジコネクタを実装しやすかったとか、思ったことはあります。 こいつにはFlashメモリやSDRAMが載ってて、デバッグ用途に使用しましたが、 最近はSRAMが多いようで、簡単にRWできるSRAMのほうが楽でよさそうです。
 結局、NESの実装には推定ゲート数5万が実装可能+内部BlockRAMが十数個(個々の容量にもよりますが) +拡張ピン100ピンほどといったところが最低ラインでしょうか。 スパ2E300評価キットは何とか合格ラインだったので、実装できたのです(!)

 次に導入したのがAltera DE1ボード。 さまざまなI/O(スイッチ、シリアルポート、FlashROM、SDRAM、SRAM、VGA出力、SDカードコネクタ) が実装されており、価格も2万円以内と、入門用には面白いボードです。
 NESは楽に3つは実装できるくらいの回路規模があり、 さらにSDカードからの読み込みや各種マッパーの実装などいろいろ試すことができました。


Copyright(C) pgate1 All Rights Reserved.