;;
;; Simple monitor program.
;;
;; Accept strings from STDIN, and execute them.
;;
;; Built-in commands
;;
;; [c]all xxxx -> Call the routine at XXXX
;;
;; [d]ump -> Dump 16 bytes of RAM at a given address.
;;
;; [i]nput -> Enter bytes
;;
;; Other input will generate an "ERR" message, and be ignored.
;;
org 0
;;
;; Ensure we have a stack-pointer setup, with some room.
;;
ld sp, stack_start
;;
;; Entry-point to the monitor.
;;
;; Read text into into `input_buffer`, appending to the buffer until a newline
;; is seen, then invoke `process` to handle the input.
;;
monitor:
;; show the prompt.
ld a, '>'
out (1),a
ld hl, input_buffer
monitor_input_loop:
;; overwrite an previous input
ld (hl), '\n'
;; read and store the new character
in a,(1)
ld (hl),a
;; was it a newline? If so process.
cp '\n'
jr z, process_input_line
;; Otherwise loop round for more input.
inc hl
jr monitor_input_loop
;;
;; process_input_line is called when the monitor has received a complete
;; newline-terminated line of text.
;;
;; We process the contents by looking for commands we understand, if we see
;; input we don't recognize we show a message and return, otherwise we invoke
;; the appropriate handler.
;;
;; C => CALL
;; D => DUMP
;; I => INPUT
;;
process_input_line:
ld hl, input_buffer
ld a, (hl)
;; C == CALL
cp 'c'
jr z, call_handler
cp 'C'
jr z, call_handler
;; D == DUMP
cp 'd'
jr z, dump_handler
cp 'D'
jr z, dump_handler
;; I == INPUT
cp 'i'
jr z, input_handler
cp 'I'
jr z, input_handler
;;
;; Unknown command: show a message and restart our monitor
;;
;; We just show "ERR" which is simple, and saves bytes compared to
;; outputting a longer message and using a print-string routine.
;;
ld a, 'E'
out (1),a
ld a, 'R'
out (1), a
out (1), a
ld a, '\n'
out (1),a
jr monitor
;;
;; Call is invoked with the address to call
;;
;; For example "C0003" will call the routine at 0x0003
;;
call_handler:
;; Our input-buffer will start with [cC], so we start looking at the
;; next character.
ld hl, input_buffer+1
;; Read the address to call into BC
call read_16_bit_ascii_number
;; We'll be making a call, so we need to have the return
;; address on the stack so that when the call'd routine ends
;; execution goes somewhere sane.
;;
;; We'll want to re-load the monitor, so we'll store the
;; entry point on the stack
;;
ld hl,monitor
push hl
;; Now we jump, indirectly, to the address in the BC register.
push bc
ret
;;
;; Dump 16 bytes from the current dump_address
;;
;; We're called with either "D" to keep going where we left off or
;; "D1234" if we should start at the given offset.
;;
dump_handler:
;; Our input-buffer will start with [dD], so we start looking at the
;; next character.
ld hl, input_buffer+1
;; Look at the next input-byte. If empty then no address.
ld a, (hl)
cp '\n'
jr z, dump_handler_no_number
;; OK we expect an (ASCII) address following HL - read it into BC.
call read_16_bit_ascii_number
ld (dump_address), bc
dump_handler_no_number:
;; The address we start from
ld hl, (dump_address)
;; show the address
call output_16_bit_number
;; Loop to print the next 16 bytes at that address.
ld b, 16
dump_byte:
;; show a space
ld a, ' '
out (1), a
;; show the memory-contents.
ld c, (hl)
call output_8_bit_number
inc hl
djnz dump_byte
;; all done
ld a, '\n'
out (1),a
;; store our updated/final address.
ld (dump_address), hl
jmp_monitor:
jr monitor
;;
;; Input handler allows code to be assembled at a given address
;;
;; Usage is:
;;
;; I01234 01 02 03 04 0f
;;
;; i.e. "I<address> byte1 byte2 .. byteN"
;;
;; If there is no address keep going from the last time, which means this
;; works as you expect:
;;
;; I1000 01 03
;; I 03 04 0F
;;
input_handler:
;; Our input-buffer will start with [iI], so we start looking at the
;; next character.
ld hl, input_buffer+1
;; Look at the next input-byte. If it is a space then no address was
;; given, so we keep appending bytes to the address set previously.
ld a, (hl)
cp ' '
jr z, input_handler_no_address
;; OK we expect an (ASCII) address following HL - Read it into BC.
call read_16_bit_ascii_number
ld (input_address), bc
input_handler_no_address:
;; HL contains the a string. Get the next byte
ld a,(hl)
inc hl
;; space? skip
cp ' '
jr z, input_handler_no_address
;; newline? If so we're done
cp '\n'
jr z, jmp_monitor
;; OK then we have a two-digit number
dec hl
call read_8_bit_ascii_number
;; store the byte in RAM
ld bc, (input_address)
ld (bc), a
;; bump to the next address
inc bc
ld (input_address), bc
;; continue
jr input_handler_no_address
;;
;; Convert a 4-digit ASCII number, pointed to by HL to a number.
;; Return that number in BC.
;;
read_16_bit_ascii_number:
;; HL is a pointer to a four-char string
;; This is read as a 16 bit hex number
;; The number is stored in BC
call read_8_bit_ascii_number
ld b, a
call read_8_bit_ascii_number
ld c, a
ret
;;
;; Read the two-digit HEX number from HL, and convert to an integer
;; stored in the A-register.
;;
;; HL will be incremented twice.
;;
read_8_bit_ascii_number:
ld a, (hl)
;; is it lower-case? If so upper-case it.
cp 'a'
jr c, read_8_bit_ascii_number_uc
cp 'z'
jr nc, read_8_bit_ascii_number_uc
sub a, 32
read_8_bit_ascii_number_uc:
call read_8_bit_ascii_number_hex
add a, a
add a, a
add a, a
add a, a
ld d, a
inc hl
ld a, (hl)
call read_8_bit_ascii_number_hex
or d
inc hl
ret
read_8_bit_ascii_number_hex:
sub a, '0'
cp 10
ret c
sub a,'A'-'0'-10
ret
;;
;; Display the 16-bit number stored in HL in hex.
;;
output_16_bit_number:
ld c,h
call output_8_bit_number
ld c,l
call output_8_bit_number
ret
;;
;; Display the 8-bit number stored in C in hex.
;;
output_8_bit_number:
ld a,c
rra
rra
rra
rra
call Conv
ld a,c
Conv:
and $0F
add a,$90
daa
adc a,$40
daa
out (1),a
ret
;;;;;;;;
;;;;;;;; RAM stuff
;;;;;;;;
;;
;; Here we store some values.
;;
;; DUMP: We track of the address from which we're dumping.
dump_address:
db 0,0
;; INPUT: Keep track of the address to which we next write.
input_address:
db 0,0
;; We don't nest calls too deeply ..
stack_end:
db 0, 0
db 0, 0
db 0, 0
db 0, 0
db 0, 0
db 0, 0
db 0, 0
db 0, 0
db 0, 0
db 0, 0
stack_start:
;; Command-line input buffer.
input_buffer: