Init code: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
m (internal link)
 
(Improve advice regarding APU IRQs, and adjust priorities for certain init tasks.)
 
(17 intermediate revisions by 4 users not shown)
Line 2: Line 2:
*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 <code>JMP ($FFFC)</code>)
*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 <code>JMP ($FFFC)</code>)
*Disable decimal mode (not strictly necessary as the 2A03 has no decimal mode, but it maintains compatibility with generic 6502 debuggers)
*Disable decimal mode (not strictly necessary as the 2A03 has no decimal mode, but it maintains compatibility with generic 6502 debuggers)
*Disable [[PPU]] NMIs and rendering
*Disable [[IRQ|IRQs]] generated by the [[APU Frame Counter]] ('''enabled at power-up''') and [[APU DMC]] [http://forums.nesdev.org/viewtopic.php?p=22538#p22538]
*Initialize stack pointer
*Initialize stack pointer
*If using a mapper that generates IRQs, disable [[APU Frame Counter|APU timer IRQs]]
*Disable [[APU DMC|DMC]] IRQs [http://nesdev.parodius.com/bbs/viewtopic.php?p=22538#22538]
*Disable [[NES PPU|PPU]] NMIs and rendering
*Initialize the mapper (if any)
*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 <code>JMP</code>:
*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). However, you don't need to set up an area of RAM that only one part of a program uses; you can set that in the same part where it's used, and in many cases, you'll need to set it up multiple times as part of the program is run multiple times.
*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.


The init code after this point may be placed in a separate bank using a bankswitch followed by a <code>JMP</code>:
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 <code>JMP</code> 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.
*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 [[Power-up state of PPU]]) 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 [[Nintendo MMC1]]. You'll have to put the interrupt vectors and the code up to the end of the <code>JMP</code> in a separate section that is duplicated in each bank.
Sample implementation:


Sample implementation:
<pre>
<pre>
reset:
reset:
Line 31: Line 30:
     ; Set up mapper and jmp to further init code here.
     ; Set up mapper and jmp to further init code here.


     ; Clear the vblank flag, so we know that we are waiting for the
     ; The vblank flag is in an unknown state after reset,
     ; start of a vertical blank and not powering on with the
    ; so it is cleared here to make sure that @vblankwait1
    ; vblank flag spuriously set
     ; does not exit immediately.
     bit $2002
     bit $2002


Line 43: Line 42:


     ; We now have about 30,000 cycles to burn before the PPU stabilizes.
     ; We now have about 30,000 cycles to burn before the PPU stabilizes.
     ; Use it to clear RAM.  X is still 0...
     ; 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 BSSConveniently, X is still 0.
     txa
     txa
@clrmem:
@clrmem:
Line 53: Line 54:
     sta $500,x
     sta $500,x
     sta $600,x
     sta $600,x
     sta $700,x ; Remove this if you're storing reset-persistent data
     sta $700,x
     inx
     inx
     bne @clrmem
     bne @clrmem
    ; Other things you can do between vblank waits are set up audio
    ; or set up other mapper registers.
    
    
@vblankwait2:
@vblankwait2:
Line 61: Line 65:
     bpl @vblankwait2
     bpl @vblankwait2
</pre>
</pre>
At this point, the program can fill the [[PPU nametables|nametables]], fill the [[PPU pattern tables|pattern tables]] (if the board uses CHR RAM), fill the [[PPU palettes|palette]], and start displaying things.
Notes on the '''clrmem''' loop:
* One of the pages of RAM will normally be reserved for an [[PPU OAM|OAM buffer]] (often $200-$2FF), and values of $00 will place all sprites in the top left corner. We may wish to initialize this page separately with $FF or some other suitable value to start with all sprites offscreen.
* If it is desired that some RAM state will persist across reset, we would also need to find some way to prevent clearing the persistent portion here.
== See also ==
*[[APU basics]] covers how to initialize the APU

Latest revision as of 03:43, 30 September 2024

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 decimal mode (not strictly necessary as the 2A03 has no decimal mode, but it maintains compatibility with generic 6502 debuggers)
  • Disable PPU NMIs and rendering
  • Disable IRQs generated by the APU Frame Counter (enabled at power-up) and APU DMC [1]
  • 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:

  • 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). However, you don't need to set up an area of RAM that only one part of a program uses; you can set that in the same part where it's used, and in many cases, you'll need to set it up multiple times as part of the program is run multiple times.
  • 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.

    ; The vblank flag is in an unknown state after reset,
    ; so it is cleared here to make sure that @vblankwait1
    ; does not exit immediately.
    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 $200,x
    sta $300,x
    sta $400,x
    sta $500,x
    sta $600,x
    sta $700,x
    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.

Notes on the clrmem loop:

  • One of the pages of RAM will normally be reserved for an OAM buffer (often $200-$2FF), and values of $00 will place all sprites in the top left corner. We may wish to initialize this page separately with $FF or some other suitable value to start with all sprites offscreen.
  • If it is desired that some RAM state will persist across reset, we would also need to find some way to prevent clearing the persistent portion here.

See also