6502 ASM trick

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

Moderator: Moderators

Movax12
Posts: 529
Joined: Sun Jan 02, 2011 11:50 am

Re: 6502 ASM trick

Flipping the MSB seems the best way to deal with comparing to a constant (pre flipped). But otherwise I would refer to the link tepples posted a few post back. It has a method using N and V flags that avoids using a temp variable.

Jarhmander
Formerly ~J-@D!~
Posts: 510
Joined: Sun Mar 12, 2006 12:36 am
Location: Rive nord de Montréal

Re: 6502 ASM trick

I revive this old thread with some recent thinkings about the use of the identity table by tokumaru (1st post in thread, also see here).

Basically, you can do these operations:

Code: Select all

``````((X or Y) ± cst) [OP A] → A
(X ± cst) → Y
(Y ± cst) → X``````
cst is an unsigned constant that you want to add/substract to X or Y;
[OP A] is some optional ALU operation with A; obviously, if OP is cmp, A isn't affected.

The restriction is that the addition/substraction of X/Y with the constant must not overflow/underflow; otherwise the result is undefined. If one want (((X or Y) ± cst) & 0xFF) and never have undefined result, they'll need an identity table twice the size, or it could be done in hardware with a non-inverting tri-state buffer that connects A0-A8 to D0-D8, output would be enabled with reads into a memory area of interest.

As you might have guessed, these pseudo-instructions use absolute indexed addressing, and the arithmetics are constant offsets added to the identity table address. Undefined results happen simply when the accesses goes out of the table. Assuming the table is page-aligned, when a constant is added to X or Y, it takes 4 cycles, whereas with the substractive case, it takes 5 cycles because of the page crossing.

Some examples of use: (idt is the identity table)

Code: Select all

``````    ldy idt+4,X     ; Y = X + 4

cmp idt-1,Y     ; Compare A with Y - 1, Y is != 0

Code: Select all

``````HideSprites:    ; Make all sprites in OAM invisible (in next sprite DMA)
; Could be modified to hide some sprites only
lda #\$FF
ldx #0
:   ldy idt+4,x
sta OAMbuff,x
ldx idt+4,y
sta OAMbuff,y
cpy #\$FC
bne :-
rts``````
It's possible that the HideSprite routine above is the fastest one without using unofficial opcodes and without total unrolling (and without clearing a bit in \$2001, you fools!). There's very little unrolling here, just what's necessary so it works.

EDIT: fixed (enhanced) code.
Last edited by Jarhmander on Sat Mar 22, 2014 6:29 pm, edited 1 time in total.
((λ (x) (x x)) (λ (x) (x x)))

Posts: 7988
Joined: Fri Nov 12, 2004 2:49 pm
Location: Chexbres, VD, Switzerland

Re: 6502 ASM trick

[...]the fastest one without using unofficial opcodes and without total unrolling
Who said total unrolling was required ?
What you're doing is 2 loop iteration in one, in which you gain roughly 50% of the speed you gain in total unrolling.
If you make 4 loop iteration in one, you gain 75% of the speed you gain in total unrolling.
There's really no point in going anything further than that...

Code: Select all

``````[...] blah blah initialization
_loop
sta \$200,Y
sta \$240,Y
sta \$280,Y
sta \$2c0,Y
dey
dey
dey
dey
bne _loop
``````
If this isn't fast enough then some other part of your code has a major problem.

tepples
Posts: 22145
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: 6502 ASM trick

Jarhmander's code can start the clear at any X position, not just 0, which allows using it to clear the rest of the sprites after the ones that are already displayed.

Jarhmander
Formerly ~J-@D!~
Posts: 510
Joined: Sun Mar 12, 2006 12:36 am
Location: Rive nord de Montréal

Re: 6502 ASM trick

I mentioned unrolled loops because obviously it would be faster (and of limited utility). Also, I tried to showcase a useful example, but it turns out that even when getting rid of the last cpy (by putting a 0 byte after the identity table, a bit of a hack) it is as fast as the following with the same number of stores in the loop:

Code: Select all

``````    ldx #0
clc
:   lda #\$FF
sta OAMbuff,x
sta OAMbuff+4,x
txa
acd #8
tax
bcc :-``````
32 iterations, 21 cycles for the loop. Bad example, then.
((λ (x) (x x)) (λ (x) (x x)))

tokumaru
Posts: 11907
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: 6502 ASM trick

This will probably sound trivial to some, but here it goes: Testing for equality without affecting the carry flag

Yesterday I caught myself in a situation where I needed to verify if a variable contained a specific value, but the carry flag was holding the result of a previous operation that I would like to keep for a future decision. This meant I couldn't use CMP, since that would modify the carry flag. Then, I realized I could use EOR for this:

Code: Select all

``````lda Variable
eor #VALUE
bne NotEqual``````
EOR is a drop-in replacement for CMP in this case (unless you need the value in the accumulator to be preserved, of course), there's no need to change anything else. This works because EOR turns bits that are the same in both bytes into 0s, and the bits that are different into 1s, so the only way to get all 0s is if all bits in both bytes are the same.

Again, I'm sure this is known to some people, but since I had never thought of this trick before (I sure hope it wasn't mentioned in this thread already!), I thought it was a good idea to share it here so everyone knows that there's another way to compare numbers for equality that might be useful in a few special cases.

zeroone
Posts: 934
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: 6502 ASM trick

tokumaru
Posts: 11907
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: 6502 ASM trick

I've been doing something similar for a while, but I use DEC and INC to flip my flags between false (\$00) to true (\$FF).

tepples
Posts: 22145
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: 6502 ASM trick

Which is fine until you try to clear to false a flag that is already false. Then the next time you try to set it to true, it'll still be false.

To clear a flag, as in the article:

Code: Select all

``lsr flag``
To set a flag, beating the article by one byte but adding two cycles:

Code: Select all

``````sec
ror flag``````

tokumaru
Posts: 11907
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: 6502 ASM trick

tepples wrote:Which is fine until you try to clear to false a flag that is already false.
True, but I only use INC/DEC when the state of the flag is known, which is most of the time in my programs so far. In the few cases when I don't know the value, I indeed have to set the new value the old LDA + STA way.

Movax12
Posts: 529
Joined: Sun Jan 02, 2011 11:50 am

Re: 6502 ASM trick

tepples wrote: To set a flag, beating the article by one byte but adding two cycles:

Code: Select all

``````sec
ror flag``````
As well as not modifying any registers!