diff --git a/h8536/emulator.py b/h8536/emulator.py index 03eee0d..c2f517b 100644 --- a/h8536/emulator.py +++ b/h8536/emulator.py @@ -18,6 +18,11 @@ SCI1_SCR = 0xFEDA SCI1_TDR = 0xFEDB SCI1_SSR = 0xFEDC SCI1_RDR = 0xFEDD +P9DDR = 0xFEFE +P9DR = 0xFEFF +IPRA = 0xFF00 +IPRE = 0xFF04 +WDT_TCSR_R = 0xFEEC SCI_SCR_TIE = 0x80 SCI_SCR_RIE = 0x40 @@ -35,6 +40,8 @@ REGISTER_FIELD_START = 0xFE80 REGISTER_FIELD_END = 0xFFFF RAMCR = 0xFF11 HEARTBEAT_FRAME = bytes([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA]) +VECTOR_INTERVAL_TIMER = 0x0042 +VECTOR_SCI1_TXI = 0x0084 class EmulatorError(Exception): @@ -80,6 +87,7 @@ class SCI1: tx_events: list[SciTxEvent] = field(default_factory=list) tx_frames: list[bytes] = field(default_factory=list) _frame_buffer: bytearray = field(default_factory=bytearray) + tx_ready_delay: int = 0 def read(self, address: int) -> int: if address == SCI1_SMR: @@ -126,6 +134,7 @@ class SCI1: self.tx_frames.append(bytes(self._frame_buffer)) self._frame_buffer.clear() self.ssr |= SCI_SSR_TDRE + self.tx_ready_delay = 2 self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted)) def inject_rx(self, value: int) -> None: @@ -135,6 +144,12 @@ class SCI1: def saw_heartbeat(self) -> bool: 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 class MemoryAccess: @@ -169,10 +184,22 @@ class MemoryMap: if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR): value = self.sci1.read(address) 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: value = self.ram[address - ON_CHIP_RAM_START] elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END: 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): value = self.rom.u8(address) else: @@ -196,9 +223,10 @@ class MemoryMap: elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END: self._set_register(address, value) elif self.rom.contains(address): - # ROM writes are logged but ignored. This keeps the scaffold useful - # for accidental writes without mutating the loaded image. - pass + # The ROM image spans the whole address space, but the H8/536 map + # can place external RAM/peripherals in these ranges. Keep writes + # as external overrides while leaving instruction fetch immutable. + self.external[address] = value else: self.external[address] = value self._log("write", address, 1, value) @@ -223,6 +251,10 @@ class CPUState: cycles: int = 0 steps: int = 0 z: bool = False + c: bool = False + n: bool = False + v: bool = False + interrupt_depth: int = 0 @dataclass @@ -254,13 +286,15 @@ class RunReport: class H8536Emulator: - def __init__(self, rom_bytes: bytes) -> None: + def __init__(self, rom_bytes: bytes, *, interval_steps: int = 2048) -> None: if not rom_bytes: raise ValueError("ROM image is empty") self.sci1 = SCI1() self.memory = MemoryMap(rom_bytes, self.sci1) self.cpu = CPUState() self.vectors = read_vectors_min(self.memory.rom) + self.interval_steps = max(1, interval_steps) + self._interval_counter = 0 self.reset() def reset(self) -> None: @@ -284,12 +318,35 @@ class H8536Emulator: if raw[0] == 0x00: 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: self.cpu.regs[raw[0] & 0x07] = int.from_bytes(raw[1:3], "big") - elif raw[:2] == bytes([0x0C, 0x07]) and len(raw) == 4: - self.cpu.sr = int.from_bytes(raw[2:4], "big") + self._set_logic_flags(self.cpu.regs[raw[0] & 0x07], 2) + 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: - 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: next_pc = self._branch8(raw, pc, next_pc) elif raw[0] in range(0x30, 0x40) and len(raw) == 3: @@ -300,6 +357,7 @@ class H8536Emulator: self.cpu.pc = next_pc self.cpu.steps += 1 self.cpu.cycles += self._rough_cycles(raw) + self._tick_peripherals() 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: @@ -330,51 +388,134 @@ class H8536Emulator: trace=trace_lines, ) - def _execute_general_abs(self, raw: bytes, pc: int, next_pc: int) -> int: - size = "W" if raw[0] == 0x1D else "B" - address = int.from_bytes(raw[1:3], "big") - op = raw[3] + def _execute_general(self, pc: int, next_pc: int) -> int: + ea = self._decode_ea(pc) + op = self.memory.rom.u8(pc + ea["length"]) + size = int(ea["size"]) + raw = self.memory.rom.slice(pc, self._general_length(pc, ea, op)) - if op == 0x06: - value = raw[4] - self.memory.write8(address, value) - elif op == 0x07: - value = int.from_bytes(raw[4:6], "big") - self.memory.write16(address, value) - elif 0x90 <= op <= 0x97: - reg = self.cpu.regs[op & 0x07] - if size == "W": - self.memory.write16(address, reg) + if op == 0x00: + ext = self.memory.rom.u8(pc + int(ea["length"]) + 1) + ext_base = ext & 0xF8 + reg = ext & 0x07 + if ext_base == 0x80: + value = self._read_ea(ea, 1) + self._reg_write(reg, value, 1) + self._set_logic_flags(value, 1) + elif ext_base == 0x90: + self._write_ea(ea, self._reg_read(reg, 1), 1) else: - self.memory.write8(address, reg & 0xFF) - elif 0x80 <= op <= 0x87: - reg = op & 0x07 - self.cpu.regs[reg] = self.memory.read16(address) if size == "W" else self.memory.read8(address) - elif 0xC0 <= op <= 0xCF: + raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) + return next_pc + + if op in (0x06, 0x07): + 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 - self.memory.write8(address, self.memory.read8(address) | (1 << bit)) - elif 0xD0 <= op <= 0xDF: - bit = op & 0x0F - self.memory.write8(address, self.memory.read8(address) & ~(1 << bit)) - elif 0xF0 <= op <= 0xFF: - bit = op & 0x0F - self.cpu.z = not bool(self.memory.read8(address) & (1 << bit)) + self._bit_operation(ea, size, op & 0xF0, bit) + elif base in (0x48, 0x58, 0x68, 0x78): + bit = self._reg_read(rd, 1) & 0x0F + self._bit_operation(ea, size, base + 0x80, bit) + elif base == 0x88: + value = self._read_ea(ea, size) + 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): - if op == 0x04: - value = raw[4] - actual = self.memory.read8(address) - 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 + compare_size = 1 if op == 0x04 else 2 + immediate = raw[-1] if op == 0x04 else int.from_bytes(raw[-2:], "big") + self._cmp(self._read_ea(ea, compare_size), immediate, compare_size) else: raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text) return next_pc @@ -383,11 +524,7 @@ class H8536Emulator: cond = raw[0] & 0x0F disp = _s8(raw[1]) target = (pc + 2 + disp) & 0xFFFF - if cond == 0x0: - return target - if cond == 0x7 and self.cpu.z: - return target - if cond == 0x6 and not self.cpu.z: + if self._branch_condition(cond): return target return next_pc @@ -395,21 +532,273 @@ class H8536Emulator: cond = raw[0] & 0x0F disp = _s16(int.from_bytes(raw[1:3], "big")) target = (pc + 3 + disp) & 0xFFFF - if cond == 0x0: - return target - if cond == 0x7 and self.cpu.z: - return target - if cond == 0x6 and not self.cpu.z: + if self._branch_condition(cond): return target return next_pc def _rough_cycles(self, raw: bytes) -> int: if raw[0] in (0x15, 0x1D): 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): return 8 if (raw[0] & 0x0F) == 0 else 4 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: 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 +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: candidates = [ 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("--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("--interval-steps", type=int, default=2048, help="rough step period for the scaffolded timer interrupt") return parser @@ -464,7 +862,7 @@ def main(argv: list[str] | None = None) -> int: print(str(exc)) return 2 - emulator = H8536Emulator(rom_bytes) + emulator = H8536Emulator(rom_bytes, interval_steps=args.interval_steps) print(f"rom={rom_path}") print(f"reset_vector={h16(emulator.reset_vector())}") if args.memory_map: diff --git a/tests/test_emulator.py b/tests/test_emulator.py index 2d3f370..68ecad2 100644 --- a/tests/test_emulator.py +++ b/tests/test_emulator.py @@ -3,6 +3,8 @@ from pathlib import Path from h8536.emulator import ( HEARTBEAT_FRAME, + IPRA, + IPRE, ON_CHIP_RAM_START, REGISTER_FIELD_START, 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): def test_memory_map_routes_rom_ram_register_and_external(self): memory = MemoryMap(bytes([0x12, 0x34, 0x56, 0x78])) @@ -71,6 +79,64 @@ class EmulatorHarnessTest(unittest.TestCase): self.assertEqual(report.steps, 4) 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__": unittest.main()