Overview
Every NES program needs this exact startup sequence. The reset handler disables interrupts, waits for the PPU to stabilise (two VBlank waits), and clears all RAM. Only after this is it safe to configure the PPU and start your game.
Code
; =============================================================================
; RESET SEQUENCE - NES
; Standard NES initialisation boilerplate
; Taught: Game 1 (Neon Nexus), Unit 1
; CPU: ~30,000 cycles | Memory: ~60 bytes
; =============================================================================
PPUCTRL = $2000
PPUMASK = $2001
PPUSTATUS = $2002
.segment "CODE"
reset:
sei ; Disable interrupts
cld ; Clear decimal mode (not used on NES, but safe)
ldx #$40
stx $4017 ; Disable APU frame IRQ
ldx #$FF
txs ; Set up stack at $01FF
inx ; X = 0
stx PPUCTRL ; Disable NMI
stx PPUMASK ; Disable rendering
stx $4010 ; Disable DMC IRQs
; === First VBlank wait ===
; PPU needs time to warm up after power-on
@vblank1:
bit PPUSTATUS ; Read status (bit 7 = in VBlank)
bpl @vblank1 ; Loop until VBlank flag set
; === Clear RAM while we wait ===
; RAM contents are undefined at power-on
lda #0
@clear_ram:
sta $0000, x
sta $0100, x
sta $0200, x ; OAM buffer
sta $0300, x
sta $0400, x
sta $0500, x
sta $0600, x
sta $0700, x
inx
bne @clear_ram ; Loop 256 times (X wraps from $FF to $00)
; === Hide all sprites in OAM buffer ===
; RAM clear set every byte to 0, which puts all 64 sprites at Y=0
; (top-left of screen). Write $FF to every Y position to hide them.
ldx #0
lda #$FF
@hide_oam:
sta $0200,x ; Y position byte for each sprite
inx
inx
inx
inx ; Stride 4 — only the Y byte
bne @hide_oam
; === Second VBlank wait ===
; PPU is now fully ready
@vblank2:
bit PPUSTATUS
bpl @vblank2
; === PPU is ready - continue with game setup ===
; Load palettes, set up sprites, etc.
jsr setup_game
; Enable NMI and rendering
lda #%10010000 ; NMI enable, sprites from pattern table 1
sta PPUCTRL
lda #%00011110 ; Show sprites and background
sta PPUMASK
; Fall through to main loop
main_loop:
jmp main_loop ; NMI does the work
setup_game:
; Your initialisation code here
rts
Trade-offs
| Aspect | Cost |
|---|---|
| CPU | ~30,000 cycles (two VBlank waits) |
| Memory | ~60 bytes |
| Limitation | None - this is mandatory |
When to use: Every NES program. Copy this exactly.
When to avoid: Never - you always need this.
Why Two VBlank Waits?
The PPU takes time to stabilise after power-on:
- First wait: PPU internal state is settling
- Clear RAM: Use this time productively
- Hide OAM sprites: Cleared RAM means all 64 sprites have Y=0 — they'd pile up at the top of the screen as soon as rendering enables. Writing $FF to every Y position pushes them off-screen.
- Second wait: PPU is now fully ready
Skipping the OAM hide step is a famous beginner footgun: rendering enables, and a column of garbage sprites appears at the top of the screen for one frame until the NMI handler updates them.
Skipping the VBlank waits entirely can cause graphics glitches or crashes on real hardware.
What Each Step Does
| Step | Purpose |
|---|---|
sei | Prevent interrupts during setup |
cld | Clear decimal flag (6502 habit, harmless) |
stx $4017 | Disable APU frame counter IRQ |
txs | Set stack pointer to $01FF |
stx PPUCTRL | Disable NMI until ready |
stx PPUMASK | Disable rendering until ready |
stx $4010 | Disable DMC channel IRQ |
| VBlank waits | Let PPU stabilise |
| Clear RAM | Initialise all variables to 0 |
Vector Table
Don't forget to set up your vectors:
.segment "VECTORS"
.word nmi ; $FFFA - NMI vector
.word reset ; $FFFC - Reset vector
.word irq ; $FFFE - IRQ vector
Related
Patterns: NMI Game Loop, Palette Loading
Vault: NES