<< Newer | Article #152 | Older >> |
Save State Fundamentals, part 3
Consider this a "sidebar" to the save state fundamentals articles. Part 4 will work through the driver example. This brief article is meant to explain the new and improved memory banking changes that went into MAME a little while back.
In the "old days", let's say you had a banked ROM which you loaded like
this:
...
ROM_LOAD ( 0x0c000, 0x1000, CRC(...) SHA1(...) )
ROM_CONTINUE( 0x10000, 0x7000 )
...
This is a common way of loading a banked ROM. Bank 0, which lives in the
first 4k, is loaded at the address where the CPU actually reads data from.
The remaining banks are loaded outside of the CPU's address space (in this
case, the CPU is an 8-bit CPU with a 16-bit address space, so it can't
access memory beyond 0xFFFF).
Then you would need to map the memory where the ROM appears in the address
space, as well as a handler for actually changing the banks. Let's say
it looked something like this:
...
AM_RANGE( 0xc000, 0xcfff ) AM_ROMBANK(1)
AM_RANGE( 0xffff, 0xffff ) AM_WRITE( bank_select_w )
...
This configures bank #1 in the memory system to map to the 4k region 0xC000-0xCFFF,
and configures a single byte address at the very end of the address space
that programs write to in order to select which of the 8 banks in the ROM
appears in the 0xC000-0xCFFF range.
Finally, the code for the bank_select_w function would look something
like this:
WRITE8_HANDLER( bank_select_w )
{
UINT8 *rombase = memory_region(REGION_CPU1);
data &= 7;
if (bank == 0)
memory_set_bankptr(1, &rombase[0xc000]);
else
memory_set_bankptr(1, &rombase[0x10000
+ (data-1) * 0x1000]);
}
In this code, we tell the memory manager where the base of bank #1 starts.
If it's bank 0, we point it back to the first bank, which was loaded at
offset 0xC000 in the memory region. For banks 1-7, which were loaded at
offset 0x10000 in the memory region, we perform a small calculation to
determine the base of the bank.
This system has worked great for years, but is unfortunately not compatible with saving the state of such a driver. Well, it can be done, but it means that every driver which has banks like this would need to store the current bank number in a global variable, and after restore, it would need to have a postload function that called memory_set_bankptr to point the memory system to the correct bank. This is a pain.
In order to make memory bank saving automatic, the memory system was augmented with a slightly different way of setting the bank base. Rather than setting the base of the bank directly in bank_select_w, you instead tell the memory system up front where the base of all the banks are. Then in your bank select handler, you tell the memory system which bank number to switch to. The memory system can then manage the saving and restoring of the banks for you.
So how does this affect the code above? Well, your ROM loading and memory
maps are identical. The first thing you need to do is to add, somewhere
in your DRIVER_INIT, MACHINE_INIT, or VIDEO_START callbacks, code to register
all the banks. Let's put it in DRIVER_INIT:
DRIVER_INIT( example )
{
UINT8 *rombase = memory_region(REGION_CPU1);
memory_configure_bank(1, 0, 1, &rombase[0xc000],
0);
memory_configure_bank(1, 1, 7, &rombase[0x10000],
0x1000);
}
So what are all those parameters to memory_configure_bank? Well,
the first parameter is which memory system bank you are configuring, in
this case 1 because we are configuring memory system bank #1. The second
and third parameters specify the range of banks you are configuring. In
the first call, we are configuring 1 bank starting at bank #0, and in the
second call we are configuring 7 banks starting at bank #1. The fourth
parameter specifies the starting address of the first bank that is being
configured. And the fifth parameter specifies the number of bytes between
successive banks.
Breaking it down further, in the example above, the first call is configuring bank #0 to start at &rombase[0xc000]. And the second call is configuring banks #1-7 to start at &rombase[0x10000], &rombase[0x11000], &rombase[0x12000], etc.
Once you've done that, your bank selection code becomes simply:
WRITE8_HANDLER( bank_select_w )
{
memory_set_bank(1, data & 7);
}
Understanding how this mechanism works is important if you want to cleanly
add save state support to drivers with banked memory.