Why does this basic program crash FCEUX?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

rox_midge
Posts: 89
Joined: Mon Sep 19, 2005 11:51 am

Why does this basic program crash FCEUX?

Post by rox_midge » Tue Apr 28, 2020 9:26 pm

I'm trying to cut my teeth a bit with the NES and I'm having a hell of a time getting a simple ROM to load in FCEUX without crashing. Can someone take a look and tell me what I'm doing wrong?

I'm using the MMC3, with no CHR-RAM, and the program just reads the controller and updates some RAM values in the zero page. I'm attempting to validate that it works by using the hex editor in FCEUX to examine the zero page to see the values change.

FCEUX crashes at different times. Sometimes it crashes on startup, sometimes it crashes when I show the hex editor, sometimes it crashes when closing.

The weird part is that seems to consistently work if I pause emulation when loading the cart, before I show the hex editor.

Any ideas?

Source:

Code: Select all

; Do the simplest thing that can possibly work

.segment "HEADER"

; iNES header
.byte "N"
.byte "E"
.byte "S"
.byte $1A
.byte $02 ; PRG-ROM size in 16KB units: 32K PRG-ROM
.byte $00 ; CHR-ROM size in 8KB units: 0K CHR-ROM
.byte $40 ; Use mapper 4; horizontal mirroring, no battery
.byte $08 ; iNES 2.0 format
.byte $00 ; PRG-ROM MSB = 0; CHR-ROM MSB = 0
.byte $00 ; PRG-RAM = 0
.byte $07 ; CHR-RAM = 8K
.byte $02 ; PPU Timing: multiple region
.byte $00 ; 
.byte $00 ; 
.byte $00 ; 
.byte $00 ; 

.segment "ZP": zeropage

iJoy_1: .res 1
iJoy_2: .res 1

pTileSrcAddr: .res 2
pTileSrcAddrLo = pTileSrcAddr + 0
pTileSrcAddrHi = pTileSrcAddr + 1

iVBlankFlag: .res 1
iCameraX: .res 1
iCameraY: .res 1

.segment "DATA_8000"

    .incbin "gfx/debug00.chr"

.segment "DATA_A000"

	.byte "Obvious Pattern"

    ; Eight palettes
    .byte $0F, $22, $12, $1C
    .byte $0d, $27, $28, $39
    .byte $0d, $37, $28, $39
    .byte $0d, $47, $38, $49
    .byte $0d, $14, $28, $39
    .byte $0d, $24, $28, $39
    .byte $0d, $34, $28, $39
    .byte $0d, $44, $28, $39

.segment "CODE_C000"

    ; This segment intentionally left blank

.segment "CODE_E000"

.proc ReadJoypads
    ldx #$01
	stx $4016
	dex
	stx $4016
	lda #$00
:	pha
	lda $4016
	and #$03
	cmp #$01
	pla
	ror a
	dex
	bne :-

	; store into local var
	sta iJoy_1

	; strobe JOY_2
	ldx #$01
	stx $4016
	dex
	stx $4016
	lda #$00
:	pha
	lda $4017
	and #$03
	cmp #$01
	pla
	ror a
	dex
	bne :-

	; store into local var
	sta iJoy_2

    rts
.endproc

.proc Main
    ; Set MMC3 mirroring to vertical
    lda #$00
    sta $a000

    ; Select MMC3 bank at $8000-$9FFF
    lda #$06
	sta $8000
	lda #$00
	sta $8001

    ; ---------------------------------------------------------------------------
    ; Copy PPU tiles from PRG ROM to PPU RAM

    lda #$00
	sta pTileSrcAddrLo
	lda #$80
	sta pTileSrcAddrHi
	ldy #$00
	sty $2006	; load destination address into the PPU
	sty $2006	;   ...second write of destination address
	ldx #$20	; number of 256-byte pages to copy
:	lda (pTileSrcAddr), y	; read one byte
	sta $2007				; write to the PPU
	iny						; y++
	bne :-					; repeat 256 times
	inc pTileSrcAddrHi		; go to the next page
	dex						; x--
	bne :-					; repeat until we've copied enough pages

    ; ---------------------------------------------------------------------------
    ; Write test tiles to nametable

    bit $2002
    ; Load the address $2000 (nametable 0) into PPUADDR ($2006)
	lda #$20
	sta $2006
	lda #$00
	sta $2006

    ; Fill nametables 0 and 1 with a pattern of bytes
	ldx #28
	ldy #32
:	stx $2007
	dey
	bne :-
	ldy #32
	dex
	bne :-

    ; Load the address $2380 (attribute table 0) into PPUADDR ($2006)
	lda #$23
	sta $2006
	lda #$80
	sta $2006

    ; Set all attributes to #$55 (all blocks using palette 1)
	lda #$55
    ldx #64
:	sta $2007
    dex
    bne :-

    ; Load the address $2780 (attribute table 1) into PPUADDR
	lda #$27
	sta $2006
	lda #$80
	sta $2006

    ; Set all attributes to #$AA (all blocks using palette 2)
	lda #$AA
    ldx #64
:	sta $2007
    dex
    bne :-

    ; ---------------------------------------------------------------------------
    ; Done with writing to PPU

    ; Wait for VBLANK to start before turning on rendering
:	bit $2002
	bpl :-

    ; Turn on NMI for VBLANK
    lda #$80
	sta $2000

    ; Wait for VBLANK to end
:	bit $2002
	bmi :-

    ; Wait for VBLANK to start again
:	bit $2002
	bpl :-

    ; Turn on rendering
	lda #$1E
	sta $2001
    
MainLoop:
    ; Wait for VBLANK NMI to finish
; 	lda #0
; :	cmp iVBlankFlag
; 	beq :-
;     ; Clear VBLANK flag
; 	sta iVBlankFlag

    ; VBLANK NMI has finished; we must assume the PPU is rendering and unavailable

	jsr ReadJoypads
    
    ; If player is pressing right, scroll right
	lda iJoy_1
	and #$80
	beq :+
	inc iCameraX
:
    ; If player is pressing left, scroll left
	lda iJoy_1
	and #$40
	beq :+
	dec iCameraX
:
    ; If player is pressing up, scroll up
	lda iJoy_1
	and #$10
	beq :+
	dec iCameraY
:
    ; If player is pressing down, scroll down
	lda iJoy_1
	and #$20
	beq :+
	inc iCameraY
:

    ; TODO: update the camera / scroll position

    jmp MainLoop
    brk
.endproc

.proc VBlankHandler
	rti

    ; Push registers
    php
	pha
	txa
	pha
	tya
	pha

    ; Do nothing of import

    ; Notify the main thread we're done with our VBLANK tasks
    ; lda #1
	; sta iVBlankFlag

    ; Pull registers
    pla
    tay
    pla
    tax
    pla
    plp

    rti
.endproc

.proc ResetHandler
    ; Initialize the CPU
	sei		; prevent maskable interrupts
	cld		; clear decimal flag, since it's unknown on bootup (NES doesn't have BCD anyway)

	ldx #$40
    stx $4017 ; disable APU frame IRQ

	; set stack pointer to $ff (just in case?)
	ldx #$ff
	txs
	inx
	stx $2000  ; disable NMI
    stx $2001  ; disable rendering
    stx $4010  ; disable DMC IRQs
	bit $2002

    ; Wait for a VBLANK
:	bit $2002
	bpl :-

	; Zero out the RAM from $0000-$07FF (2 KB in total)
	ldx #0
	txa
:	sta $0000,x
	sta $0100,x
	sta $0200,x
	sta $0300,x
	sta $0400,x
	sta $0500,x
	sta $0600,x
	sta $0700,x
	inx
	bne :-

    ; Wait for another VBLANK
:	bit $2002
	bpl :-

    jmp Main
.endproc

.proc IrqHandler
    ; Acknowledge the IRQ
    sta $E000
    rti

    ; Push registers
    php
	pha
	txa
	pha
	tya
	pha

    ; Do nothing of import

    ; Pull registers
    pla
    tay
    pla
    tax
    pla
    plp

    rti
.endproc

.segment "VECTORS"
  .addr VBlankHandler
  .addr ResetHandler
  .addr IrqHandler
Config for ld65:

Code: Select all

MEMORY {
    ZP:        start=$00,   size=$100,  type=rw;
    RAM:       start=$0200, size=$600,  type=rw;
    HDR:       start=$0000, size=$10,   type=ro, file=%O, fill=yes, fillval=$00;
    DATA_8000: start=$8000, size=$2000, type=ro, file=%O, fill=yes, fillval=$00;
    DATA_A000: start=$A000, size=$2000, type=ro, file=%O, fill=yes, fillval=$00;
    CODE_C000: start=$C000, size=$2000, type=ro, file=%O, fill=yes, fillval=$00;
    CODE_E000: start=$E000, size=$2000, type=ro, file=%O, fill=yes, fillval=$00;
}

SEGMENTS {
    ZP: load=ZP, type=zp;
    BSS: load=RAM, type=bss, align=$100, define=yes;
    HEADER: load=HDR, type=ro, align=$10;

    DATA_8000: load=DATA_8000, type=ro, start=$8000;
    DATA_A000: load=DATA_A000, type=ro, start=$A000;
    CODE_C000: load=CODE_C000, type=ro, start=$C000;
    CODE_E000: load=CODE_E000, type=ro, start=$E000;
    
    VECTORS: load=CODE_E000, type=ro,  start=$FFFA;
}

FILES {
    %O: format=bin;
}
Makefile:

Code: Select all

FCEUX=../fceux/fceux.exe
CC65_PATH=../cc65/bin

CA65=$(CC65_PATH)/ca65
LD65=$(CC65_PATH)/ld65

bin/nes-game.nes: obj/main.o
	$(LD65) -C main.cfg -o $@ $^

.PHONY: run
run: bin/nes-game.nes
	$(FCEUX) $^

obj/%.o: src/%.s
	$(CA65) $< -o $@

.PHONY: clean
clean:
		rm -f obj/*.o
		rm -f bin/*.nes

Drag
Posts: 1322
Joined: Mon Sep 27, 2004 2:57 pm
Contact:

Re: Why does this basic program crash FCEUX?

Post by Drag » Tue Apr 28, 2020 10:11 pm

If the emulator is crashing, I'd suspect there's a problem with the header, and indeed, there's an issue:

Code: Select all

.byte $08 ; iNES 2.0 format
; Mapper MSB/Submapper byte should go here but is missing
.byte $00 ; PRG-ROM MSB = 0; CHR-ROM MSB = 0
https://wiki.nesdev.com/w/index.php/NES_2.0#Header

Also check that the generated ROM file is the correct size, which should be 32784 bytes (32kb PRG rom + 16 byte header).

User avatar
rainwarrior
Posts: 7836
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Why does this basic program crash FCEUX?

Post by rainwarrior » Tue Apr 28, 2020 11:06 pm

If you actually crashed the emulator, that's a bug that should probably be addressed. Here's where to file bug reports for FCEUX, and you could attach the ROM you made to an issue.

It won't really help you to have that problem with FCEUX fixed, because this certainly also means that there's a problem with the ROM you made which you'll have to fix on your own, but the emulator shouldn't crash, and it'd be overall beneficial if it didn't.

rox_midge
Posts: 89
Joined: Mon Sep 19, 2005 11:51 am

Re: Why does this basic program crash FCEUX?

Post by rox_midge » Wed Apr 29, 2020 6:26 am

Drag wrote:
Tue Apr 28, 2020 10:11 pm
If the emulator is crashing, I'd suspect there's a problem with the header, and indeed, there's an issue:

Code: Select all

.byte $08 ; iNES 2.0 format
; Mapper MSB/Submapper byte should go here but is missing
.byte $00 ; PRG-ROM MSB = 0; CHR-ROM MSB = 0
https://wiki.nesdev.com/w/index.php/NES_2.0#Header

Also check that the generated ROM file is the correct size, which should be 32784 bytes (32kb PRG rom + 16 byte header).
Sure enough, it boiled down to missing that byte in the header. Adding it in (and removing a trailing zero from the end) put an end to the crashes. I had to fix my bodged controller logic, and I still don't fully understand why the screen isn't turned on, but at least it doesn't crash.

Oddly, I'd whittled this code down from a more ambitious start due to the instability, and I just checked the header that's being generated by that code. It's also off by a byte - in the opposite direction. I swear I've read the page you've linked a hundred times and still managed to get it wrong.

* Edited to note that the code below is off in the same direction, which explains why it crashed in the same way.

Here's the portion of the larger project that generates the iNES header - I will find and fix the bug here later:

Code: Select all

.segment "HEADER"

; NES 2.0 Header Format
; Refer to http://wiki.nesdev.com/w/index.php/NES_2.0

; We're using a TxROM board with 8K RAM, ?K PRG ROM, and ... something.

InesMapper = 4; 1		; 119
InesSubmapper = 0

; Size of PRG ROM in 16 KB units - must be a power of two
InesPrgCount = 2

; Size of CHR ROM in 8 KB units (Value 0 means the board uses CHR RAM)
InesChrCount = 0

; Use NES 2.0 header format?  Note: if this is 0, most of the following flags are ignored.
InesV20 = 1

InesUse4ScreenMode = 0
InesHasTrainer = 0
InesHasBatteryRam = 0
InesMirroring = 0

InesIsPlaychoice = 0
InesIsVsUnisystem = 0

; RAM sizes are expressed according to the following:
;  0 = 0 bytes
;  1 = 128 bytes
;  2 = 256 bytes
;  3 = 512 bytes
;  4 = 1,024 bytes
;  5 = 2,048 bytes
;  6 = 4,096 bytes
;  7 = 8,192 bytes
;  8 = 16,384 bytes
;  9 = 32,768 bytes
; 10 = 65,536 bytes
; 11 = 131,072 bytes
; 12 = 262,144 bytes
; 13 = 524,288 bytes
; 14 = 1,048,576 bytes
; 15 = reserved (do not use)
InesBatteryRamSize = 0
InesRamSize = 7

InesBatteryChrRamSize = 0
InesChrRamSize = 2

InesIsPal = 0
InesBothPalAndNtsc = 0

InesVsPpuBits = 0
InesVsCpuBits = 0


; iNES header
; bytes 0-3: iNES magic numbers
.byte "N"
.byte "E"
.byte "S"
.byte $1A

; byte 4
.byte (InesPrgCount & $ff)

; byte 5
.byte (InesChrCount & $ff)

; byte 6
.byte ((InesMapper & $0f) << 4) | ((InesUse4ScreenMode & $01) << 3) | ((InesHasTrainer & $01) << 2) | ((InesHasBatteryRam & $01) << 1) | ((InesMirroring & $01) << 0)

; byte 7
.byte ((InesMapper >> 4) & $0f) | ((InesV20 & $01) << 3) | ((InesIsPlaychoice & $01) << 1) | ((InesIsVsUnisystem & $01) << 0)

; byte 8
.byte (((InesMapper * (InesV20 & $01)) >> 8) & $0f) | (((InesSubmapper * (InesV20 & $01)) & $0f) << 4)

; byte 9
.byte (((InesChrCount * (InesV20 & $01)) >> 4) & $f0) | (((InesPrgCount * (InesV20 & $01)) >> 8) & $0f)

; byte 10
.byte (((InesBatteryRamSize * (InesV20 & $01)) & $0f) << 4) | (((InesRamSize * (InesV20 & $01)) & $0f) << 0)

; byte 11
.byte (((InesBatteryChrRamSize * (InesV20 & $01)) & $0f) << 4) | (((InesChrRamSize * (InesV20 & $01)) & $0f) << 0)

; byte 12
.byte (((InesIsPal * (InesV20 & $01)) & $01) << 0) | (((InesBothPalAndNtsc * (InesV20 & $01)) & $01) << 1)

; byte 13
.byte (((InesVsCpuBits * (InesV20 & $01)) & $0f) << 4) | (((InesVsPpuBits * (InesV20 & $01)) & $0f) << 0)

; byte 14
.byte 0

; byte 15
.byte 0
Last edited by rox_midge on Wed Apr 29, 2020 7:09 am, edited 1 time in total.

rox_midge
Posts: 89
Joined: Mon Sep 19, 2005 11:51 am

Re: Why does this basic program crash FCEUX?

Post by rox_midge » Wed Apr 29, 2020 6:32 am

rainwarrior wrote:
Tue Apr 28, 2020 11:06 pm
If you actually crashed the emulator, that's a bug that should probably be addressed. Here's where to file bug reports for FCEUX, and you could attach the ROM you made to an issue.

It won't really help you to have that problem with FCEUX fixed, because this certainly also means that there's a problem with the ROM you made which you'll have to fix on your own, but the emulator shouldn't crash, and it'd be overall beneficial if it didn't.
As part of the process I downloaded the source for FCEUX and ran it under the debugger. The crashes came from one of three places:
  • Attempting to generate sound (which is odd because this program doesn't do anything with the APU)
  • Freeing memory that's not allocated
  • Accessing memory outside the process space
Aside from the first one, which I don't understand fully, this all points toward the ROM claiming to have a lot of PRG-RAM that didn't actually exist due to the shift in the header.

rox_midge
Posts: 89
Joined: Mon Sep 19, 2005 11:51 am

Re: Why does this basic program crash FCEUX?

Post by rox_midge » Wed Apr 29, 2020 7:32 am

rainwarrior wrote:
Tue Apr 28, 2020 11:06 pm
If you actually crashed the emulator, that's a bug that should probably be addressed. Here's where to file bug reports for FCEUX, and you could attach the ROM you made to an issue.
Done. I also identified what I believe to be the actual underlying cause, which was that this test ROM is attempting to copy 8KB of data from PRG-ROM to CHR-RAM, and the cart header is claiming that there is only 256 bytes of CHR-RAM. The crash does not occur if the copy is omitted. This shouldn't crash FCEUX - the out of bound writes should be ignored, surely - but that appears to be what's happening.

Now I just have to figure out why it's not actually turning rendering on...

User avatar
dougeff
Posts: 2735
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Why does this basic program crash FCEUX?

Post by dougeff » Wed Apr 29, 2020 11:24 am

Did you load a palette? Maybe rendering is on, but you can't see it.
nesdoug.com -- blog/tutorial on programming for the NES

User avatar
rainwarrior
Posts: 7836
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Why does this basic program crash FCEUX?

Post by rainwarrior » Wed Apr 29, 2020 4:28 pm

To check that, the PPU viewer has a 9th grey palette if you click on the CHR enough times.

rox_midge
Posts: 89
Joined: Mon Sep 19, 2005 11:51 am

Re: Why does this basic program crash FCEUX?

Post by rox_midge » Wed Apr 29, 2020 7:42 pm

Of course I'm setting a palette, it would be dumb if I weren't:

Code: Select all

	lda #$3F
	sta $2006
	lda #$00
	sta $2006

	lda $0F
	sta $2007
	lda $22
	sta $2007
	lda $12
	sta $2007
	lda $1C
	sta $2007
As you can see, I'm clearly loading... arbitrary bytes from the zero page... which are all set to zero during boot... and therefore explicitly setting the palette to all black light grey.

I swear to god, despite all evidence to the contrary, that I'm not an idiot.

Everything is working peaches now - thanks!

User avatar
aa-dav
Posts: 102
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Why does this basic program crash FCEUX?

Post by aa-dav » Wed Apr 29, 2020 9:22 pm

rox_midge wrote:
Wed Apr 29, 2020 7:42 pm
I swear to god, despite all evidence to the contrary, that I'm not an idiot.
Quote from wiki:
If a rake lies in the ground with the teeth facing upwards, as shown on the top picture, and someone accidentally steps on the teeth, the rake's handle can swing rapidly upwards, colliding with the victim's face.
...
There is a Russian saying "to step on the same rake" (Russian: наступить на те же грабли), which means "to repeat the same silly mistake", also the word "rake" (Russian: грабли) in Russian slang means "troubles".
So, indeed in russian we have proverbial meaning of 'rakes' as 'again and again repeated mistake caused by design of the tool/circumstances'.

Well. So, I believe syntax of 'immediate mode' (#) in MOS 6502 assemblers are nothing more than maliciously masterplanned 'rakes'.
By so far 50% of my mistakes in programming NES are caused by this syntax. :)
So, you are not alone. { youself # is not coming.

Pokun
Posts: 1511
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Why does this basic program crash FCEUX?

Post by Pokun » Thu Apr 30, 2020 3:31 am

Heh I've stepped on that rake more than once too. Yup probably most common rake to step on in 6502.

User avatar
rainwarrior
Posts: 7836
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Why does this basic program crash FCEUX?

Post by rainwarrior » Thu Apr 30, 2020 12:19 pm

It's even shaped like a rake.

User avatar
Dwedit
Posts: 4350
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: Why does this basic program crash FCEUX?

Post by Dwedit » Thu Apr 30, 2020 1:59 pm

Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!

rox_midge
Posts: 89
Joined: Mon Sep 19, 2005 11:51 am

Re: Why does this basic program crash FCEUX?

Post by rox_midge » Thu Apr 30, 2020 6:06 pm

aa-dav wrote:
Wed Apr 29, 2020 9:22 pm
Quote from wiki:
If a rake lies in the ground with the teeth facing upwards, as shown on the top picture, and someone accidentally steps on the teeth, the rake's handle can swing rapidly upwards, colliding with the victim's face.
...
There is a Russian saying "to step on the same rake" (Russian: наступить на те же грабли), which means "to repeat the same silly mistake", also the word "rake" (Russian: грабли) in Russian slang means "troubles".
So, indeed in russian we have proverbial meaning of 'rakes' as 'again and again repeated mistake caused by design of the tool/circumstances'.

Well. So, I believe syntax of 'immediate mode' (#) in MOS 6502 assemblers are nothing more than maliciously masterplanned 'rakes'.
By so far 50% of my mistakes in programming NES are caused by this syntax. :)
So, you are not alone. { youself # is not coming.
That's brilliant, and I love it. My typical rake is missing semicolons; I'm definitely borrowing that concept when talking to my team.

To switch subjects briefly and to save me from starting a new thread - I chose mapper #4 for my project because I wanted to use the MMC3 (for both its scanline counter and its ability to switch PRG-ROM banks), and I wanted to use CHR-RAM so that I can manipulate the pattern table. I'm also using 8K of PRG-RAM (mapped into $6000-$7FFF) for extra memory.

But what I'd really like to do is to have about 16K of memory banked in 1K chunks that I could map into either CPU space (so that I can read/write directly into it) or into PPU space (so that they can be used as banks of 64 tiles). That would allow me to prepare the tiles more efficiently in CPU space and swap them into the PPU without having to indirectly write during vblank. The crazy way I'm doing it right now requires that I set aside about 1,800 bytes from the 8K PRG-RAM in which I've got an unrolled loop of code that writes to the PPU as fast as possible; doing so allows me to push 18 tiles to PPU memory each frame (or about 6 when also scrolling diagonally). I already need about 2,500 bytes to hold collision data for the visible room, which means that more than half of my PRG-RAM is being used up between the dumb generated PPU blit code and the room data.

It feels like this arrangement - being able to swap memory between the CPU and the PPU - would be possible in silicon, but does any cart have an arrangement that's anywhere close to that? Or is it not possible for some technical reason that I'm not EE enough to understand?

calima
Posts: 1186
Joined: Tue Oct 06, 2015 10:16 am

Re: Why does this basic program crash FCEUX?

Post by calima » Fri May 01, 2020 2:07 am

I believe only MMC5 can swap RAM between CPU and PPU, and it's only nametable/xattr, not for CHR. There's no technical reason for such not existing, it'd just be slightly complex/expensive, and so nobody bothered so far. If you have the funds or expertise you can certainly make such a custom mapper.

Post Reply