Strange Timing Bug

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
Lucradan
Posts: 101
Joined: Wed Sep 21, 2016 12:08 pm

Strange Timing Bug

Post by Lucradan »

I wrote some code to load pattern tiles onto the PPU, but I am having a strange bug I cannot seem to figure out. The code is below. I am using FCEUX 2.2.3, New PPU enabled. The pattern table I have included has NINE pages (or 9 x 16 tiles = 2304 bytes). When I set NUMPAGES to 9 all I get is a gray screen. When I set it to 10 I have an extra page of garbage tiles in the PPU, but the ROM works fine. If I set the NUMPAGES to 10 and add a DEX command immediately after the LDX command it also works and I don't have the extra garbage tiles in the PPU. If I set NUMPAGES to 9 and add a NOP command right after the LDX it also works. Does anyone have a clue what might be causing this? I've never seen any timing bug like this before.

Code: Select all

  NUMPAGES = 10

  LDA $2002
  LDA #$00
  STA $2006
  STA $2006
  LDX #NUMPAGES
  LDY #$00
LBL.OriginStory.LoadTiles.Background.Loop:
    LDA (PTR.Tiles),y
    STA $2007
    INY
    BNE LBL.OriginStory.LoadTiles.Background.Loop
	  INC PTR.Tiles+1;
	  DEX
	  BNE LBL.OriginStory.LoadTiles.Background.Loop
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Strange Timing Bug

Post by tepples »

What's in your NMI handler? What address is LBL.OriginStory.LoadTiles.Background.Loop getting assembled at? You can use your assembler's map file or place a breakpoint on $2006 writes to answer the latter.

I ask because I'm guessing some combination of page crossing and NMI clobbering registers.
User avatar
Lucradan
Posts: 101
Joined: Wed Sep 21, 2016 12:08 pm

Re: Strange Timing Bug

Post by Lucradan »

Here is the FCEUX debugger code. It looks like 00:82D2

Code: Select all


 00:82C2:AD 02 20  LDA PPU_STATUS = #$40
 00:82C5:A9 00       LDA #$00
 00:82C7:8D 06 20  STA PPU_ADDRESS = #$02
 00:82CA:8D 06 20  STA PPU_ADDRESS = #$02
 00:82CD:A2 09       LDX #$09
 00:82CF:EA            NOP
 00:82D0:A0 00       LDY #$00
 00:82D2:B1 10       LDA ($10),Y @ $B740 = #$00
 00:82D4:8D 07 20  STA PPU_DATA = #$00
 00:82D7:C8           INY
 00:82D8:D0 F8      BNE $82D2
 00:82DA:E6 11      INC $0011 = #$B7
 00:82DC:CA          DEX
 00:82DD:D0 F3     BNE $82D2
My NMI handler is here

Code: Select all

;--------------------------------------------------------------------
;CODE:  NMI
;--------------------------------------------------------------------
LBL.NMI:

  PHA
  TXA
  PHA
  TYA
  PHA
 
  LDA FLAG.NMI.Wait
  BEQ LBL.NMI.LoadPalette.Sprites
    JMP LBL.NMI.Exit  

LBL.NMI.LoadPalette.Sprites:
  LDA FLAG.NMI.Update.Sprites.Palette
  BEQ LBL.NMI.LoadPalette.Background
    LDA $2002
    LDA #$3F
    STA $2006
    LDA #$10
    STA $2006  
    LDX #$00
    LBL.NMI.LoadPalettes.Sprites.Loop:
      LDA BUF.Palette.Sprites, X
      STA $2007
      INX
      CPX #$10
      BNE LBL.NMI.LoadPalettes.Sprites.Loop
	
	
LBL.NMI.LoadPalette.Background:
  LDA FLAG.NMI.Update.Background.Palette
  BEQ LBL.NMI.UpdateSprites
    LDA $2002
    LDA #$3F
    STA $2006
    LDA #$00
    STA $2006   
    LDX #$00
    LBL.NMI.LoadPalettes.Background.Loop:
      LDA BUF.Palette.Background, X
      STA $2007
      INX
      CPX #$10
      BNE LBL.NMI.LoadPalettes.Background.Loop
  


LBL.NMI.UpdateSprites:
  LDA FLAG.NMI.Update.Sprites
  BEQ LBL.NMI.Exit
    LDA #$00
    STA $2003
    LDA #>BUF.Sprites
    STA $4014
	LDA FALSE
    STA FLAG.NMI.Update.Sprites	

LBL.NMI.Exit:
  LDA #%10001000
  STA $2000
  LDA #$00
  STA $2005
  STA $2005
  INC VAR.Frame
  PLA
  TAY
  PLA
  TAX
  PLA
  RTI
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Strange Timing Bug

Post by dougeff »

Assuming you've correctly set FLAG.NMI.Wait to avoid the reads from 2002 and writes to 2006/2007...

I believe the 2 writes to $2005 at the end of NMI might be the problem, but I can't remember exactly how they affect 2006/2007 writes.

Personally, I turn off NMIs during large block writes to the PPU.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
Quietust
Posts: 1918
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Strange Timing Bug

Post by Quietust »

dougeff wrote:Assuming you've correctly set FLAG.NMI.Wait to avoid the reads from 2002 and writes to 2006/2007...

I believe the 2 writes to $2005 at the end of NMI might be the problem, but I can't remember exactly how they affect 2006/2007 writes.
Writing $2000/$2005/$2005 at the end of NMI is the correct way of resetting the VRAM address for rendering, and it overrides whatever you wrote to $2006 previously.

However, the most important part is to ensure that it's before the end of VBLANK, which means you need to make sure your VRAM writes aren't taking too long (and that you do it before other non-essential stuff like reading controller input or updating your sound engine).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Strange Timing Bug

Post by dougeff »

but it's not his NMI code doing the buggy writes. it's his main code, and the NMI code is interrupting it, I think.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Strange Timing Bug

Post by tokumaru »

If the code that loads patterns is interrupted by the NMI, and the NMI uses any of $2000, $2005, $2006 or $2007, that will certainly interfere with the VRAM address and mess up the update. If you're keeping NMIs on while the main thread performs PPU updates (which is a good thing, because you can continue playing sounds during screen transitions, for example), be sure to implement a way to skip all PPU operations if rendering is disabled. I normally check the buffered copy of PPUMASK to know whether it's safe to use the PPU in the NMI handler or not.
User avatar
Quietust
Posts: 1918
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Strange Timing Bug

Post by Quietust »

Ah, I misunderstood what was going on - that's what I get for not fully reading the entire thread.

One solution to this problem is to just ensure that an NMI will never occur during VRAM writes - either disable NMI during any VRAM writes (which is probably a good idea anyways), or schedule them to occur during your NMI routine.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
User avatar
Lucradan
Posts: 101
Joined: Wed Sep 21, 2016 12:08 pm

Re: Strange Timing Bug

Post by Lucradan »

Most likely that is the problem.

The initialization code for that screen begins with

Code: Select all

  ; Disable Rendering
  LDX #$00
  STX $2001    ; disable rendering
  
  ; Set NMI to Wait
  LDA TRUE
  STA FLAG.NMI.Wait
And ends with

Code: Select all

  ; Set NMI FLAGS
  LDA FALSE
  STA FLAG.NMI.Wait  
  LDA TRUE
  STA FLAG.NMI.Update.Sprites.Palette
  STA FLAG.NMI.Update.Background.Palette
  STA FLAG.NMI.Update.Sprites	

  ; Wait for Next NMI
   - BIT $2002
    BPL - 
	
  ; Set NMI FLAGS
  LDA FALSE
  STA FLAG.NMI.Wait
  STA FLAG.NMI.Update.Sprites.Palette
  STA FLAG.NMI.Update.Background.Palette
  
  ; Re-Enable Rendering
  LDA #%00011110
  STA $2001 
It seems I need to add another NMI FLAG that can bypass the $2005 writes. Something like

Code: Select all

LBL.NMI.Exit:
  LDA FLAG.NMI.BlockWrite
  BNE LBL.NMI.BlockWriteBypass
    LDA #%10001000
    STA $2000
    LDA #$00
    STA $2005
    STA $2005
LBL.NMI.BlockWriteBypass:
  INC VAR.Frame
  PLA
  TAY
  PLA
  TAX
  PLA
  RTI
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Strange Timing Bug

Post by tokumaru »

Quietust wrote:either disable NMI during any VRAM writes (which is probably a good idea anyways)
Unless you want music to keep playing during screen transitions... It can be weird in screen-by-screen or room-by-room games to have the music interrupted or distorted every time new screens/rooms are loaded.

I usually leave NMIs on all the time, and use flags and other variables to make sure the NMI handler doesn't screw up anything the main thread is doing.
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Strange Timing Bug

Post by Memblers »

Shouldn't you have LDA #TRUE instead of LDA TRUE? I guess you might have TRUE defined with the # built-in, but it makes it like you're using a variable.. seems confusing. Definitely would recommend avoiding that when using constants, better to type one more character and have the code look more clear.
User avatar
Lucradan
Posts: 101
Joined: Wed Sep 21, 2016 12:08 pm

Re: Strange Timing Bug

Post by Lucradan »

Memblers wrote:Shouldn't you have LDA #TRUE instead of LDA TRUE?
TRUE/FALSE are values, not variables so I decided not to use the # to deferentiate.

My normal convention for variables IS to use the #.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Strange Timing Bug

Post by tokumaru »

But... it's the other way around: # is used for immediate values, and no # is used for variables (i.e. aliases for memory positions).
adam_smasher
Posts: 271
Joined: Sun Mar 27, 2011 10:49 am
Location: Victoria, BC

Re: Strange Timing Bug

Post by adam_smasher »

...and to be totally clear, it's not a convention or choice for the individual programmer - the assembler will assemble the instruction into different opcodes based on whether or not there's a '#' to indicate the immediate addressing mode.
User avatar
Lucradan
Posts: 101
Joined: Wed Sep 21, 2016 12:08 pm

Re: Strange Timing Bug

Post by Lucradan »

Here is my constant declaration header and how I handle the True/False. Probably not the best way, but it helps me to differentiate between CPU variables (those defined using .dsb/dsw that have a memory location), compiler variables (such as PRGCOUNT), and compiler enumerations (TRUE/FALSE).

Code: Select all


PRGCOUNT = 2 ; 16kb = 1, 32kb = 2, etc.
CHRCOUNT = 0
MIRRORING = %0000 ; Horizontal =  %0000, Vertical =    %0001, Four Screen = %1000
FALSE 				EQU #$00
TRUE				EQU #$01

Post Reply