STA indirect indexed double-increments PPU address?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

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

STA indirect indexed double-increments PPU address?

Post by rainwarrior »

I was playing around with cc65, and I noticed that something like the following:

Code: Select all

((unsigned char*)0)[0x2007] = a
Compiles into to something equivalent to:

Code: Select all

LDX #$20
STX $11
LDX #$00
STX $10
LDY #$07
STA ($10), Y
This example is simplified, it isn't exactly what you get from cc65, but it seems cc65's [] operator in this case results in an indexed indirect store similar to this one.

What I discovered, though, is that across all the emulators I tried, STA to $2007 via an indirect indexed address like this appears to increment the PPU write address by two, rather than just one. On the first increment, PPU memory is not written. On the second increment, my value in A is stored to the PPU. So... it skips the byte I was aiming for and writes the next one instead! What is it about indirect indexed addressing that causes this behaviour?

Anyhow, this is also a warning, I guess, that if you're going to use cc65, don't try to write memory mapped registers this way. (edit: see posts below for syntax that does not have this problem.)
Last edited by rainwarrior on Tue Oct 16, 2012 9:18 am, edited 3 times in total.
Shiru
Posts: 1161
Joined: Sat Jan 23, 2010 11:41 pm

Re: STA indirect indexed double-increments PPU address?

Post by Shiru »

I don't really understand, why ((unsigned char*)0)[0x2007] = a rather than *((unsigned char*)0x2007)=a ?
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by rainwarrior »

Ah! That works much better, thanks!

Code: Select all

*((unsigned char*)0x2007) = a
Generates:

Code: Select all

STA $2007
And by the way, the reason I had done it the other way first was because of suggestion #12 in this cc65 doc: http://www.cc65.org/doc/coding.html
I misapplied it... I suppose the advice is only for when wanting to add to a pointer as an index, not for using static addresses like this.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by tepples »

Which then leads to header files looking like this:

Code: Select all

#define PPUCTRL   (*(volatile unsigned char*)0x2000)
#define PPUMASK   (*(volatile unsigned char*)0x2001)
#define PPUSTATUS (*(volatile unsigned char*)0x2002)
#define OAMADDR   (*(volatile unsigned char*)0x2003)
#define OAM_DMA   (*(volatile unsigned char*)0x4014)
#define PPUSCROLL (*(volatile unsigned char*)0x2005)
#define PPUADDR   (*(volatile unsigned char*)0x2006)
#define PPUDATA   (*(volatile unsigned char*)0x2007)
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by thefox »

tepples wrote:Which then leads to header files looking like this:

Code: Select all

#define PPUCTRL   (*(volatile unsigned char*)0x2000)
#define PPUMASK   (*(volatile unsigned char*)0x2001)
#define PPUSTATUS (*(volatile unsigned char*)0x2002)
#define OAMADDR   (*(volatile unsigned char*)0x2003)
#define OAM_DMA   (*(volatile unsigned char*)0x4014)
#define PPUSCROLL (*(volatile unsigned char*)0x2005)
#define PPUADDR   (*(volatile unsigned char*)0x2006)
#define PPUDATA   (*(volatile unsigned char*)0x2007)
I like to do this:

Code: Select all

struct _PPU {
    byte ctrl;
    byte mask;
    byte const status;
    byte oam_addr;
    byte oam_data;
    byte scroll;
    byte addr;
    byte data;
};
#define    PPU         ( *( struct _PPU volatile * )0x2000 )
(BTW, volatile has no effect in CC65 currently.)
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
exdeath
Posts: 103
Joined: Sat Sep 15, 2012 6:58 pm

Re: STA indirect indexed double-increments PPU address?

Post by exdeath »

It's doing exactly what you're telling it to. It's compiling array/pointer dereferencing and indexing arithmetic, so an indexed indirect addressing opcode is the most perfect output code.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by thefox »

exdeath wrote:It's doing exactly what you're telling it to. It's compiling array/pointer dereferencing and indexing arithmetic, so an indexed indirect addressing opcode is the most perfect output code.
Most perfect? No way, it should/could know that the address is constant and optimize accordingly.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
exdeath
Posts: 103
Joined: Sat Sep 15, 2012 6:58 pm

Re: STA indirect indexed double-increments PPU address?

Post by exdeath »

thefox wrote:
exdeath wrote:It's doing exactly what you're telling it to. It's compiling array/pointer dereferencing and indexing arithmetic, so an indexed indirect addressing opcode is the most perfect output code.
Most perfect? No way, it should/could know that the address is constant and optimize accordingly.
:mrgreen:
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by rainwarrior »

Heh, mine is now just:

Code: Select all

#define RAW_BUS(x) (*(unsigned char*)(x))
Am I the only one who likes to use the registers by number instead of naming them?

With cc65's weak optimizer, volatile isn't really capable of doing anything, but the sentiment is right, semantically.

How does the compiler like that struct, TheFox? Does it manage to reduce 0x2000 + offset at compile time? (Edit: apparently it does! Turns into the STA $2007 it deserves.)

Also, nobody has any insight as to what is special about STA (zp), Y? That was the question I was most interested in. Why does it generate an extra increment?
exdeath
Posts: 103
Joined: Sat Sep 15, 2012 6:58 pm

Re: STA indirect indexed double-increments PPU address?

Post by exdeath »

rainwarrior wrote:Also, nobody has any insight as to what is special about STA (zp), Y? That was the question I was most interested in. Why does it generate an extra increment?
Yeah... not sure. Whats the 6502 bus doing on that opcode? Maybe the ZP accesses for the base address cause dummy bus accesses that confuse the PPU and toggle a false write and increment?
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by Dwedit »

STA (xx),Y adds a dummy read.

Code: Select all

        1      PC       R  fetch opcode, increment PC
        2      PC       R  fetch pointer address, increment PC
        3    pointer    R  fetch effective address low
        4   pointer+1   R  fetch effective address high,
                           add Y to low byte of effective address
        5   address+Y*  R  read from effective address,
                           fix high byte of effective address
        6   address+Y   W  write to effective address
They did it this way in case they needed to fix up the high byte before performing a write, because they figured that reads wouldn't have side effects like writes would.
Last edited by Dwedit on Tue Oct 16, 2012 8:51 am, edited 1 time in total.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by rainwarrior »

exdeath wrote:It's doing exactly what you're telling it to. It's compiling array/pointer dereferencing and indexing arithmetic, so an indexed indirect addressing opcode is the most perfect output code.
One of the primary advantages of C over assembly is that the compiler is able to pick from equivalent implementations of a statement, so that it can do "what's best" for the situation. There really isn't such a thing as "exactly what you tell it". That's an assembly programming concept, not a C concept.

Frankly I'm a little disturbed that cc65 isn't able to tell the difference between a static pointer and a variable in this case. It's really weird too, because if I create a named static array (via assembly/linker), it manages to reduce just fine into an absolute address. It's this strange case where ((unsigned char*)x) isn't treated as a static pointer with the [] operator. Not intuitive at all.

For instance if I do something like this:

Code: Select all

.segment "PPU_REGISTERS"
_ppu_register: .res 8
.export _ppu_register
I can get well behaved results from something like:

Code: Select all

extern unsigned char ppu_register[8];
ppu_register[7] = a;
It's only when using a number literal cast to an address that it has problems with []. As TheFox pointed out, you can cast it to a struct and it has no problem at all!
Last edited by rainwarrior on Tue Oct 16, 2012 9:02 am, edited 2 times in total.
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by rainwarrior »

Thanks Dwedit. Is that copied from a reference somewhere? (I'd like to read it, if it exists.)
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by Dwedit »

It's from this file:
http://nesdev.com/6502_cpu.txt

Yeah, there's a bunch of files linked from the main page of the site. But this one looks like the best for knowing what the CPU is actually doing.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: STA indirect indexed double-increments PPU address?

Post by thefox »

exdeath wrote:
thefox wrote:
exdeath wrote:It's doing exactly what you're telling it to. It's compiling array/pointer dereferencing and indexing arithmetic, so an indexed indirect addressing opcode is the most perfect output code.
Most perfect? No way, it should/could know that the address is constant and optimize accordingly.
:mrgreen:
Wat.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Post Reply