Well, the main question I have right now, is this okay to boot safely into a program from MMC1 to all situations? And DMC fires IRQ's, correct? FCEUX doesn't boot into multiple banks, but this is correct way to do it right amd should work? In A I return the bank number because this is going to be a bank test for an MMC1 cart later. [hopefully today] It's taken me a little to write this because going all out to get a program into X bytes is something I'm not used to yet.
Thanks!
Also, just making sure, the fixed bank is always the last bank on the chip, correct?
Code:
.bank 31
.org $FFF0
.db $00
MMC1GlitchBoot31:
SEI
LDA #$9F
RTIBank31:
STA $8040
JMP [$FFFC]
.dw RTIBank31+1 ;NMI
.dw MMC1GlitchBoot31 ;Reset
.dw RTIBank31+1 ;IRQ
Also, each bank is 8KB and we're going to use a 256K ROM for this. FFF0 is 0 in all banks, nothing useful can be used for it really.
Writing any value from $80 through $FF to any address from $8000 through $FFFF will reset the MMC1. This changes the PRG mode to fixed last bank in $C000 and resets the 5-bit shift register.
It shouldn't be JMP [$FFFC]; it should be JMP to the real entry point of your program. Otherwise, you'll end up in an infinite loop executing MMC1GlitchBoot31 over and over again because $FFFC still points at MMC1GlitchBoot31.
It's also wise to have these vectors at the end of each 16 KiB bank of the program, just in case you get reset while using 32 KiB mode or fixed-$8000 mode, or just in case you end up running on an MMC1 revision with poorly defined initial PRG bank values. See
my SGROM/SNROM template for how to get ca65 to do this. I haven't used NESASM, but I
think you'll probably have to repeat that code in every odd numbered half-bank (1, 3, 5, 7, ... 31).
DMC has three actions it can apply at the end of the waveform: stop ($00), repeat ($40), or stop and IRQ ($80). In stop and IRQ mode, the DMC will fire an interrupt eight samples (one byte) before playback ends, giving you a few hundred CPU cycles to set up the next sample for gapless playback. It won't trigger any IRQs unless you CLI (set minimum interrupt priority to 0) and actually put DMC in stop and IRQ mode, but I still wouldn't recommend handling NMI and IRQ from the same routine.
31 is the last bank, and yeah before you replied I changed it to an actual entry point for that bank, and it seems to work in FCEUX. But FCEUX doesn't open in other emulators. And yeah, I guess it could be taken out, but 16 bytes is an okay header size for me, nice and "round, I think I'll just keep it in all banks *just in case* the MMC1 didn't take the read and then the boot code could run again if it fails.
Yep, the 9F in bank 0 is $80, $81 in bank 1, etc. And yeah, 8KB sized banks is something I didn't take account for, then that means not needing it in all banks, so that should be $8F in the last bank and I need to change my code later for that.
Also, not that I'm relying on it for something, but is the reset state of the CPU the same as the cold boot state? Or would A,X,Y just all be unchanged?
Only bit 0 ($01) and bit 7 ($80) of the value written to $8000-$FFFF are ever used. If bit 7 is true, all other bits are ignored. So $9F, $81, and $8F all have the same effect.
You say having the actual entry point in the JMP fails in other emulators. What does it do if you step through it in Nintendulator?
It's not that it fails, it's just there because if I have more code I can swap banks without changing headers and also because it just seemed right to keep something there, so even if it has an undesired effect for any reason, it'll still work. And yeah, I'm at home now, I'll try it on all other emulators now to see if one boots in any bank.
And yep, that's how I understood it. That's why my load number to write to the MMC1 is also doubling as my "booted in bank" numbers for the main routine.
3gengames wrote:
Also, not that I'm relying on it for something, but is the reset state of the CPU the same as the cold boot state? Or would A,X,Y just all be unchanged?
The state of the CPU upon pressing the reset button, that is, the warm boot state, is whatever it was just before the reset line to the CPU went low. The registers A, X and Y are not affected by any of the interrupt lines or the reset line going low.
As far as A, X and Y being a known value in the warm boot state, that would depend on whether the CPU executes a JMP ($FFFC) or the reset line goes low. If a JMP ($FFFC) is performed, A X and Y could definitely contain a known value. A press of the reset button, however, could happen at any time, and with code executing incredibly fast by human standards (approximately 1,786,840 cycles in a second) the registers A X and Y will have to be taken as indeterminate.
doppelganger wrote:
A press of the reset button, however, could happen at any time
Unless you wrote a "please reset your console" message to the screen and entered a loop that doesn't alter the registers you'll want to check later, in order to tell if the player followed your instructions... =)
tokumaru wrote:
Unless you wrote a "please reset your console" message to the screen and entered a loop that doesn't alter the registers you'll want to check later, in order to tell if the player followed your instructions... =)
Then your game wouldn't run properly on some emulators or console revisions.
X-Men for Sega Genesis has exactly the situation you describe where the player needs to
press Reset to continue. On a Nomad, the game is unplayable past that point.
This isn't for anything like that, just trying [which has been done now and just needs a UI for my console] to record boot state, report to the user all info about the MMC1 boot, banks wired good, etc. I just got done with this program and it's pretty good. I'll post it later for people who want to test emulator parts with it when it's done. It's nothing big, I just kinda needed this for the project because never messing with MMC1, need to learn it inside and out, which has been done thanks to the info here.
And even if you had to reset to continue past something. Besides using a CPU location [Backup'd SRAM would be the logical place of choice, right?) if you did it just based on registers, then you *could* possibly reset at the perfect time and then have that cause them to skip past a majority of the game. I can assure you I'd never do this....that just....not well thought out, even though it'd probably never happen.
tepples wrote:
X-Men for Sega Genesis has exactly the situation you describe where the player needs to
press Reset to continue.
Cool, I never though this had actually been done! And I used to think that the second X-Men game was unusual, for dropping you directly into an action stage with a random character before even showing any sort of title screen or menu.
As for detecting resets,
Kirby's Adventure on the NES does it... Not for anything important I think, it just seems to skip the part that teaches how to draw Kirby.
Square seems to like detecting resets. In Final Fantasy games, they skip the story intro. In Rad Racer and 3D World Runner they also skip the title screen, and if you're in the ending you can press reset to watch the ending again !
They do that just by having some particular variable set to particular states, and they check those variables when booting. Note that those games does not clear the memory so this technique works.
Well, if anybody makes an MMC1 cart, this is what I was working on to learn how to use MMC1, my first mapper, and to test our cartridge we made. It works for now, but it's not final at all, I plan to add detection if SRAM is even there and then also condense it all into the last 16KB bank. It's no problem size-wise. I just have to put it together. Have fun. Source included and it's ready to compile, the rom is in program and all files should be self descriptive. Code will be comment with the later final versions.
http://www.2shared.com/file/EJYpZ4Tm/MM ... eader.html
I just ran that on the emulators NESICIDE and Nintendulator. It reports all banks bad except bank 0, and it boots in 0F so that's how it runs. Anybody know why it fails? Is it because of the indirect addressing to address the last write's location? This is the code it uses:
Code:
BankswitchControl:
LDY #$80
STY ZeroWrite
JMP MainBankSwitch
BankswitchCharacterLower:
LDY #$A0
STY ZeroWrite
JMP MainBankSwitch
BankswitchCharacterUpper:
LDY #$C0
STY ZeroWrite
JMP MainBankSwitch
BankswitchProgram:
LDY #$E0
STY ZeroWrite
MainBankSwitch:
AND #$1F
STA $8000
LSR A
STA $8000
LSR A
STA $8000
LSR A
STA $8000
LSR A
STA [Zero],Y
RTS
Also it should be noted the program works fine on an MMC1B2 that I've tested on real hardware without any problems at all. Also, one day or not I will probably also edit it to only show SRAM stuff if it's detected. For now, if you have no SRAM, just don't mind it.
I'd advise against using indirect or indexed addressing for writing to the MMC1 ports. Instead, I've seen most MMC1 games use separate subroutines that write to $8000, $A000, $C000, and $E000.
Are you using CHR ROM or CHR RAM?
CHR-RAM, 8KB.
And why would you advise against it? While I probably am going to separate them to write better equipped bank switching routines where it rewrites the least possible of the port, still, is there any reason for it to fail?
STA [d],y does a read then an immediate write to the same address, and it takes up zero page space. Two accesses so fast might confuse the MMC1. We already know the MMC1 gets confused when the CPU tries to INC a ROM address (as seen in one of the Back to the Future games).
If it's CHR RAM, you can ignore writes to $C000 entirely, and you can use $A000 just for controlling PRG RAM enable (SNROM) or extended PRG ROM/RAM bank switching (SOROM, SUROM, SXROM). If you're on SGROM (no PRG RAM), you probably don't even need to write to $A000. And if you never change the mirroring mode, and you use MMC1 just for the PRG RAM, you can setup $8000 and $A000 with unrolled code in your reset handler and leave $E000 as the only register that changes.
Hmm, why would the MMC1 be confused by a read to it then a write? Because doesn't recognize reads, right? And from what I am reading, INC is a RWM with two writes at the end when the first is garbage.
Code:
INC ABS:
Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
SLO, SRE, RLA, RRA, ISB, DCP)
# address R/W description
--- ------- --- ------------------------------------------
1 PC R fetch opcode, increment PC
2 PC R fetch address, increment PC
3 address R read from effective address
4 address W write the value back to effective address,
and do the operation on it
5 address W write the new value to effective address
Code:
STA [ZP],Y:
Indirect Indexed, thanks the fox!
Write instructions (STA, SHA)
# address R/W description
--- ----------- --- ------------------------------------------
1 PC R fetch opcode, increment PC
2 PC R fetch pointer address, increment PC
3 pointer R fetch effective address low
4 pointer+1 R fetch effective address high,
add Y to low byte of effective address
5 address+Y* R read from effective address,
fix high byte of effective address
6 address+Y W write to effective address
Notes: The effective address is always fetched from zero page,
i.e. the zero page boundary crossing is not handled.
* The high byte of the effective address may be invalid
at this time, i.e. it may be smaller by $100.
Or is this wrong?
The only thing the MMC1 does on reads is to transmit the enable signal to the PRG ROM, while on write it has to latch the value in the shift register, and if the write is the 5th to latch it in the actual pointed register. It's probably something in the shift register's decoding logic which prevent a write immediately after another to act... although we'd have to know the MMC1's blueprints to be sure.
There is no reason to know if indirect writes woud success or fail without trying.
Well the demo worked perfectly on FCEUX and on real hardware, and we've run it many many times without problems. We've shorted a data line and reset it and then it reported all the right errors for the upper mapped address lines. I'd like to try to get my father to make an MMC1 with an original MMC1 to see if that'd be a problem, I doubt it, but maybe that's something that the original does? Not sure, I'll try to test more later and report back.
About this "real hardware": Did you try it on an authentic MMC1, on a ReproPak MMC1, or on a PowerPak? If it works on FCEUX, and it works on a Nintendo MMC1 board (not retrousb.com products), then the other emulators have a defect.
MMC1B2 from an Ultima Exodus and it's worked 100% no problems on real hardware. NES CPU revision 7, although that shouldn't matter. I'll try to get my dad to make another test cart since I still don't have enough knowledge to make them as quick [Really isn't even a test cart, we're using an NES with the MMC1 board soldered to it as a development station for an NES project] but still, shouldn't matter, I'll try to get an MMC1 original board and replace the MMC1 with it and test it. But yes, MMC1B2 plus that program=100% functional. No retropak or emulated CPLD boards of any type.
It works fine for me in Nestopia, I don't see any problems. Looks exactly the same as in FCEUX. In Nintendulator it doesn't work for some reason.
BTW 3GG you took the wrong part out of the doc (indexed indirect, should be indirect indexed), here's the correct one.
Code:
Write instructions (STA, SHA)
# address R/W description
--- ----------- --- ------------------------------------------
1 PC R fetch opcode, increment PC
2 PC R fetch pointer address, increment PC
3 pointer R fetch effective address low
4 pointer+1 R fetch effective address high,
add Y to low byte of effective address
5 address+Y* R read from effective address,
fix high byte of effective address
6 address+Y W write to effective address
Notes: The effective address is always fetched from zero page,
i.e. the zero page boundary crossing is not handled.
* The high byte of the effective address may be invalid
at this time, i.e. it may be smaller by $100.
thefox wrote:
BTW 3GG you took the wrong part out of the doc (indexed indirect, should be indirect indexed)
It's kind of confusing to keep those i-words in order, which is why I usually just call these addressing modes "(d,x)" or "(d),y", as they are abbreviated in the opcode matrix on page 33 of the
65816 datasheet.
The problem is definitely in Nintendulator, from its MMC1 implementation:
Code:
// Ugly hack to get Bill & Ted's Excellent Video Game Adventure to run
// since the mapper interface currently doesn't expose a CPU timestamp
if (Reg != LastReg)
Latch = LatchPos = 0;
It seems like it doesn't support using a different register for the first four writes. That's a bit surprising, but I guess no commercial game did that.
Hmm, you'd think it'd be easier to just do it right, but still, whatever. Good to know it's not just me, also keep in mind NESICIDE does fail too which is what worried me more, I'll make sure he sees this though and he can be away of the bug.
And yeah, Bill and Ted uses it.
3gengames wrote:
Hmm, you'd think it'd be easier to just do it right, but still, whatever. Good to know it's not just me, also keep in mind NESICIDE does fail too which is what worried me more, I'll make sure he sees this though and he can be away of the bug.
And yeah, Bill and Ted uses it.
The comment explains why it wasn't easier: the mappers are DLLs, and to ignore writes that are too close to each other it'd need to know how many CPU cycles have elapsed since the last write, which it can't do because of a limitation in the mapper interface.
Also, Bill and Ted doesn't use the "use a different register for the first 4 writes than the 5th one" feature, but simply starts with an "INC foo" where foo points to $FF. So if the second write of the INC isn't ignored, a 0 is shifted in to the shift register right after the reset on the previous cycle, messing up the rest of the code which assumes the shift reg is empty.
Bump. New version created. New tons of stuff. Rewrote the check routines and stuff. I haven't had time to test on hardware, but I believe it should be fine. I've worked out all the bugs I injected via the emulator so there shouldn't be any problems with combinations of passes/fails. I hope this helps some people out. I'm going to test on an MMC1 cart I have soon to verify this is correct.
http://www.mediafire.com/?u7ukffnf6q08e0t