;;==========================================================================;; ;; Useful Routines ;; ;; Copyright 1999-2000, 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. ;; ;;==========================================================================;; ;* ======================================================================== *; ;* 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-2000, Joseph Zbiciak *; ;* ======================================================================== *; ;;==========================================================================;; ;; 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. ;; ;;==========================================================================;; ; Used by Req'd Width Description ;----------------------------------------------------- RNDLO EQU $320 ; RANDBITS 16-bit Random number state RNDHI EQU $321 ; RANDBITS 16-bit Random number state 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 adj. to DEC_2 ;;==========================================================================;; ;; FILLZERO ;; ;; Fills memory with zeros ;; ;; ;; ;; FILLMEM ;; ;; Fills memory with a constant ;; ;; ;; ;; INPUTS: ;; ;; R0 -- Fill value (FILLMEM only) ;; ;; R1 -- Number of words to fill ;; ;; R4 -- Start of fill area ;; ;; R5 -- Return address ;; ;; ;; ;; OUTPUTS: ;; ;; R0 -- Zeroed if FILLZERO, otherwise untouched. ;; ;; R1 -- Zeroed ;; ;; R4 -- Points to word after fill area ;; ;;==========================================================================;; FILLZERO PROC CLRR R0 ; Start out with R0 zeroed for FILLZERO FILLMEM MVO@ R0, R4 ; Store R0 out at R4, and move along DECR R1 ; Keep going until our count runs out BNEQ FILLMEM JR R5 ; Return to the caller. ENDP ;;==========================================================================;; ;; RANDBITS ;; ;; Returns random bits in R0. ;; ;; ;; ;; INPUTS: ;; ;; R0 -- Number of bits desired ;; ;; R5 -- Return address ;; ;; Random state in RNDLO, RNDHI ;; ;; ;; ;; OUTPUTS: ;; ;; R0 -- N random bits. ;; ;; R1, R2, R3, R4 -- Saved and restored ;; ;; R5 -- trashed. ;; ;;==========================================================================;; RAND PROC PSHR R5 ; Save return address and R1..R4 PSHR R4 PSHR R3 PSHR R2 PSHR R1 MVII #1, R1 ; Our initial mask word BEQ @@nobits MVII #$04C1, R5 ; period==(2**32 - 1) polynomial MVII #$1DB7, R4 ; (this is the CRC-32 polynomial) MVI RNDHI, R3 ; Read in our 32-bit random number state MVI RNDLO, R2 TSTR R3 ; If our random number generator is zero BNEQ @@loop ; jumpstart the process by forcing an XOR TSTR R2 ; of our generator polynomal into R2/R3 SETC ; up front. Otherwise, we won't generate BEQ @@forceit ; any random numbers! @@loop: SLLC R2, 1 ; Shift our 32-bit random number left by 1 RLC R3, 1 ; ... by using the carry and an RLC. @@forceit: SLL R1, 1 ; Shift our mask bit left by 1 BNC @@nocarry XORR R4, R2 ; If the carry was set, XOR in our generator XORR R5, R3 ; polynomial. @@nocarry: DECR R0 ; Keep generating bits. BNEQ @@loop MVO R3, RNDHI ; Store our new random number state. MVO R2, RNDLO @@nobits: DECR R1 ; Turn our mask bit into a mask word ANDR R1, R2 ; Mask the bits we actually want. MOVR R2, R0 ; Return our result in R0 PULR R1 ; Retstore our registers and return. PULR R2 PULR R3 PULR R4 PULR PC ENDP ;;==========================================================================;; ;; 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 ;; ;; ;; ;; Routine uses DEC16_0, DEC16_1 for temporary storage. ;; ;;==========================================================================;; 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 ;; ;; ;; ;; Routine uses DEC_0..DEC_3 for temporary storage. ;; ;;==========================================================================;; 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 != 0, remaining digits to suppress ;; ;; R3 -- Color mask, unmodified. ;; ;; R4 -- Pointer to character just right of string ;; ;; R5 -- Trashed ;; ;; ;; ;; DEC16Z uses DEC_0..DEC_1 for temporary storage. ;; ;; DEC32Z uses DEC_0..DEC_3 for temporary storage. ;; ;;==========================================================================;; 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 ;;==========================================================================;; ;; HEX16 ;; ;; Display a 4-digit hex number on the screen ;; ;; ;; ;; INPUTS: ;; ;; R0 -- Hex number ;; ;; R4 -- Screen offset ;; ;; R3 -- Color mask / screen format word ;; ;; ;; ;; OUTPUTS: ;; ;; R0 -- rotated left by 3 ;; ;; R1 -- zeroed ;; ;; R2 -- trashed ;; ;; R3 -- unmodified ;; ;; R4 -- points just to right of string ;; ;;==========================================================================;; HEX16 PROC ; Rotate R0 left by 3, so that our digit will be in the correct ; position within the screen format word. MOVR R0, R1 SLLC R1, 2 RLC R0, 2 ; First, rotate by two bits... SLLC R1, 1 RLC R0, 1 ; ... and then by one more. MVII #4, R1 ; Iterate through four digits. @@loop: ; Rotate R0 left by 4, so that we can cycle through each digit ; one at a time. MOVR R0, R2 SLLC R2, 2 RLC R0, 2 ; First, rotate by two bits... SLLC R2, 2 RLC R0, 2 ; ... and then by two more. ; Mask out a single hex digit MOVR R0, R2 ANDI #$78, R2 ; Is it A..F? If so, add an offset so that the correct ASCII ; value is selected. Otherwise do nothing special. CMPI #$50, R2 ; $50 is $A shifted left by 3. BLT @@digit ADDI #$38, R2 ; If the digit >= A, add 6 << 3. @@digit: ADDI #$80, R2 ; Generate proper GROM index. XORR R3, R2 ; Merge in the screen format word MVO@ R2, R4 ; Display the digit to the screen. DECR R1 ; Iterate three more times. BNE @@loop JR R5 ; Done! Return. ENDP ;;==========================================================================;; ;; DRAWSTRING ;; ;; Puts an ASCIIZ string pointed to by R0 onscreen. ;; ;; ;; ;; DRAWSTRING2 ;; ;; Puts an ASCIIZ string after a JSR R5 onscreen. ;; ;; ;; ;; INPUTS: ;; ;; R0 -- String pointer (if DRAWSTRING) ;; ;; R1 -- Screen format word ;; ;; R4 -- Output pointer ;; ;; R5 -- Return address (also, string if DRAWSTRING2). ;; ;; ;; ;; OUTPUTS: ;; ;; R0 -- Zero if DRAWSTRING2, one if DRAWSTRING ;; ;; R1 -- Untouched EXCEPT bit 15 is cleared. ;; ;; R4 -- Points just after displayed string. ;; ;; R5 -- Points just past end of string. ;; ;; R2 and R3 are not modified. ;; ;;==========================================================================;; DRAWSTRING PROC PSHR R5 ; Save the return address MOVR R0, R5 ; Move our pointer into R5 SETC ; Flag that we'll be returning via PULR PC INCR R7 ; (skip the CLRC) DRAWSTRING2: CLRC ; Flag that we'll be returning via JR R5 SLL R1, 1 RRC R1, 1 ; Put flag into bit 15 of screen format word. MVI@ R5, R0 ; Get first char of string @@tloop: SUBI #32, R0 ; Shift ASCII range to charset SLL R0, 2 ; Move it to position for BTAB word SLL R0, 1 XORR R1, R0 ; Merge with color info MVO@ R0, R4 ; Write to display MVI@ R5, R0 ; Get next character TSTR R0 ; Is it NUL? BNEQ @@tloop ; --> No, keep copying then SLLC R1, 1 ; Recover 'return' flag from screen fmt word, ADCR R0 ; ... and put it in R0. SLR R1, 1 ; Restore screen format word. ADDR R0, R7 ; If flag is set, return by PULR PC, else JR R5 ; ... return by the JR R5. PULR R7 ENDP