Files
TeslaRel410/emulator/decode_fifodump.py
T
CydandClaude Opus 4.8 4b6d910f7b Phase 3a: decode captured VPX render stream to pixels (SMPTE bars)
- vpxlog.cpp: VPX_FIFODUMP=<path> records every FIFO burst ('VPXM' records)
- decode_fifodump.py: action census + payload dumps of a capture
- render_capture.py: reconstruct the DPL scene graph from a capture and
  software-render each draw_scene frame (camera, view, materials, geometry
  all taken from the wire)
- divrgb.conf + divrgb.fifodump: flyk divrgb.scn capture fixture
- divrgb-decoded.png / divrgb-frame0.png: first images ever produced from
  the Rel 4.10 VPX protocol without a real board -- the textbook SMPTE
  color-bar pattern, validating verts/conns/materials/camera in one shot
- PHASE3-PROGRESS.md: the established Rel 4.10 wire protocol (action map,
  node types, message layouts); RENDER-HARNESS.md updated

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:13:02 -05:00

148 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Decode a VPX_FIFODUMP capture (Phase 3a).
The dump is a sequence of records, each one FIFO burst (the bytes REP OUTSWed
to the FIFO port between two outputData tag writes):
'VPXM' u32-LE length, then <length> raw bytes
Wire model (VR_COMMS.C velocirender_transmit, FIFO path): each transmit is two
bursts -- burst 1 is the 4-byte action word, burst 2 the message data. So
records pair up into [action][data] messages. velocirender_packetize splits
payloads >508 bytes into multiple transmits with the same action.
Usage: decode_fifodump.py <dump> [--hex N] [--action A]
"""
import struct
import sys
from collections import Counter
# DPL3 VR_PROT.H enum -- the Rel4.10 build extends this (0x2D sync etc.);
# names beyond the DPL3 enum are provisional and marked with '?'.
ACTION_NAMES = {
0: "init", 1: "create", 2: "delete", 3: "flush",
4: "sect_pixel", 5: "sect_vector",
6: "dcs_nest", 7: "dcs_link", 8: "dcs_prune",
9: "draw_scene", 10: "draw_scene_complete",
11: "list_add", 12: "list_remove", 13: "morph",
14: "version", 15: "statistics", 16: "readpixels",
17: "hspcode", 18: "860code", 19: "860data", 20: "860bss", 21: "860args",
22: "set_geom_verts", 23: "set_texmap_texels",
0x2D: "sync(rel410)",
}
def read_records(path):
recs = []
with open(path, "rb") as f:
while True:
hdr = f.read(8)
if len(hdr) < 8:
break
magic, ln = hdr[:4], struct.unpack("<I", hdr[4:])[0]
if magic != b"VPXM":
raise SystemExit(f"bad magic at record {len(recs)}: {magic!r}")
recs.append(f.read(ln))
return recs
def pair_messages(recs):
"""Split burst records into (action, data) messages.
DPL3's transmit sends two bursts (action, then data); the Rel4.10 build
observed in captures pumps [action:4][data] as ONE burst per tag write.
Handle both: a 4-byte record whose next record isn't 4 bytes pairs with
it; otherwise the record's first word is the action."""
msgs = []
i = 0
while i < len(recs):
r = recs[i]
if len(r) == 4 and i + 1 < len(recs) and len(recs[i + 1]) > 4:
msgs.append((struct.unpack("<I", r)[0], recs[i + 1]))
i += 2
continue
if len(r) >= 4:
msgs.append((struct.unpack("<I", r[:4])[0], r[4:]))
else:
msgs.append((None, r))
i += 1
return msgs
def aname(a):
if a is None:
return "<no-action>"
n = ACTION_NAMES.get(a)
return f"{a}:{n}" if n else f"{a}(0x{a:X})?"
def hexdump(b, limit=64):
out = []
for i in range(0, min(len(b), limit), 16):
row = b[i:i + 16]
hx = " ".join(f"{c:02x}" for c in row)
tx = "".join(chr(c) if 32 <= c < 127 else "." for c in row)
out.append(f" {i:04x} {hx:<48} {tx}")
if len(b) > limit:
out.append(f" ... {len(b)} bytes total")
return "\n".join(out)
def floats(b, n=8):
k = min(n, len(b) // 4)
return " ".join(f"{v:.4g}" for v in struct.unpack(f"<{k}f", b[:k * 4]))
def ints(b, n=8):
k = min(n, len(b) // 4)
return " ".join(f"{v:#x}" for v in struct.unpack(f"<{k}I", b[:k * 4]))
def main():
path = sys.argv[1]
hexn = 0
only = None
if "--hex" in sys.argv:
hexn = int(sys.argv[sys.argv.index("--hex") + 1])
if "--action" in sys.argv:
only = int(sys.argv[sys.argv.index("--action") + 1], 0)
recs = read_records(path)
msgs = pair_messages(recs)
print(f"{len(recs)} burst records -> {len(msgs)} messages\n")
counts = Counter()
sizes = Counter()
for a, d in msgs:
counts[a] += 1
sizes[a] += len(d)
print("action count total-bytes sizes")
persize = {}
for a, d in msgs:
persize.setdefault(a, Counter())[len(d)] += 1
for a in sorted(counts, key=lambda x: (x is None, x)):
sz = ",".join(f"{s}x{c}" if c > 1 else f"{s}"
for s, c in sorted(persize[a].items())[:6])
print(f"{aname(a):<26} {counts[a]:>5} {sizes[a]:>11} {sz}")
if hexn or only is not None:
print()
shown = 0
for i, (a, d) in enumerate(msgs):
if only is not None and a != only:
continue
print(f"[{i}] {aname(a)} len={len(d)}")
if d:
print(" ints: ", ints(d))
print(" floats:", floats(d))
print(hexdump(d, hexn if hexn else 64))
shown += 1
if hexn and shown >= 40 and only is None:
print("... (truncated)")
break
if __name__ == "__main__":
main()