"""Tiny Z80 machine-code emitter for the SMS VDP-setup viewer. Only the handful of opcodes the viewer needs, with label support (two-pass: the data blocks are appended after the code, so their addresses depend on the code length). Same spirit as the project's other hand-rolled CPU emitters (ti99/tms9900.py, coco/mc6809.py, intv/cp1610.py). """ from __future__ import annotations class Asm: def __init__(self, org: int = 0x0000): self.org = org self.code = bytearray() self.labels: dict[str, int] = {} self.fixups: list[tuple[int, str, str]] = [] # (pos, label, kind) def _b(self, *bs): for b in bs: self.code.append(b & 0xFF) def label(self, name): self.labels[name] = self.org + len(self.code) def set_label(self, name, addr): self.labels[name] = addr def _w(self, v): """emit a 16-bit little-endian operand; v is int or a label name.""" if isinstance(v, str): self.fixups.append((len(self.code), v, "abs")) self._b(0, 0) else: self._b(v & 0xFF, (v >> 8) & 0xFF) def _rel(self, label): self.fixups.append((len(self.code), label, "rel")) self._b(0) # --- instructions --- def nop(self): self._b(0x00) def di(self): self._b(0xF3) def im1(self): self._b(0xED, 0x56) def halt(self): self._b(0x76) def xor_a(self): self._b(0xAF) def ld_sp(self, n): self._b(0x31); self._w(n) def ld_a(self, n): self._b(0x3E, n) def ld_b(self, n): self._b(0x06, n) def ld_c(self, n): self._b(0x0E, n) def ld_hl(self, n): self._b(0x21); self._w(n) def ld_bc(self, n): self._b(0x01); self._w(n) def ld_a_hl(self): self._b(0x7E) # ld a,(hl) def ld_a_b(self): self._b(0x78) def ld_a_c(self): self._b(0x79) def or_n(self, n): self._b(0xF6, n) def or_c(self): self._b(0xB1) def inc_hl(self): self._b(0x23) def inc_c(self): self._b(0x0C) def dec_bc(self): self._b(0x0B) def out_a(self, port): self._b(0xD3, port) # out (port),a def djnz(self, label): self._b(0x10); self._rel(label) def jp(self, label): self._b(0xC3); self._w(label) def jp_nz(self, label): self._b(0xC2); self._w(label) def resolve(self) -> bytes: out = bytearray(self.code) for pos, label, kind in self.fixups: target = self.labels[label] if kind == "abs": out[pos] = target & 0xFF out[pos + 1] = (target >> 8) & 0xFF else: # rel: from the byte AFTER the operand disp = target - (self.org + pos + 1) if not -128 <= disp <= 127: raise ValueError(f"rel jump out of range to {label}") out[pos] = disp & 0xFF return bytes(out)