start of tape loading. hw works and have a generation script

This commit is contained in:
Joppe Blondel
2026-10-03 16:29:41 +02:00
parent 47fffc2e71
commit 5897e55094
16 changed files with 784 additions and 106 deletions
+56 -26
View File
@@ -8,8 +8,8 @@ At reset, a small ROM bootstrap copies only the BIOS and monitor into the top
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 the first such application,
linked at ROM address `1000`.
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
@@ -23,9 +23,14 @@ optional `0x` prefix is accepted.
| `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 1000` | Start the ROM-resident Z80 Tiny BASIC interpreter |
| `B` or `BASIC` | Shortcut for `G 1000` |
| `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
@@ -55,12 +60,17 @@ 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. For example:
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
@@ -72,14 +82,17 @@ 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 6,157 bytes. Its ROM layout is:
The current compact image is 12,318 bytes. Its ROM layout is:
- `0000-007F`: reset/copy bootstrap
- `0080-0FEA`: packed BIOS+monitor image copied to RAM at `C000-CF6A`
- `0FEB-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
- `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
@@ -90,8 +103,8 @@ 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-CF6A`: resident BIOS, vectors, services, and monitor
- `CF6B-EFFF`: free space for resident-image growth
- `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
@@ -119,6 +132,7 @@ Assembly applications include `include/bios.inc` and call these fixed entries:
| `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`.
@@ -127,38 +141,47 @@ 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 is 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.
`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)`. 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.
`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
- `apps/tinybasic.asm`: first ROM-resident application
- `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.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 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 `1000`. Start it from
Tiny BASIC is linked at and runs directly from ROM address `1400`. Start it from
the monitor with:
```text
B
```
`BASIC` and `G 1000` are equivalent alternatives.
`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
@@ -174,5 +197,12 @@ 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 first currently unused ROM byte is `180D`; `1900` is a convenient
aligned starting point for another ROM payload.
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.