So the other day, I got the urge to do some retro-programming. What better platform, I thought to myself, than the Nintendo Entertainment System. Unfortunately, for the beginner, programming even a very simple ROM and running it in your favorite emulator can be challenging. So here now is my small guide to getting started.
Get the Compiler
In true retro-programming form, the NES cannot be practically programmed using a high level language like C. The system featured a Ricoh 2A03 CPU, a variant of the MOS 6502. As such, games must be written in the 6502’s assembler language. Once written, we have to compile the ASM to a binary iNES file which can be read by all popular NES emulators.
The compiler of choice for 6502 ASM, from what I gather, is the CA65 ASM compiler; part of the CC65 tool chain. Download the source distribution here. Build with GCC by opening a console, navigating to the cc65 folder, and run the following command:
After this finishes, you should have the ASM compiler (ca65) and the linker (ld65) that we’ll need to build our ROMs.
At this point, I would recommend familiarizing yourself with the 6502 hardware and assembly language. Visit 6502.org for more information.
Creating a Simple Program
To jump right in here, let’s write a simple program:
; Displays a message on the screen. Demonstrates how to set up PPU
; and nametable
.byte "NES", 26, 2, 1
; CHR ROM data
.word 0, 0, 0, nmi, reset, irq
; NES Registers
PPUCTRL = $2000 ; These two control the PPU in various ways
PPUMASK = $2001
PPUSTATUS = $2002 ; Can be read to get current PPU status
PPUSCROLL = $2005 ; Sets X/Y scrolling of background
PPUADDR = $2006 ; Sets VRAM address in PPU
PPUDATA = $2007 ; Writes data to current VRAM address
; Entry point - jumped to when reset or powered up
; Initialize NES hardware
ldx #$FF ; Reset stack pointer to $FF (255)
sei ; Be sure the IRQ interrupt is disabled
sta PPUCTRL ; Be sure NMI is off (stores zero from accumulator in PPUCTRL)
sta PPUMASK ; Be sure PPU rendering is off
; Give the PPU time to warm up
@wait1: bit PPUSTATUS ; Loop until top bit of PPUSTATUS is set
; Reading PPUSTATUS also clears top bit,
; so it's clear now
@wait2: bit PPUSTATUS ; Wait for bit to be set again
; Set first four palette entries
lda #$3F ; Set PPU address to palette RAM
lda #$51 ; Set background to black
;lda #$30 ; Set three foreground colors to white
; Wait for VBL before enabling display
@wait3: bit PPUSTATUS
; Enable background display
lda #%00001000 ; Enable background
lda #0 ; Scroll to top-left of nametable at $2000
; loop forever
; Interrupt handlers
This program initializes the NES hardware and draws a blue background. To compile it, run ca65 and ld65 like this:
ld65 -t nes -o myprogram.nes myprogram.o
This should produce a ROM which you can run with an emulator. On my mac, I use Nestopia.
So what is all that?
For a better description of what this code actually does, I would hit up google, but I’ll try to explain the basics. I’m just getting started with this, so please, if this isn’t quite right, let me know.
First, let’s look at the structure of the ROM file. Each ROM starts with a header, denoted by the HEADER segment. The header tells the emulator what kind of ROM to expect. The first byte always are the characters “NES” followed by 26 (0×1A in hex). The next two bytes tell the emulator how many PRG-ROM blocks and CHR-ROM blocks to expect.
The next segment, called CHAR, can hold sprite data. After that in the VECTORS segment, we define several vectors which the program jumps to upon receiving an interrupt. We’ll define three (nmi, reset, and irq) but only use the reset vector which is our programs entry point.
Next, we give names to six of the NES’s eight memory-mapped registers. We’ll use these to manipulate the NES’s picture processing unit (PPU).
Finally, we get to the actual program. The first order of business is to initialize the NES hardware. We reset the stack pointer, turn off two interrupts and PPU rendering. Next, we wait for the PPU to warm up (that’s right - warm up). We initialize the palette next, wait for the VBL, and draw.
That’s enough to get started. Happy coding!