ゲヌムボヌむ゚ミュレヌタの䜜成、パヌト1

こんにちは



少し前たで、チップ8゚ミュレヌタヌの䜜成に関する蚘事がHabréに掲茉されたした。これにより、゚ミュレヌタヌの䜜成方法を少なくずも衚面的に理解するこずができたした。 ゚ミュレヌタヌを実装した埌、さらに進んでいくように思われたした。 遞択は元のゲヌムボヌむにかかった。 結局のずころ、この遞択は、より深刻なものを実装したい状況に理想的であり、゚ミュレヌタヌの開発経隓はほずんどありたせん。



゚ミュレヌションに関しおは、ゲヌムボヌむは比范的単玔ですが、それでもかなりの量の情報を調べる必芁がありたす。 このため、Gameboy゚ミュレヌタヌの開発に関する蚘事がいく぀かありたす。 最終結果は、他の゚ミュレヌタヌにはないこずが倚いサりンドを含む、オリゞナルのほがすべおの機胜をサポヌトする、互換性の良い゚ミュレヌタヌになりたす。 おたけずしお、゚ミュレヌタはほずんどすべおのテストROMに合栌したすが、それに぀いおは埌で詳しく説明したす。



これらの蚘事には、゚ミュレヌタヌ実装の完党な説明は含たれたせん。 これは倧きすぎお、実装からの関心はすべお消えたす。 たれな堎合にのみ特定のコヌドに到達したす。 実装の小さなヒントを䜿甚しお、より理論的な説明を行うずいうタスクを自分で蚭定したす。理想的には、゚ミュレヌタを簡単に䜜成でき、同時に自分で䜜成したような気分になりたす。 必芁に応じお、独自の実装を参照したす-必芁に応じお、倧量のコヌド行を砎るこずなく適切なコヌドを芋぀けるこずができたす。



この蚘事では、Gameboyを理解し、そのプロセッサずメモリを゚ミュレヌトするこずから始めたす。



ゲヌムボヌむ゚ミュレヌタの䜜成、パヌト1

ゲヌムボヌむ゚ミュレヌタの䜜成、パヌト2

ゲヌムボヌむ゚ミュレヌタの䜜成、パヌト3



目次

はじめに

建築

CPU

äž­æ–­

蚘憶

おわりに



はじめに



Gameboyは、1989幎に発売された任倩堂のハンドヘルドコン゜ヌルです。 それは元の癜黒のゲヌムボヌむに぀いおです。 ガむドされるさたざたなドキュメントでは、ゲヌムボヌむずいうコヌド名が䜿甚されおいるこずに泚意しおください-DMGDot Matrix Game。 さらに䜿甚したす。



開始する前に、DMGの技術的特性を理解する必芁がありたす。



CPU 4.19 MHzの呚波数で動䜜する8ビットSharp LR35902
RAM 8 kB
ビデオメモリ 8 kB
画面解像床 160x144
垂盎呚波数 59.73 Hz
音 4チャンネル、ステレオサりンド




件名を確認したら、次のステップはドキュメントです。 必芁な情報が倧量にあるため、蚘事のすべおを完党に掲茉するこずはできないため、事前にドキュメントを準備する必芁がありたす。



Gameboy CPU ManualずいうDMGの玠晎らしいドキュメントがありたす。 著名な開発者からのいく぀かの有名なドキュメントが含たれおおり、必芁なほがすべおの情報が含たれおいたす。 圓然、これですべおではありたせんが、この段階ではこれで十分です。



正匏なものであっおも、文曞に誀りがあるこずをすぐに譊告したす。 この䞀連の蚘事の䞭で、私が芋぀けるこずができるさたざたな文曞のすべおの欠点に蚀及しようずしたす芚えおおいおください。 たた、倚くのギャップを埋めようずしたす。 結論ずしおは、DMGの包括的な説明はありたせん。 利甚可胜な資料は、倚くのコン゜ヌルノヌドの䜜業に぀いおの衚面的なアむデアのみを提䟛したす。 プログラマヌがそのような「萜ずし穎」を認識しおいない堎合、゚ミュレヌタヌの開発は思ったよりもはるかに耇雑になりたす。 DMGは、信頌できる詳现な情報を手元に眮いおおくず簡単です。 そしお問題は、倚くの重芁な詳现が他の゚ミュレヌタヌの゜ヌスコヌドからしか孊べないずいうこずです。しかし、それは私たちの仕事を容易にしたせん。 よく知られおいる゚ミュレヌタヌのコヌドは、耇雑すぎるGambatteか、ひどく山ほどの䜎品質のコヌドですVisual Boy Advance-涙なしにコヌドを芋るこずはできたせん。



蚘事は私の゚ミュレヌタヌに目を向けお曞かれたものなので、Cookie ゜ヌスずCookie Cookieぞのリンクをすぐに瀺したす。



建築

将来の゚ミュレヌタのアヌキテクチャから始めたしょう。 DMGを゚ミュレヌトするには、互いにほが独立した倚くのモゞュヌルを実装する必芁がありたす。 そのような状況では、すべおを1぀のヒヌプに入れるのは愚かなこずです他の゚ミュレヌタでよく芋られたす。こんにちはVBA。 より゚レガントな゜リュヌションは、DMGの個々の郚分を、鉄の郚分を゚ミュレヌトする個別のクラスずしお実装するこずです。



これには理由がありたす-゚ミュレヌタの開発を始めたのは、すべおのコンポヌネントが1぀のスヌパヌクラスになっおいるためです。 誰もがやるべきこずだけをすれば、物事はずっず楜になるこずがすぐに明らかになりたした。 このアプロヌチには明らかな耇雑さがあるこずを認識する䟡倀はありたすが。 クラスの責任を正しく区別するには、DMGの内郚構造を十分に理解する必芁がありたす。



それでは始めたしょう。



CPU

DMGには、4194304 Hzの呚波数で動䜜する8ビットSharp LR35902プロセッサが含たれおいたすこのような粟床に驚かないでください。今埌この番号が必芁になりたす。 Zilog Z80プロセッサの簡易バヌゞョンず芋なすこずができたす。これは、Intel 8080に基づいおいたす。Z80ず比范するず、䞀郚のレゞスタず呜什セットが欠萜しおいたす。



プロセッサには、8぀の8ビットレゞスタA、B、C、D、E、F、H、L、および2぀の16ビット専甚レゞスタPCおよびSPが含たれおいたす。 䞀郚の呜什では、8ビットレゞスタを組み合わせお16ビットレゞスタずしお䜿甚できたす。぀たり、AF、BC、DE、HLです。 たずえば、BCレゞスタは「接着」レゞスタBおよびCであり、Cレゞスタは䞋䜍バむトずしお機胜し、Bは䞊䜍バむトずしお機胜したす。

レゞスタA、B、C、D、E、H、Lは汎甚レゞスタです。 レゞスタAもバッテリヌです。 レゞスタFにはプロセッサフ​​ラグが含たれおおり、盎接アクセスできたせん。 以䞋はレゞスタの抂芁です。 ビット0〜3は䜿甚されたせん。

ビット 7 6 5 4 3 2 1 0
旗 Z N H C 0 0 0 0


フラグの目的



ご想像のずおり、レゞスタPCプログラムカりンタヌは呜什カりンタヌであり、次の呜什のアドレスが含たれおいたす。



SPレゞスタスタックポむンタヌは、それぞれ、スタックの最䞊郚ぞのポむンタヌです。 知識のない人にずっおは、スタックは倉数の倀、戻りアドレスなどが曞き蟌たれるメモリ領域です。 SPには、スタックの最䞊䜍のアドレスが含たれたす-スタックは、高から䜎に成長したす。 圌にずっおは、垞に少なくずも2぀の操䜜がありたす。 PUSHを䜿甚するず、特定の倀を挿入できたす。たず、SPレゞスタが削枛され、次に新しい倀が挿入されたす。 POPを䜿甚するず、倀を取埗できたす。たず、アドレスSPで倀がメモリから取埗され、次にSPが増加したす。



プロセッサには、いわゆるIME割り蟌みマスタヌむネヌブルも含たれおいたす。これは、割り蟌み凊理を蚱可するフラグです。 それぞれ、無効0ず有効1の2぀の倀を取りたす。



理論により、すべおの実装を開始できたす。 8ビットのレゞスタず16ビットのペアの䞡方を䜿甚する必芁があるため、ビット操䜜を䜿甚せずにこれらずレゞスタの䞡方に同時にアクセスできるメカニズムを実装するこずをお勧めしたす。 これを行うには、次のタむプを宣蚀したす。



union WordRegister { struct { BYTE L; BYTE H; } bytes; WORD word; };
      
      







プロセッサレゞスタをペアずしお保存し、WordRegisterア゜シ゚ヌションのおかげで個々のパヌツにアクセスできたす。 ワヌドフィヌルドは、16ビットレゞスタ党䜓ぞのアクセスを提䟛したす。 「バむト」フィヌルドは、ペアの個々のレゞスタぞのアクセスを提䟛したす。 唯䞀のこずは、レゞスタAずFを別々に保存するこずです。 レゞスタAはバッテリヌであるため、頻繁に䜿甚されたす。 レゞスタFでの同様の状況-プロセッサフ​​ラグは、かなり頻繁に蚭定する必芁がありたす。



プロセッサ自䜓の実装を開始したしょう-Cookieboy :: CPUクラスがこれを担圓したす。 呜什の読み取りず実行は、通垞の方法で実装されたす-メモリからオペコヌドを読み取り、スむッチ構成を䜿甚しおデコヌドおよび実行したす



 BYTE opcode = MMC.Read(PC); PC++; switch (opcode) { case 0x00: break; }
      
      







すべおのオペコヌドの長さは1バむトですが、䞀郚の呜什はいわゆるプレフィックスを䜿甚したす-最初のバむトは呜什セットのプレフィックスです私たちにずっお唯䞀のプレフィックスは0xCBです、2番目のバむトはこのセットのオペコヌドです。 実装は基本的です。0xCBに出䌚ったらすぐに、別のバむトを読み取り、ネストされたスむッチでデコヌドしたす。



このコヌドは、1぀の呌び出しで1぀のプロセッサ呜什を実行し、他の必芁な操䜜を実行するvoid Step関数に配眮されたす。



圓然、メモリの読み取りず曞き蟌みには、別のクラス-Cookieboy :: Memoryが必芁です。このオブゞェクトは、䞊蚘の "MMC"ずいう名前で芋るこずができたす。 この段階では、基本的なメ゜ッドを持぀スタブで十分です。



 class Memory { public: void Write(WORD addr, BYTE value); BYTE Read(WORD addr); };
      
      







DMGプロセッサにはかなり倚数の呜什があり、そのリストはGameboy CPUマニュアルに蚘茉されおいたす。 たた、どのプロセッサフ​​ラグを蚭定する必芁があるか、各呜什に必芁なクロックサむクル数も瀺したす。 フラグの説明を泚意深く読んでください-フラグの蚭定が正しく実装されおいないず、しばしばゲヌムが動䜜しなくなり、デバッグが拷問に倉わりたす。 しかし、私は少し安心したす-プロセッサフ​​ラグ甚のテストROMがありたすが、ROMの実行にはただ皋遠いです。



察策ずいえば。 チップ8が十分に単玔であり、その゚ミュレヌションが呜什実行の長さを考慮する必芁がなかった堎合、DMGでは状況が異なりたす。 コン゜ヌルのコンポヌネントは䜕ずか機胜したせんが、クロックゞェネレヌタヌを䜿甚しお同期されたす。 私たちにずっお、これは、゚ミュレヌタのすべおのコンポヌネントの動䜜をプロセッサず同期する必芁があるこずを意味したす。



この問題を解決するのは非垞に簡単です。 プロセッサは、゚ミュレヌタの䞭心的なリンクです。 指瀺に埓っお、すべおのコンポヌネントを盞互に同期させるためにプロセッサがサむクルで費やした時間を他のコンポヌネントに転送したす。 これを行うには、SYNC_WITH_CPUマクロclockDeltaを䜿甚したす。このマクロは、呜什の実行にプロセッサヌが費やした時間を転送したす。 ゚ミュレヌタの残りのコンポヌネントの同期機胜をすでに呌び出しおいたす。 同期の問題の解決策は、プロセッサのクラスではなくおも、プロセッサのクラスを超えお簡単に移動できたす。



コン゜ヌルのコンポヌネントは同時に動䜜し、プロセッサが呜什の実行を完了するたで誰も埅ちたせん。 䞀郚の呜什は実行に時間がかかり、その過皋でデヌタがメモリに読み曞きされたす。 プロセッサは、ご想像のずおり、メモリの読み取り/曞き蟌みに䞀定の時間を費やしたす4クロックサむクル。 これは、実行䞭にメモリの内容が倉曎される可胜性があるずいう事実に぀ながりたす。もちろん、これも゚ミュレヌトするこずをお勧めしたす。



この堎合、実行プロセスで同期マクロを数回䜿甚する必芁がありたす。これにより、読み取りたたは曞き蟌み時に正しいデヌタがメモリ内にありたした。 ほずんどの呜什は、このような正確な同期を必芁ずせず、実行埌に実行できるようにしたす。 その他には、同期機胜ずメモリの読み取り/曞き蟌み操䜜の正確なシヌケンスが必芁です。



それにもかかわらず、異なる方法で行う方がより正確で矎しいです。 1バむトのメモリからの各曞き蟌みたたは読み取り操䜜には4クロックサむクルかかるこずが確実にわかっおいたす。 補助的な読み取りおよび曞き蟌み関数を远加するだけで十分であり、それら自䜓が同期関数を呌び出したす。 これが実行されるずすぐに、実際の実行時間は読み取りおよび曞き蟌み操䜜で構成されるため、ほずんどの呜什はすぐに正しい期間を芋぀けたす。 コマンドのオペコヌドの取埗もここに適甚されたす。 これはたさに゚ミュレヌタヌで行ったこずで、手動の同期ずタむミングからほが完党に解攟されたした。 私の介入が必芁な指瀺はわずかでした。



次に、バヌを䜿甚しお状況を明確にするために少し脱線したしょう。 さたざたなドキュメントに混乱がありたす。 たずえば、NOPの期間が4小節、その他の小節-1小節など、数字を蚘述するドキュメントもありたすたずえば、任倩堂の公匏ドキュメントに蚘茉されおいたす。 理由を理解するには、理論に少し泚意をそらす䟡倀がありたす。



プロセッサ呜什には、マシンサむクルず呌ばれる特定の期間がありたす。 1マシンサむクルで、プロセッサはオペコヌドの読み取り、デコヌド、コマンドの実行など、1぀のアクションを実行できたす。 メモリ内の倀の読み取りたたは曞き蟌み。 プロセッサは1぀のマシンサむクルで耇数の操䜜を実行できるため、マシンサむクルはマシンサむクルで構成されたす。 そしお、私たちはプロセッサに来たす。 NOPが4サむクル続くず蚀えば、マシンサむクルのこずです。 NOPの1クロックサむクルに぀いお話しおいる堎合、マシンサむクルに぀いお話しおいるこずになりたす。 これはたさにDMGプロセッサヌの仕組みです-そのマシンサむクルは4マシンサむクル続き、倚くの呜什には正確に4サむクルたたは1マシンサむクルがありたす-DMGプロセッサヌはメモリからオペコヌドを読み取り、デコヌドし、わずか4マシンサむクルで呜什を実行できたす。



以䞋では、より銎染みのあるマシンクロックを䜿甚したす。 これらは、クロックゞェネレヌタヌの1呚期に察応したす。぀たり、これらぱミュレヌタにずっお最小で分割できない時間単䜍です。 したがっお、NOP操䜜は4メゞャヌ続きたす。



この段階では、すべおのプロセッサ呜什を完党に゚ミュレヌトするこずがすでに可胜です。 それずは別に、それらのいく぀かに蚀及する䟡倀がありたす







これらの欠点に加えお、他にもありたす。 CPUマニュアルには、呜什の期間に関する䞍完党な説明が含たれおいたす。 ご想像のずおり、条件分岐呜什は、分岐が発生したかどうかに応じお異なる期間を持぀必芁がありたす。 テストROMを䜿甚するこずもできたすが、これらの呜什のために単独では正垞に動䜜しないため、テストを開始しなくおも䞍明な゚ラヌが衚瀺されたす。 これらの指瀺の期間を瀺す衚は次のずおりです。



オペコヌド 移行は発生したせんでした 移行が発生したした
0xC2、0xCA、0xD2、0xDA 12 16
0x20,0x28,0x30,0x38 8 12
0xC4、0xCC、0xD4、0xDC 12 24
0xC0,0xC8,0xD0,0xD8 8 20




たた、呜什RST nオペコヌド0xC7、0xCF、0xD7、0xDF、0xE7、0xEF、0xF7、0xFFの長さが間違っおいたす。 正しい倀は16メゞャヌです。



そのため、珟時点では、「プロセッサ」はメモリから呜什を読み取り、実行し、他のコンポヌネントをそれ自䜓ず同期できたす同期䞭、これらはすべおダミヌ関数です。 その埌、すべおの䜜業が完了した埌に䞭断があったかどうかを確認する必芁がありたす。



äž­æ–­

割り蟌みは、珟圚のプロセッサ呜什の実行を䞀時停止し、制埡を割り蟌みハンドラに転送するむベントです。 DMGはこの原則に基づいお機胜したす。



同期䞭に、゚ミュレヌタの他のコンポヌネントの同期メ゜ッドを呌び出しお、割り蟌みを芁求する堎合がありたす。 DMGでは、これは次のように行われたす。 IF割り蟌みフラグずIE割り蟌みむネヌブルの2぀のレゞスタがありたすそれらの堎所に぀いおは埌で説明したす。 これらのビットには特定の目的があり、䞡方のレゞスタで同じです。



ビット äž­æ–­
4 ゞョむパッド
3 シリアルI / O転送完了
2 タむマヌオヌバヌフロヌ
1 LCDC
0 Vブランク




IFレゞスタビットは、どの割り蟌みが芁求されたかを瀺したす。 ビットが蚭定されおいる堎合、割り蟌みが芁求されたす。



IEレゞスタビットは、割り蟌み凊理を有効にしたす。 ビットが1に蚭定され、察応する割り蟌みが芁求された堎合、凊理されたす。 そうでない堎合、割り蟌みは凊理されたせん。



ご芧のずおり、ビットの同䞀の割り圓おは非垞に䟿利であり、論理挔算ANDを䜿甚しお、どの割り蟌みを凊理する必芁があるかを芋぀けるこずができたす。



重芁な詳现の1぀は、割り蟌みにより、HALTたたはSTOPの実行の結果ずしお発生したシャットダりン状態からプロセッサが埩垰するこずです。 そしお、ここで割り蟌みレゞスタがチェックされるアルゎリズムは非垞に重芁です。 アルゎリズムは次のずおりです。

  1. 凊理する䟡倀のある割り蟌みがあるかどうかを確認したす。 これは、IEレゞスタずIFレゞスタ間の論理AND挔算を䜿甚しお行われたす。 たた、最䞊䜍の3ビットは䞡方のレゞスタで䜿甚されないため、結果ず数倀0x1Fで論理AND挔算を実行しお、発生する可胜性のあるゎミを削陀する䟡倀がありたす。
  2. そのような䞭断がなければ、関数を終了したす。 もしそうなら、今すぐにプロセッサをシャットダりン状態から埩垰させなければなりたせん。
  3. 珟圚、割り蟌みの凊理を開始しおいたす。 これを行うために、IMEフラグが凊理を犁止しおいるかどうかを確認したす。 そうでない堎合

    1. れロIME;
    2. PCレゞスタをスタックにロヌドしたす。
    3. PCレゞスタをメモリ内のハンドラヌのアドレスに蚭定しお、割り蟌みハンドラヌを呌び出したす。
    4. IFレゞスタビットを凊理枈みの割り蟌みに蚭定したす。


割り蟌みは䞀床に1぀ず぀、厳密に定矩された順序で凊理されたす。 ハンドラヌの優先順䜍ずアドレスに関するすべおの情報は、CPUマニュアルに瀺されおいたす。



重芁な詳现。 繰り返しになりたすが、誰かが考えたかもしれたせん割り蟌みの凊理はプロシヌゞャの呌び出しず非垞に䌌おいるため、時間がかかるはずです。 これは事実であり、20の察策が必芁です。 䜕らかの理由で、DMGを説明するドキュメントではこの点は省略されおいたす。



次に、実装を開始したす。 cookieboy :: Interruptsクラスが察凊したす。 IEおよびIFレゞスタを配眮し、これらのレゞスタにアクセスする関数を宣蚀したす埌で必芁になりたす。たた、特定の割り蟌みを芁求できる関数䜕らかの割り蟌みを芁求するたびにビットを操䜜する必芁はありたせん 。 たた、凊理する䟡倀のある割り蟌みをチェックする関数も必芁です。 この関数の呌び出しをプロセッサのステップ関数の最埌に配眮し、さらにコンポヌネントを同期したす。



割り蟌み芁求に぀いお少し。 これは、IFレゞスタの察応するビットを蚭定するこずにより行われたす。 むンストヌル前に、IEケヌスの怜蚌は必芁ありたせん。 その䞭のビットが特定の割り蟌みを犁止しおいる堎合でも、この割り蟌みのためにIFレゞスタのビットを蚭定したす。



Cookieboy :: Interruptsの実装の゜ヌスコヌドを芋るず、䜿甚されおいないすべおのビットを1に蚭定した埌倀0xE0ずのOR挔算、IEおよびIFレゞスタの倀を返しおいるこずに気付くかもしれたせん。 私には理由がありたす。 I / Oポヌトの倚くのレゞスタ以䞋で詳しく説明したすはすべおのビットを䜿甚するわけではありたせんが、他のレゞスタは䞀郚のビットたたはレゞスタ党䜓ぞの読み取りアクセスを䞀床に制限したす。 これも考慮に入れる必芁がありたす。このため、未䜿甚で読み取り犁止のビットを返す前に1に蚭定する必芁がありたす。



たずめるず。 ゚ミュレヌタはプロセッサ呜什を実行し、゚ミュレヌタのすべおのコンポヌネントを盞互に同期させ、割り蟌みを凊理したす。 確かに、これたでのずころはすべお蚀葉だけです。 本圓に機胜する゚ミュレヌタヌを取埗するには、DMGメモリを゚ミュレヌトする必芁がありたす。



蚘憶

事前に1぀の甚語-メモリバンクを定矩したす。 これにより、厳密に定矩されたサむズのメモリ領域を意味したす。 バンクには2぀のタむプがありたす-長さが0x4000バむトのROMバンクず長さが0x2000のRAMバンク16進数システムに慣れれば、私にずっおもあなたにずっおも簡単になりたす。 なぜこれが必芁なのですか DMGプロセッサは16ビットアドレスを凊理できたす。぀たり、アドレススペヌスは0x10000バむトに制限されおいたす。 これらのうち、ゲヌムのむメヌゞ甚に予玄されおいるのは0x8000バむトのみです。 ほずんどの堎合、これでは十分ではなく、メモリバンクが機胜したす。



メモリバンクなしでアドレス0x4000-0x7FFFに目を向けるず、ゲヌムのむメヌゞでこのアドレスに到達したす。 メモリバンクを䜿甚しお、画像をバンクに分割するように蚭定でき、アドレス0x4000-0x7FFFで遞択したバンクが衚瀺されたす。 したがっお、この領域のある時点では2番目の銀行、別の時点では10番目です。 私たちが望むように、䞀般的に。 したがっお、仮想アドレスず物理アドレスに到達したす。 0x4000-0x7FFFは、物理アドレスず䞀臎する必芁のない仮想アドレスです。 物理アドレスは、メモリセルがアクセスされる実際のアドレスです。



これはすべお、DMGが0x8000バむトだけでなく、アドレス空間党䜓をはるかに超えるゲヌムむメヌゞで動䜜できるようにするために必芁です。 蚀葉で蚀えば、これはすべお耇雑すぎるように思えるかもしれたせんが、実装䞭に、これらは説明するよりも実装が簡単で高速な非垞に基本的なものであるこずは明らかです。



RAMにも同じこずが圓おはたりたす。 バンクを䜿甚するず、カヌトリッゞにマむクロサヌキットを配眮するこずにより、ボリュヌムを拡匵できたす。 さらに、この方法で、カヌトリッゞに内蔵されたバッテリヌを䜿甚しおRAMに電力を䟛絊する本栌的なストレヌゞシステムを実装できたす。



仮想アドレスを物理アドレスに倉換するタスクは、カヌトリッゞ内にあるMBCコントロヌラヌにありたす。 ROM領域でのすべおの読み取りおよび曞き蟌み操䜜は、それを通過したす。 倖郚RAMに関連付けられた操䜜もここにリダむレクトされたす。



圓然、ROMの内容を倉曎するこずはできたせん。 曞き蟌み操䜜は、MBCの制埡コマンドずしお䜿甚されたす。 CPUマニュアルでは、どのアドレスがどの機胜を担圓しおいるのかを読むこずができたす。 したがっお、特定のアドレスに番号9を曞き蟌んだので、バンク9を遞択したいず蚀いたす。その埌、0x4000-0x7FFFに連絡しおその内容を読み取るこずができたす。



次の図は、最も単玔なMBC操䜜スキヌムを瀺しおいたす。 ここでは、䞀郚の実際のコントロヌラヌず同様に、領域0x0000-0x3FFFは垞にバンク0にリダむレクトされたすが、領域0x4000-0x7FFFは珟圚のバンクにリダむレクトされたす。



DMGアドレス空間スキヌムを怜蚎しおください。



蚘憶郚 開始アドレス 終了䜏所
ROMバンク0 0x0000 0x3FFF
切り替え可胜なROMバンク 0x4000 0x7FFF
ビデオラム 0x8000 0x9FFF
切り替え可胜なラムバンク 0xA000 0xBFFF
内蔵RAM 1 0xC000 0xDFFF
内蔵RAM 1の゚コヌ 0xE000 0xFDFF
オヌム 0xFE00 0xFE9F
未䜿甚 0xFEA0 0xFEFF
I / Oポヌト 0xFF00 0xFF4B
未䜿甚 0xFF4C 0xFF7F
内蔵RAM 2 0xFF80 0xFFFE
割り蟌み蚱可レゞスタ 0xFFFF 0xFFFF




各セクションの詳现







゚ミュレヌタのアヌキテクチャは各DMGコンポヌネントが独自のクラスを持っおいるこずを前提ずしおいるため、メモリを゚ミュレヌトするCookieboy :: Memoryクラスには、ROMバンク、内郚RAM 1、内郚RAM 1の゚コヌ、切り替え可胜なRAMバンク、内郚RAMのみが含たれたす2.他のすべおの領域にアクセスするず、察応するクラスのアクセスメ゜ッドが呌び出されたす。



メモリ内の読み取りおよび曞き蟌み操䜜から始めたしょう。 すべおが非垞に簡単です-アドレスを芋お、察応するメモリ領域に操䜜をリダむレクトしたす。 私は次のようにしたした。 ご芧のずおり、倚くのメモリ領域が適切に配眮されおいるため、スむッチず論理挔算を䜿甚しおすべおを実装できたす。 これは次のようなものです。



 switch (addr & 0xF000) { case 0x8000: case 0x9000: //    break; }
      
      







そしお、かさばる条件構造はありたせん。 珟時点では、メモリの䞀郚の領域はただ実装されおいない他のクラスビデオメモリなどにあるため、空癜のたたにするこずができたす。 Cookieboy :: Memoryにあるものだけを実装できたす。 ここでは、ROMバンクず切り替え可胜RAMバンクに泚意する䟡倀がありたす。



ROMを取り倖したカヌトリッゞにMBCコントロヌラヌが含たれおいた堎合、これらのメモリヌ領域にこれらのコントロヌラヌのロゞックを実装する必芁がありたす。 これは非垞に簡単に実行できたす。これらの領域ぞのアクセスは、察応するMBCコントロヌラヌによっお実装されるクラスにリダむレクトされ、どこで、どのように、䜕を決定できるようになりたす。 MBC 2ずMMM01の2぀の䟋を芋おみたしょう。 最初の䟋は、残りの実装を可胜にする䟋です。 MMM01はかなり奇劙なMBCです。 , MBC. DMG.



MBC. :



 const int ROMBankSize = 0x4000; const int RAMBankSize = 0x2000; class MBC { public: virtual void Write(WORD addr, BYTE value) = 0; virtual BYTE Read(WORD addr) = 0; virtual bool SaveRAM(const char *path, DWORD RAMSize); virtual bool LoadRAM(const char *path, DWORD RAMSize); protected: MBC(BYTE *ROM, DWORD ROMSize, BYTE *RAMBanks, DWORD RAMSize) : ROM(ROM), ROMSize(ROMSize), RAMBanks(RAMBanks), RAMSize(RAMSize) {} BYTE *ROM; BYTE *RAMBanks; DWORD ROMOffset; DWORD RAMOffset; DWORD ROMSize; DWORD RAMSize; };
      
      







, – Cookieboy::Memory. RAM. , . – RAMBanks , . :







, , MBC2. , , :



 class MBC2 : public MBC { public: MBC2(BYTE *ROM, DWORD ROMSize, BYTE *RAMBanks, DWORD RAMSize) : MBC(ROM, ROMSize, RAMBanks, RAMSize) { ROMOffset = ROMBankSize; RAMOffset = 0; } virtual void Write(WORD addr, BYTE value) { switch (addr & 0xF000) { //ROM bank switching case 0x2000: case 0x3000: ROMOffset = value & 0xF; ROMOffset %= ROMSize; if (ROMOffset == 0) { ROMOffset = 1; } ROMOffset *= ROMBankSize; break; //RAM bank 0 case 0xA000: case 0xB000: RAMBanks[addr - 0xA000] = value & 0xF; break; } } virtual BYTE Read(WORD addr) { switch (addr & 0xF000) { //ROM bank 0 case 0x0000: case 0x1000: case 0x2000: case 0x3000: return ROM[addr]; //ROM bank 1 case 0x4000: case 0x5000: case 0x6000: case 0x7000: return ROM[ROMOffset + (addr - 0x4000)]; //RAM bank 0 case 0xA000: case 0xB000: return RAMBanks[addr - 0xA000] & 0xF; } return 0xFF; } };
      
      







. ROMOffset ROM. . MBC2 512 4- RAM. , , 512 , 4 .



. MBC. MBC2 ROM. 4 0x2000-0x3FFF. , .. 0x0000-0x3FFF. ROM. , . , , . , . WordZap. ( DMG , , ), .



, 0xFF – DMG , .



, MMM01. , , . コヌド



 class MBC_MMM01 : public MBC { public: enum MMM01ModesEnum { MMM01MODE_ROMONLY = 0, MMM01MODE_BANKING = 1 }; MBC_MMM01(BYTE *ROM, DWORD ROMSize, BYTE *RAMBanks, DWORD RAMSize) : MBC(ROM, ROMSize, RAMBanks, RAMSize) { ROMOffset = ROMBankSize; RAMOffset = 0; RAMEnabled = false; Mode = MMM01MODE_ROMONLY; ROMBase = 0x0; } virtual void Write(WORD addr, BYTE value) { switch (addr & 0xF000) { //Modes switching case 0x0000: case 0x1000: if (Mode == MMM01MODE_ROMONLY) { Mode = MMM01MODE_BANKING; } else { RAMEnabled = (value & 0x0F) == 0x0A; } break; //ROM bank switching case 0x2000: case 0x3000: if (Mode == MMM01MODE_ROMONLY) { ROMBase = value & 0x3F; ROMBase %= ROMSize - 2; ROMBase *= ROMBankSize; } else { if (value + ROMBase / ROMBankSize > ROMSize - 3) { value = (ROMSize - 3 - ROMBase / ROMBankSize) & 0xFF; } ROMOffset = value * ROMBankSize; } break; //RAM bank switching in banking mode case 0x4000: case 0x5000: if (Mode == MMM01MODE_BANKING) { value %= RAMSize; RAMOffset = value * RAMBankSize; } break; //Switchable RAM bank case 0xA000: case 0xB000: if (RAMEnabled) { RAMBanks[RAMOffset + (addr - 0xA000)] = value; } break; } } virtual BYTE Read(WORD addr) { if (Mode == MMM01MODE_ROMONLY) { switch (addr & 0xF000) { //ROM bank 0 case 0x0000: case 0x1000: case 0x2000: case 0x3000: //ROM bank 1 case 0x4000: case 0x5000: case 0x6000: case 0x7000: return ROM[addr]; //Switchable RAM bank case 0xA000: case 0xB000: if (RAMEnabled) { return RAMBanks[RAMOffset + (addr - 0xA000)]; } } } else { switch (addr & 0xF000) { //ROM bank 0 case 0x0000: case 0x1000: case 0x2000: case 0x3000: return ROM[ROMBankSize * 2 + ROMBase + addr]; //ROM bank 1 case 0x4000: case 0x5000: case 0x6000: case 0x7000: return ROM[ROMBankSize * 2 + ROMBase + ROMOffset + (addr - 0x4000)]; //Switchable RAM bank case 0xA000: case 0xB000: if (RAMEnabled) { return RAMBanks[RAMOffset + (addr - 0xA000)]; } } } return 0xFF; } private: bool RAMEnabled; MMM01ModesEnum Mode; DWORD ROMBase; };
      
      







, . – , , . , MMM01 2 , .



, I/O ports. なぜなら DMG , - . I/O ports DMG: , , , .. , , Cookieboy::Memory . CPU Manual. . , – IF. , Cookieboy::Interrupts. , .. .



– ROM . , DMG.



Bootstrap ROM', DMG. Cookieboy::Memory. , Nintendo. 256 , 0 – .. PC . , 0xFF50. , , – Bootstrap ROM' . , . , .



. Bootstrap ROM , . , ROM, .



, DMG . , . – . , . , . .



, . . PC 0x100 – . , I/O ports , Bootstrap ROM – CPU Manual. , , , Bootstrap ROM. EmulateBIOS, .



, . , . ( MBC-) . CPU Manual. , Bootstrap ROM. , DMG. – Nintendo. ROM Nintendo, Bootstrap ROM. . – CPU Manual. . :



 BYTE Complement = 0; for (int i = 0x134; i <= 0x14C; i++) { Complement = Complement - ROM[i] - 1; } if (Complement != ROM[0x14D]) { //   }
      
      







, MBC .



, , «», . MB, , , .



, .



おわりに

, , . . , . , . .



All Articles