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:
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
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.
Here is the FCEUX debugger code. It looks like 00:82D2
Code:
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:
;--------------------------------------------------------------------
;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
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.
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).
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.
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.
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.
Most likely that is the problem.
The initialization code for that screen begins with
Code:
; Disable Rendering
LDX #$00
STX $2001 ; disable rendering
; Set NMI to Wait
LDA TRUE
STA FLAG.NMI.Wait
And ends with
Code:
; 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:
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
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.
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.
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 #.
But... it's the other way around: # is used for immediate values, and no # is used for variables (i.e. aliases for memory positions).
...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.
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:
PRGCOUNT = 2 ; 16kb = 1, 32kb = 2, etc.
CHRCOUNT = 0
MIRRORING = %0000 ; Horizontal = %0000, Vertical = %0001, Four Screen = %1000
FALSE EQU #$00
TRUE EQU #$01
How does that help differentiate anything when constants are used like lda TRUE and variables like lda Variable? Being all uppercase should be enough to convey that those are constants IMO. Not using # when working with immediate values is highly misleading in 6502 code, but this is your code, so it's your call.
And, for me, adding the # infront of immediate values, like your TRUE, really helped problems go away.
See here; hope my mention of God at the top doesn't halt your exploration.