Files
z80_monitor/boot.asm
T
2026-09-28 15:42:07 +02:00

251 lines
4.9 KiB
NASM

STACK_TOP = 0xFFFF
DELAY_LOOPS_PER_MS = 40
MONITOR_BASE = 0xC000
MONITOR_ROM_SOURCE = 0x0080
TINYBASIC_BASE = 0xD000
TINYBASIC_ROM_SOURCE = 0x1000
.include "tinybasic.inc"
.globl _main
.globl _delay_ms
.globl _io_out
.globl _io_in
.globl _enable_interrupts
.globl _disable_interrupts
.globl _jump_to
.globl _monitor_uart_putc
.globl _monitor_uart_getc
.globl monitor_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
ld sp, #STACK_TOP
ld hl, #MONITOR_ROM_SOURCE
ld de, #MONITOR_BASE
ld bc, #(monitor_image_end - MONITOR_BASE)
ldir
ld hl, #TINYBASIC_ROM_SOURCE
ld de, #TINYBASIC_BASE
ld bc, #TINYBASIC_SIZE
ldir
jp init
.area _ITABLE (ABS)
; The executable vector page is part of the image copied to high RAM.
.org MONITOR_BASE
jp init
.org MONITOR_BASE + 0x08
reti
.org MONITOR_BASE + 0x10
reti
.org MONITOR_BASE + 0x18
reti
.org MONITOR_BASE + 0x20
reti
.org MONITOR_BASE + 0x28
reti
.org MONITOR_BASE + 0x30
reti
.org MONITOR_BASE + 0x38
reti
; IM 2 table: the CTC supplies vectors 40, 42, 44, and 46.
.org MONITOR_BASE + 0x40
.dw ctc_isr ; CTC channel 0: vector 0x40
.dw ctc_isr ; CTC channel 1: vector 0x42
.dw ctc_isr ; CTC channel 2: vector 0x44
.dw uart_isr ; CTC channel 3: vector 0x46
; Stable entry points for programs living alongside the monitor.
; C050: write the character in A. C053: return a character in A, or 0.
.org MONITOR_BASE + 0x50
jp monitor_service_putc
.org MONITOR_BASE + 0x53
jp monitor_service_getc
.area _HOME
init:
di
ld sp, #STACK_TOP
call gsinit
; Setup interrupt mode 2
ld a, #0xC0
ld i, a
im 2
call _main
hang:
jr hang
.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
monitor_image_end:
.area _CODE
; Monitor ABI services preserve all working registers. PUTC also preserves AF;
; GETC returns the received byte (or zero) in A.
monitor_service_putc:
push af
push bc
push de
push hl
push ix
push iy
call _monitor_uart_putc
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ret
monitor_service_getc:
push bc
push de
push hl
push ix
push iy
call _monitor_uart_getc
pop iy
pop ix
pop hl
pop de
pop bc
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