世界を動かす技術を、日本語で。

F#でゲームボーイエミュレーターを作成しました

概要

  • Game Boyエミュレータ「Fame Boy」開発の経緯と学び
  • F#によるドメインモデリングと型システム活用
  • 実ハードウェアに近い構成とシンプルなインターフェース設計
  • テスト駆動開発やAI活用による効率的な実装
  • PPU(ピクセル処理)実装の難しさと学び

Game Boyエミュレータ開発のきっかけ

  • 8年以上 ソフトウェアエンジニアとして働くも、 コンピュータの仕組み を深く理解していなかった課題意識
  • 子供の頃に Pokémon に熱中した経験から、 Game Boy をエミュレート対象に選定
  • いきなり実装せず、まず From NAND to Tetris で基礎を学習
  • エミュレータ開発の練習として CHIP-8エミュレータ(Fip-8) をF#で作成
  • 数ヶ月間の夜更かしの末、 Fame Boy が完成
    • サウンド対応
    • デスクトップ・Web両対応

エミュレータの設計と構成

  • デスクトップとWeb両対応 を目指し、コアとフロントエンド間のインターフェースを極力シンプルに設計
    • framebuffer :160x144の色調配列
    • audiobuffer :32768Hzのリングバッファ
    • stepEmulator() :1命令実行し、消費サイクル数を返却
    • getJoypadState(state) :フロントエンドからジョイパッド状態をコールバック
  • 実機ハードウェアに近い構成 を意識
    • CPU はSharp LR35902を模し、ハードウェア知識はメモリマップのみ
    • Memory.fs がRAMやバスの役割を担い、 VRAM/OAM RAM はPPUと共有
    • IoController.fs でI/Oレジスタを一元管理し、安全性向上
  • stepper関数 で各コンポーネントの処理を逐次実行し、 同期 を実現
    • 実機は並列だが、エミュレータは シングルスレッド で逐次処理
  • 正しい動作速度 (1フレーム約17500CPUサイクル)を維持するため、サウンド有効時はオーディオサンプリングレートで、ミュート時はフレームレートで駆動

F#によるCPUエミュレーションとドメインモデリング

  • F#の型システム を活用し、 CPU命令 を明確にモデリング
    • 例: LoadInstrArithmeticInstr など命令種別を区別
    • From/To型 でオペランドの位置を抽象化し、 不正状態を型で防止
  • 命令数削減 :512個のオペコードを58命令まで一般化
  • F#のパターンマッチOption型 の使いやすさを実感
  • 副作用の許容 :CHIP-8エミュレータは純粋関数型だったが、 Fame Boy ではパフォーマンス重視で可変性を利用

フラグ操作とシンプルな設計

  • setFlags関数 の設計改善
    • 配列+型で冗長だったものを、 純粋関数 にリファクタ
    • inline関数 でヒープ割当を回避し、 FPSが約10%向上
    • シンプルかつ合成可能な実装に満足感

テストとAI活用

  • 最初は Tetris ROM で動作確認しながら命令実装
  • 課題 :テストケース漏れ・命令群ごとの集中実装が難しい
  • 解決策 :AIに技術仕様からテストケース生成を依頼し、 テスト駆動開発 を実践
    • バグ発見や学習効率向上に寄与
  • 学習重視 のため、AIはテスト生成に限定し、実装は自身で担当

PPU(ピクセル処理ユニット)の難しさ

  • Game Boy にはGPUではなく PPU (ピクセル処理ユニット)が搭載
  • 多くのブログがCPU中心で、PPUは数段落のみ
  • PPU実装 は創造的というより「手順をなぞる」機械的作業が多い
  • From NAND to TetrisCHIP-8エミュレータ 経験でCPUは理解しやすかったが、PPUは習得に時間を要した
  • ピクセル単位での処理に苦労しつつも、実装を通じて理解が深まった

このように、 Fame Boy 開発を通じて コンピュータの仕組みF#の強みテスト駆動開発 の有用性、 PPU実装 の難しさなど、多くの学びと発見を得ることができた。

Hackerたちの意見

ついに誰かが実際に人間の努力をして何かを学んでるね。「LLMが助けてXをY分で作った」ってのじゃなくて。人類にもまだ希望があるのかな。

いつまでも存在し続けるよ。2026年になっても手工具で何かを作る人がいるしね。これを「アーティザナルコーディング」って呼ぼう。

長年のF#開発者で、STEMの学問的いじめを受けてきた者として、ChatGPT-3.5がF#のGitHubリポジトリからのコピペがひどくて明らかだったから、LLMを使うのは拒否してる。AGIを感じたことはなくて、ただ装飾が剥がれた盗作マシンを見ただけだった。結局、Microsoftの誰かが気づいてRLHFのアラームを鳴らしたから、GPTはかなり改善されたみたい。F#にはかなり使える感じだね。今は無原則なF#erがエージェントでうまくやってるんだろうけど、「やった、盗作問題が解決された、さあ適当なものを生成しよう!」とは思わなかった。「ああ、これでChatGPTが盗作しても明らかじゃなくなるんだ」と思った。自分のコアバリューを妥協して生産性の利益を得るためにd100やd1000を振りたくない。遅くて無職のままでいいよ、ありがとう。これは真剣な話で、今はソーラーインストールやゴミ運びを始めようとしてる。 [1] 「学生は考えたくない」という問題はLLMよりずっと前からある。2007年に上級のPDEのクラスを取ったとき、ほとんどの人が私の宿題をコピーしてた。私はPDEを勉強する意欲があったから、怠け者の数学専攻に抵抗できなかったんだ。それが数学の大学院でも再び起こった!本当に信じられない。なんでプログラムにいるの?

F#はめっちゃ楽しい!素晴らしい仕事だね!

それめっちゃクール!F#大好きだけど、小さなSmalltalkインタープリタを書いたことがあって、あれを使うときはあんまり速くはないってことが分かったよ(笑)。

F#を使うと、ちょっとアホな命令型のことをやるとパフォーマンスが良くなることに気づいたよ。でも副作用は関数内に収めるようにしてる。そのおかげで、関数はほぼ「純粋」になって、そこそこ速くなるんだ。例えば、私はいつもMapデータ構造を使うのが好きで、あれは結構いい不変の構造なんだけど、パフォーマンスが重要になると、普通のハッシュマップでつまらない命令型ループに入るのも簡単なんだ。全部を一つの関数にまとめておけば、あんまり汚い感じはしないかな。

どの機能をいつ使うかに気をつければ、F#はすごく速くなるよ。便利だよね、必要なときは機能的なパラダイムを使ったり、ホットループでは低レベルの命令型コードを使ったりできるし。でも、リンクリストやシーケンス、不変データ型をあちこちで使うと、確かにRustにはならないね。

ちょっと気になったんだけど、そのインタープリターはいつ書いたの?dotnetエコシステムは年々大幅に速度改善されてるし、特にフレームワーク時代に最後に試した人にはね。実際、C#コンパイラが活用してないテールコールの改善にも取り組んでるし(dotnet 9か10の頃に、F#が再帰呼び出しがテールコールじゃない場合にコンパイラーエラーを出す属性を追加したんだよね。だから、うっかりそれを間違えることもない)。

機能言語で書かれたエミュレーターを見るといつも感心するよ。ハードウェアを命令型言語にマッピングする方がずっと簡単だからね。みんなが考え出す機能的な抽象を見て楽しんでるよ。

コード見た?F#には可変変数や配列があって、例えばメモリにそれを使ってるよ。

ここでF#を見るのは嬉しい!エミュレーターは言語を学ぶのに最適な方法だよ。最初に見たとき、各仕事に対してほぼイディオマティックなF#を選んでるのが良いと思った。アロケーションを減らすための簡単な改善点もあるよ:Instructions.fsの分離されたユニオンは[]にできて、フィールド名を再利用することで内部フィールドを再利用できる。あと、ちょっと気になるんだけど、いくつかのレジスタについて混乱してる。すでにバイト型なのに、a &&& 0xFFuyのセッターはmember val A = 0uy with get, setよりも何も追加してないと思う。これ、時間とともに変わったのかな。

Registerのソースにはこんなコメントがあるよ: 「// レジスタはレコード型にできない。書き込み時に値を8ビットに切り詰める必要があるから、セッターが必要なんだ。// これはウェブレンダラー用で、Fableがuint8をJSのNumber(8ビット以上)にトランスパイルして、切り詰めを適用しないから。// Fableの非標準的な動作として知られている(https://fable.io/docs/javascript/compatibility.html#numeric-types)」。だから、FableがウェブターゲットでJSのNumberを使って広がることによるデータの保守的なクリーニングだと思う。

記事の中で、彼がそれをFableに移植する部分で実際に話されてるよ(彼はBlazorも試したみたい)。

めっちゃクールだね。ずっとゲームボーイ用のRustコンパイラを書こうと思ってたんだけど、こういうのを見るたびにそのプロジェクトを再開したくなるよ。

ちょっと関係あるけど、CPUサイクルをステップバイステップで表示するエミュレーター(GBじゃなくてNESかSNESだったかな?)ってなかったっけ?確かすごく遅かったけど、1000%の精度を目指してたんだよね、プレイアビリティじゃなくて。

これがあなたが言ってるやつかは分からないけど、ちょっと前に見た記憶がある。https://mtmc.cs.montana.edu/

あなたが思ってるのはno$gmb、初期のゲームボーイエミュレーターのことかな?: https://gbatemp.net/threads/no-gmb-2-5-dos-full-version.6039... NAでポケモンゴールドがリリースされる前にこれを使ってプレビューした思い出があるよ!

F#を読むと、いつもティム・ミンチンの同じ名前の曲を思い出しちゃって、頭の中で流れ始めるんだよね。

F#はいい言語だけど、C#の影にずっと隠れちゃってる気がする。ライブラリのコードの多くがC#や.NETからの受け売りだし、F#用に作られたインターフェースやライブラリはあまりないし、F#で使うための明確なドキュメントもないことが多い。

これ、すごいね!こういうプロジェクトを見るのが大好き。