In Michael Martin's NES 101 tutorial, we find the following comment:
"; Actual program code. We only have one PRG-ROM chip here, so the
; origin is $C000."
I've been basing most of my experiments on his demo, so I've left the .org $c000 in there so far. Now that I want to learn about how to use memory mappers, I'm re-reading a lot of information on the NES memory map.
From what I understand, everything at $8000 is mirrored at $c000 (with no mapper). Thus, I would expect to be able to .org my code at either location without modifying anything else---however this doesn't seem to be the case in practice, only $c000 works. Why is this?
.org at $8000 should work. However remember that the vector table needs to be put at $FFFA. If you have the following code:
Code:
.org $8000
;; your program here
.pad $FFFA
;; your vector table here
This probably requires you have 2 16K PRG banks, Because if you only have one, the code might get cut-off after $C000 (and thus the vector table will be omitted from your final binary -- causing it to fail to start).
If you want to try it out with just 1 16K bank, you can "trick" the assembler by putting the vector table at $BFFA (since that will be mirrored at $FFFA by the emulator/cartridge).
Thanks Disch! That makes perfect sense. I guess it hadn't occurred to me that .pad $FFFA goes beyond the 16k that would start at $8000. Now I think I understand why some mappers can "fix" a 16k prg block at either $8000 or $C000---this is so you can basically have 32k for game code, correct? So if you have code at $8000, it should just keep on executing right past the block boundary, since when the CPU accesses anything in the second PRG block, the mapper will "override" the mirroring that normally occurs?
PRG-ROM mirroring is not that common, because very few games have only 16KB. Anyway, the CPU is not aware of any mirroring, it will try to execute whatever the PC points to in the 64KB it sees, be it RAM, ROM, registers, whatever. AFAIK, it will even wrap around from $FFFF to $0000 if you let it.
Mappers that "fix" a bank of memory to the end of the addressing space do it so that the programmer can have a place to safely store all the bankswitching logic and the interrupt vectors. In programs that use 32KB bankswitching, the reset code and the vectors must be replicated across all the banks, because you never know which one will be mapped in when execution starts. All bankswitching routines and tables will most likely need to be replicated too.