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.
 
 
 
 
 

377 lines
13 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. 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(sorted(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. if "." in cond:
  93. itype, value = cond.split(".", 1)
  94. try:
  95. value = int(value)
  96. except ValueError:
  97. value = int(value, 16)
  98. vtype = "sval" if itype.upper() in ["S8", "REL"] else "uval"
  99. test1 = "INST_IMM({0}).mask & IMM_{1}".format(num, itype.upper())
  100. test2 = "!INST_IMM({0}).is_label".format(num)
  101. test3 = "INST_IMM({0}).{1} == {2}".format(num, vtype, value)
  102. return "({0} && {1} && {2})".format(test1, test2, test3)
  103. return "INST_IMM({0}).mask & IMM_{1}".format(num, cond.upper())
  104. def _build_indirect_check(self, num, cond):
  105. """
  106. Return an expression to check for a particular indirect value.
  107. """
  108. if cond.startswith("reg."):
  109. test1 = "INST_INDIRECT({0}).type == AT_REGISTER".format(num)
  110. test2 = "INST_INDIRECT({0}).addr.reg == REG_{1}".format(
  111. num, cond[len("reg."):].upper())
  112. return "({0} && {1})".format(test1, test2)
  113. if cond == "imm" or cond == "immediate":
  114. return "INST_INDIRECT({0}).type == AT_IMMEDIATE".format(num)
  115. err = "Unknown condition for indirect argument: {0}"
  116. return RuntimeError(err.format(cond))
  117. def _build_indexed_check(self, num, cond):
  118. """
  119. Return an expression to check for a particular indexed value.
  120. """
  121. raise RuntimeError("The indexed arg type does not support conditions")
  122. def _build_condition_check(self, num, cond):
  123. """
  124. Return an expression to check for a particular condition value.
  125. """
  126. return "INST_COND({0}) == COND_{1}".format(num, cond.upper())
  127. def _build_port_check(self, num, cond):
  128. """
  129. Return an expression to check for a particular port value.
  130. """
  131. if cond.startswith("reg."):
  132. test1 = "INST_PORT({0}).type == AT_REGISTER".format(num)
  133. test2 = "INST_PORT({0}).port.reg == REG_{1}".format(
  134. num, cond[len("reg."):].upper())
  135. return "({0} && {1})".format(test1, test2)
  136. if cond == "imm" or cond == "immediate":
  137. return "INST_PORT({0}).type == AT_IMMEDIATE".format(num)
  138. err = "Unknown condition for port argument: {0}"
  139. return RuntimeError(err.format(cond))
  140. _SUBCASE_LOOKUP_TABLE = {
  141. "register": _build_register_check,
  142. "immediate": _build_immediate_check,
  143. "indirect": _build_indirect_check,
  144. "indexed": _build_indexed_check,
  145. "condition": _build_condition_check,
  146. "port": _build_port_check
  147. }
  148. def _build_subcase_check(self, types, conds):
  149. """
  150. Return the test part of an if statement for an instruction subcase.
  151. """
  152. conds = [self._SUBCASE_LOOKUP_TABLE[types[i]](self, i, cond)
  153. for i, cond in enumerate(conds) if cond != "_"]
  154. return " && ".join(conds) if conds else "1"
  155. def _iter_permutations(self, types, conds):
  156. """
  157. Iterate over all permutations of the given subcase conditions.
  158. """
  159. def split(typ, cond):
  160. if "|" in cond:
  161. splits = [split(typ, c) for c in cond.split("|")]
  162. merged = [choice for s in splits for choice in s]
  163. if len(merged) != len(set(merged)):
  164. msg = "Repeated conditions for {0}: {1}"
  165. raise RuntimeError(msg.format(typ, cond))
  166. return merged
  167. if typ == "register":
  168. if cond == "i":
  169. return ["ix", "iy"]
  170. if cond == "ih":
  171. return ["ixh", "iyh"]
  172. if cond == "il":
  173. return ["ixl", "iyl"]
  174. return [cond]
  175. splits = [split(typ, cond) for typ, cond in zip(types, conds)]
  176. num = max(len(cond) for cond in splits)
  177. if any(1 < len(cond) < num for cond in splits):
  178. msg = "Invalid condition permutations: {0}"
  179. raise RuntimeError(msg.format(conds))
  180. choices = [cond * num if len(cond) == 1 else cond for cond in splits]
  181. return zip(*choices)
  182. def _adapt_return(self, types, conds, ret):
  183. """
  184. Return a modified byte list to accomodate for prefixes and immediates.
  185. """
  186. ret = ret[:]
  187. for i, byte in enumerate(ret):
  188. if not isinstance(byte, int):
  189. if byte == "u8":
  190. index = types.index("immediate")
  191. ret[i] = "INST_IMM({0}).uval".format(index)
  192. elif byte == "u16":
  193. if i < len(ret) - 1:
  194. raise RuntimeError("U16 return byte must be last")
  195. try:
  196. index = types.index("immediate")
  197. imm = "INST_IMM({0})".format(index)
  198. except ValueError:
  199. indir = types.index("indirect")
  200. if not conds[indir].startswith("imm"):
  201. msg = "Passing non-immediate indirect as immediate"
  202. raise RuntimeError(msg)
  203. imm = "INST_INDIRECT({0}).addr.imm".format(indir)
  204. ret[i] = "INST_IMM_U16_B1({0})".format(imm)
  205. ret.append("INST_IMM_U16_B2({0})".format(imm))
  206. break
  207. else:
  208. msg = "Unsupported return byte: {0}"
  209. raise RuntimeError(msg.format(byte))
  210. for i, cond in enumerate(conds):
  211. if types[i] == "register" and cond[0] == "i":
  212. prefix = "INST_I{0}_PREFIX".format(cond[1].upper())
  213. if ret[0] != prefix:
  214. ret.insert(0, prefix)
  215. elif types[i] == "indexed":
  216. ret.insert(0, "INST_INDEX_PREFIX({0})".format(i))
  217. ret.append("INST_INDEX({0}).offset".format(i))
  218. return ret
  219. def _handle_pseudo_case(self, pseudo, case):
  220. """
  221. Return code to handle an instruction pseudo-case.
  222. Pseudo-cases are cases that have pseudo-types as arguments. This means
  223. they are expanded to cover multiple "real" argument types.
  224. """
  225. index = case["type"].index(pseudo)
  226. if pseudo == "indirect_hl_or_indexed":
  227. case["type"][index] = "indexed"
  228. indexed = self._handle_case(case)
  229. case["type"][index] = "indirect"
  230. for subcase in case["cases"]:
  231. if subcase["cond"][index] != "_":
  232. raise RuntimeError(
  233. "indirect_hl_or_indexed pseudo-type requires a "
  234. "wildcard (_) in all corresponding conditionals")
  235. subcase["cond"][index] = "reg.hl"
  236. return self._handle_case(case) + indexed
  237. raise RuntimeError("Unknown pseudo-type: {0}".format(pseudo))
  238. def _handle_case(self, case):
  239. """
  240. Return code to handle an instruction case.
  241. """
  242. for pseudo in self.PSEUDO_TYPES:
  243. if pseudo in case["type"]:
  244. return self._handle_pseudo_case(pseudo, case)
  245. lines = []
  246. cond = self._build_case_type_check(case["type"])
  247. lines.append(TAB + "if ({0}) {{".format(cond))
  248. for subcase in case["cases"]:
  249. for perm in self._iter_permutations(case["type"], subcase["cond"]):
  250. cond = self._build_subcase_check(case["type"], perm)
  251. ret = self._adapt_return(case["type"], perm, subcase["return"])
  252. lines.append(TAB * 2 + "if ({0})".format(cond))
  253. lines.append(self._handle_return(ret, 3))
  254. lines.append(TAB * 2 + "INST_ERROR(ARG_VALUE)")
  255. lines.append(TAB + "}")
  256. return lines
  257. def render(self):
  258. """
  259. Convert data for an individual instruction into a C parse function.
  260. """
  261. lines = []
  262. if self._data["args"]:
  263. lines.append("{tab}INST_TAKES_ARGS(\n{tab2}{0},\n{tab2}{1},"
  264. "\n{tab2}{2}\n{tab})".format(
  265. self._get_arg_parse_mask(0), self._get_arg_parse_mask(1),
  266. self._get_arg_parse_mask(2), tab=TAB, tab2=TAB * 2))
  267. else:
  268. lines.append(TAB + "INST_TAKES_NO_ARGS")
  269. if "return" in self._data:
  270. lines.append(self._handle_return(self._data["return"]))
  271. elif "cases" in self._data:
  272. for case in self._data["cases"]:
  273. lines.extend(self._handle_case(case))
  274. lines.append(TAB + "INST_ERROR(ARG_TYPE)")
  275. else:
  276. msg = "Missing return or case block for {0} instruction"
  277. raise RuntimeError(msg.format(self._name))
  278. contents = "\n".join(lines)
  279. return "INST_FUNC({0})\n{{\n{1}\n}}".format(self._name, contents)
  280. def build_inst_block(data):
  281. """
  282. Return the instruction parser block, given instruction data.
  283. """
  284. return "\n\n".join(
  285. Instruction(k, v).render() for k, v in sorted(data.items()))
  286. def build_lookup_block(data):
  287. """
  288. Return the instruction lookup block, given instruction data.
  289. """
  290. macro = TAB + "HANDLE({0})"
  291. return "\n".join(macro.format(inst) for inst in sorted(data.keys()))
  292. def process(template, data):
  293. """
  294. Return C code generated from a source template and instruction data.
  295. """
  296. inst_block = build_inst_block(data)
  297. lookup_block = build_lookup_block(data)
  298. date = time.asctime(time.gmtime())
  299. result = re_date.sub(r"\1{0} UTC".format(date), template)
  300. result = re_inst.sub(r"\1{0}\3".format(inst_block), result)
  301. result = re_lookup.sub(r"\1{0}\3".format(lookup_block), result)
  302. return result
  303. def main():
  304. """
  305. Main script entry point.
  306. """
  307. with open(SOURCE, "r") as fp:
  308. text = fp.read().decode(ENCODING)
  309. with open(DEST, "r") as fp:
  310. template = fp.read().decode(ENCODING)
  311. data = yaml.load(text)
  312. result = process(template, data)
  313. with open(DEST, "w") as fp:
  314. fp.write(result.encode(ENCODING))
  315. if __name__ == "__main__":
  316. main()