diff options
author | Wladimir J. van der Laan <laanwj@gmail.com> | 2013-01-08 21:42:39 +0100 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@gmail.com> | 2013-01-08 21:44:38 +0100 |
commit | 35c9f0d40d06a49e9bb8903a9251b047add5650f (patch) | |
tree | 9c45861bd080e3d80077eb177fb65fcf6015ef1d | |
parent | c2bcf0a2a0fe3f89fb9a4bf523950e5c0fee68ae (diff) |
add shader assembler
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | doc/kernel_interface.md | 92 | ||||
-rw-r--r-- | native/replay/ps_sandbox_etna.c | 3 | ||||
-rw-r--r-- | rnndb/isa.xml | 87 | ||||
-rwxr-xr-x | tools/asm.py | 324 | ||||
-rwxr-xr-x | tools/disasm.py | 182 | ||||
-rw-r--r-- | tools/etnaviv/asm_common.py | 229 | ||||
-rw-r--r-- | tools/etnaviv/parse_rng.py | 7 |
8 files changed, 739 insertions, 195 deletions
@@ -48,16 +48,18 @@ Vivante has a unified, fixed-size, predictable instruction format with explicit and outputs. This does simplify code generation, compared to a weird flow pipe system like the Mali 200/400. -Disassembler -------------- +Assembler and disassembler +---------------------------- A basic disassembler for the shader instructions can be found in the tools directory: - tools/disass.py + tools/disasm.py rnn/isa.xml <shader.bin> This can be used to disassemble shaders extracted using `dump_cmdstream.py --dump-shaders`. -An assembler still needs to be written. +There is also an assembler: + + tools/asm.py rnn/isa.xml <shader.asm> -o <shader.bin> Command stream format ----------------------- diff --git a/doc/kernel_interface.md b/doc/kernel_interface.md index 6952e37..8980c71 100644 --- a/doc/kernel_interface.md +++ b/doc/kernel_interface.md @@ -198,3 +198,95 @@ At least in the v2 kernel driver they are not used. They are used for building t userspace driver, but not for using it. +Profiling +=============== + +HW profiling registers can be read using the ioctl: + + gcvHAL_READ_ALL_PROFILE_REGISTERS + +This will return a structure `gcsPROFILER_COUNTERS`, defined in `GC_HAL_PROFILER.h`, which has the following timers: + +Hardware-wise, the memory controller keeps track of these counters in registers `MC_PROFILE_xx_READ`, +switched by corresponding bits in registers `MC_PROFILE_CONFIGx`. + +HW static counters (clock rates). These are not filled in by the kernel, it appears. + + gpuClock + axiClock + shaderClock + +HW variable counters + + gpuClockStart + gpuClockEnd + gpuCyclesCounter + gpuTotalRead64BytesPerFrame + gpuTotalWrite64BytesPerFrame + +PE (Pixel engine) + + pe_pixel_count_killed_by_color_pipe + pe_pixel_count_killed_by_depth_pipe + pe_pixel_count_drawn_by_color_pipe + pe_pixel_count_drawn_by_depth_pipe + +SH (Shader engine) + + ps_inst_counter + rendered_pixel_counter + vs_inst_counter + rendered_vertice_counter + vtx_branch_inst_counter + vtx_texld_inst_counter + pxl_branch_inst_counter + pxl_texld_inst_counter + +PA (Primitive assembly) + + pa_input_vtx_counter + pa_input_prim_counter + pa_output_prim_counter + pa_depth_clipped_counter + pa_trivial_rejected_counter + pa_culled_counter + +SE (Setup engine) + + se_culled_triangle_count + se_culled_lines_count + +RA (Rasterizer) + + ra_valid_pixel_count + ra_total_quad_count + ra_valid_quad_count_after_early_z + ra_total_primitive_count + ra_pipe_cache_miss_counter + ra_prefetch_cache_miss_counter + ra_eez_culled_counter + +TX (Texture engine) + + tx_total_bilinear_requests + tx_total_trilinear_requests + tx_total_discarded_texture_requests + tx_total_texture_requests + tx_mem_read_count + tx_mem_read_in_8B_count + tx_cache_miss_count + tx_cache_hit_texel_count + tx_cache_miss_texel_count + +MC (Memory controller) + + mc_total_read_req_8B_from_pipeline + mc_total_read_req_8B_from_IP + mc_total_write_req_8B_from_pipeline + +HI (Host interface) + + hi_axi_cycles_read_request_stalled + hi_axi_cycles_write_request_stalled + hi_axi_cycles_write_data_stalled + diff --git a/native/replay/ps_sandbox_etna.c b/native/replay/ps_sandbox_etna.c index aca3aaf..604427c 100644 --- a/native/replay/ps_sandbox_etna.c +++ b/native/replay/ps_sandbox_etna.c @@ -652,8 +652,9 @@ int main(int argc, char **argv) /* 0x06011009, 0x00000000, 0x00000000, 0x20100008, */ + /* appears that invalid instructions are simply ignored */ /* r=r+g component */ - 0x00811001, 0x00001800, 0x00000000, 0x00154018, + 0x00811013, 0x00001800, 0x00000000, 0x00154018, 0x07011009, 0x00000000, 0x00000000, 0x20100008, }; diff --git a/rnndb/isa.xml b/rnndb/isa.xml index d812420..1820024 100644 --- a/rnndb/isa.xml +++ b/rnndb/isa.xml @@ -27,14 +27,6 @@ xsi:schemaLocation="http://nouveau.freedesktop.org/ rules-ng.xsd"> <domain name="VIV_ISA"> <!-- XXX still unsure if rules-ng is a suitable format for ISA descriptions, I don't really think so, but it will initially help to put notes in a more structured format. - - Anyone know an description format better suited, and which - allows automatic generation of assemblers/disassemblers/code generators if possible? - (LLVM tblgen maybe?). - - The Envytools disassembler is table-based and could be adopted, - it would have to be modified to support 128-bit instructions. - --> <enum name="INST_OPCODE" brief="Main opcode table"> @@ -92,7 +84,14 @@ xsi:schemaLocation="http://nouveau.freedesktop.org/ rules-ng.xsd"> </doc> </value> <value value="0x0A" name="MOVAR" brief="Move address to address register"/> - <value value="0x0B" name="MOVAF" brief="Move float to address register"/> + <value value="0x0B" name="MOVAF" brief="Move float to address register"> + <doc> + dst := src2 + + Copies the floating point value of operand src2 to address register dst. + XXX does this round or floor? + </doc> + </value> <value value="0x0C" name="RCP" brief="Reciprocal"> <doc> dst := 1.0 / src2 @@ -139,13 +138,57 @@ xsi:schemaLocation="http://nouveau.freedesktop.org/ rules-ng.xsd"> <value value="0x1E" name="ENDREP" brief="Ends a REPEAT block"/> <value value="0x1F" name="LOOP" brief="Begins a LOOP block"/> <value value="0x20" name="ENDLOOP" brief="Ends a LOOP block"/> - <value value="0x21" name="SQRT" brief="Square root"/> <!-- HAS_SQRT_TRIG --> - <value value="0x22" name="SIN" brief="Sine"/> <!-- HAS_SQRT_TRIG --> - <value value="0x23" name="COS" brief="Cosine"/> <!-- HAS_SQRT_TRIG --> + <value value="0x21" name="SQRT" brief="Square root"> <!-- HAS_SQRT_TRIG --> + <doc> + dst := sqrt(src2) + + Computes the square root of src2 and puts the result in temporary register dst. + </doc> + </value> + <value value="0x22" name="SIN" brief="Sine"> <!-- HAS_SQRT_TRIG --> + <doc> + dst := sin(src2 * (PI/2)) + + Computes the sine of src2 and puts the result in temporary register dst. + + The period of the sine is 4 and not 2 PI, thus to get normal behavior the instruction + should be prefixed by a division by PI/2. + </doc> + </value> + <value value="0x23" name="COS" brief="Cosine"> <!-- HAS_SQRT_TRIG --> + <doc> + dst := cos(src2 * (PI/2)) + + Computes the cosine of src2 and puts the result in temporary register dst. + + The period of the cosine is 4 and not 2 PI, thus to get normal behavior the instruction + should be prefixed by a division by PI/2. + </doc> + </value> <value value="0x24" name="POLY"/> - <value value="0x25" name="FLOOR" brief="Largest integral value not greater than the argument"/> <!-- HAS_SIGN_FLOOR_CEIL --> - <value value="0x26" name="CEIL" brief="Smallest integral value not less than the argument"/> <!-- HAS_SIGN_FLOOR_CEIL --> - <value value="0x27" name="SIGN" brief="Return sign of the argument"/> <!-- HAS_SIGN_FLOOR_CEIL --> + <value value="0x25" name="FLOOR" brief="Largest integral value not greater than the argument"> <!-- HAS_SIGN_FLOOR_CEIL --> + <doc> + dst := floor(src2) + + Computes the largest integral value not greater than the argument, and puts the result in temporary + register dst. + </doc> + </value> + <value value="0x26" name="CEIL" brief="Smallest integral value not less than the argument"> <!-- HAS_SIGN_FLOOR_CEIL --> + <doc> + dst := ceil(src2) + + Computes the smallest integral value not less than the argument, and puts the result in temporary + register dst. + </doc> + </value> + <value value="0x27" name="SIGN" brief="Return sign of the argument"> <!-- HAS_SIGN_FLOOR_CEIL --> + <doc> + dst := sign(src2) + + Return 1.0 if the sign is positive or zero, -1.0 if negative. + </doc> + </value> <value value="0x28" name="ADDLO"/> <value value="0x29" name="MULLO"/> <value value="0x2A" name="BARRIER" brief="Thread barrier"/> @@ -292,7 +335,7 @@ xsi:schemaLocation="http://nouveau.freedesktop.org/ rules-ng.xsd"> <!-- operand 0 --> <bitfield high="11" low="11" name="SRC0_USE" brief="Source operand 0 used"/> <bitfield high="20" low="12" name="SRC0_REG" brief="Source operand 0 register"/> - <!-- bit 21? --> + <bitfield high="21" low="21" name="UNK1_21"/> <bitfield high="29" low="22" name="SRC0_SWIZ" type="INST_SWIZ" brief="Source operand 0 swizzle"/> <bitfield high="30" low="30" name="SRC0_NEG" brief="Source operand 0 negate"/> <bitfield high="31" low="31" name="SRC0_ABS" brief="Source operand 0 absolute"/> @@ -303,26 +346,28 @@ xsi:schemaLocation="http://nouveau.freedesktop.org/ rules-ng.xsd"> <!-- operand 1 --> <bitfield high="6" low="6" name="SRC1_USE" brief="Source operand 1 used"/> <bitfield high="15" low="7" name="SRC1_REG" brief="Source operand 1 register"/> - <!-- bit 16? --> + <bitfield high="16" low="16" name="UNK2_16"/> <bitfield high="24" low="17" name="SRC1_SWIZ" type="INST_SWIZ" brief="Source operand 1 swizzle"/> <bitfield high="25" low="25" name="SRC1_NEG" brief="Source operand 1 negate"/> <bitfield high="26" low="26" name="SRC1_ABS" brief="Source operand 1 absolute"/> <bitfield high="29" low="27" name="SRC1_AMODE" type="INST_AMODE" brief="Source operand 1 addressing mode"/> - <!-- bit 30,31? --> + <bitfield high="31" low="30" name="UNK2_30"/> </reg32> <reg32 offset="0x0000C" name="WORD_3"> <bitfield high="2" low="0" name="SRC1_RGROUP" type="INST_RGROUP" brief="Source operand 1 register group"/> + <!-- bits 7..21: instruction address, effectively takes the place of src2 operand --> + <bitfield high="21" low="7" name="SRC2_IMM" brief="Immediate (address) operand"/> <!-- operand 2 --> <bitfield high="3" low="3" name="SRC2_USE" brief="Source operand 2 used"/> <bitfield high="12" low="4" name="SRC2_REG" brief="Source operand 2 register"/> - <!-- bit 13? --> + <bitfield high="13" low="13" name="UNK3_13"/> <bitfield high="21" low="14" name="SRC2_SWIZ" type="INST_SWIZ" brief="Source operand 2 swizzle"/> <bitfield high="22" low="22" name="SRC2_NEG" brief="Source operand 2 negate"/> <bitfield high="23" low="23" name="SRC2_ABS" brief="Source operand 2 absolute"/> - <!-- bit 24? --> + <bitfield high="24" low="24" name="UNK3_24"/> <bitfield high="27" low="25" name="SRC2_AMODE" type="INST_AMODE" brief="Source operand 2 addressing mode"/> <bitfield high="30" low="28" name="SRC2_RGROUP" type="INST_RGROUP" brief="Source operand 2 register group"/> - <!-- bit 31? --> + <bitfield high="31" low="31" name="UNK3_31"/> </reg32> </domain> diff --git a/tools/asm.py b/tools/asm.py new file mode 100755 index 0000000..4794b98 --- /dev/null +++ b/tools/asm.py @@ -0,0 +1,324 @@ +#!/usr/bin/python +''' +Shader assembler. + +Usage: asm.py ../rnndb/isa.xml in.asm out.bin +''' +# Copyright (c) 2012-2013 Wladimir J. van der Laan +# +# 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, sub license, +# 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 NON-INFRINGEMENT. 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. +from __future__ import print_function, division, unicode_literals +import argparse,struct +import sys +from binascii import b2a_hex +import re + +from etnaviv.parse_rng import parse_rng_file, format_path, BitSet, Domain +from etnaviv.asm_common import DstOperand, DstOperandAReg, SrcOperand, TexOperand, AddrOperand, Instruction, AMODES, COMPS +from etnaviv.asm_common import disassemble, format_instruction + +reg_re = re.compile('^(t|u|a|tex)(\d+)(\[.*?\])?(\.[xyzw]{1,4})?$') +label_re = re.compile('^[0-9a-zA-Z\-\_]+$') + +def parse_amode(amode): + if not amode: + return 0 + return AMODES.index(amode[1:-1]) + +def parse_comps(comps): + if not comps: + return 15 + return ((('x' in comps)<<0)|(('y' in comps)<<1)|(('z' in comps)<<2)|(('w' in comps)<<3)) + +def parse_swiz(swiz): + if not swiz: + return 0xe4 + rv = 0 + for idx in xrange(4): + if idx < len(swiz): + comp = COMPS.index(swiz[idx+1]) + rv |= comp << (idx * 2) + return rv + +def assemble(isa, inst, warnings): + fields = {} + fields['OPCODE'] = inst.op + fields['COND'] = inst.cond + fields['SAT'] = inst.sat + + if isinstance(inst.dst, DstOperandAReg): + # XXX validate that this instruction accepts + # address destination arguments + fields['DST_REG'] = inst.dst.reg + fields['DST_COMPS'] = inst.dst.comps + elif isinstance(inst.dst, DstOperand): + fields['DST_USE'] = inst.dst.use + fields['DST_AMODE'] = inst.dst.amode + fields['DST_REG'] = inst.dst.reg + fields['DST_COMPS'] = inst.dst.comps + elif inst.dst is None: + fields['DST_USE'] = 0 + else: + warnings.append('Invalid destination argument') + + if inst.tex is not None: + fields['TEX_ID'] = inst.tex.id + fields['TEX_AMODE'] = inst.tex.amode + fields['TEX_SWIZ'] = inst.tex.swiz + + if inst.addr is not None: + fields['SRC2_IMM'] = inst.addr.addr + + for (idx, src) in enumerate(inst.src): + if src is not None: + fields['SRC%i_USE' % idx] = src.use + fields['SRC%i_REG' % idx] = src.reg + fields['SRC%i_SWIZ' % idx] = src.swiz + fields['SRC%i_NEG' % idx] = src.neg + fields['SRC%i_ABS' % idx] = src.abs + fields['SRC%i_AMODE' % idx] = src.amode + fields['SRC%i_RGROUP' % idx] = src.rgroup + + # XXX check for colliding fields + domain = isa.lookup_domain('VIV_ISA') + rv = [0,0,0,0] + for word in [0,1,2,3]: + mask = 0 + bitset = domain.lookup_address(word*4)[-1][0].type + for field in bitset.bitfields: + if field.name in fields: + try: + rv[word] |= field.fill(fields[field.name]) + del fields[field.name] + except ValueError,e: + warnings.append(str(e)) + for field in fields.iterkeys(): # warn if fields are not used, that's probably a typo + warnings.append('Field %s not used' % field) + return rv + +class Assembler(object): + ''' + Instruction assembler context. + ''' + labels = {} + linenr = 0 + instructions = None + source = None + def __init__(self, isa): + self.isa = isa + self.errors = [] + self.instructions = [] + self.source = [] + + def parse(self, line): + # remove comment + self.source.append(line) + self.linenr += 1 + (line, _, _) = line.partition(';') + + (label, _, line) = line.rpartition(':') + if label: + label = label.strip() + if not label_re.match(label): + self.errors.append((self.linenr, 'Invalid label: %s' % label)) + self.labels[label] = len(self.instructions) + + (inst, _, operands) = line.strip().partition(' ') + + inst = inst.split('.') + if not inst[0]: # empty line + return None + + try: + op = self.isa.types['INST_OPCODE'].values_by_name[inst[0]].value + except KeyError: + self.errors.append((self.linenr, 'Unknown instruction %s' % inst[0])) + return None + + cond = 0 + sat = False + conditions = self.isa.types['INST_CONDITION'].values_by_name + for atom in inst[1:]: + if atom in conditions: + cond = conditions[atom].value + elif atom == 'SAT': + sat = True + else: + self.errors.append((self.linenr, 'Unknown atom %s' % atom)) + return None + + operands = operands.split(',') + src = [] + dst = None + tex = None + addr = None + for idx,operand in enumerate(operands): + operand = operand.strip() + neg = False + abs = False + if operand.startswith('|'): + if not operand.endswith('|'): + self.errors.append((self.linenr, 'Unterminated |')) + abs = True + operand = operand[1:-1] + if operand.startswith('-'): + neg = True + operand = operand[1:] + + # check kind of operand + # (t|u|a)XXX[.xyzw] (address)register + match_reg = reg_re.match(operand) + if match_reg: + (regtype, regid, amode, swiz) = match_reg.groups() + regid = int(regid) + try: + amode = parse_amode(amode) + except LookupError: + self.errors.append((self.linenr, 'Unknown amode %s' % amode)) + amode = 0 + if idx == 0: # destination operand + comps = parse_comps(swiz) + if regtype == 't': + dst = DstOperand(use=1, amode=amode, reg=regid, comps=comps) + elif regtype == 'a': + dst = DstOperandAReg(reg=regid, comps=comps) + else: + self.errors.append((self.linenr, 'Cannot have texture or uniform as destination argument')) + else: # source operand + try: + swiz = parse_swiz(swiz) + except LookupError: + self.errors.append((self.linenr, 'Unknown swizzle %s' % swiz)) + swiz = 0 + if regtype == 't' or regtype == 'u': + rgroup = 0 + if regtype == 'u': + if regid < 128: + rgroup = 2 + else: + rgroup = 3 + regid -= 128 + src.append(SrcOperand(use=1, reg=regid, swiz=swiz, neg=neg, abs=abs, amode=amode, rgroup=rgroup)) + elif regtype == 'a': + src.append(DstOperandAReg(reg=regid, comps=comps)) + elif regtype == 'tex': + tex = TexOperand(id=regid, amode=amode, swiz=swiz) + else: + self.errors.append((self.linenr, 'Unparseable register type %s' % regtype)) + arg_obj = None + elif operand == 'void': + #print('void') + if idx == 0: # destination operand + dst = None + else: + src.append(None) + elif label_re.match(operand): + #print('label ', operand) + addr = AddrOperand(addr = operand) # will resolve labels later + else: + self.errors.append((self.linenr, 'Unknown operand ' + operand)) + inst_out = Instruction(op=op, + cond=cond,sat=sat, + tex=tex,dst=dst,src=src,addr=addr,unknowns={},linenr=self.linenr) + self.instructions.append(inst_out) + return inst_out + + def generate_code(self): + rv = [] + for inst in self.instructions: + warnings = [] + if inst.addr is not None: # fill in labels + try: + addr = AddrOperand(self.labels[inst.addr.addr]) + except LookupError: + self.errors.append((inst.linenr, 'Unknown label ' + inst.addr.addr)) + inst = inst._replace(addr=addr) + inst_out = assemble(self.isa, inst, warnings) + rv.append(inst_out) + + dis_i = disassemble(self.isa, inst_out, warnings) + if not compare_inst(inst, dis_i, warnings): + # Assembly did not match disassembly, print details + warnings.append('%08x %08x %08x %08x %s' % ( + inst_out[0], inst_out[1], inst_out[2], inst_out[3], format_instruction(self.isa, dis_i))) + warnings.append(' orig : %s' % (self.source[inst.linenr-1])) + + for warning in warnings: + self.errors.append((inst.linenr, warning)) + return rv + +def compare_inst(a,b,warnings): + match = True + for attr in ['op', 'cond', 'sat', 'tex', 'dst', 'src', 'addr']: + if getattr(a, attr) != getattr(b, attr): + warnings.append('Assembly/disassembly mismatch: %s %s %s' % (attr, getattr(a, attr), getattr(b, attr))) + match = False + return match + +def parse_arguments(): + parser = argparse.ArgumentParser(description='Disassemble shader') + parser.add_argument('isa_file', metavar='ISAFILE', type=str, + help='Shader ISA definition file (rules-ng-ng)') + parser.add_argument('input', metavar='INFILE', type=str, + help='Shader assembly file') + #parser.add_argument('output', metavar='OUTFILE', type=str, + # help='Binary shader file') + #parser.add_argument('-a', dest='addr', + # default=False, action='store_const', const=True, + # help='Show address data with instructions') + parser.add_argument('-o', dest='bin_out', type=str, + help='Write binary shader to output file') + return parser.parse_args() + +def main(): + args = parse_arguments() + out = sys.stdout + isa = parse_rng_file(args.isa_file) + + with open(args.input, 'rb') as f: + asm = Assembler(isa) + errors = [] + for linenr, line in enumerate(f): + line = line.rstrip('\n') + asm.parse(line) + + if not asm.errors: + code = asm.generate_code() + else: + code = None + + for line, error in asm.errors: + print('Line %i: %s' % (line, error)) + + if code is not None: + if args.bin_out is not None: + with open(args.bin_out, 'wb') as f: + for inst in code: + f.write(struct.pack(b'<IIII', *inst)) + else: # no binary output, print as ascii + for inst in code: + print('0x%08x,0x%08x,0x%08x,0x%08x,' % tuple(inst)) + + + +if __name__ == '__main__': + main() + + diff --git a/tools/disasm.py b/tools/disasm.py index 31d9337..2a402f7 100755 --- a/tools/disasm.py +++ b/tools/disasm.py @@ -31,171 +31,7 @@ from binascii import b2a_hex from collections import namedtuple from etnaviv.parse_rng import parse_rng_file, format_path, BitSet, Domain - -# Register groups -# t temporary -# u uniform 0..127 -# v uniform 127..255 (this is rewritten to u in format_src) -# others are unknown -rgroups = ['t', '?1?', 'u', 'v', '?4?', '?5?', '?6?', '?7?'] -# Addressing modes -amodes = ['', 'a.x', 'a.y', 'a.z', 'a.w', '?5?', '?6?', '?7?'] -# components -COMPS = 'xyzw' - -def bitextr(val, hi, lo): - '''Extract and return bits hi..lo from value val''' - return (val >> lo) & ((1<<(hi-lo+1))-1) -def format_swiz(swiz): - swiz = [(swiz >> x)&3 for x in [0,2,4,6]] - return ''.join([COMPS[c] for c in swiz]) -def format_comps(comps): - return ''.join([COMPS[c] for c in range(4) if ((comps >> c)&1)]) - -DstOperand = namedtuple('DstOperand', ['use', 'amode', 'reg', 'comps']) -SrcOperand = namedtuple('SrcOperand', ['use', 'reg', 'swiz', 'neg', 'abs', 'amode', 'rgroup']) -TexOperand = namedtuple('TexOperand', ['id', 'amode', 'swiz']) -Instruction = namedtuple('Instruction', ['op', 'cond', 'sat', 'tex', 'dst', 'src', 'unknowns']) - -def disassemble(isa, inst): - '''Parse four 32-bit instruction words into Instruction object''' - op = bitextr(inst[0], 5, 0) - cond = bitextr(inst[0], 10, 6) - sat = bitextr(inst[0], 11, 11) # saturate - - dst = DstOperand( - use = bitextr(inst[0], 12, 12), # desination used - amode = bitextr(inst[0], 15, 13), # addressing mode - reg = bitextr(inst[0], 22, 16), # reg nr - comps = bitextr(inst[0], 26, 23) # xyzw - ) - - tex = TexOperand( - id = bitextr(inst[0], 31, 27), # texture sampler id - amode = bitextr(inst[1], 2, 0), - swiz = bitextr(inst[1], 10, 3) - ) - - src = [ - SrcOperand( - use = bitextr(inst[1], 11, 11), - reg = bitextr(inst[1], 20, 12), - swiz = bitextr(inst[1], 29, 22), - neg = bitextr(inst[1], 30, 30), - abs = bitextr(inst[1], 31, 31), - amode = bitextr(inst[2], 2, 0), # addressing mode - rgroup = bitextr(inst[2], 5, 3) # reg type (0=temp, 1=?, 2=uniform, 3=uniform) - ), - SrcOperand( - use = bitextr(inst[2], 6, 6), - reg = bitextr(inst[2], 15, 7), - swiz = bitextr(inst[2], 24, 17), - neg = bitextr(inst[2], 25, 25), - abs = bitextr(inst[2], 26, 26), - amode = bitextr(inst[2], 29, 27), - rgroup = bitextr(inst[3], 2, 0) - ), - SrcOperand( - use = bitextr(inst[3], 3, 3), - reg = bitextr(inst[3], 12, 4), - swiz = bitextr(inst[3], 21, 14), - neg = bitextr(inst[3], 22, 22), - abs = bitextr(inst[3], 23, 23), - amode = bitextr(inst[3], 27, 25), - rgroup = bitextr(inst[3], 30, 28) - ) - ] - - # Unknown fields -- these must be 0 - unknowns = [ - ('bit_1_21', bitextr(inst[1], 21, 21)), - ('bit_2_16', bitextr(inst[2], 16, 16)), - ('bit_2_28', bitextr(inst[2], 31, 28)), - ('bit_3_16', bitextr(inst[3], 13, 13)), - ('bit_3_24', bitextr(inst[3], 24, 24)), - ('bit_3_31', bitextr(inst[3], 31, 31)) - ] - return Instruction(op=op,cond=cond,sat=sat,tex=tex,dst=dst,src=src,unknowns=unknowns) - -def format_dst(isa, dst, warnings): - '''Format destination operand''' - if dst.use: - # actually, target register group depends on the instruction, but usually it's a temporary... - arg = 't%i' % (dst.reg) - if dst.amode != 0: - arg += '[%s]' % amodes[dst.amode] - if dst.comps != 15: # if not all comps selected - arg += '.' + format_comps(dst.comps) - else: - arg = 'void' # unused argument - if dst.amode != 0 or dst.reg != 0 or dst.comps != 0: - warnings.append('dst not used but fields non-zero') - - return arg - -def format_src(isa, src, warnings): - '''Format source operand''' - if src.use: - if src.rgroup == 3: # map vX to uniform u(X+128) - rgroup = 2 - reg = 128 + src.reg - else: - rgroup = src.rgroup - reg = src.reg - arg = '%s%i' % (rgroups[rgroup], reg) - if src.amode != 0: - arg += '[%s]' % amodes[src.amode] - if src.swiz != 0xe4: # if not null swizzle - arg += '.' + format_swiz(src.swiz) - # XXX is the - or the | done first? In a way, -|x| is the only ordering that makes sense. - if src.abs: - return '|' + arg + '|' - if src.neg: - return '-' + arg - else: - arg = 'void' # unused argument - if src.reg != 0 or src.swiz != 0 or src.neg != 0 or src.abs != 0 or src.amode != 0 or src.rgroup != 0: - warnings.append('src not used but fields non-zero') - return arg - -def format_tex(isa, tex, warnings): - '''Format texture operand''' - arg = 'tex%i' % (tex.id) - if tex.amode != 0: - arg += '[%i]' % amodes[tex.amode] - if tex.swiz != 0xe4: # if not null swizzle - arg += '.' + format_swiz(tex.swiz) - - return arg - -def format_instruction(isa, inst, warnings): - ''' - Format instruction as text. - ''' - atoms = [] - args = [] - atoms.append(isa.types['INST_OPCODE'].describe(inst.op)) - if inst.cond: - atoms.append(isa.types['INST_CONDITION'].describe(inst.cond)) - if inst.sat: - atoms.append(sat) - opcode = '.'.join(atoms) - - args.append(format_dst(isa, inst.dst, warnings)) - if inst.op in [0x18, 0x19, 0x1A, 0x1B, 0x1C]: - args.append(format_tex(isa, inst.tex, warnings)) - else: - if inst.tex.id != 0 or inst.tex.amode != 0 or inst.tex.swiz != 0: - warnings.append('tex not used but fields non-zero') - - for src in inst.src: - args.append(format_src(isa, src, warnings)) - - # verify that all bits in unknown are 0 - for (name,value) in inst.unknowns: - if value != 0: - warnings.append('!%s=%i!' % (name,value)) - return opcode+' '+(', '.join(args)) +from etnaviv.asm_common import format_instruction, disassemble def parse_arguments(): parser = argparse.ArgumentParser(description='Disassemble shader') @@ -203,8 +39,12 @@ def parse_arguments(): help='Shader ISA definition file (rules-ng-ng)') parser.add_argument('input', metavar='INFILE', type=str, help='Binary shader file') - #parser.add_argument('-r', dest='raw_out', type=str, - # help='Export raw data to file') + parser.add_argument('-a', dest='addr', + default=False, action='store_const', const=True, + help='Show address data with instructions') + parser.add_argument('-r', dest='raw', + default=False, action='store_const', const=True, + help='Show raw data with instructions') return parser.parse_args() def main(): @@ -219,9 +59,13 @@ def main(): exit(1) for idx in xrange(len(data)//16): inst = struct.unpack(b'<IIII', data[idx*16:idx*16+16]) - parsed = disassemble(isa, inst) + if args.addr: + out.write('%3x: ' % idx) + if args.raw: + out.write('%08x %08x %08x %08x ' % inst) warnings = [] - text = format_instruction(isa, parsed, warnings) + parsed = disassemble(isa, inst, warnings) + text = format_instruction(isa, parsed) out.write(text) if warnings: out.write(' ; ') diff --git a/tools/etnaviv/asm_common.py b/tools/etnaviv/asm_common.py new file mode 100644 index 0000000..de1cff8 --- /dev/null +++ b/tools/etnaviv/asm_common.py @@ -0,0 +1,229 @@ +#!/usr/bin/python +''' +Etna shader disassembler/assembler common utils. +''' +# Copyright (c) 2012-2013 Wladimir J. van der Laan +# +# 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, sub license, +# 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 NON-INFRINGEMENT. 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. +from __future__ import print_function, division, unicode_literals +import argparse,struct +import sys +from binascii import b2a_hex +from collections import namedtuple + +from etnaviv.parse_rng import parse_rng_file, format_path, BitSet, Domain + +# Register groups +# t temporary +# u uniform 0..127 +# v uniform 127..255 (this is rewritten to u in format_src) +# others are unknown +RGROUPS = ['t', '?1?', 'u', 'v', '?4?', '?5?', '?6?', '?7?'] +# Addressing modes +AMODES = ['', 'a.x', 'a.y', 'a.z', 'a.w', '?5?', '?6?', '?7?'] +# components +COMPS = 'xyzw' + +def format_swiz(swiz): + swiz = [(swiz >> x)&3 for x in [0,2,4,6]] + return ''.join([COMPS[c] for c in swiz]) +def format_comps(comps): + return ''.join([COMPS[c] for c in range(4) if ((comps >> c)&1)]) + +DstOperand = namedtuple('DstOperand', ['use', 'amode', 'reg', 'comps']) +DstOperandAReg = namedtuple('DstOperandAReg', ['reg', 'comps']) +SrcOperand = namedtuple('SrcOperand', ['use', 'reg', 'swiz', 'neg', 'abs', 'amode', 'rgroup']) +TexOperand = namedtuple('TexOperand', ['id', 'amode', 'swiz']) +AddrOperand = namedtuple('AddrOperand', ['addr']) +Instruction = namedtuple('Instruction', ['op', 'cond', 'sat', 'tex', 'dst', 'src', 'addr', 'unknowns', 'linenr']) + +def disassemble(isa, inst, warnings): + '''Parse four 32-bit instruction words into Instruction object''' + # Extract bit fields using ISA + domain = isa.lookup_domain('VIV_ISA') + fields = {} + for word in [0,1,2,3]: + mask = 0 + bitset = domain.lookup_address(word*4)[-1][0].type + for field in bitset.bitfields: + fields[field.name] = field.extract(inst[word]) + mask |= field.mask + if mask != 0xffffffff: + warnings.append('isa for word %i incomplete' % word) + op = fields['OPCODE'] + + if op in [0x0A, 0x0B]: # Move to address register + dst = DstOperandAReg( + reg = fields['DST_REG'], # reg nr + comps = fields['DST_COMPS'] # xyzw + ) + if fields['DST_AMODE'] != 0 or fields['DST_USE'] != 0: + warnings.append('use and amode bitfields are nonzero for areg') + else: + dst = DstOperand( + use = fields['DST_USE'], # destination used + amode = fields['DST_AMODE'], # addressing mode + reg = fields['DST_REG'], # reg nr + comps = fields['DST_COMPS'] # xyzw + ) + if not dst.use: + if dst.amode != 0 or dst.reg != 0 or dst.comps != 0: + warnings.append('dst not used but fields non-zero') + dst = None + + tex = TexOperand( + id = fields['TEX_ID'], # texture sampler id + amode = fields['TEX_AMODE'], + swiz = fields['TEX_SWIZ'] + ) + if op not in [0x18, 0x19, 0x1A, 0x1B, 0x1C]: # tex op + if tex.id != 0 or tex.amode != 0 or tex.swiz != 0: + warnings.append('tex not used but fields non-zero') + tex = None + + if op in [0x14, 0x16]: # CALL, BRANCH + # Address (immediate) operand takes the place of src2 + addr = AddrOperand(fields['SRC2_IMM']) + else: + addr = None + + # Determine number of source operands + num_src = 3 + if addr is not None: # src2 is invalid when address operand used + num_src = 2 + + src = [] + for idx in xrange(num_src): + operand = SrcOperand( + use = fields['SRC%i_USE' % idx], reg = fields['SRC%i_REG' % idx], + swiz = fields['SRC%i_SWIZ' % idx], neg = fields['SRC%i_NEG' % idx], + abs = fields['SRC%i_ABS' % idx], amode = fields['SRC%i_AMODE' % idx], + rgroup = fields['SRC%i_RGROUP' % idx] + ) + if not operand.use: + if operand.reg != 0 or operand.swiz != 0 or operand.neg != 0 or operand.abs != 0 or operand.amode != 0 or operand.rgroup != 0: + warnings.append('src%i not used but fields non-zero' % idx) + operand = None + + src.append(operand) + + # Unknown fields -- will warn if these are not 0 + unknowns = [ + ('bit_1_21', fields['UNK1_21']), ('bit_2_16', fields['UNK2_16']), + ('bit_2_30', fields['UNK2_30']), ('bit_3_24', fields['UNK3_24']), + ('bit_3_31', fields['UNK3_31']) + ] + if addr is None: # bit13 may be set if immediate operand 2 + unknowns.append(('bit_3_13', fields['UNK3_13'])) + # verify that all bits in unknown are 0 + for (name,value) in unknowns: + if value != 0: + warnings.append('!%s=%i!' % (name,value)) + return Instruction(op=op, + cond=fields['COND'],sat=fields['SAT'], + tex=tex,dst=dst,src=src,addr=addr,unknowns=unknowns,linenr=None) + +def format_dst(isa, dst): + '''Format destination operand''' + if dst is not None: + # actually, target register group depends on the instruction, but usually it's a temporary... + arg = 't%i' % (dst.reg) + if dst.amode != 0: + arg += '[%s]' % amodes[dst.amode] + if dst.comps != 15: # if not all comps selected + arg += '.' + format_comps(dst.comps) + else: + arg = 'void' # unused argument + + return arg + +def format_dst_areg(isa, dst): + '''Format destination operand''' + arg = 'a%i' % (dst.reg) + if dst.comps != 15: # if not all comps selected + arg += '.' + format_comps(dst.comps) + + return arg + +def format_src(isa, src): + '''Format source operand''' + if src is not None: + if src.rgroup == 3: # map vX to uniform u(X+128) + rgroup = 2 + reg = 128 + src.reg + else: + rgroup = src.rgroup + reg = src.reg + arg = '%s%i' % (RGROUPS[rgroup], reg) + if src.amode != 0: + arg += '[%s]' % AMODES[src.amode] + if src.swiz != 0xe4: # if not null swizzle + arg += '.' + format_swiz(src.swiz) + # XXX is the - or the | done first? In a way, -|x| is the only ordering that makes sense. + if src.abs: + return '|' + arg + '|' + if src.neg: + return '-' + arg + else: + arg = 'void' # unused argument + return arg + +def format_tex(isa, tex): + '''Format texture operand''' + arg = 'tex%i' % (tex.id) + if tex.amode != 0: + arg += '[%i]' % amodes[tex.amode] + if tex.swiz != 0xe4: # if not null swizzle + arg += '.' + format_swiz(tex.swiz) + + return arg + +def format_addr(isa, addr): + return 'label_%x' % (addr.addr) + +def format_instruction(isa, inst): + ''' + Format instruction as text. + ''' + atoms = [] + args = [] + atoms.append(isa.types['INST_OPCODE'].describe(inst.op)) + if inst.cond: + atoms.append(isa.types['INST_CONDITION'].describe(inst.cond)) + if inst.sat: + atoms.append(sat) + opcode = '.'.join(atoms) + + if isinstance(inst.dst, DstOperandAReg): + args.append(format_dst_areg(isa, inst.dst)) + else: + args.append(format_dst(isa, inst.dst)) + + if inst.tex is not None: + args.append(format_tex(isa, inst.tex)) + + for src in inst.src: + args.append(format_src(isa, src)) + + if inst.addr is not None: + args.append(format_addr(isa, inst.addr)) + + return opcode+' '+(', '.join(args)) + diff --git a/tools/etnaviv/parse_rng.py b/tools/etnaviv/parse_rng.py index e61e6ee..98c9d45 100644 --- a/tools/etnaviv/parse_rng.py +++ b/tools/etnaviv/parse_rng.py @@ -241,6 +241,13 @@ class BitField(TypedValue, RNNObject): def extract(self, value): '''Extract this bit field from a value''' return (value >> self.low) & ((1<<(self.high-self.low+1))-1) + + def fill(self, value): + '''Return value filled into this bit field''' + rv = (value << self.low) + if rv != (rv & self.mask): + raise ValueError('Value %i doesn\'t fit in mask %s' % (value, self.name)) + return rv def describe(self, value): return self.type.describe(self.extract(value)) |