77 lines
2.9 KiB
Python
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)
|