Friday, July 19, 2019

Pseudo-fixed bank

I wrote about bank switching a few years ago when working on Atari Anguna. The idea being that the 6502 (or worse, the 6507 which is the variant of 6502 in the Atari) can only address a certain amount of ROM memory, so larger cartridges use multiple "banks" where you can switch which bank of ROM you're using. Nintendo cartridges used the same technique, so bank switching is important on both consoles.

Many variants of cartridge hardware (commonly known as mappers) divide ROM space into 2 (or more) sections. One that always stays active, and a second section that you can switch. This is a lot easier than switching the entire ROM space at once, as you can control things from the fixed bank, and just load data or call routines from the swappable banks as needed.

The mapper that I'm using for Halcyon (GTROM) doesn't have a fixed bank. Like Atari Anguna, it swaps out the entire bank all at once. Which means that you have to have special code to call a routine in a different bank. You put a stub of code (commonly called a trampoline) in the same place in both banks (or in RAM, which doesn't get swapped). You call that stub, it changes banks, then calls the new routine.

That's worked well for me so far on Halcyon. But now I've realized I have about 15 subroutines that I call a lot and from all sorts of different banks. I decided it would be faster (both in terms of writing code, and in execution) to avoid trampolines for those, and instead, duplicate just those routines into every bank that uses them.

Unfortunately, cc65 (the compiler/assembler suite I'm using) doesn't have direct support for duplicating routines multiple times in different places. So instead I'm doing some ugly hacks.

First, I compile the whole project once, leaving blank space in the places where the code should be duplicated to. One of the linker outputs is a nice map file, which tells the address of every piece of code in the final assembled output. So I have a python script that analyzes the map file, and extracts the portion of compiled binary that should be duplicated in other banks. It writes this to a file, and compiles everything again, this time injecting that extracted binary into each place that it needs to be duplicated.

This worked perfectly until last night when I started getting weird crashes calling code in this duplicated pseudo-fixed bank. Why was it crashing?  After an hour of angry debugging, I discovered the issue:  My python script had an off-by-one error. It was omitting the last byte of the pseudo-fixed bank.  Which means I had half an instruction dangling without the other half. That would do it.

No comments:

NES Anguna

Well, I had a little bit of time still, while Frankengraphics is finishing up her game Project Blue, to have a little downtime on Halcyon, s...