Skip to content →

about mappers – mmc1 and mmc3

To continue our quest, we want to implement some of the most used (or at least used for the most popular games) mappers of the NES. For now, we will choose MMC1 and MMC3, a rather simple, and a rather complex mapper.

mmc1

The MMC1 mapper is a mapper, that offers banking for PRG and CHR ROM. This means, the actual memory behind the (mapped) addresses can be changed from the mapper, thus giving us possibly a lot more space on the cartridge.

CPU $6000-$7FFF: 8 KB PRG RAM bank, (optional)
CPU $8000-$BFFF: 16 KB PRG ROM bank, either switchable or fixed to the first bank
CPU $C000-$FFFF: 16 KB PRG ROM bank, either fixed to the last bank or switchable
PPU $0000-$0FFF: 4 KB switchable CHR bank
PPU $1000-$1FFF: 4 KB switchable CHR bank 

We get the amount of banks of the PRG banks (16kb) and of the CHR banks (8kb) from the iNES header, more precisely byte 4 and 5 of the header.

MMC1 has a quirk, where it will take 5 writes to collect all the bits for the actual write that sets a bank. These 5 writes all are made to the Load register (0x8000-0xffff) that consists of a shift register. Each write to the load register shifts the rightmost bit onto said shift register, and on the 5th write, the whole shift register is evaluated.

if (adr >= 0x8000 && adr <= 0xffff) {
		//	resetting shift register
		if (val & 0b10000000) {
			shift_register = 0b10000;
			sr_write_counter = 0;
			return;
		}
		//	shifting 4 times, writing on the 5th
		else {
			sr_write_counter++;
			shift_register = ((shift_register >> 1) | ((val & 1) << 4)) & 0b11111;
...

There are 4 address areas which control the following settings:

Control (0x8000-0x9fff)

4bit0 
----- 
CPPMM 
||||| 
|||++- Mirroring (0: one-screen, lower bank; 1: one-screen, upper bank; 
|||               2: vertical; 3: horizontal) 
|++--- PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number; 
|                         2: fix first bank at $8000 and switch 16 KB bank at $C000; 
|                         3: fix last bank at $C000 and switch 16 KB bank at $8000) 
+----- CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks) 

CHR bank 0 (0xa000-0xbfff)

4bit0 
----- 
CCCCC 
||||| 
+++++- Select 4 KB or 8 KB CHR bank at PPU $0000 (low bit ignored in 8 KB mode) 

CHR bank 1 (0xc000-0xdfff)

4bit0 
----- 
CCCCC 
||||| 
+++++- Select 4 KB CHR bank at PPU $1000 (ignored in 8 KB mode) 

PRG bank (0xe000-0xffff)

4bit0 
----- 
RPPPP 
||||| 
|++++- Select 16 KB PRG ROM bank (low bit ignored in 32 KB mode) 
+----- PRG RAM chip enable (0: enabled; 1: disabled; ignored on MMC1A) 

So, we are able to decide, which memory addresses have a fixed bank, and which are switchable. And the banks that are switchable, we can select through the bank selector. So, when we read from memory, we can tell our mapper to select the appropriate PRG and CHR banks on-the-fly.

virtual unsigned char read(uint16_t adr) {
	if (PRGsize == 0x4000) {
		//	First PRG area
		if (adr >= 0x8000 && adr <= 0xbfff) {
			//	this area is fixed to the first PRG bank
			if (PRGswitchAddr == 0xc000) {
				return m[(adr % 0x8000) + 0x10];
			}
			//	this area is switchable
			else {
				return m[(adr % 0x8000) + PRGid * PRGsize + 0x10];
			}
		}
		//	Second PRG area
		else if (adr >= 0xc000 & adr <= 0xffff) {
			//	this area is fixed to the last PRG bank
			if (PRGswitchAddr == 0x8000) {
				return m[(adr % 0xc000) + 0x10 + (romPRG16ks - 1) * PRGsize];
			}
			//	this area is switchable
			else {
				return m[(adr % 0x8000) + PRGid * PRGsize + 0x10];
			}
		}
		else {
			return memory[adr];
		}
	}
	//	32k PRG banks
	else if (PRGsize == 0x8000) {
		if (adr >= 0x8000 && adr <= 0xffff) {
			return m[(adr % 0x8000) + 0x10 + PRGid * PRGsize];
		}
		else {
			return memory[adr];
		}
	}
}
Note: 
The '0x10' in the reads, is the skipped iNES header.
The 'romPRG16ks' is the amount of banks we get from the iNES header.

This is the whole concept of the MMC1 mapper. After we successfully implemented this mapper, we are able to boot “Zelda“.

mmc3

This mapper allows even more PRG and CHR banks to be selected, but doesn’t have the 5-write-quirk of MMC1. It does, though, have a scanline counter, which can trigger an IRQ on decrementing.

First of all, the addressable banks:

CPU $6000-$7FFF: 8 KB PRG RAM bank (optional)
CPU $8000-$9FFF (or $C000-$DFFF): 8 KB switchable PRG ROM bank
CPU $A000-$BFFF: 8 KB switchable PRG ROM bank
CPU $C000-$DFFF (or $8000-$9FFF): 8 KB PRG ROM bank, fixed to the second-last bank
CPU $E000-$FFFF: 8 KB PRG ROM bank, fixed to the last bank
PPU $0000-$07FF (or $1000-$17FF): 2 KB switchable CHR bank
PPU $0800-$0FFF (or $1800-$1FFF): 2 KB switchable CHR bank
PPU $1000-$13FF (or $0000-$03FF): 1 KB switchable CHR bank
PPU $1400-$17FF (or $0400-$07FF): 1 KB switchable CHR bank
PPU $1800-$1BFF (or $0800-$0BFF): 1 KB switchable CHR bank
PPU $1C00-$1FFF (or $0C00-$0FFF): 1 KB switchable CHR bank 

To select the banks, we can write to the Bank Register (0x8000-0x9fff). The writes are differentiated in even and odd, e.g. even 0x8000, 0x8202, etc, odd 0x8001, 0x8333 etc.

Even writes

7  bit  0 
---- ---- 
CPMx xRRR 
|||   ||| 
|||   +++- Specify which bank register to update on next write to Bank Data register 
|||        0: Select 2 KB CHR bank at PPU $0000-$07FF (or $1000-$17FF); 
|||        1: Select 2 KB CHR bank at PPU $0800-$0FFF (or $1800-$1FFF); 
|||        2: Select 1 KB CHR bank at PPU $1000-$13FF (or $0000-$03FF); 
|||        3: Select 1 KB CHR bank at PPU $1400-$17FF (or $0400-$07FF); 
|||        4: Select 1 KB CHR bank at PPU $1800-$1BFF (or $0800-$0BFF); 
|||        5: Select 1 KB CHR bank at PPU $1C00-$1FFF (or $0C00-$0FFF); 
|||        6: Select 8 KB PRG ROM bank at $8000-$9FFF (or $C000-$DFFF); 
|||        7: Select 8 KB PRG ROM bank at $A000-$BFFF 
||+------- Nothing on the MMC3, see MMC6 
|+-------- PRG ROM bank mode (0: $8000-$9FFF swappable, 
|                                $C000-$DFFF fixed to second-last bank; 
|                             1: $C000-$DFFF swappable, 
|                                $8000-$9FFF fixed to second-last bank) 
+--------- CHR A12 inversion (0: two 2 KB banks at $0000-$0FFF,                                  four 1 KB banks at $1000-$1FFF;
                              1: two 2 KB banks at $1000-$1FFF,                                  four 1 KB banks at $0000-$0FFF) 

So, with even writes we will dictate which bank will be changed, which PRG mode will be used, and if CHR A12 inversion is used or not.

The selection of these banks isn’t straightforward though, it’s depending on PRG mode and CHR A12 inversion, as can be seen in the following table.

CHR A12 inversion unused used
PPU Bank Value of MMC3 register
$0000-$03FF R0 R2
$0400-$07FF R3
$0800-$0BFF R1 R4
$0C00-$0FFF R5
$1000-$13FF R2 R0
$1400-$17FF R3
$1800-$1BFF R4 R1
$1C00-$1FFF R5
PRG bank mode 0 1
CPU Bank Value of MMC3 register
$8000-$9FFF R6 (-2)
$A000-$BFFF R7 R7
$C000-$DFFF (-2) R6
$E000-$FFFF (-1) (-1)
(-1) : the last bank
(-2) : the second last bank 

Odd writes

These writes, simply enough, are the bank number to the bank position that was select with the even write before.

irq / scanline counter

The scanline counter is used, for example, by Super Mario Bros. 3, to identify the position of the UI at the bottom. The UI will look garbled, if this function is not implemented, as we will see later on.

IRQ latch (0xc000-0xdfff, even)

This register holds the value, that will be loaded into the actual scanline counter when a reload is forced, or when the counter reached zero.

IRQ reload (0xc000-0xdfff, odd)

This register resets the actual scanline counter, and will push the IRQ latch value to the counter in the next scanline.

IRQ disable (0xe000-0xffff, even)

Writing to this register will disable the IRQ generation and also remove any pending IRQs.

IRQ enable (0xe000-$ffff, odd)

Writing to this address area will enable IRQ generation again.

In code, this will look something like this:

else if (adr >= 0xc000 && adr <= 0xdfff) {
	//	set reload value | even
	if ((adr % 2) == 0) {
		irq_latch = val;
	}
	//	actually set reload value to scanline counter | odd
	else {
		irq_reload_pending = true;
		irq_counter = 0;
	}
}
else if (adr >= 0xe000 && adr <= 0xffff) {
	//	IRQ disable | even
	if ((adr % 2) == 0) {
		irq_enabled = false;
		irq_assert = false;
	}
	//	IRQ enable | odd
	else {
		irq_enabled = true;
	}
}

scanline counter

The actual ticking of the scanline counter happens on every visible scanline, plus the prep-scanline, on cycle 260 of each line. The actual evaluation of the scanline counter is shown in code here, as this can often leads to problems and bugs, which may be hard to debug, due to conflicting documentation.

Note that, the nextScanline() function is only called, when either BG or Sprites are to be drawn (dictated from PPUMASK).

if (ppuScanline >= 0 && ppuScanline <= 239) {
	if (ppuCycles == 230) {
		renderScanline(ppuScanline);
	}
	else if (ppuCycles == 260 && (PPU_MASK.show_bg || PPU_MASK.show_sprites)) {
		nextScanline();
	}
}
virtual void nextScanline() {
	if (irq_counter == 0 || irq_reload_pending) {
		irq_counter = irq_latch;
		irq_reload_pending = false;
	}
	else {
		irq_counter--;
	}

	if (irq_counter == 0 && irq_enabled) {
		irq_assert = true;
	}
}
virtual bool IRQ() {
	bool isIRQ = irq_assert;
	irq_assert = false;

	return isIRQ;
}

This way, we are able to check for pending IRQs in our CPU. If the CPU detects a pending IRQ, it will (almost) do the same procedure as on an NMI. It will push the PC to the stack, push P / PSW to the stack, and jump to the Interrupt Vector, that is located at 0xfffe / 0xffff, and finally enable the interruptDisable flag of the PPUSTATUS.

If the IRQ and the scanline counter are implemented correctly, we will be able to boot Super Mario Bros. 3.

SMB3 without proper IRQ / scanline counter implementation
SMB3 with proper IRQ / scanline counter

mapper 37 (mmc3 subtype)

So, there exists a subtype (there are actually multiple subtypes of MMC3) of the MMC3 mapper, I have a personal interest in, because it was my first cartridge for my NES, that european readers might recognize. It’s the multi-game cartridge, filled with Super Mario Bros., Tetris, and World Cup.

Bildergebnis für smb tetris world cup

This mapper, is a complete MMC3 mapper, but it has an additional function, which is to select the outer bank. This makes it easy, to restrict the games to their own “memory cells”.

Outer Bank Select (0x6000-0x7fff)

7  bit  0
xxxx xQBB
      │││
      │└┴── If 3, forces PRG A16 high regardless of Q bit; otherwise
      └──── the MMC3's PRG A16 is ANDed with this bit before going to PRG ROM.
             Additionally connected to A17 on both PRG and CHR ROM.

This results in memory being addressable like this:

Value written PRG window 128kB CHR window
0,1,2 $00000-$0FFFF (64kB) $00000-$1FFFF
3 $10000-$1FFFF (64kB) $00000-$1FFFF
4,5,6 $20000-$3FFFF (128kB) $20000-$3FFFF
7 $30000-$3FFFF (64kB) $20000-$3FFFF

So, if we add this little feature, we will also be able, to boot the multi-game cartridge, which, indeed, is the only cartridge to ever use this mapper! 😉

Comments

Leave a Reply