; Figure 10-2 ; Debug Subroutines ; ;<---- NOTE : ; The line numbers at the extreme left are included purely ; to reference the code from the text. ; There are deliberately induced discontinuities ; in the numbers in order to allow space for expansion. ; ; Because of the need to test these routines thoroughly, ; and in case you wish to make any changes, the testbed ; routines for the debug package itself has been left in ; in this figure. ; ; Debug Testbed. ; ORG 100H START: LXI SP,Test$Stack ;Setup local stack CALL DB$Init ;Initialize the debug package CALL DB$On ;Enable Debug Output ;Simple test of A-register display MVI A,0AAH ;Preset a value in the A register LXI B,0BBCCH ;Prefill all other registers, partly LXI D,0DDEEH ; to check the debug display, but LXI H,0FF11H ; also to check register save/restore ;# ; Test register display ; ORA A ;Set M-flag, Clear Z-flag, Set E-Flag STC ;Set Carry CALL DB$Display ;Call the Debug Routine DB DB$F DB 'Flags',0 ; CALL DB$Display ;Call the Debug Routine DB DB$A DB 'A Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$B DB 'B Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$C DB 'C Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$D DB 'D Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$E DB 'E Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$H DB 'H Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$L DB 'L Register',0 ;# ; Test Memory Dump Display ; CALL DB$Display DB DB$M ;Dump memory DW 108H,128H ;Check start/end at non-multiples DB 'Memory Dump #1',0 ; of 10H. ; CALL DB$Display DB DB$M ;Dump memory DW 100H,11FH ;Check start and end on displayed DB 'Memory Dump #2',0 ; line boundaries. ; CALL DB$Display DB DB$M ;Dump memory DW 101H,100H ;Check error handling where DB 'Memory Dump #3',0 ; start > end address. ; CALL DB$Display DB DB$M ;Dump memory DW 100H,100H ;Check end-case of single byte DB 'Memory Dump #4',0 ; output. ;# ; Test register pair display ; CALL DB$Display ;Call the Debug Routine DB DB$BC DB 'BC Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$DE DB 'DE Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$HL DB 'HL Register',0 ; CALL DB$Display ;Call the Debug Routine DB DB$SP DB 'SP Register',0 ; LXI B,Byte$BC ;Set up registers for byte tests LXI D,Byte$DE LXI H,Byte$HL ;# ; Test byte indirect display ; CALL DB$Display ;Call the Debug Routine DB DB$B$BC DB 'Byte at (BC)',0 ; CALL DB$Display ;Call the Debug Routine DB DB$B$DE DB 'Byte at (DE)',0 ; CALL DB$Display ;Call the Debug Routine DB DB$B$HL DB 'Byte at (HL)',0 ; LXI B,Word$BC ;Set up the registers for word tests LXI D,Word$DE LXI H,Word$HL CALL DB$Display ;Call the Debug Routine DB DB$W$BC DB 'Word at (BC)',0 ; CALL DB$Display ;Call the Debug Routine DB DB$W$DE DB 'Word at (DE)',0 ; CALL DB$Display ;Call the Debug Routine DB DB$W$HL DB 'Word at (HL)',0 ;# ; Test DB$On/Off ; CALL DB$Off ;Disable Debug Output CALL DB$MSGI ;Display in-line message DB 0DH,0AH,'This message should NOT appear',0 CALL DB$On CALL DB$MSGI DB 0DH,0AH,'Debug output has been re-enabled.',0 ;# ; Test pass count logic ; CALL DB$Off ;Disable debug output CALL DB$Set$Pass ;Set Pass count DW 30 ; MVI A,34 ;Set loop counter greater than pass ; counter. Test$Pass$Loop: CALL DB$Pass ;Decrement pass count CALL DB$MSGI ;Display in-line message DB 0DH,0AH,'This message should display 5 times',0 DCR A JNZ Test$Pass$Loop ;# ; Test debug input/output ; CALL DB$Off ;Check that debug IN/OUT ; must still occur when debug ; output is disabled. RST 4 ;Debug input DB 11H ;Port number RST 5 ;Debug output (value return from input) DB 22H ;Port number JMP 0 ;Warm Boot at end of testbed ; ; ; Dummy values for Byte and Word displays Byte$BC: DB 0BCH Byte$DE: DB 0DEH Byte$HL: DB 0F1H ; Word$BC: DW 0B0CH Word$DE: DW 0D0EH Word$HL: DW 0F01H ; DW 9999H,9999H,9999H,9999H,9999H,9999H,9999H,9999H DW 9999H,9999H,9999H,9999H,9999H,9999H,9999H,9999H DW 9999H,9999H,9999H,9999H,9999H,9999H,9999H,9999H Test$Stack: ; ; ; ORG 400H ;To avoid unnecessary listings ; when only the testbed changes ;----------------------------------------------------------------------- ;# ; ; Debug Subroutines ; ; ; Equates for DB$Display Codes ; These equates are the offsets down the table of addresses ; for various subroutines to be used. ; DB$F EQU 00 ;Flags DB$A EQU 02 ;A register DB$B EQU 04 ;B DB$C EQU 06 ;C DB$D EQU 08 ;D DB$E EQU 10 ;E DB$H EQU 12 ;H DB$L EQU 14 ;L DB$BC EQU 16 ;BC DB$DE EQU 18 ;DE DB$HL EQU 20 ;HL DB$SP EQU 22 ;Stack Pointer DB$M EQU 24 ;Memory DB$B$BC EQU 26 ;(BC) DB$B$DE EQU 28 ;(DE) DB$B$HL EQU 30 ;(HL) DB$W$BC EQU 32 ;(BC+1),(BC) DB$W$DE EQU 34 ;(DE+1),(DE) DB$W$HL EQU 36 ;(HL+1),(HL) ; ; ; Equates RST4 EQU 20H ;Address for RST 4 - IN instruction RST5 EQU 28H ;Address for RST 5 - OUT instruction ; B$CONIN EQU 1 ;BDOS CONIN Function Code B$CONOUT EQU 2 ;BDOS CONOUT Function Code B$READCONS EQU 10 ;BDOS Read Console Function Code BDOS EQU 5 ;BDOS Entry Point ; False EQU 0 True EQU NOT False ; ;Equates to specify how DB$CONOUT ; and DB$CONIN should perform ; their Input/Output. DB$Polled$IO EQU False ;) DB$BIOS$IO EQU False ;) Only one MUST be true DB$BDOS$IO EQU True ;) ; ;Equates for Polled I/O DB$Status$Port EQU 01H ;Console status port DB$Data$Port EQU 02H ;Console data port ; DB$Input$Ready EQU 0000$0010B ;Incoming data ready DB$Output$Ready EQU 0000$0001B ;Ready for output ; ;Data for BIOS I/O BIOS$CONIN: DB JMP ;The initialization routine sets these DW 0 ; two JMP addresses into the BIOS BIOS$CONOUT: DB JMP DW 0 ; ; Main debug variables and constants ; DB$Flag: DB 0 ;Main Debug Control Flag. ; When this flag is non-zero, all debug ; output will be made. When zero, all ; debug output will be suppressed. ; It is altered either directly by the user ; or using the routines DB$On, DB$Off and ; DB$Pass. ; DB$Pass$Count: DW 0 ;Pass counter. ; When this is non-zero, calls to DB$Pass ; decrement it by one. When it reaches ; zero, the debug control flag, DB$Flag, ; is set non-zero - thereby enabling ; debug output. ; DB$Save$HL: ;Save area for HL DB$Save$L: DB 0 DB$Save$H: DB 0 DB$Save$SP: DW 0 ;Save area for Stack Pointer DB$Save$RA: DW 0 ;Save area for Return Address DB$Call$Address: DW 0 ;Starts out the same as DB$Save$RA ; but DB$Save$RA gets updated during ; debug processing. This value is ; output ahead of the caption. DB$Start$Address: ;Start address for memory display DW 0 DB$End$Address: ;End address for memory display DW 0 DB$Display$Code: ;Display code requested DB 0 ; ; ;Stack Area DW 9999H,9999H,9999H,9999H,9999H,9999H,9999H,9999H DW 9999H,9999H,9999H,9999H,9999H,9999H,9999H,9999H DW 9999H,9999H,9999H,9999H,9999H,9999H,9999H,9999H DB$Save$E: DB 0 ;E Register DB$Save$D: DB 0 ;D Register DB$Save$C: DB 0 ;C Register DB$Save$B: DB 0 ;B Register DB$Save$F: DB 0 ;Flags DB$Save$A: DB 0 ;A Register DB$Stack: ;Debug stack area ; The registers in the stack area are PUSHed ; onto the stack and accessed directly. ; ; Register Caption Messages ; ; The table below, indexed by the Display$Code is used to access ; the Register Caption String. ; DB$Register$Captions: DW DB$F$RC ;Flags DW DB$A$RC ;A register DW DB$B$RC ;B DW DB$C$RC ;C DW DB$D$RC ;D DW DB$E$RC ;E DW DB$H$RC ;H DW DB$L$RC ;L DW DB$BC$RC ;BC DW DB$DE$RC ;DE DW DB$HL$RC ;HL DW DB$SP$RC ;Stack Pointer DW DB$M$RC ;Memory DW DB$B$BC$RC ;(BC) DW DB$B$DE$RC ;(DE) DW DB$B$HL$RC ;(HL) DW DB$W$BC$RC ;(BC+1),(BC) DW DB$W$DE$RC ;(DE+1),(DE) DW DB$W$HL$RC ;(HL+1),(HL) ; DB$F$RC: DB 'Flags',0 ;Flags DB$A$RC: DB 'A',0 ;A register DB$B$RC: DB 'B',0 ;B DB$C$RC: DB 'C',0 ;C DB$D$RC: DB 'D',0 ;D DB$E$RC: DB 'E',0 ;E DB$H$RC: DB 'H',0 ;H DB$L$RC: DB 'L',0 ;L DB$BC$RC: DB 'BC',0 ;BC DB$DE$RC: DB 'DE',0 ;DE DB$HL$RC: DB 'HL',0 ;HL DB$SP$RC: DB 'SP',0 ;Stack Pointer DB$M$RC: DB 'Start, End Address ',0 ;Memory DB$B$BC$RC: DB '(BC)',0 ;(BC) DB$B$DE$RC: DB '(DE)',0 ;(DE) DB$B$HL$RC: DB '(HL)',0 ;(HL) DB$W$BC$RC: DB '(BC+1),(BC)',0 ;(BC+1),(BC) DB$W$DE$RC: DB '(DE+1),(DE)',0 ;(DE+1),(DE) DB$W$HL$RC: DB '(HL+1),(HL)',0 ;(HL+1),(HL) ; ; Flags Message ; DB$Flags$Msg: DB 'CxZxMxExIx',0 ;Compatible with DDT's display ; ; Flags Masks used to test user's flag byte ; DB$Flag$Masks: DB 0000$0001B ;Carry DB 0100$0000B ;Zero DB 1000$0000B ;Minus DB 0000$0100B ;Even Parity DB 0001$0000B ;Interdigit Carry (Aux Carry) DB 0 ;Terminator ;# ; DB$Init ; This routine initializes the debug package. ; DB$Init: IF DB$BIOS$IO ;Use BIOS for CONIN/CONOUT LHLD 1 ;Get Warm Boot address from base ; page. H = BIOS Jump Vector page MVI L,09H ;Get CONIN offset in Jump Vector SHLD BIOS$CONIN + 1 ;Setup address MVI L,0CH ;Get CONOUT offset in Jump Vector SHLD BIOS$CONOUT + 1 ENDIF ;Setup JMP instructions to receive control ; when an RST instruction is executed MVI A,JMP ;Set JMP instructions at RST points STA RST4 STA RST5 LXI H,DB$Input ;Address of Fake Input Routine SHLD RST4 + 1 LXI H,DB$Output ;Address of Fake Output Routine SHLD RST5 + 1 RET ;# ; DB$CONINU ; This routine returns the next character from the console, ; but converting 'a' to 'z' to UPPER CASE letters. ; DB$CONINU: CALL DB$CONIN ;Get character from keyboard JMP DB$A$To$Upper ;Fold to upper and return ;# ; DB$CONIN ; This routine returns the next character from the console. ; According to the setting of EQUates, it uses either simple ; polled I/O, the BDOS (function 2) or the BIOS. ; ; Exit Parameters ; ; A = Character from console ; DB$CONIN: IF DB$Polled$IO ;Simple polled input IN DB$Status$Port ;Check if incoming data ANI DB$Input$Ready JZ DB$CONIN ;No IN DB$Data$Port ;Input data character PUSH PSW ;Save data character MOV C,A ;Ready for output CALL DB$CONOUT ;Echo it back POP PSW ;Recover data character RET ENDIF IF DB$BDOS$IO ;Use BDOS for input MVI C,B$CONIN ;Read console JMP BDOS ;BDOS returns to our caller ENDIF IF DB$BIOS$IO ;Use BIOS for input JMP BIOS$CONIN ;This was setup during BIOS ; initialization ENDIF ;# ; DB$CONOUT ; This routine outputs the character in the C register to the ; Console, either using simple polled I/O, the BDOS or the BIOS. ; ; Entry Parameters ; A = Byte to be output ; DB$CONOUT: LDA DB$Flag ;Check if debug output enabled ORA A RZ ;Ignore output if disabled IF DB$Polled$IO ;Use simple polled output IN DB$Status$Port ;Check if ready for output ANI DB$Output$Ready JZ DB$CONOUT ;No MOV A,C ;Get data byte OUT DB$Data$Port RET ENDIF IF DB$BDOS$IO ;Use BDOS for output MOV E,C ;Move into correct register MVI C,B$CONOUT JMP BDOS ;BDOS returns to our caller ENDIF IF DB$BIOS$IO ;Use BIOS for output MOV A,C ;Move into correct register JMP BIOS$CONOUT ;Setup during debug initialization ENDIF ;# ; ; DB$On ; This routine enables all debug output by setting the ; DB$Flag non-zero. ; DB$On: PUSH PSW ;Preserve registers MVI A,0FFH STA DB$Flag ;Set control flag on POP PSW RET ;# ; ; DB$Off ; This routine disables all debug output by setting the ; DB$Flag to zero. ; DB$Off: PUSH PSW ;Preserve registers XRA A STA DB$Flag ;Clear control flag POP PSW RET ;# ; ; DB$Set$Pass ; This routine sets the Pass Counter. Subsequent calls to DB$Pass ; decrement the count, and when it reaches 0, debug output ; is enabled. ; ; Calling sequence ; ; CALL DB$Set$Pass ; DW Pass$Count$Value ; DB$Set$Pass: SHLD DB$Save$HL ;Preserve user's HL POP H ;Recover return address PUSH D ;Preserve user's DE MOV E,M ;Get LS byte of count INX H ;Update pointer MOV D,M ;Get MS byte INX H ;HL points to return address XCHG ;HL = pass counter SHLD DB$Pass$Count ;Set Debug pass counter XCHG ;HL points to return address POP D ;Recover user's DE XTHL ;Recover user's HL and set ; return address on top of stack RET ;# ; ; DB$Pass ; This routine decrements the Debug pass counter - ; if the result is negative, it takes no further action. ; If the result is zero, it sets the Debug control flag non-zero ; to enable debug output. ; DB$Pass: PUSH PSW ;Save user's registers PUSH H LHLD DB$Pass$Count ;Get pass count DCX H MOV A,H ;Check if count now negative ORA A JM DB$Pass$x ;Yes - take no further action SHLD DB$Pass$Count ;Save downdated count ORA L ;Check if count now zero JZ DB$Pass$ED ;Yes - enable debug DB$Pass$x: ; POP H ;Recover user's registers POP PSW RET ; DB$Pass$Ed: ;Enable debug MVI A,0FFH STA DB$Flag ;Set debug control flag JMP DB$Pass$x ;# ; ; DB$Display ; This is the primary debug display routine. ; ; Calling sequence ; ; CALL DB$Display ; DB Display$Code ; DB 'Caption String',0 ; ; Display code identifies which register(s) are to be ; displayed. ; ; When the display code specifies a block of memory ; the sequence is: ; ; CALL DB$Display ; DB Display$Code ; DW Start$Address,End$Address ; DB 'Caption String',0 ; DB$Display: ; DB$Display$Enabled: SHLD DB$Save$HL ;Save user's HL XTHL ;Get return address from stack SHLD DB$Save$RA ;This gets updated by debug code PUSH H ;Save return address temporarily DCX H ;Subtract 3 to address CALL instruction DCX H ; itself DCX H SHLD DB$Call$Address ;Save actual address of CALL POP H ;Recover return address PUSH PSW ;Temporarily save flags to avoid ; them being changed by DAD SP LXI H,0 ;Preserve Stack Pointer DAD SP INX H ;Correct for extra PUSH PSW needed INX H ; to save the flags SHLD DB$Save$SP POP PSW ;Recover flags LXI SP,DB$Stack ;Switch to local stack PUSH PSW ;Save other user's registers PUSH B ;The stack area is specially laid PUSH D ; out to access these registers LHLD DB$Save$RA ;Get return address MOV A,M ;Get display code STA DB$Display$Code INX H ;Update return address CPI DB$M ;Check if memory to be displayed JNZ DB$Not$Memory MOV E,M ;Get DE = start address INX H MOV D,M INX H XCHG ;HL = start address SHLD DB$Start$Address XCHG ;HL -> end address MOV E,M ;Get DE = end address INX H MOV D,M INX H XCHG ;HL = end address, DE -> Caption SHLD DB$End$Address XCHG ;HL -> caption string DB$Not$Memory: ; ; Output Preamble and Caption String ; The format for everything except memory display is: ; ; nnnn : Caption String : RC = vvvv ; ^ ^ ^ ; Call Address | Value ; Register Caption (A, B, C...) ; ; A Carriage Return, Line Feed is output at the start of the ; message - but NOT at the end. ; ; Memory displays look like : ; ; nnnn : Caption String : Start, End ssss, eeee ; ssss : hh hh hh hh hh hh hh hh hh hh hh hh hh hh hh hh : cccc cccc cccc cccc ; PUSH H ;Save pointer to caption string CALL DB$CRLF ;Display Carriage Return, Line Feed CALL DB$Display$CALLA ;Display DB$Call$Address in hex. POP H ;Recover pointer to caption string DB$Display$Caption: ;HL -> caption string MOV A,M ;Get character INX H ORA A ;Check if end of string JZ DB$End$Caption ;Yes PUSH H ;Save string pointer MOV C,A ;Ready for output CALL DB$CONOUT ;Display character POP H ;Recover string pointer JMP DB$Display$Caption ;Go back for next character ; DB$End$Caption: SHLD DB$Save$RA ;Save updated return address CALL DB$Colon ;Display ' : ' ;Display register caption LDA DB$Display$Code ;Get user's display code MOV E,A ;Make display code into word MVI D,0 PUSH D ;Save word value for later CPI DB$M ;Memory display is a special case JZ DB$Display$Mem$Caption ;Yes LXI H,DB$Register$Captions ;Make pointer to address in table DAD D ;HL -> Word containing address of ; register caption MOV E,M ;Get LS byte of address INX H MOV D,M ;DE -> register caption string XCHG ;HL -> register caption string CALL DB$MSG ;Display message addressed by HL CALL DB$MSGI ;Display in-line message DB ' = ',0 JMP DB$Select$Routine ;Go to correct processor ; DB$Display$Mem$Caption: ;The Memory Display requires a special ; caption with the start and end ; addresses LXI H,DB$M$RC ;Display specific caption CALL DB$MSG CALL DB$Colon ;Display ' : ' LHLD DB$Start$Address ;Display start address CALL DB$DHLH ;Display HL in Hex. CALL DB$MSGI ;Display in-line message DB ', ',0 LHLD DB$End$Address ;Get end address CALL DB$DHLH ;Display HL in Hex. CALL DB$CRLF ;Display Carriage Return, Line Feed ;Drop into select routine DB$Select$Routine: POP D ;Recover word value Display$Code LXI H,DB$Display$Table DAD D ;HL -> Address of code to process ; display requirements MOV E,M ;Get LS byte of address INX H ;Update pointer MOV D,M ;Get MS byte of address XCHG ;HL -> code LXI D,DB$Exit ;Fake link on stack PUSH D PCHL ;"CALL" display processor ; DB$Exit: ;Return to the user POP D ;Recover user's registers saved POP B ; on local debug stack POP PSW LHLD DB$Save$SP ;Revert to user's stack SPHL LHLD DB$Save$RA ;Get updated return address (bypasses ; in-line parameters XTHL ;Replace on top of user's stack LHLD DB$Save$HL ;Get user's HL RET ;Transfer to correct return address DB$Display$Table: DW DP$F ;Flags DW DP$A ;A register DW DP$B ;B DW DP$C ;C DW DP$D ;D DW DP$E ;E DW DP$H ;H DW DP$L ;L DW DP$BC ;BC DW DP$DE ;DE DW DP$HL ;HL DW DP$SP ;Stack Pointer DW DP$M ;Memory DW DP$B$BC ;(BC) DW DP$B$DE ;(DE) DW DP$B$HL ;(HL) DW DP$W$BC ;(BC+1),(BC) DW DP$W$DE ;(DE+1),(DE) DW DP$W$HL ;(HL+1),(HL) ;# ; Debug display processing routines ; DP$F: ;Flags ;The Flags are displayed in the same way that ; DDT uses : C1Z0M0E0I0 LDA DB$Save$F ;Get Flags MOV B,A ;Preserve copy LXI H,DB$Flags$Msg + 1 ;HL -> first 0/1 in message LXI D,DB$Flag$Masks ;DE -> Table of flag mask values DB$F$Next: LDAX D ;Get next flag mask ORA A ;Check if end of table JZ DB$F$Display ;Yes, display the results ANA B ;Check if this flag is set MVI A,'1' ;Assume yes JNZ DB$F$NZ ;Yes it is set MVI A,'0' ;No it is clear DB$F$NZ: MOV M,A ;Store '0' or '1' in message text INX H ;Update pointer to next 0/1 INX H INX D ;Update flag mask pointer JMP DB$F$Next DB$F$Display: ;Display results LXI H,DB$Flags$Msg JMP DB$MSG ;Display message and return ; DP$A: ;A register LDA DB$Save$A ;Get saved value JMP DB$DAH ;Display it and return ; DP$B: ;B LDA DB$Save$B ;Get saved value JMP DB$DAH ;Display it and return ; DP$C: ;C LDA DB$Save$C ;Get saved value JMP DB$DAH ;Display it and return ; DP$D: ;D LDA DB$Save$D ;Get saved value JMP DB$DAH ;Display it and return ; DP$E: ;E LDA DB$Save$E ;Get saved value JMP DB$DAH ;Display it and return ; DP$H: ;H LDA DB$Save$H ;Get saved value JMP DB$DAH ;Display it and return ; DP$L: ;L LDA DB$Save$L ;Get saved value JMP DB$DAH ;Display it and return ; DP$BC: ;BC LHLD DB$Save$C ;Get saved WORD value JMP DB$DHLH ;Display it and return ; DP$DE: ;DE LHLD DB$Save$E ;Get saved WORD value JMP DB$DHLH ;Display it and return ; DP$HL: ;HL LHLD DB$Save$HL ;Get saved WORD value JMP DB$DHLH ;Display it and return ; DP$SP: ;Stack Pointer LHLD DB$Save$SP ;Get saved WORD value JMP DB$DHLH ;Display it and return ; DP$M: ;Memory LHLD DB$End$Address ;Increment end address to make INX H ; arithmetic easier SHLD DB$End$Address LHLD DB$Start$Address CALL DB$M$Check$End ;Compare HL to End$Address JC DB$M$Address$OK ;End > Start CALL DB$MSGI ;Error Start > End DB 0DH,0AH,'** ERROR - Start Address > End **',0 RET ; DB$M$Next$Line: CALL DB$CRLF ;Output Carriage Return, Line Feed DB$M$Address$OK: ;Bypass CR,LF for first line CALL DB$MSGI ;Indent line DB ' ',0 LHLD DB$Start$Address ;Get start of line address CALL DB$DHLH ;Display in hex CALL DB$Colon ;Display ' : ' LHLD DB$Start$Address DB$M$Next$Hex$Byte: PUSH H ;Save memory address CALL DB$Blank ;Output a blank POP H ;Recover current byte address MOV A,M ;Get byte from memory INX H ;Update memory pointer PUSH H ;Save for later CALL DB$DAH ;Display in hex POP H ;Recover memory updated address CALL DB$M$Check$End ;Compare HL vs End Address JZ DB$M$Display$ASCII ;Yes - End of area MOV A,L ;Check if at start of new line ANI 0000$1111B ; by check if address is XXX0H JZ DB$M$Display$ASCII ;Yes JMP DB$M$Next$Hex$Byte ;No - loop back for another ; DB$M$Display$ASCII: ;Display bytes in ASCII CALL DB$Colon ;Display ' : ' LHLD DB$Start$Address ;Start ASCII as beginning of line DB$M$Next$ASCII$Byte: MOV A,M ;Get byte from memory PUSH H ;Save memory address ANI 0111$1111B ;Remove parity MOV C,A ;Prepare for output CPI ' ' ;Check if non-graphic JNC DB$M$Display$Char ;Char >= Space MVI C,'.' ;Display non-graphic as '.' DB$M$Display$Char: CPI 7FH ;Check if DEL (may be non-graphic) JNZ DB$M$Not$DEL ;No - it is graphic MVI C,'.' ;Force to '.' ; DB$M$Not$DEL: CALL DB$CONOUT ;Display character POP H ;Recover memory address INX H ;Update memory pointer SHLD DB$Start$Address ;Update memory copy CALL DB$M$Check$End ;Check if end of memory dump JZ DB$M$Exit ;Yes - Done MOV A,L ;Check if end of line ANI 0000$1111B ; by checking address = XXX0H JZ DB$M$Next$Line ;Yes - start next line MOV A,L ;Check if extra blank needed ANI 0000$0011B ; if address is multiple of 4 JNZ DB$M$Next$ASCII$Byte ;No - go back for next character CALL DB$Blank ;Yes, output blank JMP DB$M$Next$ASCII$Byte ;Go back for next character ; DB$M$Exit: JMP DB$CRLF ;Output Carriage Return, Line Feed ; and Return ; DB$M$Check$End: ;Compares HL vs End$Address PUSH D ;Save DE (Defensive programming) XCHG ;DE = Current Address LHLD DB$End$Address ;Get end address MOV A,D ;Compare MS bytes CMP H JNZ DB$M$Check$End$X ;Exit now as they are unequal MOV A,E ;Compare LS bytes CMP L DB$M$Check$End$X: XCHG ;HL = Current address POP D ;Recover DE RET ;Return with condition flags set ; DP$B$BC: ;(BC) LHLD DB$Save$C ;Get saved WORD value MOV A,M ;Get byte addressed by it JMP DB$DAH ;Display it and return ; DP$B$DE: ;(DE) LHLD DB$Save$E ;Get saved WORD value MOV A,M ;Get byte addressed by it JMP DB$DAH ;Display it and return ; DP$B$HL: ;(HL) LHLD DB$Save$HL ;Get saved WORD value MOV A,M ;Get byte addressed by it JMP DB$DAH ;Display it and return ; DP$W$BC: ;(BC+1),(BC) LHLD DB$Save$C ;Get saved WORD value MOV E,M ;Get WORD addresses by it INX H MOV D,M XCHG ;HL = WORD to be displayed JMP DB$DHLH ;Display it and return ; DP$W$DE: ;(DE+1),(DE) LHLD DB$Save$E ;Get saved WORD value MOV E,M ;Get WORD addresses by it INX H MOV D,M XCHG ;HL = WORD to be displayed JMP DB$DHLH ;Display it and return ; DP$W$HL: ;(HL+1),(HL) LHLD DB$Save$HL ;Get saved WORD value MOV E,M ;Get WORD addresses by it INX H MOV D,M XCHG ;HL = WORD to be displayed JMP DB$DHLH ;Display it and return ; ;# ; DB$Display$CALLA ; This routine displays the DB$Call$Address in hexadecimal, ; followed by " : ". ; DB$Display$CALLA: PUSH H ;Save caller's HL LHLD DB$Call$Address ;Get the call address CALL DB$DHLH ;Display HL in hex. POP H ;Recover caller's HL JMP DB$Colon ;Display " : " and return ; ;# ; ; DB$DHLH ; Display HL in Hex. ; ; Entry Parameters ; ; HL = Value to be displayed ; DB$DHLH: PUSH H ;Save input value MOV A,H ;Get MS byte first CALL DB$DAH ;Display A in Hex POP H ;Recover input value MOV A,L ;Get LS byte JMP DB$DAH ;Display it and return ; ;# ; ; DB$DAH ; Display A register in Hexadecimal ; ; Entry Parameters ; ; A = Value to be converted and output ; DB$DAH: PUSH PSW ;Take a copy of the value to be converted RRC ;Shift A right four places RRC RRC RRC CALL DB$Nibble$To$Hex ;Convert LS 4 bits to ASCII CALL DB$CONOUT ;Display the character POP PSW ;Get original value again CALL DB$Nibble$To$Hex ;Convert LS 4 bits to ASCII JMP DB$CONOUT ;Display and return to caller ;# ; ; DB$CAH ; Convert A register to Hexadecimal ASCII and store in ; specified address. ; ; Entry Parameters ; ; A = Value to be converted and output ; HL -> Buffer area to receive two characters of output ; ; Exit Parameters ; ; HL -> Byte following last hex byte output ; DB$CAH: PUSH PSW ;Take a copy of the value to be converted RRC ;Shift A right four places RRC RRC RRC CALL DB$Nibble$To$Hex ;Convert to ASCII Hex. MOV M,A ;Save in memory INX H ;Update pointer POP PSW ;Get original value again CALL DB$Nibble$To$Hex ;Convert to ASCII Hex. MOV M,A ;Save in memory INX H ;Update pointer RET ;# ; ; Minor subroutines. ; ; ; DB$Nibble$To$Hex ; This is a minor subroutine that converts the least ; significant four bits of the A register into an ASCII ; Hex. character in A and C ; ; Entry Parameters ; ; A = Nibble to be converted in LS 4 bits ; ; Exit Parameters ; ; A,C = ASCII Hex. character ; DB$Nibble$To$Hex: ANI 0000$1111B ;Isolate LS four bits ADI '0' ;Convert to ASCII CPI '9' + 1 ;Compare to maximum JC DB$NTH$Numeric ;No need to convert to A -> F ADI 7 ;Convert to a letter DB$NTH$Numeric: MOV C,A ;For convenience of other routines RET ; ; DB$CRLF ; Simple routine to display Carriage Return, Line Feed. ; DB$CRLF: CALL DB$MSGI ;Display in-line message DB 0DH,0AH,0 RET ; ; DB$Colon ; Simple routine to display ' : '. ; DB$Colon: CALL DB$MSGI ;Display in-line message DB ' : ',0 RET ; ; DB$Blank ; Simple routine to display ' '. ; DB$Blank: CALL DB$MSGI ;Display in-line message DB ' ',0 RET ;# ; ; Message Processing subroutines. ; ; DB$MSGI (Message In-line) ; Output null-byte terminated message that follows the ; CALL to MSGOUTI. ; ; Calling sequence ; ; CALL DB$MSGI ; DB 'Message',0 ; ... next instruction ; ; Exit Parameters ; HL -> instruction following message ; ; DB$MSGI: ;Get return address of stack, save ; user's HL on top of stack XTHL ;HL -> Message PUSH PSW ;Save all user's registers PUSH B PUSH D DB$MSGI$Next: MOV A,M ;Get next data byte INX H ;Update message pointer ORA A ;Check if null byte JNZ DB$MSGIC ;No, continue POP D ;Recover user's registers POP B POP PSW XTHL ;Recover user's HL from stack, replacing ; it with updated return address RET ;Return to address after 00-byte ;after in-line message DB$MSGIC: PUSH H ;Save message pointer MOV C,A ;Ready for output CALL DB$CONOUT POP H ;Recover message pointer JMP DB$MSGI$Next ;Go back for next char. ; ; DB$MSG ; Output null-byte terminated message. ; ; Calling sequence ; ; MESSAGE: DB 'Message',0 ; : ; LXI H,MESSAGE ; CALL DB$MSG ; ; Exit Parameters ; HL -> Null byte terminator ; ; DB$MSG: PUSH PSW ;Save user's registers PUSH B PUSH D DB$MSG$Next: MOV A,M ;Get next byte for output ORA A ;Check if 00-byte terminator JZ DB$MSG$X ;Exit INX H ;Update message pointer PUSH H ;Save updated pointer MOV C,A ;Ready for output CALL DB$CONOUT POP H ;Recover message pointer JMP DB$MSG$Next ;Go back for next character ; DB$MSG$X: POP D ;Recover user's registers POP B POP PSW RET ;# ; ; Debug Input Routine ; ; This routine helps debug code in which INput instructions ; would normally occur. The Opcode of the IN instruction ; must be replaced by a value of 0E7H (RST 4). ; ; This routine picks up the port number contained in the byte ; following the RST 4, converts it to Hexadecimal, and ; displays the message : ; ; Input from Port XX : ; ; It then accepts two characters (in Hex) from the keyboard, ; converts these to binary in A, and then returns control ; to the byte following the port number. ; ; ******* ; WARNING - This routine uses both DB$CONOUT and BDOS calls ; ******* ; DBIN$Message: DB 'Input from Port ' DBIN$Port: DB 'XX : ',0 ; ; DB$Input: SHLD DB$Save$HL ;Save user's HL POP H ;Recover address of port number DCX H ;Backup to point to RST SHLD DB$Call$Address ;Save for later display INX H ;Restore to point to port number ;Note : A need not be preserved MOV A,M ;Get Port number INX H ;Update return address to bypass port number SHLD DB$Save$RA ;Save return address PUSH B ;Save remaining registers PUSH D PUSH PSW ;Save port number for later CALL DB$Flag$Save$On ;Save current state of debug flag ; and enable debug output CALL DB$CRLF ;Display Carriage Return, Line Feed CALL DB$Display$CALLA;Display call address POP PSW ;Recover port number LXI H,DBIN$Port CALL DB$CAH ;Convert to Hex and store in message LXI H,DBIN$Message ;Output Prompting Message CALL DB$MSG MVI C,2 ;Get 2 digit hex value CALL DB$GHV ;Returns value in HL MOV A,L ;Get just single byte CALL DB$Flag$Restore ;Restore debug output to previous state POP D ;Recover registers POP B LHLD DB$Save$HL ;Get previous HL PUSH H ;Put on top of stack LHLD DB$Save$RA ;Get return address XTHL ;TOS = Return Address, HL = Previous value RET ;# ; ; Debug Output Routine ; ; This routine helps debug code in which OUTput instructions ; would normally occur. The Opcode of the OUT instruction ; must be replaced by a value of 0EFH (RST 5). ; ; This routine picks up the port number contained in the byte ; following the RST 5, converts it to Hexadecimal, and ; displays the message : ; ; Output to Port XX : AA ; ; Where AA is the contents of the A register prior to the ; RST 5 being executed. ; Control is then returned to the byte following the port number. ; ; ******* ; WARNING - This routine uses both DB$CONOUT and BDOS calls ; ******* ; ; DBO$Message: DB 'Output to Port ' DBO$Port: DB 'XX : ' DBO$Value: DB 'AA',0 ; ; DB$Output: SHLD DB$Save$HL ;Save user's HL POP H ;Recover address of port number DCX H ;Backup to point to RST SHLD DB$Call$Address ;Save for later display INX H ;Restore to point at port number STA DB$Save$A ;Preserve value to be output MOV A,M ;Get Port number INX H ;Update return address to bypass port number SHLD DB$Save$RA ;Save return address PUSH B ;Save remaining registers PUSH D PUSH PSW ;Save port number for later CALL DB$Flag$Save$On ;Save current state of debug flag ; and enable debug output CALL DB$CRLF ;Display Carriage Return, Line Feed CALL DB$Display$CALLA;Display call address POP PSW ;Recover port number LXI H,DBO$Port CALL DB$CAH ;Convert to Hex and store in message LDA DB$Save$A LXI H,DBO$Value ;Convert value to be output CALL DB$CAH ;Convert to Hex and store in message LXI H,DBO$Message ;Output Prompting Message CALL DB$MSG CALL DB$Flag$Restore ;Restore debug flag to previous state POP D ;Recover registers POP B LHLD DB$Save$HL ;Get previous HL PUSH H ;Put on top of stack LHLD DB$Save$RA ;Get return address XTHL ;TOS = Return Address, HL = Previous value LDA DB$Save$A ;Recover A (NOTE : FLAG NOT RESTORED) RET ;# ; ; DB$Flag$Save$On ; This routine is only used for DB$IN/OUT. ; It saves the current state of the debug control flag, ; D$Flag, and then enables it so as to make sure that ; DB$IN/OUT output always goes out. ; DB$Flag$Previous: DB 0 ;Previous flag value ; DB$Flag$Save$On: PUSH PSW ;Save caller's registers LDA DB$Flag ;Get current value STA DB$Flag$Previous ;Save it MVI A,0FFH ;Set flag STA DB$Flag POP PSW RET ;# ; ; DB$Flag$Restore ; This routine is only used for DB$IN/OUT. ; It restores the debug control flag, DB$Flag, to ; its former state. ; DB$Flag$Restore: PUSH PSW LDA DB$Flag$Previous ;Get previous setting STA DB$Flag ;Set debug control flag POP PSW RET ; ;# ; ; Get Hex Value ; ; This subroutine outputs a prompting message, and then reads ; the keyboard in order to get a hexadecimal value. ; It is somewhat simplistic in that the first non-hex value ; terminates the input. The maximum number of digits to be ; converted is specified as an input parameter. ; entered, only the last four are significant. ; ;**************************************************************** ; W A R N I N G ; DB$GHV will ALWAYS use the BDOS to perform a Read Console ; Function (#10). Be CAREFUL if you use this routine from ; within an executing BIOS. ;**************************************************************** ; ; Entry Parameters ; ; HL -> 00-byte terminated message to be output ; C = number of hexadecimal digits to be input ; ; DB$GHV$Buffer: ;Input buffer for console characters DB$GHV$Max$Count: DB 0 ;Set to the maximum number of chars ; to be input DB$GHV$Input$Count: DB 0 ;Set by the BDOS to the actual number ; or chars entered DB$GHV$Data$Bytes DS 5 ;Buffer space for the characters ; ; DB$GHV: MOV A,C ;Get maximum characters to be input CPI 5 ;Check against maximum count JC DB$GHV$Count$OK ;Carry set if A < 5 MVI A,4 ;Force to only four characters DB$GHV$Count$OK: STA DB$GHV$Max$Count ;Setup maximum count in input buffer CALL DB$MSG ;Output Prompting Message LXI D,DB$GHV$Buffer ;Accept characters from console MVI C,B$READCONS ;Function Code CALL BDOS MVI C,B$CONOUT ;Output a Line Feed MVI E,0AH CALL BDOS LXI H,0 ;Initial value LXI D,DB$GHV$Data$Bytes ;DE -> Data characters LDA DB$GHV$Input$Count ;Get count of characters input MOV C,A ;Keep count in C DB$GHV$Loop: DCR C ;Downdate count RM ;Return when all done (HL has value) LDAX D ;Get next character from buffer INX D ;Update buffer pointer CALL DB$A$To$Upper ;Convert A to upper case if need be CPI '0' ;Check if less than 0 RC ;Yes, terminate CPI '9' + 1 ;Check if > 9 JC DB$GHV$Hex$Digit ;No - it must be numeric CPI 'A' ;Check if < 'A' RC ;Yes - terminate CPI 'F' + 1 ;Check if > 'F' RNC ;Yes - terminate SUI 'A' - 10 ;Convert A through F to numeric JMP DB$GHV$Shift$Left$4 ;Combine with current result ; DB$GHV$Hex$Digit: SUI '0' ;Convert to binary DB$GHV$Shift$Left$4: DAD H ;Shift HL left four bits DAD H DAD H DAD H ADD L ;Add binary value in LS 4 bits of A MOV L,A ;Put back into HL total JMP DB$GHV$Loop ;Loop back for next character ;# ; ; A to Upper ; Converts the contents of the A register to an upper ; case letter if it is currently a lower case letter. ; ; Entry Parameters ; ; A = Character to be converted ; ; Exit Parameters ; ; A = Converted character ; DB$A$To$Upper: CPI 'a' ;Compare to lower limit RC ;No need to convert CPI 'z' + 1 ;Compare to upper limit RNC ;No need to convert ANI 5FH ;Convert to upper case RET