Super NES Programming/SNES memory map

=Memory Mapping= There are two main types of SNES cartridges, the SNES community refers to them as LoROM and HiROM cartridges. Both have different memory mapping. Before explaining LoROM and HiROM though, we should define some keywords here:

$ or 0x prefix: the following number is hexadecimal. Addresses are often shown as hexadecimal values.

Bank: 64 Kilobytes (65536 or $10000 bytes), basically the most significant byte of the 3 byte address the CPU understands. The bank of the address $AABBCC is $AA (170). With three bytes of address space the SNES can address up to 16 Megabytes (2^24 or 1<<24 or 16777216 Bytes == 16384 Kilobytes == 16 Megabytes). Beware that just because the SNES can address 16 Megabytes does not mean it also HAS 16 MB of RAM (here so-called WRAM) - it will be explained in detail later. Just keep in mind that therefore the SNES has $100 or 256 Banks (start at $00, end at $FF).

Page: 256 bytes ($0100 bytes). Pages are used whenever the machine has to perform mapping tasks (for example, ensuring that address $AABBCC and address $DDBBCC point to the exactly same data, if that's how the machine is supposed to work). Pages are also used for normal PCs (x86/x86-64 architectures) and are hence not exclusive for the SNES. A page is the smallest mappable unit of a machine. You will notice that in the tables below there is no entry in which a certain memory range does not end with $FF - this is because $FF is the last address of a page, and after this byte a new page begins. The SNES relies heavily on mapping - for example, without mapping it wouldn't be possible to give all banks of $00 – $3F two pages of WRAM directly at the beginning of the bank. One bank holds 256 ($100) pages, so the SNES has $100 (banks) * $100 (pages) == $10000 (65536) pages available.

Both LoROM and HiROM are capable of storing up to 4 Megabytes (32 Megabits) of ROM data. ExLoROM, ExHiROM and some expansion chips like SA-1 and SDD-1 are said to be able to store up to 8 Megabytes (64 Megabits).

How do I recognize the ROM type?
In byte $15 in the SNES header (see below for more details), the ROM makeup byte is stored.

The bitmask to use is 001A0BCD, the basic value is $20:

- A == 0 means SlowROM (+ $0), A == 1 means FastROM (+ $10). - B == 1 means ExHiROM (+ $4) - D == 0 means LoROM (+ $0), D == 1 means HiROM (+ $1), is used with B and C in case of extended ROMs.

Keep in mind that some people sometimes use "Mode 20" to refer to the LoROM mapping model, and "Mode 21" to refer to HiROM, although it's technically wrong. As the table shows, there are two LoROM and two HiROM mappings which have a different markup byte than the name suggests.

ExLoROM is an unofficial map, so it does not have its own type value. To detect it one would typically just check if the game is a regular LoROM then check the ROM file size.

Note that expansion chips like the SuperFX have their own memory map, which is not covered here. SA-1 and SDD-1 in particular are MMC chips, which means they can do bank switching to change which parts of the ROM are accessed for a given bank.

LoROM
This is the LoROM memory map:

In LoROM mode, the ROM is always mapped in the upper half of each bank, thus 32 Kilobytes per chunk. The banks $00 – $7D (address: $8000 - $FFFF) hold continuous data, as well as banks $80 - $FF. SRAM on the cartridge is mapped continuously and repeatedly - 8 Kilobyte of SRAM are mapped at $0000 - $1FFF, $2000 – $3FFF, $4000 – $5FFF and so on. Because the WRAM of the SNES is mapped at bank $7E - $7F, these banks do not map to the last SRAM/ROM chunks. This memory has to be accessed via the banks $80 - $FF. There is no other way of accessing this memory both in LoROM and HiROM mode.

LoROM was established to make sure that the system banks ($00 – $3F) higher pages (>7) are actually used. This is done by loading the entire ROM only in higher pages and in 32 Kilobyte chunks. 32 KB * $80 banks == 4 Megabyte.

HiROM
This is the HiROM memory map:

HiROM is a bit more complex to understand. Unlike LoROM, it does not use $80 (128) banks to map the ROM into the address space of the SNES, but only $40 (64) banks. Also unlike LoROM, these banks are used to their full extend, that is, 64 KB per chunk. 64 KB * $40 banks == 4 Megabytes. The banks $40 – $7D (address: $0000 - $FFFF) hold continuous data, as well as banks $C0 - $FF. Beware that HiROM also creates mappings for banks $00 – $3F and $80 - $BF. As those are system banks, their lower pages (<8) are already mapped - but the higher pages are free, so that many portions of the ROM are mirrored four times into the address space of the SNES. SRAM on the cartridge is mapped into the banks $20 – $3F, in 8 Kilobyte chunks. As there are only 32 banks reserved for this, the possible SRAM amount accessible in HiROM is theoretically lower than in LoROM (256 KB vs. 512 KB). Because the WRAM of the SNES is mapped at bank $7E - $7F, these banks do not map to the last ROM chunks. This memory has to be accessed via the $80 - $FF banks

Banks $80 - $FF can also be used for faster memory access. Many portions of memory <$80 are accessed at 2.68 MHz (200 ns). Accessing memory >$80 is done at 3.58 MHz (120 ns) if the value at address $420D (hardware register) is set to 1.

LoROM basically means that the address line A15 is ignored by the cartridge, so the cartridge doesn't distinguish between $0000-$7FFF and $8000-$FFFF in any bank. Smaller ROMs use this model to prevent wasted space in banks $00–$3F.

ExLoROM, ExHiROM
LoROM and HiROM were designed to store 4 MB in a ROM and map it into the 24 bit address space of the SNES (which can, in case you have forgotten it, address up to 16 Megabytes). Usually that is enough for many games, especially if one considers the fact that in the beginning the SNES was not supposed to render a lot of later games using the control deck alone. This is also the reason why the SNES reserved some address space for the usage of SuperFX chips that would enable games to spare the CPU - it was not intended to have the SNES play games like Tales of Phantasia, or Final Fantasy 6, or Star Fox without the aid of enhancement chips.

Apart from the hardware problems that the SNES was designed with, the programmers also have/had huge space limitations. Final Fantasy 6's English script - a early draft, to be precise - was cut down to 75% because it would have otherwise exhausted the 24 Megabit (3 Megabyte) cartridge that was used for the Japanese version. Expanding the ROM from 24 MBits to 32 MBits does not cause really big issues, but everything bigger than 4 Megabytes goes beyond the boundaries of LoROM/HiROM.

A huge advantage of the SNES is that it does not define how certain address blocks are mapped into the cartridge's ROM. The cartridge does take care that the addresses it needs to use are mapped properly to its ROM. For people who would like to program an emulator this behaviour imposes certain problems, as they have to program and emulate the SNES hardware AND the cartridge as well. Keep in mind that the cartridge, as the SNES, contains its own processors and microchips, which may perform differently from chip to chip. Take for instance a common problem: the usage of MAD-1 address decoding chips within the cartridge, which maps the content of the $40–7D:8000-FFFF ROM areas to the lower halves of the banks as well (so that there is no difference between $400005 and $408005 - see the LoROM table for the exact addresses). The real SNES does not have to care for this, emulators do.

Thus, using a format which allows the usage of up to 8 Megabytes of ROM space without using an expansion MMC chip imposed no definite hardware problems, as the address decoding chips on the cartridge would need to take care of that. Two new formats were introduced: ExLoROM (the prefix "Ex" stands for "extended," in case you wondered), and ExHiROM. Only two games which actually never officially got released in NA use ExHiROM, Tales of Phantasia and Daikaijuu Monogatari II. ExLoROM on the other hand was not used in any commercial games and is considered an unofficial map, so emulator support for it has been known to be somewhat lacking or inconsistent at times. But ROM hacks which contain new game content usually expand the original ROM size, and might use one of these two formats. In fact, both the re-translations of Chrono Trigger by "DoctorL" and "KajarLab" expand the game from 4 to 6 Megabytes.

There are many either misleading or not understandable explanations and examples of the memory layout of ExLoROM and ExHiROM, for two valid reasons:


 * hackers often do not encounter these formats.
 * incapability to see why someone would need so much memory at their hands. *

Let's first cover the memory map of ExLoROM - we will not go into detail for the mapping of the system banks lower halves, as these were already covered before and do not change for the extended ROM formats:

Note: This information are wrong, as I had to realize. A ROM which starts at offset $200000 makes no sense at all. Also, since it is still a LoROM format, the first ROM bank HAS TO be loaded to $00:8000-FFFF. Yet, the template will be corrected soon, and deleting it all serves no purpose.

(Explanation and ExHiROM table coming soon).

(*The second reason is in fact a protection mechanism. In case that a hacker encounters someone who wants to enlarge a ROM beyond the 32 Megabit limit, the hacker automatically assumes that this person is a newbie who has not much idea what he is talking about, and in an awful lot of occasions he is right. This, however, does not justify the mere lack of documentation regarding these topics.)

=The SNES header= At the end of bank 0 (the very first bank of the cartridge), 64 ($40) bytes of cartridge information are stored. This information is crucial to the execution of the ROM saved on the cartridge, as it includes the internal name of the ROM, the interrupt vectors (addresses to machine code within the ROM in 16 bit format), version, etc. This portion of the ROM is also known as the SNES header (not to confuse with the SMC header, which we will touch very soon).

The end of the very first bank of the ROM depends on with memory map it uses. In case of LoROM, the end of the first page is at $7FFF, for HiROM the first page ends at $FFFF. So you basically have to check on both positions of the ROM if the information fit the normal header format, that is, if they are plausible enough.

Some cartridges are delivered with a so-called SMC header. A SMC header is usually written by a copier device at the very beginning of the ROM, and occupies $200 (512) bytes of additional space. Often patches depend on your ROM being "headered" or being "headerless", as this changes the offsets within the ROM. A patch which is applied to the wrong type of ROM can easily kill the program code of the cartridge.

Creating a SMC header is "relatively" simple, removing it more so. Usually the SMC header contains the size of the ROM divided by 8 KB units &$FF (bitwise AND with 255) in byte 0, the size >> 8 (bitwise shift right by a whole byte) in byte 1, and the type of cartridge in byte 2. The other bytes (509) left are set to zero. For creating a header, you simply have to add a block of 512 bytes at the beginning of the ROM which contains these information. For removing a header, you simply have to save the ROM on your HDD/SSD while "forgetting" the first 512 bytes.

The reason why we poke into SMC header when we should actually deal with SNES header is simple - actually, there's two of them:

1. The existence of a SMC header has to be taken into account when mapping the ROM into memory. The bank in a ROM with header will not begin at offset $0, but at $200 (512, the size of the header), and so the end of the bank shifts accordingly.

2. If there is no SMC header in the ROM, or you simply do not trust this information when parsing the ROM, your only way of determining the type of cartridge you are trying to load in is by checking for well-formed headers at the already-mentioned positions. But IF you do trust this information (which you shouldn't), it is a way to determine whether you are dealing with LoROM or HiROM.

Before you try to load in this information, you have to determine the size of the SMC, may it be there or not. The length of the ROM modulo 1024 (ROM size % $400) gives you the size of the SMC header. If it's 0, there is no header. If it's not 512, the header is malformed.

The following offsets in the ROM are that of a headerless ROM. You have to add the size of the SMC header to all addresses in the ROM to properly read it. x is the last page of the current ROM bank ($7 if the cartridge uses the LoROM mapping, $F if it uses HiROM).

Interrupt vectors
After this information, the interrupt vector tables begin. An interrupt is a signal that is cast by the hardware directly to the CPU that needs to be dealt with if the interrupt was not masked. An interrupt vector specifies the address where the code is to deal with the given type of interrupt. These vectors are all 16 bits in size and are supposed to lie within the first bank of the ROM. This is because the DBR (Data Bank Register) and PBR (Program Bank Register) are set to 0 every time the emulation mode is changed or an interrupt is executed.

Vectors are all 2 bytes in size and are stored in little-endian format (in case the reader did not know it - the CPU is a little endian one, which means that the MSBs are stored in higher positions. Big endian means that the MSBs are stored in lower positions). Program execution begins in emulation mode at the reset vector ($xFFC-D).

Some small examples
The current section covers the initial parsing methods for both LoROM and HiROM. The games that this text is about to analyze are Final Fantasy 4 (LoROM) and Final Fantasy 6 (HiROM), both headerless.

Final Fantasy 4
We first look at the address $7FC0 in the ROM - the last byte of the LoROM bank is $7FFF, and the header takes up to 64/$40 bytes. The first byte of the header, IF it is LoROM (which we officially don't know yet, mind you) is therefore located at $7FC0.

That looks good. In the very first two lines the name of the ROM is written (since Final Fantasy 4 was released as Final Fantasy II in NA, this actually makes sense). The first 21 bytes are just ASCII characters (and a rare opportunity to see clear text in a ROM, since the dialogues are usually stored in an other format than ASCII). The next byte after this string can be misinterpreted as just another space character, but in fact the byte at $D5 indicates that it's a LoROM cartridge (now we have two proves that the cartridge is LoROM: first the position of the header, second the byte indicating that the cartridge uses LoROM. By the way, if those two do not happen to match, the ROM is considered corrupted).

One might perform other check there (for example the size of the ROM vs. the size stored in the header, the country and licensee codes, the SRAM size ...), but for now, basically only two other bytes are essential to boot the ROM: the position of the RESET interrupt vector in emulation mode (which is issued the moment the ROM was mapped into the address space of the SNES). This vector is stored at $7FFC-D in little endian format (see above), which means that the vector is calculated this way:

In this example, the vector is $8000 - the start-up code is right at the beginning of the first bank (yes, the first bank. Remember how the first bank gets stored in $8000 - $FFFF, while $0000 - $7FFF contains system information?).

Let's look a bit at the code there:

Hex:

Disassembled (with a program that I am developing myself, so don't bother asking where I'd got it):

You see that this seems plausible enough to be totally valid start-up code.

Final Fantasy 6
Again let's poke at offset $7FC0:

This seems to be a little off. Maybe it uses HiROM?

Yes, it does. And the RESET vector is at $FF00. Again in hex:

And disassembled:

In short, this code tells the executing CPU that $C0:0019 is the next instruction to execute. Since this is a HiROM memory mapping, this means that we have to look where the bank $C0 got mapped to. Referring to the former HiROM mapping table, the $C0 - $FD banks are direct mappings of $40 – $7D. And these banks in fact contain all the ROM content continuously - since $C0 <=> $40 and $40 is the beginning, the code to execute is basically at the beginning of the ROM, offset $19:

Disassembled:

=External Links=
 * SNES Memory Map
 * Another SNES Memory Map
 * Yet Another SNES Memory Map
 * List of various hardware registers
 * Explanation about Most Significant Bytes (MSBs)
 * Explanation of the 65816 architecture (SNES CPU) that is actually helpful

=See Also=


 * SNES hardware registers