*       $NetBSD: scale.sa,v 1.3 1994/10/26 07:49:34 cgd Exp $

*       MOTOROLA MICROPROCESSOR & MEMORY TECHNOLOGY GROUP
*       M68000 Hi-Performance Microprocessor Division
*       M68040 Software Package
*
*       M68040 Software Package Copyright (c) 1993, 1994 Motorola Inc.
*       All rights reserved.
*
*       THE SOFTWARE is provided on an "AS IS" basis and without warranty.
*       To the maximum extent permitted by applicable law,
*       MOTOROLA DISCLAIMS ALL WARRANTIES WHETHER EXPRESS OR IMPLIED,
*       INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
*       PARTICULAR PURPOSE and any warranty against infringement with
*       regard to the SOFTWARE (INCLUDING ANY MODIFIED VERSIONS THEREOF)
*       and any accompanying written materials.
*
*       To the maximum extent permitted by applicable law,
*       IN NO EVENT SHALL MOTOROLA BE LIABLE FOR ANY DAMAGES WHATSOEVER
*       (INCLUDING WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS
*       PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR
*       OTHER PECUNIARY LOSS) ARISING OF THE USE OR INABILITY TO USE THE
*       SOFTWARE.  Motorola assumes no responsibility for the maintenance
*       and support of the SOFTWARE.
*
*       You are hereby granted a copyright license to use, modify, and
*       distribute the SOFTWARE so long as this entire notice is retained
*       without alteration in any modified and/or redistributed versions,
*       and that such modified versions are clearly identified as such.
*       No licenses are granted by implication, estoppel or otherwise
*       under any patents or trademarks of Motorola, Inc.

*
*       scale.sa 3.3 7/30/91
*
*       The entry point sSCALE computes the destination operand
*       scaled by the source operand.  If the absoulute value of
*       the source operand is (>= 2^14) an overflow or underflow
*       is returned.
*
*       The entry point sscale is called from do_func to emulate
*       the fscale unimplemented instruction.
*
*       Input: Double-extended destination operand in FPTEMP,
*               double-extended source operand in ETEMP.
*
*       Output: The function returns scale(X,Y) to fp0.
*
*       Modifies: fp0.
*
*       Algorithm:
*

SCALE    IDNT    2,1 Motorola 040 Floating Point Software Package

       section 8

       include fpsp.h

       xref    t_ovfl2
       xref    t_unfl
       xref    round
       xref    t_resdnrm

SRC_BNDS dc.w   $3fff,$400c

*
* This entry point is used by the unimplemented instruction exception
* handler.
*
*
*
*       FSCALE
*
       xdef    sscale
sscale:
       fmove.l         #0,fpcr         ;clr user enabled exc
       clr.l           d1
       move.w          FPTEMP(a6),d1   ;get dest exponent
       smi             L_SCR1(a6)      ;use L_SCR1 to hold sign
       andi.l          #$7fff,d1       ;strip sign
       move.w          ETEMP(a6),d0    ;check src bounds
       andi.w          #$7fff,d0       ;clr sign bit
       cmp2.w          SRC_BNDS,d0
       bcc.b           src_in
       cmpi.w          #$400c,d0       ;test for too large
       bge.w           src_out
*
* The source input is below 1, so we check for denormalized numbers
* and set unfl.
*
src_small:
       move.b          DTAG(a6),d0
       andi.b          #$e0,d0
       tst.b           d0
       beq.b           no_denorm
       st              STORE_FLG(a6)   ;dest already contains result
       or.l            #unfl_mask,USER_FPSR(a6) ;set UNFL
den_done:
       lea.l           FPTEMP(a6),a0
       bra             t_resdnrm
no_denorm:
       fmove.l         USER_FPCR(a6),FPCR
       fmove.x         FPTEMP(a6),fp0  ;simply return dest
       rts


*
* Source is within 2^14 range.  To perform the int operation,
* move it to d0.
*
src_in:
       fmove.x         ETEMP(a6),fp0   ;move in src for int
       fmove.l         #rz_mode,fpcr   ;force rz for src conversion
       fmove.l         fp0,d0          ;int src to d0
       fmove.l         #0,FPSR         ;clr status from above
       tst.w           ETEMP(a6)       ;check src sign
       blt.w           src_neg
*
* Source is positive.  Add the src to the dest exponent.
* The result can be denormalized, if src = 0, or overflow,
* if the result of the add sets a bit in the upper word.
*
src_pos:
       tst.w           d1              ;check for denorm
       beq.w           dst_dnrm
       add.l           d0,d1           ;add src to dest exp
       beq.b           denorm          ;if zero, result is denorm
       cmpi.l          #$7fff,d1       ;test for overflow
       bge.b           ovfl
       tst.b           L_SCR1(a6)
       beq.b           spos_pos
       or.w            #$8000,d1
spos_pos:
       move.w          d1,FPTEMP(a6)   ;result in FPTEMP
       fmove.l         USER_FPCR(a6),FPCR
       fmove.x         FPTEMP(a6),fp0  ;write result to fp0
       rts
ovfl:
       tst.b           L_SCR1(a6)
       beq.b           sovl_pos
       or.w            #$8000,d1
sovl_pos:
       move.w          FPTEMP(a6),ETEMP(a6)    ;result in ETEMP
       move.l          FPTEMP_HI(a6),ETEMP_HI(a6)
       move.l          FPTEMP_LO(a6),ETEMP_LO(a6)
       bra             t_ovfl2

denorm:
       tst.b           L_SCR1(a6)
       beq.b           den_pos
       or.w            #$8000,d1
den_pos:
       tst.l           FPTEMP_HI(a6)   ;check j bit
       blt.b           nden_exit       ;if set, not denorm
       move.w          d1,ETEMP(a6)    ;input expected in ETEMP
       move.l          FPTEMP_HI(a6),ETEMP_HI(a6)
       move.l          FPTEMP_LO(a6),ETEMP_LO(a6)
       or.l            #unfl_bit,USER_FPSR(a6) ;set unfl
       lea.l           ETEMP(a6),a0
       bra             t_resdnrm
nden_exit:
       move.w          d1,FPTEMP(a6)   ;result in FPTEMP
       fmove.l         USER_FPCR(a6),FPCR
       fmove.x         FPTEMP(a6),fp0  ;write result to fp0
       rts

*
* Source is negative.  Add the src to the dest exponent.
* (The result exponent will be reduced).  The result can be
* denormalized.
*
src_neg:
       add.l           d0,d1           ;add src to dest
       beq.b           denorm          ;if zero, result is denorm
       blt.b           fix_dnrm        ;if negative, result is
*                                       ;needing denormalization
       tst.b           L_SCR1(a6)
       beq.b           sneg_pos
       or.w            #$8000,d1
sneg_pos:
       move.w          d1,FPTEMP(a6)   ;result in FPTEMP
       fmove.l         USER_FPCR(a6),FPCR
       fmove.x         FPTEMP(a6),fp0  ;write result to fp0
       rts


*
* The result exponent is below denorm value.  Test for catastrophic
* underflow and force zero if true.  If not, try to shift the
* mantissa right until a zero exponent exists.
*
fix_dnrm:
       cmpi.w          #$ffc0,d1       ;lower bound for normalization
       blt.w           fix_unfl        ;if lower, catastrophic unfl
       move.w          d1,d0           ;use d0 for exp
       move.l          d2,-(a7)        ;free d2 for norm
       move.l          FPTEMP_HI(a6),d1
       move.l          FPTEMP_LO(a6),d2
       clr.l           L_SCR2(a6)
fix_loop:
       add.w           #1,d0           ;drive d0 to 0
       lsr.l           #1,d1           ;while shifting the
       roxr.l          #1,d2           ;mantissa to the right
       bcc.b           no_carry
       st              L_SCR2(a6)      ;use L_SCR2 to capture inex
no_carry:
       tst.w           d0              ;it is finished when
       blt.b           fix_loop        ;d0 is zero or the mantissa
       tst.b           L_SCR2(a6)
       beq.b           tst_zero
       or.l            #unfl_inx_mask,USER_FPSR(a6)
*                                       ;set unfl, aunfl, ainex
*
* Test for zero. If zero, simply use fmove to return +/- zero
* to the fpu.
*
tst_zero:
       clr.w           FPTEMP_EX(a6)
       tst.b           L_SCR1(a6)      ;test for sign
       beq.b           tst_con
       or.w            #$8000,FPTEMP_EX(a6) ;set sign bit
tst_con:
       move.l          d1,FPTEMP_HI(a6)
       move.l          d2,FPTEMP_LO(a6)
       move.l          (a7)+,d2
       tst.l           d1
       bne.b           not_zero
       tst.l           FPTEMP_LO(a6)
       bne.b           not_zero
*
* Result is zero.  Check for rounding mode to set lsb.  If the
* mode is rp, and the zero is positive, return smallest denorm.
* If the mode is rm, and the zero is negative, return smallest
* negative denorm.
*
       btst.b          #5,FPCR_MODE(a6) ;test if rm or rp
       beq.b           no_dir
       btst.b          #4,FPCR_MODE(a6) ;check which one
       beq.b           zer_rm
zer_rp:
       tst.b           L_SCR1(a6)      ;check sign
       bne.b           no_dir          ;if set, neg op, no inc
       move.l          #1,FPTEMP_LO(a6) ;set lsb
       bra.b           sm_dnrm
zer_rm:
       tst.b           L_SCR1(a6)      ;check sign
       beq.b           no_dir          ;if clr, neg op, no inc
       move.l          #1,FPTEMP_LO(a6) ;set lsb
       or.l            #neg_mask,USER_FPSR(a6) ;set N
       bra.b           sm_dnrm
no_dir:
       fmove.l         USER_FPCR(a6),FPCR
       fmove.x         FPTEMP(a6),fp0  ;use fmove to set cc's
       rts

*
* The rounding mode changed the zero to a smallest denorm. Call
* t_resdnrm with exceptional operand in ETEMP.
*
sm_dnrm:
       move.l          FPTEMP_EX(a6),ETEMP_EX(a6)
       move.l          FPTEMP_HI(a6),ETEMP_HI(a6)
       move.l          FPTEMP_LO(a6),ETEMP_LO(a6)
       lea.l           ETEMP(a6),a0
       bra             t_resdnrm

*
* Result is still denormalized.
*
not_zero:
       or.l            #unfl_mask,USER_FPSR(a6) ;set unfl
       tst.b           L_SCR1(a6)      ;check for sign
       beq.b           fix_exit
       or.l            #neg_mask,USER_FPSR(a6) ;set N
fix_exit:
       bra.b           sm_dnrm


*
* The result has underflowed to zero. Return zero and set
* unfl, aunfl, and ainex.
*
fix_unfl:
       or.l            #unfl_inx_mask,USER_FPSR(a6)
       btst.b          #5,FPCR_MODE(a6) ;test if rm or rp
       beq.b           no_dir2
       btst.b          #4,FPCR_MODE(a6) ;check which one
       beq.b           zer_rm2
zer_rp2:
       tst.b           L_SCR1(a6)      ;check sign
       bne.b           no_dir2         ;if set, neg op, no inc
       clr.l           FPTEMP_EX(a6)
       clr.l           FPTEMP_HI(a6)
       move.l          #1,FPTEMP_LO(a6) ;set lsb
       bra.b           sm_dnrm         ;return smallest denorm
zer_rm2:
       tst.b           L_SCR1(a6)      ;check sign
       beq.b           no_dir2         ;if clr, neg op, no inc
       move.w          #$8000,FPTEMP_EX(a6)
       clr.l           FPTEMP_HI(a6)
       move.l          #1,FPTEMP_LO(a6) ;set lsb
       or.l            #neg_mask,USER_FPSR(a6) ;set N
       bra.w           sm_dnrm         ;return smallest denorm

no_dir2:
       tst.b           L_SCR1(a6)
       bge.b           pos_zero
neg_zero:
       clr.l           FP_SCR1(a6)     ;clear the exceptional operand
       clr.l           FP_SCR1+4(a6)   ;for gen_except.
       clr.l           FP_SCR1+8(a6)
       fmove.s         #:80000000,fp0
       rts
pos_zero:
       clr.l           FP_SCR1(a6)     ;clear the exceptional operand
       clr.l           FP_SCR1+4(a6)   ;for gen_except.
       clr.l           FP_SCR1+8(a6)
       fmove.s         #:00000000,fp0
       rts

*
* The destination is a denormalized number.  It must be handled
* by first shifting the bits in the mantissa until it is normalized,
* then adding the remainder of the source to the exponent.
*
dst_dnrm:
       movem.l         d2/d3,-(a7)
       move.w          FPTEMP_EX(a6),d1
       move.l          FPTEMP_HI(a6),d2
       move.l          FPTEMP_LO(a6),d3
dst_loop:
       tst.l           d2              ;test for normalized result
       blt.b           dst_norm        ;exit loop if so
       tst.l           d0              ;otherwise, test shift count
       beq.b           dst_fin         ;if zero, shifting is done
       subq.l          #1,d0           ;dec src
       add.l           d3,d3
       addx.l          d2,d2
       bra.b           dst_loop
*
* Destination became normalized.  Simply add the remaining
* portion of the src to the exponent.
*
dst_norm:
       add.w           d0,d1           ;dst is normalized; add src
       tst.b           L_SCR1(a6)
       beq.b           dnrm_pos
       or.w            #$8000,d1
dnrm_pos:
       movem.w         d1,FPTEMP_EX(a6)
       movem.l         d2,FPTEMP_HI(a6)
       movem.l         d3,FPTEMP_LO(a6)
       fmove.l         USER_FPCR(a6),FPCR
       fmove.x         FPTEMP(a6),fp0
       movem.l         (a7)+,d2/d3
       rts

*
* Destination remained denormalized.  Call t_excdnrm with
* exceptional operand in ETEMP.
*
dst_fin:
       tst.b           L_SCR1(a6)      ;check for sign
       beq.b           dst_exit
       or.l            #neg_mask,USER_FPSR(a6) ;set N
       or.w            #$8000,d1
dst_exit:
       movem.w         d1,ETEMP_EX(a6)
       movem.l         d2,ETEMP_HI(a6)
       movem.l         d3,ETEMP_LO(a6)
       or.l            #unfl_mask,USER_FPSR(a6) ;set unfl
       movem.l         (a7)+,d2/d3
       lea.l           ETEMP(a6),a0
       bra             t_resdnrm

*
* Source is outside of 2^14 range.  Test the sign and branch
* to the appropriate exception handler.
*
src_out:
       tst.b           L_SCR1(a6)
       beq.b           scro_pos
       or.w            #$8000,d1
scro_pos:
       move.l          FPTEMP_HI(a6),ETEMP_HI(a6)
       move.l          FPTEMP_LO(a6),ETEMP_LO(a6)
       tst.w           ETEMP(a6)
       blt.b           res_neg
res_pos:
       move.w          d1,ETEMP(a6)    ;result in ETEMP
       bra             t_ovfl2
res_neg:
       move.w          d1,ETEMP(a6)    ;result in ETEMP
       lea.l           ETEMP(a6),a0
       bra             t_unfl
       end