"""igc_exec.py -- Tier-1 IGC instruction-level executor (rev 2: NAMED op table). Sources of truth: PXPL5SUP/ADDR (igc_opco.h fragment), PXPL5SUP/DUMP (live op table + 2 reference packets), the expanded compiler output (PXPL5TRI.S etc.), and the firmware's own emitters (builder_trace). See IGC-ENCODING-DERIVATION.md. Word layout: addr(bits0-7) | op(bits8-15) | aux(bits16-22) | lenf(bits23-30) | S1(bit31) lenf is (len+115..117) for the length-bearing ops. Named single-word ops (low16 forms): NOOP=0, SETENABS=0x100, CLRENABS=0x200, MEMintoENAB=0x300|a, ENAB(and/or)eqMEM=0x500|a, ENABandeqMEMBAR=0x800|a, CRYintoMEM=0xb300|a, ENABintoMEM=0xb500|a, MEMoreqENAB=0xb700|a, MEMandeqENAB=0xb900|a, ENABxoreqMEM=0x10a00|a, MEMBARintoENAB=0x4800|a. 4-word tree ops (hdr + A,B,C floats): op 0x42 TREEltZERO (0x3ae94200; L0 variant aux 0x41), op 0x0d TREEgeZERO (0x3ae80d00; L0 aux 0x40), op 0x21 MEMltTREE_L3 (0x44ea21xx), op 0x43 TREEintoMEM_L3 (0x3daa43xx / 0x3d2a43xx; L0 = aux 0x8a/0x02/0x06, 1 word). 2/3-word mem ops: op 0x5a TREEclmpintoMEM (+P-word +val), op 0xf7 SCAintoMEM (+val), op 0x6c MEMgeSCA/pluseq-family (+operand [+val]), op 0x4c CPY (+operand), op 0x25/0x21-aux1 sweep pair (+operand), 0x97/0x1f/0xfa/0xf2/ 0x05/0x49/0x30/0x75/0xdb/0x7c/0xcf/0x62 eof-family (structurally parsed; semantics best-effort or stubbed -- see EXOTIC below). """ import struct TILE_W, TILE_H = 64, 128 PIXBYTES = 26 NPIX = TILE_W * TILE_H def f32(w): return struct.unpack('> 24) == 0 and ((w >> 8) & 0xff) in range(0, 0xff) # (op, aux) -> (mnemonic, n_extra_words) [aux=None matches any] OPTAB = {} def _reg(op, aux, name, extra): OPTAB[(op, aux)] = (name, extra) _reg(0x42, 0x69, 'TREEltZERO_L3', 3) _reg(0x42, 0x41, 'TREEltZERO_L0', 0) _reg(0x0d, 0x68, 'TREEgeZERO_L3', 3) _reg(0x0d, 0x40, 'TREEgeZERO_L0', 0) _reg(0x21, 0x6a, 'MEMltTREE_L3', 3) _reg(0x21, 0x01, 'SWEEP21', 1) _reg(0x25, 0x01, 'SWEEP25', 1) _reg(0x43, 0x2a, 'TREEintoMEM_L3', 3) _reg(0x43, 0x0a, 'TREEintoMEM_L0', 0) _reg(0x43, 0x02, 'TREEintoMEM_L0', 0) _reg(0x43, 0x06, 'TREEintoMEM_L0', 0) _reg(0x5a, None, 'TREEclmpintoMEM', 2) _reg(0xf7, None, 'SCAintoMEM', 1) _reg(0x6c, None, 'MEMSCA6C', 1) _reg(0x4c, None, 'CPY', 1) _reg(0x48, None, 'OP48', 1) # w/ len-field: stipple/enable-cond (operand optional) _reg(0x7c, None, 'OP7C', 1) _reg(0x1f, None, 'OP1F', 0) _reg(0xfa, None, 'OPFA', 0) _reg(0xf2, None, 'OPF2', 1) _reg(0x97, None, 'OP97', 0) _reg(0x05, None, 'OP05', 0) _reg(0x49, None, 'OP49', 1) _reg(0x30, None, 'OP30', 1) _reg(0x75, None, 'OP75', 1) _reg(0xdb, None, 'OPDB', 0) def parse(words): out, unk = [], [] i, n = 0, len(words) while i < n: w = words[i] & 0xffffffff op = (w >> 8) & 0xff addr = w & 0xff aux = (w >> 16) & 0x7f lnf = (w >> 23) & 0xff ln = lnf - 115 i += 1 if w == 0: out.append(('NOOP',)); continue if w == 0x100: out.append(('SETENABS',)); continue if w == 0x200: out.append(('CLRENABS',)); continue if w == 0x80000000 and i < n and (words[i] >> 24) == 0x48: out.append(('FBITS', 15)); i += 1; continue lowops = {0x03: 'MEMintoENAB', 0x05: 'ENABandeqMEM', 0x08: 'ENABandeqMEMBAR', 0xb3: 'CRYintoMEM', 0xb5: 'ENABintoMEM', 0xb7: 'MEMoreqENAB', 0xb9: 'MEMandeqENAB'} if (w >> 16) == 0 and op in lowops: out.append((lowops[op], addr)); continue if (w >> 16) == 1 and op == 0x0a: out.append(('ENABxoreqMEM', addr)); continue if (w >> 16) == 0 and op == 0x48: out.append(('MEMBARintoENAB', addr)); continue ent = OPTAB.get((op, aux)) or OPTAB.get((op, None)) if ent: name, extra = ent if extra == 3 and i + 2 < n + 1: out.append((name, addr, ln, f32(words[i]), f32(words[i+1]), f32(words[i+2]))) i += 3; continue if extra == 2 and i + 1 < n + 1: out.append((name, addr, ln, words[i], words[i+1])); i += 2; continue if extra == 1: # take the operand only if it looks like one if i < n and _is_operand(words[i]): out.append((name, addr, ln, words[i])); i += 1 else: out.append((name, addr, ln, None)) continue out.append((name, addr, ln)); continue unk.append((i - 1, w)) out.append(('UNK', w)) return out, unk class Tile: def __init__(self, ox=0, oy=0): self.ox, self.oy = ox, oy self.mem = [bytearray(PIXBYTES) for _ in range(NPIX)] self.enab = [1] * NPIX self.tree = (0.0, 0.0, 0.0) @staticmethod def _rd(pix, bit0, bits): v = 0 for k in range(bits): b = bit0 + k v |= ((pix[b >> 3] >> (b & 7)) & 1) << k return v @staticmethod def _wr(pix, bit0, bits, val): for k in range(bits): b = bit0 + k if (val >> k) & 1: pix[b >> 3] |= 1 << (b & 7) else: pix[b >> 3] &= ~(1 << (b & 7)) def _tv(self, x, y): A, B, C = self.tree return A * (x + self.ox) + B * (y + self.oy) + C def run(self, instrs): unhandled = set() for ins in instrs: m = ins[0] if m in ('NOOP', 'FBITS', 'UNK'): continue elif m == 'SETENABS': self.enab = [1] * NPIX elif m == 'CLRENABS': self.enab = [0] * NPIX elif m in ('TREEltZERO_L3', 'TREEgeZERO_L3'): _, addr, ln, A, B, C = ins self.tree = (A, B, C) lt = (m[4] == 'l') # ltZERO for y in range(TILE_H): ybase = y * TILE_W for x in range(TILE_W): i = x + ybase if self.enab[i]: v = self._tv(x, y) ok = (v < 0) if lt else (v >= 0) if not ok: self.enab[i] = 0 elif m == 'MEMltTREE_L3': _, addr, ln, A, B, C = ins self.tree = (A, B, C) for y in range(TILE_H): ybase = y * TILE_W for x in range(TILE_W): i = x + ybase if self.enab[i]: if not (self._rd(self.mem[i], addr, max(1, ln)) < int(self._tv(x, y))): self.enab[i] = 0 elif m in ('TREEintoMEM_L3', 'TREEintoMEM_L0'): if m.endswith('L3'): _, addr, ln, A, B, C = ins self.tree = (A, B, C) else: _, addr, ln = ins ln = max(1, ln) mask = (1 << ln) - 1 for y in range(TILE_H): ybase = y * TILE_W for x in range(TILE_W): i = x + ybase if self.enab[i]: self._wr(self.mem[i], addr, ln, int(self._tv(x, y)) & mask) elif m == 'TREEclmpintoMEM': _, addr, ln, pword, vword = ins ln = max(1, ln) v = f32(vword) iv = max(0, min((1 << ln) - 1, int(v * ((1 << ln) - 1)))) for i in range(NPIX): if self.enab[i]: self._wr(self.mem[i], addr, ln, iv) elif m == 'SCAintoMEM': _, addr, ln, operand = ins ln = max(1, ln) v = (operand or 0) & ((1 << ln) - 1) for i in range(NPIX): if self.enab[i]: self._wr(self.mem[i], addr, ln, v) elif m == 'CPY': _, dst, ln, operand = ins if operand is None: continue src = operand & 0xff ln = max(1, ln) for i in range(NPIX): if self.enab[i]: self._wr(self.mem[i], dst, ln, self._rd(self.mem[i], src, ln)) elif m == 'MEMintoENAB': _, a = ins for i in range(NPIX): self.enab[i] = (self.mem[i][a >> 3] >> (a & 7)) & 1 elif m == 'MEMBARintoENAB': _, a = ins for i in range(NPIX): self.enab[i] = 1 - ((self.mem[i][a >> 3] >> (a & 7)) & 1) elif m == 'ENABandeqMEM': _, a = ins for i in range(NPIX): self.enab[i] &= (self.mem[i][a >> 3] >> (a & 7)) & 1 elif m == 'ENABandeqMEMBAR': _, a = ins for i in range(NPIX): self.enab[i] &= 1 - ((self.mem[i][a >> 3] >> (a & 7)) & 1) elif m == 'ENABxoreqMEM': _, a = ins for i in range(NPIX): self.enab[i] ^= (self.mem[i][a >> 3] >> (a & 7)) & 1 elif m == 'ENABintoMEM': _, a = ins for i in range(NPIX): self._wr(self.mem[i], a, 1, self.enab[i]) elif m == 'MEMoreqENAB': _, a = ins for i in range(NPIX): if self.enab[i]: self.mem[i][a >> 3] |= 1 << (a & 7) elif m == 'MEMandeqENAB': _, a = ins for i in range(NPIX): if not self.enab[i]: self.mem[i][a >> 3] &= ~(1 << (a & 7)) elif m == 'SWEEP25': # bit-serial accumulate step: dst-field += src bit (best-effort: # treat the pair as part of a field ADD; the paired SWEEP21 reads # the src bit into the carry -- we implement the NET effect when # the operand gives {src,dst,len}: dst[0:len] += src bit << k is # approximated by a single-bit OR into dst (first pass). _, dst, ln, operand = ins if operand is None: continue src = operand & 0xff for i in range(NPIX): if self.enab[i]: b = (self.mem[i][src >> 3] >> (src & 7)) & 1 if b: self.mem[i][dst >> 3] |= 1 << (dst & 7) else: unhandled.add(m) return unhandled def rgb(self): out = [] for y in range(TILE_H): row = [] for x in range(TILE_W): p = self.mem[x + y * TILE_W] row.append((self._rd(p, 184, 8), self._rd(p, 192, 8), self._rd(p, 200, 8))) out.append(row) return out def rgb24(self): out = [] for y in range(TILE_H): row = [] for x in range(TILE_W): p = self.mem[x + y * TILE_W] row.append((self._rd(p, 117, 8), self._rd(p, 125, 8), self._rd(p, 133, 8))) out.append(row) return out if __name__ == '__main__': # validation: parse DUMP's reference packet (transcribed from the file) ref = [0x100, 0x3ae94200, 0xc1900000, 0x41200000, 0xc4840000, 0x3ae94200, 0xc1400000, 0x41200000, 0xc34c0000, 0x3ae94200, 0x41f00000, 0xc1a00000, 0x44a50000, 0x44ea2120, 0x80000000, 0x80000000, 0x3f7fffff, 0x438a4320, 0x3daa4352, 0x3e849bae, 0xbdd70a3b, 0xc1976c7f, 0x3daa435a, 0xbea24ddd, 0x3d4ccccc, 0x421a3788, 0x41aa4362, 0x80000000, 0x80000000, 0x3f7fffff] ins, unk = parse(ref) names = [x[0] for x in ins] assert names == ['SETENABS', 'TREEltZERO_L3', 'TREEltZERO_L3', 'TREEltZERO_L3', 'MEMltTREE_L3', 'TREEintoMEM_L0', 'TREEintoMEM_L3', 'TREEintoMEM_L3', 'TREEintoMEM_L3'], names assert not unk, unk print('DUMP reference-packet parse PASS:', names) # execute it: a triangle with edges (-18x+10y-1056), (-12x+10y-204), (30x-20y+1320) t = Tile(ox=0, oy=0) left = t.run(ins) img = t.rgb24() lit = sum(1 for row in img for px in row if px != (0, 0, 0)) print('reference triangle: %d lit pixels, unhandled=%s' % (lit, left or 'none'))