Time to get more details added to our project, and get some games really working.
mmu
The Memory Management Unit (MMU) is the interface between our CPU / PPU / SPU and the memory itself. With this interface, we can prevent writes, trigger functions on accessing certain memory addresses, and even switch the data that resides at a memory range.
So the MMU ultimately is the only unit, directly accessing the memory array, all other calls to memory are supposed to run through the MMU functions.
// initial implementation void writeToMem(uint16_t adr, unsigned char val) { memory[adr] = val; } unsigned char& readFromMem(uint16_t adr) { return memory[adr]; }
If you remember, Dr. Mario was behaving oddly, not playing the demo correctly. This was because of illegal memory writes. This can be prevented through our new MMU functions.
// MBC0 if (romtype == 0x00) { // make ROM readonly if (adr >= 0x8000) memory[adr] = val; }
MBC0 shows it is a 32 kb cartridge, which doesn't use memory banking
With this simple fix, I was able to finally run the Dr. Mario demo successfully.

The MMU makes use of multiple special cases, depending on which address is read from / written to.
// [0xff26 - enable sound, all channels] if (adr == 0xff26) { if (val) memory[0xff26] = 0xff; else memory[0xff26] = 0x00; return; } // [0xff00] - joypad input if (adr == 0xff00) { memory[adr] = readInput(val); return; } // [0xff50] - lock bootrom else if (adr == 0xff50 && val == 1) { lockBootROM(); memory[adr] = val; } // [0xff46] - oam dma transfer else if (adr == 0xff46) { dmaOAMtransfer(); memory[adr] = val; } // [0xff11] - SC1 trigger else if (adr == 0xff14 && (val >> 7)) { memory[adr] = val; resetSC1length(readFromMem(0xff11) & 0x3f); return; } // [0xff21] - SC2 trigger else if (adr == 0xff19 && (val >> 7)) { memory[adr] = val; resetSC2length(readFromMem(0xff16) & 0x3f); return; } // [0xff31] - SC3 trigger else if (adr == 0xff1e && (val >> 7)) { memory[adr] = val; resetSC3length(readFromMem(0xff1b)); return; } // [0xff41] - SC4 trigger else if (adr == 0xff23 && (val >> 7)) { memory[adr] = val; resetSC4length(readFromMem(0xff20) & 0x3f); return; }
This is from the final version of the MMU, already including sound, DMA and controller related code.
mbc (memory banking)
The probably most important part inside the MMU is the ability of Memory Banking. Each cartridge can have some form of MBC (Memory Banking Controller), each dictating a certain number of ROM and RAM banks, that can be swapped on runtime.
This means, certain writes will cause the MBC to map another bank of RAM or ROM to the available memory addresses. Therefore, it is able to provide more memory, than usually would be accessible with the limit address length.
Most common MBCs MBC0 (no memory banking) MBC1 (max 2 MByte ROM and/or 32 KByte RAM) MBC2 (max 256 KByte ROM and 512x4 bits RAM) MBC3 (max 2 MByte ROM and/or 64 KByte RAM and Timer) MBC5 (max 8 MByte ROM and/or 128 KByte RAM)
To be able to load games like Super Mario Land etc., that make use of MBC’s we will have to implement each of the MBC’s specifications.
// Example for MBC1, MBC2 and no MBC // readFromMem() // MBC1 if (romtype == 0x01 && mbc1romNumber && (adr >= 0x4000 && adr < 0x8000)) { uint32_t target = (mbc1romNumber * 0x4000) + (adr - 0x4000); return rom[target]; } // MBC2 else if (romtype == 0x02) { // ROM banking if (mbc2romNumber && (adr >= 0x4000 && adr < 0x8000)) { uint32_t target = (mbc2romNumber * 0x4000) + (adr - 0x4000); return rom[target]; } else return memory[adr]; } else return memory[adr];
// Example for MBC1, MBC2 and no MBC // writeToMem() // MBC0 if (romtype == 0x00) { // make ROM readonly if (adr >= 0x8000) memory[adr] = val; } // MBC1 else if (romtype == 0x01) { // external RAM enable / disable if (adr < 0x2000) { mbc1ramEnabled = val > 0; } // choose ROM bank nr (lower 5 bits, 0-4) else if (adr < 0x4000) { mbc1romNumber = val & 0x1f; if (val == 0x00 || val == 0x20 || val == 0x40 || val == 0x60) mbc1romNumber = (val & 0x1f) + 1; } // choose RAM bank nr OR ROM bank top 2 bits (5-6) else if (adr < 0x6000) { // mode: ROM bank 2 bits if (mbc1romMode == 0) mbc1romNumber |= (val & 3) << 5; // mode: RAM bank selection else mbc1ramNumber = val & 3; } else if (adr < 0x8000) { mbc1romMode = val > 0; } else { memory[adr] = val; } } // MBC2 else if (romtype == 0x02) { // external RAM enable / disable if (adr < 0x2000) { mbc2ramEnabled = val > 0; } // choose ROM bank nr (lower 5 bits, 0-4) else if (adr < 0x4000) { mbc2romNumber = val & 0x1f; if (val == 0x00 || val == 0x20 || val == 0x40 || val == 0x60) mbc2romNumber = (val & 0x1f) + 1; } else { memory[adr] = val; } }
You should complete this section, dude!