Files
StarPilot/tinygrad_repo/tinygrad/renderer/amd/sqtt.py
T
firestar5683 d97100bd14 tiny my BUTT
2026-06-23 12:01:44 -05:00

746 lines
28 KiB
Python

"""SQTT (SQ Thread Trace) packet encoder and decoder for AMD GPUs.
This module provides encoding and decoding of raw SQTT byte streams.
The format is nibble-based with variable-width packets determined by a state machine.
Uses BitField infrastructure from dsl.py, similar to GPU instruction encoding.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Iterator
from enum import Enum
from tinygrad.helpers import getenv, colored
from tinygrad.renderer.amd.dsl import BitField, FixedBitField, Inst, bits
from tinygrad.runtime.autogen.amd.rdna3.ins import s_endpgm # same encoding as RDNA4
# ═══════════════════════════════════════════════════════════════════════════════
# FIELD ENUMS
# ═══════════════════════════════════════════════════════════════════════════════
class MemSrc(Enum):
LDS = 0
LDS_ALT = 1
VMEM = 2
VMEM_ALT = 3
class AluSrc(Enum):
NONE = 0
SALU = 1
VALU = 2
VALU_SALU = 3
# construct other SIMD instruction operation types, name becomes OTHER_{category}_{cycles}
def add_other_simd(cls:type[Enum], ranges:list[tuple[str, int, int, int]]) -> None:
for category, start, end, base_cycle in ranges:
for value in range(start, end + 1):
cls._value2member_map_[value] = obj = object.__new__(cls)
obj._value_ = value
obj._name_ = f"OTHER_{category}_{value - start + base_cycle}"
class InstOp(Enum):
"""SQTT instruction operation types for RDNA3 (gfx1100).
Memory ops appear in two ranges depending on which SIMD executes them:
- 0x1x-0x2x range: ops on traced SIMD
- 0x5x range: ops on other SIMD (OTHER_ prefix)
GLOBAL memory ops encoding depends on addressing mode AND size:
- Loads: 0x21 (saddr=SGPR) or 0x22 (saddr=NULL), all sizes same
- Stores: base + size_offset, where VADDR is shifted +1 from SADDR
SADDR: 0x24(32) 0x25(64) 0x26(96) 0x27(128)
VADDR: 0x25(32) 0x26(64) 0x27(96) 0x28(128)
OTHER_ range follows same pattern but values overlap differently.
"""
SALU = 0x0
SMEM_RD = 0x1
JUMP = 0x3 # branch taken
JUMP_NO = 0x4 # branch not taken
CALL = 0x5 # s_call_b64
MESSAGE = 0x9
VALUT_4 = 0xb # transcendental: exp, log, rcp, sqrt, sin, cos
VALUB_2 = 0xd # 64-bit shifts: lshl, lshr, ashr
VALUB_4 = 0xe # 64-bit multiply-add
VALUB_16 = 0xf # 64-bit: add, mul, fma, rcp, sqrt, rounding, frexp, div helpers
VINTERP = 0x12 # interpolation: v_interp_p10_f32, v_interp_p2_f32
BARRIER = 0x13
# FLAT memory ops on traced SIMD (0x1x range)
FLAT_RD_2 = 0x1c
FLAT_WR_3 = 0x1d
FLAT_WR_4 = 0x1e
FLAT_WR_5 = 0x1f
FLAT_WR_6 = 0x20
# GLOBAL memory ops on traced SIMD (0x2x range)
SGMEM_RD_1 = 0x21 # saddr=SGPR, all sizes
SGMEM_RD_2 = 0x22 # saddr=NULL, all sizes
SGMEM_WR_2 = 0x24 # saddr=SGPR, 32-bit
SGMEM_WR_3 = 0x25 # saddr=SGPR 64 or saddr=NULL 32
SGMEM_WR_4 = 0x26 # saddr=SGPR 96 or saddr=NULL 64
SGMEM_WR_5 = 0x27 # saddr=SGPR 128 or saddr=NULL 96
SGMEM_WR_6 = 0x28 # saddr=NULL, 128-bit
# LDS ops on traced SIMD
LDS_RD = 0x29
LDS_WR_1 = 0x2a # ds_append, ds_consume, ds_store_addtid_b32
LDS_WR_2 = 0x2b
LDS_WR_3 = 0x2c
LDS_WR_4 = 0x2d
LDS_WR_5 = 0x2e
# EXEC-modifying ops (0x7x range)
SALU_WR_EXEC = 0x72 # s_*_saveexec_b32/b64
VALU1_WR_EXEC = 0x73 # v_cmpx_*
# Memory ops on other SIMD (0x5x range)
add_other_simd(InstOp, [("LDS", 0x50, 0x54, 1), ("FLAT", 0x55, 0x59, 2), ("VMEM", 0x5a, 0x66, 1)])
class InstOpRDNA4(Enum):
"""SQTT instruction operation types for RDNA4 (gfx1200). Different encoding from RDNA3."""
SALU = 0x0
SMEM = 0x1
SMEM_WR = 0x2
JUMP = 0x3
JUMP_NO = 0x4
CALL = 0x5
SALU_NO_EXEC = 0x7
MESSAGE = 0x9
VALU_1 = 0xa
VALUT_4 = 0xb
VALUB_1 = 0xc
VALUB_2 = 0xd
VALUB_4 = 0xe
VALUB_16 = 0xf
VINTERP = 0x12
BARRIER_WAIT = 0x13
FLAT_RD_2 = 0x1c
FLAT_WR_3 = 0x1d
FLAT_WR_4 = 0x1e
FLAT_WR_5 = 0x1f
FLAT_WR_6 = 0x20
VMEM_RD_1 = 0x21
VMEM_RD_2 = 0x22
VMEM_WR_1 = 0x23
VMEM_WR_2 = 0x24
VMEM_WR_3 = 0x25
VMEM_WR_4 = 0x26
VMEM_WR_5 = 0x27
VMEM_WR_6 = 0x28
LDS_RD = 0x29
LDS_WR_1 = 0x2a
LDS_WR_2 = 0x2b
LDS_WR_3 = 0x2c
LDS_WR_4 = 0x2d
LDS_WR_5 = 0x2e
BUF_RD_1 = 0x2f
BUF_RD_2 = 0x30
BUF_WR_1 = 0x31
BUF_WR_2 = 0x32
BUF_WR_3 = 0x33
BUF_WR_4 = 0x34
BUF_WR_5 = 0x35
BUF_WR_6 = 0x36
LDS_DIR_LOAD = 0x6e
LDS_PARAM_LOAD = 0x6f
SALU_WR_EXEC = 0x72
VALU1_WR_EXEC = 0x73
VALU_WR_EXEC_2 = 0x74
OTHER_LDS_6 = 0x77
OTHER_LDS_10 = 0x78
BARRIER_SIGNAL = 0x7a
DYN_VGPR = 0x87
BARRIER_JOIN = 0x8a
WMMA_8 = 0x8c
WMMA_16 = 0x8d
WMMA_32 = 0x8e
WMMA_64 = 0x8f
VALU_DPFP = 0x92
SALU_FLOAT_3 = 0x98
VALU_SCL_TRANS = 0x99
SALU_2 = 0x9b
SALU_5 = 0x9c
add_other_simd(InstOpRDNA4, [("LDS", 0x50, 0x54, 1), ("FLAT", 0x55, 0x59, 2), ("VMEM", 0xbc, 0xdd, 1)])
class InstOpCDNA(Enum):
SMEM_RD = 0
SALU_32 = 1
VMEM_RD = 2
VMEM_WR = 3
FLAT_WR = 4
VALU_32 = 5
LDS = 6
PC = 7
JUMP = 12
NEXT = 13
FLAT_RD = 14
OTHER_MSG = 15
SMEM_WR = 16
SALU_64 = 17
VALU_64 = 18
VALU_MAI = 28
# ═══════════════════════════════════════════════════════════════════════════════
# PACKET TYPE BASE CLASS
# ═══════════════════════════════════════════════════════════════════════════════
class PacketType:
"""Base class for SQTT packet types."""
encoding: FixedBitField
_raw: int
_time: int
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls._fields = {k: v for k, v in cls.__dict__.items() if isinstance(v, BitField)} # type: ignore[attr-defined]
cls._size_nibbles = ((max((f.hi for f in cls._fields.values()), default=0) + 4) // 4) # type: ignore[attr-defined]
@classmethod
def from_raw(cls, raw: int, time: int = 0):
inst = object.__new__(cls)
inst._raw, inst._time = raw, time
return inst
def __repr__(self) -> str:
fields_str = ", ".join(f"{k}={getattr(self, k)}" for k in self._fields if not k.startswith('_') and k != 'encoding') # type: ignore[attr-defined]
return f"{self.__class__.__name__}({fields_str})"
# ═══════════════════════════════════════════════════════════════════════════════
# TS PACKET TYPE DEFINITIONS
# ═══════════════════════════════════════════════════════════════════════════════
class TS_DELTA_S8_W3(PacketType):
encoding = bits[6:0] == 0b0100001
delta = bits[10:8]
_padding = bits[71:11]
class TS_DELTA_S5_W3(PacketType):
encoding = bits[4:0] == 0b00110
delta = bits[7:5]
_padding = bits[51:8]
class TS_DELTA_S5_W3_RDNA4(PacketType): # Layout 4: 52->56 bits
encoding = bits[4:0] == 0b00110
delta = bits[9:7]
_padding = bits[55:10]
class TS_DELTA_SHORT(PacketType):
encoding = bits[3:0] == 0b1000
delta = bits[7:4]
class TS_DELTA_OR_MARK(PacketType):
encoding = bits[6:0] == 0b0000001
delta = bits[47:12]
pl = bits[8:8]
rt = bits[9:9]
@property
def is_marker(self) -> bool: return bool(self.rt and not self.pl)
class TS_DELTA_OR_MARK_RDNA4(TS_DELTA_OR_MARK):
delta = bits[63:12]
rt = bits[7:7]
pl = bits[8:8]
tl = bits[9:9]
class TS_DELTA_S5_W2(PacketType):
encoding = bits[4:0] == 0b11100
delta = bits[6:5]
_padding = bits[47:7]
class TS_DELTA_S5_W2_RDNA4(PacketType): # Layout 4: 48->40 bits
encoding = bits[4:0] == 0b11100
delta = bits[6:5]
_padding = bits[39:7]
# ═══════════════════════════════════════════════════════════════════════════════
# PACKET TYPE DEFINITIONS
# ═══════════════════════════════════════════════════════════════════════════════
class VALUINST(PacketType): # exclude: 1 << 2
encoding = bits[2:0] == 0b011
delta = bits[5:3]
flag = bits[6:6]
wave = bits[11:7]
class VMEMEXEC(PacketType): # exclude: 1 << 0
encoding = bits[3:0] == 0b1111
delta = bits[5:4]
src = bits[7:6].enum(MemSrc)
class ALUEXEC(PacketType): # exclude: 1 << 1
encoding = bits[3:0] == 0b1110
delta = bits[5:4]
src = bits[7:6].enum(AluSrc)
class IMMEDIATE(PacketType): # exclude: 1 << 5
encoding = bits[3:0] == 0b1101
delta = bits[6:4]
wave = bits[11:7]
class IMMEDIATE_MASK(PacketType): # exclude: 1 << 5
encoding = bits[4:0] == 0b00100
delta = bits[7:5]
mask = bits[23:8]
class WAVERDY(PacketType): # exclude: 1 << 3
encoding = bits[4:0] == 0b10100
delta = bits[7:5]
mask = bits[23:8]
class WAVEEND(PacketType): # exclude: 1 << 4
encoding = bits[4:0] == 0b10101
delta = bits[7:5]
sa = bits[8:8]
simd = bits[10:9]
wgp = bits[13:11]
wave = bits[19:15]
@property
def cu(self) -> int: return self.wgp | (self.sa << 3)
class WAVEEND_RDNA4(PacketType):
encoding = bits[4:0] == 0b10101
delta = bits[7:5]
sa = bits[8:8]
simd = bits[10:9]
wgp = bits[14:11]
wave = bits[19:15]
@property
def cu(self) -> int: return self.wgp | (self.sa << 4)
class WAVESTART(PacketType): # exclude: 1 << 4
encoding = bits[4:0] == 0b01100
delta = bits[6:5]
sa = bits[7:7]
simd = bits[9:8]
wgp = bits[12:10]
wave = bits[17:13]
id7 = bits[31:18]
@property
def cu(self) -> int: return self.wgp | (self.sa << 3)
class WAVESTART_RDNA4(PacketType): # Layout 4: wgp is 4 bits, wave shifted to bits 15-19
encoding = bits[4:0] == 0b01100
delta = bits[6:5]
sa = bits[7:7]
simd = bits[9:8]
wgp = bits[13:10]
wave = bits[19:15]
id7 = bits[31:20]
@property
def cu(self) -> int: return self.wgp | (self.sa << 4)
class WAVEALLOC(PacketType): # exclude: 1 << 10
encoding = bits[4:0] == 0b00101
delta = bits[7:5]
_padding = bits[19:8]
class WAVEALLOC_RDNA4(PacketType): # Layout 4: 20->24 bits
encoding = bits[4:0] == 0b00101
delta = bits[7:5]
_padding = bits[23:8]
class PERF(PacketType): # exclude: 1 << 11
encoding = bits[4:0] == 0b10110
delta = bits[7:5]
arg = bits[27:8]
class PERF_RDNA4(PacketType): # Layout 4: 28->32 bits
encoding = bits[4:0] == 0b10110
delta = bits[9:7]
arg = bits[31:10]
class NOP(PacketType):
encoding = bits[3:0] == 0b0000
delta = None # type: ignore
_padding = bits[3:0]
class TS_WAVE_STATE(PacketType):
encoding = bits[6:0] == 0b1010001
delta = bits[15:7]
coarse = bits[23:16]
@property
def wave_interest(self) -> bool: return bool(self.coarse & 1)
@property
def terminate_all(self) -> bool: return bool(self.coarse & 8)
class EVENT(PacketType): # exclude: 1 << 7
encoding = bits[7:0] == 0b01100001
delta = bits[10:8]
event = bits[23:11]
class EVENT_BIG(PacketType):
encoding = bits[7:0] == 0b11100001
delta = bits[10:8]
event = bits[31:11]
class REG(PacketType):
encoding = bits[3:0] == 0b1001
delta = bits[6:4]
slot = bits[9:7]
hi_byte = bits[15:8]
subop = bits[31:16]
val32 = bits[63:32]
@property
def is_config(self) -> bool: return bool(self.hi_byte & 0x80)
class SNAPSHOT(PacketType):
encoding = bits[6:0] == 0b1110001
delta = bits[9:7]
snap = bits[63:10]
class LAYOUT_HEADER(PacketType):
encoding = bits[6:0] == 0b0010001
delta = None # type: ignore
layout = bits[12:7]
simd = bits[14:13]
group = bits[17:15]
sel_a = bits[31:28]
sel_b = bits[36:33]
flag4 = bits[59:59]
_padding = bits[63:60]
class INST(PacketType):
encoding = bits[2:0] == 0b010
delta = bits[6:4]
flag1 = bits[3:3]
flag2 = bits[7:7]
wave = bits[12:8]
op = bits[19:13].enum(InstOp)
class INST_RDNA4(PacketType): # Layout 4: different delta position and InstOp encoding
encoding = bits[2:0] == 0b010
delta = bits[5:3]
w64h = bits[6:6]
wave = bits[11:7]
op = bits[19:12].enum(InstOpRDNA4)
class UTILCTR(PacketType):
encoding = bits[6:0] == 0b0110001
delta = bits[8:7]
ctr = bits[47:9]
# Packet types with rocprof type IDs as keys
PACKET_TYPES_RDNA3: dict[int, type[PacketType]] = {
1: VALUINST, 2: VMEMEXEC, 3: ALUEXEC, 4: IMMEDIATE, 5: IMMEDIATE_MASK, 6: WAVERDY, 7: TS_DELTA_S8_W3, 8: WAVEEND,
9: WAVESTART, 10: TS_DELTA_S5_W2, 11: WAVEALLOC, 12: TS_DELTA_S5_W3, 13: PERF, 14: UTILCTR, 15: TS_DELTA_SHORT,
16: NOP, 17: TS_WAVE_STATE, 18: EVENT, 19: EVENT_BIG, 20: REG, 21: SNAPSHOT, 22: TS_DELTA_OR_MARK, 23: LAYOUT_HEADER, 24: INST,
}
PACKET_TYPES_RDNA4: dict[int, type[PacketType]] = {
**PACKET_TYPES_RDNA3,
8: WAVEEND_RDNA4, 9: WAVESTART_RDNA4, 10: TS_DELTA_S5_W2_RDNA4, 11: WAVEALLOC_RDNA4,
12: TS_DELTA_S5_W3_RDNA4, 13: PERF_RDNA4, 22: TS_DELTA_OR_MARK_RDNA4, 24: INST_RDNA4,
}
# ═══════════════════════════════════════════════════════════════════════════════
# CDNA PACKET TYPE DEFINITIONS
# ═══════════════════════════════════════════════════════════════════════════════
class CDNA_MISC(PacketType):
"""pkt_fmt=0: 16-bit (Misc)"""
encoding = bits[3:0] == 0
delta = bits[11:4]
sh = bits[12:12]
misc_type = bits[15:13]
class CDNA_TIMESTAMP(PacketType):
"""pkt_fmt=1: 64-bit timestamp packet (case 0x0)"""
encoding = bits[3:0] == 1
_reserved = bits[15:4]
timestamp = bits[63:16] # stored as (data_word >> 0x10) in low 46 bits of local_58
class CDNA_REG(PacketType):
"""pkt_fmt=2: 64-bit (Reg)"""
encoding = bits[3:0] == 2
pipe = bits[6:5]
_me_raw = bits[8:7]
_reserved = bits[15:9]
regaddr = bits[31:16]
regdata = bits[63:32]
class CDNA_WAVESTART(PacketType):
"""type 3: 32-bit wave start (Wave/group_id)"""
encoding = bits[3:0] == 3
sh = bits[5:5]
cu = bits[9:6]
wave = bits[13:10]
simd = bits[15:14]
pipe = bits[17:16]
me = bits[19:18]
_reserved = bits[21:20]
count = bits[28:22]
_padding = bits[31:29]
class CDNA_WAVEALLOC(PacketType):
"""pkt_fmt=4: 16-bit (Wave)"""
encoding = bits[3:0] == 4
sh = bits[5:5]
cu = bits[9:6]
wave = bits[13:10]
simd = bits[15:14]
class CDNA_REG_CS(PacketType):
"""type 5: 48-bit register CS write (RegCs)"""
encoding = bits[3:0] == 5
pipe = bits[6:5]
_me_raw = bits[8:7]
regaddr = bits[15:9]
regdata = bits[47:16]
class CDNA_WAVEEND(PacketType):
"""type 6: 16-bit wave end (group_id)"""
encoding = bits[3:0] == 6
sh = bits[5:5]
cu = bits[9:6]
wave = bits[13:10]
simd = bits[15:14]
class CDNA_INST(PacketType):
"""pkt_fmt=10: 16-bit (MsgInst)"""
encoding = bits[3:0] == 10
wave = bits[8:5]
simd = bits[10:9]
op = bits[15:11].enum(InstOpCDNA)
class CDNA_INST_PC(PacketType):
"""pkt_fmt=11: 64-bit (MsgInstPc)"""
encoding = bits[3:0] == 11
wave = bits[8:5]
simd = bits[10:9]
_reserved = bits[14:11]
err = bits[15:15]
pc = bits[63:16]
class CDNA_ISSUE(PacketType):
"""pkt_fmt=13: 32-bit (Issue)"""
encoding = bits[3:0] == 13
simd = bits[6:5]
_gap = bits[7:7]
inst0 = bits[9:8]
inst1 = bits[11:10]
inst2 = bits[13:12]
inst3 = bits[15:14]
inst4 = bits[17:16]
inst5 = bits[19:18]
inst6 = bits[21:20]
inst7 = bits[23:22]
inst8 = bits[25:24]
inst9 = bits[27:26]
_padding = bits[31:28]
class CDNA_PERF(PacketType):
"""pkt_fmt=14: 64-bit (MsgPerf)"""
encoding = bits[3:0] == 14
sh = bits[5:5]
cu = bits[9:6]
cntr_bank = bits[11:10]
cntr0 = bits[24:12]
cntr1 = bits[37:25]
cntr2 = bits[50:38]
cntr3 = bits[63:51]
class CDNA_EVENT(PacketType):
"""pkt_fmt=7: 16-bit"""
encoding = bits[3:0] == 7
_reserved = bits[15:4]
class CDNA_EVENT_CS(PacketType):
"""pkt_fmt=8: 16-bit"""
encoding = bits[3:0] == 8
_reserved = bits[15:4]
class CDNA_EVENT_GFX1(PacketType):
"""pkt_fmt=9: 16-bit"""
encoding = bits[3:0] == 9
_reserved = bits[15:4]
class CDNA_USERDATA(PacketType):
"""pkt_fmt=12: 48-bit (UserData)"""
encoding = bits[3:0] == 12
sh = bits[5:5]
cu = bits[9:6]
wave = bits[13:10]
simd = bits[15:14]
data = bits[47:16]
class CDNA_REG_CS_PRIV(PacketType):
"""pkt_fmt=15: 48-bit (RegCs)"""
encoding = bits[3:0] == 15
pipe = bits[6:5]
_me_raw = bits[8:7]
regaddr = bits[15:9]
regdata = bits[47:16]
PACKET_TYPES_CDNA: dict[int, type[PacketType]] = {
0: CDNA_MISC, 1: CDNA_TIMESTAMP, 2: CDNA_REG, 3: CDNA_WAVESTART, 4: CDNA_WAVEALLOC, 5: CDNA_REG_CS, 6: CDNA_WAVEEND,
7: CDNA_EVENT, 8: CDNA_EVENT_CS, 9: CDNA_EVENT_GFX1, 10: CDNA_INST, 11: CDNA_INST_PC, 12: CDNA_USERDATA,
13: CDNA_ISSUE, 14: CDNA_PERF, 15: CDNA_REG_CS_PRIV, 16: LAYOUT_HEADER,
}
# ═══════════════════════════════════════════════════════════════════════════════
# DECODER
# ═══════════════════════════════════════════════════════════════════════════════
def _build_decode_tables(packet_types: dict[int, type[PacketType]]) -> tuple[dict[int, tuple], bytes]:
# Build state table: byte -> opcode. Sort by mask specificity (more bits first), NOP last
sorted_types = sorted(packet_types.items(), key=lambda x: (-bin(x[1].encoding.mask).count('1'), x[0] == 16))
state_table = bytes(next((op for op, cls in sorted_types if (b & cls.encoding.mask) == cls.encoding.default), 16) for b in range(256))
# Build decode info: opcode -> (pkt_cls, nib_count, delta_lo, delta_mask, special_case)
# special_case: 0=none, 1=TS_DELTA_OR_MARK (check is_marker), 2=TS_DELTA_SHORT (add 4), 3=CDNA_MISC (*4), 4=CDNA_TIMESTAMP (absolute)
_special = {TS_DELTA_OR_MARK: 1, TS_DELTA_OR_MARK_RDNA4: 1, TS_DELTA_SHORT: 2, CDNA_MISC: 3, CDNA_TIMESTAMP: 4}
decode_info = {}
for opcode, pkt_cls in packet_types.items():
delta_field = getattr(pkt_cls, 'delta', None)
special = _special.get(pkt_cls, 0)
decode_info[opcode] = (pkt_cls, pkt_cls._size_nibbles, delta_field.lo if delta_field else 0, delta_field.mask if delta_field else 0, special) # type: ignore[attr-defined]
return decode_info, state_table
_DECODE_INFO_RDNA3, _STATE_TABLE_RDNA3 = _build_decode_tables(PACKET_TYPES_RDNA3)
_DECODE_INFO_RDNA4, _STATE_TABLE_RDNA4 = _build_decode_tables(PACKET_TYPES_RDNA4)
_DECODE_INFO_CDNA, _STATE_TABLE_CDNA = _build_decode_tables(PACKET_TYPES_CDNA)
def decode(data: bytes) -> Iterator[PacketType]:
"""Decode raw SQTT blob, yielding packet instances. Auto-detects RDNA (layout 3/4) vs CDNA."""
n, reg, pos, nib_off, nib_count, time, ts_offset = len(data), 0, 0, 0, 16, 0, None
decode_info, state_table = _DECODE_INFO_RDNA3, _STATE_TABLE_RDNA3 # start RDNA3, auto-detect switches if needed
while pos + ((nib_count + nib_off + 1) >> 1) <= n:
need = nib_count - nib_off
# 1. if unaligned, read high nibble to align
if nib_off: reg, pos = (reg >> 4) | ((data[pos] >> 4) << 60), pos + 1
# 2. read all full bytes at once
if (byte_count := need >> 1):
read_bytes = min(byte_count, 8)
chunk = int.from_bytes(data[pos:pos + read_bytes], 'little')
reg, pos = (reg >> (read_bytes * 8)) | (chunk << (64 - read_bytes * 8)), pos + byte_count
# 3. if odd, read low nibble
if (nib_off := need & 1): reg = (reg >> 4) | ((data[pos] & 0xF) << 60)
opcode = state_table[reg & 0xFF]
pkt_cls, nib_count, delta_lo, delta_mask, special = decode_info[opcode]
delta = (reg >> delta_lo) & delta_mask
if special == 1: # TS_DELTA_OR_MARK
pkt = pkt_cls.from_raw(reg, 0) # create packet to check is_marker
if pkt.is_marker: delta = 0
elif special == 2: delta += 4 # TS_DELTA_SHORT
elif special == 3: delta *= 4 # CDNA_DELTA
elif special == 4: # CDNA_TIMESTAMP (absolute timestamp anchoring)
if (reg >> 4) & 0xfff == 0: # unk_0 == 0 means absolute timestamp
abs_ts = reg >> 16
if ts_offset is None: ts_offset = abs_ts - time
else: time = ((abs_ts - ts_offset) & ~3) - 4
delta = 0
time += delta
pkt = pkt_cls.from_raw(reg, time)
# auto-detect: first packet is always LAYOUT_HEADER (RDNA layout 3/4) or misdetected (CDNA)
if pkt_cls is LAYOUT_HEADER:
if pkt.layout == 4: decode_info, state_table = _DECODE_INFO_RDNA4, _STATE_TABLE_RDNA4
elif pkt.layout != 3: # not a real LAYOUT_HEADER — switch to CDNA and re-decode first packet
decode_info, state_table = _DECODE_INFO_CDNA, _STATE_TABLE_CDNA
opcode = state_table[reg & 0xFF]
pkt_cls, nib_count, delta_lo, delta_mask, special = decode_info[opcode]
if special == 4 and (reg >> 4) & 0xfff == 0: # CDNA_TIMESTAMP absolute
ts_offset = (reg >> 16) - time
pkt = pkt_cls.from_raw(reg, time)
yield pkt
# ═══════════════════════════════════════════════════════════════════════════════
# MAPPER
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass(frozen=True)
class InstructionInfo:
pc: int
wave: int
inst: Inst
def map_insts(data:bytes, lib:bytes, target:str) -> Iterator[tuple[PacketType, InstructionInfo|None]]:
"""maps SQTT packets to instructions, yields (packet, instruction_info or None)"""
# map pcs to insts
from tinygrad.viz.serve import amd_decode
pc_map = amd_decode(lib, target)
wave_pc:dict[int, int] = {}
# only processing packets on one [CU, SIMD] unit
def simd_select(p) -> bool: return getattr(p, "cu", 0) == 0 and getattr(p, "simd", 0) == 0
for p in decode(data):
if not simd_select(p): continue
if isinstance(p, (WAVESTART, WAVESTART_RDNA4, CDNA_WAVESTART)):
assert p.wave not in wave_pc, "only one inflight wave per unit"
wave_pc[p.wave] = next(iter(pc_map))
elif isinstance(p, (WAVEEND, WAVEEND_RDNA4)):
pc = wave_pc.pop(p.wave)
yield (p, InstructionInfo(pc, p.wave, s_endpgm()))
elif isinstance(p, IMMEDIATE_MASK):
# immediate mask may yield multiple times per packet
for wave in range(16):
if p.mask & (1 << wave):
inst = pc_map[pc:=wave_pc[wave]]
wave_pc[wave] += inst.size()
yield (p, InstructionInfo(pc, wave, inst))
# map INST events on this SIMD to the program counter, we know the waves
elif isinstance(p, (VALUINST, INST, INST_RDNA4, IMMEDIATE)) and not (isinstance(p, (INST, INST_RDNA4)) and p.op.name.startswith("OTHER_")):
inst = pc_map[pc:=wave_pc[p.wave]]
# s_delay_alu, s_wait_alu and s_barrier_wait instructions are skipped
while (inst_op:=getattr(inst, 'op_name', '')) in {"S_DELAY_ALU", "S_WAIT_ALU", "S_BARRIER_WAIT"}:
wave_pc[p.wave] += inst.size()
inst = pc_map[pc:=wave_pc[p.wave]]
# assert branch always has a JUMP packet
if "BRANCH" in inst_op and not (isinstance(p, (INST, INST_RDNA4)) and p.op.name.startswith("JUMP")):
raise AssertionError(f"{inst_op} can only be followed by JUMP, got {p}")
# JUMP handling
if isinstance(p, (INST, INST_RDNA4)) and p.op in {InstOp.JUMP, InstOpRDNA4.JUMP}:
x = getattr(inst, 'simm16') & 0xffff
wave_pc[p.wave] += inst.size() + (x - 0x10000 if x & 0x8000 else x)*4
else:
wave_pc[p.wave] += inst.size()
yield (p, InstructionInfo(pc, p.wave, inst))
# for all other packets (VMEMEXEC, ALUEXEC, OTHER_ INST, etc.), yield with None
else: yield (p, None)
# ═══════════════════════════════════════════════════════════════════════════════
# PRINTER
# ═══════════════════════════════════════════════════════════════════════════════
PACKET_COLORS = {
"INST": "WHITE", "VALUINST": "BLACK", "VMEMEXEC": "yellow", "ALUEXEC": "yellow",
"IMMEDIATE": "YELLOW", "IMMEDIATE_MASK": "YELLOW", "WAVERDY": "cyan", "WAVEALLOC": "cyan",
"WAVEEND": "blue", "WAVESTART": "blue", "PERF": "magenta", "EVENT": "red", "EVENT_BIG": "red",
"REG": "green", "LAYOUT_HEADER": "white", "SNAPSHOT": "white", "UTILCTR": "green",
}
def format_packet(p) -> str:
name = type(p).__name__
if isinstance(p, (INST, INST_RDNA4)):
op_name = p.op.name if isinstance(p.op, (InstOp, InstOpRDNA4)) else f"0x{p.op:02x}"
fields = f"wave={p.wave} op={op_name}" + ((" flag1" if p.flag1 else "") + (" flag2" if p.flag2 else "") if isinstance(p, INST) else "")
elif isinstance(p, VALUINST): fields = f"wave={p.wave}" + (" flag" if p.flag else "")
elif isinstance(p, ALUEXEC): fields = f"src={p.src.name if isinstance(p.src, AluSrc) else p.src}"
elif isinstance(p, VMEMEXEC): fields = f"src={p.src.name if isinstance(p.src, MemSrc) else p.src}"
elif isinstance(p, (WAVESTART, WAVESTART_RDNA4, WAVEEND, WAVEEND_RDNA4)): fields = f"wave={p.wave} simd={p.simd} cu={p.cu}"
elif hasattr(p, '_fields'):
filt = {'delta', 'encoding'} if not isinstance(p, (TS_DELTA_OR_MARK, TS_DELTA_OR_MARK_RDNA4)) else {'encoding'}
fields = " ".join(f"{k}=0x{getattr(p, k):x}" if k in {'snap', 'val32'} else f"{k}={getattr(p, k)}"
for k in p._fields if not k.startswith('_') and k not in filt)
else: fields = ""
return f"{p._time:8}: {colored(f'{name:18}', PACKET_COLORS.get(name.replace('_RDNA4', ''), 'white'))} {fields}"
def print_packets(packets) -> None:
skip = {"NOP", "TS_DELTA_SHORT", "TS_WAVE_STATE", "TS_DELTA_OR_MARK",
"TS_DELTA_S5_W2", "TS_DELTA_S5_W3", "TS_DELTA_S8_W3", "REG", "EVENT"} if not getenv("NOSKIP") else {"NOP"}
for data in packets:
p, inst = data if isinstance(data, tuple) else (data, None)
if type(p).__name__.replace("_RDNA4", "") not in skip: print(format_packet(p), f"inst={inst.inst}" if inst is not None else '')
if __name__ == "__main__":
import sys, pickle
from tinygrad.helpers import temp
with open(temp("profile.pkl", append_user=True) if len(sys.argv) < 2 else sys.argv[1], "rb") as f:
data = pickle.load(f)
prg_events = {e.tag: e for e in data if type(e).__name__ == "ProfileProgramEvent" and e.tag is not None}
sqtt_events = [e for e in data if type(e).__name__ == "ProfileSQTTEvent"]
dev_targets = {e.device:f"gfx{e.props['gfx_target_version']//1000}" for e in data if type(e).__name__ == "ProfileDeviceEvent" and e.props}
evt_num = getenv("SQTT_EVENT", -1)
for i, event in enumerate(sqtt_events):
prg = prg_events.get(event.kern)
print(f"=== event {i} {prg.name if prg is not None else ''} ===")
if evt_num == -1 or i == evt_num:
print_packets(map_insts(event.blob, prg.lib, dev_targets[prg.device]) if prg is not None else decode(event.blob))
print("\n")