今月から、C++とOpenSiv3Dでゲームボーイのエミュレータを作り始めてます。
エミュレータといえば、学生時代に友人がファミコンのエミュレータを自作していて、マッパーがどうのこうの言ってたのを思い出すのですが。そのころ自分はろくにプログラミングができなかったので、いったいどうやればエミュレータというものが作れるのか、資料を見せてもらっても何もイメージできなかったのを覚えています。
エミュレータ開発に少し興味はあったものの、それ以降はまったく情報収集せず時は過ぎ……。
そして最近になって、ゲームボーイエミュレータを自作している人の記事を見かけたのがきっかけになり、なんとなく自分でも作れるんじゃないかと思い始めて情報収集を始めました。
きっかけになったひとたち:
そういうわけで現在少しずつ開発を進めていまして、テスト ROM もいくつか動作するようになり、本日ソースコードを GitHub で公開してみることにしました。
ソースコード
これまでの経過
hello-world.gb を動かす
さて何から手を付けようか……となったのですが、例えば最初から CPU 命令を全実装しようとしたりするのは辛いと思ったので、まずは dusterherz/gb-hello-world の hello-world.gb が動く必要最低限の実装を目標としました。
BGB でステップ実行しつつ、レジスタの内容が同じになっているか確認しながら、動作に必要な CPU 命令を追加していきました。BGB はデバッガのほか VRAM Viewer が非常に便利です。
CPU 命令の実装時、主に参考にした資料:
- GameBoy CPU Manual
- Game Boy CPU (SM83) instruction set
- CPU Instruction Set – Pan Docs
- pokemium/gb-docs-ja
上記の Game Boy CPU (SM83) instruction set では命令セットが JSON で公開されているので、これを活用してだいぶ楽に CPU を実装することができました(整形してこのあたりに利用)。
このほか、Z80 の命令に関する記事なんかも大変参考になりました。特に DAA 命令の挙動に関してなど……。
それから、実装に行き詰った時などには他のエミュレータのソースコードを眺めてみたりしました。LIJI32/SameBoy は C で書かれていて理解しやすく、たびたび参考にさせてもらっています。
画面表示については、Pixel FIFO を最初から実装するのはつらいし、テスト ROM は BG だけ描画できればよさそうだったので、まずは VBlank への移行時に VRAM の中身を描画するだけにしました。ウィンドウとスプライトはしばらく無視していました。
hello-world.gb がなんとなく動いた時のツイート。なんかよく分からないことを言っている……(ns 単位のスリープはできないです)。
cpu_instrs.gb を動かす
次は retrio/gb-test-roms の cpu_instrs.gb を動かすことにしました。実装されてない命令をひたすら実装しました。256 * 2 = 512 個の命令……というと道のり遠すぎるように思えるのですが、使わない命令(ILLEGALのやつ)もあるし、使うレジスタが違うだけで動作が同じ命令が結構あるし、そんな感じでそこまで思ったより時間かからなかったです。
cpu_instrs.gb を実行して、Failed になった部分のテスト ROM のソースコード(*.s ファイル)を眺めて、BGB と 自作エミュを同時にステップ実行したりしてバグ取りしていきました。上記の記事でも述べられていますが、やはりブレークポイントの実装は必須だなあと感じました。自分の場合は、指定した PC になったとき、もしくは指定したメモリアドレスに書き込まれたときにステップ実行に切り替わるようにしてます。メモリダンプは、あったら便利だよなあと思いつつまだ実装してません。
そんな感じでデバッグしつつ、気を付けてても発生するうっかりミスなどを修正しつつ……
そんなこんなで cpu_instrs.gb 全面クリアとなりました。
dmg-acid2.gb を動かす
CPU の動作はだいたいできたので、次に気になっているのは PPU の動作です。BG だけを超適当に描画している状態なので、Pixel FIFO をちゃんと理解して実装します。
とはいえ、実機とまったく完全に同じ動作をする必要はないと思うので、たとえばキューを用意してピクセルを1個1個キューイングして……としなくても、1フレームごとの描画結果が同じならよしとします。
PPU の実装が正しいか確かめるためのテスト ROM は、mattcurrie/dmg-acid2 の dmg-acid2.gb を使いました。
まず、PPU の動作をなんとなく理解するのに役立ったのは、いろんな記事で紹介されてますけど、この YouTube で公開されている動画 The Ultimate Game Boy Talk (33c3) です。スライドも素晴らしいので、英語がわからなくても一見の価値ありです。文章だけではよくイメージできなかった動作の理解の助けになりました。
BG、ウィンドウが実装できたときのツイート
様々な試行錯誤があり……
最終的にこうなって、PPU の実装がほぼできた感じになりました。
bgbtest.gb を動かす
PPU が実装できましたが、これまでの ROM は画面にあまり動きがなかったので、なにか動いてるところが見たい! ということで、前述の BGB に付属している bgbtest.gb を動かしてみます。この ROM は、黒い背景の上を星(スプライト)がたくさん横切っていくのですが、このスプライトの VRAM への転送に DMA という機能を使用しています。この機能は、特定のメモリアドレスへの書き込みをトリガーとして、メモリの一定の範囲を VRAM に高速転送するものです。メモリを転送する部分自体の実装は簡単なので、さくっと実装しましょう。
それからボタン入力にも反応するので、それも実装してめでたく動作確認できました。
今後について
他のテスト ROM をいろいろ動かしてみて動作がおかしい部分をデバッグしたり、吸出し機が届いたら市販のカートリッジを読み込ませてみたいです。僕もマリオとかカービィを動かしてみたい!
あとはサウンド周りの実装を全然していないのでちょっと手を付けたいです。
なにか進捗があったら都度ツイートしていこうと思います。
参考資料
- 全般
- CPU
- PPU
- テスト ROM