"""igc_exec.py -- Tier-1 IGC instruction-level executor. Parses the compiled IGC coefficient stream (the SEND payload words the i860 firmware ships to the PXPL5 array) and executes it on a modelled 64x128 tile: 26-byte-per-pixel bit memory + enable register + the shared linear-expression tree, per IGCOPS.C semantics. Encoding (derived from the expanded compiler output -- see IGC-ENCODING-DERIVATION.md; constructors verified word-exact against PXPL5TRI.S/PXPL5OPT.S/EOF.S and the captured streams): word = op<<8 | aux<<16 | addr | (len+115..117)<<23 | flags(bits18-21) | S1<<31 Instruction templates (word counts): 0x00000000 NOOP 0x00000100 SETENABS 0x0000060N EDGE N (N=1..3) + A,B,C floats (4 words) 0x422100-family (op 0x21) MEMltTREE_L3 + A,B,C floats (4 words) 0x024300-family (op 0x43, aux 0x02/0x06) TREEintoMEM_L0 (1 word) 0x2a4300-family (op 0x43, aux 0x2a) TREEintoMEM_L3 + A,B,C (4 words) 0x80435a00-family (op 0x5a) TREEclmpintoMEM_C1 + P-word + value (3 words) 0x0000f700-family (op 0xf7) SCAintoMEM_S1 + value word (2 words) 0x80016c00-family (op 0x6c) MEMgeSCA_S1 + addr word + value (3 words) 0x0300|x MEMintoENAB(x) 0x4800|x MEMBARintoENAB(x) 0xb500|x ENABintoMEM(x) 0x10a00|x ENABxoreqMEM(x) 0x414200 / 0x400d00 (len 2) opacity-intro ops (enable set + mode) 0x80000000,0x48000000 FBITS pair """ import struct TILE_W, TILE_H = 64, 128 PIXBYTES = 26 def f32(w): return struct.unpack('> 8) & 0xff addr = w & 0xff aux = (w >> 16) & 0x7f lnf = (w >> 23) & 0xff if w == 0: out.append(('NOOP',)); i += 1; continue if w == 0x100: out.append(('SETENABS',)); i += 1; continue if w in (0x601, 0x602, 0x603) and i + 3 < n: out.append(('EDGE', w & 0xf, f32(words[i+1]), f32(words[i+2]), f32(words[i+3]))) i += 4; continue if w == 0x80000000 and i + 1 < n and (words[i+1] >> 24) == 0x48: out.append(('FBITS', 15)); i += 2; continue if op == 0x21 and i + 3 < n: # MEMltTREE_L3 (z test) out.append(('MEMltTREE', addr, lnf - 117, f32(words[i+1]), f32(words[i+2]), f32(words[i+3]))) i += 4; continue if op == 0x43 and aux == 0x2a and i + 3 < n: # TREEintoMEM_L3 out.append(('TREEintoMEM_L3', addr, lnf - 115, f32(words[i+1]), f32(words[i+2]), f32(words[i+3]))) i += 4; continue if op == 0x43 and aux != 0x2a: # TREEintoMEM_L0 out.append(('TREEintoMEM_L0', addr, lnf - 115)); i += 1; continue if op == 0x5a and i + 2 < n: # TREEclmpintoMEM_C1 + P + val out.append(('TREEclmpintoMEM', addr, lnf - 115, (words[i+1] >> 16) & 0x7f, f32(words[i+2]))) i += 3; continue if op == 0xf7 and i + 1 < n: # SCAintoMEM_S1 + val out.append(('SCAintoMEM', addr, lnf - 116, f32(words[i+1]), words[i+1])) i += 2; continue if op == 0x6c and i + 2 < n: # MEMgeSCA_S1 + addr + val out.append(('MEMgeSCA', words[i+1] & 0xff, lnf - 116, words[i+2])) i += 3; continue if op == 0x03 and (w >> 16) == 0: out.append(('MEMintoENAB', addr)); i += 1; continue if op == 0x48 and (w >> 16) == 0: out.append(('MEMBARintoENAB', addr)); i += 1; continue if op == 0xb5 and (w >> 16) == 0: out.append(('ENABintoMEM', addr)); i += 1; continue if op == 0x0a and (w >> 16) == 1: out.append(('ENABxoreqMEM', addr)); i += 1; continue if op == 0x42 and aux in (0x41,): # opacity-intro A out.append(('OPAC_INTRO_A',)); i += 1; continue if op == 0x0d: # opacity-intro B out.append(('OPAC_INTRO_B',)); i += 1; continue unk.append((i, w)) out.append(('UNK', w)) i += 1 return out, unk # ---------------- the tile ---------------- class Tile: """64x128 pixels, 26-byte bit-addressed memory each, 1-bit enable, and the shared linear-expression tree evaluated per-pixel at (tile_x+x, tile_y+y).""" def __init__(self, ox=0, oy=0): self.ox, self.oy = ox, oy self.mem = [bytearray(PIXBYTES) for _ in range(TILE_W * TILE_H)] self.enab = [1] * (TILE_W * TILE_H) self.tree = (0.0, 0.0, 0.0) # A, B, C self.frac = 15 # bit-field access (LSB-first within the 208-bit pixel memory) @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 _treeval(self, x, y): A, B, C = self.tree return int(A * (x + self.ox) + B * (y + self.oy) + C) def run(self, instrs, trace=False): for ins in instrs: m = ins[0] if m in ('NOOP', 'FBITS', 'OPAC_INTRO_A', 'OPAC_INTRO_B', 'UNK'): continue if m == 'SETENABS': self.enab = [1] * (TILE_W * TILE_H); continue if m == 'EDGE': _, n, A, B, C = ins self.tree = (A, B, C) for y in range(TILE_H): for x in range(TILE_W): i = x + y * TILE_W if self.enab[i] and self._treeval(x, y) < 0: self.enab[i] = 0 continue if m == 'MEMltTREE': _, addr, ln, A, B, C = ins self.tree = (A, B, C) for y in range(TILE_H): for x in range(TILE_W): i = x + y * TILE_W if self.enab[i]: if not (self._rd(self.mem[i], addr, ln) > self._treeval(x, y)): self.enab[i] = 0 continue if m == 'TREEintoMEM_L3': _, addr, ln, A, B, C = ins self.tree = (A, B, C) for y in range(TILE_H): for x in range(TILE_W): i = x + y * TILE_W if self.enab[i]: self._wr(self.mem[i], addr, ln, self._treeval(x, y) & ((1 << ln) - 1)) continue if m == 'TREEintoMEM_L0': _, addr, ln = ins for y in range(TILE_H): for x in range(TILE_W): i = x + y * TILE_W if self.enab[i]: self._wr(self.mem[i], addr, ln, self._treeval(x, y) & ((1 << ln) - 1)) continue if m == 'TREEclmpintoMEM': _, addr, ln, slen, val = ins # colour write: the value is the flat colour (0..1 float) -> len-bit v = max(0, min((1 << ln) - 1, int(val * ((1 << ln) - 1)))) for i in range(TILE_W * TILE_H): if self.enab[i]: self._wr(self.mem[i], addr, ln, v) continue if m == 'SCAintoMEM': _, addr, ln, fval, raw = ins v = raw & ((1 << ln) - 1) for i in range(TILE_W * TILE_H): if self.enab[i]: self._wr(self.mem[i], addr, ln, v) continue if m == 'MEMgeSCA': _, addr, ln, sca = ins s = sca & ((1 << ln) - 1) for i in range(TILE_W * TILE_H): if self.enab[i] and not (self._rd(self.mem[i], addr, ln) >= s): self.enab[i] = 0 continue if m == 'MEMintoENAB': _, a = ins for i in range(TILE_W * TILE_H): self.enab[i] = (self.mem[i][a >> 3] >> (a & 7)) & 1 continue if m == 'MEMBARintoENAB': _, a = ins for i in range(TILE_W * TILE_H): self.enab[i] = 1 - ((self.mem[i][a >> 3] >> (a & 7)) & 1) continue if m == 'ENABintoMEM': _, a = ins for i in range(TILE_W * TILE_H): Tile._wr(self.mem[i], a, 1, self.enab[i]) continue if m == 'ENABxoreqMEM': _, a = ins for i in range(TILE_W * TILE_H): self.enab[i] ^= (self.mem[i][a >> 3] >> (a & 7)) & 1 continue def rgb(self): """Read out r24/g24/b24 (8 bits each at 117/125/133).""" 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__': # self-test: constructors reproduce captured/compiled words assert ix_memlttree_l3(97, 20) == 0x44ea2161, hex(ix_memlttree_l3(97, 20)) assert ix_scaintomem_s1(52, 5) == 0x3c90f734, hex(ix_scaintomem_s1(52, 5)) assert ix_memgesca_s1(5) == 0xbc916c00, hex(ix_memgesca_s1(5)) assert ix_treeclmpintomem_c1(117, 8) == 0xbdd75a75, hex(ix_treeclmpintomem_c1(117, 8)) print('constructor self-tests PASS') # smoke: one triangle on a tile tri = [('SETENABS',), ('EDGE', 1, 1.0, 0.0, -8.0), # x >= 8 ('EDGE', 2, -1.0, 0.0, 40.0), # x <= 40 ('EDGE', 3, 0.0, 1.0, -20.0), # y >= 20 ('TREEclmpintoMEM', 117, 8, 8, 0.9), ('TREEclmpintoMEM', 125, 8, 8, 0.4), ('TREEclmpintoMEM', 133, 8, 8, 0.1)] t = Tile() t.run(tri) img = t.rgb() lit = sum(1 for row in img for px in row if px != (0, 0, 0)) print('smoke: %d lit pixels (expect (40-8)x(128-20)=%d)' % (lit, 32 * 108))