undefinedundefinedbokuweb.me2019-08-06ゲームボーイエミュレータをGo言語で書いた概要Goはこれまで量を書いたことがなかったので入門にゲームボーイエミュレータを書いてみることにした。ゲームボーイである理由はたまたまよくできたゲームボーイの資料(http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf)を見つけてしまったため。成果物github.comまだ基本的なカートリッジタイプしか実装できていないがそこそこ動き始めたので公開することにした。直近は対応カートリッジを増やしながらWebAssemblyを吐けるようにしたい。パッド入力割込み、シリアル通信割込み、タイマー割込み、LCDC割込み、Vblank割込みCPUはシャープ製のLR35902でこの中には画像処理や音声の機能も含まれている。コアはカスタムZ80と聞くことが多いが、Intel8080とZ80のハイブリッドとも聞いたことがあって、もうちょっと詳しく知りたいと思い調べていたら以下の記事に辿り着いた。カスタムと呼ぶべきらしく面白かった。前述したように画像処理(ファミコンで言うところのPPU)はLR35902に含まれているため部品点数がとても少ない。大きな部品はメモリ2つとLCDだけだ。あのスペースに押し込めるのに苦労したんだろうなと思う。2019こんなツイートしたところ反応があったので書いてみることにする。ファミコン開発時の技術面やコスト面での限界もあっただろうし改善というとすこし大げさな気もするので気になった違いを挙げてみたいと思う。いざまとめてみるとそんなに量も無い気がするけど。タイマーペリフェラルがある逆にファミコンに無いということに驚くかもしれませんがファミコンにはタイマーがなかった。1秒待つような処理が必要になった場合、各命令がどのくらい時間を食うのか計算してwhile文などで待つ必要あったと思う。Vblank割り込みをカウントアップすれば簡易タイマーになるのでは。いうコメントをいただきました。ゲームボーイには簡素なものながらタイマーがついており周波数は4種類から選べるし、もちろん割り込みもついている。タイマーは指定周期経過するごとにカウンタをインクリメントしていき1byteのレジスタがオーバーフローする際に割り込みがかかるようになっている。このカウンタを読むことでどのくらい時間が経過したか測定することができる。この機能を使うことによりゲームボーイではエミュレータのCPUの実行タイミングが正しいか計測できる。そのためファミコンではなかったタイミングが正しいかどうかテストするROMがたくさんあった。(タイミングまで正確にエミュレートするのは難しくてこの手のテストROMは全然PASSできていない)エミュレータとしては多少タイミングがずれていても動くのでどこまで頑張るかは実装者のやる気次第。シリアル通信ができるこれもタイマー同様ファミコンに無いということに驚くが、ゲームボーイではシリアル通信ができる。原始的な作りになっていて制御すべきレジスタは2個だけ。0xFF01に送信データを書いたあと0xFF02に書くと送信されるっぽい。いうのはあまり真面目に実装していなくて0xFF01を標準出力に接続するだけでエミュレータとしては十分だからだ。テストROMによってはテスト結果をシリアルに吐いてくれるので描画の実装がまだできていなくてもCPUの命令テストなどが行える。これはエミュレータを作る側としては非常に助かる。自分は最後の最後までシリアルに出力される文字が化けていてこの恩恵に預かれなかったが。ここまで書いて気づいたんだが、このシリアルポートはゲームボーイ同士の通信に使われているポートらしい。ゲームボーイで通信ケーブルを使った覚えがないのですっかり頭から抜け落ちていた。テトリスは0x55をDr.マリオは0x60を連続して出力してくるのでバグっているのかと思っていたんだけど、多分通信相手を探してるんだそうな。ここのプロトコルがわからないが解析してWebSocketにでもつなげばネット対戦ができるかもしれない。Hblank割り込みや指定ラインでの割り込みがあるHblankとはあるラインを描画してから次のラインの描画が開始するまでのブランク期間で、ゲームボーイはHblankでの割り込みや指定したラインが描画された際(正確にはラインバッファに展開された際かもしれない)に割り込みをかけることができる。このタイミングを知ることで様々なことが可能になるが、代表的なものはやはりラスタスクロールじゃないかと思う。ラスタスクロールは画面描画の途中でスクロール量を調整することで部分的なスクロールなど様々な表現が可能となる。2019たとえばこのようにスコアやタイムの表記だけ固定してゲーム部分のみスクロールさせることができる。実際にこのカートリッジがどうやってるかまでは見てないが恐らく指定ラインで割り込みをかけてスクロール値を変更するなどすれば実現できると思う。それらのタイミングを取れないファミコンはどのようにラスタスクロールを実現しているかというと0爆弾という謎仕様がある。これはスプライト用RAMの先頭に格納されたスプライトがラインバッファ上に展開された際にある特定のレジスタにフラグが立つというものだ。スコアがスクロールしやがる。2018たとえばこれ。これは失敗例で意図しないとこまでスクロールしてるんだけど、そのおかげ(?)で0爆弾であるスプライトを目視することができる。本来コインが表示される位置にあるコインの影のような黒いスプライトだ。バグによりコインが流れていってしまってわかりにくいが。このスプライト描画完了を検出してからスクロールを開始することによりスコアやタイムは画面上部に固定したままゲーム部分をスクロールすることができている。このあたりのスクロールに関しては以下の記事も面白い。gridbugs.orgゼルダの伝説ではヘッダを固定したまま縦スクロールがありそれをどのように実装しているかという話。0爆弾というトリッキーな仕様をシンプルな割り込みで解決できるようになったのは改善といっても良さそうだ。ウィンドウという機能があるこれは最初説明を見てもなんのことかわからなかったが以下の記事を読んで氷解した。wentwayup.tamaliver.jp簡単に言うと背景の上にもう一枚背景をかぶせるようなことができる機能だ。透過処理ができるわけではないので8x8ピクセルの単位で完全上書きになってしまう。使用例としては以下のようなものが挙げられる。OVERの帯が下から上がってくるのはwindowという機能らしい。帯はまさにウィンドウ機能が使用されている。大した機能ではないように見えるが、ファミコンではこの挙動を実現できない*2んじゃないかと思っている。ファミコンではスプライトを並べて表現するか、背景を書き換えるかどちらかの手法になるが、スプライトは横方向最大8個しか並べられないし、背景をこのような速度で書き換えることはできないからだ。指摘をいただきました。確かにタイミングの制御はかなりシビアだけどhblankのタイミングをうまく捉えてscrollを駆使したら行けるのかなーという気がしてきました。少なからずグリッチが出るんじゃないかな...で、話は戻ってゼルダの伝説のヘッダ固定上下スクロールもひょっとしてこのウィンドウ機能があればシュッと解決できるんじゃないかと思ったりしてる。地味だけど画期的な機能だと思う。画像処理機能がCPUと同じパッケージに入ってるこれは半導体の集積度の向上やゲームボーイの筐体のサイズの都合上自然とこうなるべきという感じではあるが、ゲームボーイでは画像処理機能がCPUと同じパッケージに入ってる。エミュレータ作成者から見て、何が嬉しいかと言うとCPUからVRAMに直接アクセスできることだろう。ファミコンではVRAMはPPU(画像処理IC)に接続されていたためCPUからは直接アクセスすることができない。(VRAMに画像を配置するのはCPUの仕事であるにも関わらず。)どうするかと言うとPPU内のアドレスレジスタにアクセスするVRAMのアドレスを書いてからデータレジスタにアクセスすることでようやくVRAMを読んだり書いたりできる。ここで重要な点はPPU内のデータレジスタは初回ゴミデータが読めるので読み捨てる必要がある点だ。これはファミコン開発サイトNESDEVにもハマりポイントして紹介されており幾多のエミュレータ作者を陥れた仕様だろう。これをちゃんと実装しないと漏れなくスーパーマリオブラザーズの空が黒くなる。RAMを使ったりすることが多いと思う。RAMなんてものは無かったかもしれないし仕様面、コスト面からも使う必然性もないのでFIFOが入ったんだろう。初回はゴミデータになる。ファミコンにはこんな事情があったのでやはり、VRAMへのアクセスがシンプルになるのは嬉しい。Worldまたはそれに相当するROMを探しコードを読むことだと思う。今回は以下のものを使用した。github.comブートROMこれもファミコンとの違いの一つではあるのだけど、ゲームボーイはブートROMを持っている。0x0000~0x0100がブートROMの領域なんだけど一度起動後は0x000~0x0100はカートリッジのROM領域に再マッピングされるという仕様らしい。2019表示はできたもののスクロールが実装できていないので中央に居座っている。2019スクロールが絡む座標計算は何度実装しても難しくてすんなりいった試しがない。y方向の座標計算をミスっていたためホラーっぽい仕上がりに。色をLCDっぽく修正。自分にとってゲームボーイは緑っぽいLCDの色のイメージなのでわざわざこの色に修正した。CPUテストCPUテストROMはここにある。こいつはシリアルにも結果を出力してくれる便利なやつ。2019動かすには苦労した。デフォルトのカートリッジタイプではなくRAMを持ったカートリッジタイプでRAMにプログラムをコピーしてから実行するような作りになっていたためすんなりとはいかなかった。このROMは個別実行できたりかなり重宝した。難点としてはアセンブラが結構複雑で読んでもどこで落ちているのかわからないこともしばしば。Opus5謎のシューティング風ゲーム。敵もいなければ攻撃もできない主にスクロールとキー入力確認用ROMと言う感じ。スプライトが登場したのでここで実装した。スプライト用DMAも使用していてそれも合わせて実装した気がする。2019ゲームボーイの解像度は160×144なので4kディスプレイで遊ぶとこうなる。早くスケール機能をつけないといけない。2019テトリステトリスはなぜかすんなり動いて完成した気になってた。