--- /dev/null
+#!/usr/bin/env python3
+
+from __future__ import annotations
+from typing import *
+
+# ------------------------------------------------------------------------------
+
+
+class Cpu0(object):
+ INSTRS = {
+ 0x00: 'NOP', 0x01: '#8', 0x02: '#32', 0x03: '@8',
+ 0x04: '@32', 0x05: '!8', 0x06: '!32', 0x07: 'r@',
+ 0x08: '>r', 0x09: 'r>', 0x0a: 'CALL', 0x0b: ';',
+ 0x0c: 'JMP', 0x0d: 'JZ', 0x0e: 'JN', 0x0f: 'JP',
+ 0x10: '+', 0x11: '-', 0x12: '&', 0x13: '|',
+ 0x14: '^', 0x15: '~', 0x16: 'DUP', 0x17: 'DROP',
+ 0x18: 'SWAP', 0x19: 'IEN', 0x1a: 'IDIS', 0x1b: 'LSR',
+ 0x1c: 'ASR', 0x1d: 'SHL',
+ }
+
+ def __init__(self):
+ self._pc = 0
+ self._param_stack = list()
+ self._return_stack = list()
+ self._regions = dict()
+ self._syms = list()
+
+ def load_bin(self, data: bytes, offset: int = 0):
+ for i, b in enumerate(data):
+ self.st8(offset + i, b)
+
+ def load_map(self, filename: str):
+ with open(filename, 'r') as f:
+ for line in f:
+ saddr, symbol = line.split(' ', maxsplit=1)
+ addr = int(saddr, 0)
+ self._syms.append((addr, symbol.strip()))
+
+ def addr_to_symbol(self, address: int) -> str:
+ # Assumes symbols are sorted
+ found_symbol = ''
+ found_offset = 0
+ for sym_addr, symbol in self._syms:
+ if address >= sym_addr:
+ found_symbol = symbol
+ found_offset = address - sym_addr
+ return f'{found_symbol}+0x{found_offset:x}'
+
+ def symbol_to_addr(self, symbol: str) -> int:
+ for sym_addr, found_symbol in self._syms:
+ if found_symbol == symbol:
+ return sym_addr
+ assert False
+
+ def p_push(self, value: int) -> None:
+ self._param_stack.append(value & 0xffffffff)
+
+ def p_pop(self) -> int:
+ return self._param_stack.pop()
+
+ def p_peek(self) -> int:
+ return self._param_stack[-1]
+
+ def r_push(self, value: int) -> None:
+ self._return_stack.append(value & 0xffffffff)
+
+ def r_pop(self) -> int:
+ return self._return_stack.pop()
+
+ def r_peek(self) -> int:
+ return self._return_stack[-1]
+
+ def ld8(self, address: int) -> int:
+ aligned = address & ~0xfff
+ if aligned not in self._regions:
+ return 0
+ return self._regions[aligned][address & 0xfff]
+
+ def st8(self, address: int, value: int) -> None:
+ aligned = address & ~0xfff
+ if aligned not in self._regions:
+ self._regions[aligned] = bytearray(0x1000)
+ self._regions[aligned][address & 0xfff] = value
+
+ def ld32(self, address: int) -> int:
+ a = self.ld8(address + 0)
+ b = self.ld8(address + 1)
+ c = self.ld8(address + 2)
+ d = self.ld8(address + 3)
+ return (d << 24) | (c << 16) | (b << 8) | (a << 0)
+
+ def st32(self, address: int, value: int) -> None:
+ a = (value >> 0) & 0xff
+ b = (value >> 8) & 0xff
+ c = (value >> 16) & 0xff
+ d = (value >> 24) & 0xff
+ self.st8(address + 0, a)
+ self.st8(address + 1, b)
+ self.st8(address + 2, c)
+ self.st8(address + 3, d)
+
+ @property
+ def pc(self) -> int:
+ return self._pc
+
+ @pc.setter
+ def pc(self, value: int | str) -> None:
+ if isinstance(value, str):
+ self._pc = self.symbol_to_addr(value)
+ else:
+ self._pc = value & 0xffffffff
+
+ def run_until(self, addr: int | str):
+ if isinstance(addr, str):
+ addr = self.symbol_to_addr(addr)
+ while self.pc != addr:
+ self.step()
+
+ def step(self) -> None:
+ instr = self.ld8(self.pc)
+ self.pc += 1
+ if instr == 0x01: # #8
+ self.p_push(self.ld8(self.pc))
+ self.pc += 1
+ elif instr == 0x02: # #32
+ self.p_push(self.ld32(self.pc))
+ self.pc += 4
+ elif instr == 0x03: # @8
+ addr = self.p_pop()
+ self.p_push(self.ld8(addr))
+ elif instr == 0x04: # @32
+ addr = self.p_pop() & ~0x3
+ self.p_push(self.ld32(addr))
+ elif instr == 0x05: # !8
+ addr = self.p_pop()
+ val = self.p_peek() & 0xff
+ self.st8(addr, val)
+ elif instr == 0x06: # !32
+ addr = self.p_pop() & ~0x3
+ val = self.p_peek()
+ self.st32(addr, val)
+ elif instr == 0x07: # r@
+ self.p_push(self.r_peek())
+ elif instr == 0x08: # >r
+ self.r_push(self.p_pop())
+ elif instr == 0x09: # r>
+ self.p_push(self.r_pop())
+ elif instr == 0x0a: # CALL
+ addr = self.ld32(self.pc)
+ self.pc += 4
+ self.r_push(self.pc)
+ self.pc = addr
+ elif instr == 0x0b: # ;
+ self.pc = self.r_pop()
+ elif instr == 0x0c: # JMP
+ addr = self.ld32(self.pc)
+ self.pc = addr
+ elif instr == 0x0d: # JZ
+ addr = self.ld32(self.pc)
+ self.pc += 4
+ if self.p_pop() == 0:
+ self.pc = addr
+ elif instr == 0x0e: # JN
+ addr = self.ld32(self.pc)
+ self.pc += 4
+ if self.p_pop() & 0x80000000 != 0:
+ self.pc = addr
+ elif instr == 0x0f: # JP
+ addr = self.ld32(self.pc)
+ self.pc += 4
+ if self.p_pop() > 0:
+ self.pc = addr
+ elif instr == 0x10: # +
+ self.p_push(self.p_pop() + self.p_pop())
+ elif instr == 0x11: # -
+ self.p_push(self.p_pop() - self.p_pop())
+ elif instr == 0x12: # &
+ self.p_push(self.p_pop() & self.p_pop())
+ elif instr == 0x13: # |
+ self.p_push(self.p_pop() | self.p_pop())
+ elif instr == 0x14: # ^
+ self.p_push(self.p_pop() ^ self.p_pop())
+ elif instr == 0x15: # ~
+ self.p_push(~self.p_pop())
+ elif instr == 0x16: # DUP
+ self.p_push(self.p_peek())
+ elif instr == 0x17: # DROP
+ self.p_pop()
+ elif instr == 0x18: # SWAP
+ a = self.p_pop()
+ b = self.p_pop()
+ self.p_push(a)
+ self.p_push(b)
+ elif instr == 0x1b: # LSR
+ val = self.p_pop()
+ self.p_push(val >> 1)
+ elif instr == 0x1c: # ASR
+ val = self.p_pop()
+ self.p_push((val >> 1) | (val & 0x80000000))
+ elif instr == 0x1d: # SHL
+ val = self.p_pop()
+ self.p_push(val << 1)
+ else: # NOP
+ pass
+
+ def dump(self) -> None:
+ print('-'*80)
+ print(f'0x{self.pc:08x} {self.addr_to_symbol(self.pc)}')
+ instr = self.ld8(self.pc)
+ sinstr = self.INSTRS[instr] if instr in self.INSTRS else '???'
+ print(f' (0x{instr:02x} {sinstr})')
+ print()
+ print('Data stack:')
+ for x in reversed(self._param_stack):
+ print(f' 0x{x:08x} {self.addr_to_symbol(x)}')
+ print()
+ print('Return stack:')
+ for x in reversed(self._return_stack):
+ print(f' 0x{x:08x} {self.addr_to_symbol(x)}')
+ print()
+
+
+# ------------------------------------------------------------------------------
+
+
+def main() -> int:
+ binary = './writer.bin'
+ mapfile = './writer.map'
+
+ emu = Cpu0()
+ with open(binary, 'rb') as f:
+ data = f.read()
+ emu.load_bin(data)
+ emu.load_map(mapfile)
+
+ emu.dump()
+ emu.r_push(0xdeadbeef)
+ emu.p_push(0x1234)
+ emu.p_push(0x567)
+ emu.pc = 'mul_uu'
+ emu.dump()
+
+ emu.run_until(0xdeadbeef)
+ emu.dump()
+
+ return 0
+
+
+if __name__ == '__main__':
+ exit(main())