new memory and project layout

This commit is contained in:
Joppe Blondel
2026-10-01 12:17:47 +02:00
parent 9b1fcac8ee
commit d19ffc70a1
10 changed files with 397 additions and 272 deletions
+13 -18
View File
@@ -2,7 +2,7 @@ TARGET := monitor
BUILDDIR := build BUILDDIR := build
METAL_C_SRCFILES := $(wildcard *.c) METAL_C_SRCFILES := $(wildcard *.c)
METAL_ASM_SRCFILES := $(wildcard *.asm) METAL_ASM_SRCFILES := boot.asm
METAL_RELFILES := $(METAL_ASM_SRCFILES:%.asm=$(BUILDDIR)/metal/%.rel) \ METAL_RELFILES := $(METAL_ASM_SRCFILES:%.asm=$(BUILDDIR)/metal/%.rel) \
$(METAL_C_SRCFILES:%.c=$(BUILDDIR)/metal/%.rel) $(METAL_C_SRCFILES:%.c=$(BUILDDIR)/metal/%.rel)
@@ -14,12 +14,12 @@ METAL_CPPFLAGS := -I src -I $(BUILDDIR)/metal/generated --disable-warning 2
METAL_BIN_SIZE := 32768 METAL_BIN_SIZE := 32768
METAL_RAM_SIZE := 16384 METAL_RAM_SIZE := 16384
METAL_IMAGE_SIZE := 65536 METAL_IMAGE_SIZE := 65536
MONITOR_BASE := 0xC000 RESIDENT_BASE := 0xC000
MONITOR_ROM_OFFSET := 128 RESIDENT_ROM_OFFSET := 128
TINYBASIC_BASE := 0xD000 TINYBASIC_BASE := 0x1000
TINYBASIC_ROM_OFFSET := 4096 TINYBASIC_ROM_OFFSET := 4096
TINYBASIC_SRC := tinybasic_sdas.asm TINYBASIC_SRC := apps/tinybasic.asm
TINYBASIC_BUILD := $(BUILDDIR)/tinybasic TINYBASIC_BUILD := $(BUILDDIR)/tinybasic
.DEFAULT_GOAL := metal .DEFAULT_GOAL := metal
@@ -36,9 +36,9 @@ metal: $(BUILDDIR)/metal/$(TARGET).ihx $(BUILDDIR)/metal/$(TARGET).bin $(BUILDDI
END { \ END { \
printf "\nMemory usage:\n"; \ printf "\nMemory usage:\n"; \
printf " ROM storage: %5u / %5u bytes (%5.1f%%)\n", stored, rom_capacity, 100 * stored / rom_capacity; \ printf " ROM storage: %5u / %5u bytes (%5.1f%%)\n", stored, rom_capacity, 100 * stored / rom_capacity; \
printf " Monitor linked: %5u bytes (%5u code/vector + %5u static data)\n", code + data, code, data; \ printf " Resident BIOS+monitor: %5u bytes (%5u code/vector + %5u static data)\n", code + data, code, data; \
}' $(BUILDDIR)/metal/$(TARGET).map }' $(BUILDDIR)/metal/$(TARGET).map
@printf " Tiny BASIC: %5u bytes at D000\n" $$(wc -c < $(TINYBASIC_BUILD)/tinybasic.bin) @printf " Tiny BASIC: %5u ROM bytes at 1000\n" $$(wc -c < $(TINYBASIC_BUILD)/tinybasic.bin)
$(BUILDDIR)/metal/$(TARGET).ihx: $(METAL_RELFILES) $(BUILDDIR)/metal/$(TARGET).ihx: $(METAL_RELFILES)
@mkdir -p $(@D) @mkdir -p $(@D)
@@ -48,11 +48,12 @@ $(BUILDDIR)/metal/$(TARGET).flat.bin: $(BUILDDIR)/metal/$(TARGET).ihx
$(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@ $(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@
$(BUILDDIR)/metal/$(TARGET).prog.bin: $(BUILDDIR)/metal/$(TARGET).flat.bin $(TINYBASIC_BUILD)/tinybasic.bin $(BUILDDIR)/metal/$(TARGET).prog.bin: $(BUILDDIR)/metal/$(TARGET).flat.bin $(TINYBASIC_BUILD)/tinybasic.bin
@payload_end=$$(awk '$$1 == "DEF" && $$2 == "monitor_image_end" { print $$3 }' $(BUILDDIR)/metal/$(TARGET).noi); \ @payload_end=$$(awk '$$1 == "DEF" && $$2 == "resident_image_end" { print $$3 }' $(BUILDDIR)/metal/$(TARGET).noi); \
payload_size=$$((payload_end - $(MONITOR_BASE))); \ payload_size=$$((payload_end - $(RESIDENT_BASE))); \
test $$payload_size -gt 0; \ test $$payload_size -gt 0; \
test $$payload_size -le $$(( $(TINYBASIC_ROM_OFFSET) - $(RESIDENT_ROM_OFFSET) )); \
dd if=$< of=$@ bs=1 count=$(TINYBASIC_ROM_OFFSET) status=none; \ dd if=$< of=$@ bs=1 count=$(TINYBASIC_ROM_OFFSET) status=none; \
dd if=$< of=$@ bs=1 skip=$$(( $(MONITOR_BASE) )) seek=$(MONITOR_ROM_OFFSET) count=$$payload_size conv=notrunc status=none; \ dd if=$< of=$@ bs=1 skip=$$(( $(RESIDENT_BASE) )) seek=$(RESIDENT_ROM_OFFSET) count=$$payload_size conv=notrunc status=none; \
dd if=$(TINYBASIC_BUILD)/tinybasic.bin of=$@ bs=1 seek=$(TINYBASIC_ROM_OFFSET) conv=notrunc status=none dd if=$(TINYBASIC_BUILD)/tinybasic.bin of=$@ bs=1 seek=$(TINYBASIC_ROM_OFFSET) conv=notrunc status=none
$(BUILDDIR)/metal/$(TARGET).bin: $(BUILDDIR)/metal/$(TARGET).flat.bin $(BUILDDIR)/metal/$(TARGET).prog.bin $(BUILDDIR)/metal/$(TARGET).bin: $(BUILDDIR)/metal/$(TARGET).flat.bin $(BUILDDIR)/metal/$(TARGET).prog.bin
@@ -65,13 +66,11 @@ $(BUILDDIR)/metal/%.rel: %.c
$(BUILDDIR)/metal/%.rel: %.asm $(BUILDDIR)/metal/%.rel: %.asm
@mkdir -p $(@D) @mkdir -p $(@D)
$(METAL_AS) -I$(BUILDDIR)/metal/generated -plosgff -o $@ $< $(METAL_AS) -Iinclude -I$(BUILDDIR)/metal/generated -plosgff -o $@ $<
$(BUILDDIR)/metal/boot.rel: $(BUILDDIR)/metal/generated/tinybasic.inc
$(TINYBASIC_BUILD)/tinybasic.rel: $(TINYBASIC_SRC) $(TINYBASIC_BUILD)/tinybasic.rel: $(TINYBASIC_SRC)
@mkdir -p $(@D) @mkdir -p $(@D)
$(METAL_AS) -plosgff -o $@ $< $(METAL_AS) -Iinclude -plosgff -o $@ $<
$(TINYBASIC_BUILD)/tinybasic.ihx: $(TINYBASIC_BUILD)/tinybasic.rel $(TINYBASIC_BUILD)/tinybasic.ihx: $(TINYBASIC_BUILD)/tinybasic.rel
sdldz80 -n -i -m -j $(basename $@) $< sdldz80 -n -i -m -j $(basename $@) $<
@@ -84,10 +83,6 @@ $(TINYBASIC_BUILD)/tinybasic.bin: $(TINYBASIC_BUILD)/tinybasic.flat.bin
test $$tiny_size -gt 0; \ test $$tiny_size -gt 0; \
dd if=$< of=$@ bs=1 skip=$$(( $(TINYBASIC_BASE) )) count=$$tiny_size status=none dd if=$< of=$@ bs=1 skip=$$(( $(TINYBASIC_BASE) )) count=$$tiny_size status=none
$(BUILDDIR)/metal/generated/tinybasic.inc: $(TINYBASIC_BUILD)/tinybasic.ihx
@mkdir -p $(@D)
@awk '$$1 == "TINYBASIC0" { printf "TINYBASIC_SIZE = 0x%04X\n", $$5 }' $(TINYBASIC_BUILD)/tinybasic.map > $@
clean: clean:
rm -rf $(BUILDDIR) rm -rf $(BUILDDIR)
+60 -34
View File
@@ -1,12 +1,13 @@
# Z80 UART monitor # Z80 UART monitor
This ROM provides a small interactive monitor and a bundled Z80 Tiny BASIC This project provides a resident BIOS, an interactive monitor, and ROM-resident
interpreter on the UART/CTC card at I/O base `20`. At reset, a small ROM applications for a Z80 system with a UART/CTC card at I/O base `20`.
bootstrap copies both programs into the top 16 KiB of RAM and starts the
monitor. Only occupied code/vector bytes are stored and copied; uninitialized At reset, a small ROM bootstrap copies only the BIOS and monitor into the top
data is cleared directly in high RAM. The low ROM can subsequently be disabled 16 KiB of RAM at `C000`. The BIOS initializes interrupt mode 2 and the UART,
without taking away the monitor, Tiny BASIC, monitor data, or interrupt table. then starts the monitor. Applications remain in ROM and execute in place while
No runtime RAM below `C000` is used. calling the stable BIOS jump table in high RAM. Tiny BASIC is the first such
application, linked at ROM address `1000`.
Connect a terminal at **2400 baud, 8 data bits, no parity, 1 stop bit**. 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 Commands are case-insensitive and all numeric arguments are hexadecimal; an
@@ -20,8 +21,8 @@ optional `0x` prefix is accepted.
| `I 20` | Read I/O port 20 | | `I 20` | Read I/O port 20 |
| `O 20 57` | Write 57 to I/O port 20 | | `O 20 57` | Write 57 to I/O port 20 |
| `G 8000` | Execute code at address 8000 | | `G 8000` | Execute code at address 8000 |
| `G D000` | Start the bundled Z80 Tiny BASIC interpreter | | `G 1000` | Start the ROM-resident Z80 Tiny BASIC interpreter |
| `B` or `BASIC` | Shortcut for `G D000` | | `B` or `BASIC` | Shortcut for `G 1000` |
| `HELP` | Show the command summary | | `HELP` | Show the command summary |
`G` transfers control with a return address on the stack, so code ending in `G` transfers control with a return address on the stack, so code ending in
@@ -43,45 +44,70 @@ The outputs are:
The current compact image is 6,157 bytes. Its ROM layout is: The current compact image is 6,157 bytes. Its ROM layout is:
- `0000-007F`: reset/copy bootstrap - `0000-007F`: reset/copy bootstrap
- `0080-0F6E`: monitor image copied to RAM at `C000-CEEE` - `0080-0F9D`: packed BIOS+monitor image copied to RAM at `C000-CF1D`
- `0F6F-0FFF`: unused space reserved for monitor growth - `0F9E-0FFF`: unused space reserved for the resident image to grow
- `1000-180C`: Tiny BASIC image copied to RAM at `D000-D80C` - `1000-180C`: Tiny BASIC, executed directly from ROM
- `180D-7FFF`: unused space available for additional ROM content - `180D-7FFF`: unused space available for additional ROM content
The monitor executable starts at `C080`; its IM 2 vector table is at `C040`. The resident image starts at `C000`, its IM 2 vector table is at `C040`, and
The bootstrap obtains both copy lengths from generated/linker symbols, so the linked code starts at `C080`. The bootstrap obtains the resident copy length
copied sizes track changes to the programs automatically. from a linker symbol, so it tracks BIOS and monitor changes automatically.
The resulting high-RAM layout is: The resulting RAM layout is:
- `C000-CEEE`: monitor vectors, services, and code - `8000-AFFF`: Tiny BASIC workspace and BASIC program text
- `CEEF-CFFF`: free - `B000-B077`: Tiny BASIC variables and input buffer
- `D000-D80C`: Tiny BASIC code - `B078-BFFF`: Tiny BASIC stack space
- `D80D-DFFF`: free - `C000-CF1D`: resident BIOS, vectors, services, and monitor
- `E000-E7FF`: Tiny BASIC workspace and BASIC program text - `CF1E-EFFF`: free space for resident-image growth
- `E800-E877`: Tiny BASIC variables and input buffer - `F000-FFFF`: BIOS globals, UART buffers, and monitor stack
- `E878-EFFF`: Tiny BASIC stack space
- `F000-FFFF`: monitor globals and monitor stack The BIOS and monitor continue to work if low ROM is disabled. ROM-resident
applications naturally require ROM to remain mapped while they execute.
## BIOS interface
Assembly applications include `include/bios.inc` and call these fixed entries:
| Address | Service | Interface |
| --- | --- | --- |
| `C050` | `BIOS_PUTC` | Write character in `A` |
| `C053` | `BIOS_TRY_GETC` | Return character in `A`, or zero |
| `C056` | `BIOS_GETC` | Wait for and return a character in `A` |
| `C059` | `BIOS_IO_OUT` | Write `L` to port `A` |
| `C05C` | `BIOS_IO_IN` | Read port `A`, returning the value in `A` |
| `C05F` | `BIOS_DELAY_MS` | Delay approximately `HL` milliseconds |
| `C062` | `BIOS_MONITOR` | Warm-start the monitor; does not return |
The service wrappers preserve working registers except for documented return
values. The monitor uses the corresponding C API declared in `bios.h`.
Project organization:
- `boot.asm`: ROM bootstrap, high vectors, BIOS jump table, and assembly glue
- `bios.c` / `bios.h`: UART, buffers, interrupts, and resident BIOS API
- `monitor.c`: command interpreter implemented on the BIOS API
- `apps/tinybasic.asm`: first ROM-resident application
- `apps/README.md`: ROM application memory and integration contract
- `include/bios.inc`: stable assembly interface for ROM applications
## Tiny BASIC ## Tiny BASIC
The original GNU-z80asm listing is retained as `tinybasic/tinybasic.asm`. Its Tiny BASIC is built from `apps/tinybasic.asm`. The port replaces fixed low-page
`sdasz80` port is `tinybasic/tinybasic_sdas.asm` and is built automatically. `RST` calls with relocatable calls and uses a marker-based command table so it
The port replaces fixed low-page `RST` calls with relocatable calls and uses a can be linked at a nonzero ROM address.
marker-based command table so addresses in the `D000` page work correctly.
At reset the bootstrap copies Tiny BASIC from ROM offset `1000` to `D000`. Tiny BASIC is linked at and runs directly from ROM address `1000`. Start it from
Start it from the monitor with: the monitor with:
```text ```text
B B
``` ```
`BASIC` and `G D000` are equivalent alternatives. `BASIC` and `G 1000` are equivalent alternatives.
Tiny BASIC calls the monitor ABI at `C050` for output and `C053` for Tiny BASIC calls the BIOS at `C050` for output and `C053` for non-blocking
non-blocking input. The assembly ABI wrappers preserve the working registers, input. Tiny BASIC keeps interrupts enabled so it can share the BIOS-configured
and Tiny BASIC keeps interrupts enabled so it can share the monitor's configured
interrupt-driven UART. interrupt-driven UART.
Line editing accepts either `BS` (`08`) or `DEL` (`7F`) for backspace and emits Line editing accepts either `BS` (`08`) or `DEL` (`7F`) for backspace and emits
+26
View File
@@ -0,0 +1,26 @@
# ROM applications
Applications in this directory execute directly from ROM; the reset bootstrap
does not copy them. Each application must have its own non-overlapping `ORG`
below `8000` and should include `bios.inc` from the project's `include`
directory instead of accessing the UART hardware directly.
The current allocation is:
- `1000-180C`: Tiny BASIC
- `180D-7FFF`: available for more applications (`1900` is the next convenient
aligned origin)
The resident BIOS and monitor own high RAM at `C000-FFFF`. Tiny BASIC owns
`8000-BFFF` for its workspace and stack. A future application may reuse Tiny
BASIC's lower-RAM allocation because ROM applications run one at a time, but it
must leave `C000-FFFF` untouched.
Applications using the interrupt-driven console must leave interrupt mode 2,
the `I` register, and the BIOS UART/CTC configuration intact. They may return
normally if they preserve the caller's stack, or jump to `BIOS_MONITOR` for a
non-returning monitor warm start after replacing the stack pointer.
To add an application, add its assembler target and ROM offset to the Makefile,
overlay its binary in the `monitor.prog.bin` recipe, and add a monitor command
or use `G address` to launch it.
+15 -16
View File
@@ -42,13 +42,11 @@
; Adapted to work with GNU z80asm ; Adapted to work with GNU z80asm
; Added interrupt handling ; Added interrupt handling
; Notes for this monitor port: ; Notes for this monitor port:
; Converted to sdasz80 syntax and relocated to D000. ; Converted to sdasz80 syntax and linked as a ROM application at 1000.
; Fixed-page RST calls were converted to local CALLs. ; Fixed-page RST calls were converted to local CALLs.
; UART access uses the monitor ABI at C050/C053. ; UART access uses the resident BIOS ABI at C050/C053.
; Monitor ABI jump table. These entry points remain stable across monitor builds. .include "bios.inc"
MONITOR_PUTC = 0xC050
MONITOR_GETC = 0xC053
SPACE = 0x020 ; Space SPACE = 0x020 ; Space
@@ -67,9 +65,10 @@ CTRLU = 0x015 ; Control "U"
ESC = 0x01B ; Escape ESC = 0x01B ; Escape
DEL = 0x07F ; Delete DEL = 0x07F ; Delete
; Adjust to fit RAM mapping ; Tiny BASIC owns lower RAM 8000-BFFF. The resident BIOS and monitor own
STACK = 0xEFFF ; Tiny BASIC stack grows down below monitor data ; C000-FFFF, so BASIC cannot overwrite them even when its stack is deep.
OCSW = 0xE000 ;SWITCH FOR OUTPUT STACK = 0xBFFF ; Tiny BASIC stack grows down through B078-BFFF
OCSW = 0x8000 ;SWITCH FOR OUTPUT
CURRNT = OCSW+1 ;POINTS FOR OUTPUT CURRNT = OCSW+1 ;POINTS FOR OUTPUT
STKGOS = OCSW+3 ;SAVES SP IN 'GOSUB' STKGOS = OCSW+3 ;SAVES SP IN 'GOSUB'
VARNXT = OCSW+5 ;TEMP STORAGE VARNXT = OCSW+5 ;TEMP STORAGE
@@ -83,11 +82,11 @@ RANPNT = OCSW+19 ;RANDOM NUMBER POINTER
TXTUNF = OCSW+21 ;->UNFILLED TEXT AREA TXTUNF = OCSW+21 ;->UNFILLED TEXT AREA
TXTBGN = OCSW+23 ;TEXT SAVE AREA BEGINS TXTBGN = OCSW+23 ;TEXT SAVE AREA BEGINS
TXTEND = 0xE800 ;TEXT SAVE AREA ENDS TXTEND = 0xB000 ;TEXT SAVE AREA ENDS
VARBGN = 0xE800 ;VARIABLE @(0) VARBGN = 0xB000 ;VARIABLE @(0)
BUFFER = 0xE837 ;55-byte variable area ends here BUFFER = 0xB037 ;55-byte variable area ends here
BUFEND = 0xE877 ;64-byte input buffer ends here BUFEND = 0xB077 ;64-byte input buffer ends here
STKLMT = 0xE878 ;LOWEST ALLOWED STACK ADDRESS STKLMT = 0xB078 ;LOWEST ALLOWED STACK ADDRESS
;************************************************************* ;*************************************************************
@@ -103,7 +102,7 @@ STKLMT = 0xE878 ;LOWEST ALLOWED STACK ADDRESS
;************************************************************* ;*************************************************************
.area TINYBASIC (ABS) .area TINYBASIC (ABS)
.org 0xD000 .org 0x1000
main: main:
START: START:
LD SP,#STACK ;*** COLD START *** LD SP,#STACK ;*** COLD START ***
@@ -1601,7 +1600,7 @@ OUTC:
OUTC2: OUTC2:
CALL TX_RDY ;SEE IF TRANSMIT IS AVAILABLE CALL TX_RDY ;SEE IF TRANSMIT IS AVAILABLE
POP AF ;RESTORE THE REGISTER POP AF ;RESTORE THE REGISTER
CALL MONITOR_PUTC ;SEND THE BYTE THROUGH MONITOR UART CALL BIOS_PUTC ;SEND THE BYTE THROUGH BIOS UART
CP #CR CP #CR
RET NZ RET NZ
LD A,#LF LD A,#LF
@@ -1826,7 +1825,7 @@ RX_RDY:
; However, if a character is not available, it returns with the ; However, if a character is not available, it returns with the
; Z-flag set. ; Z-flag set.
CALL MONITOR_GETC CALL BIOS_TRY_GETC
OR A OR A
RET RET
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
+113
View File
@@ -0,0 +1,113 @@
#include <stdint.h>
#include "bios.h"
#include "circ_buf.h"
#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)
static volatile uint8_t uart_tx_busy;
static volatile circular_buffer_t rx_buf;
static volatile circular_buffer_t tx_buf;
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 bios_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();
}
void bios_puts(const char *s)
{
while (*s) bios_putc(*s++);
}
uint8_t bios_try_getc(void)
{
char c;
uint8_t received;
disable_interrupts();
received = buffer_dequeue(&rx_buf, &c);
enable_interrupts();
return received ? (uint8_t)c : 0;
}
char bios_getc(void)
{
uint8_t c;
do {
c = bios_try_getc();
} while (!c);
return (char)c;
}
void bios_flush(void)
{
while (uart_tx_busy || !buffer_is_empty(&tx_buf)) uart_kick();
}
void bios_init(void)
{
buffer_init(&rx_buf);
buffer_init(&tx_buf);
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);
io_out(CTC_CHANNEL_0, 0x40);
uart_tx_busy = 0;
io_out(UART_CONTROL, 0b00000011);
io_out(UART_CONTROL, 0b10010101);
enable_interrupts();
}
void uart_interrupt(void)
{
uint8_t status = io_in(UART_CONTROL);
if ((status & 0x21) != 0) {
char c = io_in(UART_DATA);
if ((status & 0x01) != 0) buffer_enqueue(&rx_buf, c);
}
if ((status & 0x02) != 0 && uart_tx_busy) {
char c;
if (buffer_dequeue(&tx_buf, &c)) {
io_out(UART_DATA, c);
} else {
io_out(UART_CONTROL, 0b10010101);
uart_tx_busy = 0;
}
}
}
void ctc_interrupt(void)
{
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef BIOS_H
#define BIOS_H
#include <stdint.h>
/* Resident BIOS API used by the monitor. */
void bios_init(void);
void bios_putc(char c);
void bios_puts(const char *s);
char bios_getc(void);
uint8_t bios_try_getc(void);
void bios_flush(void);
/* Low-level routines implemented by the resident assembly BIOS. */
void io_out(uint8_t port, uint8_t value);
uint8_t io_in(uint8_t port);
void delay_ms(uint16_t milliseconds);
void enable_interrupts(void);
void disable_interrupts(void);
void jump_to(uint16_t address);
#endif
+104 -38
View File
@@ -1,11 +1,9 @@
STACK_TOP = 0xFFFF STACK_TOP = 0xFFFF
DELAY_LOOPS_PER_MS = 40 DELAY_LOOPS_PER_MS = 40
MONITOR_BASE = 0xC000 RESIDENT_BASE = 0xC000
MONITOR_ROM_SOURCE = 0x0080 RESIDENT_ROM_SOURCE = 0x0080
TINYBASIC_BASE = 0xD000
TINYBASIC_ROM_SOURCE = 0x1000
.include "tinybasic.inc" .include "bios.inc"
.globl _main .globl _main
.globl _delay_ms .globl _delay_ms
@@ -14,9 +12,11 @@ TINYBASIC_ROM_SOURCE = 0x1000
.globl _enable_interrupts .globl _enable_interrupts
.globl _disable_interrupts .globl _disable_interrupts
.globl _jump_to .globl _jump_to
.globl _monitor_uart_putc .globl _bios_putc
.globl _monitor_uart_getc .globl _bios_try_getc
.globl monitor_image_end .globl _bios_getc
.globl _bios_init
.globl resident_image_end
.globl s__DATA .globl s__DATA
.globl l__DATA .globl l__DATA
.globl s__INITIALIZER .globl s__INITIALIZER
@@ -31,50 +31,56 @@ TINYBASIC_ROM_SOURCE = 0x1000
boot_copy: boot_copy:
di di
ld sp, #STACK_TOP ld sp, #STACK_TOP
ld hl, #MONITOR_ROM_SOURCE ld hl, #RESIDENT_ROM_SOURCE
ld de, #MONITOR_BASE ld de, #RESIDENT_BASE
ld bc, #(monitor_image_end - MONITOR_BASE) ld bc, #(resident_image_end - RESIDENT_BASE)
ldir
ld hl, #TINYBASIC_ROM_SOURCE
ld de, #TINYBASIC_BASE
ld bc, #TINYBASIC_SIZE
ldir ldir
jp init jp init
.area _ITABLE (ABS) .area _ITABLE (ABS)
; The executable vector page is part of the image copied to high RAM. ; The executable vector page is part of the image copied to high RAM.
.org MONITOR_BASE .org RESIDENT_BASE
jp init jp init
.org MONITOR_BASE + 0x08 .org RESIDENT_BASE + 0x08
reti reti
.org MONITOR_BASE + 0x10 .org RESIDENT_BASE + 0x10
reti reti
.org MONITOR_BASE + 0x18 .org RESIDENT_BASE + 0x18
reti reti
.org MONITOR_BASE + 0x20 .org RESIDENT_BASE + 0x20
reti reti
.org MONITOR_BASE + 0x28 .org RESIDENT_BASE + 0x28
reti reti
.org MONITOR_BASE + 0x30 .org RESIDENT_BASE + 0x30
reti reti
.org MONITOR_BASE + 0x38 .org RESIDENT_BASE + 0x38
reti reti
; IM 2 table: the CTC supplies vectors 40, 42, 44, and 46. ; IM 2 table: the CTC supplies vectors 40, 42, 44, and 46.
.org MONITOR_BASE + 0x40 .org RESIDENT_BASE + 0x40
.dw ctc_isr ; CTC channel 0: vector 0x40 .dw ctc_isr ; CTC channel 0: vector 0x40
.dw ctc_isr ; CTC channel 1: vector 0x42 .dw ctc_isr ; CTC channel 1: vector 0x42
.dw ctc_isr ; CTC channel 2: vector 0x44 .dw ctc_isr ; CTC channel 2: vector 0x44
.dw uart_isr ; CTC channel 3: vector 0x46 .dw uart_isr ; CTC channel 3: vector 0x46
; Stable entry points for programs living alongside the monitor. ; Stable BIOS jump table for ROM-resident applications.
; C050: write the character in A. C053: return a character in A, or 0. ; C050 putc(A), C053 try_getc(), C056 getc(), C059 io_out(A,L),
.org MONITOR_BASE + 0x50 ; C05C io_in(A), C05F delay_ms(HL), C062 monitor warm start.
jp monitor_service_putc .org BIOS_PUTC
.org MONITOR_BASE + 0x53 jp bios_service_putc
jp monitor_service_getc .org BIOS_TRY_GETC
jp bios_service_try_getc
.org BIOS_GETC
jp bios_service_getc
.org BIOS_IO_OUT
jp bios_service_io_out
.org BIOS_IO_IN
jp bios_service_io_in
.org BIOS_DELAY_MS
jp bios_service_delay_ms
.org BIOS_MONITOR
jp init
.area _HOME .area _HOME
init: init:
@@ -87,6 +93,7 @@ init:
ld a, #0xC0 ld a, #0xC0
ld i, a ld i, a
im 2 im 2
call _bios_init
call _main call _main
hang: hang:
@@ -124,20 +131,19 @@ clear_loop:
gsinit_done: gsinit_done:
.area _GSFINAL .area _GSFINAL
ret ret
monitor_image_end: resident_image_end:
.area _CODE .area _CODE
; Monitor ABI services preserve all working registers. PUTC also preserves AF; ; BIOS ABI services preserve registers except documented return values.
; GETC returns the received byte (or zero) in A. bios_service_putc:
monitor_service_putc:
push af push af
push bc push bc
push de push de
push hl push hl
push ix push ix
push iy push iy
call _monitor_uart_putc call _bios_putc
pop iy pop iy
pop ix pop ix
pop hl pop hl
@@ -146,13 +152,13 @@ monitor_service_putc:
pop af pop af
ret ret
monitor_service_getc: bios_service_try_getc:
push bc push bc
push de push de
push hl push hl
push ix push ix
push iy push iy
call _monitor_uart_getc call _bios_try_getc
pop iy pop iy
pop ix pop ix
pop hl pop hl
@@ -160,6 +166,66 @@ monitor_service_getc:
pop bc pop bc
ret ret
bios_service_getc:
push bc
push de
push hl
push ix
push iy
call _bios_getc
pop iy
pop ix
pop hl
pop de
pop bc
ret
bios_service_io_out:
push af
push bc
push de
push hl
push ix
push iy
call _io_out
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ret
bios_service_io_in:
push bc
push de
push hl
push ix
push iy
call _io_in
pop iy
pop ix
pop hl
pop de
pop bc
ret
bios_service_delay_ms:
push af
push bc
push de
push hl
push ix
push iy
call _delay_ms
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ret
.macro MAKE_ISR entry, handler .macro MAKE_ISR entry, handler
.globl handler .globl handler
entry: entry:
-13
View File
@@ -1,13 +0,0 @@
#ifndef __H_BOOT
#define __H_BOOT
#include <stdint.h>
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);
void disable_interrupts(void);
void jump_to(uint16_t address);
#endif
+8
View File
@@ -0,0 +1,8 @@
; Stable assembly ABI exported by the resident BIOS at C000.
BIOS_PUTC = 0xC050 ; A = character; preserves all registers
BIOS_TRY_GETC = 0xC053 ; returns A = character or 0; preserves BC..IY
BIOS_GETC = 0xC056 ; blocking; returns character in A
BIOS_IO_OUT = 0xC059 ; A = port, L = value; preserves all registers
BIOS_IO_IN = 0xC05C ; A = port; returns value in A
BIOS_DELAY_MS = 0xC05F ; HL = milliseconds; preserves all registers
BIOS_MONITOR = 0xC062 ; warm-start the monitor; does not return
+36 -153
View File
@@ -1,113 +1,11 @@
#include <stdint.h> #include <stdint.h>
#include "boot.h" #include "bios.h"
#include "circ_buf.h"
#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 LINE_SIZE 80 #define LINE_SIZE 80
#define MAX_ARGS 40 #define MAX_ARGS 40
#define DEFAULT_DUMP_LENGTH 0x40 #define DEFAULT_DUMP_LENGTH 0x40
#define MAX_DUMP_LENGTH 0x100 #define MAX_DUMP_LENGTH 0x100
volatile uint8_t uart_tx_busy;
volatile circular_buffer_t rx_buf;
volatile circular_buffer_t tx_buf;
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();
}
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();
}
/* Stable monitor ABI implementations, reached through the jump table at C050. */
void monitor_uart_putc(char c)
{
uart_putc(c);
}
uint8_t monitor_uart_getc(void)
{
char c;
uint8_t received;
disable_interrupts();
received = buffer_dequeue(&rx_buf, &c);
enable_interrupts();
return received ? (uint8_t)c : 0;
}
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) {
char c = io_in(UART_DATA);
if ((status & 0x01) != 0) buffer_enqueue(&rx_buf, c);
}
if ((status & 0x02) != 0 && uart_tx_busy) {
char c;
if (buffer_dequeue(&tx_buf, &c)) {
io_out(UART_DATA, c);
} else {
io_out(UART_CONTROL, 0b10010101);
uart_tx_busy = 0;
}
}
}
void ctc_interrupt(void)
{
}
static char hex_digit(uint8_t value) static char hex_digit(uint8_t value)
{ {
value &= 0x0f; value &= 0x0f;
@@ -116,8 +14,8 @@ static char hex_digit(uint8_t value)
static void print_hex8(uint8_t value) static void print_hex8(uint8_t value)
{ {
uart_putc(hex_digit(value >> 4)); bios_putc(hex_digit(value >> 4));
uart_putc(hex_digit(value)); bios_putc(hex_digit(value));
} }
static void print_hex16(uint16_t value) static void print_hex16(uint16_t value)
@@ -192,14 +90,14 @@ static uint8_t split_line(char *line, char **argv)
static void print_help(void) static void print_help(void)
{ {
uart_puts( bios_puts(
"Commands (hexadecimal numbers):\r\n" "Commands (hexadecimal numbers):\r\n"
" D|DUMP address [length] Dump memory (default 0040, max 0100)\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" " W|WRITE address byte ... Write one or more memory bytes\r\n"
" I|IN port Read an I/O port\r\n" " I|IN port Read an I/O port\r\n"
" O|OUT port byte Write 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" " G|GO address Execute code; RET returns here\r\n"
" B|BASIC Start Tiny BASIC at D000\r\n" " B|BASIC Start ROM Tiny BASIC at 1000\r\n"
" H|HELP|? Show this help\r\n"); " H|HELP|? Show this help\r\n");
} }
@@ -211,11 +109,11 @@ static void command_dump(uint8_t argc, char **argv)
if (argc < 2 || argc > 3 || !parse_hex(argv[1], &address) || if (argc < 2 || argc > 3 || !parse_hex(argv[1], &address) ||
(argc == 3 && !parse_hex(argv[2], &length))) { (argc == 3 && !parse_hex(argv[2], &length))) {
uart_puts("Usage: D address [length]\r\n"); bios_puts("Usage: D address [length]\r\n");
return; return;
} }
if (length > MAX_DUMP_LENGTH) { if (length > MAX_DUMP_LENGTH) {
uart_puts("Length must be 0100 or less\r\n"); bios_puts("Length must be 0100 or less\r\n");
return; return;
} }
while (offset < length) { while (offset < length) {
@@ -223,12 +121,12 @@ static void command_dump(uint8_t argc, char **argv)
uint8_t row_length = (uint8_t)((length - offset) > 16 ? 16 : (length - offset)); uint8_t row_length = (uint8_t)((length - offset) > 16 ? 16 : (length - offset));
print_hex16((uint16_t)(address + offset)); print_hex16((uint16_t)(address + offset));
uart_puts(": "); bios_puts(": ");
for (column = 0; column < row_length; ++column) { for (column = 0; column < row_length; ++column) {
print_hex8(*(volatile uint8_t *)(address + offset + column)); print_hex8(*(volatile uint8_t *)(address + offset + column));
uart_putc(' '); bios_putc(' ');
} }
uart_puts("\r\n"); bios_puts("\r\n");
offset += row_length; offset += row_length;
} }
} }
@@ -240,19 +138,19 @@ static void command_write(uint8_t argc, char **argv)
uint8_t i; uint8_t i;
if (argc < 3 || !parse_hex(argv[1], &address)) { if (argc < 3 || !parse_hex(argv[1], &address)) {
uart_puts("Usage: W address byte [byte ...]\r\n"); bios_puts("Usage: W address byte [byte ...]\r\n");
return; return;
} }
for (i = 2; i < argc; ++i) { for (i = 2; i < argc; ++i) {
if (!parse_hex(argv[i], &value) || value > 0xff) { if (!parse_hex(argv[i], &value) || value > 0xff) {
uart_puts("Bad byte: "); bios_puts("Bad byte: ");
uart_puts(argv[i]); bios_puts(argv[i]);
uart_puts("\r\n"); bios_puts("\r\n");
return; return;
} }
*(volatile uint8_t *)address++ = (uint8_t)value; *(volatile uint8_t *)address++ = (uint8_t)value;
} }
uart_puts("OK\r\n"); bios_puts("OK\r\n");
} }
static void command_in(uint8_t argc, char **argv) static void command_in(uint8_t argc, char **argv)
@@ -260,13 +158,13 @@ static void command_in(uint8_t argc, char **argv)
uint16_t port; uint16_t port;
if (argc != 2 || !parse_hex(argv[1], &port) || port > 0xff) { if (argc != 2 || !parse_hex(argv[1], &port) || port > 0xff) {
uart_puts("Usage: I port\r\n"); bios_puts("Usage: I port\r\n");
return; return;
} }
print_hex8((uint8_t)port); print_hex8((uint8_t)port);
uart_puts(": "); bios_puts(": ");
print_hex8(io_in((uint8_t)port)); print_hex8(io_in((uint8_t)port));
uart_puts("\r\n"); bios_puts("\r\n");
} }
static void command_out(uint8_t argc, char **argv) static void command_out(uint8_t argc, char **argv)
@@ -276,21 +174,21 @@ static void command_out(uint8_t argc, char **argv)
if (argc != 3 || !parse_hex(argv[1], &port) || port > 0xff || if (argc != 3 || !parse_hex(argv[1], &port) || port > 0xff ||
!parse_hex(argv[2], &value) || value > 0xff) { !parse_hex(argv[2], &value) || value > 0xff) {
uart_puts("Usage: O port byte\r\n"); bios_puts("Usage: O port byte\r\n");
return; return;
} }
io_out((uint8_t)port, (uint8_t)value); io_out((uint8_t)port, (uint8_t)value);
uart_puts("OK\r\n"); bios_puts("OK\r\n");
} }
static void execute_address(uint16_t address) static void execute_address(uint16_t address)
{ {
uart_puts("Executing at "); bios_puts("Executing at ");
print_hex16(address); print_hex16(address);
uart_puts("...\r\n"); bios_puts("...\r\n");
uart_flush(); bios_flush();
jump_to(address); jump_to(address);
uart_puts("Returned to monitor\r\n"); bios_puts("Returned to monitor\r\n");
} }
static void command_go(uint8_t argc, char **argv) static void command_go(uint8_t argc, char **argv)
@@ -298,7 +196,7 @@ static void command_go(uint8_t argc, char **argv)
uint16_t address; uint16_t address;
if (argc != 2 || !parse_hex(argv[1], &address)) { if (argc != 2 || !parse_hex(argv[1], &address)) {
uart_puts("Usage: G address\r\n"); bios_puts("Usage: G address\r\n");
return; return;
} }
execute_address(address); execute_address(address);
@@ -307,10 +205,10 @@ static void command_go(uint8_t argc, char **argv)
static void command_basic(uint8_t argc) static void command_basic(uint8_t argc)
{ {
if (argc != 1) { if (argc != 1) {
uart_puts("Usage: B\r\n"); bios_puts("Usage: B\r\n");
return; return;
} }
execute_address(0xd000); execute_address(0x1000);
} }
static void execute_line(char *line) static void execute_line(char *line)
@@ -335,7 +233,7 @@ static void execute_line(char *line)
} else if (strings_equal(argv[0], "b") || strings_equal(argv[0], "basic")) { } else if (strings_equal(argv[0], "b") || strings_equal(argv[0], "basic")) {
command_basic(argc); command_basic(argc);
} else { } else {
uart_puts("Unknown command; type HELP\r\n"); bios_puts("Unknown command; type HELP\r\n");
} }
} }
@@ -345,25 +243,10 @@ void main(void)
uint8_t length = 0; uint8_t length = 0;
uint8_t ignore_lf = 0; uint8_t ignore_lf = 0;
buffer_init(&rx_buf); bios_puts("\r\nZ80 monitor - v1\r\nType HELP for commands.\r\n> ");
buffer_init(&tx_buf);
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);
io_out(CTC_CHANNEL_0, 0x40);
uart_tx_busy = 0;
io_out(UART_CONTROL, 0b00000011);
io_out(UART_CONTROL, 0b10010101);
enable_interrupts();
uart_puts("\r\nZ80 monitor - v1\r\nType HELP for commands.\r\n> ");
for (;;) { for (;;) {
char c = uart_getc(); char c = bios_getc();
if (c == '\n' && ignore_lf) { if (c == '\n' && ignore_lf) {
ignore_lf = 0; ignore_lf = 0;
@@ -373,25 +256,25 @@ void main(void)
if (c == '\r' || c == '\n') { if (c == '\r' || c == '\n') {
ignore_lf = (c == '\r'); ignore_lf = (c == '\r');
line[length] = 0; line[length] = 0;
uart_puts("\r\n"); bios_puts("\r\n");
execute_line(line); execute_line(line);
length = 0; length = 0;
uart_puts("> "); bios_puts("> ");
} else if (c == '\b' || c == 0x7f) { } else if (c == '\b' || c == 0x7f) {
if (length) { if (length) {
--length; --length;
uart_puts("\b \b"); bios_puts("\b \b");
} }
} else if (c == 0x15) { } else if (c == 0x15) {
while (length) { while (length) {
--length; --length;
uart_puts("\b \b"); bios_puts("\b \b");
} }
} else if (c >= ' ' && c <= '~' && length < LINE_SIZE - 1) { } else if (c >= ' ' && c <= '~' && length < LINE_SIZE - 1) {
line[length++] = c; line[length++] = c;
uart_putc(c); bios_putc(c);
} else if (c >= ' ' && c <= '~') { } else if (c >= ' ' && c <= '~') {
uart_putc('\a'); bios_putc('\a');
} }
} }
} }