This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Help with noob problems in my noob code?

Help with noob problems in my noob code?
by on (#51576)
I've spent a lot of time away from NES programming and I'm trying to come back and get a better feel for it.
I'm working on a ball and paddle game and I'm running into problems right away.
My lame collision works as it is. But, if I add so much as a NOP, it hangs upon collision. It also fills the second ram page with what looks like nonsense. What could make it so fragile? I'm not doing a million things between vblanks.

Code:
   .inesprg 1
   .inesmap 0
   .inesmir 1
   .ineschr 1

   .bank 1     
   .org $FFFA
   .dw VBlank_Routine ; address to execute on VBlank
   .dw Start    ; address to execute on reset
   .dw 0        ; no whatever

   .bank 0
   .org $0000
VBlankOrNo:      .db   0
Direction:      .db 0 ;ball direction 0=left
   .org $0300 ; OAM Copy location $0300
   
Sprite0_Y:     .db  0   ; sprite #1's Y value
Sprite0_T:     .db  0   ; sprite #1's Tile Number
Sprite0_S:     .db  0   ; sprite #1's special byte
Sprite0_X:     .db  0   ; sprite #1's X value

Sprite1_Y:     .db  0   ; sprite #1's Y value
Sprite1_T:     .db  0   ; sprite #1's Tile Number
Sprite1_S:     .db  0   ; sprite #1's special byte
Sprite1_X:     .db  0   ; sprite #1's X value

Sprite2_Y:     .db  0   ; sprite #1's Y value
Sprite2_T:     .db  0   ; sprite #1's Tile Number
Sprite2_S:     .db  0   ; sprite #1's special byte
Sprite2_X:     .db  0   ; sprite #1's X value

; this would just go on and on for however many sprites you have
   .org $8000  ; code starts at $8000 or $C000
   
;-------------------------------------------------------------------------
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
   stx $2005
   stx $2005

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

    ; 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.
    ; Use it to clear RAM.  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  ; Remove this if you're storing reset-persistent data
    inx
    bne clrmem
;-------------------------------------------------------------------------

VBlank_Routine:
   ;start of function to execute on VBlank
   inc VBlankOrNo
   ; add one (1) to VBlankOrNo, will be 1 if VBlank, 0 if not.
   rti  ; RTI is (Interrupt RETurn or ReTurn from Interrupt)
   
Start:
   
   ldx #$00    ; clear X            ;; start of pallete loading code

   lda #$3F    ; have $2006 tell
   sta $2006   ; $2007 to start
   lda #$00    ; at $3F00 (pallete).
   sta $2006
   
loadpal:                ; this is a freaky loop
   lda tilepal, x  ; that gives 32 numbers
   sta $2007       ; to $2007, ending when
   inx             ; X is 32, meaning we
   cpx #32         ; are done.
   bne loadpal     ; if X isn't =32, goto "loadpal:" line.
                                   ;; end of pallete loading code
   
   lda #$20
   sta $2006 ; give $2006 both parts of address $2020.
   sta $2006

load_name_tables:
; Jam some text into the first name table (at $2400, thanks to mirroring)
        ldy #$00
        ldx #$04
      lda #low(ourMap)
      sta $10
      lda #high(ourMap)
      sta $11
        lda #$20
        sta $2006
        lda #$00
        sta $2006
load_name_tables_loop:
        lda [$10],y
        sta $2007
        iny
        bne load_name_tables_loop
      inc $11
        dex
        bne load_name_tables_loop

setup_Y_X:
   ldx #$0
   stx Sprite0_T
   stx Sprite0_S
   stx Sprite1_S
   stx Sprite2_S
   stx Sprite1_X
   inx
   stx Sprite1_T
   inx
   stx Sprite2_T
   lda #$70
   sta Sprite0_Y
   sta Sprite2_Y
   adc #$7
   sta Sprite1_Y
   lda #$80
   sta Sprite2_X
   
   
   lda #%10001000  ;
   sta $2000       ;
   lda #%00011110  ; Our typical PPU Setup code.
   sta $2001       ;
 ; ---------------------------------------------------
 ; Setup is done
 ; ---------------------------------------------------
 
infinite:  ; a label to start our infinite loop
WaitForVBlank:
   lda VBlankOrNo ; A = VBlankOrNO
   cmp #1         ; if A == 1 then is VBlank
   bne WaitForVBlank ; if not VBlank, then loop and do again
   dec VBlankOrNo ; 1-- or VBlankOrNO - 1 . VBlankOrNo will be 0 again.
   ; vblank part is done

   lda #3 ; load sprite DMA offset
   sta $4014 ; write sprite offset to PPU

   lda #$01   ; these
   sta $4016  ; lines
   lda #$00   ; setup/strobe the
   sta $4016  ; keypad.

   lda $4016  ; load Abutton Status
   and #1
    lda $4016  ; load Bbutton Status
   and #1
   lda $4016  ; load Select Status
   and #1
   lda $4016  ; load Start Status
   and #1
   lda $4016  ; load UP Status
   and #1      ; just check the least significant bit aka 00000001
   beq Load_Down ; if it wasn't pushed go to the next button
   dec Sprite0_Y ; if it was pushed move the sprite UP
Load_Down:
   lda $4016  ; load DOWN Status
   and #1      ; just check the least significant bit aka 00000001
   beq Load_Left ; if it wasn't pushed go to the next button
   inc Sprite0_Y ; if it was pushed move the sprite Down
Load_Left:
   lda $4016  ; load LEFT Status
   and #1
   lda $4016  ; load RIGHT Status
   and #1
   
   LDA Direction ; zero on start
   BNE Go_Right  ; if 1 go_right
   LDA Sprite0_X
   ADC #8
   CMP Sprite2_X ; see if sprite 1 is on same x and sprite 0
   BNE NO_COL    ; if it isn't keep going left
   LDA Sprite0_Y
   SEC           ; set carry for subtraction
   SBC Sprite2_Y ; subtract sprite 1 y from sprite 0 y to see if it is greater or less
   BPL POS       ; if 1 is less than 0 jump POS
   CLC           ; clear carry for addition
   ADC #8        ; if greater see if it's within 7
   BPL COL       ; if it is, change direction to "right"
   JMP NO_COL    ; if not, keep on going left
POS:
   SBC #8        ; if less see if it's withing 7
   SEC           ; set carry for subtraction
   BMI COL       ; if it is, change direction to "right"
   JMP NO_COL    ; if not, keep on going left
COL:
   INC Direction ; set direction to "right"
   
Go_Right:
   INC Sprite2_X ; moves right
   LDA Sprite2_X
   CMP #247      ; see if we hit the right side of the screen
   BNE infinite  ; if we didn't start the loop over
   dec Direction ; if we did, set direction to "left"
   JMP infinite
NO_COL:
   DEC Sprite2_X ; moves left
   nop
   jmp infinite

tilepal:   .incbin "pong.pal"  ; a label for our pallete data
ourMap: .incbin "pong.map" ; assuming our.map is the binary map file.
   .bank 2
   .org $0000
   .incbin "pong.bkg"
   .incbin "pong.spr"

Thanks a lot.
Re: Help with noob problems in my noob code?
by on (#51580)
For a start you need to add a jump over the Vblank_Routine after the clrmem part or was that just a typo?

Juno

by on (#51585)
I just copied the start up portion from the web. So if it is a typo, it isn't mine.
I added "JMP Start" after the bne clrmem, but NOP's still crash it.

by on (#51586)
You don't have any reset vectors at $FFFA-$FFFF defined, do you?

by on (#51587)
I'm not sure if you need it as I see you are following the GBA guy tutorials.

in the NMI you probably need to push the status registers as the inc will change the flags

Code:
VBlank_Routine:
   php
   ;start of function to execute on VBlank
   inc VBlankOrNo
   ; add one (1) to VBlankOrNo, will be 1 if VBlank, 0 if not.
   plp
   rti  ; RTI is (Interrupt RETurn or ReTurn from Interrupt)

by on (#51588)
ccovell wrote:
You don't have any reset vectors at $FFFA-$FFFF defined, do you?


*My* code doesn't start until the controller checking starts. So, the resets are probably taken from GBA Guy.

This looks like the extent of it.
Code:
   .bank 1     
   .org $FFFA
   .dw VBlank_Routine ; address to execute on VBlank
   .dw Start    ; address to execute on reset
   .dw 0        ; no whatever


I added the php and plp in vblank but it still hangs.

by on (#51591)
JunoMan wrote:
in the NMI you probably need to push the status registers as the inc will change the flags

Not necessary. On interrupts the flags are automatically pushed, and RTI restores them. You only need to push/restore A, X and Y, if you use them.

by on (#51592)
Skidlz wrote:
Code:
   .bank 1     
   .org $FFFA
   .dw VBlank_Routine ; address to execute on VBlank
   .dw Start    ; address to execute on reset
   .dw 0        ; no whatever

You used "Start" as the reset point, so none of the code before it (i.e. all the initialization) is running. Change that "Start" to "reset", which is where you want the program to start. You do need that JMP to skip over the VBlank_Routine though. Better yet, move the VBlank_Routine to after the infinite loop.

EDIT: Also, pointing your NMI vector to "0" isn't a good idea either. In case an IRQ happens, the CPU will try to execute RAM variables as if they were code... not good. Just have it point to a location in ROM with an RTI instruction, so that in case of an IRQ it simply returns.

by on (#51618)
I can't believe I missed that obvious mistake. Explains why the NMI routine wasn't breaking it when it was being executed (in theory) after the start code.

I guess it would have been easier to debug if the rest of the files were supplied to assemble it.

by on (#51620)
JunoMan wrote:
I can't believe I missed that obvious mistake. Explains why the NMI routine wasn't breaking it when it was being executed (in theory) after the start code.

I guess it would have been easier to debug if the rest of the files were supplied to assemble it.


I can upload the files somewhere if anyone can use them.
I changed "Start" to reset but the problem persists.
The code currently looks like this:

Code:
   .inesprg 1
   .inesmap 0
   .inesmir 1
   .ineschr 1

   .bank 1     
   .org $FFFA
   .dw VBlank_Routine ; address to execute on VBlank
   .dw reset    ; address to execute on reset
   .dw 0        ; no whatever

   .bank 0
   .org $0000
VBlankOrNo:      .db   0
Direction:      .db 0 ;ball direction 0=left
   .org $0300 ; OAM Copy location $0300
   
Sprite0_Y:     .db  0   ; sprite #1's Y value
Sprite0_T:     .db  0   ; sprite #1's Tile Number
Sprite0_S:     .db  0   ; sprite #1's special byte
Sprite0_X:     .db  0   ; sprite #1's X value

Sprite1_Y:     .db  0   ; sprite #1's Y value
Sprite1_T:     .db  0   ; sprite #1's Tile Number
Sprite1_S:     .db  0   ; sprite #1's special byte
Sprite1_X:     .db  0   ; sprite #1's X value

Sprite2_Y:     .db  0   ; sprite #1's Y value
Sprite2_T:     .db  0   ; sprite #1's Tile Number
Sprite2_S:     .db  0   ; sprite #1's special byte
Sprite2_X:     .db  0   ; sprite #1's X value

; this would just go on and on for however many sprites you have
   .org $8000  ; code starts at $8000 or $C000
   
;-------------------------------------------------------------------------
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
   stx $2005
   stx $2005

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

    ; 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.
    ; Use it to clear RAM.  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  ; Remove this if you're storing reset-persistent data
    inx
    bne clrmem
;-------------------------------------------------------------------------
   JMP Start
VBlank_Routine:
   ;start of function to execute on VBlank
   inc VBlankOrNo
   ; add one (1) to VBlankOrNo, will be 1 if VBlank, 0 if not.
   rti  ; RTI is (Interrupt RETurn or ReTurn from Interrupt)
   
Start:
   
   ldx #$00    ; clear X            ;; start of pallete loading code

   lda #$3F    ; have $2006 tell
   sta $2006   ; $2007 to start
   lda #$00    ; at $3F00 (pallete).
   sta $2006
   
loadpal:                ; this is a freaky loop
   lda tilepal, x  ; that gives 32 numbers
   sta $2007       ; to $2007, ending when
   inx             ; X is 32, meaning we
   cpx #32         ; are done.
   bne loadpal     ; if X isn't =32, goto "loadpal:" line.
                                   ;; end of pallete loading code
   
   lda #$20
   sta $2006 ; give $2006 both parts of address $2020.
   sta $2006

load_name_tables:
; Jam some text into the first name table (at $2400, thanks to mirroring)
        ldy #$00
        ldx #$04
      lda #low(ourMap)
      sta $10
      lda #high(ourMap)
      sta $11
        lda #$20
        sta $2006
        lda #$00
        sta $2006
load_name_tables_loop:
        lda [$10],y
        sta $2007
        iny
        bne load_name_tables_loop
      inc $11
        dex
        bne load_name_tables_loop

setup_Y_X:
   ldx #$0
   stx Sprite0_T
   stx Sprite0_S
   stx Sprite1_S
   stx Sprite2_S
   stx Sprite1_X
   inx
   stx Sprite1_T
   inx
   stx Sprite2_T
   lda #$70
   sta Sprite0_Y
   sta Sprite2_Y
   adc #$7
   sta Sprite1_Y
   lda #$80
   sta Sprite2_X
   
   
   lda #%10001000  ;
   sta $2000       ;
   lda #%00011110  ; Our typical PPU Setup code.
   sta $2001       ;
 ; ---------------------------------------------------
 ; Setup is done
 ; ---------------------------------------------------
 
infinite:  ; a label to start our infinite loop
WaitForVBlank:
   lda VBlankOrNo ; A = VBlankOrNO
   cmp #1         ; if A == 1 then is VBlank
   bne WaitForVBlank ; if not VBlank, then loop and do again
   dec VBlankOrNo ; 1-- or VBlankOrNO - 1 . VBlankOrNo will be 0 again.
   ; vblank part is done

   lda #3 ; load sprite DMA offset
   sta $4014 ; write sprite offset to PPU

   ;nop
   lda #$01   ; these
   sta $4016  ; lines
   lda #$00   ; setup/strobe the
   sta $4016  ; keypad.

   lda $4016  ; load Abutton Status
   and #1
    lda $4016  ; load Bbutton Status
   and #1
   lda $4016  ; load Select Status
   and #1
   lda $4016  ; load Start Status
   and #1
   lda $4016  ; load UP Status
   and #1      ; just check the least significant bit aka 00000001
   beq Load_Down ; if it wasn't pushed go to the next button
   dec Sprite0_Y ; if it was pushed move the sprite UP
Load_Down:
   lda $4016  ; load DOWN Status
   and #1      ; just check the least significant bit aka 00000001
   beq Load_Left ; if it wasn't pushed go to the next button
   inc Sprite0_Y ; if it was pushed move the sprite Down
Load_Left:
   lda $4016  ; load LEFT Status
   and #1
   lda $4016  ; load RIGHT Status
   and #1
   
   LDA Direction ; zero on start
   BNE Go_Right  ; if 1 go_right
   LDA Sprite0_X
   ADC #8
   CMP Sprite2_X ; see if sprite 1 is on same x and sprite 0
   BNE NO_COL    ; if it isn't keep going left
   LDA Sprite0_Y
   SEC           ; set carry for subtraction
   SBC Sprite2_Y ; subtract sprite 1 y from sprite 0 y to see if it is greater or less
   BPL POS       ; if 1 is less than 0 jump POS
   CLC           ; clear carry for addition
   ADC #8        ; if greater see if it's within 7
   BPL COL       ; if it is, change direction to "right"
   JMP NO_COL    ; if not, keep on going left
POS:
   SBC #8        ; if less see if it's withing 7
   SEC           ; set carry for subtraction
   BMI COL       ; if it is, change direction to "right"
   JMP NO_COL    ; if not, keep on going left
COL:
   INC Direction ; set direction to "right"
   
Go_Right:
   INC Sprite2_X ; moves right
   LDA Sprite2_X
   CMP #247      ; see if we hit the right side of the screen
   BNE infinite  ; if we didn't start the loop over
   dec Direction ; if we did, set direction to "left"
   JMP infinite
NO_COL:
   DEC Sprite2_X ; moves left
   nop
   jmp infinite

tilepal:   .incbin "pong.pal"  ; a label for our pallete data
ourMap: .incbin "pong.map" ; assuming our.map is the binary map file.
   .bank 2
   .org $0000
   .incbin "pong.bkg"
   .incbin "pong.spr"

Thanks for all the help so far.

by on (#51621)
Looks ok.. Can you stick the other files somewhere. That would make it much easier to fix.

by on (#51622)
Another thing that appears to be wrong (but I'm not sure because I'm not a NESASM user) is that you state that there is only one PRG bank (.inesprg 1) but in fact you have two. I can't see anything else wrong.

What exactly isn't working right when you run it in an emulator?

by on (#51625)
JunoMan wrote:
Looks ok.. Can you stick the other files somewhere. That would make it much easier to fix.


http://www.mediafire.com/download.php?jymyigz4wyj

tokumaru wrote:
Another thing that appears to be wrong (but I'm not sure because I'm not a NESASM user) is that you state that there is only one PRG bank (.inesprg 1) but in fact you have two. I can't see anything else wrong.

What exactly isn't working right when you run it in an emulator?


I saw something in another post about NESASM treating banks as 32K instead of 16K, or some such thing. It assembles so I would guess it's correct.

The demo consists of a ball and a paddle. The ball collides with the paddle and bounces in the unmodified code. If I add *any* opcode at seemingly any point in the code, the demo freezes when the ball hits the paddle.[/quote]

by on (#51627)
Skidlz wrote:
I saw something in another post about NESASM treating banks as 32K instead of 16K, or some such thing.

It doesn't really matter how NESASM treats them, iNES files always counts PRG banks in 16KB units. Anyway, it's a fact that you are trying to assemble 32KB of PRG ROM, and if ".inesprg 1" writes a 1 to the PRG bank count field in the header, it's wrong. Try using a 2 here and see if it makes any difference.

Quote:
It assembles so I would guess it's correct.

Well, NESASM is notorious for outputting glitched binaries instead of showing errors. It is known to mess up with addressing modes and such and not telling the user anything. Don't assume it's correct just because it assembles.

BTW, what are you using to test your ROM? Make sure to test on different emulators, and that new and reliable emulators (such as Nestopia and Nintendulator) are in that list. Please tell us if different emulators behave differently.

by on (#51629)
Quote:
It doesn't really matter how NESASM treats them, iNES files always counts PRG banks in 16KB units. Anyway, it's a fact that you are trying to assemble 32KB of PRG ROM, and if ".inesprg 1" writes a 1 to the PRG bank count field in the header, it's wrong. Try using a 2 here and see if it makes any difference.


I get it. If it's writing to the iNES header, all that matters is what ends up in the header.
Well I changed it to ".inesprg 2" and it wont do anything at all.

Quote:
BTW, what are you using to test your ROM? Make sure to test on different emulators, and that new and reliable emulators (such as Nestopia and Nintendulator) are in that list. Please tell us if different emulators behave differently.


I use FCEUXD/FCEU and it has always worked. I know that doesn't mean much so I downloaded Nestopia.
Nestopia gives the error "CPU Jam!" when I add a NOP. It also refuses to run with ".inesprg 2"

by on (#51631)
Skidlz wrote:
I use FCEUXD/FCEU and it has always worked.

But you must make sure it runs elsewhere too. Trusting a single emulator is a bad idea.

Quote:
Nestopia gives the error "CPU Jam!" when I add a NOP.

Yeah, "CPU Jam" is when things get really out of control and stuff that shouldn't be executed as code is.

I'm out of ideas now. If you post the ROM (the one that screws up) I'd take a quick look in a debugger to see if I can find the problem.

Quote:
It also refuses to run with ".inesprg 2"

Yeah, I don't know much about NESASM, so I'm not sure what's happening here. Anyway, I'd be willing to take a look at the ROM if you post it.

EDIT: I'm taking a look at the ROM without the error to see if I can find something, but the one with the error would be more interesting.

by on (#51633)
Quote:
EDIT: I'm taking a look at the ROM without the error to see if I can find something, but the one with the error would be more interesting.


Well that's easy enough to do. I just added a nop on line 191 just after:
Code:
lda $4016  ; load RIGHT Status
and #1

http://www.mediafire.com/?sharekey=423a ... b9a8902bda

It looks like if you add any command anywhere and assemble, it will fail.

by on (#51635)
Skidlz wrote:
Well that's easy enough to do.

Only I don't use NESASM, so I can't assemble your sources...

Good news: I found the problem. It's a branch that's out of reach, and it scares me that NESASM will not throw an error because this!

Near the end of the moving logic you have "BNE infinite". Branches have a limited reach, and adding as much as a "nop" puts that command too far from the label "infinite", so the relative displacement is miscalculated (a good assembler would throw an error instead of miscalculating it!), and when this command is executed the program counter goes crazy and the program crashes.

The generic solution for when branches are out of reach is to check for the opposite condition, skipping over a JMP instruction to the place you wanted to branch to. So you could replace this:

Code:
   BNE infinite

with this:
Code:
   BEQ skip
   JMP infinite
skip:


But in your case it would probably be easier to change this:

Code:
   BNE infinite
   dec Direction
   JMP infinite

into this:
Code:
   BNE skip
   DEC direction
skip:
   JMP infinite


Anyway, keep in mind for future reference that branches shouldn't be used to jump across large distances. And it scares the shit out of me that NESASM will assemble the program with an incorrect displacement rather than throw an error.

BTW, I seem to remember now that NESASM banks are 8KB in size, so you are in fact making a valid ROM with 16KB of PRG-ROM.

by on (#51636)
Quote:
Only I don't use NESASM, so I can't assemble your sources...

Not that it matters but, I included NESASM.exe in the zip.

I remember running into this problem but I thought it was with all commands that goto an address. I didn't know branches were more limited than JMP's.

Everything you said makes perfect sense. Anything added inside of the infinite loop will push the branch further from the address it's branching to.
That *is* really miserable that the assembler doesn't say anything.

Thanks so much! I love when a problem gets resolved and you learn something.

Just wondering, how did you catch it?

by on (#51637)
Skidlz wrote:
I love when a problem gets resolved and you learn something.

That is the idea! =)

Skidlz wrote:
Just wondering, how did you catch it?

FCEUXD's debugger. When the ball approached the paddle I started tracing instruction by instruction, until that branch sent the PC forward (as opposed to backwards, as expected) to a place with lots of 0's (which the CPU interprets as BRK's). As consequence of the BRK command the CPU jumped to the location pointed by the IRQ vector, $0000 in your case (although I advised you to have and actual IRQ routine with just an RTI instruction in it, not that it would save in this case, as the CPU was already lost when the IRQ happened, but still). well by that time the CPU was already completely lost and was trying to execute your variables as code, which is never good.

by on (#51639)
tokumaru wrote:
well by that time the CPU was already completely lost and was trying to execute your variables as code, which is never good

Ha ha ha. True and hilarious.

Quote:
FCEUXD's debugger

I'm going to have to learn how to use that.

Thanks a ton.

by on (#51640)
For the record: You're using an old version of nesasm. The newest one (nesasm3) says the branch is out of range when one adds a nop there as expected. http://www.nespowerpak.com/nesasm/NESASM3.zip

by on (#51641)
Kasumi wrote:
For the record: I'm pretty sure you're using an old version of nesasm. The one I have (nesasm3) says the branch is out of range when I add a nop there. http://www.nespowerpak.com/nesasm/NESASM3.zip

Yeah, I've got multiple versions floating around my hard drive and I was in fact using the older one. Find and replace time.
Thanks for brining that up. Might save me and the forums some headaches.

by on (#57214)
tokumaru wrote:
Skidlz wrote:
I saw something in another post about NESASM treating banks as 32K instead of 16K, or some such thing.

It doesn't really matter how NESASM treats them, iNES files always counts PRG banks in 16KB units. Anyway, it's a fact that you are trying to assemble 32KB of PRG ROM, and if ".inesprg 1" writes a 1 to the PRG bank count field in the header, it's wrong. Try using a 2 here and see if it makes any difference.



Hey tokumaru;

How precisely do I tell if I have more than one bank? My asm file for a collision test I'm working on right now is 16.2 KB, but does an assembler count all my ;comments as part of the memory?

Also, my collision test is running on FCEUXD, but is running very buggy on Nestopia and Nintendulator. I suspect it might be the case you mentioned above, where I am trying to run too much memory on one bank. I tried changing the ".inesprg 1" to ".inesprg 2" just in case my .prg is too large, but now all I get is a blank screen in all three emulators. Is there anything additional I need to add to the code besides changing the inesprg in the header?

Keep in mind I'm also using NESASM 3, which might also be part of the problem. I know you and many on here use ASM6 instead of NESASM, and I'm thinking of making the switch.

by on (#57215)
bigjt_2 wrote:
How precisely do I tell if I have more than one bank?

Does NESASM have the capability to output a table of debugging symbols? Usually you can estimate the ROM size from that. Otherwise look at it in a hex editor.

Quote:
My asm file for a collision test I'm working on right now is 16.2 KB, but does an assembler count all my ;comments as part of the memory?

No. Each line of assembly language code becomes 1 to 3 bytes of object code, except for bulk data directives like .byt, .addr, or .incbin. Indentation doesn't show up in the object code, nor do comments.

Quote:
Also, my collision test is running on FCEUXD, but is running very buggy on Nestopia and Nintendulator.

Are you exceeding vertical blank time to update the VRAM?

Quote:
I suspect it might be the case you mentioned above, where I am trying to run too much memory on one bank. I tried changing the ".inesprg 1" to ".inesprg 2" just in case my .prg is too large, but now all I get is a blank screen in all three emulators. Is there anything additional I need to add to the code besides changing the inesprg in the header?

As I understand it, you need to put something in at least bank 0 and bank 3 if you plan to use .inesprg 2. (Each unit of .inesprg size is worth two 8 KiB banks.) Interrupt handlers (nmi, reset, irq) and vectors go in the last bank.

Quote:
Keep in mind I'm also using NESASM 3, which might also be part of the problem. I know you and many on here use ASM6 instead of NESASM, and I'm thinking of making the switch.

Everyone ends up on CA65 eventually. It's the only 6502 assembler toolchain whose name is a valid hexadecimal number :-)

by on (#57217)
bigjt_2 wrote:
How precisely do I tell if I have more than one bank?

Having more than 1 bank is not something that "just happens", it's something you have to plan for. If your code spills out of the bank there should be an error message somewhere (even though NESASM is known for generating invalid ROMs instead of reporting errors in some cases). Once you run into this problem you have to start writing code in another bank, and update the iNES header when necessary.

Quote:
My asm file for a collision test I'm working on right now is 16.2 KB, but does an assembler count all my ;comments as part of the memory?

No, the size of the source file has no direct relation to the size of the resulting binary. Assembled code is usually much smaller than the source code that generated it.

Quote:
Also, my collision test is running on FCEUXD, but is running very buggy on Nestopia and Nintendulator. I suspect it might be the case you mentioned above, where I am trying to run too much memory on one bank.

It's hard to say without looking at the ROM, but there are several other things that could have gone wrong. FCEUXD is not very accurate, so if your code has bugs they are more likely to show up on Nintendulator and Nestopia.

Quote:
I tried changing the ".inesprg 1" to ".inesprg 2" just in case my .prg is too large, but now all I get is a blank screen in all three emulators. Is there anything additional I need to add to the code besides changing the inesprg in the header?

I don't know much about NESASM, so I can't give you a straight answer... I think you have to specify where each bank starts, using ".bank" followed by the number (0, 1, etc) but I'm not sure.

Quote:
Keep in mind I'm also using NESASM 3, which might also be part of the problem. I know you and many on here use ASM6 instead of NESASM, and I'm thinking of making the switch.

Yeah, it's hard to make anything serious with NESASM. It's not impossible, but you have to learn how to circumvent a few problems if you want to succeed with it.

ASM6 is great, because it's really easy to use. You don't have to configure anything, just ask it to assemble a file and it does, and as long as your source code is correctly structured, a complete NES ROM is output. It is a generic 6502 assembler though, it's not specific to the NES, so it doesn't have automatic header generation or things like that. It's your responsability to make sure the iNES header is valid and correctly placed, and the same goes for each PRG and CHR bank. But if you have a good understanding of the structure of a NES file, this is not a problem.

The problem with CA65 and other "professional" assemblers is that you have to configure a lot of things before being able to assemble anything, and for each project that uses a different mapper you are likely to change these configurations. You also have to deal with object files an linking, something that to this day I do not understand. Too much bureaucracy for me, I like to go straight to what matters.

by on (#57224)
If it's running in FCEUX then it sounds like NESASM3 is producing a .nes file that will load and run. If I understand you correctly, you said that Nestopia and Nintendulator can load the .nes file too but that the collision detection is buggy? It sounds like it's a bug in your program logic rather than a problem with assembling the ROM.

Could you attach your .asm source file?

by on (#57228)
MetalSlime wrote:
Could you attach your .asm source file?

If the collision detection alone is over 16KB it will be very hard for us to catch anything wrong.

by on (#57237)
tokumaru wrote:
MetalSlime wrote:
Could you attach your .asm source file?

If the collision detection alone is over 16KB it will be very hard for us to catch anything wrong.


Could be, but it might be less code than you expect. He said his .asm file was 16k, which includes comments. I think I recognize his name from the nintendoage forums, so if he is working off the Nerdy Nights tutorials a lot of that space is likely data (eg, .db of a full background).

Even if we don't get into his collision code, at the very least we can check his iNES header and bank layout.

by on (#57243)
tepples wrote:
Does NESASM have the capability to output a table of debugging symbols? Usually you can estimate the ROM size from that. Otherwise look at it in a hex editor.

No. Each line of assembly language code becomes 1 to 3 bytes of object code, except for bulk data directives like .byt, .addr, or .incbin. Indentation doesn't show up in the object code, nor do comments.

Thanks, tepples. I didn't think the comments would be part of the object code, but it's good to have an expert confirm it.
tepples wrote:
Are you exceeding vertical blank time to update the VRAM?

Actually, this didn't end up being the problem. But this is something I need to teach myself so I'm glad you brought it up. I understand what vblank is, but not how to calculate the update time. It's one of the many things on my list to figure out.
tokumaru wrote:
If the collision detection alone is over 16KB it will be very hard for us to catch anything wrong.

No worries. From what you and tepples described I now know how to find my PRG size, and I can tell you this is definitely under 16 KiB.

Please keep in mind as you read this (and especially if you look at my amatuerish code in the .asm files) that I am a VERY green noob and not a programmer.

This is an uber-simple beginner collision test I made just to practice the basics. You basically are moving the blue square, and my goal was to make it stop whenever it collided with the reddish-brown square. I've been working on it a few days, and just now (finally) got it to work as I expected. Woo-hoo! I know, nothing too impressive, but one small step at a time! :-)

I have two versions of this file. At first, all my collision tests for right, left, down, up were in the sections for those respective keys. (That's the collision.asm file, and now it's working on all three emulators.) My collision wasn't working right at first, so I moved the relevant code to subroutines. Partly because I thought the code might be interfering with each other and subs might help, and partly because I wanted to practice writing subroutines and finding out what works and what doesn't.

It turns out that I was doing something wrong with writing the subs. Either I have typos or they're placed in the wrong place or something else altogether. If you assemble the collision_subroutines.asm and run it on Nintendulator or Nestopia, you'll see what I mean. Those two emulators scream, "this ROM is stupid! For God's sakes just make it stop!" FCEU, as I've been warned, is just like "Sure, man, do whatever ya want. I'm just gonna sit here and chill while your incorrect rom runs as though its flawless..." I think from now on I'm definitely testing my stuff on the first two, as I've read a comment from Sivak on Nintendo Age forums saying they're more accurate to a real NES. I've also seen plenty of people on this forum say the same thing, like tokumaru.

Anyway, I guess what I need to figure out now is what I'm doing wrong with my subroutines, as I mentioned above.

If any of you want to look at this, please feel free. My collision test code is very beginner, like I said. So if you have any tips on cleaning it up I'll be glad to take it. I know I've got a long way to go and a LOT to learn. And I imagine I'll have to change it for a second collision object. That's actually my next goal.

http://www.mediafire.com/?jxdjdznjmog

metalslime wrote:
I think I recognize his name from the nintendoage forums, so if he is working off the Nerdy Nights tutorials a lot of that space is likely data (eg, .db of a full background).

I am actually using bunnyboy's tutorials, metalslime, as you'll see in the code. I've been bouncing between Nintendo Age and Nesdev here since I started getting into this a couple weeks ago. I've also been checking out your tummai games site, as well. Very cool, but most of is beyond my skill level at this point. I especially liked the button combos write up.

PS- Am I too off-topic for this thread? Do you guys need me to ask an admin to move it somewhere else?

PPS- Oh yeah, I also forgot to mention that I've noticed that annoying grey tile in the upper left-hand corner. For the life of me I can't figure out why the hell that thing is there. I know it's something I did, but I've just been kicking it down the road in regards to fixing it.

by on (#57247)
I didn't check everything but one thing, in your routine file, at the end of NMI, you do "JMP RTI". The JMP is not necessary, only RTI.

At the end of an interrupt routine or sub routine you need to tell the assembler that you need to go back to the point where it was called. For interrupt you use RTI (return from interrupt) and from sub routine you use RTS (return from sub routine). What it basically does is that it "return" to the address before the NMI or sub routine was called.

As for the reason that it goes haywire, all your logic is called from the NMI. This is a big no-no on the nes. NMI should be only used to do critical things like updating the screen. This logic should be done outside the nmi, in your game loop. For now, your code doesn't have any: there is only a forever loop. If you want your code to work properly, you will need to put the logic at the right place. Of course this is something you cannot guess from the first time you write code on the nes.

One approach is to put a flag that is updated in NMI (a counter for example), once NMI is over, the counter will be updated. In the "forever" loop, you wait that the coutner change. Once this is done, this is when you can do your collision detection logic.

I'm concerned about this comment thought:

Quote:
Please keep in mind as you read this (and especially if you look at my amatuerish code in the .asm files) that I am a VERY green noob and not a programmer.


Do you know about the basics of programming at the least? If not, this will make you life quite a living hell by starting to program on the nes. The nes is not very forgiving if you make any mistakes. I don't say that it's not possible, some people here did and succeeded but if you really don't have any background in programming, I would suggest that you read a little bit about the fundamentals like what are variables, loops etc. That will help you in the end.

by on (#57250)
bigjt_2 wrote:
PPS- Oh yeah, I also forgot to mention that I've noticed that annoying grey tile in the upper left-hand corner. For the life of me I can't figure out why the hell that thing is there. I know it's something I did, but I've just been kicking it down the road in regards to fixing it.


I haven't had a chance to look at the code in detail yet (will do once the kid goes to bed), but I know why you are getting the gray tile. Actually, you have something like 60 gray tiles there.

For unused sprites we normally set their location to somewhere offscreen. Setting a sprite's Y value to something greater than $F8 will do this. Your reset code fills the $300 page of RAM with $FE (see the "clrmem" label), but then your sprite code uses the $200 page of RAM for sprites. $200 gets filled with $00 in your reset code, so all of the sprites are set to X=0, Y=0 - the top left of the screen.

To fix this, edit your clrmem code and have it write $FE to the $200 page of RAM instead of the $300 page.


edit: the reason collision_subroutines.nes is crashing in Nintendulator/Nestopia is you have a line in your code that isn't indented. The RTS command in RightCollisionTest needs to be indented. Fix that and the program runs fine.


And as Banshaku said: don't JMP RTI. Just stick an RTI command at the end of nmi, and get rid of the RTI: RTI label you have.

He's also right about logic code in the NMI - most people try to avoid that. For a small program like this it probably won't matter. But when you start writing more complex programs you will probably want to reserve NMI for drawing and put game logic in the main loop outside NMI.

Disch wrote a great document about frames/NMI that you should read. It might not make sense to you yet, but if you reread it from time to time as you gain more experience programming the NES it will all come together eventually. I think its found here, although I'm having some trouble viewing it correctly right now: http://sites.google.com/site/nesdevhand ... frame.html

by on (#57261)
MetalSlime wrote:
He's also right about logic code in the NMI - most people try to avoid that. For a small program like this it probably won't matter. But when you start writing more complex programs you will probably want to reserve NMI for drawing and put game logic in the main loop outside NMI.

There are three ways to structure your code:
  • Like Super Mario Bros., with everything after the init code in the NMI handler; main thread is just "forever: jmp forever"
  • Like Final Fantasy and LJ65, with everything in the main thread and an NMI handler that just sets a flag that NMI occurred
  • VRAM update code in NMI, everything else in main thread

by on (#57265)
Banshaku wrote:
Quote:
Please keep in mind as you read this (and especially if you look at my amatuerish code in the .asm files) that I am a VERY green noob and not a programmer.


Do you know about the basics of programming at the least? If not, this will make you life quite a living hell by starting to program on the nes. The nes is not very forgiving if you make any mistakes. I don't say that it's not possible, some people here did and succeeded but if you really don't have any background in programming, I would suggest that you read a little bit about the fundamentals like what are variables, loops etc. That will help you in the end.


Yeah, actually I've done programming for years here and there, but only as a hobbyist. It's never been something I've studied in school or -obviously :-) - done as a profession. The basic concepts of constants, loops, variables, addresses, boolean logic I'm fairly well versed in. However there are some that are still eluding me and I am reading up on them.

I'll work on moving the logic and figuring that out. Thanks for the help, folks.

by on (#57266)
MetalSlime wrote:
edit: the reason collision_subroutines.nes is crashing in Nintendulator/Nestopia is you have a line in your code that isn't indented. The RTS command in RightCollisionTest needs to be indented. Fix that and the program runs fine.

Of course it would be something that simple I overlook. :-/ Thanks, metalslime.

metalslime wrote:
And as Banshaku said: don't JMP RTI. Just stick an RTI command at the end of nmi, and get rid of the RTI: RTI label you have.

He's also right about logic code in the NMI - most people try to avoid that. For a small program like this it probably won't matter. But when you start writing more complex programs you will probably want to reserve NMI for drawing and put game logic in the main loop outside NMI.

Disch wrote a great document about frames/NMI that you should read. It might not make sense to you yet, but if you reread it from time to time as you gain more experience programming the NES it will all come together eventually. I think its found here, although I'm having some trouble viewing it correctly right now: http://sites.google.com/site/nesdevhand ... frame.html

Definitely will take a look at it today. This is something I'm still having trouble on. Thanks again, guys.

by on (#57267)
tepples wrote:
There are three ways to structure your code:
  • Like Super Mario Bros., with everything after the init code in the NMI handler; main thread is just "forever: jmp forever"
  • Like Final Fantasy and LJ65, with everything in the main thread and an NMI handler that just sets a flag that NMI occurred
  • VRAM update code in NMI, everything else in main thread


Yeah, I actually found the SMB disassembly on ROMhacking.net (I believe you were the one that clued me in to its existence, tepples -nice!) and I've been looking at it. But obviously it's waaaaay beyond anything I'm doing now. I can understand fragments of it but here and there I start feeling like I'm reading heiroglyphs. It's a pretty valuable learning tool, though. Again guys, danke! I really appreciate you generously giving me your time here.

by on (#57286)
tepples wrote:
  • Like Super Mario Bros., with everything after the init code in the NMI handler; main thread is just "forever: jmp forever"

If by any chance processing the logic of a frame takes longer than a frame, you'll either have an NMI firing on top of the other (which will mostly crash your game) or you'll miss the vertical blank altogether, which will cause the whole game to lag.

Quote:
  • Like Final Fantasy and LJ65, with everything in the main thread and an NMI handler that just sets a flag that NMI occurred

The game won't crash if the logic takes too long, but the whole thing will still lag.

Quote:
  • VRAM update code in NMI, everything else in main thread

Here you have the option to update some stuff even if the frame logic isn't finished yet, like the music, so that even though the gameplay lags the music doesn't (this seems to annoy players less than when everything lags). Also, your game logic can prepare data for different types of updates gradually, so that the NMI can use whatever is ready when it fires (my raycaster works like that), making better use of the available time.

Conclusion: only use the first two options if your game logic is guaranteed to finish in less than one frame's time, otherwise you'll have some nasty stuff to deal with (specially if you rely on timed code or sprite 0 hits for status bars and things like that).

Also, if you do go with the first option, make sure to perform VRAM updates first, and then compute game logic, because the NMI fires when VBlank starts, and it would be a waste to use precious VBlank time with logic that is not updating the video.

by on (#57287)
Quote:
If by any chance processing the logic of a frame takes longer than a frame, you'll either have an NMI firing on top of the other (which will mostly crash your game) or you'll miss the vertical blank altogether, which will cause the whole game to lag.

SMB and Zelda will lag, including the sound, and SMB's status bar will shake horizontally.

Konami games, however, that are also structured like that, are designed in a way that a NMI can interrupt another without the game to crash. I have no idea if crashes of Castlevania (PRG0) are related to this or not - but Gradius games slow down a lot and never crash.

Even if you use the balanced option - your game might crash/get bugs if you don't proprely save the registers and separe the variables you use in the interrupt and the ones you need in the main thread. This is the kind of stuff you can work on an entiere games that everything works well but the day it lags it crashes or something and you won't understand why.

by on (#57290)
Bregalad wrote:
SMB's status bar will shake horizontally.

Exactly, that's terrible. If it misses the NMI it can't properly place the status bar, that's a very poor design choice. I always mention music as one of the critical things that should be updated even during lag frames, but status bars are definitely important too. I'm seriously annoyed by shaking status bars.

Quote:
Konami games, however, that are also structured like that, are designed in a way that a NMI can interrupt another without the game to crash.

You can code a game like that and have the NMI know if it interrupted another by using flags to indicate that, and in case it did interrupt it just takes care of the critical stuff (music, status bar). But if you think about it, this is just a simulation of the case when there's a main thread and an NMI thread. Why not use the real thing instead of simulating it?

Quote:
I have no idea if crashes of Castlevania (PRG0) are related to this or not - but Gradius games slow down a lot and never crash.

Games would most likely crash if a new frame's logic started being calculated before the previous one ended. Variables would be overwritten, the stack could overflow, and the game would basically find itself in an inconsistent state. This is something no programmer wants, and a bug that might be hard to catch because the game ran fine before (when less computation was done before you added a new feature). That's one of the reasons I'm against this set up.

Quote:
Even if you use the balanced option - your game might crash/get bugs if you don't proprely save the registers and separe the variables you use in the interrupt and the ones you need in the main thread.

Sure, no matter what you do you can always screw it up! =) But to me the idea of a main thread separated from the NMI thread is more logical in case your game logic might take more than one frame (which is hardly the case for static screen puzzle games, so these usually get away with anything).

Having the NMI set a flag is OK in my book, as long as you're sure the game logic takes less than a frame to complete. Having everything in the NMI on the other hand is the worst design choice a NES programmer can make. Not only it has the potential for causing disastrous results in case of lag, but the whole arrangement of taking care of graphical updates before taking care of the game logic is kinda awkward.

You know, you have to first process a frame worth of game logic so that you have something to update the screen with, but if you have everything inside the NMI you have that in reverse order, so you're gonna need some special handling for the first frame or you'll have to use a flag to indicate whether a frame has been processed, and if you have that you are just simulating the other method, like I mentioned before. I see absolutely no point in having everything in the NMI.

Quote:
This is the kind of stuff you can work on an entiere games that everything works well but the day it lags it crashes or something and you won't understand why.

Exactly. That's a nasty kind of bug, because one minute everything is working well, but then you add this tiny new little feature and all hell breaks loose. It will take you forever to realize it was a timing thing, rather than a problem with the new feature.

by on (#57296)
tokumaru wrote:
Here you have the option to update some stuff even if the frame logic isn't finished yet, like the music, so that even though the gameplay lags the music doesn't (this seems to annoy players less than when everything lags).

If the code signals NMI by adding 1 to a counter (which is dead easy on 6502), it will increment the counter twice, and the main thread can detect this and run music twice. Pokemon Blue's music often gets behind by five or six frames while things load into the Game Boy's VRAM, but it always catches up.

Quote:
Also, your game logic can prepare data for different types of updates gradually, so that the NMI can use whatever is ready when it fires

Which means you have to learn how to use semaphores so that your VRAM update logic never sees any half-finished buffers. If you can write a tutorial for the wiki on splitting updates into main and NMI threads, and you can remember to cover the various locking scenarios, I'd appreciate it.

Quote:
Conclusion: only use the first two options if your game logic is guaranteed to finish in less than one frame's time, otherwise you'll have some nasty stuff to deal with (specially if you rely on timed code or sprite 0 hits for status bars and things like that).

If your sprite 0 is near the bottom, you'll have problems even if you do update VRAM in an NMI thread. That's why (if I recall correctly) Gradius counts the number of "things" it does in a frame and then waits for sprite 0 if it needs to do more than the number of "things" it can safely do in a frame.

by on (#57297)
Quote:
Why not use the real thing instead of simulating it?

Well go ask Konami !

Quote:
Games would most likely crash if a new frame's logic started being calculated before the previous one ended. Variables would be overwritten, the stack could overflow, and the game would basically find itself in an inconsistent state. This is something no programmer wants, and a bug that might be hard to catch because the game ran fine before (when less computation was done before you added a new feature). That's one of the reasons I'm against this set up.

Yeah, but again you can absolutely sure the program will never lag in some condition.
Quote:

Having the NMI set a flag is OK in my book, as long as you're sure the game logic takes less than a frame to complete. Having everything in the NMI on the other hand is the worst design choice a NES programmer can make. Not only it has the potential for causing disastrous results in case of lag, but the whole arrangement of taking care of graphical updates before taking care of the game logic is kinda awkward.

Altough I agree with you that "everything in NMI" idea is questionable, I don't think it is a valid argument : you don't have to code the program in the same order as it is executed. Taking care of VRAM updates typeically is just emptying a few buffers if corresponding flags are set, and one of the first things you'd want to code anyway.

Personally I have tried all 3 : When I fist started with Nesticle and nesasm I did the "everything in NMI" thing because other programs I've investigated did that. Eventually I found it inconvenient and moved to "everything outside of NMI" philosophy. Again I found it inconvenient, and when I eventually started my real game project I opted for the balanced option and it works wonderfully well.
However this is also due that my programming skills and knowledge of hardware increased as I switched from a model to another.

For me, having everything in NMI is like having the AI of enemies starting always at the same adress... it's *possible* to do things that way, but as soon as you want to do something more complicated it will become a terrible headache because you'll need countless variables associated with if/else statements in your code and it will be VERY easy to screw up. Konami still managed to develop dozen of NES games this way, and very good ones.

Quote:
That's why (if I recall correctly) Gradius counts the number of "things" it does in a frame and then waits for sprite 0 if it needs to do more than the number of "things" it can safely do in a frame.

Well since it doesn't use any kind of IRQs that's the only way it could do that, but I wonder how they did it. Chances are that they risk to either count too fast and lag when it could run without lagging, or count too slow and result in a shaking bar.

by on (#57302)
tepples wrote:
If your sprite 0 is near the bottom, you'll have problems even if you do update VRAM in an NMI thread.

Sure, but this is a special case of "early VBlank", and the NMI can only warn you about regular VBlanks, not early ones. I guess this is why status bars at the bottom of the screen only became common once scanline counters were widely available.

The solution used by Gradius is very sub-optimal though, because the game wastes time figuring out if it can use more time, which is a paradox. If it didn't have to check, there would be more free time, but not necessarily enough. Let's agree that even though it's better than visual glitches, it's still not a very good solution.

Bregalad wrote:
Altough I agree with you that "everything in NMI" idea is questionable, I don't think it is a valid argument : you don't have to code the program in the same order as it is executed. Taking care of VRAM updates typeically is just emptying a few buffers if corresponding flags are set, and one of the first things you'd want to code anyway.

You are right, but my point is that since you are using flags to control what gets updated and what doesn't anyway, it would make more sense to clearly separate the 2 threads than awkwardly pack them together.

Quote:
However this is also due that my programming skills and knowledge of hardware increased as I switched from a model to another.

Yeah, the whole concept of interrupts is hard to grasp for a beginner, and for them it's probably easier to code it all linearly.

The problem is that many tutorials would rather teach what gives quick results than best practices. It may look like a good idea at first, but in the long run it ends up confusing people.

by on (#57327)
tokumaru wrote:
I see absolutely no point in having everything in the NMI.


Really, the only point is so you can perform the code defined in the NMI routine one time every frame. It just regulates the speed at which your code is executed. This is what some see as the easy way out of setting up a main loop that's executed once every frame by waiting for the next frame at the end of the loop. But once you figure it out, it's pretty uncomplicated:

Code:
NMI:
 ;Save A, X, and Y
 ;blah code
 ;Restore A, X, and Y
 inc VBLCount
 rti

MainLoop:
 ;blah code

 lda VBLCount
-
 cmp VBLCount
 beq -
 jmp MainLoop



So once you see the simplicity of setting that up, you're right, there's really no advantage to putting your code in the NMI routine. I think it's just being lazy. If you're making a simple test program, it's fine. If you're making a game like a platformer or something that has to work with AI and all sorts of decompression, it would be catastrophic.

by on (#57331)
I agree 100% with celius, but yet I just wanted to point out that Gradius, Contra, Super C, Castlevania 1, 2 & 3, Lagrange Point, etc.... ALL does everything in NMI, so this is possible. I'd still recommand doing what Celius says for standard applications, but in some cases if you do crazy raster effects and want to synchronize with NMI (instead of sprite zero hit) you're pretty much forced to do otherwise and have your raster effect in NMI.

However, it's rare in an actual game you'll find yourself writing to $2005 every scanline or so - but for example Marble Madness which writes to $2000 twice every scanline to display text was forced to do everything in NMI like early Nintendo and Konami games.

So the "ultimate" solution is to do what Celius says, but if you want to do something special like crazy raster effect have a flag in the NMI routine that makes it execute another portion of code instead of the normal NMI so that everything is timed etc...

by on (#57335)
tepples wrote:
tokumaru wrote:
Here you have the option to update some stuff even if the frame logic isn't finished yet, like the music, so that even though the gameplay lags the music doesn't (this seems to annoy players less than when everything lags).

If the code signals NMI by adding 1 to a counter (which is dead easy on 6502), it will increment the counter twice, and the main thread can detect this and run music twice. Pokemon Blue's music often gets behind by five or six frames while things load into the Game Boy's VRAM, but it always catches up.

Yeah so then it will not lag, it will "skip" which is equally bad in my opinion.

Bregalad wrote:
I agree 100% with celius, but yet I just wanted to point out that Gradius, Contra, Super C, Castlevania 1, 2 & 3, Lagrange Point, etc.... ALL does everything in NMI, so this is possible. I'd still recommand doing what Celius says for standard applications, but in some cases if you do crazy raster effects and want to synchronize with NMI (instead of sprite zero hit) you're pretty much forced to do otherwise and have your raster effect in NMI.

Well, that's simply not true, it could have as well returned from NMI to the main loop which checks the "nmi flag". There's no upside to doing that though if you need raster timed code, so might as well have everything in NMI. (And there's a small downside: the NMI flag polling loop will result in a more variable delay depending what code is executing when NMI occurs.)

by on (#57336)
tepples wrote:
Quote:
Also, your game logic can prepare data for different types of updates gradually, so that the NMI can use whatever is ready when it fires

Which means you have to learn how to use semaphores so that your VRAM update logic never sees any half-finished buffers.


I just realized that my drawing buffer code doesn't protect against this. Right now if the NMI comes while my main program is adding to the buffer, bad things would definitely happen!

I don't think that will ever happen in my current project, but I can't be sure. I need to fix this right away.

by on (#57339)
Quote:
Well, that's simply not true, it could have as well returned from NMI to the main loop which checks the "nmi flag". There's no upside to doing that though if you need raster timed code, so might as well have everything in NMI. (And there's a small downside: the NMI flag polling loop will result in a more variable delay depending what code is executing when NMI occurs.)

You are right - but forget an important detail : in order for it to work you'll have to design ALL your NMI routine so that everything takes exactly or almost exacly the same number of cycles no matter what. So basically if you don't need to do ALL updates to VRAM, you'll have to kill time, and design your sound code so that it always execute in a constant time, etc... which sounds quite tedious to me (altough it's very possible).

If you do the raster effect in NMI and do the variable lenght stuff (such as music) after it it would make things simpler - but you'll need to code an alternate NMI routine that does the effect (the only time I've ever done this is in my "midscanline" demo where 7 cycle jitter wasn't acceptable).

by on (#57340)
Bregalad wrote:
I'd still recommand doing what Celius says for standard applications, but in some cases if you do crazy raster effects and want to synchronize with NMI (instead of sprite zero hit) you're pretty much forced to do otherwise and have your raster effect in NMI.

SMB, for example, could have avoided the shaky status bar if the NMI, firing at the start of every VBlank, handled everything related to it (scroll, sprite 0 hit), only returning control to the game logic after having displayed it and adjusted the scroll for the gameplay window.

Things that should happen even during lag frames should go in the NMI, it's the only way to make sure they will always happen. But you can't have only the critical stuff in the NMI, because you can't, for example, run the music code and then return control to the main thread to take care of VRAM updates, because a lot of VBlank time would have been wasted with music. So the logical answer is to put all video updates in the NMI as well, before the critical stuff.

Celius' set up allows for this, VRAM updates and critical stuff (music, status bar) go into the "blah code" part. It's important that the code that updates VRAM makes sure a frame is complete and that the values in the buffers are valid though.

by on (#57341)
Bregalad wrote:
(the only time I've ever done this is in my "midscanline" demo where 7 cycle jitter wasn't acceptable).

The 7-cycle jitter will always happen, whether from a loop like this:
Code:
: cmp nmis
  beq :-

or from a single 7-cycle instruction like "rol $0100,x" (which I in fact use in my CHR decompressor).

by on (#57342)
No, inside an interrupt which happen in a cmp/beq loop, there is 3 cycle jitter because both the cmp and the beq takes 3 cycles (and all similar loops, such as lda/bne, lda/beq, cmp/bne, bit/bpl, etc... are the same which is useful). This is what I tried to explain in my latest update of my raster code FAQ.

by on (#57349)
But if the NMI occurs anywhere but a tight loop like the cmp/beq loop, it might run into one of these 7-cycle RMW instructions.

by on (#57357)
tepples wrote:
The 7-cycle jitter will always happen, whether from a loop like this:
Code:
: cmp nmis
  beq :-

With this code, the jitter will not be as big if the timing-sensitive code is inside the NMI, because the instruction that is executing when the interrupt fires is the only one that matters in this case. If however your NMI just sets a flag (which is how you said your programs work), the whole time this loop takes matters.

Quote:
or from a single 7-cycle instruction like "rol $0100,x" (which I in fact use in my CHR decompressor).

Only slow instructions like these will cause 7-cycle jitters if the timing-sensitive code is inside the NMI, but that's the worst that can happen because no instruction takes longer than this.

by on (#57421)
Thanks so much guys,

I've learned a lot from this thread and the document metalslime recommended about NMI and Vblank. Just to check that I understand and am doing things a little better, I've placed some new code at the link below where I've placed the logic into a main loop called GameLogic:, separated it from the NMI, and left only the update logic for my moving box sprite within the NMI. I also implemented the technique Celius recommended at the end of the main loop (again, called GameLogic), as well as the better controller reading bunnyboy covers in one of his tutorials.

http://www.mediafire.com/?w2hgoyzd4mm

I know it's unlikely that the small amount of logic code I have in this basic collision test would spill over the frame and get nailed by the next NMI firing, but I can easily see where this would cause problems on more advanced projects and wanted to start teaching myself better habits now. If any of you wish to take a look and let me know what I can improve, please do.

by on (#57429)
bigjt_2 wrote:
I've placed the logic into a main loop called GameLogic:, separated it from the NMI, and left only the update logic for my moving box sprite within the NMI.

Technically, updating the positions of the sprites should be part of the game logic too. Ideally, the NMI would just do the sprite DMA and set up the display for the next frame. In the NMI you should only have the bare essentials for updating VRAM.

Maybe you though you had to update the sprite coordinates during Vblank because everyone says that video-related stuff should be done at that time, but modifying the RAM where the sprite data is does not count as a video-update operation. That's just regular RAM, you can mess with it whenever you want. What must be done during Vblank is the sprite DMA, which will copy the data from RAM to the OAM. As long as the DMA is done during Vblank, you can modify the values in the page you set aside for sprites all you want, no restrictions.

Quote:
I know it's unlikely that the small amount of logic code I have in this basic collision test would spill over the frame and get nailed by the next NMI firing, but I can easily see where this would cause problems on more advanced projects and wanted to start teaching myself better habits now.

Yeah, that's the idea. If you design the structure of your program well, it will give you less headaches in the future.

by on (#57431)
Okay, I see the difference now. So in this instance the graphic update (and the only thing that really needs done in NMI) is the DMA, which is simply transferring my sprites (with their current attributes and coordinates) from my RAM (in this case the $0200 page) to the PPU (through the $4014 write register). And that, of course, is done right after the NMI: label.

I just re-incorporated my UpdateSprites back into the main logic and see it's still working fine. Cool, learned something again. Getting there slowly but surely. Thanks!

So, just as a clue for later, when I get advanced enough to do scrolling and start wanting to shift nametables, does it work kind of like this, where there's a quick graphical update in NMI and the logic to shift into one nametable or another is handled in logic outside of NMI? I'm assuming it is. (I'm not asking for specifics, just a general idea, mostly because I barely understand how scrolling works right now).

by on (#57433)
bigjt_2 wrote:
So, just as a clue for later, when I get advanced enough to do scrolling and start wanting to shift nametables, does it work kind of like this, where there's a quick graphical update in NMI and the logic to shift into one nametable or another is handled in logic outside of NMI?

It's similar, but unfortunately Nintendo didn't include something to speed things up (like the DMA does for sprites), so we must do most of it ourselves.

Inside the game logic loop is where all the camera movement will be, and whenever the camera moves enough to show a new part of the level you have to read data from your level map (what format will be used to encode it is up to you) and feed some buffers with the tile and attribute data corresponding to the new part of the level. Then, inside the NMI, you copy that data to VRAM as fast as you can.

It's the same principle: compute all the data inside the game logic loop and in the NMI just upload the computed data to VRAM.

Note that to make a scrolling game you don't have to shift the whole background, as that would take too much time and would be impossible to update in a single VBlank. This is why the hardware scroll exists, to display different parts of the name tables based on the coordinates you give it. This means that each frame you only have to worry about new background elements that enter the game view from the edges of the camera.

Like I always recommend, play a scrolling game (preferably one that doesn't use 1-screen mirroring, because that's hard to see) in an emulator that has name table viewing (FCEUX(D), Nintendulator... even Nesticle!) and watch how the new things that enter the view replace old ones that are no longer visible.