Topics

UTTORI ASM · IN DEVELOPMENT

NES hello world

Use nes hello world in 6502-family and NES projects, with syntax and limits.

This self-contained iNES NROM displays HELLO WORLD using eight embedded tiles uploaded to CHR-RAM and direct PPU setup.

Save the complete code block below as hello-world.asm. The default NES layout is a 32 KiB PRG window at $8000 with CHR-RAM (header byte 5 = 0). .segment "CODE" is required. Without it the assembler has no load region and writes nowhere.

; hello-world.asm
; A complete iNES NROM (32 KiB PRG, CHR-RAM) that displays HELLO WORLD.

.segment "CODE"

Reset:
  sei                         ; Mask IRQ during setup.
  cld                         ; Binary arithmetic.
  ldx #$40
  stx $4017                   ; Disable APU frame IRQ.
  ldx #$FF
  txs                         ; Stack at $01FF.
  inx                         ; X = 0.
  stx $2000                   ; PPUCTRL: NMI off, nametable $2000, pattern $0000.
  stx $2001                   ; PPUMASK: rendering off.
  stx $4010                   ; Disable DMC IRQ.

WaitVblank1:
  bit $2002
  bpl WaitVblank1             ; First vblank after reset is not enough.

WaitVblank2:
  bit $2002
  bpl WaitVblank2             ; PPU is now stable.

  bit $2002                   ; Reset the $2006/$2005 address latch.
  lda #$3F
  sta $2006
  lda #$00
  sta $2006                   ; Palette at PPU $3F00.
  lda #$0F
  sta $2007                   ; Color 0: black.
  lda #$30
  sta $2007                   ; Color 1: white.
  lda #$30
  sta $2007
  lda #$30
  sta $2007

  bit $2002
  lda #$00
  sta $2006
  sta $2006                   ; Pattern table 0 at PPU $0000.
  ldx #$00

CopyFont:
  lda FontTiles,x
  sta $2007
  inx
  cpx #FontTilesEnd-FontTiles
  bne CopyFont

  bit $2002
  lda #$20
  sta $2006
  lda #$00
  sta $2006                   ; Nametable 0 at PPU $2000.
  ldx #$00
  lda #$00
  ldy #$04                    ; 4 × 256 = 1024: tiles plus attributes.

ClearName:
  sta $2007
  inx
  bne ClearName
  dey
  bne ClearName

  bit $2002
  lda #$21
  sta $2006
  lda #$CB
  sta $2006                   ; Row 14, column 11 of nametable 0.
  ldx #$00

CopyMessage:
  lda MessageTiles,x
  sta $2007
  inx
  cpx #MessageTilesEnd-MessageTiles
  bne CopyMessage

  lda #$00
  sta $2005
  sta $2005                   ; Scroll 0,0.
  lda #$00
  sta $2000                   ; Keep NMI off. We never need it.
  lda #$0E
  sta $2001                   ; Show background, clip the left 8 pixels.

Forever:
  jmp Forever

EmptyHandler:
  rti

; 0=space, 1=H, 2=E, 3=L, 4=O, 5=W, 6=R, 7=D
MessageTiles:
  .byte $01,$02,$03,$03,$04,$00,$05,$04,$06,$03,$07
MessageTilesEnd:

; NES 2bpp tiles: 8 plane-0 bytes, then 8 plane-1 bytes. Bit 7 is leftmost.
FontTiles:
  ; Tile 0: space
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  ; Tile 1: H
  .byte %10000001,%10000001,%10000001,%11111111
  .byte %10000001,%10000001,%10000001,%00000000
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  ; Tile 2: E
  .byte %11111111,%10000000,%10000000,%11111110
  .byte %10000000,%10000000,%11111111,%00000000
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  ; Tile 3: L
  .byte %10000000,%10000000,%10000000,%10000000
  .byte %10000000,%10000000,%11111111,%00000000
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  ; Tile 4: O
  .byte %01111110,%10000001,%10000001,%10000001
  .byte %10000001,%10000001,%01111110,%00000000
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  ; Tile 5: W
  .byte %10000001,%10000001,%10000001,%10000001
  .byte %10010001,%10010001,%01101110,%00000000
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  ; Tile 6: R
  .byte %11111110,%10000001,%10000001,%11111110
  .byte %10001000,%10000100,%10000010,%00000000
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  ; Tile 7: D
  .byte %11111100,%10000010,%10000001,%10000001
  .byte %10000001,%10000010,%11111100,%00000000
  .byte $00,$00,$00,$00,$00,$00,$00,$00
FontTilesEnd:

.org $FFFA
.addr EmptyHandler, Reset, EmptyHandler

Assemble it from the repository root with the NES target:

npx uttori-asm hello-world.asm hello-world.nes \
  --plugin @uttori/asm-plugin-65xx \
  --target 65xx.nes

The result is a 32,784-byte .nes file: 16-byte iNES header (NES\x1A, 2 PRG banks, 0 CHR banks) plus 32 KiB of PRG. Open it in an NES emulator and the letters should appear near the center of a black screen. The layout is intentionally plain enough to understand in one sitting:

  • pattern table 0 lives at PPU $0000 (CHR-RAM we uploaded ourselves)
  • nametable 0 begins at PPU $2000
  • palette entries 0 and 1 provide black and white
  • the message is merely eleven tile indices at $21CB

Once this clicks, loading a larger font with .incbin or a CHR ROM bank feels like an upgrade rather than sorcery.

For the hardware side, check NESDev's PPU registers, PPU palettes, PPU pattern tables, init code, and iNES header notes. Vectors must land at $FFFA–$FFFF inside the $8000 window. A three-byte experiment assembles, but it gives the CPU nowhere to find RESET.