"""igc_gpu.py -- M1: the EMC tile as an OpenGL compute shader. One GPU invocation per pixel; per-pixel state = the SAME 208-bit bit-addressed memory as the golden model (7 uints), enable bit, and a per-pixel carry latch. The parsed instruction stream (igc_exec.parse) is packed into SSBO records; TREE state is resolved at pack time (L0 records carry the last L3 plane), so the shader is a straight lock-step loop with a switch -- exactly the shape an RTL sequencer would take. Conformance gate: run() reproduces igc_exec.Tile outputs field-for-field (C2 synthetic triangle, C3 bars pipeline -- asserted in main). """ import os, sys, struct import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import igc_exec TILE_W, TILE_H = igc_exec.TILE_W, igc_exec.TILE_H # op ids for the shader switch OPS = {name: i for i, name in enumerate([ 'NOP', 'SETENABS', 'CLRENABS', 'TREEgeZERO_L3', 'TREEltZERO_L3', 'MEMltTREE_L3', 'TREEintoMEM', 'TREEclmpintoMEM', 'SCAintoMEM', 'CPY', 'MEMintoENAB', 'MEMBARintoENAB', 'ENABandeqMEM', 'ENABandeqMEMBAR', 'ENABxoreqMEM', 'ENABintoMEM', 'MEMoreqENAB', 'MEMandeqENAB', 'MEMgeSCA', 'SCMEMA', 'OP48X', 'SWEEP21', 'SWEEP25', 'SEED_TEXZ'])} SHADER = """ #version 430 layout(local_size_x = 64, local_size_y = 1) in; layout(std430, binding = 0) readonly buffer Ints { ivec4 iops[]; }; // op, addr, len, iarg layout(std430, binding = 1) readonly buffer Flts { vec4 fops[]; }; // A, B, C, farg layout(std430, binding = 2) writeonly buffer Outs { uint outbuf[]; }; // 8 words/pixel: mem[7], enable uniform int n_ops; uniform int tile_ox; uniform int tile_oy; uint mem[7]; bool enab; bool latch; uint rdbits(int bit0, int len) { uint v = 0u; for (int k = 0; k < len; k++) { int b = bit0 + k; v |= ((mem[b >> 5] >> uint(b & 31)) & 1u) << uint(k); } return v; } void wrbits(int bit0, int len, uint val) { for (int k = 0; k < len; k++) { int b = bit0 + k; if (((val >> uint(k)) & 1u) != 0u) mem[b >> 5] |= (1u << uint(b & 31)); else mem[b >> 5] &= ~(1u << uint(b & 31)); } } void main() { int x = int(gl_GlobalInvocationID.x); int y = int(gl_GlobalInvocationID.y); int gx = x + tile_ox; int gy = y + tile_oy; for (int i = 0; i < 7; i++) mem[i] = 0u; enab = true; latch = false; for (int i = 0; i < n_ops; i++) { int op = iops[i].x; int addr = iops[i].y; int len = iops[i].z; int ia = iops[i].w; float A = fops[i].x, B = fops[i].y, C = fops[i].z; float tv = A * float(gx) + B * float(gy) + C; if (op == 1) { enab = true; } else if (op == 2) { enab = false; } else if (op == 3) { if (enab && !(tv >= 0.0)) enab = false; } else if (op == 4) { if (enab && !(tv < 0.0)) enab = false; } else if (op == 5) { if (enab && !(int(rdbits(addr, len)) < int(tv))) enab = false; } else if (op == 6) { if (enab) wrbits(addr, len, uint(int(tv)) ); } else if (op == 7) { if (enab) { float v = clamp(fops[i].w, 0.0, 1.0); wrbits(addr, len, uint(v * float((1 << uint(len)) - 1))); } } else if (op == 8) { if (enab) wrbits(addr, len, uint(ia)); } else if (op == 9) { if (enab) wrbits(addr, len, rdbits(ia, len)); } else if (op == 10) { enab = rdbits(addr, 1) != 0u; } else if (op == 11) { enab = rdbits(addr, 1) == 0u; } else if (op == 12) { enab = enab && (rdbits(addr, 1) != 0u); } else if (op == 13) { enab = enab && (rdbits(addr, 1) == 0u); } else if (op == 14) { enab = enab != (rdbits(addr, 1) != 0u); } else if (op == 15) { wrbits(addr, 1, enab ? 1u : 0u); } else if (op == 16) { if (enab) wrbits(addr, 1, 1u); } else if (op == 17) { if (!enab) wrbits(addr, 1, 0u); } else if (op == 18) { if (enab && !(int(rdbits(addr, len)) >= ia)) enab = false; } else if (op == 19) { if (enab) wrbits(addr, len, uint(gx) & uint((1 << uint(len)) - 1)); } else if (op == 20) { if (enab) wrbits(addr, len, uint(gx) & uint((1 << uint(len)) - 1)); } else if (op == 21) { latch = rdbits(ia, 1) != 0u; } else if (op == 22) { if (enab && latch) { uint v = (rdbits(addr, 20) + uint(ia)) & 0xFFFFFu; wrbits(addr, 20, v); } } else if (op == 23) { wrbits(32, 20, uint(gx) & 0xFFFFFu; ); } } int pix = y * 64 + x; for (int i = 0; i < 7; i++) outbuf[pix * 8 + i] = mem[i]; outbuf[pix * 8 + 7] = enab ? 1u : 0u; } """ def pack(instrs): """Flatten parsed instructions to (ivec4, vec4) records; resolve TREE state into L0 records; expand sweeps' k from len2.""" iops, fops = [], [] tree = (0.0, 0.0, 0.0) def emit(op, addr=0, ln=0, ia=0, A=0.0, B=0.0, C=0.0, fa=0.0): iops.append((OPS[op] if isinstance(op, str) else op, addr, ln, ia)) fops.append((A, B, C, fa)) for ins in instrs: m = ins[0] if m in ('NOOP', 'FBITS', 'UNK', 'SCMEMA_IMM'): continue elif m == 'SETENABS': emit('SETENABS') elif m == 'CLRENABS': emit('CLRENABS') elif m in ('TREEgeZERO_L3', 'TREEltZERO_L3'): _, addr, ln, A, B, C = ins tree = (A, B, C) emit(m, addr, ln, 0, A, B, C) elif m == 'MEMltTREE_L3': _, addr, ln, A, B, C = ins tree = (A, B, C) emit('MEMltTREE_L3', addr, max(1, ln), 0, A, B, C) elif m == 'TREEintoMEM_L3': _, addr, ln, A, B, C = ins tree = (A, B, C) emit('TREEintoMEM', addr, max(1, ln), 0, A, B, C) elif m == 'TREEintoMEM_L0': _, addr, ln = ins emit('TREEintoMEM', addr, max(1, ln), 0, *tree) elif m == 'TREEclmpintoMEM': _, addr, ln, pword, vword = ins emit('TREEclmpintoMEM', addr, max(1, ln), 0, 0, 0, 0, struct.unpack(' 0 else 8))) elif m == 'OP48': _, addr, ln, operand = ins emit('OP48X', addr, max(1, min(20, ln if ln > 0 else 8))) elif m == 'SWEEP21': _, dst, ln, operand = ins if operand is not None: emit('SWEEP21', dst, 1, operand & 0xff) elif m == 'SWEEP25': _, dst, ln, operand = ins if operand is None: continue len2 = (operand >> 16) & 0x7f emit('SWEEP25', dst, 20, 1 << max(0, 20 - len2)) # unhandled exotics are skipped (same as the CPU model's stubs) return (np.array(iops, dtype=np.int32).reshape(-1, 4), np.array(fops, dtype=np.float32).reshape(-1, 4)) class GpuTile: def __init__(self): import moderngl self.ctx = moderngl.create_context(standalone=True, require=430) src = SHADER.replace('uint(gx) & 0xFFFFFu; );', 'uint(gx) & 0xFFFFFu);') self.prog = self.ctx.compute_shader(src) def run(self, instrs, ox=0, oy=0, pre_seed_texz_x=False): iops, fops = pack(instrs) if pre_seed_texz_x: iops = np.vstack([np.array([[OPS['SEED_TEXZ'], 0, 0, 0]], np.int32), iops]) fops = np.vstack([np.zeros((1, 4), np.float32), fops]) n = len(iops) b0 = self.ctx.buffer(iops.tobytes()) b1 = self.ctx.buffer(fops.tobytes()) out = self.ctx.buffer(reserve=TILE_W * TILE_H * 8 * 4) b0.bind_to_storage_buffer(0) b1.bind_to_storage_buffer(1) out.bind_to_storage_buffer(2) self.prog['n_ops'] = n self.prog['tile_ox'] = ox self.prog['tile_oy'] = oy self.prog.run(group_x=1, group_y=TILE_H) data = np.frombuffer(out.read(), dtype=np.uint32).reshape(TILE_H * TILE_W, 8) b0.release(); b1.release(); out.release() return data # [pix, 0..6]=mem words, [pix,7]=enable @staticmethod def rdbits(memrow, bit0, bits): v = 0 for k in range(bits): b = bit0 + k v |= ((int(memrow[b >> 5]) >> (b & 31)) & 1) << k return v if __name__ == '__main__': import pickle HERE = os.path.dirname(os.path.abspath(__file__)) g = GpuTile() print("GPU:", g.ctx.info['GL_RENDERER']) # C2: synthetic triangle tri = [('SETENABS',), ('TREEgeZERO_L3', 0, 2, 1.0, 0.0, -8.0), ('TREEgeZERO_L3', 0, 2, -1.0, 0.0, 40.0), ('TREEgeZERO_L3', 0, 2, 0.0, 1.0, -20.0)] data = g.run(tri) lit = int(data[:, 7].sum()) assert lit == 3564, lit print("C2 GPU synthetic triangle: PASS (%d enabled)" % lit) # C3: bars pipeline on the GPU d = pickle.load(open(os.path.join(HERE, 'conformance', 'sends_cap7.pkl'), 'rb')) pls = {(a, sz): list(w) for h, (a, sz, w) in d['payloads'].items()} order = [(0x8015000, 4), (0x8015020, 69), (0x8015260, 33), (0x8015380, 41)] instrs = [] for key in order: ins, _ = igc_exec.parse(pls[key]) instrs += ins data = g.run(instrs, ox=256, oy=0, pre_seed_texz_x=True) for x in (0, 16, 32, 48): u = GpuTile.rdbits(data[x], 57, 20) assert u == 256 + x + 2, (x, u) print("C3 GPU bars pipeline: PASS (texu == x+2 across the tile)") print("M1 CONFORMANT ON GPU")