new memory and project layout
This commit is contained in:
@@ -2,7 +2,7 @@ TARGET := monitor
|
||||
BUILDDIR := build
|
||||
|
||||
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_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_RAM_SIZE := 16384
|
||||
METAL_IMAGE_SIZE := 65536
|
||||
MONITOR_BASE := 0xC000
|
||||
MONITOR_ROM_OFFSET := 128
|
||||
RESIDENT_BASE := 0xC000
|
||||
RESIDENT_ROM_OFFSET := 128
|
||||
|
||||
TINYBASIC_BASE := 0xD000
|
||||
TINYBASIC_BASE := 0x1000
|
||||
TINYBASIC_ROM_OFFSET := 4096
|
||||
TINYBASIC_SRC := tinybasic_sdas.asm
|
||||
TINYBASIC_SRC := apps/tinybasic.asm
|
||||
TINYBASIC_BUILD := $(BUILDDIR)/tinybasic
|
||||
|
||||
.DEFAULT_GOAL := metal
|
||||
@@ -36,9 +36,9 @@ metal: $(BUILDDIR)/metal/$(TARGET).ihx $(BUILDDIR)/metal/$(TARGET).bin $(BUILDDI
|
||||
END { \
|
||||
printf "\nMemory usage:\n"; \
|
||||
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
|
||||
@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)
|
||||
@mkdir -p $(@D)
|
||||
@@ -48,11 +48,12 @@ $(BUILDDIR)/metal/$(TARGET).flat.bin: $(BUILDDIR)/metal/$(TARGET).ihx
|
||||
$(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@
|
||||
|
||||
$(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_size=$$((payload_end - $(MONITOR_BASE))); \
|
||||
@payload_end=$$(awk '$$1 == "DEF" && $$2 == "resident_image_end" { print $$3 }' $(BUILDDIR)/metal/$(TARGET).noi); \
|
||||
payload_size=$$((payload_end - $(RESIDENT_BASE))); \
|
||||
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 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
|
||||
|
||||
$(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
|
||||
@mkdir -p $(@D)
|
||||
$(METAL_AS) -I$(BUILDDIR)/metal/generated -plosgff -o $@ $<
|
||||
|
||||
$(BUILDDIR)/metal/boot.rel: $(BUILDDIR)/metal/generated/tinybasic.inc
|
||||
$(METAL_AS) -Iinclude -I$(BUILDDIR)/metal/generated -plosgff -o $@ $<
|
||||
|
||||
$(TINYBASIC_BUILD)/tinybasic.rel: $(TINYBASIC_SRC)
|
||||
@mkdir -p $(@D)
|
||||
$(METAL_AS) -plosgff -o $@ $<
|
||||
$(METAL_AS) -Iinclude -plosgff -o $@ $<
|
||||
|
||||
$(TINYBASIC_BUILD)/tinybasic.ihx: $(TINYBASIC_BUILD)/tinybasic.rel
|
||||
sdldz80 -n -i -m -j $(basename $@) $<
|
||||
@@ -84,10 +83,6 @@ $(TINYBASIC_BUILD)/tinybasic.bin: $(TINYBASIC_BUILD)/tinybasic.flat.bin
|
||||
test $$tiny_size -gt 0; \
|
||||
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:
|
||||
rm -rf $(BUILDDIR)
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Z80 UART monitor
|
||||
|
||||
This ROM provides a small interactive monitor and a bundled Z80 Tiny BASIC
|
||||
interpreter on the UART/CTC card at I/O base `20`. At reset, a small ROM
|
||||
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
|
||||
data is cleared directly in high RAM. The low ROM can subsequently be disabled
|
||||
without taking away the monitor, Tiny BASIC, monitor data, or interrupt table.
|
||||
No runtime RAM below `C000` is used.
|
||||
This project provides a resident BIOS, an interactive monitor, and ROM-resident
|
||||
applications for a Z80 system with a UART/CTC card at I/O base `20`.
|
||||
|
||||
At reset, a small ROM bootstrap copies only the BIOS and monitor into the top
|
||||
16 KiB of RAM at `C000`. The BIOS initializes interrupt mode 2 and the UART,
|
||||
then starts the monitor. Applications remain in ROM and execute in place while
|
||||
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**.
|
||||
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 |
|
||||
| `O 20 57` | Write 57 to I/O port 20 |
|
||||
| `G 8000` | Execute code at address 8000 |
|
||||
| `G D000` | Start the bundled Z80 Tiny BASIC interpreter |
|
||||
| `B` or `BASIC` | Shortcut for `G D000` |
|
||||
| `G 1000` | Start the ROM-resident Z80 Tiny BASIC interpreter |
|
||||
| `B` or `BASIC` | Shortcut for `G 1000` |
|
||||
| `HELP` | Show the command summary |
|
||||
|
||||
`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:
|
||||
|
||||
- `0000-007F`: reset/copy bootstrap
|
||||
- `0080-0F6E`: monitor image copied to RAM at `C000-CEEE`
|
||||
- `0F6F-0FFF`: unused space reserved for monitor growth
|
||||
- `1000-180C`: Tiny BASIC image copied to RAM at `D000-D80C`
|
||||
- `0080-0F9D`: packed BIOS+monitor image copied to RAM at `C000-CF1D`
|
||||
- `0F9E-0FFF`: unused space reserved for the resident image to grow
|
||||
- `1000-180C`: Tiny BASIC, executed directly from ROM
|
||||
- `180D-7FFF`: unused space available for additional ROM content
|
||||
|
||||
The monitor executable starts at `C080`; its IM 2 vector table is at `C040`.
|
||||
The bootstrap obtains both copy lengths from generated/linker symbols, so the
|
||||
copied sizes track changes to the programs automatically.
|
||||
The resident image starts at `C000`, its IM 2 vector table is at `C040`, and
|
||||
linked code starts at `C080`. The bootstrap obtains the resident copy length
|
||||
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
|
||||
- `CEEF-CFFF`: free
|
||||
- `D000-D80C`: Tiny BASIC code
|
||||
- `D80D-DFFF`: free
|
||||
- `E000-E7FF`: Tiny BASIC workspace and BASIC program text
|
||||
- `E800-E877`: Tiny BASIC variables and input buffer
|
||||
- `E878-EFFF`: Tiny BASIC stack space
|
||||
- `F000-FFFF`: monitor globals and monitor stack
|
||||
- `8000-AFFF`: Tiny BASIC workspace and BASIC program text
|
||||
- `B000-B077`: Tiny BASIC variables and input buffer
|
||||
- `B078-BFFF`: Tiny BASIC stack space
|
||||
- `C000-CF1D`: resident BIOS, vectors, services, and monitor
|
||||
- `CF1E-EFFF`: free space for resident-image growth
|
||||
- `F000-FFFF`: BIOS globals, UART buffers, 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
|
||||
|
||||
The original GNU-z80asm listing is retained as `tinybasic/tinybasic.asm`. Its
|
||||
`sdasz80` port is `tinybasic/tinybasic_sdas.asm` and is built automatically.
|
||||
The port replaces fixed low-page `RST` calls with relocatable calls and uses a
|
||||
marker-based command table so addresses in the `D000` page work correctly.
|
||||
Tiny BASIC is built from `apps/tinybasic.asm`. The port replaces fixed low-page
|
||||
`RST` calls with relocatable calls and uses a marker-based command table so it
|
||||
can be linked at a nonzero ROM address.
|
||||
|
||||
At reset the bootstrap copies Tiny BASIC from ROM offset `1000` to `D000`.
|
||||
Start it from the monitor with:
|
||||
Tiny BASIC is linked at and runs directly from ROM address `1000`. Start it from
|
||||
the monitor with:
|
||||
|
||||
```text
|
||||
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
|
||||
non-blocking input. The assembly ABI wrappers preserve the working registers,
|
||||
and Tiny BASIC keeps interrupts enabled so it can share the monitor's configured
|
||||
Tiny BASIC calls the BIOS at `C050` for output and `C053` for non-blocking
|
||||
input. Tiny BASIC keeps interrupts enabled so it can share the BIOS-configured
|
||||
interrupt-driven UART.
|
||||
|
||||
Line editing accepts either `BS` (`08`) or `DEL` (`7F`) for backspace and emits
|
||||
|
||||
@@ -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.
|
||||
@@ -42,13 +42,11 @@
|
||||
; Adapted to work with GNU z80asm
|
||||
; Added interrupt handling
|
||||
; 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.
|
||||
; 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.
|
||||
MONITOR_PUTC = 0xC050
|
||||
MONITOR_GETC = 0xC053
|
||||
.include "bios.inc"
|
||||
|
||||
|
||||
SPACE = 0x020 ; Space
|
||||
@@ -67,9 +65,10 @@ CTRLU = 0x015 ; Control "U"
|
||||
ESC = 0x01B ; Escape
|
||||
DEL = 0x07F ; Delete
|
||||
|
||||
; Adjust to fit RAM mapping
|
||||
STACK = 0xEFFF ; Tiny BASIC stack grows down below monitor data
|
||||
OCSW = 0xE000 ;SWITCH FOR OUTPUT
|
||||
; Tiny BASIC owns lower RAM 8000-BFFF. The resident BIOS and monitor own
|
||||
; C000-FFFF, so BASIC cannot overwrite them even when its stack is deep.
|
||||
STACK = 0xBFFF ; Tiny BASIC stack grows down through B078-BFFF
|
||||
OCSW = 0x8000 ;SWITCH FOR OUTPUT
|
||||
CURRNT = OCSW+1 ;POINTS FOR OUTPUT
|
||||
STKGOS = OCSW+3 ;SAVES SP IN 'GOSUB'
|
||||
VARNXT = OCSW+5 ;TEMP STORAGE
|
||||
@@ -83,11 +82,11 @@ RANPNT = OCSW+19 ;RANDOM NUMBER POINTER
|
||||
TXTUNF = OCSW+21 ;->UNFILLED TEXT AREA
|
||||
TXTBGN = OCSW+23 ;TEXT SAVE AREA BEGINS
|
||||
|
||||
TXTEND = 0xE800 ;TEXT SAVE AREA ENDS
|
||||
VARBGN = 0xE800 ;VARIABLE @(0)
|
||||
BUFFER = 0xE837 ;55-byte variable area ends here
|
||||
BUFEND = 0xE877 ;64-byte input buffer ends here
|
||||
STKLMT = 0xE878 ;LOWEST ALLOWED STACK ADDRESS
|
||||
TXTEND = 0xB000 ;TEXT SAVE AREA ENDS
|
||||
VARBGN = 0xB000 ;VARIABLE @(0)
|
||||
BUFFER = 0xB037 ;55-byte variable area ends here
|
||||
BUFEND = 0xB077 ;64-byte input buffer ends here
|
||||
STKLMT = 0xB078 ;LOWEST ALLOWED STACK ADDRESS
|
||||
|
||||
|
||||
;*************************************************************
|
||||
@@ -103,7 +102,7 @@ STKLMT = 0xE878 ;LOWEST ALLOWED STACK ADDRESS
|
||||
;*************************************************************
|
||||
|
||||
.area TINYBASIC (ABS)
|
||||
.org 0xD000
|
||||
.org 0x1000
|
||||
main:
|
||||
START:
|
||||
LD SP,#STACK ;*** COLD START ***
|
||||
@@ -1601,7 +1600,7 @@ OUTC:
|
||||
OUTC2:
|
||||
CALL TX_RDY ;SEE IF TRANSMIT IS AVAILABLE
|
||||
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
|
||||
RET NZ
|
||||
LD A,#LF
|
||||
@@ -1826,7 +1825,7 @@ RX_RDY:
|
||||
; However, if a character is not available, it returns with the
|
||||
; Z-flag set.
|
||||
|
||||
CALL MONITOR_GETC
|
||||
CALL BIOS_TRY_GETC
|
||||
OR A
|
||||
RET
|
||||
;-------------------------------------------------------------------------------
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,11 +1,9 @@
|
||||
STACK_TOP = 0xFFFF
|
||||
DELAY_LOOPS_PER_MS = 40
|
||||
MONITOR_BASE = 0xC000
|
||||
MONITOR_ROM_SOURCE = 0x0080
|
||||
TINYBASIC_BASE = 0xD000
|
||||
TINYBASIC_ROM_SOURCE = 0x1000
|
||||
RESIDENT_BASE = 0xC000
|
||||
RESIDENT_ROM_SOURCE = 0x0080
|
||||
|
||||
.include "tinybasic.inc"
|
||||
.include "bios.inc"
|
||||
|
||||
.globl _main
|
||||
.globl _delay_ms
|
||||
@@ -14,9 +12,11 @@ TINYBASIC_ROM_SOURCE = 0x1000
|
||||
.globl _enable_interrupts
|
||||
.globl _disable_interrupts
|
||||
.globl _jump_to
|
||||
.globl _monitor_uart_putc
|
||||
.globl _monitor_uart_getc
|
||||
.globl monitor_image_end
|
||||
.globl _bios_putc
|
||||
.globl _bios_try_getc
|
||||
.globl _bios_getc
|
||||
.globl _bios_init
|
||||
.globl resident_image_end
|
||||
.globl s__DATA
|
||||
.globl l__DATA
|
||||
.globl s__INITIALIZER
|
||||
@@ -31,50 +31,56 @@ TINYBASIC_ROM_SOURCE = 0x1000
|
||||
boot_copy:
|
||||
di
|
||||
ld sp, #STACK_TOP
|
||||
ld hl, #MONITOR_ROM_SOURCE
|
||||
ld de, #MONITOR_BASE
|
||||
ld bc, #(monitor_image_end - MONITOR_BASE)
|
||||
ldir
|
||||
|
||||
ld hl, #TINYBASIC_ROM_SOURCE
|
||||
ld de, #TINYBASIC_BASE
|
||||
ld bc, #TINYBASIC_SIZE
|
||||
ld hl, #RESIDENT_ROM_SOURCE
|
||||
ld de, #RESIDENT_BASE
|
||||
ld bc, #(resident_image_end - RESIDENT_BASE)
|
||||
ldir
|
||||
jp init
|
||||
|
||||
.area _ITABLE (ABS)
|
||||
|
||||
; The executable vector page is part of the image copied to high RAM.
|
||||
.org MONITOR_BASE
|
||||
.org RESIDENT_BASE
|
||||
jp init
|
||||
.org MONITOR_BASE + 0x08
|
||||
.org RESIDENT_BASE + 0x08
|
||||
reti
|
||||
.org MONITOR_BASE + 0x10
|
||||
.org RESIDENT_BASE + 0x10
|
||||
reti
|
||||
.org MONITOR_BASE + 0x18
|
||||
.org RESIDENT_BASE + 0x18
|
||||
reti
|
||||
.org MONITOR_BASE + 0x20
|
||||
.org RESIDENT_BASE + 0x20
|
||||
reti
|
||||
.org MONITOR_BASE + 0x28
|
||||
.org RESIDENT_BASE + 0x28
|
||||
reti
|
||||
.org MONITOR_BASE + 0x30
|
||||
.org RESIDENT_BASE + 0x30
|
||||
reti
|
||||
.org MONITOR_BASE + 0x38
|
||||
.org RESIDENT_BASE + 0x38
|
||||
reti
|
||||
|
||||
; 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 1: vector 0x42
|
||||
.dw ctc_isr ; CTC channel 2: vector 0x44
|
||||
.dw uart_isr ; CTC channel 3: vector 0x46
|
||||
|
||||
; Stable entry points for programs living alongside the monitor.
|
||||
; C050: write the character in A. C053: return a character in A, or 0.
|
||||
.org MONITOR_BASE + 0x50
|
||||
jp monitor_service_putc
|
||||
.org MONITOR_BASE + 0x53
|
||||
jp monitor_service_getc
|
||||
; Stable BIOS jump table for ROM-resident applications.
|
||||
; C050 putc(A), C053 try_getc(), C056 getc(), C059 io_out(A,L),
|
||||
; C05C io_in(A), C05F delay_ms(HL), C062 monitor warm start.
|
||||
.org BIOS_PUTC
|
||||
jp bios_service_putc
|
||||
.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
|
||||
init:
|
||||
@@ -87,6 +93,7 @@ init:
|
||||
ld a, #0xC0
|
||||
ld i, a
|
||||
im 2
|
||||
call _bios_init
|
||||
call _main
|
||||
|
||||
hang:
|
||||
@@ -124,20 +131,19 @@ clear_loop:
|
||||
gsinit_done:
|
||||
.area _GSFINAL
|
||||
ret
|
||||
monitor_image_end:
|
||||
resident_image_end:
|
||||
|
||||
.area _CODE
|
||||
|
||||
; Monitor ABI services preserve all working registers. PUTC also preserves AF;
|
||||
; GETC returns the received byte (or zero) in A.
|
||||
monitor_service_putc:
|
||||
; BIOS ABI services preserve registers except documented return values.
|
||||
bios_service_putc:
|
||||
push af
|
||||
push bc
|
||||
push de
|
||||
push hl
|
||||
push ix
|
||||
push iy
|
||||
call _monitor_uart_putc
|
||||
call _bios_putc
|
||||
pop iy
|
||||
pop ix
|
||||
pop hl
|
||||
@@ -146,13 +152,13 @@ monitor_service_putc:
|
||||
pop af
|
||||
ret
|
||||
|
||||
monitor_service_getc:
|
||||
bios_service_try_getc:
|
||||
push bc
|
||||
push de
|
||||
push hl
|
||||
push ix
|
||||
push iy
|
||||
call _monitor_uart_getc
|
||||
call _bios_try_getc
|
||||
pop iy
|
||||
pop ix
|
||||
pop hl
|
||||
@@ -160,6 +166,66 @@ monitor_service_getc:
|
||||
pop bc
|
||||
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
|
||||
.globl handler
|
||||
entry:
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -1,113 +1,11 @@
|
||||
#include <stdint.h>
|
||||
#include "boot.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)
|
||||
#include "bios.h"
|
||||
|
||||
#define LINE_SIZE 80
|
||||
#define MAX_ARGS 40
|
||||
#define DEFAULT_DUMP_LENGTH 0x40
|
||||
#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)
|
||||
{
|
||||
value &= 0x0f;
|
||||
@@ -116,8 +14,8 @@ static char hex_digit(uint8_t value)
|
||||
|
||||
static void print_hex8(uint8_t value)
|
||||
{
|
||||
uart_putc(hex_digit(value >> 4));
|
||||
uart_putc(hex_digit(value));
|
||||
bios_putc(hex_digit(value >> 4));
|
||||
bios_putc(hex_digit(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)
|
||||
{
|
||||
uart_puts(
|
||||
bios_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"
|
||||
" 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");
|
||||
}
|
||||
|
||||
@@ -211,11 +109,11 @@ static void command_dump(uint8_t argc, char **argv)
|
||||
|
||||
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");
|
||||
bios_puts("Usage: D address [length]\r\n");
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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));
|
||||
|
||||
print_hex16((uint16_t)(address + offset));
|
||||
uart_puts(": ");
|
||||
bios_puts(": ");
|
||||
for (column = 0; column < row_length; ++column) {
|
||||
print_hex8(*(volatile uint8_t *)(address + offset + column));
|
||||
uart_putc(' ');
|
||||
bios_putc(' ');
|
||||
}
|
||||
uart_puts("\r\n");
|
||||
bios_puts("\r\n");
|
||||
offset += row_length;
|
||||
}
|
||||
}
|
||||
@@ -240,19 +138,19 @@ static void command_write(uint8_t argc, char **argv)
|
||||
uint8_t i;
|
||||
|
||||
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;
|
||||
}
|
||||
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");
|
||||
bios_puts("Bad byte: ");
|
||||
bios_puts(argv[i]);
|
||||
bios_puts("\r\n");
|
||||
return;
|
||||
}
|
||||
*(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)
|
||||
@@ -260,13 +158,13 @@ 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");
|
||||
bios_puts("Usage: I port\r\n");
|
||||
return;
|
||||
}
|
||||
print_hex8((uint8_t)port);
|
||||
uart_puts(": ");
|
||||
bios_puts(": ");
|
||||
print_hex8(io_in((uint8_t)port));
|
||||
uart_puts("\r\n");
|
||||
bios_puts("\r\n");
|
||||
}
|
||||
|
||||
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 ||
|
||||
!parse_hex(argv[2], &value) || value > 0xff) {
|
||||
uart_puts("Usage: O port byte\r\n");
|
||||
bios_puts("Usage: O port byte\r\n");
|
||||
return;
|
||||
}
|
||||
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)
|
||||
{
|
||||
uart_puts("Executing at ");
|
||||
bios_puts("Executing at ");
|
||||
print_hex16(address);
|
||||
uart_puts("...\r\n");
|
||||
uart_flush();
|
||||
bios_puts("...\r\n");
|
||||
bios_flush();
|
||||
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)
|
||||
@@ -298,7 +196,7 @@ 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");
|
||||
bios_puts("Usage: G address\r\n");
|
||||
return;
|
||||
}
|
||||
execute_address(address);
|
||||
@@ -307,10 +205,10 @@ static void command_go(uint8_t argc, char **argv)
|
||||
static void command_basic(uint8_t argc)
|
||||
{
|
||||
if (argc != 1) {
|
||||
uart_puts("Usage: B\r\n");
|
||||
bios_puts("Usage: B\r\n");
|
||||
return;
|
||||
}
|
||||
execute_address(0xd000);
|
||||
execute_address(0x1000);
|
||||
}
|
||||
|
||||
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")) {
|
||||
command_basic(argc);
|
||||
} 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 ignore_lf = 0;
|
||||
|
||||
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();
|
||||
|
||||
uart_puts("\r\nZ80 monitor - v1\r\nType HELP for commands.\r\n> ");
|
||||
bios_puts("\r\nZ80 monitor - v1\r\nType HELP for commands.\r\n> ");
|
||||
|
||||
for (;;) {
|
||||
char c = uart_getc();
|
||||
char c = bios_getc();
|
||||
|
||||
if (c == '\n' && ignore_lf) {
|
||||
ignore_lf = 0;
|
||||
@@ -373,25 +256,25 @@ void main(void)
|
||||
if (c == '\r' || c == '\n') {
|
||||
ignore_lf = (c == '\r');
|
||||
line[length] = 0;
|
||||
uart_puts("\r\n");
|
||||
bios_puts("\r\n");
|
||||
execute_line(line);
|
||||
length = 0;
|
||||
uart_puts("> ");
|
||||
bios_puts("> ");
|
||||
} else if (c == '\b' || c == 0x7f) {
|
||||
if (length) {
|
||||
--length;
|
||||
uart_puts("\b \b");
|
||||
bios_puts("\b \b");
|
||||
}
|
||||
} else if (c == 0x15) {
|
||||
while (length) {
|
||||
--length;
|
||||
uart_puts("\b \b");
|
||||
bios_puts("\b \b");
|
||||
}
|
||||
} else if (c >= ' ' && c <= '~' && length < LINE_SIZE - 1) {
|
||||
line[length++] = c;
|
||||
uart_putc(c);
|
||||
bios_putc(c);
|
||||
} else if (c >= ' ' && c <= '~') {
|
||||
uart_putc('\a');
|
||||
bios_putc('\a');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user