8bitlenser/lenser/sms/z80.py
2026-07-03 19:35:35 -07:00

77 lines
2.9 KiB
Python

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