An emulator, assembler, and disassembler for the Sega Game Gear
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

update_asm_instructions.py 11 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. PSEUDO_TYPES = {
  44. "indirect_hl_or_indexed": ["AT_INDIRECT", "AT_INDEXED"]
  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. if atype in self.ARG_TYPES:
  59. types.add(self.ARG_TYPES[atype])
  60. else:
  61. types.update(self.PSEUDO_TYPES[atype])
  62. else:
  63. optional = True
  64. if not types:
  65. return "AT_NONE"
  66. if optional:
  67. types.add("AT_OPTIONAL")
  68. return "|".join(types)
  69. def _handle_return(self, ret, indent=1):
  70. """
  71. Return code to handle an instruction return statement.
  72. """
  73. data = ", ".join("0x%02X" % byte if isinstance(byte, int) else byte
  74. for byte in ret)
  75. return TAB * indent + "INST_RETURN({0}, {1})".format(len(ret), data)
  76. def _build_case_type_check(self, args):
  77. """
  78. Return the test part of an if statement for an instruction case.
  79. """
  80. conds = ["INST_TYPE({0}) == {1}".format(i, self.ARG_TYPES[cond])
  81. for i, cond in enumerate(args)]
  82. return "INST_NARGS == {0} && {1}".format(len(args), " && ".join(conds))
  83. def _build_register_check(self, num, cond):
  84. """
  85. Return an expression to check for a particular register value.
  86. """
  87. return "INST_REG({0}) == REG_{1}".format(num, cond.upper())
  88. def _build_immediate_check(self, num, cond):
  89. """
  90. Return an expression to check for a particular immediate value.
  91. """
  92. # TODO: also allow direct value comparisons here
  93. return "INST_IMM({0}).mask & IMM_{1}".format(num, cond.upper())
  94. def _build_indirect_check(self, num, cond):
  95. """
  96. Return an expression to check for a particular indirect value.
  97. """
  98. if cond.startswith("reg."):
  99. test1 = "INST_INDIRECT({0}).type == AT_REGISTER".format(num)
  100. test2 = "INST_INDIRECT({0}).addr.reg == REG_{1}".format(
  101. num, cond[len("reg."):].upper())
  102. return "({0} && {1})".format(test1, test2)
  103. if cond == "imm" or cond == "immediate":
  104. return "INST_INDIRECT({0}).type == AT_IMMEDIATE".format(num)
  105. err = "Unknown condition for indirect argument: {0}"
  106. return RuntimeError(err.format(cond))
  107. def _build_indexed_check(self, num, cond):
  108. """
  109. Return an expression to check for a particular indexed value.
  110. """
  111. raise RuntimeError("The indexed arg type does not support conditions")
  112. def _build_condition_check(self, num, cond):
  113. """
  114. Return an expression to check for a particular condition value.
  115. """
  116. return "INST_COND({0}) == COND_{1}".format(num, cond.upper())
  117. def _build_port_check(self, num, cond):
  118. """
  119. Return an expression to check for a particular port value.
  120. """
  121. # TODO
  122. err = "Unknown condition for port argument: {0}"
  123. return RuntimeError(err.format(cond))
  124. _SUBCASE_LOOKUP_TABLE = {
  125. "register": _build_register_check,
  126. "immediate": _build_immediate_check,
  127. "indirect": _build_indirect_check,
  128. "indexed": _build_indexed_check,
  129. "condition": _build_condition_check,
  130. "port": _build_port_check
  131. }
  132. def _build_subcase_check(self, types, conds):
  133. """
  134. Return the test part of an if statement for an instruction subcase.
  135. """
  136. conds = [self._SUBCASE_LOOKUP_TABLE[types[i]](self, i, cond)
  137. for i, cond in enumerate(conds) if cond != "_"]
  138. return " && ".join(conds) if conds else "1"
  139. def _iter_permutations(self, types, conds):
  140. """
  141. Iterate over all permutations of the given subcase conditions.
  142. """
  143. def split(typ, cond):
  144. if "|" in cond:
  145. sets = [split(typ, c) for c in cond.split("|")]
  146. return {choice for s in sets for choice in s}
  147. if typ == "register":
  148. if cond == "i":
  149. return {"ix", "iy"}
  150. if cond == "ih":
  151. return {"ixh", "iyh"}
  152. if cond == "il":
  153. return {"ixl", "iyl"}
  154. return {cond}
  155. return product(*(split(types[i], cond)
  156. for i, cond in enumerate(conds)))
  157. def _adapt_return(self, types, conds, ret):
  158. """
  159. Return a modified byte list to accomodate for prefixes and immediates.
  160. """
  161. ret = ret[:]
  162. for i, byte in enumerate(ret):
  163. if not isinstance(byte, int):
  164. if byte == "u8":
  165. imm = types.index("immediate")
  166. ret[i] = "INST_IMM({0}).uval".format(imm)
  167. else:
  168. msg = "Unsupported return byte: {0}"
  169. raise RuntimeError(msg.format(byte))
  170. for i, cond in enumerate(conds):
  171. if types[i] == "register" and cond.startswith("ix"):
  172. ret.insert(0, "INST_IX_PREFIX")
  173. elif types[i] == "register" and cond.startswith("iy"):
  174. ret.insert(0, "INST_IY_PREFIX")
  175. elif types[i] == "indexed":
  176. ret.insert(0, "INST_INDEX_PREFIX({0})".format(i))
  177. ret.append("INST_INDEX({0}).offset".format(i))
  178. return ret
  179. def _handle_pseudo_case(self, pseudo, case):
  180. """
  181. Return code to handle an instruction pseudo-case.
  182. Pseudo-cases are cases that have pseudo-types as arguments. This means
  183. they are expanded to cover multiple "real" argument types.
  184. """
  185. index = case["type"].index(pseudo)
  186. if pseudo == "indirect_hl_or_indexed":
  187. case["type"][index] = "indexed"
  188. indexed = self._handle_case(case)
  189. case["type"][index] = "indirect"
  190. for subcase in case["cases"]:
  191. if subcase["cond"][index] != "_":
  192. raise RuntimeError(
  193. "indirect_hl_or_indexed pseudo-type requires a "
  194. "wildcard (_) in all corresponding conditionals")
  195. subcase["cond"][index] = "reg.hl"
  196. return self._handle_case(case) + indexed
  197. raise RuntimeError("Unknown pseudo-type: {0}".format(pseudo))
  198. def _handle_case(self, case):
  199. """
  200. Return code to handle an instruction case.
  201. """
  202. for pseudo in self.PSEUDO_TYPES:
  203. if pseudo in case["type"]:
  204. return self._handle_pseudo_case(pseudo, case)
  205. lines = []
  206. cond = self._build_case_type_check(case["type"])
  207. lines.append(TAB + "if ({0}) {{".format(cond))
  208. for subcase in case["cases"]:
  209. for perm in self._iter_permutations(case["type"], subcase["cond"]):
  210. cond = self._build_subcase_check(case["type"], perm)
  211. ret = self._adapt_return(case["type"], perm, subcase["return"])
  212. lines.append(TAB * 2 + "if ({0})".format(cond))
  213. lines.append(self._handle_return(ret, 3))
  214. lines.append(TAB * 2 + "INST_ERROR(ARG_VALUE)")
  215. lines.append(TAB + "}")
  216. return lines
  217. def render(self):
  218. """
  219. Convert data for an individual instruction into a C parse function.
  220. """
  221. lines = []
  222. if self._data["args"]:
  223. lines.append("{tab}INST_TAKES_ARGS(\n{tab2}{0},\n{tab2}{1},"
  224. "\n{tab2}{2}\n{tab})".format(
  225. self._get_arg_parse_mask(0), self._get_arg_parse_mask(1),
  226. self._get_arg_parse_mask(2), tab=TAB, tab2=TAB * 2))
  227. else:
  228. lines.append(TAB + "INST_TAKES_NO_ARGS")
  229. if "return" in self._data:
  230. lines.append(self._handle_return(self._data["return"]))
  231. elif "cases" in self._data:
  232. for case in self._data["cases"]:
  233. lines.extend(self._handle_case(case))
  234. lines.append(TAB + "INST_ERROR(ARG_TYPE)")
  235. else:
  236. msg = "Missing return or case block for {0} instruction"
  237. raise RuntimeError(msg.format(self._name))
  238. contents = "\n".join(lines)
  239. return "INST_FUNC({0})\n{{\n{1}\n}}".format(self._name, contents)
  240. def build_inst_block(data):
  241. """
  242. Return the instruction parser block, given instruction data.
  243. """
  244. return "\n\n".join(
  245. Instruction(k, v).render() for k, v in sorted(data.items()))
  246. def build_lookup_block(data):
  247. """
  248. Return the instruction lookup block, given instruction data.
  249. """
  250. macro = TAB + "HANDLE({0})"
  251. return "\n".join(macro.format(inst) for inst in sorted(data.keys()))
  252. def process(template, data):
  253. """
  254. Return C code generated from a source template and instruction data.
  255. """
  256. inst_block = build_inst_block(data)
  257. lookup_block = build_lookup_block(data)
  258. date = time.asctime(time.gmtime())
  259. result = re_date.sub(r"\1{0} UTC".format(date), template)
  260. result = re_inst.sub(r"\1{0}\3".format(inst_block), result)
  261. result = re_lookup.sub(r"\1{0}\3".format(lookup_block), result)
  262. return result
  263. def main():
  264. """
  265. Main script entry point.
  266. """
  267. with open(SOURCE, "r") as fp:
  268. text = fp.read().decode(ENCODING)
  269. with open(DEST, "r") as fp:
  270. template = fp.read().decode(ENCODING)
  271. data = yaml.load(text)
  272. result = process(template, data)
  273. with open(DEST, "w") as fp:
  274. fp.write(result.encode(ENCODING))
  275. if __name__ == "__main__":
  276. main()