MMC5 Hacking Adventures

Discuss hardware-related topics, such as development cartridges, CopyNES, PowerPak, EPROMs, or whatever.

Moderators: B00daW, Moderators

User avatar
Banshaku
Posts: 2396
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: MMC5 Hacking Adventures

Post by Banshaku » Thu Oct 25, 2018 5:36 pm

@Ben Boldt

I have no idea what you guys are talking about (never continued to study electronic ^^;;) but it great to see the enthusiasm for trying to find as much information about this mapper!

Good work :D

User avatar
Ben Boldt
Posts: 756
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC5 Hacking Adventures

Post by Ben Boldt » Thu Oct 25, 2018 9:02 pm

Thanks Banshaku. I am kind of up against my setup at the moment but with these new ideas I think I will be back up and running soon, finding out more about the MMC5. It seems like there are still some gems to be found, especially with that DAC. It doesn't make sense the way it is, it seems so pointless. Why go so far as to add a big expensive DAC that has basically no advantage over the one built-in to the NES's CPU? Why does it have an interrupt that triggers on value $00? Hopefully we can find some more clues that lead us somewhere. I really want that thing to play DAC samples automatically out of its own memory; I believe it might be able to do it somehow, and the first step being to have no $00s in the MMC5's memory, which requires these setup modifications.

User avatar
LightStruk
Posts: 45
Joined: Sat May 04, 2013 6:44 am

Re: MMC5 Hacking Adventures

Post by LightStruk » Mon Oct 29, 2018 7:36 am

Ben Boldt wrote:Why go so far as to add a big expensive DAC that has basically no advantage over the one built-in to the NES's CPU?
It's got one major advantage - the MMC5 PCM channel is 8 bits, whereas the 2A03 DPCM channel is 7 bits. Right?

User avatar
Bregalad
Posts: 8008
Joined: Fri Nov 12, 2004 2:49 pm
Location: Chexbres, VD, Switzerland

Re: MMC5 Hacking Adventures

Post by Bregalad » Mon Oct 29, 2018 8:24 am

LightStruk wrote:It's got one major advantage - the MMC5 PCM channel is 8 bits, whereas the 2A03 DPCM channel is 7 bits. Right?
Indeed, that's the only advantage I can think off, and also the MMC5 PCM can be used while a DPCM sample is playing for example.

User avatar
Ben Boldt
Posts: 756
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC5 Hacking Adventures

Post by Ben Boldt » Mon Oct 29, 2018 11:35 am

Yes I agree that those are advantages, but the NES pretty much has to stop everything else in order to use this DAC -- that seems to really limit the usefulness. Also, the interrupt seems to not be useful. If you are in read mode, and you are updating the DAC by means of reading, why not just check the value read? It is like a 1 cycle savings per sound sample, paid back by extra checks in the IRQ handler. That doesn't make much sense to me.

I have a gut feeling that they had something more flexible in mind that convinced them to add this DAC and interrupt. We've shown that this isn't just a drop-in copy of the CPU's built-in sound synthesizer -- this was recreated intentionally, presumably backed by lots of reviews, lessons learned, meetings and discussions about it. I think it had bigger plans. Whether or not it ended up having more features that actually worked or not I guess is another question.

lidnariq
Posts: 10275
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: MMC5 Hacking Adventures

Post by lidnariq » Mon Oct 29, 2018 12:45 pm

Ben Boldt wrote:If you are in read mode, and you are updating the DAC by means of reading, why not just check the value read? It is like a 1 cycle savings per sound sample, paid back by extra checks in the IRQ handler.
4 cycles saved, not just 1. Still not a meaningful difference, compared to the cost of IRQ entry, exit, and the pointer math to update things.

I think the cycle-timed IRQ is probably related, though.

Given that the NES collides with cartridge hardware putting anything in zero page, it'd be hard to do much better. An IRQ that where the code was dynamically generated by the mapper IC is about it (BIT $nnnn / RTI)

User avatar
Bregalad
Posts: 8008
Joined: Fri Nov 12, 2004 2:49 pm
Location: Chexbres, VD, Switzerland

Re: MMC5 Hacking Adventures

Post by Bregalad » Mon Oct 29, 2018 1:47 pm

Ben Boldt wrote:Yes I agree that those are advantages, but the NES pretty much has to stop everything else in order to use this DAC -- that seems to really limit the usefulness. Also, the interrupt seems to not be useful. If you are in read mode, and you are updating the DAC by means of reading, why not just check the value read? It is like a 1 cycle savings per sound sample, paid back by extra checks in the IRQ handler. That doesn't make much sense to me.
My random guess is that Nintendo had something better in mind, but the development wasn't finished when it was time to manufacture the MMC5 chip, so they left and unfinished feature in the chip. It seems likely to me that automatic playback of 8-bit samples at selectable rate without CPU intervention was going to be implemented. It's the same for the square waves who are missing the sweep mode, and other strange things in MMC5 which smells like unfinished work.

User avatar
Ben Boldt
Posts: 756
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC5 Hacking Adventures

Post by Ben Boldt » Mon Oct 29, 2018 6:34 pm

That is very realistic that this is sort of an unfinished thing or just not very useful. Unfortunately we don't have a huge selection of games that used this chip, so it's hard to really know what we may or may not be missing. None of them ever used that hardware timer for example -- we didn't know about it until now. It might not be all that likely to find magical functions of the DAC, but I'll just keep dreaming and let that chance drive me forward. Who knows what else we might find along the way!

I am having a little trouble with my buffer microcontroller still. It has 2 I2C ports. I want to make one of them a master, to talk to the IO expanders. I have already gotten that to work as I showed earlier. I want to make the other I2C port a slave, to talk to the PC. I am still having trouble getting that one to work. These I2C peripherals are dead simple, not sure what I could be doing wrong. I have never actually used both I2C ports at the same time on one of these dsPICs before -- maybe it can't! Will find out. I have also added a queue for operations.

User avatar
Banshaku
Posts: 2396
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: MMC5 Hacking Adventures

Post by Banshaku » Mon Oct 29, 2018 7:39 pm

I guess the only thing that would answer our question about what was supposed to be usable or not is the official document but that is beyond the point here ^^;;; Game don't always uses all the features of the mapper so looking at the code may help find things but the document is the best (but unavailable) source of information.

User avatar
Ben Boldt
Posts: 756
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC5 Hacking Adventures

Post by Ben Boldt » Tue Oct 30, 2018 3:59 am

You would think that after this many years something would have surfaced...

I got my 2 Japanese L'Empereurs last night, they are both regular MMC5 (non-A).

User avatar
Bregalad
Posts: 8008
Joined: Fri Nov 12, 2004 2:49 pm
Location: Chexbres, VD, Switzerland

Re: MMC5 Hacking Adventures

Post by Bregalad » Tue Oct 30, 2018 4:52 am

Banshaku wrote:Game don't always uses all the features of the mapper so looking at the code may help find things but the document is the best (but unavailable) source of information.
This is unrelated but now that you mention it I clearly remember Just Breed using an unknown MMC5 register, maybe it was $5800 or similar. It was really strange and unexplainable.

User avatar
Ben Boldt
Posts: 756
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC5 Hacking Adventures

Post by Ben Boldt » Tue Oct 30, 2018 3:21 pm

Bregalad wrote:
Banshaku wrote:Game don't always uses all the features of the mapper so looking at the code may help find things but the document is the best (but unavailable) source of information.
This is unrelated but now that you mention it I clearly remember Just Breed using an unknown MMC5 register, maybe it was $5800 or similar. It was really strange and unexplainable.
Cool - do you think you could find that again? I will have a look tonight. I would like to at least list it on the MMC5 wiki page as an unknown write-only register.

I figured out my I2C problems. I tried another microcontroller and it worked. :? That's what I get for using scavenged micros I guess. Still just chipping away at it here and there when I have extra time.

Edit:
I found it, first it writes $03 to $5800, followed immediately by writing $01 to $5800. Shortly thereafter, it writes $FC to $5115 (PRG bank 1). It then starts writing to the PPU.

Code: Select all

A:17 X:05 Y:17 S:EB P:nvUbdIzc                     $E14F:A9 03     LDA #$03
A:03 X:05 Y:17 S:EB P:nvUbdIzc                     $E151:8D 00 58  STA $5800 = #$03
A:03 X:05 Y:17 S:EB P:nvUbdIzc                     $E154:A9 01     LDA #$01
A:01 X:05 Y:17 S:EB P:nvUbdIzc                     $E156:8D 00 58  STA $5800 = #$01
A:01 X:05 Y:17 S:EB P:nvUbdIzc                     $E159:68        PLA
A:17 X:05 Y:17 S:EC P:nvUbdIzc                    $E15A:A5 BC     LDA $00BC = #$FA
A:FA X:05 Y:17 S:EC P:NvUbdIzc                    $E15C:48        PHA
A:FA X:05 Y:17 S:EB P:NvUbdIzc                     $E15D:A5 BD     LDA $00BD = #$FB
A:FB X:05 Y:17 S:EB P:NvUbdIzc                     $E15F:48        PHA
A:FB X:05 Y:17 S:EA P:NvUbdIzc                      $E160:A9 FC     LDA #$FC
A:FC X:05 Y:17 S:EA P:NvUbdIzc                      $E162:20 9C EB  JSR $EB9C
A:FC X:05 Y:17 S:E8 P:NvUbdIzc                        $EB9C:85 BD     STA $00BD = #$FB
A:FC X:05 Y:17 S:E8 P:NvUbdIzc                        $EB9E:8D 15 51  STA $5115 = #$BD
A:FC X:05 Y:17 S:E8 P:NvUbdIzc                        $EBA1:60        RTS (from $EB9C) ---------------------------
A:FC X:05 Y:17 S:EA P:NvUbdIzc                      $E165:A5 E5     LDA $00E5 = #$01
A:01 X:05 Y:17 S:EA P:nvUbdIzc                      $E167:F0 32     BEQ $E19B
A:01 X:05 Y:17 S:EA P:nvUbdIzc                      $E169:20 57 A7  JSR $A757
A:01 X:05 Y:17 S:E8 P:nvUbdIzc                        $A757:A9 00     LDA #$00
A:00 X:05 Y:17 S:E8 P:nvUbdIZc                        $A759:8D 03 20  STA PPU_OAM_ADDR = #$00
A:00 X:05 Y:17 S:E8 P:nvUbdIZc                        $A75C:A9 02     LDA #$02
A:02 X:05 Y:17 S:E8 P:nvUbdIzc                        $A75E:8D 14 40  STA OAM_DMA = #$02
A:02 X:05 Y:17 S:E8 P:nvUbdIzc                        $A761:60        RTS (from $A757) ---------------------------
A:02 X:05 Y:17 S:EA P:nvUbdIzc                      $E16C:20 CD AF  JSR $AFCD
A:02 X:05 Y:17 S:E8 P:nvUbdIzc                        $AFCD:A5 E6     LDA $00E6 = #$00
A:00 X:05 Y:17 S:E8 P:nvUbdIZc                        $AFCF:F0 08     BEQ $AFD9
A:00 X:05 Y:17 S:E8 P:nvUbdIZc                        $AFD9:AD 02 20  LDA PPU_STATUS = #$90
A:90 X:05 Y:17 S:E8 P:NvUbdIzc                        $AFDC:A9 3F     LDA #$3F
A:3F X:05 Y:17 S:E8 P:nvUbdIzc                        $AFDE:8D 06 20  STA PPU_ADDRESS = #$A0
A:3F X:05 Y:17 S:E8 P:nvUbdIzc                        $AFE1:A9 10     LDA #$10
A:10 X:05 Y:17 S:E8 P:nvUbdIzc                        $AFE3:8D 06 20  STA PPU_ADDRESS = #$A0
A:10 X:05 Y:17 S:E8 P:nvUbdIzc                        $AFE6:A2 00     LDX #$00
A:10 X:00 Y:17 S:E8 P:nvUbdIZc                        $AFE8:BD 90 03  LDA $0390,X @ $0390 = #$0F
A:0F X:00 Y:17 S:E8 P:nvUbdIzc                        $AFEB:8D 07 20  STA PPU_DATA = #$00
etc.
Followed by writing to all of the first 8 CHR select registers:

Code: Select all

A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E16F:A5 83     LDA $0083 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E171:8D 20 51  STA $5120 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E174:A5 84     LDA $0084 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E176:8D 21 51  STA $5121 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E179:A5 85     LDA $0085 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E17B:8D 22 51  STA $5122 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E17E:A5 86     LDA $0086 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E180:8D 23 51  STA $5123 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E183:A5 87     LDA $0087 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E185:8D 24 51  STA $5124 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E188:A5 88     LDA $0088 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E18A:8D 25 51  STA $5125 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E18D:A5 89     LDA $0089 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E18F:8D 26 51  STA $5126 = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E192:A5 8A     LDA $008A = #$00
A:00 X:10 Y:17 S:EA P:nvUbdIZC                      $E194:8D 27 51  STA $5127 = #$00
Followed by this:

Code: Select all

A:00 X:00 Y:17 S:E8 P:nvUbdiZC                        $E911:A5 5B     LDA $005B = #$00
A:00 X:00 Y:17 S:E8 P:nvUbdiZC                        $E913:8D 05 20  STA PPU_SCROLL = #$00
A:00 X:00 Y:17 S:E8 P:nvUbdiZC                        $E916:A5 5C     LDA $005C = #$EF
A:EF X:00 Y:17 S:E8 P:NvUbdizC                        $E918:8D 05 20  STA PPU_SCROLL = #$00
A:EF X:00 Y:17 S:E8 P:NvUbdizC                        $E91B:A5 00     LDA $0000 = #$A8
A:A8 X:00 Y:17 S:E8 P:NvUbdizC                        $E91D:8D 00 20  STA PPU_CTRL = #$A8
Then it writes $00 to $5800. This happens once per frame. It looks like maybe some sort of way to tell the MMC5 that the PPU is being updated, and to disable or reset scanline detection/counting? No other game writes to this register that I could find though. This is the only MMC5 game developed by Enix / Random House according to NesCartDB.

User avatar
Ben Boldt
Posts: 756
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC5 Hacking Adventures

Post by Ben Boldt » Thu Nov 01, 2018 5:16 pm

I got the I2C buffer working tonight! In this screenshot, I tested by using the multiplier:
  • Write $02 to $5205
  • Write $03 to $5206
  • Read back $5205
  • Read back $5206
And indeed I got back $0006! :) In the screenshot, M2 is across the top. The pink/blue I2C bus is between the PC (master) and the dsPIC microcontroller (slave). This is where I am writing operations to the queue in the dsPIC. Then the yellow/green I2C bus is between the dsPIC (master) and the I/O expanders (slaves). This I2C bus is synchronized to M2.

When the PC is done filling up the operation queue, it does a read/write I2C command. This command triggers the microcontroller to start running the operation queue. The microcontroller keeps the SCL held low (i.e. "clock stretch") until the queue is finished. Then it releases the I2C clock stretch and sends back the results to the PC at that time.

That is probably all for tonight, but I will be trying to fill the built-in expansion RAM soon, probably this weekend. Also, it looks like I can speed up M2 a fair bit. Too bad the USB/I2C interface I am using is so slow. But it is easy to use, so that's a fair tradeoff.
Attachments
I2C Buffer.png

User avatar
Ben Boldt
Posts: 756
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC5 Hacking Adventures

Post by Ben Boldt » Fri Nov 02, 2018 2:23 pm

I improved the efficiency of the test (same scale and operations as in previous screenshot):
I2C Buffer 2.png
I made it so that the test itself can drive M2 faster. Also, I really improved the communication between PC and dsPIC -- it now uses a more efficient command structure, which can send multiple commands in 1 message. After this screenshot, I ran the full multiplier test and got all correct answers with this, so that's a good sign that it is working well.

I am still not able to write to the built-in expansion RAM of the MMC5. Here is the code where I attempt to do it:

Code: Select all

        private void testFillRam()
        {
            data_queue = new byte[60];
            data_queue_index = 0;

            string s = "";

            // ** Initialize **
            readAndRefreshGraphics();

            data_queue[data_queue_index] = 0x03;  // Set M2 fast mode
            data_queue_index++;
            data_queue[data_queue_index] = 0x00;  // Set M2 Low
            data_queue_index++;
            data_queue[data_queue_index] = 0xFF;  // Rise CPU R/W
            data_queue_index++;
            operation_setCpuAddressBusDirection(false);  // Set CPU address bus as output.

            // Write mode value $02 to register $5102:
            operation_setCpuAddress(0x5102);
            operation_writeCpuData(0x02);

            // Write mode value $01 to register $5103:
            operation_setCpuAddress(0x5103);
            operation_writeCpuData(0x02);

            // Write Extended RAM mode value $02 to register $5104:
            operation_setCpuAddress(0x5104);
            operation_writeCpuData(0x02);

            sendOperationQueueToDsPic();
            readResultsFromDsPic(0);  // Run the queue.
            // ** End Initialize **

            // Write non-zero, non-FF, non-negative data to entire expansion RAM:
            int i_start = 0x5C00;
            int i_end = 0x6000;
            byte data_to_write = 0x10;
            for (int i = i_start; i < i_end; i++)
            {
                operation_setCpuAddress((UInt16)i);
                operation_writeCpuData(data_to_write);

                data_to_write++;
                if (data_to_write > 0x7F)
                {
                    data_to_write = 0x10;
                }

                sendOperationQueueToDsPic();
                readResultsFromDsPic(0);  // Run the queue.

                // Update progress bar in GUI:
                SetProgressThreadable(i, i_start, i_end, 100);
            }

            // Read back entire expansion RAM
            i_start = 0x5C00;
            i_end = 0x6000;
            for (int i = i_start; i < i_end; i++)
            {
                operation_setCpuAddress((UInt16)i);
                operation_readCpuData();
                sendOperationQueueToDsPic();

                byte[] readback = readResultsFromDsPic(1);

                s += i.ToString("X4") + ",";
                s += readback[0].ToString("X2") + "\r\n";

                SetProgressThreadable(i, i_start, i_end, 100);
            }

            // Log to File:
            filename = "MMC5 Expansion RAM Fill " + DateTime.Now.ToString("MM.dd.yy hh.mm.ss tt");
            using (StreamWriter outfile = new StreamWriter(folderName + filename + ".csv", true))
            {
                outfile.WriteLine(s);
            }

            SetProgressThreadable(0, 0, 0, 1);
        }

Here is the code where I write to registers. It works with the multiplier, but could you guys please verify that I am doing this on this on the correct edge of M2? I am very confused by the edge of M2. I drive the CPU address bus to the intended address right before calling this function.

Code: Select all

        private void operation_writeCpuData( byte data )
        {
            data_queue[data_queue_index] = 0x01;  // Set M2 High
            data_queue_index++;

            data_queue[data_queue_index] = 0xFE;  // set CPU R/W low
            data_queue_index++;
            data_queue[data_queue_index] = 0x56;  // Write byte to CPU data bus
            data_queue_index++;
            data_queue[data_queue_index] = data;  // ^ (data)
            data_queue_index++;
            data_queue[data_queue_index] = 0x66;  // Set as output from IO expander to MMC5
            data_queue_index++;
            data_queue[data_queue_index] = 0x00;  // ^ (Output)
            data_queue_index++;

            data_queue[data_queue_index] = 0x00;  // Set M2 Low -- Registers the write on falling edge. (I think???)
            data_queue_index++;

            data_queue[data_queue_index] = 0x66;  // Set as input to IO expander from MMC5
            data_queue_index++;
            data_queue[data_queue_index] = 0xFF;  // ^ (Input)
            data_queue_index++;
            data_queue[data_queue_index] = 0xFF;  // CPU R/W high
            data_queue_index++;
        }

I always read back all zeros from the internal expansion RAM. Any ideas would be very much appreciated.

Edit:
I tried speeding up M2. Normally, I had a 10msec period on M2 (when idle / waiting for next instruction from PC). I increased the speed to 4msec period and still read all zeros.

lidnariq
Posts: 10275
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: MMC5 Hacking Adventures

Post by lidnariq » Fri Nov 02, 2018 2:42 pm

The way the NES is "supposed" to work, R/W, all the address lines, and the data lines during a write, should only change while M2 is low. (During a read, you should care about the value of the data bus on the falling edge of M2)

Not clear that's what's going wrong, but it assuredly doesn't help.


Remember that Krzysiobal said that the timeout is 11us, not ms. You may need to have the PIC fake a series of dummy reads to some unrelated address (0 would be good) to speed things up enough.

Post Reply