高位合成を使ってNESをFPGAで動かす

高位合成ツールが使い勝手がよいとのうわさを聞いたので、Vivado HLS(2015.1)を使ってファミコン(NES)をFPGA上に動作させたメモ。FPGAはNexys4 DDRを使った。Xilinx社のご寄附に感謝。

【本音】一年生の新入生セミナ一回目に教務主任が「みんな自己紹介してね」と無茶ブリをかまし、その結果ほぼ全員が「趣味はゲームすることです。好きなゲームは○○です。」大会になってしまい(これだから情報工学ってヤツは…)、誰一人としてゲームを作るヤツが現れなかった現状を嘆いた著者が、自己紹介の時に「私の趣味はゲーム機を作ることです」と宣言して情報工学とは何たるかを身をもって示したかったため。

なげーよ、お前は特許の文面でも書いているのか!?はい、そうです。TLOが特許の予約権がどうたら取り分率がどうたらと…(以下略

全体構成

CK_kwAOVEAEdLSU.png

手前左からNexys4 DDR, Arduino+USBホストシールドで作ったゲームパッド処理, Arduinoと手配線で作ったROM読出し装置(これはマリオ)。Arduino経由で吸い出したROMをFPGAに転送する。ArduinoのUSBホストシールドを使うと簡単にUSBゲームパッドを扱えるから便利。ただし、FPGAのPmod IOは3.3VでArduino UNOは5Vなのでレベル変換回路が必要。今回は手持ちのICがなかったので抵抗で分圧したwまぁ、60分の1秒で入力できればいいから、波形が鈍っても大丈夫だったんだけど。

ROM読出し

違法性がどうとか大変そうなので、ROMからデータ読出しはArduinoを使って作ってみた。詳細はこーどねーむ「ホンコン」 with Arduinoを参照。

CJRyuwnUMAIQPeT.png

今のところマッパー0(初期のファミコン)のみ対応。てかROMが抜き差しできないYO。
吸い出したらROMとキャラクタROMに分離しておく。

C言語によるエミュレータの設計

事前に勉強したこと

まず、NESの仕様書を読んでアーキテクチャの確認。勉強したところ→NES InfoとかNES研究室とか。
レジスタはすずめ愛好会のアーカイブを読んだ。

といいながら、理解するにはやっぱりソースコードを読むのが一番手っ取り早い。
TAS(Tool Assistant Super-play)の作者が公開している 1ファイルのNESコード とか 美しい日本のファミコンエミュレータ とか。特に後者は勉強になるいいお手本コードを書いているのでお勧め。
あと、NES上でアセンブラを書くのも勉強ギコ猫でもわかるファミコンプログラミング

エミュレータのコーディング

で、あとはゴリゴリコーディング。6502はMarat Fayzulli(iNESの作者)が公開しているコードを使わせて頂いた。

開発環境はWindows8.1上のCygwin64bitでgccを使った。最初はC言語からグラフィックを扱いたいのでSDLを使おうかと思ったが、インストールが面倒なのでやめた。Visual Studioも検討したけど、結局BMP画像を出力しながらレジスタを監視する方法で開発。
エミュを書いていると思うとしんどいけど、ビヘイビアモデル(=RTLを書かない)を書いていると思えば気楽だった。気持ちの問題!?

エミュレータのデバッグ

1日ほどでようやく画面にデモ画面が出てきた。でもスプライトが化ける。うーん。 なんでだろ。

CGMLI_aUsAEv3wU.png

レジスタを追いかけるとPCが回っていないことが判明。オリジナルの6502がunsigned charだったのでオーバーフローを制御するレジスタをエミュレートできてなかった。修正したらなんとなく動いている。

CGO1DRJUIAELwWn.png

今度は減算時の符号ビットを保管していなかったのが原因。修正したらデモ画面が表示され、デモが動いていることを確認できた。

fileCGV37KqUIAADidc.mp4

その後、キーシーケンスのエミュレーションとか組み込んで、動作することを確認。途中、Synthesijerで高位合成しようとJava版まで手を出すも、高位合成に失敗…。要リベンジ。

高位合成

Vivado HLSに通す

Vivado HLSでいきなり合成をかける。…やっぱりダメだよねぇ…。 と思ったら、バグの原因レポートを読めば一発でわかった。 MNI割り込み→レジスタエミュレーション→MNI割り込み、と再帰的な記述になっていた。 もちろん、このような動作はキー入力から考えるとあり得ないのだが、 「あり得ない」入力シーケンスが行われると再帰になってしまっていた。要は

void func( int a, int b)
{
 if( a != 0)
   func( a, b);
 
 return a + b;
}

のように、a!=0を期待したコードを書いてVivado HLSに投げると a==0も推定して再帰的なコードだと判断して合成に失敗するようだった。 結局、MNI割り込みとレジスタのエミュレーションを分離して(2ステップ実行)書き直すと一発で高位合成できた。タイミング制約は135MHzまで通る。とりあえず、100MHz(=10ns)で制約をかけて終了。

検証

いきなり実機動作は怖い、というか検証のしようがないのでシミュレーションをかける。 Vivado上のISIMで…、遅すぎる。。45フレーム(デモ画面が表示されるフレーム数)をシミュレーションするまで数時間もかかってしまうのでお話にならない。もちろん、動作も確認できなかった。

そこで、Vivado HLSで生成したHDL(RTLレベル記述)をC++記述(サイクルベースレベル記述)に逆変換するVerilatorを使った。クロック精度でシミュレーションできるので早い。45フレームで数十分程度。ただ、CygwinではVerilatorが動かない(インストールが面倒)なのでUbuntuで実行。インストールは「upt-get install verilator」で一発。お手軽すぎるw

Vivado HLSで吐いたNESのコアとC++で記述したキー入力+画像出力を組み合わせて検証。不具合の原因は単なるリセットのかけっぱなしだった…。

CHEDCYtVIAA5ErJ.jpg

リセット後に45フレーム経過させると…。動いている!

CHESqTpUYAA1rU6.jpg

Nexys4上での実機動作

VGA表示をHDLで書いてVivado HLSで吐いたコードと接続。らくちん。 で、Nexys4上にビットストリームを流し込むと…

CHI6jsWUUAIWgVu.jpg

動いた!!でも画面表示がおかしい!?
原因は手書きのVGAコアだったorz
X座標とY座標の入れ間違いとGとBが入れ間違いだった。うーん、人を介したらダメね。

気を取り直して再合成。動いた!!

CHJORbrUwAA2pvj.jpg

HLSからの高位合成には5分程度。Vivado上での合成+配置配線は15分程度。一方、Verilatorを使ったサイクルベースの検証は15分。
C言語ベースでのgccによるコンパイルは…数秒。やっぱこのTAT差はデカい。とはいえ、HDL書いていたらもっとかかったのは間違いない。

終わりに

ということでなんとか1週間程度でNESをFPGA上で動かすことに成功。感想。

  • C言語で完璧に設計しておくと一発動作しやすい。再帰的な構造やライブラリはご法度。
  • ビヘイビアモデルと思って書くとよい?
  • Vivado HLSはインタフェースがシンプルで使いやすい。ただし使いこなすには500ページのマニュアルを精読する必要がありそうだ。
  • NESはエミュレータのソースコードが公開されているので勉強しやすかった。アーキテクチャがシンプルなものねぇ。そういえば、直接SFLで書いた人もいたんだった→NES on FPGA +そんなベタなファミコンはイヤだ!
  • 勝因はHDL上の検証をなるべく省いたこと。Verilator必須でした。ISIMおせーよ。

トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2015-07-28 (火) 19:20:54 (666d)