Then there's this bit that I'm trying to wrap my head around:
Code: Select all
txs ; set the stack pointer
stx $8000 ; reset the mapper
jmp reset ; must be in $C000-$FFED
.addr nmiHandler, reset_stub, irqHandler
So, let me see if I understand what's happening here.
First off, it says that with some versions of this mapper, the end of the program is switchable. That makes me think that any bank which could be at the end of the program needs to include addresses for NMI, RESET, and IRQ starting at $FFFA. Is that correct?
That is correct.
Then, if your program changes, the instructions for your vectors need to be located at the same addresses, right?
At reset, the CPU begins executing from the address stored at $FFFC. This is all that matters to the CPU. The reset vector is whatever two bytes happen to be stored $FFFC at reset/poweron. The NMI vector is whatever two bytes happen to be stored at $FFFA when the NMI fires. The IRQ vector is whatever two bytes happen to be stored at $FFFE when an IRQ fires.
be a different two bytes stored at $FFFC in every bank. It's better for consistency that it's always the same, but it doesn't need
to be that way. The CPU just reads the two bytes and starts executing from that address.
So it's saying to put the above code at the last 16 bytes of a bank. So that would be like, $BFF0, right?
Well... no and yes. Consider a bank that you .org to $8000-$BFFF. That bank can still be swapped to occupy $C000-$FFFF instead. So... yes, while code mapped to $BFF0 could end up there, you really want it in $FFF0.
The idea is to have that setup code in a part of the ROM that can't possibly be swapped "out of sync" with the vector the CPU starts executing from which will always be whatever address is at $FFFC. Using the knowledge that the reset_stub will be with a vector in the same bank, regardless of which bank that is, allows you to safely initialize the mapper to a known state using the reset_stub. Then your program can run normally.
Why does it only tell you to put the reset vector at the end of each switchable bank? Why not NMI and IRQ as well? Wouldn't those addresses need to stay the same as the banks switch?
I don't see where you're seeing to only put the reset vector. Yes, you should have a valid IRQ and NMI vector as well. The jmp reset in the example code
is not a vector at all. The reset vector is reset_stub, and the CPU has already begun executing from there by the time jmp reset is executed. reset is just a label name. It could be jmp initialize or jmp anywhereelse you wanted to go after you knew the mapper was in a known state.
I think you're getting confused by the labels. The CPU has no knowledge of what you called reset_stub or reset or anything. It only knows what address is at $FFFC. you could call "reset_stub" "homersimpson" instead, and "reset" "supermario" and the code could still work the same. It's not like C where execution starts at a given named thing. (main.)
Also it says that writing %11111111 to $8000 will reset the mapper but I don't understand why. Is there a document on the mapper instructions, particularly for MMC3?
You can read about MMC1 here
and you can read about MMC3 here
I see now that the above code isn't a reset, but a reset stub, and I'm even more confused.
All the CPU does when it starts is go to the address stored at $FFFC. In the example code, that address is reset_stub. The code below reset_stub just runs normally after reset.
First off, wouldn't NMI, RESET, and IRQ have to be .org-ed to the same addresses in each bank?
Yes. As stated above, all the CPU does is read from the address at $FFFC at reset. It will treat whatever two bytes are there as the address to start executing from, so no other kind of thing should occupy that space.
Second, I don't really get the purpose for the above code now that I realize it's not actually the reset vector. When is this reset_stub supposed to be called? Why does it disable interrupts and set the stack to $FF when this is going to happen again anyway when it JMPs to reset?
The vector is just the address at $FFFC. The CPU starts executing from whatever address is at $FFFC, always. After that, the program runs normally. So it wouldn't disable interrupts or set the stack to $FF again unless you also put those things under the reset label in the example. (And you shouldn't, because as you said it was already done.)
So in the example case, reset_stub is the very first thing executed, always.
I thought I was ready to understand this and start integrating mapper features but I need a little help. I don't even think I'm going to need to switch program banks but I'd like to understand the concepts.
I don't think it's too worth thinking about mappers until your game is about to be larger than 24 KB. At that point you can still make it larger without a mapper, but if you plan to use a mapper that's when you have to being to plan how you'll lay out your banks so you don't end up relying on too much of it being in a fixed bank.
Edit: I should note that some mappers start with a known state, in which case you have to worry less about this stuff. But it is very worth understanding even if you use those mappers.