209 lines
9.4 KiB
Markdown
209 lines
9.4 KiB
Markdown
# Z80 UART monitor
|
|
|
|
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 at `C000`. Before copying, it writes `06` to banking port `00`: ROM
|
|
remains enabled, banking is enabled, and `C000-FFFF` maps to the upper physical
|
|
16 KiB. The BIOS then initializes interrupt mode 2 and the UART and starts the
|
|
monitor. Applications remain in ROM and execute in place while calling the
|
|
stable BIOS jump table in high RAM. Tiny BASIC is linked at ROM address `1400`,
|
|
and the tape loader is linked at `3000`.
|
|
|
|
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 |
|
|
| `C 8000 BFFF` | Compute the 16-bit additive checksum of an inclusive range |
|
|
| `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 |
|
|
| `K 0` or `BANK 0` | Select lower physical 48K plus upper physical 16K |
|
|
| `K 1` or `BANK 1` | Select the complete upper physical 64K |
|
|
| `R ON` or `ROM ON` | Enable ROM while preserving the RAM bank selection |
|
|
| `R OFF` or `ROM OFF` | Disable ROM while preserving the RAM bank selection |
|
|
| `G 8000` | Execute code at address 8000 |
|
|
| `G 1400` | Start the ROM-resident Z80 Tiny BASIC interpreter |
|
|
| `B` or `BASIC` | Shortcut for `G 1400` |
|
|
| `T` or `TAPE` | Start the ROM-resident tape loader at `3000` |
|
|
| `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.
|
|
|
|
`C` adds every byte from the start through the end address, inclusive, and
|
|
prints the low 16 bits of the sum as four hexadecimal digits. The end address
|
|
must be greater than or equal to the start address.
|
|
|
|
Build the ROM and programmer-format images with:
|
|
|
|
```sh
|
|
make
|
|
```
|
|
|
|
## Uploading Intel HEX files
|
|
|
|
`program_ihex.py` can write an Intel HEX file to memory through the running
|
|
monitor. It sends data in small blocks with `W`, checks every block with `C`,
|
|
and retries a failed write or checksum before giving up. It requires Python 3
|
|
and [pySerial](https://pyserial.readthedocs.io/).
|
|
|
|
```sh
|
|
python3 -m pip install pyserial
|
|
python3 program_ihex.py /dev/ttyUSB0 program.hex
|
|
```
|
|
|
|
The default connection is 2400 baud, the default block size is 16 bytes, and
|
|
three retries are allowed after the initial attempt. Characters are paced with
|
|
a 5 ms delay because the monitor UART can lose bytes from an uninterrupted USB
|
|
serial burst. For example:
|
|
|
|
```sh
|
|
python3 program_ihex.py /dev/ttyUSB0 program.hex --retries 5 --block-size 24
|
|
```
|
|
|
|
Use `--char-delay 0.01` if commands still arrive with missing characters. A
|
|
smaller value uploads faster; `--char-delay 0` disables host-side pacing.
|
|
|
|
Use a Windows port such as `COM3` in place of `/dev/ttyUSB0`. Run
|
|
`python3 program_ihex.py --help` for all options. The monitor must be sitting at
|
|
its `>` prompt before starting the upload. Only data records present in the HEX
|
|
file are written; gaps are left unchanged. The HEX addresses must refer to
|
|
writable memory and must not overwrite the running monitor at `C000-FFFF`.
|
|
|
|
The outputs are:
|
|
|
|
- `build/metal/monitor.bin`: complete 32 KiB ROM image
|
|
- `build/metal/monitor.prog.bin`: compact image ending at the last occupied byte
|
|
- `build/tinybasic/tinybasic.bin`: standalone relocated Tiny BASIC payload
|
|
- `build/casette/tape_loader.bin`: standalone relocated tape-loader payload
|
|
|
|
The current compact image is 12,318 bytes. Its ROM layout is:
|
|
|
|
- `0000-007F`: reset/copy bootstrap
|
|
- `0080-1201`: packed BIOS+monitor image copied to RAM at `C000-D181`
|
|
- `1202-13FF`: unused space reserved for the resident image to grow
|
|
- `1400-1C0C`: Tiny BASIC, executed directly from ROM
|
|
- `1C0D-2FFF`: unused ROM
|
|
- `3000-301D`: tape loader, executed directly from ROM
|
|
- `301E-7FFF`: unused space available for additional ROM content
|
|
|
|
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 RAM layout is:
|
|
|
|
- `8000-AFFF`: Tiny BASIC workspace and BASIC program text
|
|
- `B000-B077`: Tiny BASIC variables and input buffer
|
|
- `B078-BFFF`: Tiny BASIC stack space
|
|
- `C000-D181`: resident BIOS, vectors, services, and monitor
|
|
- `D182-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.
|
|
|
|
The banking latch bits used by the bootstrap are bit 0 `ROM_DISABLE`, bit 1
|
|
`BANK_SELECT`, and bit 2 `BANK_ENABLE`. Its initial value `06` leaves ROM on and
|
|
selects the lower physical 48 KiB plus the upper physical 16 KiB. Writing `07`
|
|
later disables ROM without changing that split mapping. Writing `05` selects
|
|
the complete upper physical 64 KiB; the monitor remains available because its
|
|
`C000-FFFF` image was placed in that upper bank during boot.
|
|
|
|
## 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 |
|
|
| `C065` | `BIOS_BANK` | `A=0`: split lower 48K/upper 16K; otherwise select upper 64K |
|
|
| `C068` | `BIOS_ROM_DISABLE` | Disable ROM while retaining the selected RAM mapping |
|
|
| `C06B` | `BIOS_ROM_ENABLE` | Enable ROM while retaining the selected RAM mapping |
|
|
|
|
The service wrappers preserve working registers except for documented return
|
|
values. The monitor uses the corresponding C API declared in `bios.h`.
|
|
|
|
The banking latch at port `00` is write-only, so the BIOS keeps a persistent
|
|
shadow byte initialized to `06` by the copied resident image. `BIOS_BANK`
|
|
always leaves banking enabled and changes only the bank selection while
|
|
retaining the shadowed ROM-disable bit and auxiliary-output bits.
|
|
`BIOS_ROM_DISABLE` sets the ROM-disable bit and `BIOS_ROM_ENABLE` clears it;
|
|
both are idempotent. Code should not write port `00` through `BIOS_IO_OUT` or
|
|
the monitor's `O` command after using these services, because such writes
|
|
bypass the BIOS shadow state. Use the monitor's `BANK` and `ROM` commands
|
|
instead.
|
|
|
|
The equivalent C API is `bios_bank(uint8_t bank)` and
|
|
`bios_rom_disable(void)` / `bios_rom_enable(void)`. A ROM-resident caller cannot
|
|
return normally after selecting the complete upper 64 KiB or disabling ROM,
|
|
because its return address is no longer mapped; it must arrange its
|
|
continuation in RAM.
|
|
|
|
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
|
|
- `rom_layout.mk`: authoritative ROM and resident-memory addresses
|
|
- `apps/tinybasic/tinybasic.asm`: Tiny BASIC ROM application
|
|
- `apps/casette/tape_loader.asm`: tape-loader ROM application
|
|
- `apps/README.md`: ROM application memory and integration contract
|
|
- `include/bios.inc`: stable assembly interface for ROM applications
|
|
|
|
The build generates `rom_layout.h` for C and `rom_layout.inc` for assembly from
|
|
`rom_layout.mk`. Change application origins and resident-memory addresses only
|
|
in `rom_layout.mk`; the generated files are removed by `make clean`.
|
|
|
|
## Tiny BASIC
|
|
|
|
Tiny BASIC is built from `apps/tinybasic/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.
|
|
|
|
Tiny BASIC is linked at and runs directly from ROM address `1400`. Start it from
|
|
the monitor with:
|
|
|
|
```text
|
|
B
|
|
```
|
|
|
|
`BASIC` and `G 1400` are equivalent alternatives.
|
|
|
|
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
|
|
`BS SPACE BS` so ANSI-style terminals erase the character visually. Ctrl-U
|
|
clears the complete input line.
|
|
|
|
During execution, Ctrl-O toggles output suppression without stopping the BASIC
|
|
program. Press Ctrl-O again to resume output. Ctrl-C stops execution and
|
|
returns to the Tiny BASIC prompt.
|
|
|
|
Tiny BASIC resets the stack pointer when it starts, so it does not currently
|
|
return through the monitor's `G` call. Reset the machine to return to the
|
|
monitor. The tape loader occupies the later ROM slot at `3000-301D`; the first
|
|
unused byte after it is `301E`.
|
|
|
|
## Tape loader
|
|
|
|
The tape-loader image is built from `apps/casette/tape_loader.asm`, linked at
|
|
`3000`, and included in both ROM images. Start it with `T`, `TAPE`, or `G 3000`.
|
|
The current image polls the comparator input and prints its state through the
|
|
resident BIOS console service.
|