Bercriber's Blog

プログラミング

2021/01/06 11:14


自作ゲームボーイエミュレータメモ

正直なところエミュレータを完成させていないし、完成しない気もしているので理解がいろいろとおかしいところもあるかと思うので基本的には元資料見ていただくのが正解かと。

(ターミナルでもグラフィックのデバッグできたりします)

資料

のチャットコミュニティも活発

ざっくり

上記のgbdev.io

を見てもらえば特に説明することないんだけど、簡単に。 ゲームボーイエミュレータは主に以下で考えればよい。

CPU

8ビットのレジスタがA,F,B,C,D,E,H,L

16ビットのレジスタがAF,BC,DE,HL,SP(Stack Pointer),PC(Program Counter)

AF,BC,DE,HLは8ビットのレジスタ二つを並べて疑似的に16ビットレジスタとして扱っている。

BCはBとCで、BC=0xABCDとすれば、B=0xAB、C=0xCDとなる。

AとFは特殊でAはアキュムレーターとして、Fはフラグとしても扱われる。

フラグはZero,Negative,Half Carry,Carryの4つ

F:11110000
  |||+-> Carry
  ||+-> Half Carry
  |+-> Negative
  +-> Zero

命令のクロック数は全体を同期するときに重要。

割り込み、Interrupts

割り込みは主にV-Blank、LCD STAT、Timer、Serial、Joypadの5種類。

IME,IE(0xFFFF),IF(0xFF0F)の三つのレジスタで操作される。

タイマー、Timer

DIV(0xFF04),TIMA(0xFF05),TMA(0xFF06),TAC(0xFF07)のレジスタによって操作される。

PPU

下から順にBackground、Window、Spritesとレイヤーになっている。

スプライトないしタイルという単位でキャラクターや背景や文字などが管理されている。

スプライトはOAM(Object Attribute Tabe)で位置や向きなどが設定される。

LCD Display Timing

ディスプレイはlineごとに描写される。

lineごとにOAMからスプライト検索し、書き込み、H-Blankに入る。

144lineでV-Blankに入る。

STAT(0xFF41)で設定されていれば、OAM検索時、H-Blank時、V-Blank時に割り込みフラグが立つ。

ラインごとに描写するのは実装が難しいので、CPUと同期をとってフラグ管理だけ進めていき、

最後にまとめて描写するのが簡単な実装になるのではないかと思う。

APU

手付かずにつき、省略。

MBC

Game Boy: Complete Technical Reference

が分かりやすい。

ゲームボーイのメインメモリは1Byteが0xFFFF個しか乗らないし、PCも16ビットで0xffffまでしか数えられない。

ROMによっては1.5MBまであり、バンクと言う概念を使い、これらにアクセスしていく。

MBCにもいくつか種類がある。ROMによってMBCが変わってくる。

たとえば、ゼルダの伝説 夢を見る島であればMBC1。ポケットモンスター 赤であればMBC3。

MBC1であれば、メインメモリの0x0000-0x3fffがROM Bank1、0x4000-0x7fffがROM Bank2、

0xa000-0xbfffにRAM Bankのコントロールレジスタがある。

やたら範囲が広いがたいてい一か所に書き込まれるだけのような気がするがわからん。

アドレス0x0000-0x3fffにBank 0、0x4000-0x7fffにBank Nが配置される。

こんな感じでアドレスを組み立てていく。

// Read Bank N
let i = (bank2 << 19) | (bank1 << 14) | (index - 0x4000);
cartridge.rom[i];

個人的つまづきポイントとして、0x0000-0xbfffにMBCの設定レジスタが配置されることになっているのに、

0x0000-0x7fffのアドレスからどのようにROMの内容を読みだすのか混乱した。

これは、MBCを通した0x0000-0x7fffの読み込みの時はROMから読んで、0x0000-0xbfffに書き込むときはメモリに書き込む。

つまり、ReadとWriteは別の場所にそれぞれ行われている。

メインメモリに両方とも展開されていると思い込んでいたのが混乱した要因だった。