; SF format is: ; ; [sign] 1.[23bits] E[8bits(n-127)] ; ; SEEEEEEE Emmmmmmm mmmmmmmm mmmmmmmm ; ; [A+0] mmmmmmmm ; [A+1] mmmmmmmm ; [A+2] Emmmmmmm ; [A+3] SEEEEEEE ; ; Special values (xxx != 0): ; ; s1111111 10000000 00000000 00000000 infinity ; s1111111 1xxxxxxx xxxxxxxx xxxxxxxx NaN ; s0000000 00000000 00000000 00000000 zero ; s0000000 0xxxxxxx xxxxxxxx xxxxxxxx denormals ; ; Note that CMPtype is "signed char" for rl78 ; #include "vregs.h" #define Z PSW.6 START_FUNC ___negsf2 ;; Negate the floating point value. ;; Input at [SP+4]..[SP+7]. ;; Output to R8..R11. movw ax, [SP+4] movw r8, ax movw ax, [SP+6] xor a, #0x80 movw r10, ax ret END_FUNC ___negsf2 ;; ------------------internal functions used by later code -------------- START_FUNC __int_isnan ;; [HL] points to value, returns Z if it's a NaN mov a, [hl+2] and a, #0x80 mov x, a mov a, [hl+3] and a, #0x7f cmpw ax, #0x7f80 skz ret ; return NZ if not NaN mov a, [hl+2] and a, #0x7f or a, [hl+1] or a, [hl] bnz $1f clr1 Z ; Z, normal ret 1: set1 Z ; nan ret END_FUNC __int_isnan START_FUNC __int_eithernan ;; call from toplevel functions, returns Z if either number is a NaN, ;; or NZ if both are OK. movw ax, sp addw ax, #8 movw hl, ax call $!__int_isnan bz $1f movw ax, sp addw ax, #12 movw hl, ax call $!__int_isnan 1: ret END_FUNC __int_eithernan START_FUNC __int_iszero ;; [HL] points to value, returns Z if it's zero mov a, [hl+3] and a, #0x7f or a, [hl+2] or a, [hl+1] or a, [hl] ret END_FUNC __int_iszero START_FUNC __int_cmpsf ;; This is always called from some other function here, ;; so the stack offsets are adjusted accordingly. ;; X [SP+8] <=> Y [SP+12] : <=> 0 movw ax, sp addw ax, #8 movw hl, ax call $!__int_iszero bnz $1f movw ax, sp addw ax, #12 movw hl, ax call $!__int_iszero bnz $2f ;; At this point, both args are zero. mov a, #0 ret 2: movw ax, sp addw ax, #8 movw hl, ax 1: ;; At least one arg is non-zero so we can just compare magnitudes. ;; Args are [HL] and [HL+4]. mov a, [HL+3] xor a, [HL+7] mov1 cy, a.7 bnc $1f mov a, [HL+3] sar a, 7 or a, #1 ret 1: ;; Signs the same, compare magnitude. It's safe to lump ;; the sign bits, exponent, and mantissa together here, since they're ;; stored in the right sequence. movw ax, [HL+2] cmpw ax, [HL+6] bc $ybig_cmpsf ; branch if X < Y bnz $xbig_cmpsf ; branch if X > Y movw ax, [HL] cmpw ax, [HL+4] bc $ybig_cmpsf ; branch if X < Y bnz $xbig_cmpsf ; branch if X > Y mov a, #0 ret xbig_cmpsf: ; |X| > |Y| so return A = 1 if pos, 0xff if neg mov a, [HL+3] sar a, 7 or a, #1 ret ybig_cmpsf: ; |X| < |Y| so return A = 0xff if pos, 1 if neg mov a, [HL+3] xor a, #0x80 sar a, 7 or a, #1 ret END_FUNC __int_cmpsf ;; ---------------------------------------------------------- START_FUNC ___cmpsf2 ;; This functions calculates "A <=> B". That is, if A is less than B ;; they return -1, if A is greater than B, they return 1, and if A ;; and B are equal they return 0. If either argument is NaN the ;; behaviour is undefined. ;; Input at [SP+4]..[SP+7]. ;; Output to R8..R9. call $!__int_eithernan bnz $1f movw r8, #1 ret 1: call $!__int_cmpsf mov r8, a sar a, 7 mov r9, a ret END_FUNC ___cmpsf2 ;; ---------------------------------------------------------- ;; These functions are all basically the same as ___cmpsf2 ;; except that they define how they handle NaNs. START_FUNC ___eqsf2 ;; Returns zero iff neither argument is NaN ;; and both arguments are equal. START_ANOTHER_FUNC ___nesf2 ;; Returns non-zero iff either argument is NaN or the arguments are ;; unequal. Effectively __nesf2 is the same as __eqsf2 START_ANOTHER_FUNC ___lesf2 ;; Returns a value less than or equal to zero if neither ;; argument is NaN, and the first is less than or equal to the second. START_ANOTHER_FUNC ___ltsf2 ;; Returns a value less than zero if neither argument is ;; NaN, and the first is strictly less than the second. ;; Input at [SP+4]..[SP+7]. ;; Output to R8. mov r8, #1 ;;; Fall through START_ANOTHER_FUNC __int_cmp_common call $!__int_eithernan sknz ;; return value (pre-filled-in below) for "either is nan" ret call $!__int_cmpsf mov r8, a ret END_ANOTHER_FUNC __int_cmp_common END_ANOTHER_FUNC ___ltsf2 END_ANOTHER_FUNC ___lesf2 END_ANOTHER_FUNC ___nesf2 END_FUNC ___eqsf2 START_FUNC ___gesf2 ;; Returns a value greater than or equal to zero if neither argument ;; is a NaN and the first is greater than or equal to the second. START_ANOTHER_FUNC ___gtsf2 ;; Returns a value greater than zero if neither argument ;; is NaN, and the first is strictly greater than the second. mov r8, #0xffff br $__int_cmp_common END_ANOTHER_FUNC ___gtsf2 END_FUNC ___gesf2 ;; ---------------------------------------------------------- START_FUNC ___unordsf2 ;; Returns a nonzero value if either argument is NaN, otherwise 0. call $!__int_eithernan movw r8, #0 sknz ; this is from the call, not the movw movw r8, #1 ret END_FUNC ___unordsf2 ;; ---------------------------------------------------------- START_FUNC ___fixsfsi ;; Converts its floating point argument into a signed long, ;; rounding toward zero. ;; The behaviour with NaNs and Infinities is not well defined. ;; We choose to return 0 for NaNs, -INTMAX for -inf and INTMAX for +inf. ;; This matches the behaviour of the C function in libgcc2.c. ;; Input at [SP+4]..[SP+7], result is in (lsb) R8..R11 (msb). ;; Special case handling for infinities as __fixunssfsi ;; will not give us the values that we want. movw ax, sp addw ax, #4 movw hl, ax call !!__int_isinf bnz $1f mov a, [SP+7] bt a.7, $2f ;; +inf movw r8, #-1 movw r10, #0x7fff ret ;; -inf 2: mov r8, #0 mov r10, #0x8000 ret ;; Load the value into r10:r11:X:A 1: movw ax, [SP+4] movw r10, ax movw ax, [SP+6] ;; If the value is positive we can just use __fixunssfsi bf a.7, $__int_fixunssfsi ;; Otherwise we negate the value, call __fixunssfsi and ;; then negate its result. clr1 a.7 call $!__int_fixunssfsi movw ax, #0 subw ax, r8 movw r8, ax movw ax, #0 sknc decw ax subw ax, r10 movw r10, ax ;; Check for a positive result (which should only happen when ;; __fixunssfsi returns UINTMAX or 0). In such cases just return 0. mov a, r11 bt a.7, $1f movw r10,#0x0 movw r8, #0x0 1: ret END_FUNC ___fixsfsi START_FUNC ___fixunssfsi ;; Converts its floating point argument into an unsigned long ;; rounding towards zero. Negative arguments all become zero. ;; We choose to return 0 for NaNs and -inf, but UINTMAX for +inf. ;; This matches the behaviour of the C function in libgcc2.c. ;; Input at [SP+4]..[SP+7], result is in (lsb) R8..R11 (msb) ;; Get the input value. movw ax, [SP+4] movw r10, ax movw ax, [SP+6] ;; Fall through into the internal function. .global __int_fixunssfsi __int_fixunssfsi: ;; Input in (lsb) r10.r11.x.a (msb). ;; Test for a negative input. We shift the other bits at the ;; same time so that A ends up holding the whole exponent: ;; ;; before: ;; SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM ;; A X R11 R10 ;; ;; after: ;; EEEEEEEE MMMMMMM0 MMMMMMMM MMMMMMMM ;; A X R11 R10 shlw ax, 1 bnc $1f ;; Return zero. 2: movw r8, #0 movw r10, #0 ret ;; An exponent of -1 is either a NaN or infinity. 1: cmp a, #-1 bnz $3f ;; For NaN we return 0. For infinity we return UINTMAX. mov a, x or a, r10 or a, r11 cmp0 a bnz $2b 6: movw r8, #-1 ; -1 => UINT_MAX movw r10, #-1 ret ;; If the exponent is negative the value is < 1 and so the ;; converted value is 0. Note we must allow for the bias ;; applied to the exponent. Thus a value of 127 in the ;; EEEEEEEE bits actually represents an exponent of 0, whilst ;; a value less than 127 actually represents a negative exponent. ;; Also if the EEEEEEEE bits are all zero then this represents ;; either a denormal value or 0.0. Either way for these values ;; we return 0. 3: sub a, #127 bc $2b ;; A now holds the bias adjusted exponent, which is known to be >= 0. ;; If the exponent is > 31 then the conversion will overflow. cmp a, #32 bnc $6b 4: ;; Save the exponent in H. We increment it by one because we want ;; to be sure that the loop below will always execute at least once. inc a mov h, a ;; Get the top 24 bits of the mantissa into A:X:R10 ;; Include the implicit 1-bit that is inherent in the IEEE fp format. ;; ;; before: ;; EEEEEEEE MMMMMMM0 MMMMMMMM MMMMMMMM ;; H X R11 R10 ;; after: ;; EEEEEEEE 1MMMMMMM MMMMMMMM MMMMMMMM ;; H A X R10 mov a, r11 xch a, x shr a, 1 set1 a.7 ;; Clear B:C:R12:R13 movw bc, #0 movw r12, #0 ;; Shift bits from the mantissa (A:X:R10) into (B:C:R12:R13), ;; decrementing the exponent as we go. ;; before: ;; MMMMMMMM MMMMMMMM MMMMMMMM xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx ;; A X R10 B C R12 R13 ;; first iter: ;; MMMMMMMM MMMMMMMM MMMMMMM0 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxM ;; A X R10 B C R12 R13 ;; second iter: ;; MMMMMMMM MMMMMMMM MMMMMM00 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxMM ;; A X R10 B C R12 R13 ;; etc. 5: xch a, r10 shl a, 1 xch a, r10 rolwc ax, 1 xch a, r13 rolc a, 1 xch a, r13 xch a, r12 rolc a, 1 xch a, r12 rolwc bc, 1 dec h bnz $5b ;; Result is currently in (lsb) r13.r12. c. b. (msb), ;; Move it into (lsb) r8. r9. r10. r11 (msb). mov a, r13 mov r8, a mov a, r12 mov r9, a mov a, c mov r10, a mov a, b mov r11, a ret END_FUNC ___fixunssfsi ;; ------------------------------------------------------------------------ START_FUNC ___floatsisf ;; Converts its signed long argument into a floating point. ;; Argument in [SP+4]..[SP+7]. Result in R8..R11. ;; Get the argument. movw ax, [SP+4] movw bc, ax movw ax, [SP+6] ;; Test the sign bit. If the value is positive then drop into ;; the unsigned conversion routine. bf a.7, $2f ;; If negative convert to positive ... movw hl, ax movw ax, #0 subw ax, bc movw bc, ax movw ax, #0 sknc decw ax subw ax, hl ;; If the result is negative then the input was 0x80000000 and ;; we want to return -0.0, which will not happen if we call ;; __int_floatunsisf. bt a.7, $1f ;; Call the unsigned conversion routine. call $!__int_floatunsisf ;; Negate the result. set1 r11.7 ;; Done. ret 1: ;; Return -0.0 aka 0xcf000000 clrb a mov r8, a mov r9, a mov r10, a mov a, #0xcf mov r11, a ret START_ANOTHER_FUNC ___floatunsisf ;; Converts its unsigned long argument into a floating point. ;; Argument in [SP+4]..[SP+7]. Result in R8..R11. ;; Get the argument. movw ax, [SP+4] movw bc, ax movw ax, [SP+6] 2: ;; Internal entry point from __floatsisf ;; Input in AX (high) and BC (low) .global __int_floatunsisf __int_floatunsisf: ;; Special case handling for zero. cmpw ax, #0 bnz $1f movw ax, bc cmpw ax, #0 movw ax, #0 bnz $1f ;; Return 0.0 movw r8, ax movw r10, ax ret 1: ;; Pre-load the loop count/exponent. ;; Exponents are biased by 0x80 and we start the loop knowing that ;; we are going to skip the highest set bit. Hence the highest value ;; that we can get for the exponent is 0x1e (bits from input) + 0x80 = 0x9e. mov h, #0x9e ;; Move bits off the top of AX:BC until we hit a 1 bit. ;; Decrement the count of remaining bits as we go. 2: shlw bc, 1 rolwc ax, 1 bc $3f dec h br $2b ;; Ignore the first one bit - it is implicit in the IEEE format. ;; The count of remaining bits is the exponent. ;; Assemble the final floating point value. We have... ;; before: ;; EEEEEEEE MMMMMMMM MMMMMMMM MMMMMMMM xxxxxxxx ;; H A X B C ;; after: ;; 0EEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM ;; R11 R10 R9 R8 3: shrw ax, 1 mov r10, a mov a, x mov r9, a mov a, b rorc a, 1 ;; If the bottom bit of B was set before we shifted it out then we ;; need to round the result up. Unless none of the bits in C are set. ;; In this case we are exactly half-way between two values, and we ;; round towards an even value. We round up by increasing the ;; mantissa by 1. If this results in a zero mantissa we have to ;; increment the exponent. We round down by ignoring the dropped bits. bnc $4f cmp0 c sknz bf a.0, $4f 5: ;; Round the mantissa up by 1. add a, #1 addc r9, #0 addc r10, #0 bf r10.7, $4f inc h clr1 r10.7 4: mov r8, a mov a, h shr a, 1 mov r11, a sknc set1 r10.7 ret END_ANOTHER_FUNC ___floatunsisf END_FUNC ___floatsisf