diff --git a/Makefile b/Makefile index bbae4b9..a9cff11 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,6 @@ METAL_ASM_SRCFILES := $(wildcard *.asm) METAL_RELFILES := $(METAL_ASM_SRCFILES:%.asm=$(BUILDDIR)/metal/%.rel) \ $(METAL_C_SRCFILES:%.c=$(BUILDDIR)/metal/%.rel) -MUSIC_FILE := updraft_flight - METAL_CC := sdcc METAL_AS := sdasz80 METAL_MAKEBIN := makebin diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f997c2 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Z80 UART monitor + +This ROM provides a small interactive monitor on the UART/CTC card at I/O base +`20`. Connect a terminal at **2400 baud, 8 data bits, no parity, 1 stop bit**. +Commands are case-insensitive and all numeric arguments are hexadecimal; an +optional `0x` prefix is accepted. + +| Command | Action | +| --- | --- | +| `D 8000` | Dump 40 bytes starting at address 8000 | +| `D 8000 20` | Dump 20 bytes starting at address 8000 | +| `W 8000 3E 41 D3 25 C9` | Write bytes starting at address 8000 | +| `I 20` | Read I/O port 20 | +| `O 20 57` | Write 57 to I/O port 20 | +| `G 8000` | Execute code at address 8000 | +| `HELP` | Show the command summary | + +`G` transfers control with a return address on the stack, so code ending in +`RET` returns to the monitor. Code that changes the stack pointer, interrupt +mode, or UART/CTC setup must restore it before returning. + +Build the ROM and programmer-format images with: + +```sh +make +``` + +The outputs are `build/metal/monitor.bin` (32 KiB ROM image) and +`build/metal/monitor.prog.bin` (compact programmer image). diff --git a/boot.asm b/boot.asm index a5801d6..dd77427 100644 --- a/boot.asm +++ b/boot.asm @@ -7,6 +7,7 @@ DELAY_LOOPS_PER_MS = 40 .globl _io_in .globl _enable_interrupts .globl _disable_interrupts + .globl _jump_to .globl s__DATA .globl l__DATA .globl s__INITIALIZER @@ -153,6 +154,11 @@ _io_in:: in a, (c) ret +; Execute code at HL. Because this routine is entered with CALL, a RET in the +; target returns to the monitor immediately after jump_to(). +_jump_to:: + jp (hl) + ; Approximate millisecond delay. ; Input: HL = delay in milliseconds. _delay_ms:: diff --git a/boot.h b/boot.h index 96c8c48..4336c9d 100644 --- a/boot.h +++ b/boot.h @@ -1,10 +1,13 @@ #ifndef __H_BOOT +#define __H_BOOT + #include void io_out(uint8_t a, uint8_t d); uint8_t io_in(uint8_t d); void delay_ms(uint16_t t); -void enable_interrupts(); -void disable_interrupts(); +void enable_interrupts(void); +void disable_interrupts(void); +void jump_to(uint16_t address); -#endif \ No newline at end of file +#endif diff --git a/circ_buf.h b/circ_buf.h index 4e362d7..9a1cf7e 100644 --- a/circ_buf.h +++ b/circ_buf.h @@ -1,7 +1,9 @@ #ifndef __H_CIRC_BUF +#define __H_CIRC_BUF + #include -#define BUFFER_SIZE 5 +#define BUFFER_SIZE 64 typedef struct { char data[BUFFER_SIZE]; @@ -16,4 +18,4 @@ uint8_t buffer_is_empty(volatile circular_buffer_t* b); uint8_t buffer_enqueue(volatile circular_buffer_t* b, char c); uint8_t buffer_dequeue(volatile circular_buffer_t* b, char* c); -#endif \ No newline at end of file +#endif diff --git a/main.c b/main.c index 86f66d5..0313a4d 100644 --- a/main.c +++ b/main.c @@ -2,108 +2,362 @@ #include "boot.h" #include "circ_buf.h" -#define CPUREG 0x00 -#define CPUREG_ROM_DISABLE 0x01 -#define CPUREG_BANK_SELECT 0x02 -#define CPUREG_BANK_ENABLE 0x04 -#define CPUREG_GPO_0 0x08 -#define CPUREG_GPO_1 0x10 -#define CPUREG_GPO_2 0x20 -#define CPUREG_GPO_3 0x40 -#define CPUREG_GPO_4 0x80 -#define CPUREG_GPO_MASK 0xf8 - #define UART_CTC_CARD_BASE 0x20 +#define CTC_BASE (UART_CTC_CARD_BASE + 0x00) +#define CTC_CHANNEL_0 (CTC_BASE + 0) +#define CTC_CHANNEL_3 (CTC_BASE + 3) +#define UART_BASE (UART_CTC_CARD_BASE + 0x04) +#define UART_CONTROL (UART_BASE + 0) +#define UART_DATA (UART_BASE + 1) -#define CTC_BASE (UART_CTC_CARD_BASE+0x00) -#define CTC_CHANNEL_0 (CTC_BASE+0) -#define CTC_CHANNEL_1 (CTC_BASE+1) -#define CTC_CHANNEL_2 (CTC_BASE+2) -#define CTC_CHANNEL_3 (CTC_BASE+3) +#define LINE_SIZE 80 +#define MAX_ARGS 40 +#define DEFAULT_DUMP_LENGTH 0x40 +#define MAX_DUMP_LENGTH 0x100 -#define UART_BASE (UART_CTC_CARD_BASE+0x04) -#define UART_CONTROL (UART_BASE+0) -#define UART_DATA (UART_BASE+1) - -volatile uint8_t v; volatile uint8_t uart_tx_busy; volatile circular_buffer_t rx_buf; volatile circular_buffer_t tx_buf; -void uart_puts(char * c){ - while(*c){ - // fill tx buffer till it is full - while(*c && buffer_enqueue(&tx_buf, *c)){ - c++; - } - // if uart not busy kick off - if(!uart_tx_busy){ - disable_interrupts(); - char cc; - if(buffer_dequeue(&tx_buf, &cc)){ - uart_tx_busy = 1; - io_out(UART_DATA, cc); - io_out(UART_CONTROL, 0b10110101); - } - enable_interrupts(); - } +static void uart_kick(void) +{ + char c; + + disable_interrupts(); + if (!uart_tx_busy && buffer_dequeue(&tx_buf, &c)) { + uart_tx_busy = 1; + io_out(UART_DATA, c); + io_out(UART_CONTROL, 0b10110101); } + enable_interrupts(); } -void uart_interrupt(){ +static void uart_putc(char c) +{ + uint8_t queued; + + do { + disable_interrupts(); + queued = buffer_enqueue(&tx_buf, c); + enable_interrupts(); + if (!queued) uart_kick(); + } while (!queued); + uart_kick(); +} + +static void uart_puts(const char *s) +{ + while (*s) uart_putc(*s++); +} + +static char uart_getc(void) +{ + char c; + uint8_t received; + + do { + disable_interrupts(); + received = buffer_dequeue(&rx_buf, &c); + enable_interrupts(); + } while (!received); + return c; +} + +static void uart_flush(void) +{ + while (uart_tx_busy || !buffer_is_empty(&tx_buf)) uart_kick(); +} + +void uart_interrupt(void) +{ uint8_t status = io_in(UART_CONTROL); - if((status&0x21)!=0){ - // RDRF or OVRN + if ((status & 0x21) != 0) { char c = io_in(UART_DATA); - if((status&0x01)!=0){ - // RDRF - buffer_enqueue(&rx_buf, c); - } + if ((status & 0x01) != 0) buffer_enqueue(&rx_buf, c); } - - if((status&0x02)!=0 && uart_tx_busy){ - //TXRE + if ((status & 0x02) != 0 && uart_tx_busy) { char c; - if(buffer_dequeue(&tx_buf, &c)){ + if (buffer_dequeue(&tx_buf, &c)) { io_out(UART_DATA, c); - }else{ + } else { io_out(UART_CONTROL, 0b10010101); uart_tx_busy = 0; } } } -void ctc_interrupt(){ +void ctc_interrupt(void) +{ } -void main(){ +static char hex_digit(uint8_t value) +{ + value &= 0x0f; + return value < 10 ? (char)('0' + value) : (char)('A' + value - 10); +} + +static void print_hex8(uint8_t value) +{ + uart_putc(hex_digit(value >> 4)); + uart_putc(hex_digit(value)); +} + +static void print_hex16(uint16_t value) +{ + print_hex8((uint8_t)(value >> 8)); + print_hex8((uint8_t)value); +} + +static uint8_t hex_value(char c, uint8_t *value) +{ + if (c >= '0' && c <= '9') { + *value = (uint8_t)(c - '0'); + return 1; + } + if (c >= 'a' && c <= 'f') { + *value = (uint8_t)(c - 'a' + 10); + return 1; + } + if (c >= 'A' && c <= 'F') { + *value = (uint8_t)(c - 'A' + 10); + return 1; + } + return 0; +} + +static uint8_t parse_hex(const char *text, uint16_t *result) +{ + uint16_t value = 0; + uint8_t digit; + uint8_t digits = 0; + + if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) text += 2; + while (*text) { + if (digits == 4 || !hex_value(*text++, &digit)) return 0; + value = (uint16_t)((value << 4) | digit); + ++digits; + } + if (digits == 0) return 0; + *result = value; + return 1; +} + +static uint8_t strings_equal(const char *a, const char *b) +{ + char ca; + char cb; + + do { + ca = *a++; + cb = *b++; + if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A'; + if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A'; + if (ca != cb) return 0; + } while (ca); + return 1; +} + +static uint8_t split_line(char *line, char **argv) +{ + uint8_t argc = 0; + + while (*line) { + while (*line == ' ' || *line == '\t') ++line; + if (!*line) break; + if (argc == MAX_ARGS) return argc; + argv[argc++] = line; + while (*line && *line != ' ' && *line != '\t') ++line; + if (*line) *line++ = 0; + } + return argc; +} + +static void print_help(void) +{ + uart_puts( + "Commands (hexadecimal numbers):\r\n" + " D|DUMP address [length] Dump memory (default 0040, max 0100)\r\n" + " W|WRITE address byte ... Write one or more memory bytes\r\n" + " I|IN port Read an I/O port\r\n" + " O|OUT port byte Write an I/O port\r\n" + " G|GO address Execute code; RET returns here\r\n" + " H|HELP|? Show this help\r\n"); +} + +static void command_dump(uint8_t argc, char **argv) +{ + uint16_t address; + uint16_t length = DEFAULT_DUMP_LENGTH; + uint16_t offset = 0; + + if (argc < 2 || argc > 3 || !parse_hex(argv[1], &address) || + (argc == 3 && !parse_hex(argv[2], &length))) { + uart_puts("Usage: D address [length]\r\n"); + return; + } + if (length > MAX_DUMP_LENGTH) { + uart_puts("Length must be 0100 or less\r\n"); + return; + } + while (offset < length) { + uint8_t column; + uint8_t row_length = (uint8_t)((length - offset) > 16 ? 16 : (length - offset)); + + print_hex16((uint16_t)(address + offset)); + uart_puts(": "); + for (column = 0; column < row_length; ++column) { + print_hex8(*(volatile uint8_t *)(address + offset + column)); + uart_putc(' '); + } + uart_puts("\r\n"); + offset += row_length; + } +} + +static void command_write(uint8_t argc, char **argv) +{ + uint16_t address; + uint16_t value; + uint8_t i; + + if (argc < 3 || !parse_hex(argv[1], &address)) { + uart_puts("Usage: W address byte [byte ...]\r\n"); + return; + } + for (i = 2; i < argc; ++i) { + if (!parse_hex(argv[i], &value) || value > 0xff) { + uart_puts("Bad byte: "); + uart_puts(argv[i]); + uart_puts("\r\n"); + return; + } + *(volatile uint8_t *)address++ = (uint8_t)value; + } + uart_puts("OK\r\n"); +} + +static void command_in(uint8_t argc, char **argv) +{ + uint16_t port; + + if (argc != 2 || !parse_hex(argv[1], &port) || port > 0xff) { + uart_puts("Usage: I port\r\n"); + return; + } + print_hex8((uint8_t)port); + uart_puts(": "); + print_hex8(io_in((uint8_t)port)); + uart_puts("\r\n"); +} + +static void command_out(uint8_t argc, char **argv) +{ + uint16_t port; + uint16_t value; + + if (argc != 3 || !parse_hex(argv[1], &port) || port > 0xff || + !parse_hex(argv[2], &value) || value > 0xff) { + uart_puts("Usage: O port byte\r\n"); + return; + } + io_out((uint8_t)port, (uint8_t)value); + uart_puts("OK\r\n"); +} + +static void command_go(uint8_t argc, char **argv) +{ + uint16_t address; + + if (argc != 2 || !parse_hex(argv[1], &address)) { + uart_puts("Usage: G address\r\n"); + return; + } + uart_puts("Executing at "); + print_hex16(address); + uart_puts("...\r\n"); + uart_flush(); + jump_to(address); + uart_puts("Returned to monitor\r\n"); +} + +static void execute_line(char *line) +{ + char *argv[MAX_ARGS]; + uint8_t argc = split_line(line, argv); + + if (!argc) return; + if (strings_equal(argv[0], "h") || strings_equal(argv[0], "help") || + strings_equal(argv[0], "?")) { + print_help(); + } else if (strings_equal(argv[0], "d") || strings_equal(argv[0], "dump")) { + command_dump(argc, argv); + } else if (strings_equal(argv[0], "w") || strings_equal(argv[0], "write")) { + command_write(argc, argv); + } else if (strings_equal(argv[0], "i") || strings_equal(argv[0], "in")) { + command_in(argc, argv); + } else if (strings_equal(argv[0], "o") || strings_equal(argv[0], "out")) { + command_out(argc, argv); + } else if (strings_equal(argv[0], "g") || strings_equal(argv[0], "go")) { + command_go(argc, argv); + } else { + uart_puts("Unknown command; type HELP\r\n"); + } +} + +void main(void) +{ + char line[LINE_SIZE]; + uint8_t length = 0; + uint8_t ignore_lf = 0; + buffer_init(&rx_buf); buffer_init(&tx_buf); - io_out(CTC_CHANNEL_0, 0x57); // no int, counter @ rising, value follows, soft reset, control word - io_out(CTC_CHANNEL_0, 13); // 38.462 kHz out; - - io_out(CTC_CHANNEL_3, 0b11001111); // int, counter @ falling, ext trig, value follows, soft reset, control word + io_out(CTC_CHANNEL_0, 0x57); + io_out(CTC_CHANNEL_0, 13); + io_out(CTC_CHANNEL_3, 0b11001111); io_out(CTC_CHANNEL_3, 1); - - io_out(CTC_CHANNEL_0, 0x00); // vector follows + io_out(CTC_CHANNEL_0, 0x00); io_out(CTC_CHANNEL_0, 0x40); - + uart_tx_busy = 0; - io_out(UART_CONTROL, 0b00000011); // master reset - io_out(UART_CONTROL, 0b10010101); // recv int, 8N1, /16 -> 2403.85 baud - + io_out(UART_CONTROL, 0b00000011); + io_out(UART_CONTROL, 0b10010101); enable_interrupts(); - uart_puts("Z80 monitor - v1\r\n"); - uart_puts("----------------\r\n"); + uart_puts("\r\nZ80 monitor - v1\r\nType HELP for commands.\r\n> "); - v = 0; - for(;;){ - char c[2] = {0, 0}; - while(buffer_dequeue(&rx_buf, c)){ - uart_puts(c); + for (;;) { + char c = uart_getc(); + + if (c == '\n' && ignore_lf) { + ignore_lf = 0; + continue; + } + ignore_lf = 0; + if (c == '\r' || c == '\n') { + ignore_lf = (c == '\r'); + line[length] = 0; + uart_puts("\r\n"); + execute_line(line); + length = 0; + uart_puts("> "); + } else if (c == '\b' || c == 0x7f) { + if (length) { + --length; + uart_puts("\b \b"); + } + } else if (c == 0x15) { + while (length) { + --length; + uart_puts("\b \b"); + } + } else if (c >= ' ' && c <= '~' && length < LINE_SIZE - 1) { + line[length++] = c; + uart_putc(c); + } else if (c >= ' ' && c <= '~') { + uart_putc('\a'); } } }