It is currently Mon Oct 15, 2018 1:09 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 8 posts ] 
Author Message
 Post subject: MMC1 Emulation 32k Mode
PostPosted: Tue Jul 17, 2018 10:22 pm 
Offline

Joined: Tue Jul 17, 2018 9:29 pm
Posts: 6
Hello, I could use some help with my mmc1 emulation. I have gone through the wiki and docs best i could and have come up with an implementation that works almost all the time but a couple games still give me trouble. One is Adams Family Puglsy scavenger hunt and the other Ninja Gaiden. Adams family breaks within the first 30 instructions so its a little easier to follow but Im still getting the banks wrong. Ive included the log below for Adams Family and my mmc1 code. On startup I load 0xC into Reg0 and all other regs are 0. The lower PRG space has bank 0 and upper PRG space has the last bank which is 7.

As i follow the log below i can see that it starts by a series of resets then writes to Reg1 with an initial value of 1. Which to my understanding means 32k switching mode. Then on line 23 writes begin to Reg3 with an initial value of 1. After the 5th write i switch in bank 0 at the lower PRG Space and bank 1 into the upper PRG space. This is where it falls apart. Checking with nintendulator i can see that after the 5th write that the lower PRG space has bank 7 and the upper PRG space has 7 as well. I cant seem to figure out how bank 7 ends up in either bank when 32k should of been switched out using a Reg3 value of 1.


1 FF80 A9 FF LDA #$FF A:00 X:00 Y:00 P:24 SP:FD CYC: 0 SL:241
2 FF82 8D 00 80 STA $8000 = 00 A:FF X:00 Y:00 P:A4 SP:FD CYC: 6 SL:241
3 FF85 8D 00 A0 STA $A000 = 01 A:FF X:00 Y:00 P:A4 SP:FD CYC: 18 SL:241
4 FF88 8D 00 C0 STA $C000 = 00 A:FF X:00 Y:00 P:A4 SP:FD CYC: 30 SL:241
5 FF8B 8D 00 E0 STA $E000 = D0 A:FF X:00 Y:00 P:A4 SP:FD CYC: 42 SL:241
6 FF8E A2 01 LDX #$01 A:FF X:00 Y:00 P:A4 SP:FD CYC: 54 SL:241
7 FF90 A0 00 LDY #$00 A:FF X:01 Y:00 P:24 SP:FD CYC: 60 SL:241
8 FF92 8E FF 9F STX $9FFF = 01 A:FF X:01 Y:00 P:26 SP:FD CYC: 66 SL:241
9 FF95 8E FF 9F STX $9FFF = 01 A:FF X:01 Y:00 P:26 SP:FD CYC: 78 SL:241
10 FF98 8E FF 9F STX $9FFF = 01 A:FF X:01 Y:00 P:26 SP:FD CYC: 90 SL:241
11 FF9B 8E FF 9F STX $9FFF = 01 A:FF X:01 Y:00 P:26 SP:FD CYC:102 SL:241
12 FF9E 8C FF 9F STY $9FFF = 01 A:FF X:01 Y:00 P:26 SP:FD CYC:114 SL:241
13 FFA1 8C FF BF STY $BFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:126 SL:241
14 FFA4 8C FF BF STY $BFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:138 SL:241
15 FFA7 8C FF BF STY $BFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:150 SL:241
16 FFAA 8C FF BF STY $BFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:162 SL:241
17 FFAD 8C FF BF STY $BFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:174 SL:241
18 FFB0 8E FF DF STX $DFFF = FA A:FF X:01 Y:00 P:26 SP:FD CYC:186 SL:241
19 FFB3 8C FF DF STY $DFFF = FA A:FF X:01 Y:00 P:26 SP:FD CYC:198 SL:241
20 FFB6 8C FF DF STY $DFFF = FA A:FF X:01 Y:00 P:26 SP:FD CYC:210 SL:241
21 FFB9 8C FF DF STY $DFFF = FA A:FF X:01 Y:00 P:26 SP:FD CYC:222 SL:241
22 FFBC 8C FF DF STY $DFFF = FA A:FF X:01 Y:00 P:26 SP:FD CYC:234 SL:241
23 FFBF 8E FF FF STX $FFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:246 SL:241
24 FFC2 8E FF FF STX $FFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:258 SL:241
25 FFC5 8E FF FF STX $FFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:270 SL:241
26 FFC8 8C FF FF STY $FFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:282 SL:241
27 FFCB 8C FF FF STY $FFFF = C3 A:FF X:01 Y:00 P:26 SP:FD CYC:294 SL:241
28 FFCE 4C A4 C5 JMP $C5A4 A:FF X:01 Y:00 P:26 SP:FD CYC:306 SL:241
29 C5A4 A:FF X:01 Y:00 P:26 SP:FD CYC:315 SL:241


*Code Updated 7/22/18 CHR RAM/PRG RAM Bank Swicthing
*Code Updated 7/22/18
Code:
        public override void WriteToAddress(int MemoryAddress, int val)
        {
            if (MemoryAddress >= 0x8000 && MemoryAddress <= 0xffff && (((CPU.TotalCPUCycles - 1)!=LastWriteCycle) ))
            {
                LastWriteCycle = CPU.TotalCPUCycles;
                if (val > 127)
                {
                    MMC1COUNT = 0;
                    MMC1REG0 = MMC1REG0 | 12;
                }
                else
                {
                    MMC1COUNT++;                   
                    if (MMC1COUNT == 1)
                    {
                        MMC1FIVEBITVALUE = val & 31;
                    }
                    if (MMC1COUNT == 5)
                    {
                        if (MemoryAddress >= 0x8000 && MemoryAddress < 0xA000)
                        {
                            MMC1REG0 = MMC1FIVEBITVALUE & 31;
                            PPU.MIRROR= MMC1REG0 &3;
                            MMC1PRG();
                        }
                        if (MemoryAddress >= 0xA000 && MemoryAddress < 0xC000)
                        {
                            MMC1REG1 = MMC1FIVEBITVALUE;
                            MMC1PRG();
                           if (GAME.NUMCHRPAGES > 0)
                            {
                                if ((((MMC1REG0 >> 4) & 1) == 0))
                                {
                                    GAME.SwitchCHRBank((MMC1REG1 >> 1), 0x2000, 0x2000, 0x0000);
                                }
                                else if ((((MMC1REG0 >> 4) & 1) == 1))
                                {
                                    GAME.SwitchCHRBank(MMC1REG1, 0x1000, 0x1000, 0x0000);
                                }
                            }     
                            else if (GAME.NUMCHRPAGES == 0 && (((MMC1REG0 >> 4) & 1) == 1))
                            {
                                GAME.SwitchCHRRAMBank((MMC1REG1 & 1), 0x1000, 0x1000, 0x0000);
                            }
                            if (GAME.NUMCHRPAGES <= 2)
                            {
                                GAME.SwitchPRGRAMBank(((MMC1REG1 >> 2) & 3), 0x2000, 0x2000, 0x6000);
                            }                   
                        }
                        if (MemoryAddress >= 0xc000 && MemoryAddress <= 0xdfff)
                        {
                            MMC1REG2 = MMC1FIVEBITVALUE & 31;
                            if (GAME.NUMCHRPAGES > 0 && (((MMC1REG0 >> 4) & 1) == 1))
                            {
                                GAME.SwitchCHRBank(MMC1REG2, 0x1000, 0x1000, 0x1000);
                            }
                            else if (GAME.NUMCHRPAGES == 0 && (((MMC1REG0 >> 4) & 1) == 1))
                            {
                                GAME.SwitchCHRRAMBank((MMC1REG2 & 1), 0x1000, 0x1000, 0x1000);
                            }
                            if(GAME.NUMCHRPAGES <= 2 && (((MMC1REG0 >> 4) & 1) == 1))
                            {
                                GAME.SwitchPRGRAMBank(((MMC1REG2 >> 2) & 3), 0x2000, 0x2000, 0x6000);
                            }
                        }
                        if (MemoryAddress >= 0xe000 && MemoryAddress < 0x10000)
                        {
                            MMC1REG3 = MMC1FIVEBITVALUE & 15;
                            MMC1PRG();                           
                        }
                    }
                    if (MMC1COUNT == 5) { MMC1COUNT = 0; }
                }
            }
         if(MemoryAddress>=0x6000 && MemoryAddress < 0x8000)
            {
                GAME.TheMapper.WritePRGRAM(MemoryAddress, val);
            }
        }

 public static void MMC1PRG()
        {
            int offset = (((MMC1REG1 >> 4) & 1) * 16 * (0x4000));
            if ((((MMC1REG0 >> 2) & 3) < 2))
            {
                GAME.SwitchPRGBank((MMC1REG3 & ~1), 0x4000, 0x4000, 0x8000, offset);
                GAME.SwitchPRGBank(((MMC1REG3 & ~1) | 0x01), 0x4000, 0x4000, 0xC000, offset);
            }
            else if ((((MMC1REG0 >> 2) & 3) == 2))
            {
                GAME.SwitchPRGBank(((MMC1REG3 & 15)), 0x4000, 0x4000, 0xC000, offset);
                GAME.SwitchPRGBank(0, 0x4000, 0x4000, 0x8000, offset);

            }
            else if ((((MMC1REG0 >> 2) & 3) == 3))
            {
                GAME.SwitchPRGBank(((MMC1REG3 & 15)), 0x4000, 0x4000, 0x8000, offset);
                GAME.SwitchPRGBank(((GAME.NUMPRGPAGES - 1) & 15), 0x4000, 0x4000, 0xC000, offset);
            }
        }


Last edited by imid on Sun Jul 22, 2018 4:48 pm, edited 2 times in total.

Top
 Profile  
 
PostPosted: Tue Jul 17, 2018 11:42 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7648
Location: Seattle
imid wrote:
if (MMC1COUNT == 1)
{
MMC1FIVEBITVALUE = val & 31;
}
I understand the cleverness you're trying here, but that's not how the MMC1 works.

It only has two data pins. One pays attention to the most significant bit (& 128, or > 127) and the other only pays attention to the least significant bit (& 1).

While most games do load the value into some register, and then repeatedly shift the register and write to the MMC1, there are other games, like the traces you have here, that instead write constant values to the MMC1's registers instead by just loading A,X, or Y with 0 and/or 1.


Top
 Profile  
 
PostPosted: Wed Jul 18, 2018 1:52 am 
Offline

Joined: Tue Jul 17, 2018 9:29 pm
Posts: 6
Thank You! That did the trick. All the US MMC1 games now at least load up and look good. For anyone else I changed
Code:
if (MMC1COUNT == 1)
{
MMC1FIVEBITVALUE = val & 31;
}


to
Code:
  if ((val & 1) == 0)
  {
      MMC1FIVEBITVALUE &= ~(1 << 5);
  }
  else
  {
      MMC1FIVEBITVALUE |= 1 << 5;
  }
  MMC1FIVEBITVALUE = ((MMC1FIVEBITVALUE >> 1) & 0xFF);


There is probably a more elegant way to do it but for now im glad its working. Thanks again.


Top
 Profile  
 
PostPosted: Sun Jul 22, 2018 12:26 pm 
Offline

Joined: Tue Jul 17, 2018 9:29 pm
Posts: 6
I had a couple of more questions about MMC1 before i move on. Ive updated the code above to reflect PRG bank switching on writes to REG0 and REG1 as well as REG3. I suspect CHR switching may work the same way I just need to find a case where doing so fixes something.

One question I have is regarding Matt Richey's MMC1.txt file. There is a Further Switching Section near the bottom where he talks about these transitions between states in the MMC1 and how they are handled. I am not handling these in code right now in any special way and yet i don't see any games breaking due to it. I also don't see anything about them on the wiki aside from which banks should be used in 32K mode, which leads me to believe that the information may not be accurate. can anyone confirm if these transitions are needed or not?

The other question i had has to do with PRG RAM. Currently I just keep the entire area 0x6000-0x8000 writeable and everything seems fine. I have not implemented any bankswitching for it in MMC1 and also the same goes for CHR RAM which i also haven't implemented any bankswitching for. The only ROMS I have found that need these in MMC1 are the holy batman test ROMS. I wondering how important this is if the goal is just to be able to play all USA licensed games. Does anyone know of any games that use these extended features?


Top
 Profile  
 
PostPosted: Sun Jul 22, 2018 1:47 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20656
Location: NE Indiana, USA (NTSC)
Koei's games on the SOROM board use bank switching.

Genghis Khan, Romance of the Three Kingdoms, Nobunaga's Ambition


Top
 Profile  
 
PostPosted: Sun Jul 22, 2018 4:56 pm 
Offline

Joined: Tue Jul 17, 2018 9:29 pm
Posts: 6
Thank you, That site is really useful. I went ahead and added CHR RAM and PRG RAM bank switching. For anyone else working on this i was able to also find that Space Shuttle Project uses CHR RAM bank switching right on the title screen. Half the screen will be garbled if it isn't implemented. It makes testing so much easier when games break right away like that.


Top
 Profile  
 
PostPosted: Sun Jul 22, 2018 6:27 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20656
Location: NE Indiana, USA (NTSC)
The Curse of Possum Hollow (unlicensed, TGROM clone) breaks very visibly very quickly if its 32K CHR RAM isn't bankswitched.

Lagrange Point (licensed, Japan-only, VRC7) also breaks if CHR RAM isn't bankswitched.


Top
 Profile  
 
PostPosted: Sun Jul 22, 2018 7:04 pm 
Offline

Joined: Tue Jul 17, 2018 9:29 pm
Posts: 6
I had to google possum hollow, I thought you were pulling my leg. It looks pretty neat. I didn't even realize people were still putting out games. I've been pretty much out of the NES scene for 20 years. I got into collecting games pretty heavy around the mid nineties. Once emulators started getting developed i built a io56 and put it to absurdly good use. I built up a collection of over 700 different USA titles within a couple years, a lot were donated from people at the time. I got back into this summer with writing this emulator and I am shocked at how much information was wrong back then on the NES. By 98 it really felt like everything was figured out and working to some extant, its amazing emus worked as well as they did back then.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 8 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group