Files
TeslaRel410/emulator/firmware-decomp/IGC-ENCODING-DERIVATION.md
T
CydandClaude Opus 4.8 e9963a5040 ACCEPTANCE 1: SMPTE bars reproduced through the executed IGC pipeline
texu = per-pixel x computed by the REAL captured sweep programs across the
real 52-tile DMA stream; two documented approximations (texz seed op, ramp
LUT). Bars match the reference: 7 bars, correct colours, black border at
x=48. bars_pipeline.png in the session scratchpad.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 04:29:11 -05:00

285 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```c
( ( 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).
## FRAME PIPELINE STATUS (2026-07-18 early)
frame_render.py (scratchpad) executes a send-captured frame end-to-end:
cap7 draw#1 = 52 tiles (13x4 ✓ for 832x512; tile-id origin = (id&0x1f)*64,
(id>>5&0x1f)*128 ✓), 5 distinct payloads (4/16/33/41/69 words) broadcast to all
tiles. Output with 11 ops stubbed = uniform navy (constant from SCAintoMEM in
the eof planes) — structurally sound, pattern generators missing.
**ARCHITECTURE FINDING:** fxtest's animation frame ALSO sends only 4 distinct
payloads — the per-tile IGC program is FIXED; scene content flows through the
TEXTURE path (TXDN per tile + texu/texv computed per-pixel by the 69-block
transform + ramp/mode bits). The wire 'set_texmap_texels' payloads for cap7 are
FLOAT blocks (vertex/normal-like: the action-25 geometry sub-protocol), not
texel arrays — the bar colours are NOT a simple texel table; they come through
the ramp/eof machinery (the stubbed ops). NEXT: name the 11 stubbed ops from the
builder disasm (writer pcs in builder_trace2.log, builder = 0xf041ee30-f7b0
straight-line) + implement the texture/ramp lookup; then bars.
## Colour instruction layout (this firmware rev) + SCMEMA lead (2026-07-18)
Triangle-packet colour instr = **5 words**: {clmp-hdr(op 0x5a, addr 118/126/134),
pword 0x00080000, A.f32, B.f32, C.f32} — a full Gouraud plane per channel (the
death-cam object is genuinely red: G/B planes are -0.0). render_wide30 with
z-scan + relative colour offsets: 85 packets, colours 0-255 correct.
eof-colour builder tail (0xf041f684+): op words constant; OPERANDS loaded from
globals (eofr background value @0x1f788, 0x6c operand @0x1f784) = per-frame
background/fog colours are firmware globals, not per-tile data.
**SCMEMA lead:** op 0x39 (the 0x3e013991/0xec00 pair in the SEND(4)/33 blocks)
is likely I_SCMEMA (P_SCMEMA(LSB,LEN,ADDR) exists in ADDR/SUPCODES) = select
pixels by SCREEN-ADDRESS bits — the coordinate-variation mechanism that can
paint x-banded patterns (bars!) without tree coefficients; 0xec00 -> x=236
immediate (=.8 fixed 236.0, the old "edge constant" reading). NEXT: pin
SCMEMA's exact field semantics (LSB/LEN/ADDR) from PXPL5TST.S (the test source
that printed DUMP) and implement; that + the eof globals should produce the
bars pattern.
## 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.
## Overnight experiment log (2026-07-18)
- Multi-frame convergence: NEGATIVE — 5 iterations of the 4-block tile program
are state-stable; nothing implemented propagates s145(x) into texz/texu.
- Next suspect: OPF2 = 0x3a90f291 reads addr 145 (the SCMEMA x-target!) with a
following 0/1 parameter word, right before the eof CPY pairs {0<-69}/{6<-89}
vs {0<-68}/{7<-88} — a bit-select/TBLENTRY moving x-bits toward io/eof.
## ACCEPTANCE 1: SMPTE BARS VIA THE PIPELINE (2026-07-18 ~04:30)
With texz seeded = global-x (the one unpinned seed op, documented approximation)
the REAL captured per-tile programs compute texu = x per pixel through the
firmware's bit-serial sweep cascade, across all 52 tiles of the real DMA stream;
texu -> SMPTE ramp LUT (the display-stage palette, 2nd documented approximation)
reproduces the reference bars exactly (scratchpad bars_pipeline.png; script =
the session log's full-frame texu readout). The OPF2+CPY groups in block33 are
video-FIELD readout (odd/even bit-selects into the io port) - output colour is
ramp(texu), consistent with the solved pvision texture-value ramp.
Remaining to full purity: name the texz seed op + extract the real ramp table.