-
Notifications
You must be signed in to change notification settings - Fork 4
/
monitor.asm
571 lines (526 loc) · 15.2 KB
/
monitor.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
; 2018-08-10: WRS - based on monitor from socz80
; hardware register addresses
UART0_STATUS: equ 0x00 ; [7: RX READY] [6: TX BUSY] [6 unused bits]
UART0_DATA: equ 0x01
STACK_INIT: equ 0x0000 ; stack (grows downwards, runtime movable)
; we're copied directly into RAM by the Teensy
org 0xF800
boot:
di ; disable interrupts (for those arriving via RST 0 rather than CPU reset)
warmboot:
ld sp, STACK_INIT ; load stack pointer to point to 1 byte past top of memory
ld iy, input_buffer ; set default input buffer location
; put a reset vector in place to jump back into us (Teensy does this too)
ld a, 0xc3 ; jmp instruction
ld (0), a
ld hl, boot
ld (1), hl
; print our greeting
ld hl, greeting
call outstring
; flush UART FIFO
fifoflush:
in a, (UART0_DATA) ; read byte from UART
in a, (UART0_STATUS) ; check status
bit 7, a ; test if data waiting
jr nz, fifoflush ; keep flushing if there is
monitor_loop:
ld hl, monitor_prompt
call outstring
push iy ; HL=IY
pop hl
call instring
call outnewline
ld ix, cmd_table
testnextcmd:
push iy ; HL=IY
pop hl
; test for end of string
ld a, (hl)
cp 0
jr z, monitor_loop
; load string address
ld e, (ix+0)
ld d, (ix+1)
call cmdcompare
jp z, runcmd
; advance ix to point to next command
ld bc, 4
add ix, bc
; list is terminated with a zero, check for that
ld a, (ix+0)
or (ix+1)
cp 0
jr nz, testnextcmd
; end of list but no command found
ld hl, what_msg
call outstring
jr monitor_loop
runcmd: ; run command at ix+2, ix+3
ld e, (ix+2)
ld d, (ix+3)
push de
pop ix
jp (ix)
do_help:
ld hl, help_msg
call outstring
jp monitor_loop
; parse command line to display I/O register contents. call with (HL) pointing into input buffer after "in "
do_in:
call skipspaces
call parsehexorfail
; DE now contains the I/O address to read
push de
call outde
ld a, 0x3d
call outchar
ld a, 0x20
call outchar
pop bc
in a, (c) ; asserts full 16-bit address bus
call outcharhex
call outnewline
call skipspaces
ld a, (hl)
cp 0
jp z, monitor_loop ; don't use ret, we get here by a jump and not a call.
jr do_in ; next address
; write to I/O register
do_out:
call skipspaces
call parsehexorfail
; DE now contains the I/O address
push de
nextio:
call skipspaces
call parsehexorfail
; DE now contains the value to write
ld a, d ; check D=0
cp 0
jr nz, invalid
pop bc ; I/O address
out (c), e
inc bc ; next I/O address
call skipspaces
ld a, (hl)
cp 0
jp z, monitor_loop
push bc
jr nextio
; write to memory command
do_wm:
call skipspaces
call parsehexorfail
push de
pop ix ; put the memory address in ix
wm_nextbyte:
call skipspaces
ld a, (hl)
cp 0
jp z, monitor_loop ; end of string; go back to the monitor
call parsehex
; data value to write to now in DE
; confirm D byte is zero ie we're dealing with a single byte
ld a, d
cp 0
jr nz, invalid
; do the write
ld (ix+0), e
; move to next memory address for the next byte
inc ix
jr wm_nextbyte
invalid:
ld hl, invalid_msg
call outstring
jp monitor_loop ; don't use ret
; parse command line to display memory contents. call with (HL) pointing into input buffer after "dm "
do_dm:
call skipspaces
call parsehexorfail
push de ; start address
call skipspaces
call parsehex
ld a, e
or d
cp 0
jr nz, dmgo
; ok DE=0, which is a problem. we'll dump 64K -- takes a while!
ld e, 1 ; force DE=0x0001
dmgo:
pop hl ; start address
call dumpmem
jp monitor_loop ; don't use ret, we get here by a jump and not a call.
; display memory starting at (HL) length DE
dumpmem:
; start by writing the memory address
call outhl
ld a, 0x3a
call outchar
; now print the bytes from memory separated by spaces
dumpnb: ld a, 0x20
call outchar
ld a, (hl)
call outcharhex
; next byte
inc hl
dec de
; got to the end?
ld a, e
or d
jr z, dumpdone
; newline?
ld a, l
and 0x0f
cp 0
jr nz, dumpnb
dumpnl: call outnewline
jr dumpmem
dumpdone:
call outnewline
ret
; jump to given address
do_run:
call skipspaces
call parsehexorfail
; address to jump to is now in (DE)
ex de, hl ; put it in HL where we can use it
ld bc, warmboot
push bc ; push return address (monitor entry point)
jp (hl)
; we never get here.
do_cp:
; copy memory
call skipspaces
call parsehexorfail
; src is now in DE
push de
call skipspaces
call parsehexorfail
; dst is now in DE
push de
call skipspaces
call parsehexorfail
; len is now in DE
push de
; set up for LDIR:
; DE dst
; HL src
; BC count
pop bc
pop de
pop hl
ldir ; copy copy copy!
jp monitor_loop
do_sp:
call skipspaces
ld a, (hl) ; check if we're at the end of string?
cp 0
jr z, showsp
; if not, load SP first
call parsehex
; new SP is now in DE
push de
pop hl
ld sp, hl
showsp:
; you can't read SP directly but you can add it to HL (go figure)
ld hl, sp_msg
call outstring
ld hl, 0
add hl, sp
call outhl
call outnewline
jp monitor_loop
do_buf:
call skipspaces
ld a, (hl) ; check if we're at the end of string?
cp 0
jr z, showbuf
; if not, load SP first
call parsehex
; new SP is now in DE
push de
pop iy
showbuf:
ld hl, buf_msg
call outstring
push iy
pop hl
call outhl
call outnewline
jp monitor_loop
; compare strings at (HL) and (DE), input buffer in (HL) and command in (DE).
; return with flags NZ -> inequality
; return with flags Z -> equal ie string at (DE) is a prefix of (HL)
cmdcompare:
ld a, (de)
cp 0
ret z ; end of string at (DE) -> return zero flag
cp (hl)
ret nz ; non-matching -> return nonzero flag
inc de
inc hl
jr cmdcompare
; outstring: Print the string at (HL) until 0 byte is found
; destroys: AF HL
outstring:
ld a, (hl) ; load next character
and a ; test if zero
ret z ; return when we find a 0 byte
call outchar
inc hl ; next char please
jr outstring
; print the string at (HL) in hex (continues until 0 byte seen)
outstringhex:
ld a, (hl) ; load next character
and a ; test if zero
ret z ; return when we find a 0 byte
call outcharhex
ld a, 0x20 ; space
call outchar
inc hl ; next char please
jr outstringhex
; output a newline
outnewline:
ld a, 0x0d ; output newline
call outchar
ld a, 0x0a
call outchar
ret
outhl: ; prints HL in hex. Destroys AF.
ld a, h
call outcharhex
ld a, l
call outcharhex
ret
outbc: ; prints BC in hex. Destroys AF.
ld a, b
call outcharhex
ld a, c
call outcharhex
ret
outde: ; prints DE in hex. Destroys AF.
ld a, d
call outcharhex
ld a, e
call outcharhex
ret
; print the byte in A as a two-character hex value
outcharhex:
push bc
ld c, a ; copy value
; print the top nibble
rra
rra
rra
rra
call outnibble
; print the bottom nibble
ld a, c
call outnibble
pop bc
ret
; print the nibble in the low four bits of A
outnibble:
and 0x0f ; mask off low four bits
cp 10
jr c, numeral ; less than 10?
add 0x07 ; start at 'A' (10+7+0x30=0x41='A')
numeral:add 0x30 ; start at '0' (0x30='0')
call outchar
ret
; outchar: Wait for UART TX idle, then print the char in A
; destroys: AF
outchar:
push bc
ld b, a
; wait for transmitter to be idle
ocloop: in a, (UART0_STATUS)
bit 6, a
jr nz, ocloop ; loop while busy
; now output the char to serial port
ld a, b
out (UART0_DATA), a
pop bc
ret
; instring: Read a typed string into (HL) terminate with 0 byte when CR seen (leaves (HL) pointing at the 0 byte)
; Returns length of string (excluding 0 terminator) in C
; Special handling for newline, backspace and non-printing characters.
; If first character received is '@' it is not inserted into the buffer and the buffer is not echoed back (for bulk data load etc)
; DOES NOT PERFORM BUFFER LENGTH CHECKING!
instring:
ld c, 0 ; we use c to remember our string length
call incharwait
cp '@'
jr nz, gotchar
; we've got an @ so we should do a bulk load
call outchar
jr instringbulk
instringloop:
call incharwait ; reads single byte into A
gotchar:; test for cr/lf
cp 0x0d
jr z, cr
cp 0x0a
jr z, cr
; test for backspace
cp 0x08
jr z, backspace
cp 0x7f
jr z, backspace
; test for non-printing characters
cp 0x20 ; < 0x20?
jp c, instringloop
cp 0x7f ; > 0x7f?
jp nc, instringloop
; store the character in the buffer
ld (hl), a
inc hl
inc c
call outchar ; echo back the character typed
jr instringloop
backspace:
ld a, c
cp 0
jr z, instringloop ; cannot backspace past the start
dec hl
dec c
ld a, 0x08 ; move back
call outchar
ld a, 0x20 ; print space
call outchar
ld a, 0x08 ; move back again
call outchar
jr instringloop
cr:
ld a, 0
ld (hl), a
ret
; a fast bulk version of instring
; does not echo back characters received after the first space, in order to avoid overflowing the rx fifo
; does not perform full terminal handling
instringbulk:
call incharwait
cp 0x0d
jr z, cr
cp 0x0a
jr z, cr
ld (hl), a
inc hl
cp 0x20
jr z, isbspace
call outchar
jr instringbulk
isbspace:
call incharwait
cp 0x0d
jr z, cr
cp 0x0a
jr z, cr
ld (hl), a
inc hl
jr isbspace
; incharwait: Wait for UART RX, return character read in A
; destroys: A
incharwait:
in a, (UART0_STATUS)
bit 7, a
jr z, incharwait ; loop while no character received
in a, (UART0_DATA)
ret
; advance HL until it no longer points at a space
skipspaces:
ld a, (hl)
cp 0x20
ret nz
inc hl
jr skipspaces
; convert character in A to upper case
toupper:
cp 0x61 ; < "a" ?
ret c
cp 0x7b
ret nc
and 0x5f
ret
; if at end of string, jump to invalid, else continue
; and run parsehex
parsehexorfail:
ld a, (hl)
cp 0
jp z, invalid
; fall through to parsehex
; read hex digits pointed to by hl, load into DE
; does not work when fed with non-hex digits!!
parsehex:
ld d, 0
ld e, 0
parsemorehex:
ld a, (hl)
cp 0x30 ; not a hex character?
ret c
; convert to nibble
cp 0x40 ; <= '9'?
jr c, phnumeral
sub 7
phnumeral: sub 0x30
and 0xf
; A now contains the nibble at (hl) ie bits are 0000nnnn
; now we do:
; D = D << 4 | E << 4
; E = E << 4 | A
; remember rl shifts 9 bits total; 8 bits in the register plus the carry bit from flags register
push bc ; don't clobber B
ld b, 4 ; prepare to do this four times
phshift:
and a ; clear carry flag (so we shift zeros into the low four bits of E)
rl e ; rotate C register left one bit, top bit moves into carry flag
rl d ; rotate B register left one bit, carry flag moves into lower bit
djnz phshift ; repeat four times please
pop bc
or e ; combine C and A
ld e, a ; move A into C
inc hl
jr parsemorehex
greeting: db "\r\n"
db "Z80 ROM Monitor (Will Sowerbutts, 2013-12-12)\r\n", 0
monitor_prompt: db "Z80> ", 0
what_msg: db "Error reduces\r\nYour expensive computer\r\nTo a simple stone.\r\n", 0
invalid_msg: db "Errors have occurred.\r\nWe won't tell you where or why.\r\nLazy programmers.\r\n", 0
sp_msg: db "SP=", 0
buf_msg: db "BUF=", 0
help_msg: db "Commands:\r\n"
db "\tdm addr [len]\t\t\tdisplay memory contents from addr for len (default 1) bytes\r\n"
db "\twm addr val [val...]\t\twrite bytes to memory starting at addr\r\n"
db "\tcp src dst len\t\t\tcopy len bytes from src to dst\r\n"
db "\trun addr\t\t\trun code at addr\r\n"
db "\tin addr\t\t\t\tread I/O port at addr, display result\r\n"
db "\tout addr val [val...]\t\twrite I/O port at addr with val\r\n"
db "\tsp [addr]\t\t\tshow stack pointer (and set to addr)\r\n"
db "\tbuf [addr]\t\t\tshow input buffer (and set to addr)\r\n"
db "\t@[cmd]\t\t\t\tPerform command without echo or terminal handling (bulk operations)\r\n"
db 0
cmd_buf: db "buf", 0 ; no space at end (arg is optional)
cmd_cp: db "cp ", 0
cmd_dm: db "dm ", 0
cmd_help: db "help", 0
cmd_help2: db "?", 0
cmd_in: db "in ", 0
cmd_out: db "out ", 0
cmd_run: db "run ", 0
cmd_sp: db "sp",0 ; no space at end (arg is optional)
cmd_wm: db "wm ", 0
cmd_table:
dw cmd_cp, do_cp
dw cmd_dm, do_dm
dw cmd_help, do_help
dw cmd_help2, do_help
dw cmd_in, do_in
dw cmd_out, do_out
dw cmd_run, do_run
dw cmd_wm, do_wm
dw cmd_sp, do_sp
dw cmd_buf, do_buf
dw 0 ; terminate command table
input_buffer: ; for execution from RAM the input buffer can follow on immediately