MJHD

エモさ駆動開発

Rustでファミコンエミュレータを自作した話

前回、ゲームボーイエミュレータを開発してから2ヶ月ほど、今度はファミコンエミュレータを開発したのでゲームボーイとの開発の比較をしたいと思います。 ちなみに、ファミコンエミュレータの作成はこれで二回目。

f:id:wait0000:20210612115808p:plain
スーパーマリオを動かした様子

前回の記事: mjhd.hatenablog.com

命令がシンプル

CPUの実装の大半は数多い命令をデバッグしながらチマチマと実装していく部分なのですが、ファミコンで使用されているMOS 6502の命令セットはゲームボーイZ80, 8080と比べても命令数が少ないです。 ファミコンは8bitを素直にデコードすれば命令と一対一に対応するため、たかだか250個程度の命令しか存在しません。 比べて、ゲームボーイにはプレフィックス命令が存在して、特定のバイトが現れた時は、次の1バイトも考慮してデコードします。なので倍の500個ほど命令が存在します。

また、各命令も命令種別、アドレッシングモード、命令のカテゴリ(ロード命令、算術命令…など)をビット列として組み合わせたものなので、デコードも工夫をすればとてもシンプルになります。(CPU unofficial opcodes - Nesdev wiki

割り込みもゲームボーイに比べてかなり少なく、全体的にCPUの実装はサクッと終えることができました。

PPUはちょっとややこしい

比べて、PPUはゲームボーイより少しややこしいです。 ゲームボーイのPPUは、BG、スプライト、ネームテーブル、パターンテーブルがあれば描画できたのですが、ファミコンではここに属性テーブルが追加されます。 白黒ではなくてカラーなのでしょうがないですが、実装量は増えました。

また、ゲームボーイの時はCPUから直接VRAMを触れましたが、ファミコンレジスタ経由か、DMAが主になります。メモリ空間も共有していないので、バスの実装も倍になります。

他にもゼロスプライトヒットなどの機能や、スクロールもマッパーによってミラーリングが切り替わるなど、細かな部分で実装が増えます。

あと罠として、パターンテーブルを読むときに、ゲームボーイファミコンで2BPPの順序が逆です。今回見事にハマったため注意が必要。

全体的に、PPUはゲームボーイと比較して複雑です。

マッパーがややこしい

ゲームボーイのマッパーはバンクの切り替えだけ実装しておけば良かったのですが、ファミコンの場合はここにミラーリングの切り替えや、割り込みの実装などが追加になります。 メモリ空間もCPUのものとPPUのもので二つ存在するため、マッパーの実装も倍です。 また、MMC1はバンクの切り替えがシフトレジスタ経由なので、内部状態も複雑。

コントローラーはちょっとだけ複雑になった

ゲームボーイの時は8bitのセット/リセットでボタンの状態をレジスタに反映すれば良かっただけなのでシンプルなコントローラーの実装ができましたが、ファミコンはシフトレジスタのように読み込むたびに状態が変わり、各ボタンの状態がクルクルと切り替わりながら返ってくる仕様になってます。

全体的に

レトロゲーのエミュレータ作りで一番頭を使うのはPPUだと思っているのですが、その点でファミコンはPPUが比較的複雑でハマりポイントも多い印象です。 入門としてはゲームボーイが一番で、発展としてファミコンを作る、という順番が良いなと感じました。

今回作ったモノ(第二作目): github.com

第一作目: github.com