1
0

Emulator improvements

This commit is contained in:
Aiden
2026-05-25 17:55:07 +10:00
parent b264037e82
commit 9d93d88840
2 changed files with 523 additions and 59 deletions

View File

@@ -18,6 +18,11 @@ SCI1_SCR = 0xFEDA
SCI1_TDR = 0xFEDB SCI1_TDR = 0xFEDB
SCI1_SSR = 0xFEDC SCI1_SSR = 0xFEDC
SCI1_RDR = 0xFEDD SCI1_RDR = 0xFEDD
P9DDR = 0xFEFE
P9DR = 0xFEFF
IPRA = 0xFF00
IPRE = 0xFF04
WDT_TCSR_R = 0xFEEC
SCI_SCR_TIE = 0x80 SCI_SCR_TIE = 0x80
SCI_SCR_RIE = 0x40 SCI_SCR_RIE = 0x40
@@ -35,6 +40,8 @@ REGISTER_FIELD_START = 0xFE80
REGISTER_FIELD_END = 0xFFFF REGISTER_FIELD_END = 0xFFFF
RAMCR = 0xFF11 RAMCR = 0xFF11
HEARTBEAT_FRAME = bytes([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA]) HEARTBEAT_FRAME = bytes([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA])
VECTOR_INTERVAL_TIMER = 0x0042
VECTOR_SCI1_TXI = 0x0084
class EmulatorError(Exception): class EmulatorError(Exception):
@@ -80,6 +87,7 @@ class SCI1:
tx_events: list[SciTxEvent] = field(default_factory=list) tx_events: list[SciTxEvent] = field(default_factory=list)
tx_frames: list[bytes] = field(default_factory=list) tx_frames: list[bytes] = field(default_factory=list)
_frame_buffer: bytearray = field(default_factory=bytearray) _frame_buffer: bytearray = field(default_factory=bytearray)
tx_ready_delay: int = 0
def read(self, address: int) -> int: def read(self, address: int) -> int:
if address == SCI1_SMR: if address == SCI1_SMR:
@@ -126,6 +134,7 @@ class SCI1:
self.tx_frames.append(bytes(self._frame_buffer)) self.tx_frames.append(bytes(self._frame_buffer))
self._frame_buffer.clear() self._frame_buffer.clear()
self.ssr |= SCI_SSR_TDRE self.ssr |= SCI_SSR_TDRE
self.tx_ready_delay = 2
self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted)) self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted))
def inject_rx(self, value: int) -> None: def inject_rx(self, value: int) -> None:
@@ -135,6 +144,12 @@ class SCI1:
def saw_heartbeat(self) -> bool: def saw_heartbeat(self) -> bool:
return HEARTBEAT_FRAME in self.tx_frames return HEARTBEAT_FRAME in self.tx_frames
def tick(self) -> None:
if self.tx_ready_delay:
self.tx_ready_delay -= 1
if self.tx_ready_delay == 0:
self.ssr |= SCI_SSR_TDRE
@dataclass @dataclass
class MemoryAccess: class MemoryAccess:
@@ -169,10 +184,22 @@ class MemoryMap:
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR): if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
value = self.sci1.read(address) value = self.sci1.read(address)
self._set_register(address, value) self._set_register(address, value)
elif address in self.external:
value = self.external[address]
elif address in (0xF200, 0xF201):
# LCD E-clock/status space. Default to ready/zero so boot can pass
# busy-flag polling until a fuller external bus model exists.
value = 0x00
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END: elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
value = self.ram[address - ON_CHIP_RAM_START] value = self.ram[address - ON_CHIP_RAM_START]
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END: elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
value = self.registers[address - REGISTER_FIELD_START] value = self.registers[address - REGISTER_FIELD_START]
if address == P9DR and not (self.registers[P9DDR - REGISTER_FIELD_START] & 0x80):
# P97 is used as an input during the serial panel/camera-side
# bit-bang handshake. With no external device modeled, hold the
# input low so the firmware sees an idle/acknowledged bus rather
# than reading back its previous output latch forever.
value &= 0x7F
elif self.rom.contains(address): elif self.rom.contains(address):
value = self.rom.u8(address) value = self.rom.u8(address)
else: else:
@@ -196,9 +223,10 @@ class MemoryMap:
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END: elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
self._set_register(address, value) self._set_register(address, value)
elif self.rom.contains(address): elif self.rom.contains(address):
# ROM writes are logged but ignored. This keeps the scaffold useful # The ROM image spans the whole address space, but the H8/536 map
# for accidental writes without mutating the loaded image. # can place external RAM/peripherals in these ranges. Keep writes
pass # as external overrides while leaving instruction fetch immutable.
self.external[address] = value
else: else:
self.external[address] = value self.external[address] = value
self._log("write", address, 1, value) self._log("write", address, 1, value)
@@ -223,6 +251,10 @@ class CPUState:
cycles: int = 0 cycles: int = 0
steps: int = 0 steps: int = 0
z: bool = False z: bool = False
c: bool = False
n: bool = False
v: bool = False
interrupt_depth: int = 0
@dataclass @dataclass
@@ -254,13 +286,15 @@ class RunReport:
class H8536Emulator: class H8536Emulator:
def __init__(self, rom_bytes: bytes) -> None: def __init__(self, rom_bytes: bytes, *, interval_steps: int = 2048) -> None:
if not rom_bytes: if not rom_bytes:
raise ValueError("ROM image is empty") raise ValueError("ROM image is empty")
self.sci1 = SCI1() self.sci1 = SCI1()
self.memory = MemoryMap(rom_bytes, self.sci1) self.memory = MemoryMap(rom_bytes, self.sci1)
self.cpu = CPUState() self.cpu = CPUState()
self.vectors = read_vectors_min(self.memory.rom) self.vectors = read_vectors_min(self.memory.rom)
self.interval_steps = max(1, interval_steps)
self._interval_counter = 0
self.reset() self.reset()
def reset(self) -> None: def reset(self) -> None:
@@ -284,12 +318,35 @@ class H8536Emulator:
if raw[0] == 0x00: if raw[0] == 0x00:
pass pass
elif raw[0] == 0x02 and len(raw) == 2:
self._pop_register_mask(raw[1])
elif raw[0] == 0x12 and len(raw) == 2:
self._push_register_mask(raw[1])
elif raw[0] in (0x01, 0x06, 0x07) and len(raw) == 3 and 0xB8 <= raw[1] <= 0xBF:
next_pc = self._scb(raw, pc, next_pc)
elif raw[0] in (0x04, 0x0C):
next_pc = self._execute_general(pc, next_pc)
elif 0x40 <= raw[0] <= 0x47 and len(raw) == 2:
reg = raw[0] & 0x07
self._cmp(self._reg_read(reg, 1), raw[1], 1)
elif 0x48 <= raw[0] <= 0x4F and len(raw) == 3:
reg = raw[0] & 0x07
self._cmp(self.cpu.regs[reg], int.from_bytes(raw[1:3], "big"), 2)
elif 0x50 <= raw[0] <= 0x57 and len(raw) == 2:
self._reg_write(raw[0] & 0x07, raw[1], 1)
elif 0x58 <= raw[0] <= 0x5F and len(raw) == 3: elif 0x58 <= raw[0] <= 0x5F and len(raw) == 3:
self.cpu.regs[raw[0] & 0x07] = int.from_bytes(raw[1:3], "big") self.cpu.regs[raw[0] & 0x07] = int.from_bytes(raw[1:3], "big")
elif raw[:2] == bytes([0x0C, 0x07]) and len(raw) == 4: self._set_logic_flags(self.cpu.regs[raw[0] & 0x07], 2)
self.cpu.sr = int.from_bytes(raw[2:4], "big") elif raw[0] in (0x0E, 0x1E, 0x18):
next_pc = self._direct_call(raw, next_pc)
elif raw[0] == 0x19:
next_pc = self._pop16()
elif raw[0] == 0x0A:
next_pc = self._return_from_interrupt()
elif raw[0] in (0x15, 0x1D) and len(raw) >= 4: elif raw[0] in (0x15, 0x1D) and len(raw) >= 4:
next_pc = self._execute_general_abs(raw, pc, next_pc) next_pc = self._execute_general(pc, next_pc)
elif raw[0] in range(0xA0, 0x100):
next_pc = self._execute_general(pc, next_pc)
elif raw[0] in range(0x20, 0x30) and len(raw) == 2: elif raw[0] in range(0x20, 0x30) and len(raw) == 2:
next_pc = self._branch8(raw, pc, next_pc) next_pc = self._branch8(raw, pc, next_pc)
elif raw[0] in range(0x30, 0x40) and len(raw) == 3: elif raw[0] in range(0x30, 0x40) and len(raw) == 3:
@@ -300,6 +357,7 @@ class H8536Emulator:
self.cpu.pc = next_pc self.cpu.pc = next_pc
self.cpu.steps += 1 self.cpu.steps += 1
self.cpu.cycles += self._rough_cycles(raw) self.cpu.cycles += self._rough_cycles(raw)
self._tick_peripherals()
return f"{h16(pc)}: {' '.join(f'{byte:02X}' for byte in raw):<17} {text}" return f"{h16(pc)}: {' '.join(f'{byte:02X}' for byte in raw):<17} {text}"
def run(self, max_steps: int, trace: bool = False, stop_on_heartbeat: bool = False) -> RunReport: def run(self, max_steps: int, trace: bool = False, stop_on_heartbeat: bool = False) -> RunReport:
@@ -330,51 +388,134 @@ class H8536Emulator:
trace=trace_lines, trace=trace_lines,
) )
def _execute_general_abs(self, raw: bytes, pc: int, next_pc: int) -> int: def _execute_general(self, pc: int, next_pc: int) -> int:
size = "W" if raw[0] == 0x1D else "B" ea = self._decode_ea(pc)
address = int.from_bytes(raw[1:3], "big") op = self.memory.rom.u8(pc + ea["length"])
op = raw[3] size = int(ea["size"])
raw = self.memory.rom.slice(pc, self._general_length(pc, ea, op))
if op == 0x06: if op == 0x00:
value = raw[4] ext = self.memory.rom.u8(pc + int(ea["length"]) + 1)
self.memory.write8(address, value) ext_base = ext & 0xF8
elif op == 0x07: reg = ext & 0x07
value = int.from_bytes(raw[4:6], "big") if ext_base == 0x80:
self.memory.write16(address, value) value = self._read_ea(ea, 1)
elif 0x90 <= op <= 0x97: self._reg_write(reg, value, 1)
reg = self.cpu.regs[op & 0x07] self._set_logic_flags(value, 1)
if size == "W": elif ext_base == 0x90:
self.memory.write16(address, reg) self._write_ea(ea, self._reg_read(reg, 1), 1)
else: else:
self.memory.write8(address, reg & 0xFF) raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text)
elif 0x80 <= op <= 0x87: return next_pc
reg = op & 0x07
self.cpu.regs[reg] = self.memory.read16(address) if size == "W" else self.memory.read8(address) if op in (0x06, 0x07):
elif 0xC0 <= op <= 0xCF: value = raw[-1] if op == 0x06 else int.from_bytes(raw[-2:], "big")
self._write_ea(ea, value, 1 if op == 0x06 else 2)
self._set_logic_flags(value, 1 if op == 0x06 else 2)
return next_pc
base = op & 0xF8
rd = op & 0x07
if ea["mode"] == "imm" and base in (0x48, 0x58, 0x68):
value = self._read_ea(ea, size)
if base == 0x48:
self.cpu.sr |= value
elif base == 0x58:
self.cpu.sr &= value
else:
self.cpu.sr ^= value
elif base == 0x80:
value = self._read_ea(ea, size)
self._reg_write(rd, value, size)
self._set_logic_flags(value, size)
elif base == 0x90:
self._write_ea(ea, self._reg_read(rd, size), size)
elif base == 0x20:
lhs = self._reg_read(rd, size)
rhs = self._read_ea(ea, size)
result = lhs + rhs
self._reg_write(rd, result, size)
self._set_add_flags(lhs, rhs, result, size)
elif base == 0x30:
lhs = self._reg_read(rd, size)
rhs = self._read_ea(ea, size)
result = lhs - rhs
self._reg_write(rd, result, size)
self._set_sub_flags(lhs, rhs, result, size)
elif base == 0x50:
result = self._reg_read(rd, size) & self._read_ea(ea, size)
self._reg_write(rd, result, size)
self._set_logic_flags(result, size)
elif base == 0x40:
result = self._reg_read(rd, size) | self._read_ea(ea, size)
self._reg_write(rd, result, size)
self._set_logic_flags(result, size)
elif base == 0x60:
result = self._reg_read(rd, size) ^ self._read_ea(ea, size)
self._reg_write(rd, result, size)
self._set_logic_flags(result, size)
elif base == 0x70:
self._cmp(self._reg_read(rd, size), self._read_ea(ea, size), size)
elif op in (0x08, 0x09, 0x0C, 0x0D):
delta = {0x08: 1, 0x09: 2, 0x0C: -1, 0x0D: -2}[op]
old = self._read_ea(ea, size)
result = old + delta
self._write_ea(ea, result, size)
if delta >= 0:
self._set_add_flags(old, delta, result, size)
else:
self._set_sub_flags(old, -delta, result, size)
elif op == 0x13:
self._write_ea(ea, 0, size)
self._set_logic_flags(0, size)
elif op == 0x16:
self._set_logic_flags(self._read_ea(ea, size), size)
elif op == 0x10:
value = self._read_ea(ea, size)
result = ((value & 0xFF) << 8) | ((value >> 8) & 0xFF)
self._write_ea(ea, result, 2)
self._set_logic_flags(result, 2)
elif op == 0x12:
value = self._read_ea(ea, size) & 0xFF
self._write_ea(ea, value, 2 if size == 2 else 1)
self._set_logic_flags(value, size)
elif op == 0x1A:
value = self._read_ea(ea, size)
result = (value << 1) & _mask(size)
self.cpu.c = bool(value & _sign_bit(size))
self._write_ea(ea, result, size)
self._set_logic_flags(result, size)
elif op == 0x1B:
value = self._read_ea(ea, size)
result = value >> 1
self.cpu.c = bool(value & 1)
self._write_ea(ea, result, size)
self._set_logic_flags(result, size)
elif 0xC0 <= op <= 0xFF:
bit = op & 0x0F bit = op & 0x0F
self.memory.write8(address, self.memory.read8(address) | (1 << bit)) self._bit_operation(ea, size, op & 0xF0, bit)
elif 0xD0 <= op <= 0xDF: elif base in (0x48, 0x58, 0x68, 0x78):
bit = op & 0x0F bit = self._reg_read(rd, 1) & 0x0F
self.memory.write8(address, self.memory.read8(address) & ~(1 << bit)) self._bit_operation(ea, size, base + 0x80, bit)
elif 0xF0 <= op <= 0xFF: elif base == 0x88:
bit = op & 0x0F value = self._read_ea(ea, size)
self.cpu.z = not bool(self.memory.read8(address) & (1 << bit)) if size == 2 and rd == 0:
self.cpu.sr = value & 0xFFFF
elif size == 1 and rd == 1:
self.cpu.br = value & 0xFF
else:
raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text)
elif base == 0x98:
if size == 2 and rd == 0:
self._write_ea(ea, self.cpu.sr, 2)
elif size == 1 and rd == 1:
self._write_ea(ea, self.cpu.br, 1)
else:
raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text)
elif op in (0x04, 0x05): elif op in (0x04, 0x05):
if op == 0x04: compare_size = 1 if op == 0x04 else 2
value = raw[4] immediate = raw[-1] if op == 0x04 else int.from_bytes(raw[-2:], "big")
actual = self.memory.read8(address) self._cmp(self._read_ea(ea, compare_size), immediate, compare_size)
else:
value = int.from_bytes(raw[4:6], "big")
actual = self.memory.read16(address)
self.cpu.z = actual == value
elif op == 0x08:
value = self.memory.read16(address) if size == "W" else self.memory.read8(address)
value = (value + 1) & (0xFFFF if size == "W" else 0xFF)
if size == "W":
self.memory.write16(address, value)
else:
self.memory.write8(address, value)
self.cpu.z = value == 0
else: else:
raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text)
return next_pc return next_pc
@@ -383,11 +524,7 @@ class H8536Emulator:
cond = raw[0] & 0x0F cond = raw[0] & 0x0F
disp = _s8(raw[1]) disp = _s8(raw[1])
target = (pc + 2 + disp) & 0xFFFF target = (pc + 2 + disp) & 0xFFFF
if cond == 0x0: if self._branch_condition(cond):
return target
if cond == 0x7 and self.cpu.z:
return target
if cond == 0x6 and not self.cpu.z:
return target return target
return next_pc return next_pc
@@ -395,21 +532,273 @@ class H8536Emulator:
cond = raw[0] & 0x0F cond = raw[0] & 0x0F
disp = _s16(int.from_bytes(raw[1:3], "big")) disp = _s16(int.from_bytes(raw[1:3], "big"))
target = (pc + 3 + disp) & 0xFFFF target = (pc + 3 + disp) & 0xFFFF
if cond == 0x0: if self._branch_condition(cond):
return target
if cond == 0x7 and self.cpu.z:
return target
if cond == 0x6 and not self.cpu.z:
return target return target
return next_pc return next_pc
def _rough_cycles(self, raw: bytes) -> int: def _rough_cycles(self, raw: bytes) -> int:
if raw[0] in (0x15, 0x1D): if raw[0] in (0x15, 0x1D):
return 9 return 9
if raw[0] in (0x0E, 0x1E, 0x18):
return 14
if raw[0] in (0x19, 0x0A):
return 12
if raw[0] in range(0x20, 0x40): if raw[0] in range(0x20, 0x40):
return 8 if (raw[0] & 0x0F) == 0 else 4 return 8 if (raw[0] & 0x0F) == 0 else 4
return 3 return 3
def _direct_call(self, raw: bytes, next_pc: int) -> int:
self._push16(next_pc)
if raw[0] == 0x0E:
return (next_pc + _s8(raw[1])) & 0xFFFF
if raw[0] == 0x1E:
return (next_pc + _s16(int.from_bytes(raw[1:3], "big"))) & 0xFFFF
return int.from_bytes(raw[1:3], "big")
def _scb(self, raw: bytes, pc: int, next_pc: int) -> int:
reg = raw[1] & 0x07
condition = {0x01: False, 0x06: not self.cpu.z, 0x07: self.cpu.z}[raw[0]]
if condition:
return next_pc
value = (self.cpu.regs[reg] - 1) & 0xFFFF
self.cpu.regs[reg] = value
self.cpu.z = value == 0
if value != 0:
return (pc + 3 + _s8(raw[2])) & 0xFFFF
return next_pc
def _tick_peripherals(self) -> None:
self.sci1.tick()
self._interval_counter += 1
self._service_pending_interrupt()
def _service_pending_interrupt(self) -> None:
if self.cpu.interrupt_depth:
return
candidates: list[tuple[int, int, str]] = []
if self.sci1.scr & SCI_SCR_TIE and self.sci1.ssr & SCI_SSR_TDRE:
target = self._vector_target(VECTOR_SCI1_TXI)
if target is not None:
candidates.append((self._sci1_priority(), target, "sci1_txi"))
if self._interval_counter >= self.interval_steps:
target = self._vector_target(VECTOR_INTERVAL_TIMER)
if target is not None:
candidates.append((self._interval_priority(), target, "interval_timer"))
if not candidates:
return
priority, target, source = max(candidates, key=lambda item: item[0])
if priority <= self._interrupt_mask():
return
if source == "interval_timer":
self._interval_counter = 0
self._enter_interrupt(target)
def _enter_interrupt(self, target: int) -> None:
self._push16(self.cpu.sr)
self._push16(self.cpu.pc)
self.cpu.pc = target & 0xFFFF
self.cpu.interrupt_depth += 1
def _return_from_interrupt(self) -> int:
pc = self._pop16()
self.cpu.sr = self._pop16()
if self.cpu.interrupt_depth:
self.cpu.interrupt_depth -= 1
return pc
def _vector_target(self, vector_address: int) -> int | None:
entry = self.vectors.get(vector_address)
return entry[1] if entry else None
def _interrupt_mask(self) -> int:
return (self.cpu.sr >> 8) & 0x07
def _sci1_priority(self) -> int:
return (self.memory.read8(IPRE) >> 4) & 0x07
def _interval_priority(self) -> int:
return (self.memory.read8(IPRA) >> 4) & 0x07
def _push16(self, value: int) -> None:
sp = (self.cpu.regs[7] - 2) & 0xFFFF
self.cpu.regs[7] = sp
self.memory.write16(sp, value)
def _pop16(self) -> int:
sp = self.cpu.regs[7] & 0xFFFF
value = self.memory.read16(sp)
self.cpu.regs[7] = (sp + 2) & 0xFFFF
return value
def _push_register_mask(self, mask: int) -> None:
for reg in range(8):
if mask & (1 << reg):
self._push16(self.cpu.regs[reg])
def _pop_register_mask(self, mask: int) -> None:
for reg in reversed(range(8)):
if mask & (1 << reg):
self.cpu.regs[reg] = self._pop16()
def _decode_ea(self, pc: int) -> dict[str, int | str | None]:
first = self.memory.rom.u8(pc)
if 0xA0 <= first <= 0xAF:
return {"mode": "reg", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None}
if 0xB0 <= first <= 0xBF:
return {"mode": "predec", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None}
if 0xC0 <= first <= 0xCF:
return {"mode": "postinc", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None}
if 0xD0 <= first <= 0xDF:
return {"mode": "indirect", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None}
if 0xE0 <= first <= 0xEF:
return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 2, "value": _s8(self.memory.rom.u8(pc + 1))}
if 0xF0 <= first <= 0xFF:
return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 3, "value": _s16(self.memory.rom.u16(pc + 1))}
if first in (0x04, 0x0C):
size = 2 if first == 0x0C else 1
value = self.memory.rom.u16(pc + 1) if size == 2 else self.memory.rom.u8(pc + 1)
return {"mode": "imm", "reg": None, "size": size, "length": 3 if size == 2 else 2, "value": value}
if first in (0x15, 0x1D):
return {"mode": "abs16", "reg": None, "size": 2 if first == 0x1D else 1, "length": 3, "value": self.memory.rom.u16(pc + 1)}
raise UnsupportedInstruction(pc, self.memory.rom.slice(pc, 1), H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text)
def _general_length(self, pc: int, ea: dict[str, int | str | None], op: int) -> int:
length = int(ea["length"]) + 1
if op == 0x00:
return length + 1
if op == 0x06:
return length + 1
if op in (0x07, 0x05):
return length + 2
if op == 0x04:
return length + 1
return length
def _ea_address(self, ea: dict[str, int | str | None], size: int, *, write: bool = False) -> int:
mode = ea["mode"]
reg = ea.get("reg")
if mode == "abs16":
return int(ea["value"]) & 0xFFFF
if mode == "indirect":
return self.cpu.regs[int(reg)] & 0xFFFF
if mode == "disp":
return (self.cpu.regs[int(reg)] + int(ea["value"])) & 0xFFFF
if mode == "predec":
self.cpu.regs[int(reg)] = (self.cpu.regs[int(reg)] - size) & 0xFFFF
return self.cpu.regs[int(reg)]
if mode == "postinc":
address = self.cpu.regs[int(reg)] & 0xFFFF
if not write:
self.cpu.regs[int(reg)] = (self.cpu.regs[int(reg)] + size) & 0xFFFF
return address
raise EmulatorError(f"EA mode {mode} does not have an address")
def _read_ea(self, ea: dict[str, int | str | None], size: int) -> int:
mode = ea["mode"]
if mode == "imm":
return int(ea["value"]) & _mask(size)
if mode == "reg":
return self._reg_read(int(ea["reg"]), size)
address = self._ea_address(ea, size)
return self.memory.read16(address) if size == 2 else self.memory.read8(address)
def _write_ea(self, ea: dict[str, int | str | None], value: int, size: int) -> None:
if ea["mode"] == "reg":
self._reg_write(int(ea["reg"]), value, size)
return
address = self._ea_address(ea, size, write=True)
if size == 2:
self.memory.write16(address, value)
else:
self.memory.write8(address, value)
def _reg_read(self, reg: int, size: int) -> int:
value = self.cpu.regs[reg] & 0xFFFF
return value if size == 2 else value & 0xFF
def _reg_write(self, reg: int, value: int, size: int) -> None:
if size == 2:
self.cpu.regs[reg] = value & 0xFFFF
else:
self.cpu.regs[reg] = (self.cpu.regs[reg] & 0xFF00) | (value & 0xFF)
def _cmp(self, lhs: int, rhs: int, size: int) -> None:
self._set_sub_flags(lhs, rhs, lhs - rhs, size)
def _set_logic_flags(self, value: int, size: int) -> None:
value &= _mask(size)
self.cpu.z = value == 0
self.cpu.n = bool(value & _sign_bit(size))
self.cpu.v = False
def _set_add_flags(self, lhs: int, rhs: int, result: int, size: int) -> None:
mask = _mask(size)
sign = _sign_bit(size)
result &= mask
lhs &= mask
rhs &= mask
self.cpu.z = result == 0
self.cpu.n = bool(result & sign)
self.cpu.c = lhs + rhs > mask
self.cpu.v = bool((~(lhs ^ rhs) & (lhs ^ result) & sign) != 0)
def _set_sub_flags(self, lhs: int, rhs: int, result: int, size: int) -> None:
mask = _mask(size)
sign = _sign_bit(size)
result &= mask
lhs &= mask
rhs &= mask
self.cpu.z = result == 0
self.cpu.n = bool(result & sign)
self.cpu.c = lhs < rhs
self.cpu.v = bool(((lhs ^ rhs) & (lhs ^ result) & sign) != 0)
def _bit_operation(self, ea: dict[str, int | str | None], size: int, op_base: int, bit: int) -> None:
value = self._read_ea(ea, size)
bit &= (15 if size == 2 else 7)
mask = 1 << bit
self.cpu.z = not bool(value & mask)
if op_base == 0xC0:
self._write_ea(ea, value | mask, size)
elif op_base == 0xD0:
self._write_ea(ea, value & ~mask, size)
elif op_base == 0xE0:
self._write_ea(ea, value ^ mask, size)
def _branch_condition(self, cond: int) -> bool:
if cond == 0x0:
return True
if cond == 0x1:
return False
if cond == 0x2:
return not self.cpu.c and not self.cpu.z
if cond == 0x3:
return self.cpu.c or self.cpu.z
if cond == 0x4:
return not self.cpu.c
if cond == 0x5:
return self.cpu.c
if cond == 0x6:
return not self.cpu.z
if cond == 0x7:
return self.cpu.z
if cond == 0x8:
return not self.cpu.v
if cond == 0x9:
return self.cpu.v
if cond == 0xA:
return not self.cpu.n
if cond == 0xB:
return self.cpu.n
if cond == 0xC:
return self.cpu.n == self.cpu.v
if cond == 0xD:
return self.cpu.n != self.cpu.v
if cond == 0xE:
return not self.cpu.z and self.cpu.n == self.cpu.v
return self.cpu.z or self.cpu.n != self.cpu.v
def _s8(value: int) -> int: def _s8(value: int) -> int:
return value - 0x100 if value & 0x80 else value return value - 0x100 if value & 0x80 else value
@@ -419,6 +808,14 @@ def _s16(value: int) -> int:
return value - 0x10000 if value & 0x8000 else value return value - 0x10000 if value & 0x8000 else value
def _mask(size: int) -> int:
return 0xFFFF if size == 2 else 0xFF
def _sign_bit(size: int) -> int:
return 0x8000 if size == 2 else 0x80
def discover_rom_path(root: Path) -> Path | None: def discover_rom_path(root: Path) -> Path | None:
candidates = [ candidates = [
root / "ROM" / "M27C512@DIP28_1.BIN", root / "ROM" / "M27C512@DIP28_1.BIN",
@@ -453,6 +850,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument("--trace", action="store_true", help="print decoded/executed instruction trace") parser.add_argument("--trace", action="store_true", help="print decoded/executed instruction trace")
parser.add_argument("--stop-on-heartbeat", action="store_true", help="stop only when 00 00 00 00 80 DA is emitted through SCI1 TDR") parser.add_argument("--stop-on-heartbeat", action="store_true", help="stop only when 00 00 00 00 80 DA is emitted through SCI1 TDR")
parser.add_argument("--memory-map", action="store_true", help="print the scaffolded memory map before running") parser.add_argument("--memory-map", action="store_true", help="print the scaffolded memory map before running")
parser.add_argument("--interval-steps", type=int, default=2048, help="rough step period for the scaffolded timer interrupt")
return parser return parser
@@ -464,7 +862,7 @@ def main(argv: list[str] | None = None) -> int:
print(str(exc)) print(str(exc))
return 2 return 2
emulator = H8536Emulator(rom_bytes) emulator = H8536Emulator(rom_bytes, interval_steps=args.interval_steps)
print(f"rom={rom_path}") print(f"rom={rom_path}")
print(f"reset_vector={h16(emulator.reset_vector())}") print(f"reset_vector={h16(emulator.reset_vector())}")
if args.memory_map: if args.memory_map:

View File

@@ -3,6 +3,8 @@ from pathlib import Path
from h8536.emulator import ( from h8536.emulator import (
HEARTBEAT_FRAME, HEARTBEAT_FRAME,
IPRA,
IPRE,
ON_CHIP_RAM_START, ON_CHIP_RAM_START,
REGISTER_FIELD_START, REGISTER_FIELD_START,
SCI1_SCR, SCI1_SCR,
@@ -18,6 +20,12 @@ from h8536.emulator import (
) )
def rom_with_reset(*, reset: int = 0x1000, size: int = 0x1100) -> bytearray:
rom = bytearray([0xFF] * size)
rom[0:2] = reset.to_bytes(2, "big")
return rom
class EmulatorHarnessTest(unittest.TestCase): class EmulatorHarnessTest(unittest.TestCase):
def test_memory_map_routes_rom_ram_register_and_external(self): def test_memory_map_routes_rom_ram_register_and_external(self):
memory = MemoryMap(bytes([0x12, 0x34, 0x56, 0x78])) memory = MemoryMap(bytes([0x12, 0x34, 0x56, 0x78]))
@@ -71,6 +79,64 @@ class EmulatorHarnessTest(unittest.TestCase):
self.assertEqual(report.steps, 4) self.assertEqual(report.steps, 4)
self.assertFalse(report.heartbeat_seen) self.assertFalse(report.heartbeat_seen)
def test_scb_false_decrements_and_branches_until_zero(self):
rom = rom_with_reset()
rom[0x1000:0x1003] = b"\x58\x00\x03" # MOV:I.W #H'0003, R0
rom[0x1003:0x1006] = b"\x01\xB8\xFD" # SCB/F R0, H'1003
emulator = H8536Emulator(bytes(rom))
report = emulator.run(max_steps=4)
self.assertEqual(report.stopped_reason, "max_steps")
self.assertEqual(emulator.cpu.regs[0], 0)
self.assertEqual(emulator.cpu.pc, 0x1006)
def test_bsr_rts_and_stack_restore_return_to_caller(self):
rom = rom_with_reset()
rom[0x1000:0x1003] = b"\x5F\xFE\x80" # MOV:I.W #H'FE80, R7
rom[0x1003:0x1005] = b"\x0E\x03" # BSR H'1008
rom[0x1005:0x1008] = b"\x59\x12\x34" # MOV:I.W #H'1234, R1
rom[0x1008:0x100B] = b"\x58\xAB\xCD" # MOV:I.W #H'ABCD, R0
rom[0x100B] = 0x19 # RTS
emulator = H8536Emulator(bytes(rom))
emulator.run(max_steps=5)
self.assertEqual(emulator.cpu.regs[0], 0xABCD)
self.assertEqual(emulator.cpu.regs[1], 0x1234)
self.assertEqual(emulator.cpu.regs[7], 0xFE80)
def test_sci1_txi_interrupt_can_emit_through_tdr(self):
rom = rom_with_reset(size=0x1040)
rom[0x0084:0x0086] = (0x1010).to_bytes(2, "big")
rom[0x1000:0x1003] = b"\x5F\xFE\x80" # MOV:I.W #H'FE80, R7
rom[0x1003:0x1008] = bytes([0x15, (IPRE >> 8) & 0xFF, IPRE & 0xFF, 0x06, 0x50])
rom[0x1008:0x100D] = bytes([0x15, (SCI1_SCR >> 8) & 0xFF, SCI1_SCR & 0xFF, 0x06, 0xA0])
rom[0x100D] = 0x00 # NOP after interrupt return
rom[0x1010:0x1015] = bytes([0x15, (SCI1_SCR >> 8) & 0xFF, SCI1_SCR & 0xFF, 0x06, SCI_SCR_TE])
rom[0x1015:0x101A] = bytes([0x15, (SCI1_TDR >> 8) & 0xFF, SCI1_TDR & 0xFF, 0x06, 0x42])
rom[0x101A] = 0x0A # RTE
emulator = H8536Emulator(bytes(rom))
report = emulator.run(max_steps=8)
self.assertEqual(bytes(emulator.sci1.tx_bytes), b"\x42")
self.assertFalse(report.heartbeat_seen)
def test_interval_interrupt_vector_can_fire(self):
rom = rom_with_reset(size=0x1040)
rom[0x0042:0x0044] = (0x1020).to_bytes(2, "big")
rom[0x1000:0x1003] = b"\x5F\xFE\x80" # MOV:I.W #H'FE80, R7
rom[0x1003:0x1008] = bytes([0x15, (IPRA >> 8) & 0xFF, IPRA & 0xFF, 0x06, 0x70])
rom[0x1008:0x100A] = b"\x20\xFC" # BRA H'1006-ish idle loop
rom[0x1020:0x1025] = bytes([0x15, (ON_CHIP_RAM_START >> 8) & 0xFF, ON_CHIP_RAM_START & 0xFF, 0x06, 0x99])
rom[0x1025] = 0x0A # RTE
emulator = H8536Emulator(bytes(rom), interval_steps=2)
emulator.run(max_steps=8)
self.assertEqual(emulator.memory.read8(ON_CHIP_RAM_START), 0x99)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()