An emulator, assembler, and disassembler for the Sega Game Gear
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

272 lines
8.7 KiB

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
  4. # Released under the terms of the MIT License. See LICENSE for details.
  5. """
  6. This script generates 'src/assembler/instructions.inc.c' from
  7. 'src/assembler/instructions.yml'. It should be run automatically by make
  8. when the latter is modified, but can also be run manually.
  9. """
  10. from __future__ import print_function
  11. from itertools import product
  12. import re
  13. import time
  14. SOURCE = "src/assembler/instructions.yml"
  15. DEST = "src/assembler/instructions.inc.c"
  16. ENCODING = "utf8"
  17. TAB = " " * 4
  18. try:
  19. import yaml
  20. except ImportError:
  21. print("Error: PyYAML is required (https://pypi.python.org/pypi/PyYAML)\n"
  22. "If you don't want to rebuild {0}, do:\n`make -t {0}`".format(DEST))
  23. exit(1)
  24. re_date = re.compile(r"^(\s*@AUTOGEN_DATE\s*)(.*?)$", re.M)
  25. re_inst = re.compile(
  26. r"(/\* @AUTOGEN_INST_BLOCK_START \*/\n*)(.*?)"
  27. r"(\n*/\* @AUTOGEN_INST_BLOCK_END \*/)", re.S)
  28. re_lookup = re.compile(
  29. r"(/\* @AUTOGEN_LOOKUP_BLOCK_START \*/\n*)(.*?)"
  30. r"(\n*/\* @AUTOGEN_LOOKUP_BLOCK_END \*/)", re.S)
  31. class Instruction(object):
  32. """
  33. Represent a single ASM instruction mnemonic.
  34. """
  35. ARG_TYPES = {
  36. "register": "AT_REGISTER",
  37. "immediate": "AT_IMMEDIATE",
  38. "indirect": "AT_INDIRECT",
  39. "indexed": "AT_INDEXED",
  40. "condition": "AT_CONDITION",
  41. "port": "AT_PORT"
  42. }
  43. ARG_EXTRA = {
  44. "indexed": ["AT_INDIRECT"]
  45. }
  46. def __init__(self, name, data):
  47. self._name = name
  48. self._data = data
  49. def _get_arg_parse_mask(self, num):
  50. """
  51. Return the appropriate mask to parse_args() for the num-th argument.
  52. """
  53. types = set()
  54. optional = False
  55. for case in self._data["cases"]:
  56. if num < len(case["type"]):
  57. atype = case["type"][num]
  58. types.add(self.ARG_TYPES[atype])
  59. if atype in self.ARG_EXTRA:
  60. types.update(self.ARG_EXTRA[atype])
  61. else:
  62. optional = True
  63. if not types:
  64. return "AT_NONE"
  65. if optional:
  66. types.add("AT_OPTIONAL")
  67. return "|".join(types)
  68. def _handle_return(self, ret, indent=1):
  69. """
  70. Return code to handle an instruction return statement.
  71. """
  72. data = ", ".join("0x%02X" % byte if isinstance(byte, int) else byte
  73. for byte in ret)
  74. return TAB * indent + "INST_RETURN({0}, {1})".format(len(ret), data)
  75. def _build_case_type_check(self, args):
  76. """
  77. Return the test part of an if statement for an instruction case.
  78. """
  79. conds = ["INST_TYPE({0}) == {1}".format(i, self.ARG_TYPES[cond])
  80. for i, cond in enumerate(args)]
  81. return "INST_NARGS == {0} && {1}".format(len(args), " && ".join(conds))
  82. def _build_register_check(self, num, cond):
  83. """
  84. Return an expression to check for a particular register value.
  85. """
  86. return "INST_REG({0}) == REG_{1}".format(num, cond.upper())
  87. def _build_immediate_check(self, num, cond):
  88. """
  89. Return an expression to check for a particular immediate value.
  90. """
  91. return "INST_IMM({0}).mask & IMM_{1}".format(num, cond.upper())
  92. def _build_indirect_check(self, num, cond):
  93. """
  94. Return an expression to check for a particular indirect value.
  95. """
  96. # TODO
  97. return cond
  98. def _build_indexed_check(self, num, cond):
  99. """
  100. Return an expression to check for a particular indexed value.
  101. """
  102. # TODO
  103. return cond
  104. def _build_condition_check(self, num, cond):
  105. """
  106. Return an expression to check for a particular condition value.
  107. """
  108. return "INST_COND({0}) == COND_{1}".format(num, cond.upper())
  109. def _build_port_check(self, num, cond):
  110. """
  111. Return an expression to check for a particular port value.
  112. """
  113. # TODO
  114. return cond
  115. _SUBCASE_LOOKUP_TABLE = {
  116. "register": _build_register_check,
  117. "immediate": _build_immediate_check,
  118. "indirect": _build_indirect_check,
  119. "indexed": _build_indexed_check,
  120. "condition": _build_condition_check,
  121. "port": _build_port_check
  122. }
  123. def _build_subcase_check(self, types, conds):
  124. """
  125. Return the test part of an if statement for an instruction subcase.
  126. """
  127. return " && ".join(self._SUBCASE_LOOKUP_TABLE[types[i]](self, i, cond)
  128. for i, cond in enumerate(conds) if cond != "_")
  129. def _iter_permutations(self, types, conds):
  130. """
  131. Iterate over all permutations of the given subcase conditions.
  132. """
  133. def split(typ, cond):
  134. if "|" in cond:
  135. sets = [split(typ, c) for c in cond.split("|")]
  136. return {choice for s in sets for choice in s}
  137. if typ == "register" and cond == "ih":
  138. return {"ixh", "iyh"}
  139. if typ == "register" and cond == "il":
  140. return {"ixl", "iyl"}
  141. return {cond}
  142. return product(*(split(types[i], cond)
  143. for i, cond in enumerate(conds)))
  144. def _adapt_return(self, types, conds, ret):
  145. """
  146. Return a modified byte list to accomodate for prefixes and immediates.
  147. """
  148. for i, cond in enumerate(conds):
  149. if types[i] == "register" and cond.startswith("ix"):
  150. ret = ["INST_IX_PREFIX"] + ret
  151. elif types[i] == "register" and cond.startswith("iy"):
  152. ret = ["INST_IY_PREFIX"] + ret
  153. return ret
  154. def _handle_case(self, case):
  155. """
  156. Return code to handle an instruction case.
  157. """
  158. lines = []
  159. cond = self._build_case_type_check(case["type"])
  160. lines.append(TAB + "if ({0}) {{".format(cond))
  161. for subcase in case["cases"]:
  162. for perm in self._iter_permutations(case["type"], subcase["cond"]):
  163. cond = self._build_subcase_check(case["type"], perm)
  164. ret = self._adapt_return(case["type"], perm, subcase["return"])
  165. lines.append(TAB * 2 + "if ({0})".format(cond))
  166. lines.append(self._handle_return(ret, 3))
  167. lines.append(TAB * 2 + "INST_ERROR(ARG_VALUE)")
  168. lines.append(TAB + "}")
  169. return lines
  170. def render(self):
  171. """
  172. Convert data for an individual instruction into a C parse function.
  173. """
  174. lines = []
  175. if self._data["args"]:
  176. lines.append("{tab}INST_TAKES_ARGS(\n{tab2}{0}, \n{tab2}{1}, "
  177. "\n{tab2}{2}\n{tab})".format(
  178. self._get_arg_parse_mask(0), self._get_arg_parse_mask(1),
  179. self._get_arg_parse_mask(2), tab=TAB, tab2=TAB * 2))
  180. else:
  181. lines.append(TAB + "INST_TAKES_NO_ARGS")
  182. if "return" in self._data:
  183. lines.append(self._handle_return(self._data["return"]))
  184. elif "cases" in self._data:
  185. for case in self._data["cases"]:
  186. lines.extend(self._handle_case(case))
  187. lines.append(TAB + "INST_ERROR(ARG_TYPE)")
  188. else:
  189. msg = "Missing return or case block for {0} instruction"
  190. raise RuntimeError(msg.format(self._name))
  191. contents = "\n".join(lines)
  192. return "INST_FUNC({0})\n{{\n{1}\n}}".format(self._name, contents)
  193. def build_inst_block(data):
  194. """
  195. Return the instruction parser block, given instruction data.
  196. """
  197. return "\n\n".join(
  198. Instruction(k, v).render() for k, v in sorted(data.items()))
  199. def build_lookup_block(data):
  200. """
  201. Return the instruction lookup block, given instruction data.
  202. """
  203. macro = TAB + "HANDLE({0})"
  204. return "\n".join(macro.format(inst) for inst in sorted(data.keys()))
  205. def process(template, data):
  206. """
  207. Return C code generated from a source template and instruction data.
  208. """
  209. inst_block = build_inst_block(data)
  210. lookup_block = build_lookup_block(data)
  211. date = time.asctime(time.gmtime())
  212. result = re_date.sub(r"\1{0} UTC".format(date), template)
  213. result = re_inst.sub(r"\1{0}\3".format(inst_block), result)
  214. result = re_lookup.sub(r"\1{0}\3".format(lookup_block), result)
  215. return result
  216. def main():
  217. """
  218. Main script entry point.
  219. """
  220. with open(SOURCE, "r") as fp:
  221. text = fp.read().decode(ENCODING)
  222. with open(DEST, "r") as fp:
  223. template = fp.read().decode(ENCODING)
  224. data = yaml.load(text)
  225. result = process(template, data)
  226. # with open(DEST, "w") as fp:
  227. # fp.write(result.encode(ENCODING))
  228. print(result) # TODO: remove me!
  229. if __name__ == "__main__":
  230. main()