mirror of
https://github.com/xenia-project/xenia.git
synced 2025-12-06 07:12:03 +01:00
Seems to work and match up with the old decoder (which may be bad). Old decoder remains until all functionality is replaced.
346 lines
10 KiB
Python
346 lines
10 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright 2015 Ben Vanik & shuffle2. All Rights Reserved.
|
|
|
|
"""PPC instruction table generator.
|
|
|
|
Generates various headers/sources for looking up and handling PPC instructions.
|
|
|
|
This is based on shuffle2's PPC generator:
|
|
https://gist.github.com/shuffle2/10015968
|
|
"""
|
|
|
|
__author__ = 'ben.vanik@gmail.com (Ben Vanik)'
|
|
|
|
import os
|
|
import sys
|
|
from xml.etree.ElementTree import ElementTree, Element, SubElement, tostring, dump
|
|
|
|
|
|
self_path = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
class Insn:
|
|
pass
|
|
|
|
|
|
def bit_extract(x, leftmost, rightmost):
|
|
return (x >> (32 - 1 - rightmost)) & ((1 << (rightmost - leftmost + 1)) - 1)
|
|
|
|
|
|
extended_opcode_bits = {
|
|
'X': [(21, 30)],
|
|
'XL': [(21, 30)],
|
|
'XFX': [(21, 30)],
|
|
'XFL': [(21, 30)],
|
|
'VX': [(21, 31)],
|
|
'VX128': [(22, 25), (27, 27)],
|
|
'VX128_1': [(21, 27), (30, 31)],
|
|
'VX128_2': [(22, 22), (27, 27)],
|
|
'VX128_3': [(21, 27)],
|
|
'VX128_4': [(21, 23), (26, 27)],
|
|
'VX128_5': [(27, 27)],
|
|
'VX128_R': [(22, 24), (27, 27)],
|
|
'VX128_P': [(21, 22), (26, 27)],
|
|
'VC': [(22, 31)],
|
|
'VA': [(26, 31)],
|
|
'XO': [(22, 30)],
|
|
'XW': [(25, 30)],
|
|
'A': [(26, 30)],
|
|
'DS': [(30, 31)],
|
|
'MD': [(27, 30)],
|
|
'MDS': [(27, 30)],
|
|
'MDSH': [(27, 29)],
|
|
'XS': [(21, 29)],
|
|
'DCBZ': [(6, 10), (21, 30)], # like X
|
|
}
|
|
|
|
|
|
def opcode_primary(insn):
|
|
return bit_extract(insn, 0, 5)
|
|
|
|
|
|
def opcode_extended(insn, form):
|
|
if form in extended_opcode_bits:
|
|
parts = extended_opcode_bits[form]
|
|
value = 0
|
|
shift = 0
|
|
for part in parts:
|
|
shift = max(shift, part[1])
|
|
for part in parts:
|
|
part_value = bit_extract(insn, part[0], part[1])
|
|
value = value | (part_value << (shift - part[1]))
|
|
return value
|
|
else:
|
|
return -1
|
|
|
|
|
|
def parse_insns(filename):
|
|
root = ElementTree(file = filename)
|
|
insns = []
|
|
# Convert to python types
|
|
for e in root.findall('.//insn'):
|
|
i = Insn()
|
|
i.desc = e.attrib['desc']
|
|
i.form = e.attrib['form']
|
|
i.group = e.attrib['group']
|
|
i.mnem = e.attrib['mnem']
|
|
i.opcode = int(e.attrib['opcode'], 16)
|
|
i.subform = e.attrib['sub-form']
|
|
i.op_primary = opcode_primary(i.opcode)
|
|
i.op_extended = opcode_extended(i.opcode, i.form)
|
|
insns.append(i)
|
|
return insns
|
|
|
|
|
|
def c_mnem(x):
|
|
return x.replace('.', 'x')
|
|
|
|
|
|
def c_subform(x):
|
|
x = x.replace('-', '_')
|
|
if x[0] >= '0' and x[0] <= '9':
|
|
x = '_' + x
|
|
return x
|
|
|
|
|
|
def c_group(x):
|
|
return 'k' + x[0].upper() + x[1:]
|
|
|
|
|
|
def c_bool(x):
|
|
return 'true' if x else 'false'
|
|
|
|
|
|
def generate_opcodes(insns):
|
|
l = []
|
|
TAB = ' ' * 2
|
|
def w0(x): l.append(x)
|
|
def w1(x): w0(TAB * 1 + x)
|
|
def w2(x): w0(TAB * 2 + x)
|
|
def w3(x): w0(TAB * 3 + x)
|
|
|
|
w0('// This code was autogenerated by %s. Do not modify!' % (sys.argv[0]))
|
|
w0('// clang-format off')
|
|
w0('#ifndef XENIA_CPU_PPC_PPC_OPCODE_H_')
|
|
w0('#define XENIA_CPU_PPC_PPC_OPCODE_H_')
|
|
w0('')
|
|
w0('#include <cstdint>')
|
|
w0('')
|
|
w0('namespace xe {')
|
|
w0('namespace cpu {')
|
|
w0('namespace ppc {')
|
|
w0('')
|
|
|
|
for i in insns:
|
|
i.mnem = c_mnem(i.mnem)
|
|
i.subform = c_subform(i.subform)
|
|
insns = sorted(insns, key = lambda i: i.mnem)
|
|
|
|
w0('// All PPC opcodes in the same order they appear in ppc_instr_table.h:')
|
|
w0('enum class PPCOpcode : uint32_t {')
|
|
for i in insns:
|
|
w1('%s,' % (i.mnem))
|
|
w1('kInvalid,')
|
|
w0('};')
|
|
|
|
w0('')
|
|
w0('} // namespace ppc')
|
|
w0('} // namespace cpu')
|
|
w0('} // namespace xe')
|
|
w0('')
|
|
w0('#endif // XENIA_CPU_PPC_PPC_OPCODE_H_')
|
|
w0('')
|
|
|
|
return '\n'.join(l)
|
|
|
|
|
|
def generate_table(insns):
|
|
l = []
|
|
TAB = ' ' * 2
|
|
def w0(x): l.append(x)
|
|
def w1(x): w0(TAB * 1 + x)
|
|
def w2(x): w0(TAB * 2 + x)
|
|
def w3(x): w0(TAB * 3 + x)
|
|
|
|
w0('// This code was autogenerated by %s. Do not modify!' % (sys.argv[0]))
|
|
w0('// clang-format off')
|
|
w0('#include <cstdint>')
|
|
w0('')
|
|
w0('#include "xenia/cpu/ppc/ppc_opcode.h"')
|
|
w0('#include "xenia/cpu/ppc/ppc_opcode_info.h"')
|
|
w0('')
|
|
w0('namespace xe {')
|
|
w0('namespace cpu {')
|
|
w0('namespace ppc {')
|
|
w0('')
|
|
|
|
for i in insns:
|
|
i.mnem = '"' + c_mnem(i.mnem) + '"'
|
|
i.form = c_group(i.form)
|
|
i.subform = c_subform(i.subform)
|
|
i.desc = '"' + i.desc + '"'
|
|
i.group = c_group(i.group)
|
|
|
|
mnem_len = len(max(insns, key = lambda i: len(i.mnem)).mnem)
|
|
form_len = len(max(insns, key = lambda i: len(i.form)).form)
|
|
subform_len = len(max(insns, key = lambda i: len(i.subform)).subform)
|
|
desc_len = len(max(insns, key = lambda i: len(i.desc)).desc)
|
|
group_len = len(max(insns, key = lambda i: len(i.group)).group)
|
|
|
|
insns = sorted(insns, key = lambda i: i.mnem)
|
|
|
|
w0('#define INSTRUCTION(opcode, mnem, form, subform, group, desc) \\')
|
|
w0(' {opcode, mnem, PPCOpcodeFormat::form, PPCOpcodeGroup::group, desc}')
|
|
w0('PPCOpcodeInfo ppc_opcode_table[] = {')
|
|
fmt = 'INSTRUCTION(' + ', '.join([
|
|
'0x%08x',
|
|
'%-' + str(mnem_len) + 's',
|
|
'%-' + str(form_len) + 's',
|
|
'%-' + str(subform_len) + 's',
|
|
'%-' + str(group_len) + 's',
|
|
'%-' + str(desc_len) + 's',
|
|
]) + '),'
|
|
for i in insns:
|
|
w1(fmt % (i.opcode, i.mnem, i.form, i.subform, i.group, i.desc))
|
|
w0('};')
|
|
w0('static_assert(sizeof(ppc_opcode_table) / sizeof(PPCOpcodeInfo) == static_cast<int>(PPCOpcode::kInvalid), "PPC table mismatch - rerun ppc-table-gen");')
|
|
w0('')
|
|
w0('const PPCOpcodeInfo& GetOpcodeInfo(PPCOpcode opcode) {')
|
|
w1('return ppc_opcode_table[static_cast<int>(opcode)];')
|
|
w0('}')
|
|
|
|
w0('')
|
|
w0('} // namespace ppc')
|
|
w0('} // namespace cpu')
|
|
w0('} // namespace xe')
|
|
w0('')
|
|
|
|
return '\n'.join(l)
|
|
|
|
|
|
def generate_lookup(insns):
|
|
l = []
|
|
TAB = ' ' * 2
|
|
def w0(x): l.append(x)
|
|
def w1(x): w0(TAB * 1 + x)
|
|
def w2(x): w0(TAB * 2 + x)
|
|
def w3(x): w0(TAB * 3 + x)
|
|
|
|
for i in insns:
|
|
i.mnem = c_mnem(i.mnem)
|
|
|
|
w0('// This code was autogenerated by %s. Do not modify!' % (sys.argv[0]))
|
|
w0('// clang-format off')
|
|
w0('#include <cstdint>')
|
|
w0('')
|
|
w0('#include "xenia/base/assert.h"')
|
|
w0('#include "xenia/cpu/ppc/ppc_opcode.h"')
|
|
w0('#include "xenia/cpu/ppc/ppc_opcode_info.h"')
|
|
w0('')
|
|
w0('namespace xe {')
|
|
w0('namespace cpu {')
|
|
w0('namespace ppc {')
|
|
w0('')
|
|
w0('constexpr uint32_t ExtractBits(uint32_t v, uint32_t a, uint32_t b) {')
|
|
w0(' return (v >> (32 - 1 - b)) & ((1 << (b - a + 1)) - 1);')
|
|
w0('}')
|
|
w0('')
|
|
w0('#define PPC_DECODER_MISS assert_always(); return PPCOpcode::kInvalid')
|
|
w0('#define PPC_DECODER_HIT(name) return PPCOpcode::name;')
|
|
w0('')
|
|
w0('PPCOpcode LookupOpcode(uint32_t code) {')
|
|
w1('switch (ExtractBits(code, 0, 5)) {')
|
|
|
|
subtables = {}
|
|
for i in sorted(insns, key = lambda i: i.op_primary):
|
|
if i.op_primary not in subtables: subtables[i.op_primary] = []
|
|
subtables[i.op_primary].append(i)
|
|
|
|
for pri in sorted(subtables.iterkeys()):
|
|
# all the extended encodings (which we care about) end with bit 30. So we want to
|
|
# do the rest of the seach by bitscanning left from bit 30. This is simulated
|
|
# in the C switch-statement by creating leafs for each extended opcode,
|
|
# sorted by bitlength shortest to longest.
|
|
|
|
if len(subtables[pri]) == 1:
|
|
for i in subtables[pri]:
|
|
# the primary opcode field fully identifies the opcode
|
|
w1('case %i: PPC_DECODER_HIT(%s);' % (i.op_primary, i.mnem))
|
|
continue
|
|
|
|
extract_groups = {}
|
|
for i in subtables[pri]:
|
|
form_parts = extended_opcode_bits[i.form]
|
|
shift = 0
|
|
for form_part in form_parts:
|
|
shift = max(shift, form_part[1])
|
|
extract_parts = []
|
|
for form_part in form_parts:
|
|
extract_parts.append('(ExtractBits(code, %s, %s) << %s)' % (form_part[0], form_part[1], shift - form_part[1]))
|
|
extract_expression = '|'.join(extract_parts)
|
|
if extract_expression not in extract_groups:
|
|
extract_groups[extract_expression] = (i.form, extract_expression, [])
|
|
extract_groups[extract_expression][2].append(i)
|
|
|
|
w1('case %i:' % (pri))
|
|
for extract_expression in sorted(extract_groups.iterkeys()):
|
|
(form, extract_expression, group_insns) = extract_groups[extract_expression]
|
|
bit_span_low = 31
|
|
bit_span_high = 0
|
|
form_parts = extended_opcode_bits[form]
|
|
for form_part in form_parts:
|
|
bit_span_low = min(bit_span_low, form_part[0])
|
|
bit_span_high = max(bit_span_high, form_part[1])
|
|
bit_count = bit_span_high - bit_span_low + 1
|
|
w2('switch (%s) {' % (extract_expression))
|
|
for i in sorted(group_insns, key=lambda i: i.op_extended):
|
|
w3('case 0b%s: PPC_DECODER_HIT(%s);' % (
|
|
('{:0'+str(bit_count)+'b}').format(i.op_extended),
|
|
i.mnem))
|
|
w2('}')
|
|
w2('PPC_DECODER_MISS;')
|
|
|
|
w1('default: PPC_DECODER_MISS;')
|
|
|
|
w1('}')
|
|
w0('}')
|
|
w0('')
|
|
w0('} // namespace ppc')
|
|
w0('} // namespace cpu')
|
|
w0('} // namespace xe')
|
|
w0('')
|
|
|
|
# from this we can see some tables have bits which can be used to determine extended opcoded size:
|
|
# primary opcode 31:
|
|
# 01... = 9 bits (XO form), else 10 bits (X/XFX forms)
|
|
# primary opcode 63:
|
|
# 1.... = 7 bits (A form), else 10 bits (X/XFL forms)
|
|
# primary opcode 4:
|
|
# does not have small bit range to determine size, but you can just use the
|
|
# low 7 bits in order to "guess" the opcode. if you assume no invalid
|
|
# encodings are input, only the sequence ...0001000 actually *needs* the upper
|
|
# bits in order to differentiate the opcode (0100001000 = ps_abs, 0010001000 = ps_nabs)
|
|
# otherwise, the low 7bits can be used as the determinant, and a second comparison
|
|
# can be used against the real length of bits to fully match the extended opcode
|
|
#
|
|
# this approach can be generalized for all primary opcodes with extended opcodes of varying lengths:
|
|
# compare bits of smallest length, fall through to comparing larger sizes until found or failure
|
|
# with the optional optimization of discarding further compares for extended opcodes which
|
|
# share top bits with any other extended opcode (at the price of failing to detect invalid opcodes)
|
|
|
|
return '\n'.join(l)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
ppc_src_path = os.path.join(self_path, '..', 'src', 'xenia', 'cpu', 'ppc')
|
|
insns = parse_insns(os.path.join(self_path, 'ppc-instructions.xml'))
|
|
with open(os.path.join(ppc_src_path, 'ppc_opcode.h'), 'w') as f:
|
|
f.write(generate_opcodes(insns))
|
|
insns = parse_insns(os.path.join(self_path, 'ppc-instructions.xml'))
|
|
with open(os.path.join(ppc_src_path, 'ppc_opcode_table.cc'), 'w') as f:
|
|
f.write(generate_table(insns))
|
|
insns = parse_insns(os.path.join(self_path, 'ppc-instructions.xml'))
|
|
with open(os.path.join(ppc_src_path, 'ppc_opcode_lookup.cc'), 'w') as f:
|
|
f.write(generate_lookup(insns))
|