cpuemulationgameboy

Gameboy emulation - Clarification need on CD instruction


I'm currently in the process of writing a Gameboy emulator, and I've noticed something that seems strange to me.

My emulator is hitting a jump instruction 0xCD, for example CD B6 FF, but my understanding was that a jump should only be jumping to an address within cartridge ROM (0x7FFF maximum), because I'm assuming the CPU can only execute instructions from ROM, not RAM. The ROM in question is Dr. Mario, which I'd expect to only be carrying out valid operations. 0xFFB6 is in high RAM, which seems odd to me.

Am I correct in my thinking? If I am, presumably that means my program counter is somehow ending up at the wrong address and that the CB is actually part of another instruction's data, and not an instruction itself?

I'd be grateful for some clarification, thanks.

For reference, I've been using Gameboy Opcodes and CPU docs to implement the instructions. I know they contain a few errors, and I think I've accounted for them (for example, 0xE2 being listed as a two-byte instruction, when it's only one)


Solution

  • Just checked Dr. Mario 1.1, it copies the VBlank int routine at hFFB6 at startup, then when VBlank happens, the routine at 0:01A6 is called, which calls the OAM DMA transfer routine.

    During OAM DMA transfer, the CPU can only access HRAM, so writing a short routine in HRAM that will wait for the transfer to be completed is required. The OAM DMA transfer takes 160 µs, so you usually make a loop that will wait this amount of time after specifying the OAM transfer source.

    This is the part of the initialization routine run at startup that copies the DMA transfer routine to HRAM:

    ...
    ROM0:027E 0E B6            ld   c,B6             ;destination hFFB6
    ROM0:0280 06 0A            ld   b,0A             ;length 0xA
    ROM0:0282 21 86 23         ld   hl,2386          ;source 0:2386
    ROM0:0285 2A               ldi  a,(hl)           ;copy OAM DMA transfer routine from source
    ROM0:0286 E2               ld   (ff00+c),a       ;paste to destination
    ROM0:0287 0C               inc  c                ;destination++
    ROM0:0288 05               dec  b                ;length--
    ROM0:0289 20 FA            jr   nz,0285          ;loop until DMA transfer routine is copied
    ...
    

    When VBlank happens, it jumps to the routine at 0:01A6:

    ROM0:0040 C3 A6 01         jp   01A6
    

    Which contains a call to our OAM DMA transfer routine, waiting for DMA to be completed:

    ROM0:01A6 F5               push af
    ROM0:01A7 C5               push bc
    ROM0:01A8 D5               push de
    ROM0:01A9 E5               push hl
    ROM0:01AA F0 B1            ld   a,(ff00+B1)
    ROM0:01AC A7               and  a
    ROM0:01AD 28 0B            jr   z,01BA
    ROM0:01AF FA F1 C4         ld   a,(C4F1)
    ROM0:01B2 A7               and  a
    ROM0:01B3 28 05            jr   z,01BA
    ROM0:01B5 F0 EF            ld   a,(ff00+EF)
    ROM0:01B7 A7               and  a
    ROM0:01B8 20 09            jr   nz,01C3
    ROM0:01BA F0 E1            ld   a,(ff00+E1)
    ROM0:01BC FE 03            cp   a,03
    ROM0:01BE 28 03            jr   z,01C3
    ROM0:01C0 CD B6 FF         call FFB6             ;OAM DMA transfer routine is in HRAM
    ...
    

    OAM DMA transfer routine:

    HRAM:FFB6 3E C0            ld   a,C0
    HRAM:FFB8 E0 46            ld   (ff00+46),a      ;source is wC000
    HRAM:FFBA 3E 28            ld   a,28             ;loop start
    HRAM:FFBC 3D               dec  a
    HRAM:FFBD 20 FD            jr   nz,FFBC          ;wait for the OAM DMA to be completed
    HRAM:FFBF C9               ret                   ;ret to 0:01C3