Init code

From NESdev Wiki
Revision as of 13:31, 24 June 2010 by Tepples (talk | contribs) (skip $200,x and explain why)
Jump to navigationJump to search

When the NES is powered on or reset, the program should do the following within a fixed bank:

  • Set IRQ ignore bit (not strictly necessary as the 6502 sets this flag on all interrupts, including RESET, but it allows program code to simulate a reset by JMP ($FFFC))
  • Disable PPU NMIs and rendering
  • Initialize stack pointer
  • Initialize the mapper (if any)

The init code after this point may be placed either in the fixed bank or in a separate bank using a bankswitch followed by a JMP:

  • Disable decimal mode (not strictly necessary as the 2A03 has no decimal mode, but it maintains compatibility with generic 6502 debuggers)
  • If using a mapper that generates IRQs, disable APU timer IRQs
  • Disable DMC IRQs [1]
  • Set all RAM that your program uses to a known state. This often involves clearing internal RAM (@ $0000-$07FF) (and PRG RAM if needed (@ $6000-$7FFF)), except that which is intended to survive a reset (such as high scores).
  • Wait at least 30,000 cycles (see PPU power up state) before reading or writing registers $2003 through $2007. This is commonly done by waiting for the PPU to signal the start of vertical blank twice through $2002.

Some mappers have no fixed bank because they switch all 32 KB of PRG at a time. These include AxROM, BxROM, GxROM, and some configurations of MMC1. You'll have to put the interrupt vectors and the code up to the end of the JMP in a separate section that is duplicated in each bank. Often, the 256-byte page $FF00-$FFFF contains the vectors, the start of the init code, and a "trampoline" for jumps from code in one bank to code in another.

Sample implementation:

reset:
    sei        ; ignore IRQs
    cld        ; disable decimal mode
    ldx #$40
    stx $4017  ; disable APU frame IRQ
    ldx #$ff
    txs        ; Set up stack
    inx        ; now X = 0
    stx $2000  ; disable NMI
    stx $2001  ; disable rendering
    stx $4010  ; disable DMC IRQs

    ; Optional (omitted):
    ; Set up mapper and jmp to further init code here.

    ; Clear the vblank flag, so we know that we are waiting for the
    ; start of a vertical blank and not powering on with the
    ; vblank flag spuriously set
    bit $2002

    ; First of two waits for vertical blank to make sure that the
    ; PPU has stabilized
@vblankwait1:  
    bit $2002
    bpl @vblankwait1

    ; We now have about 30,000 cycles to burn before the PPU stabilizes.
    ; One thing we can do with this time is put RAM in a known state.
    ; Here we fill it with $00, which matches what (say) a C compiler
    ; expects for BSS.  Conveniently, X is still 0.
    txa
@clrmem:
    sta $000,x
    sta $100,x
    sta $300,x
    sta $400,x
    sta $500,x
    sta $600,x
    sta $700,x  ; Remove this if you're storing reset-persistent data

    ; We skipped $200,x on purpose.  Usually, RAM page 2 is used for the
    ; display list to be copied to OAM.  OAM needs to be initialized to
    ; $EF-$FF, not 0, or you'll get a bunch of garbage sprites at (0, 0).

    inx
    bne @clrmem

    ; Other things you can do between vblank waits are set up audio
    ; or set up other mapper registers.
   
@vblankwait2:
    bit $2002
    bpl @vblankwait2

At this point, the program can fill the nametables, fill the pattern tables (if the board uses CHR RAM), fill the palette, and start displaying things.