8bitlenser/lenser/coco/viewer.py
2026-07-03 19:35:35 -07:00

71 lines
2.6 KiB
Python

"""Generates the CoCo cartridge viewer (Motorola 6809 machine code).
Runs from the Program Pak ROM at $C000 (the CoCo autostarts it). Sets the VDG
graphics mode via PIA1 $FF22 and the SAM video registers (PMODE 3 and 4 are both
6144-byte SAM mode 6; only the $FF22 byte differs), copies the 6144-byte image
from cart ROM down to the video page at $0E00, then holds it.
display: forever (hold), key (poll the keyboard then reset), seconds (count 60 Hz
field-syncs then reset). A Program Pak can't cleanly return to BASIC, so key and
seconds reset the machine (which re-displays the picture).
"""
from __future__ import annotations
from .mc6809 import Asm
CART_BASE = 0xC000
VIDEO = 0x0E00
SCREEN_BYTES = 6144
RATE = 60 # NTSC field-syncs per second
def build(data_src: int, vdg: int = 0xF8, display: str = "forever",
seconds: int = 0) -> bytes:
a = Asm(CART_BASE)
a.orcc(0x50) # mask IRQ + FIRQ; we run standalone
a.lda_imm(vdg)
a.sta_ext(0xFF22) # VDG graphics mode (PMODE 3 = $E0/$E8, PMODE 4 = $F8)
# SAM video mode 6 (V2=1, V1=1, V0=0). SAM regs toggle by address; data ignored.
a.sta_ext(0xFFC5) # V2 set
a.sta_ext(0xFFC3) # V1 set
a.sta_ext(0xFFC0) # V0 clear
# SAM video offset = 7 ($0E00): F0,F1,F2 set; F3..F6 clear.
a.sta_ext(0xFFC7); a.sta_ext(0xFFC9); a.sta_ext(0xFFCB)
a.sta_ext(0xFFCC); a.sta_ext(0xFFCE); a.sta_ext(0xFFD0); a.sta_ext(0xFFD2)
# copy SCREEN_BYTES from cart ROM (data_src) to the video page
a.ldx_imm(data_src)
a.ldu_imm(VIDEO)
a.label("copy")
a.lda_postinc("x")
a.sta_postinc("u")
a.cmpu_imm(VIDEO + SCREEN_BYTES)
a.bne("copy")
# ---- hold the picture ----
if display == "key":
a.clra()
a.sta_ext(0xFF02) # drive all keyboard columns low
a.label("kwait")
a.lda_ext(0xFF00) # row sense
a.ora_imm(0x80) # ignore the joystick-compare bit 7
a.cmpa_imm(0xFF)
a.beq("kwait") # all rows high -> no key, keep waiting
a.jmp_ind(0xFFFE) # reset
elif display == "seconds":
a.ldd_imm(max(1, min(0xFFFF, int(seconds) * RATE)))
a.label("swait")
a.lda_ext(0xFF03) # PIA0 CB1 (field-sync) status
a.bpl("swait") # bit 7 clear -> no new field yet
a.lda_ext(0xFF02) # read PB data to clear the field-sync flag
a.subd_imm(1)
a.bne("swait")
a.jmp_ind(0xFFFE) # reset
else: # forever
a.label("hang")
a.bra("hang")
return a.resolve()