Project overview

As much as I want to dive into code, it does help to see a high-level overview of what we'll be building. That way, individual topics, such as backgrounds and sprites, will make sense in the context of the larger program. In turn, that will allow you to write your code the way you want to, instead of being stuck with the structure in this book.

The goal of this book is to construct the code and associated data that constitutes an NES game. To create a physical game, you would stuff this code and data into chips inside a cartridge, which would then inserted into a physical NES console. To avoid dealing with real hardware, we will instead put this code and data into a file, known as a ROM file, that can be loaded by an NES emulator. The ROM file will be in the iNES format, with the following sections:

The parts of the iNES file format, as described below
  1. iNES header: a fixed-size header describing the ROM file. The header isn't part of a real cartridge, but is present in a ROM file to identify the file format and provide metadata to pretty much any NES emulator that exists today.

  2. PRG-ROM: the data stored on the cartridge read by the CPU. Typically, this means the executable code, but also included is any data that is not part of the CHR-ROM. This section is 16KiB, or 16,348 bytes, long.

    There can be more than one PRG-ROM section (or "banks" as they are called), but until the mappers section, we will stick with one.

  3. CHR-ROM: the data stored on the cartridge read directly by the Picture Processing Unit (PPU, analogous to a modern GPU). In particular, this is the sprite and background tile data that defines the actual graphics, but not how those graphics are used. This section is 8KiB, or 8,192 bytes, long.

    Again, there can be multiple CHR-ROM banks, but we will start with one.

Notice that the PRG-ROM is wired directly to the CPU and the CHR-ROM directly to the PPU.

Now that we understand the structure of what we're building, let's dive into each section. The goal of this chapter is to come up with a very minimal ROM that displays a brick wall:

A 256x244 picture of a brick wall rendered using an NES emulator

iNES header

; iNES header
  .inesprg 1 ; 1 PRG-ROM bank
  .ineschr 1 ; 1 CHR-ROM bank
  .inesmir 0 ; horizonal mirroring
  .inesmap 0 ; mapper #0

Program code in the PRG-ROM

The 16KiB PRG-ROM region is wired to addresses $8000-$BFFF in system memory, and due to our choice of mapper, the region is mirrored to addresses $C000-$FFFF in system memory.
  1. Initialize the system
    1. Disable interrupts
    2. Wait two V-Blanks
    3. Load a palette
    4. Initialize the background
    5. Enable interrupts
  2. Loop forever
  3. Ensure the initialization code is called first thing by the NES

Tile data in the CHR-ROM

Finally, we come to the tile data, which is stored in CHR-ROM and directly wired up to the PPU.

Remember how we filled up the PPU's name table with all zeros? Part of that meant that the background is all tile #0, whatever that tile is. Well, here's where we define the tile data:

  .bank 2
  .org $0000

  .byte $E0,$F8,$7C,$00,$0E,$8F,$C7,$00
  .byte $1E,$06,$00,$00,$E1,$60,$00,$00

Don't worry what these bytes mean. Not only will we cover the data format in the next chapter, but I'll show a shortcut NESASM has for making this data easier to write. For now, all you need to know is that these bytes define the following tile:

A single 8x8 tile of a grayscale brick that, when repeated across the entire screen, forms a brick wall.

One thing that's important is we needed to tell the assembler two things:

  1. Put the tile data in the third 8KiB region, which is the first region that comes after the 16KiB PRG-ROM. This is what the .bank 2 directive is for.

  2. Put the title data at the beginning of that region, ensuring we are specifying tile #0, and not some other tile. This is what the .org $0000 directive is for. Note that if we had more tiles, we would just add more bytes after the ones we specified above.