Skip to content →

more todo’s – mmu, mbc and controls

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.

Dr. Mario – demo fixed by restricting memory write access

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;
	}
}

Comments

  1. LilaQ LilaQ

    You should complete this section, dude!

Leave a Reply

Your email address will not be published. Required fields are marked *