Files
Sepp J Morris 31738079c4 Upload
Digital Research
2020-11-06 18:50:37 +01:00

1423 lines
34 KiB
NASM
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

; 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