ゲームボーイのエミュレータをGoで作った話

## 前置き

1か月ほど前にbokuwebさんの[ゲームボーイエミュレータをGo言語で書いた](https://blog.bokuweb.me/entry/gopher-boy)というブログ記事を見ました。

ゲームボーイ世代（正確にはGBC）であったのとGOで何かしたいと思っていたのもあって、僕もゲームボーイのエミュレータに取り掛かってみることにしました。

#### 追記(2020/1/24)
記事を執筆したときから機能面の大幅な向上があるため加筆を加えました

## できあがったもの

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">GoでGameBoyエミュレータを自作しました<br>いつぞやのファミコンエミュレータと違って今度は60fps出てます<br>将来的にGBCに拡張予定です よかったら遊んでみてください<a href="https://t.co/MHwjCmA3PX">https://t.co/MHwjCmA3PX</a> <a href="https://t.co/PvS89Qkvrn">pic.twitter.com/PvS89Qkvrn</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1193808951927656448?ref_src=twsrc%5Etfw">November 11, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ゲームボーイカラーにも対応しました

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ゲームボーイカラーのソフトに対応しました！！<a href="https://t.co/MHwjCmA3PX">https://t.co/MHwjCmA3PX</a> <a href="https://t.co/mug5wRQLID">pic.twitter.com/mug5wRQLID</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1197125296895299588?ref_src=twsrc%5Etfw">November 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#### Github

よかったらスターお願いします
<a href="https://github.com/akatsuki-py/gameboy"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250476/b43f7846-fc40-0d94-4e72-8384936b4fb4.jpeg" height="140px" width="480px"></a>


#### ポケモンクリスタル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250476/f9819040-f572-7477-bc14-2429851357f5.png)

#### 夢をみる島
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250476/87731082-4fa1-1a57-ac56-b26923f544a3.png)

## このエミュレータの特徴

#### 60fpsで動作
実機と同じ60fpsで動作します CPU使用率も頑張って抑えているため低スペックなノートパソコンでも60fpsでプレイが可能です

#### サウンドのサポート
ここだけ[goboy](https://github.com/Humpheh/goboy)を参考に(というかほぼほぼ移植)してサウンド付きでプレイすることが可能です

#### マルチプラットフォーム
Go言語の利点を生かしてマルチプラットフォームで動作します
Windows、MacOS、Linuxで動作確認しています(ラズパイにはOpenGLの関係で非対応)

#### ゲームボーイカラーを含むほぼすべてのROMがプレイ可能
通常のゲームボーイのROMだけでなくゲームボーイカラーのROMにも対応しています。  
MBC1、MBC2、MBC3、MBC5のサポートに加え、RTCもサポートしているためポケモン金銀のようなゲームもリアルタイムにプレイすることが可能です。

#### セーブ機能のサポート
メモリをすべてダンプするクイックセーブ機能と、0xa000-0xbfffをダンプする実機と同じ形式のセーブ機能の両方に対応しています。  
後者の形式のセーブデータは実機に書き込んでそのままプレイできます

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">自作のGBエミュレータのセーブデータが実機で動いた！ <a href="https://t.co/vzFuK12Qe8">pic.twitter.com/vzFuK12Qe8</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1217588491686531072?ref_src=twsrc%5Etfw">January 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#### 一部通信対戦機能のサポート

ゲームボーイの通信機能と一部ゲームボーイカラーの通信機能をネットワークプロトコルを用いて再現しました。  
つまりネットワーク対戦などが可能になっています。(まだ同一ネットワーク内限定ですが。。。)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">まだ同一ネットワーク内ですが、自作のゲームボーイエミュレータでポケモンの通信対戦に成功しました！うれしい！ <a href="https://t.co/N23vVekrMA">pic.twitter.com/N23vVekrMA</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1205779923610193921?ref_src=twsrc%5Etfw">December 14, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 作成過程~ファミコンエミュレータ~

まずエミュレータ開発の経験も、任天堂のハードに対する知識もなかったのでまずシンプルなファミコンエミュレータから作ってみることにしました。

個人的に作ったことがないものを作るときは最初の一歩(設計とか何から作るとか、最初の段階ではどこまで作るのかなど)を考えるのが一番しんどいのですが、幸いにもbokuwebさんの[ファミコンエミュレータの創り方　- Hello, World!編 -](https://qiita.com/bokuweb/items/1575337bef44ae82f4d3)のおかげで割とすんなり最初の一歩を踏み出すことができました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ｷﾀ━━━━(ﾟ∀ﾟ)━━━━!! <a href="https://t.co/TqXxnYFGiP">pic.twitter.com/TqXxnYFGiP</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1179035048407752707?ref_src=twsrc%5Etfw">October 1, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

その後は[ギコ猫](http://gikofami.fc2web.com/)のテストROMを参考に実装を進めていき、fpsが残念ですがなんとかマリオが遊べる程度のファミコンエミュレータが完成しました。

このときCPUとPPUの同期を適当にとっていたため、ラスタースクロールの実装のところで結構つまりました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Go言語でファミコンエミュレータを自作しました<br>平均fpsが30前後ですがSuper Mario Brosが遊べます<br>時間があったら記事書くかも。楽しかったー<a href="https://t.co/0ZmOdPhyr4">https://t.co/0ZmOdPhyr4</a> <a href="https://t.co/rVDG9Y29eL">pic.twitter.com/rVDG9Y29eL</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1184149110493827072?ref_src=twsrc%5Etfw">October 15, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


レポジトリは[こちら](https://github.com/Akatsuki-py/nes-emulator)

## 作成過程~ゲームボーイ~

#### [サンプルROM](https://github.com/gitendo/helloworld)

ファミコンエミュレータを作ることにより、エミュレータを作るという経験とゲーム機に対する知識が身についたため、本命のゲームボーイエミュレータ開発にとりかかりました。

まずはファミコンエミュレータ同様に[Hello world](https://github.com/gitendo/helloworld/blob/master/dmg/hello.asm)を動かし、、、

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">おれは　いま！　GBCエミュレータへの　だいいっぽを　ふみだした！ <a href="https://t.co/7D7DViPvIe">pic.twitter.com/7D7DViPvIe</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1187871255342399488?ref_src=twsrc%5Etfw">October 25, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

そのあとほかのサンプルROMを動かしていたのですが、、、

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">贋作 <a href="https://t.co/csgmYFrymo">pic.twitter.com/csgmYFrymo</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1188584968718471168?ref_src=twsrc%5Etfw">October 27, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ここでサンプルのように作ることがなかなかできませんでした。

ゲームボーイはファミコンよりも割り込みの種類が多く、割り込みの生じるタイミングもファミコンと違って様々なのでそこら辺を実装するのに結構苦労しました。(上の場合だと、LY=104の時点で[LY=LYCのLCD割り込み](https://w.atwiki.jp/gbspec/pages/21.html)が起きている)

またHALT命令の実装ミスなどによるバグなどもあり、とにかく割り込みには苦戦した覚えがあります。

#### [テストROM](https://github.com/retrio/gb-test-roms)

色々苦労しましたが、サンプルROMを一通り終えたので、今度は有名な[テストROM](https://github.com/retrio/gb-test-roms)をクリアするためにCPUの未実装部分を作ることにしました。

個人的にはCPUを一気に作るとバグがあったときに候補の箇所が特定しづらいので、テストROMの実行に必要な部分だけをちょっとずつ作っていくことをおすすめします。(ファミコンエミュレータの教訓)

CPUはファミコンエミュレータと同じ調子でいけると思ったのですが、オペランド指定の形式が違ったり、4bit(12bit)部分のキャリーが生じたときにセットするハーフキャリーフラグを実装したりするのが地味に面倒くさかったです。(しかもこのハーフキャリーフラグ、ほとんど使わない。。。)

#### Goの罠

ゲームボーイのタイマー(一定時間ごとに割り込みをかける機能)を作るときにGoの time.Tickを使おうと思ったのですが、ここで問題が生じました。

ゲームボーイのタイマーは一秒間に何千回、何万回と割り込みを生じさせることもあるのでそのエミュレーションをGoのtime.Tickでやろうと思ったのですが、、、

```fps.go
package main

import (
	"fmt"
	"time"
)

func main() {
	var (
		frames = 0
		second = time.Tick(time.Second)
	)

	for range time.Tick(time.Microsecond) {
		frames++
		select {
		case <-second:
			fmt.Printf("FPS: %d\n", frames)
			frames = 0
		default:
		}
	}
}
```

上記のコードは1マイクロ秒ごとにframesをインクリメントしていき1秒ごとにframesの数を表示する、つまりforループ(1マイクロ秒間隔)のループ回数を計測するものなのですが、、、

```
FPS: 674
FPS: 665
...
```

あれ、おかしいですね。。。

1マイクロ秒ごとのループなので、FPSは 1000000くらいの値になりそうですが、、、

time.Tick(time.Nanosecond) で試してみましたが、マイクロ秒同様700くらいになりました。

これはどうやらWindowsのGo特有のバグ？らしいようでtime.Tickでは少なくとも1.9ミリ秒ほどの間隔(つまり数百fps)になってしまうようです 
https://github.com/golang/go/issues/29485 にissueが立っています。 ちなみにUbuntuでも試しましたが普通に1000000くらいになりました。

なんにせよ(Windowsの)time.Tickではタイマーを再現できないことが判明しました。

これは結構悩んだのですが、結局CPUのクロックが4.194304 MHzであることを利用しました。　つまり1クロックかかる命令が終わったときに、タイマーの時間を1/4.194304 M秒だけインクリメントする方式です。(しかもこっちのほうがよさそう)

追記
どうやらWindowsがnano秒単位のsleep機能を提供していないのが根本の問題のようです

#### テトリス

色々ありましたが、テストROMもクリアしたので今度はテトリスにトライ

ここでは以下のようなバグですさまじく簡単なテトリスになってしまいました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">開発中のGBエミュレータで遊んでいるテトリス、四角ブロックしか降ってこなくてワロタ <a href="https://t.co/fGZOlaFLdD">pic.twitter.com/fGZOlaFLdD</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1192683149450338305?ref_src=twsrc%5Etfw">November 8, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

これは僕のゲームボーイエミュで疑似乱数が0のままだったため初期値の四角ブロックばかりが降ってくることになっていました。

テトリスの疑似乱数はDIVレジスタという一定時間ごとにインクリメントされるレジスタの値を使って作っていたのですが、ここのインクリメントを忘れていました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">きた！ <a href="https://t.co/OijzuN4Cpa">pic.twitter.com/OijzuN4Cpa</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1193065510654464000?ref_src=twsrc%5Etfw">November 9, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#### 完成

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">GoでGameBoyエミュレータを自作しました<br>いつぞやのファミコンエミュレータと違って今度は60fps出てます<br>将来的にGBCに拡張予定です よかったら遊んでみてください<a href="https://t.co/MHwjCmA3PX">https://t.co/MHwjCmA3PX</a> <a href="https://t.co/PvS89Qkvrn">pic.twitter.com/PvS89Qkvrn</a></p>&mdash; アカツキ (@akatsuki_py) <a href="https://twitter.com/akatsuki_py/status/1193808951927656448?ref_src=twsrc%5Etfw">November 11, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

DIVレジスタのバグを修正してからはMBC1のカードリッジのバグに対応するだけだったので意外とすんなりいけました。

ポケモンが遊べるぜ

## 実装の詳細

画面描画やVsync、ジョイパッドの入力を処理するのにはGolangのpixelというフレームワークを使いました。 pixelは直感的なAPIや親切なチュートリアルなどと使っていてあまり障壁を感じる部分がなかったのがよかったです。

あとCPUを作るときに、昔暇つぶしにやった[自作エミュレータで学ぶx86アーキテクチャ　コンピュータが動く仕組みを徹底理解！](https://www.amazon.co.jp/dp/B0148FQNVC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)で作ったCPUがファミコンやゲームボーイのCPUを設計する際に結構参考になりました。　いい本なので暇があるかたはやってみるといいと思います。

## WebAssembly

Goではwasmビルド機能がサポートされているため、エミュレータの描画部分以外をwasmでビルドしそれをJavascriptでCanvasに描画することでWebアプリケーションとしてゲームボーイエミュレータを遊べるようにしました(wasmすごい)

スマホだと60fpsでないのが難点ですが誰でも気軽に遊べるためぜひ遊んでみてください。

https://akatsuki-py.github.io/Worldwide/wasm.html

## あとがき

とりあえずまともに遊べるエミュレータができてよかったです。

頑張って作ったのでぜひこのエミュレータで遊んでいただけると嬉しいです。