Goでファミコンエミュレータを書いた

cover

はじめに

先週末からNESエミュレータ(ファミコンエミュレータ)をGoを書いていた。 サウンドは未だに実装していないが、マリオがなんとなく動くレベルまで出来たので公開することにした。 感想としては開発がめちゃくちゃ楽しくて、マリオが動作するのには相当な達成感がある。オススメ。 必要最低限しか実装できていない(はず)なので、今後の開発をしたい方々へぜひ参考になれば。

completed to start super mario bros

リポジトリは以下のリンクからどうぞ。

Hello Worldが動くまで

NESエミュレータを書く人がまず始めるべきは、Hello WorldのROMを動作させることだと思う。 これを動作させるだけでも、ROM読み出し、CPUエミュレート、PPUエミュレート、ネームテーブル、属性テーブル、パレット、GUI周りの実装が必要になる。1

当時は仕様も非常に曖昧でテストも書くことが出来なかった(今でもテストを書いていないし、なにより複雑な仕様により書けない自分がいる)。2 テストを書けない以上、Hello Worldを拠り所に少しずつ進めていくしかなった。つまり新しい機能を追加したあとでも、このROMを動作させることでリグレッションテストを行いながら進めていくといった感じ。

そしてこの段階での反省点は、CPUをそれなりに作り込んでしまったことだ。Hello Worldを動かすために必要な命令セットは意外にも少ないのだが、必要以上にコードを書いてしまい、そのせいで余計なバグに悩まされたりした。

これは以前、コンパイラを開発したときも感じたことで、最初の工程だったTokenizerを作り込みすぎて、その後のParserの実装段階になってうわーとなったことを思い出した。同じ轍を踏んだが、とりあえずよほど仕様を理解していない限り、この方法だと厳しいと思う。

私のような凡人は、まずプリミティブな実装で一気通貫させたほうがベターだとは思う。

スプライトの表示まで

次に取り組んだのは「ギコ猫でもわかるファミコンプログラミング」というサイトのgiko5.nesというROMを動かすことだった。つまりスプライトの表示を可能にしたい。 スプライトの概念やテクニックについては以下の動画が詳しい3

Hello Worldの段階で背景の描画は出来ているはずなので、細かいところを気にせずに同じように実装すれば良いと思う。例えばこのツイートをしたときには気づいていなかったが、よく見るとスプライトが背景を塗りつぶしてしまっている。

https://twitter.com/adsholoko/status/1160225386027876357?s=20

コントローラの実装

次にコントローラ(ジョイパッド)の実装をしていた模様。コントローラの実装はこれまでの知識があれば容易なのだが、意外にも重要な工程だと思う。なぜならコントローラがあることによって、テスト用のROMを動作できるのでデバッグが捗るようになる。

背景スクロール

次に実装するのは背景の横スクロールのgiko08.nesである。スクロール方法はいくつかパターンがあるのだが、最も原始的な方法での横スクロールの実装が求められる。個人的には理解まで時間がかかった箇所だったりする。自分が勝手に混乱していただけだが、ネームテーブルは1面しか利用していないことに注意。ミラーリングがうんぬんだったり、ネームテーブルの切り替えなどこの段階では必要ない。

https://twitter.com/adsholoko/status/1160534806335070216?s=20

https://twitter.com/adsholoko/status/1160553855706230785?s=20

※ ちなみにこのときのパレットの実装が微妙だったのか、若干色合いが異なる

ラスタスクロールと横縦スクロール

ギコ猫の15章から17章までの実装。とにかく鬼門だった。0爆弾という特殊な仕様。そしてミラーリング周りの実装がこれまで開発した箇所にも影響を及ぼし、全体整合性を保つことが難しかった。タイミング制御的な問題も発生し始めるので、ある程度妥協は必要なのかもしれない。上の方がチラつく問題は結局、最後まで解決できなかった。

https://twitter.com/adsholoko/status/1161284507405869056?s=20

Super mario bros

スクロールが実装できると『マリオブラザーズ1』を動かす土台が整う。事前準備として、スーパーマリオブラザーズの起動には、まずはROMの吸い出しから始める必要がある。

そのために吸い出し機とマリオのカセットを購入した。吸い出し機なんて今回限りしか使わないだろうけど、やっぱりマリオを動作させてやりたかったので買った。あとは『ドンキーコング』や『アイスクライマー』のようなスクロールのないソフトを挟んでも良かったもしれない。

https://twitter.com/adsholoko/status/1162005278297952256?s=20

余談だが、私が物心ついた頃にはスーパーファミコンだった世代(20代中盤)なので、ファミコンのカセットを見たのはなにげに初めてだった。意外と軽いのね。

ここまで来るとあとは細かいバグとの戦いだった。もちろん通過儀礼のごとく、黒い空を拝んだりもした。

https://twitter.com/adsholoko/status/1161890716768882688?s=20

そして…ついに動いた!!

https://twitter.com/adsholoko/status/1162614795704516609?s=20

デバッグ

前述したが、自分でテストを作成するのは非常に難しい。なのでNesDevというFCエミュレータ専用のgeekなサイトからダウンロードできるテスト用のROMを動作させることをオススメする。大体コントローラまで実装できれば、大抵のテストROMは動くと思う。

tests for debug

よかったこと

コンピュータの仕組みを改めて理解できた

これまでもある程度の知識はあったが、実際にCPUをエミュレートしたりする中で学べたことは大きい。原始的なコンピュータという感じなので、仮想記憶やタイマなどの仕組みはないので全てではないけど。

グラフィック描画の初歩の初歩が身につく

コンピュータグラフィックスの分野には不案内なのだが、スクロールなどを実装して理解できたのは良かった。おそらく初歩中の初歩なんだろうけど、ラスタスクロールにはとても感心したりした。

Gopherの一員になれた

Golang最高やん。

楽しい

これに尽きる。

最後に

普段はWebエンジニアの自分が、趣味プログラミングでやったなかでも相当面白いプロジェクトだった。約1週間で完成したのでみなさんもぜひ。

これらはひとえにbokuwebさんを初めとする先人の方々の大変参考になるソースがあったこと、そして優良な解説記事があったからこそだと思います。ちなみにコードを見てもらえばわかりますが、実装はかなり参考にさせていただいています。

当初は自分で書いていたものも、参考にさせていただいたコードに徐々に近しいモノになってしまったのは、偉大さを痛感する限りです。 この場を借りて感謝します。ありがとうございました。せっかくなのでどこかで発表とかしたいな。

参考🎉

Footnotes

  1. スプライトは使われていないので、この段階では理解する必要はない

  2. 普段はWebエンジニアで、単体テストはちゃんと書いてます!

  3. ニコニコ動画を久しぶりに見た気さえする