I'm trying to convert my current project from CNROM to TxROM using MMC3 mapping. I'm using C scripts for software development, so I know there are things I need to change in my CC65 config file and init code (crt0.s) in order to accomplish the conversion, but I'm not totally sure what to change.
If anyone can help, or provide some example files that use MMC3 mapping, I would greatly appreciate it!
You shouldn't need to change the config file if all you need is to get a CNROM program working with the MMC3, because the ROM structure can remain the same. You only need to make sure that the init code is in the fixed bank ($E000-$FFFF), and that this code will map the remaining banks in the correct order before running the test of the program. In addition to that, CHR bankswitch operations will now need to change 6 mapper registers, instead of just one.
This code must run in $E000-$FFFF before any code outside $E000-$FFFF runs:
Code:
ldx #$06
ldy #$00
stx $8000 ; Command 6: Into PRG $8000-$9FFF...
sty $8001 ; switch PRG ROM $0000-$1FFF
sty $E000 ; Disable interval timer IRQ
sty $A001 ; Select vertical mirroring
inx
iny
stx $8000 ; Command 7: Into PRG $A000-$BFFF...
sty $8001 ; switch PRG ROM $2000-$3FFF
sty $A001 ; Select horizontal mirroring
Use only one of the mirroring select lines; comment out the other.
Old bank switch code:
Code:
.proc cnrom_switch_tile_bank
tay
sta identity_table,y
rts
.endproc
New bank switch code:
Code:
;;
; Writes bank*8 + [1, 3, 4, 5, 6, 7] to the MMC3 CHR bank registers
.proc mmc3_switch_tile_bank
asl a
asl a
sec
rol a ; A = bank number * 8 + 1
sec ; first ADC #1 will go to 3, not 2
ldy #0
@loop:
sty $8000
sta $8001
iny
adc #1
cpy #6
bcc loop
rts
.endproc
Thanks for the replies!
Don't I need to change the .cfg file though so that my PRG is divided into 8kb chunks?
No, because as long as the init code is guaranteed to be in the fixed bank, it will take care of mapping all 4 banks linearly so they look like a single 32KB. Unless you plan on extending the ROM past 32KB and actually make use of MMC3 bankswitching.
Interesting, okay I added the posted init code and everything still compiles and runs as it did before. Is there any way I can verify that the ROM type has been successfully converted from CN to Tk? (using FCEUX emulator, version 2.1.5)
Ah actually I did find something in the message log labeled "Mapper name". And it does say MMC3!
Oh but now most of the in-game graphics are messed up. Probably not bank-switching correctly....
Looks like I fixed it, thanks for the help guys!
Summary of the solution:
1) Had to go into the .cfg file and change the mapper number to 4
2) Had to add the following to my init code:
ldx #$06
ldy #$00
stx $8000 ; Command 6: Into PRG $8000-$9FFF...
sty $8001 ; switch PRG ROM $0000-$1FFF
sty $E000 ; Disable interval timer IRQ
;sty $A001 ; Select vertical mirroring
inx
iny
stx $8000 ; Command 7: Into PRG $A000-$BFFF...
sty $8001 ; switch PRG ROM $2000-$3FFF
sty $A001 ; Select horizontal mirroring
3) had to use the following to switch between CHR banks:
asl a
asl a
sec
rol a ; A = bank number * 8 + 1
sec ; first ADC #1 will go to 3, not 2
ldy #0
loop:
sty $8000
sta $8001
iny
adc #1
cpy #6
bcc loop
rts
Note: credit for the above code goes to tepples (I just copied and pasted it from his post) Thanks!
How many PRG banks of 8k are you going to use?
You should test on a flash cartridge/real hardware, and especially in an emulator that isn't FCEUX.
Also, I believe MMC3 banks need to be initialized near start-up, or you can't guarantee which bank is mapped to which addresses...especially if the user hits 'reset'.
Currently using 4 8kb PRG banks.
And thanks for the advice, I'll try to test it on some other emulators now, and will probably have someone with more knowledge and skill than me to test it on some hardware. Any emulators in particular I should test on?
Looks like it works fine on VirtuaNES v0.97, but not on Nestopia v1.40 (for some reason the video quickly cuts out, but it seems to do this even with the nes file I have from before I started the CN to MMC3 conversion so I don't think it's related to that).
Currently, I prefer Nestopia with Options/Video/Filter=NTSC
I'm not sure which emulator does MMC3 most accurately.
Can you afford a
PowerPak CF to NES adapter (135 USD) or an
EverDrive-N8 SD to NES adapter (127 USD*) to use with your NES?
* This price includes a black shell, basic labels, and standard Phillips screws.
No emulators currently randomize the MMC3's registers on boot, so it's easy to accidentally rely on undefined state. ( thefox has said that he wants to add this to NintendulatorDX, but it's not present yet. )
If you're not using IRQs, there's very little else to be accurate about. If you are, Nintendulator(/DX) is the only debugging emulator that I'd trust to be remotely accurate. Without a debugger, I'd probably trust puNES to be the most accurate.
I am definitely interested in getting something like that, so thanks for the links!
Not sure why the game doesn't run well on Nestopia though; just cuts out and gives a black screen right away...
CNROM's about as simple as you can imagine; if reproduction hardware of it is not working, I have to wonder if something else funny is going on...
Care to venture a guess as to what?
Also, the tried the game on the puNES emulator with these results:
1) CNROM version runs, but not well: lags a bunch at certain times and sprites sometimes flicker heavily
2)MMC3 version just gives a black screen from the start
Talking just about hardware, not emulators...
CNROM most often has so-called "bus conflicts", where the CPU and ROM will get in a fight when the CPU's trying to write a value to the mapper register. If you don't have an explicit region in memory of an array of the same values that you'll want to write to the mapper register, try adding one.
I don't know whether cc65 emit writes to locations in ROM. If it does, instead of a current *memory = bank; you'd instead use something like BusConflictPreventer[bank]=bank; where BusConflictPreventer[4]={0,1,2,3}.
the_doctor wrote:
I'm told that the game runs well on the Everdrive, but not on IC chips (hence the request to convert to TxROM, which should apparently solve that dilemma).
This suggestion on how to fix the problem doesn't make any sense... "Hey, your game, which uses one of the simplest mappers there is, doesn't work well on hardware, so try using a more complex mapper."
99% of the time, the reason for games working well on a Flash cart but not on a standalone cartridge are bugs in the initialization procedure. You don't see anything wrong when using Flash carts because they initialize the system for you before transferring control to the game, but if your game is known to fail on a standalone cartridge, switching mappers is hardly the way to fix it. Instead, make sure your program does the following things during the initialization step:
1- Disable interrupts using the SEI instruction;
2- After writing 0 to $2000 and $2001 to disable rendering and NMIs, wait 2 or so frames before using any PPU registers;
3- Disable both APU frame IRQs and DMC IRQs;
4- Initialize all mapper registers (specially those related to PRG mapping and IRQs) to a known state;
In addition to that, make sure that your code can safely handle NMIs (and IRQs, if you're using them) without corrupting variables and registers used by the main thread. Remember to always save and restore any registers or variables that are changed in the interrupt handlers that the main thread isn't expecting to be changed (e.g. It's fine to empty VRAM buffers if the game engine signaled the buffers as ready to be dumped, otherwise you could be corrupting data that the engine was still working on).
I strongly suggest you fix the CNROM version before doing anything else, because, like I said before and will say again,
changing the mapper is not a way to fix bugs, specially when the original mapper doesn't even do PRG-ROM swapping, meaning the mapper can't possibly be responsible for crashes. Once you get the CNROM version work, then you can proceed to convert it to MMC3, if your publisher still wants you to do it for some other reason.
tokumaru wrote:
the_doctor wrote:
I'm told that the game runs well on the Everdrive, but not on IC chips (hence the request to convert to TxROM, which should apparently solve that dilemma).
This suggestion on how to fix the problem doesn't make any sense... "Hey, your game, which uses one of the simplest mappers there is, doesn't work well on hardware, so try using a more complex mapper."
Unless the manufacturer has an MMC3 board but not a discrete board. This might be the case if, for example, someone's using Coolboy carts as donors.
Quote:
specially when the original mapper doesn't even do PRG-ROM swapping, meaning the mapper can't possibly be responsible for crashes.
That might depend on whether sprite 0 is at fault.
tepples wrote:
Unless the manufacturer has an MMC3 board but not a discrete board.
He did say the game didn't work "on IC chips" before the mapper change, so I assume it was tested with the proper IC chips.
Quote:
That might depend on whether sprite 0 is at fault.
Still not the mapper's fault. Unless you're blaming the mapper for not being able to generate IRQs and "forcing" you to use sprite 0 hits, but that'd like going into a McDonald's to complain about the taste of the hot dog you bought outside... They don't even make hot dogs, it's not their fault you decided to eat something they don't make in there!
By "mapper causing a sprite 0 routine to crash", I was referring to a sprite 0 hit failing because the wrong bank was selected. This wrong bank may be due to an uninitialized bank register or a bus conflict.
Oh, I see... Yeah, I guess you found the one way the misuse of CNROM could crash a program!
I can fill some of the background: the game in question is too big for standard CNROM, requiring more than 32kb of CHR. As such, it doesn't run on standard CNROM components, requiring either different components like mentioned in the nesdev wiki, or conversion to a mapper allowing larger CHR total.
A too-large CNROM works in most emulators, and apparently in some flashcarts too.
There's also hardware precedent for use of CNROM with more than 32 KiB of CHR ROM. The Panesian games (Hot Slots, Peek-a-Boo Poker, and Bubble Bath Babes) use such an oversize CNROM.
If you take the CNROM version and write the CHR bank number to the high nibble (bits 7-4) instead of the low nibble (bits 3-0), you should be able to run your game on a Color Dreams board (mapper 11). But that board's CIC stunner is software-controlled, so you'll have to either copy the CIC stunner init code or replace its stun circuit with a CIClone.
calima wrote:
I can fill some of the background: the game in question is too big for standard CNROM, requiring more than 32kb of CHR.
I see. It would still be a good idea to verify what's causing the program to crash before moving forward.
tepples wrote:
Color Dreams
The Color Dreams mapper is indeed a much more logical upgrade from CNROM than the MMC3. Maybe the publisher isn't willing to rewire boards, but has MMC3 boards available.
There is no crash that I know of, it's just corrupted graphics when trying to access banks over 32kb on a hw board. If you mean the puNES comment, I guess that's the emulator's bug, since the game was tested ok on hardware.
Quote:
Maybe the publisher isn't willing to rewire boards, but has MMC3 boards available.
Indeed, the conversion was to be easier to manufacture than a modified CNROM.
Hmm well all debates about which mapper should be used aside, it seems that I've already made a version of this project that uses MMC3. So now I'm focused on what is wrong in my initialization procedure.
Tokumaru, I will try to follow your advice on that matter, but I am wondering if you (or any other kind person) would mind going into a little more detail with that, and perhaps posting some example code?
Example initialization code? I believe there's one in the wiki, but it probably doesn't initialize the MMC3. Why don't you post what you have so we can take a look? It's still possible that the problem is elsewhere, though.
Sure, I'll post what I have so far, thanks!
In the mean time, it seems that what's causing trouble are the ppu_wait_frame() and set_vram_update() functions (I'm using Shiru's nes library). Any idea why those particular functions would cause any trouble / not work correctly?
Are these library functions in "Shiru's nes library" located within $E000-$FFFF or outside it? MMC3 registers 6 and 7 must be initialized before any code outside $E000-$FFFF can be called.
Eventually, if we cannot walk you through troubleshooting this mapper hack, we may have to arrange for you to securely provide a copy of the ROM for one of us to step through.
There is an MMC3 example code on my blog...
https://nesdoug.com/2016/01/15/24-mmc3- ... hing-irqs/Unfortunately, I haven't tested it on real hardware, so I can't confirm its accuracy.
The game works fine on FCEUX but crashes almost immediately on Nestopia and when run on hardware. The game loads for a brief second, but then shows a colored horizontal bar, and cuts the display to black.
Any help is very much appreciated.
the_doctor wrote:
However, if you'd prefer to help without looking at the project files, I'll tell you this: the game works fine on FCEUX but crashes almost immediately on Nestopia and when run on hardware. The game loads for a brief second, but then shows a colored horizontal bar, and cuts the display to black.
Have you tried Nintendulator? It would be very helpful if the game crashed in an emulator that offers debugging tools, which Nintendulator does, in addition to belonging in the group of accurate emulators.
Or you could
make like calima: run the ROM in one of the accurate emulators that does not offer its own debugger and debug the emulator itself in a native PC debugger.
Here's what the debugger in Nintendulator gave me when I loaded the ROM (only copied the lines that appeared after loading the ROM) :
iNES ROM image loaded: mapper 3 (CNROM) - Fully supported!
PRG: 32KB; CHR: 32KB
Flags: Vertical mirroring
Loaded successfully!
Performing hard reset...
Reset complete.
That's just a status window, it doesn't say anything useful (besides stating that the ROM is valid). The useful tools are in the "disassembly" window (Debug -> Disassembly). There you can step through the program to see if it's behaving as intended.
Is the game not working in Nintendulator?
You said your game was oversize. If so, "CHR: 32KB" in the status window looks suspect.
No in Nintendulator it does the same thing as Nestopia (flashes for a second when it loads, then the screen cuts to black). Here's an image of what the dissasembly debugger looks like:
I'm afraid that screenshot is not at all helpful.
Try setting a breakpoint for execution at $8000. It should only do it once, at the start of the game. If it's doing it more than once, then your game is resetting. Reason it might reset, if it runs into a $00 BRK instruction.
I haven't used it, but maybe you could also hit that 'start log' button. I think it does a trace log. You could see where the code was right before it reset.
Hmm, adding the breakpoint at $8000 doesn't seem to change the behavior at all. The game still crashes almost immediately, and the debugger window stays looking like the screenshot, unless i hit 'step' in which case it will step through the instructions as expected but gets stuck bouncing between 8011 and 8014 (as seen in the screenshot) forever, it seems like, unless I hit 'run' first.
Not sure what the 'start log' button does. Doesn't seem to do much of anything.
the_doctor wrote:
Hmm, adding the breakpoint at $8000 doesn't seem to change the behavior at all. The game still crashes almost immediately, and the debugger window stays looking like the screenshot, unless i hit 'step' in which case it will step through the instructions as expected
IIRC, Nintendulator starts paused by default, so that must be the reason stepping starts at $8000 unless you hit "run".
Quote:
but gets stuck bouncing between 8011 and 8014 (as seen in the screenshot) forever
Not forever, just until vblank starts, which can be a really long time... something like 30.000 cycles, which means that the loop could repeat over 4.200 times before the PC goes past $8014. And then there's another vblank wait!
Whenever there's a tedious thing like this going on, you can set a breakpoint for the address that comes after the tedious part (in this case, $801B) and hit "run", and then you can keep stepping. If the program doesn't even display anything, the crash should be happening fairly early, so you might want to keep stepping. If you run into something slow like memory being cleared or name tables being drawn, just set a breakpoint right after the slow part. If by any chance the breakpoint doesn't trigger, you'll have a clue of where things might be going wrong.
Quote:
Not sure what the 'start log' button does. Doesn't seem to do much of anything.
It probably writes a log of every executed instruction to a file. This could be better than stepping in your case, since the program crashes soon after starting up.
You'd want to Start Log, then Run until it "crashes", then Stop Log. Also note that that the log files will get very large, very fast, so you don't want to run it longer than you have to. You can find the actual log files from File -> Browse Save Files.
I didn't find the option under File, but I did find where the logs are stored in the AppData folder. Currently trying to upload a debug file I collected as suggested, but its almost 24MB
Couldn't get the file to upload here
That's pretty big. Did you stop logging as soon as the program crashes? Anyway, log files contain a lot of repeated text, so they should compress fairly well. Probably not enough to attach here, but easier to upload to some generic file sharing service.
EDIT: Will check the file when I'm on my PC.
the_doctor wrote:
I didn't find the option under File
Probably using a somewhat old version of the emulator.
the_doctor wrote:
Currently trying to upload a debug file I collected as suggested, but its almost 24MB
Zipping will probably decrease the size considerably. But preferably you would be the one looking at the file, not us.
Yeah I stopped logging right after the screen went black (when as far as I can tell, it shouldn't, so it seems to constitute a 'crash')
I'm using v0.970 of the emulator (is that old?).
So should I upload a zip or just look at it the debug file myself? What should I be looking for? And what should I even use to open it?
In any case, here's the compressed zip if anyone's interested:
the_doctor wrote:
And what should I even use to open it?
It opens in Notepad++ just fine.
Your game is expecting RAM at $6000-$7FFF. That's why it doesn't work.
Maybe Nintendulator and Nestopia deliberately disable WRAM at $6000-$7FFF for mapper 3, since no CNROM board is known to have it, while FCEUX just leaves that RAM permanently enabled. If this is indeed the only problem, it won't affect the conversion to MMC3 at all, as long as the MMC3 board has the RAM chip installed.
I don't know if Nintendulator and Nestopia support NES 2.0, but if you want to get the game working you could try using an NES 2.0 header to specify that the game needs 8KB of WRAM. If they don't support NES 2.0, maybe setting the battery bit will cause them to enable the RAM.
The default cc65 linker script seems to expect RAM at $6000; if your program can fit into less RAM, it would make hardware reproductions much more practical.
See also
viewtopic.php?t=13547
How can I make my program fit into less RAM?
Well, I guess the first question is whether just using calima's linker script worked for you. If it did, great, you're already done.
Are you declaring things as const, or are your data tables being copied to RAM when the game boots?
If you're using dramatically more state than fits into the NES's internal 2 KiB of RAM, there's probably no point in fighting with it...
Otherwise, you can try to revise your data structures so that you can directly read values from ROM instead of RAM, load less into RAM at a time (from compressed data in ROM) ...
Hopefully other people have other suggestions.
Maybe he wants a more literal answer...
Change .cfg file definitions for BSS from...
RAM: start = $6000, size = $2000, define = yes;
To...
RAM: start = $0300, size = $0500, define = yes;
Or, really, since your programming in C...
RAM: start = $0300, size = $0400, define = yes;
With the C stack going from $700-7ff.
(Why not start = $200, you might ask? People usually put the sprite updates at $200-2ff and use a DMA to push it to the sprite memory).
If it helps, here's my CFG file for CNROM. Try to use something simmilar with regards to RAM segment definition. If it works, your problem is solved.
Code:
MEMORY {
ZP: start = $00, size = $100, type = rw, define = yes;
HEADER: start = $0, size = $10, file = %O ,fill = yes;
PRG: start = $8000, size = $7f00, file = %O ,fill = yes, define = yes;
DMC: start = $ff00, size = $fa, file = %O, fill = yes;
VECTORS: start = $fffa, size = $6, file = %O, fill = yes;
CHR: start = $0000, size = $8000, file = %O, fill = yes;
RAM: start = $0300, size = $0500, define = yes;
}
SEGMENTS {
HEADER: load = HEADER, type = ro;
STARTUP: load = PRG, type = ro, define = yes;
LOWCODE: load = PRG, type = ro, optional = yes;
INIT: load = PRG, type = ro, define = yes, optional = yes;
CODE: load = PRG, type = ro, define = yes;
RODATA: load = PRG, type = ro, define = yes;
DATA: load = PRG, run = RAM, type = rw, define = yes;
VECTORS: load = VECTORS, type = rw;
SAMPLES: load = DMC, type = rw;
CHARS: load = CHR, type = rw;
BSS: load = RAM, type = bss, define = yes;
HEAP: load = RAM, type = bss, optional = yes;
ZEROPAGE: load = ZP, type = zp;
}
FEATURES {
CONDES: segment = INIT,
type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__;
CONDES: type = interruptor,
segment = RODATA,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__;
}
SYMBOLS {
__STACKSIZE__ = $0500; # 5 pages stack
}
tokumaru wrote:
I don't know if Nintendulator and Nestopia support NES 2.0, but if you want to get the game working you could try using an NES 2.0 header to specify that the game needs 8KB of WRAM. If they don't support NES 2.0, maybe setting the battery bit will cause them to enable the RAM.
Nintendulator doesn't support WRAM on any common discrete boards except NROM, and in NROM it only adds WRAM if the battery bit is set. I haven't tried CNROM specifically but I don't think FCEUX gives it WRAM either, since it didn't for UNROM.