New -> currentt
This commit is contained in:
@@ -1,2 +1 @@
|
|||||||
build
|
build
|
||||||
__pycache__/
|
|
||||||
|
|||||||
@@ -1,157 +1,55 @@
|
|||||||
TARGET := monitor
|
.SILENT:
|
||||||
BUILDDIR := build
|
include memory.mk
|
||||||
LAYOUT_CONFIG := rom_layout.mk
|
|
||||||
LAYOUT_BUILD := $(BUILDDIR)/generated
|
|
||||||
LAYOUT_HEADER := $(LAYOUT_BUILD)/rom_layout.h
|
|
||||||
LAYOUT_INCLUDE := $(LAYOUT_BUILD)/rom_layout.inc
|
|
||||||
|
|
||||||
include $(LAYOUT_CONFIG)
|
ASM := zasm
|
||||||
|
ASMFLAGS := -w -b
|
||||||
|
BUILDDIR := build
|
||||||
|
|
||||||
METAL_C_SRCFILES := $(wildcard *.c)
|
FILES := bios
|
||||||
METAL_ASM_SRCFILES := boot.asm
|
|
||||||
METAL_RELFILES := $(METAL_ASM_SRCFILES:%.asm=$(BUILDDIR)/metal/%.rel) \
|
|
||||||
$(METAL_C_SRCFILES:%.c=$(BUILDDIR)/metal/%.rel)
|
|
||||||
|
|
||||||
METAL_CC := sdcc
|
SOURCES := $(addprefix ./, $(addsuffix .asm, $(FILES)))
|
||||||
METAL_AS := sdasz80
|
TARGETS := $(addprefix $(BUILDDIR)/, $(addsuffix .bin, $(FILES)))
|
||||||
METAL_MAKEBIN := makebin
|
|
||||||
METAL_FLAGS := -mz80 --no-std-crt0 --code-loc $(RESIDENT_CODE_BASE) --code-size $(RESIDENT_CODE_SIZE) --data-loc $(RESIDENT_DATA_BASE) --opt-code-size
|
|
||||||
METAL_CPPFLAGS := -I src -I $(LAYOUT_BUILD) --disable-warning 283
|
|
||||||
METAL_BIN_SIZE := $(ROM_SIZE)
|
|
||||||
METAL_RAM_SIZE := 16384
|
|
||||||
METAL_IMAGE_SIZE := 65536
|
|
||||||
|
|
||||||
TINYBASIC_SRC := apps/tinybasic/tinybasic.asm
|
TOPTARGETS := all clean size
|
||||||
TINYBASIC_BUILD := $(BUILDDIR)/tinybasic
|
$(TOPTARGETS): $(SUBDIRS)
|
||||||
TAPE_LOADER_SRC := apps/casette/tape_loader.asm
|
|
||||||
TAPE_LOADER_BUILD := $(BUILDDIR)/casette
|
|
||||||
NASCOM_BASIC_SRC := apps/nascom_basic/basic.asm
|
|
||||||
NASCOM_BASIC_BUILD := $(BUILDDIR)/nascom_basic
|
|
||||||
|
|
||||||
.DEFAULT_GOAL := metal
|
all: $(BUILDDIR) $(BUILDDIR)/rom.bin size
|
||||||
|
|
||||||
.PHONY: all metal clean
|
|
||||||
|
|
||||||
all: metal
|
|
||||||
|
|
||||||
metal: $(BUILDDIR)/metal/$(TARGET).ihx $(BUILDDIR)/metal/$(TARGET).bin $(BUILDDIR)/metal/$(TARGET).prog.bin
|
|
||||||
@stored=$$(wc -c < $(BUILDDIR)/metal/$(TARGET).prog.bin); \
|
|
||||||
awk -v rom_capacity=$(METAL_BIN_SIZE) -v stored=$$stored -v ram_capacity=$(METAL_RAM_SIZE) '\
|
|
||||||
$$1 ~ /^_(CODE|HOME|GSINIT|GSFINAL|INITIALIZER)$$/ || $$1 ~ /^_ITABLE/ { code += $$5 } \
|
|
||||||
$$1 ~ /^_(DATA|INITIALIZED|BSEG|BSS|HEAP)$$/ { data += $$5 } \
|
|
||||||
END { \
|
|
||||||
printf "\nMemory usage:\n"; \
|
|
||||||
printf " ROM storage: %5u / %5u bytes (%5.1f%%)\n", stored, rom_capacity, 100 * stored / rom_capacity; \
|
|
||||||
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 ROM bytes at %04X\n" \
|
|
||||||
$$(wc -c < $(TINYBASIC_BUILD)/tinybasic.bin) \
|
|
||||||
$$(( $(TINYBASIC_ROM_ADDRESS) ))
|
|
||||||
@printf " Tape loader: %5u ROM bytes at %04X\n" \
|
|
||||||
$$(wc -c < $(TAPE_LOADER_BUILD)/tape_loader.bin) \
|
|
||||||
$$(( $(TAPE_LOADER_ROM_ADDRESS) ))
|
|
||||||
@printf " NASCOM BASIC: %5u ROM bytes at %04X\n" \
|
|
||||||
$$(wc -c < $(NASCOM_BASIC_BUILD)/nascom_basic.bin) \
|
|
||||||
$$(( $(NASCOM_BASIC_ROM_ADDRESS) ))
|
|
||||||
|
|
||||||
$(BUILDDIR)/metal/$(TARGET).ihx: $(METAL_RELFILES)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
$(METAL_CC) $(METAL_FLAGS) -o $@ $(METAL_RELFILES)
|
|
||||||
|
|
||||||
$(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 $(TAPE_LOADER_BUILD)/tape_loader.bin $(NASCOM_BASIC_BUILD)/nascom_basic.bin
|
|
||||||
@set -e; \
|
|
||||||
payload_end=$$(awk '$$1 == "DEF" && $$2 == "resident_image_end" { print $$3 }' $(BUILDDIR)/metal/$(TARGET).noi); \
|
|
||||||
payload_size=$$((payload_end - $(RESIDENT_RAM_BASE))); \
|
|
||||||
payload_rom_end=$$(( $(RESIDENT_ROM_OFFSET) + payload_size )); \
|
|
||||||
tiny_size=$$(wc -c < $(TINYBASIC_BUILD)/tinybasic.bin); \
|
|
||||||
tape_size=$$(wc -c < $(TAPE_LOADER_BUILD)/tape_loader.bin); \
|
|
||||||
nascom_size=$$(wc -c < $(NASCOM_BASIC_BUILD)/nascom_basic.bin); \
|
|
||||||
if test $$payload_size -le 0; then \
|
|
||||||
echo "error: resident image is empty or has an invalid end symbol" >&2; exit 1; \
|
|
||||||
fi; \
|
|
||||||
if test $$payload_rom_end -gt $$(( $(TINYBASIC_ROM_ADDRESS) )); then \
|
|
||||||
printf "error: resident ROM image ends at %04X, beyond Tiny BASIC origin %04X\n" \
|
|
||||||
$$payload_rom_end $$(( $(TINYBASIC_ROM_ADDRESS) )) >&2; exit 1; \
|
|
||||||
fi; \
|
|
||||||
if test $$(( $(TINYBASIC_ROM_ADDRESS) + tiny_size )) -gt $$(( $(TAPE_LOADER_ROM_ADDRESS) )); then \
|
|
||||||
printf "error: Tiny BASIC ends at %04X, beyond tape-loader origin %04X\n" \
|
|
||||||
$$(( $(TINYBASIC_ROM_ADDRESS) + tiny_size )) $$(( $(TAPE_LOADER_ROM_ADDRESS) )) >&2; exit 1; \
|
|
||||||
fi; \
|
|
||||||
if test $$(( $(TAPE_LOADER_ROM_ADDRESS) + tape_size )) -gt $$(( $(NASCOM_BASIC_ROM_ADDRESS) )); then \
|
|
||||||
printf "error: tape loader ends at %04X, beyond NASCOM BASIC origin %04X\n" \
|
|
||||||
$$(( $(TAPE_LOADER_ROM_ADDRESS) + tape_size )) $$(( $(NASCOM_BASIC_ROM_ADDRESS) )) >&2; exit 1; \
|
|
||||||
fi; \
|
|
||||||
if test $$(( $(NASCOM_BASIC_ROM_ADDRESS) + nascom_size )) -gt $$(( $(ROM_SIZE) )); then \
|
|
||||||
printf "error: NASCOM BASIC ends at %04X, beyond ROM size %04X\n" \
|
|
||||||
$$(( $(NASCOM_BASIC_ROM_ADDRESS) + nascom_size )) $$(( $(ROM_SIZE) )) >&2; exit 1; \
|
|
||||||
fi; \
|
|
||||||
dd if=$< of=$@ bs=1 count=$$(( $(NASCOM_BASIC_ROM_ADDRESS) )) status=none; \
|
|
||||||
dd if=$< of=$@ bs=1 skip=$$(( $(RESIDENT_RAM_BASE) )) seek=$$(( $(RESIDENT_ROM_OFFSET) )) count=$$payload_size conv=notrunc status=none; \
|
|
||||||
dd if=$(TINYBASIC_BUILD)/tinybasic.bin of=$@ bs=1 seek=$$(( $(TINYBASIC_ROM_ADDRESS) )) conv=notrunc status=none; \
|
|
||||||
dd if=$(TAPE_LOADER_BUILD)/tape_loader.bin of=$@ bs=1 seek=$$(( $(TAPE_LOADER_ROM_ADDRESS) )) conv=notrunc status=none; \
|
|
||||||
dd if=$(NASCOM_BASIC_BUILD)/nascom_basic.bin of=$@ bs=1 seek=$$(( $(NASCOM_BASIC_ROM_ADDRESS) )) conv=notrunc status=none
|
|
||||||
|
|
||||||
$(BUILDDIR)/metal/$(TARGET).bin: $(BUILDDIR)/metal/$(TARGET).flat.bin $(BUILDDIR)/metal/$(TARGET).prog.bin
|
|
||||||
dd if=$< of=$@ bs=$(METAL_BIN_SIZE) count=1 status=none
|
|
||||||
dd if=$(BUILDDIR)/metal/$(TARGET).prog.bin of=$@ conv=notrunc status=none
|
|
||||||
|
|
||||||
$(BUILDDIR)/metal/%.rel: %.c $(LAYOUT_HEADER)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
$(METAL_CC) $(METAL_FLAGS) $(METAL_CPPFLAGS) -o $@ -c $<
|
|
||||||
|
|
||||||
$(BUILDDIR)/metal/%.rel: %.asm $(LAYOUT_INCLUDE)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
$(METAL_AS) -Iinclude -I$(LAYOUT_BUILD) -plosgff -o $@ $<
|
|
||||||
|
|
||||||
$(TINYBASIC_BUILD)/tinybasic.rel: $(TINYBASIC_SRC) $(LAYOUT_INCLUDE)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
$(METAL_AS) -Iinclude -I$(LAYOUT_BUILD) -plosgff -o $@ $<
|
|
||||||
|
|
||||||
$(TINYBASIC_BUILD)/tinybasic.ihx: $(TINYBASIC_BUILD)/tinybasic.rel
|
|
||||||
sdldz80 -n -i -m -j $(basename $@) $<
|
|
||||||
|
|
||||||
$(TINYBASIC_BUILD)/tinybasic.flat.bin: $(TINYBASIC_BUILD)/tinybasic.ihx
|
|
||||||
$(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@
|
|
||||||
|
|
||||||
$(TINYBASIC_BUILD)/tinybasic.bin: $(TINYBASIC_BUILD)/tinybasic.flat.bin
|
|
||||||
@tiny_size=$$(awk '$$1 == "TINYBASIC0" { print $$5 + 0 }' $(TINYBASIC_BUILD)/tinybasic.map); \
|
|
||||||
test $$tiny_size -gt 0; \
|
|
||||||
dd if=$< of=$@ bs=1 skip=$$(( $(TINYBASIC_ROM_ADDRESS) )) count=$$tiny_size status=none
|
|
||||||
|
|
||||||
$(TAPE_LOADER_BUILD)/tape_loader.rel: $(TAPE_LOADER_SRC) $(LAYOUT_INCLUDE)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
$(METAL_AS) -Iinclude -I$(LAYOUT_BUILD) -plosgff -o $@ $<
|
|
||||||
|
|
||||||
$(TAPE_LOADER_BUILD)/tape_loader.ihx: $(TAPE_LOADER_BUILD)/tape_loader.rel
|
|
||||||
sdldz80 -n -i -m -j $(basename $@) $<
|
|
||||||
|
|
||||||
$(TAPE_LOADER_BUILD)/tape_loader.flat.bin: $(TAPE_LOADER_BUILD)/tape_loader.ihx
|
|
||||||
$(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@
|
|
||||||
|
|
||||||
$(TAPE_LOADER_BUILD)/tape_loader.bin: $(TAPE_LOADER_BUILD)/tape_loader.flat.bin
|
|
||||||
@loader_size=$$(awk '$$1 == "TAPELOAD0" { print $$5 + 0 }' $(TAPE_LOADER_BUILD)/tape_loader.map); \
|
|
||||||
test $$loader_size -gt 0; \
|
|
||||||
dd if=$< of=$@ bs=1 skip=$$(( $(TAPE_LOADER_ROM_ADDRESS) )) count=$$loader_size status=none
|
|
||||||
|
|
||||||
$(NASCOM_BASIC_BUILD)/nascom_basic.bin: $(NASCOM_BASIC_SRC)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
zasm --z80 $< -o $@ -l0
|
|
||||||
|
|
||||||
$(LAYOUT_HEADER): $(LAYOUT_CONFIG)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
@awk 'BEGIN { print "#ifndef ROM_LAYOUT_H"; print "#define ROM_LAYOUT_H"; print "" } \
|
|
||||||
/^[A-Z][A-Z0-9_]*[[:space:]]*:=[[:space:]]*/ { print "#define " $$1 " " $$3 } \
|
|
||||||
END { print ""; print "#endif" }' $< > $@
|
|
||||||
|
|
||||||
$(LAYOUT_INCLUDE): $(LAYOUT_CONFIG)
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
@awk 'BEGIN { print "; Generated from rom_layout.mk; do not edit." } \
|
|
||||||
/^[A-Z][A-Z0-9_]*[[:space:]]*:=[[:space:]]*/ { print $$1 " = " $$3 }' $< > $@
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILDDIR)
|
rm -rf $(BUILDDIR)
|
||||||
|
|
||||||
-include $(EMU_DEPFILES)
|
$(BUILDDIR):
|
||||||
|
mkdir -p $(BUILDDIR)
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.bin: ./%.asm $(BUILDDIR)/memory.inc
|
||||||
|
echo '>$<'
|
||||||
|
$(ASM) $(ASMFLAGS) $< $@
|
||||||
|
|
||||||
|
size:
|
||||||
|
@for file in $(TARGETS); do \
|
||||||
|
if [ -f "$$file" ]; then \
|
||||||
|
echo "$$file: $$(wc -c < "$$file") bytes"; \
|
||||||
|
else \
|
||||||
|
echo "$$file: Not built yet (run 'make')"; \
|
||||||
|
fi \
|
||||||
|
done
|
||||||
|
$(MAKE) -C apps/basic size --no-print-directory
|
||||||
|
|
||||||
|
$(BUILDDIR)/basic.bin:
|
||||||
|
$(MAKE) -C apps/basic $(or $(MAKECMDGOALS),all) --no-print-directory
|
||||||
|
|
||||||
|
$(BUILDDIR)/memory.inc: memory.mk
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
@awk 'BEGIN { print "; Generated from rom_layout.mk; do not edit." } \
|
||||||
|
/^[A-Z][A-Z0-9_]*[[:space:]]*:=[[:space:]]*/ { print $$1 " .equ " $$3 }' $< > $@
|
||||||
|
|
||||||
|
$(BUILDDIR)/rom.bin: $(BUILDDIR)/bios.bin $(BUILDDIR)/basic.bin
|
||||||
|
objcopy --pad-to=$$(($(BIOS_ROM_START)+$(BIOS_ROM_SIZE))) \
|
||||||
|
-I binary $(BUILDDIR)/bios.bin -O binary $(BUILDDIR)/bios_padded.bin
|
||||||
|
objcopy --pad-to=$(BASIC_ROM_SIZE) \
|
||||||
|
-I binary $(BUILDDIR)/basic.bin -O binary $(BUILDDIR)/basic_padded.bin
|
||||||
|
cat $(BUILDDIR)/bios_padded.bin $(BUILDDIR)/basic_padded.bin > $(BUILDDIR)/rom.bin
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: $(TOPTARGETS) $(SUBDIRS)
|
||||||
|
|
||||||
|
|||||||
@@ -1,214 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
`D` prints 16 bytes per row followed by an ASCII column. Bytes outside the
|
|
||||||
printable ASCII range are shown as `.`.
|
|
||||||
|
|
||||||
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,967 bytes. Its ROM layout is:
|
|
||||||
|
|
||||||
- `0000-007F`: reset/copy bootstrap
|
|
||||||
- `0080-12DD`: packed BIOS+monitor image copied to RAM at `C000-D25D`
|
|
||||||
- `12DE-13FF`: unused space reserved for the resident image to grow
|
|
||||||
- `1400-1C0C`: Tiny BASIC, executed directly from ROM
|
|
||||||
- `1C0D-2FFF`: unused ROM
|
|
||||||
- `3000-32A6`: tape loader, executed directly from ROM
|
|
||||||
- `32A7-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-D25D`: resident BIOS, vectors, services, and monitor
|
|
||||||
- `D25E-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 |
|
|
||||||
| `C06E` | `BIOS_FLUSH` | Wait until the UART output queue is empty |
|
|
||||||
|
|
||||||
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-32A6`; the first
|
|
||||||
unused byte after it is `32A7`.
|
|
||||||
|
|
||||||
## 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. Signal acquisition is bounded; a missing,
|
|
||||||
unstable, or permanently high signal reports `TAPE SIGNAL TIMEOUT` and returns
|
|
||||||
to the monitor instead of waiting forever with interrupts disabled.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# 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:
|
|
||||||
|
|
||||||
- `1400-1C0C`: Tiny BASIC
|
|
||||||
- `1C0D-2FFF`: available
|
|
||||||
- `3000-32A6`: tape loader
|
|
||||||
- `32A7-7FFF`: available for more applications (`3300` 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. Applications may reuse this lower-RAM
|
|
||||||
allocation because they run one at a time, but they 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.
|
|
||||||
|
|
||||||
Use `BIOS_BANK`, `BIOS_ROM_DISABLE`, and `BIOS_ROM_ENABLE` for the write-only
|
|
||||||
banking latch rather than writing port `00` directly. The BIOS keeps the
|
|
||||||
ROM-disable state when the RAM bank selection changes. A ROM application must
|
|
||||||
not expect a normal `RET` after it maps out the ROM containing its return
|
|
||||||
address.
|
|
||||||
|
|
||||||
Application origins and the resident-memory layout are defined centrally in
|
|
||||||
`rom_layout.mk`. The build turns that file into C and assembly includes. To add
|
|
||||||
an application, add its address there, add its build and overlay rules to the
|
|
||||||
Makefile, and add a monitor command or use `G address` to launch it.
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
Name casette;
|
|
||||||
PartNo 00;
|
|
||||||
Date 2026-10-03;
|
|
||||||
Revision 01;
|
|
||||||
Designer joppe;
|
|
||||||
Company unknown;
|
|
||||||
Assembly None;
|
|
||||||
Location unknown;
|
|
||||||
Device g22v10;
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------
|
|
||||||
Constants
|
|
||||||
------------------------------------------------------------ */
|
|
||||||
|
|
||||||
ar = 'b'0;
|
|
||||||
sp = 'b'0;
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------
|
|
||||||
Pin assignments
|
|
||||||
|
|
||||||
Physical signals IOSEL, RD and WR are active-low.
|
|
||||||
Their polarity is expressed in the equations with !.
|
|
||||||
------------------------------------------------------------ */
|
|
||||||
|
|
||||||
/* inputs */
|
|
||||||
|
|
||||||
PIN 1 = CLK;
|
|
||||||
|
|
||||||
PIN 2 = A1;
|
|
||||||
PIN 3 = A2;
|
|
||||||
|
|
||||||
PIN 4 = IOSEL;
|
|
||||||
PIN 5 = RD;
|
|
||||||
PIN 6 = A0;
|
|
||||||
|
|
||||||
PIN 7 = COMP;
|
|
||||||
PIN 8 = WR;
|
|
||||||
|
|
||||||
|
|
||||||
/* data bus */
|
|
||||||
|
|
||||||
PIN 14 = D0;
|
|
||||||
PIN 15 = D1;
|
|
||||||
PIN 16 = D2;
|
|
||||||
PIN 17 = D3;
|
|
||||||
PIN 18 = D4;
|
|
||||||
PIN 19 = D5;
|
|
||||||
PIN 20 = D6;
|
|
||||||
PIN 21 = D7;
|
|
||||||
|
|
||||||
|
|
||||||
/* registered output */
|
|
||||||
|
|
||||||
PIN 22 = LED;
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------
|
|
||||||
Read register at offset 0
|
|
||||||
|
|
||||||
Conditions:
|
|
||||||
|
|
||||||
/IOSEL = 0
|
|
||||||
/RD = 0
|
|
||||||
/WR = 1
|
|
||||||
A2:A0 = 000
|
|
||||||
|
|
||||||
Result:
|
|
||||||
|
|
||||||
D0 = COMP
|
|
||||||
D7..D1 = 0
|
|
||||||
------------------------------------------------------------ */
|
|
||||||
|
|
||||||
D0 = COMP;
|
|
||||||
|
|
||||||
D1 = 'b'0;
|
|
||||||
D2 = 'b'0;
|
|
||||||
D3 = 'b'0;
|
|
||||||
D4 = 'b'0;
|
|
||||||
D5 = 'b'0;
|
|
||||||
D6 = 'b'0;
|
|
||||||
D7 = 'b'0;
|
|
||||||
|
|
||||||
|
|
||||||
D0.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
D1.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
D2.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
D3.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
D4.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
D5.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
D6.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
D7.OE =
|
|
||||||
!IOSEL
|
|
||||||
& !RD
|
|
||||||
& WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0;
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------
|
|
||||||
Writable LED register
|
|
||||||
|
|
||||||
Write conditions:
|
|
||||||
|
|
||||||
/IOSEL = 0
|
|
||||||
/RD = 1
|
|
||||||
/WR = 0
|
|
||||||
A2:A0 = 000
|
|
||||||
|
|
||||||
During such a write:
|
|
||||||
|
|
||||||
LED.D = D0
|
|
||||||
|
|
||||||
Otherwise:
|
|
||||||
|
|
||||||
LED.D = LED
|
|
||||||
|
|
||||||
Pin 1 (CLK) clocks the registered macrocell.
|
|
||||||
------------------------------------------------------------ */
|
|
||||||
|
|
||||||
LED.D =
|
|
||||||
(
|
|
||||||
!IOSEL
|
|
||||||
& RD
|
|
||||||
& !WR
|
|
||||||
& !A2
|
|
||||||
& !A1
|
|
||||||
& !A0
|
|
||||||
& D0
|
|
||||||
)
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
(
|
|
||||||
(
|
|
||||||
IOSEL
|
|
||||||
# !RD
|
|
||||||
# WR
|
|
||||||
# A2
|
|
||||||
# A1
|
|
||||||
# A0
|
|
||||||
)
|
|
||||||
& LED
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
LED.ar = ar;
|
|
||||||
LED.sp = sp;
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
CUPL(WM) 5.0a Serial# MW-10400000
|
|
||||||
Device g22v10 Library DLIB-h-40-1
|
|
||||||
Created Sat Oct 03 16:22:02 2026
|
|
||||||
Name casette
|
|
||||||
Partno 00
|
|
||||||
Revision 01
|
|
||||||
Date 2026-10-03
|
|
||||||
Designer joppe
|
|
||||||
Company unknown
|
|
||||||
Assembly None
|
|
||||||
Location unknown
|
|
||||||
*QP24
|
|
||||||
*QF5892
|
|
||||||
*G1
|
|
||||||
*F0
|
|
||||||
*L00416 00000000000000000000000011111111
|
|
||||||
*L00448 11111111111111111111111111111111
|
|
||||||
*L00480 11111111101110111011011110111111
|
|
||||||
*L00512 10111111110111111111111011110111
|
|
||||||
*L00544 11111111111111111111111111111111
|
|
||||||
*L00576 11101111111110111111111111111111
|
|
||||||
*L00608 11111111111111101111111111111111
|
|
||||||
*L00640 11110111111111111111111111100111
|
|
||||||
*L00672 11111111111111111111111111111111
|
|
||||||
*L00704 11110110111111111111111111111111
|
|
||||||
*L00736 11111111111111111110111111111111
|
|
||||||
*L00768 01111111111111111111111100000000
|
|
||||||
*L00896 00000000000000000000000000001111
|
|
||||||
*L00928 10111011101110111011111101111111
|
|
||||||
*L00960 11111111000000000000000000000000
|
|
||||||
*L01472 00000000000000000000000011111011
|
|
||||||
*L01504 10111011101110111111011111111111
|
|
||||||
*L01536 11110000000000000000000000000000
|
|
||||||
*L02144 00000000000011111011101110111011
|
|
||||||
*L02176 10111111011111111111111100000000
|
|
||||||
*L02880 00000000000000000000000011111011
|
|
||||||
*L02912 10111011101110111111011111111111
|
|
||||||
*L02944 11110000000000000000000000000000
|
|
||||||
*L03648 00001111101110111011101110111111
|
|
||||||
*L03680 01111111111111110000000000000000
|
|
||||||
*L04288 00000000000000000000000011111011
|
|
||||||
*L04320 10111011101110111111011111111111
|
|
||||||
*L04352 11110000000000000000000000000000
|
|
||||||
*L04864 00000000000000000000111110111011
|
|
||||||
*L04896 10111011101111110111111111111111
|
|
||||||
*L05344 00000000000000000000000011111011
|
|
||||||
*L05376 10111011101110111111011111111111
|
|
||||||
*L05408 11111111111111111111111111110111
|
|
||||||
*L05440 11111111111111110000000000000000
|
|
||||||
*L05792 00000000000000000010111111111111
|
|
||||||
*L05824 11110011000000110000000000000000
|
|
||||||
*C5BE4
|
|
||||||
*6A87
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import struct
|
|
||||||
import wave
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
class PulseWriter:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
sample_rate: int,
|
|
||||||
t_short_us: float,
|
|
||||||
t_long_us: float,
|
|
||||||
amplitude: float,
|
|
||||||
) -> None:
|
|
||||||
self.sample_rate = sample_rate
|
|
||||||
self.short_samples = self._duration_samples(t_short_us)
|
|
||||||
self.long_samples = self._duration_samples(t_long_us)
|
|
||||||
self.high = round(32767 * amplitude)
|
|
||||||
self.low = -self.high
|
|
||||||
self.samples = bytearray()
|
|
||||||
|
|
||||||
self.space(0.1)
|
|
||||||
|
|
||||||
def space(self, time: float) -> None:
|
|
||||||
self._level(0, int(time*self.sample_rate))
|
|
||||||
|
|
||||||
def _duration_samples(self, duration_us: float) -> int:
|
|
||||||
sample_count = round(duration_us * self.sample_rate / 1_000_000)
|
|
||||||
if sample_count < 1:
|
|
||||||
raise ValueError(
|
|
||||||
f"pulse duration {duration_us:g} us is shorter than one sample"
|
|
||||||
)
|
|
||||||
return sample_count
|
|
||||||
|
|
||||||
def _level(self, value: int, sample_count: int) -> None:
|
|
||||||
self.samples.extend(struct.pack("<h", value) * sample_count)
|
|
||||||
|
|
||||||
def short_pulse(self) -> None:
|
|
||||||
self._level(self.high, self.short_samples)
|
|
||||||
self._level(self.low, self.long_samples)
|
|
||||||
|
|
||||||
def long_pulse(self) -> None:
|
|
||||||
self._level(self.high, self.long_samples)
|
|
||||||
self._level(self.low, self.short_samples)
|
|
||||||
|
|
||||||
def marker(self) -> None:
|
|
||||||
self._level(self.high, 2*self.long_samples)
|
|
||||||
self._level(self.low, 2*self.long_samples)
|
|
||||||
|
|
||||||
def bit(self, value: int) -> None:
|
|
||||||
if value:
|
|
||||||
self.long_pulse()
|
|
||||||
else:
|
|
||||||
self.short_pulse()
|
|
||||||
|
|
||||||
def byte(self, value: int) -> None:
|
|
||||||
# Transmit bytes most-significant bit first.
|
|
||||||
for bit_number in range(7, -1, -1):
|
|
||||||
self.bit((value >> bit_number) & 1)
|
|
||||||
|
|
||||||
def bytes(self, value: bytes) -> None:
|
|
||||||
for byte in value:
|
|
||||||
self.byte(byte)
|
|
||||||
|
|
||||||
def leader(self) -> None:
|
|
||||||
self.space(0.2)
|
|
||||||
for _ in range(50):
|
|
||||||
self._level(self.high, self.short_samples)
|
|
||||||
self._level(self.low, self.short_samples)
|
|
||||||
for _ in range(50):
|
|
||||||
self._level(self.high, self.long_samples)
|
|
||||||
self._level(self.low, self.long_samples)
|
|
||||||
self.marker()
|
|
||||||
for value in (0x55, 0x5A, 0xC3):
|
|
||||||
self.byte(value)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Generate the pulse-width encoded tape leader"
|
|
||||||
)
|
|
||||||
parser.add_argument("output", type=Path, help="output WAV file")
|
|
||||||
parser.add_argument("--sample-rate", type=int, default=48_000)
|
|
||||||
parser.add_argument(
|
|
||||||
"--t-short",
|
|
||||||
type=float,
|
|
||||||
default=250.0,
|
|
||||||
metavar="MICROSECONDS",
|
|
||||||
help="short pulse interval in microseconds (default: 250)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--t-long",
|
|
||||||
type=float,
|
|
||||||
default=500.0,
|
|
||||||
metavar="MICROSECONDS",
|
|
||||||
help="long pulse interval in microseconds (default: 500)",
|
|
||||||
)
|
|
||||||
parser.add_argument("--amplitude", type=float, default=0.75)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.sample_rate <= 0:
|
|
||||||
parser.error("--sample-rate must be positive")
|
|
||||||
if args.t_short <= 0 or args.t_long <= 0:
|
|
||||||
parser.error("pulse intervals must be positive")
|
|
||||||
if args.t_short >= args.t_long:
|
|
||||||
parser.error("--t-short must be less than --t-long")
|
|
||||||
if not 0.0 <= args.amplitude <= 1.0:
|
|
||||||
parser.error("--amplitude must be between 0 and 1")
|
|
||||||
|
|
||||||
try:
|
|
||||||
pulses = PulseWriter(
|
|
||||||
args.sample_rate,
|
|
||||||
args.t_short,
|
|
||||||
args.t_long,
|
|
||||||
args.amplitude,
|
|
||||||
)
|
|
||||||
except ValueError as error:
|
|
||||||
parser.error(str(error))
|
|
||||||
|
|
||||||
pulses.leader()
|
|
||||||
pulses.bytes(b'Hello world!\0')
|
|
||||||
|
|
||||||
pulses.space(1.0)
|
|
||||||
with wave.open(str(args.output), "wb") as wav:
|
|
||||||
wav.setnchannels(1)
|
|
||||||
wav.setsampwidth(2)
|
|
||||||
wav.setframerate(args.sample_rate)
|
|
||||||
wav.writeframes(pulses.samples)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,569 +0,0 @@
|
|||||||
.include "bios.inc"
|
|
||||||
|
|
||||||
.area TAPELOAD (ABS)
|
|
||||||
.org TAPE_LOADER_ROM_ADDRESS
|
|
||||||
|
|
||||||
.macro print_var arg1
|
|
||||||
ld a, (arg1)
|
|
||||||
ei
|
|
||||||
call print_hex8
|
|
||||||
call print_crlf
|
|
||||||
di
|
|
||||||
.endm
|
|
||||||
|
|
||||||
.macro error_ret arg1,arg2
|
|
||||||
arg1:
|
|
||||||
ld a,#arg2
|
|
||||||
scf
|
|
||||||
ret
|
|
||||||
.endm
|
|
||||||
|
|
||||||
TAPE_PORT = 0x18
|
|
||||||
MAX_BLOCK_SIZE = 0x100
|
|
||||||
|
|
||||||
STACK_TOP = 0xBFFF
|
|
||||||
WORK_BASE = 0xB000
|
|
||||||
|
|
||||||
OLD_SP = WORK_BASE + 0x00 ; word
|
|
||||||
OLD_IFF = WORK_BASE + 0x02 ; byte
|
|
||||||
SHORT_AVG = WORK_BASE + 0x03 ; byte
|
|
||||||
SHORT_MAX = WORK_BASE + 0x04 ; byte
|
|
||||||
SHORT_MIN = WORK_BASE + 0x05 ; byte
|
|
||||||
LONG_AVG = WORK_BASE + 0x06 ; byte
|
|
||||||
LONG_MAX = WORK_BASE + 0x07 ; byte
|
|
||||||
LONG_MIN = WORK_BASE + 0x08 ; byte
|
|
||||||
|
|
||||||
BLOCK_BUFFER = WORK_BASE + 0x100 ; MAX_BLOCK_SIZE
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Entry
|
|
||||||
; #################################################3
|
|
||||||
tape_loader:
|
|
||||||
ld (OLD_SP),sp
|
|
||||||
; Save interrupt state.
|
|
||||||
ld a,i
|
|
||||||
jp po,iff_was_off
|
|
||||||
ld a,#1
|
|
||||||
jr save_iff
|
|
||||||
iff_was_off:
|
|
||||||
xor a
|
|
||||||
save_iff:
|
|
||||||
ld (OLD_IFF),a
|
|
||||||
di
|
|
||||||
ld sp,#STACK_TOP
|
|
||||||
|
|
||||||
main:
|
|
||||||
; Initial message.
|
|
||||||
ld hl,#msg_start
|
|
||||||
call print_string_ei
|
|
||||||
|
|
||||||
; Calibrate reader
|
|
||||||
call calibrate
|
|
||||||
jr nc,1$
|
|
||||||
ei
|
|
||||||
call print_hex8
|
|
||||||
call print_crlf
|
|
||||||
ld hl,#msg_cal_fail
|
|
||||||
call print_string
|
|
||||||
di
|
|
||||||
jp exit
|
|
||||||
1$:
|
|
||||||
; try to read the sync bytes
|
|
||||||
call read_sync
|
|
||||||
jr nc,2$
|
|
||||||
ld hl,#msg_sync_error
|
|
||||||
call print_string_ei
|
|
||||||
jp exit
|
|
||||||
2$:
|
|
||||||
|
|
||||||
; Test, read Hello World!\0 (13 bytes)
|
|
||||||
exx
|
|
||||||
ld e,#0
|
|
||||||
ld hl,#BLOCK_BUFFER
|
|
||||||
exx
|
|
||||||
4$:
|
|
||||||
call read_byte
|
|
||||||
jr nc,5$
|
|
||||||
ld hl,#msg_read_error
|
|
||||||
call print_string_ei
|
|
||||||
exx
|
|
||||||
ei
|
|
||||||
call print_hex16
|
|
||||||
call print_crlf
|
|
||||||
di
|
|
||||||
exx
|
|
||||||
jp exit
|
|
||||||
5$:
|
|
||||||
exx
|
|
||||||
ld (hl),a
|
|
||||||
inc hl
|
|
||||||
inc e
|
|
||||||
ld a,e
|
|
||||||
cp a,#13
|
|
||||||
exx
|
|
||||||
jr nz,4$
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Exit
|
|
||||||
; #################################################3
|
|
||||||
exit:
|
|
||||||
ei
|
|
||||||
call BIOS_FLUSH
|
|
||||||
di
|
|
||||||
ld sp,(OLD_SP)
|
|
||||||
; Return code 0
|
|
||||||
ld a,#0
|
|
||||||
ld c,a
|
|
||||||
; Restore caller's interrupt state.
|
|
||||||
ld a,(OLD_IFF)
|
|
||||||
or a
|
|
||||||
ld a,c
|
|
||||||
jr z,return_di
|
|
||||||
ei
|
|
||||||
ret
|
|
||||||
return_di:
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Measure edge-to-edge pulse
|
|
||||||
; needs d (comparator state)
|
|
||||||
; returns b (loop count)
|
|
||||||
; carry is timeout
|
|
||||||
; uses a
|
|
||||||
; loop is 36T, extra 22T (0.6 loops)
|
|
||||||
; #################################################3
|
|
||||||
measure_pulse:
|
|
||||||
ld b,#0 ; 4T
|
|
||||||
1$:
|
|
||||||
; Use b as timeout counter (256 loops)
|
|
||||||
inc b ; 4T
|
|
||||||
jr z,9$ ; 12/7T
|
|
||||||
in a,(TAPE_PORT) ; 11T
|
|
||||||
cp a,d ; 4T
|
|
||||||
jp z,1$ ; 10T
|
|
||||||
; loop has 36T
|
|
||||||
; Edge found
|
|
||||||
ld d,a ; 4T
|
|
||||||
or a,a ; 4T
|
|
||||||
ret ; 10T
|
|
||||||
9$:
|
|
||||||
scf
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Ignore one pulse then measure next pulse
|
|
||||||
; needs d (comparator state)
|
|
||||||
; returns b (loop count)
|
|
||||||
; carry is timeout
|
|
||||||
; uses a
|
|
||||||
; loop is 36T, extra 26T (0.7 loops)
|
|
||||||
; #################################################3
|
|
||||||
sample_pulse:
|
|
||||||
; wait for first edge
|
|
||||||
ld b,#0 ; 4T
|
|
||||||
1$:
|
|
||||||
; Use b as timeout counter (256 loops)
|
|
||||||
inc b ; 4T
|
|
||||||
jr z,9$ ; 12/7T
|
|
||||||
in a,(TAPE_PORT) ; 11T
|
|
||||||
cp a,d ; 4T
|
|
||||||
jp z,1$ ; 10T
|
|
||||||
; loop has 36T
|
|
||||||
; Edge found
|
|
||||||
ld d,a ; 4T
|
|
||||||
; start timing pulse length
|
|
||||||
ld b,#0 ; 4T
|
|
||||||
2$:
|
|
||||||
; Use b as timeout counter (256 loops)
|
|
||||||
inc b ; 4T
|
|
||||||
jr z,9$ ; 12/7T
|
|
||||||
in a,(TAPE_PORT) ; 11T
|
|
||||||
cp a,d ; 4T
|
|
||||||
jp z,2$ ; 10T
|
|
||||||
; loop has 36T
|
|
||||||
; Edge found
|
|
||||||
ld d,a ; 4T
|
|
||||||
or a,a ; 4T
|
|
||||||
ret ; 10T
|
|
||||||
9$:
|
|
||||||
scf
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Average 8 pulse measurements
|
|
||||||
; needs d (comparator state)
|
|
||||||
; returns a (average)
|
|
||||||
; carry is timeout
|
|
||||||
; uses b,e,h
|
|
||||||
; #################################################3
|
|
||||||
average8:
|
|
||||||
ld e,#0
|
|
||||||
ld h,#8
|
|
||||||
1$:
|
|
||||||
; synchronize to next pulse
|
|
||||||
; ignore first measurement
|
|
||||||
; contains part of the calculations
|
|
||||||
call sample_pulse
|
|
||||||
call sample_pulse
|
|
||||||
; when timeout just return with carry
|
|
||||||
ret c
|
|
||||||
; Add loop count to e
|
|
||||||
ld a,e
|
|
||||||
add a,b
|
|
||||||
ld e,a
|
|
||||||
; repeat 8 times
|
|
||||||
dec h
|
|
||||||
jr nz,1$
|
|
||||||
; divide by 8
|
|
||||||
ld a,e
|
|
||||||
srl a
|
|
||||||
srl a
|
|
||||||
srl a
|
|
||||||
; clear carry
|
|
||||||
or a,a
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Make +/-25% window around a
|
|
||||||
; needs a (average)
|
|
||||||
; returns b (minimum) c (maximum)
|
|
||||||
; uses e,h
|
|
||||||
; NOTE: USES 8 BIT COUNTER
|
|
||||||
; @1MHZ t_long<1.14ms
|
|
||||||
; @2MHZ t_long<571us
|
|
||||||
; @4MHZ t_long<285us
|
|
||||||
; TODO: Check feasibility of using 16bit accumulator
|
|
||||||
; #################################################3
|
|
||||||
make_window:
|
|
||||||
ld e,a
|
|
||||||
; get 25%
|
|
||||||
srl a
|
|
||||||
srl a
|
|
||||||
ld h,a
|
|
||||||
; a-25%
|
|
||||||
ld a,e
|
|
||||||
sub h
|
|
||||||
ld b,a
|
|
||||||
; a+25%
|
|
||||||
ld a,e
|
|
||||||
add a,h
|
|
||||||
ld c,a
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Calibration
|
|
||||||
; #################################################3
|
|
||||||
calibrate:
|
|
||||||
; Get current comparator state
|
|
||||||
in a,(TAPE_PORT)
|
|
||||||
ld d,a
|
|
||||||
|
|
||||||
; make sure we start in silence
|
|
||||||
call sample_pulse
|
|
||||||
; should timeout
|
|
||||||
jp nc,cal_fail_no_silence
|
|
||||||
|
|
||||||
; Wait for first edge
|
|
||||||
ld l,#0
|
|
||||||
1$:
|
|
||||||
call sample_pulse
|
|
||||||
jr nc,2$
|
|
||||||
; Use b as timeout counter (256 loops)
|
|
||||||
; inc l
|
|
||||||
; jr nz,1$
|
|
||||||
jr 1$
|
|
||||||
; timeout
|
|
||||||
;jp cal_fail_no_short_leader ; TODO IGNORED TIMEOUT FOR NOW HERE
|
|
||||||
2$:
|
|
||||||
; edge found
|
|
||||||
|
|
||||||
; get short leader timing
|
|
||||||
call average8
|
|
||||||
jr c,cal_fail_short
|
|
||||||
ld (SHORT_AVG),a
|
|
||||||
|
|
||||||
call make_window
|
|
||||||
ld a,b
|
|
||||||
ld (SHORT_MIN),a
|
|
||||||
ld a,c
|
|
||||||
ld (SHORT_MAX),a
|
|
||||||
|
|
||||||
; find long leader
|
|
||||||
; need 4 consecutive pulses > short*1.5
|
|
||||||
ld a,(SHORT_AVG)
|
|
||||||
ld c,a
|
|
||||||
srl a
|
|
||||||
add a,c
|
|
||||||
ld c,a
|
|
||||||
; c = short*1.5
|
|
||||||
|
|
||||||
; TODO no overal timeout here. a continuous
|
|
||||||
; short pulse leader will keep on waiting
|
|
||||||
; forever
|
|
||||||
3$:
|
|
||||||
ld e,#4
|
|
||||||
4$:
|
|
||||||
; ignore first to get accurate measurement
|
|
||||||
call sample_pulse
|
|
||||||
call sample_pulse
|
|
||||||
jr c,cal_fail_find_long
|
|
||||||
ld a,b
|
|
||||||
; if it is no long pulse try again
|
|
||||||
cp a,c
|
|
||||||
jr c,3$
|
|
||||||
; long pulse found
|
|
||||||
dec e
|
|
||||||
jr nz,4$
|
|
||||||
; 4 (or well... 8) long pulses found
|
|
||||||
|
|
||||||
; get long leader timning
|
|
||||||
call average8
|
|
||||||
jr c,cal_fail_long
|
|
||||||
ld (LONG_AVG),a
|
|
||||||
|
|
||||||
call make_window
|
|
||||||
ld a,b
|
|
||||||
ld (LONG_MIN),a
|
|
||||||
ld a,c
|
|
||||||
ld (LONG_MAX),a
|
|
||||||
|
|
||||||
; A quick sanity check
|
|
||||||
; long > 1.5*short
|
|
||||||
ld a,(SHORT_AVG)
|
|
||||||
ld c,a
|
|
||||||
srl a
|
|
||||||
add a,c
|
|
||||||
ld c,a
|
|
||||||
; c = 1.5*short
|
|
||||||
ld a,(LONG_AVG)
|
|
||||||
cp a,c
|
|
||||||
jr c,cal_fail_sanity
|
|
||||||
|
|
||||||
; Find the marker
|
|
||||||
; marker is 2*long high 2*long low
|
|
||||||
; threshold is 1.5*long
|
|
||||||
ld a,(LONG_AVG)
|
|
||||||
ld c,a
|
|
||||||
srl a
|
|
||||||
add a,c
|
|
||||||
ld c,a
|
|
||||||
; c = 1.5*long
|
|
||||||
|
|
||||||
; Find two consecutive marker-width intervals.
|
|
||||||
5$:
|
|
||||||
ld e,#0
|
|
||||||
6$:
|
|
||||||
call measure_pulse
|
|
||||||
jr c,cal_fail
|
|
||||||
ld a,b
|
|
||||||
cp a,c
|
|
||||||
jr c,5$
|
|
||||||
inc e
|
|
||||||
ld a,e
|
|
||||||
cp a,#2
|
|
||||||
jr c,6$
|
|
||||||
; Both marker halves consumed. We are now at the
|
|
||||||
; beginning of the first data pulse.
|
|
||||||
|
|
||||||
; next pulse is first pulse of training byte
|
|
||||||
; must decode as 0x55
|
|
||||||
call read_byte
|
|
||||||
jr c,cal_fail_read_error
|
|
||||||
cp a,#0x55
|
|
||||||
jr nz,cal_fail_training_byte
|
|
||||||
|
|
||||||
ld a,#0
|
|
||||||
or a,a
|
|
||||||
ret
|
|
||||||
|
|
||||||
error_ret cal_fail 0
|
|
||||||
error_ret cal_fail_short 1
|
|
||||||
error_ret cal_fail_find_long 2
|
|
||||||
error_ret cal_fail_long 3
|
|
||||||
error_ret cal_fail_sanity 4
|
|
||||||
error_ret cal_fail_training_byte 5
|
|
||||||
error_ret cal_fail_no_silence 6
|
|
||||||
error_ret cal_fail_no_short_leader 7
|
|
||||||
error_ret cal_fail_read_error 8
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Read single bit
|
|
||||||
; needs d (comparator state)
|
|
||||||
; valid: z=1 c=bit
|
|
||||||
; error: z=0
|
|
||||||
; uses a,b,c
|
|
||||||
; #################################################3
|
|
||||||
read_bit:
|
|
||||||
; entry may be late into first pulse
|
|
||||||
; wait for first edge to measure
|
|
||||||
ld b,#0
|
|
||||||
1$:
|
|
||||||
inc b
|
|
||||||
jr z,bit_bad
|
|
||||||
in a,(TAPE_PORT)
|
|
||||||
cp a,d
|
|
||||||
jp z,1$
|
|
||||||
ld d,a
|
|
||||||
; ignored first edge
|
|
||||||
; measure second pulse
|
|
||||||
ld b,#0
|
|
||||||
2$:
|
|
||||||
inc b
|
|
||||||
jr z,bit_bad
|
|
||||||
in a,(TAPE_PORT)
|
|
||||||
cp a,d
|
|
||||||
jp z,2$
|
|
||||||
ld d,a
|
|
||||||
; if in short window (second half is short pulse)
|
|
||||||
; 1
|
|
||||||
ld a,(SHORT_MIN)
|
|
||||||
ld c,a
|
|
||||||
ld a,b
|
|
||||||
cp a,c
|
|
||||||
jr c,bit_bad ; pulse<SHORT_MIN
|
|
||||||
ld a,(SHORT_MAX)
|
|
||||||
ld c,a
|
|
||||||
ld a,b
|
|
||||||
cp a,c
|
|
||||||
jr c,bit_one
|
|
||||||
jr z,bit_one ; pulse<=SHORT_MAX
|
|
||||||
; test long window (second half is long pulse)
|
|
||||||
ld a,(LONG_MIN)
|
|
||||||
ld c,a
|
|
||||||
ld a,b
|
|
||||||
cp a,c
|
|
||||||
jr c,bit_bad ; pulse<LONG_MAX
|
|
||||||
ld a,(LONG_MAX)
|
|
||||||
ld c,a
|
|
||||||
ld a,b
|
|
||||||
cp a,c
|
|
||||||
jr c,bit_zero
|
|
||||||
jr z,bit_zero ; pulse<=LONG_MAX
|
|
||||||
bit_bad:
|
|
||||||
ld a,#1
|
|
||||||
or a,a
|
|
||||||
ret
|
|
||||||
bit_zero:
|
|
||||||
xor a,a
|
|
||||||
ret
|
|
||||||
bit_one:
|
|
||||||
xor a,a
|
|
||||||
scf
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Read byte, MSB first
|
|
||||||
; returns a (byte)
|
|
||||||
; carry means error
|
|
||||||
; needs d (comparator state)
|
|
||||||
; uses e,h,b,c
|
|
||||||
; #################################################3
|
|
||||||
read_byte:
|
|
||||||
ld e,#0
|
|
||||||
ld h,#8
|
|
||||||
1$:
|
|
||||||
call read_bit
|
|
||||||
jr nz,9$
|
|
||||||
; shift in the carry bit
|
|
||||||
rl e
|
|
||||||
; repeat 8 times
|
|
||||||
dec h
|
|
||||||
jr nz,1$
|
|
||||||
; byte done,
|
|
||||||
ld a,e
|
|
||||||
or a
|
|
||||||
ret
|
|
||||||
|
|
||||||
9$:
|
|
||||||
scf
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Read and check sync bytes
|
|
||||||
; carry is error
|
|
||||||
; #################################################3
|
|
||||||
read_sync:
|
|
||||||
call read_byte
|
|
||||||
ret c
|
|
||||||
cp a,#0x5a
|
|
||||||
jr nz,9$
|
|
||||||
call read_byte
|
|
||||||
ret c
|
|
||||||
cp a,#0xc3
|
|
||||||
jr nz,9$
|
|
||||||
or a,a
|
|
||||||
ret
|
|
||||||
9$:
|
|
||||||
scf
|
|
||||||
ret
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; IO Helper functions
|
|
||||||
; #################################################3
|
|
||||||
print_string_ei:
|
|
||||||
ei
|
|
||||||
call print_string
|
|
||||||
call BIOS_FLUSH
|
|
||||||
di
|
|
||||||
ret
|
|
||||||
|
|
||||||
print_string:
|
|
||||||
ld a,(hl)
|
|
||||||
or a
|
|
||||||
ret z
|
|
||||||
call BIOS_PUTC
|
|
||||||
inc hl
|
|
||||||
jr print_string
|
|
||||||
|
|
||||||
print_crlf:
|
|
||||||
ld a,#13
|
|
||||||
call BIOS_PUTC
|
|
||||||
ld a,#10
|
|
||||||
jp BIOS_PUTC
|
|
||||||
|
|
||||||
print_hex16:
|
|
||||||
push hl
|
|
||||||
ld a,h
|
|
||||||
call print_hex8
|
|
||||||
pop hl
|
|
||||||
ld a,l
|
|
||||||
call print_hex8
|
|
||||||
ret
|
|
||||||
|
|
||||||
print_hex8:
|
|
||||||
push af
|
|
||||||
rrca
|
|
||||||
rrca
|
|
||||||
rrca
|
|
||||||
rrca
|
|
||||||
and #0x0F
|
|
||||||
call print_hex_nibble
|
|
||||||
pop af
|
|
||||||
and #0x0F
|
|
||||||
|
|
||||||
; fall through
|
|
||||||
print_hex_nibble:
|
|
||||||
cp #10
|
|
||||||
jr c,print_hex_digit
|
|
||||||
add a,#('A' - 10)
|
|
||||||
jp BIOS_PUTC
|
|
||||||
|
|
||||||
print_hex_digit:
|
|
||||||
add a,#'0'
|
|
||||||
jp BIOS_PUTC
|
|
||||||
|
|
||||||
; #################################################3
|
|
||||||
; Strings
|
|
||||||
; #################################################3
|
|
||||||
msg_start:
|
|
||||||
.ascii "TAPE DECODE TEST - PLAY TAPE"
|
|
||||||
.db 13,10,0
|
|
||||||
msg_cal_fail:
|
|
||||||
.ascii "Calibration failed"
|
|
||||||
.db 13,10,0
|
|
||||||
msg_sync_error:
|
|
||||||
.ascii "Sync bytes not found"
|
|
||||||
.db 13,10,0
|
|
||||||
msg_read_error:
|
|
||||||
.ascii "read error"
|
|
||||||
.db 13,10,0
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
*.bin
|
|
||||||
*.lst
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,124 +0,0 @@
|
|||||||
#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_1 (CTC_BASE + 1)
|
|
||||||
#define CTC_CHANNEL_2 (CTC_BASE + 2)
|
|
||||||
#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);
|
|
||||||
|
|
||||||
/* A CPU-only reset does not necessarily reset the external CTC. Stop and
|
|
||||||
disable every channel before rebuilding the UART clock/IRQ path. */
|
|
||||||
io_out(CTC_CHANNEL_0, 0x03);
|
|
||||||
io_out(CTC_CHANNEL_1, 0x03);
|
|
||||||
io_out(CTC_CHANNEL_2, 0x03);
|
|
||||||
io_out(CTC_CHANNEL_3, 0x03);
|
|
||||||
|
|
||||||
/* The 6850 has no reset input; CR1:CR0 = 11 is its master reset. */
|
|
||||||
io_out(UART_CONTROL, 0b00000011);
|
|
||||||
|
|
||||||
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, 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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#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);
|
|
||||||
|
|
||||||
/* Write-only banking latch services. */
|
|
||||||
void bios_bank(uint8_t bank);
|
|
||||||
void bios_rom_disable(void);
|
|
||||||
void bios_rom_enable(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,413 +0,0 @@
|
|||||||
STACK_TOP = 0xFFFF
|
|
||||||
DELAY_LOOPS_PER_MS = 40
|
|
||||||
BANK_CONTROL_PORT = 0x00
|
|
||||||
BOOT_BANK_MAPPING = 0x06 ; ROM on, banking on, upper 16K at C000-FFFF
|
|
||||||
ROM_DISABLE_BIT = 0x01
|
|
||||||
BANK_SELECT_BIT = 0x02
|
|
||||||
BANK_ENABLE_BIT = 0x04
|
|
||||||
BANK_MODE_CLEAR_MASK = 0xF9 ; preserve ROM-disable and auxiliary outputs
|
|
||||||
|
|
||||||
.include "bios.inc"
|
|
||||||
|
|
||||||
.globl _main
|
|
||||||
.globl _delay_ms
|
|
||||||
.globl _io_out
|
|
||||||
.globl _io_in
|
|
||||||
.globl _enable_interrupts
|
|
||||||
.globl _disable_interrupts
|
|
||||||
.globl _jump_to
|
|
||||||
.globl _bios_putc
|
|
||||||
.globl _bios_try_getc
|
|
||||||
.globl _bios_getc
|
|
||||||
.globl _bios_flush
|
|
||||||
.globl _bios_init
|
|
||||||
.globl _bios_bank
|
|
||||||
.globl _bios_rom_disable
|
|
||||||
.globl _bios_rom_enable
|
|
||||||
.globl _bank_control_shadow
|
|
||||||
.globl resident_image_end
|
|
||||||
.globl s__DATA
|
|
||||||
.globl l__DATA
|
|
||||||
.globl s__INITIALIZER
|
|
||||||
.globl s__INITIALIZED
|
|
||||||
.globl l__INITIALIZER
|
|
||||||
|
|
||||||
.area _BOOT (ABS)
|
|
||||||
|
|
||||||
.org 0x00
|
|
||||||
jp boot_copy
|
|
||||||
.org 0x08
|
|
||||||
boot_copy:
|
|
||||||
di
|
|
||||||
; Keep ROM enabled while mapping the upper physical 16K at C000-FFFF.
|
|
||||||
; The resident copy therefore survives either banked mode after boot.
|
|
||||||
ld a, #BOOT_BANK_MAPPING
|
|
||||||
out (BANK_CONTROL_PORT), a
|
|
||||||
ld sp, #STACK_TOP
|
|
||||||
ld hl, #RESIDENT_ROM_OFFSET
|
|
||||||
ld de, #RESIDENT_RAM_BASE
|
|
||||||
ld bc, #(resident_image_end - RESIDENT_RAM_BASE)
|
|
||||||
ldir
|
|
||||||
jp init
|
|
||||||
|
|
||||||
.area _ITABLE (ABS)
|
|
||||||
|
|
||||||
; The executable vector page is part of the image copied to high RAM.
|
|
||||||
.org RESIDENT_RAM_BASE
|
|
||||||
jp init
|
|
||||||
.org RESIDENT_RAM_BASE + 0x08
|
|
||||||
reti
|
|
||||||
.org RESIDENT_RAM_BASE + 0x10
|
|
||||||
reti
|
|
||||||
.org RESIDENT_RAM_BASE + 0x18
|
|
||||||
reti
|
|
||||||
.org RESIDENT_RAM_BASE + 0x20
|
|
||||||
reti
|
|
||||||
.org RESIDENT_RAM_BASE + 0x28
|
|
||||||
reti
|
|
||||||
.org RESIDENT_RAM_BASE + 0x30
|
|
||||||
reti
|
|
||||||
.org RESIDENT_RAM_BASE + 0x38
|
|
||||||
reti
|
|
||||||
|
|
||||||
; IM 2 table: the CTC supplies vectors 40, 42, 44, and 46.
|
|
||||||
.org RESIDENT_RAM_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 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,
|
|
||||||
; C065 bank(A), C068 disable ROM, C06B enable ROM, C06E flush output.
|
|
||||||
.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
|
|
||||||
.org BIOS_BANK
|
|
||||||
jp _bios_bank
|
|
||||||
.org BIOS_ROM_DISABLE
|
|
||||||
jp _bios_rom_disable
|
|
||||||
.org BIOS_ROM_ENABLE
|
|
||||||
jp _bios_rom_enable
|
|
||||||
.org BIOS_FLUSH
|
|
||||||
jp bios_service_flush
|
|
||||||
|
|
||||||
; Port 00 is write-only. Keep its last BIOS-managed value outside the C data
|
|
||||||
; areas so a monitor warm start does not reset it. The bootstrap establishes
|
|
||||||
; this same value in the hardware before the resident image is copied.
|
|
||||||
.org RESIDENT_RAM_BASE + 0x73
|
|
||||||
_bank_control_shadow::
|
|
||||||
.db BOOT_BANK_MAPPING
|
|
||||||
|
|
||||||
.area _HOME
|
|
||||||
init:
|
|
||||||
di
|
|
||||||
ld sp, #STACK_TOP
|
|
||||||
|
|
||||||
; A reset may have interrupted a CTC service routine without executing
|
|
||||||
; RETI. Release any stale daisy-chain in-service latches before the
|
|
||||||
; channels are reinitialized. RETI acts as RET for each CALL while
|
|
||||||
; also signalling end-of-interrupt to daisy-chained peripherals.
|
|
||||||
call release_daisy_chain
|
|
||||||
call release_daisy_chain
|
|
||||||
call release_daisy_chain
|
|
||||||
call release_daisy_chain
|
|
||||||
|
|
||||||
call gsinit
|
|
||||||
|
|
||||||
; Setup interrupt mode 2
|
|
||||||
ld a, #0xC0
|
|
||||||
ld i, a
|
|
||||||
im 2
|
|
||||||
call _bios_init
|
|
||||||
call _main
|
|
||||||
|
|
||||||
hang:
|
|
||||||
jr hang
|
|
||||||
|
|
||||||
release_daisy_chain:
|
|
||||||
reti
|
|
||||||
|
|
||||||
.area _GSINIT
|
|
||||||
gsinit:
|
|
||||||
ld bc, #l__INITIALIZER
|
|
||||||
ld a, b
|
|
||||||
or c
|
|
||||||
jr z, zero_data
|
|
||||||
|
|
||||||
ld de, #s__INITIALIZED
|
|
||||||
ld hl, #s__INITIALIZER
|
|
||||||
ldir
|
|
||||||
|
|
||||||
zero_data:
|
|
||||||
ld bc, #l__DATA
|
|
||||||
ld a, b
|
|
||||||
or c
|
|
||||||
jr z, gsinit_done
|
|
||||||
|
|
||||||
ld hl, #s__DATA
|
|
||||||
xor a
|
|
||||||
|
|
||||||
clear_loop:
|
|
||||||
xor a ; BC loop test below changes A
|
|
||||||
ld (hl), a
|
|
||||||
inc hl
|
|
||||||
dec bc
|
|
||||||
ld a, b
|
|
||||||
or c
|
|
||||||
jr nz, clear_loop
|
|
||||||
|
|
||||||
gsinit_done:
|
|
||||||
.area _GSFINAL
|
|
||||||
ret
|
|
||||||
resident_image_end:
|
|
||||||
|
|
||||||
.area _CODE
|
|
||||||
|
|
||||||
; Select a RAM mapping while retaining the shadowed ROM-disable bit.
|
|
||||||
; Input: A=0 selects lower 48K + upper 16K; any other value selects upper 64K.
|
|
||||||
; Both modes keep banking enabled. A and flags are preserved.
|
|
||||||
_bios_bank::
|
|
||||||
push af
|
|
||||||
or a
|
|
||||||
jr z, bios_bank_split
|
|
||||||
|
|
||||||
ld a, (_bank_control_shadow)
|
|
||||||
and #BANK_MODE_CLEAR_MASK
|
|
||||||
or #BANK_ENABLE_BIT
|
|
||||||
jr bios_bank_update
|
|
||||||
|
|
||||||
bios_bank_split:
|
|
||||||
ld a, (_bank_control_shadow)
|
|
||||||
and #BANK_MODE_CLEAR_MASK
|
|
||||||
or #(BANK_ENABLE_BIT | BANK_SELECT_BIT)
|
|
||||||
|
|
||||||
bios_bank_update:
|
|
||||||
ld (_bank_control_shadow), a
|
|
||||||
out (BANK_CONTROL_PORT), a
|
|
||||||
pop af
|
|
||||||
ret
|
|
||||||
|
|
||||||
; Hide ROM while preserving the selected RAM mapping.
|
|
||||||
; A and flags are preserved.
|
|
||||||
_bios_rom_disable::
|
|
||||||
push af
|
|
||||||
ld a, (_bank_control_shadow)
|
|
||||||
or #ROM_DISABLE_BIT
|
|
||||||
jr bios_bank_update
|
|
||||||
|
|
||||||
; Make ROM visible again while preserving the selected RAM mapping.
|
|
||||||
; A and flags are preserved.
|
|
||||||
_bios_rom_enable::
|
|
||||||
push af
|
|
||||||
ld a, (_bank_control_shadow)
|
|
||||||
and #0xFE
|
|
||||||
jr bios_bank_update
|
|
||||||
|
|
||||||
; 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 _bios_putc
|
|
||||||
pop iy
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
pop de
|
|
||||||
pop bc
|
|
||||||
pop af
|
|
||||||
ret
|
|
||||||
|
|
||||||
bios_service_try_getc:
|
|
||||||
push bc
|
|
||||||
push de
|
|
||||||
push hl
|
|
||||||
push ix
|
|
||||||
push iy
|
|
||||||
call _bios_try_getc
|
|
||||||
pop iy
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
pop de
|
|
||||||
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_flush:
|
|
||||||
push af
|
|
||||||
push bc
|
|
||||||
push de
|
|
||||||
push hl
|
|
||||||
push ix
|
|
||||||
push iy
|
|
||||||
call _bios_flush
|
|
||||||
pop iy
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
pop de
|
|
||||||
pop bc
|
|
||||||
pop af
|
|
||||||
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:
|
|
||||||
push af
|
|
||||||
push bc
|
|
||||||
push de
|
|
||||||
push hl
|
|
||||||
push ix
|
|
||||||
push iy
|
|
||||||
|
|
||||||
ex af, af'
|
|
||||||
push af
|
|
||||||
exx
|
|
||||||
push bc
|
|
||||||
push de
|
|
||||||
push hl
|
|
||||||
|
|
||||||
call handler
|
|
||||||
|
|
||||||
pop hl
|
|
||||||
pop de
|
|
||||||
pop bc
|
|
||||||
exx
|
|
||||||
pop af
|
|
||||||
ex af, af'
|
|
||||||
|
|
||||||
pop iy
|
|
||||||
pop ix
|
|
||||||
pop hl
|
|
||||||
pop de
|
|
||||||
pop bc
|
|
||||||
pop af
|
|
||||||
ei
|
|
||||||
reti
|
|
||||||
.endm
|
|
||||||
|
|
||||||
MAKE_ISR ctc_isr, _ctc_interrupt
|
|
||||||
MAKE_ISR uart_isr, _uart_interrupt
|
|
||||||
|
|
||||||
_enable_interrupts::
|
|
||||||
ei
|
|
||||||
ret
|
|
||||||
|
|
||||||
_disable_interrupts::
|
|
||||||
di
|
|
||||||
ret
|
|
||||||
|
|
||||||
; Write a byte to an I/O port.
|
|
||||||
; Input: A = port address, L = data byte.
|
|
||||||
_io_out::
|
|
||||||
ld c, a
|
|
||||||
ld a, l
|
|
||||||
out (c), a
|
|
||||||
ret
|
|
||||||
|
|
||||||
; Read a byte from an I/O port.
|
|
||||||
; Input: A = port address.
|
|
||||||
; Return: A = data byte.
|
|
||||||
_io_in::
|
|
||||||
ld c, a
|
|
||||||
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::
|
|
||||||
ld a, h
|
|
||||||
or l
|
|
||||||
ret z
|
|
||||||
delay_ms_outer$:
|
|
||||||
push hl
|
|
||||||
ld hl, #DELAY_LOOPS_PER_MS
|
|
||||||
delay_ms_inner$:
|
|
||||||
dec hl
|
|
||||||
ld a, h
|
|
||||||
or l
|
|
||||||
jr nz, delay_ms_inner$
|
|
||||||
pop hl
|
|
||||||
dec hl
|
|
||||||
ld a, h
|
|
||||||
or l
|
|
||||||
jr nz, delay_ms_outer$
|
|
||||||
ret
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
#include "circ_buf.h"
|
|
||||||
|
|
||||||
void buffer_init(volatile circular_buffer_t* b){
|
|
||||||
b->count = 0;
|
|
||||||
b->head = 0;
|
|
||||||
b->tail = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t buffer_is_full(volatile circular_buffer_t* b){
|
|
||||||
return b->count == BUFFER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t buffer_is_empty(volatile circular_buffer_t* b){
|
|
||||||
return b->count == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t buffer_enqueue(volatile circular_buffer_t* b, char c){
|
|
||||||
if(buffer_is_full(b))
|
|
||||||
return 0;
|
|
||||||
b->data[b->head] = c;
|
|
||||||
b->head = (b->head+1)%BUFFER_SIZE;
|
|
||||||
b->count++;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t buffer_dequeue(volatile circular_buffer_t* b, char* c){
|
|
||||||
if(buffer_is_empty(b))
|
|
||||||
return 0;
|
|
||||||
*c = b->data[b->tail];
|
|
||||||
b->tail = (b->tail+1)%BUFFER_SIZE;
|
|
||||||
b->count--;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
#ifndef __H_CIRC_BUF
|
|
||||||
#define __H_CIRC_BUF
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define BUFFER_SIZE 64
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char data[BUFFER_SIZE];
|
|
||||||
uint8_t head;
|
|
||||||
uint8_t tail;
|
|
||||||
uint8_t count;
|
|
||||||
} circular_buffer_t;
|
|
||||||
|
|
||||||
void buffer_init(volatile circular_buffer_t* b);
|
|
||||||
uint8_t buffer_is_full(volatile circular_buffer_t* b);
|
|
||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
.include "rom_layout.inc"
|
|
||||||
|
|
||||||
; Stable assembly ABI exported by the resident BIOS.
|
|
||||||
BIOS_PUTC = RESIDENT_RAM_BASE + 0x50 ; A = character; preserves all registers
|
|
||||||
BIOS_TRY_GETC = RESIDENT_RAM_BASE + 0x53 ; returns A = character or 0; preserves BC..IY
|
|
||||||
BIOS_GETC = RESIDENT_RAM_BASE + 0x56 ; blocking; returns character in A
|
|
||||||
BIOS_IO_OUT = RESIDENT_RAM_BASE + 0x59 ; A = port, L = value; preserves all registers
|
|
||||||
BIOS_IO_IN = RESIDENT_RAM_BASE + 0x5C ; A = port; returns value in A
|
|
||||||
BIOS_DELAY_MS = RESIDENT_RAM_BASE + 0x5F ; HL = milliseconds; preserves all registers
|
|
||||||
BIOS_MONITOR = RESIDENT_RAM_BASE + 0x62 ; warm-start the monitor; does not return
|
|
||||||
BIOS_BANK = RESIDENT_RAM_BASE + 0x65 ; A=0 split 48K/16K, otherwise upper 64K
|
|
||||||
BIOS_ROM_DISABLE = RESIDENT_RAM_BASE + 0x68 ; disable ROM while preserving the bank selection
|
|
||||||
BIOS_ROM_ENABLE = RESIDENT_RAM_BASE + 0x6B ; enable ROM while preserving the bank selection
|
|
||||||
BIOS_FLUSH = RESIDENT_RAM_BASE + 0x6E ; wait until all queued UART output is sent
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include "bios.h"
|
|
||||||
#include "rom_layout.h"
|
|
||||||
|
|
||||||
#define LINE_SIZE 80
|
|
||||||
#define MAX_ARGS 40
|
|
||||||
#define DEFAULT_DUMP_LENGTH 0x40
|
|
||||||
#define MAX_DUMP_LENGTH 0x100
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
bios_putc(hex_digit(value >> 4));
|
|
||||||
bios_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)
|
|
||||||
{
|
|
||||||
bios_puts(
|
|
||||||
"Commands (hex numbers):\r\n"
|
|
||||||
" D addr [len] Dump memory\r\n"
|
|
||||||
" C start end 16-bit checksum, inclusive\r\n"
|
|
||||||
" W addr byte... Write memory\r\n"
|
|
||||||
" I port Read I/O\r\n"
|
|
||||||
" O port byte Write I/O\r\n"
|
|
||||||
" K bank Bank: 0=split, 1=upper 64K\r\n"
|
|
||||||
" R ON|OFF Enable or disable ROM\r\n"
|
|
||||||
" G addr Execute\r\n"
|
|
||||||
" B Tiny BASIC\r\n"
|
|
||||||
" T Tape loader\r\n"
|
|
||||||
" H 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))) {
|
|
||||||
bios_puts("Usage: D address [length]\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (length > MAX_DUMP_LENGTH) {
|
|
||||||
bios_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));
|
|
||||||
bios_puts(": ");
|
|
||||||
for (column = 0; column < row_length; ++column) {
|
|
||||||
print_hex8(*(volatile uint8_t *)(address + offset + column));
|
|
||||||
bios_putc(' ');
|
|
||||||
}
|
|
||||||
for (; column < 16; ++column) {
|
|
||||||
bios_puts(" ");
|
|
||||||
}
|
|
||||||
bios_putc('|');
|
|
||||||
for (column = 0; column < row_length; ++column) {
|
|
||||||
uint8_t value = *(volatile uint8_t *)(address + offset + column);
|
|
||||||
bios_putc(value >= ' ' && value <= '~' ? (char)value : '.');
|
|
||||||
}
|
|
||||||
bios_putc('|');
|
|
||||||
bios_puts("\r\n");
|
|
||||||
offset += row_length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void command_checksum(uint8_t argc, char **argv)
|
|
||||||
{
|
|
||||||
uint16_t address;
|
|
||||||
uint16_t end;
|
|
||||||
uint16_t checksum = 0;
|
|
||||||
|
|
||||||
if (argc != 3 || !parse_hex(argv[1], &address) ||
|
|
||||||
!parse_hex(argv[2], &end) || end < address) {
|
|
||||||
bios_puts("Usage: C start end\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
checksum = (uint16_t)(checksum + *(volatile uint8_t *)address);
|
|
||||||
if (address == end) break;
|
|
||||||
++address;
|
|
||||||
}
|
|
||||||
|
|
||||||
print_hex16(checksum);
|
|
||||||
bios_puts("\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
bios_puts("Usage: W address byte [byte ...]\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (i = 2; i < argc; ++i) {
|
|
||||||
if (!parse_hex(argv[i], &value) || value > 0xff) {
|
|
||||||
bios_puts("Bad byte: ");
|
|
||||||
bios_puts(argv[i]);
|
|
||||||
bios_puts("\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*(volatile uint8_t *)address++ = (uint8_t)value;
|
|
||||||
}
|
|
||||||
bios_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) {
|
|
||||||
bios_puts("Usage: I port\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
print_hex8((uint8_t)port);
|
|
||||||
bios_puts(": ");
|
|
||||||
print_hex8(io_in((uint8_t)port));
|
|
||||||
bios_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) {
|
|
||||||
bios_puts("Usage: O port byte\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
io_out((uint8_t)port, (uint8_t)value);
|
|
||||||
bios_puts("OK\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void command_bank(uint8_t argc, char **argv)
|
|
||||||
{
|
|
||||||
uint16_t bank;
|
|
||||||
|
|
||||||
if (argc != 2 || !parse_hex(argv[1], &bank) || bank > 1) {
|
|
||||||
bios_puts("Usage: BANK 0|1\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bios_bank((uint8_t)bank);
|
|
||||||
bios_puts("OK\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void command_rom(uint8_t argc, char **argv)
|
|
||||||
{
|
|
||||||
if (argc != 2) {
|
|
||||||
bios_puts("Usage: ROM ON|OFF\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (strings_equal(argv[1], "on")) {
|
|
||||||
bios_rom_enable();
|
|
||||||
} else if (strings_equal(argv[1], "off")) {
|
|
||||||
bios_rom_disable();
|
|
||||||
} else {
|
|
||||||
bios_puts("Usage: ROM ON|OFF\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bios_puts("OK\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void execute_address(uint16_t address)
|
|
||||||
{
|
|
||||||
bios_puts("Executing at ");
|
|
||||||
print_hex16(address);
|
|
||||||
bios_puts("...\r\n");
|
|
||||||
bios_flush();
|
|
||||||
jump_to(address);
|
|
||||||
bios_puts("Returned to monitor\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void command_go(uint8_t argc, char **argv)
|
|
||||||
{
|
|
||||||
uint16_t address;
|
|
||||||
|
|
||||||
if (argc != 2 || !parse_hex(argv[1], &address)) {
|
|
||||||
bios_puts("Usage: G address\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
execute_address(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void command_basic(uint8_t argc)
|
|
||||||
{
|
|
||||||
if (argc != 1) {
|
|
||||||
bios_puts("Usage: B\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
execute_address(TINYBASIC_ROM_ADDRESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void command_tape(uint8_t argc)
|
|
||||||
{
|
|
||||||
if (argc != 1) {
|
|
||||||
bios_puts("Usage: T\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
execute_address(TAPE_LOADER_ROM_ADDRESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
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], "c") || strings_equal(argv[0], "checksum")) {
|
|
||||||
command_checksum(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], "k") || strings_equal(argv[0], "bank")) {
|
|
||||||
command_bank(argc, argv);
|
|
||||||
} else if (strings_equal(argv[0], "r") || strings_equal(argv[0], "rom")) {
|
|
||||||
command_rom(argc, argv);
|
|
||||||
} else if (strings_equal(argv[0], "g") || strings_equal(argv[0], "go")) {
|
|
||||||
command_go(argc, argv);
|
|
||||||
} else if (strings_equal(argv[0], "b") || strings_equal(argv[0], "basic")) {
|
|
||||||
command_basic(argc);
|
|
||||||
} else if (strings_equal(argv[0], "t") || strings_equal(argv[0], "tape")) {
|
|
||||||
command_tape(argc);
|
|
||||||
} else {
|
|
||||||
bios_puts("Unknown command; type HELP\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main(void)
|
|
||||||
{
|
|
||||||
char line[LINE_SIZE];
|
|
||||||
uint8_t length = 0;
|
|
||||||
uint8_t ignore_lf = 0;
|
|
||||||
|
|
||||||
bios_puts("\r\nZ80 monitor - v1\r\nType HELP for commands.\r\n> ");
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
char c = bios_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;
|
|
||||||
bios_puts("\r\n");
|
|
||||||
execute_line(line);
|
|
||||||
length = 0;
|
|
||||||
bios_puts("> ");
|
|
||||||
} else if (c == '\b' || c == 0x7f) {
|
|
||||||
if (length) {
|
|
||||||
--length;
|
|
||||||
bios_puts("\b \b");
|
|
||||||
}
|
|
||||||
} else if (c == 0x15) {
|
|
||||||
while (length) {
|
|
||||||
--length;
|
|
||||||
bios_puts("\b \b");
|
|
||||||
}
|
|
||||||
} else if (c >= ' ' && c <= '~' && length < LINE_SIZE - 1) {
|
|
||||||
line[length++] = c;
|
|
||||||
bios_putc(c);
|
|
||||||
} else if (c >= ' ' && c <= '~') {
|
|
||||||
bios_putc('\a');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
build
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
.SILENT:
|
|
||||||
include memory.mk
|
|
||||||
|
|
||||||
ASM := zasm
|
|
||||||
ASMFLAGS := -w -b
|
|
||||||
BUILDDIR := build
|
|
||||||
|
|
||||||
FILES := bios
|
|
||||||
|
|
||||||
SOURCES := $(addprefix ./, $(addsuffix .asm, $(FILES)))
|
|
||||||
TARGETS := $(addprefix $(BUILDDIR)/, $(addsuffix .bin, $(FILES)))
|
|
||||||
|
|
||||||
TOPTARGETS := all clean size
|
|
||||||
$(TOPTARGETS): $(SUBDIRS)
|
|
||||||
|
|
||||||
all: $(BUILDDIR) $(BUILDDIR)/rom.bin size
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(BUILDDIR)
|
|
||||||
|
|
||||||
$(BUILDDIR):
|
|
||||||
mkdir -p $(BUILDDIR)
|
|
||||||
|
|
||||||
$(BUILDDIR)/%.bin: ./%.asm $(BUILDDIR)/memory.inc
|
|
||||||
echo '>$<'
|
|
||||||
$(ASM) $(ASMFLAGS) $< $@
|
|
||||||
|
|
||||||
size:
|
|
||||||
@for file in $(TARGETS); do \
|
|
||||||
if [ -f "$$file" ]; then \
|
|
||||||
echo "$$file: $$(wc -c < "$$file") bytes"; \
|
|
||||||
else \
|
|
||||||
echo "$$file: Not built yet (run 'make')"; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
$(MAKE) -C apps/basic size --no-print-directory
|
|
||||||
|
|
||||||
$(BUILDDIR)/basic.bin:
|
|
||||||
$(MAKE) -C apps/basic $(or $(MAKECMDGOALS),all) --no-print-directory
|
|
||||||
|
|
||||||
$(BUILDDIR)/memory.inc: memory.mk
|
|
||||||
@mkdir -p $(@D)
|
|
||||||
@awk 'BEGIN { print "; Generated from rom_layout.mk; do not edit." } \
|
|
||||||
/^[A-Z][A-Z0-9_]*[[:space:]]*:=[[:space:]]*/ { print $$1 " .equ " $$3 }' $< > $@
|
|
||||||
|
|
||||||
$(BUILDDIR)/rom.bin: $(BUILDDIR)/bios.bin $(BUILDDIR)/basic.bin
|
|
||||||
objcopy --pad-to=$$(($(BIOS_ROM_START)+$(BIOS_ROM_SIZE))) \
|
|
||||||
-I binary $(BUILDDIR)/bios.bin -O binary $(BUILDDIR)/bios_padded.bin
|
|
||||||
objcopy --pad-to=$(BASIC_ROM_SIZE) \
|
|
||||||
-I binary $(BUILDDIR)/basic.bin -O binary $(BUILDDIR)/basic_padded.bin
|
|
||||||
cat $(BUILDDIR)/bios_padded.bin $(BUILDDIR)/basic_padded.bin > $(BUILDDIR)/rom.bin
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: $(TOPTARGETS) $(SUBDIRS)
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,202 +0,0 @@
|
|||||||
;==================================================================================
|
|
||||||
; Contents of this file are copyright Grant Searle
|
|
||||||
;
|
|
||||||
; You have permission to use this for NON COMMERCIAL USE ONLY
|
|
||||||
; If you wish to use it elsewhere, please include an acknowledgement to myself.
|
|
||||||
;
|
|
||||||
; http://searle.hostei.com/grant/index.html
|
|
||||||
;
|
|
||||||
; eMail: home.micros01@btinternet.com
|
|
||||||
;
|
|
||||||
; If the above don't work, please perform an Internet search to see if I have
|
|
||||||
; updated the web page hosting service.
|
|
||||||
;
|
|
||||||
;==================================================================================
|
|
||||||
|
|
||||||
; Minimum 6850 ACIA interrupt driven serial I/O to run modified NASCOM Basic 4.7
|
|
||||||
; Full input buffering with incoming data hardware handshaking
|
|
||||||
; Handshake shows full before the buffer is totally filled to allow run-on from the sender
|
|
||||||
|
|
||||||
SER_BUFSIZE .EQU 3FH
|
|
||||||
SER_FULLSIZE .EQU 30H
|
|
||||||
SER_EMPTYSIZE .EQU 5
|
|
||||||
|
|
||||||
RTS_HIGH .EQU 0D6H
|
|
||||||
RTS_LOW .EQU 096H
|
|
||||||
|
|
||||||
serBuf .EQU $2000
|
|
||||||
serInPtr .EQU serBuf+SER_BUFSIZE
|
|
||||||
serRdPtr .EQU serInPtr+2
|
|
||||||
serBufUsed .EQU serRdPtr+2
|
|
||||||
basicStarted .EQU serBufUsed+1
|
|
||||||
TEMPSTACK .EQU $20ED ; Top of BASIC line input buffer so is "free ram" when BASIC resets
|
|
||||||
|
|
||||||
CR .EQU 0DH
|
|
||||||
LF .EQU 0AH
|
|
||||||
CS .EQU 0CH ; Clear screen
|
|
||||||
|
|
||||||
.ORG $0000
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
; Reset
|
|
||||||
|
|
||||||
RST00 DI ;Disable interrupts
|
|
||||||
JP INIT ;Initialize Hardware and go
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
; TX a character over RS232
|
|
||||||
|
|
||||||
.ORG 0008H
|
|
||||||
RST08 JP TXA
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
; RX a character over RS232 Channel A [Console], hold here until char ready.
|
|
||||||
|
|
||||||
.ORG 0010H
|
|
||||||
RST10 JP RXA
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
; Check serial status
|
|
||||||
|
|
||||||
.ORG 0018H
|
|
||||||
RST18 JP CKINCHAR
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
; RST 38 - INTERRUPT VECTOR [ for IM 1 ]
|
|
||||||
|
|
||||||
.ORG 0038H
|
|
||||||
RST38 JR serialInt
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
serialInt: PUSH AF
|
|
||||||
PUSH HL
|
|
||||||
|
|
||||||
IN A,($80)
|
|
||||||
AND $01 ; Check if interupt due to read buffer full
|
|
||||||
JR Z,rts0 ; if not, ignore
|
|
||||||
|
|
||||||
IN A,($81)
|
|
||||||
PUSH AF
|
|
||||||
LD A,(serBufUsed)
|
|
||||||
CP SER_BUFSIZE ; If full then ignore
|
|
||||||
JR NZ,notFull
|
|
||||||
POP AF
|
|
||||||
JR rts0
|
|
||||||
|
|
||||||
notFull: LD HL,(serInPtr)
|
|
||||||
INC HL
|
|
||||||
LD A,L ; Only need to check low byte becasuse buffer<256 bytes
|
|
||||||
CP (serBuf+SER_BUFSIZE) & $FF
|
|
||||||
JR NZ, notWrap
|
|
||||||
LD HL,serBuf
|
|
||||||
notWrap: LD (serInPtr),HL
|
|
||||||
POP AF
|
|
||||||
LD (HL),A
|
|
||||||
LD A,(serBufUsed)
|
|
||||||
INC A
|
|
||||||
LD (serBufUsed),A
|
|
||||||
CP SER_FULLSIZE
|
|
||||||
JR C,rts0
|
|
||||||
LD A,RTS_HIGH
|
|
||||||
OUT ($80),A
|
|
||||||
rts0: POP HL
|
|
||||||
POP AF
|
|
||||||
EI
|
|
||||||
RETI
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
RXA:
|
|
||||||
waitForChar: LD A,(serBufUsed)
|
|
||||||
CP $00
|
|
||||||
JR Z, waitForChar
|
|
||||||
PUSH HL
|
|
||||||
LD HL,(serRdPtr)
|
|
||||||
INC HL
|
|
||||||
LD A,L ; Only need to check low byte becasuse buffer<256 bytes
|
|
||||||
CP (serBuf+SER_BUFSIZE) & $FF
|
|
||||||
JR NZ, notRdWrap
|
|
||||||
LD HL,serBuf
|
|
||||||
notRdWrap: DI
|
|
||||||
LD (serRdPtr),HL
|
|
||||||
LD A,(serBufUsed)
|
|
||||||
DEC A
|
|
||||||
LD (serBufUsed),A
|
|
||||||
CP SER_EMPTYSIZE
|
|
||||||
JR NC,rts1
|
|
||||||
LD A,RTS_LOW
|
|
||||||
OUT ($80),A
|
|
||||||
rts1:
|
|
||||||
LD A,(HL)
|
|
||||||
EI
|
|
||||||
POP HL
|
|
||||||
RET ; Char ready in A
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
TXA: PUSH AF ; Store character
|
|
||||||
conout1: IN A,($80) ; Status byte
|
|
||||||
BIT 1,A ; Set Zero flag if still transmitting character
|
|
||||||
JR Z,conout1 ; Loop until flag signals ready
|
|
||||||
POP AF ; Retrieve character
|
|
||||||
OUT ($81),A ; Output the character
|
|
||||||
RET
|
|
||||||
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
CKINCHAR LD A,(serBufUsed)
|
|
||||||
CP $0
|
|
||||||
RET
|
|
||||||
|
|
||||||
PRINT: LD A,(HL) ; Get character
|
|
||||||
OR A ; Is it $00 ?
|
|
||||||
RET Z ; Then RETurn on terminator
|
|
||||||
RST 08H ; Print it
|
|
||||||
INC HL ; Next Character
|
|
||||||
JR PRINT ; Continue until $00
|
|
||||||
RET
|
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
INIT:
|
|
||||||
LD HL,TEMPSTACK ; Temp stack
|
|
||||||
LD SP,HL ; Set up a temporary stack
|
|
||||||
LD HL,serBuf
|
|
||||||
LD (serInPtr),HL
|
|
||||||
LD (serRdPtr),HL
|
|
||||||
XOR A ;0 to accumulator
|
|
||||||
LD (serBufUsed),A
|
|
||||||
LD A,RTS_LOW
|
|
||||||
OUT ($80),A ; Initialise ACIA
|
|
||||||
IM 1
|
|
||||||
EI
|
|
||||||
LD HL,SIGNON1 ; Sign-on message
|
|
||||||
CALL PRINT ; Output string
|
|
||||||
LD A,(basicStarted); Check the BASIC STARTED flag
|
|
||||||
CP 'Y' ; to see if this is power-up
|
|
||||||
JR NZ,COLDSTART ; If not BASIC started then always do cold start
|
|
||||||
LD HL,SIGNON2 ; Cold/warm message
|
|
||||||
CALL PRINT ; Output string
|
|
||||||
CORW:
|
|
||||||
CALL RXA
|
|
||||||
AND %11011111 ; lower to uppercase
|
|
||||||
CP 'C'
|
|
||||||
JR NZ, CHECKWARM
|
|
||||||
RST 08H
|
|
||||||
LD A,$0D
|
|
||||||
RST 08H
|
|
||||||
LD A,$0A
|
|
||||||
RST 08H
|
|
||||||
COLDSTART: LD A,'Y' ; Set the BASIC STARTED flag
|
|
||||||
LD (basicStarted),A
|
|
||||||
JP $0150 ; Start BASIC COLD
|
|
||||||
CHECKWARM:
|
|
||||||
CP 'W'
|
|
||||||
JR NZ, CORW
|
|
||||||
RST 08H
|
|
||||||
LD A,$0D
|
|
||||||
RST 08H
|
|
||||||
LD A,$0A
|
|
||||||
RST 08H
|
|
||||||
JP $0153 ; Start BASIC WARM
|
|
||||||
|
|
||||||
SIGNON1: .BYTE CS
|
|
||||||
.BYTE "Z80 SBC By Grant Searle",CR,LF,0
|
|
||||||
SIGNON2: .BYTE CR,LF
|
|
||||||
.BYTE "Cold or warm start (C or W)? ",0
|
|
||||||
|
|
||||||
.END
|
|
||||||
-319
@@ -1,319 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Program an Intel HEX file through the Z80 UART monitor."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
try:
|
|
||||||
import serial
|
|
||||||
except ImportError:
|
|
||||||
print(
|
|
||||||
"error: pySerial is required; install it with: "
|
|
||||||
"python3 -m pip install pyserial",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise SystemExit(2)
|
|
||||||
|
|
||||||
|
|
||||||
MAX_BLOCK_SIZE = 24 # Largest W command that fits the monitor's 80-byte line.
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def load_ihex(path: Path) -> dict[int, int]:
|
|
||||||
"""Return the 16-bit-addressed data contained in an Intel HEX file."""
|
|
||||||
memory: dict[int, int] = {}
|
|
||||||
address_base = 0
|
|
||||||
saw_eof = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
lines = path.read_text(encoding="ascii").splitlines()
|
|
||||||
except (OSError, UnicodeError) as exc:
|
|
||||||
raise ProgramError(f"cannot read {path}: {exc}") from exc
|
|
||||||
|
|
||||||
for line_number, text in enumerate(lines, 1):
|
|
||||||
text = text.strip()
|
|
||||||
if not text:
|
|
||||||
continue
|
|
||||||
if saw_eof:
|
|
||||||
raise ProgramError(f"{path}:{line_number}: data after EOF record")
|
|
||||||
if not text.startswith(":"):
|
|
||||||
raise ProgramError(f"{path}:{line_number}: record does not start with ':'")
|
|
||||||
try:
|
|
||||||
record = bytes.fromhex(text[1:])
|
|
||||||
except ValueError as exc:
|
|
||||||
raise ProgramError(f"{path}:{line_number}: invalid hexadecimal record") from exc
|
|
||||||
|
|
||||||
if len(record) < 5 or len(record) != record[0] + 5:
|
|
||||||
raise ProgramError(f"{path}:{line_number}: invalid record length")
|
|
||||||
if sum(record) & 0xFF:
|
|
||||||
raise ProgramError(f"{path}:{line_number}: Intel HEX checksum mismatch")
|
|
||||||
|
|
||||||
count = record[0]
|
|
||||||
offset = (record[1] << 8) | record[2]
|
|
||||||
record_type = record[3]
|
|
||||||
data = record[4 : 4 + count]
|
|
||||||
|
|
||||||
if record_type == 0x00:
|
|
||||||
start = address_base + offset
|
|
||||||
if start + count > 0x10000:
|
|
||||||
raise ProgramError(
|
|
||||||
f"{path}:{line_number}: address is outside the Z80's 64 KiB space"
|
|
||||||
)
|
|
||||||
for index, value in enumerate(data):
|
|
||||||
address = start + index
|
|
||||||
old_value = memory.get(address)
|
|
||||||
if old_value is not None and old_value != value:
|
|
||||||
raise ProgramError(
|
|
||||||
f"{path}:{line_number}: conflicting data at {address:04X}"
|
|
||||||
)
|
|
||||||
memory[address] = value
|
|
||||||
elif record_type == 0x01:
|
|
||||||
if count != 0:
|
|
||||||
raise ProgramError(f"{path}:{line_number}: malformed EOF record")
|
|
||||||
saw_eof = True
|
|
||||||
elif record_type == 0x02:
|
|
||||||
if count != 2:
|
|
||||||
raise ProgramError(
|
|
||||||
f"{path}:{line_number}: malformed extended segment address"
|
|
||||||
)
|
|
||||||
address_base = int.from_bytes(data, "big") << 4
|
|
||||||
elif record_type == 0x04:
|
|
||||||
if count != 2:
|
|
||||||
raise ProgramError(
|
|
||||||
f"{path}:{line_number}: malformed extended linear address"
|
|
||||||
)
|
|
||||||
address_base = int.from_bytes(data, "big") << 16
|
|
||||||
elif record_type in (0x03, 0x05):
|
|
||||||
# Start-address records do not contain bytes to program.
|
|
||||||
if count != 4:
|
|
||||||
raise ProgramError(
|
|
||||||
f"{path}:{line_number}: malformed start-address record"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise ProgramError(
|
|
||||||
f"{path}:{line_number}: unsupported record type {record_type:02X}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not saw_eof:
|
|
||||||
raise ProgramError(f"{path}: missing Intel HEX EOF record")
|
|
||||||
if not memory:
|
|
||||||
raise ProgramError(f"{path}: file contains no data")
|
|
||||||
return memory
|
|
||||||
|
|
||||||
|
|
||||||
def make_blocks(memory: dict[int, int], block_size: int) -> list[tuple[int, bytes]]:
|
|
||||||
"""Combine adjacent addresses without filling gaps from the HEX file."""
|
|
||||||
blocks: list[tuple[int, bytes]] = []
|
|
||||||
addresses = sorted(memory)
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
while index < len(addresses):
|
|
||||||
start = addresses[index]
|
|
||||||
data = bytearray([memory[start]])
|
|
||||||
index += 1
|
|
||||||
while (
|
|
||||||
index < len(addresses)
|
|
||||||
and len(data) < block_size
|
|
||||||
and addresses[index] == start + len(data)
|
|
||||||
):
|
|
||||||
data.append(memory[addresses[index]])
|
|
||||||
index += 1
|
|
||||||
blocks.append((start, bytes(data)))
|
|
||||||
|
|
||||||
return blocks
|
|
||||||
|
|
||||||
|
|
||||||
class Monitor:
|
|
||||||
def __init__(self, port: str, baud: int, timeout: float, char_delay: float):
|
|
||||||
self.char_delay = char_delay
|
|
||||||
try:
|
|
||||||
self.serial = serial.Serial(
|
|
||||||
port=port,
|
|
||||||
baudrate=baud,
|
|
||||||
bytesize=serial.EIGHTBITS,
|
|
||||||
parity=serial.PARITY_NONE,
|
|
||||||
stopbits=serial.STOPBITS_ONE,
|
|
||||||
timeout=timeout,
|
|
||||||
write_timeout=timeout,
|
|
||||||
)
|
|
||||||
except serial.SerialException as exc:
|
|
||||||
raise ProgramError(f"cannot open serial port {port}: {exc}") from exc
|
|
||||||
|
|
||||||
def close(self) -> None:
|
|
||||||
self.serial.close()
|
|
||||||
|
|
||||||
def command(self, command: str) -> str:
|
|
||||||
try:
|
|
||||||
for value in command.encode("ascii") + b"\r":
|
|
||||||
self.serial.write(bytes((value,)))
|
|
||||||
self.serial.flush()
|
|
||||||
if self.char_delay:
|
|
||||||
time.sleep(self.char_delay)
|
|
||||||
response = self.serial.read_until(b"> ")
|
|
||||||
except serial.SerialException as exc:
|
|
||||||
raise ProgramError(f"serial communication failed: {exc}") from exc
|
|
||||||
if not response.endswith(b"> "):
|
|
||||||
text = response.decode("ascii", errors="replace")
|
|
||||||
raise ProgramError(
|
|
||||||
f"timeout waiting for monitor after '{command}'; partial={text!r}"
|
|
||||||
)
|
|
||||||
return response.decode("ascii", errors="replace")
|
|
||||||
|
|
||||||
def synchronize(self) -> None:
|
|
||||||
try:
|
|
||||||
self.serial.reset_input_buffer()
|
|
||||||
# reset_input_buffer() cannot discard bytes which are still queued
|
|
||||||
# in the computer or USB adapter. Read until the line has stayed
|
|
||||||
# quiet before generating a fresh, unambiguous prompt.
|
|
||||||
normal_timeout = self.serial.timeout
|
|
||||||
self.serial.timeout = min(normal_timeout, 0.2)
|
|
||||||
while self.serial.read(256):
|
|
||||||
pass
|
|
||||||
self.serial.timeout = normal_timeout
|
|
||||||
except serial.SerialException as exc:
|
|
||||||
raise ProgramError(f"serial communication failed: {exc}") from exc
|
|
||||||
response = self.command("")
|
|
||||||
if not response.endswith("> "):
|
|
||||||
raise ProgramError("monitor prompt not found")
|
|
||||||
|
|
||||||
|
|
||||||
def response_has_ok(response: str) -> bool:
|
|
||||||
return any(line.strip() == "OK" for line in response.splitlines())
|
|
||||||
|
|
||||||
|
|
||||||
def response_checksum(response: str) -> int | None:
|
|
||||||
matches = [
|
|
||||||
int(line.strip(), 16)
|
|
||||||
for line in response.splitlines()
|
|
||||||
if re.fullmatch(r"[0-9A-Fa-f]{4}", line.strip())
|
|
||||||
]
|
|
||||||
return matches[-1] if matches else None
|
|
||||||
|
|
||||||
|
|
||||||
def write_and_verify(monitor: Monitor, start: int, data: bytes) -> tuple[bool, str]:
|
|
||||||
values = " ".join(f"{value:02X}" for value in data)
|
|
||||||
response = monitor.command(f"W {start:04X} {values}")
|
|
||||||
if not response_has_ok(response):
|
|
||||||
return False, f"monitor did not acknowledge the write; response={response!r}"
|
|
||||||
|
|
||||||
end = start + len(data) - 1
|
|
||||||
expected = sum(data) & 0xFFFF
|
|
||||||
response = monitor.command(f"C {start:04X} {end:04X}")
|
|
||||||
actual = response_checksum(response)
|
|
||||||
if actual is None:
|
|
||||||
return False, f"monitor did not return a checksum; response={response!r}"
|
|
||||||
if actual != expected:
|
|
||||||
return False, f"checksum mismatch (expected {expected:04X}, received {actual:04X})"
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Program an Intel HEX file through the Z80 UART monitor."
|
|
||||||
)
|
|
||||||
parser.add_argument("port", help="serial port, for example /dev/ttyUSB0 or COM3")
|
|
||||||
parser.add_argument("file", type=Path, help="Intel HEX input file")
|
|
||||||
parser.add_argument("--baud", type=int, default=2400, help="baud rate (default: 2400)")
|
|
||||||
parser.add_argument(
|
|
||||||
"--block-size",
|
|
||||||
type=int,
|
|
||||||
default=16,
|
|
||||||
help=f"bytes per verified block, 1-{MAX_BLOCK_SIZE} (default: 16)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--retries",
|
|
||||||
type=int,
|
|
||||||
default=3,
|
|
||||||
help="retries after the first attempt (default: 3)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--timeout",
|
|
||||||
type=float,
|
|
||||||
default=5.0,
|
|
||||||
help="serial response timeout in seconds (default: 5)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--char-delay",
|
|
||||||
type=float,
|
|
||||||
default=0.005,
|
|
||||||
help="delay after each transmitted character in seconds (default: 0.005)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
if not 1 <= args.block_size <= MAX_BLOCK_SIZE:
|
|
||||||
parser.error(f"--block-size must be between 1 and {MAX_BLOCK_SIZE}")
|
|
||||||
if args.retries < 0:
|
|
||||||
parser.error("--retries cannot be negative")
|
|
||||||
if args.baud <= 0:
|
|
||||||
parser.error("--baud must be positive")
|
|
||||||
if args.timeout <= 0:
|
|
||||||
parser.error("--timeout must be positive")
|
|
||||||
if args.char_delay < 0:
|
|
||||||
parser.error("--char-delay cannot be negative")
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
args = parse_args()
|
|
||||||
monitor: Monitor | None = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
memory = load_ihex(args.file)
|
|
||||||
blocks = make_blocks(memory, args.block_size)
|
|
||||||
print(
|
|
||||||
f"Loaded {len(memory)} bytes in {len(blocks)} blocks "
|
|
||||||
f"({min(memory):04X}-{max(memory):04X})"
|
|
||||||
)
|
|
||||||
|
|
||||||
monitor = Monitor(args.port, args.baud, args.timeout, args.char_delay)
|
|
||||||
monitor.synchronize()
|
|
||||||
|
|
||||||
for number, (start, data) in enumerate(blocks, 1):
|
|
||||||
end = start + len(data) - 1
|
|
||||||
for attempt in range(args.retries + 1):
|
|
||||||
try:
|
|
||||||
if attempt:
|
|
||||||
monitor.synchronize()
|
|
||||||
success, reason = write_and_verify(monitor, start, data)
|
|
||||||
except ProgramError as exc:
|
|
||||||
success, reason = False, str(exc)
|
|
||||||
if success:
|
|
||||||
print(
|
|
||||||
f"Block {number}/{len(blocks)} "
|
|
||||||
f"{start:04X}-{end:04X} verified"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
if attempt == args.retries:
|
|
||||||
raise ProgramError(
|
|
||||||
f"block {start:04X}-{end:04X} failed after "
|
|
||||||
f"{args.retries + 1} attempts: {reason}"
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
f"Retrying {start:04X}-{end:04X} "
|
|
||||||
f"({attempt + 1}/{args.retries}): {reason}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Programmed and verified {len(memory)} bytes")
|
|
||||||
return 0
|
|
||||||
except ProgramError as exc:
|
|
||||||
print(f"error: {exc}", file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nCancelled", file=sys.stderr)
|
|
||||||
return 130
|
|
||||||
finally:
|
|
||||||
if monitor is not None:
|
|
||||||
monitor.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Authoritative ROM and resident-memory layout.
|
|
||||||
#
|
|
||||||
# Keep assignments in the simple "NAME := value" form: the build generates
|
|
||||||
# equivalent C and sdasz80 include files from these definitions.
|
|
||||||
|
|
||||||
ROM_SIZE := 32768
|
|
||||||
|
|
||||||
RESIDENT_RAM_BASE := 0xC000
|
|
||||||
RESIDENT_ROM_OFFSET := 0x0080
|
|
||||||
RESIDENT_CODE_BASE := 0xC080
|
|
||||||
RESIDENT_CODE_SIZE := 0x0F80
|
|
||||||
RESIDENT_DATA_BASE := 0xF000
|
|
||||||
|
|
||||||
TINYBASIC_ROM_ADDRESS := 0x1400
|
|
||||||
TAPE_LOADER_ROM_ADDRESS := 0x3000
|
|
||||||
NASCOM_BASIC_ROM_ADDRESS := 0x4000
|
|
||||||
Reference in New Issue
Block a user