6502 ASM trick

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

Moderator: Moderators

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

Re: 6502 ASM trick

Post by Movax12 » Sat Jan 26, 2013 7:19 pm

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.

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

Re: 6502 ASM trick

Post by Jarhmander » Sat Mar 22, 2014 1:35 pm

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

    adc idx+3,X     ; ADC A with X+3

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)))

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

Re: 6502 ASM trick

Post by Bregalad » Sat Mar 22, 2014 2:56 pm

[...]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

Post by tepples » Sat Mar 22, 2014 3:45 pm

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.

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

Re: 6502 ASM trick

Post by Jarhmander » Sat Mar 22, 2014 8:23 pm

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)))

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

Re: 6502 ASM trick

Post by tokumaru » Wed Dec 16, 2015 4:40 am

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.

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

Re: 6502 ASM trick

Post by zeroone » Wed Dec 16, 2015 9:29 am


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

Re: 6502 ASM trick

Post by tokumaru » Wed Dec 16, 2015 9:58 am

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

Post by tepples » Wed Dec 16, 2015 10:05 am

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

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

Re: 6502 ASM trick

Post by tokumaru » Wed Dec 16, 2015 10:55 am

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.

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

Re: 6502 ASM trick

Post by Movax12 » Wed Dec 16, 2015 11:43 am

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! :D

Post Reply