"""A tiny General Instrument CP1610 machine-code emitter (Intellivision). No CP1610 assembler is installed, so -- as with the TMS9900/Z80/6809 emitters -- we emit opcodes directly. Encodings validated by disassembling a real cart (Astrosmash). Output is a list of 16-bit words (the CP1610 fetches 10-bit "decles" from the low bits; the Intellivision ROM is 16-bit big-endian at $5000). """ from __future__ import annotations class Asm: def __init__(self, base: int): self.base = base self.words: list[int] = [] self.labels: dict[str, int] = {} self._fix: list[tuple[int, str, str]] = [] # (word index, label, kind) def pos(self) -> int: return self.base + len(self.words) def label(self, name: str): self.labels[name] = self.pos() def _w(self, *ws): self.words.extend(w & 0xFFFF for w in ws) # ---- implied ---- def sdbd(self): self._w(0x0001) def eis(self): self._w(0x0002) def dis(self): self._w(0x0003) def tci(self): self._w(0x0005) def clrc(self): self._w(0x0006) def setc(self): self._w(0x0007) def nop(self): self._w(0x0034) # ---- single register ---- def incr(self, r): self._w(0x0008 | r) def decr(self, r): self._w(0x0010 | r) def comr(self, r): self._w(0x0018 | r) def negr(self, r): self._w(0x0020 | r) def adcr(self, r): self._w(0x0028 | r) def tstr(self, r): self._w(0x0080 | (r << 3) | r) # MOVR r,r sets flags # ---- register-register ---- # ---- shifts/rotates (R0-R3 only; n = 1 or 2 bits) ---- def sll(self, r, n=1): self._w(0x0048 | ((n - 1) << 2) | r) # shift left logical def slr(self, r, n=1): self._w(0x0060 | ((n - 1) << 2) | r) # shift right logical def movr(self, s, d): self._w(0x0080 | (s << 3) | d) def addr(self, s, d): self._w(0x00C0 | (s << 3) | d) def subr(self, s, d): self._w(0x0100 | (s << 3) | d) def cmpr(self, s, d): self._w(0x0140 | (s << 3) | d) def andr(self, s, d): self._w(0x0180 | (s << 3) | d) def xorr(self, s, d): self._w(0x01C0 | (s << 3) | d) def clrr(self, r): self._w(0x01C0 | (r << 3) | r) # XORR r,r def jr(self, r): self._w(0x0080 | (r << 3) | 7) # MOVR r,R7 = jump # ---- external reference (direct addr / immediate / indirect) ---- def mvi(self, addr, d): self._w(0x0280 | d, addr) # load (addr)->Rd def mvo(self, s, addr): self._w(0x0240 | s, addr) # store Rs->(addr) def mvii(self, imm, d): self._w(0x0280 | (7 << 3) | d, imm) def addi(self, imm, d): self._w(0x02C0 | (7 << 3) | d, imm) def subi(self, imm, d): self._w(0x0300 | (7 << 3) | d, imm) def cmpi(self, imm, d): self._w(0x0340 | (7 << 3) | d, imm) def andi(self, imm, d): self._w(0x0380 | (7 << 3) | d, imm) def xori(self, imm, d): self._w(0x03C0 | (7 << 3) | d, imm) def mvi_at(self, m, d): self._w(0x0280 | (m << 3) | d) # load (Rm)->Rd def mvo_at(self, s, m): self._w(0x0240 | (m << 3) | s) # store Rs->(Rm) def pshr(self, s): self._w(0x0240 | (6 << 3) | s) # MVO@ Rs,R6 def pulr(self, d): self._w(0x0280 | (6 << 3) | d) # MVI@ R6,Rd # ---- jumps (absolute, 3 words) ---- def _jword(self, addr, reg): self._w(0x0004, (reg << 8) | (((addr >> 10) & 0x3F) << 2), addr & 0x3FF) def j(self, addr): self._jword(addr, 3) # J (no return) def jsr(self, addr): self._jword(addr, 1) # JSR R5,addr def j_label(self, label): i = len(self.words) self._w(0x0004, 0, 0) self._fix.append((i, label, "j")) def jsr_label(self, label): i = len(self.words) self._w(0x0004, 0, 0) self._fix.append((i, label, "jsr")) # ---- branches (opcode + displacement word) ---- def _branch(self, cond, label): i = len(self.words) self._w(0x0200 | cond, 0) # direction/disp filled by resolve self._fix.append((i, label, "b")) def b(self, label): self._branch(0x0, label) def beq(self, label): self._branch(0x4, label) def bnze(self, label): self._branch(0xC, label) def bc(self, label): self._branch(0x1, label) def bnc(self, label): self._branch(0x9, label) def decle(self, *vals): self._w(*vals) def resolve(self) -> list[int]: for i, label, kind in self._fix: target = self.labels[label] if kind == "j": self.words[i + 1] = (3 << 8) | (((target >> 10) & 0x3F) << 2) self.words[i + 2] = target & 0x3FF elif kind == "jsr": self.words[i + 1] = (1 << 8) | (((target >> 10) & 0x3F) << 2) self.words[i + 2] = target & 0x3FF else: # branch a = self.base + i # address of the branch opcode if target <= a: # backward: CPU computes target = a + 1 - disp disp = a + 1 - target self.words[i] |= (1 << 5) else: # forward: target = a + 2 + disp disp = target - a - 2 if not 0 <= disp <= 0xFFFF: raise ValueError(f"branch to {label} out of range") self.words[i + 1] = disp & 0xFFFF return list(self.words)