"""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()