Exceptions in SREG_x rules

32 posts / 0 new
Last post
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wonder why:
QA: "com x" <=> "sub 0xFF,x"

but:
"com x" => SREG_C set,
"sub 0xFF,x" => SREG_C cleared,

Is this SREG_C being set necessary in some (16-bit) operations? This is an exception and does not stick to standard algebraic rules.

What about:
QB: "asr x" and SREG_V=SREG_N^SREG_C? Where is it used in algebraic operations? Signed division and rounding towards zero?

No RSTDISBL, no fun!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

COM X is the One's Complement of all X register bits.
It is used to invert the operation and make it easier in certain cases.

For example, adding decimal 6 to decimal 13, equals decimal 19

   Ldi R20,0x0D  ; binary 0000 1101
   Ldi R21,0x06  ; binary 0000 0110
   Add R20,R21   ; result 0001 0011

But in signed calculations it is easy to subtract One's Complement (instead of ADD) and then it is needed to subtract the Carry as the round-around borrowing bit.

   Ldi R20,0x0D  ; binary 0000 1101
   Ldi R21,0x06  ; binary 0000 0110
   Com R21       ; binary 1111 1001 Cy=1
   Sbc R20,R21   ; result 0001 0011 

This is why the COM X forces the Cy bit to "one", this is an optimization to subtract one's complement.

Another example:
Adding signed negative 21 to signed 56, that is 56 + -21, is the same as Subtracting the one's complement of -21 to +56 without worrying with signs.

   Ldi R20,0x38 ; binary 0011 1000 (signed +56d)
   Ldi R21,0xEB ; binary 1110 1011 (signed -21d)
   Add R20,R21  ; result 0010 0011 (signed +35d)
   ; it is the same as
   Ldi R20,0x38 ; binary 0011 1000 (signed +56d)
   Ldi R21,0xEB ; binary 1110 1011 (signed -21d)
   Com R21      ; binary 0001 0100 (unsigned 20)
   Sbc R20,R21  ; binary 0010 0011 (signed +35) 

Okay, now the real MacCoy
Subtracting signed -519d from 3516d, result should be +4035 (0x0FC3)

   Ldi R20,low(3516)  ; binary 0000 1101 (0xBC)
   Ldi R21,high(3516) ; binary 1011 1100 (0x0D)
   Ldi R22,low(-519)  ; binary 1111 1001 (0xF9)
   Ldi R23,high(-519) ; binary 1111 1101 (0xFD)
   Sub R20,R22        ; result 1100 0011 (0xC3) Cy=1
   Sbc R21,R23        ; result 0000 1111 (0x0F) Cy=1

The result is correct, 0x0FC3, but carry bit is on, and this will propagate a false negative number to the next byte to the left, in a multi byte signed operation. If the left byte is zero, this carry will turn the byte 0xFF, instead of the three byte result of 0x000FC3 (+4035d), it will be 0xFF0FC3 (-61501d).

There is a simple way to do that, using one's complement. So, instead of SUBtracting -519d from +3516d, lets ADD the one's complement of -519d to +3516d. Signed -519d = 0xFDF9. Its one's complement is 0x0206, decimal 518d (wow, easy again):

   Ldi R20,low(3516)  ; binary 1011 1100 (0xBC)
   Ldi R21,high(3516) ; binary 0000 1101 (0x0D)
   Ldi R22,low(-519)  ; binary 1111 1001 (0xF9)
   Ldi R23,high(-519) ; binary 1111 1101 (0xFD)
   Com R22            ; binary 0000 0110 (0x06) cy=1
   Com R23            ; binary 0000 0010 (0x02) cy=1
   Adc R20,R22        ; binary 1100 0011 (0xC3) cy=0
   Adc R21,R23        ; binary 0000 1111 (0x0F) cy=0

Result is 0x0FC3, decimal +4035... easy!!! since the carry is off, will not propagate, result is correct.

About the ASR X that may generates flags SVC, for a signed positive number, or SN/C or VNC for negative number. I wrote that in the other thread

http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=35701

but I didn't explore it deeper enough, it is possible be used to round up the half-bit or use the C or V flag to reconstruct a whole bit from two half-bits. I just wonder, since I am not that smart.

Update Text:

Z80 microprocessor has DAA instruction, that adjusts decimal results after a simple ADD. In all other processors and controlers we need to do it by hand. It is not a big deal, but if the routine should be used often it would be stealing lots of precious clock cycles.

The same for 'signed operations', the use of V S and N flags make some confusion and smoke in the air in the codeland. May be new instructions such as ADDS, ADDCS, SUBS, SBCS and MULS could reduce the code overhead for some of us that need to deal with signed numbers.

I really wonder how many code written in assembly, use signed numbers. I did in the past, when dealing with external bipolar ADCs.

Wagner Lipnharski
Orlando Florida

Wagner Lipnharski
Orlando Florida USA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wagner you have been away for a while but may want to check the date of the posts you are replying to. :)

Not that there is anything wrong with a nice explanation but it may no longer help the op.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

js wrote:
Wagner you have been away for a while but may want to check the date of the posts you are replying to. :)

Not that there is anything wrong with a nice explanation but it may no longer help the op.

He was asked by the OP in a different thread to answer this old question.
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=922769#922769

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Guys, please be patient. I am still processing :)

So, giving my answer:

"Com x" can be discussed on two levels in here:

    1. Do I ever need "com x" == ["sub 0xFF,x" + "sec"] on AVRs? That is, "is there any algebraic operation which cannot be processed on AVRs with at least the same performance, but without this sec exception"? Because if there is no such operation, the exception from algebraic rule has no value. Same as you implemented op-code for ["add 0xFF,x" +"clc"] IMHO.. 2. Usefulness of "sub 0xff,x"+"sec" on a generic ALU. Wasn't there some low transistor core (like intel 4040, '51 or PIC) in the past, which did not implement some arithmetic op-codes (like "sub" or "subi") where the existence of "com x" was crucial?

What I mean is that I understand it is cool to use SREG_C in signed arithmetics and have SREG_C cleared after subtracting two ints, if the returned value is ge 0. The problem is that I cannot see the usefulness of of this feature on AVRs as subtracting two ints can be made with "sub"+"sbc", which is half the size and double the speed of your "com com adc adc" example.

Another words - your argument proves the usefulness of "com x" on (level 2) generic ALU. But does not prove this exception of ["sub 0xFF,x" + "sec"] is of any use on AVR.

Quote:

and this will propagate a false negative number to the next byte to the left, in a multi byte signed operation.

Lets suppose I want to give a counter-example and want to perform y= inta-intb..-intz on AVR. What is the use of this SREG_C at the end, when SREG_C is never used in tests on signed arithmetics? Does it indicate some kind of an overflow/error?

Your way:

com intbL
com intbH
adc intaL,intbL
adc intaH,intbH
brbs SREG_C,we_got_overflow_on_b_sub
...
...
com intzL
com intzH
adc intaL,intzL
adc intaH,intzH
brbs SREG_C,we_got_overflow_on_z_sub
;subtraction of ints was correct??

Now how I think it is made on AVRs:

sub intaL,intbL
sbc intaH,intbH
brbs SREG_S,we_got_overflow_on_b_sub
...
...
sub intaL,intzL
sbc intaH,intzH
brbs SREG_S,we_got_overflow_on_z_sub
;subtraction completed without overflows

What do you think?

Quote:
It is used to invert the operation and make it easier in certain cases.

To prove the usefulness of ["sub 0xFF,x"+"sec"] exception you need to give a (level 1) argument, not the (level 2) one. An algorithm which cannot be performed better(-Os or -O3) than with regular algebraic rules on AVRs would do.

No RSTDISBL, no fun!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey John, sometimes I just do it, mostly in other electronic forums: wake up some old discussion when I have some new answers. Mostly is just for the record, in case someone do a search about the subject. No worries mate... :)

Brutte, I made a small research about the SVNC flags when using bit shift operations. That's neat, and I wasn't surprised. Based on that I complete the research for all other instructions that change SVNC flags.

Probably I got less than 2% of the whole truth, but you are correct. When thinking about signed numbers, bit 7 (polarity and N flag) gets in the way. If one creates instructions to deal with signed numbers, it would work only on bits zero to six, everything would be done there. Except if shifting bits right, then, bit 7 becomes bit 6.

So, when dealing with signed numbers and shifting bits to the LEFT, LSL, ADD, ADDC and ROL, the right way should be only to work with bits zero to six. Bit seven should be preserved and untouched. Of course it doesn't work like that, so it should be updated according to some results.

Strange enough, ASR is the only signed isntruction in the instruction set. ASR shifts all bits right and keep bit 7 untouched. Being a signed instruction it will give us some hints.

This makes sense, since dividing a negative number by two doesn't change polarity.

But the SNVC flags are becoming more friendly after all this typing. I like to type, the low pace organizes my polack/rusky/italian mind, considering that it is possible... :)

Z FLAG
It continues with the assumption that Z bit should be a large eight input NOR gate, if all is zero, Z bit becomes one. Just by coincidence, signed zero is also all bits off, so Z bit continues valid for signed or unsigned results.

C FLAG
It is all the way wrong for LEFT bit Shift with signed values. There is no way to fix it, except paying attention to SVN flags to decide what to do.

N FLAG
Any bit shift instruction, except ASR messes with N flag, it is ridiculous.

The value 0x40 (+64d) imediately turns 0x80 (-128d) after a single LSL or ROL.

The value 0x84 (-124d) becomes 0x42 (+66d) after a LSR or ROR, but ASR does it right, becomes 0xC2 (-62d), since the shift happens and bit 7 (N) is untouched.

Resulting in the following statements for all instructions and the SVNC flags. Wherever you find below, consider Ra and Rb the operands and R the result after the operation.

FINAL STATEMENT FOR ALL SHIFTs, LSL, LSR, ASR, ROL, ROR:

S = N ⊕ V
V = N ⊕ C

So,
S = N ⊕ N ⊕ C
removing N
S = C

When N = 1, V = /C, S = /V, so S = C
When N = 0, V = C, S = V, so S = C

Then, S will always be = C
and V will be = C for positives and /C for negatives.

FINAL STATEMENT FOR ADD and ADC:

S = N ⊕ V
V = RaN • RbN • /RN + /RaN • /RbN • RN
S = N ⊕ ( RaN • RbN • /RN + /RaN • /RbN • RN

FINAL STATEMENT FOR ADD and ADC:

S = N ⊕ V
V = RaN • RbN • /RN + /RaN • /RbN • RN

FINAL STATEMENT FOR ADIW:

S = N ⊕ V
V = 1 if highest bit (15) was zero and turns on. As this is exactly the inverse of Carry, then
V = /C

FINAL STATEMENT FOR SBIW:

S = N ⊕ V
V = /R15 • Rc7

FINAL STATEMENT FOR AND, ANDI, EOR, OR, ORI, SBR, TST and COM:

S = N ⊕ V
V = always cleared
So, S = N

FINAL STATEMENT FOR CP, CPC, CPI, SUB, SBC, SBCI, :

S = N ⊕ V
V = RaN • /RbN • /RN + /RaN • RbN • RN

FINAL STATEMENT FOR DEC:

S = N ⊕ V

V = 1 if R=0x7F (only bit 7 down) = decimal +127, and this is cause before the DEC register was 0x80, so, signed overflow.

Then, if V = 1, then S = 1 because N = 0

FINAL STATEMENT FOR INC and NEG:

Exactly the reverse for DEC.
S = N ⊕ V

V = 1 if R=0x80 (only bit 7 up) = decimal -128, and this is cause before the INC register was 0x7F, so, signed overflow.

Then, if V = 1, then S = 0 because N = 1

Now that all flags are on the table, we could think about how a code could be setup to deal with them in math operations involving signed number, dealing with overflows (V) and signed flag (S). Can't do it tonight... the italian side of the brain is complaining that the rusky and the polack are fighting... :P

Wagner Lipnharski
Orlando Florida USA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brutte, nice observation.
Then I exercised the following 16 bits operation with both ideas.

Given:

V1 = $A595 (-23147d)
V2 = $5718 (+22296d)

V3 = V1 - V2

your way:

   ldi  r16,$95
   ldi  r17,$A5

   ldi  r18,$18
   ldi  r19,$57

   sub  r16,r18          ; 0x95 - 0x18
                         ; r16 = 0x7D  Flags HSV
   sbc  r17,r19          ; 0xA5 - 0x57 - Cy
                         ; r17 = 0x4E  Flags HSV
   brbs sreg_s,overflow  ; jump executes due S=1

Result in registers is 0x4E7D, but I wonder, the S flag only says that number shall be negative, but didn't fit in 16 bits, it overflows, so the V flag is on. If necessary a third byte to the left must exist and decremented from zero, making the result negative 0xFF4E7D (-45443d). It must be done testing S and V.

my way

   ldi  r16,$95
   ldi  r17,$A5

   ldi  r18,$18
   ldi  r19,$57

   com  r18              ; r18 = 0xE7
   com  r19              ; r19 = 0xA8
   adc  r16,r18          ; 0x95 + 0xE7
                         ; r16 = 0x7D  Flags SVC
   adc  r17,r19          ; 0xA5 + 0xA8
                         ; r17 = 0x4E  Flags SVC
   brbs sreg_c,overflow  ; jump executes due Cy flag

Here the result is exactly the same, 0x4E7D, but with Cy flag set, I just use it in the SBC to the left (3rd byte) and make it 0xFF, so the result becomes negative as 0xFF4E7D. At least in this exercise I did not need to test S or V. May be I need to do it in other cases?

This is my first try, and used only Carry flag to solve it.

Thoughts?

Wagner Lipnharski
Orlando Florida

Wagner Lipnharski
Orlando Florida USA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Okay, exercised three 16 bits.

Your way

V1 = 0x5718
V2 = 0x7143
V3 = 0x7143

V4 = V1 - V2 - V3

       5718
 sub   7143
     ------
         D5  Flags SNC
       E5    Flags SNC
  sbc  7143
     ------
         92  Flags SN
       74    Flags SV

S=1 means the number shall be negative
V=1 means it doesn't fit in 16 bits and should be 0xFF7492
You need to observe both S and V to decide. V to know it overflows, and then S to make the 3rd left byte 0xFF.

my way

V1 = 0x5718
V2 = 0x7143
V3 = 0x7143

V4 = V1 - V2 - V3

 com V2 = 8EBC
      
       5718
 adc   8EBC
     ------
         D5  Flags HSN
       E5    Flags HSN
 adc   8EBC
     ------
         92  Flags HSNC
       74    Flags HSVC

S=1 means it shall be negative
V=1 overflows, doesn't fit in 16 bits
C=1 will be use in SBC to make left byte negative
Result 0xFF7492
Again, I just need to use Carry flag. See, Cy flag is set whenever the ADC overflows the MSB byte, and when it happens, the value is automatically negative.

Need to do some more exercises.

Thoughts?

Wagner Lipnharski
orlando Florida

Wagner Lipnharski
Orlando Florida USA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I like the discussion but we are approaching the outer edge of the main subject, that is, what is the use of "com"==["sub 0xFF,x"+"sec"] on AVRs.

Quote:
I just use it in the SBC to the left (3rd byte) and make it 0xFF,(...)This is my first try, and used only Carry flag to solve it.

Your argument is not enough, because:
["com" "com" "adc" "adc" + "sbc"] is still slower than ["sub" "sbc" + "sbc"] which uses same count of registers, returns a valid int24_t and is only a 3 word operation.
Quote:
Okay, exercised three 16 bits.

? Isn't a int16_t -int16_t enough for you to give a valid example?

Quote:
Need to do some more exercises.Thoughts?

Provide a (level 1) "com" exception usage example.

No RSTDISBL, no fun!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Looking at code from avr-gcc, there is a single place where that compiler uses COM in the mentioned way, i.e. uses C=1 after COM: Negate a 3- or 4-byte value.

For example, the sequence to negate a 3-byte value R is:

;; R[0..2] := -R[0..2] performed as -R = (~R) + 1
com R[2]
com R[1]
com R[0]
adc R[0], __zero_reg__
adc R[1], __zero_reg__
adc R[2], __zero_reg__

Where R[0] stands for the low byte of R and R[2] for its high byte. The sequence is used if the compiler cannot get hold of an upper register.

As far as subtractions and additions are concerned, there is no difference between signed and unsigned additions (apart from the overflow flags, of course).

Thus, COM does not help with subtractions or additions, be they signed or unsigned: If the values are in registers already, just use SUB/SBC resp. ADD/ADC sequences until all bytes are consumed.

If the subtrahend/summand is a constant, then just subtract it with SUBI/SBCI. In the case of an addition, subtract the 2s complement.

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
The sequence is used if the compiler cannot get hold of an upper register.

This is an argument in some cases of discussion on level 2, but not on level 1, as AVRs have "neg x" == "sub 0x00,x" command (although lack "negc x"=="sbc 0x00,x"). The "neg x" sticks to algebraic rules and does not need hacks, exceptions or other tweaks of instruction set:

neg R[0]
adc R[1],zero
neg R[1]
adc R[2],zero
neg R[2]

Not mentioning it is faster and smaller than "com adc".

No RSTDISBL, no fun!

Pages