diff --git a/calculator.asm b/calculator.asm index 653eada..50f5229 100644 --- a/calculator.asm +++ b/calculator.asm @@ -1,5 +1,18 @@ org 0 +cleq r0, r0, normalize + +load r0, fResultPtr+0 +load r1, fResultPtr+1 +cleq r0, r0, pushWord + +cleq r0, r0, fPack + +cleq r0, r0, fPrint +cleq r0, r0, newline + +halt + cleq r0, r0, readline load r0, linelen+0 @@ -227,6 +240,467 @@ readline: store ffff, r3 breq r0, r0, readlineLoop +; ================================================================== +; Floating point +; ================================================================== + +; ------------------------------------------------------------------ +; Format conversion +; ------------------------------------------------------------------ + +; When on-stack, a floating point number uses an 8-byte packed BCD format +; 0 1 2 3 4 5 6 7 +; seee mmmm mmmm mmmm +; |\_/ \____________/ +; | | | +; | | mantissa, first digit being before and rest after the point +; | exponent, with a bias of 500 (i.e. value of 0 means ×10⁻⁵⁰⁰) +; sign, 00 for non-negative, 01 for negative, 02 for overflow + +; For operation, the format is instead an 18-byte unpacked BCD format +; 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 +; 0s 0e 0e 0e 0o 0m 0m 0m 0m 0m 0m 0m 0m 0m 0m 0m 0m 0r +; | \______/ | \_________________________________/ | +; | | | | extra digit for rounding +; | | | mantissa +; | | extra digit for overflow +; | exponent +; sign + +; f ptr -- +fUnpack: + ; Start from the end + xor r0, r0 + load r1, #11 + cleq r0, r0, pushWord + cleq r0, r0, stAdd + + ; Zero out rounding digit + xor r0, r0 + xor r1, r1 + cleq r0, r0, pushWord + cleq r0, r0, stOver + cleq r0, r0, stStoreByte + cleq r0, r0, stDec + + ; Mantissa + cleq r0, r0, fUnpackWord + cleq r0, r0, fUnpackWord + cleq r0, r0, fUnpackWord + + ; Zero out overflow digit + xor r0, r0 + xor r1, r1 + cleq r0, r0, pushWord + cleq r0, r0, stOver + cleq r0, r0, stStoreByte + cleq r0, r0, stDec + + ; Sign and exponent + cleq r0, r0, fUnpackWord + + ; Clear off the pointer from the stack + breq r0, r0, popWord + + ; w ptr -- ptr-4 + fUnpackWord: + ; Low nybble of low byte + cleq r0, r0, stOver + cleq r0, r0, stLowNybble + cleq r0, r0, stOver + cleq r0, r0, stStoreByte + cleq r0, r0, stDec + + ; High nybble of low byte + cleq r0, r0, stOver + cleq r0, r0, stShr4 + cleq r0, r0, stLowNybble + cleq r0, r0, stOver + cleq r0, r0, stStoreByte + cleq r0, r0, stDec + + ; Low nybble of high byte + cleq r0, r0, stOver + cleq r0, r0, stShr8 + cleq r0, r0, stLowNybble + cleq r0, r0, stOver + cleq r0, r0, stStoreByte + cleq r0, r0, stDec + + ; High nybble of high byte + cleq r0, r0, stSwap ; Clear off the word from the stack + cleq r0, r0, stShr8 + cleq r0, r0, stShr4 + cleq r0, r0, stLowNybble + cleq r0, r0, stOver + cleq r0, r0, stStoreByte + cleq r0, r0, stDec + + ret + +; ptr -- f +fPack: + ; Sign and exponent + cleq r0, r0, fPackWord + + ; Skip over the overflow digit + cleq r0, r0, stInc + + ; Mantissa + cleq r0, r0, fPackWord + cleq r0, r0, fPackWord + cleq r0, r0, fPackWord + + ; Clean off the pointer from stack + breq r0, r0, popWord + + ; ptr -- w ptr+4 + fPackWord: + ; High nybble of high byte + cleq r0, r0, stDup + cleq r0, r0, stLoadByte + cleq r0, r0, stShl8 + cleq r0, r0, stShl4 + cleq r0, r0, stSwap + cleq r0, r0, stInc + cleq r0, r0, stSwap + + ; Low nybble of high byte + cleq r0, r0, stOver + cleq r0, r0, stLoadByte + cleq r0, r0, stShl8 + cleq r0, r0, stOr + cleq r0, r0, stSwap + cleq r0, r0, stInc + cleq r0, r0, stSwap + + ; High nybble of low byte + cleq r0, r0, stOver + cleq r0, r0, stLoadByte + cleq r0, r0, stShl4 + cleq r0, r0, stOr + cleq r0, r0, stSwap + cleq r0, r0, stInc + cleq r0, r0, stSwap + + ; Low nybble of low byte + cleq r0, r0, stOver + cleq r0, r0, stLoadByte + cleq r0, r0, stOr + cleq r0, r0, stSwap + cleq r0, r0, stInc + + ret + +; ------------------------------------------------------------------ +; Unpacked floating point variables +; ------------------------------------------------------------------ + +fArg1Ptr: addr fArg1 +fArg1: + data 00 ; sign + data 00 ; exponent + data 00 + data 00 + data 00 ; overflow + data 00 ; mantissa + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 ; rounding + +fResultPtr: addr fResult +fResult: + data 01 ; sign + data 04 ; exponent + data 09 + data 09 + data 00 ; overflow + data 01 ; mantissa + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 00 + data 01 + data 00 ; rounding + +; ------------------------------------------------------------------ +; Arithmetic +; ------------------------------------------------------------------ + +; x y -- tens(x+y) ones(x+y) +bcdDigitAdd: + cleq r0, r0, stAdd + + cleq r0, r0, stDup + xor r0, r0 + load r1, #a + cleq r0, r0, pushWord + cleq r0, r0, stGtEq + breq r0, r1, bcdDigitAddNoAdjust + + ; Adjust so that carry is in the high nybble + ; Basically we want 9+1=a₁₆ to become 10₁₆ + xor r0, r0 + load r1, #6 + cleq r0, r0, pushWord + cleq r0, r0, stAdd + +bcdDigitAddNoAdjust: + cleq r0, r0, stDup + cleq r0, r0, stShr4 + cleq r0, r0, stSwap + breq r0, r0, stLowNybble + +; x -- 9-x +bcdDigit9sComplement: + xor r0, r0 + load r1, #9 + cleq r0, r0, pushWord + cleq r0, r0, stSwap + breq r0, r0, stSub + +; -- +normalize: + ; Is extended mantissa all zeroes? + load r0, fResult+4 + load r1, fResult+5 + or r0, r1 + load r1, fResult+6 + or r0, r1 + load r1, fResult+7 + or r0, r1 + load r1, fResult+8 + or r0, r1 + load r1, fResult+9 + or r0, r1 + load r1, fResult+a + or r0, r1 + load r1, fResult+b + or r0, r1 + load r1, fResult+c + or r0, r1 + load r1, fResult+d + or r0, r1 + load r1, fResult+e + or r0, r1 + load r1, fResult+f + or r0, r1 + load r1, fResult+10 + or r0, r1 + load r1, fResult+11 + or r0, r1 + + xor r1, r1 + breq r0, r1, normalizeZero + + ; XXX: Implement rest + + ret + + normalizeZero: + ; Zero out the entire number + ; This causes sign to be 0 (non-negative), exponent -500, and + ; extended mantissa 00.000000000000 + load r0, fResultPtr+0 + load r1, fResultPtr+1 + cleq r0, r0, pushWord + xor r0, r0 + load r1, #12 + cleq r0, r0, pushWord + breq r0, r0, stZeroMem + +; ------------------------------------------------------------------ +; Output +; ------------------------------------------------------------------ + +fPrint: + load r0, fArg1Ptr+0 + load r1, fArg1Ptr+1 + cleq r0, r0, pushWord + cleq r0, r0, fUnpack + + ; Overflow? + load r0, fArg1+0 + load r1, #2 + breq r0, r1, fPrintOverflow + + ; Negative? + load r1, #1 + brneq r0, r1, fPrintMantissa + + load r0, #2d ; - + store ffff, r0 + + fPrintMantissa: + load r0, fArg1+5 + cleq r0, r0, writehexNybble + + load r0, #2e ; . + store ffff, r0 + + load r0, fArg1+6 + cleq r0, r0, writehexNybble + load r0, fArg1+7 + cleq r0, r0, writehexNybble + load r0, fArg1+8 + cleq r0, r0, writehexNybble + load r0, fArg1+9 + cleq r0, r0, writehexNybble + load r0, fArg1+a + cleq r0, r0, writehexNybble + load r0, fArg1+b + cleq r0, r0, writehexNybble + load r0, fArg1+c + cleq r0, r0, writehexNybble + load r0, fArg1+d + cleq r0, r0, writehexNybble + load r0, fArg1+e + cleq r0, r0, writehexNybble + load r0, fArg1+f + cleq r0, r0, writehexNybble + load r0, fArg1+10 + cleq r0, r0, writehexNybble + + fPrintExponent: + load r0, #65 ; e + store ffff, r0 + + ; Adjust the hundreds digit for the bias + xor r0, r0, + load r1, fArg1+1 + cleq r0, r0, pushWord + xor r0, r0 + load r1, #5 + cleq r0, r0, pushWord + cleq r0, r0, bcdDigitAdd + + ; Is the exponent negative? + cleq r0, r0, stSwap + cleq r0, r0, popWord + breq r0, r1, fPrintNegativeExponent + + cleq r0, r0, popWord + xor r0, r0 + or r0, r1 + cleq r0, r0, writehexNybble + + load r0, fArg1+2 + cleq r0, r0, writehexNybble + load r0, fArg1+3 + cleq r0, r0, writehexNybble + + ret + + fPrintNegativeExponent: + cleq r0, r0, popWord ; Remove result we don't need + load r0, #2d ; - + store ffff, r0 + + ; The exponent is stored with a bias of 500 + ; unbiased + 500 = biased + ; By this point we know unbiased < 0 and have printed the minus + ; sign, so we want to print the negative of unbiased + ; unbiased + 500 = biased + ; unbiased = -500 + biased + ; -unbiased = 500 - biased + + ; We implement subtraction using 9's complement, where each + ; digit of the subtrahend is subtracted from 9 before being + ; added to the corresponding minuend. This by itself gives a + ; result that is one too small, so we add one to it (c.f. binary + ; subtraction, where we negate the subtrahend and then add one). + ; Normally we'd have to split subtraction into two rounds, one + ; to add the complemented subtrahend and one to add the 1, but + ; since our minuend is a constant, we can add 1 to it instead. + ; 501 + 999 - biased + ; = 500 + 1 + 999 - biased + ; = 500 + 1000 - biased (1000 = 0 mod 1000) + ; = 500 - biased + + ; Ones + xor r0, r0 + load r1, #1 + cleq r0, r0, pushWord + xor r0, r0 + load r1, fArg1+3 + cleq r0, r0, pushWord + cleq r0, r0, bcdDigit9sComplement + cleq r0, r0, bcdDigitAdd + cleq r0, r0, stSwap + + ; Tens + xor r0, r0 + load r1, fArg1+2 + cleq r0, r0, pushWord + cleq r0, r0, bcdDigit9sComplement + cleq r0, r0, bcdDigitAdd + cleq r0, r0, stSwap + + ; Hundreds + xor r0, r0 + load r1, #5 + cleq r0, r0, pushWord + cleq r0, r0, bcdDigitAdd + cleq r0, r0, stSwap + cleq r0, r0, popWord + xor r0, r0 + load r1, fArg1+1 + cleq r0, r0, pushWord + cleq r0, r0, bcdDigit9sComplement + cleq r0, r0, bcdDigitAdd + cleq r0, r0, stSwap + cleq r0, r0, popWord + + ; Print + cleq r0, r0, popWord + xor r0, r0 + or r0, r1 + cleq r0, r0, writehexNybble + cleq r0, r0, popWord + xor r0, r0 + or r0, r1 + cleq r0, r0, writehexNybble + cleq r0, r0, popWord + xor r0, r0 + or r0, r1 + cleq r0, r0, writehexNybble + + ret + + fPrintOverflow: + load r0, #4f ; O + store ffff, r0 + load r0, #76 ; v + store ffff, r0 + load r0, #65 ; e + store ffff, r0 + load r0, #72 ; r + store ffff, r0 + load r0, #66 ; f + store ffff, r0 + load r0, #6c ; l + store ffff, r0 + load r0, #6f ; o + store ffff, r0 + load r0, #77 ; w + store ffff, r0 + ret + ; ================================================================== ; Stack-based functions ; ================================================================== @@ -265,6 +739,62 @@ stDec: cleq r0, r0, decWord breq r0, r0, pushWord +; a b -- a|b +stOr: + cleq r0, r0, popWord + cleq r0, r0, tmpStoreWord01 + cleq r0, r0, popWord + cleq r0, r0, tmpLoadWord23 + or r0, r2 + or r1, r3 + breq r0, r0, pushWord + +; a -- a<<8 +stShl8: + cleq r0, r0, popWord + xor r0, r0 + or r0, r1 + xor r1, r1 + breq r0, r0, pushWord + +; a -- a>>8 +stShr8: + cleq r0, r0, popWord + xor r1, r1 + or r1, r0 + xor r0, r0 + breq r0, r0, pushWord + +; a -- a<<4 +stShl4: + cleq r0, r0, popWord + shl r0, 4 + xor r2, r2 + or r2, r1 + shr r2, 4 + or r0, r2 + shl r1, 4 + breq r0, r0, pushWord + +; a -- a>>4 +stShr4: + cleq r0, r0, popWord + shr r1, 4 + xor r2, r2 + or r2, r0 + shl r2, 4 + or r1, r2 + shr r0, 4 + breq r0, r0, pushWord + +; a -- a&f +stLowNybble: + cleq r0, r0, popWord + xor r0, r0 + load r2, #f + and r1, r2 + breq r0, r0, pushWord + ; a b -- ; if a >= b then r0:r1 = 0001 ; else r0:r1 = 0000 @@ -393,6 +923,28 @@ stLoadWord: or r1, r2 breq r0, r0, pushWord +; ptr n -- +stZeroMem: + cleq r0, r0, peekWord + or r0, r1 + xor r2, r2 + breq r0, r2, stZeroMemDone + + cleq r0, r0, stDec + cleq r0, r0, stSwap + cleq r0, r0, stDup + xor r0, r0 + xor r1, r1 + cleq r0, r0, pushWord + cleq r0, r0, stSwap + cleq r0, r0, stStoreByte + cleq r0, r0, stInc + cleq r0, r0, stSwap + breq r0, r0, stZeroMem + + stZeroMemDone: + ret + ; ================================================================== ; Low-level functions ; ================================================================== @@ -981,6 +1533,15 @@ writehexByte: ret +; in: +; r0 = nybble +; clobbers: +; r3 +writehexNybble: + cleq r0, r0, nybble2hex + store ffff, r0 + ret + ; ------------------------------------------------------------------ ; Common output routines ; ------------------------------------------------------------------