112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
"""A tiny Z80 machine-code emitter (just what the ColecoVision/Adam viewer needs).
|
|
|
|
No Z80 assembler is installed, so -- as with the TMS9900 and 6809 emitters -- we
|
|
emit opcodes directly. Little-endian 16-bit operands; 8-bit relative jumps are
|
|
backpatched via labels.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
class Asm:
|
|
def __init__(self, base: int):
|
|
self.base = base
|
|
self.code = bytearray()
|
|
self.labels: dict[str, int] = {}
|
|
self._fix: list[tuple[int, str]] = [] # (pos of rel byte, label)
|
|
|
|
def pos(self) -> int:
|
|
return self.base + len(self.code)
|
|
|
|
def label(self, name: str):
|
|
self.labels[name] = self.pos()
|
|
|
|
def _b(self, *bs):
|
|
self.code += bytes(b & 0xFF for b in bs)
|
|
|
|
def _w(self, v): # 16-bit, little-endian
|
|
self.code += bytes([v & 0xFF, (v >> 8) & 0xFF])
|
|
|
|
# ---- 8-bit loads (immediate) ----
|
|
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_d(self, n): self._b(0x16, n)
|
|
def ld_e(self, n): self._b(0x1E, n)
|
|
|
|
# ---- 16-bit loads (immediate) ----
|
|
def ld_hl(self, nn): self._b(0x21); self._w(nn)
|
|
def ld_de(self, nn): self._b(0x11); self._w(nn)
|
|
def ld_bc(self, nn): self._b(0x01); self._w(nn)
|
|
|
|
# ---- memory <-> A (extended addressing) ----
|
|
def ld_a_mem(self, addr): self._b(0x3A); self._w(addr) # LD A,(nn)
|
|
def ld_mem_a(self, addr): self._b(0x32); self._w(addr) # LD (nn),A
|
|
|
|
# ---- register moves ----
|
|
def ld_a_b(self): self._b(0x78)
|
|
def ld_a_c(self): self._b(0x79)
|
|
def ld_a_d(self): self._b(0x7A)
|
|
def ld_a_e(self): self._b(0x7B)
|
|
def ld_a_h(self): self._b(0x7C)
|
|
def ld_a_l(self): self._b(0x7D)
|
|
def ld_b_a(self): self._b(0x47)
|
|
def ld_c_a(self): self._b(0x4F)
|
|
def ld_a_hl(self): self._b(0x7E) # LD A,(HL)
|
|
def ld_hl_a(self): self._b(0x77) # LD (HL),A
|
|
|
|
# ---- I/O ----
|
|
def out_n_a(self, n): self._b(0xD3, n) # OUT (n),A
|
|
def in_a_n(self, n): self._b(0xDB, n) # IN A,(n)
|
|
|
|
# ---- arithmetic / logic ----
|
|
def inc_a(self): self._b(0x3C)
|
|
def dec_a(self): self._b(0x3D)
|
|
def dec_c(self): self._b(0x0D)
|
|
def inc_hl(self): self._b(0x23)
|
|
def dec_hl(self): self._b(0x2B)
|
|
def inc_de(self): self._b(0x13)
|
|
def dec_de(self): self._b(0x1B)
|
|
def dec_bc(self): self._b(0x0B)
|
|
def or_a(self): self._b(0xB7) # OR A (set flags from A)
|
|
def or_c(self): self._b(0xB1)
|
|
def or_e(self): self._b(0xB3)
|
|
def and_n(self, n): self._b(0xE6, n)
|
|
def cp_n(self, n): self._b(0xFE, n)
|
|
def add_hl_de(self): self._b(0x19)
|
|
|
|
# ---- block ops ----
|
|
def ldir(self): self._b(0xED, 0xB0) # (HL)->(DE), BC times
|
|
|
|
# ---- control ----
|
|
def di(self): self._b(0xF3)
|
|
def ei(self): self._b(0xFB)
|
|
def ret(self): self._b(0xC9)
|
|
def retn(self): self._b(0xED, 0x45)
|
|
def nop(self): self._b(0x00)
|
|
|
|
def jp(self, addr): self._b(0xC3); self._w(addr)
|
|
def jp_label(self, lbl):
|
|
self._b(0xC3); self._fix.append((len(self.code), lbl)); self._w(0)
|
|
|
|
def _jr(self, op, lbl):
|
|
self._b(op); self._fix.append((len(self.code), lbl)); self._b(0)
|
|
|
|
def jr(self, lbl): self._jr(0x18, lbl)
|
|
def jr_nz(self, lbl): self._jr(0x20, lbl)
|
|
def jr_z(self, lbl): self._jr(0x28, lbl)
|
|
def djnz(self, lbl): self._jr(0x10, lbl)
|
|
|
|
def resolve(self) -> bytes:
|
|
for pos, lbl in self._fix:
|
|
target = self.labels[lbl]
|
|
opcode = self.code[pos - 1] # the byte just before the operand
|
|
if opcode == 0xC3: # JP nn (absolute, 2 bytes)
|
|
self.code[pos] = target & 0xFF
|
|
self.code[pos + 1] = (target >> 8) & 0xFF
|
|
else: # JR / DJNZ (relative, 1 byte)
|
|
rel = target - (self.base + pos + 1)
|
|
if not -128 <= rel <= 127:
|
|
raise ValueError(f"relative jump to {lbl} out of range ({rel})")
|
|
self.code[pos] = rel & 0xFF
|
|
return bytes(self.code)
|