8bitlenser/lenser/intv/cp1610.py
2026-07-03 19:35:35 -07:00

125 lines
5.3 KiB
Python

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