1 Tejind

Nes Mmc3 Irq Assignments

Multi-memory controllers or memory management controllers[1] (MMC) are different kinds of special chips designed by various video game developers for use in Nintendo Entertainment System (NES) cartridges. These chips extend the capabilities of the original console and make it possible to create NES games with features the original console cannot offer alone.

These chips are also known as mappers.

List of MMC chips[edit]


Early NES MMCs are composed of 7400 series discrete logic chips.[1]:29 The UNROM, implemented with two such chips, divides the program space into two 16 KB banks. The MMC allows a program to switch one bank while keeping one bank always available.[2] Instead of a dedicated ROM chip to hold graphics data (called CHR by Nintendo), games using UNROM store graphics data on the program ROM and copy it to a RAM on the cartridge at run time.[1]:29


The MMC1 is Nintendo's first custom MMC integrated circuit to incorporate support for saved games and multi-directional scrolling configurations.[1]:p.30 The chip comes in at least five different versions: MMC1A, MMC1B1, MMC1B2, MMC1B3 and MMC1C. The differences between the different versions are slight, mostly owing to savegame memory protection behavior. The MMC1 chip allows for switching of different memory banks. Program ROM can be selected in 16KB or 32KB chunks, and character ROM can be selected in 4KB or 8KB chunks. An unusual feature of this memory controller is that its input is serial, rather than parallel, so 5 sequential writes (with bit shifting) are needed to send a command to the circuit.[3]


The MMC2 is only used in Mike Tyson's Punch-Out!! and the later rerelease which replaced Mike Tyson. A single 8KB bank of program ROM can be selected (with the remaining 24KB locked) and character ROM can be selected in two pairs of 4KB banks, which would be automatically switched when the video hardware attempts to load particular graphic tiles from memory, thus allowing a larger amount of graphics to be used on the screen without the need for the game itself to manually switch them.[4]


The MMC3 is Nintendo's most popular MMC chip. It comes in MMC3A, B, and C revisions. The MMC3 adds a scanline based IRQ counter to make split screen scrolling easier to perform (mainly to allow the playfield to scroll while the status bar remains motionless at the top or bottom of the screen), along with two selectable 8KB program ROM banks and two 2KB+four 1KB selectable character ROM banks.[5]


This chip is only used in three games, all of which were released only for the Famicom in Japan, and were developed by Intelligent Systems. Functionally, it is nearly identical to the MMC2, with the only difference being that the MMC4 switches program ROM in 16KB banks instead of 8KB banks and has support for a battery-backed SRAM to save game data.[6]


  • Manufacturer: Nintendo
  • Games used in: Castlevania III: Dracula's CurseNA/EU, Just Breed, Metal Slader Glory, Laser Invasion, Uchuu Keibitai SDF, Nobunaga's Ambition II, Nobunaga no Yabou - Sengoku Gunyuu Den, Bandit Kings of Ancient China, Romance of the Three Kingdoms II, Uncharted Waters, Genghis Khan II: Clan of the Gray Wolf, Gemfire, L'Empereur, Ishin no Arashi, Shin 4 Nin Uchi Mahjong - Yakuman Tengoku

The MMC5 is Nintendo's largest MMC. It was originally also the most expensive. Only Koei used this chip regularly. The chip has 1 KB of extra RAM, two extra square wave sound channels, one extra PCM sound channel, support for vertical split screen scrolling, improved graphics capabilities (making 16,384 different tiles available per screen rather than only 256, and allowing each individual 8x8-pixel background tile to have its own color assignment instead of being restricted to one color set per 2x2 tile group), highly configurable program ROM and character ROM bank switching, and a scanline-based IRQ counter.[7]


The MMC6 is similar to the MMC3, with an additional 1 KB of RAM which can be saved with battery backup.[8]

Famicom Disk System[edit]

  • Manufacturer: Nintendo
  • Games used in: Akumajo Dracula, Arumana no Kiseki, Ai Senshi Nicol, Big Challenge! Dogfight Spirit, Bio Miracle Bokutte Upa, CrackoutJP, Double DribbleJP, Dracula II, Falsion, Famicom Tantei Club Part II: Ushiro ni Tatsu Shōjo, Kid IcarusJP, KiKi KaiKai, Meikyuu Jiin Dababa, MetroidJP, Pro WrestlingJP, Relics - Ankoku Yousai, Risa no Yōsei Densetsu, Shin Onigashima, Super Mario Bros. 2JP, The Legend of Zelda (FDS release), The Mysterious Murasame Castle, Vs. Excitebike, Yūyūki, Zelda II: The Adventure of LinkJP, and more.

The Famicom Disk System's ASIC is an extended audio chip, which supports one channel of single-cycle (6-bit × 64 step) wavetable-lookup synthesis with a built in phase modulator (PM) for sound generation similar to frequency modulation synthesis.


The A*ROM MMC, named after the AMROM, ANROM, and AOROM cartridge boards that use it, was developed by Chris Stamper of Rare, and manufactured by Nintendo. It is found in games developed by Rare for Nintendo, Tradewest, GameTek, Acclaim, and Milton Bradley.[9] It uses 32 KB ROM switch and a CHR RAM. Unlike other chips, it uses one screen mirroring.[10]

Third-party chips[edit]

As Nintendo's NES licensing program allowed North America and Europe distribution to include only first-party hardware in its cartridges, these third party chips were supposed to be used only in Japan. However, some of them had been found in a few North American cartridges as well.


  • Manufacturer: Konami, Jaleco (SS8805)
  • Games used in: Exciting Boxing, Ganbare Goemon! Karakuri Douchuu, Jajamaru Ninpouchou, King Kong 2: Ikari no Megaton Punch, Moero!! Junior Basket: Two on Two, Tetsuwan Atom, and more


The VRC2 is a chip from Konami that allows program ROM to be switched in 8KB banks, and character ROM to be switched in 1KB banks. This MMC has two known revisions: VRC2a and VRC2b.[12]


Exclusively used in the Japanese version of Salamander.


  • Manufacturer: Konami
  • Games used in: Gradius IIJP, Crisis Force, Bio Miracle:Boku tte Upa, Ganbare Goemon Gaiden 2, WaiWai World, WaiWai World 2, Twinbee 3


The VRC6 (Virtual Rom Controller) is an advanced MMC chip from Konami, supporting bank switching for both program code and graphics as well as a CPU cycle–based IRQ counter, which can also act as a scanline counter. The chip also contains support for three extra sound channels (two square waves of eight duty cycles each, and one sawtooth wave). It is used in Akumajō Densetsu (the Japanese version of Castlevania III: Dracula's Curse), while the Western version uses the MMC5 from Nintendo. Since the Nintendo Entertainment System does not allow cartridges to add additional sound channels, the Famicom version's soundtrack was reworked to follow those specifications; thus, the soundtrack on the Western version is implemented by the five sound channels built into the stock NES.[14]


The VRC7 is a very advanced MMC chip from Konami, not only supporting bank switching and IRQ counting equivalent to the VRC6 but also containing a YM2413 derivative providing 6 channels of FM Synthesis audio. This advanced audio is used only in the Famicom game Lagrange Point; while the Japanese version of Tiny Toon Adventures 2 also used the VRC7, it does not make use of the extended audio.[15]

Namco 163[edit]

  • Manufacturer: Namco
  • Games used in: Erika to Satoru no Yume Bōken, Final Lap, King of Kings, Mappy Kids, Digital Devil Story: Megami Tensei II, Namco Classic II, Romance of the Three Kingdoms, Romance of the Three Kingdoms II, Yokai Dochuki, Rolling ThunderJP, Top Striker

The 163 has been only used in games exclusive to Japan. Its capabilities were a little better than Nintendo's MMC3. A variant contained extra sound hardware that plays 4-bit wave samples. It supports 1 to 8 extra sound channels, but audible aliasing appears when a sufficiently large number of channels are enabled.


The FME-7 is a memory mapping circuit developed by Sunsoft for use in NES and Famicom cartridges. It switches program ROM in 8KB banks and switches the character ROM 1KB banks. It also contains hardware to generate IRQ signals after a specified number of CPU clock cycles, thus achieving split-screen effects with minimal use of processing power. A special version of this MMC, labeled as "SUNSOFT 5B" rather than "FME-7", contains a version of the widely used Yamaha YM2149. This sound generation hardware is used on only one Famicom title: Gimmick!.


Unlicensed MMCs[edit]

Some individual (homebrew) and unlicensed developers have made custom MMCs for the NES, most of which simply expand the available memory.


228 is a simple bank switching MMC developed for use in the games Action 52 and Cheetahmen II. It does not have a nametable control bit. In the Action 52 multicart, it also contains a small 16-bit register area that contains the old menu selection when exiting a game.

See also[edit]


External links[edit]

Nintendo developed its MMC chip line for use in the NES's cartridges to offload processing and expand the system's capabilities.
A Tetris cartridge showing an MMC1B1 chip.
The MMC3 chip, soldered onto a PCB containing Super Mario Bros. 3 and others.
The VRC7 with a post-processor for the FM synthesis

(Thanks to thefox for pointing out an error in my example code, see note at the very bottom of the page)

So far, all I’ve been using is small NROM sized .nes files. I’m going to show how to set up a much larger .nes file using the MMC3 mapper. I don’t know every mapper, but I know the MMC3, so I’ll use that for my example. NROM was the first cartridge design, but they soon used up the 32k bytes of PRG ROM (code), and especially the 8k of CHR ROM (graphics). A mapper is a way to trick the NES into accessing more ROM, by swapping (mapping) banks in and out of the CPU memory.

I’m going to pretend that I’m designing for a real actual NES cartridge, and use the actual size of an actual MMC3 board. According to this website…


The choices we have, are…

Max. 64K PRG, 64K CHR
Max. 512K PRG, 64K CHR
Max. 512K PRG, VRAM
Max. 512K PRG, 256K CHR
Max. 128K PRG, 64K CHR, 8K CHR RAM

(and some others that I omitted). I’m going to use the smallest example, 64k and 64k. First, we have to set this up correctly in the header. The header is a few bytes of metadata that is used by emulators, so it knows which mapper we’re using, and how many banks, etc. Here’s how it should look for our 64/64 MMC3…

.byte $4e,$45,$53,$1a
.byte $04   ; = 4 x $4000 bytes of PRG ROM
.byte $08  ; = 8 x $2000 bytes of CHR ROM
.byte $40  ; = mapper # 4 = MMC3

Now, we need to set up the .cfg file for multiple banks…

#ROM Addresses:
#they are all at $8000, because I will be swapping them into that bank
PRG0: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG1: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG2: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG3: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG4: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG5: start = $a000, size = $2000, file = %O ,fill = yes, define = yes;
PRG6: start = $c000, size = $2000, file = %O ,fill = yes, define = yes;
PRG7: start = $e000, size = $1ffa, file = %O ,fill = yes, define = yes;

# Hardware Vectors at end of the ROM
VECTORS: start = $fffa, size = $6, file = %O, fill = yes;

Actually, what the $8000 does here, is every label inside that bank will be given an address indexing from the start of that bank + $8000. I actually only have code in the last bank, so I could put the start address anywhere, and it would work the same, but you will probably have code in some of these banks, and so you need to give the code the correct addresses for where it will actually be going when it’s swapped in.

And, defined these segments in the .cfg file…


HEADER:   load = HEADER,         type = ro;
CODE0:    load = PRG0,           type = ro,  define = yes;
CODE1:    load = PRG1,           type = ro,  define = yes;
CODE2:    load = PRG2,           type = ro,  define = yes;
CODE3:    load = PRG3,           type = ro,  define = yes;
CODE4:    load = PRG4,           type = ro,  define = yes;
CODE5:    load = PRG5,           type = ro,  define = yes;
CODE6:    load = PRG6,           type = ro,  define = yes;
STARTUP:  load = PRG7,           type = ro,  define = yes;
CODE:     load = PRG7,           type = ro,  define = yes;
VECTORS:  load = VECTORS,        type = ro;
CHARS:    load = CHR,            type = rw;

BSS:      load = RAM,            type = bss, define = yes;
HEAP:     load = RAM,            type = bss, optional = yes;
ZEROPAGE: load = ZP,             type = zp;
#OAM:   load = OAM1,    type = bss, define = yes;

(the OAM segment is not used in this example).

Ok, now I’m going to write something in each bank, so we can see how it loads into the ROM file. I’m writting the words “Bank0”, “Bank1”, etc. in every bank. And, I’m going to load those words onto the screen, so we can see the switch visually. (I set it to be triggered by pressing ‘Start’).

I had to write a bunch of PRAGMAs so that each bank will be compiled into the correct bank. Like this…

#pragma rodata-name (“CODE0”)
#pragma code-name (“CODE0”)
const unsigned char TEXT1[]={

#pragma rodata-name (“CODE1”)
#pragma code-name (“CODE1”)
const unsigned char TEXT2[]={

#pragma rodata-name (“CODE2”)
#pragma code-name (“CODE2”)
const unsigned char TEXT3[]={

etc. And when START is pressed, it will switch banks into CPU addresses $8000-9fff, and then load the first 5 bytes of that bank and write it to the screen, with this code…

void Draw_Bank_Num(void){ //this draws some text to the screen
for (index = 0;index < 5;++index){
PPU_DATA = TEXT1[index];

When this compiles, it will write the address of TEXT1 into the code. It’s the only thing in the first bank (bank #0), and in the .cfg file, I defined that bank to start at $8000. So, it will be fetching the first 5 bytes from $8000-8004. That is the bank that I keep switching, so every time it goes here, it will be pulling those 5 bytes from whatever bank is mapped to $8000. Here’s the code that switches which bank will be mapped to addresses $8000-9fff…

if (((joypad1old & START) == 0)&&((joypad1 & START) != 0)){
if (PRGbank > 7) PRGbank = 0;
*((unsigned char*)0x8000) = 6; //bankswitch a PRG bank into $8000
*((unsigned char*)0x8001) = PRGbank;
Draw_Bank_Num(); //re-writes the text on the screen

I know, it looks like we’re storing 6 at address $8000. But, you can’t do that, because that’s a ROM address. What this does is send a signal to the MMC mapper that we want to switch a PRG bank into $8000. The next line defines which bank will be swapped in. You can get some more detailed info on the wiki…


I feel like this may be confusing. It’s an unfortunate coincidence, that the bank we’re swapping and the MMC3 register are both called $8000. If you wanted to instead swap a bank into the CPU address $a000-bfff, you would do this…

*((unsigned char*)0x8000) = 7; //bankswitch a PRG bank into $a000
*((unsigned char*)0x8001) = which_PRG_bank;

Is that clearer? Bank swapping is done with a $8000 / $8001 write combination.

I also added a few lines at the start of the main() function, which sets the initial state how everything should be mapped at the start. I don’t know for sure, but I think that the only bank that is certain at RESET is…that last PRG bank will definitely be at $e000-ffff. All our startup code should (ie. MUST) be located in that bank.

Now, that’s cool and all, but you will not be using bank swapping like this (having every bank have things located at fixed positions, like $8000). In reality, you will probably have an array of addresses at the start of every bank, which point to the location of data within the bank (that way, the data can be anywhere within the bank). If you have code in that bank, you will perhaps put a ‘JUMP TABLE’ at the start of the bank…that’s an array of addresses of the start of every function inside the bank. Essentially, the code in a fixed bank will read the address, and then jump (indirect) to that address. Or maybe, use the ‘push address to the stack and RTS’ Trick…


That’s kind of complicated ASM stuff, but it might be worthwhile learning it, if you’re going to make the most of using multiple banks.

Anyway, I wanted to add a cool ‘moving background’ effect. This effect can be done several ways, but I think bank swapping CHR ROM is the easiest. This code waits 4 frames, and then switches which CHR banks will be mapped to $0000-$03ff of the PPU. When the PPU goes to draw tile #23 to the screen, the mapper will direct it to tile #23 of a specific CHR bank in the ROM.

MMC3 actually breaks the CHR ROM into $400 byte chunks (64 tiles), so bank 0 = the first $400 bytes, 1 = the next $400 bytes, etc. It takes multiple MMC map writes to fill the PPU full of new tiles. I’m just changing that first $400 bytes (PPU addresses 0-$3ff). I made a little waterfall. There are 4 nearly identical CHR ROM banks, with the water tiles shifted downward 1 pixel each bank down.

Here’s the link to the source code. Press ‘START’ to see the PRG ROM bank switch.



Now, that’s not all MMC3 can do…it can also count scanlines. Normally, you would need to set up a Sprite Zero hit to time something to happen midframe, and you can only do that once. With MMC3, you can time multiple things to happen midframe, like changing the Scroll, or swapping CHR ROM, or other cool tricks. I’m going to change the scroll, about every 20 lines. You don’t have to sit and wait for those 20 lines, the MMC3 mapper will count for you, and you can go on to do other things (game logic). It will generate an IRQ, and jump to the IRQ code. I have the IRQ code changing the Horizontal Scroll (several times a frame).

Things we need to do…The first line of every NES game (startup code) is to turn off interrupts. But, that’s what an IRQ is, so in our main() function, I turned interrupts back on.

asm (“cli”); //turns ON IRQ interrupts

Also, I have to make sure that the vectors at the end of the reset.s code is pointing to the address of the IRQ code.

Now, during V-blank (you can turn this on any time, I just wanted to start it at the top of the screen) I set the MMC3 to start counting scanlines with this code…

*((unsigned char*)0xe000) = 1; //turn off MMC3 IRQ
*((unsigned char*)0xc000) = 20; //count 20 scanlines, then IRQ
*((unsigned char*)0xc001) = 20;
*((unsigned char*)0xe001) = 1; //turn on MMC3 IRQ

Note: I think it skips the first scanline, so it actually doesn’t generate an IRQ until at the end of scanline 21. Now, I want to change the horizontal scroll, which isn’t a problem, but if I immediately tried to change the scroll, there would be a slight glitch (misalignment) of the screen at that point. I’m always amazed at how many professional games have these kind of glitches. Anyway, to avoid the glitch, you have to change the scroll during the very very very short H-blank period. What’s an H-blank? When the PPU is drawing the picture to your TV, it goes left to right, then it jumps from right to left (not drawing) very quickly. That’s the H-blank period.

Well, the MMC3 IRQ triggers right at the H-blank period, but by the time it jumps to the IRQ code, and you load a number to the scroll, it’s already drawing the next scanline. So, in order to get it to change the scroll during H-blank, we have to wait for the next one. I wrote a tiny little loop, to wait about 100 CPU cycles, and then switch the scroll position. I think I’m timing it just right, but each emulator seems to be just a tiny bit different (ie. inaccurate), so I can’t be sure.

After the H-scroll is changed, I set up another ‘wait 20 scanlines and IRQ’ bit. It’s a bit tricky to get it to split exactly where you want. I’ve noticed that actual games don’t do the wait loop like I’m doing here. What they do is have nothing but a flat single color at the scroll split point across the entire scanline, so the glitch isn’t visible. Or, they just have a big glitch and don’t worry about it.

If you want to see the glitch (so you know what I’m talking about), edit that tiny wait loop (in the IRQ code in reset.s) to be just 1 number bigger, or 1 number smaller. Recompile it. Glitches at every split! The H-blank is really that small.

I still have ‘START’ change the bank number on screen…if you can read it. Here’s the link to the source code…




It occurred to me that, maybe people won’t want to recompile this, but still want to see the glitch thing I’ve been yapping about. Here’s an animated gif, with the wait loop off by just 1 loop. Look at the right side of the screen, the last scanline of each segment is misaligned, and that misalignment changes each frame, so it kind of dances around oddly. That’s just being a few pixels off on the scroll split…imagine if the Scroll split was done halfway into the scanline. You’d have one scanline of each segment be as much as 80-100 pixels out of alignment. That would look terrible, and be distracting.

You might wonder why we would split the screen like this anyway? For parallax scrolling. Go to YouTube, and search for NES and Parallax Scrolling. You’ll notice also, what I mentioned earlier, about most games do their scroll splits at a flat mono-color portion of the screen so glitches aren’t noticable. That would have been better (and easier) than trying to carefully time an H-blank split.


NOTE about the error I made (and fixed). I named the first bank “CODE” in the .cfg file and defined it to be at $8000, and it was a swappable bank. Apparently “CODE” is the default name that the C compiler uses to put all the C library functions. We can’t have that in a swappable bank, because if you go to call one of those C library functions (really, most code in C uses them) the bank that contains them might not be in place, and the game would crash. So, I called the last bank “CODE”. That’s the fixed bank. It will always be in place. Now, our C library functions will always be in the right position.

The other error that I fixed, was the IRQ code in “lesson20/reset.s”. If the C code was doing anything that required the C stack or the C variables when the IRQ was called…doing any more C functions inside the IRQ handler will screw up the stack/variables, and when the IRQ is finished, and it jumps back to the MAIN() function, the game will promptly crash. So, I rewrote the entire IRQ code in ASM, to make sure none of the C stack / C variables are affected. Here’s a link about that…



Like this:


Leave a Comment


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