Exceptions in SREG_x rules

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

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
       	adc     drem16sH,dv16sH
       	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
   ADC   DLow,SLow   ; ADC instead of ADD save one instruction
   ADC   DHigh,SHigh

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      
   ADD   DLow,Slow
   ADC   DHigh,SHigh

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

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

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.

Please don't.

No RSTDISBL, no fun!

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

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]
adc R[1],zero
neg R[1]
adc R[2],zero
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]
adc R[1],zero
adc R[2],zero
neg R[1]
adc R[2],zero
neg R[2]

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

avrfreaks does not support Opera. Profile inactive.

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

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.

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

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!

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

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

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

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!

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

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

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

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!

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

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

Pages