116 lines
4.3 KiB
Python
116 lines
4.3 KiB
Python
"""Generates the TI-99/4A cartridge viewer (TMS9900 machine code).
|
|
|
|
Sets the TMS9918A to Graphics Mode 2 (256x192 bitmap), builds the name table,
|
|
copies the 6144-byte pattern from cartridge ROM to VRAM >0000, and expands the
|
|
768 per-cell colour bytes x8 into the 6144-byte colour table at VRAM >2000.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from .tms9900 import Asm
|
|
|
|
VDP_DATA = 0x8C00 # write VRAM data
|
|
VDP_CTRL = 0x8C02 # write address / register
|
|
VDP_STATUS = 0x8802 # read VDP status (bit 7 = frame flag, cleared on read)
|
|
WORKSPACE = 0x8300 # scratchpad RAM
|
|
|
|
# VDP register values for Graphics Mode 2.
|
|
VDP_REGS = [
|
|
0x02, # R0 M3=1 (bitmap)
|
|
0xC0, # R1 16K + display on, interrupts off
|
|
0x0E, # R2 name table >3800
|
|
0xFF, # R3 colour table >2000 (full 768 rows)
|
|
0x03, # R4 pattern table >0000 (full 768 rows)
|
|
0x36, # R5 sprite attr >1B00 (parked)
|
|
0x07, # R6 sprite patt >3800 (parked)
|
|
0x01, # R7 backdrop = black
|
|
]
|
|
|
|
PATTERN_BYTES = 6144
|
|
NCELLS = 768
|
|
|
|
|
|
def _set_write_addr(a: Asm, addr: int):
|
|
"""Point the VDP write address at VRAM `addr` (low byte then high|>40)."""
|
|
a.li(0, (addr & 0xFF) << 8); a.movb_r_sym(0, VDP_CTRL)
|
|
a.li(0, (((addr >> 8) | 0x40) & 0xFF) << 8); a.movb_r_sym(0, VDP_CTRL)
|
|
|
|
|
|
def build(code_base: int, data_addr: int, display: str = "forever",
|
|
seconds: int = 0, video: str = "ntsc") -> bytes:
|
|
"""Emit the viewer. `data_addr` = ROM address of the 6912-byte image data.
|
|
|
|
`display` (forever/key/seconds) chooses how long the picture is held; on
|
|
key/seconds the console is reset (BLWP @>0000) back to the TI title screen.
|
|
"""
|
|
a = Asm(code_base)
|
|
a.limi(0x0000) # interrupts off
|
|
a.lwpi(WORKSPACE) # our register file in scratchpad
|
|
|
|
# ---- programme the 8 VDP registers ----
|
|
for reg, val in enumerate(VDP_REGS):
|
|
a.li(0, val << 8)
|
|
a.movb_r_sym(0, VDP_CTRL)
|
|
a.li(0, (0x80 | reg) << 8)
|
|
a.movb_r_sym(0, VDP_CTRL)
|
|
|
|
# ---- name table >3800 = 0,1,...,255 repeated three times (768 bytes) ----
|
|
_set_write_addr(a, 0x3800)
|
|
a.clr(3) # R3 high byte = current value
|
|
a.li(2, NCELLS) # 768 entries
|
|
a.label("nameloop")
|
|
a.movb_r_sym(3, VDP_DATA)
|
|
a.ai(3, 0x0100) # value += 1 (in high byte, wraps mod 256)
|
|
a.dec(2)
|
|
a.jne("nameloop")
|
|
|
|
# ---- pattern table >0000 = 6144 bytes copied from ROM ----
|
|
_set_write_addr(a, 0x0000)
|
|
a.li(1, data_addr)
|
|
a.li(2, PATTERN_BYTES)
|
|
a.label("patloop")
|
|
a.movb_sinc_sym(1, VDP_DATA)
|
|
a.dec(2)
|
|
a.jne("patloop")
|
|
|
|
# ---- colour table >2000 = each cell colour written 8x (768 -> 6144) ----
|
|
_set_write_addr(a, 0x2000)
|
|
a.li(1, data_addr + PATTERN_BYTES)
|
|
a.li(2, NCELLS)
|
|
a.label("colloop")
|
|
a.movb_sinc_r(1, 4) # R4 high byte = colour byte, advance ROM ptr
|
|
for _ in range(8):
|
|
a.movb_r_sym(4, VDP_DATA)
|
|
a.dec(2)
|
|
a.jne("colloop")
|
|
|
|
# ---- how long to hold the picture ----
|
|
if display == "seconds":
|
|
rate = 50 if video == "pal" else 60
|
|
frames = max(1, min(0xFFFF, int(seconds) * rate))
|
|
a.li(2, frames) # R2 = frames to wait
|
|
a.label("fwait")
|
|
a.movb_sym_r(VDP_STATUS, 1) # read status (clears frame flag)
|
|
a.andi(1, 0x8000) # bit 7 = a new frame elapsed
|
|
a.jeq("fwait")
|
|
a.dec(2)
|
|
a.jne("fwait")
|
|
a.blwp_sym(0x0000) # reset -> TI title screen
|
|
elif display == "key":
|
|
# scan keyboard columns 0..5; any pressed key (a row reads 0) -> reset.
|
|
a.label("kscan")
|
|
for col in range(6):
|
|
a.li(12, 0x0024) # CRU base for the column select
|
|
a.li(5, col << 8) # column in the low 3 bits of the high byte
|
|
a.ldcr(5, 3) # drive the 3 column-select lines
|
|
a.li(12, 0x0006) # CRU base for the 8 row inputs
|
|
a.stcr(6, 8) # read rows into R6 high byte (idle = >FF)
|
|
a.ci(6, 0xFF00) # any key down makes a row 0
|
|
a.jne("kdone")
|
|
a.jmp("kscan")
|
|
a.label("kdone")
|
|
a.blwp_sym(0x0000) # reset -> TI title screen
|
|
else: # forever
|
|
a.label("halt")
|
|
a.jmp("halt")
|
|
return a.resolve()
|