概要
- CAMLBOY はOCamlで実装されたGame Boyエミュレータ
- ブラウザ上で動作 し、スマートフォンでも60FPSを実現
- 中規模プロジェクトの実践例 としてOCamlの高度な機能を活用
- テスト容易性・保守性重視 の設計と実装
- アーキテクチャやインターフェース設計 の具体例を紹介
CAMLBOY: OCaml製Game Boyエミュレータの開発記
- CAMLBOY はOCamlで書かれたGame Boyエミュレータで、 ブラウザ上で動作 する実装
- デモページで Bouncing ball や Rocket Man Demo など複数のROMを体験可能
- スマートフォンでも60FPS で快適に動作
- GitHubリポジトリ :https://github.com/linoscope/CAMLBOY
- スクリーンショット やコード例も公開
なぜOCamlでGame Boyエミュレータを作るのか
- 新しい言語学習時に感じる「 中規模以上のコードが書けない」「 高度な言語機能の実践的な使い方が分からない」という課題
- OCamlの理解を深めるため、実践的なプロジェクトとしてエミュレータ開発を選択
- プロジェクト選定理由
- 仕様が明確で実装範囲がはっきりしている
- 数日・数週間では終わらないが、数ヶ月で完了可能な規模
- 子供時代の思い出 としてのGame Boy
- 実装目標
- 可読性・保守性重視のコード
- js_of_ocaml でJavaScript化し、ブラウザで動作
- スマホで遊べるFPS を実現
- ベンチマーク を取り、コンパイラごとの比較
本記事の対象・内容
- Game Boyエミュレータ実装 や 中規模OCamlプロジェクト に興味がある方向け
- OCamlの高度な機能活用例 (ファンクタ、GADT、ファーストクラスモジュール等)
- Game Boyアーキテクチャ概要
- テスト容易・再利用性の高いコード構造
- ボトルネック発見やパフォーマンス改善
- OCamlに関する所感
- 基本文法や詳細なGame Boy仕様は割愛 (参考資料を別途案内)
アーキテクチャ設計
- CAMLBOY全体構成図 を簡単に説明
- CPU/タイマー/GPU はクロックに従い固定レートで動作
- Bus が各ハードウェアモジュール間のデータ転送を制御
- アドレスごとに適切なモジュールへデータをルーティング
- Addressable_intf.S というインターフェースを各モジュールが実装
- 割り込み要求 はタイマー・GPU・シリアルポート・ジョイパッドから可能
メインループ同期手法
- CPU/タイマー/GPU の動作を同期させるための工夫
- CPUが命令1つを実行し、消費サイクル数を計測
- タイマー・GPUを同じサイクル分だけ進める
- 「 catch up手法」と呼ばれる同期方式
- 実装例(抜粋)
let mcycles = Cpu.run_instruction t.cpu inTimer.run t.timer ~mcycles;Gpu.run t.gpu ~mcycles
データ読み書きインターフェース設計
8ビットデータ用インターフェース
- Bus がGPUやRAMなど複数モジュールと8ビットデータやり取り
- OCamlのモジュール型シグネチャ でインターフェースを共通化
Addressable_intf.Sとして定義
- 各モジュール(RAM/GPU/Timer/Joypad等) でこのシグネチャをinclude
16ビットデータ用インターフェース
- CPUとBus間では16ビットデータの読み書きも必要
- 8ビット用シグネチャを拡張 し、
Word_addressable_intf.Sを定義- 16ビット読み書き関数(
read_word/write_word)を追加
- 16ビット読み書き関数(
Busモジュール実装
- Bus は各モジュールをフィールドとして保持
- アドレスに応じて適切なモジュールへread/writeをルーティング
- 16ビット読み出しは8ビット2回で実現 (実機と同様)
レジスタ・CPU実装
レジスタ
- Game Boy CPUの8ビットレジスタ(A/B/C/D/E/F/H/L)
- 16ビットレジスタ(AF/BC/DE/HL) としても利用可能
- read/write関数 でアクセス
CPU
- 初期実装
- BusやRegisters等をフィールドとして保持
- 命令取得・デコード・実行の流れ
- 課題:依存関係が多くテストしづらい
- Busが他モジュールに依存するため、CPU単体テストが困難
ファンクタ活用によるテスト容易性向上
- OCamlのファンクタ でBus実装を抽象化
module Make (Bus : Word_addressable_intf.S) : sig ... end
- Mock実装を注入することでCPU単体テストが容易
- 依存モジュール未完成でもテスト可能な設計
まとめ
- OCamlによる中規模プロジェクト の実践例として、CAMLBOYは 設計・テスト容易性・高度な言語機能活用 の好例
- モジュール型・ファンクタ・シグネチャ を活用し、 保守性・拡張性・テスト性 を両立
- ブラウザ動作・スマホ対応 も実現し、 実用的なエミュレータ として完成度が高い