| #encoding=utf-8 | |
| # Copyright (C) 2016 Intel Corporation | |
| # Copyright (C) 2016 Broadcom | |
| # Copyright (C) 2020 Collabora, Ltd. | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a | |
| # copy of this software and associated documentation files (the "Software"), | |
| # to deal in the Software without restriction, including without limitation | |
| # the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| # and/or sell copies of the Software, and to permit persons to whom the | |
| # Software is furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice (including the next | |
| # paragraph) shall be included in all copies or substantial portions of the | |
| # Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| # IN THE SOFTWARE. | |
| import os | |
| import textwrap | |
| import xml.etree.ElementTree as ET | |
| import sys | |
| # All instructions in the ISA | |
| instructions = [] | |
| MODIFIERS = {} | |
| enums = {} | |
| immediates = [] | |
| def xmlbool(s): | |
| assert(s.lower() in ["false", "true"]) | |
| return False if s.lower() == "false" else True | |
| class EnumValue: | |
| def __init__(self, value, default): | |
| self.value = value | |
| self.default = default | |
| class Enum: | |
| def __init__(self, name, values): | |
| self.name = name | |
| self.values = values | |
| self.bare_values = [x.value for x in values] | |
| defaults = [x.value for x in values if x.default] | |
| if len(defaults) > 0: | |
| assert(len(defaults) == 1) | |
| self.default = defaults[0] | |
| def build_enum(el): | |
| values = [] | |
| for child in el: | |
| if child.tag == 'value': | |
| is_default = child.attrib.get('default', False) | |
| values.append(EnumValue(child.text, is_default)) | |
| elif child.tag == 'reserved': | |
| values.append(EnumValue("reserved", False)) | |
| return Enum(el.attrib['name'], values) | |
| class Modifier: | |
| def __init__(self, name, start, size, implied = False, force_enum = None): | |
| self.name = name | |
| self.start = start | |
| self.size = size | |
| self.implied = implied | |
| self.is_enum = (force_enum is not None) or size > 1 | |
| self.enum = force_enum or name | |
| if not self.is_enum: | |
| self.bare_values = ['', name] | |
| self.default = 0 | |
| else: | |
| self.bare_values = [x.value for x in enums[self.enum].values] | |
| defaults = [x for x in enums[self.enum].values if x.default] | |
| assert(len(defaults) <= 1) | |
| if len(defaults) > 0: | |
| self.default = self.bare_values.index(defaults[0].value) | |
| else: | |
| self.default = None | |
| def Flag(name, start): | |
| return Modifier(name, start, 1) | |
| # Model a single instruction | |
| class Source: | |
| def __init__(self, index, size, is_float = False, swizzle = False, | |
| halfswizzle = False, widen = False, lanes = False, combine = False, lane = None, absneg = False, notted = False, name = ""): | |
| self.is_float = is_float or absneg | |
| self.start = (index * 8) | |
| self.size = size | |
| self.absneg = absneg | |
| self.notted = notted | |
| self.swizzle = swizzle | |
| self.halfswizzle = halfswizzle | |
| self.widen = widen | |
| self.lanes = lanes | |
| self.lane = lane | |
| self.combine = combine | |
| self.name = name | |
| self.offset = {} | |
| self.bits = {} | |
| if absneg: | |
| self.offset['neg'] = 32 + 2 + ((2 - index) * 2) | |
| self.offset['abs'] = 33 + 2 + ((2 - index) * 2) | |
| self.bits['neg'] = 1 | |
| self.bits['abs'] = 1 | |
| if notted: | |
| self.offset['not'] = 35 | |
| self.bits['not'] = 1 | |
| if widen or lanes or halfswizzle: | |
| self.offset['widen'] = 26 if index == 1 else 36 | |
| self.bits['widen'] = 4 # XXX: too much? | |
| if lane: | |
| self.offset['lane'] = self.lane | |
| self.bits['lane'] = 2 if size in (8, 32) else 1 | |
| if swizzle: | |
| assert(size in [16, 32]) | |
| self.offset['swizzle'] = 24 + ((2 - index) * 2) | |
| self.bits['swizzle'] = 2 | |
| if combine: | |
| self.offset['combine'] = 37 | |
| self.bits['combine'] = 3 | |
| class Dest: | |
| def __init__(self, name = ""): | |
| self.name = name | |
| class Staging: | |
| def __init__(self, read = False, write = False, count = 0, flags = 'true', name = ""): | |
| self.name = name | |
| self.read = read | |
| self.write = write | |
| self.count = count | |
| self.flags = (flags != 'false') | |
| self.start = 40 | |
| if write and not self.flags: | |
| self.start = 16 | |
| # For compatibility | |
| self.absneg = False | |
| self.swizzle = False | |
| self.notted = False | |
| self.widen = False | |
| self.lanes = False | |
| self.lane = False | |
| self.halfswizzle = False | |
| self.combine = False | |
| self.size = 32 | |
| if not self.flags: | |
| self.encoded_flags = 0 | |
| elif flags == 'rw': | |
| self.encoded_flags = 0xc0 | |
| else: | |
| assert(flags == 'true') | |
| self.encoded_flags = (0x80 if write else 0) | (0x40 if read else 0) | |
| class Immediate: | |
| def __init__(self, name, start, size, signed): | |
| self.name = name | |
| self.start = start | |
| self.size = size | |
| self.signed = signed | |
| class Instruction: | |
| def __init__(self, name, opcode, opcode2, srcs = [], dests = [], immediates = [], modifiers = [], staging = None, unit = None): | |
| self.name = name | |
| self.srcs = srcs | |
| self.dests = dests | |
| self.opcode = opcode | |
| self.opcode2 = opcode2 or 0 | |
| self.immediates = immediates | |
| self.modifiers = modifiers | |
| self.staging = staging | |
| self.unit = unit | |
| self.is_signed = len(name.split(".")) > 1 and ('s' in name.split(".")[1]) | |
| # Message-passing instruction <===> not ALU instruction | |
| self.message = unit not in ["FMA", "CVT", "SFU"] | |
| self.secondary_shift = max(len(self.srcs) * 8, 16) | |
| self.secondary_mask = 0xF if opcode2 is not None else 0x0 | |
| if "left" in [x.name for x in self.modifiers]: | |
| self.secondary_mask |= 0x100 | |
| if len(srcs) == 3 and (srcs[1].widen or srcs[1].lanes or srcs[1].swizzle): | |
| self.secondary_mask &= ~0xC # conflicts | |
| if opcode == 0x90: | |
| # XXX: XMLify this, but disambiguates sign of conversions | |
| self.secondary_mask |= 0x10 | |
| if name.startswith("LOAD.i") or name.startswith("STORE.i") or name.startswith("LD_BUFFER.i"): | |
| self.secondary_shift = 27 # Alias with memory_size | |
| self.secondary_mask = 0x7 | |
| if "descriptor_type" in [x.name for x in self.modifiers]: | |
| self.secondary_mask = 0x3 | |
| self.secondary_shift = 37 | |
| elif "memory_width" in [x.name for x in self.modifiers]: | |
| self.secondary_mask = 0x7 | |
| self.secondary_shift = 27 | |
| assert(len(dests) == 0 or not staging) | |
| assert(not opcode2 or (opcode2 & self.secondary_mask) == opcode2) | |
| def __str__(self): | |
| return self.name | |
| # Build a single source from XML | |
| def build_source(el, i, size): | |
| lane = el.get('lane', None) | |
| if lane == "true": | |
| lane = 38 if i == 0 else 36 | |
| elif lane is not None: | |
| lane = int(lane) | |
| return Source(i, int(el.get('size', size)), | |
| absneg = el.get('absneg', False), | |
| is_float = el.get('float', False), | |
| swizzle = el.get('swizzle', False), | |
| halfswizzle = el.get('halfswizzle', False), | |
| widen = el.get('widen', False), | |
| lanes = el.get('lanes', False), | |
| combine = el.get('combine', False), | |
| lane = lane, | |
| notted = el.get('not', False), | |
| name = el.text or "") | |
| def build_imm(el): | |
| return Immediate(el.attrib['name'], int(el.attrib['start']), | |
| int(el.attrib['size']), bool(el.attrib.get('signed', False))) | |
| def build_staging(i, el): | |
| r = xmlbool(el.attrib.get('read', 'false')) | |
| w = xmlbool(el.attrib.get('write', 'false')) | |
| count = int(el.attrib.get('count', '0')) | |
| flags = el.attrib.get('flags', 'true') | |
| return Staging(r, w, count, flags, el.text or '') | |
| def build_modifier(el): | |
| name = el.attrib['name'] | |
| start = int(el.attrib['start']) | |
| size = int(el.attrib['size']) | |
| implied = xmlbool(el.get('implied', 'false')) | |
| return Modifier(name, start, size, implied) | |
| # Build a single instruction from XML and group based overrides | |
| def build_instr(el, overrides = {}): | |
| # Get overridables | |
| name = overrides.get('name') or el.attrib.get('name') | |
| opcode = overrides.get('opcode') or el.attrib.get('opcode') | |
| opcode2 = overrides.get('opcode2') or el.attrib.get('opcode2') | |
| unit = overrides.get('unit') or el.attrib.get('unit') | |
| opcode = int(opcode, base=0) | |
| opcode2 = int(opcode2, base=0) if opcode2 else None | |
| # Get explicit sources/dests | |
| tsize = typesize(name) | |
| sources = [] | |
| i = 0 | |
| for src in el.findall('src'): | |
| if (src.attrib.get('pseudo', False)): | |
| continue | |
| built = build_source(src, i, tsize) | |
| sources += [built] | |
| # 64-bit sources in a 32-bit (message) instruction count as two slots | |
| # Affects BLEND, ST_CVT | |
| if tsize != 64 and built.size == 64: | |
| i = i + 2 | |
| else: | |
| i = i + 1 | |
| dests = [Dest(dest.text or '') for dest in el.findall('dest')] | |
| # Get implicit ones | |
| sources = sources + ([Source(i, int(tsize)) for i in range(int(el.attrib.get('srcs', 0)))]) | |
| dests = dests + ([Dest()] * int(el.attrib.get('dests', 0))) | |
| # Get staging registers | |
| staging = [build_staging(i, el) for i, el in enumerate(el.findall('sr'))] | |
| # Get immediates | |
| imms = [build_imm(imm) for imm in el.findall('imm')] | |
| modifiers = [] | |
| for mod in el: | |
| if (mod.tag in MODIFIERS) and not (mod.attrib.get('pseudo', False)): | |
| modifiers.append(MODIFIERS[mod.tag]) | |
| elif mod.tag =='va_mod': | |
| modifiers.append(build_modifier(mod)) | |
| instr = Instruction(name, opcode, opcode2, srcs = sources, dests = dests, immediates = imms, modifiers = modifiers, staging = staging, unit = unit) | |
| instructions.append(instr) | |
| # Build all the instructions in a group by duplicating the group itself with | |
| # overrides for each distinct instruction | |
| def build_group(el): | |
| for ins in el.findall('ins'): | |
| build_instr(el, overrides = { | |
| 'name': ins.attrib['name'], | |
| 'opcode': ins.attrib.get('opcode'), | |
| 'opcode2': ins.attrib.get('opcode2'), | |
| 'unit': ins.attrib.get('unit'), | |
| }) | |
| def to_alphanum(name): | |
| substitutions = { | |
| ' ': '_', | |
| '/': '_', | |
| '[': '', | |
| ']': '', | |
| '(': '', | |
| ')': '', | |
| '-': '_', | |
| ':': '', | |
| '.': '', | |
| ',': '', | |
| '=': '', | |
| '>': '', | |
| '#': '', | |
| '&': '', | |
| '*': '', | |
| '"': '', | |
| '+': '', | |
| '\'': '', | |
| } | |
| for i, j in substitutions.items(): | |
| name = name.replace(i, j) | |
| return name | |
| def safe_name(name): | |
| name = to_alphanum(name) | |
| if not name[0].isalpha(): | |
| name = '_' + name | |
| return name.lower() | |
| # Parses out the size part of an opcode name | |
| def typesize(opcode): | |
| if opcode[-3:] == '128': | |
| return 128 | |
| if opcode[-2:] == '48': | |
| return 48 | |
| elif opcode[-1] == '8': | |
| return 8 | |
| else: | |
| try: | |
| return int(opcode[-2:]) | |
| except: | |
| return 32 | |
| # Parse the ISA | |
| def valhall_parse_isa(xmlfile = False): | |
| global MODIFIERS | |
| global enums | |
| global immediates | |
| global root | |
| xmlfile = os.path.join(os.path.dirname(__file__), 'ISA.xml') | |
| tree = ET.parse(xmlfile) | |
| root = tree.getroot() | |
| # All immediates in the ISA | |
| ilut = root.findall('lut')[0] | |
| assert(ilut.attrib['name'] == "Immediates") | |
| immediates = [int(imm.text, base=0) for imm in ilut.findall('constant')] | |
| for child in root.findall('enum'): | |
| enums[safe_name(child.attrib['name'])] = build_enum(child) | |
| MODIFIERS = { | |
| # Texture instructions share a common encoding | |
| "wide_indices": Flag("wide_indices", 8), | |
| "array_enable": Flag("array_enable", 10), | |
| "texel_offset": Flag("texel_offset", 11), | |
| "shadow": Flag("shadow", 12), | |
| "integer_coordinates": Flag("integer_coordinates", 13), | |
| "fetch_component": Modifier("fetch_component", 14, 2), | |
| "lod_mode": Modifier("lod_mode", 13, 3), | |
| "lod_bias_disable": Modifier("lod_mode", 13, 1), | |
| "lod_clamp_disable": Modifier("lod_mode", 14, 1), | |
| "write_mask": Modifier("write_mask", 22, 4), | |
| "register_type": Modifier("register_type", 26, 2), | |
| "dimension": Modifier("dimension", 28, 2), | |
| "skip": Flag("skip", 39), | |
| "register_width": Modifier("register_width", 46, 1, force_enum = "register_width"), | |
| "secondary_register_width": Modifier("secondary_register_width", 47, 1, force_enum = "register_width"), | |
| "vartex_register_width": Modifier("varying_texture_register_width", 24, 2), | |
| "atom_opc": Modifier("atomic_operation", 22, 4), | |
| "atom_opc_1": Modifier("atomic_operation_with_1", 22, 4), | |
| "inactive_result": Modifier("inactive_result", 22, 4), | |
| "memory_access": Modifier("memory_access", 24, 2), | |
| "regfmt": Modifier("register_format", 24, 3), | |
| "source_format": Modifier("source_format", 24, 4), | |
| "vecsize": Modifier("vector_size", 28, 2), | |
| "slot": Modifier("slot", 30, 3), | |
| "roundmode": Modifier("round_mode", 30, 2), | |
| "result_type": Modifier("result_type", 30, 2), | |
| "saturate": Flag("saturate", 30), | |
| "not_result": Flag("not_result", 30), | |
| "lane_op": Modifier("lane_operation", 32, 2), | |
| "cmp": Modifier("condition", 32, 3), | |
| "clamp": Modifier("clamp", 32, 2), | |
| "sr_count": Modifier("staging_register_count", 33, 3, implied = True), | |
| "sample_and_update": Modifier("sample_and_update_mode", 33, 3), | |
| "sr_write_count": Modifier("staging_register_write_count", 36, 3, implied = True), | |
| "conservative": Flag("conservative", 35), | |
| "subgroup": Modifier("subgroup_size", 36, 4), | |
| "update": Modifier("update_mode", 36, 2), | |
| "sample": Modifier("sample_mode", 38, 2), | |
| } | |
| for child in root: | |
| if child.tag == 'group': | |
| build_group(child) | |
| elif child.tag == 'ins': | |
| build_instr(child) | |
| instruction_dict = { ins.name: ins for ins in instructions } | |
| # Validate there are no duplicated instructions | |
| if len(instruction_dict) != len(instructions): | |
| import collections | |
| counts = collections.Counter([i.name for i in instructions]) | |
| for c in counts: | |
| if counts[c] != 1: | |
| print(f'{c} appeared {counts[c]} times.') | |
| assert(len(instruction_dict) == len(instructions)) | |
| return (instructions, immediates, enums, typesize, safe_name) |