MMC1 emulation

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
MMC1 emulation
by on (#91329)
I'm just sitting here reading through Disch's mapper doc for MMC1.

Then it hit me...I think. I'm not sure if I just missed the bus on this and everyone else has already figured this out...

001.txt wrote:
Consecutive writes that are too close together are apparently ignored.


Of course...in the INC $FFFF case:

001.txt wrote:
you know that this will read $FFFF (getting $FF), write that
value ($FF) back to $FFFF, increment it by one, then write the new value ($00) to $FFFF. This results in
two register writes: $FF, then $00.


The 6502 issues "back-to-back" writes, not toggling RW between. If the MMC1 address decoder is negative-edge-triggered on RW, that would explain why the second write is ignored.

Here's a Visual6502 trace of a program that is nothing more than

Code:
$0000: INC $FFFF
$0003: KIL

(data at $FFFF is $00 -- couldn't get the sim to accept ?a=ffff&d=ff)


Code:
cycle   ab   db   rw   Fetch   pc   a   x   y   s   p
0   0000   ee   1   INC Abs   0000   aa   00   00   fd   nv‑BdIZc
0   0000   ee   1   INC Abs   0000   aa   00   00   fd   nv‑BdIZc
1   0001   ff   1      0001   aa   00   00   fd   nv‑BdIZc
1   0001   ff   1      0001   aa   00   00   fd   nv‑BdIZc
2   0002   ff   1      0002   aa   00   00   fd   nv‑BdIZc
2   0002   ff   1      0002   aa   00   00   fd   nv‑BdIZc
3   ffff   00   1      0003   aa   00   00   fd   nv‑BdIZc
3   ffff   00   1      0003   aa   00   00   fd   nv‑BdIZc
4   ffff   00   0      0003   aa   00   00   fd   nv‑BdIZc
4   ffff   00   0      0003   aa   00   00   fd   nv‑BdIZc
5   ffff   00   0      0003   aa   00   00   fd   nv‑BdIzc
5   ffff   01   0      0003   aa   00   00   fd   nv‑BdIzc
6   0003   02   1   unknown   0003   aa   00   00   fd   nv‑BdIzc
6   0003   02   1   unknown   0003   aa   00   00   fd   nv‑BdIzc


In cycle 6 of course RW goes back high to do the opcode fetch. Note there's no transition on RW between cycles 4 and 5.

So perhaps emulating MMC1 like: keep track of the current CPU cycle of a write and "ignore" a write in the immediately-following CPU cycle is correct?

by on (#91334)
I was looking that this the other week to emulate it in hardware. I suspected that PRG R/W never goes high when I looked at scope traces like you've pointed out.
http://nesdev.com/bbs/viewtopic.php?t=8277&postdays=0&postorder=asc&start=15

The only question I have is when is data valid in relation to PRG R/W (or M2 for that matter). From what I can tell PRG R/W goes low about 20nsec after M2 goes low durring writes. I would imagine data isn't valid until after M2 goes low, and if so it's a race through the logic for data to be vaild before PRG R/W goes low. Although... your idea is that the counter is clocked by negedge of PRG R/W, the shift register wouldn't have to have the same clock and if so data wouldn't have to be valid when clocking the counter. What would the shift register be clocked by then? Because not only to we have to prevent from having the counter incremented but we can't load a bit into the shift register either. The only way I could think of to it was establish a control signal based on what cycle the CPU is in like your solution.

However everything I've said above is NULL if data is valid with enough setup time when PRG R/W goes low. Then the shift register and counter could have the same clock and could likely be how the MMC1 actually works.

So I'm accounting for the consecutive writes using a state machine that clocks on negedge of M2. I basically do the same thing you're saying. I block any writes from occurring the clock cycle after an initial write. I've yet to get it working though... ;)

EDIT my verilog if anyone is curious
Code:
//ALLOW LOAD STATE MACHINE
//prevent two consecutive writes to shift register, by blocking for one cycle
//after every write. 
reg allow_ps;    //set high when allowing load of shift register
reg allow_ns;

//ALLOW LOAD next state logic
always @ (allow_ps, write, power_up)
begin
   case (allow_ps)
      
      0:   allow_ns = 1;      //one cycle has passed, allow now

      1:    if (write == 1)
            allow_ns = 0;   //last was write so block
         else
            allow_ns = 1;   

      endcase
end

//ALLOW LOAD present state logic
always @ (negedge m2)
begin
   
   allow_ps <= allow_ns;   
end



In any event your solution for emulation should be fine, the discussion above is more about what's going on with the hardware like you've brought up.

As an aside I thought it was interesting that this double write could be utilized as a possible method of copy protection in the reverse engineering of the 6502 video. Which agrees my hypothesis that Nintendo did this on purpose, not to save logic. http://nesdev.com/bbs/viewtopic.php?t=8688&highlight=

by on (#91335)
I suppose it would take someone with a MMC1 donor cart to figure this out, but I'm curious what would happen if 'INC absolute' were used during the 5-cycle register update instead of the typical 'LSR/STA'. As I understand it the INC-issue has been described only related to resetting the MMC1.

Code:
$E000: $0F
$E001: $07
$E002: $03
$E003: $01
$E004: $00


Code:
switchPrg:
   INC $E000
   INC $E001
   INC $E002
   INC $E003
   INC $E004


Does it switch to bank $0F, or bank $00?
Re: MMC1 emulation
by on (#91336)
cpow wrote:
So perhaps emulating MMC1 like: keep track of the current CPU cycle of a write and "ignore" a write in the immediately-following CPU cycle is correct?

That's how I do it.

by on (#91357)
cpow wrote:
Does it switch to bank $0F, or bank $00?


I would expect $0F.

Code:
$E000: $0F
$E001: $07
$E002: $03
$E003: $01
$E004: $00


Those are the values initially written back to the MMC1, The write you'd expect from the instruction get ignored so these would never be seen by the MMC1's registers unless I'm missing something:

Code:
$E000: $00
$E001: $08
$E002: $04
$E003: $02
$E004: $01


(Also wouldn't it have been bank $10 not $00 since $00 increments to $01?)

by on (#91358)
infiniteneslives wrote:
So I agree it'll probably get $0F for the register. I'm not sure
(Also wouldn't it have been bank $10 not $00 since $00 increments to $01?)


Good point, yes.

by on (#91361)
I think I already have a MMC1 (SNROM) devcard ready so I should be able to easily test this behavior. It's just that I need some way to print the result to the screen, but this should be trivial.

by on (#91362)
Bregalad wrote:
It's just that I need some way to print the result to the screen, but this should be trivial.


Alternatively you could just probe the address pins with a DMM if it was quicker/easier.

I'm working on whipping up a logic analyzer and MMC1 board for testing as well. I'm interested in better characterizing how this thing is works exactly. If all goes well I should be able to apply signals without the NES and see how it responds to individual signal changes.

by on (#91364)
Bregalad wrote:
I think I already have a MMC1 (SNROM) devcard ready so I should be able to easily test this behavior. It's just that I need some way to print the result to the screen, but this should be trivial.


Here's a possible test ROM.

I modified Tepples' SNROM demo project. Instead of this bankswitch routine:

Code:
.segment "CODE"
; To write to one of the four registers on MMC1, write bits 0 through
; 3 to D0 of any mapper port address ($8000-$FFFF), then write bit 4
; to D0 at the correct address (e.g. $E000-$FFFF).
; The typical sequence is sta lsr sta lsr sta lsr sta lsr sta.
   
.proc setPRGBank
  sta lastPRGBank
  .repeat 4
    sta $E000
    lsr a
  .endrepeat
  sta $E000
  rts
.endproc


it now uses this one:

Code:
.segment "CODE"
.res $2000
bankToGoToTable: ; Ends up at $E16B
; The first byte in a row is the bank-to-go-to
; Subsequent bytes are that value shifted left
; Table is eight bytes wide so ROL ROL ROL gets from
; bank-to-go-to to table-byte-offset
   .byt $00, $00, $00, $00, $00, $00, $00, $00
   .byt $01, $00, $00, $00, $00, $00, $00, $00
   .byt $02, $01, $00, $00, $00, $00, $00, $00
   .byt $03, $01, $00, $00, $00, $00, $00, $00
   .byt $04, $02, $01, $00, $00, $00, $00, $00
   .byt $05, $02, $01, $00, $00, $00, $00, $00
   .byt $06, $03, $01, $00, $00, $00, $00, $00
   .byt $07, $03, $01, $00, $00, $00, $00, $00
   .byt $08, $04, $02, $01, $00, $00, $00, $00
   .byt $09, $04, $02, $01, $00, $00, $00, $00
   .byt $0a, $05, $02, $01, $00, $00, $00, $00
   .byt $0b, $05, $02, $01, $00, $00, $00, $00
   .byt $0c, $06, $03, $01, $00, $00, $00, $00
   .byt $0d, $06, $03, $01, $00, $00, $00, $00
   .byt $0e, $07, $03, $01, $00, $00, $00, $00
   .byt $0f, $07, $03, $01, $00, $00, $00, $00
   
.proc setPRGBank
  sta lastPRGBank
   STX savedX ;we're borrowing X so save it
   CLC
   ROL
   ROL
   ROL
   TAX
   INC bankToGoToTable,X
   INX
   INC bankToGoToTable,X
   INX
   INC bankToGoToTable,X
   INX
   INC bankToGoToTable,X
   INX
   INC bankToGoToTable,X
   LDX savedX
  rts
.endproc


In my emulator I get the usual Tepples character that I can move left or right if I set my MMC1 code to trigger on the first write in a two-write sequence and ignore the second write. I get nothing but bluescreen if the MMC1 code uses both writes.

by on (#91365)
Well I'm embarassed 'cause I was going to test it, I found my old devcard but my EPROM programmer somehow disappeared completely from my house for some mysterious reason.

So until I find it again I can't test it... sorry.

by on (#91366)
cpow wrote:
In my emulator I get the usual Tepples character that I can move left or right if I set my MMC1 code to trigger on the first write in a two-write sequence and ignore the second write. I get nothing but bluescreen if the MMC1 code uses both writes.


Is there a reason it doesn't work in other emulators? I just get blue screen in fceux and nestopia says CPU jam. Where's the original test rom? I couldn't find it anywhere...

I get the impression we're testing something that is already well known. That the MMC1 ignores the second write with instructions like INC. So are we not just testing something that is supposedly already known? Maybe I'm missing something.

One question I had when coding up my MMC1 in verilog was if it ignores the second write for ALL 5 bits. Because the sequence of operations is different for the last bit when the shift register gets loaded into the destination register. So it's conceivable that it might not ignore a second write when the second write is the 5th bit. In my design atleast you would have to add extra logic to stop a double write on the 4->5th bit, and 5th->1st bit. From what I understand from disch's doc most game's roms only use INC to reset the shift register, but perpaps that's not the case. Your test rom appears to be checking for double writes on all 5 bits.

Either way I want to test this and other things out on the original MMC1 once I get things together. But it would be nice if the Rom did something other than blue screen if both writes are being acknowledged. That way things like connectivity could be verified and prove that the issue isn't wiring or something.

by on (#91367)
infiniteneslives wrote:
Is there a reason it doesn't work in other emulators? I just get blue screen in fceux and nestopia says CPU jam. Where's the original test rom? I couldn't find it anywhere...

The original test ROM is available as a NESICIDE project here. It's the Tepples-SNROM.tar.bz2 package.

Nintendulator and Nestopia both indicate CPU jam, which I assume means KIL opcode execution, which probably means the bank-switch didn't work out right. I didn't make the ROM to make it pass in emulators.

infiniteneslives wrote:
I get the impression we're testing something that is already well known.

You'd think so but then...you have a lot of questions... :D

infiniteneslives wrote:
One question I had when coding up my MMC1 in verilog was if it ignores the second write for ALL 5 bits. Because the sequence of operations is different for the last bit when the shift register gets loaded into the destination register. So it's conceivable that it might not ignore a second write when the second write is the 5th bit. In my design atleast you would have to add extra logic to stop a double write on the 4->5th bit, and 5th->1st bit. From what I understand from disch's doc most game's roms only use INC to reset the shift register, but perpaps that's not the case. Your test rom appears to be checking for double writes on all 5 bits.

I admit it's a bit esoteric, yes.

infiniteneslives wrote:
Either way I want to test this and other things out on the original MMC1 once I get things together. But it would be nice if the Rom did something other than blue screen if both writes are being acknowledged. That way things like connectivity could be verified and prove that the issue isn't wiring or something.


I'll look into putting some code in each bank that dumps the bank number to the screen somewhere.

by on (#91368)
My ROM dumps the bank number to the screen it boots in, and tests each bank. Cpow, you can just link to what I sent you. It should help some. Although I will admit, the way I did a start up code for each bank wasn't set up for changing...

by on (#91369)
3gengames wrote:
My ROM dumps the bank number to the screen it boots in, and tests each bank. Cpow, you can just link to what I sent you. It should help some. Although I will admit, the way I did a start up code for each bank wasn't set up for changing...


My Windows pulled a muscle and decided it needed to reboot so I've since lost what you sent. Sorry.

by on (#91370)
Here it is for anyone interested in it. It fails in (Possibly) NESICIDE, Nintendulator.

http://www.mediafire.com/download.php?2jbhofw89p3dmuo

by on (#91373)
Is this good or bad?

Image

by on (#91374)
Looks OK here.

by on (#91378)
Good to me, although it should maybe have wram disableable. But that means all the banks are switching in so more than likely everything is A okay. Run in nintendoulator to see what happens when you emulate incorrectly. It doesn't use any INC's in the ROM to reset the mapper or anything, but those tests could be added.

by on (#91387)
Okay so I've got a basic test rig setup to toggle individual pins on the MMC1 and check it's response.

I'm using my kazzo hardware with my own version of the firmware. I basically write up a binary file of how I want the pins to change, (control, address, data, control, repeat). Then I program it quickly to the kazzo the same way I program files to the NESDEV1. So it's super quick to modify the test sequence on my PC and then run the MMC1 through the sequence. All I do is hit save in the hex editor and click write in my programmer app, and it's stimulating the MMC1. I can quickly apply a lengthy sequence without operating anything or single step each 'command step' with a toggle switch. Right now I'm just sensing outputs with a scope. But I might wire up some leds or something so I can see things easily.

I played around with a little bit already doing some basic stuff. I'm running a MMC1B1 off of a SEROM board from Tetris.

(EDIT: removed some wrong stuff about prg bank at startup)

Over the next couple days I'll try out applying different sequences of signals. Should be able to find out things like if the double write has anything to do with PRG R/W staying low, and what things are being clocked by etc. Let me know if there is anything specific you'd like me to test out. I'll be testing out the double writes first.

by on (#91512)
Well I got my little test rig set up and checked some things and found some interesting stuff.

So since I was able to do some testing that isn't possible I was able to peer deeper into the MMC1 and figure out how some things REALLY work. I'll discuss what's relevant to the post, but plan to implement more detail post with my official MMC1 design as accurate as it can be from an external standpoint.

The thing I was checking mostly was the double writes. So it appears that in ANY consecutive sequence of writes (to anywhere) ONLY the FIRST write is acknowledged. So if you do something like write to the ROM once, then write to any address for multiple cycles ALL subsequent writes are ignored. Also if you toggle PRG R/W between writes it will still ignore subsequent writes (nothing is being clocked by PRG R/W. So as for your test rom it would appear that my original guess should be right. I'll get it burned on a EEPROM and verify soon though.

Interestingly enough if you don't clock M2 the subsequent writes aren't ignored. They are all accepted.

So this doesn't matter much as far as emulation goes because you can't write more than 2 clock cycles in a row. Nor can you operate without M2 cycling. But from what I can see it appears that there is a state machine that is clocked by M2. And it will only allow writes if the previous cycle was had PRG R/W high. And so if you don't clock the statemachine with M2 then it's stuck in "allow writes" state and will accept consecutive writes.

But like I've said none of this matters much for emulator or hardware emulation of the MMC1 because it's impossible to do the things I did when running/emulating the NES. But it does answer my question to as whether or not Nintendo ignores the second write intentionally. Based on what I found it's pretty obvious they did, and I'm pretty sure I know how they did it. So while it the knowledge might not be of much value it was a fun puzzle to solve and my design wasn't too far off from the original :)

I still have a few things to check before I'm certain about everything but so far that's what makes sense to me.