;;==========================================================================;;
;;  Useful Routines                                                         ;;
;;  Copyright 1999-2002, Joe Zbiciak.                                       ;;
;;                                                                          ;;
;;  This file contains a number of useful routines that you're welcome      ;;
;;  to use in your own software.  Please keep in mind that these routines   ;;
;;  are licensed under the GNU General Public License, and so if you plan   ;;
;;  to distribute a program which incorporates these routines, it too must  ;;
;;  be distributed under the GNU General Public License.                    ;;
;;==========================================================================;;

;; ======================================================================== ;;
;;  GLOBAL VARIABLES USED BY THESE ROUTINES                                 ;;
;;                                                                          ;;
;;  Note that some of these routines may use one or more global variables.  ;;
;;  If you use these routines, you will need to allocate the appropriate    ;;
;;  space in either 16-bit or 8-bit memory as appropriate.  Each global     ;;
;;  variable is listed with the routines which use it and the required      ;;
;;  memory width.                                                           ;;
;;                                                                          ;;
;;  Example declarations for these routines are shown below, commented out. ;;
;;  You should uncomment these and add them to your program to make use of  ;;
;;  the routine that needs them.  Make sure to assign these variables to    ;;
;;  locations that aren't used for anything else.                           ;;
;; ======================================================================== ;;

                        ; Used by       Req'd Width     Description
                        ;-----------------------------------------------------
;DEC_0  EQU     $110    ; DEC16/DEC32   8-bit           Temp. storage
;DEC_1  EQU     $111    ; DEC16/DEC32   8-bit           Temp. storage
;DEC_2  EQU     $112    ; DEC32         8-bit           Temp. storage
;DEC_3  EQU     DEC_2+1 ; DEC32         8-bit           Must be next to DEC_2


;; ======================================================================== ;;
;;  POW10                                                                   ;;
;;      Look-up table with powers of 10 as 32-bit numbers (little endian).  ;;
;;                                                                          ;;
;;  NPW10                                                                   ;;
;;      Same as POW10, only -(10**x) instead of 10**x.                      ;;
;; ======================================================================== ;;

POW10   PROC
@@9     WORD   1000000000 AND $FFFF, 1000000000 SHR 16  ; 10**9
@@8     WORD   100000000  AND $FFFF, 100000000  SHR 16  ; 10**8
@@7     WORD   10000000   AND $FFFF, 10000000   SHR 16  ; 10**7 
@@6     WORD   1000000    AND $FFFF, 1000000    SHR 16  ; 10**6
@@5     WORD   100000     AND $FFFF, 100000     SHR 16  ; 10**5
@@4     WORD   10000      AND $FFFF, 10000      SHR 16  ; 10**4
@@3     WORD   1000       AND $FFFF, 1000       SHR 16  ; 10**3
@@2     WORD   100        AND $FFFF, 100        SHR 16  ; 10**2
@@1     WORD   10         AND $FFFF, 10         SHR 16  ; 10**1
@@0     WORD   1          AND $FFFF, 1          SHR 16  ; 10**0
        ENDP

NPW10   PROC
@@9     WORD  -1000000000 AND $FFFF,-1000000000 SHR 16  ;-10**9
@@8     WORD  -100000000  AND $FFFF,-100000000  SHR 16  ;-10**8
@@7     WORD  -10000000   AND $FFFF,-10000000   SHR 16  ;-10**7 
@@6     WORD  -1000000    AND $FFFF,-1000000    SHR 16  ;-10**6
@@5     WORD  -100000     AND $FFFF,-100000     SHR 16  ;-10**5
@@4     WORD  -10000      AND $FFFF,-10000      SHR 16  ;-10**4
@@3     WORD  -1000       AND $FFFF,-1000       SHR 16  ;-10**3
@@2     WORD  -100        AND $FFFF,-100        SHR 16  ;-10**2
@@1     WORD  -10         AND $FFFF,-10         SHR 16  ;-10**1
@@0     WORD  -1          AND $FFFF,-1          SHR 16  ;-10**0
        ENDP

;; ======================================================================== ;;
;;  DEC16                                                                   ;;
;;      Displays a 16-bit decimal number on the screen with leading blanks  ;;
;;      in a field up to 5 characters wide.  Displays all blanks if the     ;;
;;      number is zero.                                                     ;;
;;                                                                          ;;
;;  DEC16A                                                                  ;;
;;      Same as DEC16, only displays leading zeroes.                        ;;
;;                                                                          ;;
;;  DEC16B                                                                  ;;
;;      Same as DEC16, only leading zeros are controlled by bit 15 of R3.   ;;
;;      (If set, suppress leading zeros.  If clear, show leading zeros.)    ;;
;;                                                                          ;;
;;  DEC16C                                                                  ;;
;;      Same as DEC16B, except R1 contains an amount to add to the first    ;;
;;      digit.                                                              ;;
;;                                                                          ;;
;;  DEC16Z                                                                  ;;
;;      Same as DEC16, except displays a single zero if the value is zero.  ;;
;;      (Note:  DEC16Z is actually defined separately along with DEC32Z).   ;;
;;                                                                          ;;
;;  INPUTS:                                                                 ;;
;;      R0 -- Number to be displayed in decimal.                            ;;
;;      R1 -- (DEC16C only): Amount to add to initial digit.                ;;
;;      R2 -- Number of digits to suppress  (If R2<=5, it is 5-field_width) ;;
;;      R3 -- Color mask / screen format word (XORd with char index)        ;;
;;      R4 -- Screen offset (lower 8-bits)                                  ;;
;;                                                                          ;;
;;  OUTPUTS:                                                                ;;
;;      R0 -- Zeroed                                                        ;;
;;      R1 -- Trashed                                                       ;;
;;      R2 -- Remaining digits to suppress (0 if initially <= 5.)           ;;
;;      R3 -- Color mask, with bit 15 set if no digits displayed.           ;;
;;      R4 -- Pointer to character just right of string                     ;;
;;      R5 -- Trashed                                                       ;;
;;                                                                          ;;
;;  NOTES:                                                                  ;;
;;      Routine uses DEC_0, DEC_1 for temporary storage.  This routine      ;;
;;      is NOT REENTRANT, so be careful calling this routine or DEC32 from  ;;
;;      an interrupt handler if you also call either from outside an        ;;
;;      interrupt handler as well.                                          ;;
;;                                                                          ;;
;;      To make this code re-entrant, save the contents of DEC_0 and        ;;
;;      DEC_1 before calling any variant of DEC16 from within an interrupt  ;;
;;      handler and then restore DEC_0 and DEC_1 afterwards.                ;;
;; ======================================================================== ;;
DEC16   PROC
@@so    EQU     DEC_0
@@fw    EQU     DEC_1

        SETC                    ; Prepare to set bit 15 of color mask
        INCR    R7              ; Skip the CLRC
DEC16A
        CLRC                    ; Prepare to clear bit 15 of clrmask
        SLL     R3,     1
        RRC     R3,     1       ; Set/clear bit 15 of color mask
DEC16B
        CLRR    R1
DEC16C
        PSHR    R5              ; Save return address
        MOVR    PC,     R5      ; (generate PC-relative address)
        SUBI    #$-POW10.4, R5  ; Point to '10000' entry in POW10
        MVO     R4,     @@so    ; Save screen offset 
        MVO     R2,     @@fw    ; Save field width
        MVII    #5,     R4      ; Iterate 5 (16-bit goes to 65536)
        MOVR    R1,     R2
        INCR    R7
@@digitlp:
        
        CLRR    R2              ; Start with division result == 0
        SDBD
        MVI@    R5,     R1      ; Load power of 10
        ADDI    #2,     R5      ; Point to next smaller power of 10
@@divloop:
        INCR    R2
        SUBR    R1,     R0      ; Divide by repeated subtraction
        BC      @@divloop
        ADDR    R1,     R0      ; Loop iterates 1 extra time: Fix it.
        DECR    R2              ; Fix extra iter.  Also test if 0
        BNEQ    @@disp          ; If digit != 0, display it.
        TSTR    R3              ; If digit == 0 and no lead 0, skip
        BMI     @@blank
@@disp:
        SLL     R3,     1       ; Clear "no leading 0" flag
        SLR     R3,     1       ; 
        MVI     @@fw,   R1      ; Get field width 
        DECR    R1              ; Are we in active field yet?
        BMI     @@ok            ; Yes: Go ahead and display
        MVO     R1,     @@fw    ; No: Save our count-down till field
        B       @@iter          ;     and don't display the digit.
@@blank:
        MOVR    R3,     R2      ; Blank character _just_ gets format
        MVI     @@fw,   R1      ; Get field width 
        DECR    R1              ; Are we in active field yet?
        BMI     @@drawit        ; Yes: Go ahead and display
        MVO     R1,     @@fw    ; No: Save our count-down till field
        B       @@iter          ;     and don't display the digit.
@@ok:
        ADDI    #$10,   R2      ; Pseudo-ASCII digits start at 0x10
        SLL     R2,     2       ; Put pseudo-ASCII char in position
        ADDR    R2,     R2      ; ... by shifting left 3
        XORR    R3,     R2      ; Merge with display format
@@drawit:
        MVI     @@so,   R1      ; Get screen offset
        XORI    #$200,  R1      ; Move to screen
        MVO@    R2,     R1      ; Put character on screen
        INCR    R1              ; Move the pointer
        MVO     R1,     @@so    ; Save the new offset
@@iter:
        DECR    R4              ; Count down our digit count
        BNEQ    @@digitlp       ; Keep iterating

        MVI     @@so,   R4      ; Restore offset
        MVI     @@fw,   R2      ; Restore digit suppress ocunt

        PULR    PC              ; Whew!  Done!
        ENDP

;; ======================================================================== ;;
;;  DEC32                                                                   ;;
;;      Displays a 32 bit number without leading zeros.  It performs this   ;;
;;      feat by calling DEC16 multiple times.                               ;;
;;                                                                          ;;
;;  DEC32A                                                                  ;;
;;      Same as DEC32, except leading zeros are displayed.                  ;;
;;                                                                          ;;
;;  DEC32B                                                                  ;;
;;      Same as DEC32, except leading zeros are controlled by bit 15 of R3  ;;
;;      (If set, suppress leading zeros.  If clear, show leading zeros.)    ;;
;;                                                                          ;;
;;  DEC32Z                                                                  ;;
;;      Same as DEC32, except displays a single zero if the value is zero.  ;;
;;      (Note:  DEC32Z is actually defined separately along with DEC16Z).   ;;
;;                                                                          ;;
;;  INPUTS:                                                                 ;;
;;      R0 -- Low half of 32-bit number                                     ;;
;;      R1 -- High half of 32-bit number                                    ;;
;;      R2 -- Number of leading digits to suppress (10 - field width)       ;;
;;      R3 -- Screen format word                                            ;;
;;      R4 -- Screen offset (lower 8-bits)                                  ;;
;;                                                                          ;;
;;  OUTPUTS:                                                                ;;
;;      R0 -- Zeroed                                                        ;;
;;      R1 -- Trashed                                                       ;;
;;      R2 -- Remaining digits to suppress (0 if initially <= 10.)          ;;
;;      R3 -- Color mask, with bit 15 set if no digits displayed.           ;;
;;      R4 -- Pointer to character just right of string                     ;;
;;      R5 -- Trashed                                                       ;;
;;                                                                          ;;
;;  NOTES:                                                                  ;;
;;      Routine uses DEC_0..DEC_3 for temporary storage.  This routine      ;;
;;      is NOT REENTRANT, so be careful calling this routine or DEC16 from  ;;
;;      an interrupt handler if you also call either from outside an        ;;
;;      interrupt handler as well.                                          ;;
;;                                                                          ;;
;;      To make this code re-entrant, save the contents of DEC_0 through    ;;
;;      DEC_3 before calling any variant of DEC32 from within an interrupt  ;;
;;      handler and then restore DEC_0 through DEC_3 afterwards.            ;;
;; ======================================================================== ;;
DEC32   PROC
@@so    EQU     DEC_0
@@fw    EQU     DEC_1
@@fmt   EQU     DEC_2 ; and DEC_3.  We store 16 bits here.

        SETC                    ; Prepare to set bit 15 of color mask
        INCR    R7              ; Skip the CLRC
DEC32A
        CLRC                    ; Prepare to clear bit 15 of clrmask
        SLL     R3,     1
        RRC     R3,     1       ; Set/clear bit 15 of color mask
DEC32B
        PSHR    R5              ; Save return address
        MVO     R2,     @@fw    ; Save field width
        MVO     R4,     @@so    ; Save screen offset
        MVO     R3,     @@fmt
        SWAP    R3
        MVO     R3,     @@fmt+1

        CLRR    R3
        PSHR    R3              ; Push accumulator (init'd to 0)

        ; Use division by repeated subtraction to generate a 16-bit
        ; value which represents the first 5 digits of the 10 digit number.
        MOVR    PC,     R5      ; (generate PC-relative address)
        SUBI    #$-NPW10.9, R5  ; Point to -10**9

@@digitlp:
        CLRR    R3
        SDBD
        MVI@    R5,     R2      ; Load low half of 32-bit -10**x
        SDBD
        MVI@    R5,     R4      ; Load high half of 32-bit -10**x
@@divlp:
        SLR     R3,     1
@@divlpb:
        INCR    R3
        ADDR    R2,     R0      ; Add the low half
        ADCR    R1              ; Add carry from low half
        RLC     R3,     1       ; See if adding the carry carried
        ADDR    R4,     R1      ; Add high half
        BC      @@divlp         ; Loop if we had a carry from either
        SARC    R3,     1       ;   upper half ADD.  (We can't get
        BC      @@divlpb        ;   a carry from both, though.)

        ; Subtract off the extra iteration
        SUBR    R2,     R0      ; Subtract the low half.
        ADCR    R1              ; Add in the "not-borrow"
        DECR    R1              ; Turn "not-borrow" into "borrow"
        SUBR    R4,     R1      ; Subtract the high half.

        DECR    R3
        BEQ     @@nxtdigit

        ; Take our count and multiply it by the appropriate power of 10.
        MOVR    R5,     R2
        MOVR    R3,     R4
        SUBR    PC,     R2
        ADDI    #$-NPW10.4, R2  ; Translate 10**x to 10**(x-5)
        BEQ     @@donemult
@@mult:
        ADDR    R4,     R4      ; To mult by 10, do (x<<1)+(x<<3)
        MOVR    R4,     R3
        SLL     R3,     2
        ADDR    R3,     R4
        ADDI    #$4,    R2
        BLT     @@mult
@@donemult:
        ADD@    SP,     R4      ; Add this to our 16-bit accum. 
        PSHR    R4              ; that we keep on top-of-stack
@@nxtdigit:
        CMPI    #NPW10.4, R5
        BLT     @@digitlp

        MVI     @@fw,   R2      ; Restore field width
        MVI     @@so,   R4      ; Restore screen offset
        MVI     @@fmt+1,R3      ; Restore fmt word
        SWAP    R3,     1       ; ...
        XOR     @@fmt,  R3      ; ...
        MVO     R0,     @@fmt   ; Save low byte of lower 16 bits 
        SWAP    R0,     1       ; ...
        MVO     R0,     @@fmt+1 ; Save high byte of lower 16 bits
        PULR    R0              ; Get accumulated word for display
        PSHR    R1              ; Save upper bit
        CALL    DEC16B          ; Display first five digits

        ; Now, our 32-bit number should be less than 100000.  That
        ; means R1 should be 0 or 1.  We display the last five digits
        ; as a single 16-bit number by handling that bit separately.

        MVI     @@fmt+1,R0      ; Restore lower 16 bits
        SWAP    R0,     1       ; ...
        XOR     @@fmt,  R0      ; ...

        PULR    R1              ; Get upper bit
        TSTR    R1              ; Was it zero?
        BEQ     @@noextra       ; Yes:  Nothing special to do
        MVII    #6,     R1      ; No: Add 6 to the leading digit
        ADDI    #5536,  R0      ; ... and "5536" to remaining digits
@@noextra:
        PULR    R5              ; Chain the return.
        B       DEC16C          ; Display remaining digits.  WHEW!
        ENDP

;; ======================================================================== ;;
;;  DEC32Z                                                                  ;;
;;      Same as DEC32, except a zero is displayed in the final position if  ;;
;;      the whole number's value is zero.                                   ;;
;;                                                                          ;;
;;  DEC16Z                                                                  ;;
;;      Same as DEC16, except a zero is displayed in the final position if  ;;
;;      the whole number's value is zero.                                   ;;
;;                                                                          ;;
;;  INPUTS:                                                                 ;;
;;      R0 -- Lower 16-bits of number                                       ;;
;;      R1 -- Upper 16-bits of number (if DEC32Z)                           ;;
;;      R2 -- Number of leading digits to suppress                          ;;
;;            (10 - field width for DEC32Z, 5 - field width for DEC16Z).    ;;
;;      R3 -- Screen format word                                            ;;
;;      R4 -- Screen offset (lower 8-bits)                                  ;;
;;                                                                          ;;
;;  OUTPUTS:                                                                ;;
;;      R0 -- Zeroed                                                        ;;
;;      R1 -- Trashed                                                       ;;
;;      R2 -- If number == 0, unchanged.                                    ;;
;;            If number != 0, remaining digits to suppress.                 ;;
;;      R3 -- Color mask, unmodified.                                       ;;
;;      R4 -- Pointer to character just right of string                     ;;
;;      R5 -- Trashed                                                       ;;
;;                                                                          ;;
;;  NOTES:                                                                  ;;
;;       DEC16Z uses DEC_0..DEC_1 for temporary storage.                    ;;
;;       DEC32Z uses DEC_0..DEC_3 for temporary storage.                    ;;
;;       Neither routine is re-entrant.  See DEC16 and DEC32 above for      ;;
;;       more details.                                                      ;;
;; ======================================================================== ;;
DEC32Z  PROC
        TSTR    R1              ; Is upper half non-zero?
        BNEQ    DEC32           ; Yes:  Call DEC32.
        TSTR    R0              ; Is lower half non-zero?
        BNEQ    DEC32           ; Yes:  Call DEC32
        MVII    #10,    R1      ; No:  Prepare to clear field and draw the 
        B       @@dozero        ;      zero.
DEC16Z:
        TSTR    R0              ; Is the number non-zero?
        BNEQ    DEC16           ; Yes:  Call DEC16
        MVII    #5,     R1      ; No:  Prepare to clear the field and draw 0.

@@dozero:
        SUBR    R2,     R1      ; Is our field wide enough to display the 0?
        BLE     @@nodisp        ; No:  Don't display it then.
        ADDI    #$200,  R4      ; Yes:  Calculate our screen pointer.
        INCR    R7              ; (skip first iteration of loop)
@@loop:
        MVO@    R3,     R4      ; Clear the leading digits.
        DECR    R1
        BNEQ    @@loop

        XORI    #$80,   R3      ; Now display a zero.
        MVO@    R3,     R4
        XORI    #$80,   R3      ; Leave R3 unchanged.

@@nodisp:
        JR      R5              ; Return to the caller.
        ENDP

;* ======================================================================== *;
;*  This program is free software; you can redistribute it and/or modify    *;
;*  it under the terms of the GNU General Public License as published by    *;
;*  the Free Software Foundation; either version 2 of the License, or       *;
;*  (at your option) any later version.                                     *;
;*                                                                          *;
;*  This program is distributed in the hope that it will be useful,         *;
;*  but WITHOUT ANY WARRANTY; without even the implied warranty of          *;
;*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *;
;*  General Public License for more details.                                *;
;*                                                                          *;
;*  You should have received a copy of the GNU General Public License       *;
;*  along with this program; if not, write to the Free Software             *;
;*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               *;
;* ======================================================================== *;
;*                Copyright (c) 1999-2002, Joseph Zbiciak                   *;
;* ======================================================================== *;
