6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Wed Sep 25, 2024 8:23 pm

All times are UTC




Post new topic Reply to topic  [ 12 posts ] 
Author Message
 Post subject: Nice arithmetic trick...
PostPosted: Fri Feb 11, 2011 9:52 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1041
Location: near Heidelberg, Germany
I found here, like to share it with you... Hope you find it useful.

While programming my (warning: teaser....) USB stack (end teaser) I needed to substract AC from a value in a memory location, of course with as little code as possible.

The code transfers a data packet in. The memory location contains the number of bytes that were scheduled to be received, while AC is the number of bytes still to transfer after the transfer (that value is what the SL811H gives you after the transfer).

I need to compute the number of bytes transferred. That would be
Code:
mem - AC

which of course is difficult with the 6502 if you cannot or don't want to store AC into some immediate memory location. But you can do this:
Code:
- (AC - mem)

which can be done as
Code:
; substract
SEC
SBC mem
; invert sign, two's complement
EOR #$FF
CLC
ADC #1

The CLC might be left out if you know your value range.
But you can do it much shorter, which is the trick I found:
Code:
CLC
SBC mem
EOR #$FF

This folds the ADC #1 into the SBC by using CLC instead of SEC, i.e. it substracts one more than mem contains, which is the same as adding one after the one's complement (EOR #$FF)

Hope you like it
André


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Fri Feb 11, 2011 11:03 pm 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
-AC+mem

Code:
 eor #$FF
 sec
 adc mem


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sat Feb 12, 2011 10:42 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1041
Location: near Heidelberg, Germany
bogax wrote:
-AC+mem

Code:
 eor #$FF
 sec
 adc mem


Yes, that's what I thought of after posting here :-)

Incidently, IIRC, that is how the 6502 internally implements SBC, by inverting (EOR #$FF) the operand (mem, not AC as in the example here) and using the carry flag as a borrow (that is why SBC requires SEC and not CLC like ADC)

André


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Feb 13, 2011 9:09 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
Am I right in thinking that both code sequences are correct, and take the same time, and leave all four flags in the same state?


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 14, 2011 4:55 am 
Offline

Joined: Fri Aug 30, 2002 2:05 pm
Posts: 347
Location: UK
Yes. As long as you do SEC with ADD or CLC with SBC, either preceeded or followed by the EOR #$FF, you will get the same result.

Lee.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 14, 2011 10:23 am 
Offline

Joined: Wed Oct 06, 2010 9:05 am
Posts: 95
Location: Palma, Spain
Not having too much time to consider this very carefully, does this also leave the carry flag set up appropriately for a 16-bit subtraction?

e.g. would the following work?

Code:
; code to calculate result = address - value * 4

; get value * 4 in A/temp
        lda #0
        sta temp
        lda value
        asl a
        rol temp
        asl a
        rol temp

; subtract value * 4 from address
        sbc address       ; C known to be clear
        eor #$ff
        sta result
        lda address+1
        sbc temp          ; MSB of value * 4
        sta result+1


If so, that's a very useful little trick! Thanks for sharing it.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 14, 2011 1:48 pm 
Offline

Joined: Wed Oct 06, 2010 9:05 am
Posts: 95
Location: Palma, Spain
Now I've had a moment to think about it, I think the above code won't work, because the carry flag will always be incorrect for the MSB subtraction. But it should work if you use the EOR/SEC/ADC trick, like this:

Code:
; code to calculate result = address - value * 4

; get value * 4 in A/temp
        lda #0
        sta temp
        lda value
        asl a
        rol temp
        asl a
        rol temp

; subtract value * 4 from address
        eor #$ff
        sec
        adc address
        sta result
        lda address+1
        sbc temp          ; MSB of value * 4
        sta result+1


So, I don't think the two methods are exactly equivalent. For 8-bit arithmetic though, if you already know the state of the carry flag, it's good to have the option to use either method. For 16-bit arithmetic, I think you have to do it this way.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 15, 2011 7:37 am 
Offline

Joined: Wed Oct 06, 2010 9:05 am
Posts: 95
Location: Palma, Spain
Just wrote a little test program, and can confirm that this is true.

Of course it makes sense: inverting the input to the add works because it's a simple 8-bit value; inverting the output from the subtract doesn't work, because it's a 9-bit (including the C flag) value.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 15, 2011 8:28 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
well put!


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 15, 2011 8:36 am 
Offline

Joined: Wed Oct 06, 2010 9:05 am
Posts: 95
Location: Palma, Spain
Here's another nice arithmetic trick I used recently. It's not especially clever, but I never saw it mentioned anywhere before, so here's as good a place as any.

I needed to divide the accumulator by 64, to yield a value between 0-3.

The obvious way to do this is
Code:
LSR A
LSR A
LSR A
LSR A
LSR A
LSR A

which obviously works, but takes 12 cycles.

You could use a 256 byte lookup table, but this is a horrible waste of memory. In the end, I realised the best way was this:
Code:
ASL A
ROL A
ROL A
AND #3

which saves 4 cycles and 1 byte.

Anyone have any more little tricks like this to share which have barely ever been touched upon?


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 15, 2011 7:11 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
I could mention the quiz from an old Apple Assembly line issue... although it was only a couple of months ago that I posted it.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 16, 2011 1:54 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
Hmmmm... tricks? Off the top of my head, here's a little snippet I find very interesting... a modest example, perhaps, but it doesn't take much to amuse some people! :roll:

In some programming scenarios it's necessary to take a one-bit value and copy it into all the bits of a register -- in other words, to load the register with a word containing either all 1s or all 0s, based on the 1-bit input value. Although you're essentially creating redundancy, the word-wide all-1s or all-0s format can be handy if you plan to subsequently perform masking operations involving another word-wide value.

Here are some code sequences to accomplish this. The input value is taken from the Carry Flag.

Code:
       lda # 0      ;set up default value in A
       bcc proceed  ;test Carry Flag
       lda # $FF    ;optionally replace default
proceed:            ;A reg contains all 0s or all 1s
Ho, hum -- boring! But you get the idea.

Code:
 sta  Z_Pg_Temp  ;stash A reg to zero page
 sbc  Z_Pg_Temp  ;subtract value from itself, with borrow if Carry=0. Result=$00 or $FF
 eor  # $FF      ;adjust result so C=0 yields $00 and C=1 yields $FF
Huhh?? That can't be right! It looks incomplete! Don't we need an instruction to initialize the A reg first ?!
No, we do not; the strange thing about this code is that it works regardless of what's initially in A.

Code:
 lda  # 0         ;initialize A = 0
 sbc  # 0         ;borrow if Carry=0. Result= $00 or $FF
 eor  # $FF       ;adjust result so C=0 yields $00 and C=1 yields $FF
This is a less provocative alternative. No Z-Pg reference is required, saving 2 cycles.


Code:
 sbb   edx, edx   ;edx for example only, use any reg
But, looking back at the wacky approach, we find that "subtracting a value from itself" works really well on the x86 family!
(or other chips featuring register-to-register arithmetic)

With apologies for mentioning that other architecture,

Jeff :oops:


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 7 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: