## Exceptions in SREG_x rules

32 posts / 0 new
Author
Message

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.

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!

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

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

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

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?

```com intbL
com intbH
brbs SREG_C,we_got_overflow_on_b_sub
...
...
com intzL
com 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!

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.

S = N âŠ• V
V = RaN â€¢ RbN â€¢ /RN + /RaN â€¢ /RbN â€¢ RN
S = N âŠ• ( RaN â€¢ RbN â€¢ /RN + /RaN â€¢ /RbN â€¢ RN

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

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

Given:

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

V3 = V1 - V2

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

Okay, exercised three 16 bits.

```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
------
D5  Flags HSN
E5    Flags HSN
------
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

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!

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]

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.

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]
neg R[1]
neg R[2]```

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

No RSTDISBL, no fun!

Brutte wrote:
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.

Okay, the most common use is to change sign of a negative number. See an example below, in the signed 16 bits division.

```                                    ;***************************************************************************
;*
;* "div16s" - 16/16 Bit Signed Division
;*
;* This subroutine divides two 16 bit signed numbers
;* "dd16sH:dd16sL" (dividend) and "dv16sH:dv16sL" (divisor).
;* The result is placed in "dres16sH:dres16sL" and the remainder in
;* "drem16sH:drem16sL".
;*
;* Number of words      :45
;* Number of cycles     :252/268 (Min/Max)
;* Low registers used   :3 (d16s,drem16sL,drem16sH)
;* High registers used  :7 (dres16sL/dd16sL,dres16sH/dd16sH,dv16sL,dv16sH,
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	d16s     =r13		;sign register
.def	drem16sL =r14		;remainder low byte
.def	drem16sH =r15		;remainder high byte
.def	dres16sL =r16		;result low byte
.def	dres16sH =r17		;result high byte
.def	dd16sL   =r16		;dividend low byte
.def	dd16sH   =r17		;dividend high byte
.def	dv16sL   =r18		;divisor low byte
.def	dv16sH   =r19		;divisor high byte
.def	dcnt16s  =r20		;loop counter

;***** Code

div16s:	mov     d16s,dd16sH         ;move dividend High to sign register
eor     d16s,dv16sH         ;xor divisor High with sign register

sbrs    dd16sH,7            ;if MSB in dividend set
rjmp    d16s_1
com     dd16sH              ;    change sign of dividend
com     dd16sL
subi    dd16sL,low(-1)
sbci    dd16sL,high(-1)
d16s_1:	sbrs    dv16sH,7            ;if MSB in divisor set
rjmp    d16s_2
com     dv16sH              ;    change sign of divisor
com     dv16sL
subi    dv16sL,low(-1)
sbci    dv16sL,high(-1)
d16s_2:	clr     drem16sL            ;clear remainder Low byte
sub     drem16sH,drem16sH   ;clear remainder High byte and carry
ldi     dcnt16s,17          ;init loop counter

d16s_3:	rol     dd16sL              ;shift left dividend
rol     dd16sH
dec     dcnt16s             ;decrement counter
brne    d16s_5              ;if done
sbrs    d16s,7              ;    if MSB in sign register set
rjmp    d16s_4
com     dres16sH            ;        change sign of result
com     dres16sL
subi    dres16sL,low(-1)
sbci    dres16sH,high(-1)

d16s_4: ret                         ;    return
d16s_5:	rol     drem16sL            ;shift dividend into remainder
rol     drem16sH
sub     drem16sL,dv16sL     ;remainder = remainder - divisor
sbc     drem16sH,dv16sH     ;
brcc    d16s_6              ;if result negative
add     drem16sL,dv16sL     ;    restore remainder
clc                         ;    clear carry to be shifted into result
rjmp    d16s_3              ;else
d16s_6:	sec                         ;    set carry to be shifted into result
rjmp    d16s_3
```

For consideration about the use of the +Cy in the COM instruction (it may be your answer):

```   ; To change the sign of a 16 bits negative:
ldi   r21, high(nnn)
ldi   r20, low(nnn)
COM   R20            ; same as 0xFF - R20 + Cy=1
COM   R21            ; same as 0xFF - R21 + Cy=1
SBCI  R21,0xFF       ; same as R21 + 1 - Cy (in case it was zero)

; the same as
ldi   r21, high(nnn) ; val = 0x2005
ldi   r20, low(nnn)
SER   R22
SER   R23
SUB   R22,R20
SBC   R23,R21        ; res = 0xDFFA
```

I like better to use this one:

```   COM   SLow
COM   SHigh
SBCI  SHigh,0xFF

Then the used in the "div16s routine" above

```   COM   SHigh
COM   SLow
SUBI  SLow,0xFF   ; doesn't need, if using ADC as above
SBCI  SHigh,0xFF

Interesting discoveries while testing:

```   ; NEG always set H=1, except when lower nibble is zero.
; Here's a neat way to test lower nibble for zero.
NEG   R*
NEG   R*
BRHC  Low_Nibble_Z
; is the same as the following, that needs extra register Rb for mask.
LDI   Rb,0x0F
AND   Rb,R*
BREQ  Low_Nibble_Z

;----------------------
SER   R16
SUB   R16,R*    ; Cy=0
; is the same as
SER   R16
EOR   R*,R16    ; Cy=0
; also the same as
COM   R*        ; Cy=1
```

After double NEG R*, Z=1 if both nibbles are zero, H=0 if lower nibble is zero. I can think of few uses for it in BCD routines.

Wagner Lipnharski
Orlando Florida USA

wagnerlip wrote:
See an example below

I watch..

```com     dd16sL
subi    dd16sL,low(-1)
...
com     dv16sL
subi    dv16sL,low(-1)
...
com     dres16sL
subi    dres16sL,low(-1)```

but cannot see a damn thing in it. Please enlighten me, where the SREG_C from "com" is used in it?

Quote:
; To change the sign of a 16 bits negative:

Where in this code you negate an int16_t? What about LSB?
Quote:
I like better to use this one:

It needs 5 op-codes, 5 clocks and requires a high register.. I only do not get what this code is supposed to do :) Would you please comment your code?

wagnerlip wrote:
Okay, the most common use is

Either my English is bad or you didn't get the idea of this topic. As I wrote earlier:
Brutte wrote:
What I mean is that I understand it is cool to use SREG_C in signed arithmetics

I really appreciate our discussion but you are pushing it further and further into its border. Why are you giving me the most common usages of "com"? Is it supposed to be an argument in our discussion? Like "com was present in 57% of programs on AVRs, so it has to be useful with that SREG_C"?
It is not an argument for me.
Quote:
NEG R*
NEG R*

what about cpi x,0x01?
Come on! This is a discussion about "com"! Leave "neg" alone, even if you like it.
Quote:
I can think of few uses for it in BCD routines.

No RSTDISBL, no fun!

Brutte wrote:
SprinterSB wrote:
The sequence is used if the compiler cannot get hold of an upper register.
The "neg x" sticks to algebraic rules and does not need hacks, exceptions or other tweaks of instruction set:
```neg R[0]
neg R[1]
neg R[2]```

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

The sequence is wrong. It does not propagate carry correctly.

Just negate -1 = 0xffffff and with your code the result is 0x010001 instead of the correct 0x000001.

You could have used

```neg R[0]
neg R[1]
neg R[2]```

but that is not shorter than the clear and evident -R = 1 + ~R

avrfreaks does not support Opera. Profile inactive.

wagnerlip wrote:

```com     dd16sH              ;    change sign of dividend
com     dd16sL
subi    dd16sL,low(-1)
sbci    dd16sL,high(-1)```

This is wrong, too. The 4th command must operate on the high byte. Similar for divisor. And even if you correct it:

You can do smarter here with

```com     dd16sH
neg     dd16sL
sbci    dd16sH,-1```

avrfreaks does not support Opera. Profile inactive.

SprinterSB wrote:
The sequence is wrong. It does not propagate carry correctly.

Yes, you are right, appending "adc neg" will not do the int24_t trick because of lack of SREG_C propagation on a third byte.

Quote:
but that is not shorter than the clear and evident -R = 1 + ~R

Well, I would not call a "clear and evident" a valid argument in the discussion, but I suppose expanding 4*[com adc] for an int32_t would prove the usefulness of [sub 0xFF,x +sec]!

Quote:
You can do smarter here with

"neg" "adc" "neg" (for int16_t) does not need an upper register.

No RSTDISBL, no fun!

Wagner wrote:

```   ; NEG always set H=1, except when lower nibble is zero.
; Here's a neat way to test lower nibble for zero.
NEG   R*
NEG   R*
BRHC  Low_Nibble_Z
; is the same as the following, that needs extra register Rb for mask.
LDI   Rb,0x0F
AND   Rb,R*
BREQ  Low_Nibble_Z ```

Brutte wrote:
NEG R*
NEG R*
what about cpi x,0x01?
Come on! This is a discussion about "com"! Leave "neg" alone, even if you like it.

Yes Brutte! what about CPI x,0x01??? perhaps you would like to educate us all about how one could test the lower nibble for zero, using the instruction CPI x,0x01. I am fully curious now. :?

___________________
Wagner Lipnharski
Orlando Florida USA

Wagner Lipnharski
Orlando Florida USA

Quote:
Yes Brutte! what about CPI x,0x01???(...)I am fully curious now.

Are you curious about it in "Exceptions in SREG_x rules" topic? There is nothing exceptional in cpi - standard algebraic rules apply.

No RSTDISBL, no fun!

Yeah, yeah, yeah, I know, but, come on! it is not that difficult, explain CPI x,0x01 testing lower nibble for zero... 10 seconds of your time. Everyone will learn one more trick in AVR assembly.

___________________
Wagner Lipnharski
Orlando Florida USA

Wagner Lipnharski
Orlando Florida USA

Ok, ok :)
"cpi x,0x01" or "cp x,0x01" or "cpc x,zero +SREG_C" compares/substitutes a "1" from x, so iff lower nibble of x was 0x0, a SREG_H is set(half-carry). Actually it is cpi x,0x?1 as we are not interested in higher nibble in this case.
Is that correct?

No RSTDISBL, no fun!

Correct, it does, but as always with prejudice.

CPI only works in upper registers.

CP needs an extra register. Good if keeping a usual 0x01 reg onboard.

NEG + NEG can work with any single register, but at expense of an extra code word and clock cycle.

Wagner Lipnharski
Orlando Florida USA

Brutte wrote:
"neg" "adc" "neg" (for int16_t) does not need an upper register.
Thanks for pointing that out. It's just another micro-optimization to avr-gcc now:
http://gcc.gnu.org/viewcvs/trunk...

avrfreaks does not support Opera. Profile inactive.

SprinterSB wrote:
It's just another micro-optimization to avr-gcc now:

You mean it was added into GCC?
Great!
Unfortunately I do not understand these patches.. Cannot find "adc" op-code there.

You mean:

Quote:
neg B
neg A
sbc B,zero
?
Looks ok.
I am not sure about that but does GCC mind the SREG_S flag is not set correctly (when you neg 0x8000 )? SREG_Z propagates fine, it is set iff int==0.

No RSTDISBL, no fun!

Brutte wrote:

Unfortunately I do not understand these patches.. Cannot find "adc" op-code there.

Brutte I don't understand the "inner works of avr-gcc" nor am i an assembler expert.

But for a quick "patch" tour ....

The lines starting with - are removed from the source, the lines starting with + are added.

Maybe that gives a bit of a hint , to what SB has done (with the source) :-) .

/Bingo

More to the point is that if you follow on from Sprinter's link you get to see the whole avr.md:

http://gcc.gnu.org/viewcvs/trunk...

It appears to be a work of genius in something resembling Forth a bit. The .md = machine description and it's presumably how the actual AVR architecture is known by GCC's code generator. Ultimately it looks like the _insn (instructions) it defines lead to the insertion of small snippets of AVr source code in the .s file.

Bingo600 wrote:
The lines starting with - are removed from the source, the lines starting with + are added.

Thanks, there is an option to "colour" what was added and what was removed but there is no "adc" in what was added! So I guess SB must have made some further tweaks with -int and changed "neg adc neg" into "neg neg sbc" (which is ok).
I suspect GCC is not very clever and ignores SREG's arguments and returned values and thus SREG_S and SREG_Z is of no use with GCC anyway.

Quote:
(set_attr "cc" "set_czn")

??

No RSTDISBL, no fun!

Brutte wrote:

Quote:
(set_attr "cc" "set_czn")

??

I really have no idea , but a guess would be that gcc needs to know what flags are affected/set by the things just executed.

set C + Z + N flag or ??

/Bingo

Brutte wrote:
Unfortunately I do not understand these patches...
There is also colored diff which is more readable. It's not a patch actually but a diff between two SVN versions of GCC.
Quote:
Cannot find "adc" op-code there. You mean?
Quote:
neg B
neg A
sbc B, zero
Yes, I used that sequence because it sets Z flag in a usable way: SBC propagates Z from NEG wheras ADC would clobber condition code.
Quote:
I am not sure about that but does GCC mind the SREG_S flag is not set correctly (when you neg 0x8000)? SREG_Z propagates fine, it is set iff int==0.
avr-gcc's representation of AVR machine status is somewhat limitet and is not modelled 1:1. Condition code is not modelled explicitely but only implicitly by insn attribute "cc" that can have several states:
• none: not affected by insn, for example LDI, SWAP
• clobber: insn left cc in a mess (worst case), for example CALL
• compare: after a comparison (best case)
• set_zn: Z and N are set in a usable way, i.e. represent the output operand of the last insn
• set_czn: same as set_zn, but some other flags clobbered. Currently same effect like set_zn.
• set_n: N is set in a usable way. Currently not taken advantage of, same effect like clobber
clawson wrote:
The .md = machine description and it's presumably how the actual AVR architecture is known by GCC's code generator. Ultimately it looks like the _insn (instructions) it defines lead to the insertion of small snippets of AVR source code in the .s file.
Insns (pronounced "insns" in order not to confuse them with instructions) are pieces of one of GCC's intermediate representations (IRs): RTL (Register Transfer Language) with lisp-like syntax.

The insn's assembler template is very much like assembler template you know from inline assembler.

And the constraints for the operands are very much like the operand constraints you know from inline assembler, except that GCC allows multiple alternatives (actually, inline asm allows multiple alternatives, too, but they are not very helpful because inline asm does not allow multiple asm templates).

The insn pattern describes the insn's action on algebraic level, for neghi2 (\$0 = -\$1) from above it is

```(set (match_operand:HI 0 "register_operand")
(neg:HI (match_operand:HI 1 "register_operand")))```

HI stands for "Half Integer" which is a 16 bit scalar integer.

This means "take a HI register \$1 as input, negate it, and set HI register \$0 to the result of the operation".

Brutte wrote:
I suspect GCC is not very clever and ignores SREG's arguments
Consider
```void bar (int);

void neg (int x)
{
x = -x;

if (x)
bar(x);
}```

compiled -Os -dp -S and you get (older version without the change from above, i.e. insn neghi2 from the left side of the diff)

```neg:
com r25	 ;  6	neghi2/1	[length = 3]
neg r24
sbci r25,lo8(-1)
breq .L3	 ;  8	branch	[length = 1]
rjmp bar	 ;  11	call_insn/4	[length = 1]
.L3:
ret	 ;  22	return	[length = 1]```

Left is the filled-in insn template and in asm comments the insn name, constraint alternative used and length in words.

avrfreaks does not support Opera. Profile inactive.

Quote:
avr-gcc's representation of AVR machine status is somewhat limitet and is not modelled 1:1

But still impressive.
Quote:
Consider

Ok, so I guess SREG_S is not a problem in above example as GCC does not understand SREG_V or SREG_S? Is there a chance that "com com adc adc" which sets SREG_S correctly could have any value over "neg neg sbc" in GCC?

No RSTDISBL, no fun!

Brutte wrote:
I guess SREG_S is not a problem in above example as GCC does not understand SREG_V or SREG_S?
It's not a problem. As said, avr-gcc does not model each bit of SREG. Instead, the compiler tracks how usable the contents of SREG are. That information is only used when a branch is emit. If the information is not there, avr-gcc emits a comparison. Otherwise, the comparision can be omitted.

Other iformation that's used is if the value to be compared dies or not. For example, in

```char c;

void func_1 (long x)
{
if (x < 1234567)
c = 0;
}

void func_14 (int x)
{
if (x == -14)
c = 0;
}```

x is unused after the comparison. Thus:

```func_1:
cpi r22,-121
sbci r23,-42
sbci r24,18
cpc r25,__zero_reg__
brge .L1
sts c,__zero_reg__
.L1:
ret

func_14:
brne .L3
sts c,__zero_reg__
.L3:
ret```

avrfreaks does not support Opera. Profile inactive.

Quote:
It's not a problem.

Thanks for the detailed explanation. I can see GCC is quite advanced in the SREG subject.

`brge .L1 `

I suppose this "brbx SREG_S,.." happens only after "compare: after a comparison (best case)" so it looks like even with "com com adc adc" (which does not propagate SREG_Z at all) that SREG_S is useless as there is no option in GCC: "SREG_S and SREG_N is ok, but the rest of the flags - not really as SREG_C inticates zero and SREG_Z tests for zero only on highest byte"..

No RSTDISBL, no fun!