Files
TeslaRel410/emulator/firmware-decomp/IGC-ENCODING-DERIVATION.md
T
CydandClaude Opus 4.8 ecdb4b6122 IGC decode: op table named via PXPL5SUP/ADDR (igc_opco.h fragment) + DUMP (live op-table print)
ADDR carries exact I_*/P_* macro bases; DUMP is a pxpl5tst log printing the
named op values plus two complete live triangle packets. Retroactive
unification: tri_recover's 'scale 0.00178' was TREEltZERO_L3 (0x3ae94200)
read as a float -- the DRAM stride-0x10 groups were always 4-word edge
instructions in the DPL3 direct-float format igc_exec.py already executes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:40:00 -05:00

13 KiB
Raw Blame History

IGC instruction-word encoding — derivation from EOF.C ↔ EOF.S (2026-07-17)

Goal: the Tier-1 decoder (coefficient stream → pixels). igc_opco.h (the macro definitions) is NOT in the dump — but sda4/DPL3/VRENDER/PXPL5SUP/ has both EOF.C (ordered IGC_* macro calls) and EOF.S (its PGC-compiled output with every emitted literal visible). Aligning them derives each macro's word template. This supersedes blind reversing; MICROCODE-DECODE-NOTES.md has the value layer (x2 doubling chains, 4-word SENDE stride, fixed-point constants) already decoded.

Derived so far (from _init_screenbin, EOF.S lines 330-510 ↔ EOF.C 269-360)

macro emitted words encoding
IGC_SETENABS 1 0x00000100
IGC_MEMintoENAB(ix) 1 0x0300 | ix
IGC_ENABxoreqMEM(ix) 1 0x10A00 | ix (bit16 set)
IGC_ENABintoMEM(ix) 1 0xB500 | ix
IGC_TREEintoMEM_L3(dst,len,A,B,C) 4 {hdr, A.f32, B.f32, C.f32}

TREE header samples: (dst=57,len=7)0x3d2a4339; (dst=64,len=15)0x412a4340.

  • bits 0-7 = dst bit-address (0x39=57 ✓, 0x40=64 ✓)
  • bits 8-15 = op code 0x43 (cf. 0x3a in the captured SENDE sweep = the MEMpluseqMEM-family op; 0x03/0xB5/0x0A+bit16 above — op byte is bits 8-15)
  • bits 16-23 = 0x2a in both (probably format/variant field for _L3)
  • bits 24-31 = 0x3d vs 0x41 for len 7 vs 15 — length-derived, exact formula needs more samples (Δ=4 for Δlen=8 → maybe (len>>1)+base? PIN WITH MINER).

Also from the same region: the coeff stream is written via st.l/fst.l through the rolling pointer; float args go through .C00741-style data literals (= the float constants, e.g. -1.0f, 1.0f) — EOF.S shows them as fld.l l%.C00741; read the .data section at the end of EOF.S for their values.

The captured-stream correspondence (already verified earlier)

  • SENDE z/colour sweep instruction (4 words): {inc.f32, 00 LL 3a AA, place-value.f32, 000000NN} → op 0x3a at bits 8-15 of word1, LL=countdown at bits 16-23, AA=src addr bits 0-7. This is consistent with the header field layout above (op byte = bits 8-15).
  • 0x00000100 recurs as "header" in captured payloads = SETENABS (or the 1-word op family with op=0x01 at bits 8-15).

Key sources

  • sda4/DPL3/VRENDER/PXPL5SUP/EOF.C — macro call sequences (init_screenbin @224, linterp @396 = MEMpluseqMEM samples, multuu_unc @451, send_em @499, perspective_divides @592 has TREEgeZERO/ltZERO/TREEintoMEM_C1/SCAintoMEM_S1/ MEMintoENAB/CLEAR samples, texture path further down).
  • sda4/DPL3/VRENDER/PXPL5SUP/EOF.S — compiled, all literals visible (_init_screenbin @330, _send_em @1442, _configEMCs @221/@58).
  • DIVPXMAP.H — dvpx_* bit map: io=0(32b), texz=32(20b), opacity=52(5b), texu=57(20b), texv=77(20b), zbuf=97(20b), r24=117 g24=125 b24=133, scalar=141(13b: texsize@141/2,texid@143/6,rampsel@149/2,texmode@151/3), enblpush=154, eof block @160+ (fog=160/8, subu=168/5, subv=173/5, ramp=178/2, mode=180/3...).
  • GOODEQNS.C — edgeize/planarize/binitize math + binchunk DMA layout (BIN_FULL=63*2, {addr, count|opcode} 64-bit pairs, GOTO chaining).
  • NUREGMAP.SS — dpl_REMOTE_VERTEX (80B: pos@48, normcol@64, tex@32) and dpl_REMOTE_CONNECTION (next@0, n_verts@4, indices[6]@8, planeEqn@32, rgb@48).
  • FOOTER.SS — scales: .Czscale=0x497fffff≈2^20, .Ctexscale=0x477fffff≈2^16, Cturn_z_to_tex(=5.1118e5, /4, /8 variants).
  • IGCOPS.C — op vocabulary + pixel-memory semantics (read/write_pixmem_word, eval_ltree=int(Ax+By+C)).
  • PXPL5001/2.DOC (WinWord2) — design notes: flat-shaded textured quad packet = edge×4, plane Z/S/T/MIP, const colour/type (~25 regs); texturing math.

Miner results (eofs_mine.py, round 1 — scratchpad, uses EOF.S // lineno: markers)

macro words encoding (op byte = bits 8-15 unless noted)
IGC_NOOP 1 0x00000000
IGC_FBITS(15) 2 {0x80000000, 0x48000000} (n=15 packing TBD — need a 2nd sample)
IGC_SETENABS 1 0x00000100
IGC_MEMintoENAB(x) 1 0x0300 | x (confirmed symbolically: rN&0xff|0x0300)
IGC_MEMBARintoENAB(x) 1 0x4800 | x (NEW — linterp line 426)
IGC_ENABxoreqMEM(x) 1 0x10A00 | x
IGC_ENABintoMEM(x) 1 0xB500 | x
IGC_TREEintoMEM_L3(d,l,A,B,C) 4 {hdr, A.f32, B.f32, C.f32}; hdr bits0-7=d, bits8-15=0x43, bits16-23=0x2a, bits24-31=0x39+ceil(l/2) (fits l=5:0x3c, 7:0x3d, 15:0x41)
IGC_CPY(d,s,l) 2 {0x80|((0x39+ceil(l/2))<<24) | 0x4c00 | d, s} (sample d=0x34,s=0x40,l=5 → 0xbc004c34, 0x40)
IGC_MEMgeSCA_S1(d,l,sca) 2 sample → {0xbc916c00, 0x34} (op 0x6c; hdr low byte NOT dst here — per-op layouts differ)
IGC_SCAintoMEM_S1(d,l,sca) 2 sample → {0x3c90f734, 0x1d} (dst 0x34 bits0-7, operand word = scalar 29)
DMA SEND(n) 0x10000000 | n, chunked at 127 longwords (send_em)

Byte 24-31 = 0x39+ceil(len/2) looks like the bit-serial cycle count (2 bits/clock)

  • 0x80 flag on the enable-conditioned (S1?) forms. Bits 16-23 vary per op (0x2a TREE / 0x00 CPY / 0x91 geSCA / 0x90 SCAinto) — likely aux operand/format. Word-constructor macros also exist (Ix_TBLENTRY_S1, P_TBLENTRY — EOF.C:1104) = more single-word samples in EOF.S.

THE ENCODING FORMULA (cracked 2026-07-17 — PXPL5OPT.S:1649, expanded macro in the clear)

_zbuffer_fn (PXPL5SUP/PXPL5OPT.S line 1649) contains the fully-expanded constructor:

( ( 0x422100 | ((addr & 0xff) << 0) | (((len+2)+115 & 0xff) << 23) ) |(2<<18)|(2<<20) )

Universal IGC instruction-word template:

  • bits 0-7 = operand bit-plane address
  • bits 8-15 = opcode (0x21=MEMltTREE, 0x43=TREEintoMEM, 0x03=MEMintoENAB, 0x48=MEMBARintoENAB, 0xB5=ENABintoMEM, 0x4c=CPY, 0x6c=MEMgeSCA, 0x01=SETENABS...)
  • bits 16-22 = aux/format field (0x2a for TREE_L3, 0x42 for MEMltTREE(+flag bits 18-21 e.g. (2<<18)|(2<<20)), 0x00 CPY, ...)
  • bits 23-30 = (len + 115) [zbuffer uses len+2+115] — VERIFIED: TREE l=7→0x3D..., l=15→0x41..., CPY l=5→0x3C...
  • bit 31 = flag on the S1/enable-conditioned forms (CPY sample had it)

Edge headers are tiny constants: 0x601, 0x602, 0x603 (edgize_tri_fn: edge 1/2/3, each followed by A,B,C floats — 4-word instructions). Suffix system: _L3 = +3 coeff floats; _L0 = 0 args (reuses the live tree state); _C1 = +1 const; _S1 = +1 scalar. The tree is loaded by one instruction and REUSED by the next (z-compare loads the z plane; z-write is a bare 1-word TREEintoMEM_L0).

The triangle packet (tri_zb_f, PXPL5OK.SS:1063): 3× edge{hdr,A,B,C} → z-test {Ix_MEMltTREE_L3(texz? zbuf,20), zA,zB,zC} → z-write {Ix_TREEintoMEM_L0} → 3× colour {Ix_TREEclmpintoMEM_C1(r/g/b24,8), P_TREEclmpintoMEM(...), col.f32} → {Ix_SCAintoMEM_S1(scalar,13), material.f32} → 0-pad. C-callable wrappers (PXPL5TRI.S) call edgize_tri_fn + zbuffer_fn + planarize_fn(0x666, vert-field-idx).

Harvest sources (expanded, numeric): PXPL5SUP/PXPL5OPT.S (edgize@1492, zbuffer@1646, planarize@1743, preplanarize@1137) + PXPL5SUP/PXPL5TRI.S + parent PXPL5OPT.S (rev differs, both useful) + EOF.S. Grep pattern: 0x[0-9a-f]{4,6} \| parenthesized expressions — eval them with the known (op,addr,len) from the .SS source lines to fill the whole op table.

The bit-serial SWEEP family (firmware's builder) — 2-word format DERIVED

The firmware's own packet builder (and EOF.C's linterp/multuu_unc) emit 2-word memory-op instructions. From _linterp compiled (EOF.S 663-860):

op-word  = BASE | dstaddr | ((len1+116)&0x7f)<<23      (BASE e.g. 0x80006c00 = MEMpluseqMEM-family K)
operand  = srcaddr | dstaddr<<8 | (len2&0x7f)<<16

VERIFIED against capture: {0xba01253a, 0x00143a21} → op 0x25 dst=58, operand src=0x21(33) dst=0x3a(58)✓ len=0x14(20). The 0x48-family builds as (len+116)<<23 | 0x4800 | addr (linterp line 699-707, emitted right after SETENABS). 104192 = 0x19700 = the 0x97-family base (bit16 set) — captured 0x0001979d = 0x19700|0x9d ✓; bare 0x0001008f = bit16 operand words. CPY 2-word pairs in the eof blocks: {0xbb004cb3, 0x0001008f} = CPY(dst=179, src=143=texid) etc. Known family bases so far: 0x25??, 0x21 (with decrementing len = the multuu cascade), 0x6c (K=0x80006c00), 0x4c (CPY), 0x48, 0x97(+bit16), 0x1f, 0xfa, 0x39, 0xec (last four TBD — likely SHIFTL/TBLENTRY/OVFIX/SPLAT-family from IGCOPS.C).

The 69-word SENDE block structure: SETENABS + 3-word lead-in + 16 pairs of {0x25-op, operand}/{0x21-op, operand} with len1 decrementing 9..0 and src walking 0x21..0x31 = a bit-serial multiply cascade (multuu_unc's exact shape: IGC_MEMpluseqMEM(res+j, Ub, Rlen-j, Ublen)).

Builder-trace tooling: scratchpad/builder_trace.py logs (pc, addr, val) for all firmware writes into 0x08015000-0x08015800 during cap7 build → writer pc histogram names the firmware's builder fns for disassembly.

FIRMWARE SWEEP EMITTER READ (builder trace + disasm @0xf041db90-dc50) — the

"x2 doubling chain" was an ARTIFACT, not data

builder_trace.py pinned the SENDE(69) emitter (writes at cmd 737 = draw#1): loop constants r9 = 0x80012500|dst|(116<<23) (= 0xba01253a for dst=0x3a), r10 = 0x80012100|dst, r8 = dst<<8. Per bit k (k=0..19, 4 words):

w0 = 0x80012100 | dst | (((136-k)&0x7f)<<23)   ; op 0x21 aux 1, len-field decrementing
w1 = src+k                                      ; bare operand word
w2 = 0x80012500 | dst | (116<<23)               ; op 0x25 aux 1, len=0 (constant)
w3 = (src+k) | dst<<8 | ((20-k)<<16)            ; operand: src | dst<<8 | len<<16

The len-field (136-k) walks straight through the IEEE exponent bits, so w0 "decodes" as a float that halves each step — the earlier doubling-chain reading (MICROCODE-DECODE-NOTES.md) was a coincidence artifact. These words are INSTRUCTIONS, not coefficients. The SENDE(69) is a fixed bit-serial transform program: src bits 33..52 (texz) → dst bit 58 (texu region) — per-frame plumbing, not per-triangle data. The real primitive coefficients live in the SEND(33)/ SEND(41) blocks (builders = the 0xf04126xx cluster, 336-344 writes each) and the SEND(4) edge block (0xf041fc64/fc84/fc9c; word1 0x3e013991 computed = a real float, word2 0xec00 = fixed-point .8 coordinate 236.0).

Lead-in confirmed: {0x100 SETENABS, 0x3a804834 = op 0x48 addr 52 (opacity) len 1 = the 50% stipple enable}.

NEXT for the decoder: (1) disassemble the 0xf04126xx builders (the 33/41-block emitters) the same way — builder_trace.py already exists, just widen the range / dump more words; (2) implement the sweep ops (0x21/0x25 pair = bit-serial add/carry per IGCOPS.C MEMpluseqMEM semantics) + run the whole per-tile program in igc_exec; (3) bars.

GOLDMINE: PXPL5SUP/ADDR + PXPL5SUP/DUMP (found 2026-07-17 late)

ADDR = a literal fragment of the missing igc_opco.h — exact bases: I_MCWRITE=0x20000|(a&0x1ff)<<8, I_MCREAD=(a&0x1ff)<<8, MEMintoENAB=0x300, ENABandeqMEM=0x500, ENABandeqMEMBAR=0x800, SEDGE=0x400a00, CRYintoMEM=0xb300, ENABintoMEM=0xb500, MEMoreqENAB=0xb700, MEMandeqENAB=0xb900, OVFIX=0x8000e600, ENABxoreqMEM=0x10a00, MEMEDGE=0x413c00, SEDGEBAR=0x413f00 (+ P_* second-word forms: bare (addr&0xff)).

DUMP = pxpl5tst run log that PRINTS the op table with names: SETENABS=0x100, CLRENABS=0x200, TREEltZERO_L3=0x3ae94200, TREEgeZERO_L3=0x3ae80d00, MEMltTREE_L3=0x44ea2120(texz)/0x...61(zbuf), TREEintoMEM_L0(zbuf)=0x438a4320, TREEintoMEM_L3: diff=0x3daa4352 spec=0x3daa435a texu=0x41aa4362 texv=0x41aa4372 texz=0x41aa4382 — plus TWO complete live triangle packets word-by-word (edges {0x3ae94200,A,B,C}×3 with real coefficients, z-test 4W, z-write 1W, diff/spec/texu/texv/texz planes 4W each, SCAintoMEM 0x0910f734+1.0). SUPCODES = the emission-sequence listing.

RETROACTIVE UNIFICATION: the old tri_recover stride-0x10 groups' "scale ≈0.00178" IS 0x3ae94200 read as a float — those DRAM groups were always {TREEltZERO_L3, A, B, C} edge instructions. The direct-float DPL3 packet format (already in igc_exec.py) is what per-triangle payloads use; the fixed 33/41/69-word blocks are the eof/background program (bars). Remaining unnamed: the eof-block ops 0xfa/0x7c/0x1f/0xf2/0x97/0x05/0x49/0x30/0x75/0xdb (builder = one linear fn 0xf041ee30-0xf041f7b0, per-word pcs logged in builder_trace2.log — disasm each site to name them).

NEXT (the miner)

Write eofs_mine.py: parse EOF.S per function; track st.l/fst.l stores through the coeff pointer; recover, in order, each emitted word (int literals from or/orh pairs; floats from the fld of .data literals — parse EOF.S's .data tail for values). Align against EOF.C's macro sequence per function (start with init_screenbin, perspective_divides, linterp — together they cover: FBITS, NOOP, TREEintoMEM_L3/_C1, TREEgeZERO_L3, TREEltZERO_L3, MEMintoENAB, ENABxoreqMEM, ENABintoMEM, SETENABS, CPY, MEMgeSCA_S1, SCAintoMEM_S1, CLEAR, MEMpluseqMEM, MEMintoENAB, SHIFTL-family, send_em's DMA emit). Output: the op table (op byte, word count, arg-field packing). Then:

  1. parse captured payloads (fxtest_frame.pkl / cap7 bench) into instruction sequences with the table — every word must decode (acceptance: 0 unknown ops);
  2. execute per-tile via igc_array.py (pixel-memory model exists there);
  3. acceptance 1 = SMPTE bars from the cap7 bench stream; acceptance 2 = the fxtest asset frame.