NES targets

From llvm-mos

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 nes target contains only functionality that is generically applicable across mapper chips. Individual mappers each have their own derived target:

Currently Supported Mappers
Number Name Target
000 NROM nes-nrom
001 MMC1 nes-mmc1
002 UNROM nes-unrom
003 CNROM nes-cnrom
004 MMC3 nes-mmc3
028 Action 53 nes-action53
030 UNROM-512 nes-unrom-512

For example, to build a C++ file in a project targeting MMC1, you would use a command like:

$ mos-nes-mmc1-clang++ main.cpp -Os -o main.nes

Mapper Basics[edit | edit source]

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?[edit | edit source]

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[edit | edit source]

The NES allocates an 8KiB address space for CHR-ROM, from 0x0000 to 0x1FFF. 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.

bank_bg(0);   // Use lower half of CHR-ROM for backgrounds
bank_spr(1);  // Use upper half of CHR-ROM for 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[edit | edit source]

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).

// Map PPU CHR-ROM to the 3rd and 4th 4KiB regions of physical ROM
set_chr_bank_0(2);
set_chr_bank_1(3);

PRG-RAM[edit | edit source]

The addresses available for cartridges to use in the PRG-ROM space range from 0x4020 to 0xFFFF. 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 0x4020 and 0x7FFF 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 0x6000-0x7FFF 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:

set_wram_mode(WRAM_ON);
PRG-RAM Banking[edit | edit source]

This gets a bit more complicated [ TODO ]

PRG-ROM Banking[edit | edit source]

This gets quite a bit more complicated [ TODO ]

Mapper/iNES Header Configuration[edit | edit source]

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, __prg_nvram_size_raw corresponds to the high nibble of header field 10. This means that the lowest nibble of __prg_nvram_size_raw is mapped to the high nibble of header field 10.

You can set these symbols with the macros in ines.h or manually - using inline assembly or a separate assembly file. For example:

// main.c

#include <ines.h>

MAPPER_CHR_ROM_KB(0);
MAPPER_CHR_RAM_KB(8);
// main.c

asm(".globl __chr_rom_size\n"
    "__chr_rom_size = 0\n"
    ".globl __chr_ram_size\n"
    "__chr_ram_size = 8\n");
; config.s

.global __prg_rom_size, __chr_rom_size, __prg_ram_size
.global __mirroring, __four_screen

; Kilobytes
__prg_rom_size = 512
__chr_rom_size = 256
__prg_ram_size = 8

; Flags
__mirroring = 1    ; horizontal mirroring
Configuration Symbols
Symbol Description
__prg_rom_size KiB of PRG-ROM
__chr_rom_size KiB of CHR-ROM
__prg_ram_size KiB of PRG-RAM
__prg_nvram_size KiB of PRG-NVRAM
__chr_ram_size KiB of CHR-RAM
__chr_nvram_size KiB of CHR-NVRAM
__mirroring Bit 0 of field 6
__battery Bit 1 of field 6
__trainer Bit 2 of field 6
__four_screen Bit 3 of field 6
__mapper Mapper number.
__console_type Low two bits of field 7
__timing Low two bits of field 12
__ppu_type Low nibble of byte 13 when Vs. System
__hw_type High nibble of byte 13 when Vs. System
__extended_console_type Low nibble of byte 13 when Extended Console Type
__misc_roms Low two bits of byte 14
__default_expansion_device Low six bits of byte 15
__prg_rom_size_raw The most significant nibble is the lower nibble of field 9, and the least significant byte is field 4. Overrides __prg_rom_size.
__chr_rom_size_raw The most significant nibble is the upper nibble of field 9, and the least significant byte is field 5. Overrides __chr_rom_size.
__prg_ram_size_raw Raw value of low 4 bits of header field 10. Overrides __prg_ram_size.
__prg_nvram_size_raw Raw value of high 4 bits of header field 10. Overrides __prg_nvram_size.
__chr_ram_size_raw Raw value of low 4 bits of header field 11. Overrides __chr_ram_size.
__chr_nvram_size_raw Raw value of high 4 bits of header field 11. Overrides __chr_nvram_size.

Linker layout[edit | edit source]

Sections[edit | edit source]

PRG-ROM[edit | edit source]

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

CHR-ROM[edit | edit source]

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

PRG-RAM[edit | edit source]

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

INCLUDE Configuration[edit | edit source]

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 -Tfilename to the linker. For example:

# makefile
LDFLAGS = -lneslib -lnesdoug -Tcommon.ld -Tc-in-prg-ram.ld -Tprg-rom-banked-mode-0
INCLUDE Files
Name Description
common.ld Functionality common to all linker scripts for a given target. Must be included. Included in default linker script.
c-in-ram.ld Place the writable C sections (static/global variables and such) into NES RAM. Exactly one c-in- script must be included. Included in default linker script.

ELF address space[edit | edit source]

On the NES target, the ELF address space is split into three sections:

NES target - LMA format
Type (bits 31-24) Bits Name Values
0x00 (CPU) 23 - 16 Bank 0x00 .. 0xFF
15 - 0 Address 0x0000 .. 0xFFFF

Matches 16-bit address for the CPU

0x01 (CHR) 23 - 0 Address 0x000000 .. 0xFFFFFF

Mapper-specific information[edit | edit source]

NROM, CNROM[edit | edit source]

INCLUDE Configuration[edit | edit source]

INCLUDE Files
Name Description
c-in-prg-ram.ld Place the writable C sections (static/global variables and such) into PRG-RAM instead of system RAM.

MMC1[edit | edit source]

The 512KiB PRG-ROM mode and PRG-ROM bank modes other than 3 are not yet supported, since we don't yet have a general mechanism for copying arbitrary code into all banks to simulate a fixed bank at the end of the address space.

INCLUDE Configuration[edit | edit source]

INCLUDE Files
Name Description
c-in-prg-ram-0.ld Place the writable C sections (static/global variables and such) into PRG-RAM instead of system RAM.
prg-rom-banked.ld Up to 256KiB of banked PRG-ROM.
prg-rom-fixed.ld Up to 32KiB of fixed PRG-ROM.

MMC3[edit | edit source]

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 __prg_rom_NN, where NN is the bank number.

INCLUDE Configuration[edit | edit source]

INCLUDE Files
Name Description
c-in-prg-ram.ld Place the writable C sections (static/global variables and such) into PRG-RAM instead of system RAM.
prg-rom-banked-8.ld One mappable 8KiB bank at 0x8000, and 3 contiguous fixed banks (24 KiB total) starting at 0xa000.
prg-rom-banked-mode-0.ld Two separately mappable 8KiB banks at 0x8000 and 0xa000, and 2 contiguous fixed banks starting at 0xc000.
prg-rom-banked-mode-1.ld Two separately mappable 8KiB banks at 0xc000 and 0xa000, the next-to-last bank fixed at 0x8000, and the last bank fixed at 0xe000.
prg-rom-fixed.ld Up to 32KiB of fixed PRG-ROM.

Action 53[edit | edit source]

This mapper is the primary target for the NES Game Jam "NESDEV Compo." Because the common developer use case for this mapper is to make a ROM that's a part of a multi-cart, the defaults for this target are set for that purpose. The defaults are as follows: 64KiB PRG-ROM, 32KiB CHR-RAM, with 16KiB banking is available at 0x8000. This can be altered by changing the standard iNES configuration symbols, but adds another symbol __supervisor_outer_bank which will be ORed with the value written to $80 if you need to change the switchable bank to 0xc000.