; ;================================================================================================== ; UTILITY FUNCTIONS ;================================================================================================== ; ; CHR_CR .EQU 0DH CHR_LF .EQU 0AH CHR_BS .EQU 08H CHR_ESC .EQU 1BH ; ;__________________________________________________________________________________________________ ; ; UTILITY PROCS TO PRINT SINGLE CHARACTERS WITHOUT TRASHING ANY REGISTERS ; PC_SPACE: PUSH AF LD A,' ' JR PC_PRTCHR PC_PERIOD: PUSH AF LD A,'.' JR PC_PRTCHR PC_COLON: PUSH AF LD A,':' JR PC_PRTCHR PC_COMMA: PUSH AF LD A,',' JR PC_PRTCHR PC_LBKT: PUSH AF LD A,'[' JR PC_PRTCHR PC_RBKT: PUSH AF LD A,']' JR PC_PRTCHR PC_LT: PUSH AF LD A,'<' JR PC_PRTCHR PC_GT: PUSH AF LD A,'>' JR PC_PRTCHR PC_LPAREN: PUSH AF LD A,'(' JR PC_PRTCHR PC_RPAREN: PUSH AF LD A,')' JR PC_PRTCHR PC_ASTERISK: PUSH AF LD A,'*' JR PC_PRTCHR PC_CR: PUSH AF LD A,CHR_CR JR PC_PRTCHR PC_LF: PUSH AF LD A,CHR_LF JR PC_PRTCHR PC_PRTCHR: CALL COUT POP AF RET NEWLINE2: CALL NEWLINE NEWLINE: CALL PC_CR CALL PC_LF RET ; ; PRINT A CHARACTER REFERENCED BY POINTER AT TOP OF STACK ; USAGE: ; CALL PRTCH ; .DB 'X' ; PRTCH: EX (SP),HL PUSH AF LD A,(HL) CALL COUT POP AF INC HL EX (SP),HL RET ; ; PRINT A STRING AT ADDRESS SPECIFIED IN HL ; STRING MUST BE TERMINATED BY '$' ; USAGE: ; LD HL,MYSTR ; CALL PRTSTR ; ... ; MYSTR: .DB "HELLO$" ; PRTSTR: LD A,(HL) INC HL CP '$' RET Z CALL COUT JR PRTSTR ; ; PRINT A STRING DIRECT: REFERENCED BY POINTER AT TOP OF STACK ; STRING MUST BE TERMINATED BY '$' ; USAGE: ; CALL PRTSTR ; .DB "HELLO$" ; ... ; PRTSTRD: EX (SP),HL PUSH AF CALL PRTSTR POP AF EX (SP),HL RET ; ; PRINT A STRING INDIRECT: REFERENCED BY INDIRECT POINTER AT TOP OF STACK ; STRING MUST BE TERMINATED BY '$' ; USAGE: ; CALL PRTSTRI(MYSTRING) ; MYSTRING .DB "HELLO$" ; PRTSTRI: EX (SP),HL PUSH AF LD A,(HL) INC HL PUSH HL LD H,(HL) LD L,A CALL PRTSTR POP HL INC HL POP AF EX (SP),HL RET ; ; PRINT THE HEX BYTE VALUE IN A ; PRTHEXBYTE: PUSH AF PUSH DE CALL HEXASCII LD A,D CALL COUT LD A,E CALL COUT POP DE POP AF RET ; ; PRINT THE HEX WORD VALUE IN BC ; PRTHEXWORD: PUSH AF LD A,B CALL PRTHEXBYTE LD A,C CALL PRTHEXBYTE POP AF RET ; ; PRINT THE HEX DWORD VALUE IN DE:HL ; PRTHEX32: PUSH BC PUSH DE POP BC CALL PRTHEXWORD PUSH HL POP BC CALL PRTHEXWORD POP BC RET ; ; CONVERT BINARY VALUE IN A TO ASCII HEX CHARACTERS IN DE ; HEXASCII: LD D,A CALL HEXCONV LD E,A LD A,D RLCA RLCA RLCA RLCA CALL HEXCONV LD D,A RET ; ; CONVERT LOW NIBBLE OF A TO ASCII HEX ; HEXCONV: AND 0FH ;LOW NIBBLE ONLY ADD A,90H DAA ADC A,40H DAA RET ; ; PRINT A BYTE BUFFER IN HEX POINTED TO BY DE ; REGISTER A HAS SIZE OF BUFFER ; PRTHEXBUF: OR A RET Z ; EMPTY BUFFER ; LD B,A PRTHEXBUF1: CALL PC_SPACE LD A,(DE) CALL PRTHEXBYTE INC DE DJNZ PRTHEXBUF1 RET ; ; PRINT A BLOCK OF MEMORY NICELY FORMATTED ; DE=BUFFER ADDRESS ; DUMP_BUFFER: CALL NEWLINE PUSH DE POP HL INC D INC D DB_BLKRD: PUSH BC PUSH HL POP BC CALL PRTHEXWORD ; PRINT START LOCATION POP BC CALL PC_SPACE ; LD C,16 ; SET FOR 16 LOCS PUSH HL ; SAVE STARTING HL DB_NXTONE: LD A,(HL) ; GET BYTE CALL PRTHEXBYTE ; PRINT IT CALL PC_SPACE ; DB_UPDH: INC HL ; POINT NEXT DEC C ; DEC. LOC COUNT JR NZ,DB_NXTONE ; IF LINE NOT DONE ; NOW PRINT 'DECODED' DATA TO RIGHT OF DUMP DB_PCRLF: CALL PC_SPACE ; SPACE IT LD C,16 ; SET FOR 16 CHARS POP HL ; GET BACK START DB_PCRLF0: LD A,(HL) ; GET BYTE AND 060H ; SEE IF A 'DOT' LD A,(HL) ; O.K. TO GET JR NZ,DB_PDOT ; DB_DOT: LD A,2EH ; LOAD A DOT DB_PDOT: CALL COUT ; PRINT IT INC HL ; LD A,D ; CP H ; JR NZ,DB_UPDH1 ; LD A,E ; CP L ; JP Z,DB_END ; ; ;IF BLOCK NOT DUMPED, DO NEXT CHARACTER OR LINE DB_UPDH1: DEC C ; DEC. CHAR COUNT JR NZ,DB_PCRLF0 ; DO NEXT DB_CONTD: CALL NEWLINE ; JP DB_BLKRD ; DB_END: RET ; ; ; OUTPUT A '$' TERMINATED STRING ; WRITESTR: PUSH AF WRITESTR1: LD A,(DE) CP '$' ; TEST FOR STRING TERMINATOR JP Z,WRITESTR2 CALL COUT INC DE JP WRITESTR1 WRITESTR2: POP AF RET ; ; ; TSTPT: PUSH DE LD DE,STR_TSTPT CALL WRITESTR POP DE JR REGDMP ; DUMP REGISTERS AND RETURN ; ; PANIC: TRY TO DUMP MACHINE STATE ; PANIC: PUSH DE LD DE,STR_PANIC CALL WRITESTR POP DE CALL _REGDMP ; DUMP REGISTERS CALL CONTINUE ; CHECK W/ USER RET ; ; ; REGDMP: CALL _REGDMP RET ; _REGDMP: EX (SP),HL ; RET ADR TO HL, SAVE HL ON TOS LD (REGDMP_RET),HL ; SAVE RETURN ADDRESS POP HL ; RESTORE HL AND BURN STACK ENTRY EX (SP),HL ; PC TO HL, SAVE HL ON TOS LD (REGDMP_PC),HL ; SAVE PC VALUE EX (SP),HL ; BACK THE WAY IT WAS LD (REGDMP_SP),SP ; SAVE STACK POINTER ;LD (RD_STKSAV),SP ; SAVE ORIGINAL STACK POINTER ;LD SP,RD_STACK ; SWITCH TO PRIVATE STACK PUSH AF PUSH BC PUSH DE PUSH HL PUSH AF LD A,'@' CALL COUT POP AF PUSH BC LD BC,(REGDMP_PC) CALL PRTHEXWORD ; PC POP BC CALL PC_LBKT PUSH BC PUSH AF POP BC CALL PRTHEXWORD ; AF POP BC CALL PC_COLON CALL PRTHEXWORD ; BC CALL PC_COLON PUSH DE POP BC CALL PRTHEXWORD ; DE CALL PC_COLON PUSH HL POP BC CALL PRTHEXWORD ; HL CALL PC_COLON LD BC,(REGDMP_SP) CALL PRTHEXWORD ; SP CALL PC_COLON PUSH IX POP BC CALL PRTHEXWORD ; IX CALL PC_COLON PUSH IY POP BC CALL PRTHEXWORD ; IY CALL PC_RBKT CALL PC_SPACE POP HL POP DE POP BC POP AF ;LD SP,(RD_STKSAV) ; BACK TO ORIGINAL STACK FRAME JP $FFFF ; RETURN, $FFFF IS DYNAMICALLY UPDATED REGDMP_RET .EQU $-2 ; RETURN ADDRESS GOES HERE ; REGDMP_PC .DW 0 REGDMP_SP .DW 0 ; ;RD_STKSAV .DW 0 ; .FILL $FF,16*2 ; 16 LEVEL PRIVATE STACK ;RD_STACK .EQU $ ; ; ; CONTINUE: PUSH AF PUSH DE LD DE,STR_CONTINUE CALL WRITESTR POP DE CONTINUE1: CALL CIN CP 'Y' JR Z,CONTINUE3 CP 'y' JR Z,CONTINUE3 CP 'N' JR Z,CONTINUE2 CP 'n' JR Z,CONTINUE2 JR CONTINUE1 CONTINUE2: HALT CONTINUE3: POP AF RET ; ; ; STR_PANIC .DB "\r\n\r\n>>> PANIC: $" STR_TSTPT .TEXT "\r\n+++ TSTPT: $" STR_CONTINUE .TEXT " Continue? (Y/N): $" ;STR_AF .DB " AF=$" ;STR_BC .DB " BC=$" ;STR_DE .DB " DE=$" ;STR_HL .DB " HL=$" ;STR_PC .DB " PC=$" ;STR_SP .DB " SP=$" ; ; INDIRECT JUMP TO ADDRESS IN HL ; ; MOSTLY USEFUL TO PERFORM AN INDIRECT CALL LIKE: ; LD HL,xxxx ; CALL JPHL ; JPHL: JP (HL) ; ; ADD HL,A ; ; A REGISTER IS DESTROYED! ; ADDHLA: ADD A,L LD L,A RET NC INC H RET ; ;**************************** ; A(BCD) => A(BIN) ; [00H..99H] -> [0..99] ;**************************** ; BCD2BYTE: PUSH BC LD C,A AND 0F0H SRL A LD B,A SRL A SRL A ADD A,B LD B,A LD A,C AND 0FH ADD A,B POP BC RET ; ;***************************** ; A(BIN) => A(BCD) ; [0..99] => [00H..99H] ;***************************** ; BYTE2BCD: PUSH BC LD B,10 LD C,-1 BYTE2BCD1: INC C SUB B JR NC,BYTE2BCD1 ADD A,B LD B,A LD A,C ADD A,A ADD A,A ADD A,A ADD A,A OR B POP BC RET #IF (PLATFORM != PLT_UNA) #IFDEF USEDELAY ; ; DELAY 16US (CPU SPEED COMPENSATED) INCUDING CALL/RET INVOCATION ; REGISTER A AND FLAGS DESTROYED ; NO COMPENSATION FOR Z180 MEMORY WAIT STATES ; THERE IS AN OVERHEAD OF 3TS PER INVOCATION ; IMPACT OF OVERHEAD DIMINISHES AS CPU SPEED INCREASES ; ; CPU SCALER (CPUSCL) = (CPUHMZ - 2) FOR 16US + 3TS DELAY ; NOTE: CPUSCL MUST BE >= 1! ; ; EXAMPLE: 8MHZ CPU (DELAY GOAL IS 16US) ; LOOP = ((6 * 16) - 5) = 91TS ; TOTAL COST = (91 + 40) = 131TS ; ACTUAL DELAY = (131 / 8) = 16.375US ; ; --- TOTAL COST = (LOOP COST + 40) TS -----------------+ DELAY: ; 17TS (FROM INVOKING CALL) | LD A,(CPUSCL) ; 13TS | ; | DELAY1: ; | ; --- LOOP = ((CPUSCL * 16) - 5) TS ------------+ | DEC A ; 4TS | | #IFDEF CPU_Z180 ; | | OR A ; +4TS FOR Z180 | | #ENDIF ; | | JR NZ,DELAY1 ; 12TS (NZ) / 7TS (Z) | | ; ----------------------------------------------+ | ; | RET ; 10TS (RETURN) | ;-------------------------------------------------------+ ; ; DELAY 16US * DE (CPU SPEED COMPENSATED) ; REGISTER DE, A, AND FLAGS DESTROYED ; NO COMPENSATION FOR Z180 MEMORY WAIT STATES ; THERE IS A 27TS OVERHEAD FOR CALL/RET PER INVOCATION ; IMPACT OF OVERHEAD DIMINISHES AS DE AND/OR CPU SPEED INCREASES ; ; CPU SCALER (CPUSCL) = (CPUHMZ - 2) FOR 16US OUTER LOOP COST ; NOTE: CPUSCL MUST BE > 0! ; ; EXAMPLE: 8MHZ CPU, DE=6250 (DELAY GOAL IS .1 SEC OR 100,000US) ; INNER LOOP = ((16 * 6) - 5) = 91TS ; OUTER LOOP = ((91 + 37) * 6250) = 800,000TS ; ACTUAL DELAY = ((800,000 + 27) / 8) = 100,003US ; ; --- TOTAL COST = (OUTER LOOP + 27) TS ------------------------+ VDELAY: ; 17TS (FROM INVOKING CALL) | ; | ; --- OUTER LOOP = ((INNER LOOP + 37) * DE) TS ---------+ | LD A,(CPUSCL) ; 13TS | | ; | | VDELAY1: ; | | ; --- INNER LOOP = ((CPUSCL * 16) - 5) TS ------+ | | #IFDEF CPU_Z180 ; | | | OR A ; +4TS FOR Z180 | | | #ENDIF ; | | | DEC A ; 4TS | | | JR NZ,VDELAY1 ; 12TS (NZ) / 7TS (Z) | | | ; ----------------------------------------------+ | | ; | | DEC DE ; 6TS | | #IFDEF CPU_Z180 ; | | OR A ; +4TS FOR Z180 | | #ENDIF ; | | LD A,D ; 4TS | | OR E ; 4TS | | JP NZ,VDELAY ; 10TS | | ;-------------------------------------------------------+ | ; | RET ; 10TS (FINAL RETURN) | ;---------------------------------------------------------------+ ; ; DELAY ABOUT 0.5 SECONDS ; 500000US / 16US = 31250 ; LDELAY: PUSH AF PUSH DE LD DE,31250 CALL VDELAY POP DE POP AF RET ; ; INITIALIZE DELAY SCALER BASED ON OPERATING CPU SPEED ; HBIOS *MUST* BE INSTALLED AND AVAILABLE VIA RST 8!!! ; CPU SCALER := MAX(1, (PHIMHZ - 2)) ; DELAY_INIT: #IF (PLATFORM == PLT_UNA) LD C,$F8 ; UNA BIOS GET PHI FUNCTION RST 08 ; RETURNS SPEED IN HZ IN DE:HL LD B,4 ; DIVIDE MHZ IN DE:HL BY 100000H DELAY_INIT0: SRL D ; ... TO GET APPROX CPU SPEED IN RR E ; ...MHZ. THROW AWAY HL, AND DJNZ DELAY_INIT0 ; ...RIGHT SHIFT DE BY 4. INC E ; FIX UP FOR VALUE TRUNCATION LD A,E ; PUT IN A #ELSE LD B,BF_SYSGET ; HBIOS FUNC=GET SYS INFO LD C,BF_SYSGET_CPUINFO ; HBIOS SUBFUNC=GET CPU INFO RST 08 ; CALL HBIOS, RST 08 NOT YET INSTALLED LD A,L ; PUT SPEED IN MHZ IN ACCUM #ENDIF CP 3 ; TEST FOR <= 2 (SPECIAL HANDLING) JR C,DELAY_INIT1 ; IF <= 2, SPECIAL PROCESSING SUB 2 ; ADJUST AS REQUIRED BY DELAY FUNCTIONS JR DELAY_INIT2 ; AND CONTINUE DELAY_INIT1: LD A,1 ; USE THE MIN VALUE OF 1 DELAY_INIT2: LD (CPUSCL),A ; UPDATE CPU SCALER VALUE RET #IF (CPUMHZ < 3) CPUSCL .DB 1 ; CPU SCALER MUST BE > 0 #ELSE CPUSCL .DB CPUMHZ - 2 ; OTHERWISE 2 LESS THAN PHI MHZ #ENDIF ; #ENDIF ; #ENDIF ; ; SHORT DELAY FUNCTIONS. NO CLOCK SPEED COMPENSATION, SO THEY ; WILL RUN LONGER ON SLOWER SYSTEMS. THE NUMBER INDICATES THE ; NUMBER OF CALL/RET INVOCATIONS. A SINGLE CALL/RET IS ; 27 T-STATES ON A Z80, 25 T-STATES ON A Z180 ; DLY64: CALL DLY32 DLY32: CALL DLY16 DLY16: CALL DLY8 DLY8: CALL DLY4 DLY4: CALL DLY2 DLY2: CALL DLY1 DLY1: RET ; ; MULTIPLY 8-BIT VALUES ; IN: MULTIPLY H BY E ; OUT: HL = RESULT, E = 0, B = 0 ; MULT8: LD D,0 LD L,D LD B,8 MULT8_LOOP: ADD HL,HL JR NC,MULT8_NOADD ADD HL,DE MULT8_NOADD: DJNZ MULT8_LOOP RET ; ; MULTIPLY A 16 BIT BY 8 BIT INTO 16 BIT ; IN: MULTIPLY DE BY A ; OUT: HL = RESULT, B=0, A, C, DE UNCHANGED ; MULT8X16: LD B,8 LD HL,0 MULT8X16_1: ADD HL,HL RLCA JR NC,MULT8X16_2 ADD HL,DE MULT8X16_2: DJNZ MULT8X16_1 RET ;; ;; COMPUTE HL / DE ;; RESULT IN BC, REMAINDER IN HL, AND SET ZF DEPENDING ON REMAINDER ;; A, DE DESTROYED ;; ;DIV: ; XOR A ; LD BC,0 ;DIV1: ; SBC HL,DE ; JR C,DIV2 ; INC BC ; JR DIV1 ;DIV2: ; XOR A ; ADC HL,DE ; USE ADC SO ZF IS SET ; RET ;=============================================================== ; ; COMPUTE HL / DE = BC W/ REMAINDER IN HL & ZF ; DIV16: LD A,H ; HL -> AC LD C,L ; ... LD HL,0 ; INIT HL LD B,16 ; INIT LOOP COUNT DIV16A: SCF RL C RLA ADC HL,HL SBC HL,DE JR NC,DIV16B ADD HL,DE DEC C DIV16B: DJNZ DIV16A ; LOOP AS NEEDED LD B,A ; AC -> BC LD A,H ; SET ZF OR L ; ... BASED ON REMAINDER RET ; DONE ; ; INTEGER DIVIDE DE:HL BY C ; RESULT IN DE:HL, REMAINDER IN A ; CLOBBERS F, B ; DIV32X8: XOR A LD B,32 DIV32X8A: ADD HL,HL RL E RL D RLA CP C JR C,DIV32X8B SUB C INC L DIV32X8B: DJNZ DIV32X8A RET ; ; FILL MEMORY AT HL WITH VALUE A, LENGTH IN BC, ALL REGS USED ; LENGTH *MUST* BE GREATER THAN 1 FOR PROPER OPERATION!!! ; FILL: LD D,H ; SET DE TO HL LD E,L ; SO DESTINATION EQUALS SOURCE LD (HL),A ; FILL THE FIRST BYTE WITH DESIRED VALUE INC DE ; INCREMENT DESTINATION DEC BC ; DECREMENT THE COUNT LDIR ; DO THE REST RET ; RETURN ; ; SET A BIT IN BYTE ARRAY AT HL, INDEX IN A ; BITSET: CALL BITLOC ; LOCATE THE BIT OR (HL) ; SET THE SPECIFIED BIT LD (HL),A ; SAVE IT RET ; RETURN ; ; CLEAR A BIT IN BYTE ARRAY AT HL, INDEX IN A ; BITCLR: CALL BITLOC ; LOCATE THE BIT CPL ; INVERT ALL BITS AND (HL) ; CLEAR SPECIFIED BIT LD (HL),A ; SAVE IT RET ; RETURN ; ; GET VALUE OF A BIT IN BYTE ARRAY AT HL, INDEX IN A ; BITTST: CALL BITLOC ; LOCATE THE BIT AND (HL) ; SET Z FLAG BASED ON BIT RET ; RETURN ; ; LOCATE A BIT IN BYTE ARRAY AT HL, INDEX IN A ; RETURN WITH HL POINTING TO BYTE AND A WITH MASK FOR SPECIFIC BIT ; BITLOC: PUSH AF ; SAVE BIT INDEX SRL A ; DIVIDE BY 8 TO GET BYTE INDEX SRL A ; " SRL A ; " LD C,A ; MOVE TO BC LD B,0 ; " ADD HL,BC ; HL NOW POINTS TO BYTE CONTAINING BIT POP AF ; RECOVER A (INDEX) AND $07 ; ISOLATE REMAINDER, Z SET IF ZERO LD B,A ; SETUP SHIFT COUNTER LD A,1 ; SETUP A WITH MASK RET Z ; DONE IF ZERO BITLOC1: SLA A ; SHIFT DJNZ BITLOC1 ; LOOP AS NEEDED RET ; DONE ; ; PRINT VALUE OF A IN DECIMAL WITH LEADING ZERO SUPPRESSION ; PRTDECB: PUSH HL PUSH AF LD L,A LD H,0 CALL PRTDEC POP AF POP HL RET ; ; PRINT VALUE OF HL IN DECIMAL WITH LEADING ZERO SUPPRESSION ; PRTDEC: PUSH BC PUSH DE PUSH HL LD E,'0' LD BC,-10000 CALL PRTDEC1 LD BC,-1000 CALL PRTDEC1 LD BC,-100 CALL PRTDEC1 LD C,-10 CALL PRTDEC1 LD E,0 LD C,-1 CALL PRTDEC1 POP HL POP DE POP BC RET PRTDEC1: LD A,'0' - 1 PRTDEC2: INC A ADD HL,BC JR C,PRTDEC2 SBC HL,BC CP E JR Z,PRTDEC3 LD E,0 CALL COUT PRTDEC3: RET ; ; SHIFT HL:DE BY B BITS ; SRL32: ; ROTATE RIGHT 32 BITS, HIGH ORDER BITS BECOME ZERO SRL D RR E RR H RR L DJNZ SRL32 RET ; SLA32: ; ROTATE LEFT 32 BITS, LOW ORDER BITS BECOME ZERO SLA L RL H RL E RL D DJNZ SLA32 RET ; ; LOAD OR STORE DE:HL ; LD32: ; LD DE:HL,(HL) PUSH AF LD E,(HL) INC HL LD D,(HL) INC HL LD A,(HL) INC HL LD H,(HL) LD L,A POP AF EX DE,HL RET ; ST32: ; LD (BC),DE:HL PUSH AF LD A,L LD (BC),A INC BC LD A,H LD (BC),A INC BC LD A,E LD (BC),A INC BC LD A,D LD (BC),A POP AF RET ; ; INC/ADD/DEC/SUB 32 BIT VALUE IN DE:HL ; FOR ADD/SUB, OPERAND IS IN BC ; INC32: LD BC,1 ADD32: ADD HL,BC RET NC INC DE RET ; DEC32: LD BC,1 SUB32: OR A SBC HL,BC RET NC DEC DE RET ; ; INC32 (HL) ; INCREMENT 32 BIT BINARY AT ADDRESS ; INC32HL: PUSH HL PUSH BC LD B,4 INC32HL1: INC (HL) INC HL JR NZ,INC32HL2 DJNZ INC32HL1 INC32HL2: POP BC POP HL RET