; ** FAST signed/unsigned 32bit arithmetic Routines for Atmel AVR RISC **
; ** March 1999, by Jack Tidwell <jackt@igalaxy.net> **
; ** These routines may be used for personal and educational
; **      purposes only, and are free to download. **
;

.include	"8515def.inc"	; (Any AVR w/Sram)

.def	PROD	=r8
.def	QUOT	=r8	; Quot/Prod LSByte
.def	PRODM	=r9
.def	QUOTM	=r9
.def	PRODH	=r10
.def	QUOTH	=r10
.def	PRODHH	=r11
.def	QUOTHH	=r11	; Quot/Prod MSByte

.def	MANT1	=r16	; ACC1 LSByte
.def	MANT1m	=r17
.def	MANT1h	=r18
.def	MANT1hh	=r19	; ACC1 MSByte

.def	MANT2	=r20	; 32bit Multiplier/Divisor
.def	MANT2m	=r21
.def	MANT2h	=r22
.def	MANT2hh	=r23
.def	count	=r24	; bit count
.def	temp	=r25

	.dseg
Buf:	.byte	12	; Ascii Number buffer.

	.cseg
 .org	$000
	rjmp	RESET

;* Some test code for the AVR Studio
 .org	$00b
RESET:	ldi	temp,low(RAMEND)
	out	SPL,temp
	ldi	temp,high(RAMEND)
	out	SPH,temp
	clr	mant1h
	clr	mant1hh
;** Do 50000 / -50
	ldi	mant1,low(50000)
	ldi	mant1m,high(50000)
	ldi	mant2,low(-50)
	ldi	mant2m,high(-50) ; 0xff
	ldi	mant2h,high(-50) ; 0xff
	ldi	mant2hh,high(-50); 0xff
	rcall	SDIV

Main:	
	rjmp	Main
;
;** Acc1 += Acc2 **
ADD:	add	mant1,mant2
	adc	mant1m,mant2m
	adc	mant1h,mant2h
	adc	mant1hh,mant2hh
	ret
;
;** Acc1 -= Acc2 **
SUB:	sub	mant1,mant2
	sbc	mant1m,mant2m
	sbc	mant1h,mant2h
	sbc	mant1hh,mant2hh
	ret
;
TSTZERO1:
	clr	temp	; Test for Acc1 == 0
	or	temp,mant1
	or	temp,mant1m
	or	temp,mant1h
	or	temp,mant1hh
	ret
;
TSTZERO2:
	clr	temp	; Test for Acc2 == 0
	or	temp,mant2
	or	temp,mant2m
	or	temp,mant2h
	or	temp,mant2hh
	ret
;
;** Acc1 %= Acc2 **
MOD:	rcall	SDIV
;* put the remainder into Acc1 *
	mov	mant1,quot
	mov	mant1m,quotm
	mov	mant1h,quoth
	mov	mant1hh,quothh
	ret
;
;** Signed 32bit Division **
SDIV:	tst	mant1hh	 ; test MSBit (b31) for Sign
	brmi	SDIV1n
	tst	mant2hh
	brpl	UDIV	 ; both are Pos, normal Division
	rcall	NEGMANT2 ; Acc1+, Acc2-, Negate Acc2
SDIV1a:	rcall	UDIV	 ; Normal Div
	rjmp	NEGMANT1 ; Negate Results
SDIV1n:	rcall	NEGMANT1
	tst	mant2hh
	brpl	SDIV1a
;*	both are '-', Neg 2 & fall through to normal UDIV
	rcall	NEGMANT2
;** Straight 32bit Unsigned ACC1/ACC2 **
UDIV:	rcall	tstzero2	; test for Div/0
	breq	DIVZERO
	rcall	tstzero1
	breq	UDIVX		; save lots of time!
	clr	quot
	clr	quotm
	clr	quoth
	clr	quothh
	lsl	mant1
	rol	mant1m
	rol	mant1h
	rol	mant1hh
	ldi	count,32
UDIVLP:	rol	quot
	rol	quotm
	rol	quoth
	rol	quothh
	cp	quot,mant2
	cpc	quotm,mant2m
	cpc	quoth,mant2h
	cpc	quothh,mant2hh
	brlo	UDIV2
	sub	quot,mant2
	sbc	quotm,mant2m
	sbc	quoth,mant2h
	sbc	quothh,mant2hh
	sec
	rjmp	UDIV3
UDIV2:	clc
UDIV3:	rol	mant1
	rol	mant1m
	rol	mant1h
	rol	mant1hh
	dec	count
	brne	UDIVLP
UDIVX:	ret
DIVZERO:	;error(Div0Msg) 
	ret
;
;** Get the Absolute value in Acc1 **
;** abs(int) **
_iabs:
ABS:	tst	mant1m
	brpl	ABSX
	ser	mant1h		; set MSBytes to 0xff
	ser	mant1hh
	rjmp	NEGMANT1
ABSX:	ret
;** long integer abs((long)) **
_labs:	tst	mant1hh
	brpl	NEGMANT1x
NEGMANT1:
	com	mant1
	com	mant1m
	com	mant1h
	com	mant1hh
	subi	mant1,-1
	sbci	mant1m,-1
	sbci	mant1h,-1
	sbci	mant1hh,-1
NEGMANT1x:
	ret
;
NEGMANT2:
	com	mant2
	com	mant2m
	com	mant2h
	com	mant2hh
	subi	mant2,-1
	sbci	mant2m,-1
	sbci	mant2h,-1
	sbci	mant2hh,-1
	ret
;
;** Signed 32bit Mul **
SMUL:
	tst 	mant1hh
	brmi	SMUL1n
	tst 	mant2hh
	brpl	UMUL	; both are Pos, normal
	rcall	NEGMANT2 ; Acc1+, Acc2-, Negate Acc2
SMUL1a:	rcall	UMUL	; Normal Mul
	rjmp	NEGMANT1 ; Negate Results
SMUL1n:	rcall	NEGMANT1
	tst 	mant2hh
	brpl	SMUL1a
;*	both are '-', Neg 2 & fall through to normal UMUL
	rcall	NEGMANT2
;** Unsigned 32bit Mul **
;** Acc1 *= Acc2 **
UMUL:	rcall	tstzero1	; speeds things Up! if Acc1 == 0
	breq	UMULX		; if Acc1 == 0, return
	rcall	tstzero2	; if Acc2 == 0
	brne	UMULOK
	clr 	mant1		; yes, x * 0 = 0. If this part is to big, remove.
	clr 	mant1m
	clr 	mant1h
	clr 	mant1hh
	ret
UMULOK:	clr	prod
	clr 	prodm
	clr 	prodh
	clr 	prodhh
	clr 	temp		; used for 'adci mant1x,0'
	ldi 	count,32	; number of bits to proc.
UMULLP:	lsl	prod
	rol 	prodm
	rol 	prodh
	rol 	prodhh
	rol 	mant1
	rol 	mant1m
	rol 	mant1h
	rol 	mant1hh
	brcc	UMUL1
	add 	prod,mant2
	adc 	prodm,mant2m
	adc 	prodh,mant2h
	adc 	prodhh,mant2hh
	adc 	mant1,temp	; addi 0+Carry
	adc 	mant1m,temp	; trickle the Carry on down.
	adc 	mant1h,temp
	adc 	mant1hh,temp
UMUL1:	dec	count
	brne	UMULLP
	mov 	mant1,prod
	mov 	mant1m,prodm
	mov 	mant1h,prodh
	mov 	mant1hh,prodhh
UMULX:	ret
;
;-----------------------------------------------------
;** atoi() and atol() routines **
_imul10: mov	mant2,mant1
	mov	mant2m,mant1m
	mov	mant2h,mant1h
	mov	mant2hh,mant1hh
	rcall	_rolmant	; * 2
	rcall	_rolmant	; * 4
	add	mant1,mant2	; * 5
	adc	mant1m,mant2m
	adc	mant1h,mant2h
	adc	mant1hh,mant2hh ; fall through to final * 10
_rolmant:
	lsl	mant1
	rol	mant1m
	rol	mant1h
	rol	mant1hh
	ret
;
_getch:	; put temp <- 'ascii char'
	; use your own
	ret
;
_junk: 	rcall	_getch	; skip any leading white space
	cpi	temp,' '
	breq	_junk
	cpi	temp,0x09 ; Tab?
	breq	_junk
	ret
;
ATOI:	; use atol for both 16bit & 32bit, saves AVR code space
ATOL:	clr	mant1
	clr	mant1m
	clr	mant1h
	clr	mant1hh
	clt			; assume Pos
	rcall	_junk
	cpi	temp,'-'	; is it Neg?
	brne	ATOL2		; change to ATOLp if you want '+' detection.
	set			; yes, flag the op.
;	rjmp	ATOL1		; added bug fix!, used to fall through
;ATOLp:	cpi	temp,'+'	; not really nessec, but some like using '+'
;	brne	ATOL2
ATOL1:	rcall	_getch
ATOL2:	cpi	temp,'9'+1
	brsh	ATOLX
	cpi	temp,'0'
	brlo	ATOLX
	rcall	_imul10
	subi	temp,'0'
	add	mant1,temp	; add new digit
	clr	temp		; just add 0+Carry
	adc	mant1m,temp
	adc	mant1h,temp
	adc	mant1hh,temp
	rjmp	ATOL1
ATOLX:	brtc	ATOLXX		; did we encounter a '-'?
	rjmp	NEGMANT1	; yes, negate the num
ATOLXX:	ret
;
;** Fairly fast, & code eff. ltoa((long)num) and itoa((int)num) **
;** Also used w/my FP routines **
;** Blanks Leading Zeros **
;** Acc1 is destroyed, you must preserv it yourself **
;* enter w/'T' == 0, then UNSIGNED Print *
;* else Signed Print *
;* else, SIGNED Decimal Print *
;* 'T' status flag used for Suppressing Leading Zeros *
;* 16 bit 'shared' Print *
ITOA:	clr	mant1h		; assume 16 bit Pos num
	clr	mant1hh
	tst	mant1m		; test bit 15
	brpl	SLTOA
	ser	mant1h		; set the MSB to 0xff
	ser	mant1hh
;* Fall through to 32 bit Signed LTOA *
SLTOA:	set			; Signed Print Decimal
	rjmp	LTOA
ULTOA:	clt			; Un-Signed Print Decimal
LTOA:	ldi	yl,low(Buf)	; Ascii Buffer Ptr, or enter here w/YL,YH=Ptr to Dest
	ldi	yh,high(Buf)
	brtc	LTOA1		; signed/unsigned flag from caller
	tst	mant1hh		; test Sign bit 31
	brpl	LTOA1
	rcall	NEGMANT1	; 2's comp to Pos num
	ldi	temp,'-'
	st	y+,temp		; store the Sign, Buf pointer++
LTOA1:	clr	count
	clr	temp
	or	temp,mant1
	or	temp,mant1m
	or	temp,mant1h
	or	temp,mant1hh
	breq	DECDIGX
	ldi	zl,low(I1E9 *2)	; Decimal Table below
	ldi	zh,high(I1E9 *2)
	ldi	count,9		; 9 rows of 4 columns ea.
	clt			; leading zero suppression flag
LTOAloop:	; load each sequential table group
	lpm
	mov	mant2,r0
	adiw	zl,1
	lpm
	mov	mant2m,r0
	adiw	zl,1
	lpm
	mov	mant2h,r0
	adiw	zl,1
	lpm
	mov	mant2hh,r0
	adiw	zl,1
	clr	temp		; now test Acc1 w/table in Acc2.
DECDIG1: cp	mant1,mant2
	cpc	mant1m,mant2m
	cpc	mant1h,mant2h
	cpc	mant1hh,mant2hh
	brlo	DECDIG2		; can we subtract without borrow?
	sub	mant1,mant2	; yes
	sbc	mant1m,mant2m
	sbc	mant1h,mant2h
	sbc	mant1hh,mant2hh
	inc	temp		; inc this each time we can subtract.
	rjmp	DECDIG1		; keep subtracting until underflow!
DECDIG2: ori	temp,'0'	; convert to Ascii char
	brts	DECDIG3		; Blank leading Zeros
	cpi	temp,'0'	; does temp == '0'?
	breq	DECDIG4		; yes, don't Print it until firs non-zero char.
	set			; signal first non-zero digit.
DECDIG3: st	y+,temp		; store the dig in the SRAM Buffer, and inc 'y' reg
DECDIG4: dec	count		; have we processed all tables yet?
	brne	LTOAloop
DECDIGX: ori	mant1,'0'	; print the last '0-9' residual digit!
	st	y+,mant1	; save it.
; * count = 0 here, use it to add ending 'NULL' char. *
	st	y+,count
	ret			; return to caller!
;
;** Number to ASCII convertion Table **
;*Note: if code space is tight, put them in EEPROM (36bytes are 36bytes!) *
;*  just be shure to change lpm op to EEread!!! *
I1E9:	.db	0x00,0xca,0x9a,0x3b	; 1,000,000,000
I1E8:	.db	0x00,0xe1,0xf5,0x05	;   100,000,000
I1E7:	.db	0x80,0x96,0x98,0	;    10,000,000
I1E6:	.db	0x40,0x42,0x0f,0	;     1,000,000
I1E5:	.db	0xa0,0x86,0x01,0	;       100,000
I1E4:	.db	0x10,0x27,0x00,0	;        10,000
I1E3:	.db	0xe8,0x03,0x00,0	;         1,000
I1E2:	.db	0x64,0x00,0x00,0	;           100
I1E1:	.db	0x0a,0x00,0x00,0	;            10

;** End **
