NES targets

The NES is a particularly challenging compiler target, due to the extremely large number of configurations supported by the various "mapper" ASICs on its cartridges.

The generic  target contains only functionality that is generically applicable across mapper chips. Individual mappers each have their own derived target: For example, to build a C++ file in a project targeting MMC1, you would use a command like:

Mapper Basics
The NESdev Wiki has a comprehensive guide on NES Mappers. Here are the basics you'll need to get up and running with a mapper using llvm-mos.

TLDR: What is a mapper?
Mapper chips most commonly allow a cartridge have a lot more memory than the NES would otherwise support. They do this by letting you redirect a region of the NES' memory space (a bank) to a different place in your cartridge's memory. Some offer advanced features like synchronizing code with a particular scan line of the screen drawing. This allows for special effects like having a HUD at the bottom of the screen or doing certain kinds of parallax effects. The most basic mappers simply allow you to include an extra RAM chip on the cartridge, with or without battery-backup.

To control the mapper, a game will usually "write" to specific addresses in the ROM. No data is written since the memory is read-only, but the mapper chip "sees" this attempt and responds to the data that is being "written".

PPU Banking
The NES allocates an 8KiB address space for CHR-ROM, from  to. Conceptually these are split into two separate 4KiB regions which can be used for sprite data, background data, or both. So you might have the title screen using the lower 4KiB of ROM for both backgrounds and sprites, but then the main game switches to the upper bank for the sprites. Note: This form of banking is built into the NES and does not require any mapper. The rest of this article is referring to mapper-based banking when it uses "banking" without qualification.

CHR-ROM Banking
CHR-ROM banking allows you to select a different region of the physical memory in your cartridge to map to some region of the 8 KiB CHR-ROM. For example, with MMC1, you can have up to 128 KiB of CHR-ROM in your cartidge/image and select any 4 KiB bank using a 5-bit address (0-31).

PRG-RAM
The addresses available for cartridges to use in the PRG-ROM space range from  to. The simplest cartridges would connect a 32 KiB ROM chip and enable or disable it based on the highest bit of the memory address being read. Using the address range between  and   is a bit more complicated since it takes more bits of the address to distinguish it from other ranges. It is significantly easier to use the -  range, so a lot of cartridges included an 8KiB RAM chip behind a simple logic gate to check the upper bits of the address. This area is often called Work RAM (WRAM).

Depending on the mapper you may need to call a function to enable WRAM. E.g. with MMC3 you would do:

PRG-RAM Banking
This gets a bit more complicated [ TODO ]

PRG-ROM Banking
This gets quite a bit more complicated [ TODO ]

PRG-ROM
If PRG-ROM banking is disabled, then data can be placed in PRG-ROM using the regular  C sections. Otherwise, data can be placed into a specific PRG-ROM bank using section  or any section that begins with. The load addresses of PRG-ROM banks begin at. This allows the bottom 24-bits to represent a logical PRG-ROM address space, while the high byte being  indicates that the address is PRG-ROM.

CHR-ROM
If CHR-ROM banking is disabled, then data can be placed in CHR-ROM using the  section or any section that begins with. Otherwise, data can be placed into a specific CHR-ROM bank using section  or any section that begins with. The logical addresses of CHR-ROM banks begins at. This allows the bottom 24-bits to represent a logical CHR-ROM address space, while the highest byte being  indicates that the address is CHR-ROM.

PRG-RAM
If PRG-RAM banking is disabled, then data can be placed in PRG-RAM using the  section or any section that begins with. Otherwise, variables can be placed into a specific PRG-RAM bank using section  or any section that begins with. The PRG-RAM is not initialized at program start, so any initializers given in C or data provided in assembly are ignored.

Symbol Configuration
As far as is possible, mappers are configured by assigning symbol values. These control the contents of the iNES 2.0 header in the resulting binary, as well as the addresses assigned to program sections. Some of the following values have logical sizes less than the full 32-bits available for symbol values. In such cases, only the lowest order bits are considered. For example,  corresponds to the high nibble of header field 10. This means that the lowest nibble of  is mapped to the high nibble of header field 10. You can set these symbols with an assembly file or inline assembly. For example:

INCLUDE Configuration
In some cases, the linker script semantics are insufficiently powerful to configure the linker alone. In this case, linker scripts can be composed by `INCLUDE`-ing script files to build up a custom linker script (rather than using the default). The following script files are common to all targets. You can either include these in your own projects linker script, or pass  to the linker. For example:

MMC1
Different PRG-ROM sizes of the MMC1 require different numbers of reset stubs to be available, so the PRG-ROM size must be set by INCLUDE, not by symbol. The below INCLUDE files automatically set the  header field to the corresponding value. The 512KiB PRG-ROM mode and PRG-ROM bank modes other than 3 are not yet supported, since the C runtime model currently used by the compiler is only really practical if a fixed bank is present where e.g. libcalls can be placed.

MMC3
By default, the mapper is configured such that 24KiB of PRG-ROM is fixed, while a 8KiB bank is available at 0x8000. This can be altered by INCLUDEing alternative linker scripts.

In banking mode 0, the even banks are available at 0x8000, and the odd banks are available at 0xa000. In banking mode 1, the even banks are available at 0xc000, and the odd banks at 0xa000. This assignment can be changed on a per-bank basis by defining the symbol, where   is the bank number.