mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-15 20:18:26 +00:00
Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f85319722 | ||
|
|
d9f0e9c40c | ||
|
|
3dcffbea25 | ||
|
|
41c5368266 | ||
|
|
4598a21f94 | ||
|
|
27084cd618 | ||
|
|
d2616e5daf | ||
|
|
3130c53f85 | ||
|
|
1c66e41383 | ||
|
|
93823b272c | ||
|
|
1c6147e9bf | ||
|
|
7f5656d236 | ||
|
|
5f55a61700 | ||
|
|
ed097df864 | ||
|
|
a83c97f17e | ||
|
|
c793076fb6 | ||
|
|
1f45601a97 | ||
|
|
14c4989f65 | ||
|
|
31b38640ac | ||
|
|
fe770e822c | ||
|
|
768231c065 | ||
|
|
4165594b30 | ||
|
|
c03b7b0da1 | ||
|
|
66249836c0 | ||
|
|
a0d6ed9914 | ||
|
|
99fcfc0e97 | ||
|
|
cf8bb15aef | ||
|
|
32dfc9b1d0 | ||
|
|
9803e389fe | ||
|
|
1f893b65cc | ||
|
|
35f5f05ad5 | ||
|
|
b9f08ad18a | ||
|
|
222ae38aa4 | ||
|
|
f0bf20d7b2 | ||
|
|
85ef097da6 | ||
|
|
0e240fb987 | ||
|
|
d2c1712e4c | ||
|
|
96b0ee0966 | ||
|
|
9b5c4bc698 | ||
|
|
6ea3586101 | ||
|
|
92cb8b6776 | ||
|
|
c416b20668 | ||
|
|
415b83ba18 | ||
|
|
8c7eacea59 | ||
|
|
81542699f8 | ||
|
|
79f55a5d5e | ||
|
|
37518fb236 | ||
|
|
672008ccab | ||
|
|
849af761a4 | ||
|
|
ab46b3d8d3 | ||
|
|
df20197bfb | ||
|
|
2b56c264d5 | ||
|
|
c7e5c2f996 | ||
|
|
659aa14043 | ||
|
|
21ffa1a86b | ||
|
|
29f3fb7af3 | ||
|
|
1edc7fc519 | ||
|
|
c9a3ac988c | ||
|
|
77d96acbe3 | ||
|
|
660ecf272b | ||
|
|
267bbb163e | ||
|
|
de29a49ea3 | ||
|
|
742e10a572 | ||
|
|
447fe8907b | ||
|
|
b0cfcec183 | ||
|
|
1726084b2a | ||
|
|
de069a4876 | ||
|
|
4573e91e61 | ||
|
|
8d43212bc6 | ||
|
|
a8bea4ec52 | ||
|
|
388514c5b1 | ||
|
|
729bb04d8c | ||
|
|
8f4de73141 | ||
|
|
a5959ef0f1 | ||
|
|
5ba06892c0 | ||
|
|
469efe313d | ||
|
|
e3b3cb163d | ||
|
|
3e32185faf | ||
|
|
5328913d2b | ||
|
|
9c49ec1cc1 | ||
|
|
000d4a125b | ||
|
|
63289902d8 | ||
|
|
b596f77e33 |
@@ -43,17 +43,27 @@ jobs:
|
||||
run: sudo apt-get install -y --no-install-recommends libclang-20-dev llvm-20-dev hip-dev libusb-1.0-0-dev
|
||||
- name: Regenerate autogen files
|
||||
run: |
|
||||
find tinygrad/runtime/autogen -type f -name "*.py" -not -name "__init__.py" -not -name "comgr_3.py" -not -name "metal.py" -not -name "libclang.py" -delete
|
||||
rm tinygrad/runtime/autogen/opencl.py
|
||||
python3 -c "from tinygrad.runtime.autogen import opencl"
|
||||
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv_580, nv"
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr, hsa, hip, amd_gpu, sqtt, rocprof, amdgpu_kd"
|
||||
python3 -c "from tinygrad.runtime.autogen.am import am, pm4_soc15, pm4_nv, sdma_4_0_0, sdma_5_0_0, sdma_6_0_0, smu_v13_0_0, smu_v13_0_6, smu_v14_0_2"
|
||||
rm tinygrad/runtime/autogen/{cuda,nvrtc,nvjitlink,nv_570,nv}.py
|
||||
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv"
|
||||
rm tinygrad/runtime/autogen/{comgr,hsa,hip,amd_gpu,sqtt,rocprof}.py
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr, hsa, hip, amd_gpu, sqtt, rocprof"
|
||||
rm tinygrad/runtime/autogen/am/{am,pm4_soc15,pm4_nv,sdma_4_0_0,sdma_5_0_0,sdma_6_0_0,smu_v13_0_0,smu_v14_0_2}.py
|
||||
python3 -c "from tinygrad.runtime.autogen.am import am, pm4_soc15, pm4_nv, sdma_4_0_0, sdma_5_0_0, sdma_6_0_0, smu_v13_0_0, smu_v14_0_2"
|
||||
rm tinygrad/runtime/autogen/{libc,kfd,io_uring,ib,pci,vfio}.py
|
||||
python3 -c "from tinygrad.runtime.autogen import libc, kfd, io_uring, ib, pci, vfio"
|
||||
rm tinygrad/runtime/autogen/llvm.py
|
||||
python3 -c "from tinygrad.runtime.autogen import llvm"
|
||||
rm tinygrad/runtime/autogen/webgpu.py
|
||||
python3 -c "from tinygrad.runtime.autogen import webgpu"
|
||||
rm tinygrad/runtime/autogen/{kgsl,qcom_dsp}.py
|
||||
python3 -c "from tinygrad.runtime.autogen import kgsl, qcom_dsp"
|
||||
rm tinygrad/runtime/autogen/libusb.py
|
||||
python3 -c "from tinygrad.runtime.autogen import libusb"
|
||||
rm tinygrad/runtime/autogen/mesa.py
|
||||
python3 -c "from tinygrad.runtime.autogen import mesa"
|
||||
rm tinygrad/runtime/autogen/avcodec.py
|
||||
python3 -c "from tinygrad.runtime.autogen import avcodec"
|
||||
REGEN=1 python3 -c "from tinygrad.runtime.autogen import libclang"
|
||||
- name: Check for differences
|
||||
|
||||
@@ -329,12 +329,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setcap to python
|
||||
run: ./extra/amdpci/setup_python_cap.sh
|
||||
- name: Remove amd modules
|
||||
run: ./extra/hcq/hcq_smi.py amd rmmod
|
||||
- name: Kill stale pids
|
||||
run: ./extra/hcq/hcq_smi.py amd kill_pids
|
||||
- name: Remove amdgpu
|
||||
run: sudo rmmod amdgpu || true
|
||||
- name: Cleanup running AM processes
|
||||
run: python extra/amdpci/am_smi.py --pids --kill
|
||||
#- name: Insert amdgpu
|
||||
# run: sudo modprobe amdgpu
|
||||
- name: Symlink models and datasets
|
||||
@@ -441,12 +439,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setcap to python
|
||||
run: ./extra/amdpci/setup_python_cap.sh
|
||||
- name: Remove amd modules
|
||||
run: ./extra/hcq/hcq_smi.py amd rmmod
|
||||
- name: Kill stale pids
|
||||
run: ./extra/hcq/hcq_smi.py amd kill_pids
|
||||
- name: Remove amdgpu
|
||||
run: sudo rmmod amdgpu || true
|
||||
- name: Cleanup running AM processes
|
||||
run: python extra/amdpci/am_smi.py --pids --kill
|
||||
- name: Symlink models and datasets
|
||||
run: |
|
||||
mkdir -p weights
|
||||
@@ -493,12 +489,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setcap to python
|
||||
run: ./extra/amdpci/setup_python_cap.sh
|
||||
- name: Remove amd modules
|
||||
run: ./extra/hcq/hcq_smi.py amd rmmod
|
||||
- name: Kill stale pids
|
||||
run: ./extra/hcq/hcq_smi.py amd kill_pids
|
||||
- name: Remove amdgpu
|
||||
run: sudo rmmod amdgpu || true
|
||||
- name: Cleanup running AM processes
|
||||
run: python extra/amdpci/am_smi.py --pids --kill
|
||||
- name: Symlink models and datasets
|
||||
run: |
|
||||
mkdir -p weights
|
||||
@@ -547,7 +541,7 @@ jobs:
|
||||
- name: reset process replay
|
||||
run: test/external/process_replay/reset.py
|
||||
- name: openpilot compile3 0.10.0 driving_policy
|
||||
run: BENCHMARK_LOG=openpilot_0_10_0_policy PYTHONPATH="." ASSERT_MIN_STEP_TIME=3 DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.10.0/selfdrive/modeld/models/driving_policy.onnx
|
||||
run: BENCHMARK_LOG=openpilot_0_10_0_policy PYTHONPATH="." ASSERT_MIN_STEP_TIME=4 DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.10.0/selfdrive/modeld/models/driving_policy.onnx
|
||||
- name: openpilot compile3 0.10.0 dmonitoring
|
||||
run: BENCHMARK_LOG=openpilot_0_10_0_dmonitoring PYTHONPATH="." ASSERT_MIN_STEP_TIME=11 DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.10.0/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||
- name: DEBUG=2 openpilot compile3 0.10.1 driving_vision
|
||||
@@ -559,7 +553,7 @@ jobs:
|
||||
- name: openpilot compile3 0.10.1 driving_vision
|
||||
run: BENCHMARK_LOG=openpilot_0_10_1_vision PYTHONPATH="." ASSERT_MIN_STEP_TIME=17 DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/720392c9a5b986981fdbed1bb8c47a6c5573a50e/selfdrive/modeld/models/driving_vision.onnx
|
||||
- name: openpilot compile3 0.10.1 driving_policy
|
||||
run: BENCHMARK_LOG=openpilot_0_10_1_policy PYTHONPATH="." ASSERT_MIN_STEP_TIME=3 DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/720392c9a5b986981fdbed1bb8c47a6c5573a50e/selfdrive/modeld/models/driving_policy.onnx
|
||||
run: BENCHMARK_LOG=openpilot_0_10_1_policy PYTHONPATH="." ASSERT_MIN_STEP_TIME=4 DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/720392c9a5b986981fdbed1bb8c47a6c5573a50e/selfdrive/modeld/models/driving_policy.onnx
|
||||
- name: openpilot compile3 0.10.1 dmonitoring
|
||||
run: BENCHMARK_LOG=openpilot_0_10_1_dmonitoring PYTHONPATH="." ASSERT_MIN_STEP_TIME=10 DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/720392c9a5b986981fdbed1bb8c47a6c5573a50e/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||
- name: benchmark MobileNetV2 on DSP
|
||||
@@ -584,8 +578,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setcap to python
|
||||
run: ./extra/amdpci/setup_python_cap.sh
|
||||
- name: Remove amd modules
|
||||
run: ./extra/hcq/hcq_smi.py amd rmmod
|
||||
- name: Kill stale pids
|
||||
@@ -648,8 +640,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setcap to python
|
||||
run: ./extra/amdpci/setup_python_cap.sh
|
||||
- name: Remove nv modules
|
||||
run: ./extra/hcq/hcq_smi.py nv rmmod
|
||||
- name: Kill stale pids
|
||||
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
- name: Test ResNet-18
|
||||
run: DEBUG=2 python3 extra/torch_backend/example.py
|
||||
- name: custom tests
|
||||
run: python3 -m pytest -n auto extra/torch_backend/test.py --durations=20
|
||||
run: python3 extra/torch_backend/test.py
|
||||
- name: Test one op in torch tests
|
||||
run: DEBUG=2 python3 extra/torch_backend/torch_tests.py TestTinyBackendPRIVATEUSE1.test_unary_log_tiny_float32
|
||||
- name: Test Ops with TINY_BACKEND
|
||||
@@ -670,7 +670,7 @@ jobs:
|
||||
python-version: '3.13'
|
||||
- name: Verify AMD autogen is up to date
|
||||
run: |
|
||||
python -m extra.assembly.amd.amdxml
|
||||
python -m extra.assembly.amd.pdf
|
||||
git diff --exit-code extra/assembly/amd/autogen/
|
||||
- name: Install LLVM 21
|
||||
run: |
|
||||
|
||||
@@ -6,7 +6,6 @@ Elementwise ops operate on a per element basis. They don't change the shape of t
|
||||
::: tinygrad.Tensor.neg
|
||||
::: tinygrad.Tensor.log
|
||||
::: tinygrad.Tensor.log2
|
||||
::: tinygrad.Tensor.log10
|
||||
::: tinygrad.Tensor.exp
|
||||
::: tinygrad.Tensor.exp2
|
||||
::: tinygrad.Tensor.sqrt
|
||||
@@ -88,8 +87,4 @@ Elementwise ops operate on a per element basis. They don't change the shape of t
|
||||
::: tinygrad.Tensor.float
|
||||
::: tinygrad.Tensor.half
|
||||
::: tinygrad.Tensor.int
|
||||
::: tinygrad.Tensor.bool
|
||||
::: tinygrad.Tensor.bfloat16
|
||||
::: tinygrad.Tensor.double
|
||||
::: tinygrad.Tensor.long
|
||||
::: tinygrad.Tensor.short
|
||||
::: tinygrad.Tensor.bool
|
||||
@@ -27,6 +27,5 @@
|
||||
::: tinygrad.Tensor.flatten
|
||||
::: tinygrad.Tensor.unflatten
|
||||
::: tinygrad.Tensor.diag
|
||||
::: tinygrad.Tensor.diagonal
|
||||
::: tinygrad.Tensor.roll
|
||||
::: tinygrad.Tensor.rearrange
|
||||
@@ -7,7 +7,6 @@
|
||||
::: tinygrad.Tensor.any
|
||||
::: tinygrad.Tensor.all
|
||||
::: tinygrad.Tensor.isclose
|
||||
::: tinygrad.Tensor.allclose
|
||||
::: tinygrad.Tensor.mean
|
||||
::: tinygrad.Tensor.var
|
||||
::: tinygrad.Tensor.var_mean
|
||||
@@ -31,9 +30,7 @@
|
||||
::: tinygrad.Tensor.matmul
|
||||
::: tinygrad.Tensor.einsum
|
||||
::: tinygrad.Tensor.cumsum
|
||||
::: tinygrad.Tensor.cumprod
|
||||
::: tinygrad.Tensor.cummax
|
||||
::: tinygrad.Tensor.cummin
|
||||
::: tinygrad.Tensor.triu
|
||||
::: tinygrad.Tensor.tril
|
||||
::: tinygrad.Tensor.interpolate
|
||||
@@ -41,9 +38,7 @@
|
||||
::: tinygrad.Tensor.scatter_reduce
|
||||
::: tinygrad.Tensor.masked_select
|
||||
::: tinygrad.Tensor.masked_fill
|
||||
::: tinygrad.Tensor.nonzero
|
||||
::: tinygrad.Tensor.sort
|
||||
::: tinygrad.Tensor.argsort
|
||||
::: tinygrad.Tensor.topk
|
||||
::: tinygrad.Tensor.multinomial
|
||||
|
||||
@@ -61,8 +56,3 @@
|
||||
::: tinygrad.Tensor.sparse_categorical_crossentropy
|
||||
::: tinygrad.Tensor.cross_entropy
|
||||
::: tinygrad.Tensor.nll_loss
|
||||
|
||||
## Linear Algebra
|
||||
|
||||
::: tinygrad.Tensor.qr
|
||||
::: tinygrad.Tensor.svd
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." AMD=1 DEBUG=0 JIT=1 FLASH_ATTENTION=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
|
||||
# similar to https://github.com/mlcommons/training_results_v3.1/blob/d06288b2bd675a9d88e0e6181f5bb5626b71ec19/Quanta_Cloud_Technology/results/D54U-3U/bert/result_1.txt#L54
|
||||
export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
|
||||
export TRAIN_STEPS=3900
|
||||
|
||||
export IGNORE_OOB=1
|
||||
export REWRITE_STACK_LIMIT=5000000
|
||||
|
||||
export BEAM=0 BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1 FREE_INTERMEDIATE=0
|
||||
export BASEDIR="/raid/datasets/wiki"
|
||||
|
||||
export WANDB=1 PARALLEL=0
|
||||
|
||||
RUNMLPERF=1 python3 examples/mlperf/model_train.py
|
||||
@@ -1,345 +0,0 @@
|
||||
# AMD ISA code generator - generates enum.py, ins.py, operands.py, str_pcode.py
|
||||
# Sources: XML from https://gpuopen.com/download/machine-readable-isa/latest/
|
||||
# PDF manuals from AMD documentation
|
||||
import re, zlib, xml.etree.ElementTree as ET, zipfile
|
||||
from tinygrad.helpers import fetch
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Configuration
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ARCHS = {
|
||||
"rdna3": {"xml": "amdgpu_isa_rdna3_5.xml", "pdf": "https://docs.amd.com/api/khub/documents/UVVZM22UN7tMUeiW_4ShTQ/content"},
|
||||
"rdna4": {"xml": "amdgpu_isa_rdna4.xml", "pdf": "https://docs.amd.com/api/khub/documents/uQpkEvk3pv~kfAb2x~j4uw/content"},
|
||||
"cdna": {"xml": "amdgpu_isa_cdna4.xml", "pdf": "https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/instruction-set-architectures/amd-instinct-cdna4-instruction-set-architecture.pdf"},
|
||||
}
|
||||
XML_URL = "https://gpuopen.com/download/machine-readable-isa/latest/"
|
||||
# Map XML encoding names to codebase names (arch-specific overrides in ARCH_NAME_MAP)
|
||||
NAME_MAP = {"VOP3_SDST_ENC": "VOP3SD", "VOPDXY": "VOPD", "VDS": "DS"}
|
||||
ARCH_NAME_MAP = {"cdna": {"VOP3": "VOP3A", "VOP3_SDST_ENC": "VOP3B"}}
|
||||
# Instructions missing from XML but present in PDF
|
||||
FIXES = {"rdna3": {"SOPK": {22: "S_SUBVECTOR_LOOP_BEGIN", 23: "S_SUBVECTOR_LOOP_END"}, "FLAT": {55: "FLAT_ATOMIC_CSUB_U32"}},
|
||||
"rdna4": {"SOP1": {80: "S_GET_BARRIER_STATE", 81: "S_BARRIER_INIT", 82: "S_BARRIER_JOIN"}, "SOPP": {9: "S_WAITCNT", 21: "S_BARRIER_LEAVE"}}}
|
||||
# Encoding suffixes to strip (variants we don't generate separate classes for)
|
||||
_ENC_SUFFIXES = ("_INST_LITERAL", "_VOP_DPP16", "_VOP_DPP8", "_VOP_DPP", "_VOP_SDWA", "_NSA1", "_MFMA")
|
||||
# Field name normalization
|
||||
_FIELD_RENAMES = {"opsel_hi_2": "opsel_hi2", "op_sel_hi_2": "opsel_hi2", "op_sel": "opsel", "bound_ctrl": "bc",
|
||||
"tgt": "target", "row_en": "row", "unorm": "unrm", "clamp": "clmp", "wait_exp": "waitexp"}
|
||||
# Encoding variants to skip entirely
|
||||
_SKIP_ENCODINGS = ("LITERAL", "NSA", "DPP", "SDWA", "MFMA")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# XML parsing helpers
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _strip_enc(name: str) -> str:
|
||||
"""Strip ENC_ prefix and encoding variant suffixes."""
|
||||
name = name.removeprefix("ENC_")
|
||||
for sfx in _ENC_SUFFIXES: name = name.replace(sfx, "")
|
||||
return name
|
||||
|
||||
def _norm_field(name: str) -> str:
|
||||
"""Normalize field name to match expected names."""
|
||||
for old, new in _FIELD_RENAMES.items(): name = name.replace(old, new)
|
||||
return name
|
||||
|
||||
def _map_flat(enc_name: str, instr_name: str) -> str:
|
||||
"""Map FLAT/GLOBAL/SCRATCH encoding to correct enum based on instruction prefix."""
|
||||
if enc_name in ("FLAT_GLBL", "FLAT_GLOBAL"): return "GLOBAL"
|
||||
if enc_name == "FLAT_SCRATCH": return "SCRATCH"
|
||||
if enc_name in ("FLAT", "VFLAT", "VGLOBAL", "VSCRATCH"):
|
||||
v = "V" if enc_name.startswith("V") else ""
|
||||
if instr_name.startswith("GLOBAL_"): return f"{v}GLOBAL"
|
||||
if instr_name.startswith("SCRATCH_"): return f"{v}SCRATCH"
|
||||
return f"{v}FLAT"
|
||||
return enc_name
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# XML parsing
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def parse_xml(filename: str, arch: str):
|
||||
root = ET.fromstring(zipfile.ZipFile(fetch(XML_URL)).read(filename))
|
||||
name_map = {**NAME_MAP, **ARCH_NAME_MAP.get(arch, {})}
|
||||
encodings, enums, types, fmts, op_types_set = {}, {}, {}, {}, set()
|
||||
# Extract DataFormats with BitCount
|
||||
for df in root.findall("ISA/DataFormats/DataFormat"):
|
||||
name, bits = df.findtext("DataFormatName"), df.findtext("BitCount")
|
||||
if name and bits: fmts[name] = int(bits)
|
||||
# Extract encoding definitions
|
||||
for enc in root.findall("ISA/Encodings/Encoding"):
|
||||
name = enc.findtext("EncodingName")
|
||||
if not name.startswith("ENC_") and name not in ("VOP3_SDST_ENC", "VOPDXY"): continue
|
||||
if any(s in name for s in _SKIP_ENCODINGS): continue
|
||||
fields = [(_norm_field(f.findtext("FieldName").lower()), int(f.find("BitLayout/Range").findtext("BitOffset") or 0) + int(f.find("BitLayout/Range").findtext("BitCount") or 0) - 1,
|
||||
int(f.find("BitLayout/Range").findtext("BitOffset") or 0))
|
||||
for f in enc.findall(".//MicrocodeFormat/BitMap/Field") if f.find("BitLayout/Range") is not None]
|
||||
ident = (enc.findall("EncodingIdentifiers/EncodingIdentifier") or [None])[0]
|
||||
enc_field = next((f for f in fields if f[0] == "encoding"), None)
|
||||
enc_bits = "".join(ident.text[len(ident.text)-1-b] for b in range(enc_field[1], enc_field[2]-1, -1)) if ident is not None and enc_field else None
|
||||
base_name = _strip_enc(name)
|
||||
encodings[name_map.get(base_name, base_name)] = (fields, enc_bits)
|
||||
# Extract instruction opcodes and operand info
|
||||
for instr in root.findall("ISA/Instructions/Instruction"):
|
||||
name = instr.findtext("InstructionName")
|
||||
for enc in instr.findall("InstructionEncodings/InstructionEncoding"):
|
||||
if enc.findtext("EncodingCondition") != "default": continue
|
||||
base, opcode = _map_flat(_strip_enc(enc.findtext("EncodingName")), name), int(enc.findtext("Opcode") or 0)
|
||||
enc_name = name_map.get(base, base)
|
||||
# ADDTID instructions go in both FLAT and GLOBAL enums (pcode uses FLATOp for these)
|
||||
if "ADDTID" in name:
|
||||
if base == "GLOBAL": enums.setdefault("FLAT", {})[opcode] = name
|
||||
elif base == "VGLOBAL": enums.setdefault("VFLAT", {})[opcode] = name
|
||||
enums.setdefault(enc_name, {})[opcode] = name
|
||||
# Extract operand info
|
||||
op_info = {op.findtext("FieldName").lower(): (op.findtext("DataFormatName"), int(op.findtext("OperandSize") or 0), op.findtext("OperandType"))
|
||||
for op in enc.findall("Operands/Operand") if op.findtext("FieldName")}
|
||||
for fmt, _, otype in op_info.values():
|
||||
if fmt and fmt not in fmts: fmts[fmt] = 0
|
||||
if otype: op_types_set.add(otype)
|
||||
if op_info: types[(name, enc_name)] = op_info
|
||||
return encodings, enums, types, fmts, op_types_set
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# PDF parsing
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def extract_pdf_text(url: str) -> list[list[tuple[float, float, str, str]]]:
|
||||
"""Extract positioned text from PDF. Returns list of text elements (x, y, text, font) per page."""
|
||||
data = fetch(url).read_bytes()
|
||||
# Parse xref table to locate objects
|
||||
xref: dict[int, int] = {}
|
||||
pos = int(re.search(rb'startxref\s+(\d+)', data).group(1)) + 4
|
||||
while data[pos:pos+7] != b'trailer':
|
||||
while data[pos:pos+1] in b' \r\n': pos += 1
|
||||
line_end = data.find(b'\n', pos)
|
||||
start_obj, count = map(int, data[pos:line_end].split()[:2])
|
||||
pos = line_end + 1
|
||||
for i in range(count):
|
||||
if data[pos+17:pos+18] == b'n' and (off := int(data[pos:pos+10])) > 0: xref[start_obj + i] = off
|
||||
pos += 20
|
||||
|
||||
def get_stream(n: int) -> bytes:
|
||||
obj = data[xref[n]:data.find(b'endobj', xref[n])]
|
||||
raw = obj[obj.find(b'stream\n') + 7:obj.find(b'\nendstream')]
|
||||
return zlib.decompress(raw) if b'/FlateDecode' in obj else raw
|
||||
|
||||
pages = []
|
||||
for n in sorted(xref):
|
||||
if b'/Type /Page' not in data[xref[n]:xref[n]+500]: continue
|
||||
if not (m := re.search(rb'/Contents (\d+) 0 R', data[xref[n]:xref[n]+500])): continue
|
||||
stream = get_stream(int(m.group(1))).decode('latin-1')
|
||||
elements, font = [], ''
|
||||
for bt in re.finditer(r'BT(.*?)ET', stream, re.S):
|
||||
x, y = 0.0, 0.0
|
||||
for m in re.finditer(r'(/F[\d.]+) [\d.]+ Tf|([\d.+-]+) ([\d.+-]+) Td|[\d.+-]+ [\d.+-]+ [\d.+-]+ [\d.+-]+ ([\d.+-]+) ([\d.+-]+) Tm|<([0-9A-Fa-f]+)>.*?Tj|\[([^\]]+)\] TJ', bt.group(1)):
|
||||
if m.group(1): font = m.group(1)
|
||||
elif m.group(2): x, y = x + float(m.group(2)), y + float(m.group(3))
|
||||
elif m.group(4): x, y = float(m.group(4)), float(m.group(5))
|
||||
elif m.group(6) and (t := bytes.fromhex(m.group(6)).decode('latin-1')).strip(): elements.append((x, y, t, font))
|
||||
elif m.group(7) and (t := ''.join(bytes.fromhex(h).decode('latin-1') for h in re.findall(r'<([0-9A-Fa-f]+)>', m.group(7)))).strip(): elements.append((x, y, t, font))
|
||||
pages.append(sorted(elements, key=lambda e: (-e[1], e[0])))
|
||||
return pages
|
||||
|
||||
def extract_pcode(pages: list[list[tuple[float, float, str, str]]], name_to_op: dict[str, int]) -> dict[tuple[str, int], str]:
|
||||
"""Extract pseudocode for instructions. Returns {(name, opcode): pseudocode}."""
|
||||
# First pass: find all instruction headers across all pages
|
||||
all_instructions: list[tuple[int, float, str, int]] = [] # (page_idx, y, name, opcode)
|
||||
for page_idx, page in enumerate(pages):
|
||||
by_y: dict[int, list[tuple[float, str]]] = {}
|
||||
for x, y, t, _ in page:
|
||||
by_y.setdefault(round(y), []).append((x, t))
|
||||
for y, items in sorted(by_y.items(), reverse=True):
|
||||
left = [(x, t) for x, t in items if 55 < x < 65]
|
||||
right = [(x, t) for x, t in items if 535 < x < 550]
|
||||
if left and right and left[0][1] in name_to_op and right[0][1].isdigit():
|
||||
all_instructions.append((page_idx, y, left[0][1], int(right[0][1])))
|
||||
|
||||
# Second pass: extract pseudocode between consecutive instructions
|
||||
pcode: dict[tuple[str, int], str] = {}
|
||||
for i, (page_idx, y, name, opcode) in enumerate(all_instructions):
|
||||
if i + 1 < len(all_instructions):
|
||||
next_page, next_y = all_instructions[i + 1][0], all_instructions[i + 1][1]
|
||||
else:
|
||||
next_page, next_y = page_idx, 0
|
||||
# Collect F6 text from current position to next instruction (pseudocode is at x ≈ 69)
|
||||
lines = []
|
||||
for p in range(page_idx, next_page + 1):
|
||||
start_y = y if p == page_idx else 800
|
||||
end_y = next_y if p == next_page else 0
|
||||
lines.extend((p, y2, t) for x, y2, t, f in pages[p] if f in ('/F6.0', '/F7.0') and end_y < y2 < start_y and 60 < x < 80)
|
||||
if lines:
|
||||
sorted_lines = sorted(lines, key=lambda x: (x[0], -x[1]))
|
||||
# Stop at large Y gaps (>30) - indicates section break
|
||||
filtered = [sorted_lines[0]]
|
||||
for j in range(1, len(sorted_lines)):
|
||||
prev_page, prev_y, _ = sorted_lines[j-1]
|
||||
curr_page, curr_y, _ = sorted_lines[j]
|
||||
if curr_page == prev_page and prev_y - curr_y > 30: break
|
||||
if curr_page != prev_page and prev_y > 60 and curr_y < 730: break
|
||||
filtered.append(sorted_lines[j])
|
||||
pcode_lines = [t.replace('Ê', '').strip() for _, _, t in filtered]
|
||||
if pcode_lines: pcode[(name, opcode)] = '\n'.join(pcode_lines)
|
||||
return pcode
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Code generation
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def write_common(all_fmts, all_op_types, path):
|
||||
lines = ["# autogenerated from AMD ISA XML - do not edit", "from enum import Enum, auto", ""]
|
||||
lines.append("class Fmt(Enum):")
|
||||
for fmt in sorted(all_fmts.keys()): lines.append(f" {fmt} = auto()")
|
||||
lines.append("")
|
||||
lines.append("FMT_BITS = {")
|
||||
for fmt, bits in sorted(all_fmts.items()): lines.append(f" Fmt.{fmt}: {bits},")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
lines.append("class OpType(Enum):")
|
||||
for ot in sorted(all_op_types): lines.append(f" {ot} = auto()")
|
||||
with open(path, "w") as f: f.write("\n".join(lines))
|
||||
|
||||
def write_enum(enums, path):
|
||||
lines = ["# autogenerated from AMD ISA XML - do not edit", "from enum import Enum", "from extra.assembly.amd.autogen.common import Fmt, FMT_BITS, OpType # noqa: F401", ""]
|
||||
for name, ops in sorted(enums.items()):
|
||||
if not ops: continue
|
||||
suffix = "_E32" if name in ("VOP1", "VOP2", "VOPC") else "_E64" if name == "VOP3" else ""
|
||||
lines.append(f"class {name}Op(Enum):")
|
||||
aliases = []
|
||||
for op, mem in sorted(ops.items()):
|
||||
msuf = suffix if name != "VOP3" or op < 512 else ""
|
||||
lines.append(f" {mem}{msuf} = {op}")
|
||||
if msuf: aliases.append((mem, msuf))
|
||||
for mem, msuf in aliases: lines.append(f" {mem} = {mem}{msuf}")
|
||||
lines.append("")
|
||||
with open(path, "w") as f: f.write("\n".join(lines))
|
||||
|
||||
def write_ins(encodings, enums, arch, path):
|
||||
_VGPR_FIELDS = {"vdst", "vdstx", "vsrc0", "vsrc1", "vsrc2", "vsrc3", "vsrcx1", "vsrcy1", "vaddr", "vdata", "data", "data0", "data1", "addr"}
|
||||
def field_def(name, hi, lo, fmt, enc_bits=None):
|
||||
bits = hi - lo + 1
|
||||
if name == "encoding" and enc_bits: return f"FixedBitField({hi}, {lo}, 0b{enc_bits})"
|
||||
if name == "op" and fmt not in ("DPP", "SDWA"): return f"EnumBitField({hi}, {lo}, {fmt}Op)"
|
||||
if name in ("opx", "opy"): return f"EnumBitField({hi}, {lo}, VOPDOp)"
|
||||
if name == "vdsty": return f"VDSTYField({hi}, {lo})"
|
||||
if name in _VGPR_FIELDS and bits == 8: return f"VGPRField({hi}, {lo})"
|
||||
if name == "sbase" and bits == 6: return f"SBaseField({hi}, {lo})"
|
||||
if name in ("srsrc", "ssamp") and bits == 5: return f"SRsrcField({hi}, {lo})"
|
||||
if name in ("sdst", "sdata") and bits == 7: return f"SGPRField({hi}, {lo})"
|
||||
if name in ("soffset", "saddr") and bits == 7: return f"SGPRField({hi}, {lo}, default=NULL)"
|
||||
if name.startswith("ssrc") and bits == 8: return f"SSrcField({hi}, {lo})"
|
||||
if name in ("saddr", "soffset") and bits == 8: return f"SSrcField({hi}, {lo}, default=NULL)"
|
||||
if name.startswith("src") and bits == 9: return f"SrcField({hi}, {lo})"
|
||||
if fmt == "VOP3P" and name == "opsel_hi": return f"BitField({hi}, {lo}, default=3)"
|
||||
if fmt == "VOP3P" and name == "opsel_hi2": return f"BitField({hi}, {lo}, default=1)"
|
||||
return f"BitField({hi}, {lo})"
|
||||
ORDER = ['encoding', 'op', 'opx', 'opy', 'vdst', 'vdstx', 'vdsty', 'sdst', 'vdata', 'sdata', 'addr', 'vaddr', 'data', 'data0', 'data1',
|
||||
'src0', 'srcx0', 'srcy0', 'vsrc0', 'ssrc0', 'src1', 'vsrc1', 'vsrcx1', 'vsrcy1', 'ssrc1', 'src2', 'vsrc2', 'src3', 'vsrc3',
|
||||
'saddr', 'sbase', 'srsrc', 'ssamp', 'soffset', 'offset', 'simm16', 'en', 'target', 'attr', 'attr_chan',
|
||||
'omod', 'neg', 'neg_hi', 'abs', 'clmp', 'opsel', 'opsel_hi', 'waitexp', 'wait_va',
|
||||
'dmask', 'dim', 'seg', 'format', 'offen', 'idxen', 'glc', 'dlc', 'slc', 'tfe', 'unrm', 'done', 'row']
|
||||
sort_fields = lambda fields: sorted(fields, key=lambda f: (ORDER.index(f[0]) if f[0] in ORDER else 999, f[2]))
|
||||
|
||||
lines = ["# autogenerated from AMD ISA XML - do not edit", "# ruff: noqa: F401,F403",
|
||||
"from extra.assembly.amd.dsl import *", f"from extra.assembly.amd.autogen.{arch}.enum import *", "import functools", ""]
|
||||
for enc_name, (fields, enc_bits) in sorted(encodings.items()):
|
||||
if enc_name in ("FLAT", "VFLAT"):
|
||||
prefix = "V" if enc_name == "VFLAT" else ""
|
||||
for cls, seg, op_enum in [(f"{prefix}FLAT", 0, f"{prefix}FLATOp"), (f"{prefix}GLOBAL", 2, f"{prefix}GLOBALOp"), (f"{prefix}SCRATCH", 1, f"{prefix}SCRATCHOp")]:
|
||||
lines.append(f"class {cls}(Inst):")
|
||||
for fn, hi, lo in sort_fields(fields):
|
||||
if fn == "seg": lines.append(f" seg = FixedBitField({hi}, {lo}, {seg})")
|
||||
elif fn == "op": lines.append(f" op = EnumBitField({hi}, {lo}, {op_enum})")
|
||||
else: lines.append(f" {fn} = {field_def(fn, hi, lo, cls, enc_bits)}")
|
||||
lines.append("")
|
||||
elif enc_name not in ("FLAT_GLOBAL", "FLAT_SCRATCH", "FLAT_GLBL", "VGLOBAL", "VSCRATCH", "DPP", "SDWA"):
|
||||
lines.append(f"class {enc_name}(Inst):")
|
||||
for fn, hi, lo in sort_fields(fields):
|
||||
lines.append(f" {fn} = {field_def(fn, hi, lo, enc_name, enc_bits if fn == 'encoding' else None)}")
|
||||
lines.append("")
|
||||
# SDST variants
|
||||
for base, field in [("VOP1", "vdst = SSrcField(24, 17)"), ("VOP3", "vdst = SSrcField(7, 0)")]:
|
||||
if base in encodings: lines += [f"class {base}_SDST({base}):", f" {field}", ""]
|
||||
# Instruction helpers
|
||||
lines.append("# instruction helpers")
|
||||
SDST_OPS = {"V_READFIRSTLANE_B32", "V_READLANE_B32"}
|
||||
for fmt, ops in sorted(enums.items()):
|
||||
if fmt not in encodings and fmt not in ("GLOBAL", "SCRATCH", "VGLOBAL", "VSCRATCH"): continue
|
||||
suffix = "_E32" if fmt in ("VOP1", "VOP2", "VOPC") else "_E64" if fmt == "VOP3" else ""
|
||||
for op, name in sorted(ops.items()):
|
||||
msuf = suffix if fmt != "VOP3" or op < 512 else ""
|
||||
cls = "VOP1_SDST" if fmt == "VOP1" and name in SDST_OPS else "VOP3_SDST" if fmt == "VOP3" and (name in SDST_OPS or op < 256) else fmt
|
||||
lines.append(f"{name.lower()}{msuf.lower()} = functools.partial({cls}, {fmt}Op.{name}{msuf})")
|
||||
with open(path, "w") as f: f.write("\n".join(lines))
|
||||
|
||||
def write_operands(types, enums, arch, path):
|
||||
valid = {(name, fmt) for fmt, ops in enums.items() for name in ops.values()}
|
||||
lines = ["# autogenerated from AMD ISA XML - do not edit",
|
||||
"from extra.assembly.amd.autogen.common import Fmt, OpType",
|
||||
f"from extra.assembly.amd.autogen.{arch}.enum import *", ""]
|
||||
lines.append("# instruction operand info: {Op: {field: (Fmt, size_bits, OpType)}}")
|
||||
lines.append("OPERANDS = {")
|
||||
def fmt_val(v):
|
||||
fmt, size, otype = v
|
||||
return f"({f'Fmt.{fmt}' if fmt else 'None'}, {size}, {f'OpType.{otype}' if otype else 'None'})"
|
||||
for (name, enc_base), fields in sorted(types.items()):
|
||||
if (name, enc_base) not in valid: continue
|
||||
fstr = ", ".join(f'"{k}": {fmt_val(v)}' for k, v in sorted(fields.items()))
|
||||
lines.append(f' {enc_base}Op.{name}: {{{fstr}}},')
|
||||
lines.append("}")
|
||||
with open(path, "w") as f: f.write("\n".join(lines))
|
||||
|
||||
def write_pcode(pcode: dict[tuple[str, int], str], enums: dict[str, dict[int, str]], arch: str, path: str):
|
||||
"""Write str_pcode.py file from extracted pseudocode."""
|
||||
entries: list[tuple[str, str, int, str]] = []
|
||||
for fmt_name, ops in enums.items():
|
||||
member_suffix = "_E32" if fmt_name in ("VOP1", "VOP2", "VOPC") else "_E64" if fmt_name == "VOP3" else ""
|
||||
for opcode, name in ops.items():
|
||||
if (name, opcode) in pcode:
|
||||
msuf = member_suffix if fmt_name != "VOP3" or opcode < 512 else ""
|
||||
entries.append((f"{fmt_name}Op", f"{name}{msuf}", opcode, pcode[(name, opcode)]))
|
||||
enum_names = sorted(set(e[0] for e in entries))
|
||||
lines = ["# autogenerated from AMD ISA PDF - do not edit", "# ruff: noqa: E501",
|
||||
f"from extra.assembly.amd.autogen.{arch}.enum import {', '.join(enum_names)}", "", "PCODE = {"]
|
||||
for enum_name, name, opcode, code in sorted(entries, key=lambda x: (x[0], x[2])):
|
||||
lines.append(f" {enum_name}.{name}: {code!r},")
|
||||
lines.append("}")
|
||||
with open(path, "w") as f: f.write("\n".join(lines))
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Main
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pathlib
|
||||
all_fmts, all_op_types, arch_data = {}, set(), {}
|
||||
# First pass: parse XML for all architectures
|
||||
for arch, cfg in ARCHS.items():
|
||||
print(f"Parsing XML: {cfg['xml']} -> {arch}")
|
||||
encodings, enums, types, fmts, op_types_set = parse_xml(cfg["xml"], arch)
|
||||
for fmt, ops in FIXES.get(arch, {}).items(): enums.setdefault(fmt, {}).update(ops)
|
||||
arch_data[arch] = {"encodings": encodings, "enums": enums, "types": types}
|
||||
for fmt, bits in fmts.items():
|
||||
assert fmt not in all_fmts or all_fmts[fmt] == bits, f"FMT_BITS mismatch for {fmt}: {all_fmts[fmt]} vs {bits}"
|
||||
all_fmts[fmt] = bits
|
||||
all_op_types.update(op_types_set)
|
||||
# Write common.py
|
||||
common_path = pathlib.Path(__file__).parent / "autogen" / "common.py"
|
||||
write_common(all_fmts, all_op_types, common_path)
|
||||
print(f"Wrote common.py: {len(all_fmts)} formats, {len(all_op_types)} op types")
|
||||
# Write per-arch files from XML
|
||||
for arch, data in arch_data.items():
|
||||
base = pathlib.Path(__file__).parent / "autogen" / arch
|
||||
write_enum(data["enums"], base / "enum.py")
|
||||
write_ins(data["encodings"], data["enums"], arch, base / "ins.py")
|
||||
write_operands(data["types"], data["enums"], arch, base / "operands.py")
|
||||
print(f" {arch}: {len(data['encodings'])} encodings, {sum(len(v) for v in data['enums'].values())} instructions")
|
||||
# Second pass: parse PDFs and write pcode
|
||||
for arch, cfg in ARCHS.items():
|
||||
print(f"Parsing PDF: {arch}...")
|
||||
pages = extract_pdf_text(cfg["pdf"])
|
||||
name_to_op = {name: op for ops in arch_data[arch]["enums"].values() for op, name in ops.items()}
|
||||
pcode = extract_pcode(pages, name_to_op)
|
||||
base = pathlib.Path(__file__).parent / "autogen" / arch
|
||||
write_pcode(pcode, arch_data[arch]["enums"], arch, base / "str_pcode.py")
|
||||
print(f" {arch}: {len(pcode)} pcode entries")
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
# autogenerated from AMD ISA PDF - do not edit
|
||||
# autogenerated by pdf.py - do not edit
|
||||
# to regenerate: python -m extra.assembly.amd.pdf
|
||||
# ruff: noqa: E501
|
||||
from extra.assembly.amd.autogen.cdna.enum import DSOp, FLATOp, GLOBALOp, MTBUFOp, MUBUFOp, SCRATCHOp, SMEMOp, SOP1Op, SOP2Op, SOPCOp, SOPKOp, SOPPOp, VOP1Op, VOP2Op, VOP3AOp, VOP3BOp, VOP3POp, VOPCOp
|
||||
|
||||
PCODE = {
|
||||
DSOp_PCODE = {
|
||||
DSOp.DS_ADD_U32: 'addr = CalcDsAddr(ADDR.b32, OFFSET0.b32, OFFSET1.b32);\ntmp = MEM[addr].u32;\nMEM[addr].u32 += DATA.u32;\nRETURN_DATA.u32 = tmp',
|
||||
DSOp.DS_SUB_U32: 'addr = CalcDsAddr(ADDR.b32, OFFSET0.b32, OFFSET1.b32);\ntmp = MEM[addr].u32;\nMEM[addr].u32 -= DATA.u32;\nRETURN_DATA.u32 = tmp',
|
||||
DSOp.DS_RSUB_U32: 'addr = CalcDsAddr(ADDR.b32, OFFSET0.b32, OFFSET1.b32);\ntmp = MEM[addr].u32;\nMEM[addr].u32 = DATA.u32 - MEM[addr].u32;\nRETURN_DATA.u32 = tmp',
|
||||
@@ -122,6 +123,9 @@ PCODE = {
|
||||
DSOp.DS_WRITE_B128: 'addr = CalcDsAddr(ADDR.b32, 0x0, 0x0);\nMEM[addr + OFFSET.u32].b32 = DATA[31 : 0];\nMEM[addr + OFFSET.u32 + 4U].b32 = DATA[63 : 32];\nMEM[addr + OFFSET.u32 + 8U].b32 = DATA[95 : 64];\nMEM[addr + OFFSET.u32 + 12U].b32 = DATA[127 : 96]',
|
||||
DSOp.DS_READ_B96: 'addr = CalcDsAddr(ADDR.b32, 0x0, 0x0);\nRETURN_DATA[31 : 0] = MEM[addr + OFFSET.u32].b32;\nRETURN_DATA[63 : 32] = MEM[addr + OFFSET.u32 + 4U].b32;\nRETURN_DATA[95 : 64] = MEM[addr + OFFSET.u32 + 8U].b32',
|
||||
DSOp.DS_READ_B128: 'addr = CalcDsAddr(ADDR.b32, 0x0, 0x0);\nRETURN_DATA[31 : 0] = MEM[addr + OFFSET.u32].b32;\nRETURN_DATA[63 : 32] = MEM[addr + OFFSET.u32 + 4U].b32;\nRETURN_DATA[95 : 64] = MEM[addr + OFFSET.u32 + 8U].b32;\nRETURN_DATA[127 : 96] = MEM[addr + OFFSET.u32 + 12U].b32',
|
||||
}
|
||||
|
||||
FLATOp_PCODE = {
|
||||
FLATOp.FLAT_LOAD_UBYTE: "addr = CalcFlatAddr(ADDR.b32, OFFSET.b32);\nVDATA.u32 = 32'U({ 24'0U, MEM[addr].u8 })",
|
||||
FLATOp.FLAT_LOAD_SBYTE: "addr = CalcFlatAddr(ADDR.b32, OFFSET.b32);\nVDATA.i32 = 32'I(signext(MEM[addr].i8))",
|
||||
FLATOp.FLAT_LOAD_USHORT: "addr = CalcFlatAddr(ADDR.b32, OFFSET.b32);\nVDATA.u32 = 32'U({ 16'0U, MEM[addr].u16 })",
|
||||
@@ -176,6 +180,9 @@ PCODE = {
|
||||
FLATOp.FLAT_ATOMIC_XOR_X2: 'addr = CalcFlatAddr(ADDR.b32, OFFSET.b32);\ntmp = MEM[addr].b64;\nMEM[addr].b64 = (tmp ^ DATA.b64);\nRETURN_DATA.b64 = tmp',
|
||||
FLATOp.FLAT_ATOMIC_INC_X2: 'addr = CalcFlatAddr(ADDR.b32, OFFSET.b32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = tmp >= src ? 0ULL : tmp + 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
FLATOp.FLAT_ATOMIC_DEC_X2: 'addr = CalcFlatAddr(ADDR.b32, OFFSET.b32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = ((tmp == 0ULL) || (tmp > src)) ? src : tmp - 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
}
|
||||
|
||||
GLOBALOp_PCODE = {
|
||||
GLOBALOp.GLOBAL_LOAD_UBYTE: "addr = CalcGlobalAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA.u32 = 32'U({ 24'0U, MEM[addr].u8 })",
|
||||
GLOBALOp.GLOBAL_LOAD_SBYTE: "addr = CalcGlobalAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA.i32 = 32'I(signext(MEM[addr].i8))",
|
||||
GLOBALOp.GLOBAL_LOAD_USHORT: "addr = CalcGlobalAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA.u32 = 32'U({ 16'0U, MEM[addr].u16 })",
|
||||
@@ -230,6 +237,9 @@ PCODE = {
|
||||
GLOBALOp.GLOBAL_ATOMIC_XOR_X2: 'addr = CalcGlobalAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\ntmp = MEM[addr].b64;\nMEM[addr].b64 = (tmp ^ DATA.b64);\nRETURN_DATA.b64 = tmp',
|
||||
GLOBALOp.GLOBAL_ATOMIC_INC_X2: 'addr = CalcGlobalAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = tmp >= src ? 0ULL : tmp + 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
GLOBALOp.GLOBAL_ATOMIC_DEC_X2: 'addr = CalcGlobalAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = ((tmp == 0ULL) || (tmp > src)) ? src : tmp - 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
}
|
||||
|
||||
MTBUFOp_PCODE = {
|
||||
MTBUFOp.TBUFFER_LOAD_FORMAT_X: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nVDATA[31 : 0].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetX()]);\n// Mem access size depends on format',
|
||||
MTBUFOp.TBUFFER_LOAD_FORMAT_XY: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nVDATA[31 : 0].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetX()]);\n// Mem access size depends on format\nVDATA[63 : 32].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetY()])',
|
||||
MTBUFOp.TBUFFER_LOAD_FORMAT_XYZ: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nVDATA[31 : 0].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetX()]);\n// Mem access size depends on format\nVDATA[63 : 32].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetY()]);\nVDATA[95 : 64].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetZ()])',
|
||||
@@ -246,6 +256,9 @@ PCODE = {
|
||||
MTBUFOp.TBUFFER_STORE_FORMAT_D16_XY: "addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nMEM[addr + ChannelOffsetX()] = ConvertToFormat(32'B(VDATA[15 : 0].b16));\n// Mem access size depends on format\nMEM[addr + ChannelOffsetY()] = ConvertToFormat(32'B(VDATA[31 : 16].b16))",
|
||||
MTBUFOp.TBUFFER_STORE_FORMAT_D16_XYZ: "addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nMEM[addr + ChannelOffsetX()] = ConvertToFormat(32'B(VDATA[15 : 0].b16));\n// Mem access size depends on format\nMEM[addr + ChannelOffsetY()] = ConvertToFormat(32'B(VDATA[31 : 16].b16));\nMEM[addr + ChannelOffsetZ()] = ConvertToFormat(32'B(VDATA[47 : 32].b16))",
|
||||
MTBUFOp.TBUFFER_STORE_FORMAT_D16_XYZW: "addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nMEM[addr + ChannelOffsetX()] = ConvertToFormat(32'B(VDATA[15 : 0].b16));\n// Mem access size depends on format\nMEM[addr + ChannelOffsetY()] = ConvertToFormat(32'B(VDATA[31 : 16].b16));\nMEM[addr + ChannelOffsetZ()] = ConvertToFormat(32'B(VDATA[47 : 32].b16));\nMEM[addr + ChannelOffsetW()] = ConvertToFormat(32'B(VDATA[63 : 48].b16))",
|
||||
}
|
||||
|
||||
MUBUFOp_PCODE = {
|
||||
MUBUFOp.BUFFER_LOAD_FORMAT_X: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nVDATA[31 : 0].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetX()]);\n// Mem access size depends on format',
|
||||
MUBUFOp.BUFFER_LOAD_FORMAT_XY: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nVDATA[31 : 0].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetX()]);\n// Mem access size depends on format\nVDATA[63 : 32].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetY()])',
|
||||
MUBUFOp.BUFFER_LOAD_FORMAT_XYZ: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\nVDATA[31 : 0].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetX()]);\n// Mem access size depends on format\nVDATA[63 : 32].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetY()]);\nVDATA[95 : 64].b32 = ConvertFromFormat(MEM[addr + ChannelOffsetZ()])',
|
||||
@@ -318,6 +331,9 @@ PCODE = {
|
||||
MUBUFOp.BUFFER_ATOMIC_XOR_X2: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\ntmp = MEM[addr].b64;\nMEM[addr].b64 = (tmp ^ DATA.b64);\nRETURN_DATA.b64 = tmp',
|
||||
MUBUFOp.BUFFER_ATOMIC_INC_X2: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = tmp >= src ? 0ULL : tmp + 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
MUBUFOp.BUFFER_ATOMIC_DEC_X2: 'addr = CalcBufferAddr(VADDR.b32, SRSRC.b32, SOFFSET.b32, OFFSET.b32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = ((tmp == 0ULL) || (tmp > src)) ? src : tmp - 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
}
|
||||
|
||||
SCRATCHOp_PCODE = {
|
||||
SCRATCHOp.SCRATCH_LOAD_UBYTE: "addr = CalcScratchAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA.u32 = 32'U({ 24'0U, MEM[addr].u8 })",
|
||||
SCRATCHOp.SCRATCH_LOAD_SBYTE: "addr = CalcScratchAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA.i32 = 32'I(signext(MEM[addr].i8))",
|
||||
SCRATCHOp.SCRATCH_LOAD_USHORT: "addr = CalcScratchAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA.u32 = 32'U({ 16'0U, MEM[addr].u16 })",
|
||||
@@ -340,6 +356,9 @@ PCODE = {
|
||||
SCRATCHOp.SCRATCH_LOAD_SBYTE_D16_HI: "addr = CalcScratchAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA[31 : 16].i16 = 16'I(signext(MEM[addr].i8));\n// VDATA[15:0] is preserved.",
|
||||
SCRATCHOp.SCRATCH_LOAD_SHORT_D16: 'addr = CalcScratchAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA[15 : 0].b16 = MEM[addr].b16;\n// VDATA[31:16] is preserved.',
|
||||
SCRATCHOp.SCRATCH_LOAD_SHORT_D16_HI: 'addr = CalcScratchAddr(ADDR.b32, SADDR.b32, OFFSET.b32);\nVDATA[31 : 16].b16 = MEM[addr].b16;\n// VDATA[15:0] is preserved.',
|
||||
}
|
||||
|
||||
SMEMOp_PCODE = {
|
||||
SMEMOp.S_LOAD_DWORD: 'addr = CalcScalarGlobalAddr(SBASE.b32, SOFFSET.b32, OFFSET.i32);\nSDATA[31 : 0] = MEM[addr].b32',
|
||||
SMEMOp.S_LOAD_DWORDX2: 'addr = CalcScalarGlobalAddr(SBASE.b32, SOFFSET.b32, OFFSET.i32);\nSDATA[31 : 0] = MEM[addr].b32;\nSDATA[63 : 32] = MEM[addr + 4U].b32',
|
||||
SMEMOp.S_LOAD_DWORDX4: 'addr = CalcScalarGlobalAddr(SBASE.b32, SOFFSET.b32, OFFSET.i32);\nSDATA[31 : 0] = MEM[addr].b32;\nSDATA[63 : 32] = MEM[addr + 4U].b32;\nSDATA[95 : 64] = MEM[addr + 8U].b32;\nSDATA[127 : 96] = MEM[addr + 12U].b32',
|
||||
@@ -414,6 +433,9 @@ PCODE = {
|
||||
SMEMOp.S_ATOMIC_XOR_X2: 'addr = CalcScalarGlobalAddr(SBASE.b32, SOFFSET.b32, OFFSET.i32);\ntmp = MEM[addr].b64;\nMEM[addr].b64 = (tmp ^ DATA.b64);\nRETURN_DATA.b64 = tmp',
|
||||
SMEMOp.S_ATOMIC_INC_X2: 'addr = CalcScalarGlobalAddr(SBASE.b32, SOFFSET.b32, OFFSET.i32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = tmp >= src ? 0ULL : tmp + 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
SMEMOp.S_ATOMIC_DEC_X2: 'addr = CalcScalarGlobalAddr(SBASE.b32, SOFFSET.b32, OFFSET.i32);\ntmp = MEM[addr].u64;\nsrc = DATA.u64;\nMEM[addr].u64 = ((tmp == 0ULL) || (tmp > src)) ? src : tmp - 1ULL;\nRETURN_DATA.u64 = tmp',
|
||||
}
|
||||
|
||||
SOP1Op_PCODE = {
|
||||
SOP1Op.S_MOV_B32: 'D0.b32 = S0.b32',
|
||||
SOP1Op.S_MOV_B64: 'D0.b64 = S0.b64',
|
||||
SOP1Op.S_CMOV_B32: 'if SCC then\nD0.b32 = S0.b32\nendif',
|
||||
@@ -468,6 +490,9 @@ PCODE = {
|
||||
SOP1Op.S_ANDN1_WREXEC_B64: 'EXEC.u64 = (~S0.u64 & EXEC.u64);\nD0.u64 = EXEC.u64;\nSCC = EXEC.u64 != 0ULL',
|
||||
SOP1Op.S_ANDN2_WREXEC_B64: 'EXEC.u64 = (S0.u64 & ~EXEC.u64);\nD0.u64 = EXEC.u64;\nSCC = EXEC.u64 != 0ULL',
|
||||
SOP1Op.S_BITREPLICATE_B64_B32: 'tmp = S0.u32;\nfor i in 0 : 31 do\nD0.u64[i * 2] = tmp[i];\nD0.u64[i * 2 + 1] = tmp[i]\nendfor',
|
||||
}
|
||||
|
||||
SOP2Op_PCODE = {
|
||||
SOP2Op.S_ADD_U32: "tmp = 64'U(S0.u32) + 64'U(S1.u32);\nSCC = tmp >= 0x100000000ULL ? 1'1U : 1'0U;\n// unsigned overflow or carry-out for S_ADDC_U32.\nD0.u32 = tmp.u32",
|
||||
SOP2Op.S_SUB_U32: "tmp = S0.u32 - S1.u32;\nSCC = S1.u32 > S0.u32 ? 1'1U : 1'0U;\n// unsigned overflow or carry-out for S_SUBB_U32.\nD0.u32 = tmp.u32",
|
||||
SOP2Op.S_ADD_I32: 'tmp = S0.i32 + S1.i32;\nSCC = ((S0.u32[31] == S1.u32[31]) && (S0.u32[31] != tmp.u32[31]));\n// signed overflow.\nD0.i32 = tmp.i32',
|
||||
@@ -520,6 +545,9 @@ PCODE = {
|
||||
SOP2Op.S_PACK_LL_B32_B16: 'D0 = { S1[15 : 0].u16, S0[15 : 0].u16 }',
|
||||
SOP2Op.S_PACK_LH_B32_B16: 'D0 = { S1[31 : 16].u16, S0[15 : 0].u16 }',
|
||||
SOP2Op.S_PACK_HH_B32_B16: 'D0 = { S1[31 : 16].u16, S0[31 : 16].u16 }',
|
||||
}
|
||||
|
||||
SOPCOp_PCODE = {
|
||||
SOPCOp.S_CMP_EQ_I32: 'SCC = S0.i32 == S1.i32',
|
||||
SOPCOp.S_CMP_LG_I32: 'SCC = S0.i32 <> S1.i32',
|
||||
SOPCOp.S_CMP_GT_I32: 'SCC = S0.i32 > S1.i32',
|
||||
@@ -540,6 +568,9 @@ PCODE = {
|
||||
SOPCOp.S_SET_GPR_IDX_ON: "WAVE_MODE.GPR_IDX_EN = 1'1U;\nM0[7 : 0] = S0.u32[7 : 0].b8;\nM0[15 : 12] = SRC1.u32[3 : 0].b4;\n// this is the direct content of raw S1 field\n// Remaining bits of M0 are unmodified.",
|
||||
SOPCOp.S_CMP_EQ_U64: 'SCC = S0.u64 == S1.u64',
|
||||
SOPCOp.S_CMP_LG_U64: 'SCC = S0.u64 <> S1.u64',
|
||||
}
|
||||
|
||||
SOPKOp_PCODE = {
|
||||
SOPKOp.S_MOVK_I32: "D0.i32 = 32'I(signext(S0.i16))",
|
||||
SOPKOp.S_CMOVK_I32: "if SCC then\nD0.i32 = 32'I(signext(S0.i16))\nendif",
|
||||
SOPKOp.S_CMPK_EQ_I32: "SCC = S0.i32 == 32'I(signext(S1.i16))",
|
||||
@@ -561,6 +592,9 @@ PCODE = {
|
||||
SOPKOp.S_SETREG_B32: "hwRegId = SIMM16.u16[5 : 0];\noffset = SIMM16.u16[10 : 6];\nsize = SIMM16.u16[15 : 11].u32 + 1U;\n// logical size is in range 1:32\nmask = (1 << size) - 1;\nmask = (mask << offset.u32);\nmask = (mask & HwRegWriteMask(hwRegId, WAVE_STATUS.PRIV));\n// Mask of bits that can be modified\nvalue = ((S0.u32 << offset.u32) & mask.u32);\nvalue = (value | 32'U(HW_REGISTERS[hwRegId].i32 & ~mask));\nHW_REGISTERS[hwRegId] = value.b32;\n// Side-effects may trigger here if certain bits are modified",
|
||||
SOPKOp.S_SETREG_IMM32_B32: "hwRegId = SIMM16.u16[5 : 0];\noffset = SIMM16.u16[10 : 6];\nsize = SIMM16.u16[15 : 11].u32 + 1U;\n// logical size is in range 1:32\nmask = (1 << size) - 1;\nmask = (mask << offset.u32);\nmask = (mask & HwRegWriteMask(hwRegId, WAVE_STATUS.PRIV));\n// Mask of bits that can be modified\nvalue = ((SIMM32.u32 << offset.u32) & mask.u32);\nvalue = (value | 32'U(HW_REGISTERS[hwRegId].i32 & ~mask));\nHW_REGISTERS[hwRegId] = value.b32;\n// Side-effects may trigger here if certain bits are modified",
|
||||
SOPKOp.S_CALL_B64: "D0.i64 = PC + 4LL;\nPC = PC + signext(SIMM16.i16 * 16'4) + 4LL",
|
||||
}
|
||||
|
||||
SOPPOp_PCODE = {
|
||||
SOPPOp.S_NOP: 'for i in 0U : SIMM16.u16[3 : 0].u32 do\nnop()\nendfor',
|
||||
SOPPOp.S_BRANCH: "PC = PC + signext(SIMM16.i16 * 16'4) + 4LL;\n// short jump.",
|
||||
SOPPOp.S_CBRANCH_SCC0: "if SCC == 1'0U then\nPC = PC + signext(SIMM16.i16 * 16'4) + 4LL\nelse\nPC = PC + 4LL\nendif",
|
||||
@@ -578,150 +612,159 @@ PCODE = {
|
||||
SOPPOp.S_CBRANCH_CDBGSYS_AND_USER: "if (WAVE_STATUS.COND_DBG_SYS && WAVE_STATUS.COND_DBG_USER) then\nPC = PC + signext(SIMM16.i16 * 16'4) + 4LL\nelse\nPC = PC + 4LL\nendif",
|
||||
SOPPOp.S_SET_GPR_IDX_OFF: "WAVE_MODE.GPR_IDX_EN = 1'0U",
|
||||
SOPPOp.S_SET_GPR_IDX_MODE: 'M0[15 : 12] = SIMM16.u16[3 : 0].b4',
|
||||
VOP1Op.V_MOV_B32_E32: 'D0.b32 = S0.b32',
|
||||
VOP1Op.V_READFIRSTLANE_B32_E32: "declare lane : 32'I;\nif EXEC == 0x0LL then\nlane = 0;\n// Force lane 0 if all lanes are disabled\nelse\nlane = s_ff1_i32_b64(EXEC);\n// Lowest active lane\nendif;\nD0.b32 = VGPR[lane][SRC0.u32]",
|
||||
VOP1Op.V_CVT_I32_F64_E32: 'D0.i32 = f64_to_i32(S0.f64)',
|
||||
VOP1Op.V_CVT_F64_I32_E32: 'D0.f64 = i32_to_f64(S0.i32)',
|
||||
VOP1Op.V_CVT_F32_I32_E32: 'D0.f32 = i32_to_f32(S0.i32)',
|
||||
VOP1Op.V_CVT_F32_U32_E32: 'D0.f32 = u32_to_f32(S0.u32)',
|
||||
VOP1Op.V_CVT_U32_F32_E32: 'D0.u32 = f32_to_u32(S0.f32)',
|
||||
VOP1Op.V_CVT_I32_F32_E32: 'D0.i32 = f32_to_i32(S0.f32)',
|
||||
VOP1Op.V_CVT_F16_F32_E32: 'D0.f16 = f32_to_f16(S0.f32)',
|
||||
VOP1Op.V_CVT_F32_F16_E32: 'D0.f32 = f16_to_f32(S0.f16)',
|
||||
VOP1Op.V_CVT_RPI_I32_F32_E32: 'D0.i32 = f32_to_i32(floor(S0.f32 + 0.5F))',
|
||||
VOP1Op.V_CVT_FLR_I32_F32_E32: 'D0.i32 = f32_to_i32(floor(S0.f32))',
|
||||
VOP1Op.V_CVT_OFF_F32_I4_E32: "declare CVT_OFF_TABLE : 32'F[16];\nD0.f32 = CVT_OFF_TABLE[S0.u32[3 : 0]]",
|
||||
VOP1Op.V_CVT_F32_F64_E32: 'D0.f32 = f64_to_f32(S0.f64)',
|
||||
VOP1Op.V_CVT_F64_F32_E32: 'D0.f64 = f32_to_f64(S0.f32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE0_E32: 'D0.f32 = u32_to_f32(S0[7 : 0].u32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE1_E32: 'D0.f32 = u32_to_f32(S0[15 : 8].u32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE2_E32: 'D0.f32 = u32_to_f32(S0[23 : 16].u32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE3_E32: 'D0.f32 = u32_to_f32(S0[31 : 24].u32)',
|
||||
VOP1Op.V_CVT_U32_F64_E32: 'D0.u32 = f64_to_u32(S0.f64)',
|
||||
VOP1Op.V_CVT_F64_U32_E32: 'D0.f64 = u32_to_f64(S0.u32)',
|
||||
VOP1Op.V_TRUNC_F64_E32: 'D0.f64 = trunc(S0.f64)',
|
||||
VOP1Op.V_CEIL_F64_E32: 'D0.f64 = trunc(S0.f64);\nif ((S0.f64 > 0.0) && (S0.f64 != D0.f64)) then\nD0.f64 += 1.0\nendif',
|
||||
VOP1Op.V_RNDNE_F64_E32: 'D0.f64 = floor(S0.f64 + 0.5);\nif (isEven(floor(S0.f64)) && (fract(S0.f64) == 0.5)) then\nD0.f64 -= 1.0\nendif',
|
||||
VOP1Op.V_FLOOR_F64_E32: 'D0.f64 = trunc(S0.f64);\nif ((S0.f64 < 0.0) && (S0.f64 != D0.f64)) then\nD0.f64 += -1.0\nendif',
|
||||
VOP1Op.V_FRACT_F32_E32: 'D0.f32 = S0.f32 + -floor(S0.f32)',
|
||||
VOP1Op.V_TRUNC_F32_E32: 'D0.f32 = trunc(S0.f32)',
|
||||
VOP1Op.V_CEIL_F32_E32: 'D0.f32 = trunc(S0.f32);\nif ((S0.f32 > 0.0F) && (S0.f32 != D0.f32)) then\nD0.f32 += 1.0F\nendif',
|
||||
VOP1Op.V_RNDNE_F32_E32: "D0.f32 = floor(S0.f32 + 0.5F);\nif (isEven(64'F(floor(S0.f32))) && (fract(S0.f32) == 0.5F)) then\nD0.f32 -= 1.0F\nendif",
|
||||
VOP1Op.V_FLOOR_F32_E32: 'D0.f32 = trunc(S0.f32);\nif ((S0.f32 < 0.0F) && (S0.f32 != D0.f32)) then\nD0.f32 += -1.0F\nendif',
|
||||
VOP1Op.V_EXP_F32_E32: 'D0.f32 = pow(2.0F, S0.f32)',
|
||||
VOP1Op.V_LOG_F32_E32: 'D0.f32 = log2(S0.f32)',
|
||||
VOP1Op.V_RCP_F32_E32: 'D0.f32 = 1.0F / S0.f32',
|
||||
VOP1Op.V_RCP_IFLAG_F32_E32: 'D0.f32 = 1.0F / S0.f32;\n// Can only raise integer DIV_BY_ZERO exception',
|
||||
VOP1Op.V_RSQ_F32_E32: 'D0.f32 = 1.0F / sqrt(S0.f32)',
|
||||
VOP1Op.V_RCP_F64_E32: 'D0.f64 = 1.0 / S0.f64',
|
||||
VOP1Op.V_RSQ_F64_E32: 'D0.f64 = 1.0 / sqrt(S0.f64)',
|
||||
VOP1Op.V_SQRT_F32_E32: 'D0.f32 = sqrt(S0.f32)',
|
||||
VOP1Op.V_SQRT_F64_E32: 'D0.f64 = sqrt(S0.f64)',
|
||||
VOP1Op.V_SIN_F32_E32: "D0.f32 = sin(S0.f32 * 32'F(PI * 2.0))",
|
||||
VOP1Op.V_COS_F32_E32: "D0.f32 = cos(S0.f32 * 32'F(PI * 2.0))",
|
||||
VOP1Op.V_NOT_B32_E32: 'D0.u32 = ~S0.u32',
|
||||
VOP1Op.V_BFREV_B32_E32: 'D0.u32[31 : 0] = S0.u32[0 : 31]',
|
||||
VOP1Op.V_FFBH_U32_E32: "D0.i32 = -1;\n// Set if no ones are found\nfor i in 0 : 31 do\n// Search from MSB\nif S0.u32[31 - i] == 1'1U then\nD0.i32 = i;\nbreak\nendif\nendfor",
|
||||
VOP1Op.V_FFBL_B32_E32: "D0.i32 = -1;\n// Set if no ones are found\nfor i in 0 : 31 do\n// Search from LSB\nif S0.u32[i] == 1'1U then\nD0.i32 = i;\nbreak\nendif\nendfor",
|
||||
VOP1Op.V_FFBH_I32_E32: 'D0.i32 = -1;\n// Set if all bits are the same\nfor i in 1 : 31 do\n// Search from MSB\nif S0.i32[31 - i] != S0.i32[31] then\nD0.i32 = i;\nbreak\nendif\nendfor',
|
||||
VOP1Op.V_FREXP_EXP_I32_F64_E32: 'if ((S0.f64 == +INF) || (S0.f64 == -INF) || isNAN(S0.f64)) then\nD0.i32 = 0\nelse\nD0.i32 = exponent(S0.f64) - 1023 + 1\nendif',
|
||||
VOP1Op.V_FREXP_MANT_F64_E32: 'if ((S0.f64 == +INF) || (S0.f64 == -INF) || isNAN(S0.f64)) then\nD0.f64 = S0.f64\nelse\nD0.f64 = mantissa(S0.f64)\nendif',
|
||||
VOP1Op.V_FRACT_F64_E32: 'D0.f64 = S0.f64 + -floor(S0.f64)',
|
||||
VOP1Op.V_FREXP_EXP_I32_F32_E32: "if ((64'F(S0.f32) == +INF) || (64'F(S0.f32) == -INF) || isNAN(64'F(S0.f32))) then\nD0.i32 = 0\nelse\nD0.i32 = exponent(S0.f32) - 127 + 1\nendif",
|
||||
VOP1Op.V_FREXP_MANT_F32_E32: "if ((64'F(S0.f32) == +INF) || (64'F(S0.f32) == -INF) || isNAN(64'F(S0.f32))) then\nD0.f32 = S0.f32\nelse\nD0.f32 = mantissa(S0.f32)\nendif",
|
||||
VOP1Op.V_MOV_B64_E32: 'D0.b64 = S0.b64',
|
||||
VOP1Op.V_CVT_F16_U16_E32: 'D0.f16 = u16_to_f16(S0.u16)',
|
||||
VOP1Op.V_CVT_F16_I16_E32: 'D0.f16 = i16_to_f16(S0.i16)',
|
||||
VOP1Op.V_CVT_U16_F16_E32: 'D0.u16 = f16_to_u16(S0.f16)',
|
||||
VOP1Op.V_CVT_I16_F16_E32: 'D0.i16 = f16_to_i16(S0.f16)',
|
||||
VOP1Op.V_RCP_F16_E32: "D0.f16 = 16'1.0 / S0.f16",
|
||||
VOP1Op.V_SQRT_F16_E32: 'D0.f16 = sqrt(S0.f16)',
|
||||
VOP1Op.V_RSQ_F16_E32: "D0.f16 = 16'1.0 / sqrt(S0.f16)",
|
||||
VOP1Op.V_LOG_F16_E32: 'D0.f16 = log2(S0.f16)',
|
||||
VOP1Op.V_EXP_F16_E32: "D0.f16 = pow(16'2.0, S0.f16)",
|
||||
VOP1Op.V_FREXP_MANT_F16_E32: "if ((64'F(S0.f16) == +INF) || (64'F(S0.f16) == -INF) || isNAN(64'F(S0.f16))) then\nD0.f16 = S0.f16\nelse\nD0.f16 = mantissa(S0.f16)\nendif",
|
||||
VOP1Op.V_FREXP_EXP_I16_F16_E32: "if ((64'F(S0.f16) == +INF) || (64'F(S0.f16) == -INF) || isNAN(64'F(S0.f16))) then\nD0.i16 = 16'0\nelse\nD0.i16 = 16'I(exponent(S0.f16) - 15 + 1)\nendif",
|
||||
VOP1Op.V_FLOOR_F16_E32: "D0.f16 = trunc(S0.f16);\nif ((S0.f16 < 16'0.0) && (S0.f16 != D0.f16)) then\nD0.f16 += -16'1.0\nendif",
|
||||
VOP1Op.V_CEIL_F16_E32: "D0.f16 = trunc(S0.f16);\nif ((S0.f16 > 16'0.0) && (S0.f16 != D0.f16)) then\nD0.f16 += 16'1.0\nendif",
|
||||
VOP1Op.V_TRUNC_F16_E32: 'D0.f16 = trunc(S0.f16)',
|
||||
VOP1Op.V_RNDNE_F16_E32: "D0.f16 = floor(S0.f16 + 16'0.5);\nif (isEven(64'F(floor(S0.f16))) && (fract(S0.f16) == 16'0.5)) then\nD0.f16 -= 16'1.0\nendif",
|
||||
VOP1Op.V_FRACT_F16_E32: 'D0.f16 = S0.f16 + -floor(S0.f16)',
|
||||
VOP1Op.V_SIN_F16_E32: "D0.f16 = sin(S0.f16 * 16'F(PI * 2.0))",
|
||||
VOP1Op.V_COS_F16_E32: "D0.f16 = cos(S0.f16 * 16'F(PI * 2.0))",
|
||||
VOP1Op.V_CVT_NORM_I16_F16_E32: 'D0.i16 = f16_to_snorm(S0.f16)',
|
||||
VOP1Op.V_CVT_NORM_U16_F16_E32: 'D0.u16 = f16_to_unorm(S0.f16)',
|
||||
VOP1Op.V_SAT_PK_U8_I16_E32: "SAT8 = lambda(n) (\nif n <= 16'0 then\nreturn 8'0U\nelsif n >= 16'255 then\nreturn 8'255U\nelse\nreturn n[7 : 0].u8\nendif);\ntmp = 16'0;\ntmp[7 : 0].u8 = SAT8(S0[15 : 0].i16);\ntmp[15 : 8].u8 = SAT8(S0[31 : 16].i16);\nD0.b16 = tmp.b16",
|
||||
VOP1Op.V_SWAP_B32_E32: 'tmp = D0.b32;\nD0.b32 = S0.b32;\nS0.b32 = tmp',
|
||||
VOP1Op.V_CVT_F32_FP8_E32: 'if SDWA_SRC0_SEL == BYTE1.b3 then\nD0.f32 = fp8_to_f32(S0[15 : 8].fp8)\nelsif SDWA_SRC0_SEL == BYTE2.b3 then\nD0.f32 = fp8_to_f32(S0[23 : 16].fp8)\nelsif SDWA_SRC0_SEL == BYTE3.b3 then\nD0.f32 = fp8_to_f32(S0[31 : 24].fp8)\nelse\n// BYTE0 implied\nD0.f32 = fp8_to_f32(S0[7 : 0].fp8)\nendif',
|
||||
VOP1Op.V_CVT_F32_BF8_E32: 'if SDWA_SRC0_SEL == BYTE1.b3 then\nD0.f32 = bf8_to_f32(S0[15 : 8].bf8)\nelsif SDWA_SRC0_SEL == BYTE2.b3 then\nD0.f32 = bf8_to_f32(S0[23 : 16].bf8)\nelsif SDWA_SRC0_SEL == BYTE3.b3 then\nD0.f32 = bf8_to_f32(S0[31 : 24].bf8)\nelse\n// BYTE0 implied\nD0.f32 = bf8_to_f32(S0[7 : 0].bf8)\nendif',
|
||||
VOP1Op.V_CVT_PK_F32_FP8_E32: 'tmp = SDWA_SRC0_SEL[1 : 0] == WORD1.b2 ? S0[31 : 16] : S0[15 : 0];\nD0[31 : 0].f32 = fp8_to_f32(tmp[7 : 0].fp8);\nD0[63 : 32].f32 = fp8_to_f32(tmp[15 : 8].fp8)',
|
||||
VOP1Op.V_CVT_PK_F32_BF8_E32: 'tmp = SDWA_SRC0_SEL[1 : 0] == WORD1.b2 ? S0[31 : 16] : S0[15 : 0];\nD0[31 : 0].f32 = bf8_to_f32(tmp[7 : 0].bf8);\nD0[63 : 32].f32 = bf8_to_f32(tmp[15 : 8].bf8)',
|
||||
VOP1Op.V_PRNG_B32_E32: 'in = S0.u32;\nD0.u32 = ((in << 1U) ^ (in[31] ? 197U : 0U))',
|
||||
VOP1Op.V_PERMLANE16_SWAP_B32_E32: 'for pass in 0 : 1 do\nfor lane in 0 : 15 do\ntmp = VGPR[pass * 32 + lane][SRC0.u32];\nVGPR[pass * 32 + lane][SRC0.u32] = VGPR[pass * 32 + lane + 16][VDST.u32];\nVGPR[pass * 32 + lane + 16][VDST.u32] = tmp\nendfor\nendfor',
|
||||
VOP1Op.V_PERMLANE32_SWAP_B32_E32: 'for lane in 0 : 31 do\ntmp = VGPR[lane][SRC0.u32];\nVGPR[lane][SRC0.u32] = VGPR[lane + 32][VDST.u32];\nVGPR[lane + 32][VDST.u32] = tmp\nendfor',
|
||||
VOP1Op.V_CVT_F32_BF16_E32: "D0.f32 = 32'F({ S0.b16, 16'0U })",
|
||||
VOP2Op.V_CNDMASK_B32_E32: 'D0.u32 = VCC.u64[laneId] ? S1.u32 : S0.u32',
|
||||
VOP2Op.V_ADD_F32_E32: 'D0.f32 = S0.f32 + S1.f32',
|
||||
VOP2Op.V_SUB_F32_E32: 'D0.f32 = S0.f32 - S1.f32',
|
||||
VOP2Op.V_SUBREV_F32_E32: 'D0.f32 = S1.f32 - S0.f32',
|
||||
VOP2Op.V_FMAC_F64_E32: 'D0.f64 = fma(S0.f64, S1.f64, D0.f64)',
|
||||
VOP2Op.V_MUL_F32_E32: 'D0.f32 = S0.f32 * S1.f32',
|
||||
VOP2Op.V_MUL_I32_I24_E32: "D0.i32 = 32'I(S0.i24) * 32'I(S1.i24)",
|
||||
VOP2Op.V_MUL_HI_I32_I24_E32: "D0.i32 = 32'I((64'I(S0.i24) * 64'I(S1.i24)) >> 32U)",
|
||||
VOP2Op.V_MUL_U32_U24_E32: "D0.u32 = 32'U(S0.u24) * 32'U(S1.u24)",
|
||||
VOP2Op.V_MUL_HI_U32_U24_E32: "D0.u32 = 32'U((64'U(S0.u24) * 64'U(S1.u24)) >> 32U)",
|
||||
VOP2Op.V_MIN_F32_E32: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S0.f32)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S1.f32)))\nelsif isNAN(64'F(S0.f32)) then\nD0.f32 = S1.f32\nelsif isNAN(64'F(S1.f32)) then\nD0.f32 = S0.f32\nelsif ((64'F(S0.f32) == +0.0) && (64'F(S1.f32) == -0.0)) then\nD0.f32 = S1.f32\nelsif ((64'F(S0.f32) == -0.0) && (64'F(S1.f32) == +0.0)) then\nD0.f32 = S0.f32\nelse\n// Note: there's no IEEE case here like there is for V_MAX_F32.\nD0.f32 = S0.f32 < S1.f32 ? S0.f32 : S1.f32\nendif",
|
||||
VOP2Op.V_MAX_F32_E32: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S0.f32)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S1.f32)))\nelsif isNAN(64'F(S0.f32)) then\nD0.f32 = S1.f32\nelsif isNAN(64'F(S1.f32)) then\nD0.f32 = S0.f32\nelsif ((64'F(S0.f32) == +0.0) && (64'F(S1.f32) == -0.0)) then\nD0.f32 = S0.f32\nelsif ((64'F(S0.f32) == -0.0) && (64'F(S1.f32) == +0.0)) then\nD0.f32 = S1.f32\nelsif WAVE_MODE.IEEE then\nD0.f32 = S0.f32 >= S1.f32 ? S0.f32 : S1.f32\nelse\nD0.f32 = S0.f32 > S1.f32 ? S0.f32 : S1.f32\nendif",
|
||||
VOP2Op.V_MIN_I32_E32: 'D0.i32 = S0.i32 < S1.i32 ? S0.i32 : S1.i32',
|
||||
VOP2Op.V_MAX_I32_E32: 'D0.i32 = S0.i32 >= S1.i32 ? S0.i32 : S1.i32',
|
||||
VOP2Op.V_MIN_U32_E32: 'D0.u32 = S0.u32 < S1.u32 ? S0.u32 : S1.u32',
|
||||
VOP2Op.V_MAX_U32_E32: 'D0.u32 = S0.u32 >= S1.u32 ? S0.u32 : S1.u32',
|
||||
VOP2Op.V_LSHRREV_B32_E32: 'D0.u32 = (S1.u32 >> S0[4 : 0].u32)',
|
||||
VOP2Op.V_ASHRREV_I32_E32: 'D0.i32 = (S1.i32 >> S0[4 : 0].u32)',
|
||||
VOP2Op.V_LSHLREV_B32_E32: 'D0.u32 = (S1.u32 << S0[4 : 0].u32)',
|
||||
VOP2Op.V_AND_B32_E32: 'D0.u32 = (S0.u32 & S1.u32)',
|
||||
VOP2Op.V_OR_B32_E32: 'D0.u32 = (S0.u32 | S1.u32)',
|
||||
VOP2Op.V_XOR_B32_E32: 'D0.u32 = (S0.u32 ^ S1.u32)',
|
||||
VOP2Op.V_DOT2C_F32_BF16_E32: 'tmp = D0.f32;\ntmp += bf16_to_f32(S0[15 : 0].bf16) * bf16_to_f32(S1[15 : 0].bf16);\ntmp += bf16_to_f32(S0[31 : 16].bf16) * bf16_to_f32(S1[31 : 16].bf16);\nD0.f32 = tmp',
|
||||
VOP2Op.V_FMAMK_F32_E32: 'D0.f32 = fma(S0.f32, SIMM32.f32, S1.f32)',
|
||||
VOP2Op.V_FMAAK_F32_E32: 'D0.f32 = fma(S0.f32, S1.f32, SIMM32.f32)',
|
||||
VOP2Op.V_ADD_CO_U32_E32: "tmp = 64'U(S0.u32) + 64'U(S1.u32);\nVCC.u64[laneId] = tmp >= 0x100000000ULL ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_ADDC_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUB_CO_U32_E32: "tmp = S0.u32 - S1.u32;\nVCC.u64[laneId] = S1.u32 > S0.u32 ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUBREV_CO_U32_E32: "tmp = S1.u32 - S0.u32;\nVCC.u64[laneId] = S0.u32 > S1.u32 ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_ADDC_CO_U32_E32: "tmp = 64'U(S0.u32) + 64'U(S1.u32) + VCC.u64[laneId].u64;\nVCC.u64[laneId] = tmp >= 0x100000000ULL ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_ADDC_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUBB_CO_U32_E32: "tmp = S0.u32 - S1.u32 - VCC.u64[laneId].u32;\nVCC.u64[laneId] = 64'U(S1.u32) + VCC.u64[laneId].u64 > 64'U(S0.u32) ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUBBREV_CO_U32_E32: "tmp = S1.u32 - S0.u32 - VCC.u64[laneId].u32;\nVCC.u64[laneId] = 64'U(S0.u32) + VCC.u64[laneId].u64 > 64'U(S1.u32) ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_ADD_F16_E32: 'D0.f16 = S0.f16 + S1.f16',
|
||||
VOP2Op.V_SUB_F16_E32: 'D0.f16 = S0.f16 - S1.f16',
|
||||
VOP2Op.V_SUBREV_F16_E32: 'D0.f16 = S1.f16 - S0.f16',
|
||||
VOP2Op.V_MUL_F16_E32: 'D0.f16 = S0.f16 * S1.f16',
|
||||
VOP2Op.V_MAC_F16_E32: "tmp = S0.f16 * S1.f16 + D0.f16;\nif OPSEL.u4[3] then\nD0 = { tmp.f16, D0[15 : 0] }\nelse\nD0 = { 16'0, tmp.f16 }\nendif",
|
||||
VOP2Op.V_MADMK_F16_E32: "tmp = S0.f16 * SIMM16.f16 + S1.f16;\nD0 = { 16'0, tmp.f16 }",
|
||||
VOP2Op.V_MADAK_F16_E32: "tmp = S0.f16 * S1.f16 + SIMM16.f16;\nD0 = { 16'0, tmp.f16 }",
|
||||
VOP2Op.V_ADD_U16_E32: 'D0.u16 = S0.u16 + S1.u16',
|
||||
VOP2Op.V_SUB_U16_E32: 'D0.u16 = S0.u16 - S1.u16',
|
||||
VOP2Op.V_SUBREV_U16_E32: 'D0.u16 = S1.u16 - S0.u16',
|
||||
VOP2Op.V_MUL_LO_U16_E32: 'D0.u16 = S0.u16 * S1.u16',
|
||||
VOP2Op.V_LSHLREV_B16_E32: 'D0.u16 = (S1.u16 << S0[3 : 0].u32)',
|
||||
VOP2Op.V_LSHRREV_B16_E32: 'D0.u16 = (S1.u16 >> S0[3 : 0].u32)',
|
||||
VOP2Op.V_ASHRREV_I16_E32: 'D0.i16 = (S1.i16 >> S0[3 : 0].u32)',
|
||||
VOP2Op.V_MAX_F16_E32: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S0.f16)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S1.f16)))\nelsif isNAN(64'F(S0.f16)) then\nD0.f16 = S1.f16\nelsif isNAN(64'F(S1.f16)) then\nD0.f16 = S0.f16\nelsif ((64'F(S0.f16) == +0.0) && (64'F(S1.f16) == -0.0)) then\nD0.f16 = S0.f16\nelsif ((64'F(S0.f16) == -0.0) && (64'F(S1.f16) == +0.0)) then\nD0.f16 = S1.f16\nelsif WAVE_MODE.IEEE then\nD0.f16 = S0.f16 >= S1.f16 ? S0.f16 : S1.f16\nelse\nD0.f16 = S0.f16 > S1.f16 ? S0.f16 : S1.f16\nendif",
|
||||
VOP2Op.V_MIN_F16_E32: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S0.f16)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S1.f16)))\nelsif isNAN(64'F(S0.f16)) then\nD0.f16 = S1.f16\nelsif isNAN(64'F(S1.f16)) then\nD0.f16 = S0.f16\nelsif ((64'F(S0.f16) == +0.0) && (64'F(S1.f16) == -0.0)) then\nD0.f16 = S1.f16\nelsif ((64'F(S0.f16) == -0.0) && (64'F(S1.f16) == +0.0)) then\nD0.f16 = S0.f16\nelse\n// Note: there's no IEEE case here like there is for V_MAX_F16.\nD0.f16 = S0.f16 < S1.f16 ? S0.f16 : S1.f16\nendif",
|
||||
VOP2Op.V_MAX_U16_E32: 'D0.u16 = S0.u16 >= S1.u16 ? S0.u16 : S1.u16',
|
||||
VOP2Op.V_MAX_I16_E32: 'D0.i16 = S0.i16 >= S1.i16 ? S0.i16 : S1.i16',
|
||||
VOP2Op.V_MIN_U16_E32: 'D0.u16 = S0.u16 < S1.u16 ? S0.u16 : S1.u16',
|
||||
VOP2Op.V_MIN_I16_E32: 'D0.i16 = S0.i16 < S1.i16 ? S0.i16 : S1.i16',
|
||||
VOP2Op.V_LDEXP_F16_E32: "D0.f16 = S0.f16 * 16'F(2.0F ** 32'I(S1.i16))",
|
||||
VOP2Op.V_ADD_U32_E32: 'D0.u32 = S0.u32 + S1.u32',
|
||||
VOP2Op.V_SUB_U32_E32: 'D0.u32 = S0.u32 - S1.u32',
|
||||
VOP2Op.V_SUBREV_U32_E32: 'D0.u32 = S1.u32 - S0.u32',
|
||||
VOP2Op.V_DOT2C_F32_F16_E32: 'tmp = D0.f32;\ntmp += f16_to_f32(S0[15 : 0].f16) * f16_to_f32(S1[15 : 0].f16);\ntmp += f16_to_f32(S0[31 : 16].f16) * f16_to_f32(S1[31 : 16].f16);\nD0.f32 = tmp',
|
||||
VOP2Op.V_DOT2C_I32_I16_E32: 'tmp = D0.i32;\ntmp += i16_to_i32(S0[15 : 0].i16) * i16_to_i32(S1[15 : 0].i16);\ntmp += i16_to_i32(S0[31 : 16].i16) * i16_to_i32(S1[31 : 16].i16);\nD0.i32 = tmp',
|
||||
VOP2Op.V_DOT4C_I32_I8_E32: 'tmp = D0.i32;\ntmp += i8_to_i32(S0[7 : 0].i8) * i8_to_i32(S1[7 : 0].i8);\ntmp += i8_to_i32(S0[15 : 8].i8) * i8_to_i32(S1[15 : 8].i8);\ntmp += i8_to_i32(S0[23 : 16].i8) * i8_to_i32(S1[23 : 16].i8);\ntmp += i8_to_i32(S0[31 : 24].i8) * i8_to_i32(S1[31 : 24].i8);\nD0.i32 = tmp',
|
||||
VOP2Op.V_DOT8C_I32_I4_E32: 'tmp = D0.i32;\ntmp += i4_to_i32(S0[3 : 0].i4) * i4_to_i32(S1[3 : 0].i4);\ntmp += i4_to_i32(S0[7 : 4].i4) * i4_to_i32(S1[7 : 4].i4);\ntmp += i4_to_i32(S0[11 : 8].i4) * i4_to_i32(S1[11 : 8].i4);\ntmp += i4_to_i32(S0[15 : 12].i4) * i4_to_i32(S1[15 : 12].i4);\ntmp += i4_to_i32(S0[19 : 16].i4) * i4_to_i32(S1[19 : 16].i4);\ntmp += i4_to_i32(S0[23 : 20].i4) * i4_to_i32(S1[23 : 20].i4);\ntmp += i4_to_i32(S0[27 : 24].i4) * i4_to_i32(S1[27 : 24].i4);\ntmp += i4_to_i32(S0[31 : 28].i4) * i4_to_i32(S1[31 : 28].i4);\nD0.i32 = tmp',
|
||||
VOP2Op.V_FMAC_F32_E32: 'D0.f32 = fma(S0.f32, S1.f32, D0.f32)',
|
||||
VOP2Op.V_PK_FMAC_F16_E32: 'D0[15 : 0].f16 = fma(S0[15 : 0].f16, S1[15 : 0].f16, D0[15 : 0].f16);\nD0[31 : 16].f16 = fma(S0[31 : 16].f16, S1[31 : 16].f16, D0[31 : 16].f16)',
|
||||
VOP2Op.V_XNOR_B32_E32: 'D0.u32 = ~(S0.u32 ^ S1.u32)',
|
||||
}
|
||||
|
||||
VOP1Op_PCODE = {
|
||||
VOP1Op.V_MOV_B32: 'D0.b32 = S0.b32',
|
||||
VOP1Op.V_READFIRSTLANE_B32: "declare lane : 32'I;\nif EXEC == 0x0LL then\nlane = 0;\n// Force lane 0 if all lanes are disabled\nelse\nlane = s_ff1_i32_b64(EXEC);\n// Lowest active lane\nendif;\nD0.b32 = VGPR[lane][SRC0.u32]",
|
||||
VOP1Op.V_CVT_I32_F64: 'D0.i32 = f64_to_i32(S0.f64)',
|
||||
VOP1Op.V_CVT_F64_I32: 'D0.f64 = i32_to_f64(S0.i32)',
|
||||
VOP1Op.V_CVT_F32_I32: 'D0.f32 = i32_to_f32(S0.i32)',
|
||||
VOP1Op.V_CVT_F32_U32: 'D0.f32 = u32_to_f32(S0.u32)',
|
||||
VOP1Op.V_CVT_U32_F32: 'D0.u32 = f32_to_u32(S0.f32)',
|
||||
VOP1Op.V_CVT_I32_F32: 'D0.i32 = f32_to_i32(S0.f32)',
|
||||
VOP1Op.V_CVT_F16_F32: 'D0.f16 = f32_to_f16(S0.f32)',
|
||||
VOP1Op.V_CVT_F32_F16: 'D0.f32 = f16_to_f32(S0.f16)',
|
||||
VOP1Op.V_CVT_RPI_I32_F32: 'D0.i32 = f32_to_i32(floor(S0.f32 + 0.5F))',
|
||||
VOP1Op.V_CVT_FLR_I32_F32: 'D0.i32 = f32_to_i32(floor(S0.f32))',
|
||||
VOP1Op.V_CVT_OFF_F32_I4: "declare CVT_OFF_TABLE : 32'F[16];\nD0.f32 = CVT_OFF_TABLE[S0.u32[3 : 0]]",
|
||||
VOP1Op.V_CVT_F32_F64: 'D0.f32 = f64_to_f32(S0.f64)',
|
||||
VOP1Op.V_CVT_F64_F32: 'D0.f64 = f32_to_f64(S0.f32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE0: 'D0.f32 = u32_to_f32(S0[7 : 0].u32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE1: 'D0.f32 = u32_to_f32(S0[15 : 8].u32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE2: 'D0.f32 = u32_to_f32(S0[23 : 16].u32)',
|
||||
VOP1Op.V_CVT_F32_UBYTE3: 'D0.f32 = u32_to_f32(S0[31 : 24].u32)',
|
||||
VOP1Op.V_CVT_U32_F64: 'D0.u32 = f64_to_u32(S0.f64)',
|
||||
VOP1Op.V_CVT_F64_U32: 'D0.f64 = u32_to_f64(S0.u32)',
|
||||
VOP1Op.V_TRUNC_F64: 'D0.f64 = trunc(S0.f64)',
|
||||
VOP1Op.V_CEIL_F64: 'D0.f64 = trunc(S0.f64);\nif ((S0.f64 > 0.0) && (S0.f64 != D0.f64)) then\nD0.f64 += 1.0\nendif',
|
||||
VOP1Op.V_RNDNE_F64: 'D0.f64 = floor(S0.f64 + 0.5);\nif (isEven(floor(S0.f64)) && (fract(S0.f64) == 0.5)) then\nD0.f64 -= 1.0\nendif',
|
||||
VOP1Op.V_FLOOR_F64: 'D0.f64 = trunc(S0.f64);\nif ((S0.f64 < 0.0) && (S0.f64 != D0.f64)) then\nD0.f64 += -1.0\nendif',
|
||||
VOP1Op.V_FRACT_F32: 'D0.f32 = S0.f32 + -floor(S0.f32)',
|
||||
VOP1Op.V_TRUNC_F32: 'D0.f32 = trunc(S0.f32)',
|
||||
VOP1Op.V_CEIL_F32: 'D0.f32 = trunc(S0.f32);\nif ((S0.f32 > 0.0F) && (S0.f32 != D0.f32)) then\nD0.f32 += 1.0F\nendif',
|
||||
VOP1Op.V_RNDNE_F32: "D0.f32 = floor(S0.f32 + 0.5F);\nif (isEven(64'F(floor(S0.f32))) && (fract(S0.f32) == 0.5F)) then\nD0.f32 -= 1.0F\nendif",
|
||||
VOP1Op.V_FLOOR_F32: 'D0.f32 = trunc(S0.f32);\nif ((S0.f32 < 0.0F) && (S0.f32 != D0.f32)) then\nD0.f32 += -1.0F\nendif',
|
||||
VOP1Op.V_EXP_F32: 'D0.f32 = pow(2.0F, S0.f32)',
|
||||
VOP1Op.V_LOG_F32: 'D0.f32 = log2(S0.f32)',
|
||||
VOP1Op.V_RCP_F32: 'D0.f32 = 1.0F / S0.f32',
|
||||
VOP1Op.V_RCP_IFLAG_F32: 'D0.f32 = 1.0F / S0.f32;\n// Can only raise integer DIV_BY_ZERO exception',
|
||||
VOP1Op.V_RSQ_F32: 'D0.f32 = 1.0F / sqrt(S0.f32)',
|
||||
VOP1Op.V_RCP_F64: 'D0.f64 = 1.0 / S0.f64',
|
||||
VOP1Op.V_RSQ_F64: 'D0.f64 = 1.0 / sqrt(S0.f64)',
|
||||
VOP1Op.V_SQRT_F32: 'D0.f32 = sqrt(S0.f32)',
|
||||
VOP1Op.V_SQRT_F64: 'D0.f64 = sqrt(S0.f64)',
|
||||
VOP1Op.V_SIN_F32: "D0.f32 = sin(S0.f32 * 32'F(PI * 2.0))",
|
||||
VOP1Op.V_COS_F32: "D0.f32 = cos(S0.f32 * 32'F(PI * 2.0))",
|
||||
VOP1Op.V_NOT_B32: 'D0.u32 = ~S0.u32',
|
||||
VOP1Op.V_BFREV_B32: 'D0.u32[31 : 0] = S0.u32[0 : 31]',
|
||||
VOP1Op.V_FFBH_U32: "D0.i32 = -1;\n// Set if no ones are found\nfor i in 0 : 31 do\n// Search from MSB\nif S0.u32[31 - i] == 1'1U then\nD0.i32 = i;\nbreak\nendif\nendfor",
|
||||
VOP1Op.V_FFBL_B32: "D0.i32 = -1;\n// Set if no ones are found\nfor i in 0 : 31 do\n// Search from LSB\nif S0.u32[i] == 1'1U then\nD0.i32 = i;\nbreak\nendif\nendfor",
|
||||
VOP1Op.V_FFBH_I32: 'D0.i32 = -1;\n// Set if all bits are the same\nfor i in 1 : 31 do\n// Search from MSB\nif S0.i32[31 - i] != S0.i32[31] then\nD0.i32 = i;\nbreak\nendif\nendfor',
|
||||
VOP1Op.V_FREXP_EXP_I32_F64: 'if ((S0.f64 == +INF) || (S0.f64 == -INF) || isNAN(S0.f64)) then\nD0.i32 = 0\nelse\nD0.i32 = exponent(S0.f64) - 1023 + 1\nendif',
|
||||
VOP1Op.V_FREXP_MANT_F64: 'if ((S0.f64 == +INF) || (S0.f64 == -INF) || isNAN(S0.f64)) then\nD0.f64 = S0.f64\nelse\nD0.f64 = mantissa(S0.f64)\nendif',
|
||||
VOP1Op.V_FRACT_F64: 'D0.f64 = S0.f64 + -floor(S0.f64)',
|
||||
VOP1Op.V_FREXP_EXP_I32_F32: "if ((64'F(S0.f32) == +INF) || (64'F(S0.f32) == -INF) || isNAN(64'F(S0.f32))) then\nD0.i32 = 0\nelse\nD0.i32 = exponent(S0.f32) - 127 + 1\nendif",
|
||||
VOP1Op.V_FREXP_MANT_F32: "if ((64'F(S0.f32) == +INF) || (64'F(S0.f32) == -INF) || isNAN(64'F(S0.f32))) then\nD0.f32 = S0.f32\nelse\nD0.f32 = mantissa(S0.f32)\nendif",
|
||||
VOP1Op.V_MOV_B64: 'D0.b64 = S0.b64',
|
||||
VOP1Op.V_CVT_F16_U16: 'D0.f16 = u16_to_f16(S0.u16)',
|
||||
VOP1Op.V_CVT_F16_I16: 'D0.f16 = i16_to_f16(S0.i16)',
|
||||
VOP1Op.V_CVT_U16_F16: 'D0.u16 = f16_to_u16(S0.f16)',
|
||||
VOP1Op.V_CVT_I16_F16: 'D0.i16 = f16_to_i16(S0.f16)',
|
||||
VOP1Op.V_RCP_F16: "D0.f16 = 16'1.0 / S0.f16",
|
||||
VOP1Op.V_SQRT_F16: 'D0.f16 = sqrt(S0.f16)',
|
||||
VOP1Op.V_RSQ_F16: "D0.f16 = 16'1.0 / sqrt(S0.f16)",
|
||||
VOP1Op.V_LOG_F16: 'D0.f16 = log2(S0.f16)',
|
||||
VOP1Op.V_EXP_F16: "D0.f16 = pow(16'2.0, S0.f16)",
|
||||
VOP1Op.V_FREXP_MANT_F16: "if ((64'F(S0.f16) == +INF) || (64'F(S0.f16) == -INF) || isNAN(64'F(S0.f16))) then\nD0.f16 = S0.f16\nelse\nD0.f16 = mantissa(S0.f16)\nendif",
|
||||
VOP1Op.V_FREXP_EXP_I16_F16: "if ((64'F(S0.f16) == +INF) || (64'F(S0.f16) == -INF) || isNAN(64'F(S0.f16))) then\nD0.i16 = 16'0\nelse\nD0.i16 = 16'I(exponent(S0.f16) - 15 + 1)\nendif",
|
||||
VOP1Op.V_FLOOR_F16: "D0.f16 = trunc(S0.f16);\nif ((S0.f16 < 16'0.0) && (S0.f16 != D0.f16)) then\nD0.f16 += -16'1.0\nendif",
|
||||
VOP1Op.V_CEIL_F16: "D0.f16 = trunc(S0.f16);\nif ((S0.f16 > 16'0.0) && (S0.f16 != D0.f16)) then\nD0.f16 += 16'1.0\nendif",
|
||||
VOP1Op.V_TRUNC_F16: 'D0.f16 = trunc(S0.f16)',
|
||||
VOP1Op.V_RNDNE_F16: "D0.f16 = floor(S0.f16 + 16'0.5);\nif (isEven(64'F(floor(S0.f16))) && (fract(S0.f16) == 16'0.5)) then\nD0.f16 -= 16'1.0\nendif",
|
||||
VOP1Op.V_FRACT_F16: 'D0.f16 = S0.f16 + -floor(S0.f16)',
|
||||
VOP1Op.V_SIN_F16: "D0.f16 = sin(S0.f16 * 16'F(PI * 2.0))",
|
||||
VOP1Op.V_COS_F16: "D0.f16 = cos(S0.f16 * 16'F(PI * 2.0))",
|
||||
VOP1Op.V_CVT_NORM_I16_F16: 'D0.i16 = f16_to_snorm(S0.f16)',
|
||||
VOP1Op.V_CVT_NORM_U16_F16: 'D0.u16 = f16_to_unorm(S0.f16)',
|
||||
VOP1Op.V_SAT_PK_U8_I16: "SAT8 = lambda(n) (\nif n <= 16'0 then\nreturn 8'0U\nelsif n >= 16'255 then\nreturn 8'255U\nelse\nreturn n[7 : 0].u8\nendif);\ntmp = 16'0;\ntmp[7 : 0].u8 = SAT8(S0[15 : 0].i16);\ntmp[15 : 8].u8 = SAT8(S0[31 : 16].i16);\nD0.b16 = tmp.b16",
|
||||
VOP1Op.V_SWAP_B32: 'tmp = D0.b32;\nD0.b32 = S0.b32;\nS0.b32 = tmp',
|
||||
VOP1Op.V_CVT_F32_FP8: 'if SDWA_SRC0_SEL == BYTE1.b3 then\nD0.f32 = fp8_to_f32(S0[15 : 8].fp8)\nelsif SDWA_SRC0_SEL == BYTE2.b3 then\nD0.f32 = fp8_to_f32(S0[23 : 16].fp8)\nelsif SDWA_SRC0_SEL == BYTE3.b3 then\nD0.f32 = fp8_to_f32(S0[31 : 24].fp8)\nelse\n// BYTE0 implied\nD0.f32 = fp8_to_f32(S0[7 : 0].fp8)\nendif',
|
||||
VOP1Op.V_CVT_F32_BF8: 'if SDWA_SRC0_SEL == BYTE1.b3 then\nD0.f32 = bf8_to_f32(S0[15 : 8].bf8)\nelsif SDWA_SRC0_SEL == BYTE2.b3 then\nD0.f32 = bf8_to_f32(S0[23 : 16].bf8)\nelsif SDWA_SRC0_SEL == BYTE3.b3 then\nD0.f32 = bf8_to_f32(S0[31 : 24].bf8)\nelse\n// BYTE0 implied\nD0.f32 = bf8_to_f32(S0[7 : 0].bf8)\nendif',
|
||||
VOP1Op.V_CVT_PK_F32_FP8: 'tmp = SDWA_SRC0_SEL[1 : 0] == WORD1.b2 ? S0[31 : 16] : S0[15 : 0];\nD0[31 : 0].f32 = fp8_to_f32(tmp[7 : 0].fp8);\nD0[63 : 32].f32 = fp8_to_f32(tmp[15 : 8].fp8)',
|
||||
VOP1Op.V_CVT_PK_F32_BF8: 'tmp = SDWA_SRC0_SEL[1 : 0] == WORD1.b2 ? S0[31 : 16] : S0[15 : 0];\nD0[31 : 0].f32 = bf8_to_f32(tmp[7 : 0].bf8);\nD0[63 : 32].f32 = bf8_to_f32(tmp[15 : 8].bf8)',
|
||||
VOP1Op.V_PRNG_B32: 'in = S0.u32;\nD0.u32 = ((in << 1U) ^ (in[31] ? 197U : 0U))',
|
||||
VOP1Op.V_PERMLANE16_SWAP_B32: 'for pass in 0 : 1 do\nfor lane in 0 : 15 do\ntmp = VGPR[pass * 32 + lane][SRC0.u32];\nVGPR[pass * 32 + lane][SRC0.u32] = VGPR[pass * 32 + lane + 16][VDST.u32];\nVGPR[pass * 32 + lane + 16][VDST.u32] = tmp\nendfor\nendfor',
|
||||
VOP1Op.V_PERMLANE32_SWAP_B32: 'for lane in 0 : 31 do\ntmp = VGPR[lane][SRC0.u32];\nVGPR[lane][SRC0.u32] = VGPR[lane + 32][VDST.u32];\nVGPR[lane + 32][VDST.u32] = tmp\nendfor',
|
||||
VOP1Op.V_CVT_F32_BF16: "D0.f32 = 32'F({ S0.b16, 16'0U })",
|
||||
}
|
||||
|
||||
VOP2Op_PCODE = {
|
||||
VOP2Op.V_CNDMASK_B32: 'D0.u32 = VCC.u64[laneId] ? S1.u32 : S0.u32',
|
||||
VOP2Op.V_ADD_F32: 'D0.f32 = S0.f32 + S1.f32',
|
||||
VOP2Op.V_SUB_F32: 'D0.f32 = S0.f32 - S1.f32',
|
||||
VOP2Op.V_SUBREV_F32: 'D0.f32 = S1.f32 - S0.f32',
|
||||
VOP2Op.V_FMAC_F64: 'D0.f64 = fma(S0.f64, S1.f64, D0.f64)',
|
||||
VOP2Op.V_MUL_F32: 'D0.f32 = S0.f32 * S1.f32',
|
||||
VOP2Op.V_MUL_I32_I24: "D0.i32 = 32'I(S0.i24) * 32'I(S1.i24)",
|
||||
VOP2Op.V_MUL_HI_I32_I24: "D0.i32 = 32'I((64'I(S0.i24) * 64'I(S1.i24)) >> 32U)",
|
||||
VOP2Op.V_MUL_U32_U24: "D0.u32 = 32'U(S0.u24) * 32'U(S1.u24)",
|
||||
VOP2Op.V_MUL_HI_U32_U24: "D0.u32 = 32'U((64'U(S0.u24) * 64'U(S1.u24)) >> 32U)",
|
||||
VOP2Op.V_MIN_F32: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S0.f32)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S1.f32)))\nelsif isNAN(64'F(S0.f32)) then\nD0.f32 = S1.f32\nelsif isNAN(64'F(S1.f32)) then\nD0.f32 = S0.f32\nelsif ((64'F(S0.f32) == +0.0) && (64'F(S1.f32) == -0.0)) then\nD0.f32 = S1.f32\nelsif ((64'F(S0.f32) == -0.0) && (64'F(S1.f32) == +0.0)) then\nD0.f32 = S0.f32\nelse\n// Note: there's no IEEE case here like there is for V_MAX_F32.\nD0.f32 = S0.f32 < S1.f32 ? S0.f32 : S1.f32\nendif",
|
||||
VOP2Op.V_MAX_F32: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S0.f32)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f32))) then\nD0.f32 = 32'F(cvtToQuietNAN(64'F(S1.f32)))\nelsif isNAN(64'F(S0.f32)) then\nD0.f32 = S1.f32\nelsif isNAN(64'F(S1.f32)) then\nD0.f32 = S0.f32\nelsif ((64'F(S0.f32) == +0.0) && (64'F(S1.f32) == -0.0)) then\nD0.f32 = S0.f32\nelsif ((64'F(S0.f32) == -0.0) && (64'F(S1.f32) == +0.0)) then\nD0.f32 = S1.f32\nelsif WAVE_MODE.IEEE then\nD0.f32 = S0.f32 >= S1.f32 ? S0.f32 : S1.f32\nelse\nD0.f32 = S0.f32 > S1.f32 ? S0.f32 : S1.f32\nendif",
|
||||
VOP2Op.V_MIN_I32: 'D0.i32 = S0.i32 < S1.i32 ? S0.i32 : S1.i32',
|
||||
VOP2Op.V_MAX_I32: 'D0.i32 = S0.i32 >= S1.i32 ? S0.i32 : S1.i32',
|
||||
VOP2Op.V_MIN_U32: 'D0.u32 = S0.u32 < S1.u32 ? S0.u32 : S1.u32',
|
||||
VOP2Op.V_MAX_U32: 'D0.u32 = S0.u32 >= S1.u32 ? S0.u32 : S1.u32',
|
||||
VOP2Op.V_LSHRREV_B32: 'D0.u32 = (S1.u32 >> S0[4 : 0].u32)',
|
||||
VOP2Op.V_ASHRREV_I32: 'D0.i32 = (S1.i32 >> S0[4 : 0].u32)',
|
||||
VOP2Op.V_LSHLREV_B32: 'D0.u32 = (S1.u32 << S0[4 : 0].u32)',
|
||||
VOP2Op.V_AND_B32: 'D0.u32 = (S0.u32 & S1.u32)',
|
||||
VOP2Op.V_OR_B32: 'D0.u32 = (S0.u32 | S1.u32)',
|
||||
VOP2Op.V_XOR_B32: 'D0.u32 = (S0.u32 ^ S1.u32)',
|
||||
VOP2Op.V_DOT2C_F32_BF16: 'tmp = D0.f32;\ntmp += bf16_to_f32(S0[15 : 0].bf16) * bf16_to_f32(S1[15 : 0].bf16);\ntmp += bf16_to_f32(S0[31 : 16].bf16) * bf16_to_f32(S1[31 : 16].bf16);\nD0.f32 = tmp',
|
||||
VOP2Op.V_FMAMK_F32: 'D0.f32 = fma(S0.f32, SIMM32.f32, S1.f32)',
|
||||
VOP2Op.V_FMAAK_F32: 'D0.f32 = fma(S0.f32, S1.f32, SIMM32.f32)',
|
||||
VOP2Op.V_ADD_CO_U32: "tmp = 64'U(S0.u32) + 64'U(S1.u32);\nVCC.u64[laneId] = tmp >= 0x100000000ULL ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_ADDC_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUB_CO_U32: "tmp = S0.u32 - S1.u32;\nVCC.u64[laneId] = S1.u32 > S0.u32 ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUBREV_CO_U32: "tmp = S1.u32 - S0.u32;\nVCC.u64[laneId] = S0.u32 > S1.u32 ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_ADDC_CO_U32: "tmp = 64'U(S0.u32) + 64'U(S1.u32) + VCC.u64[laneId].u64;\nVCC.u64[laneId] = tmp >= 0x100000000ULL ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_ADDC_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUBB_CO_U32: "tmp = S0.u32 - S1.u32 - VCC.u64[laneId].u32;\nVCC.u64[laneId] = 64'U(S1.u32) + VCC.u64[laneId].u64 > 64'U(S0.u32) ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_SUBBREV_CO_U32: "tmp = S1.u32 - S0.u32 - VCC.u64[laneId].u32;\nVCC.u64[laneId] = 64'U(S0.u32) + VCC.u64[laneId].u64 > 64'U(S1.u32) ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP2Op.V_ADD_F16: 'D0.f16 = S0.f16 + S1.f16',
|
||||
VOP2Op.V_SUB_F16: 'D0.f16 = S0.f16 - S1.f16',
|
||||
VOP2Op.V_SUBREV_F16: 'D0.f16 = S1.f16 - S0.f16',
|
||||
VOP2Op.V_MUL_F16: 'D0.f16 = S0.f16 * S1.f16',
|
||||
VOP2Op.V_MAC_F16: "tmp = S0.f16 * S1.f16 + D0.f16;\nif OPSEL.u4[3] then\nD0 = { tmp.f16, D0[15 : 0] }\nelse\nD0 = { 16'0, tmp.f16 }\nendif",
|
||||
VOP2Op.V_MADMK_F16: "tmp = S0.f16 * SIMM16.f16 + S1.f16;\nD0 = { 16'0, tmp.f16 }",
|
||||
VOP2Op.V_MADAK_F16: "tmp = S0.f16 * S1.f16 + SIMM16.f16;\nD0 = { 16'0, tmp.f16 }",
|
||||
VOP2Op.V_ADD_U16: 'D0.u16 = S0.u16 + S1.u16',
|
||||
VOP2Op.V_SUB_U16: 'D0.u16 = S0.u16 - S1.u16',
|
||||
VOP2Op.V_SUBREV_U16: 'D0.u16 = S1.u16 - S0.u16',
|
||||
VOP2Op.V_MUL_LO_U16: 'D0.u16 = S0.u16 * S1.u16',
|
||||
VOP2Op.V_LSHLREV_B16: 'D0.u16 = (S1.u16 << S0[3 : 0].u32)',
|
||||
VOP2Op.V_LSHRREV_B16: 'D0.u16 = (S1.u16 >> S0[3 : 0].u32)',
|
||||
VOP2Op.V_ASHRREV_I16: 'D0.i16 = (S1.i16 >> S0[3 : 0].u32)',
|
||||
VOP2Op.V_MAX_F16: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S0.f16)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S1.f16)))\nelsif isNAN(64'F(S0.f16)) then\nD0.f16 = S1.f16\nelsif isNAN(64'F(S1.f16)) then\nD0.f16 = S0.f16\nelsif ((64'F(S0.f16) == +0.0) && (64'F(S1.f16) == -0.0)) then\nD0.f16 = S0.f16\nelsif ((64'F(S0.f16) == -0.0) && (64'F(S1.f16) == +0.0)) then\nD0.f16 = S1.f16\nelsif WAVE_MODE.IEEE then\nD0.f16 = S0.f16 >= S1.f16 ? S0.f16 : S1.f16\nelse\nD0.f16 = S0.f16 > S1.f16 ? S0.f16 : S1.f16\nendif",
|
||||
VOP2Op.V_MIN_F16: "if (WAVE_MODE.IEEE && isSignalNAN(64'F(S0.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S0.f16)))\nelsif (WAVE_MODE.IEEE && isSignalNAN(64'F(S1.f16))) then\nD0.f16 = 16'F(cvtToQuietNAN(64'F(S1.f16)))\nelsif isNAN(64'F(S0.f16)) then\nD0.f16 = S1.f16\nelsif isNAN(64'F(S1.f16)) then\nD0.f16 = S0.f16\nelsif ((64'F(S0.f16) == +0.0) && (64'F(S1.f16) == -0.0)) then\nD0.f16 = S1.f16\nelsif ((64'F(S0.f16) == -0.0) && (64'F(S1.f16) == +0.0)) then\nD0.f16 = S0.f16\nelse\n// Note: there's no IEEE case here like there is for V_MAX_F16.\nD0.f16 = S0.f16 < S1.f16 ? S0.f16 : S1.f16\nendif",
|
||||
VOP2Op.V_MAX_U16: 'D0.u16 = S0.u16 >= S1.u16 ? S0.u16 : S1.u16',
|
||||
VOP2Op.V_MAX_I16: 'D0.i16 = S0.i16 >= S1.i16 ? S0.i16 : S1.i16',
|
||||
VOP2Op.V_MIN_U16: 'D0.u16 = S0.u16 < S1.u16 ? S0.u16 : S1.u16',
|
||||
VOP2Op.V_MIN_I16: 'D0.i16 = S0.i16 < S1.i16 ? S0.i16 : S1.i16',
|
||||
VOP2Op.V_LDEXP_F16: "D0.f16 = S0.f16 * 16'F(2.0F ** 32'I(S1.i16))",
|
||||
VOP2Op.V_ADD_U32: 'D0.u32 = S0.u32 + S1.u32',
|
||||
VOP2Op.V_SUB_U32: 'D0.u32 = S0.u32 - S1.u32',
|
||||
VOP2Op.V_SUBREV_U32: 'D0.u32 = S1.u32 - S0.u32',
|
||||
VOP2Op.V_DOT2C_F32_F16: 'tmp = D0.f32;\ntmp += f16_to_f32(S0[15 : 0].f16) * f16_to_f32(S1[15 : 0].f16);\ntmp += f16_to_f32(S0[31 : 16].f16) * f16_to_f32(S1[31 : 16].f16);\nD0.f32 = tmp',
|
||||
VOP2Op.V_DOT2C_I32_I16: 'tmp = D0.i32;\ntmp += i16_to_i32(S0[15 : 0].i16) * i16_to_i32(S1[15 : 0].i16);\ntmp += i16_to_i32(S0[31 : 16].i16) * i16_to_i32(S1[31 : 16].i16);\nD0.i32 = tmp',
|
||||
VOP2Op.V_DOT4C_I32_I8: 'tmp = D0.i32;\ntmp += i8_to_i32(S0[7 : 0].i8) * i8_to_i32(S1[7 : 0].i8);\ntmp += i8_to_i32(S0[15 : 8].i8) * i8_to_i32(S1[15 : 8].i8);\ntmp += i8_to_i32(S0[23 : 16].i8) * i8_to_i32(S1[23 : 16].i8);\ntmp += i8_to_i32(S0[31 : 24].i8) * i8_to_i32(S1[31 : 24].i8);\nD0.i32 = tmp',
|
||||
VOP2Op.V_DOT8C_I32_I4: 'tmp = D0.i32;\ntmp += i4_to_i32(S0[3 : 0].i4) * i4_to_i32(S1[3 : 0].i4);\ntmp += i4_to_i32(S0[7 : 4].i4) * i4_to_i32(S1[7 : 4].i4);\ntmp += i4_to_i32(S0[11 : 8].i4) * i4_to_i32(S1[11 : 8].i4);\ntmp += i4_to_i32(S0[15 : 12].i4) * i4_to_i32(S1[15 : 12].i4);\ntmp += i4_to_i32(S0[19 : 16].i4) * i4_to_i32(S1[19 : 16].i4);\ntmp += i4_to_i32(S0[23 : 20].i4) * i4_to_i32(S1[23 : 20].i4);\ntmp += i4_to_i32(S0[27 : 24].i4) * i4_to_i32(S1[27 : 24].i4);\ntmp += i4_to_i32(S0[31 : 28].i4) * i4_to_i32(S1[31 : 28].i4);\nD0.i32 = tmp',
|
||||
VOP2Op.V_FMAC_F32: 'D0.f32 = fma(S0.f32, S1.f32, D0.f32)',
|
||||
VOP2Op.V_PK_FMAC_F16: 'D0[15 : 0].f16 = fma(S0[15 : 0].f16, S1[15 : 0].f16, D0[15 : 0].f16);\nD0[31 : 16].f16 = fma(S0[31 : 16].f16, S1[31 : 16].f16, D0[31 : 16].f16)',
|
||||
VOP2Op.V_XNOR_B32: 'D0.u32 = ~(S0.u32 ^ S1.u32)',
|
||||
}
|
||||
|
||||
VOP3AOp_PCODE = {
|
||||
VOP3AOp.V_CMP_CLASS_F32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f32)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f32)) then\nresult = S1.u32[1]\nelsif exponent(S0.f32) == 255 then\n// +-INF\nresult = S1.u32[sign(S0.f32) ? 2 : 9]\nelsif exponent(S0.f32) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f32) ? 3 : 8]\nelsif 64'F(abs(S0.f32)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f32) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f32) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
VOP3AOp.V_CMPX_CLASS_F32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f32)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f32)) then\nresult = S1.u32[1]\nelsif exponent(S0.f32) == 255 then\n// +-INF\nresult = S1.u32[sign(S0.f32) ? 2 : 9]\nelsif exponent(S0.f32) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f32) ? 3 : 8]\nelsif 64'F(abs(S0.f32)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f32) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f32) ? 5 : 6]\nendif;\nEXEC.u64[laneId] = D0.u64[laneId] = result",
|
||||
VOP3AOp.V_CMP_CLASS_F64: "declare result : 1'U;\nif isSignalNAN(S0.f64) then\nresult = S1.u32[0]\nelsif isQuietNAN(S0.f64) then\nresult = S1.u32[1]\nelsif exponent(S0.f64) == 2047 then\n// +-INF\nresult = S1.u32[sign(S0.f64) ? 2 : 9]\nelsif exponent(S0.f64) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f64) ? 3 : 8]\nelsif abs(S0.f64) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f64) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f64) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
@@ -972,6 +1015,67 @@ PCODE = {
|
||||
VOP3AOp.V_FMAC_F32: 'D0.f32 = fma(S0.f32, S1.f32, D0.f32)',
|
||||
VOP3AOp.V_PK_FMAC_F16: 'D0[15 : 0].f16 = fma(S0[15 : 0].f16, S1[15 : 0].f16, D0[15 : 0].f16);\nD0[31 : 16].f16 = fma(S0[31 : 16].f16, S1[31 : 16].f16, D0[31 : 16].f16)',
|
||||
VOP3AOp.V_XNOR_B32: 'D0.u32 = ~(S0.u32 ^ S1.u32)',
|
||||
VOP3AOp.V_MOV_B32: 'D0.b32 = S0.b32',
|
||||
VOP3AOp.V_READFIRSTLANE_B32: "declare lane : 32'I;\nif EXEC == 0x0LL then\nlane = 0;\n// Force lane 0 if all lanes are disabled\nelse\nlane = s_ff1_i32_b64(EXEC);\n// Lowest active lane\nendif;\nD0.b32 = VGPR[lane][SRC0.u32]",
|
||||
VOP3AOp.V_CVT_I32_F64: 'D0.i32 = f64_to_i32(S0.f64)',
|
||||
VOP3AOp.V_CVT_F64_I32: 'D0.f64 = i32_to_f64(S0.i32)',
|
||||
VOP3AOp.V_CVT_F32_I32: 'D0.f32 = i32_to_f32(S0.i32)',
|
||||
VOP3AOp.V_CVT_F32_U32: 'D0.f32 = u32_to_f32(S0.u32)',
|
||||
VOP3AOp.V_CVT_U32_F32: 'D0.u32 = f32_to_u32(S0.f32)',
|
||||
VOP3AOp.V_CVT_I32_F32: 'D0.i32 = f32_to_i32(S0.f32)',
|
||||
VOP3AOp.V_CVT_F16_F32: 'D0.f16 = f32_to_f16(S0.f32)',
|
||||
VOP3AOp.V_CVT_F32_F16: 'D0.f32 = f16_to_f32(S0.f16)',
|
||||
VOP3AOp.V_CVT_RPI_I32_F32: 'D0.i32 = f32_to_i32(floor(S0.f32 + 0.5F))',
|
||||
VOP3AOp.V_CVT_FLR_I32_F32: 'D0.i32 = f32_to_i32(floor(S0.f32))',
|
||||
VOP3AOp.V_CVT_OFF_F32_I4: "declare CVT_OFF_TABLE : 32'F[16];\nD0.f32 = CVT_OFF_TABLE[S0.u32[3 : 0]]",
|
||||
VOP3AOp.V_CVT_F32_F64: 'D0.f32 = f64_to_f32(S0.f64)',
|
||||
VOP3AOp.V_CVT_F64_F32: 'D0.f64 = f32_to_f64(S0.f32)',
|
||||
VOP3AOp.V_CVT_F32_UBYTE0: 'D0.f32 = u32_to_f32(S0[7 : 0].u32)',
|
||||
VOP3AOp.V_CVT_F32_UBYTE1: 'D0.f32 = u32_to_f32(S0[15 : 8].u32)',
|
||||
VOP3AOp.V_CVT_F32_UBYTE2: 'D0.f32 = u32_to_f32(S0[23 : 16].u32)',
|
||||
VOP3AOp.V_CVT_F32_UBYTE3: 'D0.f32 = u32_to_f32(S0[31 : 24].u32)',
|
||||
VOP3AOp.V_CVT_U32_F64: 'D0.u32 = f64_to_u32(S0.f64)',
|
||||
VOP3AOp.V_CVT_F64_U32: 'D0.f64 = u32_to_f64(S0.u32)',
|
||||
VOP3AOp.V_TRUNC_F64: 'D0.f64 = trunc(S0.f64)',
|
||||
VOP3AOp.V_CEIL_F64: 'D0.f64 = trunc(S0.f64);\nif ((S0.f64 > 0.0) && (S0.f64 != D0.f64)) then\nD0.f64 += 1.0\nendif',
|
||||
VOP3AOp.V_RNDNE_F64: 'D0.f64 = floor(S0.f64 + 0.5);\nif (isEven(floor(S0.f64)) && (fract(S0.f64) == 0.5)) then\nD0.f64 -= 1.0\nendif',
|
||||
VOP3AOp.V_FLOOR_F64: 'D0.f64 = trunc(S0.f64);\nif ((S0.f64 < 0.0) && (S0.f64 != D0.f64)) then\nD0.f64 += -1.0\nendif',
|
||||
VOP3AOp.V_FRACT_F32: 'D0.f32 = S0.f32 + -floor(S0.f32)',
|
||||
VOP3AOp.V_TRUNC_F32: 'D0.f32 = trunc(S0.f32)',
|
||||
VOP3AOp.V_CEIL_F32: 'D0.f32 = trunc(S0.f32);\nif ((S0.f32 > 0.0F) && (S0.f32 != D0.f32)) then\nD0.f32 += 1.0F\nendif',
|
||||
VOP3AOp.V_RNDNE_F32: "D0.f32 = floor(S0.f32 + 0.5F);\nif (isEven(64'F(floor(S0.f32))) && (fract(S0.f32) == 0.5F)) then\nD0.f32 -= 1.0F\nendif",
|
||||
VOP3AOp.V_FLOOR_F32: 'D0.f32 = trunc(S0.f32);\nif ((S0.f32 < 0.0F) && (S0.f32 != D0.f32)) then\nD0.f32 += -1.0F\nendif',
|
||||
VOP3AOp.V_EXP_F32: 'D0.f32 = pow(2.0F, S0.f32)',
|
||||
VOP3AOp.V_LOG_F32: 'D0.f32 = log2(S0.f32)',
|
||||
VOP3AOp.V_RCP_F32: 'D0.f32 = 1.0F / S0.f32',
|
||||
VOP3AOp.V_RCP_IFLAG_F32: 'D0.f32 = 1.0F / S0.f32;\n// Can only raise integer DIV_BY_ZERO exception',
|
||||
VOP3AOp.V_RSQ_F32: 'D0.f32 = 1.0F / sqrt(S0.f32)',
|
||||
VOP3AOp.V_RCP_F64: 'D0.f64 = 1.0 / S0.f64',
|
||||
VOP3AOp.V_RSQ_F64: 'D0.f64 = 1.0 / sqrt(S0.f64)',
|
||||
VOP3AOp.V_SQRT_F32: 'D0.f32 = sqrt(S0.f32)',
|
||||
VOP3AOp.V_SQRT_F64: 'D0.f64 = sqrt(S0.f64)',
|
||||
VOP3AOp.V_SIN_F32: "D0.f32 = sin(S0.f32 * 32'F(PI * 2.0))",
|
||||
VOP3AOp.V_COS_F32: "D0.f32 = cos(S0.f32 * 32'F(PI * 2.0))",
|
||||
VOP3AOp.V_NOT_B32: 'D0.u32 = ~S0.u32',
|
||||
VOP3AOp.V_BFREV_B32: 'D0.u32[31 : 0] = S0.u32[0 : 31]',
|
||||
VOP3AOp.V_FFBH_U32: "D0.i32 = -1;\n// Set if no ones are found\nfor i in 0 : 31 do\n// Search from MSB\nif S0.u32[31 - i] == 1'1U then\nD0.i32 = i;\nbreak\nendif\nendfor",
|
||||
VOP3AOp.V_FFBL_B32: "D0.i32 = -1;\n// Set if no ones are found\nfor i in 0 : 31 do\n// Search from LSB\nif S0.u32[i] == 1'1U then\nD0.i32 = i;\nbreak\nendif\nendfor",
|
||||
VOP3AOp.V_FFBH_I32: 'D0.i32 = -1;\n// Set if all bits are the same\nfor i in 1 : 31 do\n// Search from MSB\nif S0.i32[31 - i] != S0.i32[31] then\nD0.i32 = i;\nbreak\nendif\nendfor',
|
||||
VOP3AOp.V_FREXP_EXP_I32_F64: 'if ((S0.f64 == +INF) || (S0.f64 == -INF) || isNAN(S0.f64)) then\nD0.i32 = 0\nelse\nD0.i32 = exponent(S0.f64) - 1023 + 1\nendif',
|
||||
VOP3AOp.V_FREXP_MANT_F64: 'if ((S0.f64 == +INF) || (S0.f64 == -INF) || isNAN(S0.f64)) then\nD0.f64 = S0.f64\nelse\nD0.f64 = mantissa(S0.f64)\nendif',
|
||||
VOP3AOp.V_FRACT_F64: 'D0.f64 = S0.f64 + -floor(S0.f64)',
|
||||
VOP3AOp.V_FREXP_EXP_I32_F32: "if ((64'F(S0.f32) == +INF) || (64'F(S0.f32) == -INF) || isNAN(64'F(S0.f32))) then\nD0.i32 = 0\nelse\nD0.i32 = exponent(S0.f32) - 127 + 1\nendif",
|
||||
VOP3AOp.V_FREXP_MANT_F32: "if ((64'F(S0.f32) == +INF) || (64'F(S0.f32) == -INF) || isNAN(64'F(S0.f32))) then\nD0.f32 = S0.f32\nelse\nD0.f32 = mantissa(S0.f32)\nendif",
|
||||
VOP3AOp.V_MOV_B64: 'D0.b64 = S0.b64',
|
||||
VOP3AOp.V_CVT_F16_U16: 'D0.f16 = u16_to_f16(S0.u16)',
|
||||
VOP3AOp.V_CVT_F16_I16: 'D0.f16 = i16_to_f16(S0.i16)',
|
||||
VOP3AOp.V_CVT_U16_F16: 'D0.u16 = f16_to_u16(S0.f16)',
|
||||
VOP3AOp.V_CVT_I16_F16: 'D0.i16 = f16_to_i16(S0.f16)',
|
||||
VOP3AOp.V_RCP_F16: "D0.f16 = 16'1.0 / S0.f16",
|
||||
VOP3AOp.V_SQRT_F16: 'D0.f16 = sqrt(S0.f16)',
|
||||
VOP3AOp.V_RSQ_F16: "D0.f16 = 16'1.0 / sqrt(S0.f16)",
|
||||
VOP3AOp.V_LOG_F16: 'D0.f16 = log2(S0.f16)',
|
||||
VOP3AOp.V_EXP_F16: "D0.f16 = pow(16'2.0, S0.f16)",
|
||||
VOP3AOp.V_MAD_I32_I24: "D0.i32 = 32'I(S0.i24) * 32'I(S1.i24) + S2.i32",
|
||||
VOP3AOp.V_MAD_U32_U24: "D0.u32 = 32'U(S0.u24) * 32'U(S1.u24) + S2.u32",
|
||||
VOP3AOp.V_CUBEID_F32: '// Set D0.f = cubemap face ID ({0.0, 1.0, ..., 5.0}).\n// XYZ coordinate is given in (S0.f, S1.f, S2.f).\n// S0.f = x\n// S1.f = y\n// S2.f = z\nif ((abs(S2.f32) >= abs(S0.f32)) && (abs(S2.f32) >= abs(S1.f32))) then\nif S2.f32 < 0.0F then\nD0.f32 = 5.0F\nelse\nD0.f32 = 4.0F\nendif\nelsif abs(S1.f32) >= abs(S0.f32) then\nif S1.f32 < 0.0F then\nD0.f32 = 3.0F\nelse\nD0.f32 = 2.0F\nendif\nelse\nif S0.f32 < 0.0F then\nD0.f32 = 1.0F\nelse\nD0.f32 = 0.0F\nendif\nendif',
|
||||
@@ -1134,6 +1238,9 @@ PCODE = {
|
||||
VOP3AOp.V_CVT_SR_BF16_F32: "prev_mode = ROUND_MODE;\nROUND_MODE = ROUND_NEAREST_EVEN;\nif OPSEL[3].u2 == 2'0U then\nVGPR[laneId][VDST.u32][15 : 0].bf16 = 16'BF(f32_to_bf16_SR(S0.f32, S1.u32))\nelse\nVGPR[laneId][VDST.u32][31 : 16].bf16 = 16'BF(f32_to_bf16_sr(S0.f32, S1.u32))\nendif;\n// Unwritten bytes of D are preserved.\nROUND_MODE = prev_mode",
|
||||
VOP3AOp.V_MINIMUM3_F32: "D0.f32 = 32'F(v_minimum_f32(v_minimum_f32(S0.f32, S1.f32), S2.f32))",
|
||||
VOP3AOp.V_MAXIMUM3_F32: "D0.f32 = 32'F(v_maximum_f32(v_maximum_f32(S0.f32, S1.f32), S2.f32))",
|
||||
}
|
||||
|
||||
VOP3BOp_PCODE = {
|
||||
VOP3BOp.V_ADD_CO_U32: "tmp = 64'U(S0.u32) + 64'U(S1.u32);\nVCC.u64[laneId] = tmp >= 0x100000000ULL ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_ADDC_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP3BOp.V_SUB_CO_U32: "tmp = S0.u32 - S1.u32;\nVCC.u64[laneId] = S1.u32 > S0.u32 ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
VOP3BOp.V_SUBREV_CO_U32: "tmp = S1.u32 - S0.u32;\nVCC.u64[laneId] = S0.u32 > S1.u32 ? 1'1U : 1'0U;\n// VCC is an UNSIGNED overflow/carry-out for V_SUBB_CO_U32.\nD0.u32 = tmp.u32",
|
||||
@@ -1144,6 +1251,9 @@ PCODE = {
|
||||
VOP3BOp.V_DIV_SCALE_F64: 'VCC = 0x0LL;\nif ((S2.f64 == 0.0) || (S1.f64 == 0.0)) then\nD0.f64 = NAN.f64\nelsif exponent(S2.f64) - exponent(S1.f64) >= 768 then\n// N/D near MAX_FLOAT_F64\nVCC = 0x1LL;\nif S0.f64 == S1.f64 then\n// Only scale the denominator\nD0.f64 = ldexp(S0.f64, 128)\nendif\nelsif S1.f64 == DENORM.f64 then\nD0.f64 = ldexp(S0.f64, 128)\nelsif ((1.0 / S1.f64 == DENORM.f64) && (S2.f64 / S1.f64 == DENORM.f64)) then\nVCC = 0x1LL;\nif S0.f64 == S1.f64 then\n// Only scale the denominator\nD0.f64 = ldexp(S0.f64, 128)\nendif\nelsif 1.0 / S1.f64 == DENORM.f64 then\nD0.f64 = ldexp(S0.f64, -128)\nelsif S2.f64 / S1.f64 == DENORM.f64 then\nVCC = 0x1LL;\nif S0.f64 == S2.f64 then\n// Only scale the numerator\nD0.f64 = ldexp(S0.f64, 128)\nendif\nelsif exponent(S2.f64) <= 53 then\n// Numerator is tiny\nD0.f64 = ldexp(S0.f64, 128)\nendif',
|
||||
VOP3BOp.V_MAD_U64_U32: "{ D1.u1, D0.u64 } = 65'B(65'U(S0.u32) * 65'U(S1.u32) + 65'U(S2.u64))",
|
||||
VOP3BOp.V_MAD_I64_I32: "{ D1.i1, D0.i64 } = 65'B(65'I(S0.i32) * 65'I(S1.i32) + 65'I(S2.i64))",
|
||||
}
|
||||
|
||||
VOP3POp_PCODE = {
|
||||
VOP3POp.V_PK_MAD_I16: "declare tmp : 32'B;\ntmp[15 : 0].i16 = S0[15 : 0].i16 * S1[15 : 0].i16 + S2[15 : 0].i16;\ntmp[31 : 16].i16 = S0[31 : 16].i16 * S1[31 : 16].i16 + S2[31 : 16].i16;\nD0.b32 = tmp",
|
||||
VOP3POp.V_PK_MUL_LO_U16: 'tmp[31 : 16].u16 = S0[31 : 16].u16 * S1[31 : 16].u16;\ntmp[15 : 0].u16 = S0[15 : 0].u16 * S1[15 : 0].u16;\nD0.b32 = tmp.b32',
|
||||
VOP3POp.V_PK_ADD_I16: "declare tmp : 32'B;\ntmp[15 : 0].i16 = S0[15 : 0].i16 + S1[15 : 0].i16;\ntmp[31 : 16].i16 = S0[31 : 16].i16 + S1[31 : 16].i16;\nD0.b32 = tmp",
|
||||
@@ -1246,202 +1356,207 @@ PCODE = {
|
||||
VOP3POp.V_SMFMAC_F32_32X32X32_BF8_FP8: 'D = A (sparse 32x32) * B (32x32) + D (32x32)',
|
||||
VOP3POp.V_SMFMAC_F32_32X32X32_FP8_BF8: 'D = A (sparse 32x32) * B (32x32) + D (32x32)',
|
||||
VOP3POp.V_SMFMAC_F32_32X32X32_FP8_FP8: 'D = A (sparse 32x32) * B (32x32) + D (32x32)',
|
||||
VOPCOp.V_CMP_CLASS_F32_E32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f32)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f32)) then\nresult = S1.u32[1]\nelsif exponent(S0.f32) == 255 then\n// +-INF\nresult = S1.u32[sign(S0.f32) ? 2 : 9]\nelsif exponent(S0.f32) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f32) ? 3 : 8]\nelsif 64'F(abs(S0.f32)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f32) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f32) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_CLASS_F32_E32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f32)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f32)) then\nresult = S1.u32[1]\nelsif exponent(S0.f32) == 255 then\n// +-INF\nresult = S1.u32[sign(S0.f32) ? 2 : 9]\nelsif exponent(S0.f32) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f32) ? 3 : 8]\nelsif 64'F(abs(S0.f32)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f32) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f32) ? 5 : 6]\nendif;\nEXEC.u64[laneId] = D0.u64[laneId] = result",
|
||||
VOPCOp.V_CMP_CLASS_F64_E32: "declare result : 1'U;\nif isSignalNAN(S0.f64) then\nresult = S1.u32[0]\nelsif isQuietNAN(S0.f64) then\nresult = S1.u32[1]\nelsif exponent(S0.f64) == 2047 then\n// +-INF\nresult = S1.u32[sign(S0.f64) ? 2 : 9]\nelsif exponent(S0.f64) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f64) ? 3 : 8]\nelsif abs(S0.f64) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f64) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f64) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_CLASS_F64_E32: "declare result : 1'U;\nif isSignalNAN(S0.f64) then\nresult = S1.u32[0]\nelsif isQuietNAN(S0.f64) then\nresult = S1.u32[1]\nelsif exponent(S0.f64) == 2047 then\n// +-INF\nresult = S1.u32[sign(S0.f64) ? 2 : 9]\nelsif exponent(S0.f64) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f64) ? 3 : 8]\nelsif abs(S0.f64) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f64) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f64) ? 5 : 6]\nendif;\nEXEC.u64[laneId] = D0.u64[laneId] = result",
|
||||
VOPCOp.V_CMP_CLASS_F16_E32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f16)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f16)) then\nresult = S1.u32[1]\nelsif exponent(S0.f16) == 31 then\n// +-INF\nresult = S1.u32[sign(S0.f16) ? 2 : 9]\nelsif exponent(S0.f16) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f16) ? 3 : 8]\nelsif 64'F(abs(S0.f16)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f16) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f16) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_CLASS_F16_E32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f16)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f16)) then\nresult = S1.u32[1]\nelsif exponent(S0.f16) == 31 then\n// +-INF\nresult = S1.u32[sign(S0.f16) ? 2 : 9]\nelsif exponent(S0.f16) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f16) ? 3 : 8]\nelsif 64'F(abs(S0.f16)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f16) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f16) ? 5 : 6]\nendif;\nEXEC.u64[laneId] = D0.u64[laneId] = result",
|
||||
VOPCOp.V_CMP_F_F16_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_F16_E32: 'D0.u64[laneId] = S0.f16 < S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_F16_E32: 'D0.u64[laneId] = S0.f16 == S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_F16_E32: 'D0.u64[laneId] = S0.f16 <= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_F16_E32: 'D0.u64[laneId] = S0.f16 > S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LG_F16_E32: 'D0.u64[laneId] = S0.f16 <> S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_F16_E32: 'D0.u64[laneId] = S0.f16 >= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_O_F16_E32: "D0.u64[laneId] = (!isNAN(64'F(S0.f16)) && !isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_U_F16_E32: "D0.u64[laneId] = (isNAN(64'F(S0.f16)) || isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_NGE_F16_E32: 'D0.u64[laneId] = !(S0.f16 >= S1.f16);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLG_F16_E32: 'D0.u64[laneId] = !(S0.f16 <> S1.f16);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGT_F16_E32: 'D0.u64[laneId] = !(S0.f16 > S1.f16);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLE_F16_E32: 'D0.u64[laneId] = !(S0.f16 <= S1.f16);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NEQ_F16_E32: 'D0.u64[laneId] = !(S0.f16 == S1.f16);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLT_F16_E32: 'D0.u64[laneId] = !(S0.f16 < S1.f16);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_TRU_F16_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_F16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 < S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 == S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 <= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 > S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LG_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 <> S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 >= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_O_F16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = (!isNAN(64'F(S0.f16)) && !isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_U_F16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = (isNAN(64'F(S0.f16)) || isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_NGE_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 >= S1.f16);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLG_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 <> S1.f16);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGT_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 > S1.f16);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLE_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 <= S1.f16);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NEQ_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 == S1.f16);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLT_F16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 < S1.f16);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_TRU_F16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_F32_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_F32_E32: 'D0.u64[laneId] = S0.f32 < S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_F32_E32: 'D0.u64[laneId] = S0.f32 == S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_F32_E32: 'D0.u64[laneId] = S0.f32 <= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_F32_E32: 'D0.u64[laneId] = S0.f32 > S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LG_F32_E32: 'D0.u64[laneId] = S0.f32 <> S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_F32_E32: 'D0.u64[laneId] = S0.f32 >= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_O_F32_E32: "D0.u64[laneId] = (!isNAN(64'F(S0.f32)) && !isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_U_F32_E32: "D0.u64[laneId] = (isNAN(64'F(S0.f32)) || isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_NGE_F32_E32: 'D0.u64[laneId] = !(S0.f32 >= S1.f32);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLG_F32_E32: 'D0.u64[laneId] = !(S0.f32 <> S1.f32);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGT_F32_E32: 'D0.u64[laneId] = !(S0.f32 > S1.f32);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLE_F32_E32: 'D0.u64[laneId] = !(S0.f32 <= S1.f32);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NEQ_F32_E32: 'D0.u64[laneId] = !(S0.f32 == S1.f32);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLT_F32_E32: 'D0.u64[laneId] = !(S0.f32 < S1.f32);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_TRU_F32_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_F32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 < S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 == S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 <= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 > S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LG_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 <> S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 >= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_O_F32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = (!isNAN(64'F(S0.f32)) && !isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_U_F32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = (isNAN(64'F(S0.f32)) || isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_NGE_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 >= S1.f32);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLG_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 <> S1.f32);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGT_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 > S1.f32);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLE_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 <= S1.f32);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NEQ_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 == S1.f32);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLT_F32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 < S1.f32);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_TRU_F32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_F64_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_F64_E32: 'D0.u64[laneId] = S0.f64 < S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_F64_E32: 'D0.u64[laneId] = S0.f64 == S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_F64_E32: 'D0.u64[laneId] = S0.f64 <= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_F64_E32: 'D0.u64[laneId] = S0.f64 > S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LG_F64_E32: 'D0.u64[laneId] = S0.f64 <> S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_F64_E32: 'D0.u64[laneId] = S0.f64 >= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_O_F64_E32: 'D0.u64[laneId] = (!isNAN(S0.f64) && !isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_U_F64_E32: 'D0.u64[laneId] = (isNAN(S0.f64) || isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGE_F64_E32: 'D0.u64[laneId] = !(S0.f64 >= S1.f64);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLG_F64_E32: 'D0.u64[laneId] = !(S0.f64 <> S1.f64);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGT_F64_E32: 'D0.u64[laneId] = !(S0.f64 > S1.f64);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLE_F64_E32: 'D0.u64[laneId] = !(S0.f64 <= S1.f64);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NEQ_F64_E32: 'D0.u64[laneId] = !(S0.f64 == S1.f64);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLT_F64_E32: 'D0.u64[laneId] = !(S0.f64 < S1.f64);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_TRU_F64_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_F64_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 < S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 == S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 <= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 > S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LG_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 <> S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 >= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_O_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = (!isNAN(S0.f64) && !isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_U_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = (isNAN(S0.f64) || isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGE_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 >= S1.f64);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLG_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 <> S1.f64);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGT_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 > S1.f64);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLE_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 <= S1.f64);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NEQ_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 == S1.f64);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLT_F64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 < S1.f64);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_TRU_F64_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_I16_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_I16_E32: 'D0.u64[laneId] = S0.i16 < S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_I16_E32: 'D0.u64[laneId] = S0.i16 == S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_I16_E32: 'D0.u64[laneId] = S0.i16 <= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_I16_E32: 'D0.u64[laneId] = S0.i16 > S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_I16_E32: 'D0.u64[laneId] = S0.i16 <> S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_I16_E32: 'D0.u64[laneId] = S0.i16 >= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_I16_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_U16_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_U16_E32: 'D0.u64[laneId] = S0.u16 < S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_U16_E32: 'D0.u64[laneId] = S0.u16 == S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_U16_E32: 'D0.u64[laneId] = S0.u16 <= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_U16_E32: 'D0.u64[laneId] = S0.u16 > S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_U16_E32: 'D0.u64[laneId] = S0.u16 <> S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_U16_E32: 'D0.u64[laneId] = S0.u16 >= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_U16_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_I16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_I16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 < S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_I16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 == S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_I16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 <= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_I16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 > S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_I16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 <> S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_I16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 >= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_I16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_U16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_U16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 < S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_U16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 == S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_U16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 <= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_U16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 > S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_U16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 <> S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_U16_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 >= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_U16_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_I32_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_I32_E32: 'D0.u64[laneId] = S0.i32 < S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_I32_E32: 'D0.u64[laneId] = S0.i32 == S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_I32_E32: 'D0.u64[laneId] = S0.i32 <= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_I32_E32: 'D0.u64[laneId] = S0.i32 > S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_I32_E32: 'D0.u64[laneId] = S0.i32 <> S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_I32_E32: 'D0.u64[laneId] = S0.i32 >= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_I32_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_U32_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_U32_E32: 'D0.u64[laneId] = S0.u32 < S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_U32_E32: 'D0.u64[laneId] = S0.u32 == S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_U32_E32: 'D0.u64[laneId] = S0.u32 <= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_U32_E32: 'D0.u64[laneId] = S0.u32 > S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_U32_E32: 'D0.u64[laneId] = S0.u32 <> S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_U32_E32: 'D0.u64[laneId] = S0.u32 >= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_U32_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_I32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_I32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 < S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_I32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 == S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_I32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 <= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_I32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 > S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_I32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 <> S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_I32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 >= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_I32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_U32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_U32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 < S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_U32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 == S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_U32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 <= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_U32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 > S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_U32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 <> S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_U32_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 >= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_U32_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_I64_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_I64_E32: 'D0.u64[laneId] = S0.i64 < S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_I64_E32: 'D0.u64[laneId] = S0.i64 == S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_I64_E32: 'D0.u64[laneId] = S0.i64 <= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_I64_E32: 'D0.u64[laneId] = S0.i64 > S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_I64_E32: 'D0.u64[laneId] = S0.i64 <> S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_I64_E32: 'D0.u64[laneId] = S0.i64 >= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_I64_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_U64_E32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_U64_E32: 'D0.u64[laneId] = S0.u64 < S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_U64_E32: 'D0.u64[laneId] = S0.u64 == S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_U64_E32: 'D0.u64[laneId] = S0.u64 <= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_U64_E32: 'D0.u64[laneId] = S0.u64 > S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_U64_E32: 'D0.u64[laneId] = S0.u64 <> S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_U64_E32: 'D0.u64[laneId] = S0.u64 >= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_U64_E32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_I64_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_I64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 < S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_I64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 == S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_I64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 <= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_I64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 > S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_I64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 <> S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_I64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 >= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_I64_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_U64_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_U64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 < S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_U64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 == S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_U64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 <= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_U64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 > S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_U64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 <> S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_U64_E32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 >= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_U64_E32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
}
|
||||
}
|
||||
|
||||
VOPCOp_PCODE = {
|
||||
VOPCOp.V_CMP_CLASS_F32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f32)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f32)) then\nresult = S1.u32[1]\nelsif exponent(S0.f32) == 255 then\n// +-INF\nresult = S1.u32[sign(S0.f32) ? 2 : 9]\nelsif exponent(S0.f32) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f32) ? 3 : 8]\nelsif 64'F(abs(S0.f32)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f32) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f32) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_CLASS_F32: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f32)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f32)) then\nresult = S1.u32[1]\nelsif exponent(S0.f32) == 255 then\n// +-INF\nresult = S1.u32[sign(S0.f32) ? 2 : 9]\nelsif exponent(S0.f32) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f32) ? 3 : 8]\nelsif 64'F(abs(S0.f32)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f32) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f32) ? 5 : 6]\nendif;\nEXEC.u64[laneId] = D0.u64[laneId] = result",
|
||||
VOPCOp.V_CMP_CLASS_F64: "declare result : 1'U;\nif isSignalNAN(S0.f64) then\nresult = S1.u32[0]\nelsif isQuietNAN(S0.f64) then\nresult = S1.u32[1]\nelsif exponent(S0.f64) == 2047 then\n// +-INF\nresult = S1.u32[sign(S0.f64) ? 2 : 9]\nelsif exponent(S0.f64) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f64) ? 3 : 8]\nelsif abs(S0.f64) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f64) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f64) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_CLASS_F64: "declare result : 1'U;\nif isSignalNAN(S0.f64) then\nresult = S1.u32[0]\nelsif isQuietNAN(S0.f64) then\nresult = S1.u32[1]\nelsif exponent(S0.f64) == 2047 then\n// +-INF\nresult = S1.u32[sign(S0.f64) ? 2 : 9]\nelsif exponent(S0.f64) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f64) ? 3 : 8]\nelsif abs(S0.f64) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f64) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f64) ? 5 : 6]\nendif;\nEXEC.u64[laneId] = D0.u64[laneId] = result",
|
||||
VOPCOp.V_CMP_CLASS_F16: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f16)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f16)) then\nresult = S1.u32[1]\nelsif exponent(S0.f16) == 31 then\n// +-INF\nresult = S1.u32[sign(S0.f16) ? 2 : 9]\nelsif exponent(S0.f16) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f16) ? 3 : 8]\nelsif 64'F(abs(S0.f16)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f16) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f16) ? 5 : 6]\nendif;\nD0.u64[laneId] = result;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_CLASS_F16: "declare result : 1'U;\nif isSignalNAN(64'F(S0.f16)) then\nresult = S1.u32[0]\nelsif isQuietNAN(64'F(S0.f16)) then\nresult = S1.u32[1]\nelsif exponent(S0.f16) == 31 then\n// +-INF\nresult = S1.u32[sign(S0.f16) ? 2 : 9]\nelsif exponent(S0.f16) > 0 then\n// +-normal value\nresult = S1.u32[sign(S0.f16) ? 3 : 8]\nelsif 64'F(abs(S0.f16)) > 0.0 then\n// +-denormal value\nresult = S1.u32[sign(S0.f16) ? 4 : 7]\nelse\n// +-0.0\nresult = S1.u32[sign(S0.f16) ? 5 : 6]\nendif;\nEXEC.u64[laneId] = D0.u64[laneId] = result",
|
||||
VOPCOp.V_CMP_F_F16: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_F16: 'D0.u64[laneId] = S0.f16 < S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_F16: 'D0.u64[laneId] = S0.f16 == S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_F16: 'D0.u64[laneId] = S0.f16 <= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_F16: 'D0.u64[laneId] = S0.f16 > S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LG_F16: 'D0.u64[laneId] = S0.f16 <> S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_F16: 'D0.u64[laneId] = S0.f16 >= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_O_F16: "D0.u64[laneId] = (!isNAN(64'F(S0.f16)) && !isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_U_F16: "D0.u64[laneId] = (isNAN(64'F(S0.f16)) || isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_NGE_F16: 'D0.u64[laneId] = !(S0.f16 >= S1.f16);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLG_F16: 'D0.u64[laneId] = !(S0.f16 <> S1.f16);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGT_F16: 'D0.u64[laneId] = !(S0.f16 > S1.f16);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLE_F16: 'D0.u64[laneId] = !(S0.f16 <= S1.f16);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NEQ_F16: 'D0.u64[laneId] = !(S0.f16 == S1.f16);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLT_F16: 'D0.u64[laneId] = !(S0.f16 < S1.f16);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_TRU_F16: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_F16: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 < S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 == S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 <= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 > S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LG_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 <> S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f16 >= S1.f16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_O_F16: "EXEC.u64[laneId] = D0.u64[laneId] = (!isNAN(64'F(S0.f16)) && !isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_U_F16: "EXEC.u64[laneId] = D0.u64[laneId] = (isNAN(64'F(S0.f16)) || isNAN(64'F(S1.f16)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_NGE_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 >= S1.f16);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLG_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 <> S1.f16);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGT_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 > S1.f16);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLE_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 <= S1.f16);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NEQ_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 == S1.f16);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLT_F16: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f16 < S1.f16);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_TRU_F16: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_F32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_F32: 'D0.u64[laneId] = S0.f32 < S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_F32: 'D0.u64[laneId] = S0.f32 == S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_F32: 'D0.u64[laneId] = S0.f32 <= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_F32: 'D0.u64[laneId] = S0.f32 > S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LG_F32: 'D0.u64[laneId] = S0.f32 <> S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_F32: 'D0.u64[laneId] = S0.f32 >= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_O_F32: "D0.u64[laneId] = (!isNAN(64'F(S0.f32)) && !isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_U_F32: "D0.u64[laneId] = (isNAN(64'F(S0.f32)) || isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_NGE_F32: 'D0.u64[laneId] = !(S0.f32 >= S1.f32);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLG_F32: 'D0.u64[laneId] = !(S0.f32 <> S1.f32);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGT_F32: 'D0.u64[laneId] = !(S0.f32 > S1.f32);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLE_F32: 'D0.u64[laneId] = !(S0.f32 <= S1.f32);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NEQ_F32: 'D0.u64[laneId] = !(S0.f32 == S1.f32);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLT_F32: 'D0.u64[laneId] = !(S0.f32 < S1.f32);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_TRU_F32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_F32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 < S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 == S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 <= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 > S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LG_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 <> S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f32 >= S1.f32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_O_F32: "EXEC.u64[laneId] = D0.u64[laneId] = (!isNAN(64'F(S0.f32)) && !isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_U_F32: "EXEC.u64[laneId] = D0.u64[laneId] = (isNAN(64'F(S0.f32)) || isNAN(64'F(S1.f32)));\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_NGE_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 >= S1.f32);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLG_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 <> S1.f32);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGT_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 > S1.f32);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLE_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 <= S1.f32);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NEQ_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 == S1.f32);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLT_F32: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f32 < S1.f32);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_TRU_F32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_F64: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_F64: 'D0.u64[laneId] = S0.f64 < S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_F64: 'D0.u64[laneId] = S0.f64 == S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_F64: 'D0.u64[laneId] = S0.f64 <= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_F64: 'D0.u64[laneId] = S0.f64 > S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LG_F64: 'D0.u64[laneId] = S0.f64 <> S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_F64: 'D0.u64[laneId] = S0.f64 >= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_O_F64: 'D0.u64[laneId] = (!isNAN(S0.f64) && !isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_U_F64: 'D0.u64[laneId] = (isNAN(S0.f64) || isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGE_F64: 'D0.u64[laneId] = !(S0.f64 >= S1.f64);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLG_F64: 'D0.u64[laneId] = !(S0.f64 <> S1.f64);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NGT_F64: 'D0.u64[laneId] = !(S0.f64 > S1.f64);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLE_F64: 'D0.u64[laneId] = !(S0.f64 <= S1.f64);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NEQ_F64: 'D0.u64[laneId] = !(S0.f64 == S1.f64);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NLT_F64: 'D0.u64[laneId] = !(S0.f64 < S1.f64);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_TRU_F64: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_F64: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 < S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 == S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 <= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 > S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LG_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 <> S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.f64 >= S1.f64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_O_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = (!isNAN(S0.f64) && !isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_U_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = (isNAN(S0.f64) || isNAN(S1.f64));\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGE_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 >= S1.f64);\n// With NAN inputs this is not the same operation as <\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLG_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 <> S1.f64);\n// With NAN inputs this is not the same operation as ==\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NGT_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 > S1.f64);\n// With NAN inputs this is not the same operation as <=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLE_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 <= S1.f64);\n// With NAN inputs this is not the same operation as >\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NEQ_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 == S1.f64);\n// With NAN inputs this is not the same operation as !=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NLT_F64: 'EXEC.u64[laneId] = D0.u64[laneId] = !(S0.f64 < S1.f64);\n// With NAN inputs this is not the same operation as >=\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_TRU_F64: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_I16: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_I16: 'D0.u64[laneId] = S0.i16 < S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_I16: 'D0.u64[laneId] = S0.i16 == S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_I16: 'D0.u64[laneId] = S0.i16 <= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_I16: 'D0.u64[laneId] = S0.i16 > S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_I16: 'D0.u64[laneId] = S0.i16 <> S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_I16: 'D0.u64[laneId] = S0.i16 >= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_I16: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_U16: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_U16: 'D0.u64[laneId] = S0.u16 < S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_U16: 'D0.u64[laneId] = S0.u16 == S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_U16: 'D0.u64[laneId] = S0.u16 <= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_U16: 'D0.u64[laneId] = S0.u16 > S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_U16: 'D0.u64[laneId] = S0.u16 <> S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_U16: 'D0.u64[laneId] = S0.u16 >= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_U16: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_I16: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_I16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 < S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_I16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 == S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_I16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 <= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_I16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 > S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_I16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 <> S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_I16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i16 >= S1.i16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_I16: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_U16: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_U16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 < S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_U16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 == S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_U16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 <= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_U16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 > S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_U16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 <> S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_U16: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u16 >= S1.u16;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_U16: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_I32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_I32: 'D0.u64[laneId] = S0.i32 < S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_I32: 'D0.u64[laneId] = S0.i32 == S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_I32: 'D0.u64[laneId] = S0.i32 <= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_I32: 'D0.u64[laneId] = S0.i32 > S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_I32: 'D0.u64[laneId] = S0.i32 <> S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_I32: 'D0.u64[laneId] = S0.i32 >= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_I32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_U32: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_U32: 'D0.u64[laneId] = S0.u32 < S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_U32: 'D0.u64[laneId] = S0.u32 == S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_U32: 'D0.u64[laneId] = S0.u32 <= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_U32: 'D0.u64[laneId] = S0.u32 > S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_U32: 'D0.u64[laneId] = S0.u32 <> S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_U32: 'D0.u64[laneId] = S0.u32 >= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_U32: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_I32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_I32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 < S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_I32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 == S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_I32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 <= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_I32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 > S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_I32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 <> S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_I32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i32 >= S1.i32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_I32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_U32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_U32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 < S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_U32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 == S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_U32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 <= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_U32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 > S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_U32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 <> S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_U32: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u32 >= S1.u32;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_U32: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_I64: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_I64: 'D0.u64[laneId] = S0.i64 < S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_I64: 'D0.u64[laneId] = S0.i64 == S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_I64: 'D0.u64[laneId] = S0.i64 <= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_I64: 'D0.u64[laneId] = S0.i64 > S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_I64: 'D0.u64[laneId] = S0.i64 <> S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_I64: 'D0.u64[laneId] = S0.i64 >= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_I64: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_F_U64: "D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMP_LT_U64: 'D0.u64[laneId] = S0.u64 < S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_EQ_U64: 'D0.u64[laneId] = S0.u64 == S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_LE_U64: 'D0.u64[laneId] = S0.u64 <= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GT_U64: 'D0.u64[laneId] = S0.u64 > S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_NE_U64: 'D0.u64[laneId] = S0.u64 <> S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_GE_U64: 'D0.u64[laneId] = S0.u64 >= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMP_T_U64: "D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_I64: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_I64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 < S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_I64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 == S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_I64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 <= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_I64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 > S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_I64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 <> S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_I64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.i64 >= S1.i64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_I64: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_F_U64: "EXEC.u64[laneId] = D0.u64[laneId] = 1'0U;\n// D0 = VCC in VOPC encoding.",
|
||||
VOPCOp.V_CMPX_LT_U64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 < S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_EQ_U64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 == S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_LE_U64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 <= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GT_U64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 > S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_NE_U64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 <> S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_GE_U64: 'EXEC.u64[laneId] = D0.u64[laneId] = S0.u64 >= S1.u64;\n// D0 = VCC in VOPC encoding.',
|
||||
VOPCOp.V_CMPX_T_U64: "EXEC.u64[laneId] = D0.u64[laneId] = 1'1U;\n// D0 = VCC in VOPC encoding.",
|
||||
}
|
||||
|
||||
PSEUDOCODE_STRINGS = {DSOp: DSOp_PCODE, FLATOp: FLATOp_PCODE, GLOBALOp: GLOBALOp_PCODE, MTBUFOp: MTBUFOp_PCODE, MUBUFOp: MUBUFOp_PCODE, SCRATCHOp: SCRATCHOp_PCODE, SMEMOp: SMEMOp_PCODE, SOP1Op: SOP1Op_PCODE, SOP2Op: SOP2Op_PCODE, SOPCOp: SOPCOp_PCODE, SOPKOp: SOPKOp_PCODE, SOPPOp: SOPPOp_PCODE, VOP1Op: VOP1Op_PCODE, VOP2Op: VOP2Op_PCODE, VOP3AOp: VOP3AOp_PCODE, VOP3BOp: VOP3BOp_PCODE, VOP3POp: VOP3POp_PCODE, VOPCOp: VOPCOp_PCODE}
|
||||
@@ -1,320 +0,0 @@
|
||||
# autogenerated from AMD ISA XML - do not edit
|
||||
from enum import Enum, auto
|
||||
|
||||
class Fmt(Enum):
|
||||
FMT_ANY = auto()
|
||||
FMT_BUF = auto()
|
||||
FMT_IMG = auto()
|
||||
FMT_IMG_BVH = auto()
|
||||
FMT_NUM_B1 = auto()
|
||||
FMT_NUM_B1024 = auto()
|
||||
FMT_NUM_B128 = auto()
|
||||
FMT_NUM_B16 = auto()
|
||||
FMT_NUM_B256 = auto()
|
||||
FMT_NUM_B32 = auto()
|
||||
FMT_NUM_B512 = auto()
|
||||
FMT_NUM_B64 = auto()
|
||||
FMT_NUM_B8 = auto()
|
||||
FMT_NUM_B96 = auto()
|
||||
FMT_NUM_BF16 = auto()
|
||||
FMT_NUM_BF6 = auto()
|
||||
FMT_NUM_BF8 = auto()
|
||||
FMT_NUM_F16 = auto()
|
||||
FMT_NUM_F32 = auto()
|
||||
FMT_NUM_F64 = auto()
|
||||
FMT_NUM_FP4 = auto()
|
||||
FMT_NUM_FP6 = auto()
|
||||
FMT_NUM_FP8 = auto()
|
||||
FMT_NUM_I16 = auto()
|
||||
FMT_NUM_I24 = auto()
|
||||
FMT_NUM_I32 = auto()
|
||||
FMT_NUM_I4 = auto()
|
||||
FMT_NUM_I64 = auto()
|
||||
FMT_NUM_I8 = auto()
|
||||
FMT_NUM_IU4 = auto()
|
||||
FMT_NUM_IU8 = auto()
|
||||
FMT_NUM_M64 = auto()
|
||||
FMT_NUM_PK16_BF16 = auto()
|
||||
FMT_NUM_PK16_BF8 = auto()
|
||||
FMT_NUM_PK16_F16 = auto()
|
||||
FMT_NUM_PK16_F32 = auto()
|
||||
FMT_NUM_PK16_FP8 = auto()
|
||||
FMT_NUM_PK16_I32 = auto()
|
||||
FMT_NUM_PK16_I8 = auto()
|
||||
FMT_NUM_PK2_B16 = auto()
|
||||
FMT_NUM_PK2_B32 = auto()
|
||||
FMT_NUM_PK2_B64 = auto()
|
||||
FMT_NUM_PK2_BF16 = auto()
|
||||
FMT_NUM_PK2_BF8 = auto()
|
||||
FMT_NUM_PK2_F16 = auto()
|
||||
FMT_NUM_PK2_F32 = auto()
|
||||
FMT_NUM_PK2_FP4 = auto()
|
||||
FMT_NUM_PK2_FP8 = auto()
|
||||
FMT_NUM_PK2_I16 = auto()
|
||||
FMT_NUM_PK2_I8 = auto()
|
||||
FMT_NUM_PK2_U16 = auto()
|
||||
FMT_NUM_PK2_U8 = auto()
|
||||
FMT_NUM_PK32_BF16 = auto()
|
||||
FMT_NUM_PK32_BF6 = auto()
|
||||
FMT_NUM_PK32_BF8 = auto()
|
||||
FMT_NUM_PK32_F16 = auto()
|
||||
FMT_NUM_PK32_F32 = auto()
|
||||
FMT_NUM_PK32_FP6 = auto()
|
||||
FMT_NUM_PK32_FP8 = auto()
|
||||
FMT_NUM_PK32_I32 = auto()
|
||||
FMT_NUM_PK32_I8 = auto()
|
||||
FMT_NUM_PK4_B8 = auto()
|
||||
FMT_NUM_PK4_BF16 = auto()
|
||||
FMT_NUM_PK4_BF8 = auto()
|
||||
FMT_NUM_PK4_F16 = auto()
|
||||
FMT_NUM_PK4_F32 = auto()
|
||||
FMT_NUM_PK4_F64 = auto()
|
||||
FMT_NUM_PK4_FP8 = auto()
|
||||
FMT_NUM_PK4_I32 = auto()
|
||||
FMT_NUM_PK4_I8 = auto()
|
||||
FMT_NUM_PK4_IU8 = auto()
|
||||
FMT_NUM_PK4_U8 = auto()
|
||||
FMT_NUM_PK8_B32 = auto()
|
||||
FMT_NUM_PK8_BF16 = auto()
|
||||
FMT_NUM_PK8_BF8 = auto()
|
||||
FMT_NUM_PK8_F16 = auto()
|
||||
FMT_NUM_PK8_FP8 = auto()
|
||||
FMT_NUM_PK8_I4 = auto()
|
||||
FMT_NUM_PK8_I8 = auto()
|
||||
FMT_NUM_PK8_IU4 = auto()
|
||||
FMT_NUM_PK8_U4 = auto()
|
||||
FMT_NUM_PK8_U8 = auto()
|
||||
FMT_NUM_PK_F16 = auto()
|
||||
FMT_NUM_PK_I16 = auto()
|
||||
FMT_NUM_PK_I8 = auto()
|
||||
FMT_NUM_PK_U16 = auto()
|
||||
FMT_NUM_PK_U8 = auto()
|
||||
FMT_NUM_U16 = auto()
|
||||
FMT_NUM_U24 = auto()
|
||||
FMT_NUM_U32 = auto()
|
||||
FMT_NUM_U4 = auto()
|
||||
FMT_NUM_U64 = auto()
|
||||
FMT_NUM_U8 = auto()
|
||||
FMT_RSRC = auto()
|
||||
FMT_RSRC_SCALAR = auto()
|
||||
FMT_RSRC_SCRATCH = auto()
|
||||
FMT_RSRC_SCRATCH_BYTE = auto()
|
||||
FMT_RSRC_SCRATCH_STRIDE = auto()
|
||||
FMT_RSRC_TYPED = auto()
|
||||
FMT_RSRC_TYPED_BYTE = auto()
|
||||
FMT_RSRC_TYPED_SCRATCH = auto()
|
||||
FMT_RSRC_TYPED_STRIDE = auto()
|
||||
FMT_RSRC_VECTOR = auto()
|
||||
FMT_RSRC_VECTOR_BYTE = auto()
|
||||
FMT_RSRC_VECTOR_STRIDE = auto()
|
||||
FMT_SAMP = auto()
|
||||
FMT_WMMA_AB_16X16_BF16 = auto()
|
||||
FMT_WMMA_AB_16X16_BF8 = auto()
|
||||
FMT_WMMA_AB_16X16_F16 = auto()
|
||||
FMT_WMMA_AB_16X16_FP8 = auto()
|
||||
FMT_WMMA_AB_16X16_IU4 = auto()
|
||||
FMT_WMMA_AB_16X16_IU8 = auto()
|
||||
FMT_WMMA_AB_16X32_BF16 = auto()
|
||||
FMT_WMMA_AB_16X32_BF8 = auto()
|
||||
FMT_WMMA_AB_16X32_F16 = auto()
|
||||
FMT_WMMA_AB_16X32_FP8 = auto()
|
||||
FMT_WMMA_AB_16X32_IU4 = auto()
|
||||
FMT_WMMA_AB_16X32_IU8 = auto()
|
||||
FMT_WMMA_AB_16X64_IU4 = auto()
|
||||
FMT_WMMA_AB_BF16 = auto()
|
||||
FMT_WMMA_AB_F16 = auto()
|
||||
FMT_WMMA_AB_IU4 = auto()
|
||||
FMT_WMMA_AB_IU8 = auto()
|
||||
FMT_WMMA_DC_16X16_BF16 = auto()
|
||||
FMT_WMMA_DC_16X16_F16 = auto()
|
||||
FMT_WMMA_DC_16X16_F32 = auto()
|
||||
FMT_WMMA_DC_16X16_I32 = auto()
|
||||
FMT_WMMA_DC_BF16 = auto()
|
||||
FMT_WMMA_DC_F16 = auto()
|
||||
FMT_WMMA_DC_F32 = auto()
|
||||
FMT_WMMA_DC_I32 = auto()
|
||||
FMT_WMMA_INDEX_SET = auto()
|
||||
|
||||
FMT_BITS = {
|
||||
Fmt.FMT_ANY: 1,
|
||||
Fmt.FMT_BUF: 64,
|
||||
Fmt.FMT_IMG: 256,
|
||||
Fmt.FMT_IMG_BVH: 128,
|
||||
Fmt.FMT_NUM_B1: 1,
|
||||
Fmt.FMT_NUM_B1024: 1024,
|
||||
Fmt.FMT_NUM_B128: 128,
|
||||
Fmt.FMT_NUM_B16: 16,
|
||||
Fmt.FMT_NUM_B256: 256,
|
||||
Fmt.FMT_NUM_B32: 32,
|
||||
Fmt.FMT_NUM_B512: 512,
|
||||
Fmt.FMT_NUM_B64: 64,
|
||||
Fmt.FMT_NUM_B8: 8,
|
||||
Fmt.FMT_NUM_B96: 96,
|
||||
Fmt.FMT_NUM_BF16: 16,
|
||||
Fmt.FMT_NUM_BF6: 6,
|
||||
Fmt.FMT_NUM_BF8: 8,
|
||||
Fmt.FMT_NUM_F16: 16,
|
||||
Fmt.FMT_NUM_F32: 32,
|
||||
Fmt.FMT_NUM_F64: 64,
|
||||
Fmt.FMT_NUM_FP4: 4,
|
||||
Fmt.FMT_NUM_FP6: 6,
|
||||
Fmt.FMT_NUM_FP8: 8,
|
||||
Fmt.FMT_NUM_I16: 16,
|
||||
Fmt.FMT_NUM_I24: 24,
|
||||
Fmt.FMT_NUM_I32: 32,
|
||||
Fmt.FMT_NUM_I4: 4,
|
||||
Fmt.FMT_NUM_I64: 64,
|
||||
Fmt.FMT_NUM_I8: 8,
|
||||
Fmt.FMT_NUM_IU4: 4,
|
||||
Fmt.FMT_NUM_IU8: 8,
|
||||
Fmt.FMT_NUM_M64: 64,
|
||||
Fmt.FMT_NUM_PK16_BF16: 256,
|
||||
Fmt.FMT_NUM_PK16_BF8: 128,
|
||||
Fmt.FMT_NUM_PK16_F16: 256,
|
||||
Fmt.FMT_NUM_PK16_F32: 512,
|
||||
Fmt.FMT_NUM_PK16_FP8: 128,
|
||||
Fmt.FMT_NUM_PK16_I32: 512,
|
||||
Fmt.FMT_NUM_PK16_I8: 128,
|
||||
Fmt.FMT_NUM_PK2_B16: 32,
|
||||
Fmt.FMT_NUM_PK2_B32: 64,
|
||||
Fmt.FMT_NUM_PK2_B64: 128,
|
||||
Fmt.FMT_NUM_PK2_BF16: 32,
|
||||
Fmt.FMT_NUM_PK2_BF8: 16,
|
||||
Fmt.FMT_NUM_PK2_F16: 32,
|
||||
Fmt.FMT_NUM_PK2_F32: 64,
|
||||
Fmt.FMT_NUM_PK2_FP4: 8,
|
||||
Fmt.FMT_NUM_PK2_FP8: 16,
|
||||
Fmt.FMT_NUM_PK2_I16: 32,
|
||||
Fmt.FMT_NUM_PK2_I8: 16,
|
||||
Fmt.FMT_NUM_PK2_U16: 32,
|
||||
Fmt.FMT_NUM_PK2_U8: 16,
|
||||
Fmt.FMT_NUM_PK32_BF16: 512,
|
||||
Fmt.FMT_NUM_PK32_BF6: 192,
|
||||
Fmt.FMT_NUM_PK32_BF8: 256,
|
||||
Fmt.FMT_NUM_PK32_F16: 512,
|
||||
Fmt.FMT_NUM_PK32_F32: 1024,
|
||||
Fmt.FMT_NUM_PK32_FP6: 192,
|
||||
Fmt.FMT_NUM_PK32_FP8: 256,
|
||||
Fmt.FMT_NUM_PK32_I32: 1024,
|
||||
Fmt.FMT_NUM_PK32_I8: 256,
|
||||
Fmt.FMT_NUM_PK4_B8: 32,
|
||||
Fmt.FMT_NUM_PK4_BF16: 64,
|
||||
Fmt.FMT_NUM_PK4_BF8: 32,
|
||||
Fmt.FMT_NUM_PK4_F16: 64,
|
||||
Fmt.FMT_NUM_PK4_F32: 128,
|
||||
Fmt.FMT_NUM_PK4_F64: 256,
|
||||
Fmt.FMT_NUM_PK4_FP8: 32,
|
||||
Fmt.FMT_NUM_PK4_I32: 128,
|
||||
Fmt.FMT_NUM_PK4_I8: 32,
|
||||
Fmt.FMT_NUM_PK4_IU8: 32,
|
||||
Fmt.FMT_NUM_PK4_U8: 32,
|
||||
Fmt.FMT_NUM_PK8_B32: 256,
|
||||
Fmt.FMT_NUM_PK8_BF16: 128,
|
||||
Fmt.FMT_NUM_PK8_BF8: 64,
|
||||
Fmt.FMT_NUM_PK8_F16: 128,
|
||||
Fmt.FMT_NUM_PK8_FP8: 64,
|
||||
Fmt.FMT_NUM_PK8_I4: 32,
|
||||
Fmt.FMT_NUM_PK8_I8: 64,
|
||||
Fmt.FMT_NUM_PK8_IU4: 32,
|
||||
Fmt.FMT_NUM_PK8_U4: 32,
|
||||
Fmt.FMT_NUM_PK8_U8: 64,
|
||||
Fmt.FMT_NUM_PK_F16: 32,
|
||||
Fmt.FMT_NUM_PK_I16: 32,
|
||||
Fmt.FMT_NUM_PK_I8: 32,
|
||||
Fmt.FMT_NUM_PK_U16: 32,
|
||||
Fmt.FMT_NUM_PK_U8: 32,
|
||||
Fmt.FMT_NUM_U16: 16,
|
||||
Fmt.FMT_NUM_U24: 24,
|
||||
Fmt.FMT_NUM_U32: 32,
|
||||
Fmt.FMT_NUM_U4: 4,
|
||||
Fmt.FMT_NUM_U64: 64,
|
||||
Fmt.FMT_NUM_U8: 8,
|
||||
Fmt.FMT_RSRC: 128,
|
||||
Fmt.FMT_RSRC_SCALAR: 128,
|
||||
Fmt.FMT_RSRC_SCRATCH: 128,
|
||||
Fmt.FMT_RSRC_SCRATCH_BYTE: 128,
|
||||
Fmt.FMT_RSRC_SCRATCH_STRIDE: 128,
|
||||
Fmt.FMT_RSRC_TYPED: 128,
|
||||
Fmt.FMT_RSRC_TYPED_BYTE: 128,
|
||||
Fmt.FMT_RSRC_TYPED_SCRATCH: 128,
|
||||
Fmt.FMT_RSRC_TYPED_STRIDE: 128,
|
||||
Fmt.FMT_RSRC_VECTOR: 128,
|
||||
Fmt.FMT_RSRC_VECTOR_BYTE: 128,
|
||||
Fmt.FMT_RSRC_VECTOR_STRIDE: 128,
|
||||
Fmt.FMT_SAMP: 128,
|
||||
Fmt.FMT_WMMA_AB_16X16_BF16: 128,
|
||||
Fmt.FMT_WMMA_AB_16X16_BF8: 64,
|
||||
Fmt.FMT_WMMA_AB_16X16_F16: 128,
|
||||
Fmt.FMT_WMMA_AB_16X16_FP8: 64,
|
||||
Fmt.FMT_WMMA_AB_16X16_IU4: 32,
|
||||
Fmt.FMT_WMMA_AB_16X16_IU8: 64,
|
||||
Fmt.FMT_WMMA_AB_16X32_BF16: 256,
|
||||
Fmt.FMT_WMMA_AB_16X32_BF8: 128,
|
||||
Fmt.FMT_WMMA_AB_16X32_F16: 256,
|
||||
Fmt.FMT_WMMA_AB_16X32_FP8: 128,
|
||||
Fmt.FMT_WMMA_AB_16X32_IU4: 64,
|
||||
Fmt.FMT_WMMA_AB_16X32_IU8: 128,
|
||||
Fmt.FMT_WMMA_AB_16X64_IU4: 128,
|
||||
Fmt.FMT_WMMA_AB_BF16: 256,
|
||||
Fmt.FMT_WMMA_AB_F16: 256,
|
||||
Fmt.FMT_WMMA_AB_IU4: 64,
|
||||
Fmt.FMT_WMMA_AB_IU8: 128,
|
||||
Fmt.FMT_WMMA_DC_16X16_BF16: 128,
|
||||
Fmt.FMT_WMMA_DC_16X16_F16: 128,
|
||||
Fmt.FMT_WMMA_DC_16X16_F32: 256,
|
||||
Fmt.FMT_WMMA_DC_16X16_I32: 256,
|
||||
Fmt.FMT_WMMA_DC_BF16: 256,
|
||||
Fmt.FMT_WMMA_DC_F16: 256,
|
||||
Fmt.FMT_WMMA_DC_F32: 256,
|
||||
Fmt.FMT_WMMA_DC_I32: 256,
|
||||
Fmt.FMT_WMMA_INDEX_SET: 32,
|
||||
}
|
||||
|
||||
class OpType(Enum):
|
||||
OPR_ACCVGPR = auto()
|
||||
OPR_ATTR = auto()
|
||||
OPR_CLAUSE = auto()
|
||||
OPR_DELAY = auto()
|
||||
OPR_EXEC = auto()
|
||||
OPR_HWREG = auto()
|
||||
OPR_LABEL = auto()
|
||||
OPR_SDST = auto()
|
||||
OPR_SDST_NULL = auto()
|
||||
OPR_SENDMSG = auto()
|
||||
OPR_SENDMSG_RTN = auto()
|
||||
OPR_SIMM16 = auto()
|
||||
OPR_SIMM24 = auto()
|
||||
OPR_SIMM4 = auto()
|
||||
OPR_SIMM5 = auto()
|
||||
OPR_SIMM8 = auto()
|
||||
OPR_SLEEP = auto()
|
||||
OPR_SMEM_OFFSET = auto()
|
||||
OPR_SMEM_OFFSET_NOK = auto()
|
||||
OPR_SRC = auto()
|
||||
OPR_SRC_ACCVGPR = auto()
|
||||
OPR_SRC_NOLDS = auto()
|
||||
OPR_SRC_NOLIT = auto()
|
||||
OPR_SRC_SIMPLE = auto()
|
||||
OPR_SRC_VGPR = auto()
|
||||
OPR_SRC_VGPR_OR_ACCVGPR = auto()
|
||||
OPR_SRC_VGPR_OR_ACCVGPR_OR_CONST = auto()
|
||||
OPR_SRC_VGPR_OR_INLINE = auto()
|
||||
OPR_SREG = auto()
|
||||
OPR_SREG_LITERAL = auto()
|
||||
OPR_SREG_M0 = auto()
|
||||
OPR_SREG_M0_INL = auto()
|
||||
OPR_SREG_NOVCC = auto()
|
||||
OPR_SSRC = auto()
|
||||
OPR_SSRC_BARRIER_ID = auto()
|
||||
OPR_SSRC_LANESEL = auto()
|
||||
OPR_SSRC_NOLIT = auto()
|
||||
OPR_TGT = auto()
|
||||
OPR_VERSION = auto()
|
||||
OPR_VGPR = auto()
|
||||
OPR_VGPR_OR_ACCVGPR = auto()
|
||||
OPR_VGPR_OR_LDS = auto()
|
||||
OPR_WAITCNT = auto()
|
||||
OPR_WAITCNT_DEPCTR = auto()
|
||||
OPR_WAIT_ALU = auto()
|
||||
OPR_WAIT_EVENT = auto()
|
||||
OPR_WAIT_MEM_DS = auto()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
||||
# Instruction format detection and decoding
|
||||
from __future__ import annotations
|
||||
from extra.assembly.amd.dsl import Inst, FixedBitField
|
||||
from extra.assembly.amd.autogen.rdna3.ins import (VOP1, VOP1_SDST, VOP2, VOP3, VOP3_SDST, VOP3SD, VOP3P, VOPC, VOPD, VINTERP,
|
||||
SOP1, SOP2, SOPC, SOPK, SOPP, SMEM, DS, FLAT)
|
||||
from extra.assembly.amd.autogen.rdna4.ins import (VOP1 as R4_VOP1, VOP1_SDST as R4_VOP1_SDST, VOP2 as R4_VOP2,
|
||||
VOP3 as R4_VOP3, VOP3_SDST as R4_VOP3_SDST, VOP3SD as R4_VOP3SD, VOP3P as R4_VOP3P,
|
||||
VOPC as R4_VOPC, VOPD as R4_VOPD, VINTERP as R4_VINTERP, SOP1 as R4_SOP1, SOP2 as R4_SOP2, SOPC as R4_SOPC, SOPK as R4_SOPK, SOPP as R4_SOPP,
|
||||
SMEM as R4_SMEM, DS as R4_DS)
|
||||
from extra.assembly.amd.autogen.cdna.ins import (VOP1 as C_VOP1, VOP2 as C_VOP2, VOPC as C_VOPC, VOP3A, VOP3B, VOP3P as C_VOP3P,
|
||||
SOP1 as C_SOP1, SOP2 as C_SOP2, SOPC as C_SOPC, SOPK as C_SOPK, SOPP as C_SOPP, SMEM as C_SMEM, DS as C_DS, FLAT as C_FLAT)
|
||||
|
||||
def _matches_encoding(word: int, cls: type[Inst]) -> bool:
|
||||
"""Check if word matches the encoding pattern of an instruction class."""
|
||||
enc = next(((n, f) for n, f in cls._fields if isinstance(f, FixedBitField) and n == 'encoding'), None)
|
||||
if enc is None: return False
|
||||
bf = enc[1]
|
||||
return ((word >> bf.lo) & bf.mask) == bf.default
|
||||
|
||||
# Order matters: more specific encodings first, VOP2 last (it's a catch-all for bit31=0)
|
||||
_RDNA_FORMATS_64 = [VOPD, VOP3P, VINTERP, VOP3, DS, FLAT, SMEM]
|
||||
_RDNA_FORMATS_32 = [SOP1, SOPC, SOPP, SOPK, VOPC, VOP1, SOP2, VOP2] # SOP2/VOP2 are catch-alls
|
||||
_CDNA_FORMATS_64 = [C_VOP3P, VOP3A, C_DS, C_FLAT, C_SMEM]
|
||||
_CDNA_FORMATS_32 = [C_SOP1, C_SOPC, C_SOPP, C_SOPK, C_VOPC, C_VOP1, C_SOP2, C_VOP2]
|
||||
_CDNA_VOP3B_OPS = {281, 282, 283, 284, 285, 286, 480, 481, 488, 489} # VOP3B opcodes
|
||||
_RDNA4_FORMATS_64 = [R4_VOPD, R4_VOP3P, R4_VINTERP, R4_VOP3, R4_DS, R4_SMEM]
|
||||
_RDNA4_FORMATS_32 = [R4_SOP1, R4_SOPC, R4_SOPP, R4_SOPK, R4_VOPC, R4_VOP1, R4_SOP2, R4_VOP2]
|
||||
_RDNA4_VOP3SD_OPS = {288, 289, 290, 764, 765, 766, 767, 768, 769, 770}
|
||||
_RDNA3_VOP3SD_OPS = {288, 289, 290, 764, 765, 766, 767, 768, 769, 770}
|
||||
# Instructions with SGPR destination (READLANE, READFIRSTLANE, and VOP3-encoded VOPC)
|
||||
_VOP1_SDST_OPS = {2} # V_READFIRSTLANE_B32_E32
|
||||
_VOP3_SDST_OPS = {386, 864} # V_READFIRSTLANE_B32_E64, V_READLANE_B32 (V_WRITELANE_B32=865 writes to VGPR)
|
||||
# VOP3-encoded VOPC instructions (opcodes < 256) also have SGPR destination
|
||||
|
||||
def detect_format(data: bytes, arch: str = "rdna3") -> type[Inst]:
|
||||
"""Detect instruction format from machine code bytes."""
|
||||
assert len(data) >= 4, f"need at least 4 bytes, got {len(data)}"
|
||||
word = int.from_bytes(data[:4], 'little')
|
||||
if arch == "cdna":
|
||||
# SDWA (0xf9) and DPP (0xfa) use special src0 encodings - not supported
|
||||
if (word & 0x1ff) in (0xf9, 0xfa): raise ValueError(f"CDNA SDWA/DPP not supported word={word:#010x}")
|
||||
if (word >> 30) == 0b11:
|
||||
for cls in _CDNA_FORMATS_64:
|
||||
if _matches_encoding(word, cls):
|
||||
return VOP3B if cls is VOP3A and ((word >> 16) & 0x3ff) in _CDNA_VOP3B_OPS else cls
|
||||
raise ValueError(f"unknown CDNA 64-bit format word={word:#010x}")
|
||||
for cls in _CDNA_FORMATS_32:
|
||||
if _matches_encoding(word, cls): return cls
|
||||
raise ValueError(f"unknown CDNA 32-bit format word={word:#010x}")
|
||||
if arch == "rdna4":
|
||||
if (word >> 30) == 0b11:
|
||||
for cls in _RDNA4_FORMATS_64:
|
||||
if _matches_encoding(word, cls):
|
||||
if cls is R4_VOP3:
|
||||
opcode = (word >> 16) & 0x3ff
|
||||
if opcode in _RDNA4_VOP3SD_OPS: return R4_VOP3SD
|
||||
if opcode in _VOP3_SDST_OPS or opcode < 256: return R4_VOP3_SDST # VOP3-encoded VOPC (op < 256) writes to SGPR
|
||||
return cls
|
||||
raise ValueError(f"unknown RDNA4 64-bit format word={word:#010x}")
|
||||
for cls in _RDNA4_FORMATS_32:
|
||||
if _matches_encoding(word, cls):
|
||||
if cls is R4_VOP1 and ((word >> 9) & 0xff) in _VOP1_SDST_OPS: return R4_VOP1_SDST
|
||||
return cls
|
||||
raise ValueError(f"unknown RDNA4 32-bit format word={word:#010x}")
|
||||
# RDNA3 (default)
|
||||
if (word >> 30) == 0b11:
|
||||
for cls in _RDNA_FORMATS_64:
|
||||
if _matches_encoding(word, cls):
|
||||
if cls is VOP3:
|
||||
opcode = (word >> 16) & 0x3ff
|
||||
if opcode in _RDNA3_VOP3SD_OPS: return VOP3SD
|
||||
if opcode in _VOP3_SDST_OPS or opcode < 256: return VOP3_SDST # VOP3-encoded VOPC (op < 256) writes to SGPR
|
||||
return cls
|
||||
raise ValueError(f"unknown 64-bit format word={word:#010x}")
|
||||
for cls in _RDNA_FORMATS_32:
|
||||
if _matches_encoding(word, cls):
|
||||
if cls is VOP1 and ((word >> 9) & 0xff) in _VOP1_SDST_OPS: return VOP1_SDST
|
||||
return cls
|
||||
raise ValueError(f"unknown 32-bit format word={word:#010x}")
|
||||
|
||||
def decode_inst(data: bytes, arch: str = "rdna3") -> Inst:
|
||||
"""Decode machine code bytes into an instruction."""
|
||||
return detect_format(data, arch).from_bytes(data)
|
||||
@@ -1,671 +0,0 @@
|
||||
# RDNA3/RDNA4/CDNA disassembler
|
||||
from __future__ import annotations
|
||||
import re, struct
|
||||
from extra.assembly.amd.dsl import Inst, Reg
|
||||
|
||||
# Special register mappings for disassembly
|
||||
SPECIAL_GPRS = {106: 'vcc_lo', 107: 'vcc_hi', 124: 'null', 125: 'm0', 126: 'exec_lo', 127: 'exec_hi',
|
||||
128: '0', 240: '0.5', 241: '-0.5', 242: '1.0', 243: '-1.0', 244: '2.0', 245: '-2.0', 246: '4.0', 247: '-4.0', 248: '0x3e22f983', 253: 'scc'}
|
||||
SPECIAL_GPRS_CDNA = {106: 'vcc_lo', 107: 'vcc_hi', 124: 'null', 125: 'm0', 126: 'exec_lo', 127: 'exec_hi',
|
||||
128: '0', 240: '0.5', 241: '-0.5', 242: '1.0', 243: '-1.0', 244: '2.0', 245: '-2.0', 246: '4.0', 247: '-4.0', 248: '0x3e22f983', 253: 'scc',
|
||||
102: 'flat_scratch_lo', 103: 'flat_scratch_hi', 104: 'xnack_mask_lo', 105: 'xnack_mask_hi'}
|
||||
SPECIAL_PAIRS = {106: 'vcc', 126: 'exec'}
|
||||
SPECIAL_PAIRS_CDNA = {106: 'vcc', 126: 'exec', 102: 'flat_scratch', 104: 'xnack_mask'}
|
||||
|
||||
def decode_src(v, cdna: bool = False) -> str:
|
||||
"""Decode a source operand encoding to its string representation."""
|
||||
v = _unwrap(v)
|
||||
gprs = SPECIAL_GPRS_CDNA if cdna else SPECIAL_GPRS
|
||||
if v in gprs: return gprs[v]
|
||||
if v < 106: return f's{v}'
|
||||
if 108 <= v < 124: return f'ttmp{v - 108}'
|
||||
if 129 <= v <= 192: return str(v - 128) # positive integers 1-64
|
||||
if 193 <= v <= 208: return str(-(v - 192)) # negative integers -1 to -16
|
||||
if v >= 256: return f'v{v - 256}'
|
||||
return f's{v}'
|
||||
|
||||
def _unwrap(v) -> int:
|
||||
"""Unwrap Reg to int offset, or return int as-is."""
|
||||
return v.offset if isinstance(v, Reg) else v
|
||||
|
||||
def _vi(v) -> int:
|
||||
"""Get VGPR index from Reg or int (for v[N] fields that encode as 256+N)."""
|
||||
off = _unwrap(v)
|
||||
return off - 256 if off >= 256 else off
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# LITERAL FORMATTING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_FLOAT_DEC = {240: 0.5, 241: -0.5, 242: 1.0, 243: -1.0, 244: 2.0, 245: -2.0, 246: 4.0, 247: -4.0}
|
||||
|
||||
def _lit(inst, v, neg=0) -> str:
|
||||
"""Format literal/inline constant value."""
|
||||
v = _unwrap(v)
|
||||
if v == 255:
|
||||
lit = inst._literal
|
||||
if lit is None: return "0"
|
||||
s = f"0x{lit:x}"
|
||||
elif v in _FLOAT_DEC: s = str(_FLOAT_DEC[v])
|
||||
elif 128 <= v <= 192: s = str(v - 128)
|
||||
elif 193 <= v <= 208: s = str(-(v - 192))
|
||||
elif v < 128: s = decode_src(v)
|
||||
elif v >= 256: s = f"v{v - 256}"
|
||||
else: s = decode_src(v)
|
||||
return f"-{s}" if neg else s
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# INSTRUCTION METADATA - fallback functions when inst.num_srcs()/inst.operands unavailable
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _num_srcs(inst) -> int:
|
||||
"""Fallback: get number of source operands from instruction name."""
|
||||
name = getattr(inst, 'op_name', '') or ''
|
||||
n = name.upper()
|
||||
# FMAC/MAC ops are 2-source (dst is implicit accumulator), but FMA/MAD ops are 3-source
|
||||
if 'FMAC' in n or 'V_MAC_' in n: return 2
|
||||
if any(x in n for x in ('FMA', 'MAD', 'CNDMASK', 'BFE', 'BFI', 'LERP', 'MED3', 'SAD', 'DIV_FMAS', 'DIV_FIXUP', 'DIV_SCALE', 'CUBE')): return 3
|
||||
# PERMLANE_VAR ops are 2-source, but PERMLANE (non-VAR) are 3-source
|
||||
if 'PERMLANE' in n and '_VAR' not in n: return 3
|
||||
if any(x in n for x in ('_ADD3', '_LSHL_ADD', '_ADD_LSHL', '_LSHL_OR', '_AND_OR', 'OR3_B32', 'AND_OR_B32', 'ALIGNBIT', 'ALIGNBYTE', 'V_PERM_', 'XOR3', 'XAD', 'MULLIT', 'MINMAX', 'MAXMIN', 'MINIMUMMAXIMUM', 'MAXIMUMMINIMUM', 'MINIMUM3', 'MAXIMUM3', 'MIN3', 'MAX3', 'DOT2', 'CVT_PK_U8_F32', 'DOT4', 'DOT8', 'WMMA', 'SWMMAC')): return 3
|
||||
return 2
|
||||
|
||||
# SWMMAC register counts: (dst, src0, src1, src2)
|
||||
def _swmmac_regs(name: str) -> tuple[int, int, int, int]:
|
||||
"""Return (dst, src0, src1, src2) register counts for SWMMAC instructions."""
|
||||
if 'f16_16x16x32' in name or 'bf16_16x16x32' in name: return (4, 4, 8, 1)
|
||||
if 'f32_16x16x32_f16' in name or 'f32_16x16x32_bf16' in name: return (8, 4, 8, 1)
|
||||
if 'i32_16x16x32_iu4' in name: return (8, 1, 2, 1)
|
||||
if 'i32_16x16x64_iu4' in name: return (8, 2, 4, 1)
|
||||
if 'i32_16x16x32_iu8' in name or 'f32_16x16x32_fp8' in name or 'f32_16x16x32_bf8' in name: return (8, 2, 4, 1)
|
||||
return (8, 8, 8, 8)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# IMPORTS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
from extra.assembly.amd.autogen.rdna3.ins import (VOP1, VOP1_SDST, VOP2, VOP3, VOP3_SDST, VOP3SD, VOP3P, VOPC, VOPD, VINTERP, SOP1, SOP2, SOPC, SOPK, SOPP, SMEM, DS, FLAT, GLOBAL, SCRATCH,
|
||||
VOP1Op, VOP2Op, VOP3Op, VOP3SDOp, VOPDOp, SOP1Op, SOPKOp, SOPPOp, SMEMOp, DSOp)
|
||||
from extra.assembly.amd.autogen.rdna4.ins import (VOP1 as R4_VOP1, VOP1_SDST as R4_VOP1_SDST, VOP2 as R4_VOP2, VOP3 as R4_VOP3, VOP3_SDST as R4_VOP3_SDST, VOP3SD as R4_VOP3SD, VOP3P as R4_VOP3P,
|
||||
VOPC as R4_VOPC, VOPD as R4_VOPD, VINTERP as R4_VINTERP, SOP1 as R4_SOP1, SOP2 as R4_SOP2, SOPC as R4_SOPC, SOPK as R4_SOPK, SOPP as R4_SOPP,
|
||||
SMEM as R4_SMEM, DS as R4_DS, VOPDOp as R4_VOPDOp)
|
||||
from extra.assembly.amd.autogen.cdna.ins import FLAT as C_FLAT
|
||||
|
||||
def _is_cdna(inst: Inst) -> bool: return 'cdna' in inst.__class__.__module__
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# CONSTANTS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
HWREG = {1: 'HW_REG_MODE', 2: 'HW_REG_STATUS', 3: 'HW_REG_TRAPSTS', 4: 'HW_REG_HW_ID', 5: 'HW_REG_GPR_ALLOC',
|
||||
6: 'HW_REG_LDS_ALLOC', 7: 'HW_REG_IB_STS', 15: 'HW_REG_SH_MEM_BASES', 18: 'HW_REG_PERF_SNAPSHOT_PC_LO',
|
||||
19: 'HW_REG_PERF_SNAPSHOT_PC_HI', 20: 'HW_REG_FLAT_SCR_LO', 21: 'HW_REG_FLAT_SCR_HI', 22: 'HW_REG_XNACK_MASK',
|
||||
23: 'HW_REG_HW_ID1', 24: 'HW_REG_HW_ID2', 25: 'HW_REG_POPS_PACKER', 28: 'HW_REG_IB_STS2'}
|
||||
HWREG_RDNA4 = {1: 'HW_REG_MODE', 2: 'HW_REG_STATUS', 4: 'HW_REG_STATE_PRIV', 5: 'HW_REG_GPR_ALLOC',
|
||||
6: 'HW_REG_LDS_ALLOC', 7: 'HW_REG_IB_STS', 10: 'HW_REG_PERF_SNAPSHOT_DATA', 11: 'HW_REG_PERF_SNAPSHOT_PC_LO',
|
||||
12: 'HW_REG_PERF_SNAPSHOT_PC_HI', 15: 'HW_REG_PERF_SNAPSHOT_DATA1', 16: 'HW_REG_PERF_SNAPSHOT_DATA2',
|
||||
17: 'HW_REG_EXCP_FLAG_PRIV', 18: 'HW_REG_EXCP_FLAG_USER', 19: 'HW_REG_TRAP_CTRL',
|
||||
20: 'HW_REG_SCRATCH_BASE_LO', 21: 'HW_REG_SCRATCH_BASE_HI', 23: 'HW_REG_HW_ID1',
|
||||
24: 'HW_REG_HW_ID2', 26: 'HW_REG_SCHED_MODE', 29: 'HW_REG_SHADER_CYCLES_LO',
|
||||
30: 'HW_REG_SHADER_CYCLES_HI', 31: 'HW_REG_DVGPR_ALLOC_LO', 32: 'HW_REG_DVGPR_ALLOC_HI'}
|
||||
MSG = {128: 'MSG_RTN_GET_DOORBELL', 129: 'MSG_RTN_GET_DDID', 130: 'MSG_RTN_GET_TMA',
|
||||
131: 'MSG_RTN_GET_REALTIME', 132: 'MSG_RTN_SAVE_WAVE', 133: 'MSG_RTN_GET_TBA',
|
||||
134: 'MSG_RTN_GET_TBA_TO_PC', 135: 'MSG_RTN_GET_SE_AID_ID'}
|
||||
# CDNA opcode name aliases for disasm (new name -> old name expected by tests)
|
||||
_CDNA_DISASM_ALIASES = {'v_fmac_f64': 'v_mul_legacy_f32', 'v_dot2c_f32_bf16': 'v_mac_f32', 'v_fmamk_f32': 'v_madmk_f32', 'v_fmaak_f32': 'v_madak_f32'}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# HELPERS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _reg(p: str, b: int, n: int = 1) -> str: return f"{p}{_unwrap(b)}" if n == 1 else f"{p}[{_unwrap(b)}:{_unwrap(b)+n-1}]"
|
||||
def _sreg(b: int, n: int = 1) -> str: return _reg("s", _unwrap(b), n)
|
||||
def _vreg(b: int, n: int = 1) -> str: b = _unwrap(b); return _reg("v", b - 256 if b >= 256 else b, n)
|
||||
def _areg(b: int, n: int = 1) -> str: b = _unwrap(b); return _reg("a", b - 256 if b >= 256 else b, n) # accumulator registers for GFX90a
|
||||
def _ttmp(b, n: int = 1) -> str: b = _unwrap(b); return _reg("ttmp", b - 108, n) if 108 <= b <= 123 else None
|
||||
def _sreg_or_ttmp(b, n: int = 1) -> str: return _ttmp(b, n) or _sreg(b, n)
|
||||
|
||||
def _fmt_sdst(v, n: int = 1, cdna: bool = False) -> str:
|
||||
v = _unwrap(v)
|
||||
if t := _ttmp(v, n): return t
|
||||
pairs = SPECIAL_PAIRS_CDNA if cdna else SPECIAL_PAIRS
|
||||
gprs = SPECIAL_GPRS_CDNA if cdna else SPECIAL_GPRS
|
||||
if n > 1: return pairs.get(v) or gprs.get(v) or _sreg(v, n) # also check gprs for null/m0
|
||||
return gprs.get(v, f"s{v}")
|
||||
|
||||
def _fmt_src(v, n: int = 1, cdna: bool = False) -> str:
|
||||
v = _unwrap(v)
|
||||
if v == 253: return "src_scc" # SCC as source operand
|
||||
if n == 1: return decode_src(v, cdna)
|
||||
if v >= 256: return _vreg(v, n)
|
||||
if v <= 101: return _sreg(v, n) # s0-s101 can be pairs, but 102+ are special on CDNA
|
||||
pairs = SPECIAL_PAIRS_CDNA if cdna else SPECIAL_PAIRS
|
||||
if n == 2 and v in pairs: return pairs[v]
|
||||
if v <= 105: return _sreg(v, n) # s102-s105 regular pairs for RDNA
|
||||
if t := _ttmp(v, n): return t
|
||||
return decode_src(v, cdna)
|
||||
|
||||
def _fmt_v16(v, base: int = 256, hi_thresh: int = 384) -> str:
|
||||
v = _unwrap(v)
|
||||
return f"v{(v - base) & 0x7f}.{'h' if v >= hi_thresh else 'l'}"
|
||||
|
||||
def _has(op: str, *subs) -> bool: return any(s in op for s in subs)
|
||||
def _omod(v: int) -> str: return {1: " mul:2", 2: " mul:4", 3: " div:2"}.get(v, "")
|
||||
def _src16(inst, v: int) -> str: v = _unwrap(v); return _fmt_v16(v) if v >= 256 else _lit(inst, v) # format 16-bit src: vgpr.h/l or literal
|
||||
def _mods(*pairs) -> str: return " ".join(m for c, m in pairs if c)
|
||||
def _fmt_bits(label: str, val: int, count: int) -> str: return f"{label}:[{','.join(str((val >> i) & 1) for i in range(count))}]"
|
||||
|
||||
def _vop3_src(inst, v: int, neg: int, abs_: int, hi: int, n: int, f16: bool) -> str:
|
||||
"""Format VOP3 source operand with modifiers."""
|
||||
v = _unwrap(v)
|
||||
if v == 255: s = _lit(inst, v) # literal constant takes priority
|
||||
elif n > 1: s = _fmt_src(v, n)
|
||||
elif f16 and v >= 256: s = f"v{v - 256}.h" if hi else f"v{v - 256}.l"
|
||||
elif v == 253: s = "src_scc" # VOP3 sources use src_scc not scc
|
||||
else: s = _lit(inst, v)
|
||||
if abs_: s = f"|{s}|"
|
||||
return f"-{s}" if neg else s
|
||||
|
||||
def _opsel_str(opsel: int, n: int, need: bool, is16_d: bool) -> str:
|
||||
"""Format op_sel modifier string."""
|
||||
if not need: return ""
|
||||
dst_hi = (opsel >> 3) & 1
|
||||
if n == 1: return f" op_sel:[{opsel & 1},{dst_hi}]"
|
||||
if n == 2: return f" op_sel:[{opsel & 1},{(opsel >> 1) & 1},{dst_hi}]"
|
||||
return f" op_sel:[{opsel & 1},{(opsel >> 1) & 1},{(opsel >> 2) & 1},{dst_hi}]"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# DISASSEMBLER
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _disasm_vop1(inst: VOP1) -> str:
|
||||
name, cdna = inst.op_name.lower() or f'vop1_op_{inst.op}', _is_cdna(inst)
|
||||
name = name.replace('_e32', '') # Strip _e32 suffix
|
||||
if any(x in name for x in ('v_nop', 'v_pipeflush', 'v_clrexcp')): return name # no operands
|
||||
if 'readfirstlane' in name:
|
||||
src = inst.src0.fmt() if inst.src0.offset >= 256 else decode_src(inst.src0.offset, cdna)
|
||||
vdst_off = inst.vdst.offset - 256 if inst.vdst.offset >= 256 else inst.vdst.offset
|
||||
return f"{name} {_fmt_sdst(vdst_off, 1, cdna)}, {src}"
|
||||
bits = inst.canonical_op_bits
|
||||
is16_dst, is16_src = not cdna and bits['d'] == 16, not cdna and bits['s0'] == 16
|
||||
# v_cvt_pk_f32_fp8/bf8: pcode has None dst type but outputs 2 VGPRs
|
||||
if 'cvt_pk_f32_fp8' in name or 'cvt_pk_f32_bf8' in name: is16_src = True
|
||||
# Format dst
|
||||
if is16_dst: dst = _fmt_v16(inst.vdst)
|
||||
else: dst = inst.vdst.fmt()
|
||||
# Format src
|
||||
if inst.src0.offset == 255: src = _lit(inst, inst.src0)
|
||||
elif is16_src and inst.src0.offset >= 256: src = _fmt_v16(inst.src0)
|
||||
elif inst.src0.sz > 1: src = _fmt_src(inst.src0, inst.src0.sz, cdna)
|
||||
else: src = _lit(inst, inst.src0)
|
||||
return f"{name} {dst}, {src}"
|
||||
|
||||
_VOP2_CARRY_OUT = {'v_add_co_u32', 'v_sub_co_u32', 'v_subrev_co_u32'} # carry out only
|
||||
_VOP2_CARRY_INOUT = {'v_addc_co_u32', 'v_subb_co_u32', 'v_subbrev_co_u32'} # carry in and out (CDNA)
|
||||
_VOP2_CARRY_INOUT_RDNA = {'v_add_co_ci_u32', 'v_sub_co_ci_u32', 'v_subrev_co_ci_u32'} # carry in and out (RDNA)
|
||||
def _disasm_vop2(inst: VOP2) -> str:
|
||||
name, cdna = inst.op_name.lower(), _is_cdna(inst)
|
||||
if cdna: name = _CDNA_DISASM_ALIASES.get(name, name) # apply CDNA aliases
|
||||
suf = "" if cdna or name.endswith('_e32') or (not cdna and inst.op == VOP2Op.V_DOT2ACC_F32_F16_E32) else "_e32"
|
||||
lit = getattr(inst, '_literal', None)
|
||||
is16 = not cdna and inst.canonical_op_bits['d'] == 16
|
||||
# fmaak/madak: dst = src0 * vsrc1 + K, fmamk/madmk: dst = src0 * K + vsrc1
|
||||
if 'fmaak' in name or 'madak' in name or (not cdna and inst.op in (VOP2Op.V_FMAAK_F32_E32, VOP2Op.V_FMAAK_F16_E32)):
|
||||
if lit is None: return f"op_{inst.op.value if hasattr(inst.op, 'value') else inst.op}"
|
||||
if is16: return f"{name}{suf} {_fmt_v16(inst.vdst)}, {_src16(inst, inst.src0)}, {_fmt_v16(inst.vsrc1)}, 0x{lit:x}"
|
||||
return f"{name}{suf} {inst.vdst.fmt()}, {_lit(inst, inst.src0)}, {inst.vsrc1.fmt()}, 0x{lit:x}"
|
||||
if 'fmamk' in name or 'madmk' in name or (not cdna and inst.op in (VOP2Op.V_FMAMK_F32_E32, VOP2Op.V_FMAMK_F16_E32)):
|
||||
if lit is None: return f"op_{inst.op.value if hasattr(inst.op, 'value') else inst.op}"
|
||||
if is16: return f"{name}{suf} {_fmt_v16(inst.vdst)}, {_src16(inst, inst.src0)}, 0x{lit:x}, {_fmt_v16(inst.vsrc1)}"
|
||||
return f"{name}{suf} {inst.vdst.fmt()}, {_lit(inst, inst.src0)}, 0x{lit:x}, {inst.vsrc1.fmt()}"
|
||||
if is16: return f"{name}{suf} {_fmt_v16(inst.vdst)}, {_src16(inst, inst.src0)}, {_fmt_v16(inst.vsrc1)}"
|
||||
vcc = "vcc" if cdna else "vcc_lo"
|
||||
if cdna and name in _VOP2_CARRY_OUT: return f"{name}{suf} {inst.vdst.fmt()}, {vcc}, {_lit(inst, inst.src0)}, {inst.vsrc1.fmt()}"
|
||||
if cdna and name in _VOP2_CARRY_INOUT: return f"{name}{suf} {inst.vdst.fmt()}, {vcc}, {_lit(inst, inst.src0)}, {inst.vsrc1.fmt()}, {vcc}"
|
||||
if not cdna and name in _VOP2_CARRY_INOUT_RDNA: return f"{name}{suf} {inst.vdst.fmt()}, {vcc}, {_lit(inst, inst.src0)}, {inst.vsrc1.fmt()}, {vcc}"
|
||||
sn0 = inst.canonical_op_regs.get('s0', 1)
|
||||
if inst.vdst.sz > 1 or sn0 > 1 or inst.vsrc1.sz > 1:
|
||||
src0 = _lit(inst, inst.src0) if inst.src0.offset == 255 else _fmt_src(inst.src0, sn0, cdna)
|
||||
return f"{name.replace('_e32', '')} {inst.vdst.fmt()}, {src0}, {inst.vsrc1.fmt()}"
|
||||
return f"{name}{suf} {inst.vdst.fmt()}, {_lit(inst, inst.src0)}, {inst.vsrc1.fmt()}" + (f", {vcc}" if name == 'v_cndmask_b32' else "")
|
||||
|
||||
def _disasm_vopc(inst: VOPC) -> str:
|
||||
name, cdna = inst.op_name.lower(), _is_cdna(inst)
|
||||
bits = inst.canonical_op_bits
|
||||
is16 = bits['s0'] == 16
|
||||
if cdna:
|
||||
s0 = _lit(inst, inst.src0) if inst.src0.offset == 255 else _fmt_src(inst.src0, inst.src0.sz, cdna)
|
||||
return f"{name} vcc, {s0}, {inst.vsrc1.fmt()}" # CDNA VOPC always outputs vcc
|
||||
# RDNA: v_cmpx_* writes to exec (no vcc), v_cmp_* writes to vcc_lo
|
||||
has_vcc = 'cmpx' not in name
|
||||
s0 = _lit(inst, inst.src0) if inst.src0.offset == 255 else inst.src0.fmt() if inst.src0.sz > 1 else _src16(inst, inst.src0.offset) if is16 else _lit(inst, inst.src0)
|
||||
s1 = inst.vsrc1.fmt() if inst.vsrc1.sz > 1 else _fmt_v16(inst.vsrc1) if is16 else inst.vsrc1.fmt()
|
||||
suf = "" if name.endswith('_e32') else "_e32"
|
||||
return f"{name}{suf} vcc_lo, {s0}, {s1}" if has_vcc else f"{name}{suf} {s0}, {s1}"
|
||||
|
||||
NO_ARG_SOPP = {SOPPOp.S_BARRIER, SOPPOp.S_WAKEUP, SOPPOp.S_ICACHE_INV,
|
||||
SOPPOp.S_WAIT_IDLE, SOPPOp.S_ENDPGM_SAVED, SOPPOp.S_CODE_END, SOPPOp.S_ENDPGM_ORDERED_PS_DONE, SOPPOp.S_TTRACEDATA}
|
||||
_CDNA_NO_ARG_SOPP = {'s_endpgm', 's_barrier', 's_wakeup', 's_icache_inv', 's_ttracedata', 's_nop', 's_sethalt', 's_sleep',
|
||||
's_setprio', 's_trap', 's_incperflevel', 's_decperflevel', 's_sendmsg', 's_sendmsghalt'}
|
||||
|
||||
def _disasm_sopp(inst: SOPP) -> str:
|
||||
name, cdna = inst.op_name.lower(), _is_cdna(inst)
|
||||
is_rdna4 = 'rdna4' in inst.__class__.__module__
|
||||
# Ops that have no argument when simm16 == 0
|
||||
no_arg_zero = {'s_barrier', 's_wakeup', 's_icache_inv', 's_ttracedata', 's_wait_idle', 's_endpgm_saved',
|
||||
's_endpgm_ordered_ps_done', 's_code_end'}
|
||||
if name in no_arg_zero: return name if inst.simm16 == 0 else f"{name} {inst.simm16}"
|
||||
if name == 's_endpgm': return name if inst.simm16 == 0 else f"{name} {inst.simm16}"
|
||||
if cdna:
|
||||
if name == 's_waitcnt':
|
||||
vm, lgkm, exp = inst.simm16 & 0xf, (inst.simm16 >> 8) & 0x3f, (inst.simm16 >> 4) & 0x7
|
||||
p = [f"vmcnt({vm})" if vm != 0xf else "", f"expcnt({exp})" if exp != 7 else "", f"lgkmcnt({lgkm})" if lgkm != 0x3f else ""]
|
||||
return f"s_waitcnt {' '.join(x for x in p if x) or '0'}"
|
||||
if name.startswith(('s_cbranch', 's_branch')): return f"{name} {inst.simm16}"
|
||||
return f"{name} 0x{inst.simm16:x}" if inst.simm16 else name
|
||||
# RDNA (use name-based checks instead of enum-based for cross-arch compatibility)
|
||||
if name == 's_waitcnt':
|
||||
if is_rdna4:
|
||||
return f"{name} {inst.simm16}" if inst.simm16 else f"{name} 0"
|
||||
vm, exp, lgkm = (inst.simm16 >> 10) & 0x3f, inst.simm16 & 0xf, (inst.simm16 >> 4) & 0x3f
|
||||
p = [f"vmcnt({vm})" if vm != 0x3f else "", f"expcnt({exp})" if exp != 7 else "", f"lgkmcnt({lgkm})" if lgkm != 0x3f else ""]
|
||||
return f"s_waitcnt {' '.join(x for x in p if x) or '0'}"
|
||||
if name == 's_delay_alu':
|
||||
deps = ['VALU_DEP_1','VALU_DEP_2','VALU_DEP_3','VALU_DEP_4','TRANS32_DEP_1','TRANS32_DEP_2','TRANS32_DEP_3','FMA_ACCUM_CYCLE_1','SALU_CYCLE_1','SALU_CYCLE_2','SALU_CYCLE_3']
|
||||
skips = ['SAME','NEXT','SKIP_1','SKIP_2','SKIP_3','SKIP_4']
|
||||
id0, skip, id1 = inst.simm16 & 0xf, (inst.simm16 >> 4) & 0x7, (inst.simm16 >> 7) & 0xf
|
||||
dep = lambda v: deps[v-1] if 0 < v <= len(deps) else str(v)
|
||||
p = [f"instid0({dep(id0)})" if id0 else "", f"instskip({skips[skip]})" if skip else "", f"instid1({dep(id1)})" if id1 else ""]
|
||||
return f"s_delay_alu {' | '.join(x for x in p if x) or '0'}"
|
||||
if name.startswith(('s_cbranch', 's_branch')): return f"{name} 0x{inst.simm16:x}"
|
||||
return f"{name} 0x{inst.simm16:x}"
|
||||
|
||||
def _disasm_smem(inst: SMEM) -> str:
|
||||
name, cdna = inst.op_name.lower(), _is_cdna(inst)
|
||||
if name in ('s_gl1_inv', 's_dcache_inv'): return name
|
||||
soe, imm = getattr(inst, 'soe', 0), getattr(inst, 'imm', 1)
|
||||
is_rdna4 = 'rdna4' in inst.__class__.__module__
|
||||
offset = inst.ioffset if is_rdna4 else getattr(inst, 'offset', 0)
|
||||
if cdna:
|
||||
if soe and imm: off_s = f"{decode_src(inst.soffset, cdna)} offset:0x{offset:x}"
|
||||
elif imm: off_s = f"0x{offset:x}"
|
||||
elif offset < 256: off_s = decode_src(offset, cdna)
|
||||
else: off_s = decode_src(inst.soffset, cdna)
|
||||
elif offset and inst.soffset != 124: off_s = f"{decode_src(inst.soffset, cdna)} offset:0x{offset:x}"
|
||||
elif offset: off_s = f"0x{offset:x}"
|
||||
else: off_s = decode_src(inst.soffset, cdna)
|
||||
is_buffer = 'buffer' in name or 's_atc_probe_buffer' == name
|
||||
sbase_idx, sbase_count = _unwrap(inst.sbase), 4 if is_buffer else 2
|
||||
sbase_str = _fmt_src(sbase_idx, sbase_count, cdna) if sbase_count == 2 else _sreg(sbase_idx, sbase_count) if sbase_idx <= 105 else _reg("ttmp", sbase_idx - 108, sbase_count)
|
||||
if name in ('s_atc_probe', 's_atc_probe_buffer'): return f"{name} {_unwrap(inst.sdata)}, {sbase_str}, {off_s}"
|
||||
if 'prefetch' in name:
|
||||
off = getattr(inst, 'ioffset', getattr(inst, 'offset', 0))
|
||||
if off >= 0x800000: off = off - 0x1000000
|
||||
off_s = f"0x{off:x}" if off > 255 else str(off)
|
||||
soff_s = decode_src(inst.soffset, cdna) if inst.soffset != 124 else "null"
|
||||
if 'pc_rel' in name: return f"{name} {off_s}, {soff_s}, {_unwrap(inst.sdata)}"
|
||||
return f"{name} {sbase_str}, {off_s}, {soff_s}, {_unwrap(inst.sdata)}"
|
||||
# Use get_field_bits for register count
|
||||
dst_n = inst.canonical_op_regs.get('d', 1)
|
||||
th, scope = getattr(inst, 'th', 0), getattr(inst, 'scope', 0)
|
||||
if is_rdna4: # RDNA4 uses th/scope instead of glc/dlc
|
||||
th_names = ['TH_LOAD_RT', 'TH_LOAD_NT', 'TH_LOAD_HT', 'TH_LOAD_LU']
|
||||
scope_names = ['SCOPE_CU', 'SCOPE_SE', 'SCOPE_DEV', 'SCOPE_SYS']
|
||||
mods = (f" th:{th_names[th]}" if th else "") + (f" scope:{scope_names[scope]}" if scope else "")
|
||||
return f"{name} {_fmt_sdst(inst.sdata, dst_n, cdna)}, {sbase_str}, {off_s}{mods}"
|
||||
if th or scope:
|
||||
th_names = ['TH_LOAD_RT', 'TH_LOAD_NT', 'TH_LOAD_HT', 'TH_LOAD_LU']
|
||||
scope_names = ['SCOPE_CU', 'SCOPE_SE', 'SCOPE_DEV', 'SCOPE_SYS']
|
||||
mods = (f" th:{th_names[th]}" if th else "") + (f" scope:{scope_names[scope]}" if scope else "")
|
||||
return f"{name} {_fmt_sdst(inst.sdata, dst_n, cdna)}, {sbase_str}, {off_s}{mods}"
|
||||
return f"{name} {_fmt_sdst(inst.sdata, dst_n, cdna)}, {sbase_str}, {off_s}" + _mods((inst.glc, " glc"), (getattr(inst, 'dlc', 0), " dlc"))
|
||||
|
||||
def _disasm_flat(inst: FLAT) -> str:
|
||||
name, cdna = inst.op_name.lower(), _is_cdna(inst)
|
||||
acc = getattr(inst, 'acc', 0)
|
||||
reg_fn = _areg if acc else _vreg
|
||||
seg = ['flat', 'scratch', 'global'][inst.seg] if inst.seg < 3 else 'flat'
|
||||
instr = f"{seg}_{name.split('_', 1)[1] if '_' in name else name}"
|
||||
off_val = inst.offset if seg == 'flat' else (inst.offset if inst.offset < 4096 else inst.offset - 8192)
|
||||
# Use get_field_bits: data for stores/atomics, d for loads
|
||||
regs = inst.canonical_op_regs
|
||||
w = regs.get('data', regs.get('d', 1)) if 'store' in name or 'atomic' in name else regs.get('d', 1)
|
||||
off_s = f" offset:{off_val}" if off_val else ""
|
||||
if cdna: mods = f"{off_s}{' glc' if inst.sc0 else ''}{' slc' if inst.nt else ''}"
|
||||
else: mods = f"{off_s}{' glc' if inst.glc else ''}{' slc' if inst.slc else ''}{' dlc' if inst.dlc else ''}"
|
||||
if seg == 'flat' or _unwrap(inst.saddr) == 0x7F: saddr_s = ""
|
||||
elif _unwrap(inst.saddr) == 124: saddr_s = ", off"
|
||||
elif seg == 'scratch': saddr_s = f", {decode_src(inst.saddr, cdna)}"
|
||||
elif _unwrap(inst.saddr) in (SPECIAL_PAIRS_CDNA if cdna else SPECIAL_PAIRS): saddr_s = f", {(SPECIAL_PAIRS_CDNA if cdna else SPECIAL_PAIRS)[_unwrap(inst.saddr)]}"
|
||||
elif t := _ttmp(inst.saddr, 2): saddr_s = f", {t}"
|
||||
else: saddr_s = f", {_sreg(inst.saddr, 2) if _unwrap(inst.saddr) < 106 else decode_src(_unwrap(inst.saddr), cdna)}"
|
||||
if 'addtid' in name: return f"{instr} {reg_fn(inst.data if 'store' in name else inst.vdst)}{saddr_s}{mods}"
|
||||
if cdna: addr_w = 1 if seg == 'scratch' else 2
|
||||
else: addr_w = 1 if seg == 'scratch' or (_unwrap(inst.saddr) not in (0x7F, 124)) else 2
|
||||
addr_s = "off" if not inst.sve and seg == 'scratch' else _vreg(inst.addr, addr_w)
|
||||
data_s, vdst_s = reg_fn(inst.data, w), reg_fn(inst.vdst, w // 2 if 'cmpswap' in name else w)
|
||||
glc_or_sc0 = inst.sc0 if cdna else inst.glc
|
||||
if 'atomic' in name:
|
||||
return f"{instr} {vdst_s}, {addr_s}, {data_s}{saddr_s if seg != 'flat' else ''}{mods}" if glc_or_sc0 else f"{instr} {addr_s}, {data_s}{saddr_s if seg != 'flat' else ''}{mods}"
|
||||
if 'store' in name: return f"{instr} {addr_s}, {data_s}{saddr_s}{mods}"
|
||||
return f"{instr} {reg_fn(inst.vdst, w)}, {addr_s}{saddr_s}{mods}"
|
||||
|
||||
def _disasm_ds(inst: DS) -> str:
|
||||
op, name = inst.op, inst.op_name.lower()
|
||||
acc = getattr(inst, 'acc', 0)
|
||||
reg_fn = _areg if acc else _vreg
|
||||
gds = " gds" if getattr(inst, 'gds', 0) else ""
|
||||
off = f" offset:{inst.offset0 | (inst.offset1 << 8)}" if inst.offset0 or inst.offset1 else ""
|
||||
off2 = (" offset0:" + str(inst.offset0) if inst.offset0 else "") + (" offset1:" + str(inst.offset1) if inst.offset1 else "")
|
||||
# Use get_field_bits: data for stores/writes/atomics, d for loads
|
||||
regs = inst.canonical_op_regs
|
||||
w = regs.get('data', regs.get('d', 1)) if 'store' in name or 'write' in name or ('load' not in name and 'read' not in name) else regs.get('d', 1)
|
||||
d0, d1, dst, addr = reg_fn(inst.data0, w), reg_fn(inst.data1, w), reg_fn(inst.vdst, w), _vreg(inst.addr)
|
||||
|
||||
if name == 'ds_nop': return name
|
||||
if name == 'ds_bvh_stack_rtn_b32': return f"{name} {_vreg(inst.vdst)}, {addr}, {_vreg(inst.data0)}, {_vreg(inst.data1, 4)}{off}{gds}"
|
||||
if 'bvh_stack_push' in name:
|
||||
d1_regs = 8 if 'push8' in name else 4
|
||||
vdst_regs = 2 if 'pop2' in name else 1
|
||||
vdst_s = _vreg(inst.vdst, vdst_regs) if vdst_regs > 1 else _vreg(inst.vdst)
|
||||
return f"{name} {vdst_s}, {addr}, {_vreg(inst.data0)}, {_vreg(inst.data1, d1_regs)}{off}{gds}"
|
||||
if 'gws_sema' in name and 'sema_br' not in name: return f"{name}{off}{gds}"
|
||||
if 'gws_' in name: return f"{name} {addr}{off}{gds}"
|
||||
if name in ('ds_consume', 'ds_append'): return f"{name} {reg_fn(inst.vdst)}{off}{gds}"
|
||||
if 'gs_reg' in name: return f"{name} {reg_fn(inst.vdst, 2)}, {reg_fn(inst.data0)}{off}{gds}"
|
||||
if '2addr' in name:
|
||||
if 'load' in name: return f"{name} {reg_fn(inst.vdst, regs.get('d', 1))}, {addr}{off2}{gds}"
|
||||
if 'store' in name and 'xchg' not in name: return f"{name} {addr}, {d0}, {d1}{off2}{gds}"
|
||||
return f"{name} {reg_fn(inst.vdst, regs.get('d', 1))}, {addr}, {d0}, {d1}{off2}{gds}"
|
||||
if 'write2' in name: return f"{name} {addr}, {d0}, {d1}{off2}{gds}"
|
||||
if 'read2' in name: return f"{name} {reg_fn(inst.vdst, regs.get('d', 1))}, {addr}{off2}{gds}"
|
||||
if 'load' in name: return f"{name} {reg_fn(inst.vdst)}{off}{gds}" if 'addtid' in name else f"{name} {dst}, {addr}{off}{gds}"
|
||||
if 'store' in name and not _has(name, 'cmp', 'xchg'):
|
||||
return f"{name} {reg_fn(inst.data0)}{off}{gds}" if 'addtid' in name else f"{name} {addr}, {d0}{off}{gds}"
|
||||
if 'swizzle' in name or name == 'ds_ordered_count': return f"{name} {reg_fn(inst.vdst)}, {addr}{off}{gds}"
|
||||
if 'permute' in name: return f"{name} {reg_fn(inst.vdst)}, {addr}, {reg_fn(inst.data0)}{off}{gds}"
|
||||
if 'condxchg' in name: return f"{name} {reg_fn(inst.vdst, 2)}, {addr}, {reg_fn(inst.data0, 2)}{off}{gds}"
|
||||
if _has(name, 'cmpstore', 'mskor', 'wrap'):
|
||||
return f"{name} {dst}, {addr}, {d0}, {d1}{off}{gds}" if '_rtn' in name else f"{name} {addr}, {d0}, {d1}{off}{gds}"
|
||||
return f"{name} {dst}, {addr}, {d0}{off}{gds}" if '_rtn' in name else f"{name} {addr}, {d0}{off}{gds}"
|
||||
|
||||
def _disasm_vop3(inst: VOP3) -> str:
|
||||
op, name = inst.op, inst.op_name.lower()
|
||||
n_up = name.upper()
|
||||
bits = inst.canonical_op_bits
|
||||
|
||||
# RDNA4 v_s_* scalar VOP3 instructions - vdst is SGPR (VGPRField adds 256)
|
||||
if name.startswith('v_s_'):
|
||||
src = _lit(inst, inst.src0) if _unwrap(inst.src0) == 255 else ("src_scc" if _unwrap(inst.src0) == 253 else _fmt_src(inst.src0, max(1, bits['s0'] // 32)))
|
||||
if inst.neg & 1: src = f"-{src}"
|
||||
if inst.abs & 1: src = f"|{src}|"
|
||||
clamp = getattr(inst, 'cm', None) or getattr(inst, 'clmp', 0)
|
||||
vdst_raw = _unwrap(inst.vdst)
|
||||
return f"{name} s{vdst_raw - 256 if vdst_raw >= 256 else vdst_raw}, {src}" + (" clamp" if clamp else "") + _omod(inst.omod)
|
||||
|
||||
# Use get_field_bits for register sizes and 16-bit detection
|
||||
r0, r1, r2 = max(1, bits['s0'] // 32), max(1, bits['s1'] // 32), max(1, bits['s2'] // 32)
|
||||
dn = max(1, bits['d'] // 32)
|
||||
is16_d, is16_s, is16_s2 = bits['d'] == 16, bits['s0'] == 16, bits['s2'] == 16
|
||||
|
||||
s0 = _vop3_src(inst, inst.src0, inst.neg&1, inst.abs&1, inst.opsel&1, r0, is16_s)
|
||||
s1 = _vop3_src(inst, inst.src1, inst.neg&2, inst.abs&2, inst.opsel&2, r1, is16_s)
|
||||
s2 = _vop3_src(inst, inst.src2, inst.neg&4, inst.abs&4, inst.opsel&4, r2, is16_s2)
|
||||
|
||||
# Format destination
|
||||
if 'readlane' in name:
|
||||
vdst_off = inst.vdst.offset - 256 if inst.vdst.offset >= 256 else inst.vdst.offset
|
||||
dst = _fmt_sdst(vdst_off, 1)
|
||||
elif is16_d: dst = f"{inst.vdst.fmt()}.h" if (inst.opsel & 8) else f"{inst.vdst.fmt()}.l"
|
||||
else: dst = inst.vdst.fmt()
|
||||
|
||||
clamp = getattr(inst, 'cm', None) or getattr(inst, 'clmp', 0)
|
||||
cl, om = " clamp" if clamp else "", _omod(inst.omod)
|
||||
nonvgpr_opsel = (inst.src0.offset < 256 and (inst.opsel & 1)) or (inst.src1.offset < 256 and (inst.opsel & 2)) or (inst.src2.offset < 256 and (inst.opsel & 4))
|
||||
need_opsel = nonvgpr_opsel or (inst.opsel and not is16_s)
|
||||
|
||||
op_val = inst.op.value if hasattr(inst.op, 'value') else inst.op
|
||||
e64 = "" if name.endswith('_e64') else "_e64"
|
||||
if op_val < 256: # VOPC
|
||||
vdst_off = inst.vdst.offset - 256 if inst.vdst.offset >= 256 else inst.vdst.offset
|
||||
return f"{name}{e64} {s0}, {s1}{cl}" if name.startswith('v_cmpx') else f"{name}{e64} {_fmt_sdst(vdst_off, 1)}, {s0}, {s1}{cl}"
|
||||
if op_val < 384: # VOP2
|
||||
n = inst.num_srcs() or 2
|
||||
os = _opsel_str(inst.opsel, n, need_opsel, is16_d)
|
||||
return f"{name}{e64} {dst}, {s0}, {s1}, {s2}{os}{cl}{om}" if n == 3 else f"{name}{e64} {dst}, {s0}, {s1}{os}{cl}{om}"
|
||||
if op_val < 512: # VOP1
|
||||
if re.match(r'v_cvt_f32_(bf|fp)8', name) and inst.opsel:
|
||||
os = f" byte_sel:{((inst.opsel & 1) << 1) | ((inst.opsel >> 1) & 1)}"
|
||||
else:
|
||||
os = _opsel_str(inst.opsel, 1, need_opsel, is16_d)
|
||||
if 'v_nop' in name or 'v_pipeflush' in name: return f"{name}{e64}"
|
||||
return f"{name}{e64} {dst}, {s0}{os}{cl}{om}"
|
||||
# Native VOP3
|
||||
n = inst.num_srcs() or 2
|
||||
os = f" byte_sel:{inst.opsel >> 2}" if 'cvt_sr' in name and inst.opsel else _opsel_str(inst.opsel, n, need_opsel, is16_d)
|
||||
return f"{name} {dst}, {s0}, {s1}, {s2}{os}{cl}{om}" if n == 3 else f"{name} {dst}, {s0}, {s1}{os}{cl}{om}"
|
||||
|
||||
def _disasm_vop3sd(inst: VOP3SD) -> str:
|
||||
name = inst.op_name.lower()
|
||||
def src(reg, neg):
|
||||
s = _lit(inst, reg.offset) if reg.offset == 255 else ("src_scc" if reg.offset == 253 else (reg.fmt() if reg.sz > 1 else _lit(inst, reg.offset)))
|
||||
return f"neg({s})" if neg and reg.offset == 255 else (f"-{s}" if neg else s)
|
||||
s0, s1, s2 = src(inst.src0, inst.neg & 1), src(inst.src1, inst.neg & 2), src(inst.src2, inst.neg & 4)
|
||||
# VOP3SD: _co_ ops (add/sub) without _ci_ have only 2 sources, all others (mad, div_scale, _co_ci_) have 3 sources
|
||||
has_only_two_srcs = '_co_' in name and '_ci_' not in name and 'mad' not in name
|
||||
srcs = f"{s0}, {s1}" if has_only_two_srcs else f"{s0}, {s1}, {s2}"
|
||||
clamp = getattr(inst, 'cm', None) or getattr(inst, 'clmp', 0)
|
||||
return f"{name} {inst.vdst.fmt()}, {_fmt_sdst(inst.sdst, 1)}, {srcs}{' clamp' if clamp else ''}{_omod(inst.omod)}"
|
||||
|
||||
def _disasm_vopd(inst: VOPD) -> str:
|
||||
lit = inst._literal or getattr(inst, 'literal', None)
|
||||
is_rdna4 = 'rdna4' in inst.__class__.__module__
|
||||
op_enum = R4_VOPDOp if is_rdna4 else VOPDOp
|
||||
vdst_y, nx, ny = (_unwrap(inst.vdsty) << 1) | ((_unwrap(inst.vdstx) & 1) ^ 1), op_enum(inst.opx).name.lower(), op_enum(inst.opy).name.lower()
|
||||
def half(n, vd, s0, vs1):
|
||||
vd, vs1 = _vi(vd), _vi(vs1)
|
||||
if 'mov' in n: return f"{n} v{vd}, {_lit(inst, s0)}"
|
||||
if 'fmamk' in n and lit: return f"{n} v{vd}, {_lit(inst, s0)}, 0x{lit:x}, v{vs1}"
|
||||
if 'fmaak' in n and lit: return f"{n} v{vd}, {_lit(inst, s0)}, v{vs1}, 0x{lit:x}"
|
||||
return f"{n} v{vd}, {_lit(inst, s0)}, v{vs1}"
|
||||
return f"{half(nx, inst.vdstx, inst.srcx0, inst.vsrcx1)} :: {half(ny, vdst_y, inst.srcy0, inst.vsrcy1)}"
|
||||
|
||||
def _disasm_vop3p(inst: VOP3P) -> str:
|
||||
name = inst.op_name.lower()
|
||||
is_wmma, is_swmmac, n, is_fma_mix = 'wmma' in name, 'swmmac' in name, inst.num_srcs() or 2, 'fma_mix' in name
|
||||
def get_src(reg):
|
||||
return _lit(inst, reg.offset) if reg.offset == 255 else reg.fmt()
|
||||
src0, src1, src2, dst = get_src(inst.src0), get_src(inst.src1), get_src(inst.src2), inst.vdst.fmt()
|
||||
opsel_hi = inst.opsel_hi | (inst.opsel_hi2 << 2)
|
||||
clamp = getattr(inst, 'cm', None) or getattr(inst, 'clmp', 0)
|
||||
if is_fma_mix:
|
||||
def m(s, neg, abs_): return f"-{f'|{s}|' if abs_ else s}" if neg else (f"|{s}|" if abs_ else s)
|
||||
src0, src1, src2 = m(src0, inst.neg & 1, inst.neg_hi & 1), m(src1, inst.neg & 2, inst.neg_hi & 2), m(src2, inst.neg & 4, inst.neg_hi & 4)
|
||||
mods = ([_fmt_bits("op_sel", inst.opsel, n)] if inst.opsel else []) + ([_fmt_bits("op_sel_hi", opsel_hi, n)] if opsel_hi else []) + (["clamp"] if clamp else [])
|
||||
elif is_swmmac:
|
||||
mods = ([f"index_key:{inst.opsel}"] if inst.opsel else []) + ([_fmt_bits("neg_lo", inst.neg, n)] if inst.neg else []) + \
|
||||
([_fmt_bits("neg_hi", inst.neg_hi, n)] if inst.neg_hi else []) + (["clamp"] if clamp else [])
|
||||
else:
|
||||
opsel_hi_default = 7 if n == 3 else 3
|
||||
mods = ([_fmt_bits("op_sel", inst.opsel, n)] if inst.opsel else []) + ([_fmt_bits("op_sel_hi", opsel_hi, n)] if opsel_hi != opsel_hi_default else []) + \
|
||||
([_fmt_bits("neg_lo", inst.neg, n)] if inst.neg else []) + ([_fmt_bits("neg_hi", inst.neg_hi, n)] if inst.neg_hi else []) + (["clamp"] if clamp else [])
|
||||
return f"{name} {dst}, {src0}, {src1}, {src2}{' ' + ' '.join(mods) if mods else ''}" if n == 3 else f"{name} {dst}, {src0}, {src1}{' ' + ' '.join(mods) if mods else ''}"
|
||||
|
||||
def _disasm_sop1(inst: SOP1) -> str:
|
||||
op, name, cdna = inst.op, inst.op_name.lower(), _is_cdna(inst)
|
||||
# Use get_field_bits for register sizes
|
||||
regs = inst.canonical_op_regs
|
||||
dst_regs, src_regs = regs.get('d', 1), regs.get('s0', 1)
|
||||
src = _lit(inst, inst.ssrc0) if _unwrap(inst.ssrc0) == 255 else _fmt_src(inst.ssrc0, src_regs, cdna)
|
||||
if not cdna:
|
||||
if 'getpc_b64' in name: return f"{name} {_fmt_sdst(inst.sdst, 2)}"
|
||||
if 'setpc_b64' in name or 'rfe_b64' in name: return f"{name} {src}"
|
||||
if 'swappc_b64' in name: return f"{name} {_fmt_sdst(inst.sdst, 2)}, {src}"
|
||||
if 'sendmsg_rtn' in name:
|
||||
v = _unwrap(inst.ssrc0)
|
||||
msg_str = MSG.get(v)
|
||||
return f"{name} {_fmt_sdst(inst.sdst, dst_regs)}, sendmsg({msg_str})" if msg_str else f"{name} {_fmt_sdst(inst.sdst, dst_regs)}, 0x{v:x}"
|
||||
sop1_src_only = ('S_ALLOC_VGPR', 'S_SLEEP_VAR', 'S_BARRIER_SIGNAL', 'S_BARRIER_SIGNAL_ISFIRST', 'S_BARRIER_INIT', 'S_BARRIER_JOIN')
|
||||
if inst.op_name in sop1_src_only: return f"{name} {src}"
|
||||
return f"{name} {_fmt_sdst(inst.sdst, dst_regs, cdna)}, {src}"
|
||||
|
||||
def _disasm_sop2(inst: SOP2) -> str:
|
||||
cdna, name = _is_cdna(inst), inst.op_name.lower()
|
||||
lit = getattr(inst, '_literal', None)
|
||||
# Use get_field_bits for register sizes
|
||||
regs = inst.canonical_op_regs
|
||||
dn, s0n, s1n = regs['d'], regs['s0'], regs['s1']
|
||||
s0 = _lit(inst, inst.ssrc0) if _unwrap(inst.ssrc0) == 255 else _fmt_src(inst.ssrc0, s0n, cdna)
|
||||
s1 = _lit(inst, inst.ssrc1) if _unwrap(inst.ssrc1) == 255 else _fmt_src(inst.ssrc1, s1n, cdna)
|
||||
dst = _fmt_sdst(inst.sdst, dn, cdna)
|
||||
if 'fmamk' in name and lit is not None: return f"{name} {dst}, {s0}, 0x{lit:x}, {s1}"
|
||||
if 'fmaak' in name and lit is not None: return f"{name} {dst}, {s0}, {s1}, 0x{lit:x}"
|
||||
return f"{name} {dst}, {s0}, {s1}"
|
||||
|
||||
def _disasm_sopc(inst: SOPC) -> str:
|
||||
cdna, regs = _is_cdna(inst), inst.canonical_op_regs
|
||||
s0 = _lit(inst, inst.ssrc0) if _unwrap(inst.ssrc0) == 255 else _fmt_src(inst.ssrc0, regs['s0'], cdna)
|
||||
s1 = _lit(inst, inst.ssrc1) if _unwrap(inst.ssrc1) == 255 else _fmt_src(inst.ssrc1, regs['s1'], cdna)
|
||||
return f"{inst.op_name.lower()} {s0}, {s1}"
|
||||
|
||||
def _disasm_sopk(inst: SOPK) -> str:
|
||||
op, name, cdna = inst.op, inst.op_name.lower(), _is_cdna(inst)
|
||||
is_rdna4 = 'rdna4' in inst.__class__.__module__
|
||||
hw = HWREG_RDNA4 if is_rdna4 else HWREG
|
||||
def fmt_hwreg(hid, hoff, hsz):
|
||||
if hid not in hw: return f"0x{inst.simm16:x}"
|
||||
hr_name = hw[hid]
|
||||
return f"hwreg({hr_name})" if hoff == 0 and hsz == 32 else f"hwreg({hr_name}, {hoff}, {hsz})"
|
||||
if name == 's_setreg_imm32_b32':
|
||||
hid, hoff, hsz = inst.simm16 & 0x3f, (inst.simm16 >> 6) & 0x1f, ((inst.simm16 >> 11) & 0x1f) + 1
|
||||
return f"{name} {fmt_hwreg(hid, hoff, hsz)}, 0x{inst._literal:x}"
|
||||
if name == 's_version': return f"{name} 0x{inst.simm16:x}"
|
||||
if name in ('s_setreg_b32', 's_getreg_b32'):
|
||||
hid, hoff, hsz = inst.simm16 & 0x3f, (inst.simm16 >> 6) & 0x1f, ((inst.simm16 >> 11) & 0x1f) + 1
|
||||
hs = fmt_hwreg(hid, hoff, hsz)
|
||||
return f"{name} {hs}, {_fmt_sdst(inst.sdst, 1, cdna)}" if 'setreg' in name else f"{name} {_fmt_sdst(inst.sdst, 1, cdna)}, {hs}"
|
||||
if name in ('s_subvector_loop_begin', 's_subvector_loop_end'):
|
||||
return f"{name} {_fmt_sdst(inst.sdst, 1)}, 0x{inst.simm16:x}"
|
||||
return f"{name} {_fmt_sdst(inst.sdst, inst.canonical_op_regs['d'], cdna)}, 0x{inst.simm16:x}"
|
||||
|
||||
def _disasm_vinterp(inst: VINTERP) -> str:
|
||||
mods = _mods((inst.waitexp, f"wait_exp:{inst.waitexp}"), (inst.clmp, "clamp"))
|
||||
return f"{inst.op_name.lower()} {inst.vdst.fmt()}, {_lit(inst, inst.src0, inst.neg & 1)}, {_lit(inst, inst.src1, inst.neg & 2)}, {_lit(inst, inst.src2, inst.neg & 4)}" + (" " + mods if mods else "")
|
||||
|
||||
DISASM_HANDLERS: dict[type, callable] = {
|
||||
VOP1: _disasm_vop1, VOP1_SDST: _disasm_vop1, VOP2: _disasm_vop2, VOPC: _disasm_vopc, VOP3: _disasm_vop3, VOP3_SDST: _disasm_vop3, VOP3SD: _disasm_vop3sd, VOPD: _disasm_vopd, VOP3P: _disasm_vop3p,
|
||||
VINTERP: _disasm_vinterp, SOPP: _disasm_sopp, SMEM: _disasm_smem, DS: _disasm_ds, FLAT: _disasm_flat, GLOBAL: _disasm_flat, SCRATCH: _disasm_flat,
|
||||
SOP1: _disasm_sop1, SOP2: _disasm_sop2, SOPC: _disasm_sopc, SOPK: _disasm_sopk,
|
||||
# RDNA4
|
||||
R4_VOP1: _disasm_vop1, R4_VOP1_SDST: _disasm_vop1, R4_VOP2: _disasm_vop2, R4_VOPC: _disasm_vopc, R4_VOP3: _disasm_vop3, R4_VOP3_SDST: _disasm_vop3, R4_VOP3SD: _disasm_vop3sd,
|
||||
R4_VOPD: _disasm_vopd, R4_VOP3P: _disasm_vop3p, R4_VINTERP: _disasm_vinterp, R4_SOPP: _disasm_sopp, R4_SMEM: _disasm_smem,
|
||||
R4_DS: _disasm_ds, R4_SOP1: _disasm_sop1, R4_SOP2: _disasm_sop2, R4_SOPC: _disasm_sopc, R4_SOPK: _disasm_sopk}
|
||||
|
||||
def disasm(inst: Inst) -> str: return DISASM_HANDLERS[type(inst)](inst)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# CDNA DISASSEMBLER SUPPORT
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
try:
|
||||
from extra.assembly.amd.autogen.cdna.ins import (VOP1 as CDNA_VOP1, VOP2 as CDNA_VOP2, VOPC as CDNA_VOPC, VOP3A, VOP3B, VOP3P as CDNA_VOP3P,
|
||||
SOP1 as CDNA_SOP1, SOP2 as CDNA_SOP2, SOPC as CDNA_SOPC, SOPK as CDNA_SOPK, SOPP as CDNA_SOPP, SMEM as CDNA_SMEM, DS as CDNA_DS,
|
||||
FLAT as CDNA_FLAT, VOP1Op as CDNA_VOP1Op, VOP2Op as CDNA_VOP2Op, VOPCOp as CDNA_VOPCOp)
|
||||
|
||||
def _cdna_src(inst, v, neg, abs_=0, n=1):
|
||||
s = _lit(inst, v) if v == 255 else _fmt_src(v, n, cdna=True)
|
||||
if abs_: s = f"|{s}|"
|
||||
return f"neg({s})" if neg and v == 255 else (f"-{s}" if neg else s)
|
||||
|
||||
_CDNA_VOP3_ALIASES = {'v_fmac_f64': 'v_mul_legacy_f32', 'v_dot2c_f32_bf16': 'v_mac_f32'}
|
||||
|
||||
def _disasm_vop3a(inst) -> str:
|
||||
op_val = inst.op.value if hasattr(inst.op, 'value') else inst.op
|
||||
name = inst.op_name.lower() or f'vop3a_op_{op_val}'
|
||||
n = inst.num_srcs() or _num_srcs(inst)
|
||||
cl, om = " clamp" if inst.clmp else "", _omod(inst.omod)
|
||||
orig_name = name
|
||||
name = _CDNA_VOP3_ALIASES.get(name, name)
|
||||
if name != orig_name:
|
||||
s0, s1 = _cdna_src(inst, inst.src0, inst.neg&1, inst.abs&1, 1), _cdna_src(inst, inst.src1, inst.neg&2, inst.abs&2, 1)
|
||||
s2 = ""
|
||||
dst = _vreg(inst.vdst)
|
||||
else:
|
||||
regs = inst.canonical_op_regs
|
||||
dregs, r0, r1, r2 = regs['d'], regs['s0'], regs['s1'], regs['s2']
|
||||
s0, s1, s2 = _cdna_src(inst, inst.src0, inst.neg&1, inst.abs&1, r0), _cdna_src(inst, inst.src1, inst.neg&2, inst.abs&2, r1), _cdna_src(inst, inst.src2, inst.neg&4, inst.abs&4, r2)
|
||||
dst = _vreg(inst.vdst, dregs) if dregs > 1 else _vreg(inst.vdst)
|
||||
if op_val >= 512:
|
||||
return f"{name} {dst}, {s0}, {s1}, {s2}{cl}{om}" if n == 3 else f"{name} {dst}, {s0}, {s1}{cl}{om}"
|
||||
if op_val < 256:
|
||||
sdst = _fmt_sdst(inst.vdst, 2, cdna=True)
|
||||
return f"{name}_e64 {sdst}, {s0}, {s1}{cl}"
|
||||
if 320 <= op_val < 512:
|
||||
if name in ('v_nop', 'v_clrexcp'): return f"{name}_e64"
|
||||
return f"{name}_e64 {dst}, {s0}{cl}{om}"
|
||||
if name == 'v_cndmask_b32':
|
||||
s2 = _fmt_src(inst.src2, 2, cdna=True)
|
||||
return f"{name}_e64 {dst}, {s0}, {s1}, {s2}{cl}{om}"
|
||||
if name in ('v_mul_legacy_f32', 'v_mac_f32'):
|
||||
return f"{name}_e64 {dst}, {s0}, {s1}{cl}{om}"
|
||||
suf = "_e64" if op_val < 512 else ""
|
||||
return f"{name}{suf} {dst}, {s0}, {s1}, {s2}{cl}{om}" if n == 3 else f"{name}{suf} {dst}, {s0}, {s1}{cl}{om}"
|
||||
|
||||
def _disasm_vop3b(inst) -> str:
|
||||
op_val = inst.op.value if hasattr(inst.op, 'value') else inst.op
|
||||
name = inst.op_name.lower() or f'vop3b_op_{op_val}'
|
||||
n = inst.num_srcs() or _num_srcs(inst)
|
||||
regs = inst.canonical_op_regs
|
||||
dregs, r0, r1, r2 = regs['d'], regs['s0'], regs['s1'], regs['s2']
|
||||
s0, s1, s2 = _cdna_src(inst, inst.src0, inst.neg&1, n=r0), _cdna_src(inst, inst.src1, inst.neg&2, n=r1), _cdna_src(inst, inst.src2, inst.neg&4, n=r2)
|
||||
dst = _vreg(inst.vdst, dregs) if dregs > 1 else _vreg(inst.vdst)
|
||||
sdst = _fmt_sdst(inst.sdst, 2, cdna=True)
|
||||
cl, om = " clamp" if inst.clmp else "", _omod(inst.omod)
|
||||
if name in ('v_addc_co_u32', 'v_subb_co_u32', 'v_subbrev_co_u32'):
|
||||
s2 = _fmt_src(inst.src2, 2, cdna=True)
|
||||
return f"{name}_e64 {dst}, {sdst}, {s0}, {s1}, {s2}{cl}{om}"
|
||||
suf = "_e64" if 'co_' in name else ""
|
||||
return f"{name}{suf} {dst}, {sdst}, {s0}, {s1}, {s2}{cl}{om}" if n == 3 else f"{name}{suf} {dst}, {sdst}, {s0}, {s1}{cl}{om}"
|
||||
|
||||
def _disasm_cdna_vop3p(inst) -> str:
|
||||
name, n = inst.op_name.lower(), inst.num_srcs() or 2
|
||||
is_mfma = 'mfma' in name or 'smfmac' in name
|
||||
is_accvgpr = 'accvgpr' in name
|
||||
get_src = lambda v, sc: _lit(inst, v) if v == 255 else _fmt_src(v, sc, cdna=True)
|
||||
|
||||
# Handle accvgpr read/write (accumulator register operations)
|
||||
if is_accvgpr:
|
||||
src0_off = _unwrap(inst.src0)
|
||||
vdst_off = _vi(inst.vdst)
|
||||
if 'read' in name:
|
||||
# v_accvgpr_read_b32 vN, aM - reads from accumulator to VGPR
|
||||
return f"{name}_b32 v{vdst_off}, a{src0_off - 256 if src0_off >= 256 else src0_off}"
|
||||
if 'write' in name:
|
||||
# v_accvgpr_write_b32 aM, src - writes to accumulator from source
|
||||
src = _lit(inst, inst.src0) if src0_off == 255 else (f"v{src0_off - 256}" if src0_off >= 256 else decode_src(src0_off, cdna=True))
|
||||
return f"{name}_b32 a{vdst_off}, {src}"
|
||||
|
||||
# Handle MFMA instructions with accumulator destinations
|
||||
if is_mfma:
|
||||
sc = 2 if 'iu4' in name else 4 if 'iu8' in name or 'i4' in name else 8 if 'f16' in name or 'bf16' in name else 4
|
||||
src0, src1, src2 = get_src(inst.src0, sc), get_src(inst.src1, sc), get_src(inst.src2, 16)
|
||||
dst = _areg(inst.vdst, 16) # MFMA uses accumulator registers
|
||||
opsel_hi = inst.opsel_hi
|
||||
mods = ([_fmt_bits("op_sel", inst.opsel, n)] if inst.opsel else []) + ([_fmt_bits("op_sel_hi", opsel_hi, n)] if opsel_hi != 3 else []) + \
|
||||
([_fmt_bits("neg_lo", inst.neg, n)] if inst.neg else []) + ([_fmt_bits("neg_hi", inst.neg_hi, n)] if inst.neg_hi else []) + (["clamp"] if inst.clmp else [])
|
||||
return f"{name} {dst}, {src0}, {src1}, {src2}{' ' + ' '.join(mods) if mods else ''}"
|
||||
|
||||
# Standard VOP3P instructions
|
||||
src0, src1, src2, dst = get_src(inst.src0, 1), get_src(inst.src1, 1), get_src(inst.src2, 1), _vreg(inst.vdst)
|
||||
opsel_hi = inst.opsel_hi # CDNA VOP3P only has 2 bits for opsel_hi (no opsel_hi2)
|
||||
opsel_hi_default = 3 # CDNA default is 0b11 (2 bits), not 0b111 like RDNA
|
||||
mods = ([_fmt_bits("op_sel", inst.opsel, n)] if inst.opsel else []) + ([_fmt_bits("op_sel_hi", opsel_hi, n)] if opsel_hi != opsel_hi_default else []) + \
|
||||
([_fmt_bits("neg_lo", inst.neg, n)] if inst.neg else []) + ([_fmt_bits("neg_hi", inst.neg_hi, n)] if inst.neg_hi else []) + (["clamp"] if inst.clmp else [])
|
||||
return f"{name} {dst}, {src0}, {src1}, {src2}{' ' + ' '.join(mods) if mods else ''}" if n == 3 else f"{name} {dst}, {src0}, {src1}{' ' + ' '.join(mods) if mods else ''}"
|
||||
|
||||
DISASM_HANDLERS.update({CDNA_VOP1: _disasm_vop1, CDNA_VOP2: _disasm_vop2, CDNA_VOPC: _disasm_vopc,
|
||||
CDNA_SOP1: _disasm_sop1, CDNA_SOP2: _disasm_sop2, CDNA_SOPC: _disasm_sopc, CDNA_SOPK: _disasm_sopk, CDNA_SOPP: _disasm_sopp,
|
||||
CDNA_SMEM: _disasm_smem, CDNA_DS: _disasm_ds, CDNA_FLAT: _disasm_flat,
|
||||
VOP3A: _disasm_vop3a, VOP3B: _disasm_vop3b, CDNA_VOP3P: _disasm_cdna_vop3p})
|
||||
except ImportError:
|
||||
pass
|
||||
+603
-335
@@ -1,373 +1,641 @@
|
||||
# dsl.py - clean DSL for AMD assembly
|
||||
# library for RDNA3 assembly DSL
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
import struct, math, re
|
||||
from enum import IntEnum
|
||||
from functools import cache
|
||||
from typing import overload, Annotated, TypeVar, Generic
|
||||
from extra.assembly.amd.autogen.rdna3.enum import (VOP1Op, VOP2Op, VOP3Op, VOP3SDOp, VOP3POp, VOPCOp, VOPDOp, SOP1Op, SOP2Op,
|
||||
SOPCOp, SOPKOp, SOPPOp, SMEMOp, DSOp, FLATOp, MUBUFOp, MTBUFOp, MIMGOp, VINTERPOp)
|
||||
from extra.assembly.amd.autogen.cdna.enum import VOP1Op as CDNA_VOP1Op, VOP2Op as CDNA_VOP2Op
|
||||
from extra.assembly.amd.autogen.rdna4.enum import (VOP1Op as RDNA4_VOP1Op, VOP2Op as RDNA4_VOP2Op, VOP3Op as RDNA4_VOP3Op,
|
||||
VOP3SDOp as RDNA4_VOP3SDOp, VOP3POp as RDNA4_VOP3POp, VOPCOp as RDNA4_VOPCOp, VOPDOp as RDNA4_VOPDOp,
|
||||
SOP1Op as RDNA4_SOP1Op, SOP2Op as RDNA4_SOP2Op, SOPCOp as RDNA4_SOPCOp, SOPKOp as RDNA4_SOPKOp, SOPPOp as RDNA4_SOPPOp,
|
||||
SMEMOp as RDNA4_SMEMOp, DSOp as RDNA4_DSOp, VBUFFEROp as RDNA4_VBUFFEROp, VINTERPOp as RDNA4_VINTERPOp)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Registers - unified src encoding space (0-511)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Source operand encoding - constant across all AMD ISAs
|
||||
class SrcEnum(IntEnum):
|
||||
VCC_LO=106; VCC_HI=107; NULL=124; M0=125; EXEC_LO=126; EXEC_HI=127; ZERO=128
|
||||
DPP8=233; DPP8FI=234; SHARED_BASE=235; SHARED_LIMIT=236; PRIVATE_BASE=237; PRIVATE_LIMIT=238
|
||||
POS_HALF=240; NEG_HALF=241; POS_ONE=242; NEG_ONE=243; POS_TWO=244; NEG_TWO=245
|
||||
POS_FOUR=246; NEG_FOUR=247; INV_2PI=248; DPP16=250; VCCZ=251; EXECZ=252; SCC=253; LDS_DIRECT=254
|
||||
VCC_LO, VCC_HI, NULL, M0, EXEC_LO, EXEC_HI, ZERO = SrcEnum.VCC_LO, SrcEnum.VCC_HI, SrcEnum.NULL, SrcEnum.M0, SrcEnum.EXEC_LO, SrcEnum.EXEC_HI, SrcEnum.ZERO
|
||||
DPP8FI, SHARED_BASE, SHARED_LIMIT, PRIVATE_BASE, PRIVATE_LIMIT = SrcEnum.DPP8FI, SrcEnum.SHARED_BASE, SrcEnum.SHARED_LIMIT, SrcEnum.PRIVATE_BASE, SrcEnum.PRIVATE_LIMIT
|
||||
POS_HALF, NEG_HALF, POS_ONE, NEG_ONE, POS_TWO, NEG_TWO = SrcEnum.POS_HALF, SrcEnum.NEG_HALF, SrcEnum.POS_ONE, SrcEnum.NEG_ONE, SrcEnum.POS_TWO, SrcEnum.NEG_TWO
|
||||
POS_FOUR, NEG_FOUR, INV_2PI, VCCZ, EXECZ, SCC, LDS_DIRECT = SrcEnum.POS_FOUR, SrcEnum.NEG_FOUR, SrcEnum.INV_2PI, SrcEnum.VCCZ, SrcEnum.EXECZ, SrcEnum.SCC, SrcEnum.LDS_DIRECT
|
||||
OFF = NULL
|
||||
|
||||
def _reg_size(t: str | None) -> int: return {'b64': 2, 'f64': 2, 'u64': 2, 'i64': 2, 'b128': 4}.get(t, 1)
|
||||
# Common masks and bit conversion functions
|
||||
MASK32, MASK64, MASK128 = 0xffffffff, 0xffffffffffffffff, (1 << 128) - 1
|
||||
_struct_f, _struct_I = struct.Struct("<f"), struct.Struct("<I")
|
||||
_struct_e, _struct_H = struct.Struct("<e"), struct.Struct("<H")
|
||||
_struct_d, _struct_Q = struct.Struct("<d"), struct.Struct("<Q")
|
||||
def _f32(i):
|
||||
i = i & MASK32
|
||||
# RDNA3 default mode: flush f32 denormals to zero (FTZ)
|
||||
# Denormal: exponent=0 (bits 23-30) and mantissa!=0 (bits 0-22)
|
||||
if (i & 0x7f800000) == 0 and (i & 0x007fffff) != 0: return 0.0
|
||||
return _struct_f.unpack(_struct_I.pack(i))[0]
|
||||
def _i32(f):
|
||||
if isinstance(f, int): f = float(f)
|
||||
if math.isnan(f): return 0xffc00000 if math.copysign(1.0, f) < 0 else 0x7fc00000
|
||||
if math.isinf(f): return 0x7f800000 if f > 0 else 0xff800000
|
||||
try:
|
||||
bits = _struct_I.unpack(_struct_f.pack(f))[0]
|
||||
# RDNA3 default mode: flush f32 denormals to zero (FTZ)
|
||||
if (bits & 0x7f800000) == 0 and (bits & 0x007fffff) != 0: return 0x80000000 if bits & 0x80000000 else 0
|
||||
return bits
|
||||
except (OverflowError, struct.error): return 0x7f800000 if f > 0 else 0xff800000
|
||||
def _sext(v, b): return v - (1 << b) if v & (1 << (b - 1)) else v
|
||||
def _f16(i): return _struct_e.unpack(_struct_H.pack(i & 0xffff))[0]
|
||||
def _i16(f):
|
||||
if math.isnan(f): return 0x7e00
|
||||
if math.isinf(f): return 0x7c00 if f > 0 else 0xfc00
|
||||
try: return _struct_H.unpack(_struct_e.pack(f))[0]
|
||||
except (OverflowError, struct.error): return 0x7c00 if f > 0 else 0xfc00
|
||||
def _f64(i): return _struct_d.unpack(_struct_Q.pack(i & MASK64))[0]
|
||||
def _i64(f):
|
||||
if math.isnan(f): return 0x7ff8000000000000
|
||||
if math.isinf(f): return 0x7ff0000000000000 if f > 0 else 0xfff0000000000000
|
||||
try: return _struct_Q.unpack(_struct_d.pack(f))[0]
|
||||
except (OverflowError, struct.error): return 0x7ff0000000000000 if f > 0 else 0xfff0000000000000
|
||||
|
||||
class Reg:
|
||||
_NAMES = {106: "VCC_LO", 107: "VCC_HI", 124: "NULL", 125: "M0", 126: "EXEC_LO", 127: "EXEC_HI",
|
||||
240: "0.5", 241: "-0.5", 242: "1.0", 243: "-1.0", 244: "2.0", 245: "-2.0", 246: "4.0", 247: "-4.0",
|
||||
248: "INV_2PI", 250: "DPP16", 253: "SCC", 255: "LIT"}
|
||||
_PAIRS = {106: "VCC", 126: "EXEC"}
|
||||
# Instruction spec - register counts and dtypes derived from instruction names
|
||||
_REGS = {'B32': 1, 'B64': 2, 'B96': 3, 'B128': 4, 'B256': 8, 'B512': 16,
|
||||
'F32': 1, 'I32': 1, 'U32': 1, 'F64': 2, 'I64': 2, 'U64': 2,
|
||||
'F16': 1, 'I16': 1, 'U16': 1, 'B16': 1, 'I8': 1, 'U8': 1, 'B8': 1,
|
||||
'DWORD': 1, 'DWORDX2': 2, 'DWORDX3': 3, 'DWORDX4': 4, 'DWORDX8': 8, 'DWORDX16': 16,
|
||||
'BYTE': 1, 'SHORT': 1, 'UBYTE': 1, 'SBYTE': 1, 'USHORT': 1, 'SSHORT': 1}
|
||||
_CVT_RE = re.compile(r'CVT_([FIUB]\d+)_([FIUB]\d+)$')
|
||||
_MAD_MUL_RE = re.compile(r'(?:MAD|MUL)_([IU]\d+)_([IU]\d+)$')
|
||||
_PACK_RE = re.compile(r'PACK_([FIUB]\d+)_([FIUB]\d+)$')
|
||||
_DST_SRC_RE = re.compile(r'_([FIUB]\d+)_([FIUB]\d+)$')
|
||||
_SINGLE_RE = re.compile(r'_([FIUB](?:32|64|16|8|96|128|256|512)|DWORD(?:X(?:2|3|4|8|16))?|[US]?BYTE|[US]?SHORT)$')
|
||||
@cache
|
||||
def _suffix(name: str) -> tuple[str | None, str | None]:
|
||||
name = name.upper()
|
||||
if m := _CVT_RE.search(name): return m.group(1), m.group(2)
|
||||
if m := _MAD_MUL_RE.search(name): return m.group(1), m.group(2)
|
||||
if m := _PACK_RE.search(name): return m.group(1), m.group(2)
|
||||
if m := _DST_SRC_RE.search(name): return m.group(1), m.group(2)
|
||||
if m := _SINGLE_RE.search(name): return m.group(1), m.group(1)
|
||||
return None, None
|
||||
_SPECIAL_REGS = {
|
||||
'V_LSHLREV_B64': (2, 1, 2, 1), 'V_LSHRREV_B64': (2, 1, 2, 1), 'V_ASHRREV_I64': (2, 1, 2, 1),
|
||||
'S_LSHL_B64': (2, 2, 1, 1), 'S_LSHR_B64': (2, 2, 1, 1), 'S_ASHR_I64': (2, 2, 1, 1),
|
||||
'S_BFE_U64': (2, 2, 1, 1), 'S_BFE_I64': (2, 2, 1, 1), 'S_BFM_B64': (2, 1, 1, 1),
|
||||
'S_BITSET0_B64': (2, 1, 1, 1), 'S_BITSET1_B64': (2, 1, 1, 1),
|
||||
'S_BITCMP0_B64': (1, 2, 1, 1), 'S_BITCMP1_B64': (1, 2, 1, 1),
|
||||
'V_LDEXP_F64': (2, 2, 1, 1), 'V_TRIG_PREOP_F64': (2, 2, 1, 1),
|
||||
'V_CMP_CLASS_F64': (1, 2, 1, 1), 'V_CMPX_CLASS_F64': (1, 2, 1, 1),
|
||||
'V_CMP_CLASS_F32': (1, 1, 1, 1), 'V_CMPX_CLASS_F32': (1, 1, 1, 1),
|
||||
'V_CMP_CLASS_F16': (1, 1, 1, 1), 'V_CMPX_CLASS_F16': (1, 1, 1, 1),
|
||||
'V_MAD_U64_U32': (2, 1, 1, 2), 'V_MAD_I64_I32': (2, 1, 1, 2),
|
||||
'V_QSAD_PK_U16_U8': (2, 2, 1, 2), 'V_MQSAD_PK_U16_U8': (2, 2, 1, 2), 'V_MQSAD_U32_U8': (4, 2, 1, 4),
|
||||
'V_CVT_PK_F32_BF8': (2, 1, 1, 1), 'V_CVT_PK_F32_FP8': (2, 1, 1, 1),
|
||||
}
|
||||
_SPECIAL_DTYPE = {
|
||||
'V_LSHLREV_B64': ('B64', 'U32', 'B64', None), 'V_LSHRREV_B64': ('B64', 'U32', 'B64', None), 'V_ASHRREV_I64': ('I64', 'U32', 'I64', None),
|
||||
'S_LSHL_B64': ('B64', 'B64', 'U32', None), 'S_LSHR_B64': ('B64', 'B64', 'U32', None), 'S_ASHR_I64': ('I64', 'I64', 'U32', None),
|
||||
'S_BFE_U64': ('U64', 'U64', 'U32', None), 'S_BFE_I64': ('I64', 'I64', 'U32', None),
|
||||
'S_BFM_B64': ('B64', 'U32', 'U32', None), 'S_BITSET0_B64': ('B64', 'U32', None, None), 'S_BITSET1_B64': ('B64', 'U32', None, None),
|
||||
'S_BITCMP0_B64': ('SCC', 'B64', 'U32', None), 'S_BITCMP1_B64': ('SCC', 'B64', 'U32', None),
|
||||
'V_LDEXP_F64': ('F64', 'F64', 'I32', None), 'V_TRIG_PREOP_F64': ('F64', 'F64', 'U32', None),
|
||||
'V_CMP_CLASS_F64': ('VCC', 'F64', 'U32', None), 'V_CMPX_CLASS_F64': ('EXEC', 'F64', 'U32', None),
|
||||
'V_CMP_CLASS_F32': ('VCC', 'F32', 'U32', None), 'V_CMPX_CLASS_F32': ('EXEC', 'F32', 'U32', None),
|
||||
'V_CMP_CLASS_F16': ('VCC', 'F16', 'U32', None), 'V_CMPX_CLASS_F16': ('EXEC', 'F16', 'U32', None),
|
||||
'V_MAD_U64_U32': ('U64', 'U32', 'U32', 'U64'), 'V_MAD_I64_I32': ('I64', 'I32', 'I32', 'I64'),
|
||||
'V_QSAD_PK_U16_U8': ('B64', 'B64', 'B64', 'B64'), 'V_MQSAD_PK_U16_U8': ('B64', 'B64', 'B64', 'B64'),
|
||||
'V_MQSAD_U32_U8': ('B128', 'B64', 'B64', 'B128'),
|
||||
}
|
||||
@cache
|
||||
def spec_regs(name: str) -> tuple[int, int, int, int]:
|
||||
uname = name.upper()
|
||||
if uname in _SPECIAL_REGS: return _SPECIAL_REGS[uname]
|
||||
if 'SAD' in uname and 'U8' in uname and 'QSAD' not in uname and 'MQSAD' not in uname: return 1, 1, 1, 1
|
||||
dst_suf, src_suf = _suffix(name)
|
||||
return _REGS.get(dst_suf, 1), _REGS.get(src_suf, 1), _REGS.get(src_suf, 1), _REGS.get(src_suf, 1)
|
||||
@cache
|
||||
def spec_dtype(name: str) -> tuple[str | None, str | None, str | None, str | None]:
|
||||
uname = name.upper()
|
||||
if uname in _SPECIAL_DTYPE: return _SPECIAL_DTYPE[uname]
|
||||
if 'SAD' in uname and ('U8' in uname or 'U16' in uname) and 'QSAD' not in uname and 'MQSAD' not in uname: return 'U32', 'U32', 'U32', 'U32'
|
||||
if '_CMP_' in uname or '_CMPX_' in uname:
|
||||
dst_suf, src_suf = _suffix(name)
|
||||
return 'EXEC' if '_CMPX_' in uname else 'VCC', src_suf, src_suf, None
|
||||
dst_suf, src_suf = _suffix(name)
|
||||
return dst_suf, src_suf, src_suf, src_suf
|
||||
_F16_RE = re.compile(r'_[FIUB]16(?:_|$)')
|
||||
_F64_RE = re.compile(r'_[FIUB]64(?:_|$)')
|
||||
@cache
|
||||
def spec_is_16bit(name: str) -> bool:
|
||||
uname = name.upper()
|
||||
if 'SAD' in uname or 'PACK' in uname or '_PK_' in uname or 'SAT_PK' in uname or 'DOT2' in uname: return False
|
||||
if '_F32' in uname or '_I32' in uname or '_U32' in uname or '_B32' in uname: return False
|
||||
return bool(_F16_RE.search(uname))
|
||||
@cache
|
||||
def spec_is_64bit(name: str) -> bool: return bool(_F64_RE.search(name.upper()))
|
||||
_3SRC = {'FMA', 'MAD', 'MIN3', 'MAX3', 'MED3', 'DIV_FIX', 'DIV_FMAS', 'DIV_SCALE', 'SAD', 'LERP', 'ALIGN', 'CUBE', 'BFE', 'BFI',
|
||||
'PERM_B32', 'PERMLANE', 'CNDMASK', 'XOR3', 'OR3', 'ADD3', 'LSHL_OR', 'AND_OR', 'LSHL_ADD', 'ADD_LSHL', 'XAD', 'MAXMIN',
|
||||
'MINMAX', 'MINIMUMMAXIMUM', 'MAXIMUMMINIMUM', 'MINIMUM3', 'MAXIMUM3', 'DOT2', 'DOT4', 'DOT8', 'WMMA', 'CVT_PK_U8', 'MULLIT', 'CO_CI'}
|
||||
_2SRC = {'FMAC', 'PERMLANE16_VAR', 'PERMLANEX16_VAR'} # FMAC uses dst as implicit accumulator, _VAR permlane only 2 sources
|
||||
def spec_num_srcs(name: str) -> int:
|
||||
name = name.upper()
|
||||
if any(k in name for k in _2SRC): return 2
|
||||
return 3 if any(k in name for k in _3SRC) else 2
|
||||
def is_dtype_16(dt: str | None) -> bool: return dt is not None and '16' in dt
|
||||
def is_dtype_64(dt: str | None) -> bool: return dt is not None and '64' in dt
|
||||
|
||||
def __init__(self, offset: int = 0, sz: int = 512, *, neg: bool = False, abs_: bool = False, hi: bool = False):
|
||||
self.offset, self.sz = offset, sz
|
||||
self.neg, self.abs_, self.hi = neg, abs_, hi
|
||||
|
||||
# TODO: remove these legacy aliases
|
||||
# Bit field DSL
|
||||
class BitField:
|
||||
def __init__(self, hi: int, lo: int, name: str | None = None): self.hi, self.lo, self.name, self._marker = hi, lo, name, None
|
||||
def __set_name__(self, owner, name):
|
||||
import typing
|
||||
self.name, self._owner = name, owner
|
||||
# Cache marker at class definition time
|
||||
hints = typing.get_type_hints(owner, include_extras=True)
|
||||
if name in hints:
|
||||
hint = hints[name]
|
||||
if typing.get_origin(hint) is Annotated:
|
||||
args = typing.get_args(hint)
|
||||
self._marker = args[1] if len(args) > 1 else None
|
||||
def __eq__(self, val: int) -> tuple[BitField, int]: return (self, val) # type: ignore
|
||||
def mask(self) -> int: return (1 << (self.hi - self.lo + 1)) - 1
|
||||
@property
|
||||
def count(self): return self.sz
|
||||
@property
|
||||
def idx(self): return self.offset
|
||||
|
||||
def __hash__(self): return hash((self.offset, self.sz, self.neg, self.abs_, self.hi))
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, slice):
|
||||
start, stop = key.start or 0, key.stop or (self.sz - 1)
|
||||
if start < 0 or stop >= self.sz: raise RuntimeError(f"slice [{start}:{stop}] out of bounds for size {self.sz}")
|
||||
return Reg(self.offset + start, stop - start + 1)
|
||||
if key < 0 or key >= self.sz: raise RuntimeError(f"index {key} out of bounds for size {self.sz}")
|
||||
return Reg(self.offset + key, 1)
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Reg):
|
||||
return (self.offset == other.offset and self.sz == other.sz and
|
||||
self.neg == other.neg and self.abs_ == other.abs_ and self.hi == other.hi)
|
||||
return NotImplemented
|
||||
def __add__(self, other):
|
||||
if isinstance(other, int): return Reg(self.offset + other, self.sz)
|
||||
return NotImplemented
|
||||
def __neg__(self) -> 'Reg': return Reg(self.offset, self.sz, neg=not self.neg, abs_=self.abs_, hi=self.hi)
|
||||
def __abs__(self) -> 'Reg': return Reg(self.offset, self.sz, neg=self.neg, abs_=True, hi=self.hi)
|
||||
@property
|
||||
def h(self) -> 'Reg': return Reg(self.offset, self.sz, neg=self.neg, abs_=self.abs_, hi=True)
|
||||
@property
|
||||
def l(self) -> 'Reg': return Reg(self.offset, self.sz, neg=self.neg, abs_=self.abs_, hi=False)
|
||||
def fmt(self, sz=None, parens=False, upper=False) -> str:
|
||||
o, sz = self.offset, sz or self.sz
|
||||
l, r = ("[", "]") if parens or sz > 1 else ("", "") # brackets for multi-reg or when parens=True
|
||||
if 256 <= o < 512: idx = o - 256; base = f"v{l}{idx}{r}" if sz == 1 else f"v[{idx}:{idx + sz - 1}]"
|
||||
elif o < 106: base = f"s{l}{o}{r}" if sz == 1 else f"s[{o}:{o + sz - 1}]"
|
||||
elif sz == 2 and o in self._PAIRS: base = self._PAIRS[o] if upper else self._PAIRS[o].lower()
|
||||
elif o in self._NAMES: base = self._NAMES[o] if upper else self._NAMES[o].lower() # special regs (any sz)
|
||||
elif 108 <= o < 124: idx = o - 108; base = f"ttmp{l}{idx}{r}" if sz == 1 else f"ttmp[{idx}:{idx + sz - 1}]"
|
||||
elif 128 <= o <= 192: base = str(o - 128) # inline int constants (0-64)
|
||||
elif 193 <= o <= 208: base = str(-(o - 192)) # inline negative int constants (-1 to -16)
|
||||
else: raise RuntimeError(f"unknown register: offset={o}, sz={sz}")
|
||||
if self.hi: base += ".h"
|
||||
if self.abs_: base = f"abs({base})" if upper else f"|{base}|"
|
||||
if self.neg: base = f"-{base}"
|
||||
return base
|
||||
def __repr__(self): return self.fmt(parens=True, upper=True)
|
||||
|
||||
# Full src encoding space
|
||||
src = Reg(0, 512)
|
||||
|
||||
# Slices for each region (inclusive end)
|
||||
s = src[0:105] # SGPR0-105
|
||||
VCC_LO = src[106]
|
||||
VCC_HI = src[107]
|
||||
VCC = src[106:107]
|
||||
ttmp = src[108:123] # TTMP0-15
|
||||
NULL = OFF = src[124]
|
||||
M0 = src[125]
|
||||
EXEC_LO = src[126]
|
||||
EXEC_HI = src[127]
|
||||
EXEC = src[126:127]
|
||||
# 128: 0, 129-192: integers 1-64, 193-208: integers -1 to -16
|
||||
# 240-248: float constants (0.5, -0.5, 1.0, -1.0, 2.0, -2.0, 4.0, -4.0, 1/(2*PI))
|
||||
INV_2PI = src[248]
|
||||
DPP16 = src[250]
|
||||
SCC = src[253]
|
||||
# 255: literal constant
|
||||
v = src[256:511] # VGPR0-255
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# BitField
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
def marker(self) -> type | None: return self._marker
|
||||
@overload
|
||||
def __get__(self, obj: None, objtype: type) -> BitField: ...
|
||||
@overload
|
||||
def __get__(self, obj: object, objtype: type | None = None) -> int: ...
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None: return self
|
||||
val = unwrap(obj._values.get(self.name, 0))
|
||||
# Convert to IntEnum if marker is an IntEnum subclass
|
||||
if self.marker and isinstance(self.marker, type) and issubclass(self.marker, IntEnum):
|
||||
# VOP3 with VOPC opcodes (0-255) -> VOPCOp, VOP3SD opcodes -> VOP3SDOp
|
||||
if self.marker is VOP3Op:
|
||||
if val < 256: return VOPCOp(val)
|
||||
if val in Inst._VOP3SD_OPS: return VOP3SDOp(val)
|
||||
try: return self.marker(val)
|
||||
except ValueError: pass
|
||||
return val
|
||||
|
||||
class _Bits:
|
||||
"""Helper for defining bit fields with slice syntax: bits[hi:lo] or bits[n]."""
|
||||
def __getitem__(self, key) -> 'BitField': return BitField(key.start, key.stop) if isinstance(key, slice) else BitField(key, key)
|
||||
def __getitem__(self, key) -> BitField: return BitField(key.start, key.stop) if isinstance(key, slice) else BitField(key, key)
|
||||
bits = _Bits()
|
||||
|
||||
class BitField:
|
||||
def __init__(self, hi: int, lo: int, default: int = 0):
|
||||
self.hi, self.lo, self.default, self.name, self.mask = hi, lo, default, None, (1 << (hi - lo + 1)) - 1
|
||||
def __set_name__(self, owner, name): self.name = name
|
||||
def __eq__(self, other) -> 'FixedBitField':
|
||||
if isinstance(other, int): return FixedBitField(self.hi, self.lo, other)
|
||||
return NotImplemented
|
||||
def enum(self, enum_cls) -> 'EnumBitField': return EnumBitField(self.hi, self.lo, enum_cls)
|
||||
def encode(self, val) -> int:
|
||||
assert isinstance(val, int), f"BitField.encode expects int, got {type(val).__name__}"
|
||||
return val
|
||||
def decode(self, val): return val
|
||||
def set(self, raw: int, val) -> int:
|
||||
if val is None: val = self.default
|
||||
encoded = self.encode(val)
|
||||
if encoded < 0 or encoded > self.mask: raise RuntimeError(f"field '{self.name}': value {encoded} doesn't fit in {self.hi - self.lo + 1} bits")
|
||||
return (raw & ~(self.mask << self.lo)) | (encoded << self.lo)
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None: return self
|
||||
return self.decode((obj._raw >> self.lo) & self.mask)
|
||||
# Source operand with modifiers - base class for anything that can be a src with neg/abs
|
||||
class SrcMod:
|
||||
__slots__ = ('val', 'neg', 'abs_')
|
||||
def __init__(self, val: int, neg: bool = False, abs_: bool = False): self.val, self.neg, self.abs_ = val, neg, abs_
|
||||
def __repr__(self): return f"{'-' if self.neg else ''}{'|' if self.abs_ else ''}{self.val}{'|' if self.abs_ else ''}"
|
||||
def __neg__(self): return SrcMod(self.val, not self.neg, self.abs_)
|
||||
def __abs__(self): return SrcMod(self.val, self.neg, True)
|
||||
|
||||
class FixedBitField(BitField):
|
||||
def set(self, raw: int, val=None) -> int:
|
||||
assert val is None, f"FixedBitField does not accept values, got {val}"
|
||||
return super().set(raw, self.default)
|
||||
# Register types
|
||||
class Reg(SrcMod):
|
||||
__slots__ = ('idx', 'count', 'hi')
|
||||
def __init__(self, idx: int, count: int = 1, hi: bool = False, neg: bool = False, abs_: bool = False):
|
||||
self.idx, self.count, self.hi = idx, count, hi
|
||||
super().__init__(idx, neg, abs_)
|
||||
def __repr__(self): return f"{self.__class__.__name__.lower()[0]}[{self.idx}]" if self.count == 1 else f"{self.__class__.__name__.lower()[0]}[{self.idx}:{self.idx + self.count}]"
|
||||
def __neg__(self): return self.__class__(self.idx, self.count, self.hi, not self.neg, self.abs_)
|
||||
def __abs__(self): return self.__class__(self.idx, self.count, self.hi, self.neg, True)
|
||||
@property
|
||||
def l(self): return self.__class__(self.idx, self.count, False, self.neg, self.abs_)
|
||||
@property
|
||||
def h(self): return self.__class__(self.idx, self.count, True, self.neg, self.abs_)
|
||||
|
||||
class EnumBitField(BitField):
|
||||
def __init__(self, hi: int, lo: int, enum_cls):
|
||||
super().__init__(hi, lo)
|
||||
self._enum = enum_cls
|
||||
def encode(self, val) -> int:
|
||||
if not isinstance(val, self._enum): raise RuntimeError(f"expected {self._enum.__name__}, got {type(val).__name__}")
|
||||
return val.value
|
||||
def decode(self, raw): return self._enum(raw)
|
||||
T = TypeVar('T', bound=Reg)
|
||||
class _RegFactory(Generic[T]):
|
||||
def __init__(self, cls: type[T], name: str): self._cls, self._name = cls, name
|
||||
@overload
|
||||
def __getitem__(self, key: int) -> Reg: ...
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> Reg: ...
|
||||
def __getitem__(self, key: int | slice) -> Reg:
|
||||
return self._cls(key.start, key.stop - key.start + 1) if isinstance(key, slice) else self._cls(key)
|
||||
def __repr__(self): return f"<{self._name} factory>"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Typed fields
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
class SGPR(Reg): pass
|
||||
class VGPR(Reg): pass
|
||||
class TTMP(Reg): pass
|
||||
s: _RegFactory[SGPR] = _RegFactory(SGPR, "SGPR")
|
||||
v: _RegFactory[VGPR] = _RegFactory(VGPR, "VGPR")
|
||||
ttmp: _RegFactory[TTMP] = _RegFactory(TTMP, "TTMP")
|
||||
|
||||
import struct
|
||||
def _f32(f: float) -> int: return struct.unpack('I', struct.pack('f', f))[0]
|
||||
# Special registers as SrcMod objects (support -VCC_LO, abs(EXEC_LO), etc.)
|
||||
VCC_LO, VCC_HI, VCC = SrcMod(106), SrcMod(107), SrcMod(106)
|
||||
EXEC_LO, EXEC_HI, EXEC = SrcMod(126), SrcMod(127), SrcMod(126)
|
||||
SCC, M0, NULL, OFF = SrcMod(253), SrcMod(125), SrcMod(124), SrcMod(124)
|
||||
|
||||
class SrcField(BitField):
|
||||
_valid_range = (0, 511) # inclusive
|
||||
_FLOAT_ENC = {0.5: 240, -0.5: 241, 1.0: 242, -1.0: 243, 2.0: 244, -2.0: 245, 4.0: 246, -4.0: 247}
|
||||
# Field type markers (runtime classes for validation)
|
||||
class _SSrc: pass
|
||||
class _Src: pass
|
||||
class _Imm: pass
|
||||
class _SImm: pass
|
||||
class _VDSTYEnc: pass # VOPD vdsty: encoded = actual >> 1, actual = (encoded << 1) | ((vdstx & 1) ^ 1)
|
||||
class _SGPRField: pass
|
||||
class _VGPRField: pass
|
||||
|
||||
def __init__(self, hi: int, lo: int, default=s[0]):
|
||||
super().__init__(hi, lo, default)
|
||||
expected_size = self._valid_range[1] - self._valid_range[0] + 1
|
||||
actual_size = 1 << (hi - lo + 1)
|
||||
if actual_size != expected_size:
|
||||
raise RuntimeError(f"{self.__class__.__name__}: field size {hi - lo + 1} bits ({actual_size}) doesn't match range {self._valid_range} ({expected_size})")
|
||||
# Type aliases for annotations - tells mypy it's a BitField while preserving marker info
|
||||
SSrc = Annotated[BitField, _SSrc]
|
||||
Src = Annotated[BitField, _Src]
|
||||
Imm = Annotated[BitField, _Imm]
|
||||
SImm = Annotated[BitField, _SImm]
|
||||
VDSTYEnc = Annotated[BitField, _VDSTYEnc]
|
||||
SGPRField = Annotated[BitField, _SGPRField]
|
||||
VGPRField = Annotated[BitField, _VGPRField]
|
||||
class RawImm:
|
||||
def __init__(self, val: int): self.val = val
|
||||
def __repr__(self): return f"RawImm({self.val})"
|
||||
def __eq__(self, other): return isinstance(other, RawImm) and self.val == other.val
|
||||
|
||||
def encode(self, val) -> int:
|
||||
"""Encode value. Returns 255 (literal marker) for out-of-range values."""
|
||||
if isinstance(val, Reg): offset = val.offset
|
||||
elif isinstance(val, float): offset = self._FLOAT_ENC.get(val, 255)
|
||||
elif isinstance(val, int) and 0 <= val <= 64: offset = 128 + val
|
||||
elif isinstance(val, int) and -16 <= val < 0: offset = 192 - val
|
||||
elif isinstance(val, int): offset = 255 # literal
|
||||
else: raise TypeError(f"invalid src value {val}")
|
||||
if not (self._valid_range[0] <= offset <= self._valid_range[1]):
|
||||
raise TypeError(f"{self.__class__.__name__}: {val} (offset {offset}) out of range {self._valid_range}")
|
||||
return offset - self._valid_range[0]
|
||||
def unwrap(val) -> int:
|
||||
if isinstance(val, RawImm): return val.val
|
||||
if isinstance(val, SrcMod) and not isinstance(val, Reg): return val.val # Special registers like VCC_LO, NULL
|
||||
if hasattr(val, 'value'): return val.value # IntEnum
|
||||
if hasattr(val, 'idx'): return val.idx # Reg
|
||||
return val
|
||||
|
||||
def decode(self, raw): return src[raw + self._valid_range[0]]
|
||||
# Encoding/decoding constants
|
||||
FLOAT_ENC = {0.5: 240, -0.5: 241, 1.0: 242, -1.0: 243, 2.0: 244, -2.0: 245, 4.0: 246, -4.0: 247}
|
||||
FLOAT_DEC = {v: str(k) for k, v in FLOAT_ENC.items()}
|
||||
SPECIAL_GPRS = {106: "vcc_lo", 107: "vcc_hi", 124: "null", 125: "m0", 126: "exec_lo", 127: "exec_hi", 253: "scc"}
|
||||
SPECIAL_GPRS_CDNA = {102: "flat_scratch_lo", 103: "flat_scratch_hi", 104: "xnack_mask_lo", 105: "xnack_mask_hi",
|
||||
106: "vcc_lo", 107: "vcc_hi", 124: "m0", 126: "exec_lo", 127: "exec_hi",
|
||||
251: "src_vccz", 252: "src_execz", 253: "src_scc", 254: "src_lds_direct"}
|
||||
SPECIAL_PAIRS = {106: "vcc", 126: "exec"}
|
||||
SPECIAL_PAIRS_CDNA = {102: "flat_scratch", 104: "xnack_mask", 106: "vcc", 126: "exec"}
|
||||
SRC_FIELDS = {'src0', 'src1', 'src2', 'ssrc0', 'ssrc1', 'soffset', 'srcx0', 'srcy0'}
|
||||
RAW_FIELDS = {'vdata', 'vdst', 'vaddr', 'addr', 'data', 'data0', 'data1', 'sdst', 'sdata', 'vsrc1'}
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None: return self
|
||||
reg = self.decode((obj._raw >> self.lo) & self.mask)
|
||||
# Resize register based on operand info (skip non-resizable special registers)
|
||||
# VCC/EXEC pairs (106, 126), NULL (124), M0 (125), float constants (240-255)
|
||||
if reg.offset not in (124, 125) and not 240 <= reg.offset <= 255:
|
||||
if sz := obj.op_regs.get(self.name, 1): reg = Reg(reg.offset, sz, neg=reg.neg, abs_=reg.abs_, hi=reg.hi)
|
||||
return reg
|
||||
def _encode_reg(val: Reg) -> int: return (108 if isinstance(val, TTMP) else 0) + val.idx
|
||||
|
||||
class VGPRField(SrcField):
|
||||
_valid_range = (256, 511)
|
||||
def __init__(self, hi: int, lo: int, default=v[0]): super().__init__(hi, lo, default)
|
||||
def encode(self, val) -> int:
|
||||
if not isinstance(val, Reg): raise TypeError(f"VGPRField requires Reg, got {type(val).__name__}")
|
||||
# For 8-bit vdst fields in VOP1/VOP2 16-bit ops, bit 7 is opsel for dest half
|
||||
encoded = super().encode(val)
|
||||
if val.hi and (self.hi - self.lo + 1) == 8:
|
||||
if encoded >= 128:
|
||||
raise ValueError(f"VGPRField: v[{encoded}].h not encodable in 8-bit field (v[0:127] only for .h)")
|
||||
encoded |= 0x80
|
||||
return encoded
|
||||
class SGPRField(SrcField): _valid_range = (0, 127)
|
||||
class SSrcField(SrcField): _valid_range = (0, 255)
|
||||
def _is_encoded_src(v: int) -> bool: return 106 <= v <= 127 or 128 <= v <= 208 or 240 <= v <= 255 # Special regs (106-127) or inline const
|
||||
|
||||
class AlignedSGPRField(BitField):
|
||||
"""SGPR field with alignment requirement. Encoded as sgpr_index // alignment."""
|
||||
_align: int = 2
|
||||
def encode(self, val):
|
||||
if isinstance(val, int) and val == 0: return 0 # default: encode as s[0]
|
||||
if not isinstance(val, Reg): raise TypeError(f"{self.__class__.__name__} requires Reg, got {type(val).__name__}")
|
||||
if not (0 <= val.offset < 128): raise ValueError(f"{self.__class__.__name__} requires SGPR, got offset {val.offset}")
|
||||
if val.offset & (self._align - 1): raise ValueError(f"{self.__class__.__name__} requires {self._align}-aligned SGPR, got s[{val.offset}]")
|
||||
return val.offset >> (self._align.bit_length() - 1)
|
||||
def decode(self, raw): return src[raw << (self._align.bit_length() - 1)]
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None: return self
|
||||
reg = self.decode((obj._raw >> self.lo) & self.mask)
|
||||
if sz := obj.op_regs.get(self.name, 1): reg = Reg(reg.offset, sz, neg=reg.neg, abs_=reg.abs_, hi=reg.hi)
|
||||
return reg
|
||||
def encode_src(val) -> int:
|
||||
if isinstance(val, VGPR): return 256 + _encode_reg(val)
|
||||
if isinstance(val, Reg): return _encode_reg(val)
|
||||
if isinstance(val, SrcMod) and not isinstance(val, Reg):
|
||||
v = val.val
|
||||
if _is_encoded_src(v): return v # Already encoded (special reg 106-127 or inline const 128-208 or float 240-255)
|
||||
if isinstance(v, int) and 0 <= v <= 64: return 128 + v # Encode as inline constant
|
||||
if isinstance(v, int) and -16 <= v <= -1: return 192 - v
|
||||
return 255 # Literal
|
||||
if hasattr(val, 'value'): return val.value # IntEnum
|
||||
if isinstance(val, float): return 128 if val == 0.0 else FLOAT_ENC.get(val, 255)
|
||||
if isinstance(val, int): return 128 + val if 0 <= val <= 64 else 192 - val if -16 <= val <= -1 else 255
|
||||
return 255
|
||||
|
||||
class SBaseField(AlignedSGPRField): _align = 2
|
||||
class SRsrcField(AlignedSGPRField): _align = 4
|
||||
|
||||
class VDSTYField(BitField):
|
||||
"""VOPD vdsty: encoded = vgpr_idx >> 1. Actual vgpr = (encoded << 1) | ((vdstx & 1) ^ 1)."""
|
||||
def encode(self, val):
|
||||
if not isinstance(val, Reg): raise TypeError(f"VDSTYField requires Reg, got {type(val).__name__}")
|
||||
if not (256 <= val.offset < 512): raise ValueError(f"VDSTYField requires VGPR, got offset {val.offset}")
|
||||
return (val.offset - 256) >> 1
|
||||
def decode(self, raw): return raw # raw value, actual vdsty = (raw << 1) | ((vdstx & 1) ^ 1)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Operand info from XML
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
|
||||
import functools
|
||||
from extra.assembly.amd.autogen.rdna3.operands import OPERANDS as OPERANDS_RDNA3
|
||||
from extra.assembly.amd.autogen.rdna4.operands import OPERANDS as OPERANDS_RDNA4
|
||||
from extra.assembly.amd.autogen.cdna.operands import OPERANDS as OPERANDS_CDNA
|
||||
OPERANDS = {**OPERANDS_CDNA, **OPERANDS_RDNA3, **OPERANDS_RDNA4}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Inst base class
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
def decode_src(val: int, cdna: bool = False) -> str:
|
||||
special = SPECIAL_GPRS_CDNA if cdna else SPECIAL_GPRS
|
||||
if val in special: return special[val]
|
||||
if val <= 105: return f"s{val}"
|
||||
if val in FLOAT_DEC: return FLOAT_DEC[val]
|
||||
if 108 <= val <= 123: return f"ttmp{val - 108}"
|
||||
if 128 <= val <= 192: return str(val - 128)
|
||||
if 193 <= val <= 208: return str(-(val - 192))
|
||||
if 256 <= val <= 511: return f"v{val - 256}"
|
||||
return "lit" if val == 255 else f"?{val}"
|
||||
|
||||
# Instruction base class
|
||||
class Inst:
|
||||
_fields: list[tuple[str, BitField]]
|
||||
_base_size: int
|
||||
_fields: dict[str, BitField]
|
||||
_encoding: tuple[BitField, int] | None = None
|
||||
_defaults: dict[str, int] = {}
|
||||
_values: dict[str, int | RawImm]
|
||||
_words: int # size in 32-bit words, set by decode_program
|
||||
_literal: int | None
|
||||
|
||||
def __init_subclass__(cls):
|
||||
# Collect fields from all parent classes, then override with this class's fields
|
||||
inherited = {}
|
||||
for base in reversed(cls.__mro__[1:]):
|
||||
if hasattr(base, '_fields'):
|
||||
inherited.update({name: field for name, field in base._fields})
|
||||
inherited.update({name: val for name, val in cls.__dict__.items() if isinstance(val, BitField)})
|
||||
cls._fields = list(inherited.items())
|
||||
cls._base_size = (max(f.hi for _, f in cls._fields) + 8) // 8
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
# Merge fields from parent classes
|
||||
cls._fields = {}
|
||||
for base in reversed(cls.__mro__):
|
||||
if base is Inst or not hasattr(base, '_fields'): continue
|
||||
cls._fields.update(base._fields)
|
||||
# Add this class's own fields (overrides parents)
|
||||
cls._fields.update({n: v[0] if isinstance(v, tuple) else v for n, v in cls.__dict__.items() if isinstance(v, BitField) or (isinstance(v, tuple) and len(v) == 2 and isinstance(v[0], BitField))})
|
||||
# Compute size from max bit (exclude optional MIMG NSA fields: addr1/addr2 at bits 64+)
|
||||
optional_nsa = {'addr1', 'addr2'}
|
||||
max_bit = max((bf.hi for n, bf in cls._fields.items() if n not in optional_nsa), default=0) if cls._fields else 0
|
||||
cls._sz = 12 if max_bit > 63 else 8 if max_bit > 31 else 4
|
||||
if 'encoding' in cls._fields and isinstance(cls.__dict__.get('encoding'), tuple): cls._encoding = cls.__dict__['encoding']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._raw = 0
|
||||
self._literal: int | None = kwargs.pop('literal', None)
|
||||
# Map positional args to field names (skip FixedBitFields)
|
||||
args_iter = iter(args)
|
||||
vals = {}
|
||||
for name, field in self._fields:
|
||||
if isinstance(field, FixedBitField): vals[name] = None
|
||||
elif name in kwargs: vals[name] = kwargs[name]
|
||||
else: vals[name] = next(args_iter, None)
|
||||
remaining = list(args_iter)
|
||||
assert not remaining, f"too many positional args: {remaining}"
|
||||
# Extract modifiers from Reg objects and merge into neg/abs/opsel
|
||||
neg_bits, abs_bits, opsel_bits = 0, 0, 0
|
||||
for name, bit in [('src0', 0), ('src1', 1), ('src2', 2)]:
|
||||
if name in vals and isinstance(vals[name], Reg):
|
||||
reg = vals[name]
|
||||
if reg.neg: neg_bits |= (1 << bit)
|
||||
if reg.abs_: abs_bits |= (1 << bit)
|
||||
if reg.hi: opsel_bits |= (1 << bit)
|
||||
if 'vdst' in vals and isinstance(vals['vdst'], Reg) and vals['vdst'].hi:
|
||||
opsel_bits |= (1 << 3)
|
||||
if neg_bits: vals['neg'] = (vals.get('neg') or 0) | neg_bits
|
||||
if abs_bits: vals['abs'] = (vals.get('abs') or 0) | abs_bits
|
||||
if opsel_bits: vals['opsel'] = (vals.get('opsel') or 0) | opsel_bits
|
||||
# Set all field values
|
||||
for name, field in self._fields:
|
||||
val = vals[name]
|
||||
self._raw = field.set(self._raw, val)
|
||||
# Capture literal for SrcFields that encoded to 255
|
||||
if isinstance(field, SrcField) and val is not None and field.encode(val) + field._valid_range[0] == 255 and self._literal is None:
|
||||
self._literal = _f32(val) if isinstance(val, float) else val & 0xFFFFFFFF
|
||||
# Validate register sizes against operand info (skip special registers like NULL, VCC, EXEC)
|
||||
for name, expected in self.op_regs.items():
|
||||
if (val := vals.get(name)) is None: continue
|
||||
if isinstance(val, Reg) and val.sz != expected and not (106 <= val.offset <= 127 or val.offset == 253):
|
||||
raise TypeError(f"{name} expects {expected} register(s), got {val.sz}")
|
||||
def _or_field(self, name: str, bit: int):
|
||||
cur = self._values.get(name, 0)
|
||||
self._values[name] = (cur.val if isinstance(cur, RawImm) else cur) | bit
|
||||
|
||||
@property
|
||||
def op_name(self) -> str: return self.op.name
|
||||
@property
|
||||
def operands(self) -> dict: return OPERANDS.get(self.op, {}) if hasattr(self, 'op') else {}
|
||||
def _is_cdna(self) -> bool: return 'cdna' in type(self).__module__
|
||||
def _encode_src(self, name: str, val):
|
||||
"""Encode a source field, handling modifiers and literals."""
|
||||
encoded = encode_src(val)
|
||||
has_opsel = 'opsel' in self._fields
|
||||
if isinstance(val, Reg) and val.hi and not has_opsel: encoded |= 0x80 # hi bit in src for VOP1/2/C
|
||||
self._values[name] = RawImm(encoded)
|
||||
# Handle neg/abs/opsel modifiers
|
||||
if isinstance(val, SrcMod):
|
||||
mod_bit = {'src0': 1, 'src1': 2, 'src2': 4}.get(name, 0)
|
||||
if val.neg and 'neg' in self._fields: self._or_field('neg', mod_bit)
|
||||
if val.abs_ and 'abs' in self._fields: self._or_field('abs', mod_bit)
|
||||
if isinstance(val, Reg) and val.hi and has_opsel:
|
||||
self._or_field('opsel', {'src0': 1, 'src1': 2, 'src2': 4}.get(name, 0))
|
||||
# Track literal value if needed
|
||||
if encoded == 255 and self._literal is None:
|
||||
import struct
|
||||
# Check if THIS source uses 64-bit encoding (not just src0)
|
||||
src_idx = {'src0': 0, 'src1': 1, 'src2': 2, 'ssrc0': 0, 'ssrc1': 1}.get(name, 0)
|
||||
src_regs = self.src_regs(src_idx)
|
||||
is_64 = src_regs == 2
|
||||
if isinstance(val, SrcMod) and not isinstance(val, Reg): lit32 = val.val & MASK32
|
||||
elif isinstance(val, int) and not isinstance(val, IntEnum): lit32 = val & MASK32
|
||||
elif isinstance(val, float): lit32 = (_i64(val) >> 32) if is_64 else _i32(val) # f64: high 32 bits of f64 repr
|
||||
else: return
|
||||
self._literal = (lit32 << 32) if is_64 else lit32
|
||||
|
||||
@functools.cached_property
|
||||
def op_bits(self) -> dict[str, int]:
|
||||
"""Get bit widths for each operand field, with WAVE32 and addr/saddr adjustments."""
|
||||
if not hasattr(self, 'op'): return {k: v[1] for k, v in self.operands.items()}
|
||||
bits = {k: v[1] for k, v in self.operands.items()}
|
||||
# RDNA (WAVE32): condition masks, carry flags, and compare results are 32-bit
|
||||
if not self._is_cdna():
|
||||
name = self.op_name.lower()
|
||||
if 'cndmask' in name and 'src2' in bits: bits['src2'] = 32
|
||||
if '_co_ci_' in name:
|
||||
if 'src2' in bits: bits['src2'] = 32
|
||||
if 'sdst' in bits: bits['sdst'] = 32
|
||||
if 'cmp' in name and 'vdst' in bits: bits['vdst'] = 32
|
||||
# GLOBAL/FLAT: addr is 32-bit if saddr is valid SGPR, 64-bit if saddr is NULL
|
||||
if 'addr' in bits and (saddr_field := getattr(type(self), 'saddr', None)):
|
||||
saddr_val = (self._raw >> saddr_field.lo) & saddr_field.mask # access _raw directly to avoid recursion
|
||||
bits['addr'] = 64 if saddr_val in (124, 125) else 32 # 124=NULL, 125=M0
|
||||
# MUBUF/MTBUF: vaddr size depends on offen/idxen (1 or 2 regs)
|
||||
if 'vaddr' in bits and hasattr(self, 'offen') and hasattr(self, 'idxen'):
|
||||
bits['vaddr'] = max(1, self.offen + self.idxen) * 32
|
||||
return bits
|
||||
@property
|
||||
def op_regs(self) -> dict[str, int]:
|
||||
"""Get register counts for each operand field."""
|
||||
return {k: max(1, v // 32) for k, v in self.op_bits.items()}
|
||||
def _encode_raw(self, name: str, val):
|
||||
"""Encode a raw register field (vdst, vdata, etc.)."""
|
||||
if isinstance(val, Reg):
|
||||
encoded = _encode_reg(val)
|
||||
if val.hi and 'opsel' not in self._fields: encoded |= 0x80
|
||||
self._values[name] = encoded
|
||||
if name == 'vdst' and val.hi and 'opsel' in self._fields: self._or_field('opsel', 8)
|
||||
elif hasattr(val, 'value'): self._values[name] = val.value
|
||||
|
||||
@functools.cached_property
|
||||
def canonical_op_bits(self) -> dict[str, int]:
|
||||
"""Get bit widths with canonical names: {'s0', 's1', 's2', 'd', 'data'}."""
|
||||
bits = {'d': 32, 's0': 32, 's1': 32, 's2': 32, 'data': 32}
|
||||
for name, val in self.op_bits.items():
|
||||
if name in ('src0', 'vsrc0', 'ssrc0'): bits['s0'] = val
|
||||
elif name in ('src1', 'vsrc1', 'ssrc1'): bits['s1'] = val
|
||||
elif name == 'src2': bits['s2'] = val
|
||||
elif name in ('vdst', 'sdst', 'sdata'): bits['d'] = val
|
||||
elif name in ('data', 'vdata', 'data0'): bits['data'] = val
|
||||
return bits
|
||||
@property
|
||||
def canonical_op_regs(self) -> dict[str, int]:
|
||||
"""Get register counts with canonical names: {'s0', 's1', 's2', 'd', 'data'}."""
|
||||
return {k: max(1, v // 32) for k, v in self.canonical_op_bits.items()}
|
||||
def _validate(self, orig_args: dict):
|
||||
"""Format-specific validation. Override in subclass or check by class name."""
|
||||
cls_name, op = self.__class__.__name__, orig_args.get('op')
|
||||
op_val = op.value if hasattr(op, 'value') else op
|
||||
op_name = op.name if hasattr(op, 'name') else None
|
||||
# SMEM: register count must match opcode (derive from name: b32=1, b64=2, b96=3, b128=4, b256=8, b512=16, i8/u8/i16/u16=1)
|
||||
if cls_name == 'SMEM' and op_name:
|
||||
expected = {'B32': 1, 'B64': 2, 'B96': 3, 'B128': 4, 'B256': 8, 'B512': 16, 'I8': 1, 'U8': 1, 'I16': 1, 'U16': 1}.get(op_name.split('_')[-1])
|
||||
sdata = orig_args.get('sdata')
|
||||
if expected and isinstance(sdata, Reg) and sdata.count != expected:
|
||||
raise ValueError(f"SMEM op {op_name} expects {expected} registers, got {sdata.count}")
|
||||
# SOP1: derive expected register sizes from op name (e.g., S_MOV_B64 -> dst=2, src=2; S_CTZ_I32_B64 -> dst=1, src=2)
|
||||
if cls_name == 'SOP1' and hasattr(orig_args.get('op'), 'name'):
|
||||
op_name = orig_args['op'].name
|
||||
# Special cases: BITSET takes bit index (1 reg) regardless of dst size
|
||||
if 'BITSET' in op_name:
|
||||
dst_size = 2 if op_name.endswith('_B64') else 1
|
||||
src_size = 1 # bit index is always 1 reg
|
||||
else:
|
||||
# Extract sizes from name: last suffix is src type, second-to-last (if exists) is dst type
|
||||
sizes = {'B32': 1, 'I32': 1, 'U32': 1, 'B64': 2, 'I64': 2, 'U64': 2, 'B128': 4, 'B256': 8, 'B512': 16}
|
||||
parts = op_name.split('_')
|
||||
src_size = sizes.get(parts[-1], 1) if parts[-1] in sizes else 1
|
||||
dst_size = sizes.get(parts[-2], src_size) if len(parts) >= 2 and parts[-2] in sizes else src_size
|
||||
for fld, expected in [('sdst', dst_size), ('ssrc0', src_size)]:
|
||||
if isinstance(orig_args.get(fld), Reg) and orig_args[fld].count != expected:
|
||||
raise ValueError(f"SOP1 {op_name} expects {expected} register(s) for {fld}, got {orig_args[fld].count}")
|
||||
|
||||
def num_srcs(self) -> int:
|
||||
"""Get number of source operands from operand info."""
|
||||
ops = self.operands
|
||||
if 'src2' in ops: return 3
|
||||
if 'src1' in ops or 'vsrc1' in ops or 'ssrc1' in ops: return 2
|
||||
if 'src0' in ops or 'vsrc0' in ops or 'ssrc0' in ops: return 1
|
||||
return 0
|
||||
@classmethod
|
||||
def _size(cls) -> int: return cls._base_size
|
||||
def size(self) -> int: return self._base_size + (4 if self._literal is not None else 0)
|
||||
def disasm(self) -> str:
|
||||
from extra.assembly.amd.disasm import disasm
|
||||
return disasm(self)
|
||||
def __init__(self, *args, literal: int | None = None, **kwargs):
|
||||
self._values, self._literal = dict(self._defaults), None
|
||||
field_names = [n for n in self._fields if n != 'encoding']
|
||||
# Map Python-friendly names to actual field names (abs_ -> abs for Python reserved word)
|
||||
if 'abs_' in kwargs: kwargs['abs'] = kwargs.pop('abs_')
|
||||
# If more args than fields, treat extra arg as literal (for FMAAK/FMAMK style instructions)
|
||||
# FMAMK has K in middle (vdst, src0, K, vsrc1), FMAAK has K at end (vdst, src0, vsrc1, K)
|
||||
args = list(args)
|
||||
if len(args) > len(field_names) and literal is None:
|
||||
for i, a in enumerate(args):
|
||||
if isinstance(a, int) and not isinstance(a, SrcEnum) and i < len(field_names) and field_names[i] in ('vsrc1',):
|
||||
literal = args.pop(i)
|
||||
break
|
||||
else:
|
||||
literal = args.pop() # fallback: last arg is literal
|
||||
orig_args = dict(zip(field_names, args)) | kwargs
|
||||
self._values.update(orig_args)
|
||||
self._precompute()
|
||||
self._validate(orig_args)
|
||||
# Pre-shift literal for 64-bit sources (literal param is always raw 32-bit value from user)
|
||||
if literal is not None:
|
||||
# Find which source uses the literal (255) and check its register count
|
||||
for n, idx in [('src0', 0), ('src1', 1), ('src2', 2), ('ssrc0', 0), ('ssrc1', 1)]:
|
||||
v = orig_args.get(n)
|
||||
if (isinstance(v, RawImm) and v.val == 255) or (isinstance(v, int) and v == 255):
|
||||
self._literal = (literal << 32) if self.src_regs(idx) == 2 else literal
|
||||
break
|
||||
else:
|
||||
self._literal = literal # fallback if no literal source found
|
||||
cls_name = self.__class__.__name__
|
||||
|
||||
# Format-specific setup
|
||||
if cls_name == 'FLAT' and 'sve' in self._fields:
|
||||
seg = self._values.get('seg', 0)
|
||||
if (seg.val if isinstance(seg, RawImm) else seg) == 1 and isinstance(orig_args.get('addr'), VGPR): self._values['sve'] = 1
|
||||
if cls_name == 'VOP3P':
|
||||
op = orig_args.get('op')
|
||||
if hasattr(op, 'value'): op = op.value
|
||||
# fma_mix ops (32-34) default to opsel_hi=0, WMMA ops (64-69) default to opsel_hi=7 to match LLVM
|
||||
if op in (32, 33, 34) and 'opsel_hi' not in orig_args: self._values['opsel_hi'] = self._values['opsel_hi2'] = 0
|
||||
if op in range(64, 70) and 'opsel_hi' not in orig_args: self._values['opsel_hi'], self._values['opsel_hi2'] = 3, 1
|
||||
|
||||
# Encode all fields
|
||||
for name, val in list(self._values.items()):
|
||||
if name == 'encoding': continue
|
||||
if isinstance(val, RawImm):
|
||||
if name in RAW_FIELDS: self._values[name] = val.val
|
||||
continue
|
||||
field = self._fields.get(name)
|
||||
marker = field.marker if field else None
|
||||
# Type validation
|
||||
if marker is _SGPRField and isinstance(val, VGPR): raise TypeError(f"field '{name}' requires SGPR, got VGPR")
|
||||
if marker is _VGPRField and not isinstance(val, VGPR): raise TypeError(f"field '{name}' requires VGPR, got {type(val).__name__}")
|
||||
if marker is _SSrc and isinstance(val, VGPR): raise TypeError(f"field '{name}' requires scalar source, got VGPR")
|
||||
# Encode by field type
|
||||
if name in SRC_FIELDS: self._encode_src(name, val)
|
||||
elif name in RAW_FIELDS: self._encode_raw(name, val)
|
||||
elif name == 'sbase': self._values[name] = (val.idx if isinstance(val, Reg) else val.val if isinstance(val, SrcMod) else val * 2) // 2
|
||||
elif name in {'srsrc', 'ssamp'} and isinstance(val, Reg): self._values[name] = _encode_reg(val) // 4
|
||||
elif marker is _VDSTYEnc and isinstance(val, VGPR): self._values[name] = val.idx >> 1
|
||||
self._precompute_fields()
|
||||
|
||||
def _encode_field(self, name: str, val) -> int:
|
||||
if isinstance(val, RawImm): return val.val
|
||||
if isinstance(val, SrcMod) and not isinstance(val, Reg): return val.val # Special regs like VCC_LO
|
||||
if name in {'srsrc', 'ssamp'}: return _encode_reg(val) // 4 if isinstance(val, Reg) else val
|
||||
if name == 'sbase': return val.idx // 2 if isinstance(val, Reg) else val.val // 2 if isinstance(val, SrcMod) else val
|
||||
if name in RAW_FIELDS: return _encode_reg(val) if isinstance(val, Reg) else val
|
||||
if isinstance(val, Reg) or name in SRC_FIELDS: return encode_src(val)
|
||||
return val.value if hasattr(val, 'value') else val
|
||||
|
||||
def to_int(self) -> int:
|
||||
word = (self._encoding[1] & self._encoding[0].mask()) << self._encoding[0].lo if self._encoding else 0
|
||||
for n, bf in self._fields.items():
|
||||
if n != 'encoding' and n in self._values: word |= (self._encode_field(n, self._values[n]) & bf.mask()) << bf.lo
|
||||
return word
|
||||
|
||||
def _get_literal(self) -> int | None:
|
||||
for n in SRC_FIELDS:
|
||||
if n in self._values and not isinstance(v := self._values[n], RawImm) and isinstance(v, int) and not isinstance(v, IntEnum) and not (0 <= v <= 64 or -16 <= v <= -1): return v
|
||||
return None
|
||||
|
||||
def _is_64bit_op(self) -> bool:
|
||||
"""Check if this instruction uses 64-bit operands (and thus 64-bit literals)."""
|
||||
op = self._values.get('op')
|
||||
if op is None: return False
|
||||
op_name = op.name if hasattr(op, 'name') else None
|
||||
# Look up op name from int if needed (happens in from_bytes path)
|
||||
if op_name is None and self.__class__.__name__ == 'VOP3':
|
||||
try: op_name = VOP3Op(op).name
|
||||
except ValueError: pass
|
||||
if op_name is None and self.__class__.__name__ == 'VOPC':
|
||||
try: op_name = VOPCOp(op).name
|
||||
except ValueError: pass
|
||||
if op_name is None: return False
|
||||
# V_LDEXP_F64 has 32-bit integer src1, so literal is 32-bit
|
||||
return op_name != 'V_LDEXP_F64' and op_name.endswith(('_F64', '_B64', '_I64', '_U64'))
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
result = self._raw.to_bytes(self._base_size, 'little')
|
||||
if self._literal is not None:
|
||||
result += (self._literal & 0xFFFFFFFF).to_bytes(4, 'little')
|
||||
return result
|
||||
result = self.to_int().to_bytes(self._size(), 'little')
|
||||
lit = self._get_literal() or getattr(self, '_literal', None)
|
||||
if lit is None: return result
|
||||
# For 64-bit sources, literal is stored in high 32 bits internally, but encoded as 4 bytes
|
||||
# Find which source uses the literal (255) and check its register count
|
||||
lit_src_is_64 = False
|
||||
for n, idx in [('src0', 0), ('src1', 1), ('src2', 2), ('ssrc0', 0), ('ssrc1', 1)]:
|
||||
if n not in self._values: continue
|
||||
v = self._values[n]
|
||||
if (isinstance(v, RawImm) and v.val == 255) or (isinstance(v, int) and v == 255):
|
||||
lit_src_is_64 = self.is_src_64(idx)
|
||||
break
|
||||
lit32 = (lit >> 32) if lit_src_is_64 else lit
|
||||
return result + (lit32 & MASK32).to_bytes(4, 'little')
|
||||
|
||||
def has_literal(self) -> bool:
|
||||
"""Check if instruction has a 32-bit literal constant."""
|
||||
for name, field in self._fields:
|
||||
if isinstance(field, SrcField) and getattr(self, name).offset == 255:
|
||||
return True
|
||||
# Check op, opx, opy for instructions that always have literals
|
||||
for attr in ('op', 'opx', 'opy'):
|
||||
if hasattr(self, attr) and any(x in getattr(self, attr).name for x in ('FMAMK', 'FMAAK', 'MADMK', 'MADAK', 'SETREG_IMM32')):
|
||||
return True
|
||||
return False
|
||||
@classmethod
|
||||
def _size(cls) -> int: return cls._sz
|
||||
def size(self) -> int:
|
||||
# Literal is always 4 bytes in the binary (for 64-bit ops, it's in high 32 bits)
|
||||
return self._size() + (4 if self._literal is not None else 0)
|
||||
|
||||
@classmethod
|
||||
def from_int(cls, word: int):
|
||||
inst = object.__new__(cls)
|
||||
inst._values = {n: RawImm(v) if n in SRC_FIELDS else v for n, bf in cls._fields.items() if n != 'encoding' for v in [(word >> bf.lo) & bf.mask()]}
|
||||
inst._literal = None
|
||||
inst._precompute()
|
||||
inst._precompute_fields()
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
inst = object.__new__(cls)
|
||||
inst._raw = int.from_bytes(data[:cls._base_size], 'little')
|
||||
inst._literal = int.from_bytes(data[cls._base_size:cls._base_size + 4], 'little') if inst.has_literal() else None
|
||||
import typing
|
||||
inst = cls.from_int(int.from_bytes(data[:cls._size()], 'little'))
|
||||
op_val = inst._values.get('op', 0)
|
||||
# Check for instructions that always have a literal constant (FMAMK/FMAAK/MADMK/MADAK, SETREG_IMM32)
|
||||
op_name = ''
|
||||
if cls.__name__ in ('VOP2', 'SOP2', 'SOPK') and 'op' in (hints := typing.get_type_hints(cls, include_extras=True)):
|
||||
if typing.get_origin(hints['op']) is typing.Annotated:
|
||||
try: op_name = typing.get_args(hints['op'])[1](op_val).name
|
||||
except (ValueError, TypeError): pass
|
||||
has_literal = any(x in op_name for x in ('FMAMK', 'FMAAK', 'MADMK', 'MADAK', 'SETREG_IMM32'))
|
||||
# VOPD fmaak/fmamk always have a literal (opx/opy value 1 or 2)
|
||||
opx, opy = inst._values.get('opx', 0), inst._values.get('opy', 0)
|
||||
has_literal = has_literal or (cls.__name__ == 'VOPD' and (opx in (1, 2) or opy in (1, 2)))
|
||||
for n in SRC_FIELDS:
|
||||
if n in inst._values and isinstance(inst._values[n], RawImm) and inst._values[n].val == 255: has_literal = True
|
||||
if has_literal:
|
||||
# For 64-bit ops, the literal is 32 bits placed in the HIGH 32 bits of the 64-bit value
|
||||
# (low 32 bits are zero). This is how AMD hardware interprets 32-bit literals for 64-bit ops.
|
||||
# Check which source uses the literal and whether THAT source is 64-bit
|
||||
if len(data) >= cls._size() + 4:
|
||||
lit32 = int.from_bytes(data[cls._size():cls._size()+4], 'little')
|
||||
# Find which source has literal (255) and check its register count
|
||||
lit_src_is_64 = False
|
||||
for n, idx in [('src0', 0), ('src1', 1), ('src2', 2), ('ssrc0', 0), ('ssrc1', 1)]:
|
||||
if n in inst._values and isinstance(inst._values[n], RawImm) and inst._values[n].val == 255:
|
||||
lit_src_is_64 = inst.src_regs(idx) == 2
|
||||
break
|
||||
inst._literal = (lit32 << 32) if lit_src_is_64 else lit32
|
||||
return inst
|
||||
|
||||
def __eq__(self, other): return type(self) is type(other) and self._raw == other._raw and self._literal == other._literal
|
||||
def __hash__(self): return hash((type(self), self._raw, self._literal))
|
||||
|
||||
def __repr__(self):
|
||||
# collect (repr, is_default) pairs, strip trailing defaults so repr roundtrips with eval
|
||||
name = self.op.name.lower() if hasattr(self, 'op') else type(self).__name__
|
||||
parts = [(repr(v := getattr(self, n)), v == f.default) for n, f in self._fields if n != 'op' and not isinstance(f, FixedBitField)]
|
||||
while parts and parts[-1][1]: parts.pop()
|
||||
return f"{name}({', '.join(p[0] for p in parts)})"
|
||||
# Use _fields order and exclude fields that are 0/default (for consistent repr after roundtrip)
|
||||
def is_zero(v): return (isinstance(v, int) and v == 0) or (isinstance(v, VGPR) and v.idx == 0 and v.count == 1)
|
||||
items = [(k, self._values[k]) for k in self._fields if k in self._values and k != 'encoding'
|
||||
and not (is_zero(self._values[k]) and k not in {'op'})]
|
||||
lit = f", literal={hex(self._literal)}" if self._literal is not None else ""
|
||||
return f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in items)}{lit})"
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
if name.startswith('_'): raise AttributeError(name)
|
||||
return unwrap(self._values.get(name, 0))
|
||||
|
||||
def lit(self, v: int, neg: bool = False) -> str:
|
||||
if v == 255 and self._literal is not None:
|
||||
# For 64-bit sources, literal is stored shifted - extract the 32-bit value
|
||||
lit32 = (self._literal >> 32) if self._literal > 0xffffffff else self._literal
|
||||
s = f"0x{lit32:x}"
|
||||
else:
|
||||
s = decode_src(v, 'cdna' in self.__class__.__module__)
|
||||
return f"-{s}" if neg else s
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Inst): return NotImplemented
|
||||
return self.__class__ == other.__class__ and self._values == other._values and self._literal == other._literal
|
||||
|
||||
def __hash__(self): return hash((self.__class__.__name__, tuple(sorted((k, repr(v)) for k, v in self._values.items())), self._literal))
|
||||
|
||||
def disasm(self) -> str:
|
||||
from extra.assembly.amd.asm import disasm
|
||||
return disasm(self)
|
||||
|
||||
_enum_map = {'VOP1': VOP1Op, 'VOP2': VOP2Op, 'VOP3': VOP3Op, 'VOP3SD': VOP3SDOp, 'VOP3P': VOP3POp, 'VOPC': VOPCOp,
|
||||
'SOP1': SOP1Op, 'SOP2': SOP2Op, 'SOPC': SOPCOp, 'SOPK': SOPKOp, 'SOPP': SOPPOp,
|
||||
'SMEM': SMEMOp, 'DS': DSOp, 'FLAT': FLATOp, 'MUBUF': MUBUFOp, 'MTBUF': MTBUFOp, 'MIMG': MIMGOp,
|
||||
'VOPD': VOPDOp, 'VINTERP': VINTERPOp}
|
||||
_VOP3SD_OPS = {288, 289, 290, 764, 765, 766, 767, 768, 769, 770}
|
||||
|
||||
def _precompute(self):
|
||||
"""Precompute op, op_name, _spec_regs, _spec_dtype for fast access."""
|
||||
val = self._values.get('op')
|
||||
if val is None: self.op = None
|
||||
elif hasattr(val, 'name'): self.op = val
|
||||
else:
|
||||
cls_name = self.__class__.__name__
|
||||
is_cdna = cls_name in ('VOP3A', 'VOP3B')
|
||||
# Try marker enum first (VOP3AOp, VOP3BOp, etc.)
|
||||
marker = self._fields['op'].marker if 'op' in self._fields else None
|
||||
if marker and issubclass(marker, IntEnum):
|
||||
try: self.op = marker(val)
|
||||
except ValueError: self.op = val
|
||||
elif cls_name in self._enum_map:
|
||||
try: self.op = self._enum_map[cls_name](val)
|
||||
except ValueError: self.op = val
|
||||
else: self.op = val
|
||||
# Fallback for promoted instructions when marker lookup failed
|
||||
if not hasattr(self.op, 'name') and cls_name in ('VOP3', 'VOP3A', 'VOP3B') and isinstance(val, int):
|
||||
if val < 256:
|
||||
try: self.op = VOPCOp(val)
|
||||
except ValueError: pass
|
||||
elif is_cdna and 256 <= val < 512:
|
||||
try: self.op = (CDNA_VOP1Op(val - 320) if val >= 320 else CDNA_VOP2Op(val - 256))
|
||||
except ValueError: pass
|
||||
elif val in self._VOP3SD_OPS and not is_cdna:
|
||||
try: self.op = VOP3SDOp(val)
|
||||
except ValueError: pass
|
||||
elif 256 <= val < 512 and not is_cdna:
|
||||
try: self.op = VOP1Op(val - 384) if val >= 384 else VOP2Op(val - 256)
|
||||
except ValueError: pass
|
||||
self.op_name = self.op.name if hasattr(self.op, 'name') else ''
|
||||
self._spec_regs = spec_regs(self.op_name)
|
||||
self._spec_dtype = spec_dtype(self.op_name)
|
||||
|
||||
def _precompute_fields(self):
|
||||
"""Unwrap all field values as direct attributes for fast access."""
|
||||
for name, val in self._values.items():
|
||||
if name != 'op': setattr(self, name, unwrap(val))
|
||||
def dst_regs(self) -> int: return self._spec_regs[0]
|
||||
def src_regs(self, n: int) -> int: return self._spec_regs[n + 1]
|
||||
def num_srcs(self) -> int: return spec_num_srcs(self.op_name)
|
||||
def dst_dtype(self) -> str | None: return self._spec_dtype[0]
|
||||
def src_dtype(self, n: int) -> str | None: return self._spec_dtype[n + 1]
|
||||
def is_src_16(self, n: int) -> bool: return self._spec_regs[n + 1] == 1 and is_dtype_16(self._spec_dtype[n + 1])
|
||||
def is_src_64(self, n: int) -> bool: return self._spec_regs[n + 1] == 2
|
||||
def is_16bit(self) -> bool: return spec_is_16bit(self.op_name)
|
||||
def is_64bit(self) -> bool: return spec_is_64bit(self.op_name)
|
||||
def is_dst_16(self) -> bool: return self._spec_regs[0] == 1 and is_dtype_16(self._spec_dtype[0])
|
||||
|
||||
|
||||
|
||||
+367
-126
@@ -2,43 +2,17 @@
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
import ctypes, functools
|
||||
from enum import IntEnum
|
||||
from tinygrad.helpers import DEBUG, colored, ansilen
|
||||
from tinygrad.runtime.autogen import hsa
|
||||
from extra.assembly.amd.dsl import Inst, NULL, SCC, VCC_LO, VCC_HI, EXEC_LO, EXEC_HI, v, s
|
||||
from extra.assembly.amd.pcode import _f32, _i32, _sext, _f16, _i16, _f64, _i64
|
||||
from extra.assembly.amd.decode import decode_inst
|
||||
from extra.assembly.amd.pcode import compile_pseudocode
|
||||
from extra.assembly.amd.autogen.rdna3.str_pcode import PCODE
|
||||
from extra.assembly.amd.dsl import Inst, unwrap, FLOAT_ENC, MASK32, MASK64, _f32, _i32, _sext, _f16, _i16, _f64, _i64, SrcEnum
|
||||
from extra.assembly.amd.pcode import Reg, compile_pseudocode
|
||||
from extra.assembly.amd.asm import detect_format, disasm
|
||||
from extra.assembly.amd.autogen.rdna3.str_pcode import PSEUDOCODE_STRINGS
|
||||
from extra.assembly.amd.autogen.rdna3.ins import (SOP1, SOP2, SOPC, SOPK, SOPP, SMEM, VOP1, VOP2, VOP3, VOP3SD, VOP3P, VOPC, DS, FLAT, VOPD,
|
||||
SOP1Op, SOP2Op, SOPCOp, SOPKOp, SOPPOp, SMEMOp, VOP1Op, VOP2Op, VOP3Op, VOP3SDOp, VOP3POp, VOPCOp, DSOp, FLATOp, GLOBALOp, SCRATCHOp, VOPDOp)
|
||||
|
||||
# Constants and helpers defined locally (not imported from dsl.py)
|
||||
MASK32, MASK64 = 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF
|
||||
FLOAT_ENC = {0.5: 240, -0.5: 241, 1.0: 242, -1.0: 243, 2.0: 244, -2.0: 245, 4.0: 246, -4.0: 247}
|
||||
|
||||
class SGPRArray:
|
||||
"""SGPR array indexed by Reg or int."""
|
||||
__slots__ = ('_data',)
|
||||
def __init__(self, size: int): self._data = [0] * size
|
||||
def __getitem__(self, key): return self._data[getattr(key, 'offset', key)]
|
||||
def __setitem__(self, key, val): self._data[getattr(key, 'offset', key)] = val
|
||||
def __len__(self): return len(self._data)
|
||||
def __iter__(self): return iter(self._data)
|
||||
|
||||
class VGPRLane:
|
||||
"""Single lane of VGPRs indexed by Reg (offset 256-511) or int (0-255)."""
|
||||
__slots__ = ('_data',)
|
||||
def __init__(self, size: int): self._data = [0] * size
|
||||
def __getitem__(self, key):
|
||||
i = getattr(key, 'offset', key)
|
||||
return self._data[i - 256 if i >= 256 else i]
|
||||
def __setitem__(self, key, val):
|
||||
i = getattr(key, 'offset', key)
|
||||
self._data[i - 256 if i >= 256 else i] = val
|
||||
def __len__(self): return len(self._data)
|
||||
def __iter__(self): return iter(self._data)
|
||||
|
||||
WAVE_SIZE, SGPR_COUNT, VGPR_COUNT = 32, 128, 256
|
||||
VCC_LO, VCC_HI, NULL, EXEC_LO, EXEC_HI, SCC = SrcEnum.VCC_LO, SrcEnum.VCC_HI, SrcEnum.NULL, SrcEnum.EXEC_LO, SrcEnum.EXEC_HI, SrcEnum.SCC
|
||||
|
||||
# Inline constants for src operands 128-254. Build tables for f32, f16, and f64 formats.
|
||||
_FLOAT_CONSTS = {v: k for k, v in FLOAT_ENC.items()} | {248: 0.15915494309189535} # INV_2PI
|
||||
@@ -53,8 +27,8 @@ _INLINE_CONSTS_F64 = _build_inline_consts(MASK64, _i64)
|
||||
# Helper: extract/write 16-bit half from/to 32-bit value
|
||||
def _src16(raw: int, is_hi: bool) -> int: return ((raw >> 16) & 0xffff) if is_hi else (raw & 0xffff)
|
||||
def _dst16(cur: int, val: int, is_hi: bool) -> int: return (cur & 0x0000ffff) | ((val & 0xffff) << 16) if is_hi else (cur & 0xffff0000) | (val & 0xffff)
|
||||
def _vgpr_hi(src) -> bool: return src.offset >= 256 and ((src.offset - 256) & 0x80) != 0
|
||||
def _vgpr_masked(src): return v[(src.offset - 256) & 0x7f] if src.offset >= 256 else src
|
||||
def _vgpr_hi(src: int) -> bool: return src >= 256 and ((src - 256) & 0x80) != 0
|
||||
def _vgpr_masked(src: int) -> int: return ((src - 256) & 0x7f) + 256 if src >= 256 else src
|
||||
|
||||
# VOP3 source modifier: apply abs/neg to value
|
||||
def _mod_src(val: int, idx: int, neg: int, abs_: int, is64: bool = False) -> int:
|
||||
@@ -66,10 +40,8 @@ def _mod_src(val: int, idx: int, neg: int, abs_: int, is64: bool = False) -> int
|
||||
# Read source operand with VOP3 modifiers
|
||||
def _read_src(st, inst, src, idx: int, lane: int, neg: int, abs_: int, opsel: int) -> int:
|
||||
if src is None: return 0
|
||||
src_off = src.offset
|
||||
src_bits = inst.canonical_op_bits[f's{idx}']
|
||||
literal, is_src_64, is_src_16 = inst._literal, src_bits == 64, src_bits == 16
|
||||
if is_src_64: return _mod_src(st.rsrc64(src, lane, literal), idx, neg, abs_, is64=True)
|
||||
literal, regs, is_src_16 = inst._literal, inst.src_regs(idx), inst.is_src_16(idx)
|
||||
if regs == 2: return _mod_src(st.rsrc64(src, lane, literal), idx, neg, abs_, is64=True)
|
||||
if isinstance(inst, VOP3P):
|
||||
opsel_hi = inst.opsel_hi | (inst.opsel_hi2 << 2)
|
||||
if 'FMA_MIX' in inst.op_name:
|
||||
@@ -83,13 +55,13 @@ def _read_src(st, inst, src, idx: int, lane: int, neg: int, abs_: int, opsel: in
|
||||
lo = _src16(raw, opsel & (1 << idx)) ^ (0x8000 if neg & (1 << idx) else 0)
|
||||
return (hi << 16) | lo
|
||||
if is_src_16 and isinstance(inst, VOP3):
|
||||
raw = st.rsrc_f16(src, lane, literal) if 128 <= src_off < 255 else st.rsrc(src, lane, literal)
|
||||
raw = st.rsrc_f16(src, lane, literal) if 128 <= src < 255 else st.rsrc(src, lane, literal)
|
||||
val = _src16(raw, bool(opsel & (1 << idx)))
|
||||
if abs_ & (1 << idx): val &= 0x7fff
|
||||
if neg & (1 << idx): val ^= 0x8000
|
||||
return val
|
||||
if is_src_16 and isinstance(inst, (VOP1, VOP2, VOPC)):
|
||||
if src_off >= 256: return _src16(_mod_src(st.rsrc(_vgpr_masked(src), lane, literal), idx, neg, abs_), _vgpr_hi(src))
|
||||
if src >= 256: return _src16(_mod_src(st.rsrc(_vgpr_masked(src), lane, literal), idx, neg, abs_), _vgpr_hi(src))
|
||||
return _mod_src(st.rsrc_f16(src, lane, literal), idx, neg, abs_) & 0xffff
|
||||
return _mod_src(st.rsrc(src, lane, literal), idx, neg, abs_)
|
||||
|
||||
@@ -101,12 +73,11 @@ def _op_ndwords(name: str) -> int:
|
||||
return 1
|
||||
|
||||
# Helper: build multi-dword int from consecutive VGPRs
|
||||
def _vgpr_read(V: VGPRLane, reg, ndwords: int) -> int:
|
||||
return sum(V[reg + i] << (32 * i) for i in range(ndwords))
|
||||
def _vgpr_read(V: list, base: int, ndwords: int) -> int: return sum(V[base + i] << (32 * i) for i in range(ndwords))
|
||||
|
||||
# Helper: write multi-dword value to consecutive VGPRs
|
||||
def _vgpr_write(V: VGPRLane, reg, val: int, ndwords: int):
|
||||
for i in range(ndwords): V[reg + i] = (val >> (32 * i)) & MASK32
|
||||
def _vgpr_write(V: list, base: int, val: int, ndwords: int):
|
||||
for i in range(ndwords): V[base + i] = (val >> (32 * i)) & MASK32
|
||||
|
||||
# Memory access
|
||||
_valid_mem_ranges: list[tuple[int, int]] = []
|
||||
@@ -158,19 +129,19 @@ SMEM_DST_COUNT = {SMEMOp.S_LOAD_B32: 1, SMEMOp.S_LOAD_B64: 2, SMEMOp.S_LOAD_B128
|
||||
|
||||
# VOPD op -> VOP3 op mapping (VOPD is dual-issue of VOP1/VOP2 ops, use VOP3 enums for pseudocode lookup)
|
||||
_VOPD_TO_VOP = {
|
||||
VOPDOp.V_DUAL_FMAC_F32: VOP3Op.V_FMAC_F32_E64, VOPDOp.V_DUAL_FMAAK_F32: VOP2Op.V_FMAAK_F32_E32, VOPDOp.V_DUAL_FMAMK_F32: VOP2Op.V_FMAMK_F32_E32,
|
||||
VOPDOp.V_DUAL_MUL_F32: VOP3Op.V_MUL_F32_E64, VOPDOp.V_DUAL_ADD_F32: VOP3Op.V_ADD_F32_E64, VOPDOp.V_DUAL_SUB_F32: VOP3Op.V_SUB_F32_E64,
|
||||
VOPDOp.V_DUAL_SUBREV_F32: VOP3Op.V_SUBREV_F32_E64, VOPDOp.V_DUAL_MUL_DX9_ZERO_F32: VOP3Op.V_MUL_DX9_ZERO_F32_E64,
|
||||
VOPDOp.V_DUAL_MOV_B32: VOP3Op.V_MOV_B32_E64, VOPDOp.V_DUAL_CNDMASK_B32: VOP3Op.V_CNDMASK_B32_E64,
|
||||
VOPDOp.V_DUAL_MAX_F32: VOP3Op.V_MAX_F32_E64, VOPDOp.V_DUAL_MIN_F32: VOP3Op.V_MIN_F32_E64,
|
||||
VOPDOp.V_DUAL_ADD_NC_U32: VOP3Op.V_ADD_NC_U32_E64, VOPDOp.V_DUAL_LSHLREV_B32: VOP3Op.V_LSHLREV_B32_E64, VOPDOp.V_DUAL_AND_B32: VOP3Op.V_AND_B32_E64,
|
||||
VOPDOp.V_DUAL_FMAC_F32: VOP3Op.V_FMAC_F32, VOPDOp.V_DUAL_FMAAK_F32: VOP2Op.V_FMAAK_F32, VOPDOp.V_DUAL_FMAMK_F32: VOP2Op.V_FMAMK_F32,
|
||||
VOPDOp.V_DUAL_MUL_F32: VOP3Op.V_MUL_F32, VOPDOp.V_DUAL_ADD_F32: VOP3Op.V_ADD_F32, VOPDOp.V_DUAL_SUB_F32: VOP3Op.V_SUB_F32,
|
||||
VOPDOp.V_DUAL_SUBREV_F32: VOP3Op.V_SUBREV_F32, VOPDOp.V_DUAL_MUL_DX9_ZERO_F32: VOP3Op.V_MUL_DX9_ZERO_F32,
|
||||
VOPDOp.V_DUAL_MOV_B32: VOP3Op.V_MOV_B32, VOPDOp.V_DUAL_CNDMASK_B32: VOP3Op.V_CNDMASK_B32,
|
||||
VOPDOp.V_DUAL_MAX_F32: VOP3Op.V_MAX_F32, VOPDOp.V_DUAL_MIN_F32: VOP3Op.V_MIN_F32,
|
||||
VOPDOp.V_DUAL_ADD_NC_U32: VOP3Op.V_ADD_NC_U32, VOPDOp.V_DUAL_LSHLREV_B32: VOP3Op.V_LSHLREV_B32, VOPDOp.V_DUAL_AND_B32: VOP3Op.V_AND_B32,
|
||||
}
|
||||
|
||||
|
||||
class WaveState:
|
||||
__slots__ = ('sgpr', 'vgpr', 'scc', 'pc', '_pend_sgpr', 'lds', 'n_lanes')
|
||||
def __init__(self, lds: LDSMem | None = None, n_lanes: int = WAVE_SIZE):
|
||||
self.sgpr, self.vgpr = SGPRArray(SGPR_COUNT), [VGPRLane(VGPR_COUNT) for _ in range(WAVE_SIZE)]
|
||||
self.sgpr, self.vgpr = [0] * SGPR_COUNT, [[0] * VGPR_COUNT for _ in range(WAVE_SIZE)]
|
||||
self.sgpr[EXEC_LO], self.scc, self.pc, self._pend_sgpr, self.lds, self.n_lanes = 0xffffffff, 0, 0, {}, lds, n_lanes
|
||||
|
||||
@property
|
||||
@@ -182,35 +153,26 @@ class WaveState:
|
||||
@exec_mask.setter
|
||||
def exec_mask(self, v: int): self.sgpr[EXEC_LO], self.sgpr[EXEC_HI] = v & MASK32, (v >> 32) & MASK32
|
||||
|
||||
def rsgpr(self, reg) -> int:
|
||||
if reg == NULL: return 0
|
||||
if reg == SCC: return self.scc
|
||||
return self.sgpr[reg]
|
||||
def wsgpr(self, reg, v: int):
|
||||
if reg != NULL: self.sgpr[reg] = v & MASK32
|
||||
def rsgpr64(self, reg) -> int:
|
||||
off = reg.offset
|
||||
return self.sgpr._data[off] | (self.sgpr._data[off + 1] << 32)
|
||||
def wsgpr64(self, reg, v: int):
|
||||
off = reg.offset
|
||||
self.sgpr._data[off] = v & MASK32; self.sgpr._data[off + 1] = (v >> 32) & MASK32
|
||||
def rsgpr(self, i: int) -> int: return 0 if i == NULL else self.scc if i == SCC else self.sgpr[i] if i < SGPR_COUNT else 0
|
||||
def wsgpr(self, i: int, v: int):
|
||||
if i < SGPR_COUNT and i != NULL: self.sgpr[i] = v & MASK32
|
||||
def rsgpr64(self, i: int) -> int: return self.rsgpr(i) | (self.rsgpr(i+1) << 32)
|
||||
def wsgpr64(self, i: int, v: int): self.wsgpr(i, v & MASK32); self.wsgpr(i+1, (v >> 32) & MASK32)
|
||||
|
||||
def _rsrc_base(self, reg, lane: int, consts, literal: int):
|
||||
off = reg.offset
|
||||
if off < SGPR_COUNT: return self.sgpr._data[off]
|
||||
if off == SCC.offset: return self.scc
|
||||
if off < 255: return consts[off - 128]
|
||||
if off == 255: return literal
|
||||
return self.vgpr[lane]._data[off - 256] if off <= 511 else 0
|
||||
def rsrc(self, reg, lane: int, literal: int = 0) -> int: return self._rsrc_base(reg, lane, _INLINE_CONSTS, literal)
|
||||
def rsrc_f16(self, reg, lane: int, literal: int = 0) -> int: return self._rsrc_base(reg, lane, _INLINE_CONSTS_F16, literal)
|
||||
def rsrc64(self, reg, lane: int, literal: int = 0) -> int:
|
||||
off = reg.offset
|
||||
if 128 <= off < 255: return _INLINE_CONSTS_F64[off - 128]
|
||||
if off == 255: return literal << 32 # 32-bit literal forms upper 32 bits of 64-bit value
|
||||
return self.rsrc(reg, lane, literal) | ((self.rsrc(reg + 1, lane, literal) if off < VCC_LO.offset or 256 <= off <= 511 else 0) << 32)
|
||||
def _rsrc_base(self, v: int, lane: int, consts, literal: int):
|
||||
if v < SGPR_COUNT: return self.sgpr[v]
|
||||
if v == SCC: return self.scc
|
||||
if v < 255: return consts[v - 128]
|
||||
if v == 255: return literal
|
||||
return self.vgpr[lane][v - 256] if v <= 511 else 0
|
||||
def rsrc(self, v: int, lane: int, literal: int = 0) -> int: return self._rsrc_base(v, lane, _INLINE_CONSTS, literal)
|
||||
def rsrc_f16(self, v: int, lane: int, literal: int = 0) -> int: return self._rsrc_base(v, lane, _INLINE_CONSTS_F16, literal)
|
||||
def rsrc64(self, v: int, lane: int, literal: int = 0) -> int:
|
||||
if 128 <= v < 255: return _INLINE_CONSTS_F64[v - 128]
|
||||
if v == 255: return literal # literal is already shifted in from_bytes for 64-bit ops
|
||||
return self.rsrc(v, lane, literal) | ((self.rsrc(v+1, lane, literal) if v < VCC_LO or 256 <= v <= 511 else 0) << 32)
|
||||
|
||||
def pend_sgpr_lane(self, reg, lane: int, val: int):
|
||||
def pend_sgpr_lane(self, reg: int, lane: int, val: int):
|
||||
if reg not in self._pend_sgpr: self._pend_sgpr[reg] = 0
|
||||
if val: self._pend_sgpr[reg] |= (1 << lane)
|
||||
def commit_pends(self):
|
||||
@@ -235,8 +197,8 @@ def exec_scalar(st: WaveState, inst: Inst):
|
||||
|
||||
# SMEM: memory loads
|
||||
if isinstance(inst, SMEM):
|
||||
addr = st.rsgpr64(inst.sbase) + _sext(inst.offset, 21)
|
||||
if inst.soffset != NULL: addr += st.rsrc(inst.soffset, 0, inst._literal)
|
||||
addr = st.rsgpr64(inst.sbase * 2) + _sext(inst.offset, 21)
|
||||
if inst.soffset not in (NULL, 0x7f): addr += st.rsrc(inst.soffset, 0, inst._literal)
|
||||
result = inst._fn(GlobalMem, addr & MASK64)
|
||||
if 'SDATA' in result:
|
||||
sdata = result['SDATA']
|
||||
@@ -244,11 +206,11 @@ def exec_scalar(st: WaveState, inst: Inst):
|
||||
st.pc += inst._words
|
||||
return 0
|
||||
|
||||
# Build context - use canonical_op_bits to determine operand sizes
|
||||
# Build context - use inst methods to determine operand sizes
|
||||
literal = inst._literal
|
||||
s0 = st.rsrc64(ssrc0, 0, literal) if inst.canonical_op_bits['s0'] == 64 else (st.rsrc(ssrc0, 0, literal) if not isinstance(inst, (SOPK, SOPP)) else (st.rsgpr(inst.sdst) if isinstance(inst, SOPK) else 0))
|
||||
s1 = st.rsrc64(inst.ssrc1, 0, literal) if inst.canonical_op_bits['s1'] == 64 else (st.rsrc(inst.ssrc1, 0, literal) if isinstance(inst, (SOP2, SOPC)) else inst.simm16 if isinstance(inst, SOPK) else 0)
|
||||
d0 = st.rsgpr64(sdst) if inst.canonical_op_bits['d'] == 64 and sdst is not None else (st.rsgpr(sdst) if sdst is not None else 0)
|
||||
s0 = st.rsrc64(ssrc0, 0, literal) if inst.is_src_64(0) else (st.rsrc(ssrc0, 0, literal) if not isinstance(inst, (SOPK, SOPP)) else (st.rsgpr(inst.sdst) if isinstance(inst, SOPK) else 0))
|
||||
s1 = st.rsrc64(inst.ssrc1, 0, literal) if inst.is_src_64(1) else (st.rsrc(inst.ssrc1, 0, literal) if isinstance(inst, (SOP2, SOPC)) else inst.simm16 if isinstance(inst, SOPK) else 0)
|
||||
d0 = st.rsgpr64(sdst) if inst.dst_regs() == 2 and sdst is not None else (st.rsgpr(sdst) if sdst is not None else 0)
|
||||
literal = inst.simm16 if isinstance(inst, (SOPK, SOPP)) else inst._literal
|
||||
|
||||
# Call compiled function with int parameters
|
||||
@@ -256,7 +218,7 @@ def exec_scalar(st: WaveState, inst: Inst):
|
||||
|
||||
# Apply results (already int values)
|
||||
if sdst is not None and 'D0' in result:
|
||||
(st.wsgpr64 if inst.canonical_op_bits['d'] == 64 else st.wsgpr)(sdst, result['D0'])
|
||||
(st.wsgpr64 if inst.dst_regs() == 2 else st.wsgpr)(sdst, result['D0'])
|
||||
if 'SCC' in result: st.scc = result['SCC'] & 1
|
||||
if 'EXEC' in result: st.exec_mask = result['EXEC']
|
||||
if 'PC' in result:
|
||||
@@ -272,25 +234,24 @@ def exec_scalar(st: WaveState, inst: Inst):
|
||||
# VECTOR INSTRUCTIONS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def exec_vopd(st: WaveState, inst, V: VGPRLane, lane: int) -> None:
|
||||
def exec_vopd(st: WaveState, inst, V: list, lane: int) -> None:
|
||||
"""VOPD: dual-issue, execute two ops simultaneously (read all inputs before writes)."""
|
||||
literal, vdstx = inst._literal, inst.vdstx
|
||||
vdsty = v[(inst.vdsty << 1) | ((inst.vdstx.offset & 1) ^ 1)] # vdsty is raw int from VDSTYField.decode
|
||||
literal, vdstx, vdsty = inst._literal, inst.vdstx, (inst.vdsty << 1) | ((inst.vdstx & 1) ^ 1)
|
||||
sx0, sx1, dx, sy0, sy1, dy = st.rsrc(inst.srcx0, lane, literal), V[inst.vsrcx1], V[vdstx], st.rsrc(inst.srcy0, lane, literal), V[inst.vsrcy1], V[vdsty]
|
||||
V[vdstx] = inst._fnx(sx0, sx1, 0, dx, st.scc, st.vcc, lane, st.exec_mask, literal, None)['D0']
|
||||
V[vdsty] = inst._fny(sy0, sy1, 0, dy, st.scc, st.vcc, lane, st.exec_mask, literal, None)['D0']
|
||||
|
||||
def exec_flat(st: WaveState, inst, V: VGPRLane, lane: int) -> None:
|
||||
def exec_flat(st: WaveState, inst, V: list, lane: int) -> None:
|
||||
"""FLAT/GLOBAL/SCRATCH memory ops."""
|
||||
ndwords = _op_ndwords(inst.op_name)
|
||||
addr = V[inst.addr] | (V[inst.addr + 1] << 32)
|
||||
ADDR = (st.rsgpr64(inst.saddr) + V[inst.addr] + _sext(inst.offset, 13)) & MASK64 if inst.saddr != NULL else (addr + _sext(inst.offset, 13)) & MASK64
|
||||
ADDR = (st.rsgpr64(inst.saddr) + V[inst.addr] + _sext(inst.offset, 13)) & MASK64 if inst.saddr not in (NULL, 0x7f) else (addr + _sext(inst.offset, 13)) & MASK64
|
||||
vdata_src = inst.vdst if 'LOAD' in inst.op_name else inst.data
|
||||
result = inst._fn(GlobalMem, ADDR, _vgpr_read(V, vdata_src, ndwords), V[inst.vdst])
|
||||
if 'VDATA' in result: _vgpr_write(V, inst.vdst, result['VDATA'], ndwords)
|
||||
if 'RETURN_DATA' in result: _vgpr_write(V, inst.vdst, result['RETURN_DATA'], ndwords)
|
||||
|
||||
def exec_ds(st: WaveState, inst, V: VGPRLane, lane: int) -> None:
|
||||
def exec_ds(st: WaveState, inst, V: list, lane: int) -> None:
|
||||
"""DS (LDS) memory ops."""
|
||||
ndwords = _op_ndwords(inst.op_name)
|
||||
data0, data1 = _vgpr_read(V, inst.data0, ndwords), _vgpr_read(V, inst.data1, ndwords) if inst.data1 is not None else 0
|
||||
@@ -298,59 +259,52 @@ def exec_ds(st: WaveState, inst, V: VGPRLane, lane: int) -> None:
|
||||
if 'RETURN_DATA' in result and ('_RTN' in inst.op_name or '_LOAD' in inst.op_name):
|
||||
_vgpr_write(V, inst.vdst, result['RETURN_DATA'], ndwords * 2 if '_2ADDR_' in inst.op_name else ndwords)
|
||||
|
||||
def exec_vop(st: WaveState, inst: Inst, V: VGPRLane, lane: int) -> None:
|
||||
def exec_vop(st: WaveState, inst: Inst, V: list, lane: int) -> None:
|
||||
"""VOP1/VOP2/VOP3/VOP3SD/VOP3P/VOPC: standard ALU ops."""
|
||||
is_dst_16 = inst.canonical_op_bits['d'] == 16
|
||||
if isinstance(inst, VOP3P):
|
||||
src0, src1, src2, vdst, dst_hi = inst.src0, inst.src1, inst.src2, inst.vdst, False
|
||||
neg, abs_, opsel = inst.neg, 0, inst.opsel
|
||||
elif isinstance(inst, VOP1):
|
||||
src0, src1, src2, vdst = inst.src0, None, None, inst.vdst
|
||||
neg, abs_, opsel, dst_hi = 0, 0, 0, (inst.vdst.offset & 0x80) != 0 and is_dst_16
|
||||
if is_dst_16: vdst = v[inst.vdst.offset & 0x7f]
|
||||
src0, src1, src2, vdst = inst.src0, None, None, inst.vdst & 0x7f if inst.is_dst_16() else inst.vdst
|
||||
neg, abs_, opsel, dst_hi = 0, 0, 0, (inst.vdst & 0x80) != 0 and inst.is_dst_16()
|
||||
elif isinstance(inst, VOP2):
|
||||
src0, src1, src2, vdst = inst.src0, inst.vsrc1, None, inst.vdst
|
||||
neg, abs_, opsel, dst_hi = 0, 0, 0, (inst.vdst.offset & 0x80) != 0 and is_dst_16
|
||||
if is_dst_16: vdst = v[inst.vdst.offset & 0x7f]
|
||||
src0, src1, src2, vdst = inst.src0, inst.vsrc1 + 256, None, inst.vdst & 0x7f if inst.is_dst_16() else inst.vdst
|
||||
neg, abs_, opsel, dst_hi = 0, 0, 0, (inst.vdst & 0x80) != 0 and inst.is_dst_16()
|
||||
elif isinstance(inst, (VOP3, VOP3SD)):
|
||||
src0, src1, src2, vdst = inst.src0, inst.src1, (None if isinstance(inst, VOP3) and inst.op.value < 256 else inst.src2), inst.vdst
|
||||
neg, abs_, opsel, dst_hi = (inst.neg, inst.abs, inst.opsel, False) if isinstance(inst, VOP3) else (0, 0, 0, False)
|
||||
elif isinstance(inst, VOPC):
|
||||
src0, src1, src2, vdst, neg, abs_, opsel, dst_hi = inst.src0, inst.vsrc1, None, VCC_LO, 0, 0, 0, False
|
||||
src0, src1, src2, vdst, neg, abs_, opsel, dst_hi = inst.src0, inst.vsrc1 + 256, None, VCC_LO, 0, 0, 0, False
|
||||
else:
|
||||
raise NotImplementedError(f"exec_vop: unhandled instruction type {type(inst).__name__}")
|
||||
|
||||
s0 = _read_src(st, inst, src0, 0, lane, neg, abs_, opsel)
|
||||
s1 = _read_src(st, inst, src1, 1, lane, neg, abs_, opsel)
|
||||
s2 = _read_src(st, inst, src2, 2, lane, neg, abs_, opsel)
|
||||
if isinstance(inst, VOP2) and is_dst_16: d0 = _src16(V[vdst], dst_hi)
|
||||
elif inst.canonical_op_bits['d'] == 64: d0 = V[vdst] | (V[vdst + 1] << 32)
|
||||
if isinstance(inst, VOP2) and inst.is_16bit(): d0 = _src16(V[vdst], dst_hi)
|
||||
elif inst.dst_regs() == 2: d0 = V[vdst] | (V[vdst + 1] << 32)
|
||||
else: d0 = V[vdst]
|
||||
|
||||
if isinstance(inst, VOP3SD) and 'CO_CI' in inst.op_name: vcc_for_fn = st.rsgpr64(inst.src2)
|
||||
elif isinstance(inst, VOP3) and inst.op in (VOP3Op.V_CNDMASK_B32_E64, VOP3Op.V_CNDMASK_B16) and src2 is not None and src2.offset < 256: vcc_for_fn = st.rsgpr64(src2)
|
||||
elif isinstance(inst, VOP3) and inst.op in (VOP3Op.V_CNDMASK_B32, VOP3Op.V_CNDMASK_B16) and src2 is not None and src2 < 256: vcc_for_fn = st.rsgpr64(src2)
|
||||
else: vcc_for_fn = st.vcc
|
||||
src0_off = src0.offset if src0 is not None else 0
|
||||
src0_idx = (src0_off - 256) if src0_off >= 256 else src0_off
|
||||
vdst_off = vdst.offset
|
||||
src0_idx = (src0 - 256) if src0 is not None and src0 >= 256 else (src0 if src0 is not None else 0)
|
||||
extra_kwargs = {'opsel': opsel, 'opsel_hi': inst.opsel_hi | (inst.opsel_hi2 << 2)} if isinstance(inst, VOP3P) and 'FMA_MIX' in inst.op_name else {}
|
||||
result = inst._fn(s0, s1, s2, d0, st.scc, vcc_for_fn, lane, st.exec_mask, inst._literal, st.vgpr, src0_idx, vdst_off, **extra_kwargs)
|
||||
result = inst._fn(s0, s1, s2, d0, st.scc, vcc_for_fn, lane, st.exec_mask, inst._literal, st.vgpr, src0_idx, vdst, **extra_kwargs)
|
||||
|
||||
# Check if this is a VOPC instruction (either standalone VOPC or VOP3 with VOPC opcode)
|
||||
is_vopc = isinstance(inst.op, VOPCOp) or (isinstance(inst, VOP3) and inst.op.value < 256)
|
||||
if 'VCC' in result:
|
||||
if isinstance(inst, VOP3SD): st.pend_sgpr_lane(inst.sdst, lane, (result['VCC'] >> lane) & 1)
|
||||
elif isinstance(inst, VOP2) and 'CO_CI' in inst.op_name: st.pend_sgpr_lane(VCC_LO, lane, (result['VCC'] >> lane) & 1)
|
||||
elif is_vopc: st.pend_sgpr_lane(vdst, lane, (result['VCC'] >> lane) & 1) # vdst is VCC_LO for VOPC
|
||||
else: st.pend_sgpr_lane(VCC_LO, lane, (result['VCC'] >> lane) & 1)
|
||||
else: st.pend_sgpr_lane(VCC_LO if isinstance(inst, VOP2) and 'CO_CI' in inst.op_name else vdst, lane, (result['VCC'] >> lane) & 1)
|
||||
if 'EXEC' in result:
|
||||
st.pend_sgpr_lane(EXEC_LO, lane, (result['EXEC'] >> lane) & 1)
|
||||
elif is_vopc:
|
||||
st.pend_sgpr_lane(vdst, lane, (result['D0'] >> lane) & 1)
|
||||
if not is_vopc:
|
||||
d0_val = result['D0']
|
||||
if inst.canonical_op_bits['d'] == 64: V[vdst], V[vdst + 1] = d0_val & MASK32, (d0_val >> 32) & MASK32
|
||||
elif not isinstance(inst, VOP3P) and is_dst_16: V[vdst] = _dst16(V[vdst], d0_val, bool(opsel & 8) if isinstance(inst, VOP3) else dst_hi)
|
||||
if inst.dst_regs() == 2: V[vdst], V[vdst + 1] = d0_val & MASK32, (d0_val >> 32) & MASK32
|
||||
elif not isinstance(inst, VOP3P) and inst.is_dst_16(): V[vdst] = _dst16(V[vdst], d0_val, bool(opsel & 8) if isinstance(inst, VOP3) else dst_hi)
|
||||
else: V[vdst] = d0_val & MASK32
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -359,7 +313,7 @@ def exec_vop(st: WaveState, inst: Inst, V: VGPRLane, lane: int) -> None:
|
||||
|
||||
def exec_wmma(st: WaveState, inst, op: VOP3POp) -> None:
|
||||
"""Execute WMMA instruction - 16x16x16 matrix multiply across the wave."""
|
||||
src0, src1, src2, vdst = inst.src0.offset, inst.src1.offset, inst.src2.offset, inst.vdst.offset
|
||||
src0, src1, src2, vdst = inst.src0, inst.src1, inst.src2, inst.vdst
|
||||
# Read 16x16 f16 matrix from 16 lanes × 8 VGPRs (2 f16 per VGPR)
|
||||
def read_f16_mat(src):
|
||||
return [f for l in range(16) for r in range(8) for v in [st.vgpr[l][src-256+r] if src >= 256 else st.rsgpr(src+r)] for f in [_f16(v&0xffff), _f16((v>>16)&0xffff)]]
|
||||
@@ -371,9 +325,297 @@ def exec_wmma(st: WaveState, inst, op: VOP3POp) -> None:
|
||||
# Write result - f16 packed or f32
|
||||
if op == VOP3POp.V_WMMA_F16_16X16X16_F16:
|
||||
for i in range(0, 256, 2):
|
||||
st.vgpr[(i//2) % 32][vdst - 256 + (i//2)//32] = ((_i16(mat_d[i+1]) & 0xffff) << 16) | (_i16(mat_d[i]) & 0xffff)
|
||||
st.vgpr[(i//2) % 32][vdst + (i//2)//32] = ((_i16(mat_d[i+1]) & 0xffff) << 16) | (_i16(mat_d[i]) & 0xffff)
|
||||
else:
|
||||
for i in range(256): st.vgpr[i % 32][vdst - 256 + i//32] = _i32(mat_d[i])
|
||||
for i in range(256): st.vgpr[i % 32][vdst + i//32] = _i32(mat_d[i])
|
||||
|
||||
# SQTT TRACING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
WAVESTART_TO_INST_CYCLES = 32
|
||||
SNOP_EXTRA_DELAY_MIN, SNOP_EXTRA_DELAY_MAX = 11, 22 # s_nop(11-22) has +4 penalty
|
||||
SNOP_EXTRA_DELAY_CYCLES = 4
|
||||
|
||||
from extra.assembly.amd.sqtt import WAVESTART, WAVEEND, IMMEDIATE, VALUINST, ALUEXEC, AluSrc
|
||||
|
||||
def _get_src_vgprs(inst: Inst) -> list[int]:
|
||||
if isinstance(inst, VOP1): return [inst.src0 - 256] if inst.src0 >= 256 else []
|
||||
if isinstance(inst, VOP2): return ([inst.src0 - 256] if inst.src0 >= 256 else []) + [inst.vsrc1]
|
||||
if isinstance(inst, VOP3): return [s - 256 for s in [inst.src0, inst.src1, getattr(inst, 'src2', None)] if s is not None and s >= 256]
|
||||
return []
|
||||
|
||||
class SQTTState:
|
||||
"""SQTT tracing with cycle-accurate RDNA3 VALU pipeline model.
|
||||
|
||||
NOTE: This is a hardware-plausible model derived from observed SQTT timing patterns.
|
||||
The model should be verified by tests against real hardware traces, not by fitting
|
||||
formulas to expected outputs. If tests fail, the model needs to be understood and
|
||||
fixed, not hacked with magic constants.
|
||||
|
||||
Physical model:
|
||||
- alu[4]: 4-stage ALU pipeline, each slot holds dest_vgpr or None
|
||||
- in_flight: up to 12 in-flight instructions (issued but not yet completed)
|
||||
- issue_queue: instructions waiting to enter ALU (sources not ready)
|
||||
- fwd_slots: 4 forwarding slots, reserved at issue, freed when consumer forwards
|
||||
- completed: vgprs with results ready (exited ALU)
|
||||
|
||||
Forwarding model (4 slots):
|
||||
- Slot reserved at ISSUE time if available (len(fwd_slots) < 4)
|
||||
- Slot freed when a consumer uses the result for forwarding
|
||||
- Consumer can forward if: has a slot AND producer is completed
|
||||
- If no slot at issue, instruction uses regfile path (+4 cycle penalty)
|
||||
"""
|
||||
def __init__(self, wave_id: int = 0, simd: int = 0, cu: int = 0):
|
||||
self.wave_id, self.simd, self.cu = wave_id, simd, cu
|
||||
self.cycle = 0
|
||||
self.packets = []
|
||||
|
||||
# 4-stage ALU pipeline: each slot holds dest_vgpr or None
|
||||
self.alu = [None, None, None, None]
|
||||
|
||||
# In-flight instructions: max 12 at a time, each is (dest_vgpr, srcs, has_fwd_slot)
|
||||
self.in_flight: list[tuple[int, list[int], bool]] = []
|
||||
|
||||
# Issue queue: list of (dest_vgpr, srcs, ready_at, has_fwd_slot, was_warm) waiting for deps
|
||||
# ready_at: cycle when this instruction can enter ALU (0 = no restriction)
|
||||
# has_fwd_slot: True if this instruction reserved a forwarding slot at issue time
|
||||
# was_warm: True if forwarding path was warm when this instruction was issued
|
||||
self.issue_queue: list[tuple[int, list[int], int, bool, bool]] = []
|
||||
|
||||
# 4 forwarding slots: consumer adds producer at issue, freed when consumer forwards
|
||||
self.fwd_slots: list[int] = [] # producer vgprs reserved for forwarding
|
||||
|
||||
# VGPRs that had a dependent try to add them to fwd_slots (successful or not)
|
||||
self.had_dependent: set[int] = set()
|
||||
|
||||
# VGPRs that were issued after forwarding chain broke (can't forward)
|
||||
self.fwd_chain_broken: set[int] = set()
|
||||
|
||||
# Set of completed vgprs (results ready, exited ALU)
|
||||
self.completed: set[int] = set()
|
||||
|
||||
# Cold start: first forwarding use has +1 cycle penalty
|
||||
self.forward_warm = False
|
||||
self.cold_used = False # True if cold start penalty was applied
|
||||
|
||||
def emit(self, pkt_class, **kwargs):
|
||||
self.packets.append(pkt_class(_time=self.cycle, **kwargs))
|
||||
|
||||
def _fmt_alu(self) -> str:
|
||||
# Fixed width: each slot 3 chars, total ALU[xxx,xxx,xxx,xxx] = 20 chars
|
||||
slots = [f'v{v}' if v is not None else '-' for v in self.alu]
|
||||
return 'ALU[' + ','.join(f'{s:>3}' for s in slots) + ']'
|
||||
|
||||
def _fmt_fwd(self) -> str:
|
||||
items = [f'v{v}' for v in self.fwd_slots]
|
||||
content = 'FWD[' + ','.join(items) + ']' if items else 'FWD[]'
|
||||
padded = f'{content:<24}'
|
||||
return colored(padded, 'yellow') if items else padded
|
||||
|
||||
def _fmt_iq(self) -> str:
|
||||
def fmt_item(d, r, fwd):
|
||||
s = f'v{d}'
|
||||
if r != 0: s += f'@{abs(r)}'
|
||||
if not fwd: s += 'R'
|
||||
return s
|
||||
items = [fmt_item(d, r, fwd) for d, _, r, fwd, _ in self.issue_queue]
|
||||
return 'IQ[' + ','.join(items) + ']' if items else 'IQ[]'
|
||||
|
||||
def _debug_line(self, events: list[str] | None = None):
|
||||
if DEBUG < 3: return
|
||||
# Skip empty cycles (nothing in ALU, no events, no IQ)
|
||||
has_alu = any(s is not None for s in self.alu)
|
||||
if not has_alu and not events and not self.issue_queue: return
|
||||
cycle = colored(f'C{self.cycle:>3}:', 'cyan')
|
||||
alu = self._fmt_alu()
|
||||
fwd = self._fmt_fwd()
|
||||
iq = f'{self._fmt_iq():<28}'
|
||||
ev_str = ' '.join(events) if events else ''
|
||||
ev_padded = f'{ev_str:<20}' if ev_str else ' ' * 20
|
||||
print(f"{cycle} {alu} {fwd} {iq} {ev_padded}")
|
||||
|
||||
def _can_issue(self) -> bool:
|
||||
return len(self.in_flight) < 12
|
||||
|
||||
def _has_pending_write(self, vgpr: int) -> bool:
|
||||
"""Check if there's a pending write to this VGPR (in ALU, in-flight, or issue queue)."""
|
||||
if any(slot == vgpr for slot in self.alu if slot is not None): return True
|
||||
if any(d == vgpr for d, _, _ in self.in_flight): return True
|
||||
if any(d == vgpr for d, _, _, _, _ in self.issue_queue): return True
|
||||
return False
|
||||
|
||||
def _all_srcs_ready(self, srcs: list[int]) -> bool:
|
||||
"""Returns True if all sources are ready (completed or no pending write)."""
|
||||
for src in srcs:
|
||||
if src in self.completed: continue
|
||||
if not self._has_pending_write(src): continue # initial value
|
||||
return False
|
||||
return True
|
||||
|
||||
def tick(self):
|
||||
self.cycle += 1
|
||||
if self.cycle > 10000: raise RuntimeError("cycle limit exceeded")
|
||||
events = []
|
||||
|
||||
# 1. ALU[3] exits - capture but don't add to completed yet
|
||||
exiting = self.alu[3]
|
||||
if exiting is not None:
|
||||
self.emit(ALUEXEC, src=AluSrc.VALU)
|
||||
events.append(colored(f"EXEC v{exiting}", 'red'))
|
||||
|
||||
# 2. Slide ALU pipeline
|
||||
self.alu[3] = self.alu[2]
|
||||
self.alu[2] = self.alu[1]
|
||||
self.alu[1] = self.alu[0]
|
||||
self.alu[0] = None
|
||||
|
||||
# 3. Try to promote from issue_queue to ALU[0] (before adding exiting to completed)
|
||||
if self.alu[0] is None and self.issue_queue:
|
||||
for i, (dest, srcs, ready_at, has_fwd_slot, was_warm) in enumerate(self.issue_queue):
|
||||
# Check if instruction has a minimum ready cycle
|
||||
if ready_at > 0 and self.cycle < ready_at:
|
||||
continue
|
||||
# Check if sources are ready
|
||||
ready = self._all_srcs_ready(srcs)
|
||||
has_deps = len(srcs) > 0
|
||||
if not ready:
|
||||
continue
|
||||
# Cold start penalty: first dependent instruction has +1 cycle delay (delta=6 vs delta=5)
|
||||
# Only applies if forwarding path wasn't warm when this instruction was issued
|
||||
if has_deps and not was_warm and not self.cold_used:
|
||||
self.cold_used = True
|
||||
self.issue_queue[i] = (dest, srcs, self.cycle + 1, has_fwd_slot, was_warm)
|
||||
continue
|
||||
# Forwarding: consumer can forward if:
|
||||
# 1. Not in fwd_chain_broken (chain must be intact), AND
|
||||
# 2. Producer has a slot (source is in fwd_slots), AND
|
||||
# 3. Either activated by dependent OR successfully added producer at issue
|
||||
# Note: if issued cold with no slot, activation only counts if the activator also has a dependent
|
||||
chain_intact = dest not in self.fwd_chain_broken
|
||||
producer_has_slot = has_deps and any(src in self.fwd_slots for src in srcs)
|
||||
# Check activation validity
|
||||
if dest in self.had_dependent:
|
||||
if was_warm or has_fwd_slot:
|
||||
activated_by_dependent = True
|
||||
else:
|
||||
# Cold + no slot: activation only counts if activator itself has a dependent
|
||||
# This handles the chain_6 vs chain_7 difference (chain_7 has v6 which activates v5)
|
||||
activated_by_dependent = (dest + 1) in self.had_dependent # activator is dest+1 in a chain
|
||||
else:
|
||||
activated_by_dependent = False
|
||||
can_forward = chain_intact and producer_has_slot and (activated_by_dependent or has_fwd_slot)
|
||||
# Regfile path: has dependencies but can't forward
|
||||
must_use_regfile = has_deps and not can_forward
|
||||
# Regfile penalty: add +4 cycles latency (only apply once)
|
||||
if must_use_regfile and ready_at == 0:
|
||||
self.issue_queue[i] = (dest, srcs, self.cycle + 4, has_fwd_slot, was_warm)
|
||||
continue
|
||||
# Enter ALU
|
||||
self.alu[0] = dest
|
||||
self.issue_queue.pop(i)
|
||||
# Free producer's forwarding slot when consumer dispatches (regardless of fwd/rf)
|
||||
for src in srcs:
|
||||
if src in self.fwd_slots:
|
||||
self.fwd_slots.remove(src)
|
||||
break
|
||||
events.append(colored(f"v{dest}->ALU" + ("(fwd)" if can_forward else "(rf)" if must_use_regfile else ""), 'green'))
|
||||
break
|
||||
|
||||
# 4. Now add exiting instruction to completed (after promotion decision)
|
||||
if exiting is not None:
|
||||
self.completed.add(exiting)
|
||||
# Remove from in_flight - any VALU completing warms up the forward path
|
||||
for idx, (d, _, _) in enumerate(self.in_flight):
|
||||
if d == exiting:
|
||||
self.forward_warm = True
|
||||
self.in_flight.pop(idx)
|
||||
break
|
||||
|
||||
self._debug_line(events)
|
||||
|
||||
def _pipeline_empty(self) -> bool:
|
||||
if any(s is not None for s in self.alu): return False
|
||||
if self.issue_queue: return False
|
||||
if self.in_flight: return False
|
||||
return True
|
||||
|
||||
def process_instruction(self, inst: Inst):
|
||||
if isinstance(inst, SOPP) and inst.op == SOPPOp.S_DELAY_ALU:
|
||||
# TODO: implement s_delay_alu properly
|
||||
return
|
||||
|
||||
elif isinstance(inst, SOPP) and inst.op == SOPPOp.S_NOP:
|
||||
# s_nop(N) delays N+1 cycles, plus extra penalty for s_nop(11-22)
|
||||
cycles = inst.simm16 + 1
|
||||
if SNOP_EXTRA_DELAY_MIN <= inst.simm16 <= SNOP_EXTRA_DELAY_MAX:
|
||||
cycles += SNOP_EXTRA_DELAY_CYCLES
|
||||
if DEBUG >= 3:
|
||||
cycle = colored(f'C{self.cycle:>3}:', 'cyan')
|
||||
# 20 (ALU) + 1 + 24 (FWD) + 1 + 28 (IQ) + 1 + 20 (events) = 95 padding after cycle
|
||||
print(f"{cycle} {' ' * 95} {disasm(inst)}")
|
||||
for _ in range(cycles): self.tick()
|
||||
self.emit(IMMEDIATE, wave=self.wave_id)
|
||||
|
||||
elif isinstance(inst, SOPP) and inst.op == SOPPOp.S_ENDPGM:
|
||||
# Drain pipeline before ending
|
||||
while not self._pipeline_empty(): self.tick()
|
||||
self.emit(WAVEEND, wave=self.wave_id, simd=self.simd, cu_lo=self.cu & 0x7, flag7=self.cu >> 3)
|
||||
|
||||
elif isinstance(inst, (VOP1, VOP2, VOP3)):
|
||||
# Check for issue stall (no free in-flight slots)
|
||||
while not self._can_issue():
|
||||
self.tick()
|
||||
|
||||
# Issue: add to in_flight and issue_queue
|
||||
srcs = _get_src_vgprs(inst)
|
||||
dest = inst.vdst
|
||||
# Clear stale state for this dest (WAW hazard)
|
||||
self.completed.discard(dest)
|
||||
if dest in self.fwd_slots: self.fwd_slots.remove(dest)
|
||||
|
||||
# Consumer adds producer to fwd_slots (if room and has dependency)
|
||||
# If producer is in fwd_chain_broken, or we can't add, the chain breaks for this instruction too
|
||||
has_fwd_slot = False
|
||||
if srcs:
|
||||
producer = srcs[0]
|
||||
self.had_dependent.add(producer) # record that producer has a dependent
|
||||
# Check if producer's forwarding chain is already broken
|
||||
if producer in self.fwd_chain_broken:
|
||||
# Chain is broken, this instruction also can't forward
|
||||
self.fwd_chain_broken.add(dest)
|
||||
elif len(self.fwd_slots) >= 4:
|
||||
# Can't add producer, chain breaks
|
||||
self.fwd_chain_broken.add(dest)
|
||||
else:
|
||||
# Can add producer
|
||||
if producer not in self.fwd_slots:
|
||||
self.fwd_slots.append(producer)
|
||||
has_fwd_slot = len(self.fwd_slots) < 4
|
||||
|
||||
# Record if forwarding path was warm at issue time
|
||||
was_warm = self.forward_warm
|
||||
|
||||
self.in_flight.append((dest, srcs, has_fwd_slot))
|
||||
self.issue_queue.append((dest, srcs, 0, has_fwd_slot, was_warm))
|
||||
self.emit(VALUINST, wave=self.wave_id)
|
||||
|
||||
if DEBUG >= 3:
|
||||
cycle = colored(f'C{self.cycle:>3}:', 'cyan')
|
||||
slot_info = "" if has_fwd_slot else colored(" NO_SLOT", 'red')
|
||||
issue = colored(f'ISSUE v{dest}', 'magenta') + slot_info
|
||||
padding = 95 - ansilen(issue)
|
||||
print(f"{cycle} {issue}{' ' * padding} {disasm(inst)}")
|
||||
|
||||
# One cycle per instruction issued, then try to enter ALU
|
||||
self.tick()
|
||||
return
|
||||
|
||||
# One cycle per instruction issued (for non-VALU)
|
||||
self.tick()
|
||||
|
||||
def emit_wavestart(self):
|
||||
self.emit(WAVESTART, wave=self.wave_id, simd=self.simd, cu_lo=self.cu & 0x7, flag7=self.cu >> 3)
|
||||
for _ in range(WAVESTART_TO_INST_CYCLES): self.tick()
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# PROGRAM DECODE
|
||||
@@ -384,13 +626,12 @@ def dispatch_endpgm(st, inst): return -1
|
||||
def dispatch_barrier(st, inst): st.pc += inst._words; return -2
|
||||
def dispatch_nop(st, inst): st.pc += inst._words; return 0
|
||||
def dispatch_wmma(st, inst): exec_wmma(st, inst, inst.op); st.pc += inst._words; return 0
|
||||
def dispatch_writelane(st, inst): st.vgpr[st.rsrc(inst.src1, 0, inst._literal) & 0x1f][inst.vdst.offset - 256] = st.rsrc(inst.src0, 0, inst._literal) & MASK32; st.pc += inst._words; return 0
|
||||
def dispatch_writelane(st, inst): st.vgpr[st.rsrc(inst.src1, 0, inst._literal) & 0x1f][inst.vdst] = st.rsrc(inst.src0, 0, inst._literal) & MASK32; st.pc += inst._words; return 0
|
||||
def dispatch_readlane(st, inst):
|
||||
src0_off = inst.src0.offset
|
||||
src0_idx = (src0_off - 256) if src0_off >= 256 else src0_off
|
||||
src0_idx = (inst.src0 - 256) if inst.src0 >= 256 else inst.src0
|
||||
s1 = st.rsrc(inst.src1, 0, inst._literal) if getattr(inst, 'src1', None) is not None else 0
|
||||
result = inst._fn(0, s1, 0, 0, st.scc, st.vcc, 0, st.exec_mask, inst._literal, st.vgpr, src0_idx, inst.vdst.offset)
|
||||
st.wsgpr(inst.vdst.offset, result['D0'])
|
||||
result = inst._fn(0, s1, 0, 0, st.scc, st.vcc, 0, st.exec_mask, inst._literal, st.vgpr, src0_idx, inst.vdst)
|
||||
st.wsgpr(inst.vdst, result['D0'])
|
||||
st.pc += inst._words; return 0
|
||||
|
||||
# Per-lane dispatch wrapper: wraps per-lane exec functions into wave-level dispatch
|
||||
@@ -409,7 +650,7 @@ def decode_program(data: bytes) -> dict[int, Inst]:
|
||||
result: dict[int, Inst] = {}
|
||||
i = 0
|
||||
while i < len(data):
|
||||
inst = decode_inst(data[i:])
|
||||
inst = detect_format(data[i:]).from_bytes(data[i:])
|
||||
inst._words = inst.size() // 4
|
||||
|
||||
# Determine dispatch function and pcode function
|
||||
@@ -418,10 +659,10 @@ def decode_program(data: bytes) -> dict[int, Inst]:
|
||||
elif isinstance(inst, SOPP) and inst.op == SOPPOp.S_BARRIER: inst._dispatch = dispatch_barrier
|
||||
elif isinstance(inst, SOPP) and inst.op in (SOPPOp.S_CLAUSE, SOPPOp.S_WAITCNT, SOPPOp.S_WAITCNT_DEPCTR, SOPPOp.S_SENDMSG, SOPPOp.S_SET_INST_PREFETCH_DISTANCE, SOPPOp.S_DELAY_ALU): inst._dispatch = dispatch_nop
|
||||
elif isinstance(inst, (SOP1, SOP2, SOPC, SOPK, SOPP, SMEM)): inst._dispatch = exec_scalar
|
||||
elif isinstance(inst, VOP1) and inst.op == VOP1Op.V_NOP_E32: inst._dispatch = dispatch_nop
|
||||
elif isinstance(inst, VOP1) and inst.op == VOP1Op.V_NOP: inst._dispatch = dispatch_nop
|
||||
elif isinstance(inst, VOP3P) and 'WMMA' in inst.op_name: inst._dispatch = dispatch_wmma
|
||||
elif isinstance(inst, VOP3) and inst.op == VOP3Op.V_WRITELANE_B32: inst._dispatch = dispatch_writelane
|
||||
elif isinstance(inst, (VOP1, VOP3)) and inst.op in (VOP1Op.V_READFIRSTLANE_B32_E32, VOP3Op.V_READFIRSTLANE_B32, VOP3Op.V_READLANE_B32): inst._dispatch = dispatch_readlane
|
||||
elif isinstance(inst, (VOP1, VOP3)) and inst.op in (VOP1Op.V_READFIRSTLANE_B32, VOP3Op.V_READFIRSTLANE_B32, VOP3Op.V_READLANE_B32): inst._dispatch = dispatch_readlane
|
||||
elif isinstance(inst, VOPD): inst._dispatch = dispatch_lane(exec_vopd)
|
||||
elif isinstance(inst, FLAT): inst._dispatch = dispatch_lane(exec_flat)
|
||||
elif isinstance(inst, DS): inst._dispatch = dispatch_lane(exec_ds)
|
||||
@@ -430,11 +671,11 @@ def decode_program(data: bytes) -> dict[int, Inst]:
|
||||
# Compile pcode for instructions that use it (not VOPD which has _fnx/_fny, not special dispatches)
|
||||
# VOPD needs separate functions for X and Y ops
|
||||
if isinstance(inst, VOPD):
|
||||
def _compile_vopd_op(op): return compile_pseudocode(type(op).__name__, op.name, PCODE[op])
|
||||
def _compile_vopd_op(op): return compile_pseudocode(type(op).__name__, op.name, PSEUDOCODE_STRINGS[type(op)][op])
|
||||
inst._fnx, inst._fny = _compile_vopd_op(_VOPD_TO_VOP[inst.opx]), _compile_vopd_op(_VOPD_TO_VOP[inst.opy])
|
||||
elif inst._dispatch not in (dispatch_endpgm, dispatch_barrier, dispatch_nop, dispatch_wmma, dispatch_writelane):
|
||||
assert type(inst.op) != int, f"inst op of {inst} is int"
|
||||
inst._fn = compile_pseudocode(type(inst.op).__name__, inst.op.name, PCODE[inst.op])
|
||||
inst._fn = compile_pseudocode(type(inst.op).__name__, inst.op.name, PSEUDOCODE_STRINGS[type(inst.op)][inst.op])
|
||||
result[i // 4] = inst
|
||||
i += inst._words * 4
|
||||
return result
|
||||
@@ -458,7 +699,7 @@ def exec_workgroup(program: dict[int, Inst], workgroup_id: tuple[int, int, int],
|
||||
n_lanes = min(WAVE_SIZE, total_threads - wave_start)
|
||||
st = WaveState(lds, n_lanes)
|
||||
st.exec_mask = (1 << n_lanes) - 1
|
||||
st.wsgpr64(s[0:1], args_ptr) # s[0:1] = kernel arguments pointer
|
||||
st.wsgpr64(0, args_ptr) # s[0:1] = kernel arguments pointer
|
||||
# COMPUTE_PGM_RSRC2: USER_SGPR_COUNT is where workgroup IDs start, ENABLE_SGPR_WORKGROUP_ID_X/Y/Z control which are passed
|
||||
sgpr_idx = (rsrc2 & hsa.AMD_COMPUTE_PGM_RSRC_TWO_USER_SGPR_COUNT) >> hsa.AMD_COMPUTE_PGM_RSRC_TWO_USER_SGPR_COUNT_SHIFT
|
||||
if rsrc2 & hsa.AMD_COMPUTE_PGM_RSRC_TWO_ENABLE_SGPR_WORKGROUP_ID_X: st.sgpr[sgpr_idx] = workgroup_id[0]; sgpr_idx += 1
|
||||
|
||||
@@ -1,41 +1,6 @@
|
||||
# DSL for RDNA3 pseudocode - makes pseudocode expressions work directly as Python
|
||||
import struct, math, re, functools
|
||||
|
||||
MASK32, MASK64 = 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
# Float/int bit conversion functions
|
||||
_struct_f, _struct_I = struct.Struct("<f"), struct.Struct("<I")
|
||||
_struct_e, _struct_H = struct.Struct("<e"), struct.Struct("<H")
|
||||
_struct_d, _struct_Q = struct.Struct("<d"), struct.Struct("<Q")
|
||||
def _f32(i):
|
||||
i = i & MASK32
|
||||
# RDNA3 default mode: flush f32 denormals to zero (FTZ)
|
||||
# Denormal: exponent=0 (bits 23-30) and mantissa!=0 (bits 0-22)
|
||||
if (i & 0x7f800000) == 0 and (i & 0x007fffff) != 0: return 0.0
|
||||
return _struct_f.unpack(_struct_I.pack(i))[0]
|
||||
def _i32(f):
|
||||
if isinstance(f, int): f = float(f)
|
||||
if math.isnan(f): return 0xffc00000 if math.copysign(1.0, f) < 0 else 0x7fc00000
|
||||
if math.isinf(f): return 0x7f800000 if f > 0 else 0xff800000
|
||||
try:
|
||||
bits = _struct_I.unpack(_struct_f.pack(f))[0]
|
||||
# RDNA3 default mode: flush f32 denormals to zero (FTZ)
|
||||
if (bits & 0x7f800000) == 0 and (bits & 0x007fffff) != 0: return 0x80000000 if bits & 0x80000000 else 0
|
||||
return bits
|
||||
except (OverflowError, struct.error): return 0x7f800000 if f > 0 else 0xff800000
|
||||
def _sext(v, b): return v - (1 << b) if v & (1 << (b - 1)) else v
|
||||
def _f16(i): return _struct_e.unpack(_struct_H.pack(i & 0xffff))[0]
|
||||
def _i16(f):
|
||||
if math.isnan(f): return 0x7e00
|
||||
if math.isinf(f): return 0x7c00 if f > 0 else 0xfc00
|
||||
try: return _struct_H.unpack(_struct_e.pack(f))[0]
|
||||
except (OverflowError, struct.error): return 0x7c00 if f > 0 else 0xfc00
|
||||
def _f64(i): return _struct_d.unpack(_struct_Q.pack(i & MASK64))[0]
|
||||
def _i64(f):
|
||||
if math.isnan(f): return 0x7ff8000000000000
|
||||
if math.isinf(f): return 0x7ff0000000000000 if f > 0 else 0xfff0000000000000
|
||||
try: return _struct_Q.unpack(_struct_d.pack(f))[0]
|
||||
except (OverflowError, struct.error): return 0x7ff0000000000000 if f > 0 else 0xfff0000000000000
|
||||
from extra.assembly.amd.dsl import MASK32, MASK64, _f32, _i32, _sext, _f16, _i16, _f64, _i64
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# INTERNAL HELPERS
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
# Generic PDF text extractor - no external dependencies
|
||||
import re, zlib
|
||||
from tinygrad.helpers import fetch, merge_dicts
|
||||
|
||||
PDF_URLS = {
|
||||
"rdna3": "https://docs.amd.com/api/khub/documents/UVVZM22UN7tMUeiW_4ShTQ/content",
|
||||
"rdna4": "https://docs.amd.com/api/khub/documents/uQpkEvk3pv~kfAb2x~j4uw/content",
|
||||
"cdna": "https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/instruction-set-architectures/amd-instinct-cdna4-instruction-set-architecture.pdf",
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Generic PDF extraction tools
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def extract(url: str) -> list[list[tuple[float, float, str, str]]]:
|
||||
"""Extract positioned text from PDF. Returns list of text elements (x, y, text, font) per page."""
|
||||
data = fetch(url).read_bytes()
|
||||
|
||||
# Parse xref table to locate objects
|
||||
xref: dict[int, int] = {}
|
||||
pos = int(re.search(rb'startxref\s+(\d+)', data).group(1)) + 4
|
||||
while data[pos:pos+7] != b'trailer':
|
||||
while data[pos:pos+1] in b' \r\n': pos += 1
|
||||
line_end = data.find(b'\n', pos)
|
||||
start_obj, count = map(int, data[pos:line_end].split()[:2])
|
||||
pos = line_end + 1
|
||||
for i in range(count):
|
||||
if data[pos+17:pos+18] == b'n' and (off := int(data[pos:pos+10])) > 0: xref[start_obj + i] = off
|
||||
pos += 20
|
||||
|
||||
def get_stream(n: int) -> bytes:
|
||||
obj = data[xref[n]:data.find(b'endobj', xref[n])]
|
||||
raw = obj[obj.find(b'stream\n') + 7:obj.find(b'\nendstream')]
|
||||
return zlib.decompress(raw) if b'/FlateDecode' in obj else raw
|
||||
|
||||
# Find page content streams and extract text
|
||||
pages = []
|
||||
for n in sorted(xref):
|
||||
if b'/Type /Page' not in data[xref[n]:xref[n]+500]: continue
|
||||
if not (m := re.search(rb'/Contents (\d+) 0 R', data[xref[n]:xref[n]+500])): continue
|
||||
stream = get_stream(int(m.group(1))).decode('latin-1')
|
||||
elements, font = [], ''
|
||||
for bt in re.finditer(r'BT(.*?)ET', stream, re.S):
|
||||
x, y = 0.0, 0.0
|
||||
for m in re.finditer(r'(/F[\d.]+) [\d.]+ Tf|([\d.+-]+) ([\d.+-]+) Td|[\d.+-]+ [\d.+-]+ [\d.+-]+ [\d.+-]+ ([\d.+-]+) ([\d.+-]+) Tm|<([0-9A-Fa-f]+)>.*?Tj|\[([^\]]+)\] TJ', bt.group(1)):
|
||||
if m.group(1): font = m.group(1)
|
||||
elif m.group(2): x, y = x + float(m.group(2)), y + float(m.group(3))
|
||||
elif m.group(4): x, y = float(m.group(4)), float(m.group(5))
|
||||
elif m.group(6) and (t := bytes.fromhex(m.group(6)).decode('latin-1')).strip(): elements.append((x, y, t, font))
|
||||
elif m.group(7) and (t := ''.join(bytes.fromhex(h).decode('latin-1') for h in re.findall(r'<([0-9A-Fa-f]+)>', m.group(7)))).strip(): elements.append((x, y, t, font))
|
||||
pages.append(sorted(elements, key=lambda e: (-e[1], e[0])))
|
||||
return pages
|
||||
|
||||
def extract_tables(pages: list[list[tuple[float, float, str, str]]]) -> dict[int, tuple[str, list[list[str]]]]:
|
||||
"""Extract numbered tables from PDF pages. Returns {table_num: (title, rows)} where rows is list of cells per row."""
|
||||
def group_by_y(texts, key=lambda y: round(y)):
|
||||
by_y: dict[int, list[tuple[float, float, str]]] = {}
|
||||
for x, y, t, _ in texts:
|
||||
by_y.setdefault(key(y), []).append((x, y, t))
|
||||
return by_y
|
||||
|
||||
# Find all table headers by merging text on same line
|
||||
table_positions = []
|
||||
for page_idx, texts in enumerate(pages):
|
||||
for items in group_by_y(texts).values():
|
||||
line = ''.join(t for _, t in sorted((x, t) for x, _, t in items))
|
||||
if m := re.search(r'Table (\d+)\. (.+)', line):
|
||||
table_positions.append((int(m.group(1)), m.group(2).strip(), page_idx, items[0][1]))
|
||||
table_positions.sort(key=lambda t: (t[2], -t[3]))
|
||||
|
||||
# For each table, find rows with matching X positions
|
||||
result: dict[int, tuple[str, list[list[str]]]] = {}
|
||||
for num, title, start_page, header_y in table_positions:
|
||||
rows, col_xs = [], None
|
||||
for page_idx in range(start_page, len(pages)):
|
||||
page_texts = [(x, y, t) for x, y, t, _ in pages[page_idx] if 30 < y < 760 and (page_idx > start_page or y < header_y)]
|
||||
for items in sorted(group_by_y([(x, y, t, '') for x, y, t in page_texts], key=lambda y: round(y / 5)).values(), key=lambda items: -items[0][1]):
|
||||
xs = tuple(sorted(round(x) for x, _, _ in items))
|
||||
if col_xs is None:
|
||||
if len(xs) < 2: continue # Skip single-column rows before table starts
|
||||
col_xs = xs
|
||||
elif len(xs) == 1 and xs[0] in col_xs: continue # Skip continuation rows at known column positions
|
||||
elif not any(c in xs for c in col_xs[:2]): break # Row missing first columns = end of table
|
||||
rows.append([t for _, t in sorted((x, t) for x, _, t in items)])
|
||||
else: continue
|
||||
break
|
||||
if rows: result[num] = (title, rows)
|
||||
return result
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# AMD specific extraction
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def extract_enums(tables: dict[int, tuple[str, list[list[str]]]]) -> dict[str, dict[int, str]]:
|
||||
"""Extract all enums from tables. Returns {enum_name: {value: name}}."""
|
||||
enums: dict[str, dict[int, str]] = {}
|
||||
for num, (title, rows) in tables.items():
|
||||
# Opcode enums from "XXX Opcodes" tables
|
||||
if m := re.match(r'(\w+) (?:Y-)?Opcodes', title):
|
||||
fmt_name = 'VOPD' if 'Y-Opcodes' in title else m.group(1)
|
||||
ops: dict[int, str] = {}
|
||||
for row in rows:
|
||||
for i in range(0, len(row) - 1, 2):
|
||||
if row[i].isdigit() and re.match(r'^[A-Z][A-Z0-9_]+$', row[i + 1]):
|
||||
ops[int(row[i])] = row[i + 1]
|
||||
if ops: enums[fmt_name] = ops
|
||||
# BufFmt from "Data Format" tables
|
||||
if 'Data Format' in title:
|
||||
for row in rows:
|
||||
for i in range(0, len(row) - 1, 2):
|
||||
if row[i].isdigit() and re.match(r'^[\dA-Z_]+$', row[i + 1]) and 'INVALID' not in row[i + 1]:
|
||||
enums.setdefault('BufFmt', {})[int(row[i])] = row[i + 1]
|
||||
return enums
|
||||
|
||||
def extract_ins(tables: dict[int, tuple[str, list[list[str]]]]) -> tuple[dict[str, list[tuple[str, int, int]]], dict[str, str]]:
|
||||
"""Extract formats and encodings from 'XXX Fields' tables. Returns (formats, encodings)."""
|
||||
formats: dict[str, list[tuple[str, int, int]]] = {}
|
||||
encodings: dict[str, str] = {}
|
||||
for num, (title, rows) in tables.items():
|
||||
if not (m := re.match(r'(\w+) Fields$', title)): continue
|
||||
fmt_name = m.group(1)
|
||||
fields = []
|
||||
for row in rows:
|
||||
if len(row) < 2: continue
|
||||
if (bits := re.match(r'\[?(\d+):(\d+)\]?$', row[1])) or (bits := re.match(r'\[(\d+)\]$', row[1])):
|
||||
field_name = row[0].lower()
|
||||
hi, lo = int(bits.group(1)), int(bits.group(2)) if bits.lastindex >= 2 else int(bits.group(1))
|
||||
if field_name == 'encoding' and len(row) >= 3:
|
||||
enc_bits = None
|
||||
if "'b" in row[2]: enc_bits = row[2].split("'b")[-1].replace('_', '')
|
||||
elif (enc := re.search(r':\s*([01_]+)', row[2])): enc_bits = enc.group(1).replace('_', '')
|
||||
if enc_bits:
|
||||
# If encoding bits exceed field width, extend field to match (AMD docs sometimes have this)
|
||||
declared_width, actual_width = hi - lo + 1, len(enc_bits)
|
||||
if actual_width > declared_width: lo = hi - actual_width + 1
|
||||
encodings[fmt_name] = enc_bits
|
||||
fields.append((field_name, hi, lo))
|
||||
if fields: formats[fmt_name] = fields
|
||||
return formats, encodings
|
||||
|
||||
def extract_pcode(pages: list[list[tuple[float, float, str, str]]], enums: dict[str, dict[int, str]]) -> dict[tuple[str, int], str]:
|
||||
"""Extract pseudocode for instructions. Returns {(name, opcode): pseudocode}."""
|
||||
# Build lookup from instruction name to opcode
|
||||
name_to_op = {name: op for ops in enums.values() for op, name in ops.items()}
|
||||
|
||||
# First pass: find all instruction headers across all pages
|
||||
all_instructions: list[tuple[int, float, str, int]] = [] # (page_idx, y, name, opcode)
|
||||
for page_idx, page in enumerate(pages):
|
||||
by_y: dict[int, list[tuple[float, str]]] = {}
|
||||
for x, y, t, _ in page:
|
||||
by_y.setdefault(round(y), []).append((x, t))
|
||||
for y, items in sorted(by_y.items(), reverse=True):
|
||||
left = [(x, t) for x, t in items if 55 < x < 65]
|
||||
right = [(x, t) for x, t in items if 535 < x < 550]
|
||||
if left and right and left[0][1] in name_to_op and right[0][1].isdigit():
|
||||
all_instructions.append((page_idx, y, left[0][1], int(right[0][1])))
|
||||
|
||||
# Second pass: extract pseudocode between consecutive instructions
|
||||
pcode: dict[tuple[str, int], str] = {}
|
||||
for i, (page_idx, y, name, opcode) in enumerate(all_instructions):
|
||||
# Get end boundary from next instruction
|
||||
if i + 1 < len(all_instructions):
|
||||
next_page, next_y = all_instructions[i + 1][0], all_instructions[i + 1][1]
|
||||
else:
|
||||
next_page, next_y = page_idx, 0
|
||||
# Collect F6 text from current position to next instruction (pseudocode is at x ≈ 69)
|
||||
lines = []
|
||||
for p in range(page_idx, next_page + 1):
|
||||
start_y = y if p == page_idx else 800
|
||||
end_y = next_y if p == next_page else 0
|
||||
lines.extend((p, y2, t) for x, y2, t, f in pages[p] if f in ('/F6.0', '/F7.0') and end_y < y2 < start_y and 60 < x < 80)
|
||||
if lines:
|
||||
# Sort by page first, then by y descending within each page (higher y = earlier text in PDF)
|
||||
sorted_lines = sorted(lines, key=lambda x: (x[0], -x[1]))
|
||||
# Stop at large Y gaps (>30) - indicates section break (Notes, examples, etc)
|
||||
filtered = [sorted_lines[0]]
|
||||
for j in range(1, len(sorted_lines)):
|
||||
prev_page, prev_y, _ = sorted_lines[j-1]
|
||||
curr_page, curr_y, _ = sorted_lines[j]
|
||||
if curr_page == prev_page and prev_y - curr_y > 30: break
|
||||
if curr_page != prev_page and prev_y > 60 and curr_y < 730: break # examples spilled to next page (not at very top)
|
||||
filtered.append(sorted_lines[j])
|
||||
pcode_lines = [t.replace('Ê', '').strip() for _, _, t in filtered]
|
||||
if pcode_lines: pcode[(name, opcode)] = '\n'.join(pcode_lines)
|
||||
return pcode
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Write autogen files
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def write_enums(enums: dict[str, dict[int, str]], arch: str, path: str):
|
||||
"""Write enum.py file from extracted enums."""
|
||||
lines = ["# autogenerated from AMD ISA PDF by pdf.py - do not edit", "from enum import IntEnum", ""]
|
||||
for name, values in sorted(enums.items()):
|
||||
suffix = "Op" if name not in ('Src', 'BufFmt') else ("Enum" if name == 'Src' else "")
|
||||
prefix = "BUF_FMT_" if name == 'BufFmt' else ""
|
||||
lines.append(f"class {name}{suffix}(IntEnum):")
|
||||
for val, member in sorted(values.items()):
|
||||
lines.append(f" {prefix}{member} = {val}")
|
||||
lines.append("")
|
||||
with open(path, "w") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
def write_ins(formats: dict[str, list[tuple[str, int, int]]], encodings: dict[str, str], enums: dict[str, dict[int, str]], arch: str, path: str):
|
||||
"""Write ins.py file from extracted formats and enums."""
|
||||
# Field types and ordering
|
||||
def field_type(name, fmt):
|
||||
if name == 'op' and fmt in enums: return f'Annotated[BitField, {fmt}Op]'
|
||||
if name in ('opx', 'opy'): return 'Annotated[BitField, VOPDOp]'
|
||||
if name == 'vdsty': return 'VDSTYEnc'
|
||||
if name in ('vdst', 'vsrc1', 'vaddr', 'vdata', 'data', 'data0', 'data1', 'addr', 'vsrc0', 'vsrc2', 'vsrc3'): return 'VGPRField'
|
||||
if name in ('sdst', 'sbase', 'sdata', 'srsrc', 'ssamp'): return 'SGPRField'
|
||||
if name.startswith('ssrc') or name in ('saddr', 'soffset'): return 'SSrc'
|
||||
if name in ('src0', 'srcx0', 'srcy0') or name.startswith('src') and name[3:].isdigit(): return 'Src'
|
||||
if name.startswith('simm'): return 'SImm'
|
||||
if name == 'offset' or name.startswith('imm'): return 'Imm'
|
||||
return None
|
||||
field_priority = ['encoding', 'op', 'opx', 'opy', 'vdst', 'vdstx', 'vdsty', 'sdst', 'vdata', 'sdata', 'addr', 'vaddr', 'data', 'data0', 'data1',
|
||||
'src0', 'srcx0', 'srcy0', 'vsrc0', 'ssrc0', 'src1', 'vsrc1', 'vsrcx1', 'vsrcy1', 'ssrc1', 'src2', 'vsrc2', 'src3', 'vsrc3',
|
||||
'saddr', 'sbase', 'srsrc', 'ssamp', 'soffset', 'offset', 'simm16', 'en', 'target', 'attr', 'attr_chan',
|
||||
'omod', 'neg', 'neg_hi', 'abs', 'clmp', 'opsel', 'opsel_hi', 'waitexp', 'wait_va',
|
||||
'dmask', 'dim', 'seg', 'format', 'offen', 'idxen', 'glc', 'dlc', 'slc', 'tfe', 'unrm', 'done', 'row']
|
||||
def sort_fields(fields):
|
||||
order = {name: i for i, name in enumerate(field_priority)}
|
||||
return sorted(fields, key=lambda f: (order.get(f[0], 1000), f[2]))
|
||||
|
||||
# Generate format classes
|
||||
lines = ["# autogenerated from AMD ISA PDF by pdf.py - do not edit", "# ruff: noqa: F401,F403",
|
||||
"from typing import Annotated",
|
||||
"from extra.assembly.amd.dsl import *",
|
||||
f"from extra.assembly.amd.autogen.{arch}.enum import *", "import functools", ""]
|
||||
for fmt_name, fields in sorted(formats.items()):
|
||||
lines.append(f"class {fmt_name}(Inst):")
|
||||
for name, hi, lo in sort_fields(fields):
|
||||
bits_str = f"bits[{hi}:{lo}]" if hi != lo else f"bits[{hi}]"
|
||||
if name == 'encoding' and fmt_name in encodings: lines.append(f" encoding = {bits_str} == 0b{encodings[fmt_name]}")
|
||||
else:
|
||||
ftype = field_type(name, fmt_name)
|
||||
lines.append(f" {name}{f':{ftype}' if ftype else ''} = {bits_str}")
|
||||
lines.append("")
|
||||
|
||||
# Generate instruction helpers
|
||||
lines.append("# instruction helpers")
|
||||
for fmt_name, ops in sorted(enums.items()):
|
||||
seg = {"GLOBAL": ", seg=2", "SCRATCH": ", seg=1"}.get(fmt_name, "")
|
||||
tgt = {"GLOBAL": "FLAT, GLOBALOp", "SCRATCH": "FLAT, SCRATCHOp"}.get(fmt_name, f"{fmt_name}, {fmt_name}Op")
|
||||
suffix = "_e32" if fmt_name in ("VOP1", "VOP2", "VOPC") else "_e64" if fmt_name == "VOP3" and len(ops) > 0 else ""
|
||||
if fmt_name in formats or fmt_name in ("GLOBAL", "SCRATCH"):
|
||||
for op_val, name in sorted(ops.items()):
|
||||
fn_suffix = suffix if fmt_name != "VOP3" or op_val < 512 else ""
|
||||
lines.append(f"{name.lower()}{fn_suffix} = functools.partial({tgt}.{name}{seg})")
|
||||
|
||||
with open(path, "w") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
def write_pcode(pcode: dict[tuple[str, int], str], enums: dict[str, dict[int, str]], arch: str, path: str):
|
||||
"""Write str_pcode.py file from extracted pseudocode."""
|
||||
# Group pseudocode by enum class
|
||||
by_enum: dict[str, list[tuple[str, int, str]]] = {}
|
||||
for fmt_name, ops in enums.items():
|
||||
for opcode, name in ops.items():
|
||||
if (name, opcode) in pcode: by_enum.setdefault(f"{fmt_name}Op", []).append((name, opcode, pcode[(name, opcode)]))
|
||||
# Generate file
|
||||
enum_names = sorted(by_enum.keys())
|
||||
lines = [f"# autogenerated by pdf.py - do not edit", f"# to regenerate: python -m extra.assembly.amd.pdf",
|
||||
"# ruff: noqa: E501", f"from extra.assembly.amd.autogen.{arch}.enum import {', '.join(enum_names)}", ""]
|
||||
for enum_name in enum_names:
|
||||
lines.append(f"{enum_name}_PCODE = {{")
|
||||
for name, opcode, code in sorted(by_enum[enum_name], key=lambda x: x[1]):
|
||||
lines.append(f" {enum_name}.{name}: {code!r},")
|
||||
lines.append("}\n")
|
||||
lines.append(f"PSEUDOCODE_STRINGS = {{{', '.join(f'{e}: {e}_PCODE' for e in enum_names)}}}")
|
||||
with open(path, "w") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pathlib
|
||||
for arch, url in PDF_URLS.items():
|
||||
print(f"Processing {arch}...")
|
||||
pages = extract(url)
|
||||
tables = extract_tables(pages)
|
||||
enums = extract_enums(tables)
|
||||
formats, encodings = extract_ins(tables)
|
||||
pcode = extract_pcode(pages, enums)
|
||||
# Fix known PDF errors
|
||||
if arch == 'rdna3':
|
||||
fixes = {'SOPP': {8: 'S_WAITCNT_DEPCTR', 58: 'S_TTRACEDATA', 59: 'S_TTRACEDATA_IMM'},
|
||||
'SOPK': {22: 'S_SUBVECTOR_LOOP_BEGIN', 23: 'S_SUBVECTOR_LOOP_END'},
|
||||
'SMEM': {34: 'S_ATC_PROBE', 35: 'S_ATC_PROBE_BUFFER'},
|
||||
'DS': {24: 'DS_GWS_SEMA_RELEASE_ALL', 25: 'DS_GWS_INIT', 26: 'DS_GWS_SEMA_V', 27: 'DS_GWS_SEMA_BR', 28: 'DS_GWS_SEMA_P', 29: 'DS_GWS_BARRIER'},
|
||||
'FLAT': {40: 'GLOBAL_LOAD_ADDTID_B32', 41: 'GLOBAL_STORE_ADDTID_B32', 55: 'FLAT_ATOMIC_CSUB_U32'}}
|
||||
for fmt, ops in fixes.items(): enums[fmt] = merge_dicts([enums[fmt], ops])
|
||||
if arch == 'rdna4':
|
||||
fixes = {'SMEM': {34: 'S_ATC_PROBE', 35: 'S_ATC_PROBE_BUFFER'},
|
||||
'SOP1': {81: 'S_BARRIER_INIT', 82: 'S_BARRIER_JOIN'},
|
||||
'SOPP': {21: 'S_BARRIER_LEAVE', 58: 'S_TTRACEDATA', 59: 'S_TTRACEDATA_IMM'}}
|
||||
for fmt, ops in fixes.items(): enums[fmt] = merge_dicts([enums[fmt], ops])
|
||||
if arch in ('rdna3', 'rdna4'):
|
||||
# RDNA SMEM: PDF says DLC=[14], GLC=[16] but hardware uses DLC=[13], GLC=[14]
|
||||
if 'SMEM' in formats:
|
||||
formats['SMEM'] = [(n, 13 if n == 'dlc' else 14 if n == 'glc' else h, 13 if n == 'dlc' else 14 if n == 'glc' else l)
|
||||
for n, h, l in formats['SMEM']]
|
||||
if arch == 'cdna':
|
||||
# CDNA DS: PDF is missing the GDS field (bit 16)
|
||||
if 'DS' in formats and not any(n == 'gds' for n, _, _ in formats['DS']):
|
||||
formats['DS'].append(('gds', 16, 16))
|
||||
# CDNA DPP/SDWA: PDF only documents modifier fields (bits[63:32]), need to add VOP overlay fields (bits[31:0])
|
||||
vop_overlay = [('encoding', 8, 0), ('vop_op', 16, 9), ('vdst', 24, 17), ('vop2_op', 31, 25)]
|
||||
if 'DPP' in formats and not any(n == 'encoding' for n, _, _ in formats['DPP']):
|
||||
formats['DPP'] = vop_overlay + [('bc' if n == 'bound_ctrl' else n, h, l) for n, h, l in formats['DPP']]
|
||||
encodings['DPP'] = '11111010'
|
||||
if 'SDWA' in formats and not any(n == 'encoding' for n, _, _ in formats['SDWA']):
|
||||
formats['SDWA'] = vop_overlay + [(n, h, l) for n, h, l in formats['SDWA']]
|
||||
encodings['SDWA'] = '11111001'
|
||||
base = pathlib.Path(__file__).parent / "autogen" / arch
|
||||
write_enums(enums, arch, base / "enum.py")
|
||||
write_ins(formats, encodings, enums, arch, base / "ins.py")
|
||||
write_pcode(pcode, enums, arch, base / "str_pcode.py")
|
||||
print(f" {len(tables)} tables, {len(pcode)} pcode -> {base}")
|
||||
+135
-71
@@ -5,27 +5,27 @@ The format is nibble-based with variable-width packets determined by a state mac
|
||||
Uses BitField infrastructure from dsl.py, similar to GPU instruction encoding.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Iterator
|
||||
from enum import Enum
|
||||
from extra.assembly.amd.dsl import BitField, FixedBitField, bits
|
||||
from enum import IntEnum
|
||||
from typing import get_type_hints
|
||||
from extra.assembly.amd.dsl import BitField, bits
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# FIELD ENUMS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class MemSrc(Enum):
|
||||
class MemSrc(IntEnum):
|
||||
LDS = 0
|
||||
LDS_ALT = 1
|
||||
VMEM = 2
|
||||
VMEM_ALT = 3
|
||||
|
||||
class AluSrc(Enum):
|
||||
class AluSrc(IntEnum):
|
||||
NONE = 0
|
||||
SALU = 1
|
||||
VALU = 2
|
||||
VALU_SALU = 3
|
||||
VALU_ALT = 3
|
||||
|
||||
class InstOp(Enum):
|
||||
class InstOp(IntEnum):
|
||||
"""SQTT instruction operation types.
|
||||
|
||||
Memory ops appear in two ranges depending on which SIMD executes them:
|
||||
@@ -101,23 +101,60 @@ class InstOp(Enum):
|
||||
|
||||
class PacketType:
|
||||
"""Base class for SQTT packet types."""
|
||||
encoding: FixedBitField
|
||||
_encoding: tuple[BitField, int] | None = None
|
||||
_field_types: dict[str, type] = {}
|
||||
_values: dict[str, int]
|
||||
_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)}
|
||||
if 'encoding' in cls.__dict__ and isinstance(cls.__dict__['encoding'], tuple):
|
||||
cls._encoding = cls.__dict__['encoding']
|
||||
# Cache field type annotations for enum conversion
|
||||
try: cls._field_types = {k: v for k, v in get_type_hints(cls).items() if isinstance(v, type) and issubclass(v, IntEnum)}
|
||||
except Exception: cls._field_types = {}
|
||||
# Cache fields and precompute extraction info: (name, lo, mask, enum_type)
|
||||
cls._fields = {k: v for k, v in cls.__dict__.items() if isinstance(v, BitField) and k != 'encoding'}
|
||||
cls._extract_info = [(name, bf.lo, bf.mask(), cls._field_types.get(name)) for name, bf in cls._fields.items()]
|
||||
cls._size_nibbles = ((max((f.hi for f in cls._fields.values()), default=0) + 4) // 4)
|
||||
|
||||
def __init__(self, _time: int = 0, **kwargs):
|
||||
"""Construct packet from named fields (like assembly instructions)."""
|
||||
raw = 0
|
||||
if self._encoding:
|
||||
bf, pattern = self._encoding
|
||||
raw |= pattern << bf.lo
|
||||
for name, bf in self._fields.items():
|
||||
val = kwargs.get(name, 0)
|
||||
if isinstance(val, IntEnum): val = val.value
|
||||
raw |= (val & bf.mask()) << bf.lo
|
||||
self._raw, self._time, self._values = raw, _time, {}
|
||||
for name, lo, mask, enum_type in self._extract_info:
|
||||
val = (raw >> lo) & mask
|
||||
if enum_type is not None:
|
||||
try: val = enum_type(val)
|
||||
except ValueError: pass
|
||||
self._values[name] = val
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw: int, time: int = 0):
|
||||
inst = object.__new__(cls)
|
||||
inst._raw, inst._time = raw, time
|
||||
inst._raw, inst._time, inst._values = raw, time, {}
|
||||
for name, lo, mask, enum_type in cls._extract_info:
|
||||
val = (raw >> lo) & mask
|
||||
if enum_type is not None:
|
||||
try: val = enum_type(val)
|
||||
except ValueError: pass
|
||||
inst._values[name] = val
|
||||
return inst
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
if name.startswith('_'): raise AttributeError(name)
|
||||
return self._values.get(name, 0)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
fields_str = ", ".join(f"{k}={getattr(self, k)}" for k in self._fields if not k.startswith('_'))
|
||||
fields_str = ", ".join(f"{k}={v}" for k, v in self._values.items() if not k.startswith('_'))
|
||||
return f"{self.__class__.__name__}({fields_str})"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -133,12 +170,12 @@ class VALUINST(PacketType): # exclude: 1 << 2
|
||||
class VMEMEXEC(PacketType): # exclude: 1 << 0
|
||||
encoding = bits[3:0] == 0b1111
|
||||
delta = bits[5:4]
|
||||
src = bits[7:6].enum(MemSrc)
|
||||
src: MemSrc = bits[7:6]
|
||||
|
||||
class ALUEXEC(PacketType): # exclude: 1 << 1
|
||||
encoding = bits[3:0] == 0b1110
|
||||
delta = bits[5:4]
|
||||
src = bits[7:6].enum(AluSrc)
|
||||
src: AluSrc = bits[7:6]
|
||||
|
||||
class IMMEDIATE(PacketType): # exclude: 1 << 5
|
||||
encoding = bits[3:0] == 0b1101
|
||||
@@ -269,7 +306,7 @@ class INST(PacketType):
|
||||
flag1 = bits[3:3]
|
||||
flag2 = bits[7:7]
|
||||
wave = bits[12:8]
|
||||
op = bits[19:13].enum(InstOp)
|
||||
op: InstOp = bits[19:13]
|
||||
|
||||
class UTILCTR(PacketType):
|
||||
encoding = bits[6:0] == 0b0110001
|
||||
@@ -286,13 +323,17 @@ PACKET_TYPES: list[type[PacketType]] = [
|
||||
NOP,
|
||||
]
|
||||
|
||||
PACKET_BY_NAME: dict[str, type[PacketType]] = {cls.__name__: cls for cls in PACKET_TYPES}
|
||||
|
||||
def _build_state_table() -> tuple[bytes, dict[int, type[PacketType]]]:
|
||||
table = [len(PACKET_TYPES) - 1] * 256 # default to NOP
|
||||
opcode_to_class: dict[int, type[PacketType]] = {i: cls for i, cls in enumerate(PACKET_TYPES)}
|
||||
|
||||
for byte_val in range(256):
|
||||
for opcode, pkt_cls in enumerate(PACKET_TYPES):
|
||||
if (byte_val & pkt_cls.encoding.mask) == pkt_cls.encoding.default:
|
||||
if pkt_cls._encoding is None: continue
|
||||
mask_bf, pattern = pkt_cls._encoding
|
||||
if (byte_val & mask_bf.mask()) == pattern:
|
||||
table[byte_val] = opcode
|
||||
break
|
||||
|
||||
@@ -300,9 +341,16 @@ def _build_state_table() -> tuple[bytes, dict[int, type[PacketType]]]:
|
||||
|
||||
STATE_TO_OPCODE, OPCODE_TO_CLASS = _build_state_table()
|
||||
|
||||
OPCODE_TO_BYTES: dict[int, list[int]] = {}
|
||||
for _byte_val, _opcode in enumerate(STATE_TO_OPCODE):
|
||||
if _opcode not in OPCODE_TO_BYTES: OPCODE_TO_BYTES[_opcode] = []
|
||||
OPCODE_TO_BYTES[_opcode].append(_byte_val)
|
||||
|
||||
# Precompute special case opcodes
|
||||
_TS_DELTA_OR_MARK_OPCODE = next(op for op, cls in OPCODE_TO_CLASS.items() if cls is TS_DELTA_OR_MARK)
|
||||
_TS_DELTA_SHORT_OPCODE = next(op for op, cls in OPCODE_TO_CLASS.items() if cls is TS_DELTA_SHORT)
|
||||
_TS_DELTA_OR_MARK_BIT8 = (TS_DELTA_OR_MARK.bit8.lo, TS_DELTA_OR_MARK.bit8.mask())
|
||||
_TS_DELTA_OR_MARK_BIT9 = (TS_DELTA_OR_MARK.bit9.lo, TS_DELTA_OR_MARK.bit9.mask())
|
||||
|
||||
# Combined lookup: opcode -> (pkt_cls, nib_count, delta_lo, delta_mask, special_case)
|
||||
# special_case: 0=none, 1=TS_DELTA_OR_MARK, 2=TS_DELTA_SHORT
|
||||
@@ -310,7 +358,7 @@ _DECODE_INFO: dict[int, tuple] = {}
|
||||
for _opcode, _pkt_cls in OPCODE_TO_CLASS.items():
|
||||
_delta_field = getattr(_pkt_cls, 'delta', None)
|
||||
_delta_lo = _delta_field.lo if _delta_field else 0
|
||||
_delta_mask = _delta_field.mask if _delta_field else 0
|
||||
_delta_mask = _delta_field.mask() if _delta_field else 0
|
||||
_special = 1 if _opcode == _TS_DELTA_OR_MARK_OPCODE else (2 if _opcode == _TS_DELTA_SHORT_OPCODE else 0)
|
||||
_DECODE_INFO[_opcode] = (_pkt_cls, _pkt_cls._size_nibbles, _delta_lo, _delta_mask, _special)
|
||||
|
||||
@@ -318,69 +366,85 @@ for _opcode, _pkt_cls in OPCODE_TO_CLASS.items():
|
||||
# DECODER
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def decode(data: bytes) -> Iterator[PacketType]:
|
||||
"""Decode raw SQTT blob, yielding packet instances."""
|
||||
n, reg, pos, nib_off, nib_count, time = len(data), 0, 0, 0, 16, 0
|
||||
def decode(data: bytes) -> list[PacketType]:
|
||||
"""Decode raw SQTT blob into list of packet instances."""
|
||||
packets: list[PacketType] = []
|
||||
packets_append = packets.append
|
||||
n = len(data)
|
||||
reg = 0
|
||||
offset = 0
|
||||
nib_count = 16
|
||||
time = 0
|
||||
state_to_opcode = STATE_TO_OPCODE
|
||||
decode_info = _DECODE_INFO
|
||||
mask64 = (1 << 64) - 1
|
||||
|
||||
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):
|
||||
chunk = int.from_bytes(data[pos:pos + byte_count], 'little')
|
||||
reg, pos = (reg >> (byte_count * 8)) | (chunk << (64 - byte_count * 8)), pos + byte_count
|
||||
# 3. if odd, read low nibble
|
||||
if (nib_off := need & 1): reg = (reg >> 4) | ((data[pos] & 0xF) << 60)
|
||||
while (offset >> 3) < n:
|
||||
target = offset + nib_count * 4
|
||||
while offset < target and (offset >> 3) < n:
|
||||
byte = data[offset >> 3]
|
||||
nib = (byte >> (offset & 4)) & 0xF
|
||||
reg = ((reg >> 4) | (nib << 60)) & mask64
|
||||
offset += 4
|
||||
if offset < target: break
|
||||
|
||||
opcode = state_to_opcode[reg & 0xFF]
|
||||
pkt_cls, nib_count, delta_lo, delta_mask, special = decode_info[opcode]
|
||||
|
||||
opcode = STATE_TO_OPCODE[reg & 0xFF]
|
||||
pkt_cls, nib_count, delta_lo, delta_mask, special = _DECODE_INFO[opcode]
|
||||
delta = (reg >> delta_lo) & delta_mask
|
||||
if special == 1 and (reg >> 9) & 1 and not (reg >> 8) & 1: delta = 0 # TS_DELTA_OR_MARK marker
|
||||
elif special == 2: delta += 8 # TS_DELTA_SHORT
|
||||
|
||||
if special == 1: # TS_DELTA_OR_MARK
|
||||
bit8 = (reg >> _TS_DELTA_OR_MARK_BIT8[0]) & _TS_DELTA_OR_MARK_BIT8[1]
|
||||
bit9 = (reg >> _TS_DELTA_OR_MARK_BIT9[0]) & _TS_DELTA_OR_MARK_BIT9[1]
|
||||
if bit9 and not bit8: delta = 0
|
||||
elif special == 2: # TS_DELTA_SHORT
|
||||
delta = delta + 8
|
||||
|
||||
time += delta
|
||||
yield pkt_cls.from_raw(reg, time)
|
||||
packets_append(pkt_cls.from_raw(reg, time))
|
||||
|
||||
return packets
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# PRINTER
|
||||
# ENCODER
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
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 encode(packets: list[PacketType]) -> bytes:
|
||||
"""Encode a list of packet instances into raw SQTT blob."""
|
||||
if not packets: return b''
|
||||
|
||||
def format_packet(p) -> str:
|
||||
from tinygrad.helpers import colored
|
||||
name = type(p).__name__
|
||||
if isinstance(p, INST):
|
||||
op_name = p.op.name if isinstance(p.op, InstOp) else f"0x{p.op:02x}"
|
||||
fields = f"wave={p.wave} op={op_name}" + (" flag1" if p.flag1 else "") + (" flag2" if p.flag2 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, WAVEEND)): fields = f"wave={p.wave} simd={p.simd} cu={p.cu}"
|
||||
elif hasattr(p, '_fields'):
|
||||
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 {'delta', 'encoding'})
|
||||
else: fields = ""
|
||||
return f"{p._time:8}: {colored(f'{name:18}', PACKET_COLORS.get(name, 'white'))} {fields}"
|
||||
read_lengths = [16]
|
||||
for p in packets[:-1]:
|
||||
read_lengths.append(type(p)._size_nibbles)
|
||||
|
||||
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"}
|
||||
for p in packets:
|
||||
if type(p).__name__ not in skip: print(format_packet(p))
|
||||
total_nibbles = sum(read_lengths)
|
||||
bits_arr = [0] * (total_nibbles * 4)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys, pickle
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python sqtt.py <pkl_file>")
|
||||
sys.exit(1)
|
||||
with open(sys.argv[1], "rb") as f:
|
||||
data = pickle.load(f)
|
||||
sqtt_events = [e for e in data if type(e).__name__ == "ProfileSQTTEvent"]
|
||||
for i, event in enumerate(sqtt_events):
|
||||
print(f"\n=== event {i} ===")
|
||||
print_packets(decode(event.blob))
|
||||
cumulative = 0
|
||||
for i, p in enumerate(packets):
|
||||
cumulative += read_lengths[i]
|
||||
pkt_cls = type(p)
|
||||
opcode = next(op for op, cls in OPCODE_TO_CLASS.items() if cls is pkt_cls)
|
||||
|
||||
byte_vals = OPCODE_TO_BYTES.get(opcode)
|
||||
if not byte_vals: raise ValueError(f"No encoding for {pkt_cls.__name__}")
|
||||
opcode_byte = byte_vals[0]
|
||||
|
||||
delta_field = getattr(pkt_cls, 'delta', None)
|
||||
if delta_field is not None and delta_field.hi < 8:
|
||||
delta = p._values.get('delta', 0)
|
||||
if isinstance(delta, IntEnum): delta = delta.value
|
||||
if pkt_cls is TS_DELTA_SHORT: delta = max(0, delta - 8)
|
||||
delta = delta & delta_field.mask()
|
||||
opcode_byte = (opcode_byte & ~(delta_field.mask() << delta_field.lo)) | (delta << delta_field.lo)
|
||||
|
||||
opcode_nibble_pos = max(0, cumulative - 16)
|
||||
opcode_bit_pos = opcode_nibble_pos * 4
|
||||
|
||||
for b in range(8):
|
||||
if opcode_bit_pos + b < len(bits_arr):
|
||||
bits_arr[opcode_bit_pos + b] = (opcode_byte >> b) & 1
|
||||
|
||||
nibbles = [sum(bits_arr[i + j] << j for j in range(4) if i + j < len(bits_arr)) for i in range(0, len(bits_arr), 4)]
|
||||
while len(nibbles) % 2: nibbles.append(0)
|
||||
return bytes(nibbles[i] | (nibbles[i + 1] << 4) for i in range(0, len(nibbles), 2))
|
||||
|
||||
@@ -0,0 +1,855 @@
|
||||
#!/usr/bin/env python3
|
||||
"""SQTT InstOp discovery tool - finds instruction opcodes by running different instructions.
|
||||
|
||||
Requires profiling enabled:
|
||||
echo 'profile_standard' | sudo tee /sys/class/drm/card1/device/power_dpm_force_performance_level
|
||||
|
||||
Run with: DEBUG=1 python extra/assembly/amd/test/discover_instops.py
|
||||
For full traces: DEBUG=2 python extra/assembly/amd/test/discover_instops.py
|
||||
"""
|
||||
import os
|
||||
os.environ["SQTT"] = "1"
|
||||
os.environ["PROFILE"] = "1"
|
||||
os.environ["SQTT_LIMIT_SE"] = "2" # Force work to traced SE only
|
||||
os.environ["SQTT_TOKEN_EXCLUDE"] = "3784" # Exclude WAVERDY, REG, EVENT, UTILCTR, WAVEALLOC, PERF
|
||||
|
||||
from tinygrad.helpers import DEBUG, colored
|
||||
from tinygrad.runtime.ops_amd import SQTT_SIMD_SEL
|
||||
|
||||
from extra.assembly.amd.autogen.rdna3.ins import (
|
||||
# VALU - basic (these are safe, just register ops)
|
||||
v_mov_b32_e32, v_add_f32_e32, v_mul_f32_e32,
|
||||
v_and_b32_e32, v_or_b32_e32, v_xor_b32_e32,
|
||||
v_lshlrev_b32_e32, v_lshrrev_b32_e32,
|
||||
# VALU - transcendental
|
||||
v_exp_f32_e32, v_log_f32_e32, v_rcp_f32_e32, v_sqrt_f32_e32,
|
||||
v_sin_f32_e32, v_cos_f32_e32,
|
||||
# VALU - 64-bit
|
||||
v_lshlrev_b64, v_lshrrev_b64, v_ashrrev_i64,
|
||||
v_add_f64, v_mul_f64, v_max_f64, v_min_f64,
|
||||
v_fma_f64,
|
||||
# VALU - 64-bit transcendental
|
||||
v_rcp_f64_e32, v_rsq_f64_e32, v_sqrt_f64_e32,
|
||||
v_trunc_f64_e32, v_ceil_f64_e32, v_floor_f64_e32, v_fract_f64_e32,
|
||||
v_frexp_exp_i32_f64_e32, v_frexp_mant_f64_e32,
|
||||
# VALU - div helpers
|
||||
v_div_fixup_f32, v_div_fixup_f64, v_div_fmas_f32, v_div_fmas_f64, v_div_scale_f32,
|
||||
# VALU - MAD64
|
||||
v_mad_u64_u32, v_mad_i64_i32,
|
||||
# VALU - compare (writes to VCC, safe)
|
||||
v_cmp_eq_u32_e32,
|
||||
# VALU - cmpx (modifies EXEC) - various types
|
||||
v_cmpx_eq_u32_e32, v_cmpx_lt_u32_e32, v_cmpx_gt_u32_e32,
|
||||
v_cmpx_eq_f32_e32, v_cmpx_lt_f32_e32,
|
||||
v_cmpx_eq_i32_e32,
|
||||
v_cmpx_class_f32_e32,
|
||||
# VALU - readlane/writelane
|
||||
v_readlane_b32, v_writelane_b32,
|
||||
v_readfirstlane_b32_e32,
|
||||
# SALU - basic (safe, just register ops)
|
||||
s_mov_b32, s_add_u32, s_and_b32, s_or_b32,
|
||||
s_lshl_b32, s_lshr_b32,
|
||||
s_nop, s_endpgm, s_waitcnt,
|
||||
# SALU - float
|
||||
s_ceil_f32, s_floor_f32, s_trunc_f32,
|
||||
# SALU - branch (safe if offset is 0 = next instruction)
|
||||
s_branch, s_cbranch_scc0, s_cbranch_execz, s_cbranch_execnz,
|
||||
# SALU - message
|
||||
s_sendmsg,
|
||||
# SALU - bit manipulation
|
||||
s_brev_b32, s_bcnt1_i32_b32, s_ctz_i32_b32, s_clz_i32_u32,
|
||||
# SALU - saveexec (modifies EXEC)
|
||||
s_and_saveexec_b32, s_or_saveexec_b32, s_xor_saveexec_b32,
|
||||
# SMEM - scalar memory (load from kernarg pointer in s[0:1])
|
||||
s_load_b32, s_load_b64,
|
||||
# GLOBAL - global memory (load/store) - various widths
|
||||
global_load_u8, global_load_u16, global_load_b32, global_load_b64, global_load_b96, global_load_b128,
|
||||
global_store_b8, global_store_b16, global_store_b32, global_store_b64, global_store_b96, global_store_b128,
|
||||
# GLOBAL - atomics
|
||||
global_atomic_add_u32, global_atomic_add_u64,
|
||||
# FLAT - flat memory access
|
||||
flat_load_b32, flat_load_b64, flat_load_b96, flat_load_b128,
|
||||
flat_store_b8, flat_store_b16, flat_store_b32, flat_store_b64, flat_store_b96, flat_store_b128,
|
||||
# LDS - local data share - various widths
|
||||
ds_load_b32, ds_load_b64, ds_load_b128,
|
||||
ds_store_b32, ds_store_b64, ds_store_b128,
|
||||
# LDS - atomics
|
||||
ds_add_u32, ds_max_u32, ds_min_u32,
|
||||
# VOP3P - packed
|
||||
v_pk_add_f16, v_pk_mul_f16, v_pk_fma_f16, v_pk_add_i16,
|
||||
# VOP3 - misc
|
||||
v_bfe_u32, v_bfi_b32, v_alignbit_b32, v_fma_f32,
|
||||
v_add3_u32, v_xad_u32, v_lshl_or_b32, v_add_nc_u32_e32,
|
||||
# VOP3 - carry-out
|
||||
v_add_co_u32, v_add_co_ci_u32_e32,
|
||||
# VOPD - dual issue
|
||||
v_dual_add_f32, v_dual_mul_f32,
|
||||
# VOP2 - fmac
|
||||
v_fmac_f32_e32,
|
||||
# DOT
|
||||
v_dot2_f16_f16,
|
||||
# WMMA
|
||||
v_wmma_f32_16x16x16_f16, v_wmma_f16_16x16x16_f16, v_wmma_i32_16x16x16_iu8,
|
||||
# Permlane ops
|
||||
v_permlane64_b32_e32, v_permlane16_b32, v_permlanex16_b32,
|
||||
# Interpolation
|
||||
v_interp_p10_f32, v_interp_p2_f32,
|
||||
# Barrier
|
||||
s_barrier,
|
||||
# SrcEnum for NULL soffset
|
||||
SrcEnum,
|
||||
)
|
||||
from extra.assembly.amd.dsl import v, s
|
||||
from extra.assembly.amd.sqtt import InstOp, INST, WAVESTART, WAVEEND, ALUEXEC, VMEMEXEC
|
||||
|
||||
from extra.assembly.amd.test.test_sqtt_hw import (
|
||||
run_asm_sqtt, decode_all_blobs, get_inst_ops, print_blobs, get_wave_packets, format_packet, PACKET_COLORS, count_valuinst
|
||||
)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# INSTRUCTION TEST CASES - only safe instructions that don't access memory
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Helper: load buffer address from kernarg (s[0:1] -> s[2:3])
|
||||
# The runtime passes kernarg pointer in s[0:1], kernarg contains buffer address
|
||||
def _load_buf_addr():
|
||||
return [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL), # load buf addr from kernarg
|
||||
s_waitcnt(lgkmcnt=0), # wait for SMEM load
|
||||
]
|
||||
|
||||
INSTRUCTION_TESTS: dict[str, tuple[str, list]] = {
|
||||
# SALU (0x0) - scalar ALU, just register operations
|
||||
"SALU_mov": ("s_mov_b32", [s_mov_b32(s[4], 0), s_mov_b32(s[5], 1)]),
|
||||
"SALU_add": ("s_add_u32", [s_mov_b32(s[4], 1), s_mov_b32(s[5], 2), s_add_u32(s[6], s[4], s[5])]),
|
||||
"SALU_logic": ("s_and/or", [s_and_b32(s[6], s[4], s[5]), s_or_b32(s[7], s[4], s[5])]),
|
||||
"SALU_shift": ("s_lshl/lshr", [s_lshl_b32(s[6], s[4], 1), s_lshr_b32(s[7], s[4], 1)]),
|
||||
"SALU_nop": ("s_nop", [s_nop(0)]),
|
||||
|
||||
# JUMP (0x3) - branch taken
|
||||
"JUMP_branch": ("s_branch", [s_branch(0)]),
|
||||
"JUMP_cbranch_execnz": ("s_cbranch_execnz", [s_cbranch_execnz(0)]), # EXEC != 0, branch taken
|
||||
|
||||
# JUMP_NO (0x4) - branch not taken
|
||||
"JUMP_NO_cbranch_execz": ("s_cbranch_execz", [s_cbranch_execz(0)]), # EXEC != 0, branch not taken
|
||||
|
||||
# VALU (0xb) - vector ALU, just register operations
|
||||
"VALU_mov": ("v_mov_b32", [v_mov_b32_e32(v[0], 0), v_mov_b32_e32(v[1], 1.0)]),
|
||||
"VALU_add": ("v_add_f32", [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[1], 2.0), v_add_f32_e32(v[2], v[0], v[1])]),
|
||||
"VALU_mul": ("v_mul_f32", [v_mul_f32_e32(v[2], v[0], v[1])]),
|
||||
"VALU_logic": ("v_and/or/xor", [v_and_b32_e32(v[2], v[0], v[1]), v_or_b32_e32(v[3], v[0], v[1]), v_xor_b32_e32(v[4], v[0], v[1])]),
|
||||
"VALU_shift": ("v_lshl/lshr", [v_lshlrev_b32_e32(v[2], 1, v[0]), v_lshrrev_b32_e32(v[3], 1, v[0])]),
|
||||
|
||||
# VALU transcendental - still just register ops
|
||||
"VALU_exp": ("v_exp_f32", [v_mov_b32_e32(v[0], 1.0), v_exp_f32_e32(v[1], v[0])]),
|
||||
"VALU_log": ("v_log_f32", [v_mov_b32_e32(v[0], 1.0), v_log_f32_e32(v[1], v[0])]),
|
||||
"VALU_rcp": ("v_rcp_f32", [v_mov_b32_e32(v[0], 1.0), v_rcp_f32_e32(v[1], v[0])]),
|
||||
"VALU_sqrt": ("v_sqrt_f32", [v_mov_b32_e32(v[0], 1.0), v_sqrt_f32_e32(v[1], v[0])]),
|
||||
|
||||
# VALU 64-bit shift (0xd)
|
||||
"VALU64_lshl": ("v_lshlrev_b64", [v_lshlrev_b64(v[0:1], 1, v[2:3])]),
|
||||
"VALU64_lshr": ("v_lshrrev_b64", [v_lshrrev_b64(v[0:1], 1, v[2:3])]),
|
||||
"VALU64_ashr": ("v_ashrrev_i64", [v_ashrrev_i64(v[0:1], 1, v[2:3])]),
|
||||
|
||||
# VALU 64-bit arithmetic
|
||||
"VALU64_add": ("v_add_f64", [v_add_f64(v[0:1], v[2:3], v[4:5])]),
|
||||
"VALU64_mul": ("v_mul_f64", [v_mul_f64(v[0:1], v[2:3], v[4:5])]),
|
||||
"VALU64_max": ("v_max_f64", [v_max_f64(v[0:1], v[2:3], v[4:5])]),
|
||||
"VALU64_min": ("v_min_f64", [v_min_f64(v[0:1], v[2:3], v[4:5])]),
|
||||
"VALU64_fma": ("v_fma_f64", [v_fma_f64(v[0:1], v[2:3], v[4:5], v[6:7])]),
|
||||
|
||||
# VALU 64-bit transcendental
|
||||
"VALU64_rcp": ("v_rcp_f64", [v_rcp_f64_e32(v[0:1], v[2:3])]),
|
||||
"VALU64_rsq": ("v_rsq_f64", [v_rsq_f64_e32(v[0:1], v[2:3])]),
|
||||
"VALU64_sqrt": ("v_sqrt_f64", [v_sqrt_f64_e32(v[0:1], v[2:3])]),
|
||||
|
||||
# VALU 64-bit rounding
|
||||
"VALU64_trunc": ("v_trunc_f64", [v_trunc_f64_e32(v[0:1], v[2:3])]),
|
||||
"VALU64_ceil": ("v_ceil_f64", [v_ceil_f64_e32(v[0:1], v[2:3])]),
|
||||
"VALU64_floor": ("v_floor_f64", [v_floor_f64_e32(v[0:1], v[2:3])]),
|
||||
"VALU64_fract": ("v_fract_f64", [v_fract_f64_e32(v[0:1], v[2:3])]),
|
||||
|
||||
# VALU 64-bit frexp
|
||||
"VALU64_frexp_exp": ("v_frexp_exp_i32_f64", [v_frexp_exp_i32_f64_e32(v[0], v[2:3])]),
|
||||
"VALU64_frexp_mant": ("v_frexp_mant_f64", [v_frexp_mant_f64_e32(v[0:1], v[2:3])]),
|
||||
|
||||
# VALU 64-bit div helpers
|
||||
"VALU64_div_fixup": ("v_div_fixup_f64", [v_div_fixup_f64(v[0:1], v[2:3], v[4:5], v[6:7])]),
|
||||
"VALU64_div_fmas": ("v_div_fmas_f64", [v_div_fmas_f64(v[0:1], v[2:3], v[4:5], v[6:7])]),
|
||||
|
||||
# VALU 32-bit div helpers
|
||||
"VALU_div_fixup": ("v_div_fixup_f32", [v_div_fixup_f32(v[0], v[1], v[2], v[3])]),
|
||||
"VALU_div_fmas": ("v_div_fmas_f32", [v_div_fmas_f32(v[0], v[1], v[2], v[3])]),
|
||||
"VALU_div_scale": ("v_div_scale_f32", [v_div_scale_f32(v[0], SrcEnum.VCC_LO, v[1], v[2], v[3])]),
|
||||
|
||||
# VALU MAD64 (0xe)
|
||||
"VALU_mad64u": ("v_mad_u64_u32", [
|
||||
v_mov_b32_e32(v[2], 2),
|
||||
v_mov_b32_e32(v[3], 3),
|
||||
v_mov_b32_e32(v[4], 0),
|
||||
v_mov_b32_e32(v[5], 0),
|
||||
v_mad_u64_u32(v[0:1], SrcEnum.NULL, v[2], v[3], v[4:5]),
|
||||
]),
|
||||
"VALU_mad64i": ("v_mad_i64_i32", [
|
||||
v_mov_b32_e32(v[2], 2),
|
||||
v_mov_b32_e32(v[3], 3),
|
||||
v_mov_b32_e32(v[4], 0),
|
||||
v_mov_b32_e32(v[5], 0),
|
||||
v_mad_i64_i32(v[0:1], SrcEnum.NULL, v[2], v[3], v[4:5]),
|
||||
]),
|
||||
|
||||
# VALU compare - writes to VCC
|
||||
"VALU_cmp": ("v_cmp_eq_u32", [v_cmp_eq_u32_e32(v[0], v[1])]),
|
||||
|
||||
# VALU CMPX (0x73) - modifies EXEC
|
||||
"VALU_cmpx_eq_u32": ("v_cmpx_eq_u32", [v_cmpx_eq_u32_e32(v[0], v[1])]),
|
||||
|
||||
# SALU saveexec (0x72) - modifies EXEC safely by ANDing with all-ones mask
|
||||
"SALU_saveexec": ("s_and_saveexec_b32", [
|
||||
s_mov_b32(s[5], 0xFFFFFFFF), # all lanes mask
|
||||
s_and_saveexec_b32(s[4], s[5]), # EXEC = EXEC & 0xFFFFFFFF = EXEC (unchanged)
|
||||
]),
|
||||
|
||||
# SALU float ops
|
||||
"SALU_ceil": ("s_ceil_f32", [s_ceil_f32(s[4], s[5])]),
|
||||
"SALU_floor": ("s_floor_f32", [s_floor_f32(s[4], s[5])]),
|
||||
"SALU_trunc": ("s_trunc_f32", [s_trunc_f32(s[4], s[5])]),
|
||||
|
||||
# SALU bit ops
|
||||
"SALU_brev": ("s_brev_b32", [s_brev_b32(s[4], s[5])]),
|
||||
"SALU_bcnt1": ("s_bcnt1_i32_b32", [s_bcnt1_i32_b32(s[4], s[5])]),
|
||||
"SALU_ctz": ("s_ctz_i32_b32", [s_ctz_i32_b32(s[4], s[5])]),
|
||||
"SALU_clz": ("s_clz_i32_u32", [s_clz_i32_u32(s[4], s[5])]),
|
||||
|
||||
# VALU sin/cos
|
||||
"VALU_sin": ("v_sin_f32", [v_sin_f32_e32(v[0], v[1])]),
|
||||
"VALU_cos": ("v_cos_f32", [v_cos_f32_e32(v[0], v[1])]),
|
||||
|
||||
# VOP3P - packed operations
|
||||
"VALU_pk_add_f16": ("v_pk_add_f16", [v_pk_add_f16(v[0], v[1], v[2])]),
|
||||
"VALU_pk_mul_f16": ("v_pk_mul_f16", [v_pk_mul_f16(v[0], v[1], v[2])]),
|
||||
"VALU_pk_fma_f16": ("v_pk_fma_f16", [v_pk_fma_f16(v[0], v[1], v[2], v[3])]),
|
||||
"VALU_pk_add_i16": ("v_pk_add_i16", [v_pk_add_i16(v[0], v[1], v[2])]),
|
||||
|
||||
# VOP3 - misc
|
||||
"VALU_bfe_u32": ("v_bfe_u32", [v_bfe_u32(v[0], v[1], 0, 8)]),
|
||||
"VALU_bfi_b32": ("v_bfi_b32", [v_bfi_b32(v[0], v[1], v[2], v[3])]),
|
||||
"VALU_alignbit": ("v_alignbit_b32", [v_alignbit_b32(v[0], v[1], v[2], 4)]),
|
||||
"VALU_fma_f32": ("v_fma_f32", [v_fma_f32(v[0], v[1], v[2], v[3])]),
|
||||
|
||||
# VOP3 - integer add variants (used by tinygrad kernels)
|
||||
"VALU_add3": ("v_add3_u32", [v_add3_u32(v[0], v[1], v[2], v[3])]),
|
||||
"VALU_xad": ("v_xad_u32", [v_xad_u32(v[0], v[1], v[2], v[3])]),
|
||||
"VALU_lshl_or": ("v_lshl_or_b32", [v_lshl_or_b32(v[0], v[1], 4, v[2])]),
|
||||
"VALU_add_nc": ("v_add_nc_u32", [v_add_nc_u32_e32(v[0], v[1], v[2])]),
|
||||
|
||||
# VOP3 - carry-out adds (used for 64-bit address calculation)
|
||||
"VALU_add_co": ("v_add_co_u32", [v_add_co_u32(v[0], SrcEnum.VCC_LO, v[1], v[2])]),
|
||||
"VALU_add_co_ci": ("v_add_co_ci_u32", [v_add_co_ci_u32_e32(v[0], v[1], v[2])]),
|
||||
|
||||
# VOPD - dual issue (used by tinygrad kernels)
|
||||
"VALU_dual_add": ("v_dual_add_f32", [v_dual_add_f32(v[0], v[1], v[2], v[3], v[4], v[5])]),
|
||||
"VALU_dual_mul": ("v_dual_mul_f32", [v_dual_mul_f32(v[0], v[1], v[2], v[3], v[4], v[5])]),
|
||||
|
||||
# VOP2 - fmac
|
||||
"VALU_fmac": ("v_fmac_f32", [v_fmac_f32_e32(v[0], v[1], v[0])]),
|
||||
|
||||
# DOT products
|
||||
"VALU_dot2": ("v_dot2_f16_f16", [v_dot2_f16_f16(v[0], v[1], v[2], v[3])]),
|
||||
|
||||
# WMMA - wave matrix multiply accumulate
|
||||
"VALU_wmma_f32_f16": ("v_wmma_f32_16x16x16_f16", [v_wmma_f32_16x16x16_f16(v[0:7], v[8:15], v[16:23], v[0:7])]),
|
||||
"VALU_wmma_f16_f16": ("v_wmma_f16_16x16x16_f16", [v_wmma_f16_16x16x16_f16(v[0:7], v[8:15], v[16:23], v[0:7])]),
|
||||
"VALU_wmma_i32_iu8": ("v_wmma_i32_16x16x16_iu8", [v_wmma_i32_16x16x16_iu8(v[0:7], v[8:11], v[12:15], v[0:7])]),
|
||||
|
||||
# Permlane operations - cross-lane data movement
|
||||
# NOTE: permlane64 produces NO SQTT packets in wave32 mode (it's for wave64 pairs)
|
||||
# NOTE: permlane16/x16 produce VALUINST packets (no specific InstOp)
|
||||
"VALU_permlane16": ("v_permlane16_b32", [v_permlane16_b32(v[0], v[1], s[2], s[3])]),
|
||||
"VALU_permlanex16": ("v_permlanex16_b32", [v_permlanex16_b32(v[0], v[1], s[2], s[3])]),
|
||||
|
||||
# Interpolation - used in graphics shaders (produces InstOp 0x12 VINTERP)
|
||||
"VINTERP_p10": ("v_interp_p10_f32", [v_interp_p10_f32(v[0], v[1], v[2], v[3])]),
|
||||
"VINTERP_p2": ("v_interp_p2_f32", [v_interp_p2_f32(v[0], v[1], v[2], v[3])]),
|
||||
|
||||
# Barrier - wave synchronization
|
||||
# NOTE: s_barrier produces NO SQTT instruction packets (with 1 wave, it's essentially a no-op)
|
||||
"SALU_barrier": ("s_barrier", [s_barrier()]),
|
||||
|
||||
# LDS atomics
|
||||
"LDS_atomic_add": ("ds_add_u32", [
|
||||
v_mov_b32_e32(v[0], 0), # LDS address
|
||||
v_mov_b32_e32(v[1], 1), # data to add
|
||||
ds_add_u32(addr=v[0], data0=v[1]),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# GLOBAL ATOMICS - access real buffer passed via kernarg
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# GLOBAL atomic add 32-bit (0x28 GLOBAL_ATOMIC)
|
||||
"GLOBAL_atomic_add": ("global_atomic_add_u32", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL), # load buf addr from kernarg
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0), # offset = 0
|
||||
v_mov_b32_e32(v[1], 1), # data to add
|
||||
global_atomic_add_u32(addr=v[0], data=v[1], saddr=s[2]),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL atomic add 64-bit
|
||||
"GLOBAL_atomic_add64": ("global_atomic_add_u64", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[2], 1),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
global_atomic_add_u64(addr=v[0], data=v[2:3], saddr=s[2]),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# MEMORY INSTRUCTIONS - access real buffer passed via kernarg
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# SMEM (0x1) - scalar memory load from buffer
|
||||
"SMEM_load": ("s_load_b32", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL), # load buf addr from kernarg
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_load_b32(s[4], s[2], 0, soffset=SrcEnum.NULL), # load from buffer
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL load (0x21 GLOBAL_LOAD) - global memory load
|
||||
"GLOBAL_load": ("global_load_b32", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL), # load buf addr from kernarg
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0), # offset = 0
|
||||
global_load_b32(v[1], addr=v[0], saddr=s[2], offset=0), # load from buffer
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL store (0x24 GLOBAL_STORE) - global memory store
|
||||
"GLOBAL_store": ("global_store_b32", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL), # load buf addr from kernarg
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0), # offset = 0
|
||||
v_mov_b32_e32(v[1], 42), # data to store
|
||||
global_store_b32(addr=v[0], data=v[1], saddr=s[2], offset=0), # store to buffer
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL 8-bit load/store
|
||||
"GLOBAL_load8": ("global_load_u8", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
global_load_u8(v[1], addr=v[0], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
"GLOBAL_store8": ("global_store_b8", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 42),
|
||||
global_store_b8(addr=v[0], data=v[1], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL 16-bit load/store
|
||||
"GLOBAL_load16": ("global_load_u16", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
global_load_u16(v[1], addr=v[0], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
"GLOBAL_store16": ("global_store_b16", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 42),
|
||||
global_store_b16(addr=v[0], data=v[1], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# LDS load (0x29 LDS_LOAD) - local data share read
|
||||
"LDS_load": ("ds_load_b32", [
|
||||
v_mov_b32_e32(v[0], 0), # LDS address = 0
|
||||
ds_load_b32(v[1], v[0], offset=0), # read from LDS
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# LDS store (0x2b LDS_STORE) - local data share write
|
||||
"LDS_store": ("ds_store_b32", [
|
||||
v_mov_b32_e32(v[0], 0), # LDS address = 0
|
||||
v_mov_b32_e32(v[1], 42), # data to store
|
||||
ds_store_b32(v[0], v[1], offset=0), # write to LDS
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# WIDER MEMORY OPERATIONS - to discover more InstOp variants
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# GLOBAL 64-bit load
|
||||
"GLOBAL_load64": ("global_load_b64", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
global_load_b64(v[2:3], addr=v[0], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL 96-bit load
|
||||
"GLOBAL_load96": ("global_load_b96", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
global_load_b96(v[4:6], addr=v[0], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL 128-bit load
|
||||
"GLOBAL_load128": ("global_load_b128", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
global_load_b128(v[4:7], addr=v[0], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL 64-bit store
|
||||
"GLOBAL_store64": ("global_store_b64", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[2], 42),
|
||||
v_mov_b32_e32(v[3], 43),
|
||||
global_store_b64(addr=v[0], data=v[2:3], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL 96-bit store
|
||||
"GLOBAL_store96": ("global_store_b96", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
v_mov_b32_e32(v[6], 44),
|
||||
global_store_b96(addr=v[0], data=v[4:6], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL 128-bit store
|
||||
"GLOBAL_store128": ("global_store_b128", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
v_mov_b32_e32(v[6], 44),
|
||||
v_mov_b32_e32(v[7], 45),
|
||||
global_store_b128(addr=v[0], data=v[4:7], saddr=s[2], offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# GLOBAL VADDR (vector-only addressing, saddr=NULL) - used by tinygrad kernels
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# GLOBAL VADDR load (all sizes use same opcode 0x22)
|
||||
"GLOBAL_VADDR_load": ("global_load_b32 vaddr", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
global_load_b32(v[4], addr=v[0:1], saddr=SrcEnum.NULL, offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
"GLOBAL_VADDR_load128": ("global_load_b128 vaddr", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
global_load_b128(v[4:7], addr=v[0:1], saddr=SrcEnum.NULL, offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# GLOBAL VADDR stores (size encoded: 32->0x25, 64->0x26, 96->0x27, 128->0x28)
|
||||
"GLOBAL_VADDR_store": ("global_store_b32 vaddr", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
global_store_b32(addr=v[0:1], data=v[4], saddr=SrcEnum.NULL, offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
"GLOBAL_VADDR_store64": ("global_store_b64 vaddr", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
global_store_b64(addr=v[0:1], data=v[4:5], saddr=SrcEnum.NULL, offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
"GLOBAL_VADDR_store96": ("global_store_b96 vaddr", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
v_mov_b32_e32(v[6], 44),
|
||||
global_store_b96(addr=v[0:1], data=v[4:6], saddr=SrcEnum.NULL, offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
"GLOBAL_VADDR_store128": ("global_store_b128 vaddr", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
v_mov_b32_e32(v[6], 44),
|
||||
v_mov_b32_e32(v[7], 45),
|
||||
global_store_b128(addr=v[0:1], data=v[4:7], saddr=SrcEnum.NULL, offset=0),
|
||||
s_waitcnt(vmcnt=0),
|
||||
]),
|
||||
|
||||
# LDS 64-bit load
|
||||
"LDS_load64": ("ds_load_b64", [
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
ds_load_b64(v[2:3], v[0], offset=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# LDS 128-bit load
|
||||
"LDS_load128": ("ds_load_b128", [
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
ds_load_b128(v[4:7], v[0], offset=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# LDS 64-bit store
|
||||
"LDS_store64": ("ds_store_b64", [
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[2], 42),
|
||||
v_mov_b32_e32(v[3], 43),
|
||||
ds_store_b64(v[0], v[2:3], offset=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# LDS 128-bit store
|
||||
"LDS_store128": ("ds_store_b128", [
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
v_mov_b32_e32(v[6], 44),
|
||||
v_mov_b32_e32(v[7], 45),
|
||||
ds_store_b128(v[0], v[4:7], offset=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# MESSAGE (0x9) - s_sendmsg
|
||||
"MESSAGE": ("s_sendmsg", [
|
||||
s_sendmsg(0), # send message 0 (NOP message)
|
||||
]),
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# FLAT MEMORY - uses 64-bit virtual address in VGPRs
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# FLAT load - load using 64-bit address from buffer
|
||||
"FLAT_load": ("flat_load_b32", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL), # load buf addr from kernarg
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]), # addr lo
|
||||
v_mov_b32_e32(v[1], s[3]), # addr hi
|
||||
flat_load_b32(v[2], addr=v[0:1]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# FLAT store
|
||||
"FLAT_store": ("flat_store_b32", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[2], 42),
|
||||
flat_store_b32(addr=v[0:1], data=v[2]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# FLAT 64-bit
|
||||
"FLAT_load64": ("flat_load_b64", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
flat_load_b64(v[2:3], addr=v[0:1]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
"FLAT_store64": ("flat_store_b64", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
flat_store_b64(addr=v[0:1], data=v[4:5]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# FLAT 96-bit
|
||||
"FLAT_load96": ("flat_load_b96", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
flat_load_b96(v[4:6], addr=v[0:1]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
"FLAT_store96": ("flat_store_b96", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
v_mov_b32_e32(v[6], 44),
|
||||
flat_store_b96(addr=v[0:1], data=v[4:6]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# FLAT 128-bit
|
||||
"FLAT_load128": ("flat_load_b128", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
flat_load_b128(v[4:7], addr=v[0:1]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
"FLAT_store128": ("flat_store_b128", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[4], 42),
|
||||
v_mov_b32_e32(v[5], 43),
|
||||
v_mov_b32_e32(v[6], 44),
|
||||
v_mov_b32_e32(v[7], 45),
|
||||
flat_store_b128(addr=v[0:1], data=v[4:7]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
# FLAT 8/16-bit stores
|
||||
"FLAT_store8": ("flat_store_b8", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[2], 42),
|
||||
flat_store_b8(addr=v[0:1], data=v[2]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
"FLAT_store16": ("flat_store_b16", [
|
||||
s_load_b64(s[2:3], s[0], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
v_mov_b32_e32(v[2], 42),
|
||||
flat_store_b16(addr=v[0:1], data=v[2]),
|
||||
s_waitcnt(vmcnt=0, lgkmcnt=0),
|
||||
]),
|
||||
|
||||
}
|
||||
|
||||
|
||||
def run_with_retry(instructions: list, max_attempts: int = 20) -> tuple[list[tuple[int, list[bytes]]], list[list], set, int]:
|
||||
"""Run instructions multiple times to collect InstOp variants.
|
||||
|
||||
Memory ops produce different InstOp values (0x2x vs 0x5x) depending on which SIMD executes them:
|
||||
- 0x2x range: wave ran on traced SIMD (matched)
|
||||
- 0x5x range: wave ran on other SIMD (not matched)
|
||||
|
||||
Returns list of (traced_simd, blobs) tuples, all_packets, all_ops, max_valuinst_count.
|
||||
"""
|
||||
all_ops = set()
|
||||
all_runs: list[tuple[int, list[bytes]]] = []
|
||||
all_packets = []
|
||||
max_valuinst = 0
|
||||
SQTT_SIMD_SEL.value = 0 # only trace SIMD 0
|
||||
for _ in range(max_attempts):
|
||||
blobs = run_asm_sqtt(instructions)
|
||||
packets = decode_all_blobs(blobs)
|
||||
# get ops and valuinst from all SIMDs
|
||||
ops = set()
|
||||
valuinst_count = 0
|
||||
for simd in [0, 1, 2, 3]:
|
||||
ops.update(get_inst_ops(packets, traced_simd=simd))
|
||||
valuinst_count = max(valuinst_count, count_valuinst(packets, traced_simd=simd))
|
||||
all_runs.append((0, blobs))
|
||||
all_packets.append(packets)
|
||||
all_ops.update(ops)
|
||||
max_valuinst = max(max_valuinst, valuinst_count)
|
||||
return all_runs, all_packets, all_ops, max_valuinst
|
||||
|
||||
def discover_all_instops() -> tuple[dict[int, set[str]], dict[str, Exception], dict[str, int]]:
|
||||
"""Run all instruction tests and collect InstOp values."""
|
||||
discovered: dict[int, set[str]] = {}
|
||||
failures: dict[str, Exception] = {}
|
||||
valuinst_tests: dict[str, int] = {} # tests that produced VALUINST packets
|
||||
|
||||
for test_name, (instr_name, instructions) in INSTRUCTION_TESTS.items():
|
||||
try:
|
||||
all_runs, _, ops, valuinst_count = run_with_retry(instructions)
|
||||
|
||||
for op in ops:
|
||||
if op not in discovered:
|
||||
discovered[op] = set()
|
||||
discovered[op].add(f"{test_name}")
|
||||
|
||||
if valuinst_count > 0:
|
||||
valuinst_tests[test_name] = valuinst_count
|
||||
|
||||
if DEBUG >= 2:
|
||||
print(f"\n{'─'*60}")
|
||||
print(f"{test_name} ({instr_name}): ops={[hex(op) for op in sorted(ops)]}")
|
||||
|
||||
# collect wave patterns from traced SIMD runs (group by exact timing)
|
||||
patterns: dict[tuple, list] = {} # pattern (types + timing) -> list of (wave_packets, t0)
|
||||
for traced_simd, blobs in all_runs:
|
||||
for blob in blobs:
|
||||
packets = decode_all_blobs([blob])
|
||||
wave_packets = get_wave_packets(packets)
|
||||
# only include runs where wave ran on traced SIMD
|
||||
ws = next((p for p in wave_packets if isinstance(p, WAVESTART)), None)
|
||||
if ws and ws.simd == traced_simd and wave_packets:
|
||||
t0 = wave_packets[0]._time
|
||||
# pattern includes types AND normalized timing
|
||||
pattern = tuple((type(p).__name__, p._time - t0) for p in wave_packets)
|
||||
if pattern not in patterns:
|
||||
patterns[pattern] = []
|
||||
patterns[pattern].append((wave_packets, t0))
|
||||
|
||||
if patterns:
|
||||
counts = {p: len(runs) for p, runs in patterns.items()}
|
||||
most_common = max(counts, key=counts.get)
|
||||
count = counts[most_common]
|
||||
total = sum(counts.values())
|
||||
print(f"\n=== most common pattern ({count}/{total} runs) ===")
|
||||
wave_packets, t0 = patterns[most_common][0]
|
||||
last_time = t0
|
||||
for p in wave_packets:
|
||||
print(format_packet(p, last_time, t0))
|
||||
last_time = p._time
|
||||
if len(patterns) > 1:
|
||||
print(f"\n variations: {len(patterns)} unique timing patterns")
|
||||
|
||||
if DEBUG >= 3:
|
||||
for traced_simd, blobs in all_runs:
|
||||
print(f"\n=== traced simd={traced_simd} ===")
|
||||
print_blobs(blobs, wave_only=False)
|
||||
if DEBUG >= 1:
|
||||
status = colored("✓", "green") if ops else (colored("V", "cyan") if valuinst_count > 0 else colored("∅", "yellow"))
|
||||
ops_str = ", ".join(hex(op) for op in sorted(ops)) if ops else "none"
|
||||
valuinst_str = f" valuinst={valuinst_count}" if valuinst_count > 0 and not ops else ""
|
||||
print(f" {status} {test_name:25s} ops=[{ops_str}]{valuinst_str}")
|
||||
|
||||
except Exception as e:
|
||||
failures[test_name] = e
|
||||
if DEBUG >= 1:
|
||||
print(f" {colored('✗', 'red')} {test_name:25s} FAILED: {e}")
|
||||
|
||||
return discovered, failures, valuinst_tests
|
||||
|
||||
|
||||
def print_summary(discovered: dict[int, set[str]], failures: dict[str, Exception], valuinst_tests: dict[str, int]) -> None:
|
||||
"""Print discovery summary."""
|
||||
known_ops = {e.value for e in InstOp}
|
||||
discovered_ops = set(discovered.keys())
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("DISCOVERED INSTOP VALUES")
|
||||
print("=" * 60)
|
||||
|
||||
for op in sorted(discovered_ops):
|
||||
try:
|
||||
name = InstOp(op).name
|
||||
status = colored("known", "green")
|
||||
except ValueError:
|
||||
name = f"UNKNOWN"
|
||||
status = colored("NEW!", "yellow")
|
||||
|
||||
sources = ", ".join(sorted(discovered[op]))
|
||||
print(f" 0x{op:02x} {name:20s} ({status}) <- {sources}")
|
||||
|
||||
# VALUINST tests (instructions that only produce VALUINST, not INST packets)
|
||||
valuinst_only = {k: v for k, v in valuinst_tests.items() if not any(k in tests for tests in discovered.values())}
|
||||
if valuinst_only:
|
||||
print("\n" + "=" * 60)
|
||||
print(colored("VALUINST-ONLY INSTRUCTIONS (no InstOp, use VALUINST packet)", "cyan"))
|
||||
print("=" * 60)
|
||||
for test_name, count in sorted(valuinst_only.items()):
|
||||
print(f" {test_name}: {count} VALUINST packets")
|
||||
|
||||
# Missing from enum
|
||||
missing = known_ops - discovered_ops
|
||||
if missing:
|
||||
print("\n" + "=" * 60)
|
||||
print("ENUM VALUES NOT DISCOVERED")
|
||||
print("=" * 60)
|
||||
print("(need memory ops: SMEM, VMEM, LDS)")
|
||||
for op in sorted(missing):
|
||||
print(f" 0x{op:02x} {InstOp(op).name}")
|
||||
|
||||
# New values to add
|
||||
new_ops = discovered_ops - known_ops
|
||||
if new_ops:
|
||||
print("\n" + "=" * 60)
|
||||
print(colored("NEW INSTOP VALUES TO ADD TO ENUM", "yellow"))
|
||||
print("=" * 60)
|
||||
for op in sorted(new_ops):
|
||||
sources = ", ".join(sorted(discovered[op]))
|
||||
print(f" {op:#04x}: \"{sources}\",")
|
||||
|
||||
# Stats
|
||||
print("\n" + "=" * 60)
|
||||
print("STATISTICS")
|
||||
print("=" * 60)
|
||||
print(f" Tests run: {len(INSTRUCTION_TESTS)}")
|
||||
print(f" Tests passed: {len(INSTRUCTION_TESTS) - len(failures)}")
|
||||
print(f" Tests failed: {len(failures)}")
|
||||
print(f" Known ops: {len(known_ops)}")
|
||||
print(f" Discovered: {len(discovered_ops)}")
|
||||
if known_ops:
|
||||
print(f" Coverage: {len(discovered_ops & known_ops)}/{len(known_ops)} ({100*len(discovered_ops & known_ops)//len(known_ops)}%)")
|
||||
print(f" New ops found: {len(new_ops)}")
|
||||
print(f" VALUINST-only: {len(valuinst_only)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("SQTT InstOp Discovery Tool")
|
||||
print("=" * 60)
|
||||
print(f"Testing {len(INSTRUCTION_TESTS)} instruction categories...\n")
|
||||
|
||||
discovered, failures, valuinst_tests = discover_all_instops()
|
||||
print_summary(discovered, failures, valuinst_tests)
|
||||
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python3
|
||||
"""SQTT InstOp discovery from tinygrad-generated kernels.
|
||||
|
||||
Runs various tinygrad operations and captures SQTT traces to find new InstOp values.
|
||||
|
||||
Requires profiling enabled:
|
||||
echo 'profile_standard' | sudo tee /sys/class/drm/card1/device/power_dpm_force_performance_level
|
||||
|
||||
Run with: DEBUG=1 python extra/assembly/amd/test/discover_instops_tensor.py
|
||||
For full traces: DEBUG=2 python extra/assembly/amd/test/discover_instops_tensor.py
|
||||
"""
|
||||
import os
|
||||
os.environ["SQTT"] = "1"
|
||||
os.environ["PROFILE"] = "1"
|
||||
os.environ["SQTT_LIMIT_SE"] = "2" # Force work to traced SE only
|
||||
os.environ["SQTT_TOKEN_EXCLUDE"] = "3784" # Exclude noisy packet types
|
||||
|
||||
from tinygrad import Tensor, dtypes, Device
|
||||
from tinygrad.helpers import DEBUG, colored
|
||||
from tinygrad.runtime.ops_amd import ProfileSQTTEvent, SQTT_SIMD_SEL
|
||||
|
||||
from extra.assembly.amd.sqtt import InstOp, decode, INST, WAVESTART, WAVEEND
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# HELPERS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def get_inst_ops_from_blobs(blobs: list[bytes]) -> set[int]:
|
||||
"""Extract all InstOp values from SQTT blobs."""
|
||||
ops = set()
|
||||
for blob in blobs:
|
||||
packets = decode(blob)
|
||||
in_wave = False
|
||||
for p in packets:
|
||||
if isinstance(p, WAVESTART):
|
||||
in_wave = True
|
||||
if in_wave and isinstance(p, INST):
|
||||
ops.add(p.op if isinstance(p.op, int) else p.op.value)
|
||||
if isinstance(p, WAVEEND):
|
||||
in_wave = False
|
||||
return ops
|
||||
|
||||
def run_and_capture(fn, attempts: int = 5) -> tuple[set[int], list[bytes]]:
|
||||
"""Run a function multiple times and collect SQTT traces."""
|
||||
dev = Device["AMD"]
|
||||
all_ops = set()
|
||||
all_blobs = []
|
||||
SQTT_SIMD_SEL.value = 0
|
||||
|
||||
for _ in range(attempts):
|
||||
dev.profile_events.clear()
|
||||
fn()
|
||||
blobs = [ev.blob for ev in dev.profile_events if isinstance(ev, ProfileSQTTEvent)]
|
||||
ops = get_inst_ops_from_blobs(blobs)
|
||||
all_ops.update(ops)
|
||||
all_blobs.extend(blobs)
|
||||
|
||||
return all_ops, all_blobs
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# TENSOR OPERATIONS TO TEST
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
TENSOR_TESTS: dict[str, tuple[str, callable]] = {
|
||||
# Basic arithmetic
|
||||
"add_f32": ("tensor add float32", lambda: (Tensor.rand(1024) + Tensor.rand(1024)).realize()),
|
||||
"mul_f32": ("tensor mul float32", lambda: (Tensor.rand(1024) * Tensor.rand(1024)).realize()),
|
||||
"sub_f32": ("tensor sub float32", lambda: (Tensor.rand(1024) - Tensor.rand(1024)).realize()),
|
||||
"div_f32": ("tensor div float32", lambda: (Tensor.rand(1024) / (Tensor.rand(1024) + 0.1)).realize()),
|
||||
|
||||
# Transcendental
|
||||
"exp_f32": ("tensor exp float32", lambda: Tensor.rand(1024).exp().realize()),
|
||||
"log_f32": ("tensor log float32", lambda: (Tensor.rand(1024) + 0.1).log().realize()),
|
||||
"sqrt_f32": ("tensor sqrt float32", lambda: Tensor.rand(1024).sqrt().realize()),
|
||||
"sin_f32": ("tensor sin float32", lambda: Tensor.rand(1024).sin().realize()),
|
||||
"cos_f32": ("tensor cos float32", lambda: Tensor.rand(1024).cos().realize()),
|
||||
"tanh_f32": ("tensor tanh float32", lambda: Tensor.rand(1024).tanh().realize()),
|
||||
"sigmoid_f32": ("tensor sigmoid float32", lambda: Tensor.rand(1024).sigmoid().realize()),
|
||||
|
||||
# Reductions
|
||||
"sum_f32": ("tensor sum float32", lambda: Tensor.rand(1024).sum().realize()),
|
||||
"max_f32": ("tensor max float32", lambda: Tensor.rand(1024).max().realize()),
|
||||
"mean_f32": ("tensor mean float32", lambda: Tensor.rand(1024).mean().realize()),
|
||||
|
||||
# Matmul - small
|
||||
"matmul_small": ("matmul 32x32", lambda: (Tensor.rand(32, 32) @ Tensor.rand(32, 32)).realize()),
|
||||
|
||||
# Matmul - medium (might use WMMA)
|
||||
"matmul_medium": ("matmul 128x128", lambda: (Tensor.rand(128, 128) @ Tensor.rand(128, 128)).realize()),
|
||||
|
||||
# Matmul - larger (more likely to use WMMA)
|
||||
"matmul_large": ("matmul 256x256", lambda: (Tensor.rand(256, 256) @ Tensor.rand(256, 256)).realize()),
|
||||
|
||||
# Different dtypes
|
||||
"add_f16": ("tensor add float16", lambda: (Tensor.rand(1024, dtype=dtypes.float16) + Tensor.rand(1024, dtype=dtypes.float16)).realize()),
|
||||
"mul_f16": ("tensor mul float16", lambda: (Tensor.rand(1024, dtype=dtypes.float16) * Tensor.rand(1024, dtype=dtypes.float16)).realize()),
|
||||
"matmul_f16": ("matmul float16 128x128", lambda: (Tensor.rand(128, 128, dtype=dtypes.float16) @ Tensor.rand(128, 128, dtype=dtypes.float16)).realize()),
|
||||
|
||||
# Integer ops
|
||||
"add_i32": ("tensor add int32", lambda: (Tensor.randint(1024, high=1000) + Tensor.randint(1024, high=1000)).realize()),
|
||||
"mul_i32": ("tensor mul int32", lambda: (Tensor.randint(1024, high=100) * Tensor.randint(1024, high=100)).realize()),
|
||||
|
||||
# Bitwise
|
||||
"and_i32": ("tensor bitwise and", lambda: (Tensor.randint(1024, high=1000) & Tensor.randint(1024, high=1000)).realize()),
|
||||
"or_i32": ("tensor bitwise or", lambda: (Tensor.randint(1024, high=1000) | Tensor.randint(1024, high=1000)).realize()),
|
||||
"xor_i32": ("tensor bitwise xor", lambda: (Tensor.randint(1024, high=1000) ^ Tensor.randint(1024, high=1000)).realize()),
|
||||
"lshift_i32": ("tensor left shift", lambda: (Tensor.randint(1024, high=1000) << 2).realize()),
|
||||
"rshift_i32": ("tensor right shift", lambda: (Tensor.randint(1024, high=1000) >> 2).realize()),
|
||||
|
||||
# Comparisons
|
||||
"cmp_eq": ("tensor compare eq", lambda: (Tensor.rand(1024) == 0.5).realize()),
|
||||
"cmp_lt": ("tensor compare lt", lambda: (Tensor.rand(1024) < 0.5).realize()),
|
||||
"cmp_gt": ("tensor compare gt", lambda: (Tensor.rand(1024) > 0.5).realize()),
|
||||
|
||||
# Where/select
|
||||
"where": ("tensor where", lambda: Tensor.rand(1024).where(Tensor.rand(1024), Tensor.rand(1024)).realize()),
|
||||
|
||||
# Reshaping/movement (may not generate interesting ops but let's check)
|
||||
"reshape": ("tensor reshape", lambda: Tensor.rand(32, 32).reshape(16, 64).realize()),
|
||||
"permute": ("tensor permute", lambda: Tensor.rand(32, 32).permute(1, 0).contiguous().realize()),
|
||||
"expand": ("tensor expand", lambda: Tensor.rand(1, 32).expand(32, 32).contiguous().realize()),
|
||||
|
||||
# Pad
|
||||
"pad": ("tensor pad", lambda: Tensor.rand(30, 30).pad(((1, 1), (1, 1))).realize()),
|
||||
|
||||
# Conv2D - small
|
||||
"conv2d_small": ("conv2d 3x3", lambda: Tensor.rand(1, 3, 32, 32).conv2d(Tensor.rand(8, 3, 3, 3)).realize()),
|
||||
|
||||
# Conv2D - larger
|
||||
"conv2d_medium": ("conv2d 3x3 64ch", lambda: Tensor.rand(1, 64, 32, 32).conv2d(Tensor.rand(64, 64, 3, 3)).realize()),
|
||||
|
||||
# Pooling
|
||||
"maxpool": ("max pool 2x2", lambda: Tensor.rand(1, 3, 32, 32).max_pool2d((2, 2)).realize()),
|
||||
"avgpool": ("avg pool 2x2", lambda: Tensor.rand(1, 3, 32, 32).avg_pool2d((2, 2)).realize()),
|
||||
|
||||
# Softmax
|
||||
"softmax": ("softmax", lambda: Tensor.rand(32, 128).softmax().realize()),
|
||||
|
||||
# LayerNorm-like
|
||||
"layernorm": ("layer norm pattern", lambda: _layernorm(Tensor.rand(32, 128))),
|
||||
|
||||
# BatchNorm-like
|
||||
"batchnorm": ("batch norm pattern", lambda: _batchnorm(Tensor.rand(1, 64, 32, 32))),
|
||||
|
||||
# Dropout-like (during training)
|
||||
"dropout": ("dropout pattern", lambda: (Tensor.rand(1024) * (Tensor.rand(1024) > 0.5)).realize()),
|
||||
|
||||
# Cast operations
|
||||
"cast_f32_to_f16": ("cast f32->f16", lambda: Tensor.rand(1024).cast(dtypes.float16).realize()),
|
||||
"cast_f16_to_f32": ("cast f16->f32", lambda: Tensor.rand(1024, dtype=dtypes.float16).cast(dtypes.float32).realize()),
|
||||
"cast_f32_to_i32": ("cast f32->i32", lambda: (Tensor.rand(1024) * 100).cast(dtypes.int32).realize()),
|
||||
"cast_i32_to_f32": ("cast i32->f32", lambda: Tensor.randint(1024, high=100).cast(dtypes.float32).realize()),
|
||||
|
||||
# Clamp/clip
|
||||
"clamp": ("tensor clamp", lambda: Tensor.rand(1024).clamp(0.2, 0.8).realize()),
|
||||
|
||||
# Abs/neg
|
||||
"abs": ("tensor abs", lambda: (Tensor.rand(1024) - 0.5).abs().realize()),
|
||||
"neg": ("tensor neg", lambda: (-Tensor.rand(1024)).realize()),
|
||||
|
||||
# Reciprocal
|
||||
"recip": ("tensor reciprocal", lambda: (Tensor.rand(1024) + 0.1).reciprocal().realize()),
|
||||
|
||||
# Power
|
||||
"pow2": ("tensor pow 2", lambda: (Tensor.rand(1024) ** 2).realize()),
|
||||
"pow3": ("tensor pow 3", lambda: (Tensor.rand(1024) ** 3).realize()),
|
||||
}
|
||||
|
||||
def _layernorm(x: Tensor) -> Tensor:
|
||||
"""Simple layer normalization pattern."""
|
||||
mean = x.mean(axis=-1, keepdim=True)
|
||||
var = ((x - mean) ** 2).mean(axis=-1, keepdim=True)
|
||||
return ((x - mean) / (var + 1e-5).sqrt()).realize()
|
||||
|
||||
def _batchnorm(x: Tensor) -> Tensor:
|
||||
"""Simple batch normalization pattern."""
|
||||
mean = x.mean(axis=(0, 2, 3), keepdim=True)
|
||||
var = ((x - mean) ** 2).mean(axis=(0, 2, 3), keepdim=True)
|
||||
return ((x - mean) / (var + 1e-5).sqrt()).realize()
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# DISCOVERY
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def discover_all_instops() -> tuple[dict[int, set[str]], dict[str, Exception]]:
|
||||
"""Run all tensor tests and collect InstOp values."""
|
||||
discovered: dict[int, set[str]] = {}
|
||||
failures: dict[str, Exception] = {}
|
||||
|
||||
for test_name, (desc, fn) in TENSOR_TESTS.items():
|
||||
try:
|
||||
ops, blobs = run_and_capture(fn)
|
||||
|
||||
for op in ops:
|
||||
if op not in discovered:
|
||||
discovered[op] = set()
|
||||
discovered[op].add(test_name)
|
||||
|
||||
if DEBUG >= 1:
|
||||
status = colored("✓", "green") if ops else colored("∅", "yellow")
|
||||
ops_str = ", ".join(hex(op) for op in sorted(ops)) if ops else "none"
|
||||
print(f" {status} {test_name:25s} [{desc:25s}] ops=[{ops_str}]")
|
||||
|
||||
if DEBUG >= 2 and blobs:
|
||||
# Show first wave trace
|
||||
for blob in blobs[:1]:
|
||||
packets = decode(blob)
|
||||
print(f" First blob: {len(blob)} bytes, {len(packets)} packets")
|
||||
|
||||
except Exception as e:
|
||||
failures[test_name] = e
|
||||
if DEBUG >= 1:
|
||||
print(f" {colored('✗', 'red')} {test_name:25s} FAILED: {e}")
|
||||
|
||||
return discovered, failures
|
||||
|
||||
|
||||
def print_summary(discovered: dict[int, set[str]], failures: dict[str, Exception]) -> None:
|
||||
"""Print discovery summary."""
|
||||
known_ops = {e.value for e in InstOp}
|
||||
discovered_ops = set(discovered.keys())
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("DISCOVERED INSTOP VALUES FROM TINYGRAD KERNELS")
|
||||
print("=" * 70)
|
||||
|
||||
for op in sorted(discovered_ops):
|
||||
try:
|
||||
name = InstOp(op).name
|
||||
status = colored("known", "green")
|
||||
except ValueError:
|
||||
name = "UNKNOWN"
|
||||
status = colored("NEW!", "yellow")
|
||||
|
||||
sources = ", ".join(sorted(discovered[op]))
|
||||
# Truncate sources if too long
|
||||
if len(sources) > 60:
|
||||
sources = sources[:57] + "..."
|
||||
print(f" 0x{op:02x} {name:20s} ({status}) <- {sources}")
|
||||
|
||||
# New values to add
|
||||
new_ops = discovered_ops - known_ops
|
||||
if new_ops:
|
||||
print("\n" + "=" * 70)
|
||||
print(colored("NEW INSTOP VALUES TO ADD TO ENUM", "yellow"))
|
||||
print("=" * 70)
|
||||
for op in sorted(new_ops):
|
||||
sources = ", ".join(sorted(discovered[op]))
|
||||
print(f" 0x{op:02x}: discovered from [{sources}]")
|
||||
|
||||
# Missing from enum (not discovered)
|
||||
missing = known_ops - discovered_ops
|
||||
if missing:
|
||||
print("\n" + "=" * 70)
|
||||
print("ENUM VALUES NOT DISCOVERED (may need specific instruction patterns)")
|
||||
print("=" * 70)
|
||||
for op in sorted(missing):
|
||||
print(f" 0x{op:02x} {InstOp(op).name}")
|
||||
|
||||
# Stats
|
||||
print("\n" + "=" * 70)
|
||||
print("STATISTICS")
|
||||
print("=" * 70)
|
||||
print(f" Tests run: {len(TENSOR_TESTS)}")
|
||||
print(f" Tests passed: {len(TENSOR_TESTS) - len(failures)}")
|
||||
print(f" Tests failed: {len(failures)}")
|
||||
print(f" Known ops: {len(known_ops)}")
|
||||
print(f" Discovered: {len(discovered_ops)}")
|
||||
if known_ops:
|
||||
coverage = len(discovered_ops & known_ops)
|
||||
print(f" Coverage: {coverage}/{len(known_ops)} ({100*coverage//len(known_ops)}%)")
|
||||
print(f" New ops found: {len(new_ops)}")
|
||||
|
||||
if failures:
|
||||
print("\n" + "=" * 70)
|
||||
print("FAILURES")
|
||||
print("=" * 70)
|
||||
for name, e in failures.items():
|
||||
print(f" {name}: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 70)
|
||||
print("SQTT InstOp Discovery from Tinygrad Kernels")
|
||||
print("=" * 70)
|
||||
print(f"Testing {len(TENSOR_TESTS)} tensor operations...\n")
|
||||
|
||||
discovered, failures = discover_all_instops()
|
||||
print_summary(discovered, failures)
|
||||
@@ -5,30 +5,11 @@ Set USE_HW=1 to run on both emulator and real hardware, comparing results.
|
||||
"""
|
||||
import ctypes, os, struct
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
|
||||
from extra.assembly.amd.dsl import RawImm
|
||||
from extra.assembly.amd.emu import WaveState, run_asm, set_valid_mem_ranges
|
||||
from extra.assembly.amd.dsl import NULL, SCC, VCC_LO, VCC_HI, EXEC_LO, EXEC_HI, M0
|
||||
from extra.assembly.amd.pcode import _i32, _f32
|
||||
|
||||
# For backwards compatibility with tests using SrcEnum.NULL etc.
|
||||
class SrcEnum:
|
||||
NULL = NULL
|
||||
VCC_LO = VCC_LO
|
||||
VCC_HI = VCC_HI
|
||||
EXEC_LO = EXEC_LO
|
||||
EXEC_HI = EXEC_HI
|
||||
SCC = SCC
|
||||
M0 = M0
|
||||
POS_HALF = 0.5
|
||||
NEG_HALF = -0.5
|
||||
POS_ONE = 1.0
|
||||
NEG_ONE = -1.0
|
||||
POS_TWO = 2.0
|
||||
NEG_TWO = -2.0
|
||||
POS_FOUR = 4.0
|
||||
NEG_FOUR = -4.0
|
||||
|
||||
VCC = VCC_LO # For VOP3SD sdst field (VCC_LO is exported from dsl)
|
||||
VCC = SrcEnum.VCC_LO # For VOP3SD sdst field
|
||||
USE_HW = os.environ.get("USE_HW", "0") == "1"
|
||||
FLOAT_TOLERANCE = 1e-5
|
||||
|
||||
@@ -58,29 +39,29 @@ def get_prologue_epilogue(n_lanes: int) -> tuple[list, list]:
|
||||
prologue.append(v_mov_b32_e32(v[i], 0))
|
||||
for i in range(N_SGPRS):
|
||||
prologue.append(s_mov_b32(s[i], 0))
|
||||
prologue.append(s_mov_b32(VCC_LO, 0))
|
||||
prologue.append(s_mov_b32(s[SrcEnum.VCC_LO - 128], 0))
|
||||
|
||||
epilogue = [
|
||||
s_mov_b32(s[90], VCC_LO),
|
||||
s_mov_b32(s[90], SrcEnum.VCC_LO),
|
||||
s_cselect_b32(s[91], 1, 0),
|
||||
s_load_b64(s[92:93], s[80:81], 0, soffset=NULL),
|
||||
s_waitcnt(0), # simm16=0 waits for all
|
||||
s_load_b64(s[92:93], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_lshlrev_b32_e32(v[240], 2, v[255]),
|
||||
]
|
||||
for i in range(N_VGPRS):
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[i], saddr=s[92:93], offset=i * WAVE_SIZE * 4))
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[i], saddr=s[92], offset=i * WAVE_SIZE * 4))
|
||||
epilogue.append(v_mov_b32_e32(v[241], 0))
|
||||
epilogue.append(v_cmp_eq_u32_e32(v[255], v[241]))
|
||||
epilogue.append(s_and_saveexec_b32(s[94], VCC_LO))
|
||||
epilogue.append(s_and_saveexec_b32(s[94], SrcEnum.VCC_LO))
|
||||
epilogue.append(v_mov_b32_e32(v[240], 0))
|
||||
for i in range(N_SGPRS):
|
||||
epilogue.append(v_mov_b32_e32(v[243], s[i]))
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[243], saddr=s[92:93], offset=VGPR_BYTES + i * 4))
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[243], saddr=s[92], offset=VGPR_BYTES + i * 4))
|
||||
epilogue.append(v_mov_b32_e32(v[243], s[90]))
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[243], saddr=s[92:93], offset=VGPR_BYTES + SGPR_BYTES))
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[243], saddr=s[92], offset=VGPR_BYTES + SGPR_BYTES))
|
||||
epilogue.append(v_mov_b32_e32(v[243], s[91]))
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[243], saddr=s[92:93], offset=VGPR_BYTES + SGPR_BYTES + 4))
|
||||
epilogue.append(s_mov_b32(EXEC_LO, s[94]))
|
||||
epilogue.append(global_store_b32(addr=v[240], data=v[243], saddr=s[92], offset=VGPR_BYTES + SGPR_BYTES + 4))
|
||||
epilogue.append(s_mov_b32(s[SrcEnum.EXEC_LO - 128], s[94]))
|
||||
epilogue.append(s_endpgm())
|
||||
return prologue, epilogue
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class TestDS2Addr(unittest.TestCase):
|
||||
v_mov_b32_e32(v[1], s[0]),
|
||||
DS(DSOp.DS_STORE_2ADDR_B32, addr=v[10], data0=v[0], data1=v[1], vdst=v[0], offset0=0, offset1=1),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[2:3], offset0=0, offset1=1),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[2], offset0=0, offset1=1),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -39,9 +39,9 @@ class TestDS2Addr(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0x9ABCDEF0),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
DS(DSOp.DS_STORE_2ADDR_B64, addr=v[10], data0=v[0:1], data1=v[2:3], vdst=v[0], offset0=0, offset1=2),
|
||||
DS(DSOp.DS_STORE_2ADDR_B64, addr=v[10], data0=v[0], data1=v[2], vdst=v[0], offset0=0, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B64, addr=v[10], vdst=v[4:7], offset0=0, offset1=2),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B64, addr=v[10], vdst=v[4], offset0=0, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -64,7 +64,7 @@ class TestDS2AddrMore(unittest.TestCase):
|
||||
v_mov_b32_e32(v[1], s[2]),
|
||||
DS(DSOp.DS_STORE_2ADDR_B32, addr=v[10], data0=v[0], data1=v[1], vdst=v[0], offset0=2, offset1=5),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[2:3], offset0=2, offset1=5),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[2], offset0=2, offset1=5),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -88,7 +88,7 @@ class TestDS2AddrMore(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
ds_store_b32(addr=v[10], data0=v[0], offset0=12),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B64, addr=v[10], vdst=v[4:7], offset0=0, offset1=1),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B64, addr=v[10], vdst=v[4], offset0=0, offset1=1),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -109,7 +109,7 @@ class TestDS2AddrMore(unittest.TestCase):
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[2], 0xDEADBEEF),
|
||||
v_mov_b32_e32(v[4], s[2]), # Sentinel
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[2:3], offset0=0, offset1=1),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[2], offset0=0, offset1=1),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -125,11 +125,11 @@ class TestDS2AddrMore(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
s_mov_b32(s[2], 0xCAFEBABE),
|
||||
v_mov_b32_e32(v[1], s[2]),
|
||||
ds_store_b64(addr=v[10], data0=v[0:1], offset0=0),
|
||||
ds_store_b64(addr=v[10], data0=v[0], offset0=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[2], 0x12345678),
|
||||
v_mov_b32_e32(v[4], s[2]), # Sentinel
|
||||
ds_load_b64(addr=v[10], vdst=v[2:3], offset0=0),
|
||||
ds_load_b64(addr=v[10], vdst=v[2], offset0=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -452,7 +452,7 @@ class TestDS2AddrStride64(unittest.TestCase):
|
||||
v_mov_b32_e32(v[1], s[0]),
|
||||
DS(DSOp.DS_STORE_2ADDR_STRIDE64_B32, addr=v[10], data0=v[0], data1=v[1], vdst=v[0], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B32, addr=v[10], vdst=v[2:3], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B32, addr=v[10], vdst=v[2], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -471,9 +471,9 @@ class TestDS2AddrStride64(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0x9ABCDEF0),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
DS(DSOp.DS_STORE_2ADDR_STRIDE64_B64, addr=v[10], data0=v[0:1], data1=v[2:3], vdst=v[0], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_STORE_2ADDR_STRIDE64_B64, addr=v[10], data0=v[0], data1=v[2], vdst=v[0], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B64, addr=v[10], vdst=v[4:7], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B64, addr=v[10], vdst=v[4], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -496,9 +496,9 @@ class TestDS2AddrStride64(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0xBBBBBBBB),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
DS(DSOp.DS_STOREXCHG_2ADDR_RTN_B32, addr=v[10], data0=v[2], data1=v[3], vdst=v[4:5], offset0=0, offset1=1),
|
||||
DS(DSOp.DS_STOREXCHG_2ADDR_RTN_B32, addr=v[10], data0=v[2], data1=v[3], vdst=v[4], offset0=0, offset1=1),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[6:7], offset0=0, offset1=1),
|
||||
DS(DSOp.DS_LOAD_2ADDR_B32, addr=v[10], vdst=v[6], offset0=0, offset1=1),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -516,15 +516,15 @@ class TestDS2AddrStride64(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[0]), # initial low
|
||||
s_mov_b32(s[0], 0xCAFEBABE),
|
||||
v_mov_b32_e32(v[1], s[0]), # initial high
|
||||
DS(DSOp.DS_STORE_B64, addr=v[10], data0=v[0:1], vdst=v[0], offset0=0),
|
||||
DS(DSOp.DS_STORE_B64, addr=v[10], data0=v[0], vdst=v[0], offset0=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[0], 0x12345678),
|
||||
v_mov_b32_e32(v[2], s[0]), # new low
|
||||
s_mov_b32(s[0], 0x9ABCDEF0),
|
||||
v_mov_b32_e32(v[3], s[0]), # new high
|
||||
DS(DSOp.DS_STOREXCHG_RTN_B64, addr=v[10], data0=v[2:3], vdst=v[4:5], offset0=0),
|
||||
DS(DSOp.DS_STOREXCHG_RTN_B64, addr=v[10], data0=v[2], vdst=v[4], offset0=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_B64, addr=v[10], vdst=v[6:7], offset0=0),
|
||||
DS(DSOp.DS_LOAD_B64, addr=v[10], vdst=v[6], offset0=0),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -541,9 +541,9 @@ class TestDS2AddrStride64(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
s_mov_b32(s[0], 0x22222222),
|
||||
v_mov_b32_e32(v[1], s[0]),
|
||||
DS(DSOp.DS_STORE_2ADDR_STRIDE64_B64, addr=v[10], data0=v[0:1], data1=v[0:1], vdst=v[0], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_STORE_2ADDR_STRIDE64_B64, addr=v[10], data0=v[0], data1=v[0], vdst=v[0], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B64, addr=v[10], vdst=v[2:5], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B64, addr=v[10], vdst=v[2], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -566,9 +566,9 @@ class TestDS2AddrStride64(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0xBBBBBBBB),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
DS(DSOp.DS_STOREXCHG_2ADDR_STRIDE64_RTN_B32, addr=v[10], data0=v[2], data1=v[3], vdst=v[4:5], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_STOREXCHG_2ADDR_STRIDE64_RTN_B32, addr=v[10], data0=v[2], data1=v[3], vdst=v[4], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B32, addr=v[10], vdst=v[6:7], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_LOAD_2ADDR_STRIDE64_B32, addr=v[10], vdst=v[6], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -585,13 +585,13 @@ class TestDS2AddrStride64(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
s_mov_b32(s[0], 0x22222222),
|
||||
v_mov_b32_e32(v[1], s[0]),
|
||||
DS(DSOp.DS_STORE_2ADDR_STRIDE64_B64, addr=v[10], data0=v[0:1], data1=v[0:1], vdst=v[0], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_STORE_2ADDR_STRIDE64_B64, addr=v[10], data0=v[0], data1=v[0], vdst=v[0], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[0], 0xAAAAAAAA),
|
||||
v_mov_b32_e32(v[6], s[0]),
|
||||
s_mov_b32(s[0], 0xBBBBBBBB),
|
||||
v_mov_b32_e32(v[7], s[0]),
|
||||
DS(DSOp.DS_STOREXCHG_2ADDR_STRIDE64_RTN_B64, addr=v[10], data0=v[6:7], data1=v[6:7], vdst=v[8:11], offset0=1, offset1=2),
|
||||
DS(DSOp.DS_STOREXCHG_2ADDR_STRIDE64_RTN_B64, addr=v[10], data0=v[6], data1=v[6], vdst=v[8], offset0=1, offset1=2),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
|
||||
@@ -11,7 +11,7 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
def _make_test(self, setup_instrs, atomic_instr, check_fn, test_offset=2000):
|
||||
"""Helper to create atomic test instructions."""
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
@@ -30,12 +30,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 100),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 50),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_ADD_U32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_ADD_U32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 100)
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -46,12 +46,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 0xAAAAAAAA),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 0xBBBBBBBB),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_SWAP_B32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_SWAP_B32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 0xAAAAAAAA)
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -62,12 +62,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 0xFF00FF00),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 0xFFFF0000),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_AND_B32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_AND_B32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 0xFF00FF00)
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -78,12 +78,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 0x00FF0000),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 0x0000FF00),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_OR_B32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_OR_B32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 0x00FF0000)
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -94,12 +94,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 10),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 100), # threshold
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_INC_U32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_INC_U32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 10)
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -110,12 +110,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 10),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 100),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_DEC_U32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_DEC_U32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 10)
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -126,12 +126,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 100),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 30),
|
||||
v_mov_b32_e32(v[3], s[0]), # sub 30
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_SUB_U32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_SUB_U32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 100, "v4 should have old value (100)")
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -142,12 +142,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 0xAAAAAAAA),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 0xFFFFFFFF),
|
||||
v_mov_b32_e32(v[3], s[0]), # XOR mask
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_XOR_B32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_XOR_B32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 0xAAAAAAAA, "v4 should have old value")
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -158,12 +158,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 100),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 50),
|
||||
v_mov_b32_e32(v[3], s[0]), # compare value (smaller)
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_MIN_U32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_MIN_U32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 100, "v4 should have old value (100)")
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -174,12 +174,12 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 50),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 100),
|
||||
v_mov_b32_e32(v[3], s[0]), # compare value (larger)
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_MAX_U32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_MAX_U32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 50, "v4 should have old value (50)")
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -193,14 +193,14 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0xCAFEBABE),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
global_store_b64(addr=v[0:1], data=v[2:3], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b64(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
# Threshold: 0xFFFFFFFF_FFFFFFFF
|
||||
s_mov_b32(s[0], 0xFFFFFFFF),
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
v_mov_b32_e32(v[5], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_INC_U64, addr=v[0:1], data=v[4:5], vdst=v[6:7], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_INC_U64, addr=v[0], data=v[4], vdst=v[6], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][6], 0xDEADBEEF, "v6 should have old value low dword")
|
||||
self.assertEqual(st.vgpr[0][7], 0xCAFEBABE, "v7 should have old value high dword")
|
||||
@@ -214,14 +214,14 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0x22222222),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
global_store_b64(addr=v[0:1], data=v[2:3], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b64(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 0x00000001), # add 1
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
s_mov_b32(s[0], 0x00000000),
|
||||
v_mov_b32_e32(v[5], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_ADD_U64, addr=v[0:1], data=v[4:5], vdst=v[6:7], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_ADD_U64, addr=v[0], data=v[4], vdst=v[6], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][6], 0x11111111, "v6 should have old value low")
|
||||
self.assertEqual(st.vgpr[0][7], 0x22222222, "v7 should have old value high")
|
||||
@@ -235,14 +235,14 @@ class TestFlatAtomic(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0xBBBBBBBB),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
global_store_b64(addr=v[0:1], data=v[2:3], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b64(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 0xCCCCCCCC),
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
s_mov_b32(s[0], 0xDDDDDDDD),
|
||||
v_mov_b32_e32(v[5], s[0]),
|
||||
]
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_SWAP_B64, addr=v[0:1], data=v[4:5], vdst=v[6:7], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(FLATOp.FLAT_ATOMIC_SWAP_B64, addr=v[0], data=v[4], vdst=v[6], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][6], 0xAAAAAAAA, "v6 should have old value low")
|
||||
self.assertEqual(st.vgpr[0][7], 0xBBBBBBBB, "v7 should have old value high")
|
||||
@@ -256,15 +256,15 @@ class TestFlatLoad(unittest.TestCase):
|
||||
"""FLAT_LOAD_B32 loads 32-bit value correctly."""
|
||||
TEST_OFFSET = 2000
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
s_mov_b32(s[0], 0xDEADBEEF),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
FLAT(FLATOp.FLAT_LOAD_B32, addr=v[0:1], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
FLAT(FLATOp.FLAT_LOAD_B32, addr=v[0], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
@@ -278,7 +278,7 @@ class TestFlatLoad(unittest.TestCase):
|
||||
"""FLAT_LOAD_B64 loads 64-bit value correctly."""
|
||||
TEST_OFFSET = 2000
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
@@ -286,9 +286,9 @@ class TestFlatLoad(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0xCAFEBABE),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
global_store_b64(addr=v[0:1], data=v[2:3], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b64(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
FLAT(FLATOp.FLAT_LOAD_B64, addr=v[0:1], vdst=v[4:5], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
FLAT(FLATOp.FLAT_LOAD_B64, addr=v[0], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
@@ -303,7 +303,7 @@ class TestFlatLoad(unittest.TestCase):
|
||||
"""FLAT_LOAD_B96 loads 96-bit (3 dword) value correctly."""
|
||||
TEST_OFFSET = 2000
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
@@ -313,9 +313,9 @@ class TestFlatLoad(unittest.TestCase):
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
s_mov_b32(s[0], 0x33333333),
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
global_store_b96(addr=v[0:1], data=v[2:4], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b96(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
FLAT(FLATOp.FLAT_LOAD_B96, addr=v[0:1], vdst=v[5:7], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
FLAT(FLATOp.FLAT_LOAD_B96, addr=v[0], vdst=v[5], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
@@ -331,7 +331,7 @@ class TestFlatLoad(unittest.TestCase):
|
||||
"""FLAT_LOAD_B128 loads 128-bit value correctly."""
|
||||
TEST_OFFSET = 2000
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
@@ -343,9 +343,9 @@ class TestFlatLoad(unittest.TestCase):
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
s_mov_b32(s[0], 0x44444444),
|
||||
v_mov_b32_e32(v[5], s[0]),
|
||||
global_store_b128(addr=v[0:1], data=v[2:5], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b128(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
FLAT(FLATOp.FLAT_LOAD_B128, addr=v[0:1], vdst=v[6:9], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
FLAT(FLATOp.FLAT_LOAD_B128, addr=v[0], vdst=v[6], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
|
||||
@@ -11,7 +11,7 @@ class TestGlobalAtomic(unittest.TestCase):
|
||||
def _make_test(self, setup_instrs, atomic_instr, check_fn, test_offset=2000):
|
||||
"""Helper to create atomic test instructions."""
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
@@ -30,12 +30,12 @@ class TestGlobalAtomic(unittest.TestCase):
|
||||
setup = [
|
||||
s_mov_b32(s[0], 100),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 50),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
]
|
||||
atomic = GLOBAL(GLOBALOp.GLOBAL_ATOMIC_ADD_U32, addr=v[0:1], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(GLOBALOp.GLOBAL_ATOMIC_ADD_U32, addr=v[0], data=v[3], vdst=v[4], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1, seg=2)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][4], 100)
|
||||
self._make_test(setup, atomic, check, TEST_OFFSET)
|
||||
@@ -48,14 +48,14 @@ class TestGlobalAtomic(unittest.TestCase):
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
s_mov_b32(s[0], 0x00000000),
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
global_store_b64(addr=v[0:1], data=v[2:3], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b64(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[0], 0x00000001),
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
s_mov_b32(s[0], 0x00000000),
|
||||
v_mov_b32_e32(v[5], s[0]),
|
||||
]
|
||||
atomic = GLOBAL(GLOBALOp.GLOBAL_ATOMIC_ADD_U64, addr=v[0:1], data=v[4:5], vdst=v[6:7], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1)
|
||||
atomic = FLAT(GLOBALOp.GLOBAL_ATOMIC_ADD_U64, addr=v[0], data=v[4], vdst=v[6], saddr=SrcEnum.NULL, offset=TEST_OFFSET, glc=1, seg=2)
|
||||
def check(st):
|
||||
self.assertEqual(st.vgpr[0][6], 0xFFFFFFFF)
|
||||
self.assertEqual(st.vgpr[0][7], 0x00000000)
|
||||
@@ -69,7 +69,7 @@ class TestGlobalLoad(unittest.TestCase):
|
||||
"""GLOBAL_LOAD_B96 loads 96-bit value correctly."""
|
||||
TEST_OFFSET = 2000
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
@@ -79,9 +79,9 @@ class TestGlobalLoad(unittest.TestCase):
|
||||
v_mov_b32_e32(v[3], s[0]),
|
||||
s_mov_b32(s[0], 0xCCCCCCCC),
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
global_store_b96(addr=v[0:1], data=v[2:4], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b96(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_B96, addr=v[0:1], vdst=v[5:7], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_B96, addr=v[0], vdst=v[5], saddr=SrcEnum.NULL, offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
@@ -97,7 +97,7 @@ class TestGlobalLoad(unittest.TestCase):
|
||||
"""GLOBAL_LOAD_B128 loads 128-bit value correctly."""
|
||||
TEST_OFFSET = 2000
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
@@ -109,9 +109,9 @@ class TestGlobalLoad(unittest.TestCase):
|
||||
v_mov_b32_e32(v[4], s[0]),
|
||||
s_mov_b32(s[0], 0x9ABCDEF0),
|
||||
v_mov_b32_e32(v[5], s[0]),
|
||||
global_store_b128(addr=v[0:1], data=v[2:5], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b128(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_B128, addr=v[0:1], vdst=v[6:9], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_B128, addr=v[0], vdst=v[6], saddr=SrcEnum.NULL, offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
@@ -132,16 +132,16 @@ class TestGlobalStore(unittest.TestCase):
|
||||
"""GLOBAL_STORE_B64 stores 8 bytes from v[n:n+1] to memory."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[4], 0xDEADBEEF),
|
||||
s_mov_b32(s[5], 0xCAFEBABE),
|
||||
v_mov_b32_e32(v[2], s[4]),
|
||||
v_mov_b32_e32(v[3], s[5]),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
global_store_b64(addr=v[0], data=v[2:3], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
global_store_b64(addr=v[0], data=v[2], saddr=s[2], offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_B64, addr=v[0], vdst=v[4:5], data=v[4:5], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_B64, addr=v[0], vdst=v[4], data=v[4], saddr=s[2], offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[4]),
|
||||
v_mov_b32_e32(v[1], v[5]),
|
||||
@@ -160,17 +160,17 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
"""GLOBAL_LOAD_D16_HI_B16 must preserve low 16 bits of destination."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
v_mov_b32_e32(v[1], s[3]),
|
||||
s_mov_b32(s[4], 0xCAFE),
|
||||
v_mov_b32_e32(v[2], s[4]),
|
||||
global_store_b16(addr=v[0:1], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
global_store_b16(addr=v[0], data=v[2], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[4], 0x0000BEEF),
|
||||
v_mov_b32_e32(v[3], s[4]),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[0:1], vdst=v[3], data=v[3], saddr=SrcEnum.NULL, offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[0], vdst=v[3], data=v[3], saddr=SrcEnum.NULL, offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[3]),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
@@ -185,17 +185,17 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
"""GLOBAL_LOAD_D16_HI_B16 where data field differs from vdst."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[4], 0xCAFE),
|
||||
v_mov_b32_e32(v[2], s[4]),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
global_store_b16(addr=v[3], data=v[2], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
global_store_b16(addr=v[3], data=v[2], saddr=s[2], offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[4], 0x0000DEAD),
|
||||
v_mov_b32_e32(v[0], s[4]), # data field - should NOT affect result
|
||||
v_mov_b32_e32(v[1], 0), # vdst - low bits should be preserved
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[0], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[0], saddr=s[2], offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[1]),
|
||||
s_mov_b32(s[2], 0),
|
||||
@@ -209,19 +209,19 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
"""GLOBAL_LOAD_D16_HI_U8 where data field differs from vdst."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[4], 0xAB),
|
||||
v_mov_b32_e32(v[2], s[4]),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
global_store_b8(addr=v[3], data=v[2], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
global_store_b8(addr=v[3], data=v[2], saddr=s[2], offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[4], 0x0000DEAD),
|
||||
v_mov_b32_e32(v[4], s[4]), # data field
|
||||
s_mov_b32(s[4], 0x0000BEEF),
|
||||
v_mov_b32_e32(v[5], s[4]), # vdst
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_D16_HI_U8, addr=v[3], vdst=v[5], data=v[4], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_D16_HI_U8, addr=v[3], vdst=v[5], data=v[4], saddr=s[2], offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[5]),
|
||||
s_mov_b32(s[2], 0),
|
||||
@@ -235,15 +235,15 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
"""GLOBAL_LOAD_D16_HI_B16 with same register for addr and vdst, addr value=0."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[4], 0xCAFE),
|
||||
v_mov_b32_e32(v[2], s[4]),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
global_store_b16(addr=v[3], data=v[2], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
global_store_b16(addr=v[3], data=v[2], saddr=s[2], offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[1], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[1], saddr=s[2], offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[1]),
|
||||
s_mov_b32(s[2], 0),
|
||||
@@ -257,13 +257,13 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
"""Exact pattern from tril() failure: data=v0 differs from vdst=v1."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[4], 0x01010101),
|
||||
v_mov_b32_e32(v[10], s[4]),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
global_store_b32(addr=v[3], data=v[10], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[3], data=v[10], saddr=s[2:3], offset=TEST_OFFSET+4),
|
||||
global_store_b32(addr=v[3], data=v[10], saddr=s[2], offset=TEST_OFFSET),
|
||||
global_store_b32(addr=v[3], data=v[10], saddr=s[2], offset=TEST_OFFSET+4),
|
||||
s_waitcnt(vmcnt=0),
|
||||
# Set v[0] to 0x0101 (simulating prior u16 load result)
|
||||
s_mov_b32(s[4], 0x0101),
|
||||
@@ -271,7 +271,7 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
# Set v[1] to 0
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
# Load using v[1] as addr AND vdst, but v[0] as data
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[0], saddr=s[2:3], offset=TEST_OFFSET+6),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[0], saddr=s[2], offset=TEST_OFFSET+6, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[1]),
|
||||
s_mov_b32(s[2], 0),
|
||||
@@ -286,19 +286,19 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
"""GLOBAL_LOAD_D16_HI_I8 where data field differs from vdst."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[4], 0x80), # negative signed byte = -128
|
||||
v_mov_b32_e32(v[2], s[4]),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
global_store_b8(addr=v[3], data=v[2], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
global_store_b8(addr=v[3], data=v[2], saddr=s[2], offset=TEST_OFFSET),
|
||||
s_waitcnt(vmcnt=0),
|
||||
s_mov_b32(s[4], 0x0000DEAD),
|
||||
v_mov_b32_e32(v[4], s[4]), # data field
|
||||
s_mov_b32(s[4], 0x0000BEEF),
|
||||
v_mov_b32_e32(v[5], s[4]), # vdst
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_D16_HI_I8, addr=v[3], vdst=v[5], data=v[4], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_D16_HI_I8, addr=v[3], vdst=v[5], data=v[4], saddr=s[2], offset=TEST_OFFSET, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[5]),
|
||||
s_mov_b32(s[2], 0),
|
||||
@@ -313,7 +313,7 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
"""Test the exact pattern from tril() kernel that was failing."""
|
||||
TEST_OFFSET = 256
|
||||
instructions = [
|
||||
s_load_b64(s[2:3], s[80:81], 0, soffset=SrcEnum.NULL),
|
||||
s_load_b64(s[2:3], s[80], 0, soffset=SrcEnum.NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
s_mov_b32(s[4], 0x01010101),
|
||||
v_mov_b32_e32(v[10], s[4]),
|
||||
@@ -321,16 +321,16 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
s_mov_b32(s[4], 0x01),
|
||||
v_mov_b32_e32(v[12], s[4]),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
global_store_b64(addr=v[0], data=v[10:11], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
global_store_b8(addr=v[0], data=v[12], saddr=s[2:3], offset=TEST_OFFSET+8),
|
||||
global_store_b64(addr=v[0], data=v[10], saddr=s[2], offset=TEST_OFFSET),
|
||||
global_store_b8(addr=v[0], data=v[12], saddr=s[2], offset=TEST_OFFSET+8),
|
||||
s_waitcnt(vmcnt=0),
|
||||
|
||||
v_mov_b32_e32(v[2], 0),
|
||||
v_mov_b32_e32(v[1], 0),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_U16, addr=v[2], vdst=v[0], data=v[0], saddr=s[2:3], offset=TEST_OFFSET+3),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[1], saddr=s[2:3], offset=TEST_OFFSET+6),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_U8, addr=v[2], vdst=v[3], data=v[3], saddr=s[2:3], offset=TEST_OFFSET),
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_U8, addr=v[2], vdst=v[4], data=v[4], saddr=s[2:3], offset=TEST_OFFSET+8),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_U16, addr=v[2], vdst=v[0], data=v[0], saddr=s[2], offset=TEST_OFFSET+3, seg=2),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_D16_HI_B16, addr=v[1], vdst=v[1], data=v[1], saddr=s[2], offset=TEST_OFFSET+6, seg=2),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_U8, addr=v[2], vdst=v[3], data=v[3], saddr=s[2], offset=TEST_OFFSET, seg=2),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_U8, addr=v[2], vdst=v[4], data=v[4], saddr=s[2], offset=TEST_OFFSET+8, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
|
||||
v_and_b32_e32(v[5], 0xffff, v[0]),
|
||||
@@ -339,10 +339,10 @@ class TestD16HiLoads(unittest.TestCase):
|
||||
v_or_b32_e32(v[0], v[3], v[0]),
|
||||
v_or_b32_e32(v[1], v[5], v[1]),
|
||||
|
||||
global_store_b64(addr=v[2], data=v[0:1], saddr=s[2:3], offset=TEST_OFFSET+16),
|
||||
global_store_b64(addr=v[2], data=v[0], saddr=s[2], offset=TEST_OFFSET+16),
|
||||
s_waitcnt(vmcnt=0),
|
||||
|
||||
GLOBAL(GLOBALOp.GLOBAL_LOAD_B64, addr=v[2], vdst=v[6:7], data=v[6:7], saddr=s[2:3], offset=TEST_OFFSET+16),
|
||||
FLAT(GLOBALOp.GLOBAL_LOAD_B64, addr=v[2], vdst=v[6], data=v[6], saddr=s[2], offset=TEST_OFFSET+16, seg=2),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[0], v[6]),
|
||||
v_mov_b32_e32(v[1], v[7]),
|
||||
|
||||
@@ -142,8 +142,8 @@ class TestBranch(unittest.TestCase):
|
||||
"""S_CBRANCH_VCCNZ should only check VCC_LO in wave32."""
|
||||
instructions = [
|
||||
# Set VCC_LO = 0, VCC_HI = 1
|
||||
s_mov_b32(VCC_LO, 0),
|
||||
s_mov_b32(VCC_HI, 1),
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 0),
|
||||
s_mov_b32(s[SrcEnum.VCC_HI - 128], 1),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
# If VCC_HI is incorrectly used, branch will be taken
|
||||
s_cbranch_vccnz(1), # Skip next instruction if VCC != 0
|
||||
@@ -156,8 +156,8 @@ class TestBranch(unittest.TestCase):
|
||||
"""S_CBRANCH_VCCZ should only check VCC_LO in wave32."""
|
||||
instructions = [
|
||||
# Set VCC_LO = 1, VCC_HI = 0
|
||||
s_mov_b32(VCC_LO, 1),
|
||||
s_mov_b32(VCC_HI, 0),
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 1),
|
||||
s_mov_b32(s[SrcEnum.VCC_HI - 128], 0),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
# If VCC_HI is incorrectly used, branch will be taken
|
||||
s_cbranch_vccz(1), # Skip next instruction if VCC == 0
|
||||
@@ -169,7 +169,7 @@ class TestBranch(unittest.TestCase):
|
||||
def test_cbranch_vccnz_branches_on_vcc_lo(self):
|
||||
"""S_CBRANCH_VCCNZ branches when VCC_LO is non-zero."""
|
||||
instructions = [
|
||||
s_mov_b32(VCC_LO, 1),
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 1),
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
s_cbranch_vccnz(1), # Skip next instruction if VCC != 0
|
||||
v_mov_b32_e32(v[0], 42), # This should be skipped
|
||||
@@ -195,6 +195,15 @@ class Test64BitLiterals(unittest.TestCase):
|
||||
result = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
self.assertAlmostEqual(result, -4294967296.0, places=5)
|
||||
|
||||
def test_64bit_literal_positive_encoding(self):
|
||||
"""64-bit instruction encodes large positive literals correctly."""
|
||||
large_val = 0x12345678
|
||||
inst = v_add_f64(v[2], v[0], large_val)
|
||||
self.assertIsNotNone(inst._literal, "Literal should be set")
|
||||
actual_lit = (inst._literal >> 32) & 0xffffffff
|
||||
self.assertEqual(actual_lit, large_val, f"Literal should be {large_val:#x}, got {actual_lit:#x}")
|
||||
|
||||
|
||||
class TestSCCBehavior(unittest.TestCase):
|
||||
"""Tests for SCC condition code behavior."""
|
||||
|
||||
@@ -329,53 +338,5 @@ class TestSignedArithmetic(unittest.TestCase):
|
||||
self.assertEqual(st.sgpr[7], ((dividend * 2) + 1) & 0xFFFFFFFF)
|
||||
|
||||
|
||||
class Test64BitCompare(unittest.TestCase):
|
||||
"""Tests for 64-bit scalar compare instructions."""
|
||||
|
||||
def test_s_cmp_eq_u64_equal(self):
|
||||
"""S_CMP_EQ_U64: comparing equal 64-bit values sets SCC=1."""
|
||||
val = 0x123456789ABCDEF0
|
||||
instructions = [
|
||||
s_mov_b32(s[0], val & 0xFFFFFFFF),
|
||||
s_mov_b32(s[1], val >> 32),
|
||||
s_mov_b32(s[2], val & 0xFFFFFFFF),
|
||||
s_mov_b32(s[3], val >> 32),
|
||||
s_cmp_eq_u64(s[0:1], s[2:3]),
|
||||
s_cselect_b32(s[4], 1, 0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.scc, 1)
|
||||
self.assertEqual(st.sgpr[4], 1)
|
||||
|
||||
def test_s_cmp_eq_u64_different_upper_bits(self):
|
||||
"""S_CMP_EQ_U64: values differing only in upper 32 bits are not equal."""
|
||||
# This is the bug case - if only lower 32 bits are compared, these would be equal
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0), # lower 32 bits of value 0
|
||||
s_mov_b32(s[1], 0), # upper 32 bits of value 0
|
||||
s_mov_b32(s[2], 0), # lower 32 bits of 0x100000000
|
||||
s_mov_b32(s[3], 1), # upper 32 bits of 0x100000000
|
||||
s_cmp_eq_u64(s[0:1], s[2:3]),
|
||||
s_cselect_b32(s[4], 1, 0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.scc, 0, "0 != 0x100000000, SCC should be 0")
|
||||
self.assertEqual(st.sgpr[4], 0)
|
||||
|
||||
def test_s_cmp_lg_u64_different(self):
|
||||
"""S_CMP_LG_U64: different 64-bit values sets SCC=1."""
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0),
|
||||
s_mov_b32(s[1], 0), # s[0:1] = 0
|
||||
s_mov_b32(s[2], 0),
|
||||
s_mov_b32(s[3], 1), # s[2:3] = 0x100000000
|
||||
s_cmp_lg_u64(s[0:1], s[2:3]),
|
||||
s_cselect_b32(s[4], 1, 0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.scc, 1, "0 != 0x100000000, SCC should be 1")
|
||||
self.assertEqual(st.sgpr[4], 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -526,16 +526,16 @@ class TestExp(unittest.TestCase):
|
||||
class TestReadFirstLane(unittest.TestCase):
|
||||
"""Tests for V_READFIRSTLANE_B32."""
|
||||
|
||||
def _readfirstlane(self, sdst, vsrc):
|
||||
def _readfirstlane(self, sdst_idx, vsrc):
|
||||
"""Helper to create V_READFIRSTLANE_B32 with SGPR destination."""
|
||||
return v_readfirstlane_b32_e32(sdst, vsrc)
|
||||
return VOP1(VOP1Op.V_READFIRSTLANE_B32, vdst=RawImm(sdst_idx), src0=vsrc)
|
||||
|
||||
def test_v_readfirstlane_b32_basic(self):
|
||||
"""V_READFIRSTLANE_B32 reads from the first active lane."""
|
||||
instructions = [
|
||||
v_lshlrev_b32_e32(v[0], 2, v[255]),
|
||||
v_add_nc_u32_e32(v[0], 1000, v[0]),
|
||||
self._readfirstlane(s[0], v[0]),
|
||||
self._readfirstlane(0, v[0]),
|
||||
v_mov_b32_e32(v[1], s[0]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
@@ -547,7 +547,7 @@ class TestReadFirstLane(unittest.TestCase):
|
||||
instructions = [
|
||||
v_lshlrev_b32_e32(v[7], 5, v[255]),
|
||||
v_add_nc_u32_e32(v[7], 200, v[7]),
|
||||
self._readfirstlane(s[0], v[7]),
|
||||
self._readfirstlane(0, v[7]),
|
||||
v_mov_b32_e32(v[8], s[0]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
|
||||
@@ -46,7 +46,7 @@ class TestBasicArithmetic(unittest.TestCase):
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], 2.0),
|
||||
v_mov_b32_e32(v[1], 4.0),
|
||||
v_fmaak_f32_e32(v[2], v[0], v[1], literal=0x3f800000),
|
||||
v_fmaak_f32_e32(v[2], v[0], v[1], 0x3f800000),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertAlmostEqual(i2f(st.vgpr[0][2]), 9.0, places=5)
|
||||
@@ -56,7 +56,7 @@ class TestBasicArithmetic(unittest.TestCase):
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], 2.0),
|
||||
v_mov_b32_e32(v[1], 1.0),
|
||||
v_fmamk_f32_e32(v[2], v[0], v[1], literal=0x40800000),
|
||||
v_fmamk_f32_e32(v[2], v[0], 0x40800000, v[1]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertAlmostEqual(i2f(st.vgpr[0][2]), 9.0, places=5)
|
||||
@@ -66,7 +66,7 @@ class TestBasicArithmetic(unittest.TestCase):
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], 4.0),
|
||||
v_mov_b32_e32(v[1], 1.0),
|
||||
v_fmamk_f32_e32(v[2], v[0], v[1], literal=f2i(0.5)),
|
||||
v_fmamk_f32_e32(v[2], v[0], f2i(0.5), v[1]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertAlmostEqual(i2f(st.vgpr[0][2]), 3.0, places=5)
|
||||
@@ -244,7 +244,7 @@ class TestF16Ops(unittest.TestCase):
|
||||
s_mov_b32(s[1], 0x4200), # f16 3.0
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_fmaak_f16_e32(v[2], v[0], v[1], literal=0x3c00), # + f16 1.0
|
||||
v_fmaak_f16_e32(v[2], v[0], v[1], 0x3c00), # + f16 1.0
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = st.vgpr[0][2] & 0xffff
|
||||
@@ -347,7 +347,7 @@ class TestCndmask(unittest.TestCase):
|
||||
def test_v_cndmask_b16_select_src0(self):
|
||||
"""V_CNDMASK_B16 selects src0 when VCC bit is 0."""
|
||||
instructions = [
|
||||
s_mov_b32(VCC_LO, 0), # VCC = 0
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 0), # VCC = 0
|
||||
s_mov_b32(s[0], 0x3c00), # f16 1.0
|
||||
s_mov_b32(s[1], 0x4000), # f16 2.0
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
@@ -361,7 +361,7 @@ class TestCndmask(unittest.TestCase):
|
||||
def test_v_cndmask_b16_select_src1(self):
|
||||
"""V_CNDMASK_B16 selects src1 when VCC bit is 1."""
|
||||
instructions = [
|
||||
s_mov_b32(VCC_LO, 1), # VCC = 1
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 1), # VCC = 1
|
||||
s_mov_b32(s[0], 0x3c00), # f16 1.0
|
||||
s_mov_b32(s[1], 0x4000), # f16 2.0
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
@@ -381,7 +381,7 @@ class TestCndmask(unittest.TestCase):
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
s_mov_b32(s[2], 0xDEAD0000), # v2 initial: hi=0xDEAD, lo=0
|
||||
v_mov_b32_e32(v[2], s[2]),
|
||||
s_mov_b32(VCC_LO, 0), # vcc = 0, select src0
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 0), # vcc = 0, select src0
|
||||
# opsel=0b1011: bit0=src0 hi, bit1=src1 hi, bit3=dst hi
|
||||
VOP3(VOP3Op.V_CNDMASK_B16, vdst=v[2], src0=v[0], src1=v[1], src2=SrcEnum.VCC_LO, opsel=0b1011),
|
||||
]
|
||||
|
||||
@@ -178,7 +178,7 @@ class TestDivFmas(unittest.TestCase):
|
||||
def test_div_fmas_f32_no_scale(self):
|
||||
"""V_DIV_FMAS_F32: VCC=0 -> normal FMA."""
|
||||
instructions = [
|
||||
s_mov_b32(VCC_LO, 0),
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 0),
|
||||
v_mov_b32_e32(v[0], 2.0),
|
||||
v_mov_b32_e32(v[1], 3.0),
|
||||
v_mov_b32_e32(v[2], 1.0),
|
||||
@@ -190,7 +190,7 @@ class TestDivFmas(unittest.TestCase):
|
||||
def test_div_fmas_f32_scale_up(self):
|
||||
"""V_DIV_FMAS_F32: VCC=1 with S2 >= 2.0 -> scale by 2^+64."""
|
||||
instructions = [
|
||||
s_mov_b32(VCC_LO, 1),
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 1),
|
||||
v_mov_b32_e32(v[0], 1.0),
|
||||
v_mov_b32_e32(v[1], 1.0),
|
||||
v_mov_b32_e32(v[2], 2.0),
|
||||
@@ -203,7 +203,7 @@ class TestDivFmas(unittest.TestCase):
|
||||
def test_div_fmas_f32_scale_down(self):
|
||||
"""V_DIV_FMAS_F32: VCC=1 with S2 < 2.0 -> scale by 2^-64."""
|
||||
instructions = [
|
||||
s_mov_b32(VCC_LO, 1),
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 1),
|
||||
v_mov_b32_e32(v[0], 2.0),
|
||||
v_mov_b32_e32(v[1], 3.0),
|
||||
v_mov_b32_e32(v[2], 1.0),
|
||||
@@ -216,7 +216,7 @@ class TestDivFmas(unittest.TestCase):
|
||||
def test_div_fmas_f32_per_lane_vcc(self):
|
||||
"""V_DIV_FMAS_F32: different VCC per lane with S2 < 2.0."""
|
||||
instructions = [
|
||||
s_mov_b32(VCC_LO, 0b0101),
|
||||
s_mov_b32(s[SrcEnum.VCC_LO - 128], 0b0101),
|
||||
v_mov_b32_e32(v[0], 1.0),
|
||||
v_mov_b32_e32(v[1], 1.0),
|
||||
v_mov_b32_e32(v[2], 1.0),
|
||||
@@ -493,7 +493,7 @@ class TestMad64(unittest.TestCase):
|
||||
s_mov_b32(s[1], 4),
|
||||
v_mov_b32_e32(v[2], 5),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
v_mad_u64_u32(v[4:5], SrcEnum.NULL, s[0], s[1], v[2:3]),
|
||||
v_mad_u64_u32(v[4], SrcEnum.NULL, s[0], s[1], v[2]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result_lo = st.vgpr[0][4]
|
||||
@@ -508,7 +508,7 @@ class TestMad64(unittest.TestCase):
|
||||
s_mov_b32(s[1], 2),
|
||||
v_mov_b32_e32(v[2], 0),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
v_mad_u64_u32(v[4:5], SrcEnum.NULL, s[0], s[1], v[2:3]),
|
||||
v_mad_u64_u32(v[4], SrcEnum.NULL, s[0], s[1], v[2]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result_lo = st.vgpr[0][4]
|
||||
@@ -520,8 +520,8 @@ class TestMad64(unittest.TestCase):
|
||||
class TestLaneOps(unittest.TestCase):
|
||||
"""Tests for lane operations (readlane, writelane)."""
|
||||
|
||||
def _readlane(self, sdst, vsrc, lane_idx):
|
||||
return v_readlane_b32(sdst, vsrc, lane_idx)
|
||||
def _readlane(self, sdst_idx, vsrc, lane_idx):
|
||||
return VOP3(VOP3Op.V_READLANE_B32, vdst=RawImm(sdst_idx), src0=vsrc, src1=lane_idx)
|
||||
|
||||
def test_v_readlane_b32_basic(self):
|
||||
"""V_READLANE_B32 reads a value from a specific lane's VGPR."""
|
||||
@@ -529,7 +529,7 @@ class TestLaneOps(unittest.TestCase):
|
||||
v_lshlrev_b32_e32(v[0], 1, v[255]),
|
||||
v_lshlrev_b32_e32(v[1], 3, v[255]),
|
||||
v_add_nc_u32_e32(v[0], v[0], v[1]),
|
||||
self._readlane(s[0], v[0], 2),
|
||||
self._readlane(0, v[0], 2),
|
||||
v_mov_b32_e32(v[2], s[0]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
@@ -541,7 +541,7 @@ class TestLaneOps(unittest.TestCase):
|
||||
instructions = [
|
||||
v_lshlrev_b32_e32(v[0], 2, v[255]), # v0 = lane_id * 4
|
||||
v_add_nc_u32_e32(v[0], 100, v[0]), # v0 = 100 + lane_id * 4
|
||||
self._readlane(s[0], v[0], 0), # s0 = lane 0's v0 = 100
|
||||
self._readlane(0, v[0], 0), # s0 = lane 0's v0 = 100
|
||||
v_mov_b32_e32(v[1], s[0]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
@@ -553,7 +553,7 @@ class TestLaneOps(unittest.TestCase):
|
||||
instructions = [
|
||||
v_lshlrev_b32_e32(v[0], 2, v[255]), # v0 = lane_id * 4
|
||||
v_add_nc_u32_e32(v[0], 100, v[0]), # v0 = 100 + lane_id * 4
|
||||
self._readlane(s[0], v[0], 3), # s0 = lane 3's v0 = 112
|
||||
self._readlane(0, v[0], 3), # s0 = lane 3's v0 = 112
|
||||
v_mov_b32_e32(v[1], s[0]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
@@ -565,7 +565,7 @@ class TestLaneOps(unittest.TestCase):
|
||||
instructions = [
|
||||
v_lshlrev_b32_e32(v[5], 3, v[255]), # v5 = lane_id * 8
|
||||
v_add_nc_u32_e32(v[5], 50, v[5]), # v5 = 50 + lane_id * 8
|
||||
self._readlane(s[0], v[5], 1), # s0 = lane 1's v5 = 58
|
||||
self._readlane(0, v[5], 1), # s0 = lane 1's v5 = 58
|
||||
v_mov_b32_e32(v[6], s[0]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
@@ -592,7 +592,7 @@ class TestLaneOps(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], 0),
|
||||
s_mov_b32(s[0], 0xdeadbeef),
|
||||
v_writelane_b32(v[0], s[0], 1), # Write to lane 1
|
||||
self._readlane(s[1], v[0], 1), # Read back from lane 1 into s1
|
||||
self._readlane(1, v[0], 1), # Read back from lane 1 into s1
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
@@ -603,12 +603,12 @@ class TestLaneOps(unittest.TestCase):
|
||||
"""Simulate a wave reduction using readlane - common WMMA/reduction pattern."""
|
||||
instructions = [
|
||||
v_add_nc_u32_e32(v[0], 1, v[255]), # v0 = lane_id + 1 (1, 2, 3, 4)
|
||||
self._readlane(s[0], v[0], 0), # s0 = 1
|
||||
self._readlane(s[1], v[0], 1), # s1 = 2
|
||||
self._readlane(0, v[0], 0), # s0 = 1
|
||||
self._readlane(1, v[0], 1), # s1 = 2
|
||||
s_add_u32(s[0], s[0], s[1]), # s0 = 3
|
||||
self._readlane(s[1], v[0], 2), # s1 = 3
|
||||
self._readlane(1, v[0], 2), # s1 = 3
|
||||
s_add_u32(s[0], s[0], s[1]), # s0 = 6
|
||||
self._readlane(s[1], v[0], 3), # s1 = 4
|
||||
self._readlane(1, v[0], 3), # s1 = 4
|
||||
s_add_u32(s[0], s[0], s[1]), # s0 = 10
|
||||
v_mov_b32_e32(v[1], s[0]), # Broadcast sum to all lanes
|
||||
]
|
||||
@@ -711,7 +711,7 @@ class TestLaneOps(unittest.TestCase):
|
||||
v_mov_b32_e32(v[8], 0), # Initialize v8 = 0
|
||||
s_mov_b32(s[0], 0xABCD1234),
|
||||
v_writelane_b32(v[8], s[0], 2), # Write to lane 2's v8
|
||||
self._readlane(s[1], v[8], 2), # Read back from lane 2's v8 into s1
|
||||
self._readlane(1, v[8], 2), # Read back from lane 2's v8 into s1
|
||||
v_mov_b32_e32(v[1], s[1]), # Broadcast to all lanes
|
||||
]
|
||||
st = run_program(instructions, n_lanes=4)
|
||||
@@ -741,12 +741,12 @@ class TestLaneOps(unittest.TestCase):
|
||||
s_mov_b32(s[0], 40),
|
||||
v_writelane_b32(v[6], s[0], 3), # lane 3 gets 40
|
||||
# Now read them all back and sum
|
||||
self._readlane(s[0], v[6], 0), # s0 = 10
|
||||
self._readlane(s[1], v[6], 1), # s1 = 20
|
||||
self._readlane(0, v[6], 0), # s0 = 10
|
||||
self._readlane(1, v[6], 1), # s1 = 20
|
||||
s_add_u32(s[0], s[0], s[1]), # s0 = 30
|
||||
self._readlane(s[1], v[6], 2), # s1 = 30
|
||||
self._readlane(1, v[6], 2), # s1 = 30
|
||||
s_add_u32(s[0], s[0], s[1]), # s0 = 60
|
||||
self._readlane(s[1], v[6], 3), # s1 = 40
|
||||
self._readlane(1, v[6], 3), # s1 = 40
|
||||
s_add_u32(s[0], s[0], s[1]), # s0 = 100
|
||||
v_mov_b32_e32(v[7], s[0]), # Broadcast sum to all lanes
|
||||
]
|
||||
@@ -861,7 +861,7 @@ class TestF16Modifiers(unittest.TestCase):
|
||||
s_mov_b32(s[1], 0x38000000), # v1 = {hi=0.5, lo=0.0}
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
# v_fmac_f16 v0.h, literal(0.318...), v1.l: D.h = D.h + S0 * S1 = 0.5 + 0.318 * 0.0 = 0.5
|
||||
v_fmac_f16_e32(v[0].h, 0x3518, v[1]),
|
||||
VOP2(VOP2Op.V_FMAC_F16, vdst=RawImm(128), src0=RawImm(255), vsrc1=RawImm(1), literal=0x3518),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
v0 = st.vgpr[0][0]
|
||||
@@ -901,7 +901,7 @@ class TestF64Ops(unittest.TestCase):
|
||||
s_mov_b32(s[1], one_f64 >> 32),
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_add_f64(v[2:3], v[0:1], SrcEnum.POS_ONE), # 1.0 + 1.0 = 2.0
|
||||
v_add_f64(v[2:4], v[0:2], SrcEnum.POS_ONE), # 1.0 + 1.0 = 2.0
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][2] | (st.vgpr[0][3] << 32))
|
||||
@@ -920,7 +920,7 @@ class TestF64Ops(unittest.TestCase):
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_mov_b32_e32(v[2], s[2]),
|
||||
v_mov_b32_e32(v[3], s[3]),
|
||||
v_mul_f64(v[4:5], v[0:1], v[2:3]),
|
||||
v_mul_f64(v[4:6], v[0:2], v[2:4]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][4] | (st.vgpr[0][5] << 32))
|
||||
@@ -936,7 +936,7 @@ class TestF64Ops(unittest.TestCase):
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
s_mov_b32(s[2], 0xDEADBEEF),
|
||||
v_mov_b32_e32(v[3], s[2]), # Canary in v3
|
||||
v_cvt_i32_f64_e32(v[2], v[0:1]),
|
||||
v_cvt_i32_f64_e32(v[2], v[0:2]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.vgpr[0][2], 0xffffffff, "-1.0 converts to -1")
|
||||
@@ -952,7 +952,7 @@ class TestF64Ops(unittest.TestCase):
|
||||
s_mov_b32(s[1], val_bits >> 32),
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_ldexp_f64(v[2:3], v[0:1], 0xffffffe0), # -32
|
||||
v_ldexp_f64(v[2:4], v[0:2], 0xffffffe0), # -32
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][2] | (st.vgpr[0][3] << 32))
|
||||
@@ -964,8 +964,8 @@ class TestF64Ops(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], two_f64 & 0xffffffff),
|
||||
s_mov_b32(s[1], two_f64 >> 32),
|
||||
v_frexp_mant_f64_e32(v[0:1], s[0:1]),
|
||||
v_frexp_exp_i32_f64_e32(v[2], s[0:1]),
|
||||
v_frexp_mant_f64_e32(v[0:2], s[0:2]),
|
||||
v_frexp_exp_i32_f64_e32(v[2], s[0:2]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
mant = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -988,7 +988,7 @@ class TestF64Ops(unittest.TestCase):
|
||||
s_mov_b32(s[3], one_f64 >> 32),
|
||||
v_mov_b32_e32(v[2], s[2]),
|
||||
v_mov_b32_e32(v[3], s[3]),
|
||||
VOP3SD(VOP3SDOp.V_DIV_SCALE_F64, vdst=v[4:5], sdst=s[10:11], src0=v[0:1], src1=v[0:1], src2=v[2:3]),
|
||||
VOP3SD(VOP3SDOp.V_DIV_SCALE_F64, vdst=v[4], sdst=s[10], src0=v[0], src1=v[0], src2=v[2]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][4] | (st.vgpr[0][5] << 32))
|
||||
@@ -1003,14 +1003,14 @@ class TestF64Ops(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], val & 0xffffffff),
|
||||
s_mov_b32(s[1], (val >> 32) & 0xffffffff),
|
||||
v_trunc_f64_e32(v[0:1], s[0:1]),
|
||||
v_ldexp_f64(v[2:3], v[0:1], 0xffffffe0), # -32
|
||||
v_floor_f64_e32(v[2:3], v[2:3]),
|
||||
v_trunc_f64_e32(v[0:2], s[0:2]),
|
||||
v_ldexp_f64(v[2:4], v[0:2], 0xffffffe0), # -32
|
||||
v_floor_f64_e32(v[2:4], v[2:4]),
|
||||
s_mov_b32(s[2], f2i64(-4294967296.0) & 0xffffffff),
|
||||
s_mov_b32(s[3], f2i64(-4294967296.0) >> 32),
|
||||
v_fma_f64(v[0:1], s[2:3], v[2:3], v[0:1]),
|
||||
v_cvt_u32_f64_e32(v[4], v[0:1]),
|
||||
v_cvt_i32_f64_e32(v[5], v[2:3]),
|
||||
v_fma_f64(v[0:2], s[2:4], v[2:4], v[0:2]),
|
||||
v_cvt_u32_f64_e32(v[4], v[0:2]),
|
||||
v_cvt_i32_f64_e32(v[5], v[2:4]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
lo = st.vgpr[0][4]
|
||||
@@ -1025,7 +1025,7 @@ class TestF64Ops(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x00000000), # low bits of 1.0
|
||||
s_mov_b32(s[1], 0x3ff00000), # high bits of 1.0
|
||||
v_trig_preop_f64(v[0:1], abs(s[0:1]), 0),
|
||||
v_trig_preop_f64(v[0], abs(s[0]), 0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -1038,9 +1038,9 @@ class TestF64Ops(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x00000000), # low bits of 1.0
|
||||
s_mov_b32(s[1], 0x3ff00000), # high bits of 1.0
|
||||
v_trig_preop_f64(v[0:1], abs(s[0:1]), 0),
|
||||
v_trig_preop_f64(v[2:3], abs(s[0:1]), 1),
|
||||
v_trig_preop_f64(v[4:5], abs(s[0:1]), 2),
|
||||
v_trig_preop_f64(v[0], abs(s[0]), 0),
|
||||
v_trig_preop_f64(v[2], abs(s[0]), 1),
|
||||
v_trig_preop_f64(v[4], abs(s[0]), 2),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
p0 = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -1075,7 +1075,7 @@ class TestF64Ops(unittest.TestCase):
|
||||
v_mov_b32_e32(v[3], s[3]),
|
||||
v_mov_b32_e32(v[4], s[4]),
|
||||
v_mov_b32_e32(v[5], s[5]),
|
||||
v_fma_f64(v[6:7], v[0:1], v[2:3], v[4:5]),
|
||||
v_fma_f64(v[6], v[0], v[2], v[4]),
|
||||
]
|
||||
# run_program with USE_HW=1 will verify exact bit match with hardware
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
@@ -1093,7 +1093,7 @@ class TestMad64More(unittest.TestCase):
|
||||
s_mov_b32(s[1], 1000),
|
||||
v_mov_b32_e32(v[2], 0), # S2 lo
|
||||
v_mov_b32_e32(v[3], 1), # S2 hi = 0x100000000
|
||||
v_mad_u64_u32(v[4:5], SrcEnum.NULL, s[0], s[1], v[2:3]),
|
||||
v_mad_u64_u32(v[4], SrcEnum.NULL, s[0], s[1], v[2]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result_lo = st.vgpr[0][4]
|
||||
@@ -1109,7 +1109,7 @@ class TestMad64More(unittest.TestCase):
|
||||
s_mov_b32(s[1], 0xFFFFFFFF),
|
||||
v_mov_b32_e32(v[2], 0),
|
||||
v_mov_b32_e32(v[3], 0),
|
||||
v_mad_u64_u32(v[4:5], SrcEnum.NULL, s[0], s[1], v[2:3]),
|
||||
v_mad_u64_u32(v[4], SrcEnum.NULL, s[0], s[1], v[2]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result_lo = st.vgpr[0][4]
|
||||
@@ -1185,7 +1185,7 @@ class TestF64LiteralOps(unittest.TestCase):
|
||||
s_mov_b32(s[3], (val_m1 >> 32) & 0xffffffff),
|
||||
v_mov_b32_e32(v[2], s[2]),
|
||||
v_mov_b32_e32(v[3], s[3]),
|
||||
VOP3(VOP3Op.V_FMA_F64, vdst=v[4:5], src0=lit, src1=v[2:3], src2=v[0:1]),
|
||||
VOP3(VOP3Op.V_FMA_F64, vdst=v[4], src0=RawImm(255), src1=v[2], src2=v[0], literal=lit),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][4] | (st.vgpr[0][5] << 32))
|
||||
@@ -1201,7 +1201,7 @@ class TestF64LiteralOps(unittest.TestCase):
|
||||
s_mov_b32(s[1], (val >> 32) & 0xffffffff),
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_ldexp_f64(v[2:3], v[0:1], 0xFFFFFFE0), # -32
|
||||
v_ldexp_f64(v[2:4], v[0:2], 0xFFFFFFE0), # -32
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][2] | (st.vgpr[0][3] << 32))
|
||||
@@ -1220,12 +1220,12 @@ class TestF64ToI64Conversion(unittest.TestCase):
|
||||
s_mov_b32(s[1], (val >> 32) & 0xffffffff),
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_trunc_f64_e32(v[0:1], v[0:1]),
|
||||
v_ldexp_f64(v[2:3], v[0:1], 0xFFFFFFE0),
|
||||
v_floor_f64_e32(v[2:3], v[2:3]),
|
||||
VOP3(VOP3Op.V_FMA_F64, vdst=v[0:1], src0=lit, src1=v[2:3], src2=v[0:1]),
|
||||
v_cvt_u32_f64_e32(v[4], v[0:1]),
|
||||
v_cvt_i32_f64_e32(v[5], v[2:3]),
|
||||
v_trunc_f64_e32(v[0:2], v[0:2]),
|
||||
v_ldexp_f64(v[2:4], v[0:2], 0xFFFFFFE0),
|
||||
v_floor_f64_e32(v[2:4], v[2:4]),
|
||||
VOP3(VOP3Op.V_FMA_F64, vdst=v[0], src0=RawImm(255), src1=v[2], src2=v[0], literal=lit),
|
||||
v_cvt_u32_f64_e32(v[4], v[0:2]),
|
||||
v_cvt_i32_f64_e32(v[5], v[2:4]),
|
||||
]
|
||||
return instructions
|
||||
|
||||
@@ -1281,7 +1281,7 @@ class TestWMMAMore(unittest.TestCase):
|
||||
instructions.append(v_mov_b32_e32(v[i], s[0]))
|
||||
for i in range(8):
|
||||
instructions.append(v_mov_b32_e32(v[i], 0))
|
||||
instructions.append(v_wmma_f32_16x16x16_f16(v[0:7], v[16:23], v[24:31], v[0:7]))
|
||||
instructions.append(v_wmma_f32_16x16x16_f16(v[0], v[16], v[24], v[0]))
|
||||
st = run_program(instructions, n_lanes=32)
|
||||
any_nonzero = any(st.vgpr[lane][0] != 0 for lane in range(32))
|
||||
self.assertTrue(any_nonzero, "WMMA should produce non-zero output")
|
||||
@@ -1385,7 +1385,7 @@ class TestTrigPreop(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x00000000), # low bits of 1.0
|
||||
s_mov_b32(s[1], 0x3ff00000), # high bits of 1.0
|
||||
v_trig_preop_f64(v[0:1], abs(s[0:1]), 0),
|
||||
v_trig_preop_f64(v[0], abs(s[0]), 0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -1396,7 +1396,7 @@ class TestTrigPreop(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x00000000),
|
||||
s_mov_b32(s[1], 0x3ff00000),
|
||||
v_trig_preop_f64(v[0:1], abs(s[0:1]), 1),
|
||||
v_trig_preop_f64(v[0], abs(s[0]), 1),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -1408,7 +1408,7 @@ class TestTrigPreop(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x00000000),
|
||||
s_mov_b32(s[1], 0x3ff00000),
|
||||
v_trig_preop_f64(v[0:1], abs(s[0:1]), 2),
|
||||
v_trig_preop_f64(v[0], abs(s[0]), 2),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -1421,9 +1421,9 @@ class TestTrigPreop(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x00000000),
|
||||
s_mov_b32(s[1], 0x3ff00000),
|
||||
v_trig_preop_f64(v[0:1], abs(s[0:1]), 0),
|
||||
v_trig_preop_f64(v[2:3], abs(s[0:1]), 1),
|
||||
v_trig_preop_f64(v[4:5], abs(s[0:1]), 2),
|
||||
v_trig_preop_f64(v[0], abs(s[0]), 0),
|
||||
v_trig_preop_f64(v[2], abs(s[0]), 1),
|
||||
v_trig_preop_f64(v[4], abs(s[0]), 2),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
p0 = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -1440,7 +1440,7 @@ class TestTrigPreop(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], large_bits & 0xffffffff),
|
||||
s_mov_b32(s[1], (large_bits >> 32) & 0xffffffff),
|
||||
v_trig_preop_f64(v[0:1], abs(s[0:1]), 0),
|
||||
v_trig_preop_f64(v[0], abs(s[0]), 0),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = i642f(st.vgpr[0][0] | (st.vgpr[0][1] << 32))
|
||||
@@ -1455,7 +1455,7 @@ class TestModifierInteractions(unittest.TestCase):
|
||||
"""-|x| should negate the absolute value."""
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], -5.0),
|
||||
VOP3(VOP3Op.V_MUL_F32, vdst=v[1], src0=1.0, src1=v[0], neg=0b10, abs=0b10),
|
||||
VOP3(VOP3Op.V_MUL_F32, vdst=v[1], src0=1.0, src1=v[0], neg=0b10, abs_=0b10),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertAlmostEqual(i2f(st.vgpr[0][1]), -5.0, places=5)
|
||||
@@ -1466,8 +1466,8 @@ class TestModifierInteractions(unittest.TestCase):
|
||||
instructions = [
|
||||
s_mov_b32(s[0], neg_zero),
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
VOP3(VOP3Op.V_MUL_F32, vdst=v[1], src0=1.0, src1=v[0], abs=0b10),
|
||||
VOP3(VOP3Op.V_MUL_F32, vdst=v[2], src0=1.0, src1=v[0], neg=0b10, abs=0b10),
|
||||
VOP3(VOP3Op.V_MUL_F32, vdst=v[1], src0=1.0, src1=v[0], abs_=0b10),
|
||||
VOP3(VOP3Op.V_MUL_F32, vdst=v[2], src0=1.0, src1=v[0], neg=0b10, abs_=0b10),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.vgpr[0][1], 0x00000000, "|(-0.0)| = +0.0")
|
||||
@@ -1636,12 +1636,15 @@ class TestReadlane(unittest.TestCase):
|
||||
|
||||
def test_reduction_pattern(self):
|
||||
"""Test reduction using readlane."""
|
||||
def _readlane(sdst_idx, vsrc, lane_idx):
|
||||
return VOP3(VOP3Op.V_READLANE_B32, vdst=RawImm(sdst_idx), src0=vsrc, src1=lane_idx)
|
||||
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], v[255]),
|
||||
v_readlane_b32(s[0], v[0], 0),
|
||||
v_readlane_b32(s[1], v[0], 1),
|
||||
v_readlane_b32(s[2], v[0], 2),
|
||||
v_readlane_b32(s[3], v[0], 3),
|
||||
_readlane(0, v[0], 0),
|
||||
_readlane(1, v[0], 1),
|
||||
_readlane(2, v[0], 2),
|
||||
_readlane(3, v[0], 3),
|
||||
s_add_u32(s[4], s[0], s[1]),
|
||||
s_add_u32(s[4], s[4], s[2]),
|
||||
s_add_u32(s[4], s[4], s[3]),
|
||||
|
||||
@@ -23,12 +23,14 @@ class TestPackInstructions(unittest.TestCase):
|
||||
|
||||
def test_v_pack_b32_f16_opsel_hi_hi(self):
|
||||
"""V_PACK_B32_F16 with opsel to read high halves."""
|
||||
inst = v_pack_b32_f16(v[2], v[0], v[1])
|
||||
inst._values['opsel'] = 0b0011
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x40003c00), # hi=2.0, lo=1.0
|
||||
s_mov_b32(s[1], 0x44004200), # hi=4.0, lo=3.0
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_pack_b32_f16(v[2], v[0], v[1], opsel=0b0011),
|
||||
inst,
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = st.vgpr[0][2]
|
||||
@@ -81,12 +83,14 @@ class TestPackMore(unittest.TestCase):
|
||||
|
||||
def test_v_pack_b32_f16_opsel_lo_hi(self):
|
||||
"""V_PACK_B32_F16 with opsel=0b0010 to read lo from src0, hi from src1."""
|
||||
inst = v_pack_b32_f16(v[2], v[0], v[1])
|
||||
inst._values['opsel'] = 0b0010
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x40003c00),
|
||||
s_mov_b32(s[1], 0x44004200),
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_pack_b32_f16(v[2], v[0], v[1], opsel=0b0010),
|
||||
inst,
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = st.vgpr[0][2]
|
||||
@@ -94,12 +98,14 @@ class TestPackMore(unittest.TestCase):
|
||||
|
||||
def test_v_pack_b32_f16_opsel_hi_lo(self):
|
||||
"""V_PACK_B32_F16 with opsel=0b0001 to read hi from src0, lo from src1."""
|
||||
inst = v_pack_b32_f16(v[2], v[0], v[1])
|
||||
inst._values['opsel'] = 0b0001
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0x40003c00),
|
||||
s_mov_b32(s[1], 0x44004200),
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
v_mov_b32_e32(v[1], s[1]),
|
||||
v_pack_b32_f16(v[2], v[0], v[1], opsel=0b0001),
|
||||
inst,
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
result = st.vgpr[0][2]
|
||||
@@ -360,7 +366,7 @@ class TestWMMA(unittest.TestCase):
|
||||
instructions.append(v_mov_b32_e32(v[i], s[0]))
|
||||
for i in range(8):
|
||||
instructions.append(v_mov_b32_e32(v[i], 0))
|
||||
instructions.append(v_wmma_f32_16x16x16_f16(v[0:7], v[16:23], v[24:31], v[0:7]))
|
||||
instructions.append(v_wmma_f32_16x16x16_f16(v[0], v[16], v[24], v[0]))
|
||||
st = run_program(instructions, n_lanes=32)
|
||||
expected = f2i(16.0)
|
||||
for lane in range(32):
|
||||
@@ -377,7 +383,7 @@ class TestWMMA(unittest.TestCase):
|
||||
instructions.append(v_mov_b32_e32(v[i], s[0]))
|
||||
for i in range(8):
|
||||
instructions.append(v_mov_b32_e32(v[i], s[1]))
|
||||
instructions.append(v_wmma_f32_16x16x16_f16(v[0:7], v[16:23], v[24:31], v[0:7]))
|
||||
instructions.append(v_wmma_f32_16x16x16_f16(v[0], v[16], v[24], v[0]))
|
||||
st = run_program(instructions, n_lanes=32)
|
||||
expected = f2i(21.0) # 16 + 5
|
||||
for lane in range(32):
|
||||
|
||||
@@ -271,7 +271,7 @@ class TestCmpClassF16(unittest.TestCase):
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], 0), # f16 +0.0
|
||||
s_mov_b32(s[0], 0x1f8), # class mask
|
||||
v_cmp_class_f16_e64(VCC_LO, v[0], s[0]),
|
||||
VOP3(VOP3Op.V_CMP_CLASS_F16, vdst=RawImm(VCC), src0=v[0], src1=s[0]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.vcc & 1, 1, "VCC should be 1 for +zero with VOP3 encoding")
|
||||
@@ -282,7 +282,7 @@ class TestCmpClassF16(unittest.TestCase):
|
||||
s_mov_b32(s[0], 0x3c00), # f16 +1.0
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
s_mov_b32(s[1], 0x1f8), # class mask
|
||||
v_cmp_class_f16_e64(VCC_LO, v[0], s[1]),
|
||||
VOP3(VOP3Op.V_CMP_CLASS_F16, vdst=RawImm(VCC), src0=v[0], src1=s[1]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.vcc & 1, 1, "VCC should be 1 for +1.0 (normal) with mask 0x1f8")
|
||||
@@ -293,7 +293,7 @@ class TestCmpClassF16(unittest.TestCase):
|
||||
s_mov_b32(s[0], 0x7e00), # f16 quiet NaN
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
s_mov_b32(s[1], 0x1f8), # class mask
|
||||
v_cmp_class_f16_e64(VCC_LO, v[0], s[1]),
|
||||
VOP3(VOP3Op.V_CMP_CLASS_F16, vdst=RawImm(VCC), src0=v[0], src1=s[1]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.vcc & 1, 0, "VCC should be 0 for NaN with mask 0x1f8 (no NaN bits)")
|
||||
@@ -304,7 +304,7 @@ class TestCmpClassF16(unittest.TestCase):
|
||||
s_mov_b32(s[0], 0x7c00), # f16 +inf
|
||||
v_mov_b32_e32(v[0], s[0]),
|
||||
s_mov_b32(s[1], 0x1f8), # class mask
|
||||
v_cmp_class_f16_e64(VCC_LO, v[0], s[1]),
|
||||
VOP3(VOP3Op.V_CMP_CLASS_F16, vdst=RawImm(VCC), src0=v[0], src1=s[1]),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.vcc & 1, 0, "VCC should be 0 for +inf with mask 0x1f8 (no inf bits)")
|
||||
@@ -337,15 +337,21 @@ class TestCmpInt(unittest.TestCase):
|
||||
self.assertEqual(st.vcc & 1, 1, "Lo halves should be equal")
|
||||
|
||||
def test_cmp_eq_u16_opsel_hi_hi(self):
|
||||
"""V_CMP_EQ_U16 comparing hi halves with VOP3 opsel."""
|
||||
"""V_CMP_EQ_U16 comparing hi halves with VOP3 opsel.
|
||||
|
||||
VOPC doesn't have opsel, so we use VOP3 form for hi-half comparisons.
|
||||
VOP3 compares write result to SGPR via vdst field.
|
||||
"""
|
||||
instructions = [
|
||||
s_mov_b32(s[2], 0x00051234), # hi=5, lo=0x1234
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
s_mov_b32(s[2], 0x0005ABCD), # hi=5, lo=0xABCD
|
||||
v_mov_b32_e32(v[1], s[2]),
|
||||
v_cmp_eq_u16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
||||
# opsel=3 means compare hi halves, vdst=v[0] actually writes to s[0]
|
||||
VOP3(VOP3Op.V_CMP_EQ_U16, vdst=v[0], src0=v[0], src1=v[1], opsel=3),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
# Result is in sgpr[0], not vcc
|
||||
self.assertEqual(st.sgpr[0] & 1, 1, "Hi halves should be equal: 5==5")
|
||||
|
||||
def test_cmp_eq_u16_opsel_hi_hi_equal(self):
|
||||
@@ -355,7 +361,7 @@ class TestCmpInt(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
s_mov_b32(s[2], 0x12340009), # lo=9, hi=0x1234
|
||||
v_mov_b32_e32(v[1], s[2]),
|
||||
v_cmp_eq_u16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
||||
VOP3(VOP3Op.V_CMP_EQ_U16, vdst=v[0], src0=v[0], src1=v[1], opsel=3),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.sgpr[0] & 1, 1, "hi==hi should be true: 0x1234==0x1234")
|
||||
@@ -367,7 +373,7 @@ class TestCmpInt(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
s_mov_b32(s[2], 0x12340005), # lo=5, hi=0x1234
|
||||
v_mov_b32_e32(v[1], s[2]),
|
||||
v_cmp_gt_u16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
||||
VOP3(VOP3Op.V_CMP_GT_U16, vdst=v[0], src0=v[0], src1=v[1], opsel=3),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
self.assertEqual(st.sgpr[0] & 1, 1, "hi>hi should be true: 0x9999>0x1234")
|
||||
@@ -383,9 +389,11 @@ class TestCmpFloat(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
s_mov_b32(s[2], 0x40000000), # hi=2.0 (f16), lo=0
|
||||
v_mov_b32_e32(v[1], s[2]),
|
||||
v_cmp_lt_f16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
||||
# opsel=3 means read hi halves for both src0 and src1
|
||||
VOP3(VOP3Op.V_CMP_LT_F16, vdst=v[0], src0=v[0], src1=v[1], opsel=3),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
# Result is in sgpr[0]
|
||||
self.assertEqual(st.sgpr[0] & 1, 1, "1.0 < 2.0 should be true")
|
||||
|
||||
def test_v_cmp_gt_f16_vsrc1_hi(self):
|
||||
@@ -395,9 +403,11 @@ class TestCmpFloat(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[2]),
|
||||
s_mov_b32(s[2], 0x3c000000), # hi=1.0 (f16), lo=0
|
||||
v_mov_b32_e32(v[1], s[2]),
|
||||
v_cmp_gt_f16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
||||
# opsel=3 means read hi halves for both src0 and src1
|
||||
VOP3(VOP3Op.V_CMP_GT_F16, vdst=v[0], src0=v[0], src1=v[1], opsel=3),
|
||||
]
|
||||
st = run_program(instructions, n_lanes=1)
|
||||
# Result is in sgpr[0]
|
||||
self.assertEqual(st.sgpr[0] & 1, 1, "2.0 > 1.0 should be true")
|
||||
|
||||
def test_v_cmp_eq_f16_vsrc1_hi_equal(self):
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import unittest
|
||||
import functools
|
||||
from tinygrad import Tensor, Device, dtypes
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.renderer import Estimates
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCompiler
|
||||
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
from extra.assembly.amd.dsl import s, v, Inst
|
||||
|
||||
def assemble_insts(insts:list[Inst], name:str, arch:str, kernarg_size:int=8) -> tuple[UOp, UOp]:
|
||||
kd = {"kernarg_size":kernarg_size, "user_sgpr_kernarg_segment_ptr":1, "next_free_vgpr":8, "next_free_sgpr":8, "wavefront_size32":1}
|
||||
disasm = "\n".join([inst.disasm() for inst in insts])
|
||||
hsasrc = f".text\n.globl {name}\n.p2align 8\n.type fn_name,@function\n{name}:\n{disasm}\ns_code_end\n"
|
||||
hsasrc += f".rodata\n.p2align 6\n.amdhsa_kernel {name}\n"+"\n".join([f".amdhsa_{k} {v}" for k,v in kd.items()])+"\n.end_amdhsa_kernel"
|
||||
binary = HIPCompiler(arch).compile(hsasrc)
|
||||
return UOp(Ops.SOURCE, arg=disasm), UOp(Ops.BINARY, arg=binary)
|
||||
|
||||
def custom_add_one(A:UOp, arch:str) -> UOp:
|
||||
A = A.flatten()
|
||||
assert dtypes.is_float(A.dtype.base), f"buffer dtype must be float32, got {A.dtype}"
|
||||
threads = UOp.special(A.size, "lidx0")
|
||||
insts = [
|
||||
s_load_b64(s[0:1], s[0:1], soffset=NULL),
|
||||
s_waitcnt(lgkmcnt=0),
|
||||
v_lshlrev_b32_e32(v[0], 2, v[0]), # element offset
|
||||
global_load_b32(v[1], v[0], saddr=s[0:1]),
|
||||
s_waitcnt(vmcnt=0),
|
||||
v_mov_b32_e32(v[2], 1.0),
|
||||
v_add_f32_e32(v[1], v[1], v[2]),
|
||||
global_store_b32(addr=v[0], data=v[1], saddr=s[0:1]),
|
||||
s_endpgm(),
|
||||
]
|
||||
sink = UOp.sink(A.base, threads, arg=KernelInfo(name:=f"custom_add_one_{A.size}", estimates=Estimates(ops=A.size, mem=A.size*4*2)))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg="AMD"), UOp(Ops.LINEAR, src=(*sink.src, sink)), *assemble_insts(insts, name, arch)))
|
||||
|
||||
class TestCustomKernel(unittest.TestCase):
|
||||
def test_simple(self):
|
||||
a = Tensor.full((16, 16), 1.).contiguous().realize()
|
||||
a = Tensor.custom_kernel(a, fxn=functools.partial(custom_add_one, arch=Device[Device.DEFAULT].arch))[0]
|
||||
ei = a.schedule()[-1].lower()
|
||||
self.assertEqual(ei.prg.estimates.ops, a.numel())
|
||||
self.assertEqual(ei.prg.estimates.mem, a.nbytes()*2)
|
||||
ei.run()
|
||||
self.assertTrue((a.numpy() == 2.).all())
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,170 +0,0 @@
|
||||
import unittest
|
||||
from extra.assembly.amd.dsl import *
|
||||
from extra.assembly.amd.dsl import VDSTYField
|
||||
from extra.assembly.amd.autogen.rdna3.enum import VOP1Op, VOP2Op
|
||||
from extra.assembly.amd.autogen.rdna3.ins import VOP1
|
||||
|
||||
class TestRegisters(unittest.TestCase):
|
||||
def test_vgpr_single(self):
|
||||
self.assertEqual(repr(v[5]), "v[5]")
|
||||
self.assertEqual(v[5].offset, 261) # 256 + 5
|
||||
self.assertEqual(v[5].sz, 1)
|
||||
|
||||
def test_sgpr_single(self):
|
||||
self.assertEqual(repr(s[10]), "s[10]")
|
||||
self.assertEqual(s[10].offset, 10)
|
||||
|
||||
def test_vgpr_range(self):
|
||||
self.assertEqual(repr(v[0:3]), "v[0:3]")
|
||||
self.assertEqual(v[0:3].offset, 256)
|
||||
self.assertEqual(v[0:3].sz, 4)
|
||||
|
||||
def test_sgpr_range(self):
|
||||
self.assertEqual(repr(s[4:5]), "s[4:5]")
|
||||
self.assertEqual(s[4:5].sz, 2)
|
||||
|
||||
def test_ttmp_reslice(self):
|
||||
# ttmp is src[108:123], so ttmp[0] should be src[108]
|
||||
self.assertEqual(ttmp[0].offset, 108)
|
||||
self.assertEqual(ttmp[1].offset, 109)
|
||||
# ttmp[0:1] is 2 elements (inclusive slicing)
|
||||
self.assertEqual(ttmp[0:1].offset, 108)
|
||||
self.assertEqual(ttmp[0:1].sz, 2)
|
||||
# ttmp[0:1][0] should be src[108]
|
||||
self.assertEqual(ttmp[0:1][0].offset, 108)
|
||||
|
||||
def test_special_regs(self):
|
||||
self.assertEqual(NULL.offset, 124)
|
||||
self.assertEqual(M0.offset, 125)
|
||||
self.assertEqual(EXEC_LO.offset, 126)
|
||||
self.assertEqual(EXEC_HI.offset, 127)
|
||||
# Check repr round-trips
|
||||
self.assertEqual(repr(NULL), "NULL")
|
||||
self.assertEqual(repr(M0), "M0")
|
||||
self.assertEqual(repr(EXEC_LO), "EXEC_LO")
|
||||
self.assertEqual(repr(EXEC), "EXEC")
|
||||
|
||||
def test_vcc(self):
|
||||
self.assertEqual(VCC.offset, 106)
|
||||
self.assertEqual(VCC.sz, 2)
|
||||
self.assertEqual(VCC_LO.offset, 106)
|
||||
self.assertEqual(VCC_HI.offset, 107)
|
||||
# Check repr round-trips
|
||||
self.assertEqual(repr(VCC_LO), "VCC_LO")
|
||||
self.assertEqual(repr(VCC_HI), "VCC_HI")
|
||||
self.assertEqual(repr(VCC), "VCC")
|
||||
|
||||
def test_float_constants(self):
|
||||
self.assertEqual(src[240].offset, 240)
|
||||
self.assertEqual(repr(src[240]), "0.5")
|
||||
self.assertEqual(repr(src[242]), "1.0")
|
||||
self.assertEqual(repr(src[243]), "-1.0")
|
||||
|
||||
def test_int_constants(self):
|
||||
self.assertEqual(repr(src[128]), "0")
|
||||
self.assertEqual(repr(src[129]), "1")
|
||||
self.assertEqual(repr(src[192]), "64")
|
||||
self.assertEqual(repr(src[193]), "-1")
|
||||
self.assertEqual(repr(src[208]), "-16")
|
||||
|
||||
class TestEnumBitField(unittest.TestCase):
|
||||
def test_enum_name(self):
|
||||
self.assertEqual(VOP1Op.V_MOV_B32_E32.name, "V_MOV_B32_E32")
|
||||
|
||||
def test_enum_value(self):
|
||||
self.assertEqual(VOP1Op.V_MOV_B32_E32.value, 1)
|
||||
|
||||
def test_enum_comparison(self):
|
||||
self.assertEqual(VOP1Op.V_MOV_B32_E32, VOP1Op.V_MOV_B32_E32)
|
||||
self.assertNotEqual(VOP1Op.V_NOP_E32, VOP1Op.V_MOV_B32_E32)
|
||||
|
||||
def test_enum_different_types(self):
|
||||
# VOP1Op and VOP2Op are different enums, even if same value
|
||||
self.assertNotEqual(VOP1Op.V_MOV_B32_E32, VOP2Op.V_CNDMASK_B32_E32)
|
||||
|
||||
def test_wrong_enum_type_raises(self):
|
||||
# Passing VOP2Op to VOP1 should raise
|
||||
with self.assertRaises(RuntimeError):
|
||||
VOP1(VOP2Op.V_CNDMASK_B32_E32, v[5], v[6])
|
||||
|
||||
class TestVOP1(unittest.TestCase):
|
||||
def test_class_setup(self):
|
||||
self.assertEqual(VOP1._size(), 4)
|
||||
field_names = [n for n, _ in VOP1._fields]
|
||||
self.assertIn('encoding', field_names)
|
||||
self.assertIn('op', field_names)
|
||||
self.assertIn('vdst', field_names)
|
||||
self.assertIn('src0', field_names)
|
||||
|
||||
def test_encoding_vgpr_vgpr(self):
|
||||
i = VOP1(VOP1Op.V_MOV_B32_E32, v[5], v[6])
|
||||
raw = i._raw
|
||||
# Check each field
|
||||
self.assertEqual((raw >> 25) & 0x7f, 0b0111111) # encoding
|
||||
self.assertEqual((raw >> 17) & 0xff, 5) # vdst (just VGPR index)
|
||||
self.assertEqual((raw >> 9) & 0xff, 1) # op
|
||||
self.assertEqual(raw & 0x1ff, 262) # src0 (256 + 6)
|
||||
|
||||
def test_encoding_vgpr_sgpr(self):
|
||||
i = VOP1(VOP1Op.V_MOV_B32_E32, v[5], s[10])
|
||||
raw = i._raw
|
||||
self.assertEqual((raw >> 17) & 0xff, 5) # vdst (just VGPR index)
|
||||
self.assertEqual(raw & 0x1ff, 10) # src0 (SGPR encoded)
|
||||
|
||||
def test_to_bytes(self):
|
||||
i = VOP1(VOP1Op.V_MOV_B32_E32, v[5], v[6])
|
||||
b = i.to_bytes()
|
||||
self.assertEqual(len(b), 4)
|
||||
self.assertEqual(int.from_bytes(b, 'little'), i._raw)
|
||||
|
||||
def test_from_bytes(self):
|
||||
i1 = VOP1(VOP1Op.V_MOV_B32_E32, v[5], v[6])
|
||||
i2 = VOP1.from_bytes(i1.to_bytes())
|
||||
self.assertEqual(i1._raw, i2._raw)
|
||||
|
||||
def test_repr(self):
|
||||
i = VOP1(VOP1Op.V_MOV_B32_E32, v[5], v[6])
|
||||
self.assertEqual(repr(i), "v_mov_b32_e32(v[5], v[6])")
|
||||
|
||||
def test_repr_sgpr_src(self):
|
||||
i = VOP1(VOP1Op.V_MOV_B32_E32, v[5], s[10])
|
||||
self.assertEqual(repr(i), "v_mov_b32_e32(v[5], s[10])")
|
||||
|
||||
def test_kwargs(self):
|
||||
i1 = VOP1(VOP1Op.V_MOV_B32_E32, v[5], v[6])
|
||||
i2 = VOP1(op=VOP1Op.V_MOV_B32_E32, vdst=v[5], src0=v[6])
|
||||
self.assertEqual(i1._raw, i2._raw)
|
||||
|
||||
def test_kwargs_partial(self):
|
||||
i1 = VOP1(VOP1Op.V_MOV_B32_E32, v[5], v[6])
|
||||
i2 = VOP1(VOP1Op.V_MOV_B32_E32, src0=v[6], vdst=v[5])
|
||||
self.assertEqual(i1._raw, i2._raw)
|
||||
|
||||
class TestVDSTYField(unittest.TestCase):
|
||||
def test_encode_even_vgpr(self):
|
||||
f = VDSTYField(6, 0) # 7-bit field
|
||||
self.assertEqual(f.encode(v[0]), 0)
|
||||
self.assertEqual(f.encode(v[2]), 1)
|
||||
self.assertEqual(f.encode(v[4]), 2)
|
||||
self.assertEqual(f.encode(v[254]), 127)
|
||||
|
||||
def test_encode_non_vgpr_raises(self):
|
||||
f = VDSTYField(6, 0)
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
f.encode(s[0])
|
||||
self.assertIn("VGPR", str(ctx.exception))
|
||||
|
||||
def test_encode_non_reg_raises(self):
|
||||
f = VDSTYField(6, 0)
|
||||
with self.assertRaises(TypeError) as ctx:
|
||||
f.encode(42)
|
||||
self.assertIn("Reg", str(ctx.exception))
|
||||
|
||||
def test_decode_returns_raw(self):
|
||||
f = VDSTYField(6, 0)
|
||||
# decode returns raw value, actual vdsty computed with vdstx context
|
||||
self.assertEqual(f.decode(0), 0)
|
||||
self.assertEqual(f.decode(127), 127)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,13 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test DS and other compute-relevant instruction formats.
|
||||
|
||||
Note: Graphics-only formats (EXP, MUBUF, MTBUF, MIMG) are not supported - use GLOBAL/FLAT for memory access in compute.
|
||||
"""
|
||||
"""Test MUBUF, MTBUF, MIMG, EXP, DS formats against LLVM."""
|
||||
import unittest
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
from extra.assembly.amd.dsl import VCC_HI, EXEC_LO, NULL
|
||||
OFF = NULL # OFF is alias for NULL
|
||||
from extra.assembly.amd.decode import detect_format
|
||||
from extra.assembly.amd.dsl import encode_src, RawImm
|
||||
from extra.assembly.amd.asm import detect_format
|
||||
|
||||
class TestMUBUF(unittest.TestCase):
|
||||
"""Test MUBUF (buffer) instructions."""
|
||||
|
||||
def test_buffer_load_b32_basic(self):
|
||||
# buffer_load_b32 v5, off, s[8:11], s3 offset:4095
|
||||
# GFX11: encoding: [0xff,0x0f,0x50,0xe0,0x00,0x05,0x02,0x03]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0x50,0xe0,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_buffer_load_b32_idxen(self):
|
||||
# buffer_load_b32 v5, v0, s[8:11], s3 idxen offset:4095
|
||||
# GFX11: encoding: [0xff,0x0f,0x50,0xe0,0x00,0x05,0x82,0x03]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, idxen=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0x50,0xe0,0x00,0x05,0x82,0x03]))
|
||||
|
||||
def test_buffer_load_b32_offen(self):
|
||||
# buffer_load_b32 v5, v0, s[8:11], s3 offen offset:4095
|
||||
# GFX11: encoding: [0xff,0x0f,0x50,0xe0,0x00,0x05,0x42,0x03]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, offen=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0x50,0xe0,0x00,0x05,0x42,0x03]))
|
||||
|
||||
def test_buffer_load_b32_glc(self):
|
||||
# buffer_load_b32 v5, off, s[8:11], s3 offset:4095 glc
|
||||
# GFX11: encoding: [0xff,0x4f,0x50,0xe0,0x00,0x05,0x02,0x03]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, glc=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x4f,0x50,0xe0,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_buffer_load_b32_slc(self):
|
||||
# buffer_load_b32 v5, off, s[8:11], s3 offset:4095 slc
|
||||
# GFX11: encoding: [0xff,0x1f,0x50,0xe0,0x00,0x05,0x02,0x03]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, slc=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x1f,0x50,0xe0,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_buffer_load_b32_dlc(self):
|
||||
# buffer_load_b32 v5, off, s[8:11], s3 offset:4095 dlc
|
||||
# GFX11: encoding: [0xff,0x2f,0x50,0xe0,0x00,0x05,0x02,0x03]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, dlc=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x2f,0x50,0xe0,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_buffer_load_b32_all_flags(self):
|
||||
# buffer_load_b32 v5, off, s[8:11], s3 offset:4095 glc slc dlc
|
||||
# GFX11: encoding: [0xff,0x7f,0x50,0xe0,0x00,0x05,0x02,0x03]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, glc=1, slc=1, dlc=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x7f,0x50,0xe0,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_buffer_store_b32(self):
|
||||
# buffer_store_b32 v1, off, s[12:15], s4 offset:4095
|
||||
# GFX11: encoding: [0xff,0x0f,0x68,0xe0,0x00,0x01,0x03,0x04]
|
||||
inst = buffer_store_b32(vdata=v[1], vaddr=v[0], srsrc=s[12:16], soffset=s[4], offset=4095)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0x68,0xe0,0x00,0x01,0x03,0x04]))
|
||||
|
||||
def test_buffer_load_b64(self):
|
||||
# buffer_load_b64 v[5:6], off, s[8:11], s3 offset:4095
|
||||
# GFX11: encoding: [0xff,0x0f,0x54,0xe0,0x00,0x05,0x02,0x03]
|
||||
inst = buffer_load_b64(vdata=v[5:7], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0x54,0xe0,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_buffer_load_soffset_m0(self):
|
||||
# buffer_load_b32 v5, off, s[8:11], m0 offset:4095
|
||||
# GFX11: encoding: [0xff,0x0f,0x50,0xe0,0x00,0x05,0x02,0x7d]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=M0, offset=4095)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0x50,0xe0,0x00,0x05,0x02,0x7d]))
|
||||
|
||||
def test_buffer_load_soffset_inline_const(self):
|
||||
# buffer_load_b32 v5, off, s[8:11], 0 offset:4095
|
||||
# GFX11: encoding: [0xff,0x0f,0x50,0xe0,0x00,0x05,0x02,0x80]
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=0, offset=4095)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0x50,0xe0,0x00,0x05,0x02,0x80]))
|
||||
|
||||
def test_buffer_disasm_roundtrip(self):
|
||||
inst = buffer_load_b32(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, glc=1)
|
||||
decoded = MUBUF.from_bytes(inst.to_bytes())
|
||||
self.assertEqual(decoded.to_bytes(), inst.to_bytes())
|
||||
|
||||
|
||||
class TestMTBUF(unittest.TestCase):
|
||||
"""Test MTBUF (typed buffer) instructions."""
|
||||
|
||||
def test_tbuffer_load_format_x(self):
|
||||
# tbuffer_load_format_x v5, off, s[8:11], s3 format:[BUF_FMT_32_FLOAT] offset:4095
|
||||
# BUF_FMT_32_FLOAT = 22
|
||||
# GFX11: encoding: [0xff,0x0f,0xb0,0xe8,0x00,0x05,0x02,0x03]
|
||||
inst = tbuffer_load_format_x(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, format=22)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0xb0,0xe8,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_tbuffer_store_format_x(self):
|
||||
# tbuffer_store_format_x v5, off, s[8:11], s3 format:[BUF_FMT_32_FLOAT] offset:4095
|
||||
# BUF_FMT_32_FLOAT = 22
|
||||
# GFX11: encoding: [0xff,0x0f,0xb2,0xe8,0x00,0x05,0x02,0x03]
|
||||
inst = tbuffer_store_format_x(vdata=v[5], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, format=22)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x0f,0xb2,0xe8,0x00,0x05,0x02,0x03]))
|
||||
|
||||
def test_tbuffer_load_format_xy(self):
|
||||
# tbuffer_load_format_xy v[5:6], off, s[8:11], s3 format:[BUF_FMT_32_32_FLOAT] offset:4095
|
||||
# BUF_FMT_32_32_FLOAT = 50
|
||||
# GFX11: encoding: [0xff,0x8f,0x90,0xe9,0x00,0x05,0x02,0x03]
|
||||
inst = tbuffer_load_format_xy(vdata=v[5:7], vaddr=v[0], srsrc=s[8:12], soffset=s[3], offset=4095, format=50)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0xff,0x8f,0x90,0xe9,0x00,0x05,0x02,0x03]))
|
||||
|
||||
|
||||
class TestMIMG(unittest.TestCase):
|
||||
"""Test MIMG (image) instructions."""
|
||||
|
||||
def test_image_load_2d(self):
|
||||
# image_load v[0:3], v[4:5], s[0:7] dmask:0xf dim:SQ_RSRC_IMG_2D
|
||||
# GFX11: encoding: [0x04,0x0f,0x00,0xf0,0x04,0x00,0x00,0x00]
|
||||
inst = image_load(vdata=v[0:4], vaddr=v[4:6], srsrc=s[0:8], dmask=0xf, dim=1) # dim=1 is SQ_RSRC_IMG_2D
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x04,0x0f,0x00,0xf0,0x04,0x00,0x00,0x00]))
|
||||
|
||||
def test_image_store_2d(self):
|
||||
# image_store v[0:3], v[4:5], s[0:7] dmask:0xf dim:SQ_RSRC_IMG_2D
|
||||
# GFX11: encoding: [0x04,0x0f,0x18,0xf0,0x04,0x00,0x00,0x00]
|
||||
inst = image_store(vdata=v[0:4], vaddr=v[4:6], srsrc=s[0:8], dmask=0xf, dim=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x04,0x0f,0x18,0xf0,0x04,0x00,0x00,0x00]))
|
||||
|
||||
def test_image_load_1d(self):
|
||||
# image_load v[0:3], v4, s[0:7] dmask:0xf dim:SQ_RSRC_IMG_1D
|
||||
# GFX11: encoding: [0x00,0x0f,0x00,0xf0,0x04,0x00,0x00,0x00]
|
||||
inst = image_load(vdata=v[0:4], vaddr=v[4], srsrc=s[0:8], dmask=0xf, dim=0) # dim=0 is SQ_RSRC_IMG_1D
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x00,0x0f,0x00,0xf0,0x04,0x00,0x00,0x00]))
|
||||
|
||||
def test_image_sample(self):
|
||||
# image_sample v[0:3], v[4:5], s[0:7], s[8:11] dmask:0xf dim:SQ_RSRC_IMG_2D
|
||||
# GFX11: encoding: [0x04,0x0f,0x6c,0xf0,0x04,0x00,0x00,0x08]
|
||||
inst = image_sample(vdata=v[0:4], vaddr=v[4:6], srsrc=s[0:8], ssamp=s[8:12], dmask=0xf, dim=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x04,0x0f,0x6c,0xf0,0x04,0x00,0x00,0x08]))
|
||||
|
||||
def test_image_load_d16(self):
|
||||
# image_load v[0:1], v[4:5], s[0:7] dmask:0xf dim:SQ_RSRC_IMG_2D d16
|
||||
# GFX11: encoding: [0x04,0x0f,0x02,0xf0,0x04,0x00,0x00,0x00]
|
||||
inst = image_load(vdata=v[0:2], vaddr=v[4:6], srsrc=s[0:8], dmask=0xf, dim=1, d16=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x04,0x0f,0x02,0xf0,0x04,0x00,0x00,0x00]))
|
||||
|
||||
|
||||
class TestEXP(unittest.TestCase):
|
||||
"""Test EXP (export) instructions."""
|
||||
|
||||
def test_exp_mrt0(self):
|
||||
# exp mrt0 v0, v1, v2, v3
|
||||
# GFX11: encoding: [0x0f,0x00,0x00,0xf8,0x00,0x01,0x02,0x03]
|
||||
inst = EXP(en=0xf, target=0, vsrc0=v[0], vsrc1=v[1], vsrc2=v[2], vsrc3=v[3])
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x0f,0x00,0x00,0xf8,0x00,0x01,0x02,0x03]))
|
||||
|
||||
def test_exp_mrtz(self):
|
||||
# exp mrtz v4, v3, v2, v1
|
||||
# GFX11: encoding: [0x8f,0x00,0x00,0xf8,0x04,0x03,0x02,0x01]
|
||||
inst = EXP(en=0xf, target=8, vsrc0=v[4], vsrc1=v[3], vsrc2=v[2], vsrc3=v[1])
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x8f,0x00,0x00,0xf8,0x04,0x03,0x02,0x01]))
|
||||
|
||||
def test_exp_mrtz_done(self):
|
||||
# exp mrtz v4, v3, v2, v1 done
|
||||
# GFX11: encoding: [0x8f,0x08,0x00,0xf8,0x04,0x03,0x02,0x01]
|
||||
inst = EXP(en=0xf, target=8, vsrc0=v[4], vsrc1=v[3], vsrc2=v[2], vsrc3=v[3], done=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x8f,0x08,0x00,0xf8,0x04,0x03,0x02,0x03]))
|
||||
|
||||
def test_exp_partial_mask(self):
|
||||
# exp mrt0 v0, v1, off, off (en=0x3, only first two components)
|
||||
# GFX11: encoding: [0x03,0x00,0x00,0xf8,0x00,0x01,0x00,0x00]
|
||||
inst = EXP(en=0x3, target=0, vsrc0=v[0], vsrc1=v[1], vsrc2=v[0], vsrc3=v[0])
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x03,0x00,0x00,0xf8,0x00,0x01,0x00,0x00]))
|
||||
|
||||
def test_exp_row_en(self):
|
||||
# exp mrtz v4, v3, v2, v1 row_en
|
||||
# GFX11: encoding: [0x8f,0x20,0x00,0xf8,0x04,0x03,0x02,0x01]
|
||||
inst = EXP(en=0xf, target=8, vsrc0=v[4], vsrc1=v[3], vsrc2=v[2], vsrc3=v[1], row=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x8f,0x20,0x00,0xf8,0x04,0x03,0x02,0x01]))
|
||||
|
||||
|
||||
class TestDS(unittest.TestCase):
|
||||
@@ -34,7 +197,7 @@ class TestDS(unittest.TestCase):
|
||||
def test_ds_load_b64(self):
|
||||
# ds_load_b64 v[0:1], v2
|
||||
# GFX11: encoding: [0x00,0x00,0xd8,0xd9,0x02,0x00,0x00,0x00]
|
||||
inst = ds_load_b64(vdst=v[0:1], addr=v[2])
|
||||
inst = ds_load_b64(vdst=v[0:2], addr=v[2])
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x00,0x00,0xd8,0xd9,0x02,0x00,0x00,0x00]))
|
||||
|
||||
def test_ds_add_u32(self):
|
||||
@@ -78,31 +241,31 @@ class TestFLAT(unittest.TestCase):
|
||||
def test_global_load_b32(self):
|
||||
# global_load_b32 v0, v[1:2], off (seg=2 for global)
|
||||
# GFX11: encoding: [0x00,0x00,0x52,0xdc,0x01,0x00,0x7c,0x00]
|
||||
inst = global_load_b32(vdst=v[0], addr=v[1:2], saddr=OFF)
|
||||
inst = global_load_b32(vdst=v[0], addr=v[1:3], saddr=OFF)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x00,0x00,0x52,0xdc,0x01,0x00,0x7c,0x00]))
|
||||
|
||||
def test_global_store_b32(self):
|
||||
# global_store_b32 v[0:1], v2, off (seg=2 for global)
|
||||
# GFX11: encoding: [0x00,0x00,0x6a,0xdc,0x00,0x02,0x7c,0x00]
|
||||
inst = global_store_b32(addr=v[0:1], data=v[2], saddr=OFF)
|
||||
inst = global_store_b32(addr=v[0:2], data=v[2], saddr=OFF)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x00,0x00,0x6a,0xdc,0x00,0x02,0x7c,0x00]))
|
||||
|
||||
def test_global_load_b32_saddr(self):
|
||||
# global_load_b32 v0, v1, s[0:1] (seg=2 for global)
|
||||
# GFX11: encoding: [0x00,0x00,0x52,0xdc,0x01,0x00,0x00,0x00]
|
||||
inst = global_load_b32(vdst=v[0], addr=v[1], saddr=s[0:1])
|
||||
inst = global_load_b32(vdst=v[0], addr=v[1], saddr=s[0:2])
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x00,0x00,0x52,0xdc,0x01,0x00,0x00,0x00]))
|
||||
|
||||
def test_global_load_b32_offset(self):
|
||||
# global_load_b32 v0, v[1:2], off offset:256 (seg=2 for global)
|
||||
# GFX11: encoding: [0x00,0x01,0x52,0xdc,0x01,0x00,0x7c,0x00]
|
||||
inst = global_load_b32(vdst=v[0], addr=v[1:2], saddr=OFF, offset=256)
|
||||
inst = global_load_b32(vdst=v[0], addr=v[1:3], saddr=OFF, offset=256)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x00,0x01,0x52,0xdc,0x01,0x00,0x7c,0x00]))
|
||||
|
||||
def test_global_load_b64(self):
|
||||
# global_load_b64 v[0:1], v[2:3], off (seg=2 for global)
|
||||
# GFX11: encoding: [0x00,0x00,0x56,0xdc,0x02,0x00,0x7c,0x00]
|
||||
inst = global_load_b64(vdst=v[0:1], addr=v[2:3], saddr=OFF)
|
||||
inst = global_load_b64(vdst=v[0:2], addr=v[2:4], saddr=OFF)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x00,0x00,0x56,0xdc,0x02,0x00,0x7c,0x00]))
|
||||
|
||||
|
||||
@@ -112,19 +275,19 @@ class TestSMEM(unittest.TestCase):
|
||||
def test_smem_dlc_bit_position(self):
|
||||
# s_load_b32 s5, s[2:3], s0 dlc - tests that DLC is at bit 13 (not bit 14)
|
||||
# GFX11: encoding: [0x41,0x21,0x00,0xf4,0x00,0x00,0x00,0x00]
|
||||
inst = s_load_b32(sdata=s[5], sbase=s[2:3], soffset=s[0], dlc=1)
|
||||
inst = s_load_b32(sdata=s[5], sbase=s[2], soffset=s[0], dlc=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x41,0x21,0x00,0xf4,0x00,0x00,0x00,0x00]))
|
||||
|
||||
def test_smem_glc_bit_position(self):
|
||||
# s_load_b32 s5, s[2:3], s0 glc - tests that GLC is at bit 14 (not bit 16)
|
||||
# GFX11: encoding: [0x41,0x41,0x00,0xf4,0x00,0x00,0x00,0x00]
|
||||
inst = s_load_b32(sdata=s[5], sbase=s[2:3], soffset=s[0], glc=1)
|
||||
inst = s_load_b32(sdata=s[5], sbase=s[2], soffset=s[0], glc=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x41,0x41,0x00,0xf4,0x00,0x00,0x00,0x00]))
|
||||
|
||||
def test_smem_glc_dlc_combined(self):
|
||||
# s_load_b32 s5, s[2:3], s0 glc dlc - tests both flags together
|
||||
# GFX11: encoding: [0x41,0x61,0x00,0xf4,0x00,0x00,0x00,0x00]
|
||||
inst = s_load_b32(sdata=s[5], sbase=s[2:3], soffset=s[0], glc=1, dlc=1)
|
||||
inst = s_load_b32(sdata=s[5], sbase=s[2], soffset=s[0], glc=1, dlc=1)
|
||||
self.assertEqual(inst.to_bytes(), bytes([0x41,0x61,0x00,0xf4,0x00,0x00,0x00,0x00]))
|
||||
|
||||
def test_smem_disasm_roundtrip_dlc(self):
|
||||
@@ -146,20 +309,23 @@ class TestVOP3Literal(unittest.TestCase):
|
||||
def test_vop3_with_literal(self):
|
||||
# v_add3_u32 v5, vcc_hi, 0xaf123456, v255
|
||||
# GFX11: encoding: [0x05,0x00,0x55,0xd6,0x6b,0xfe,0xfd,0x07,0x56,0x34,0x12,0xaf]
|
||||
inst = VOP3(VOP3Op.V_ADD3_U32, vdst=v[5], src0=VCC_HI, src1=0xaf123456, src2=v[255])
|
||||
from extra.assembly.amd.dsl import RawImm
|
||||
inst = VOP3(VOP3Op.V_ADD3_U32, vdst=v[5], src0=RawImm(107), src1=0xaf123456, src2=v[255])
|
||||
expected = bytes([0x05,0x00,0x55,0xd6,0x6b,0xfe,0xfd,0x07,0x56,0x34,0x12,0xaf])
|
||||
self.assertEqual(inst.to_bytes(), expected)
|
||||
|
||||
def test_vop3_literal_null_operand(self):
|
||||
# v_add3_u32 v5, null, exec_lo, 0xaf123456
|
||||
# GFX11: encoding: [0x05,0x00,0x55,0xd6,0x7c,0xfc,0xfc,0x03,0x56,0x34,0x12,0xaf]
|
||||
inst = VOP3(VOP3Op.V_ADD3_U32, vdst=v[5], src0=NULL, src1=EXEC_LO, src2=0xaf123456)
|
||||
from extra.assembly.amd.dsl import RawImm
|
||||
inst = VOP3(VOP3Op.V_ADD3_U32, vdst=v[5], src0=NULL, src1=RawImm(126), src2=0xaf123456)
|
||||
expected = bytes([0x05,0x00,0x55,0xd6,0x7c,0xfc,0xfc,0x03,0x56,0x34,0x12,0xaf])
|
||||
self.assertEqual(inst.to_bytes(), expected)
|
||||
|
||||
def test_vop3p_with_literal(self):
|
||||
# Test VOP3P literal encoding (also uses Inst64)
|
||||
inst = VOP3P(VOP3POp.V_PK_ADD_F16, vdst=v[5], src0=0.5, src1=0x12345678, src2=v[0])
|
||||
from extra.assembly.amd.dsl import RawImm
|
||||
inst = VOP3P(VOP3POp.V_PK_ADD_F16, vdst=v[5], src0=RawImm(240), src1=0x12345678, src2=v[0])
|
||||
self.assertEqual(len(inst.to_bytes()), 12) # 8 bytes + 4 byte literal
|
||||
|
||||
|
||||
@@ -205,16 +371,28 @@ class TestDetectFormat(unittest.TestCase):
|
||||
self.assertEqual(detect_format(VOP3P(VOP3POp.V_PK_ADD_F16, v[0], v[1], v[2], v[3]).to_bytes()), VOP3P)
|
||||
|
||||
def test_detect_smem(self):
|
||||
self.assertEqual(detect_format(s_load_b32(sdata=s[0], sbase=s[2:3], offset=0).to_bytes()), SMEM)
|
||||
self.assertEqual(detect_format(s_load_b64(sdata=s[0:1], sbase=s[2:3], soffset=s[5]).to_bytes()), SMEM)
|
||||
self.assertEqual(detect_format(s_load_b32(s[0], s[2:3], 0).to_bytes()), SMEM)
|
||||
self.assertEqual(detect_format(s_load_b64(s[0:1], s[2:3], s[5]).to_bytes()), SMEM)
|
||||
|
||||
def test_detect_ds(self):
|
||||
self.assertEqual(detect_format(ds_load_b32(v[0], v[1]).to_bytes()), DS)
|
||||
self.assertEqual(detect_format(ds_store_b32(v[0], v[1]).to_bytes()), DS)
|
||||
|
||||
def test_detect_flat(self):
|
||||
self.assertEqual(detect_format(global_load_b32(vdst=v[0], addr=v[1:2], saddr=NULL).to_bytes()), FLAT)
|
||||
self.assertEqual(detect_format(global_store_b32(addr=v[0:1], data=v[2], saddr=NULL).to_bytes()), FLAT)
|
||||
self.assertEqual(detect_format(global_load_b32(v[0], v[1:3], RawImm(124)).to_bytes()), FLAT)
|
||||
self.assertEqual(detect_format(global_store_b32(v[0:2], v[2], RawImm(124)).to_bytes()), FLAT)
|
||||
|
||||
def test_detect_mubuf(self):
|
||||
self.assertEqual(detect_format(buffer_load_b32(v[0], v[1], s[0:4], s[5]).to_bytes()), MUBUF)
|
||||
|
||||
def test_detect_mtbuf(self):
|
||||
self.assertEqual(detect_format(tbuffer_load_format_x(v[0], v[1], s[0:4], s[5], format=22).to_bytes()), MTBUF)
|
||||
|
||||
def test_detect_mimg(self):
|
||||
self.assertEqual(detect_format(image_load(v[0:4], v[4:6], s[0:8], dmask=0xf, dim=1).to_bytes()), MIMG)
|
||||
|
||||
def test_detect_exp(self):
|
||||
self.assertEqual(detect_format(EXP(en=0xf, target=0, vsrc0=v[0], vsrc1=v[1], vsrc2=v[2], vsrc3=v[3]).to_bytes()), EXP)
|
||||
|
||||
def test_detect_vopd(self):
|
||||
inst = VOPD(VOPDOp.V_DUAL_MOV_B32, VOPDOp.V_DUAL_MOV_B32, vdstx=v[0], vdsty=v[1], srcx0=0, srcy0=0)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import unittest, struct
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
from extra.assembly.amd.dsl import Inst
|
||||
from extra.assembly.amd.asm import asm
|
||||
from extra.assembly.amd.test.test_roundtrip import compile_asm
|
||||
|
||||
class TestIntegration(unittest.TestCase):
|
||||
@@ -12,20 +13,23 @@ class TestIntegration(unittest.TestCase):
|
||||
if not hasattr(self, 'inst'): return
|
||||
b = self.inst.to_bytes()
|
||||
st = self.inst.disasm()
|
||||
# Test that the instruction can be compiled by LLVM and produces the same bytes
|
||||
desc = f"{st:25s} {self.inst} {b!r}"
|
||||
reasm = asm(st)
|
||||
desc = f"{st:25s} {self.inst} {b!r} {reasm}"
|
||||
self.assertEqual(b, compile_asm(st), desc)
|
||||
# TODO: this compare should work for valid things
|
||||
#self.assertEqual(self.inst, reasm)
|
||||
self.assertEqual(repr(self.inst), repr(reasm))
|
||||
print(desc)
|
||||
|
||||
def test_wmma(self):
|
||||
self.inst = v_wmma_f32_16x16x16_f16(v[0:7], v[184:191], v[136:143], v[0:7])
|
||||
self.inst = v_wmma_f32_16x16x16_f16(v[0:7], v[189:192], v[140:143], v[0:7])
|
||||
|
||||
def test_load_b128(self):
|
||||
self.inst = s_load_b128(s[4:7], s[0:1], NULL, 0)
|
||||
|
||||
def test_load_b128_wrong_size(self):
|
||||
# this should have to be 4 regs on the loaded to
|
||||
with self.assertRaises(TypeError):
|
||||
with self.assertRaises(Exception):
|
||||
self.inst = s_load_b128(s[4:6], s[0:1], NULL, 0)
|
||||
|
||||
def test_mov_b32(self):
|
||||
@@ -139,7 +143,7 @@ class TestRegisterSliceSyntax(unittest.TestCase):
|
||||
def test_register_slice_count(self):
|
||||
# s[4:7] should give 4 registers: s4, s5, s6, s7 (AMD convention, inclusive)
|
||||
reg = s[4:7]
|
||||
self.assertEqual(reg.sz, 4, "s[4:7] should give 4 registers (s4, s5, s6, s7)")
|
||||
self.assertEqual(reg.count, 4, "s[4:7] should give 4 registers (s4, s5, s6, s7)")
|
||||
|
||||
def test_register_slice_roundtrip(self):
|
||||
# Round-trip: DSL -> disasm -> DSL should preserve register count
|
||||
@@ -150,7 +154,7 @@ class TestRegisterSliceSyntax(unittest.TestCase):
|
||||
self.assertIn("s[4:7]", disasm)
|
||||
# And s[4:7] in DSL should give the same 4 registers
|
||||
reg_from_disasm = s[4:7]
|
||||
self.assertEqual(reg_from_disasm.sz, 4, "s[4:7] from disasm should give 4 registers")
|
||||
self.assertEqual(reg_from_disasm.count, 4, "s[4:7] from disasm should give 4 registers")
|
||||
|
||||
class TestInstructionEquality(unittest.TestCase):
|
||||
"""
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Integration test: round-trip RDNA3 assembly through AMD toolchain."""
|
||||
import unittest, io, sys
|
||||
import unittest, re, io, sys, subprocess
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
|
||||
def waitcnt(vmcnt: int = 0x3f, expcnt: int = 0x7, lgkmcnt: int = 0x3f) -> int:
|
||||
return (expcnt & 0x7) | ((lgkmcnt & 0x3f) << 4) | ((vmcnt & 0x3f) << 10)
|
||||
from extra.assembly.amd.asm import waitcnt, asm
|
||||
from extra.assembly.amd.test.helpers import get_llvm_mc
|
||||
|
||||
def disassemble(lib: bytes, arch: str = "gfx1100") -> str:
|
||||
"""Disassemble ELF binary using tinygrad's compiler, return raw output."""
|
||||
@@ -41,7 +40,7 @@ def assemble_and_disassemble(instructions: list, arch: str = "gfx1100") -> list[
|
||||
return parse_disassembly(disassemble(lib, arch))
|
||||
|
||||
class TestIntegration(unittest.TestCase):
|
||||
"""Test our DSL output matches LLVM disassembly."""
|
||||
"""Test our assembler output matches LLVM disassembly."""
|
||||
|
||||
def test_simple_sop1(self):
|
||||
"""Test SOP1 instructions round-trip."""
|
||||
@@ -91,9 +90,9 @@ class TestIntegration(unittest.TestCase):
|
||||
def test_memory_ops(self):
|
||||
"""Test memory instructions."""
|
||||
instructions = [
|
||||
s_load_b32(s[0], s[0:1], NULL),
|
||||
s_load_b32(s[0], s[0:2], NULL),
|
||||
s_waitcnt(simm16=waitcnt(lgkmcnt=0)),
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=OFF),
|
||||
global_store_b32(addr=v[0:2], data=v[2], saddr=OFF),
|
||||
s_endpgm(),
|
||||
]
|
||||
disasm = assemble_and_disassemble(instructions)
|
||||
@@ -109,12 +108,12 @@ class TestIntegration(unittest.TestCase):
|
||||
v_mov_b32_e32(v[0], s[0]), # base addr low
|
||||
v_mov_b32_e32(v[1], s[1]), # base addr high
|
||||
# Load value
|
||||
global_load_b32(vdst=v[2], addr=v[0:1], saddr=OFF),
|
||||
global_load_b32(vdst=v[2], addr=v[0:2], saddr=OFF),
|
||||
s_waitcnt(simm16=waitcnt(vmcnt=0)),
|
||||
# Add 1.0
|
||||
v_add_f32_e32(v[2], 1.0, v[2]),
|
||||
# Store result
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=OFF),
|
||||
global_store_b32(addr=v[0:2], data=v[2], saddr=OFF),
|
||||
s_endpgm(),
|
||||
]
|
||||
disasm = assemble_and_disassemble(instructions)
|
||||
@@ -149,6 +148,79 @@ class TestIntegration(unittest.TestCase):
|
||||
return
|
||||
self.fail("Could not find s_mov_b32 in disassembly")
|
||||
|
||||
class TestAsm(unittest.TestCase):
|
||||
"""Test asm() string parsing."""
|
||||
|
||||
def test_asm_basic(self):
|
||||
"""Test basic instruction parsing."""
|
||||
inst = asm('s_mov_b32 s0, s1')
|
||||
self.assertEqual(inst.to_bytes(), s_mov_b32(s[0], s[1]).to_bytes())
|
||||
|
||||
def test_asm_with_immediates(self):
|
||||
"""Test parsing with immediate values."""
|
||||
inst = asm('s_add_u32 s0, s1, 10')
|
||||
self.assertEqual(inst.to_bytes(), s_add_u32(s[0], s[1], 10).to_bytes())
|
||||
|
||||
def test_asm_float_const(self):
|
||||
"""Test parsing float constants."""
|
||||
inst = asm('v_mul_f32_e32 v0, 1.0, v1')
|
||||
self.assertEqual(inst.to_bytes(), v_mul_f32_e32(v[0], 1.0, v[1]).to_bytes())
|
||||
|
||||
def test_asm_hex_immediate(self):
|
||||
"""Test parsing hex immediates."""
|
||||
inst = asm('s_waitcnt 0xfc07')
|
||||
self.assertEqual(inst.to_bytes(), s_waitcnt(simm16=0xfc07).to_bytes())
|
||||
|
||||
def test_asm_special_regs(self):
|
||||
"""Test parsing special registers."""
|
||||
inst = asm('s_mov_b32 s0, vcc_lo')
|
||||
self.assertEqual(inst.to_bytes(), s_mov_b32(s[0], VCC_LO).to_bytes())
|
||||
|
||||
def test_asm_register_range(self):
|
||||
"""Test parsing register ranges."""
|
||||
inst = asm('s_load_b128 s[4:7], s[0:1], null')
|
||||
self.assertEqual(inst.to_bytes(), s_load_b128(s[4:7], s[0:1], NULL).to_bytes())
|
||||
|
||||
def test_asm_matches_llvm(self):
|
||||
"""Test asm() output matches LLVM assembler."""
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCompiler
|
||||
compiler = HIPCompiler('gfx1100')
|
||||
|
||||
def get_llvm_bytes(instr: str) -> bytes:
|
||||
src = f'.text\n.globl test\n.p2align 8\n.type test,@function\ntest:\n{instr}\n'
|
||||
lib = compiler.compile(src)
|
||||
raw = disassemble(lib)
|
||||
for line in raw.splitlines():
|
||||
if instr.split()[0] in line and '//' in line:
|
||||
hex_str = line.split('//')[1].strip().split(':')[1].strip()
|
||||
return bytes.fromhex(hex_str)[::-1]
|
||||
return b''
|
||||
|
||||
tests = ['s_mov_b32 s0, s1', 's_endpgm', 'v_add_f32_e32 v0, v1, v2']
|
||||
for t in tests:
|
||||
self.assertEqual(asm(t).to_bytes(), get_llvm_bytes(t), f"mismatch for: {t}")
|
||||
|
||||
def test_asm_vop3_modifiers(self):
|
||||
"""Test asm() with VOP3 modifiers (neg, abs, clamp)."""
|
||||
def get_llvm_encoding(instr: str) -> str:
|
||||
result = subprocess.run([get_llvm_mc(), '-triple=amdgcn', '-mcpu=gfx1100', '-show-encoding'],
|
||||
input=instr, capture_output=True, text=True)
|
||||
if m := re.search(r'encoding:\s*\[(.*?)\]', result.stdout):
|
||||
return m.group(1).replace('0x','').replace(',','').replace(' ','')
|
||||
return ''
|
||||
|
||||
tests = [
|
||||
'v_fma_f32 v0, -v1, v2, v3', # neg on src0
|
||||
'v_fma_f32 v0, v1, |v2|, v3', # abs on src1
|
||||
'v_fma_f32 v0, v1, v2, v3 clamp', # clamp
|
||||
'v_fma_f32 v0, -v1, |v2|, v3 clamp', # all modifiers
|
||||
'v_fma_f32 v0, -|v1|, v2, v3', # neg+abs on same operand
|
||||
]
|
||||
for t in tests:
|
||||
our_hex = asm(t).to_bytes().hex()
|
||||
llvm_hex = get_llvm_encoding(t)
|
||||
self.assertEqual(our_hex, llvm_hex, f"mismatch for: {t}")
|
||||
|
||||
class TestTinygradIntegration(unittest.TestCase):
|
||||
"""Test that we can parse disassembled tinygrad kernels."""
|
||||
|
||||
|
||||
@@ -1,45 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test AMD assembler/disassembler against LLVM test vectors.
|
||||
|
||||
Only compute-relevant instruction formats are tested. Graphics-only formats not supported:
|
||||
- MUBUF/MTBUF: buffer instructions with resource descriptors (use GLOBAL/FLAT instead)
|
||||
- MIMG: image/texture instructions
|
||||
- EXP/VEXPORT: export instructions for pixel/vertex output
|
||||
- VIMAGE/VSAMPLE: image sampling instructions (RDNA4)
|
||||
- VBUFFER: buffer instructions (RDNA4)
|
||||
"""
|
||||
"""Test AMD assembler/disassembler against LLVM test vectors."""
|
||||
import unittest, re, subprocess, functools
|
||||
from tinygrad.helpers import fetch
|
||||
from extra.assembly.amd.disasm import disasm
|
||||
from extra.assembly.amd.decode import decode_inst, detect_format
|
||||
from extra.assembly.amd.asm import asm, disasm, detect_format
|
||||
from extra.assembly.amd.test.helpers import get_llvm_mc
|
||||
|
||||
LLVM_BASE = "https://raw.githubusercontent.com/llvm/llvm-project/llvmorg-21.1.0/llvm/test/MC/AMDGPU"
|
||||
|
||||
# RDNA3 (gfx11) test files for compute instructions
|
||||
# Excluded: gfx11_asm_mubuf.s, gfx11_asm_mtbuf.s, gfx11_asm_mimg.s, gfx11_asm_mubuf_alias.s, gfx11_asm_mtbuf_alias.s (graphics-only)
|
||||
RDNA_FILES = ['gfx11_asm_sop1.s', 'gfx11_asm_sop2.s', 'gfx11_asm_sopp.s', 'gfx11_asm_sopk.s', 'gfx11_asm_sopc.s',
|
||||
'gfx11_asm_vop1.s', 'gfx11_asm_vop2.s', 'gfx11_asm_vopc.s', 'gfx11_asm_vop3.s', 'gfx11_asm_vop3p.s', 'gfx11_asm_vinterp.s',
|
||||
'gfx11_asm_vopd.s', 'gfx11_asm_vopcx.s', 'gfx11_asm_vop3_from_vop1.s', 'gfx11_asm_vop3_from_vop2.s', 'gfx11_asm_vop3_from_vopc.s',
|
||||
'gfx11_asm_vop3_from_vopcx.s', 'gfx11_asm_ds.s', 'gfx11_asm_smem.s', 'gfx11_asm_flat.s',
|
||||
'gfx11_asm_wmma.s', 'gfx11_asm_vop3_features.s', 'gfx11_asm_vop3p_features.s', 'gfx11_asm_vopd_features.s',
|
||||
'gfx11_asm_vop3_from_vopcx.s', 'gfx11_asm_ds.s', 'gfx11_asm_smem.s', 'gfx11_asm_flat.s', 'gfx11_asm_mubuf.s', 'gfx11_asm_mtbuf.s',
|
||||
'gfx11_asm_mimg.s', 'gfx11_asm_wmma.s', 'gfx11_asm_vop3_features.s', 'gfx11_asm_vop3p_features.s', 'gfx11_asm_vopd_features.s',
|
||||
'gfx11_asm_vop3_alias.s', 'gfx11_asm_vop3p_alias.s', 'gfx11_asm_vopc_alias.s', 'gfx11_asm_vopcx_alias.s', 'gfx11_asm_vinterp_alias.s',
|
||||
'gfx11_asm_smem_alias.s']
|
||||
# CDNA (gfx9/gfx90a/gfx942) test files for compute instructions
|
||||
# Excluded: gfx9_asm_mubuf.s, gfx9_asm_mtbuf.s, gfx90a_ldst_acc.s (has MIMG mixed in)
|
||||
'gfx11_asm_smem_alias.s', 'gfx11_asm_mubuf_alias.s', 'gfx11_asm_mtbuf_alias.s']
|
||||
# CDNA test files - includes gfx9 files for shared instructions, plus gfx90a/gfx942 specific files
|
||||
# gfx90a_ldst_acc.s has MIMG mixed in, filtered via is_mimg check
|
||||
CDNA_FILES = ['gfx9_asm_sop1.s', 'gfx9_asm_sop2.s', 'gfx9_asm_sopp.s', 'gfx9_asm_sopk.s', 'gfx9_asm_sopc.s',
|
||||
'gfx9_asm_vop1.s', 'gfx9_asm_vop2.s', 'gfx9_asm_vopc.s', 'gfx9_asm_vop3.s', 'gfx9_asm_vop3p.s',
|
||||
'gfx9_asm_ds.s', 'gfx9_asm_flat.s', 'gfx9_asm_smem.s',
|
||||
'gfx90a_asm_features.s', 'flat-scratch-gfx942.s', 'gfx942_asm_features.s',
|
||||
'gfx9_asm_ds.s', 'gfx9_asm_flat.s', 'gfx9_asm_smem.s', 'gfx9_asm_mubuf.s', 'gfx9_asm_mtbuf.s',
|
||||
'gfx90a_ldst_acc.s', 'gfx90a_asm_features.s', 'flat-scratch-gfx942.s', 'gfx942_asm_features.s',
|
||||
'mai-gfx90a.s', 'mai-gfx942.s']
|
||||
# RDNA4 (gfx12) test files for compute instructions
|
||||
# Excluded: gfx12_asm_vbuffer_mubuf.s, gfx12_asm_vbuffer_mtbuf.s, gfx12_asm_exp.s (graphics-only)
|
||||
# RDNA4 (gfx12) test files - excludes alias/err/fake16/dpp files, and vimage/vsample (not supported)
|
||||
# NOTE: vflat/vdsdir excluded - not implemented; features.s has mixed formats
|
||||
RDNA4_FILES = ['gfx12_asm_sop1.s', 'gfx12_asm_sop2.s', 'gfx12_asm_sopp.s', 'gfx12_asm_sopk.s', 'gfx12_asm_sopc.s',
|
||||
'gfx12_asm_vop1.s', 'gfx12_asm_vop2.s', 'gfx12_asm_vopc.s', 'gfx12_asm_vopcx.s', 'gfx12_asm_vop3.s', 'gfx12_asm_vop3c.s',
|
||||
'gfx12_asm_vop3cx.s', 'gfx12_asm_vop3p.s', 'gfx12_asm_vop3_from_vop1.s', 'gfx12_asm_vop3_from_vop2.s',
|
||||
'gfx12_asm_vop3p_features.s', 'gfx12_asm_vopd.s', 'gfx12_asm_vopd_features.s',
|
||||
'gfx12_asm_ds.s', 'gfx12_asm_smem.s',
|
||||
'gfx12_asm_wmma_w32.s']
|
||||
'gfx12_asm_vbuffer_mubuf.s', 'gfx12_asm_vbuffer_mtbuf.s', 'gfx12_asm_wmma_w32.s', 'gfx12_asm_exp.s']
|
||||
|
||||
def _is_mimg(data: bytes) -> bool: return (int.from_bytes(data[:4], 'little') >> 26) & 0x3f == 0b111100
|
||||
|
||||
def _parse_llvm_tests(text: str, pattern: str) -> list[tuple[str, bytes]]:
|
||||
tests = []
|
||||
@@ -61,16 +52,15 @@ def _parse_llvm_tests(text: str, pattern: str) -> list[tuple[str, bytes]]:
|
||||
def _get_tests(f: str, arch: str) -> list[tuple[str, bytes]]:
|
||||
text = fetch(f"{LLVM_BASE}/{f}").read_bytes().decode('utf-8', errors='ignore')
|
||||
if arch == "rdna3":
|
||||
# Match GFX11 and W32 only (wavefront32 mode)
|
||||
tests = _parse_llvm_tests(text, r'(?:GFX11|W32)')
|
||||
tests = _parse_llvm_tests(text, r'(?:GFX11|W32|W64)')
|
||||
elif arch == "rdna4":
|
||||
# Match GFX12 (but not GFX1250) and W32 only (wavefront32 mode)
|
||||
tests = _parse_llvm_tests(text, r'(?:GFX12(?!50)|W32)')
|
||||
# Match GFX12 but not GFX1250 (which has different lit64 encoding)
|
||||
tests = _parse_llvm_tests(text, r'(?:GFX12(?!50)|W32|W64)')
|
||||
elif 'gfx90a' in f or 'gfx942' in f:
|
||||
tests = _parse_llvm_tests(text, r'(?:GFX90A|GFX942)')
|
||||
else:
|
||||
tests = _parse_llvm_tests(text, r'(?:VI9|GFX9|CHECK)')
|
||||
return tests
|
||||
return [(a, d) for a, d in tests if not _is_mimg(d)] if arch == "cdna" else tests
|
||||
|
||||
def _compile_asm_batch(instrs: list[str], arch: str = "rdna3") -> list[bytes]:
|
||||
if not instrs: return []
|
||||
@@ -86,21 +76,23 @@ def _make_test(f: str, arch: str, test_type: str):
|
||||
tests = _get_tests(f, arch)
|
||||
name = f"{arch}_{test_type}_{f}"
|
||||
if test_type == "roundtrip":
|
||||
passed, skipped = 0, 0
|
||||
for _, data in tests:
|
||||
decoded = detect_format(data, arch).from_bytes(data)
|
||||
self.assertEqual(decoded.to_bytes()[:len(data)], data)
|
||||
print(f"{name}: {len(tests)} passed")
|
||||
elif test_type == "asm":
|
||||
passed, skipped = 0, 0
|
||||
for asm_text, expected in tests:
|
||||
try:
|
||||
decoded = detect_format(data, arch).from_bytes(data)
|
||||
self.assertEqual(decoded.to_bytes()[:len(data)], data)
|
||||
self.assertEqual(asm(asm_text, arch).to_bytes(), expected)
|
||||
passed += 1
|
||||
except ValueError: skipped += 1 # skip invalid opcodes not in enum
|
||||
except: skipped += 1
|
||||
print(f"{name}: {passed} passed, {skipped} skipped")
|
||||
if arch in ("rdna3", "rdna4"):
|
||||
self.assertEqual(skipped, 0, f"{name}: {skipped} tests skipped, expected 0")
|
||||
elif test_type == "disasm":
|
||||
to_test = []
|
||||
for _, data in tests:
|
||||
try:
|
||||
decoded = decode_inst(data, arch)
|
||||
decoded = detect_format(data, arch).from_bytes(data)
|
||||
# Skip if roundtrip fails, disasm fails, or op_name is missing (disasm starts with space)
|
||||
if decoded.to_bytes()[:len(data)] == data and (d := disasm(decoded)) and not d.startswith(' '): to_test.append((data, d))
|
||||
except: pass
|
||||
@@ -115,12 +107,14 @@ class TestLLVM(unittest.TestCase): pass
|
||||
|
||||
for f in RDNA_FILES:
|
||||
setattr(TestLLVM, f"test_rdna3_roundtrip_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "rdna3", "roundtrip"))
|
||||
setattr(TestLLVM, f"test_rdna3_asm_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "rdna3", "asm"))
|
||||
setattr(TestLLVM, f"test_rdna3_disasm_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "rdna3", "disasm"))
|
||||
for f in CDNA_FILES:
|
||||
setattr(TestLLVM, f"test_cdna_roundtrip_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "cdna", "roundtrip"))
|
||||
setattr(TestLLVM, f"test_cdna_disasm_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "cdna", "disasm"))
|
||||
for f in RDNA4_FILES:
|
||||
setattr(TestLLVM, f"test_rdna4_roundtrip_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "rdna4", "roundtrip"))
|
||||
setattr(TestLLVM, f"test_rdna4_asm_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "rdna4", "asm"))
|
||||
setattr(TestLLVM, f"test_rdna4_disasm_{f.replace('.s', '').replace('-', '_')}", _make_test(f, "rdna4", "disasm"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,12 +5,12 @@ from extra.assembly.amd.pcode import (Reg, TypedView, TypedView, MASK32, MASK64,
|
||||
_f32, _i32, _f16, _i16, f32_to_f16, isNAN, _bf16, _ibf16, bf16_to_f32, f32_to_bf16,
|
||||
BYTE_PERMUTE, v_sad_u8, v_msad_u8, _compile_pseudocode, _expr, compile_pseudocode)
|
||||
from extra.assembly.amd.test.helpers import ExecContext
|
||||
from extra.assembly.amd.autogen.rdna3.str_pcode import PCODE
|
||||
from extra.assembly.amd.autogen.rdna3.str_pcode import VOP3SDOp_PCODE, VOPCOp_PCODE
|
||||
from extra.assembly.amd.autogen.rdna3.enum import VOP3SDOp, VOPCOp
|
||||
|
||||
# Compile pseudocode functions on demand for regression tests
|
||||
_VOP3SDOp_V_DIV_SCALE_F32 = compile_pseudocode('VOP3SDOp', 'V_DIV_SCALE_F32', PCODE[VOP3SDOp.V_DIV_SCALE_F32])
|
||||
_VOPCOp_V_CMP_CLASS_F32 = compile_pseudocode('VOPCOp', 'V_CMP_CLASS_F32', PCODE[VOPCOp.V_CMP_CLASS_F32_E32])
|
||||
_VOP3SDOp_V_DIV_SCALE_F32 = compile_pseudocode('VOP3SDOp', 'V_DIV_SCALE_F32', VOP3SDOp_PCODE[VOP3SDOp.V_DIV_SCALE_F32])
|
||||
_VOPCOp_V_CMP_CLASS_F32 = compile_pseudocode('VOPCOp', 'V_CMP_CLASS_F32', VOPCOp_PCODE[VOPCOp.V_CMP_CLASS_F32])
|
||||
|
||||
class TestReg(unittest.TestCase):
|
||||
def test_u32_read(self):
|
||||
|
||||
@@ -1,29 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test PDF pseudocode extraction from amdxml.py."""
|
||||
import unittest
|
||||
from extra.assembly.amd.amdxml import extract_pdf_text, extract_pcode, parse_xml, ARCHS, FIXES
|
||||
"""Test pdf.py PDF parser and enum generation."""
|
||||
import unittest, tempfile, importlib.util
|
||||
from extra.assembly.amd.pdf import extract, extract_tables, extract_enums, extract_pcode, write_enums, PDF_URLS
|
||||
|
||||
EXPECTED_PAGES = {"rdna3": 655, "rdna4": 711, "cdna": 610}
|
||||
EXPECTED = {
|
||||
"rdna3": {"pages": 655, "tables": 115, "sop2_ops": 67, "sop2_first": "S_ADD_U32"},
|
||||
"rdna4": {"pages": 711, "tables": 125, "sop2_ops": 74, "sop2_first": "S_ADD_CO_U32"},
|
||||
"cdna": {"pages": 610, "tables": 104, "sop2_ops": 52, "sop2_first": "S_ADD_U32"},
|
||||
}
|
||||
|
||||
class TestPcodePDF(unittest.TestCase):
|
||||
class TestPDF2(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.pages = {arch: extract_pdf_text(cfg["pdf"]) for arch, cfg in ARCHS.items()}
|
||||
cls.enums = {}
|
||||
for arch, cfg in ARCHS.items():
|
||||
_, enums, _, _, _ = parse_xml(cfg["xml"], arch)
|
||||
for fmt, ops in FIXES.get(arch, {}).items(): enums.setdefault(fmt, {}).update(ops)
|
||||
cls.enums[arch] = enums
|
||||
cls.pcode = {arch: extract_pcode(cls.pages[arch], {n: op for ops in cls.enums[arch].values() for op, n in ops.items()}) for arch in ARCHS}
|
||||
cls.data = {name: extract(url) for name, url in PDF_URLS.items()}
|
||||
cls.tables = {name: extract_tables(pages) for name, pages in cls.data.items()}
|
||||
cls.enums = {name: extract_enums(cls.tables[name]) for name in PDF_URLS}
|
||||
cls.pcode = {name: extract_pcode(cls.data[name], cls.enums[name]) for name in PDF_URLS}
|
||||
|
||||
def test_page_counts(self):
|
||||
for name, exp in EXPECTED_PAGES.items():
|
||||
self.assertEqual(len(self.pages[name]), exp, f"{name} page count")
|
||||
for name, exp in EXPECTED.items():
|
||||
self.assertEqual(len(self.data[name]), exp["pages"], f"{name} page count")
|
||||
|
||||
def test_pcode_extracted(self):
|
||||
"""Check we extracted a reasonable number of pcode entries."""
|
||||
for name in ARCHS:
|
||||
self.assertGreater(len(self.pcode[name]), 500, f"{name} pcode count too low")
|
||||
def test_table_counts(self):
|
||||
for name, exp in EXPECTED.items():
|
||||
self.assertEqual(len(self.tables[name]), exp["tables"], f"{name} table count")
|
||||
|
||||
def test_tables_sequential(self):
|
||||
for name in PDF_URLS:
|
||||
nums = sorted(self.tables[name].keys())
|
||||
missing = set(range(1, max(nums) + 1)) - set(nums)
|
||||
self.assertEqual(missing, set(), f"{name} missing tables: {missing}")
|
||||
|
||||
def test_generate_enums(self):
|
||||
for name, exp in EXPECTED.items():
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
||||
write_enums(self.enums[name], name, f.name)
|
||||
spec = importlib.util.spec_from_file_location("enum", f.name)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
# Check SOP2Op
|
||||
self.assertTrue(hasattr(mod, 'SOP2Op'), f"{name} missing SOP2Op")
|
||||
self.assertEqual(len(mod.SOP2Op), exp["sop2_ops"], f"{name} SOP2Op count")
|
||||
self.assertEqual(mod.SOP2Op(0).name, exp["sop2_first"], f"{name} SOP2Op first")
|
||||
# Check all enums have at least 2 ops
|
||||
for attr in dir(mod):
|
||||
if attr.endswith('Op'):
|
||||
self.assertGreaterEqual(len(getattr(mod, attr)), 2, f"{name} {attr} has too few ops")
|
||||
|
||||
def test_pcode_rdna3_tricky(self):
|
||||
"""Test specific pseudocode patterns that are tricky to extract correctly."""
|
||||
@@ -40,8 +62,9 @@ class TestPcodePDF(unittest.TestCase):
|
||||
|
||||
def test_pcode_no_examples(self):
|
||||
"""Pseudocode should not contain example lines with '=>'."""
|
||||
for name in ARCHS:
|
||||
for name in PDF_URLS:
|
||||
for (op_name, opcode), code in self.pcode[name].items():
|
||||
# No example lines (test vectors like "S_CTZ_I32_B32(0xaaaaaaaa) => 1")
|
||||
self.assertNotIn('=>', code, f"{name} {op_name} contains example line with '=>'")
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -37,10 +37,10 @@ class TestRDNA3Asm(unittest.TestCase):
|
||||
v_add_co_u32(v[2], VCC_LO, s[6], v[2]),
|
||||
v_add_co_ci_u32_e32(v[3], s[7], v[3]),
|
||||
v_add_co_u32(v[0], VCC_LO, s[4], v[0]),
|
||||
global_load_b32(vdst=v[2], addr=v[2:3], saddr=OFF),
|
||||
global_load_b32(vdst=v[2], addr=v[2], saddr=OFF),
|
||||
v_add_co_ci_u32_e32(v[1], s[5], v[1]),
|
||||
s_waitcnt(0x03f7), # vmcnt(0)
|
||||
global_store_b32(addr=v[0:1], data=v[2], saddr=OFF),
|
||||
global_store_b32(addr=v[0], data=v[2], saddr=OFF),
|
||||
s_endpgm(),
|
||||
]
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Roundtrip tests: generate tinygrad kernels, decode instructions, re-encode, verify match."""
|
||||
import unittest, io, sys, re, subprocess, os
|
||||
from extra.assembly.amd.dsl import Inst
|
||||
from extra.assembly.amd.decode import decode_inst, detect_format
|
||||
from extra.assembly.amd.asm import asm, detect_format
|
||||
from extra.assembly.amd.test.helpers import get_llvm_mc, get_llvm_objdump
|
||||
|
||||
# arch: (mcpu, mattr)
|
||||
@@ -85,8 +85,8 @@ class TestTinygradKernelRoundtrip(unittest.TestCase):
|
||||
def _test_kernel_roundtrip(self, op_fn):
|
||||
"""Generate kernel from op_fn, test:
|
||||
1. decode -> reencode matches original bytes
|
||||
2. disasm() -> LLVM asm -> bytes matches original (validates disasm correctness)
|
||||
3. our disasm() matches LLVM's disassembly string (informational)
|
||||
2. asm(disasm()) matches LLVM output
|
||||
3. our disasm() matches LLVM's disassembly string exactly
|
||||
"""
|
||||
arch = self.arch
|
||||
mcpu, mattr = ARCH_CONFIG[arch]
|
||||
@@ -129,19 +129,19 @@ class TestTinygradKernelRoundtrip(unittest.TestCase):
|
||||
offset += size
|
||||
|
||||
# Collect disasm strings for batched LLVM calls - skip unknown opcodes (op_X) that LLVM can't compile
|
||||
asm_test_instrs: list[tuple[int, str, bytes]] = [] # (idx, our_disasm, orig_bytes) for asm test
|
||||
asm_test_instrs: list[tuple[int, str]] = [] # (idx, our_disasm) for asm test
|
||||
disasm_test_instrs: list[tuple[int, str]] = [] # (idx, our_disasm) for disasm comparison test
|
||||
|
||||
for idx, (ki, offset, orig_bytes, decoded, our_disasm, decode_ok, decode_err) in enumerate(decoded_instrs):
|
||||
if our_disasm is None: continue
|
||||
# Skip unknown opcodes and malformed instructions
|
||||
# Skip unknown opcodes and malformed instructions for both tests
|
||||
if our_disasm.startswith('op_') or re.search(r', \d+, \d+, \d+,', our_disasm): continue
|
||||
asm_test_instrs.append((idx, our_disasm, orig_bytes))
|
||||
asm_test_instrs.append((idx, our_disasm))
|
||||
disasm_test_instrs.append((idx, our_disasm))
|
||||
|
||||
# Batch compile for asm test (our disasm -> LLVM asm -> bytes)
|
||||
asm_llvm_results = compile_asm_batch([d for _, d, _ in asm_test_instrs], arch)
|
||||
asm_llvm_map = {idx: (result, orig) for (idx, _, orig), result in zip(asm_test_instrs, asm_llvm_results)}
|
||||
# Batch compile for asm test
|
||||
asm_llvm_results = compile_asm_batch([d for _, d in asm_test_instrs], arch)
|
||||
asm_llvm_map = {idx: result for (idx, _), result in zip(asm_test_instrs, asm_llvm_results)}
|
||||
|
||||
# Batch compile+disasm for disasm comparison test
|
||||
disasm_llvm_results = compile_and_disasm_batch([d for _, d in disasm_test_instrs], arch)
|
||||
@@ -165,16 +165,20 @@ class TestTinygradKernelRoundtrip(unittest.TestCase):
|
||||
decode_failed += 1
|
||||
decode_failures.append(f"K{ki}@{offset}: {our_disasm}: {decode_err}")
|
||||
|
||||
# Asm test: our disasm -> LLVM asm -> compare bytes with original
|
||||
# Asm test
|
||||
if our_disasm is None:
|
||||
asm_skipped += 1
|
||||
elif idx in asm_llvm_map:
|
||||
llvm_bytes, orig = asm_llvm_map[idx]
|
||||
if llvm_bytes == orig[:len(llvm_bytes)]:
|
||||
asm_passed += 1
|
||||
else:
|
||||
asm_failed += 1
|
||||
asm_failures.append(f"K{ki}@{offset}: '{our_disasm}': llvm={llvm_bytes.hex()} orig={orig[:len(llvm_bytes)].hex()}")
|
||||
llvm_bytes = asm_llvm_map[idx]
|
||||
try:
|
||||
our_bytes = asm(our_disasm).to_bytes()
|
||||
if our_bytes[:len(llvm_bytes)] == llvm_bytes:
|
||||
asm_passed += 1
|
||||
else:
|
||||
asm_failed += 1
|
||||
asm_failures.append(f"K{ki}@{offset}: '{our_disasm}': ours={our_bytes[:len(llvm_bytes)].hex()} llvm={llvm_bytes.hex()}")
|
||||
except Exception:
|
||||
asm_skipped += 1
|
||||
else:
|
||||
asm_skipped += 1
|
||||
|
||||
@@ -192,7 +196,7 @@ class TestTinygradKernelRoundtrip(unittest.TestCase):
|
||||
disasm_skipped += 1
|
||||
|
||||
print(f"[{arch}] decode roundtrip: {decode_passed} passed, {decode_failed} failed, {decode_skipped} skipped")
|
||||
print(f"[{arch}] asm via llvm: {asm_passed} passed, {asm_failed} failed, {asm_skipped} skipped")
|
||||
print(f"[{arch}] asm vs llvm: {asm_passed} passed, {asm_failed} failed, {asm_skipped} skipped")
|
||||
print(f"[{arch}] disasm vs llvm: {disasm_passed} passed, {disasm_failed} failed, {disasm_skipped} skipped")
|
||||
self.assertEqual(decode_failed, 0, f"Decode failures:\n" + "\n".join(decode_failures[:20]))
|
||||
self.assertEqual(asm_failed, 0, f"Asm failures:\n" + "\n".join(asm_failures[:20]))
|
||||
@@ -243,10 +247,10 @@ class TestTinygradKernelRoundtrip(unittest.TestCase):
|
||||
# Fused ops
|
||||
def test_fma(self): self._test_kernel_roundtrip(lambda T: (T([1.0, 2.0]) * T([3.0, 4.0]) + T([5.0, 6.0])))
|
||||
|
||||
@unittest.skip("RDNA4 decode roundtrip not yet supported")
|
||||
@unittest.skip("no asm support for RDNA4")
|
||||
class TestTinygradKernelRoundtripRDNA4(TestTinygradKernelRoundtrip): arch = 'rdna4'
|
||||
|
||||
@unittest.skip("CDNA decode roundtrip not yet supported")
|
||||
@unittest.skip("no asm support for CDNA")
|
||||
class TestTinygradKernelRoundtripCDNA(TestTinygradKernelRoundtrip): arch = 'cdna'
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests for SQTT packet codec (no hardware required)."""
|
||||
import unittest
|
||||
from extra.assembly.amd.sqtt import (
|
||||
LAYOUT_HEADER, WAVESTART, WAVEEND, INST, NOP,
|
||||
decode, encode, PACKET_TYPES, OPCODE_TO_CLASS
|
||||
)
|
||||
|
||||
|
||||
class TestSQTTCodec(unittest.TestCase):
|
||||
"""Tests for SQTT encoder/decoder roundtrip."""
|
||||
|
||||
def test_roundtrip_simple(self):
|
||||
"""Test encode/decode roundtrip for simple packets."""
|
||||
test_packets = [
|
||||
LAYOUT_HEADER.from_raw(0x100),
|
||||
WAVESTART.from_raw(0x0),
|
||||
INST.from_raw(0x10), # delta=1
|
||||
INST.from_raw(0x10), # delta=1
|
||||
WAVEEND.from_raw(0x40), # delta=2
|
||||
]
|
||||
encoded = encode(test_packets)
|
||||
decoded = decode(encoded)
|
||||
|
||||
self.assertGreaterEqual(len(decoded), len(test_packets))
|
||||
for i, (orig, dec) in enumerate(zip(test_packets, decoded)):
|
||||
self.assertEqual(type(orig), type(dec), f"type mismatch at {i}")
|
||||
|
||||
def test_decode_empty(self):
|
||||
"""Test decoding empty data."""
|
||||
packets = decode(b'')
|
||||
self.assertEqual(packets, [])
|
||||
|
||||
def test_encode_empty(self):
|
||||
"""Test encoding empty list."""
|
||||
data = encode([])
|
||||
self.assertEqual(data, b'')
|
||||
|
||||
def test_all_packet_types_have_encoding(self):
|
||||
"""All packet types should have an encoding defined."""
|
||||
for pkt_cls in PACKET_TYPES:
|
||||
self.assertIsNotNone(pkt_cls._encoding, f"{pkt_cls.__name__} missing encoding")
|
||||
|
||||
def test_packet_from_raw(self):
|
||||
"""Test creating packets from raw values."""
|
||||
# INST with wave=5, op=0x21, delta=2
|
||||
raw = (0x21 << 13) | (5 << 8) | (2 << 4) | 0b010
|
||||
pkt = INST.from_raw(raw)
|
||||
self.assertEqual(pkt.wave, 5)
|
||||
self.assertEqual(pkt.op, 0x21)
|
||||
self.assertEqual(pkt.delta, 2)
|
||||
|
||||
|
||||
class TestDecodeRealBlob(unittest.TestCase):
|
||||
"""Test decoding real SQTT blobs from examples."""
|
||||
|
||||
def test_decode_example_file(self):
|
||||
"""Test decoding a real SQTT blob from examples."""
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
example_path = Path(__file__).parent.parent.parent.parent / "sqtt/examples/profile_plus_run_0.pkl"
|
||||
if not example_path.exists():
|
||||
self.skipTest(f"Example file not found: {example_path}")
|
||||
|
||||
from tinygrad.runtime.ops_amd import ProfileSQTTEvent
|
||||
with open(example_path, "rb") as f:
|
||||
data = pickle.load(f)
|
||||
|
||||
sqtt_events = [e for e in data if isinstance(e, ProfileSQTTEvent)]
|
||||
self.assertGreater(len(sqtt_events), 0, "No SQTT events in example")
|
||||
|
||||
packets = decode(sqtt_events[0].blob)
|
||||
self.assertGreater(len(packets), 0, "No packets decoded")
|
||||
# First packet should be LAYOUT_HEADER
|
||||
self.assertIsInstance(packets[0], LAYOUT_HEADER)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,545 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests for SQTT emulator correctness against known hardware patterns.
|
||||
|
||||
NOTE: This file only tests NOP and VALU behavior. For WMMA/DP/trans tests,
|
||||
see test_sqtt_compare.py.
|
||||
|
||||
Run emulator tests: PYTHONPATH="." python3 extra/assembly/amd/test/test_sqtt_correct.py
|
||||
Run hardware tests: SQTT_HW=1 PYTHONPATH="." python3 extra/assembly/amd/test/test_sqtt_correct.py
|
||||
"""
|
||||
import os
|
||||
import unittest
|
||||
|
||||
USE_HW = os.environ.get("SQTT_HW", "0") == "1"
|
||||
|
||||
if USE_HW:
|
||||
os.environ["SQTT"] = "1"
|
||||
os.environ["PROFILE"] = "1"
|
||||
os.environ["SQTT_LIMIT_SE"] = "2"
|
||||
os.environ["SQTT_TOKEN_EXCLUDE"] = "3784"
|
||||
|
||||
from extra.assembly.amd.emu import SQTTState, decode_program, exec_wave, WaveState, LDSMem
|
||||
from extra.assembly.amd.sqtt import WAVESTART, WAVEEND
|
||||
from extra.assembly.amd.autogen.rdna3.ins import v_mov_b32_e32, v_add_f32_e32, s_nop, s_endpgm, s_delay_alu
|
||||
from extra.assembly.amd.dsl import v
|
||||
|
||||
def assemble(instructions: list) -> bytes:
|
||||
return b''.join(inst.to_bytes() for inst in instructions)
|
||||
|
||||
def wrap_with_nops(instructions: list, nops=16) -> list:
|
||||
return instructions + [s_nop(0)]*nops + [s_endpgm()]
|
||||
|
||||
def get_wave_packets(packets: list) -> list:
|
||||
result, in_wave = [], False
|
||||
for p in packets:
|
||||
if isinstance(p, WAVESTART) and p.simd == 0:
|
||||
in_wave, result = True, [p]
|
||||
elif in_wave:
|
||||
result.append(p)
|
||||
if isinstance(p, WAVEEND): break
|
||||
return result
|
||||
|
||||
def get_timing_deltas(packets: list) -> list[tuple[str, int]]:
|
||||
skip_types = {"NOP", "TS_DELTA_SHORT", "TS_WAVE_STATE", "TS_DELTA_OR_MARK", "TS_DELTA_S5_W2", "TS_DELTA_S5_W3", "TS_DELTA_S8_W3", "REG"}
|
||||
filtered = [p for p in packets if type(p).__name__ not in skip_types]
|
||||
if not filtered: return []
|
||||
result = [(type(filtered[0]).__name__, 0)]
|
||||
for i in range(1, len(filtered)):
|
||||
result.append((type(filtered[i]).__name__, filtered[i]._time - filtered[i-1]._time))
|
||||
return result
|
||||
|
||||
def run_emulator(instructions: list) -> list:
|
||||
code = assemble(instructions)
|
||||
program = decode_program(code)
|
||||
st = WaveState()
|
||||
st.exec_mask = (1 << 32) - 1
|
||||
lds = LDSMem(bytearray(65536))
|
||||
trace = SQTTState(wave_id=0, simd=0, cu=0)
|
||||
exec_wave(program, st, lds, 32, trace)
|
||||
return get_wave_packets(trace.packets)
|
||||
|
||||
def get_all_waves(packets: list) -> list[list]:
|
||||
"""Extract all WAVESTART..WAVEEND ranges on simd 0."""
|
||||
waves, in_wave, current = [], False, []
|
||||
for p in packets:
|
||||
if isinstance(p, WAVESTART) and p.simd == 0:
|
||||
in_wave, current = True, [p]
|
||||
elif in_wave:
|
||||
current.append(p)
|
||||
if isinstance(p, WAVEEND):
|
||||
waves.append(current)
|
||||
in_wave, current = False, []
|
||||
return waves
|
||||
|
||||
def run_hardware(instructions: list) -> list:
|
||||
from extra.assembly.amd.test.test_sqtt_hw import compile_asm_sqtt, run_prg_sqtt_batch
|
||||
from extra.assembly.amd.sqtt import decode
|
||||
from collections import Counter
|
||||
|
||||
prg = compile_asm_sqtt(instructions, alu_only=True)
|
||||
|
||||
for _ in range(10):
|
||||
blobs = run_prg_sqtt_batch(prg, n_runs=200)
|
||||
# Extract all waves from all blobs
|
||||
traces = []
|
||||
for blob in blobs:
|
||||
traces.extend(get_all_waves(decode(blob)))
|
||||
if not traces:
|
||||
continue
|
||||
# Find most common pattern
|
||||
delta_sets = [tuple(get_timing_deltas(t)) for t in traces]
|
||||
most_common = Counter(delta_sets).most_common(1)[0][0]
|
||||
for t in traces:
|
||||
if tuple(get_timing_deltas(t)) == most_common:
|
||||
return t
|
||||
return []
|
||||
|
||||
def run_sqtt(instructions: list, nops: int = 16) -> list:
|
||||
instructions = wrap_with_nops(instructions, nops=nops)
|
||||
return run_hardware(instructions) if USE_HW else run_emulator(instructions)
|
||||
|
||||
def get_deltas(instructions: list) -> tuple[list[int], list[int]]:
|
||||
"""Run and return (issue deltas, exec deltas).
|
||||
Issue = IMMEDIATE + VALUINST, Exec = ALUEXEC.
|
||||
Deltas are between consecutive packets of same stream."""
|
||||
deltas = get_timing_deltas(run_sqtt(instructions))
|
||||
time = 0
|
||||
issue_times, exec_times = [], []
|
||||
for ptype, delta in deltas:
|
||||
time += delta
|
||||
if ptype in ('IMMEDIATE', 'VALUINST'):
|
||||
issue_times.append(time)
|
||||
elif ptype == 'ALUEXEC':
|
||||
exec_times.append(time)
|
||||
issue = [issue_times[i] - issue_times[i-1] for i in range(1, len(issue_times))]
|
||||
execd = [exec_times[i] - exec_times[i-1] for i in range(1, len(exec_times))]
|
||||
return issue, execd
|
||||
|
||||
# ************************************ tests ************************************
|
||||
|
||||
class TestVALUChains(unittest.TestCase):
|
||||
"""VALU dependency chains."""
|
||||
def _chain(self, n, expected_issue, expected_exec):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0)] + [v_add_f32_e32(v[i], v[i-1], v[i-1]) for i in range(1, n)]
|
||||
issue, execd = get_deltas(instrs)
|
||||
self.assertEqual(issue[:n-1], expected_issue)
|
||||
if isinstance(expected_exec[0], list): self.assertIn(execd, expected_exec)
|
||||
else: self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_chain_2(self): self._chain(2, [1], [6])
|
||||
def test_chain_3(self): self._chain(3, [1, 1], [6, 5])
|
||||
def test_chain_4(self): self._chain(4, [1, 1, 1], [6, 5, 5])
|
||||
def test_chain_5(self): self._chain(5, [1, 1, 1, 1], [6, 5, 5, 9])
|
||||
def test_chain_6(self): self._chain(6, [1, 1, 1, 1, 1], [6, 5, 5, 9, 9])
|
||||
def test_chain_7(self): self._chain(7, [1, 1, 1, 1, 1, 1], [6, 5, 5, 5, 9, 9])
|
||||
def test_chain_8(self): self._chain(8, [1, 1, 1, 1, 1, 1, 1], [6, 5, 5, 5, 9, 9, 9])
|
||||
# NOTE: position 8 can be 5 or 9 depending on GPU variant
|
||||
def test_chain_12(self): self._chain(12, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [[6, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9], [6, 5, 5, 5, 5, 9, 9, 9, 5, 9, 9]])
|
||||
def test_chain_14(self): self._chain(14, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [[6, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9], [6, 5, 5, 5, 5, 9, 9, 9, 5, 9, 9, 9, 9]])
|
||||
# issue stalls start here
|
||||
def test_chain_15(self): self._chain(15, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], [[6, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9], [6, 5, 5, 5, 5, 5, 9, 9, 5, 9, 9, 9, 9, 9]])
|
||||
def test_chain_16(self): self._chain(16, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5], [[6, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9], [6, 5, 5, 5, 5, 5, 5, 9, 5, 9, 9, 9, 9, 9, 9]])
|
||||
def test_chain_18(self): self._chain(18, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 5, 5], [6, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9])
|
||||
def test_chain_20(self): self._chain(20, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 5, 5, 5, 5], [6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9])
|
||||
|
||||
|
||||
class TestVALUChainsWithWarmup(unittest.TestCase):
|
||||
"""VALU dependency chains with early VALUs to isolate warmup effects."""
|
||||
# just the first stupid VALU takes 6
|
||||
def _chain(self, n, warmup=True):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), s_nop(100)] if warmup else [s_nop(100)]
|
||||
instrs += [v_mov_b32_e32(v[0], 1.0)] + [v_add_f32_e32(v[i], v[i-1], v[i-1]) for i in range(1, n)]
|
||||
issue, execd = get_deltas(instrs)
|
||||
return execd[1:] if warmup else execd
|
||||
|
||||
def test_warmup_chain_2(self): self.assertEqual(self._chain(2), [5])
|
||||
def test_warmup_chain_3(self): self.assertEqual(self._chain(3), [5, 5])
|
||||
def test_warmup_chain_4(self): self.assertEqual(self._chain(4), [5, 5, 5])
|
||||
def test_warmup_chain_5(self): self.assertEqual(self._chain(5), [5, 5, 5, 9])
|
||||
def test_warmup_chain_6(self): self.assertEqual(self._chain(6), [5, 5, 5, 5, 9])
|
||||
def test_warmup_chain_7(self): self.assertEqual(self._chain(7), [5, 5, 5, 5, 9, 9])
|
||||
def test_warmup_chain_8(self): self.assertEqual(self._chain(8), [5, 5, 5, 5, 9, 9, 9])
|
||||
|
||||
def test_cold_chain_2(self): self.assertEqual(self._chain(2, False), [6])
|
||||
def test_cold_chain_3(self): self.assertEqual(self._chain(3, False), [6, 5])
|
||||
def test_cold_chain_4(self): self.assertEqual(self._chain(4, False), [6, 5, 5])
|
||||
def test_cold_chain_5(self): self.assertEqual(self._chain(5, False), [6, 5, 5, 9])
|
||||
def test_cold_chain_6(self): self.assertEqual(self._chain(6, False), [6, 5, 5, 9, 9])
|
||||
def test_cold_chain_7(self): self.assertEqual(self._chain(7, False), [6, 5, 5, 5, 9, 9])
|
||||
def test_cold_chain_8(self): self.assertEqual(self._chain(8, False), [6, 5, 5, 5, 9, 9, 9])
|
||||
|
||||
|
||||
class TestVALUIndependent(unittest.TestCase):
|
||||
"""Independent VALU instructions."""
|
||||
def _ind(self, n, expected_exec):
|
||||
instrs = [v_mov_b32_e32(v[i], float(i)) for i in range(n)]
|
||||
issue, execd = get_deltas(instrs)
|
||||
self.assertEqual(issue[:n-1], [1]*(n-1))
|
||||
self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_ind_2(self): self._ind(2, [1])
|
||||
def test_ind_3(self): self._ind(3, [1, 1])
|
||||
def test_ind_4(self): self._ind(4, [1, 1, 1])
|
||||
def test_ind_5(self): self._ind(5, [1, 1, 1, 1])
|
||||
def test_ind_6(self): self._ind(6, [1, 1, 1, 1, 1])
|
||||
def test_ind_7(self): self._ind(7, [1, 1, 1, 1, 1, 1])
|
||||
def test_ind_8(self): self._ind(8, [1, 1, 1, 1, 1, 1, 1])
|
||||
|
||||
|
||||
class TestForwardingGap(unittest.TestCase):
|
||||
"""Producer + N independent instructions + consumer - tests forwarding window."""
|
||||
def _exec_deltas(self, n_gap):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0)]
|
||||
instrs += [v_mov_b32_e32(v[10+i], float(i)) for i in range(n_gap)]
|
||||
instrs += [v_add_f32_e32(v[1], v[0], v[0])]
|
||||
_, execd = get_deltas(instrs)
|
||||
return execd
|
||||
|
||||
def test_gap0(self): self.assertEqual(self._exec_deltas(0), [6])
|
||||
def test_gap1(self): self.assertEqual(self._exec_deltas(1), [1, 5])
|
||||
def test_gap2(self): self.assertEqual(self._exec_deltas(2), [1, 1, 4])
|
||||
def test_gap3(self): self.assertIn(self._exec_deltas(3), [[1, 1, 1, 3], [1, 1, 1, 4]])
|
||||
def test_gap4(self): self.assertIn(self._exec_deltas(4), [[1, 1, 1, 1, 3], [1, 1, 1, 1, 4]])
|
||||
def test_gap5(self): self.assertEqual(self._exec_deltas(5), [1, 1, 1, 1, 1, 4]) # anomaly
|
||||
def test_gap6(self): self.assertEqual(self._exec_deltas(6), [1, 1, 1, 1, 1, 1, 3])
|
||||
def test_gap7(self): self.assertEqual(self._exec_deltas(7), [1, 1, 1, 1, 1, 1, 1, 3])
|
||||
def test_gap8(self): self.assertEqual(self._exec_deltas(8), [1, 1, 1, 1, 1, 1, 1, 1, 3])
|
||||
def test_gap9(self): self.assertEqual(self._exec_deltas(9), [1, 1, 1, 1, 1, 1, 1, 1, 1, 3])
|
||||
|
||||
|
||||
class TestChainWithIndependentGap(unittest.TestCase):
|
||||
"""Chain of dependent VALUs with independent VALUs inserted before the last one.
|
||||
|
||||
Hardware observation: In a chain v0->v1->v2->v3->v4, if we insert N independent VALUs
|
||||
before v4, the forwarding behavior changes:
|
||||
- 0-1 independent VALUs: v4 cannot forward from v3 (delta=9)
|
||||
- 2+ independent VALUs: v4 can forward from v3 (delta=5)
|
||||
|
||||
This suggests forwarding eligibility depends on whether the direct source is in the ALU
|
||||
at issue time, not just at dispatch time.
|
||||
"""
|
||||
def _chain5_gap(self, n_ind):
|
||||
"""Chain v0->v1->v2->v3->v4 with N independent VALUs before v4. Returns v3->v4 delta."""
|
||||
instrs = [s_nop(100),
|
||||
v_mov_b32_e32(v[0], 1.0),
|
||||
v_mov_b32_e32(v[1], v[0]),
|
||||
v_mov_b32_e32(v[2], v[1]),
|
||||
v_mov_b32_e32(v[3], v[2])]
|
||||
instrs += [v_mov_b32_e32(v[10+i], float(i)) for i in range(n_ind)]
|
||||
instrs += [v_mov_b32_e32(v[4], v[3])]
|
||||
_, execd = get_deltas(instrs)
|
||||
# Chain execs are at indices 0,1,2,3 and last one. Independent ones are in between.
|
||||
# v3->v4 delta = last exec time - 4th exec time (index 3)
|
||||
# With n_ind independent VALUs, execd has 4 + n_ind entries
|
||||
# We want delta between exec[3] (v3) and exec[4+n_ind-1] (v4)
|
||||
# Actually execd is already deltas, so we need absolute times
|
||||
time, exec_times = 0, []
|
||||
packets = run_sqtt(instrs)
|
||||
for ptype, delta in get_timing_deltas(packets):
|
||||
time += delta
|
||||
if ptype == 'ALUEXEC': exec_times.append(time)
|
||||
# v0,v1,v2,v3 are first 4, v4 is last
|
||||
return exec_times[-1] - exec_times[3]
|
||||
|
||||
def test_gap0(self): self.assertEqual(self._chain5_gap(0), 9)
|
||||
def test_gap1(self): self.assertEqual(self._chain5_gap(1), 9)
|
||||
def test_gap2(self): self.assertEqual(self._chain5_gap(2), 5)
|
||||
def test_gap3(self): self.assertEqual(self._chain5_gap(3), 5)
|
||||
def test_gap4(self): self.assertEqual(self._chain5_gap(4), 5)
|
||||
|
||||
|
||||
class TestVALULatency(unittest.TestCase):
|
||||
"""VALU latency depends on VGPR source reads.
|
||||
6 cycles: no VGPR source (constant only), stays 6 regardless of warmup
|
||||
8-11 cycles: VGPR source read, decreases with warmup (11->10->9->8)
|
||||
s_nop(0) after VALU immediately drops VGPR read latency to 8
|
||||
Anomalies:
|
||||
- 7 consecutive VALUs (no s_nop) causes +1 cycle penalty
|
||||
- n=0 or n=3 const VALUs + nop + vgpr = 9 cycles (not 8)
|
||||
"""
|
||||
def _get_latency(self, instrs):
|
||||
if not isinstance(instrs, list): instrs = [instrs]
|
||||
packets = run_sqtt(instrs)
|
||||
deltas = get_timing_deltas(packets)
|
||||
time, valu_times, exec_times = 0, [], []
|
||||
for ptype, delta in deltas:
|
||||
time += delta
|
||||
if ptype == 'VALUINST': valu_times.append(time)
|
||||
if ptype == 'ALUEXEC': exec_times.append(time)
|
||||
return exec_times[-1] - valu_times[-1] if valu_times and exec_times else None
|
||||
|
||||
# 6-cycle latency: no VGPR source (constant), always 6
|
||||
def test_const_single(self): self.assertEqual(self._get_latency(v_mov_b32_e32(v[0], 1.0)), 6)
|
||||
def test_const_literal(self): self.assertEqual(self._get_latency(v_mov_b32_e32(v[0], 565.0)), 6)
|
||||
def test_const_after_const(self): self.assertEqual(self._get_latency([v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[1], 2.0)]), 6)
|
||||
def test_const_after_nop(self): self.assertEqual(self._get_latency([v_mov_b32_e32(v[0], 1.0), s_nop(0), v_mov_b32_e32(v[1], 2.0)]), 6)
|
||||
|
||||
# VGPR read latency: cold start = 9
|
||||
def test_vgpr_cold(self): self.assertEqual(self._get_latency(v_mov_b32_e32(v[0], v[1])), 9)
|
||||
|
||||
# VGPR read latency: warmup decreases 11->10->9->8
|
||||
def _vgpr_after_n_const(self, n):
|
||||
return self._get_latency([v_mov_b32_e32(v[i], float(i)) for i in range(n)] + [v_mov_b32_e32(v[10], v[99])])
|
||||
def test_vgpr_after_1_const(self): self.assertEqual(self._vgpr_after_n_const(1), 11)
|
||||
def test_vgpr_after_2_const(self): self.assertEqual(self._vgpr_after_n_const(2), 10)
|
||||
def test_vgpr_after_3_const(self): self.assertEqual(self._vgpr_after_n_const(3), 9)
|
||||
def test_vgpr_after_4_const(self): self.assertIn(self._vgpr_after_n_const(4), [8, 9])
|
||||
def test_vgpr_after_5_const(self): self.assertIn(self._vgpr_after_n_const(5), [8, 9])
|
||||
def test_vgpr_after_6_const(self): self.assertEqual(self._vgpr_after_n_const(6), 9) # anomaly
|
||||
def test_vgpr_after_7_const(self): self.assertEqual(self._vgpr_after_n_const(7), 8)
|
||||
def test_vgpr_after_8_const(self): self.assertEqual(self._vgpr_after_n_const(8), 8)
|
||||
|
||||
# s_nop(0) immediately drops VGPR read latency to 8 (or 9 on some variants)
|
||||
def test_vgpr_nop_warmup(self): self.assertIn(self._get_latency([v_mov_b32_e32(v[0], 1.0), s_nop(0), v_mov_b32_e32(v[1], v[99])]), [8, 9])
|
||||
|
||||
# s_nop + vgpr read: latency depends on # of const VALUs before nop
|
||||
def _n_const_nop_vgpr(self, n):
|
||||
"""N const VALUs + s_nop(0) + vgpr read."""
|
||||
instrs = [v_mov_b32_e32(v[i], float(i)) for i in range(n)]
|
||||
instrs += [s_nop(0)]
|
||||
instrs += [v_mov_b32_e32(v[10], v[99])]
|
||||
return self._get_latency(instrs)
|
||||
def test_0_const_nop_vgpr(self): self.assertEqual(self._n_const_nop_vgpr(0), 9)
|
||||
def test_1_const_nop_vgpr(self): self.assertIn(self._n_const_nop_vgpr(1), [8, 9])
|
||||
def test_2_const_nop_vgpr(self): self.assertIn(self._n_const_nop_vgpr(2), [8, 9])
|
||||
def test_3_const_nop_vgpr(self): self.assertEqual(self._n_const_nop_vgpr(3), 9) # anomaly
|
||||
def test_4_const_nop_vgpr(self): self.assertEqual(self._n_const_nop_vgpr(4), 8)
|
||||
def test_5_const_nop_vgpr(self): self.assertEqual(self._n_const_nop_vgpr(5), 8)
|
||||
def test_6_const_nop_vgpr(self): self.assertEqual(self._n_const_nop_vgpr(6), 8)
|
||||
def test_7_const_nop_vgpr(self): self.assertEqual(self._n_const_nop_vgpr(7), 8)
|
||||
|
||||
|
||||
class TestChainWithNop(unittest.TestCase):
|
||||
"""Dependency chain with s_nop between instructions."""
|
||||
def _test(self, nop_val, expected_issue, expected_exec):
|
||||
issue, execd = get_deltas([v_mov_b32_e32(v[0], 1.0), s_nop(nop_val), v_add_f32_e32(v[1], v[0], v[0])])
|
||||
self.assertEqual(issue[:2], expected_issue)
|
||||
if isinstance(expected_exec[0], list): self.assertIn(execd, expected_exec)
|
||||
else: self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_nop0(self): self._test(0, [3, 1], [[6], [7]])
|
||||
def test_nop1(self): self._test(1, [4, 1], [[7], [8]])
|
||||
def test_nop2(self): self._test(2, [5, 1], [9])
|
||||
def test_nop3(self): self._test(3, [6, 1], [9])
|
||||
def test_nop4(self): self._test(4, [11, 1], [10])
|
||||
def test_nop5(self): self._test(5, [12, 1], [11])
|
||||
|
||||
|
||||
class TestIndWithNop(unittest.TestCase):
|
||||
"""Independent instructions with s_nop between."""
|
||||
def _test(self, nop_val, expected_issue, expected_exec):
|
||||
issue, execd = get_deltas([v_mov_b32_e32(v[0], 1.0), s_nop(nop_val), v_mov_b32_e32(v[1], 2.0)])
|
||||
self.assertEqual(issue[:2], expected_issue)
|
||||
self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_nop0(self): self._test(0, [3, 1], [4])
|
||||
def test_nop1(self): self._test(1, [4, 1], [5])
|
||||
def test_nop3(self): self._test(3, [6, 1], [7])
|
||||
def test_nop4(self): self._test(4, [11, 1], [8])
|
||||
def test_nop5(self): self._test(5, [12, 1], [9])
|
||||
|
||||
|
||||
class TestChain3NopMid(unittest.TestCase):
|
||||
"""3-instruction chain with s_nop in middle."""
|
||||
def _test(self, nop_val, expected_issue, expected_exec):
|
||||
issue, execd = get_deltas([
|
||||
v_mov_b32_e32(v[0], 1.0), v_add_f32_e32(v[1], v[0], v[0]),
|
||||
s_nop(nop_val), v_add_f32_e32(v[2], v[1], v[1])])
|
||||
self.assertEqual(issue[:3], expected_issue)
|
||||
self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_nop0(self): self._test(0, [1, 3, 1], [6, 5])
|
||||
def test_nop1(self): self._test(1, [1, 4, 1], [6, 5])
|
||||
def test_nop2(self): self._test(2, [1, 5, 1], [6, 5])
|
||||
def test_nop3(self): self._test(3, [1, 10, 1], [6, 5])
|
||||
|
||||
|
||||
class TestInd3NopMid(unittest.TestCase):
|
||||
"""3 independent instructions with s_nop in middle."""
|
||||
def _test(self, nop_val, expected_issue, expected_exec):
|
||||
issue, execd = get_deltas([
|
||||
v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[1], 2.0),
|
||||
s_nop(nop_val), v_mov_b32_e32(v[2], 3.0)])
|
||||
self.assertEqual(issue[:3], expected_issue)
|
||||
self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_nop0(self): self._test(0, [1, 3, 1], [1, 4])
|
||||
def test_nop1(self): self._test(1, [1, 4, 1], [1, 5])
|
||||
def test_nop2(self): self._test(2, [1, 5, 1], [1, 6])
|
||||
def test_nop3(self): self._test(3, [1, 10, 1], [1, 7])
|
||||
|
||||
|
||||
class TestSNopDelay(unittest.TestCase):
|
||||
"""Single s_nop delay between two independent v_movs.
|
||||
s_nop(n) delays n+1 cycles, plus +4 extra for n in [11, 22].
|
||||
Exec delta = n + 4 (baseline) + 4 (if 11 <= n <= 22)."""
|
||||
def _test(self, n, expected):
|
||||
_, execd = get_deltas([v_mov_b32_e32(v[0], 1.0), s_nop(n), v_mov_b32_e32(v[1], 2.0)])
|
||||
if isinstance(expected, list): self.assertIn(execd[0], expected)
|
||||
else: self.assertEqual(execd, [expected])
|
||||
|
||||
def test_snop_0(self): self._test(0, 4)
|
||||
def test_snop_1(self): self._test(1, 5)
|
||||
def test_snop_2(self): self._test(2, 6)
|
||||
def test_snop_3(self): self._test(3, 7)
|
||||
def test_snop_4(self): self._test(4, 8)
|
||||
def test_snop_5(self): self._test(5, 9)
|
||||
def test_snop_6(self): self._test(6, 10)
|
||||
def test_snop_7(self): self._test(7, 11)
|
||||
def test_snop_10(self): self._test(10, 14)
|
||||
def test_snop_11(self): self._test(11, 19) # +4 extra starts here
|
||||
def test_snop_15(self): self._test(15, 23)
|
||||
def test_snop_22(self): self._test(22, 30) # +4 extra ends here
|
||||
def test_snop_23(self): self._test(23, 27)
|
||||
def test_snop_31(self): self._test(31, 35)
|
||||
def test_snop_32(self): self._test(32, 36)
|
||||
def test_snop_63(self): self._test(63, [67, 71])
|
||||
|
||||
|
||||
class TestVALUExecWithNop(unittest.TestCase):
|
||||
"""Single VALU followed by s_nop - measures VALUINST to ALUEXEC delay."""
|
||||
def _get_delay(self, instrs, nops=16):
|
||||
deltas = get_timing_deltas(run_sqtt(instrs, nops=nops))
|
||||
time, valu_time, exec_time = 0, None, None
|
||||
for ptype, delta in deltas:
|
||||
time += delta
|
||||
if ptype == 'VALUINST' and valu_time is None: valu_time = time
|
||||
if ptype == 'ALUEXEC' and exec_time is None: exec_time = time
|
||||
return exec_time - valu_time
|
||||
|
||||
# Boundary: s_nop(0-3) = 6 cycles, s_nop(4+) = 10 cycles
|
||||
def test_nop0(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(0)]), 6)
|
||||
def test_nop1(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(1)]), 6)
|
||||
def test_nop2(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(2)]), 6)
|
||||
def test_nop3(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(3)]), 6)
|
||||
def test_nop4(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(4)]), 10)
|
||||
def test_nop5(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(5)]), 10)
|
||||
def test_nop6(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(6)]), 10)
|
||||
def test_nop7(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(7)]), 10)
|
||||
def test_nop8(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(8)]), 10)
|
||||
def test_nop9(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(9)]), 10)
|
||||
def test_nop10(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(10)]), 10)
|
||||
# No nop = slow path, one s_nop(0) padding = fast path
|
||||
def test_no_padding(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0)], nops=0), 10)
|
||||
def test_one_padding(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0)], nops=1), 6)
|
||||
# Multiple s_nop(0)s don't accumulate - still fast path
|
||||
def test_nop0_x2(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(0), s_nop(0)]), 6)
|
||||
# First nop determines path: s_nop(0) then s_nop(4) = fast, s_nop(4) then s_nop(0) = slow
|
||||
def test_nop0_nop4(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(0), s_nop(4)]), 6)
|
||||
def test_nop4_nop0(self): self.assertEqual(self._get_delay([v_mov_b32_e32(v[0], 1.0), s_nop(4), s_nop(0)]), 10)
|
||||
|
||||
|
||||
class TestDelayALU(unittest.TestCase):
|
||||
"""s_delay_alu behavior - helps understand hardware pipeline latencies.
|
||||
|
||||
s_delay_alu(simm16) where simm16 encodes:
|
||||
instid0[3:0] = dependency on VALU N instructions back (1-4), 0=none
|
||||
skip[6:4] = skip count for second dependency
|
||||
instid1[10:7] = second dependency
|
||||
|
||||
Key insight: s_delay_alu tells hardware to wait for a previous VALU to complete.
|
||||
The hardware determines how many cycles to stall based on pipeline state.
|
||||
"""
|
||||
def _exec_delta(self, instrs):
|
||||
"""Return exec delta for last instruction."""
|
||||
_, execd = get_deltas(instrs)
|
||||
return execd[-1] if execd else None
|
||||
|
||||
# Direct dependency (producer -> consumer), instid0=1 means "wait for VALU 1 back"
|
||||
def test_direct_no_delay(self):
|
||||
# Without s_delay_alu: 6 cycles
|
||||
self.assertEqual(self._exec_delta([v_mov_b32_e32(v[0], 1.0), v_add_f32_e32(v[1], v[0], v[0])]), 6)
|
||||
|
||||
def test_direct_delay1(self):
|
||||
# With s_delay_alu(instid0=1): 7-8 cycles (+1 from the delay instruction)
|
||||
self.assertIn(self._exec_delta([v_mov_b32_e32(v[0], 1.0), s_delay_alu(simm16=1), v_add_f32_e32(v[1], v[0], v[0])]), [7, 8])
|
||||
|
||||
def test_direct_delay2(self):
|
||||
# instid0=2 doesn't apply (only 1 VALU back), so no extra delay
|
||||
self.assertEqual(self._exec_delta([v_mov_b32_e32(v[0], 1.0), s_delay_alu(simm16=2), v_add_f32_e32(v[1], v[0], v[0])]), 6)
|
||||
|
||||
def test_direct_delay3(self):
|
||||
self.assertEqual(self._exec_delta([v_mov_b32_e32(v[0], 1.0), s_delay_alu(simm16=3), v_add_f32_e32(v[1], v[0], v[0])]), 6)
|
||||
|
||||
def test_direct_delay4(self):
|
||||
self.assertEqual(self._exec_delta([v_mov_b32_e32(v[0], 1.0), s_delay_alu(simm16=4), v_add_f32_e32(v[1], v[0], v[0])]), 6)
|
||||
|
||||
# With 1 independent instruction between producer and consumer
|
||||
def test_gap1_delay1(self):
|
||||
# instid0=1 waits for the independent instruction (not the producer)
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[5], 5.0), s_delay_alu(simm16=1), v_add_f32_e32(v[1], v[0], v[0])]
|
||||
self.assertEqual(self._exec_delta(instrs), 8)
|
||||
|
||||
def test_gap1_delay2(self):
|
||||
# instid0=2 waits for the producer (2 VALUs back)
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[5], 5.0), s_delay_alu(simm16=2), v_add_f32_e32(v[1], v[0], v[0])]
|
||||
self.assertIn(self._exec_delta(instrs), [6, 7])
|
||||
|
||||
def test_gap1_delay3(self):
|
||||
# instid0=3 doesn't apply (only 2 VALUs back)
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[5], 5.0), s_delay_alu(simm16=3), v_add_f32_e32(v[1], v[0], v[0])]
|
||||
self.assertEqual(self._exec_delta(instrs), 5)
|
||||
|
||||
# With 2 independent instructions between
|
||||
def test_gap2_delay1(self):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[5], 5.0), v_mov_b32_e32(v[6], 6.0),
|
||||
s_delay_alu(simm16=1), v_add_f32_e32(v[1], v[0], v[0])]
|
||||
self.assertEqual(self._exec_delta(instrs), 7)
|
||||
|
||||
def test_gap2_delay2(self):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[5], 5.0), v_mov_b32_e32(v[6], 6.0),
|
||||
s_delay_alu(simm16=2), v_add_f32_e32(v[1], v[0], v[0])]
|
||||
self.assertEqual(self._exec_delta(instrs), 7)
|
||||
|
||||
def test_gap2_delay3(self):
|
||||
# instid0=3 waits for the producer (3 VALUs back)
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[5], 5.0), v_mov_b32_e32(v[6], 6.0),
|
||||
s_delay_alu(simm16=3), v_add_f32_e32(v[1], v[0], v[0])]
|
||||
self.assertIn(self._exec_delta(instrs), [5, 6])
|
||||
|
||||
def test_gap2_delay4(self):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0), v_mov_b32_e32(v[5], 5.0), v_mov_b32_e32(v[6], 6.0),
|
||||
s_delay_alu(simm16=4), v_add_f32_e32(v[1], v[0], v[0])]
|
||||
self.assertEqual(self._exec_delta(instrs), 4)
|
||||
|
||||
|
||||
class TestNopTimingSensitivity(unittest.TestCase):
|
||||
"""Forwarding behavior has 128-cycle periodicity.
|
||||
|
||||
Hardware observation: when nop_cycles % 128 is in [72, 75], chain_6 gets 5 forwards
|
||||
instead of 4. This 4-cycle window repeats every 128 cycles, suggesting alignment
|
||||
with some hardware scheduling period (possibly wave scheduler or cache).
|
||||
|
||||
Windows found: nop 72-75, 200-203, 328-331, 456-459, ...
|
||||
"""
|
||||
def _chain6_fwd_count(self, nop_size):
|
||||
"""Count initial consecutive forwards for a 6-instruction chain after s_nop(n)."""
|
||||
instrs = [s_nop(nop_size), v_mov_b32_e32(v[99], 1.0)]
|
||||
instrs += [v_mov_b32_e32(v[0], 1.0)]
|
||||
for i in range(1, 6):
|
||||
instrs += [v_mov_b32_e32(v[i], v[i-1])]
|
||||
_, execd = get_deltas(instrs)
|
||||
chain_deltas = execd[1:]
|
||||
fwd_count = 0
|
||||
for d in chain_deltas:
|
||||
if d == 5: fwd_count += 1
|
||||
else: break
|
||||
return fwd_count
|
||||
|
||||
# Normal case: 4 forwards
|
||||
def test_nop71(self): self.assertEqual(self._chain6_fwd_count(71), 4)
|
||||
def test_nop76(self): self.assertEqual(self._chain6_fwd_count(76), 4)
|
||||
def test_nop199(self): self.assertEqual(self._chain6_fwd_count(199), 4)
|
||||
def test_nop204(self): self.assertEqual(self._chain6_fwd_count(204), 4)
|
||||
|
||||
# Anomaly window at nop % 128 == 72-75: 5 forwards on RDNA3, 4 on other variants
|
||||
def test_nop72(self): self.assertIn(self._chain6_fwd_count(72), [4, 5])
|
||||
def test_nop75(self): self.assertIn(self._chain6_fwd_count(75), [4, 5])
|
||||
def test_nop200(self): self.assertIn(self._chain6_fwd_count(200), [4, 5])
|
||||
def test_nop203(self): self.assertIn(self._chain6_fwd_count(203), [4, 5])
|
||||
def test_nop328(self): self.assertIn(self._chain6_fwd_count(328), [4, 5])
|
||||
def test_nop331(self): self.assertIn(self._chain6_fwd_count(331), [4, 5])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests for SQTT packet decoding using real captured examples."""
|
||||
import pickle, unittest, ctypes, threading
|
||||
import pickle, unittest, ctypes
|
||||
from pathlib import Path
|
||||
from tinygrad.helpers import DEBUG
|
||||
from tinygrad.helpers import DEBUG, colored
|
||||
from tinygrad.runtime.autogen import rocprof
|
||||
from tinygrad.runtime.support.elf import elf_loader
|
||||
from extra.assembly.amd.decode import decode_inst
|
||||
from extra.assembly.amd.asm import detect_format
|
||||
from extra.assembly.amd.autogen.rdna3.ins import SOPP
|
||||
from extra.assembly.amd.autogen.rdna3.enum import SOPPOp
|
||||
from extra.assembly.amd.sqtt import (decode, LAYOUT_HEADER, WAVESTART, WAVEEND, INST, VALUINST, IMMEDIATE, IMMEDIATE_MASK,
|
||||
ALUEXEC, VMEMEXEC, PACKET_TYPES, InstOp, print_packets)
|
||||
ALUEXEC, VMEMEXEC, PACKET_TYPES, InstOp, AluSrc, MemSrc)
|
||||
|
||||
EXAMPLES_DIR = Path(__file__).parent.parent.parent.parent / "sqtt/examples"
|
||||
# INST ops for non-traced SIMDs (excluded from instruction count)
|
||||
@@ -19,6 +19,34 @@ OTHER_SIMD_OPS = {InstOp.OTHER_LDS_LOAD, InstOp.OTHER_LDS_STORE, InstOp.OTHER_LD
|
||||
InstOp.OTHER_GLOBAL_STORE_64, InstOp.OTHER_GLOBAL_STORE_96, InstOp.OTHER_GLOBAL_STORE_128,
|
||||
InstOp.OTHER_GLOBAL_STORE_VADDR_128}
|
||||
|
||||
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, time_offset: int = 0) -> str:
|
||||
name, cycle = type(p).__name__, p._time - time_offset
|
||||
if isinstance(p, INST):
|
||||
op_name = p.op.name if isinstance(p.op, InstOp) else f"0x{p.op:02x}"
|
||||
fields = f"wave={p.wave} op={op_name}" + (" flag1" if p.flag1 else "") + (" flag2" if p.flag2 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, WAVEEND)): fields = f"wave={p.wave} simd={p.simd} cu={p.cu}"
|
||||
elif hasattr(p, '_values'):
|
||||
fields = " ".join(f"{k}=0x{v:x}" if k in {'snap', 'val32'} else f"{k}={v}"
|
||||
for k, v in p._values.items() if not k.startswith('_') and k != 'delta')
|
||||
else: fields = ""
|
||||
return f"{cycle:8}: {colored(f'{name:18}', PACKET_COLORS.get(name, 'white'))} {fields}"
|
||||
|
||||
def print_packets(packets: list) -> 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"}
|
||||
time_offset = packets[0]._time if packets else 0
|
||||
for p in packets:
|
||||
if type(p).__name__ not in skip: print(format_packet(p, time_offset))
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# ROCPROF DECODER
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -65,7 +93,7 @@ def run_rocprof_decoder(blobs: list[bytes], lib: bytes, base: int):
|
||||
mem_size_ptr[0] = 0
|
||||
return rocprof.ROCPROFILER_THREAD_TRACE_DECODER_STATUS_SUCCESS
|
||||
try:
|
||||
inst = decode_inst(image[offset:])
|
||||
inst = detect_format(data := image[offset:]).from_bytes(data)
|
||||
mem_size_ptr[0] = inst._size()
|
||||
except (ValueError, AssertionError):
|
||||
mem_size_ptr[0] = 0
|
||||
@@ -77,15 +105,7 @@ def run_rocprof_decoder(blobs: list[bytes], lib: bytes, base: int):
|
||||
size_ptr[0] = min(5, max_sz - 1)
|
||||
return rocprof.ROCPROFILER_THREAD_TRACE_DECODER_STATUS_SUCCESS
|
||||
|
||||
exc = None
|
||||
def worker():
|
||||
nonlocal exc
|
||||
try: rocprof.rocprof_trace_decoder_parse_data(copy_cb, trace_cb, isa_cb, None)
|
||||
except Exception as e: exc = e
|
||||
(t:=threading.Thread(target=worker, daemon=True)).start()
|
||||
t.join(timeout=1)
|
||||
if exc is not None: raise exc
|
||||
if t.is_alive(): raise RuntimeError("rocprof decoder timeout")
|
||||
rocprof.rocprof_trace_decoder_parse_data(copy_cb, trace_cb, isa_cb, None)
|
||||
return occupancy_records, wave_insts
|
||||
|
||||
class TestSQTTExamples(unittest.TestCase):
|
||||
@@ -107,7 +127,7 @@ class TestSQTTExamples(unittest.TestCase):
|
||||
for name, (events, *_) in self.examples.items():
|
||||
for i, event in enumerate(events):
|
||||
with self.subTest(example=name, event=i):
|
||||
packets = list(decode(event.blob))
|
||||
packets = decode(event.blob)
|
||||
if DEBUG >= 2: print(f"\n=== {name} event {i} ==="); print_packets(packets)
|
||||
self.assertGreater(len(packets), 0, f"no packets decoded from {name} event {i}")
|
||||
self.assertIsInstance(packets[0], LAYOUT_HEADER, f"first packet should be LAYOUT_HEADER in {name}")
|
||||
@@ -141,20 +161,6 @@ class TestSQTTExamples(unittest.TestCase):
|
||||
all_packets = [p for e in events for p in decode(e.blob)]
|
||||
self.assertGreater(len([p for p in all_packets if isinstance(p, INST)]), 0, f"no INST packets in {name}")
|
||||
|
||||
def test_packet_counts(self):
|
||||
expected = {
|
||||
"profile_empty_run_0": [559, 600],
|
||||
"profile_empty_run_1": [517, 570],
|
||||
"profile_gemm_run_0": [1489, 604, 1789, 466, 17570, 407],
|
||||
"profile_gemm_run_1": [1453, 604, 1871, 493, 17827, 460],
|
||||
"profile_plus_run_0": [695, 668],
|
||||
"profile_plus_run_1": [663, 593],
|
||||
}
|
||||
for name, (events, *_) in self.examples.items():
|
||||
with self.subTest(example=name):
|
||||
counts = [len(list(decode(e.blob))) for e in events]
|
||||
self.assertEqual(counts, expected[name], f"packet count mismatch in {name}")
|
||||
|
||||
def test_rocprof_wave_times_match(self):
|
||||
"""Wave start/end times must match rocprof exactly."""
|
||||
for name, (events, lib, base) in self.examples.items():
|
||||
@@ -170,8 +176,9 @@ class TestSQTTExamples(unittest.TestCase):
|
||||
# extract from our decoder
|
||||
our_waves: list[tuple[int, int]] = []
|
||||
for event in events:
|
||||
packets = decode(event.blob)
|
||||
wave_starts: dict[tuple[int, int, int], int] = {}
|
||||
for p in decode(event.blob):
|
||||
for p in packets:
|
||||
if isinstance(p, WAVESTART): wave_starts[(p.wave, p.simd, p.cu)] = p._time
|
||||
elif isinstance(p, WAVEEND) and (key := (p.wave, p.simd, p.cu)) in wave_starts:
|
||||
our_waves.append((wave_starts[key], p._time))
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Hardware tests for SQTT decoder - validates decoding of real SQTT streams.
|
||||
|
||||
Run with: python -m pytest extra/assembly/amd/test/test_sqtt_hw.py -v -s
|
||||
Requires AMD GPU with SQTT support.
|
||||
|
||||
For pretty trace output: DEBUG=2 python -m pytest extra/assembly/amd/test/test_sqtt_hw.py -v -s
|
||||
"""
|
||||
import os
|
||||
os.environ["SQTT"] = "1"
|
||||
os.environ["PROFILE"] = "1"
|
||||
os.environ["SQTT_ITRACE_SE_MASK"] = "1" # Enable instruction tracing on SE0
|
||||
os.environ["SQTT_LIMIT_SE"] = "2" # Force work to traced SE only
|
||||
|
||||
import unittest
|
||||
from tinygrad.helpers import DEBUG, colored
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.runtime.ops_amd import AMDProgram, ProfileSQTTEvent
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCompiler
|
||||
|
||||
from extra.assembly.amd.autogen.rdna3.ins import v_mov_b32_e32, v_add_f32_e32, v_mul_f32_e32, s_mov_b32, s_add_u32, s_nop, s_waitcnt, s_endpgm
|
||||
from extra.assembly.amd.dsl import v, s
|
||||
from extra.assembly.amd.sqtt import decode, LAYOUT_HEADER, WAVESTART, WAVEEND, INST, VALUINST, ALUEXEC, VMEMEXEC, InstOp, AluSrc, MemSrc
|
||||
|
||||
dev = Device["AMD"]
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# PRETTY PRINTING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
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",
|
||||
"TS_DELTA_SHORT": "BLACK", "NOP": "BLACK", "TS_WAVE_STATE": "BLACK",
|
||||
"SNAPSHOT": "white", "TS_DELTA_OR_MARK": "BLACK",
|
||||
"TS_DELTA_S8_W3": "BLACK", "TS_DELTA_S5_W2": "BLACK", "TS_DELTA_S5_W3": "BLACK",
|
||||
"UTILCTR": "green",
|
||||
}
|
||||
|
||||
def format_packet(p, last_time: int = 0, time_offset: int = 0) -> str:
|
||||
"""Format a packet for pretty printing."""
|
||||
name = type(p).__name__
|
||||
color = PACKET_COLORS.get(name, "white")
|
||||
|
||||
fields = []
|
||||
if isinstance(p, INST):
|
||||
op = p.op
|
||||
op_name = op.name if isinstance(op, InstOp) else f"0x{op:02x}"
|
||||
fields = [f"wave={p.wave}", f"op={op_name}"]
|
||||
if p.flag1: fields.append("flag1")
|
||||
if p.flag2: fields.append("flag2")
|
||||
elif isinstance(p, VALUINST):
|
||||
fields = [f"wave={p.wave}"]
|
||||
if p.flag: fields.append("flag")
|
||||
elif isinstance(p, ALUEXEC):
|
||||
src_name = p.src.name if isinstance(p.src, AluSrc) else f"{p.src}"
|
||||
fields = [f"src={src_name}"]
|
||||
elif isinstance(p, VMEMEXEC):
|
||||
src_name = p.src.name if isinstance(p.src, MemSrc) else f"{p.src}"
|
||||
fields = [f"src={src_name}"]
|
||||
elif isinstance(p, WAVESTART):
|
||||
fields = [f"wave={p.wave}", f"simd={p.simd}", f"cu={p.cu}"]
|
||||
elif isinstance(p, WAVEEND):
|
||||
fields = [f"wave={p.wave}", f"simd={p.simd}", f"cu={p.cu}"]
|
||||
elif hasattr(p, '_values'):
|
||||
# Format hex fields appropriately
|
||||
hex_fields = {'snap', 'val32'}
|
||||
fields = [f"{k}=0x{v:x}" if k in hex_fields else f"{k}={v}" for k, v in p._values.items() if not k.startswith('_') and k != 'delta']
|
||||
|
||||
return colored(f"{name:18s}", color) + " " + ", ".join(fields)
|
||||
|
||||
def get_wave_packets(packets: list) -> list:
|
||||
"""Extract packets from WAVESTART to WAVEEND, filtering pure timing packets."""
|
||||
skip_types = {"NOP", "TS_DELTA_SHORT", "TS_WAVE_STATE", "TS_DELTA_OR_MARK", "TS_DELTA_S5_W2", "TS_DELTA_S5_W3", "TS_DELTA_S8_W3"}
|
||||
result = []
|
||||
in_wave = False
|
||||
for p in packets:
|
||||
name = type(p).__name__
|
||||
if isinstance(p, WAVESTART):
|
||||
in_wave = True
|
||||
if in_wave and name not in skip_types:
|
||||
result.append(p)
|
||||
if isinstance(p, WAVEEND):
|
||||
in_wave = False
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def print_wave_trace(packets: list) -> None:
|
||||
"""Print packets from WAVESTART to WAVEEND with normalized time."""
|
||||
wave_packets = get_wave_packets(packets)
|
||||
if not wave_packets:
|
||||
return
|
||||
time_offset = wave_packets[0]._time
|
||||
last_time = time_offset
|
||||
for p in wave_packets:
|
||||
print(format_packet(p, last_time, time_offset))
|
||||
last_time = p._time
|
||||
|
||||
def print_blobs(blobs: list[bytes], wave_only: bool = True) -> None:
|
||||
"""Print traces for all blobs. wave_only=True filters to WAVESTART..WAVEEND only."""
|
||||
for i, blob in enumerate(blobs):
|
||||
packets = decode(blob)
|
||||
print(f"\n--- Blob {i}: {len(blob)} bytes, {len(packets)} packets ---")
|
||||
if wave_only:
|
||||
print_wave_trace(packets)
|
||||
else:
|
||||
print_all_packets(packets)
|
||||
|
||||
def print_all_packets(packets: list) -> None:
|
||||
"""Print all packets, filtering out pure timing packets."""
|
||||
skip_types = {"NOP", "TS_DELTA_SHORT", "TS_WAVE_STATE", "TS_DELTA_OR_MARK", "TS_DELTA_S5_W2", "TS_DELTA_S5_W3", "TS_DELTA_S8_W3"}
|
||||
if not packets: return
|
||||
time_offset = packets[0]._time
|
||||
last_time = time_offset
|
||||
for p in packets:
|
||||
if type(p).__name__ not in skip_types:
|
||||
print(format_packet(p, last_time, time_offset))
|
||||
last_time = p._time
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# ASSEMBLY HELPERS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def assemble(instructions: list) -> bytes:
|
||||
return b''.join(inst.to_bytes() for inst in instructions)
|
||||
|
||||
def wrap_with_nops(instructions: list, nops=16) -> list:
|
||||
"""Add epilogue for clean SQTT timing.
|
||||
|
||||
Need enough NOPs to cover long-latency ops (DP: 42 cycles, WMMA: 47 cycles).
|
||||
With 64 NOPs, the IMMEDIATE phase extends to cover these completions.
|
||||
"""
|
||||
return instructions + [s_nop(0)]*nops + [s_endpgm()]
|
||||
|
||||
def compile_asm_sqtt(instructions: list, alu_only: bool = False) -> AMDProgram:
|
||||
"""Compile instructions to an AMDProgram for SQTT tracing.
|
||||
|
||||
Args:
|
||||
instructions: List of instructions to compile
|
||||
alu_only: If True, use minimal kernel config with no kernargs/LDS/scratch
|
||||
Returns:
|
||||
Compiled AMDProgram ready to run
|
||||
"""
|
||||
compiler = HIPCompiler(dev.arch)
|
||||
# Add NOPs before s_endpgm to flush pipeline and get clean timing
|
||||
code = assemble(instructions)
|
||||
byte_str = ', '.join(f'0x{b:02x}' for b in code)
|
||||
|
||||
if alu_only:
|
||||
asm_src = f""".text
|
||||
.globl test
|
||||
.p2align 8
|
||||
.type test,@function
|
||||
test:
|
||||
.byte {byte_str}
|
||||
|
||||
.rodata
|
||||
.p2align 6
|
||||
.amdhsa_kernel test
|
||||
# basic memory
|
||||
.amdhsa_group_segment_fixed_size 0
|
||||
.amdhsa_private_segment_fixed_size 0
|
||||
.amdhsa_kernarg_size 32
|
||||
.amdhsa_enable_private_segment 0
|
||||
# register usage
|
||||
.amdhsa_next_free_vgpr 64
|
||||
.amdhsa_next_free_sgpr 8
|
||||
# RSRC1
|
||||
.amdhsa_wavefront_size32 1
|
||||
.amdhsa_memory_ordered 1
|
||||
.amdhsa_forward_progress 1
|
||||
# this is key
|
||||
.amdhsa_workgroup_processor_mode 0
|
||||
.end_amdhsa_kernel
|
||||
|
||||
.amdgpu_metadata
|
||||
---
|
||||
amdhsa.version:
|
||||
- 1
|
||||
- 0
|
||||
amdhsa.kernels:
|
||||
- .name: test
|
||||
.symbol: test.kd
|
||||
.kernarg_segment_size: 0
|
||||
.group_segment_fixed_size: 0
|
||||
.private_segment_fixed_size: 0
|
||||
.kernarg_segment_align: 8
|
||||
.wavefront_size: 32
|
||||
.sgpr_count: 8
|
||||
.vgpr_count: 64
|
||||
.max_flat_workgroup_size: 1024
|
||||
...
|
||||
.end_amdgpu_metadata
|
||||
"""
|
||||
else:
|
||||
asm_src = f""".text
|
||||
.globl test
|
||||
.p2align 8
|
||||
.type test,@function
|
||||
test:
|
||||
.byte {byte_str}
|
||||
|
||||
.rodata
|
||||
.p2align 6
|
||||
.amdhsa_kernel test
|
||||
.amdhsa_next_free_vgpr 8
|
||||
.amdhsa_next_free_sgpr 16
|
||||
.amdhsa_wavefront_size32 1
|
||||
.amdhsa_user_sgpr_kernarg_segment_ptr 1
|
||||
.amdhsa_kernarg_size 8
|
||||
.amdhsa_group_segment_fixed_size 0
|
||||
.amdhsa_private_segment_fixed_size 0
|
||||
.end_amdhsa_kernel
|
||||
|
||||
.amdgpu_metadata
|
||||
---
|
||||
amdhsa.version:
|
||||
- 1
|
||||
- 0
|
||||
amdhsa.kernels:
|
||||
- .name: test
|
||||
.symbol: test.kd
|
||||
.kernarg_segment_size: 8
|
||||
.group_segment_fixed_size: 0
|
||||
.private_segment_fixed_size: 0
|
||||
.kernarg_segment_align: 8
|
||||
.wavefront_size: 32
|
||||
.sgpr_count: 16
|
||||
.vgpr_count: 8
|
||||
.max_flat_workgroup_size: 1024
|
||||
...
|
||||
.end_amdgpu_metadata
|
||||
"""
|
||||
|
||||
lib = compiler.compile(asm_src)
|
||||
return AMDProgram(dev, "test", lib)
|
||||
|
||||
def run_asm_sqtt(instructions: list, n_lanes: int = 1, alu_only: bool = False) -> list[bytes]:
|
||||
"""Compile and run instructions on AMD hardware, return SQTT blobs.
|
||||
|
||||
Args:
|
||||
instructions: List of instructions to run
|
||||
n_lanes: Number of lanes to use
|
||||
alu_only: If True, use minimal kernel config with no kernargs/LDS/scratch
|
||||
"""
|
||||
prg = compile_asm_sqtt(instructions, alu_only=alu_only)
|
||||
return run_prg_sqtt(prg, n_lanes=n_lanes, alu_only=alu_only)
|
||||
|
||||
def run_prg_sqtt(prg: AMDProgram, n_lanes: int = 1, alu_only: bool = False) -> list[bytes]:
|
||||
"""Run a compiled AMDProgram and return SQTT blobs.
|
||||
|
||||
Args:
|
||||
prg: Compiled AMDProgram to run
|
||||
n_lanes: Number of lanes to use
|
||||
alu_only: If True, don't allocate kernarg buffer
|
||||
"""
|
||||
dev.profile_events.clear()
|
||||
if alu_only:
|
||||
prg(global_size=(1, 1, 1), local_size=(n_lanes, 1, 1), wait=True)
|
||||
else:
|
||||
out_gpu = dev.allocator.alloc(2048)
|
||||
prg(out_gpu, global_size=(1, 1, 1), local_size=(n_lanes, 1, 1), wait=True)
|
||||
return [ev.blob for ev in dev.profile_events if isinstance(ev, ProfileSQTTEvent)]
|
||||
|
||||
def run_prg_sqtt_batch(prg: AMDProgram, n_runs: int, n_lanes: int = 1) -> list[bytes]:
|
||||
"""Run a compiled AMDProgram N times in a single queue submission and return SQTT blobs.
|
||||
|
||||
This builds one queue with N kernel executions, submits it once, and collects SQTT.
|
||||
All N runs are captured in the same SQTT trace, reducing startup jitter.
|
||||
|
||||
Args:
|
||||
prg: Compiled AMDProgram to run
|
||||
n_runs: Number of times to execute the kernel in the queue
|
||||
n_lanes: Number of lanes to use
|
||||
Returns:
|
||||
List of SQTT blobs (one per shader engine)
|
||||
"""
|
||||
from typing import cast
|
||||
from tinygrad.runtime.ops_amd import AMDComputeQueue, SQTT_ITRACE_SE_MASK
|
||||
from tinygrad.device import Compiled
|
||||
import struct
|
||||
|
||||
dev.profile_events.clear()
|
||||
|
||||
# Build queue with sqtt_start, N kernel executions, sqtt_stop
|
||||
kernargs = prg.fill_kernargs([], ())
|
||||
q = cast(AMDComputeQueue, dev.hw_compute_queue_t())
|
||||
q.wait(dev.timeline_signal, dev.timeline_value - 1).memory_barrier()
|
||||
q.sqtt_start(dev.sqtt_buffers)
|
||||
|
||||
# Execute kernel N times
|
||||
for _ in range(n_runs):
|
||||
q.exec(prg, kernargs, (1, 1, 1), (n_lanes, 1, 1))
|
||||
|
||||
q.sqtt_stop(dev.sqtt_wptrs)
|
||||
q.signal(dev.timeline_signal, dev.next_timeline())
|
||||
q.submit(dev)
|
||||
dev.synchronize()
|
||||
|
||||
# Collect SQTT blobs
|
||||
blobs = []
|
||||
for se, buf in enumerate(dev.sqtt_buffers):
|
||||
wptr = (dev.sqtt_wptrs.cpu_view().view(fmt='I')[se] & 0x1FFFFFFF) * 32
|
||||
if dev.target[:2] == (11, 0): wptr -= ((buf.va_addr // 32) & 0x1FFFFFFF) * 32
|
||||
if wptr > 0 and wptr <= buf.size:
|
||||
dev.allocator._copyout(sqtt_mv:=memoryview(bytearray(wptr)), buf)
|
||||
resbuf = (struct.pack('<Q', 0x11 | (4 << 13) | (0xf << 16) | (se << 24)) + bytes(sqtt_mv)) if dev.target[0] == 9 else bytes(sqtt_mv)
|
||||
blobs.append(resbuf)
|
||||
|
||||
return blobs
|
||||
|
||||
def decode_all_blobs(blobs: list[bytes]) -> list:
|
||||
"""Decode all blobs and combine packets."""
|
||||
all_packets = []
|
||||
for blob in blobs:
|
||||
all_packets.extend(decode(blob))
|
||||
return all_packets
|
||||
|
||||
def get_inst_ops(packets: list, traced_simd: int | None = None) -> set:
|
||||
"""Extract all InstOp values from INST packets within WAVESTART..WAVEEND on traced SIMD."""
|
||||
ops = set()
|
||||
in_wave = False
|
||||
for p in packets:
|
||||
if isinstance(p, WAVESTART):
|
||||
in_wave = traced_simd is None or p.simd == traced_simd
|
||||
if in_wave and isinstance(p, INST):
|
||||
ops.add(p.op if isinstance(p.op, int) else p.op.value)
|
||||
if isinstance(p, WAVEEND):
|
||||
in_wave = False
|
||||
return ops
|
||||
|
||||
def count_valuinst(packets: list, traced_simd: int | None = None) -> int:
|
||||
"""Count VALUINST packets within WAVESTART..WAVEEND on traced SIMD."""
|
||||
count = 0
|
||||
in_wave = False
|
||||
for p in packets:
|
||||
if isinstance(p, WAVESTART):
|
||||
in_wave = traced_simd is None or p.simd == traced_simd
|
||||
if in_wave and isinstance(p, VALUINST):
|
||||
count += 1
|
||||
if isinstance(p, WAVEEND):
|
||||
in_wave = False
|
||||
return count
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# TESTS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@unittest.skipIf(not hasattr(dev, 'profile_events'), "AMD device required")
|
||||
class TestSQTTDecode(unittest.TestCase):
|
||||
"""Test SQTT decoder with real hardware traces."""
|
||||
|
||||
def test_basic_structure(self):
|
||||
"""Verify basic SQTT stream structure: LAYOUT_HEADER, WAVESTART, instructions, WAVEEND."""
|
||||
blobs = run_asm_sqtt([v_mov_b32_e32(v[0], 0)])
|
||||
|
||||
self.assertGreater(len(blobs), 0, "No SQTT data captured")
|
||||
packets = decode_all_blobs(blobs)
|
||||
|
||||
self.assertGreater(len(packets), 0, "No packets decoded")
|
||||
self.assertGreater(len([p for p in packets if isinstance(p, LAYOUT_HEADER)]), 0, "No LAYOUT_HEADER packets")
|
||||
self.assertGreater(len([p for p in packets if isinstance(p, WAVESTART)]), 0, "No WAVESTART packets")
|
||||
self.assertGreater(len([p for p in packets if isinstance(p, WAVEEND)]), 0, "No WAVEEND packets")
|
||||
|
||||
if DEBUG >= 2:
|
||||
print("\n=== Basic structure trace ===")
|
||||
print_trace(packets)
|
||||
|
||||
def test_valu_instructions(self):
|
||||
"""Verify VALU instructions produce INST or VALUINST packets."""
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], 1.0),
|
||||
v_mov_b32_e32(v[1], 2.0),
|
||||
v_add_f32_e32(v[2], v[0], v[1]),
|
||||
v_add_f32_e32(v[3], v[2], v[1]),
|
||||
v_mul_f32_e32(v[4], v[2], v[3]),
|
||||
]
|
||||
blobs = run_asm_sqtt(instructions)
|
||||
|
||||
self.assertGreater(len(blobs), 0, "No SQTT data captured")
|
||||
packets = decode_all_blobs(blobs)
|
||||
|
||||
inst_packets = [p for p in packets if isinstance(p, (INST, VALUINST))]
|
||||
self.assertGreater(len(inst_packets), 0, "No INST/VALUINST packets for VALU instructions")
|
||||
|
||||
if DEBUG >= 2:
|
||||
print("\n=== VALU instructions trace ===")
|
||||
print_trace(packets)
|
||||
|
||||
def test_salu_instructions(self):
|
||||
"""Verify SALU instructions produce appropriate packets."""
|
||||
instructions = [
|
||||
s_mov_b32(s[0], 0),
|
||||
s_mov_b32(s[1], 1),
|
||||
s_add_u32(s[2], s[0], s[1]),
|
||||
s_add_u32(s[3], s[2], s[1]),
|
||||
s_nop(0),
|
||||
]
|
||||
blobs = run_asm_sqtt(instructions)
|
||||
|
||||
self.assertGreater(len(blobs), 0, "No SQTT data captured")
|
||||
packets = decode_all_blobs(blobs)
|
||||
|
||||
if DEBUG >= 2:
|
||||
print("\n=== SALU instructions trace ===")
|
||||
print_trace(packets)
|
||||
|
||||
def test_timing_increases(self):
|
||||
"""Verify time increases monotonically through packets within each blob."""
|
||||
instructions = [
|
||||
v_mov_b32_e32(v[0], 1.0),
|
||||
v_mov_b32_e32(v[1], 2.0),
|
||||
v_add_f32_e32(v[2], v[0], v[1]),
|
||||
v_mul_f32_e32(v[3], v[2], v[1]),
|
||||
]
|
||||
blobs = run_asm_sqtt(instructions)
|
||||
|
||||
self.assertGreater(len(blobs), 0, "No SQTT data captured")
|
||||
for blob in blobs:
|
||||
packets = decode(blob)
|
||||
prev_time = 0
|
||||
for p in packets:
|
||||
self.assertGreaterEqual(p._time, prev_time, f"Time decreased: {prev_time} -> {p._time}")
|
||||
prev_time = p._time
|
||||
|
||||
def test_wave_id_consistency(self):
|
||||
"""Verify wave IDs are consistent between WAVESTART/WAVEEND."""
|
||||
blobs = run_asm_sqtt([v_mov_b32_e32(v[0], 0)])
|
||||
|
||||
self.assertGreater(len(blobs), 0, "No SQTT data captured")
|
||||
packets = decode_all_blobs(blobs)
|
||||
|
||||
wavestarts = [p for p in packets if isinstance(p, WAVESTART)]
|
||||
waveends = [p for p in packets if isinstance(p, WAVEEND)]
|
||||
|
||||
if wavestarts and waveends:
|
||||
start_waves = {p.wave for p in wavestarts}
|
||||
end_waves = {p.wave for p in waveends}
|
||||
self.assertTrue(start_waves & end_waves, "No matching wave IDs between WAVESTART and WAVEEND")
|
||||
|
||||
def test_nop_sequence(self):
|
||||
"""Test a sequence of NOP instructions."""
|
||||
blobs = run_asm_sqtt([s_nop(0), s_nop(0), s_nop(0)])
|
||||
|
||||
self.assertGreater(len(blobs), 0, "No SQTT data captured")
|
||||
packets = decode_all_blobs(blobs)
|
||||
self.assertGreater(len(packets), 0, "No packets decoded")
|
||||
|
||||
if DEBUG >= 2:
|
||||
print("\n=== NOP sequence trace ===")
|
||||
print_trace(packets, filter_timing=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
os.environ["SQTT"] = "1"
|
||||
os.environ["PROFILE"] = "1"
|
||||
os.environ["SQTT_LIMIT_SE"] = "2"
|
||||
os.environ["SQTT_SIMD_SEL"] = "0"
|
||||
os.environ["SQTT_TOKEN_EXCLUDE"] = "3784" # Exclude WAVERDY, REG, EVENT, UTILCTR, WAVEALLOC, PERF
|
||||
|
||||
import unittest
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
from extra.assembly.amd.sqtt import decode
|
||||
from extra.assembly.amd.test.test_sqtt_hw import compile_asm_sqtt, run_prg_sqtt_batch, format_packet
|
||||
from extra.assembly.amd.test.test_sqtt_compare import filter_noise_packets
|
||||
from tinygrad.uop.ops import UOp
|
||||
from tinygrad.engine.realize import get_runner
|
||||
|
||||
class SQTTMultiwave(unittest.TestCase):
|
||||
def test_simple_multiwave(self):
|
||||
ins = [
|
||||
s_barrier(),
|
||||
v_mov_b32_e32(v[0], v[1]),
|
||||
s_nop(0),
|
||||
s_nop(100),
|
||||
s_endpgm(),
|
||||
]
|
||||
#prg = get_runner("AMD", UOp.sink())._prg
|
||||
prg = compile_asm_sqtt(ins, alu_only=True)
|
||||
print(prg)
|
||||
blobs = run_prg_sqtt_batch(prg, n_runs=1, n_lanes=32*16)
|
||||
for blob in blobs:
|
||||
packets = decode(blob)
|
||||
for p in filter_noise_packets(packets):
|
||||
print(f" {p._time:8d}: {format_packet(p)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests validating SQTT packet definitions against the reference implementation.
|
||||
|
||||
Verifies that:
|
||||
1. Encoding patterns produce the correct STATE_TO_OPCODE table
|
||||
2. Packet sizes (derived from fields) match expected budget values
|
||||
3. Field extractions match attempt_sqtt_parse.py
|
||||
"""
|
||||
import unittest
|
||||
from extra.assembly.amd.sqtt import (
|
||||
VALUINST, VMEMEXEC, ALUEXEC, IMMEDIATE, IMMEDIATE_MASK, WAVERDY,
|
||||
WAVEEND, WAVESTART, PERF, TS_WAVE_STATE, EVENT, EVENT_BIG, REG, SNAPSHOT,
|
||||
TS_DELTA_OR_MARK, LAYOUT_HEADER, INST, UTILCTR, TS_DELTA_SHORT, NOP,
|
||||
TS_DELTA_S8_W3, TS_DELTA_S5_W2, TS_DELTA_S5_W3, WAVEALLOC,
|
||||
decode, encode, OPCODE_TO_CLASS, STATE_TO_OPCODE, PACKET_TYPES, BUDGET,
|
||||
AluSrc, MemSrc, InstOp
|
||||
)
|
||||
|
||||
# Reference table from rocprof trace decoder (attempt_sqtt_parse.py)
|
||||
REFERENCE_STATE_TABLE = bytes([
|
||||
0x10, 0x16, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x17, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
0x10, 0x07, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x19, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
0x10, 0x00, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x11, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
0x10, 0x12, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x15, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
0x10, 0x16, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x17, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
0x10, 0x07, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x19, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
0x10, 0x00, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x11, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
0x10, 0x13, 0x18, 0x01, 0x05, 0x0b, 0x0c, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x09, 0x04, 0x03, 0x02,
|
||||
0x10, 0x15, 0x18, 0x01, 0x06, 0x08, 0x0d, 0x00, 0x0f, 0x14, 0x18, 0x01, 0x0a, 0x04, 0x03, 0x02,
|
||||
])
|
||||
|
||||
# Reference opcode -> name mapping (old opcode values from rocprof)
|
||||
OLD_OPCODE_TO_NAME = {
|
||||
0x01: 'VALUINST', 0x02: 'VMEMEXEC', 0x03: 'ALUEXEC', 0x04: 'IMMEDIATE',
|
||||
0x05: 'IMMEDIATE_MASK', 0x06: 'WAVERDY', 0x07: 'TS_DELTA_S8_W3',
|
||||
0x08: 'WAVEEND', 0x09: 'WAVESTART', 0x0A: 'TS_DELTA_S5_W2',
|
||||
0x0B: 'WAVEALLOC', 0x0C: 'TS_DELTA_S5_W3', 0x0D: 'PERF',
|
||||
0x0F: 'TS_DELTA_SHORT', 0x10: 'NOP', 0x11: 'TS_WAVE_STATE',
|
||||
0x12: 'EVENT', 0x13: 'EVENT_BIG', 0x14: 'REG', 0x15: 'SNAPSHOT',
|
||||
0x16: 'TS_DELTA_OR_MARK', 0x17: 'LAYOUT_HEADER', 0x18: 'INST',
|
||||
0x19: 'UTILCTR', 0x00: 'NOP',
|
||||
}
|
||||
|
||||
# Reference budget values (nibbles for NEXT packet) from rocprof
|
||||
REFERENCE_BUDGET_NIBBLES = {
|
||||
'VALUINST': 3, 'VMEMEXEC': 2, 'ALUEXEC': 2, 'IMMEDIATE': 3,
|
||||
'IMMEDIATE_MASK': 6, 'WAVERDY': 6, 'TS_DELTA_S8_W3': 16,
|
||||
'WAVEEND': 5, 'WAVESTART': 8, 'TS_DELTA_S5_W2': 12,
|
||||
'WAVEALLOC': 5, 'TS_DELTA_S5_W3': 13, 'PERF': 7,
|
||||
'TS_DELTA_SHORT': 2, 'NOP': 1, 'TS_WAVE_STATE': 6,
|
||||
'EVENT': 6, 'EVENT_BIG': 8, 'REG': 16, 'SNAPSHOT': 16,
|
||||
'TS_DELTA_OR_MARK': 12, 'LAYOUT_HEADER': 16, 'INST': 5,
|
||||
'UTILCTR': 12,
|
||||
}
|
||||
|
||||
|
||||
class TestEncodingsMatchStateTable(unittest.TestCase):
|
||||
"""Verify encoding patterns produce the correct state decode table."""
|
||||
|
||||
def test_all_256_bytes_decode_correctly(self):
|
||||
"""Each byte value should decode to the same packet type as reference."""
|
||||
mismatches = []
|
||||
for byte_val in range(256):
|
||||
ref_opcode = REFERENCE_STATE_TABLE[byte_val]
|
||||
ref_name = OLD_OPCODE_TO_NAME.get(ref_opcode, f"UNK_{ref_opcode:02x}")
|
||||
|
||||
our_opcode = STATE_TO_OPCODE[byte_val]
|
||||
our_name = OPCODE_TO_CLASS[our_opcode].__name__
|
||||
|
||||
if ref_name != our_name:
|
||||
mismatches.append((byte_val, ref_name, our_name))
|
||||
|
||||
if mismatches:
|
||||
msg = "\n".join(f" 0x{b:02x}: expected {r}, got {o}" for b, r, o in mismatches[:10])
|
||||
self.fail(f"State table mismatches ({len(mismatches)} total):\n{msg}")
|
||||
|
||||
|
||||
class TestPacketSizesMatchBudget(unittest.TestCase):
|
||||
"""Verify packet sizes (from field definitions) match expected budget values."""
|
||||
|
||||
def test_all_packet_sizes(self):
|
||||
"""Each packet type's size should match the reference budget."""
|
||||
for pkt_cls in PACKET_TYPES:
|
||||
name = pkt_cls.__name__
|
||||
expected = REFERENCE_BUDGET_NIBBLES.get(name)
|
||||
if expected is None:
|
||||
continue
|
||||
|
||||
actual = pkt_cls.size_nibbles()
|
||||
self.assertEqual(expected, actual,
|
||||
f"{name}: expected {expected} nibbles, got {actual} (size_bits={pkt_cls.size_bits()})")
|
||||
|
||||
|
||||
class TestFieldExtraction(unittest.TestCase):
|
||||
"""Test that field values are extracted correctly."""
|
||||
|
||||
def test_valuinst(self):
|
||||
reg = 0b11110_1_001_011 # wave=0x1E, flag=1, delta=1
|
||||
pkt = VALUINST.from_raw(reg)
|
||||
self.assertEqual(pkt.delta, 1)
|
||||
self.assertEqual(pkt.flag, 1)
|
||||
self.assertEqual(pkt.wave, 0x1E)
|
||||
|
||||
def test_vmemexec_enum(self):
|
||||
reg = 0b11_00_1111 # src=3 (VMEM_ALT), delta=0
|
||||
pkt = VMEMEXEC.from_raw(reg)
|
||||
self.assertEqual(pkt.src, MemSrc.VMEM_ALT)
|
||||
|
||||
def test_aluexec_enum(self):
|
||||
reg = 0b10_01_1110 # src=2 (VALU), delta=1
|
||||
pkt = ALUEXEC.from_raw(reg)
|
||||
self.assertEqual(pkt.src, AluSrc.VALU)
|
||||
|
||||
def test_waveend(self):
|
||||
reg = (0x15 << 15) | (0x7 << 11) | (0x3 << 9) | (1 << 8) | 0b10101
|
||||
pkt = WAVEEND.from_raw(reg)
|
||||
self.assertEqual(pkt.flag7, 1)
|
||||
self.assertEqual(pkt.simd, 3)
|
||||
self.assertEqual(pkt.cu_lo, 7)
|
||||
self.assertEqual(pkt.wave, 0x15)
|
||||
self.assertEqual(pkt.cu, 0xF) # cu_lo | (flag7 << 3) = 7 | 8 = 15
|
||||
|
||||
def test_wavestart(self):
|
||||
reg = (0x7F << 18) | (0x15 << 13) | (0x7 << 10) | (0x3 << 8) | (1 << 7) | 0b01100
|
||||
pkt = WAVESTART.from_raw(reg)
|
||||
self.assertEqual(pkt.flag7, 1)
|
||||
self.assertEqual(pkt.simd, 3)
|
||||
self.assertEqual(pkt.cu_lo, 7)
|
||||
self.assertEqual(pkt.wave, 0x15)
|
||||
self.assertEqual(pkt.id7, 0x7F)
|
||||
self.assertEqual(pkt.cu, 0xF)
|
||||
|
||||
def test_inst_enum(self):
|
||||
reg = (0x21 << 13) | (0x15 << 8) | (1 << 7) | (1 << 3) | 0b010
|
||||
pkt = INST.from_raw(reg)
|
||||
self.assertEqual(pkt.flag1, 1)
|
||||
self.assertEqual(pkt.flag2, 1)
|
||||
self.assertEqual(pkt.wave, 0x15)
|
||||
self.assertEqual(pkt.op, InstOp.VMEM_LOAD)
|
||||
|
||||
def test_layout_header(self):
|
||||
reg = (0b101 << 33) | (0b1010 << 28) | (0b111 << 15) | (0b11 << 13) | (0b101010 << 7) | 0b0010001
|
||||
pkt = LAYOUT_HEADER.from_raw(reg)
|
||||
self.assertEqual(pkt.layout, 0b101010)
|
||||
self.assertEqual(pkt.simd, 0b11)
|
||||
self.assertEqual(pkt.group, 0b111)
|
||||
self.assertEqual(pkt.sel_a, 0b1010)
|
||||
self.assertEqual(pkt.sel_b, 0b101)
|
||||
|
||||
def test_ts_delta_or_mark_modes(self):
|
||||
# delta mode: bit9=0, bit8=0
|
||||
pkt_delta = TS_DELTA_OR_MARK.from_raw(0b0000001) # just the encoding pattern
|
||||
self.assertFalse(pkt_delta.is_marker)
|
||||
|
||||
# marker mode: bit9=1, bit8=0
|
||||
pkt_marker = TS_DELTA_OR_MARK.from_raw(0b0000001 | (1 << 9)) # bit9=1, bit8=0
|
||||
self.assertTrue(pkt_marker.is_marker)
|
||||
|
||||
# other mode: bit9=1, bit8=1 (not marker)
|
||||
pkt_other = TS_DELTA_OR_MARK.from_raw(0b0000001 | (1 << 8) | (1 << 9))
|
||||
self.assertFalse(pkt_other.is_marker)
|
||||
|
||||
def test_reg(self):
|
||||
# REG fields: slot=bits[9:7], hi_byte=bits[15:8], subop=bits[31:16], val32=bits[63:32]
|
||||
# Note: slot[2:1] overlaps with hi_byte[1:0], so we need to set them consistently
|
||||
# hi_byte=0x55 means bits 8-15 = 0b01010101, so slot bits 8-9 = 0b01
|
||||
# slot bit 7 = 1, so slot = 0b011 = 3
|
||||
reg = (0xDEADBEEF << 32) | (0xCAFE << 16) | (0x55 << 8) | (1 << 7) | 0b1001
|
||||
pkt = REG.from_raw(reg)
|
||||
self.assertEqual(pkt.slot, 0b011) # bit7=1, bits 8-9 from hi_byte low 2 bits = 01
|
||||
self.assertEqual(pkt.hi_byte, 0x55)
|
||||
self.assertEqual(pkt.subop, 0xCAFE)
|
||||
self.assertEqual(pkt.val32, 0xDEADBEEF)
|
||||
|
||||
|
||||
class TestRoundtrip(unittest.TestCase):
|
||||
"""Test encode/decode roundtrip."""
|
||||
|
||||
def test_simple_roundtrip(self):
|
||||
"""Test encode/decode roundtrip preserves packet types."""
|
||||
test_packets = [
|
||||
LAYOUT_HEADER.from_raw(0x100),
|
||||
WAVESTART.from_raw(0x0),
|
||||
INST.from_raw(0x10),
|
||||
INST.from_raw(0x10),
|
||||
WAVEEND.from_raw(0x40),
|
||||
]
|
||||
encoded = encode(test_packets)
|
||||
decoded = decode(encoded)
|
||||
|
||||
self.assertGreaterEqual(len(decoded), len(test_packets))
|
||||
for i, (orig, dec) in enumerate(zip(test_packets, decoded)):
|
||||
self.assertEqual(type(orig), type(dec), f"type mismatch at {i}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+105
-134
@@ -11,10 +11,9 @@
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from tinygrad import Tensor, Device, Context, GlobalCounters
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.helpers import getenv, colored
|
||||
from tinygrad.engine.realize import Estimates
|
||||
from extra.assembly.amd.dsl import s, v, VCC_LO, NULL
|
||||
from tinygrad.engine.realize import Runner, Estimates, ExecItem
|
||||
from extra.assembly.amd.dsl import s, v, VCC_LO, RawImm, EXEC_LO
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
|
||||
# =============================================================================
|
||||
@@ -35,21 +34,22 @@ WAIT_VMEM = 1015 # wait for VMEM only (vm_cnt=0, lgkm_cnt=63)
|
||||
# =============================================================================
|
||||
# Named register assignments (VGPRs) - COMPACT LAYOUT
|
||||
# =============================================================================
|
||||
V_LANE_ID_MOD8 = 182 # lane_id & 7 (column within 8-wide tile chunk)
|
||||
V_OUTPUT_ROW = 171 # output row coordinate
|
||||
V_LANE_MOD8_X4 = 174 # V_LANE_ID_MOD8 << 2 (byte offset)
|
||||
V_LANE_DIV8_X4 = 175 # (lane_id >> 3) << 2
|
||||
V_ADDR_HI_ZERO = 188 # always 0 (for 64-bit address high bits)
|
||||
V_LDS_A_BASE = 186 # LDS A-tile base address for inner loop
|
||||
V_LDS_B_BASE = 170 # LDS B-tile base address for inner loop
|
||||
V_GLOBAL_A_ADDR = 171 # global memory A prefetch address (reuses V_OUTPUT_ROW slot during main loop)
|
||||
V_GLOBAL_B_ADDR = 178 # global memory B prefetch address
|
||||
V_LANE_ID_MOD8 = 214 # lane_id & 7 (column within 8-wide tile chunk)
|
||||
V_OUTPUT_ROW = 131 # output row coordinate
|
||||
V_LANE_MOD8_X4 = 134 # V_LANE_ID_MOD8 << 2 (byte offset)
|
||||
V_LANE_DIV8_X4 = 135 # (lane_id >> 3) << 2
|
||||
V_ADDR_HI_ZERO = 136 # always 0 (for 64-bit address high bits)
|
||||
V_LDS_A_BASE = 133 # LDS A-tile base address for inner loop (in ACC_RESERVED gap)
|
||||
V_LDS_B_BASE = 130 # LDS B-tile base address for inner loop (in ACC_RESERVED gap)
|
||||
V_GLOBAL_A_ADDR = 131 # global memory A prefetch address (reuses V_OUTPUT_ROW slot during main loop)
|
||||
V_GLOBAL_B_ADDR = 154 # global memory B prefetch address
|
||||
|
||||
# LDS tile register destinations - SEPARATE from DATA to avoid overlap
|
||||
# DATA regs (v155-170) receive global prefetch
|
||||
# A on banks 2-3, B on banks 0-1 to avoid bank conflicts in VOPD
|
||||
# Double-buffered: buffer 0 (v130-v161), buffer 1 (v194-v225)
|
||||
V_A_TILE_REGS = [[130, 134, 138, 142], [194, 198, 202, 206]] # A tile: banks 2,2,2,2 (mod 4 = 2)
|
||||
V_B_TILE_REGS = [[132, 136, 140, 144, 148, 152, 156, 160], [192, 196, 200, 204, 208, 212, 216, 220]] # B tile: banks 0,0,0,0 (mod 4 = 0)
|
||||
# This layout matches kernel8's optimization for VGPR cache utilization
|
||||
V_A_TILE_REGS = [186, 190, 194, 198] # A tile: banks 2,2,2,2 (186%4=2, 190%4=2, etc.)
|
||||
V_B_TILE_REGS = [184, 188, 192, 196, 200, 204, 208, 212] # B tile: banks 0,0,0,0,0,0,0,0
|
||||
|
||||
# =============================================================================
|
||||
# Named register assignments (SGPRs)
|
||||
@@ -99,8 +99,10 @@ FMAC_PAIR_ORDER = [
|
||||
(0,4),(0,5),(1,5),(1,4), (2,4),(2,5),(3,5),(3,6), (0,6),(0,7),(1,7),(1,6), (2,6),(2,7),(3,7),(3,0),
|
||||
]
|
||||
|
||||
def derive_fmac_pattern(acc_grid, a_tile_regs, b_tile_regs):
|
||||
def derive_fmac_pattern(acc_grid, a_tile_regs=None, b_tile_regs=None):
|
||||
"""Generate 64 dual FMAC ops from accumulator grid with optimized iteration order."""
|
||||
if a_tile_regs is None: a_tile_regs = V_A_TILE_REGS
|
||||
if b_tile_regs is None: b_tile_regs = V_B_TILE_REGS
|
||||
pattern = []
|
||||
for idx, (a_pair, b_pair) in enumerate(FMAC_PAIR_ORDER):
|
||||
a_even, a_odd = a_pair * 2, a_pair * 2 + 1
|
||||
@@ -118,8 +120,8 @@ def derive_fmac_pattern(acc_grid, a_tile_regs, b_tile_regs):
|
||||
a_base+1, b_base, a_base, b_base+1))
|
||||
return pattern
|
||||
|
||||
# Derived: 64 dual FMAC operations for each buffer
|
||||
FMAC_PATTERN = [derive_fmac_pattern(ACC_GRID, V_A_TILE_REGS[i], V_B_TILE_REGS[i]) for i in range(2)]
|
||||
# Derived: 64 dual FMAC operations
|
||||
FMAC_PATTERN = derive_fmac_pattern(ACC_GRID)
|
||||
|
||||
def derive_permute_swaps(acc_grid, out_regs):
|
||||
"""Derive swap sequence to permute accumulators from FMAC layout to output order.
|
||||
@@ -158,10 +160,10 @@ PERMUTE_SWAPS = derive_permute_swaps(ACC_GRID, OUT_REGS)
|
||||
# DATA regs receive contiguous global prefetch, then write to LDS
|
||||
# TILE regs receive scattered LDS loads (ds_load_b64 pairs), then feed FMACs
|
||||
# These are SEPARATE - DATA lives during prefetch/store, TILE lives during inner loop
|
||||
V_LDS_A_ADDR = 189 # single base register for A stores (use +512 offsets)
|
||||
V_LDS_A_DATA = [155, 172, 173, 154, 159, 176, 177, 158] # 8 data registers for A prefetch (mod 4: 3,0,1,2,3,0,1,2)
|
||||
V_LDS_B_ADDR = 190 # single base register for B stores (use 16-bit offsets)
|
||||
V_LDS_B_DATA = [163, 180, 181, 162, 167, 184, 185, 166] # 8 data registers for B prefetch (mod 4: 3,0,1,2,3,0,1,2)
|
||||
V_LDS_A_ADDR = 153 # single base register for A stores (use +512 offsets)
|
||||
V_LDS_A_DATA = list(range(155, 163)) # 8 data registers for A prefetch (v155-162)
|
||||
V_LDS_B_ADDR = 145 # single base register for B stores (use 16-bit offsets)
|
||||
V_LDS_B_DATA = list(range(163, 171)) # 8 data registers for B prefetch (v163-170)
|
||||
|
||||
# Global memory prefetch schedule: (vdst1, vdst2, addr_vreg, saddr_lo1, saddr_lo2)
|
||||
# First 2 pairs from B prefetch pointers (s[32:39]), next 4 pairs from A prefetch pointers (s[40:55])
|
||||
@@ -175,7 +177,7 @@ INIT_PREFETCH = [(V_LDS_A_DATA[i], S_PREFETCH_B+2*i) for i in range(4)]
|
||||
INIT_TILE_LOADS = [(23,5),(24,9),(25,7),(26,2),(27,11),(28,13),(29,6),(30,8),(31,10),(12,12),(13,14),(3,2),(4,4),(5,8),(6,6),(7,10)]
|
||||
|
||||
# A matrix row offset registers (scattered to avoid accumulator conflicts)
|
||||
ROW_REGS = [165, 146, 147, 164, 169, 150, 151, 168] # mod 4: 1,2,3,0,1,2,3,0
|
||||
ROW_REGS = list(range(137, 145)) # v137-v144 (8 regs)
|
||||
|
||||
# =============================================================================
|
||||
# Kernel class
|
||||
@@ -189,11 +191,29 @@ class Kernel:
|
||||
def label(self, name): self.labels[name] = len(self.instructions)
|
||||
def branch_to(self, label): self.branch_targets[len(self.instructions) - 1] = label
|
||||
|
||||
def add64(self, dst_lo, dst_hi, src_lo, src_hi, off):
|
||||
"""s[dst_lo:dst_hi] = s[src_lo:src_hi] + off"""
|
||||
if off: self.emit(s_add_u32(s[dst_lo], s[src_lo], off)); self.emit(s_addc_u32(s[dst_hi], s[src_hi], 0))
|
||||
elif dst_lo != src_lo: self.emit(s_mov_b64(s[dst_lo:dst_hi], s[src_lo:src_hi]))
|
||||
|
||||
def global_load(self, vdst, addr, saddr=None):
|
||||
"""Global load b32"""
|
||||
self.emit(global_load_b32(vdst=v[vdst], addr=v[addr:addr+1],
|
||||
saddr=s[saddr:saddr+2] if saddr else RawImm(124)))
|
||||
|
||||
def waitcnt(self, lgkm=None, vm=None):
|
||||
"""Wait for memory operations. lgkm=N waits until N lgkm ops remain, vm=N waits until N vmem ops remain."""
|
||||
vmcnt, lgkmcnt, expcnt = vm if vm is not None else 63, lgkm if lgkm is not None else 63, 7
|
||||
waitcnt = (expcnt & 0x7) | ((lgkmcnt & 0x3f) << 4) | ((vmcnt & 0x3f) << 10)
|
||||
self.emit(s_waitcnt(simm16=waitcnt))
|
||||
from extra.assembly.amd.asm import waitcnt as encode_waitcnt
|
||||
if lgkm == 0 and vm is None: self.emit(s_waitcnt(simm16=WAIT_LGKM))
|
||||
elif vm == 0 and lgkm is None: self.emit(s_waitcnt(simm16=WAIT_VMEM))
|
||||
elif lgkm == 0 and vm == 0: self.emit(s_waitcnt(simm16=WAIT_ALL))
|
||||
elif vm is not None and lgkm is None:
|
||||
self.emit(s_waitcnt(simm16=encode_waitcnt(vmcnt=vm, expcnt=7, lgkmcnt=63)))
|
||||
elif lgkm is not None and vm is None:
|
||||
self.emit(s_waitcnt(simm16=encode_waitcnt(vmcnt=63, expcnt=7, lgkmcnt=lgkm)))
|
||||
else: raise ValueError(f"unsupported waitcnt: lgkm={lgkm}, vm={vm}")
|
||||
|
||||
def barrier(self): self.emit(s_barrier())
|
||||
|
||||
def to_asm(self):
|
||||
import re
|
||||
@@ -207,17 +227,14 @@ class Kernel:
|
||||
asm = re.sub(r'(s_cbranch_\w+|s_branch)\s+\S+', rf'\1 .{self.branch_targets[i]}', asm)
|
||||
body.append('\t' + asm)
|
||||
|
||||
# limit wave occupancy by using more LDS
|
||||
lds_size = max(LDS_SIZE, 65536//getenv("LIMIT_OCC", 65536))
|
||||
|
||||
# HSA kernel descriptor attributes (zeros included for compatibility)
|
||||
hsa = [
|
||||
('group_segment_fixed_size', lds_size), ('private_segment_fixed_size', 0), ('kernarg_size', 36),
|
||||
('group_segment_fixed_size', LDS_SIZE), ('private_segment_fixed_size', 0), ('kernarg_size', 36),
|
||||
('user_sgpr_count', 14), ('user_sgpr_dispatch_ptr', 0), ('user_sgpr_queue_ptr', 0),
|
||||
('user_sgpr_kernarg_segment_ptr', 1), ('user_sgpr_dispatch_id', 0), ('user_sgpr_private_segment_size', 0),
|
||||
('wavefront_size32', 1), ('uses_dynamic_stack', 0), ('enable_private_segment', 0),
|
||||
('system_sgpr_workgroup_id_x', 1), ('system_sgpr_workgroup_id_y', 1), ('system_sgpr_workgroup_id_z', 0),
|
||||
('system_sgpr_workgroup_info', 0), ('system_vgpr_workitem_id', 0), ('next_free_vgpr', 222),
|
||||
('system_sgpr_workgroup_info', 0), ('system_vgpr_workitem_id', 0), ('next_free_vgpr', 214),
|
||||
('next_free_sgpr', 16), ('float_round_mode_32', 0), ('float_round_mode_16_64', 0),
|
||||
('float_denorm_mode_32', 3), ('float_denorm_mode_16_64', 3), ('dx10_clamp', 1), ('ieee_mode', 1),
|
||||
('fp16_overflow', 0), ('workgroup_processor_mode', 0), ('memory_ordered', 1), ('forward_progress', 0),
|
||||
@@ -232,10 +249,10 @@ class Kernel:
|
||||
'\t.end_amdhsa_kernel', '\t.text', '.Lfunc_end0:', '\t.size\tkernel, .Lfunc_end0-kernel',
|
||||
'\t.amdgpu_metadata', '---', 'amdhsa.kernels:', ' - .args:',
|
||||
*[f' - .address_space: global\n .offset: {i*8}\n .size: 8\n .value_kind: global_buffer' for i in range(3)],
|
||||
f' .group_segment_fixed_size: {lds_size}', ' .kernarg_segment_align: 8',
|
||||
f' .group_segment_fixed_size: {LDS_SIZE}', ' .kernarg_segment_align: 8',
|
||||
' .kernarg_segment_size: 24', ' .max_flat_workgroup_size: 128', ' .name: kernel',
|
||||
' .private_segment_fixed_size: 0', ' .sgpr_count: 60', ' .symbol: kernel.kd',
|
||||
' .vgpr_count: 222', ' .wavefront_size: 32', f'amdhsa.target: amdgcn-amd-amdhsa--{self.arch}',
|
||||
' .vgpr_count: 214', ' .wavefront_size: 32', f'amdhsa.target: amdgcn-amd-amdhsa--{self.arch}',
|
||||
'amdhsa.version:', ' - 1', ' - 2', '...', '\t.end_amdgpu_metadata'])
|
||||
|
||||
|
||||
@@ -249,8 +266,8 @@ def build_kernel(arch='gfx1100'):
|
||||
# ===========================================================================
|
||||
# PROLOGUE: Load kernel arguments, compute tile coordinates and addresses
|
||||
# ===========================================================================
|
||||
k.emit(s_load_b128(sdata=s[S_KERNARG_A[0]:S_KERNARG_B[1]], sbase=s[0:1], offset=0x0, soffset=NULL))
|
||||
k.emit(s_load_b64(sdata=s[S_KERNARG_OUT[0]:S_KERNARG_OUT[1]], sbase=s[0:1], offset=0x10, soffset=NULL))
|
||||
k.emit(s_load_b128(sdata=s[S_KERNARG_A[0]:S_KERNARG_B[1]], sbase=s[0:1], offset=0x0, soffset=RawImm(124)))
|
||||
k.emit(s_load_b64(sdata=s[S_KERNARG_OUT[0]:S_KERNARG_OUT[1]], sbase=s[0:1], offset=0x10, soffset=RawImm(124)))
|
||||
k.emit(s_mov_b32(s[S_DIM_N], MATRIX_DIM))
|
||||
k.emit(s_mov_b32(s[S_LOOP_CTR], 0)) # used by LDS swizzle, always 0 for valid workgroups
|
||||
k.emit(s_lshl_b32(s[S_TILE_X], s[S_WORKGROUP_X], 7))
|
||||
@@ -272,14 +289,8 @@ def build_kernel(arch='gfx1100'):
|
||||
k.emit(s_mov_b64(s[S_B_PTR[0]:S_B_PTR[1]], s[S_KERNARG_B[0]:S_KERNARG_B[1]]))
|
||||
|
||||
# Compute 8 A and B matrix tile base pointers for prefetch
|
||||
k.emit(s_mov_b64(s[S_PREFETCH_B:S_PREFETCH_B+1], s[S_KERNARG_B[0]:S_KERNARG_B[1]])) # B[0]: no offset
|
||||
for i in range(1, 8): # B: 16KB apart
|
||||
k.emit(s_add_u32(s[S_PREFETCH_B+i*2], s[S_KERNARG_B[0]], i * 0x4000))
|
||||
k.emit(s_addc_u32(s[S_PREFETCH_B+i*2+1], s[S_KERNARG_B[1]], 0))
|
||||
k.emit(s_mov_b64(s[S_PREFETCH_A:S_PREFETCH_A+1], s[S_KERNARG_A[0]:S_KERNARG_A[1]])) # A[0]: no offset
|
||||
for i in range(1, 8): # A: 256KB apart
|
||||
k.emit(s_add_u32(s[S_PREFETCH_A+i*2], s[S_KERNARG_A[0]], i * 0x40000))
|
||||
k.emit(s_addc_u32(s[S_PREFETCH_A+i*2+1], s[S_KERNARG_A[1]], 0))
|
||||
for i in range(8): k.add64(S_PREFETCH_B + i*2, S_PREFETCH_B + i*2 + 1, S_KERNARG_B[0], S_KERNARG_B[1], i * 0x4000) # B: 16KB apart
|
||||
for i in range(8): k.add64(S_PREFETCH_A + i*2, S_PREFETCH_A + i*2 + 1, S_KERNARG_A[0], S_KERNARG_A[1], i * 0x40000) # A: 256KB apart
|
||||
|
||||
# Global prefetch addresses: B = (tile_x + lane_id) * 4, A = ((tile_y << 12) + (lane_id/8)*4K + lane_id%8) * 4
|
||||
k.emit(v_add_nc_u32_e32(v[V_GLOBAL_B_ADDR], s[S_TILE_X], v[0]))
|
||||
@@ -324,20 +335,20 @@ def build_kernel(arch='gfx1100'):
|
||||
k.emit(v_add_co_ci_u32_e32(v[6], s[S_B_PTR[1]], v[6]))
|
||||
for dst, mult in [(9,1), (7,2), (2,3), (11,4), (13,5)]: b_addr(dst, mult)
|
||||
k.emit(s_clause(simm16=5)) # 6 consecutive global loads
|
||||
for vdst, addr in INIT_TILE_LOADS[:6]: k.emit(global_load_b32(vdst=v[vdst], addr=v[addr:addr+1], saddr=NULL))
|
||||
for vdst, addr in INIT_TILE_LOADS[:6]: k.global_load(vdst, addr)
|
||||
|
||||
# Batch 2: A addresses (rows 0-4) and loads
|
||||
for dst, ri in [(6,0), (8,1), (10,2), (12,3), (14,4)]:
|
||||
k.emit(v_add_nc_u32_e32(v[dst], v[ROW_REGS[ri]], v[V_LANE_ID_MOD8]))
|
||||
addr64(dst, S_A_PTR[0])
|
||||
k.emit(s_clause(simm16=4)) # 5 consecutive global loads
|
||||
for vdst, addr in INIT_TILE_LOADS[6:11]: k.emit(global_load_b32(vdst=v[vdst], addr=v[addr:addr+1], saddr=NULL))
|
||||
for vdst, addr in INIT_TILE_LOADS[6:11]: k.global_load(vdst, addr)
|
||||
|
||||
# Batch 3: B cols 6-7, A rows 5-7, and loads
|
||||
for dst, mult, tmp in [(2,6,15), (4,7,4)]: b_addr(dst, mult, tmp)
|
||||
for dst, ri, tmp in [(8,5,16), (6,6,18), (10,7,20)]: a_addr(dst, ROW_REGS[ri], tmp)
|
||||
k.emit(s_clause(simm16=4)) # 5 consecutive global loads
|
||||
for vdst, addr in INIT_TILE_LOADS[11:]: k.emit(global_load_b32(vdst=v[vdst], addr=v[addr:addr+1], saddr=NULL))
|
||||
for vdst, addr in INIT_TILE_LOADS[11:]: k.global_load(vdst, addr)
|
||||
|
||||
# ===========================================================================
|
||||
# LDS store address computation (bank-conflict-avoiding swizzle)
|
||||
@@ -397,6 +408,8 @@ def build_kernel(arch='gfx1100'):
|
||||
for i, idx in enumerate([6,7,8,9,10,13,14,15]):
|
||||
offset = i * 64
|
||||
k.emit(ds_store_b32(addr=v[V_LDS_B_ADDR], data0=v[INIT_TILE_LOADS[idx][0]], offset0=offset & 0xFF, offset1=offset >> 8))
|
||||
k.waitcnt(lgkm=0)
|
||||
k.barrier()
|
||||
|
||||
# ===========================================================================
|
||||
# INIT: Compute LDS base addresses, then zero accumulators
|
||||
@@ -425,7 +438,7 @@ def build_kernel(arch='gfx1100'):
|
||||
# MAIN GEMM LOOP
|
||||
# ===========================================================================
|
||||
|
||||
NO_ALU, NO_DS, NO_GLOBAL = getenv("NO_ALU", 0), getenv("NO_DS", 0), getenv("NO_GLOBAL", 0)
|
||||
NO_DS, NO_GLOBAL = getenv("NO_DS", 0), getenv("NO_GLOBAL", 0)
|
||||
|
||||
k.label('LOOP_INC')
|
||||
k.emit(s_add_i32(s[S_LOOP_CTR], s[S_LOOP_CTR], 8))
|
||||
@@ -437,103 +450,61 @@ def build_kernel(arch='gfx1100'):
|
||||
k.emit(s_cselect_b32(s[S_PREFETCH_FLAG], -1, 0)) # s_cselect doesn't modify SCC
|
||||
k.emit(s_cbranch_scc0(simm16=0)); k.branch_to('SKIP_PREFETCH') # branch if loop_ctr >= loop_bound
|
||||
|
||||
# Advance prefetch pointers
|
||||
k.emit(v_add_nc_u32_e32(v[V_GLOBAL_B_ADDR], 0x20000, v[V_GLOBAL_B_ADDR]))
|
||||
k.emit(v_add_nc_u32_e32(v[V_GLOBAL_A_ADDR], 0x20, v[V_GLOBAL_A_ADDR]))
|
||||
|
||||
if not NO_GLOBAL:
|
||||
# Advance prefetch pointers (VGPR)
|
||||
k.emit(v_add_nc_u32_e32(v[V_GLOBAL_B_ADDR], 0x20000, v[V_GLOBAL_B_ADDR]))
|
||||
k.emit(v_add_nc_u32_e32(v[V_GLOBAL_A_ADDR], 0x20, v[V_GLOBAL_A_ADDR]))
|
||||
|
||||
"""
|
||||
# Advance prefetch pointers (SGPRs, 64-bit adds)
|
||||
k.emit(s_clause(simm16=31))
|
||||
for i in range(8):
|
||||
k.emit(s_add_u32(s[S_PREFETCH_B+i*2], s[S_PREFETCH_B+i*2], 0x20000))
|
||||
k.emit(s_addc_u32(s[S_PREFETCH_B+i*2+1], s[S_PREFETCH_B+i*2+1], 0))
|
||||
for i in range(8):
|
||||
k.emit(s_add_u32(s[S_PREFETCH_A+i*2], s[S_PREFETCH_A+i*2], 0x20))
|
||||
k.emit(s_addc_u32(s[S_PREFETCH_A+i*2+1], s[S_PREFETCH_A+i*2+1], 0))
|
||||
"""
|
||||
|
||||
# do the fetch
|
||||
for vdst, saddr_lo in INIT_PREFETCH:
|
||||
k.emit(global_load_b32(vdst=v[vdst], addr=v[V_GLOBAL_B_ADDR], saddr=s[saddr_lo:saddr_lo+1]))
|
||||
k.global_load(vdst, V_GLOBAL_B_ADDR, saddr_lo)
|
||||
|
||||
k.label('SKIP_PREFETCH')
|
||||
|
||||
# wait for local stores to finish (either initial or loop)
|
||||
# then sync the warp so it's safe to load local
|
||||
k.waitcnt(lgkm=0)
|
||||
k.emit(s_barrier())
|
||||
|
||||
# Load initial tiles for iter=0 into buffer 0
|
||||
if not NO_DS:
|
||||
a_tile_regs = V_A_TILE_REGS[0]
|
||||
b_tile_regs = V_B_TILE_REGS[0]
|
||||
k.emit(s_clause(simm16=len(a_tile_regs) + len(b_tile_regs) - 1))
|
||||
for i, vdst in enumerate(a_tile_regs):
|
||||
a_off = (i & 1) * 8 + (i >> 1) * 64 # iter=0
|
||||
k.emit(ds_load_b64(vdst=v[vdst:vdst+1], addr=v[V_LDS_A_BASE], offset0=a_off & 0xFF, offset1=a_off >> 8))
|
||||
for i, vdst in enumerate(b_tile_regs):
|
||||
b_off = (i & 1) * 8 + (i & 2) * 64 + (i >> 2) * 256 # iter=0
|
||||
k.emit(ds_load_b64(vdst=v[vdst:vdst+1], addr=v[V_LDS_B_BASE], offset0=b_off & 0xFF, offset1=b_off >> 8))
|
||||
|
||||
# 8 inner loop iterations
|
||||
# Double-buffered inner loop: load next iteration's tiles while computing current
|
||||
# Buffer 0 used for even iterations, buffer 1 for odd iterations
|
||||
for iter in range(8):
|
||||
buf = iter & 1 # current compute buffer
|
||||
next_buf = 1 - buf # next load buffer
|
||||
|
||||
# Load tiles for NEXT iteration into next_buf (except on last iteration)
|
||||
if not NO_DS and iter < 7:
|
||||
next_iter = iter + 1
|
||||
a_tile_regs = V_A_TILE_REGS[next_buf]
|
||||
b_tile_regs = V_B_TILE_REGS[next_buf]
|
||||
k.emit(s_clause(simm16=len(a_tile_regs) + len(b_tile_regs) - 1)) # 12 loads total: 4 A + 8 B
|
||||
# Load A tile (4 pairs) and B tile (8 pairs) from LDS
|
||||
if not NO_DS:
|
||||
k.emit(s_clause(simm16=11)) # 12 loads total: 4 A + 8 B
|
||||
# A tile: 4 ds_load_b64
|
||||
for i, vdst in enumerate(a_tile_regs):
|
||||
a_off = (i & 1) * 8 + (i >> 1) * 64 + next_iter * LDS_A_STRIDE
|
||||
for i, vdst in enumerate(V_A_TILE_REGS):
|
||||
a_off = (i & 1) * 8 + (i >> 1) * 64 + iter * LDS_A_STRIDE
|
||||
k.emit(ds_load_b64(vdst=v[vdst:vdst+1], addr=v[V_LDS_A_BASE], offset0=a_off & 0xFF, offset1=a_off >> 8))
|
||||
# B tile: 8 ds_load_b64
|
||||
for i, vdst in enumerate(b_tile_regs):
|
||||
b_off = (i & 1) * 8 + (i & 2) * 64 + (i >> 2) * 256 + next_iter * LDS_B_STRIDE
|
||||
for i, vdst in enumerate(V_B_TILE_REGS):
|
||||
b_off = (i & 1) * 8 + (i & 2) * 64 + (i >> 2) * 256 + iter * LDS_B_STRIDE
|
||||
k.emit(ds_load_b64(vdst=v[vdst:vdst+1], addr=v[V_LDS_B_BASE], offset0=b_off & 0xFF, offset1=b_off >> 8))
|
||||
k.waitcnt(lgkm=0)
|
||||
|
||||
# Issue global prefetch (first 6 iterations only)
|
||||
# 64 dual FMACs
|
||||
k.emit(s_clause(simm16=63))
|
||||
for i, (vdst_x, vdst_y, ax, bx, ay, by) in enumerate(FMAC_PATTERN):
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_FMAC_F32, VOPDOp.V_DUAL_FMAC_F32,
|
||||
vdstx=v[vdst_x], vdsty=v[vdst_y], srcx0=v[ax], vsrcx1=v[bx], srcy0=v[ay], vsrcy1=v[by]))
|
||||
|
||||
# Issue global prefetch AFTER FMACs (first 6 iterations only)
|
||||
if iter < 6 and not NO_GLOBAL:
|
||||
vdst1, vdst2, addr, slo1, slo2 = PREFETCH_LOADS[iter]
|
||||
k.emit(global_load_b32(vdst=v[vdst1], addr=v[addr], saddr=s[slo1:slo1+1]))
|
||||
k.emit(global_load_b32(vdst=v[vdst2], addr=v[addr], saddr=s[slo2:slo2+1]))
|
||||
k.global_load(vdst1, addr, slo1)
|
||||
k.global_load(vdst2, addr, slo2)
|
||||
|
||||
# Wait for current buffer's loads to complete
|
||||
# iter 0-6: 12 loads for next iteration are in flight, wait for the other 12 (lgkm=12)
|
||||
# iter 7: no loads in flight, wait for all (lgkm=0)
|
||||
k.waitcnt(lgkm=0 if iter == 7 else 12)
|
||||
|
||||
# 64 dual FMACs using current buffer
|
||||
if not NO_ALU:
|
||||
fmac_pattern = FMAC_PATTERN[buf]
|
||||
k.emit(s_clause(simm16=len(fmac_pattern)-1))
|
||||
for i, (vdst_x, vdst_y, ax, bx, ay, by) in enumerate(fmac_pattern):
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_FMAC_F32, VOPDOp.V_DUAL_FMAC_F32,
|
||||
vdstx=v[vdst_x], vdsty=v[vdst_y], srcx0=v[ax], vsrcx1=v[bx], srcy0=v[ay], vsrcy1=v[by]))
|
||||
|
||||
# wait for all global stores to finish
|
||||
# then sync the warp so it's safe to store local
|
||||
k.emit(s_and_not1_b32(VCC_LO, EXEC_LO, s[S_PREFETCH_FLAG]))
|
||||
k.waitcnt(vm=0)
|
||||
k.emit(s_barrier())
|
||||
k.barrier()
|
||||
k.emit(s_cbranch_vccnz(simm16=0)); k.branch_to('LOOP_INC')
|
||||
|
||||
# Store prefetched data to LDS
|
||||
# NOTE: Register naming reflects LDS tile organization, not source matrix:
|
||||
# V_LDS_A_DATA (v155-162) holds data that goes to LDS A-tile region
|
||||
# V_LDS_B_DATA (v163-170) holds data that goes to LDS B-tile region
|
||||
# The data sources are swapped: A-tile receives B matrix rows, B-tile receives A matrix columns
|
||||
if not NO_DS:
|
||||
for i in range(4): # A tile: 8 values via 4 stride64 stores
|
||||
k.emit(ds_store_2addr_stride64_b32(addr=v[V_LDS_A_ADDR], data0=v[V_LDS_A_DATA[i*2]], data1=v[V_LDS_A_DATA[i*2+1]], offset0=i*4, offset1=i*4+2))
|
||||
for i in range(8): # B tile: 8 values via 8 scalar stores with 64-byte spacing
|
||||
offset = i * 64
|
||||
k.emit(ds_store_b32(addr=v[V_LDS_B_ADDR], data0=v[V_LDS_B_DATA[i]], offset0=offset & 0xFF, offset1=offset >> 8))
|
||||
for i in range(4): # A tile: 8 values via 4 stride64 stores
|
||||
k.emit(ds_store_2addr_stride64_b32(addr=v[V_LDS_A_ADDR], data0=v[V_LDS_A_DATA[i*2]], data1=v[V_LDS_A_DATA[i*2+1]], offset0=i*4, offset1=i*4+2))
|
||||
for i in range(8): # B tile: 8 values via 8 scalar stores with 64-byte spacing
|
||||
offset = i * 64
|
||||
k.emit(ds_store_b32(addr=v[V_LDS_B_ADDR], data0=v[V_LDS_B_DATA[i]], offset0=offset & 0xFF, offset1=offset >> 8))
|
||||
|
||||
k.waitcnt(lgkm=0)
|
||||
k.barrier()
|
||||
k.emit(s_branch(simm16=0)); k.branch_to('LOOP_INC')
|
||||
|
||||
# ===========================================================================
|
||||
@@ -592,9 +563,9 @@ def build_kernel(arch='gfx1100'):
|
||||
k.emit(v_add_co_u32(v[0], VCC_LO, s[S_PREFETCH_FLAG], v[0]))
|
||||
k.emit(v_add_co_ci_u32_e32(v[1], v[1], v[V_ADDR_HI_ZERO]))
|
||||
|
||||
k.emit(global_store_b128(addr=v[0:1], data=v[tmp:tmp+3], saddr=NULL))
|
||||
k.emit(global_store_b128(addr=v[0:1], data=v[tmp:tmp+3], saddr=RawImm(124)))
|
||||
|
||||
k.emit(s_sendmsg(simm16=3)) # DEALLOC_VGPRS
|
||||
k.emit(s_sendmsg(simm16=3))
|
||||
k.emit(s_endpgm())
|
||||
|
||||
return k.to_asm()
|
||||
@@ -618,6 +589,7 @@ def test_matmul():
|
||||
print(f"Loaded stock kernel from {stock_path}")
|
||||
else:
|
||||
asm = build_kernel(dev.arch)
|
||||
if getenv("PRINT_ASM", 0): print(asm)
|
||||
|
||||
binary = dev.compiler.compile(asm)
|
||||
print(f"Compiled! Binary size: {len(binary)} bytes")
|
||||
@@ -631,16 +603,15 @@ def test_matmul():
|
||||
grid, local = (N // BLOCK_N, N // BLOCK_M, 1), (THREADS, 1, 1)
|
||||
print(f"Grid: {grid}, Local: {local}")
|
||||
|
||||
dname:str = Device.DEFAULT
|
||||
def asm_kernel(A:UOp, B:UOp, C:UOp) -> UOp:
|
||||
gidxs = [UOp.special(n, f"gidx{i}") for i,n in enumerate(grid)]
|
||||
lidxs = [UOp.special(n, f"lidx{i}") for i,n in enumerate(local)]
|
||||
sink = UOp.sink(A.base, B.base, C.base, *gidxs, *lidxs, arg=KernelInfo(name=colored("kernel", "cyan"),
|
||||
estimates=Estimates(ops=N*N*N*2, mem=N*N*4*3)))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg=dname), UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=asm),
|
||||
UOp(Ops.BINARY, arg=binary)))
|
||||
c = Tensor.custom_kernel(a, b, c, fxn=asm_kernel)[2]
|
||||
ei = c.schedule()[0].lower()
|
||||
_prg = dev.runtime("kernel", binary)
|
||||
class AsmRunner(Runner):
|
||||
def __init__(self):
|
||||
super().__init__(colored("kernel", "cyan"), Device.DEFAULT, Estimates(ops=N*N*N*2, mem=N*N*4*3))
|
||||
def __call__(self, rawbufs, var_vals, wait=False):
|
||||
c_buf, a_buf, b_buf = [x.ensure_allocated()._buf for x in rawbufs]
|
||||
return _prg(a_buf, b_buf, c_buf, global_size=grid, local_size=local, wait=wait)
|
||||
|
||||
ei = ExecItem(None, [c.uop.buffer, a.uop.buffer, b.uop.buffer], prg=AsmRunner())
|
||||
|
||||
ets = []
|
||||
with Context(DEBUG=2):
|
||||
@@ -652,7 +623,7 @@ def test_matmul():
|
||||
with Context(DEBUG=2): tc = (a @ b).realize()
|
||||
with Context(DEBUG=0): err = (c - tc).square().mean().item()
|
||||
print(f"mean squared error {err}")
|
||||
if err != err or err > 1e-06: raise RuntimeError("matmul is wrong!")
|
||||
if err > 1e-06: raise RuntimeError("matmul is wrong!")
|
||||
|
||||
def run_sqtt():
|
||||
"""Run with SQTT profiling and write trace files."""
|
||||
|
||||
@@ -45,7 +45,7 @@ def custom_asm_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
|
||||
sz = UOp.variable("SZ", 256, 8192)
|
||||
|
||||
sink = UOp.sink(C.base, A.base, B.base, sz, lidx, gidx, arg=KernelInfo(name="gemm"))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg=Device.DEFAULT), UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src)))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg=Device.DEFAULT), UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src)), arg=())
|
||||
|
||||
C_asm = Tensor.custom_kernel(C_asm, from_torch(A), from_torch(Bt), fxn=custom_asm_gemm)[0]
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ def asm_kernel() -> UOp:
|
||||
src = template.replace("INSTRUCTIONS", (pathlib.Path(__file__).parent/"gemm.s").read_text())
|
||||
|
||||
sink = UOp.sink(a, b, c, lidx, gidx, arg=KernelInfo(name="gemm"))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg=dname), UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src)))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg=dname), UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src)), arg=())
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_matmul(asm_kernel(), dtype=dtypes.half, N=N)
|
||||
|
||||
@@ -46,22 +46,6 @@ def install_hook(c_function, python_function):
|
||||
# *** ioctl lib end ***
|
||||
|
||||
import tinygrad.runtime.autogen.kfd as kfd_ioctl
|
||||
import tinygrad.runtime.autogen.hsa as hsa
|
||||
|
||||
def print_aql_queue(read_pointer_address):
|
||||
rptr_offset = getattr(hsa.amd_queue_v2_t, 'read_dispatch_id').offset
|
||||
queue_base = read_pointer_address - rptr_offset
|
||||
queue = hsa.amd_queue_v2_t.from_address(queue_base)
|
||||
print(f" AQL Queue @ 0x{queue_base:X}:")
|
||||
for field_name, field_type in hsa.amd_queue_v2_t._fields_:
|
||||
val = getattr(queue, field_name)
|
||||
if isinstance(val, int): print(f" {field_name}: 0x{val:X}")
|
||||
elif hasattr(val, '_length_'):
|
||||
arr_vals = [f"{format_struct(v)}" if hasattr(v, '_fields_') else f"{v:#X}" for v in val]
|
||||
print(f" {field_name}: [{', '.join(arr_vals)}]")
|
||||
elif hasattr(val, '_fields_'): print(f" {field_name}: {format_struct(val)}")
|
||||
else: print(f" {field_name}: {val}")
|
||||
|
||||
def ioctls_from_header():
|
||||
hdr = (pathlib.Path(__file__).parent / "kfd_ioctl.h").read_text().replace("\\\n", "")
|
||||
pattern = r'#define\s+(AMDKFD_IOC_[A-Z0-9_]+)\s+AMDKFD_IOW?R?\((0x[0-9a-fA-F]+),\s+struct\s([A-Za-z0-9_]+)\)'
|
||||
@@ -83,7 +67,6 @@ def ioctl(fd, request, argp):
|
||||
if name == "AMDKFD_IOC_SVM":
|
||||
out = ctypes.cast(s.attrs, ctypes.POINTER(kfd_ioctl.struct_kfd_ioctl_svm_attribute))
|
||||
for i in range(s.nattr): print(f"{i}: {kfd_ioctl.enum_kfd_ioctl_svm_attr_type.get(out[i].type):40s}: {out[i].value:#x}")
|
||||
if name == "AMDKFD_IOC_CREATE_QUEUE" and s.queue_type == kfd_ioctl.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL: print_aql_queue(s.read_pointer_address)
|
||||
else:
|
||||
print(f"{(st-start)*1000:7.2f} ms +{et*1000.:7.2f} ms : ioctl",
|
||||
f"{idir=} {size=} {itype=} {nr=} {fd=} {ret=}", os.readlink(f"/proc/self/fd/{fd}") if fd >= 0 else "")
|
||||
|
||||
@@ -145,7 +145,7 @@ OPNAME = {
|
||||
ALUSRC = {
|
||||
1: "SALU",
|
||||
2: "VALU",
|
||||
3: "VALU_SALU",
|
||||
3: "VALU_ALT",
|
||||
}
|
||||
|
||||
MEMSRC = {
|
||||
|
||||
@@ -275,7 +275,7 @@ class RGP:
|
||||
),
|
||||
shader_engine_index=sqtt_event.se,
|
||||
sqtt_version={11: sqtt.SQTT_VERSION_3_2, 12: sqtt.SQTT_VERSION_3_3}.get(gfx_ver),
|
||||
v1=sqtt.struct_sqtt_file_chunk_sqtt_desc_v1(
|
||||
v1=sqtt.struct_sqtt_file_chunk_sqtt_desc_0_v1(
|
||||
instrumentation_spec_version=1,
|
||||
instrumentation_api_version=0,
|
||||
compute_unit_index=0,
|
||||
|
||||
+3
-1
@@ -22,7 +22,9 @@ class WaveSlot:
|
||||
@property
|
||||
def cu_loc(self) -> str: return f"SE:{self.se} CU:{self.cu}"
|
||||
@property
|
||||
def wave_loc(self) -> str: return f"{self.cu_loc} SIMD:{self.simd} W:{self.wave_id}"
|
||||
def simd_loc(self) -> str: return f"{self.cu_loc} SIMD:{self.simd}"
|
||||
@property
|
||||
def wave_loc(self) -> str: return f"{self.simd_loc} W:{self.wave_id}"
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class WaveExec(WaveSlot):
|
||||
|
||||
+20
-32
@@ -11,15 +11,6 @@ NUM_WORKERS = 1
|
||||
Q_BLOCK_SIZE = 16
|
||||
KV_BLOCK_SIZE = 16
|
||||
|
||||
def _sharded_empty(shape:Tensor, ref:Tensor, axis:int|None) -> Tensor:
|
||||
if not isinstance(ref.device, tuple): return Tensor.empty(*shape, device=ref.device)
|
||||
shape = tuple(s // len(ref.device) if i == ref.uop.axis else s for i, s in enumerate(shape))
|
||||
axis = ref.uop.axis if axis is None else axis
|
||||
return Tensor(Tensor.empty(*shape, device=ref.device).uop.multi(axis), device=ref.device)
|
||||
|
||||
def _sharded_empty_like(ref:Tensor, axis:int|None=None) -> Tensor:
|
||||
return _sharded_empty(ref.shape, ref, axis)
|
||||
|
||||
def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False):
|
||||
if len(xq.shape) == 3: xq, xk, xv = xq.unsqueeze(0), xk.unsqueeze(0), xv.unsqueeze(0)
|
||||
|
||||
@@ -318,12 +309,12 @@ def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False
|
||||
|
||||
return ker.finish()
|
||||
|
||||
def custom_backward_v(dvu:UOp, dou:UOp, qu:UOp, ku:UOp, vu:UOp, masku:UOp, l_vecu:UOp) -> UOp:
|
||||
def custom_backward_v(dvu:UOp, dou:UOp, qu:UOp, ku:UOp, vu:UOp, masku:UOp, l_vecu:UOp, delta_vecu:UOp) -> UOp:
|
||||
with Kernel("fa_custom_backward_v", (H_KV, N // (KV_BLOCK_SIZE*NUM_WORKERS), B), NUM_WORKERS * WARP_THREADS) as ker:
|
||||
warp = ker.warp
|
||||
|
||||
dv, do, q, k, v, mask = GL(dvu, ker), GL(dou, ker), GL(qu, ker), GL(ku, ker), GL(vu, ker), GL(masku, ker)
|
||||
l_vec = GL(l_vecu, ker)
|
||||
l_vec, delta_vec = GL(l_vecu, ker), GL(delta_vecu, ker)
|
||||
|
||||
head_kv = ker.blockIdx_x
|
||||
batch = ker.blockIdx_z
|
||||
@@ -352,6 +343,7 @@ def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False
|
||||
att_block_row = ker.rt((Q_BLOCK_SIZE, KV_BLOCK_SIZE), dtypes.bfloat16)
|
||||
|
||||
l_vec_reg = ker.rv(Q_BLOCK_SIZE, dtypes.float32)
|
||||
delta_vec_reg = ker.rv(Q_BLOCK_SIZE, dtypes.float32)
|
||||
|
||||
dv_reg = warp.zero(dv_reg)
|
||||
|
||||
@@ -374,9 +366,10 @@ def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False
|
||||
do_reg = warp.load(do_reg, do_smem)
|
||||
do_reg_col = warp.load(do_reg_col, do_smem)
|
||||
|
||||
# load l_vec
|
||||
# load l_vec and delta_vec
|
||||
l_vec_reg = warp.load(l_vec_reg, l_vec, (), (batch, head_q, 0, q_idx), axis=2)
|
||||
l_vec_reg *= 1.0 / math.log(2)
|
||||
delta_vec_reg = warp.load(delta_vec_reg, delta_vec, (), (batch, head_q, 0, q_idx), axis=2)
|
||||
|
||||
# mma qk^t
|
||||
att_block = warp.zero(att_block.after(g))
|
||||
@@ -398,41 +391,36 @@ def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False
|
||||
dv_reg = warp.mma_AB(dv_reg, att_block_row, do_reg_col)
|
||||
dv_reg = ker.endrange(2)
|
||||
|
||||
dv_reg = warp.map(dv_reg, lambda x, idx: x + v_reg[*idx].cast(dtypes.float32) * 1e-30)
|
||||
|
||||
dv = warp.store(dv, dv_reg, (batch, kv_seq, head_kv, 0), axis=1)
|
||||
|
||||
return ker.finish()
|
||||
|
||||
single_device = xq.device[0] if isinstance(xq.device, tuple) else xq.device
|
||||
|
||||
if is_causal:
|
||||
if attn_mask is not None: raise RuntimeError("cannot set attn_mask when is_causal=True")
|
||||
attn_mask = Tensor.ones((B, 1, N, N), requires_grad=False, device=single_device, dtype=dtypes.bool).tril()
|
||||
attn_mask = Tensor.ones((B, 1, N, N), requires_grad=False, device=xq.device, dtype=dtypes.bool).tril()
|
||||
if attn_mask is not None:
|
||||
if attn_mask.dtype == dtypes.bool: attn_mask = attn_mask.where(0, -float("inf"))
|
||||
else:
|
||||
attn_mask = Tensor.zeros((B, 1, N, N), requires_grad=False, device=single_device, dtype=dtypes.float32)
|
||||
if isinstance(xq.device, tuple) and not isinstance(attn_mask.device, tuple):
|
||||
attn_mask = attn_mask.shard(xq.device, axis=0)
|
||||
attn_mask = Tensor.zeros((B, 1, N, N), requires_grad=False, device=xq.device, dtype=dtypes.float32)
|
||||
|
||||
attn = _sharded_empty_like(xq, axis=0)
|
||||
l_vec = _sharded_empty((B, H, 1, N), xq, axis=0)
|
||||
attn = Tensor.empty_like(xq)
|
||||
l_vec = Tensor.empty(B, H, 1, N, requires_grad=False, device=xq.device, dtype=dtypes.float32).detach()
|
||||
|
||||
def grad(gradu:UOp, _) -> tuple[None, None, UOp, UOp, UOp, None]:
|
||||
grad = Tensor(gradu, device=gradu.device)
|
||||
grad_q = _sharded_empty_like(xq, axis=0)
|
||||
grad_k = _sharded_empty_like(xk, axis=0)
|
||||
grad_v = _sharded_empty_like(xv, axis=0)
|
||||
def grad(gradu:UOp, kernel:UOp) -> tuple[None, None, UOp, UOp, UOp, None]:
|
||||
grad = Tensor(gradu)
|
||||
grad_q = Tensor.empty_like(q := Tensor(kernel.src[2]))
|
||||
grad_k = Tensor.empty_like(k := Tensor(kernel.src[3]))
|
||||
grad_v = Tensor.empty_like(v := Tensor(kernel.src[4]))
|
||||
mask = Tensor(kernel.src[5])
|
||||
|
||||
delta_vec = (grad * attn).sum(-1, dtype=dtypes.float32).transpose(1, 2).unsqueeze(-2).detach()
|
||||
|
||||
grad_q = Tensor.custom_kernel(grad_q, grad, xq, xk, xv, attn_mask, l_vec, delta_vec, fxn=custom_backward_q)[0]
|
||||
grad_k = Tensor.custom_kernel(grad_k, grad, xq, xk, xv, attn_mask, l_vec, delta_vec, fxn=custom_backward_k)[0]
|
||||
grad_v = Tensor.custom_kernel(grad_v, grad, xq, xk, xv, attn_mask, l_vec, fxn=custom_backward_v)[0]
|
||||
grad_q = Tensor.custom_kernel(grad_q, grad, q, k, v, mask, l_vec, delta_vec, fxn=custom_backward_q)[0]
|
||||
grad_k = Tensor.custom_kernel(grad_k, grad, q, k, v, mask, l_vec, delta_vec, fxn=custom_backward_k)[0]
|
||||
grad_v = Tensor.custom_kernel(grad_v, grad, q, k, v, mask, l_vec, delta_vec, fxn=custom_backward_v)[0]
|
||||
return (None, None, grad_q.uop, grad_k.uop, grad_v.uop, None)
|
||||
|
||||
attn, l_vec = Tensor.custom_kernel(attn, l_vec, xq, xk, xv, attn_mask, fxn=custom_forward, grad_fxn=grad)[:2]
|
||||
attn_ = attn[:, :N_, :, :D_]
|
||||
attn = attn[:, :N_, :, :D_]
|
||||
|
||||
return attn_.transpose(1, 2).cast(odtype)
|
||||
return attn.transpose(1, 2).cast(odtype)
|
||||
|
||||
@@ -7,7 +7,7 @@ from tinygrad.uop.ops import Ops
|
||||
from tinygrad.helpers import getenv, prod, strides_for_shape, argfix
|
||||
import torch.lib
|
||||
TORCH_DEBUG = getenv("TORCH_DEBUG")
|
||||
import torch, pathlib, operator, functools, weakref
|
||||
import torch, pathlib, math, operator, functools, weakref
|
||||
torch.autograd.grad_mode.set_multithreading_enabled(False)
|
||||
from tinygrad.dtype import _from_torch_dtype, _to_torch_dtype
|
||||
|
||||
@@ -74,7 +74,6 @@ view_ops = {
|
||||
"aten.select.int": lambda self, dim, idx: self[(slice(None),) * (dim%self.ndim) + (idx,)],
|
||||
"aten.permute": Tensor.permute,
|
||||
"aten.alias": lambda self: self,
|
||||
"aten.diagonal": Tensor.diagonal,
|
||||
}
|
||||
|
||||
# torch 2.10 handles this natively
|
||||
@@ -155,9 +154,7 @@ def index_put(self, indices, values, accumulate=False):
|
||||
return aten.index_put(self.cpu(), [z.cpu() if isinstance(z, torch.Tensor) else None for z in indices], values.clone().cpu(), accumulate).tiny()
|
||||
|
||||
@torch.library.impl("aten::isin.Tensor_Tensor_out", "privateuseone")
|
||||
def isin_tensor_tensor_out(x, y, *, assume_unique=False, invert=False, out=None):
|
||||
result = (unwrap(x).unsqueeze(-1) == unwrap(y).flatten()).any(-1)
|
||||
return out.copy_(wrap(~result if invert else result))
|
||||
def isin_tensor_tensor_out(x, y, *, assume_unique=False, invert=False, out=None): return out.copy_(aten.isin(x.cpu(), y.cpu(), assume_unique=assume_unique, invert=invert).tiny())
|
||||
|
||||
@torch.library.impl("aten::randperm.generator_out", "privateuseone")
|
||||
def randperm_generator(n, generator=None, out=None):
|
||||
@@ -165,16 +162,13 @@ def randperm_generator(n, generator=None, out=None):
|
||||
|
||||
@torch.library.impl("aten::cummax", "privateuseone")
|
||||
def cummax(self, dim):
|
||||
values, indices = unwrap(self).cummax(dim)
|
||||
return (wrap(values), wrap(indices.cast(dtypes.int64)))
|
||||
|
||||
@torch.library.impl("aten::cummin", "privateuseone")
|
||||
def cummin(self, dim):
|
||||
values, indices = unwrap(self).cummin(dim)
|
||||
return (wrap(values), wrap(indices.cast(dtypes.int64)))
|
||||
# TODO: support cummax with indices to match torch
|
||||
cummax, indices = aten.cummax(self.cpu(), dim)
|
||||
return (cummax.tiny(), indices.tiny())
|
||||
|
||||
@torch.library.impl("aten::nonzero", "privateuseone")
|
||||
def nonzero(self): return wrap(unwrap(self).nonzero())
|
||||
# TODO: move to tinygrad
|
||||
def nonzero(self): return aten.nonzero(self.cpu()).tiny()
|
||||
|
||||
@torch.library.impl("aten::_linalg_eigh", "privateuseone")
|
||||
# TODO: move to tinygrad
|
||||
@@ -478,7 +472,7 @@ for k,v in get_decompositions(decomps).items():
|
||||
# the goal is to make as much as we can this
|
||||
simple_tensor_methods = [
|
||||
# unary (ish)
|
||||
"log", "log2", "log10", "sqrt", "rsqrt", "sign", "silu", "hardsigmoid", "exp", "exp2", "neg", "reciprocal", "bitwise_not",
|
||||
"log", "log2", "sqrt", "rsqrt", "sign", "silu", "hardsigmoid", "exp", "exp2", "neg", "reciprocal", "bitwise_not",
|
||||
"sigmoid", "clamp", "mish", "erf", "leaky_relu",
|
||||
# trig
|
||||
"acos", "acosh", "cos", "cosh", "asin", "asinh", "sin", "sinh", "atan", "atanh", "tan", "tanh",
|
||||
@@ -522,6 +516,7 @@ tiny_backend_out = {**{f"aten.{x}.out":getattr(Tensor,x) for x in simple_tensor_
|
||||
"aten.bitwise_left_shift.Tensor_out": lambda x,y: x*(2**y),
|
||||
"aten.bitwise_right_shift.Tensor_out": lambda x,y: x//(2**y),
|
||||
# not in tinygrad. are there decomps for these?
|
||||
"aten.log10.out": lambda self: self.log2() * (math.log(2) / math.log(10)),
|
||||
"aten.log1p.out": lambda self: (self+1).log(),
|
||||
"aten.expm1.out": lambda self: self.exp() - 1,
|
||||
"aten.fmax.out": lambda input,other: Tensor.where(input.isnan() & ~other.isnan(), other, Tensor.where(~input.isnan() & other.isnan(), input, Tensor.maximum(input, other))),
|
||||
@@ -630,8 +625,7 @@ tiny_backend = {**{k:wrap_out(v) for k,v in tiny_backend_out.items()}, **{
|
||||
Tensor.linspace(start, stop, steps, **({"dtype": _from_torch_dtype(dtype)} if dtype is not None else {})),
|
||||
"aten.topk": Tensor.topk,
|
||||
"aten.constant_pad_nd": lambda self, padding, value=0.0: self.pad(padding, mode="constant", value=value).contiguous(),
|
||||
# TODO: input contiguous is needed to prevent CFGContext circular dependency assertion for shapes >512 (see test_cumsum_arange_large)
|
||||
"aten.cumsum": lambda self, dim: self.contiguous().cumsum(dim),
|
||||
"aten.cumsum": lambda self, dim: self.cumsum(dim).contiguous(), # TODO: fix test_simple_cumsum, fails without contiguous for shapes >512
|
||||
"aten.logsumexp": lambda self, axis, keepdim=False: self.logsumexp(axis[0], keepdim=keepdim),
|
||||
"aten.roll": Tensor.roll,
|
||||
"aten.logcumsumexp": Tensor.logcumsumexp,
|
||||
@@ -757,3 +751,16 @@ def _pad_circular(self, padding): return _PadCircular.apply(self, padding)
|
||||
|
||||
@torch.library.impl("aten::_pad_circular", "AutogradPrivateUse1")
|
||||
def _pad_circular_autograd(self, padding): return _PadCircular.apply(self, padding)
|
||||
|
||||
# only needed for test_diag_backward_gradient_values
|
||||
# was going through torch before, but now we are using tinygrad directly and tracking views
|
||||
# Tensor.diagonal does not support all cases tests in the tests
|
||||
@torch.library.impl("aten::diagonal", "privateuseone")
|
||||
@wrap_view_op
|
||||
def diagonal(self, offset=0, dim1=0, dim2=1):
|
||||
if offset != 0: raise NotImplementedError(f"diagonal with {offset=} not implemented")
|
||||
dim1, dim2 = dim1 % self.ndim, dim2 % self.ndim
|
||||
if dim1 != self.ndim - 2 or dim2 != self.ndim - 1: raise NotImplementedError(f"diagonal with {dim1=}, {dim2=} not implemented, only last two dims supported")
|
||||
batch_shape, m, n = self.shape[:-2], self.shape[-2], self.shape[-1]
|
||||
diag_len = min(m, n)
|
||||
return self.reshape(*batch_shape, m*n).pad(tuple((0,0) for _ in batch_shape) + ((0, diag_len),)).reshape(*batch_shape, diag_len, n+1)[..., :, 0]
|
||||
|
||||
@@ -191,20 +191,6 @@ class TestTorchBackend(unittest.TestCase):
|
||||
assert torch.equal(tensor_a, tensor_b)
|
||||
assert not torch.equal(tensor_a, tensor_c)
|
||||
|
||||
@unittest.skip("# TODO: this test is slow")
|
||||
def test_linalg_svd(self):
|
||||
A = torch.randn(5, 5, device=device)
|
||||
U, S, Vh = torch.linalg.svd(A)
|
||||
np.testing.assert_equal(U.shape, (5,5))
|
||||
np.testing.assert_equal(Vh.shape, (5,5))
|
||||
np.testing.assert_allclose(torch.dist(A, U @ torch.diag(S) @ Vh).cpu().numpy(), 0, atol=1e-5)
|
||||
|
||||
A = torch.randn(5, 3, device=device)
|
||||
U, S, Vh = torch.linalg.svd(A, full_matrices=False)
|
||||
np.testing.assert_equal(U.shape, (5,3))
|
||||
np.testing.assert_equal(Vh.shape, (3,3))
|
||||
np.testing.assert_allclose(torch.dist(A, U @ torch.diag(S) @ Vh).cpu().numpy(), 0, atol=1e-5)
|
||||
|
||||
def test_linalg_eigh(self):
|
||||
a = torch.tensor([[1, 2], [2, 1]], dtype=torch.float32, device=device)
|
||||
w, v = torch.linalg.eigh(a)
|
||||
@@ -700,15 +686,6 @@ class TestTorchBackend(unittest.TestCase):
|
||||
expected = np.array([4.0, 3.0, 2.0, 1.0])
|
||||
np.testing.assert_allclose(a.grad.cpu().numpy(), expected, rtol=1e-5)
|
||||
|
||||
def test_cumsum_arange_large(self):
|
||||
# Tests cumsum with an unrealized arange input with size > 512 (the split threshold)
|
||||
# This exercises the _split_cumalu path which uses a two-stage algorithm
|
||||
for size in [513, 1022]:
|
||||
a = torch.arange(size, dtype=torch.float32, device=device)
|
||||
result = torch.cumsum(a, dim=0)
|
||||
expected = torch.arange(size, dtype=torch.float32).cumsum(dim=0)
|
||||
np.testing.assert_allclose(result.cpu().numpy(), expected.numpy(), rtol=1e-5)
|
||||
|
||||
def test_diag_1d_to_2d(self):
|
||||
a = torch.tensor([1.0, 2.0, 3.0], dtype=torch.float32, device=device, requires_grad=True)
|
||||
b = torch.diag(a)
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
#include "TinyGPUDriver.h"
|
||||
#include "TinyGPUDriverUserClient.h"
|
||||
#include <AudioDriverKit/AudioDriverKit.h>
|
||||
#include <DriverKit/IOUserServer.h>
|
||||
#include <DriverKit/IOLib.h>
|
||||
#include <DriverKit/OSString.h>
|
||||
#include <DriverKit/IOMemoryMap.h>
|
||||
#include <DriverKit/IODMACommand.h>
|
||||
#include <DriverKit/IODispatchQueue.h>
|
||||
#include <PCIDriverKit/PCIDriverKit.h>
|
||||
#include <DriverKit/OSAction.h>
|
||||
|
||||
struct TinyGPUDriver_IVars
|
||||
{
|
||||
@@ -27,6 +34,9 @@ bool TinyGPUDriver::init()
|
||||
|
||||
void TinyGPUDriver::free()
|
||||
{
|
||||
if (ivars != nullptr) {
|
||||
|
||||
}
|
||||
IOSafeDeleteNULL(ivars, TinyGPUDriver_IVars, 1);
|
||||
super::free();
|
||||
}
|
||||
@@ -54,6 +64,32 @@ kern_return_t TinyGPUDriver::Start_Impl(IOService* in_provider)
|
||||
ivars->pci->ConfigurationRead16(kIOPCIConfigurationOffsetDeviceID, &dev);
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: opened device ven=0x%04x dev=0x%04x", ven, dev);
|
||||
|
||||
#if 0
|
||||
uint32_t off = 0x100;
|
||||
while (off) {
|
||||
uint32_t hdr = 0, next = 0, cap_id = 0;
|
||||
ivars->pci->ConfigurationRead32(off, &hdr);
|
||||
cap_id = hdr & 0xFFFFu;
|
||||
next = (hdr >> 20) & 0xFFCu;
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: cap: %u", cap_id);
|
||||
if (cap_id == 0x15) {
|
||||
uint32_t cap = 0, ctrl = 0;
|
||||
ivars->pci->ConfigurationRead32(off+0x4, &cap);
|
||||
ivars->pci->ConfigurationRead32(off+0x8, &ctrl);
|
||||
|
||||
uint32_t new_bar_size = 31 - __builtin_clz(cap >> 4);
|
||||
uint32_t new_ctrl = (ctrl & ~0x1f00) | (new_bar_size << 8);
|
||||
ivars->pci->ConfigurationWrite32(off+0x8, new_ctrl);
|
||||
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: rebar: cap=%u ctrl=%u new_bar_size=%u new_ctrl=%u", cap, ctrl, new_bar_size, new_ctrl);
|
||||
ivars->pci->Reset(0);
|
||||
break;
|
||||
}
|
||||
off = next;
|
||||
}
|
||||
ivars->pci->Reset(kIOPCIDeviceResetTypeHotReset);
|
||||
#endif
|
||||
|
||||
uint16_t commandRegister;
|
||||
ivars->pci->ConfigurationRead16(kIOPCIConfigurationOffsetCommand, &commandRegister);
|
||||
commandRegister |= (kIOPCICommandIOSpace | kIOPCICommandBusMaster | kIOPCICommandMemorySpace);
|
||||
@@ -94,66 +130,96 @@ error:
|
||||
|
||||
kern_return_t TinyGPUDriver::MapBar(uint32_t bar, IOMemoryDescriptor** memory)
|
||||
{
|
||||
kern_return_t err = 0;
|
||||
uint8_t barMemoryIndex, barMemoryType;
|
||||
uint64_t barMemorySize;
|
||||
kern_return_t err = ivars->pci->GetBARInfo(bar, &barMemoryIndex, &barMemorySize, &barMemoryType);
|
||||
err = ivars->pci->GetBARInfo(bar, &barMemoryIndex, &barMemorySize, &barMemoryType);
|
||||
if (err) return err;
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: bar mapping %d idx=%d", bar, barMemoryIndex);
|
||||
return ivars->pci->_CopyDeviceMemoryWithIndex(barMemoryIndex, memory, this);
|
||||
}
|
||||
|
||||
static kern_return_t WriteDMASegments(IOMemoryDescriptor* mem, IOAddressSegment* segments, uint32_t segCount,
|
||||
uint64_t mapOffset = 0, uint64_t mapSize = 0)
|
||||
{
|
||||
// write dma segments to mapped memory as [addr0, len0, addr1, len1, ..., 0, 0]
|
||||
|
||||
IOMemoryMap* map = nullptr;
|
||||
kern_return_t err = mem->CreateMapping(0, 0, 0, mapOffset, mapSize, &map);
|
||||
if (err || !map) return err ?: kIOReturnError;
|
||||
|
||||
uint64_t* out = (uint64_t*)map->GetAddress();
|
||||
for (uint32_t i = 0; i < segCount; i++) { out[i * 2] = segments[i].address; out[i * 2 + 1] = segments[i].length; }
|
||||
out[segCount * 2] = 0; out[segCount * 2 + 1] = 0;
|
||||
map->release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
kern_return_t TinyGPUDriver::SetupDMA(IOMemoryDescriptor* memory, uint64_t size, IODMACommand** outCmd,
|
||||
IOAddressSegment* segments, uint32_t* segCount)
|
||||
{
|
||||
IODMACommandSpecification dmaSpec = {.options = 0, .maxAddressBits = 40};
|
||||
IODMACommand* dmaCmd = nullptr;
|
||||
|
||||
kern_return_t err = IODMACommand::Create(ivars->pci, kIODMACommandCreateNoOptions, &dmaSpec, &dmaCmd);
|
||||
if (err) { os_log(OS_LOG_DEFAULT, "tinygpu: DMA create failed err=%d", err); return err; }
|
||||
|
||||
uint64_t flags = kIOMemoryDirectionInOut;
|
||||
err = dmaCmd->PrepareForDMA(kIODMACommandPrepareForDMANoOptions, memory, 0, size, &flags, segCount, segments);
|
||||
if (err) { os_log(OS_LOG_DEFAULT, "tinygpu: PrepareForDMA failed err=%d", err); dmaCmd->release(); return err; }
|
||||
|
||||
*outCmd = dmaCmd;
|
||||
return 0;
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: requested bar mapping %d, %d", bar, (uint32_t)barMemoryIndex);
|
||||
err = ivars->pci->_CopyDeviceMemoryWithIndex(barMemoryIndex, memory, this);
|
||||
return err;
|
||||
}
|
||||
|
||||
kern_return_t TinyGPUDriver::CreateDMA(size_t size, TinyGPUCreateDMAResp* dmaDesc)
|
||||
{
|
||||
kern_return_t err = 0;
|
||||
IOMemoryMap* memoryMap = nullptr;
|
||||
IOBufferMemoryDescriptor* sharedBuf = nullptr;
|
||||
kern_return_t err = IOBufferMemoryDescriptor::Create(kIOMemoryDirectionInOut, size, IOVMPageSize, &sharedBuf);
|
||||
if (err) { os_log(OS_LOG_DEFAULT, "tinygpu: alloc failed err=%d", err); return err; }
|
||||
|
||||
IODMACommand* dmaCmd = nullptr;
|
||||
IOAddressSegment segments[32];
|
||||
uint64_t flags = kIOMemoryDirectionInOut;
|
||||
uint32_t segCount = 32;
|
||||
err = SetupDMA(sharedBuf, size, &dmaCmd, segments, &segCount);
|
||||
if (err) { sharedBuf->release(); return err; }
|
||||
IOAddressSegment segments[32];
|
||||
IODMACommandSpecification dmaSpec = {
|
||||
.options = 0,
|
||||
.maxAddressBits = 40,
|
||||
};
|
||||
|
||||
err = WriteDMASegments(sharedBuf, segments, segCount, IOVMPageSize, IOVMPageSize);
|
||||
if (err) { dmaCmd->CompleteDMA(kIODMACommandCompleteDMANoOptions); dmaCmd->release(); sharedBuf->release(); return err; }
|
||||
err = IOBufferMemoryDescriptor::Create(kIOMemoryDirectionInOut, size, IOVMPageSize, &sharedBuf);
|
||||
if (err) {
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: failed to alloc user buffer, err=%d", err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = IODMACommand::Create(ivars->pci, kIODMACommandCreateNoOptions, &dmaSpec, &dmaCmd);
|
||||
if (err) {
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: failed to create dma command, err=%d", err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = dmaCmd->PrepareForDMA(kIODMACommandPrepareForDMANoOptions, sharedBuf, 0, size,
|
||||
&flags, &segCount, segments);
|
||||
if (err) {
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: failed to prepare for dma, err=%d", err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// pass addresses to userland
|
||||
{
|
||||
// debug
|
||||
for (int i = 0; i < segCount; i++) {
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: new dma mapping (sz=0x%zx) %d 0x%llx 0x%llx", size, i, segments[i].address, segments[i].length);
|
||||
}
|
||||
|
||||
err = sharedBuf->CreateMapping(0, 0, 0, IOVMPageSize, IOVMPageSize, &memoryMap); // one page should be fine
|
||||
if (err) {
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: failed to map memory, err=%d", err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Send back gpu addresses
|
||||
uint64_t* addr = (uint64_t*)memoryMap->GetAddress();
|
||||
for (int i = 0; i < segCount; i++) {
|
||||
addr[i * 2] = segments[i].address;
|
||||
addr[i * 2 + 1] = segments[i].length;
|
||||
}
|
||||
addr[segCount * 2] = 0;
|
||||
addr[segCount * 2 + 1] = 0;
|
||||
|
||||
// free memoryMap
|
||||
memoryMap->release();
|
||||
memoryMap = nullptr;
|
||||
}
|
||||
|
||||
dmaDesc->sharedBuf = sharedBuf;
|
||||
dmaDesc->dmaCmd = dmaCmd;
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: CreateDMA size=0x%zx segs=%u", size, segCount);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (memoryMap) {
|
||||
memoryMap->release();
|
||||
memoryMap = nullptr;
|
||||
}
|
||||
if (dmaCmd) {
|
||||
dmaCmd->CompleteDMA(kIODMACommandCompleteDMANoOptions);
|
||||
dmaCmd->release();
|
||||
dmaCmd = nullptr;
|
||||
}
|
||||
if (sharedBuf) {
|
||||
sharedBuf->release();
|
||||
sharedBuf = nullptr;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
kern_return_t TinyGPUDriver::CfgRead(uint32_t off, uint32_t size, uint32_t* outVal)
|
||||
@@ -191,8 +257,3 @@ kern_return_t TinyGPUDriver::ResetDevice()
|
||||
ivars->pci->Reset(kIOPCIDeviceResetTypeFunctionReset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
IOPCIDevice* TinyGPUDriver::GetPCI()
|
||||
{
|
||||
return ivars->pci;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,20 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.driverkit</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.driverkit.transport.pci</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IOPCIPrimaryMatch</key>
|
||||
<string>0xFFFFFFFF&0x00000000</string>
|
||||
<key>IOPCIMatch</key>
|
||||
<string>0x00001002&0x0000FFFF</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IOPCIMatch</key>
|
||||
<string>0x000010de&0x0000FFFF</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>com.apple.developer.driverkit.allow-any-userclient-access</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.driverkit</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -28,13 +28,11 @@ public:
|
||||
|
||||
kern_return_t MapBar(uint32_t bar, IOMemoryDescriptor** memory) LOCALONLY;
|
||||
kern_return_t CreateDMA(size_t size, TinyGPUCreateDMAResp* dmaDesc) LOCALONLY;
|
||||
kern_return_t SetupDMA(IOMemoryDescriptor* memory, uint64_t size, IODMACommand** outCmd,
|
||||
IOAddressSegment* segments, uint32_t* segCount) LOCALONLY;
|
||||
|
||||
kern_return_t CfgRead(uint32_t off, uint32_t size, uint32_t* val) LOCALONLY;
|
||||
kern_return_t CfgWrite(uint32_t off, uint32_t size, uint32_t val) LOCALONLY;
|
||||
kern_return_t ResetDevice() LOCALONLY;
|
||||
IOPCIDevice* GetPCI() LOCALONLY;
|
||||
kern_return_t BarInfo() LOCALONLY;
|
||||
};
|
||||
|
||||
#endif /* TinyGPUDriver_h */
|
||||
|
||||
@@ -129,36 +129,6 @@ kern_return_t TinyGPUDriverUserClient::ExternalMethod(uint64_t selector, IOUserC
|
||||
} else if (selector == TinyGPURPC::Reset) {
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: reset");
|
||||
return ivars->provider->ResetDevice();
|
||||
} else if (selector == TinyGPURPC::PrepareDMA) {
|
||||
// both input and output buffers must be >= 4097 bytes for IOMemoryDescriptor
|
||||
if (!args->structureInputDescriptor || !args->structureOutputDescriptor) {
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: PrepareDMA requires buffers >= 4097 bytes");
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
if (ivars->ensureDMACap(ivars->dmaCount + 1)) return kIOReturnNoMemory;
|
||||
|
||||
uint64_t size = 0;
|
||||
args->structureInputDescriptor->GetLength(&size);
|
||||
|
||||
IODMACommand* dmaCmd = nullptr;
|
||||
IOAddressSegment segments[32];
|
||||
uint32_t segCount = 32;
|
||||
err = ivars->provider->SetupDMA(args->structureInputDescriptor, size, &dmaCmd, segments, &segCount);
|
||||
if (err) return err;
|
||||
|
||||
// write physical addresses to output: [addr0, len0, addr1, len1, ..., 0, 0]
|
||||
IOMemoryMap* outMap = nullptr;
|
||||
err = args->structureOutputDescriptor->CreateMapping(0, 0, 0, 0, 0, &outMap);
|
||||
if (err || !outMap) { os_log(OS_LOG_DEFAULT, "tinygpu: output map failed err=%d", err); dmaCmd->release(); return err; }
|
||||
|
||||
uint64_t* out = (uint64_t*)outMap->GetAddress();
|
||||
for (uint32_t i = 0; i < segCount; i++) { out[i * 2] = segments[i].address; out[i * 2 + 1] = segments[i].length; }
|
||||
out[segCount * 2] = 0; out[segCount * 2 + 1] = 0;
|
||||
outMap->release();
|
||||
|
||||
os_log(OS_LOG_DEFAULT, "tinygpu: PrepareDMA size=%llu segs=%u", size, segCount);
|
||||
ivars->dmas[ivars->dmaCount++] = {nullptr, dmaCmd};
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
return kIOReturnUnsupported;
|
||||
|
||||
@@ -7,8 +7,7 @@ enum TinyGPURPC
|
||||
{
|
||||
ReadCfg,
|
||||
WriteCfg,
|
||||
Reset,
|
||||
PrepareDMA,
|
||||
Reset
|
||||
};
|
||||
|
||||
class TinyGPUDriverUserClient : public IOUserClient
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "tinygrad"
|
||||
version = "0.12.0"
|
||||
version = "0.11.0"
|
||||
description = "You like pytorch? You like micrograd? You love tinygrad! <3"
|
||||
authors = [{ name = "George Hotz" }]
|
||||
|
||||
@@ -244,7 +244,7 @@ select = [
|
||||
"F541",
|
||||
"F841",
|
||||
]
|
||||
"tinygrad/runtime/autogen/**/*.py" = ["E501", "F401", "E722", "E731", "F821", "A006", "A002", "F811"]
|
||||
"tinygrad/runtime/autogen/**/*.py" = ["E501", "F401", "E722", "E731", "F821", "A006"]
|
||||
|
||||
[tool.ruff.format]
|
||||
exclude = ["*"]
|
||||
|
||||
+8
@@ -140,9 +140,13 @@ backend_test.exclude('test_affine_grid_3d_expanded_cpu')
|
||||
backend_test.exclude('test_range_int32_type_negative_delta_expanded_cpu')
|
||||
|
||||
# unsupported (strange) ops
|
||||
backend_test.exclude('test_blackmanwindow_*')
|
||||
backend_test.exclude('test_bernoulli_*')
|
||||
backend_test.exclude('test_det_*')
|
||||
backend_test.exclude('test_col2im_*')
|
||||
backend_test.exclude('test_hammingwindow_*')
|
||||
backend_test.exclude('test_hannwindow_*')
|
||||
backend_test.exclude('test_hardmax_*')
|
||||
backend_test.exclude('test_gridsample_*')
|
||||
backend_test.exclude('test_dft_*')
|
||||
backend_test.exclude('test_unique_*')
|
||||
@@ -162,6 +166,10 @@ backend_test.exclude('test_scan_*')
|
||||
backend_test.exclude('test_split_to_sequence_*')
|
||||
backend_test.exclude('test_ai_onnx_ml_tree_ensemble_*') # https://github.com/onnx/onnx/blob/main/onnx/reference/ops/aionnxml/op_tree_ensemble.py#L121
|
||||
|
||||
# TODO: not yet implemented
|
||||
backend_test.exclude('test_l1normalization_*')
|
||||
backend_test.exclude('test_l2normalization_*')
|
||||
backend_test.exclude('test_lpnormalization_*')
|
||||
backend_test.exclude('test_attention_4d_diff_heads_mask4d_padded_kv_cpu') # needs nonpad_kv_seqlen handling
|
||||
backend_test.exclude('test_attention_4d_fp16_cpu') # fp16 numerical issues
|
||||
backend_test.exclude('test_attention_4d_fp16_expanded_cpu') # fp16 numerical issues
|
||||
|
||||
-19
@@ -75,25 +75,6 @@ class TestMainOnnxOps(TestOnnxOps):
|
||||
outputs = ["y"]
|
||||
self.helper_test_single_op("Gather", inputs, attributes, outputs)
|
||||
|
||||
def test_gather_jit_different_indices(self):
|
||||
# Gather should not assume indices is const when it can change at runtime
|
||||
from tinygrad import TinyJit
|
||||
from tinygrad.nn.onnx import onnx_ops, _cached_to_python_const
|
||||
_cached_to_python_const.cache_clear()
|
||||
Gather = onnx_ops["Gather"]
|
||||
|
||||
x = Tensor([10, 20, 30, 40, 50])
|
||||
indices_list = [[0, 1], [2, 3], [4, 0]]
|
||||
expected = [[10, 20], [30, 40], [50, 10]]
|
||||
|
||||
# without JIT: correct
|
||||
self.assertEqual([Gather(x, Tensor(idx)).tolist() for idx in indices_list], expected)
|
||||
|
||||
# TODO: Gather should not assume indices is const, result should be [[10, 20], [30, 40], [50, 10]]
|
||||
@TinyJit
|
||||
def gather_jit(x, indices): return Gather(x, indices)
|
||||
self.assertEqual([gather_jit(x, Tensor(idx)).tolist() for idx in indices_list], [[10, 20], [30, 40], [30, 40]])
|
||||
|
||||
# NOTE: resize OP is sensitive to numerical errors
|
||||
def _test_resize_scales(self, scale_values, **kwargs):
|
||||
for sc in scale_values:
|
||||
|
||||
Vendored
+2
-2
@@ -1,6 +1,6 @@
|
||||
import random
|
||||
import z3
|
||||
from tinygrad import dtypes, Device
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.uop.validate import uops_to_z3, z3_cdiv
|
||||
from tinygrad.uop.ops import UOp
|
||||
from tinygrad.uop.decompositions import fast_idiv
|
||||
@@ -15,7 +15,7 @@ if __name__ == "__main__":
|
||||
u = UOp.variable('x', random.randint(dt.min, 0), random.randint(1, dt.max), dtype=dt)
|
||||
d = random.randint(1, max(1, u.arg[2]))
|
||||
if d in powers_of_two: continue
|
||||
expr = fast_idiv(Device.DEFAULT, u, d)
|
||||
expr = fast_idiv(None, u, d)
|
||||
if expr is None: continue
|
||||
|
||||
solver = z3.Solver()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import ctypes, time
|
||||
from test.mockgpu.gpu import VirtGPU
|
||||
from test.mockgpu.helpers import _try_dlopen_remu
|
||||
from tinygrad.helpers import getbits, to_mv
|
||||
from tinygrad.runtime.support import c
|
||||
from tinygrad.helpers import getbits, to_mv, init_c_struct_t
|
||||
import tinygrad.runtime.autogen.amd_gpu as amd_gpu, tinygrad.runtime.autogen.am.pm4_nv as pm4
|
||||
|
||||
SDMA_MAX_COPY_SIZE = 0x400000
|
||||
@@ -38,18 +37,20 @@ def create_sdma_packets():
|
||||
for name,pkt in [(name,s) for name,s in amd_gpu.__dict__.items() if name.startswith("rocr_AMD_SDMA_PKT_") and name.endswith("_TAG")]:
|
||||
names = set()
|
||||
fields = []
|
||||
for pkt_fields in pkt._real_fields_:
|
||||
for pkt_fields in pkt._fields_:
|
||||
if not pkt_fields[0].endswith("_UNION"): fields.append(pkt_fields)
|
||||
else:
|
||||
for union_fields in pkt_fields[1]._real_fields_[:-1]:
|
||||
assert pkt_fields[1]._fields_[0][0] == '_0'
|
||||
for union_fields in pkt_fields[1]._fields_[0][1]._fields_:
|
||||
fname = union_fields[0]
|
||||
if fname in names: fname = pkt_fields[0]+fname
|
||||
names.add(fname)
|
||||
# merge together 64-bit fields, otherwise just append them
|
||||
if fname.endswith("_63_32") and fields[-1][0].endswith("_31_0"): fields[-1] = (fname[:-6], ctypes.c_ulong, fields[-1][2], 64, 0)
|
||||
else: fields.append((fname, union_fields[1], union_fields[2] + pkt_fields[2], *union_fields[3:]))
|
||||
if fname.endswith("_63_32") and fields[-1][0].endswith("_31_0"): fields[-1] = tuple([fname[:-6], ctypes.c_ulong, 64])
|
||||
else: fields.append(tuple([fname, *union_fields[1:]]))
|
||||
new_name = name[18:-4].lower()
|
||||
structs[new_name] = c.init_c_struct_t(ctypes.sizeof(pkt), tuple(fields))
|
||||
structs[new_name] = init_c_struct_t(tuple(fields))
|
||||
assert ctypes.sizeof(structs[new_name]) == ctypes.sizeof(pkt), f"{ctypes.sizeof(structs[new_name])} != {ctypes.sizeof(pkt)}"
|
||||
return type("SDMA_PKTS", (object, ), structs)
|
||||
sdma_pkts = create_sdma_packets()
|
||||
|
||||
|
||||
@@ -70,11 +70,11 @@ def cuMemFree_v2(dptr) -> int:
|
||||
return orig_cuda.CUDA_ERROR_INVALID_VALUE
|
||||
|
||||
def cuMemcpyHtoDAsync_v2(dst, src: ctypes.c_void_p, bytesize: int, stream: Any) -> int:
|
||||
ctypes.memmove(dst if isinstance(dst, int) else dst.value, src, bytesize)
|
||||
ctypes.memmove(dst.value, src, bytesize)
|
||||
return orig_cuda.CUDA_SUCCESS
|
||||
|
||||
def cuMemcpyDtoH_v2(dst: ctypes.c_void_p, src, bytesize: int) -> int:
|
||||
ctypes.memmove(dst, src if isinstance(src, int) else src.value, bytesize)
|
||||
ctypes.memmove(dst, src.value, bytesize)
|
||||
return orig_cuda.CUDA_SUCCESS
|
||||
|
||||
def cuEventCreate(phEvent, flags: int) -> int:
|
||||
@@ -127,7 +127,7 @@ def cuModuleUnload(hmod) -> int:
|
||||
|
||||
def cuLaunchKernel(f, gx: int, gy: int, gz: int, lx: int, ly: int, lz: int, sharedMemBytes: int,
|
||||
hStream: Any, kernelParams: Any, extra: Any) -> int:
|
||||
cargs = [ctypes.cast(getattr(extra, field[0]), ctypes.c_void_p) for field in extra._real_fields_]
|
||||
cargs = [ctypes.cast(getattr(extra, field[0]), ctypes.c_void_p) for field in extra._fields_]
|
||||
try: gpuocelot_lib.ptx_run(ctypes.cast(f.value, ctypes.c_char_p), len(cargs), (ctypes.c_void_p*len(cargs))(*cargs), lx, ly, lz, gx, gy, gz, 0)
|
||||
except Exception as e:
|
||||
print("Error in cuLaunchKernel:", e)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ctypes, mmap, collections, functools, os
|
||||
from tinygrad.runtime.autogen import nv_570 as nv_gpu
|
||||
from typing import cast, Any
|
||||
from typing import Any
|
||||
from tinygrad.helpers import to_mv
|
||||
from test.mockgpu.driver import VirtDriver, VirtFileDesc, VirtFile
|
||||
from test.mockgpu.nv.nvgpu import NVGPU
|
||||
@@ -92,7 +92,7 @@ class NVDriver(VirtDriver):
|
||||
|
||||
def rm_alloc(self, argp):
|
||||
struct = nv_gpu.NVOS21_PARAMETERS.from_address(argp)
|
||||
params_ptr = cast(int, struct.pAllocParms)
|
||||
params_ptr = struct.pAllocParms
|
||||
if struct.hClass == nv_gpu.NV01_ROOT_CLIENT: self.root_handle = struct.hObjectNew = self._alloc_handle()
|
||||
elif struct.hClass == nv_gpu.NV01_DEVICE_0:
|
||||
params:Any = nv_gpu.NV0080_ALLOC_PARAMETERS.from_address(params_ptr)
|
||||
@@ -145,7 +145,7 @@ class NVDriver(VirtDriver):
|
||||
|
||||
def rm_control(self, argp):
|
||||
struct = nv_gpu.NVOS54_PARAMETERS.from_address(argp)
|
||||
params_ptr = cast(int, struct.params)
|
||||
params_ptr = struct.params
|
||||
if struct.cmd == nv_gpu.NV0000_CTRL_CMD_GPU_GET_ID_INFO_V2:
|
||||
params:Any = nv_gpu.NV0000_CTRL_GPU_GET_ID_INFO_V2_PARAMS.from_address(params_ptr)
|
||||
params.deviceInstance = params.gpuId # emulate them to be the same
|
||||
|
||||
@@ -3,8 +3,7 @@ from tinygrad.runtime.autogen import nv_570 as nv_gpu
|
||||
from enum import Enum, auto
|
||||
from test.mockgpu.gpu import VirtGPU
|
||||
from test.mockgpu.helpers import _try_dlopen_gpuocelot
|
||||
from tinygrad.helpers import to_mv
|
||||
from tinygrad.runtime.support.c import init_c_struct_t
|
||||
from tinygrad.helpers import to_mv, init_c_struct_t
|
||||
|
||||
def make_qmd_struct_type():
|
||||
fields = []
|
||||
@@ -12,9 +11,11 @@ def make_qmd_struct_type():
|
||||
bits += [(name+f"_{i}",dt(i)) for name,dt in nv_gpu.__dict__.items() for i in range(8) if name.startswith("NVC6C0_QMDV03_00") and callable(dt)]
|
||||
bits = sorted(bits, key=lambda x: x[1][1])
|
||||
for i,(name, data) in enumerate(bits):
|
||||
fields.append((name.replace("NVC6C0_QMDV03_00_", "").lower(), ctypes.c_uint32, data[1]//8, data[0]-data[1]+1, data[1]%8))
|
||||
return init_c_struct_t(0x40 * 4, tuple(fields))
|
||||
if i > 0 and (gap:=(data[1] - bits[i-1][1][0] - 1)) != 0: fields.append((f"_reserved{i}", ctypes.c_uint32, gap))
|
||||
fields.append((name.replace("NVC6C0_QMDV03_00_", "").lower(), ctypes.c_uint32, data[0]-data[1]+1))
|
||||
return init_c_struct_t(tuple(fields))
|
||||
qmd_struct_t = make_qmd_struct_type()
|
||||
assert ctypes.sizeof(qmd_struct_t) == 0x40 * 4
|
||||
|
||||
gpuocelot_lib = _try_dlopen_gpuocelot()
|
||||
|
||||
|
||||
@@ -424,239 +424,5 @@ class TestAssign(unittest.TestCase):
|
||||
a = Tensor.empty(5, device=f"disk:{temp('disk_assignment')}").assign(Tensor.ones(5)).numpy()
|
||||
np.testing.assert_equal(a, np.ones(5))
|
||||
|
||||
def test_assign_slice_then_read(self):
|
||||
"""Assign to slice then read from buffer - read should see the assigned values.
|
||||
This is the KV cache pattern from llm.py.
|
||||
"""
|
||||
v_pos = Variable("pos", 0, 3).bind(0)
|
||||
|
||||
# without .realize() after assign, the read doesn't see the assigned values
|
||||
cache = Tensor.zeros(4, 4).contiguous().realize()
|
||||
cache[v_pos:v_pos+1, :].assign(Tensor.ones(1, 4))
|
||||
self.assertEqual(cache.sum().item(), 0.0) # should be 4.0!
|
||||
|
||||
# TODO: remove .realize() workaround once assign-read dependency is fixed
|
||||
cache2 = Tensor.zeros(4, 4).contiguous().realize()
|
||||
cache2[v_pos:v_pos+1, :].assign(Tensor.ones(1, 4)).realize()
|
||||
self.assertEqual(cache2.sum().item(), 4.0)
|
||||
|
||||
class TestAssignOrdering(unittest.TestCase):
|
||||
"""Tests for complex assign orderings that could differ between lazy and eager execution.
|
||||
|
||||
The key principle: tinygrad's lazy execution with RAW/WAR dependency tracking should
|
||||
produce the same results as eager (immediate) execution for valid programs.
|
||||
|
||||
These tests exercise edge cases where incorrect dependency tracking could cause:
|
||||
- Stale reads (reading before write completes)
|
||||
- Lost writes (write ordering reversed)
|
||||
- Race conditions (concurrent access to same buffer)
|
||||
"""
|
||||
|
||||
def test_overlapping_slice_assigns(self):
|
||||
"""Overlapping slice assigns - later write should win for overlapping elements."""
|
||||
# without .realize(): assigns not executed, buffer stays zeros
|
||||
buf = Tensor.zeros(8).contiguous().realize()
|
||||
buf[0:4].assign(Tensor.ones(4))
|
||||
buf[2:6].assign(Tensor.ones(4) * 2)
|
||||
np.testing.assert_equal(buf.numpy(), [0,0,0,0,0,0,0,0]) # TODO: wrong! should be [1,1,2,2,2,2,0,0]
|
||||
|
||||
# with .realize(): assigns execute in order
|
||||
buf = Tensor.zeros(8).contiguous().realize()
|
||||
buf[0:4].assign(Tensor.ones(4)).realize()
|
||||
buf[2:6].assign(Tensor.ones(4) * 2).realize()
|
||||
np.testing.assert_equal(buf.numpy(), [1,1,2,2,2,2,0,0])
|
||||
|
||||
def test_overlapping_slice_assigns_reverse(self):
|
||||
"""Overlapping slice assigns in reverse order."""
|
||||
# without .realize(): assigns not executed
|
||||
buf = Tensor.zeros(8).contiguous().realize()
|
||||
buf[2:6].assign(Tensor.ones(4) * 2)
|
||||
buf[0:4].assign(Tensor.ones(4))
|
||||
np.testing.assert_equal(buf.numpy(), [0,0,0,0,0,0,0,0]) # TODO: wrong! should be [1,1,1,1,2,2,0,0]
|
||||
|
||||
# with .realize(): assigns execute in order
|
||||
buf = Tensor.zeros(8).contiguous().realize()
|
||||
buf[2:6].assign(Tensor.ones(4) * 2).realize()
|
||||
buf[0:4].assign(Tensor.ones(4)).realize()
|
||||
np.testing.assert_equal(buf.numpy(), [1,1,1,1,2,2,0,0])
|
||||
|
||||
def test_read_between_writes(self):
|
||||
"""Read should see first write before second write happens."""
|
||||
buf = Tensor.zeros(4).contiguous().realize()
|
||||
buf.assign(Tensor.ones(4))
|
||||
r1 = buf.sum().realize() # should see ones = 4
|
||||
buf.assign(Tensor.ones(4) * 2)
|
||||
r2 = buf.sum().realize() # should see twos = 8
|
||||
self.assertEqual(r1.item(), 4)
|
||||
self.assertEqual(r2.item(), 8)
|
||||
|
||||
def test_write_read_write_chain(self):
|
||||
"""Write, read, write chain - middle read must complete before second write."""
|
||||
buf = Tensor.zeros(4).contiguous().realize()
|
||||
buf.assign(Tensor.ones(4) * 3)
|
||||
mid_sum = buf.sum() # lazy read, should be 12
|
||||
buf.assign(Tensor.ones(4) * 5)
|
||||
final_sum = buf.sum() # lazy read, should be 20
|
||||
# Realize in "wrong" order - final first
|
||||
self.assertEqual(final_sum.realize().item(), 20)
|
||||
self.assertEqual(mid_sum.realize().item(), 12)
|
||||
|
||||
def test_slice_read_then_full_write(self):
|
||||
"""Read from slice, then overwrite full buffer - WAR dependency works for full buffer assigns."""
|
||||
buf = Tensor([1.,2.,3.,4.]).contiguous().realize()
|
||||
partial = buf[0:2].sum() # lazy read
|
||||
buf.assign(Tensor.ones(4) * 10) # overwrite everything
|
||||
full = buf.sum()
|
||||
# WAR dependency correctly tracked - partial sees original data
|
||||
self.assertEqual(partial.realize().item(), 3) # 1+2
|
||||
self.assertEqual(full.realize().item(), 40)
|
||||
|
||||
def test_slice_write_then_full_read(self):
|
||||
"""Write to slice, then read full buffer."""
|
||||
# without .realize(): orphan slice assign not triggered by .numpy()
|
||||
buf = Tensor.zeros(4).contiguous().realize()
|
||||
buf[1:3].assign(Tensor([5, 6]))
|
||||
np.testing.assert_equal(buf.numpy(), [0, 0, 0, 0]) # TODO: wrong! should be [0, 5, 6, 0]
|
||||
|
||||
# with .realize(): assign executes
|
||||
buf = Tensor.zeros(4).contiguous().realize()
|
||||
buf[1:3].assign(Tensor([5, 6])).realize()
|
||||
np.testing.assert_equal(buf.numpy(), [0, 5, 6, 0])
|
||||
|
||||
def test_chained_slice_copies(self):
|
||||
"""Copy from one slice to another within same buffer."""
|
||||
# without .realize(): orphan slice assign not triggered
|
||||
buf = Tensor([1, 2, 3, 4, 5, 6, 7, 8]).contiguous().realize()
|
||||
buf[4:8].assign(buf[0:4].contiguous())
|
||||
np.testing.assert_equal(buf.numpy(), [1, 2, 3, 4, 5, 6, 7, 8]) # TODO: wrong! should be [1,2,3,4,1,2,3,4]
|
||||
|
||||
# with .realize(): assign executes
|
||||
buf = Tensor([1, 2, 3, 4, 5, 6, 7, 8]).contiguous().realize()
|
||||
buf[4:8].assign(buf[0:4].contiguous()).realize()
|
||||
np.testing.assert_equal(buf.numpy(), [1, 2, 3, 4, 1, 2, 3, 4])
|
||||
|
||||
def test_swap_slices(self):
|
||||
"""Swap two non-overlapping slices - requires reading both before writing."""
|
||||
# without .realize() on temps: values not captured before overwriting
|
||||
buf = Tensor([1, 2, 3, 4, 5, 6, 7, 8]).contiguous().realize()
|
||||
left = buf[0:4].contiguous() # lazy - not captured yet
|
||||
right = buf[4:8].contiguous() # lazy - not captured yet
|
||||
buf[0:4].assign(right).realize() # this works
|
||||
buf[4:8].assign(left).realize() # left now reads from modified buf!
|
||||
np.testing.assert_equal(buf.numpy(), [5, 6, 7, 8, 5, 6, 7, 8]) # TODO: wrong! should be [5,6,7,8,1,2,3,4]
|
||||
|
||||
# with .realize() on temps: values captured before writes
|
||||
buf = Tensor([1, 2, 3, 4, 5, 6, 7, 8]).contiguous().realize()
|
||||
left = buf[0:4].contiguous().realize()
|
||||
right = buf[4:8].contiguous().realize()
|
||||
buf[0:4].assign(right).realize()
|
||||
buf[4:8].assign(left).realize()
|
||||
np.testing.assert_equal(buf.numpy(), [5, 6, 7, 8, 1, 2, 3, 4])
|
||||
|
||||
def test_reduction_after_partial_assign(self):
|
||||
"""Reduction over buffer after partial assign - must see the assigned values."""
|
||||
# without .realize(): orphan slice assign not triggered by reduction
|
||||
buf = Tensor.zeros(4, 4).contiguous().realize()
|
||||
buf[0:2, :].assign(Tensor.ones(2, 4)) # top half = 1
|
||||
total = buf.sum()
|
||||
self.assertEqual(total.item(), 0) # TODO: wrong! should be 8 (2*4 ones)
|
||||
|
||||
# with .realize(): assign executes before reduction
|
||||
buf = Tensor.zeros(4, 4).contiguous().realize()
|
||||
buf[0:2, :].assign(Tensor.ones(2, 4)).realize()
|
||||
total = buf.sum()
|
||||
self.assertEqual(total.item(), 8)
|
||||
|
||||
def test_multiple_reductions_different_views(self):
|
||||
"""Multiple reductions over different views of same buffer after assign."""
|
||||
buf = Tensor.zeros(4, 4).contiguous().realize()
|
||||
buf.assign(Tensor.arange(16).reshape(4, 4).float())
|
||||
row_sums = buf.sum(axis=1) # [6, 22, 38, 54]
|
||||
col_sums = buf.sum(axis=0) # [24, 28, 32, 36]
|
||||
total = buf.sum() # 120
|
||||
# All should see the assigned values
|
||||
np.testing.assert_equal(row_sums.numpy(), [6, 22, 38, 54])
|
||||
np.testing.assert_equal(col_sums.numpy(), [24, 28, 32, 36])
|
||||
self.assertEqual(total.item(), 120)
|
||||
|
||||
def test_assign_from_self_transformed(self):
|
||||
"""Assign to buffer from transformed view of itself."""
|
||||
buf = Tensor([1, 2, 3, 4]).contiguous().realize()
|
||||
# Read and transform, then write back (requires reading before writing)
|
||||
buf.assign((buf * 2).contiguous())
|
||||
np.testing.assert_equal(buf.numpy(), [2, 4, 6, 8])
|
||||
|
||||
def test_two_buffers_cross_assign(self):
|
||||
"""Two buffers each reading from the other before writing."""
|
||||
a = Tensor([1, 2, 3, 4]).contiguous().realize()
|
||||
b = Tensor([10, 20, 30, 40]).contiguous().realize()
|
||||
# Both read from each other's original values
|
||||
a_new = (a + b).contiguous()
|
||||
b_new = (a * b).contiguous()
|
||||
a.assign(a_new)
|
||||
b.assign(b_new)
|
||||
Tensor.realize(a, b)
|
||||
np.testing.assert_equal(a.numpy(), [11, 22, 33, 44])
|
||||
np.testing.assert_equal(b.numpy(), [10, 40, 90, 160])
|
||||
|
||||
def test_three_buffer_chain(self):
|
||||
"""Chain: A depends on B, B depends on C - ordering matters."""
|
||||
a = Tensor.zeros(4).contiguous().realize()
|
||||
b = Tensor([1, 2, 3, 4]).contiguous().realize()
|
||||
c = Tensor([10, 10, 10, 10]).contiguous().realize()
|
||||
# b reads from c, a reads from b
|
||||
b.assign((b + c).contiguous()) # b = [11, 12, 13, 14]
|
||||
a.assign((a + b).contiguous()) # a should see new b = [11, 12, 13, 14]
|
||||
Tensor.realize(a, b)
|
||||
np.testing.assert_equal(b.numpy(), [11, 12, 13, 14])
|
||||
np.testing.assert_equal(a.numpy(), [11, 12, 13, 14])
|
||||
|
||||
def test_interleaved_assign_read_patterns(self):
|
||||
"""Complex interleaved pattern: write A, read A into B, write B, read B."""
|
||||
a = Tensor.zeros(4).contiguous().realize()
|
||||
b = Tensor.zeros(4).contiguous().realize()
|
||||
|
||||
a.assign(Tensor([1, 2, 3, 4]))
|
||||
b.assign(a.contiguous()) # b should get [1,2,3,4]
|
||||
a.assign(Tensor([5, 6, 7, 8]))
|
||||
result = b.sum() # should be 10, not 26
|
||||
|
||||
self.assertEqual(result.item(), 10)
|
||||
np.testing.assert_equal(a.numpy(), [5, 6, 7, 8])
|
||||
np.testing.assert_equal(b.numpy(), [1, 2, 3, 4])
|
||||
|
||||
def test_variable_slice_ordering(self):
|
||||
"""Variable-indexed slices - tests symbolic dependency tracking."""
|
||||
v_i = Variable("i", 0, 3)
|
||||
|
||||
# without .realize(): orphan slice assigns not triggered
|
||||
buf = Tensor.zeros(4, 4).contiguous().realize()
|
||||
buf[v_i.bind(0):v_i.bind(0)+1, :].assign(Tensor.ones(1, 4))
|
||||
row0_sum = buf[0:1, :].sum()
|
||||
self.assertEqual(row0_sum.item(), 0) # TODO: wrong! should be 4
|
||||
|
||||
# with .realize(): assigns execute
|
||||
buf = Tensor.zeros(4, 4).contiguous().realize()
|
||||
buf[v_i.bind(0):v_i.bind(0)+1, :].assign(Tensor.ones(1, 4)).realize()
|
||||
row0_sum = buf[0:1, :].sum()
|
||||
buf[v_i.bind(1):v_i.bind(1)+1, :].assign(Tensor.ones(1, 4) * 2).realize()
|
||||
row1_sum = buf[1:2, :].sum()
|
||||
self.assertEqual(row0_sum.item(), 4)
|
||||
self.assertEqual(row1_sum.item(), 8)
|
||||
|
||||
def test_multiple_slice_assigns_then_read(self):
|
||||
"""Multiple non-overlapping slice assigns then read - RAW dependencies must ensure all writes complete before read."""
|
||||
buf = Tensor.zeros(4).contiguous().realize()
|
||||
buf[0:1].assign(Tensor.ones(1))
|
||||
buf[1:2].assign(Tensor.full((1,), 2.0))
|
||||
buf[2:3].assign(Tensor.full((1,), 3.0))
|
||||
self.assertEqual(buf.sum().realize().item(), 0.0) # TODO: wrong! should be 1 + 2 + 3 + 0 = 6
|
||||
|
||||
buf = Tensor.zeros(4).contiguous().realize()
|
||||
buf[0:1].assign(Tensor.ones(1)).realize()
|
||||
buf[1:2].assign(Tensor.full((1,), 2.0)).realize()
|
||||
buf[2:3].assign(Tensor.full((1,), 3.0)).realize()
|
||||
self.assertEqual(buf.sum().realize().item(), 6.0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -7,7 +7,6 @@ Comments marked "should be X!" indicate the intuitively expected value.
|
||||
|
||||
SILENT MISMATCHES (highest priority - wrong results, no error):
|
||||
class_method_shared_across_instances EASY could check if first arg is self and warn
|
||||
slice_assign_requires_realize MED assign graph not connected to read during JIT replay
|
||||
output_buffer_reuse MED performance tradeoff, could add option or better docs
|
||||
python_constants_frozen HARD inherent to tracing JITs
|
||||
conditional_branches_frozen HARD inherent to tracing JITs
|
||||
@@ -50,31 +49,6 @@ class TestJitFootguns(unittest.TestCase):
|
||||
|
||||
self.assertEqual([r1.item(), r2.item(), r3.item()], [2, 4, 6])
|
||||
|
||||
def test_slice_assign_requires_realize(self):
|
||||
"""Slice assign then read from same buffer - assign isn't connected to read without explicit realize()."""
|
||||
from tinygrad import Variable
|
||||
v_pos = Variable("pos", 0, 3)
|
||||
|
||||
# without .realize() after assign, the read doesn't see the assigned values
|
||||
cache = Tensor.zeros(4, 4).contiguous().realize()
|
||||
@TinyJit
|
||||
def f_broken(pos):
|
||||
cache[pos:pos+1, :].assign(Tensor.ones(1, 4))
|
||||
return cache.sum().realize()
|
||||
for i in range(4):
|
||||
cache.assign(Tensor.zeros(4, 4)).realize()
|
||||
self.assertEqual(f_broken(v_pos.bind(i)).item(), 0.0) # should be 4.0!
|
||||
|
||||
# workaround: add .realize() after assign
|
||||
cache2 = Tensor.zeros(4, 4).contiguous().realize()
|
||||
@TinyJit
|
||||
def f_fixed(pos):
|
||||
cache2[pos:pos+1, :].assign(Tensor.ones(1, 4)).realize()
|
||||
return cache2.sum().realize()
|
||||
for i in range(4):
|
||||
cache2.assign(Tensor.zeros(4, 4)).realize()
|
||||
self.assertEqual(f_fixed(v_pos.bind(i)).item(), 4.0)
|
||||
|
||||
def test_non_tensor_outputs_error(self):
|
||||
@TinyJit
|
||||
def f(x, mult): return (x * 2).realize(), mult * 10
|
||||
|
||||
+30
-79
@@ -916,10 +916,6 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op([(45,65)], torch.log, Tensor.log)
|
||||
helper_test_op(None, torch.log, Tensor.log, vals=[[math.inf, -math.inf, math.nan]])
|
||||
helper_test_op([()], torch.log, Tensor.log)
|
||||
def test_log10(self):
|
||||
helper_test_op([(45,65)], torch.log10, Tensor.log10)
|
||||
helper_test_op(None, torch.log10, Tensor.log10, vals=[[math.inf, -math.inf, math.nan]])
|
||||
helper_test_op([()], torch.log10, Tensor.log10)
|
||||
def test_log2(self):
|
||||
helper_test_op([(45,65)], torch.log2, Tensor.log2)
|
||||
helper_test_op(None, torch.log2, Tensor.log2, vals=[[math.inf, -math.inf, math.nan]])
|
||||
@@ -1074,72 +1070,26 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op([(2, 3, 0)],lambda x: torch.cumprod(x, dim=2),lambda x: Tensor.cumprod(x, axis=2))
|
||||
|
||||
def test_small_cummax(self):
|
||||
helper_test_op([(10)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0)[0])
|
||||
helper_test_op([(10)], lambda x: torch.cummax(x, dim=0).indices.int(), lambda x: Tensor.cummax(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(10)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0))
|
||||
@slow_test
|
||||
def test_simple_cummax(self):
|
||||
helper_test_op([(512)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0)[0])
|
||||
helper_test_op([(512)], lambda x: torch.cummax(x, dim=0).indices.int(), lambda x: Tensor.cummax(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(1022)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0)[0])
|
||||
helper_test_op([(1022)], lambda x: torch.cummax(x, dim=0).indices.int(), lambda x: Tensor.cummax(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(512)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0))
|
||||
helper_test_op([(1022)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0))
|
||||
@slow_test
|
||||
def test_cummax(self):
|
||||
helper_test_op([()], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0)[0])
|
||||
helper_test_op([()], lambda x: torch.cummax(x, dim=0).indices.int(), lambda x: Tensor.cummax(x, axis=0)[1], forward_only=True)
|
||||
self.helper_test_exception([()], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1)[0], expected=IndexError)
|
||||
helper_test_op([(5,)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0)[0])
|
||||
helper_test_op([(5,)], lambda x: torch.cummax(x, dim=0).indices.int(), lambda x: Tensor.cummax(x, axis=0)[1], forward_only=True)
|
||||
self.helper_test_exception([(5,)], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1)[0], expected=IndexError)
|
||||
self.helper_test_exception([(5,)], lambda x: torch.cummax(x, dim=-2).values, lambda x: Tensor.cummax(x, axis=-2)[0], expected=IndexError)
|
||||
helper_test_op([(5,6)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0)[0])
|
||||
helper_test_op([(5,6)], lambda x: torch.cummax(x, dim=0).indices.int(), lambda x: Tensor.cummax(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(5,6)], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1)[0])
|
||||
helper_test_op([(5,6)], lambda x: torch.cummax(x, dim=1).indices.int(), lambda x: Tensor.cummax(x, axis=1)[1], forward_only=True)
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummax(x, dim=2).values, lambda x: Tensor.cummax(x, axis=2)[0])
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummax(x, dim=2).indices.int(), lambda x: Tensor.cummax(x, axis=2)[1], forward_only=True)
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummax(x, dim=-1).values, lambda x: Tensor.cummax(x, axis=-1)[0])
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummax(x, dim=-1).indices.int(), lambda x: Tensor.cummax(x, axis=-1)[1], forward_only=True)
|
||||
helper_test_op([()], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0))
|
||||
self.helper_test_exception([()], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1), expected=IndexError)
|
||||
helper_test_op([(20,)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0))
|
||||
self.helper_test_exception([(20,)], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1), expected=IndexError)
|
||||
self.helper_test_exception([(20,)], lambda x: torch.cummax(x, dim=-2).values, lambda x: Tensor.cummax(x, axis=-2), expected=IndexError)
|
||||
helper_test_op([(20,30)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0))
|
||||
helper_test_op([(20,30)], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1))
|
||||
helper_test_op([(20,30,40)], lambda x: torch.cummax(x, dim=2).values, lambda x: Tensor.cummax(x, axis=2))
|
||||
helper_test_op([(20,30,40)], lambda x: torch.cummax(x, dim=-1).values, lambda x: Tensor.cummax(x, axis=-1))
|
||||
def test_cummax_zero_axis(self):
|
||||
helper_test_op([(2,0,4)], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1)[0])
|
||||
helper_test_op([(2,0,4)], lambda x: torch.cummax(x, dim=1).indices.int(), lambda x: Tensor.cummax(x, axis=1)[1], forward_only=True)
|
||||
helper_test_op([(0,3)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0)[0])
|
||||
helper_test_op([(0,3)], lambda x: torch.cummax(x, dim=0).indices.int(), lambda x: Tensor.cummax(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(2,3,0)], lambda x: torch.cummax(x, dim=2).values, lambda x: Tensor.cummax(x, axis=2)[0])
|
||||
helper_test_op([(2,3,0)], lambda x: torch.cummax(x, dim=2).indices.int(), lambda x: Tensor.cummax(x, axis=2)[1], forward_only=True)
|
||||
|
||||
def test_small_cummin(self):
|
||||
helper_test_op([(10)], lambda x: torch.cummin(x, dim=0).values, lambda x: Tensor.cummin(x, axis=0)[0])
|
||||
helper_test_op([(10)], lambda x: torch.cummin(x, dim=0).indices.int(), lambda x: Tensor.cummin(x, axis=0)[1], forward_only=True)
|
||||
@slow_test
|
||||
def test_simple_cummin(self):
|
||||
helper_test_op([(512)], lambda x: torch.cummin(x, dim=0).values, lambda x: Tensor.cummin(x, axis=0)[0])
|
||||
helper_test_op([(512)], lambda x: torch.cummin(x, dim=0).indices.int(), lambda x: Tensor.cummin(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(1022)], lambda x: torch.cummin(x, dim=0).values, lambda x: Tensor.cummin(x, axis=0)[0])
|
||||
helper_test_op([(1022)], lambda x: torch.cummin(x, dim=0).indices.int(), lambda x: Tensor.cummin(x, axis=0)[1], forward_only=True)
|
||||
@slow_test
|
||||
def test_cummin(self):
|
||||
helper_test_op([()], lambda x: torch.cummin(x, dim=0).values, lambda x: Tensor.cummin(x, axis=0)[0])
|
||||
helper_test_op([()], lambda x: torch.cummin(x, dim=0).indices.int(), lambda x: Tensor.cummin(x, axis=0)[1], forward_only=True)
|
||||
self.helper_test_exception([()], lambda x: torch.cummin(x, dim=1).values, lambda x: Tensor.cummin(x, axis=1)[0], expected=IndexError)
|
||||
helper_test_op([(5,)], lambda x: torch.cummin(x, dim=0).values, lambda x: Tensor.cummin(x, axis=0)[0])
|
||||
helper_test_op([(5,)], lambda x: torch.cummin(x, dim=0).indices.int(), lambda x: Tensor.cummin(x, axis=0)[1], forward_only=True)
|
||||
self.helper_test_exception([(5,)], lambda x: torch.cummin(x, dim=1).values, lambda x: Tensor.cummin(x, axis=1)[0], expected=IndexError)
|
||||
self.helper_test_exception([(5,)], lambda x: torch.cummin(x, dim=-2).values, lambda x: Tensor.cummin(x, axis=-2)[0], expected=IndexError)
|
||||
helper_test_op([(5,6)], lambda x: torch.cummin(x, dim=0).values, lambda x: Tensor.cummin(x, axis=0)[0])
|
||||
helper_test_op([(5,6)], lambda x: torch.cummin(x, dim=0).indices.int(), lambda x: Tensor.cummin(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(5,6)], lambda x: torch.cummin(x, dim=1).values, lambda x: Tensor.cummin(x, axis=1)[0])
|
||||
helper_test_op([(5,6)], lambda x: torch.cummin(x, dim=1).indices.int(), lambda x: Tensor.cummin(x, axis=1)[1], forward_only=True)
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummin(x, dim=2).values, lambda x: Tensor.cummin(x, axis=2)[0])
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummin(x, dim=2).indices.int(), lambda x: Tensor.cummin(x, axis=2)[1], forward_only=True)
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummin(x, dim=-1).values, lambda x: Tensor.cummin(x, axis=-1)[0])
|
||||
helper_test_op([(5,6,7)], lambda x: torch.cummin(x, dim=-1).indices.int(), lambda x: Tensor.cummin(x, axis=-1)[1], forward_only=True)
|
||||
def test_cummin_zero_axis(self):
|
||||
helper_test_op([(2,0,4)], lambda x: torch.cummin(x, dim=1).values, lambda x: Tensor.cummin(x, axis=1)[0])
|
||||
helper_test_op([(2,0,4)], lambda x: torch.cummin(x, dim=1).indices.int(), lambda x: Tensor.cummin(x, axis=1)[1], forward_only=True)
|
||||
helper_test_op([(0,3)], lambda x: torch.cummin(x, dim=0).values, lambda x: Tensor.cummin(x, axis=0)[0])
|
||||
helper_test_op([(0,3)], lambda x: torch.cummin(x, dim=0).indices.int(), lambda x: Tensor.cummin(x, axis=0)[1], forward_only=True)
|
||||
helper_test_op([(2,3,0)], lambda x: torch.cummin(x, dim=2).values, lambda x: Tensor.cummin(x, axis=2)[0])
|
||||
helper_test_op([(2,3,0)], lambda x: torch.cummin(x, dim=2).indices.int(), lambda x: Tensor.cummin(x, axis=2)[1], forward_only=True)
|
||||
helper_test_op([(2,0,4)], lambda x: torch.cummax(x, dim=1).values, lambda x: Tensor.cummax(x, axis=1))
|
||||
helper_test_op([(0,3)], lambda x: torch.cummax(x, dim=0).values, lambda x: Tensor.cummax(x, axis=0))
|
||||
helper_test_op([(2,3,0)], lambda x: torch.cummax(x, dim=2).values, lambda x: Tensor.cummax(x, axis=2))
|
||||
|
||||
def test_argmax(self):
|
||||
# check if it returns the first index for multiple occurrences
|
||||
@@ -2058,15 +2008,6 @@ class TestOps(unittest.TestCase):
|
||||
|
||||
def test_diagonal(self):
|
||||
helper_test_op([(5,5)], lambda x: x.diagonal())
|
||||
helper_test_op([(3,4)], lambda x: x.diagonal()) # rectangular
|
||||
helper_test_op([(4,3)], lambda x: x.diagonal()) # rectangular (other way)
|
||||
helper_test_op([(3,3,3)], lambda x: x.diagonal(dim1=-2, dim2=-1)) # batched
|
||||
helper_test_op([(4,5,6)], lambda x: x.diagonal(dim1=-2, dim2=-1)) # batched rectangular
|
||||
helper_test_op([(2,3,4,5)], lambda x: x.diagonal(dim1=-2, dim2=-1)) # 4D batched
|
||||
helper_test_op([(5,5)], lambda x: x.diagonal(offset=1)) # positive offset
|
||||
helper_test_op([(5,5)], lambda x: x.diagonal(offset=-1)) # negative offset
|
||||
helper_test_op([(3,5)], lambda x: x.diagonal(offset=2)) # offset on rectangular
|
||||
self.helper_test_exception([(3,3)], lambda x: x.diagonal(dim1=0, dim2=0), expected=RuntimeError)
|
||||
|
||||
def test_roll(self):
|
||||
helper_test_op([(2, 4)], lambda x: x.roll(1))
|
||||
@@ -3262,11 +3203,6 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op([(32, 10)], lambda x: x.masked_select(x>0.5), lambda x: x.masked_select(x>0.5), forward_only=True)
|
||||
helper_test_op([(32, 10)], lambda x: x.masked_select(torch.tensor(True)), lambda x: x.masked_select(Tensor(True)), forward_only=True)
|
||||
|
||||
def test_nonzero(self):
|
||||
helper_test_op([(32, 10)], lambda x: (x>0.5).nonzero().int(), lambda x: (x>0.5).nonzero(), forward_only=True)
|
||||
helper_test_op([(20,)], lambda x: (x>0.5).nonzero().int(), lambda x: (x>0.5).nonzero(), forward_only=True)
|
||||
helper_test_op([(10, 5, 3)], lambda x: (x>0.5).nonzero().int(), lambda x: (x>0.5).nonzero(), forward_only=True)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "QCOM", "OpenCL fails to compile this (both on GPU(qcom)/QCOM backends)")
|
||||
def test_cast(self):
|
||||
helper_test_op([(3, 3)], lambda x: x.float())
|
||||
@@ -3278,6 +3214,21 @@ class TestOps(unittest.TestCase):
|
||||
def test_bitcast(self):
|
||||
helper_test_op([(3, 3)], lambda x: x.view(torch.int32), lambda x: x.bitcast(dtypes.int32), forward_only=True)
|
||||
|
||||
@unittest.skip("we have test_linalg, no need to test here. TODO: should be in torch backend tests")
|
||||
def test_svd(self):
|
||||
# test for tiny backend. real svd tests are in test_linalg
|
||||
A = torch.randn(5, 5)
|
||||
U, S, Vh = torch.linalg.svd(A)
|
||||
np.testing.assert_equal(U.shape, (5,5))
|
||||
np.testing.assert_equal(Vh.shape, (5,5))
|
||||
np.testing.assert_allclose(torch.dist(A, U @ torch.diag(S) @ Vh).cpu().numpy(), 0, atol=1e-5)
|
||||
|
||||
A = torch.randn(5, 3)
|
||||
U, S, Vh = torch.linalg.svd(A, full_matrices=False)
|
||||
np.testing.assert_equal(U.shape, (5,3))
|
||||
np.testing.assert_equal(Vh.shape, (3,3))
|
||||
np.testing.assert_allclose(torch.dist(A, U @ torch.diag(S) @ Vh).cpu().numpy(), 0, atol=1e-5)
|
||||
|
||||
@unittest.skipUnless(is_dtype_supported(dtypes.uchar), f"no uint8 on {Device.DEFAULT}")
|
||||
class TestOpsUint8(unittest.TestCase):
|
||||
def test_cast(self):
|
||||
|
||||
@@ -182,7 +182,7 @@ class TestSchedule(unittest.TestCase):
|
||||
assert not a.uop.is_realized
|
||||
|
||||
def test_simplify_padded_const(self):
|
||||
a, _ = Tensor.empty(1022).cummax(axis=0)
|
||||
a = Tensor.empty(1022).cummax(axis=0)
|
||||
check_schedule(a, 3)
|
||||
|
||||
def test_basic_binop_fusion(self):
|
||||
|
||||
+26
-27
@@ -49,7 +49,7 @@ xfail_broken_const_wraparound = pytest.mark.xfail(reason="const folding does not
|
||||
class TestModularWraparound(unittest.TestCase):
|
||||
def _test(self, uop:UOp, expected:int):
|
||||
results = to_uops_list([uop])
|
||||
self.assertEqual(len(results), 2) # +1 for SINK
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0].op, Ops.CONST)
|
||||
self.assertEqual(results[0].dtype, uop.dtype)
|
||||
self.assertEqual(results[0].arg, expected)
|
||||
@@ -198,8 +198,8 @@ class TestUOpGraph(unittest.TestCase):
|
||||
c2 = UOp(Ops.CONST, dtypes.float, arg=2.0)
|
||||
out = UOp(Ops.ADD, dtypes.float, (c1, c2))
|
||||
uops = to_uops_list([out])
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
out = uops[-2]
|
||||
self.assertEqual(len(uops), 1)
|
||||
out = uops[-1]
|
||||
self.assertEqual(out.op, Ops.CONST)
|
||||
self.assertEqual(out.arg, 3.0)
|
||||
|
||||
@@ -210,8 +210,8 @@ class TestUOpGraph(unittest.TestCase):
|
||||
c1 = UOp(Ops.CONST, dtypes.float, arg=1.0)
|
||||
out = UOp(Ops.WHERE, dtypes.float, (vc, c1, c1))
|
||||
uops = to_uops_list([out])
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
out = uops[-2]
|
||||
self.assertEqual(len(uops), 1)
|
||||
out = uops[-1]
|
||||
self.assertEqual(out.op, Ops.CONST)
|
||||
self.assertEqual(out.arg, 1.0)
|
||||
|
||||
@@ -221,8 +221,8 @@ class TestUOpGraph(unittest.TestCase):
|
||||
c2 = UOp(Ops.CONST, dtypes.float, arg=2.0)
|
||||
out = UOp(Ops.WHERE, dtypes.float, (bf, c1, c2))
|
||||
uops = to_uops_list([out])
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
out = uops[-2]
|
||||
self.assertEqual(len(uops), 1)
|
||||
out = uops[-1]
|
||||
self.assertEqual(out.op, Ops.CONST)
|
||||
self.assertEqual(out.arg, 2.0)
|
||||
|
||||
@@ -230,8 +230,8 @@ class TestUOpGraph(unittest.TestCase):
|
||||
bf = UOp(Ops.CONST, dtypes.bool, arg=False)
|
||||
out = UOp(Ops.CAST, dtypes.int, (bf,))
|
||||
uops = to_uops_list([out])
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
out = uops[-2]
|
||||
self.assertEqual(len(uops), 1)
|
||||
out = uops[-1]
|
||||
self.assertEqual(out.op, Ops.CONST)
|
||||
self.assertEqual(out.arg, 0)
|
||||
|
||||
@@ -239,8 +239,8 @@ class TestUOpGraph(unittest.TestCase):
|
||||
bf = UOp(Ops.CONST, dtypes.float, arg=1.0)
|
||||
out = UOp(Ops.BITCAST, dtypes.uint32, (bf,))
|
||||
uops = to_uops_list([out])
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
out = uops[-2]
|
||||
self.assertEqual(len(uops), 1)
|
||||
out = uops[-1]
|
||||
self.assertEqual(out.op, Ops.CONST)
|
||||
self.assertEqual(out.arg, 0x3F800000)
|
||||
|
||||
@@ -249,7 +249,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
bf = UOp(Ops.CONST, dtypes.uint8, arg=0x3F)
|
||||
out = UOp(Ops.BITCAST, dtypes.half, (bf,))
|
||||
uops = to_uops_list([out])
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
self.assertEqual(len(uops), 1)
|
||||
|
||||
@unittest.skip("this test isn't valid uops")
|
||||
def test_noop_vectorize_fold(self):
|
||||
@@ -276,7 +276,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
if DEBUG >= 4:
|
||||
from tinygrad import Device
|
||||
print(Device[Device.DEFAULT].renderer.render(uops))
|
||||
return uops[-2].src[-1] # -2 to skip SINK
|
||||
return uops[-1].src[-1]
|
||||
|
||||
# possible
|
||||
val = UOp(Ops.LOAD, dtypes.float.vec(4), (d1.index(idx),))
|
||||
@@ -321,7 +321,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (vec, var, acc))
|
||||
uops = to_uops_list([wmma])
|
||||
self.assertEqual(uops[0], acc)
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
self.assertEqual(len(uops), 1)
|
||||
|
||||
for i in [2, 4, 8]:
|
||||
var = UOp(Ops.DEFINE_VAR, dtypes.half.vec(i))
|
||||
@@ -330,7 +330,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (var, vec, acc))
|
||||
uops = to_uops_list([wmma])
|
||||
self.assertEqual(uops[0], acc)
|
||||
self.assertEqual(len(uops), 2) # +1 for SINK
|
||||
self.assertEqual(len(uops), 1)
|
||||
|
||||
@unittest.skip("wmma is wrong here, it needs an arg")
|
||||
def test_wmma_vectorize_no_fold(self):
|
||||
@@ -342,7 +342,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
acc = UOp(Ops.DEFINE_VAR, dtypes.half.vec(i), arg=('acc', UOp.const(dtypes.half, 0), UOp.const(dtypes.half, 1)))
|
||||
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (vec, var, acc))
|
||||
uops = to_uops_list([wmma])
|
||||
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
|
||||
self.assertEqual(uops[-1], wmma)
|
||||
|
||||
for i in [4, 8]:
|
||||
var = UOp(Ops.DEFINE_VAR, dtypes.half.vec(i), arg=(f'tmp{i}', UOp.const(dtypes.half, 0), UOp.const(dtypes.half, 1)))
|
||||
@@ -352,7 +352,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
acc = UOp(Ops.DEFINE_VAR, dtypes.half.vec(i), arg=('acc', UOp.const(dtypes.half, 0), UOp.const(dtypes.half, 1)))
|
||||
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (var, vec, acc))
|
||||
uops = to_uops_list([wmma])
|
||||
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
|
||||
self.assertEqual(uops[-1], wmma)
|
||||
|
||||
for i in [2, 4, 8]:
|
||||
vec = UOp(Ops.VECTORIZE, dtypes.half.vec(i),
|
||||
@@ -361,7 +361,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
acc = UOp(Ops.DEFINE_VAR, dtypes.half.vec(i), arg=('acc', UOp.const(dtypes.half, 0), UOp.const(dtypes.half, 1)))
|
||||
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (vec, var, acc))
|
||||
uops = to_uops_list([wmma])
|
||||
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
|
||||
self.assertEqual(uops[-1], wmma)
|
||||
|
||||
for i in [2, 4, 8]:
|
||||
var = UOp(Ops.DEFINE_VAR, dtypes.half.vec(i), arg=(f'tmp{i}', UOp.const(dtypes.half, 0), UOp.const(dtypes.half, 1)))
|
||||
@@ -370,7 +370,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
acc = UOp(Ops.DEFINE_VAR, dtypes.half.vec(i), arg=('acc', UOp.const(dtypes.half, 0), UOp.const(dtypes.half, 1)))
|
||||
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (var, vec, acc))
|
||||
uops = to_uops_list([wmma])
|
||||
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
|
||||
self.assertEqual(uops[-1], wmma)
|
||||
|
||||
def test_cast_alu_fold(self):
|
||||
d0 = UOp(Ops.DEFINE_GLOBAL, dtypes.bool.ptr(), arg=0)
|
||||
@@ -399,8 +399,8 @@ class TestUOpGraph(unittest.TestCase):
|
||||
vc = UOp(Ops.ADD, dtypes.int, (v, c2))
|
||||
out = UOp(Ops.ADD, dtypes.int, (vc, c4))
|
||||
uops = to_uops_list([out])
|
||||
self.assertEqual(len(uops), 4) # +1 for SINK
|
||||
out = uops[-2] # -2 to skip SINK
|
||||
self.assertEqual(len(uops), 3)
|
||||
out = uops[-1]
|
||||
self.assertEqual(out.op, Ops.ADD)
|
||||
self.assertEqual(out.src[1].op, Ops.CONST)
|
||||
self.assertEqual(out.src[1].arg, 6)
|
||||
@@ -415,8 +415,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
def test_sub_with_cast_folds(self):
|
||||
a = Variable("a", 0, 5)
|
||||
uops = to_uops_list([a.cast(dtypes.int)+(-a).cast(dtypes.int)])
|
||||
assert uops[0] == UOp.const(dtypes.int, 0)
|
||||
assert uops[-1].op == Ops.SINK
|
||||
assert uops == [UOp.const(dtypes.int, 0)]
|
||||
|
||||
def test_where_on_gated_load_fold(self):
|
||||
ridx0 = UOp.range(100, 0)
|
||||
@@ -487,7 +486,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
ld0 = glbl1.index(UOp.invalid())
|
||||
ld1 = glbl2.index(idx.valid(UOp.const(dtypes.bool, True)))
|
||||
uops = to_uops_list([UOp(Ops.STORE, dtypes.void, (glbl0.index(idx), ld1+ld0))])
|
||||
ld0 = uops[-2].src[-1] # -2 to skip SINK
|
||||
ld0 = uops[-1].src[-1]
|
||||
# the gate and invalid value are deleted from ld1
|
||||
self.assertEqual(ld0, UOp.load(glbl2.index(idx, ptr=True), dtype=dtypes.int))
|
||||
|
||||
@@ -501,7 +500,7 @@ class TestUOpGraph(unittest.TestCase):
|
||||
ld1 = smem.after(barrier).index((lidx+2).valid(UOp.const(dtypes.bool, True)))
|
||||
uops = to_uops_list([UOp(Ops.STORE, dtypes.void, (glbl0.index(lidx), ld1+ld0))])
|
||||
|
||||
ld0 = uops[-2].src[-1] # -2 to skip SINK
|
||||
ld0 = uops[-1].src[-1]
|
||||
# the gate and invalid value are deleted from ld1
|
||||
self.assertEqual(ld0.src[0], smem.after(barrier).index(lidx+2, ptr=True))
|
||||
|
||||
@@ -514,8 +513,8 @@ class TestUOpGraph(unittest.TestCase):
|
||||
st1 = glbl.index(idx0.valid(UOp.const(dtypes.bool, True)), ptr=True).store(val)
|
||||
uops = to_uops_list([st0, st1])
|
||||
# only the second store happens
|
||||
self.assertEqual(len(uops), 6) # +1 for SINK
|
||||
self.assertEqual(uops[-2], glbl.index(idx1, ptr=True).store(val)) # -2 to skip SINK
|
||||
self.assertEqual(len(uops), 5)
|
||||
self.assertEqual(uops[-1], glbl.index(idx1, ptr=True).store(val))
|
||||
|
||||
@unittest.skip("this is a uop type error")
|
||||
def test_asserts_bad_gate(self):
|
||||
|
||||
+2
-1
@@ -20,9 +20,10 @@ from dataclasses import replace
|
||||
def to_uops_list(u:list[UOp], ren=None) -> list[UOp]:
|
||||
sink = UOp.group(*u)
|
||||
for r in sink.ranges: sink = sink.end(r)
|
||||
# we strip the SINK here for legacy reasons
|
||||
ret = get_uops(sink.sink(arg=KernelInfo(opts_to_apply=())), ren)
|
||||
assert ret[-1].op is Ops.SINK
|
||||
return ret
|
||||
return ret[:-1]
|
||||
|
||||
def _uops_to_prg(uops_list):
|
||||
prg = get_program(UOp.sink(*uops_list), Device[Device.DEFAULT].renderer)
|
||||
|
||||
@@ -7,8 +7,6 @@ import textwrap, functools
|
||||
from tinygrad import Device, Tensor
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.helpers import getenv
|
||||
from tinygrad.device import Compiler
|
||||
from tinygrad.viz.serve import amdgpu_cfg
|
||||
|
||||
from extra.assembly.amd.autogen.rdna3.ins import *
|
||||
from extra.assembly.amd.dsl import Inst
|
||||
@@ -57,23 +55,16 @@ amdhsa.kernels:
|
||||
.end_amdgpu_metadata
|
||||
"""
|
||||
|
||||
# TODO: shouldn't need compiler once we can output ELF
|
||||
# outputs a text disassembly for humans and a machine readable binary
|
||||
def assemble(name:str, insts:list[str|Inst], compiler:Compiler) -> tuple[str, bytes]:
|
||||
asm = "\n".join([inst if isinstance(inst, str) else inst.disasm() for inst in insts])
|
||||
src = template.replace("fn_name", name).replace("INSTRUCTION", textwrap.dedent(asm))
|
||||
return (src, compiler.compile(src))
|
||||
|
||||
def asm_kernel(out:UOp, insts:list[str|Inst], name:str, device:str, compiler:Compiler, n_threads:int=1, n_workgroups:int=1) -> UOp:
|
||||
def asm_kernel(out:UOp, insts:list[str|Inst], name:str, device:str, n_threads:int=1, n_workgroups:int=1) -> UOp:
|
||||
lidx = UOp.special(n_threads, "lidx0")
|
||||
gidx = UOp.special(n_workgroups, "gidx0")
|
||||
sink = UOp.sink(out, lidx, gidx, arg=KernelInfo(name=name))
|
||||
src, lib = assemble(name, insts, compiler)
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg=device), UOp(Ops.LINEAR, src=(*sink.src, sink)),
|
||||
UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
|
||||
asm = "\n".join([inst if isinstance(inst, str) else inst.disasm() for inst in insts])
|
||||
src = template.replace("fn_name", name).replace("INSTRUCTION", textwrap.dedent(asm))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg=device), UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src)), arg=())
|
||||
|
||||
def run_asm(name:str, insts:list) -> None:
|
||||
fxn = functools.partial(asm_kernel, insts=insts, name=name, device=Device.DEFAULT, compiler=Device[Device.DEFAULT].compiler)
|
||||
fxn = functools.partial(asm_kernel, insts=insts, name=name, device=Device.DEFAULT)
|
||||
out = Tensor.custom_kernel(Tensor.empty(1), fxn=fxn)[0]
|
||||
out.realize()
|
||||
|
||||
@@ -87,37 +78,25 @@ class TestCfg(unittest.TestCase):
|
||||
def test_simple(self):
|
||||
run_asm("simple", [
|
||||
"entry:",
|
||||
"s_branch bb1",
|
||||
s_branch("bb1"),
|
||||
"bb1:",
|
||||
s_endpgm(),
|
||||
])
|
||||
|
||||
def test_diamond(self):
|
||||
run_asm("diamond", insts:=[
|
||||
run_asm("diamond", [
|
||||
"entry:",
|
||||
s_mov_b32(s[0], 0),
|
||||
s_mov_b32(s[1], 0),
|
||||
s_cmp_eq_u64(s[0:1], 0),
|
||||
"s_cbranch_scc1 if",
|
||||
"s_branch else",
|
||||
s_cmp_eq_i32(s[0], 0),
|
||||
s_cbranch_scc1("if"),
|
||||
s_branch("else"),
|
||||
"if:",
|
||||
s_nop(1),
|
||||
"s_branch end",
|
||||
s_branch("end"),
|
||||
"else:",
|
||||
s_nop(0),
|
||||
"end:",
|
||||
s_endpgm(),
|
||||
])
|
||||
_, lib = assemble("diamond", insts, Device[Device.DEFAULT].compiler)
|
||||
cfg = amdgpu_cfg(lib, Device[Device.DEFAULT].device_props()["gfx_target_version"])["data"]
|
||||
self.assertEqual(len(cfg["blocks"]), 5)
|
||||
references:dict[str, list[str]] = {}
|
||||
for pc, tokens in cfg["pc_tokens"].items():
|
||||
for t in tokens:
|
||||
for key in t["keys"]: references.setdefault(key, []).append(pc)
|
||||
self.assertEqual(len(references["r0"]), 2)
|
||||
insts = [cfg["pc_tokens"][pc][0]["st"] for pc in references["r0"]]
|
||||
self.assertEqual(insts, ['s_mov_b32', 's_cmp_eq_u64'])
|
||||
|
||||
def test_loop(self):
|
||||
run_asm("simple_loop", [
|
||||
@@ -126,7 +105,7 @@ class TestCfg(unittest.TestCase):
|
||||
"loop:",
|
||||
s_add_u32(s[1], s[1], -1),
|
||||
s_cmp_eq_i32(s[1], 0),
|
||||
"s_cbranch_scc0 loop",
|
||||
s_cbranch_scc0("loop"),
|
||||
s_endpgm(),
|
||||
])
|
||||
|
||||
@@ -137,13 +116,13 @@ class TestCfg(unittest.TestCase):
|
||||
"loop:",
|
||||
s_add_u32(s[1], s[1], -1),
|
||||
s_cmp_eq_i32(s[1], 2),
|
||||
"s_cbranch_scc1 cond",
|
||||
"s_branch cont",
|
||||
s_cbranch_scc1("cond"),
|
||||
s_branch("cont"),
|
||||
"cond:",
|
||||
s_add_u32(s[1], s[1], -2),
|
||||
"cont:",
|
||||
s_cmp_eq_i32(s[1], 0),
|
||||
"s_cbranch_scc0 loop",
|
||||
s_cbranch_scc0("loop"),
|
||||
s_endpgm(),
|
||||
])
|
||||
|
||||
@@ -154,9 +133,9 @@ class TestCfg(unittest.TestCase):
|
||||
"loop:",
|
||||
s_add_u32(s[1], s[1], -1),
|
||||
s_cmp_eq_i32(s[1], 5),
|
||||
"s_cbranch_scc1 break",
|
||||
s_cbranch_scc1("break"),
|
||||
s_cmp_eq_i32(s[1], 0),
|
||||
"s_cbranch_scc0 loop",
|
||||
s_cbranch_scc0("loop"),
|
||||
"break:",
|
||||
s_endpgm(),
|
||||
])
|
||||
@@ -165,19 +144,19 @@ class TestCfg(unittest.TestCase):
|
||||
run_asm("switch_case", [
|
||||
"entry:",
|
||||
s_cmp_eq_i32(s[0], 0),
|
||||
"s_cbranch_scc1 case0",
|
||||
s_cbranch_scc1("case0"),
|
||||
s_cmp_eq_i32(s[0], 1),
|
||||
"s_cbranch_scc1 case1",
|
||||
"s_branch case2",
|
||||
s_cbranch_scc1("case1"),
|
||||
s_branch("case2"),
|
||||
"case0:",
|
||||
s_nop(0),
|
||||
"s_branch join",
|
||||
s_branch("join"),
|
||||
"case1:",
|
||||
s_nop(1),
|
||||
"s_branch join",
|
||||
s_branch("join"),
|
||||
"case2:",
|
||||
s_nop(2),
|
||||
"s_branch join",
|
||||
s_branch("join"),
|
||||
"join:",
|
||||
s_endpgm(),
|
||||
])
|
||||
@@ -186,47 +165,34 @@ class TestCfg(unittest.TestCase):
|
||||
run_asm("ping_pong", [
|
||||
"entry:",
|
||||
s_cmp_eq_i32(s[0], 0),
|
||||
"s_cbranch_scc1 ping",
|
||||
"s_branch pong",
|
||||
s_cbranch_scc1("ping"),
|
||||
s_branch("pong"),
|
||||
"ping:",
|
||||
s_cmp_eq_i32(s[1], 0),
|
||||
"s_cbranch_scc1 pong",
|
||||
"s_branch end",
|
||||
s_cbranch_scc1("pong"),
|
||||
s_branch("end"),
|
||||
"pong:",
|
||||
s_cmp_eq_i32(s[2], 0),
|
||||
"s_cbranch_scc1 ping",
|
||||
s_cbranch_scc1("ping"),
|
||||
"end:",
|
||||
s_endpgm(),
|
||||
])
|
||||
|
||||
def test_colored_blocks(self):
|
||||
N = 10
|
||||
asm = ["entry:", "s_branch init0"]
|
||||
asm = ["entry:", s_branch("init0"),]
|
||||
for i in range(N):
|
||||
asm += [f"init{i}:", s_mov_b32(s[1], i + 1), f"s_branch {(loop:=f'loop{i}')}"]
|
||||
asm += [f"init{i}:", s_mov_b32(s[1], i + 1), s_branch(loop:=f"loop{i}")]
|
||||
asm += [
|
||||
f"{loop}:",
|
||||
s_nop(i & 7),
|
||||
s_add_u32(s[1], s[1], -1),
|
||||
s_cmp_eq_i32(s[1], 0),
|
||||
f"s_cbranch_scc0 {loop}",
|
||||
f"s_branch {'init' + str(i+1) if i + 1 < N else 'end'}",
|
||||
s_cbranch_scc0(loop),
|
||||
s_branch(f"init{i+1}" if i + 1 < N else "end"),
|
||||
]
|
||||
asm += ["end:", s_endpgm()]
|
||||
run_asm("test_colored_blocks", asm)
|
||||
|
||||
def test_jump_back_to_end(self):
|
||||
run_asm("jump_back_to_end", [
|
||||
"entry:",
|
||||
s_mov_b32(s[1], 2),
|
||||
"s_cbranch_execz loop",
|
||||
"end:",
|
||||
s_endpgm(),
|
||||
"loop:",
|
||||
s_add_u32(s[1], s[1], -1),
|
||||
s_cmp_eq_i32(s[1], 0),
|
||||
"s_branch end",
|
||||
])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -840,6 +840,7 @@ class TestTK(unittest.TestCase):
|
||||
np.testing.assert_allclose(v.grad.numpy(), v_ref.grad.numpy(), atol=2e-2, rtol=2e-2)
|
||||
np.testing.assert_allclose(k.grad.numpy(), k_ref.grad.numpy(), atol=5e-2, rtol=2e-2)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_fast_fa_bwd_causal_jitted(self):
|
||||
from extra.thunder.tiny.fa import flash_attention
|
||||
|
||||
@@ -882,63 +883,14 @@ class TestTK(unittest.TestCase):
|
||||
Tensor.realize(q_ref, k_ref, v_ref)
|
||||
|
||||
q_ref_, k_ref_, v_ref_ = q_ref.transpose(1, 2), k_ref.transpose(1, 2), v_ref.transpose(1, 2)
|
||||
ref = flash_attention(q_ref_, k_ref_, v_ref_, is_causal=True)
|
||||
ref = q_ref_.scaled_dot_product_attention(k_ref_, v_ref_, is_causal=True)
|
||||
ref = ref.float().transpose(1, 2)
|
||||
ref.backward(do)
|
||||
Tensor.realize(q_ref.grad, k_ref.grad, v_ref.grad)
|
||||
|
||||
np.testing.assert_allclose(q.grad.numpy(), q_ref.grad.numpy(), atol=1e-5, rtol=1e-5)
|
||||
np.testing.assert_allclose(k.grad.numpy(), k_ref.grad.numpy(), atol=1e-5, rtol=1e-5)
|
||||
np.testing.assert_allclose(v.grad.numpy(), v_ref.grad.numpy(), atol=1e-5, rtol=1e-5)
|
||||
|
||||
def test_fast_fa_bwd_multidevice(self):
|
||||
from extra.thunder.tiny.fa import flash_attention
|
||||
|
||||
Tensor.manual_seed(42)
|
||||
|
||||
B, N, H, H_KV, D = 2, 1024, 32, 32, 128
|
||||
GPUS = tuple(f"AMD:{i}" for i in range(B))
|
||||
|
||||
with Context(DEBUG=0):
|
||||
base_q = Tensor.randn(B, N, H, D, dtype=dtypes.bfloat16, requires_grad=True).contiguous()
|
||||
base_k = Tensor.randn(B, N, H_KV, D, dtype=dtypes.bfloat16, requires_grad=True).contiguous()
|
||||
base_v = Tensor.randn(B, N, H_KV, D, dtype=dtypes.bfloat16, requires_grad=True).contiguous()
|
||||
|
||||
base_do = Tensor.ones(B, N, H, D, dtype=dtypes.float32).contiguous()
|
||||
|
||||
with Context(DEBUG=0):
|
||||
q = base_q.clone().requires_grad_(True).shard(GPUS, axis=0)
|
||||
k = base_k.clone().requires_grad_(True).shard(GPUS, axis=0)
|
||||
v = base_v.clone().requires_grad_(True).shard(GPUS, axis=0)
|
||||
Tensor.realize(q, k, v)
|
||||
|
||||
do = base_do.clone().shard(GPUS, axis=0)
|
||||
Tensor.realize(do)
|
||||
|
||||
q_, k_, v_ = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
||||
out = flash_attention(q_, k_, v_, is_causal=True)
|
||||
out = out.float().transpose(1, 2)
|
||||
out.backward(do)
|
||||
Tensor.realize(q.grad, k.grad, v.grad)
|
||||
|
||||
with Context(DEBUG=0):
|
||||
q_ref = base_q.clone().requires_grad_(True)
|
||||
k_ref = base_k.clone().requires_grad_(True)
|
||||
v_ref = base_v.clone().requires_grad_(True)
|
||||
Tensor.realize(q_ref, k_ref, v_ref)
|
||||
|
||||
do_ref = base_do.clone()
|
||||
Tensor.realize(do_ref)
|
||||
|
||||
q_ref_, k_ref_, v_ref_ = q_ref.transpose(1, 2), k_ref.transpose(1, 2), v_ref.transpose(1, 2)
|
||||
ref = flash_attention(q_ref_, k_ref_, v_ref_, is_causal=True)
|
||||
ref = ref.float().transpose(1, 2)
|
||||
ref.backward(do_ref)
|
||||
Tensor.realize(q_ref.grad, k_ref.grad, v_ref.grad)
|
||||
|
||||
np.testing.assert_allclose(q.grad.numpy(), q_ref.grad.numpy(), atol=1e-5, rtol=1e-5)
|
||||
np.testing.assert_allclose(v.grad.numpy(), v_ref.grad.numpy(), atol=1e-5, rtol=1e-5)
|
||||
np.testing.assert_allclose(k.grad.numpy(), k_ref.grad.numpy(), atol=1e-5, rtol=1e-5)
|
||||
np.testing.assert_allclose(q.grad.numpy(), q_ref.grad.numpy(), atol=5e-2, rtol=2e-2)
|
||||
np.testing.assert_allclose(v.grad.numpy(), v_ref.grad.numpy(), atol=2e-2, rtol=2e-2)
|
||||
np.testing.assert_allclose(k.grad.numpy(), k_ref.grad.numpy(), atol=5e-2, rtol=2e-2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+156
-414
@@ -1,61 +1,30 @@
|
||||
import ctypes, struct, subprocess, tempfile, unittest
|
||||
from typing import Annotated
|
||||
from tinygrad.helpers import OSX, WIN
|
||||
from tinygrad.runtime.support.c import DLL, record, init_records
|
||||
from tinygrad.runtime.support import c
|
||||
import ctypes, subprocess, tempfile, unittest
|
||||
from tinygrad.helpers import WIN
|
||||
from tinygrad.runtime.support.c import Struct
|
||||
from tinygrad.runtime.support.autogen import gen
|
||||
|
||||
class TestAutogen(unittest.TestCase):
|
||||
def compile(self, src):
|
||||
with tempfile.NamedTemporaryFile(suffix=".so") as f:
|
||||
subprocess.check_output(('clang', '-x', 'c', '-fPIC', '-shared', '-', '-o', f.name), input=src.encode())
|
||||
return DLL("test", f.name)
|
||||
|
||||
def run_gen(self, contents):
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.h') as f:
|
||||
f.write(contents)
|
||||
f.flush()
|
||||
|
||||
generated_code = gen(name="test_header", dll=None, files=[f.name])
|
||||
|
||||
namespace = {}
|
||||
exec(generated_code, namespace)
|
||||
return namespace
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_packed_struct(self):
|
||||
@record
|
||||
class Baz:
|
||||
SIZE = 8
|
||||
a: Annotated[ctypes.c_uint, 0, 30]
|
||||
b: Annotated[ctypes.c_uint, 3, 30, 6]
|
||||
c: Annotated[ctypes.c_uint, 7, 2, 4]
|
||||
d: Annotated[ctypes.c_uint, 7, 2, 6]
|
||||
init_records()
|
||||
|
||||
b = Baz(0x3AAADEAD, 0xBEEF, 1, 0)
|
||||
assert b.a == 0x3AAADEAD
|
||||
assert b.b == 0xBEEF
|
||||
assert b.c == 1
|
||||
assert b.d == 0
|
||||
|
||||
b.a = 0xCAFE
|
||||
assert b.a == 0xCAFE
|
||||
assert b.b == 0xBEEF
|
||||
assert b.c == 1
|
||||
assert b.d == 0
|
||||
def test_packed_struct_sizeof(self):
|
||||
layout = [('a', ctypes.c_char), ('b', ctypes.c_int, 5), ('c', ctypes.c_char)]
|
||||
class Y(ctypes.Structure): _fields_, _pack_, _layout_ = layout, 1, 'ms'
|
||||
class Z(Struct): pass
|
||||
Z._packed_, Z._fields_ = True, layout
|
||||
self.assertEqual(ctypes.sizeof(Y), 6)
|
||||
self.assertEqual(ctypes.sizeof(Z), 3)
|
||||
layout = [('a', ctypes.c_int, 31), ('b', ctypes.c_int, 31), ('c', ctypes.c_int, 1), ('d', ctypes.c_int, 1)]
|
||||
class Foo(ctypes.Structure): _fields_, _layout_ = layout, 'gcc-sysv'
|
||||
class Bar(ctypes.Structure): _fields_, _pack_, _layout_ = layout, 1, 'ms'
|
||||
class Baz(Struct): pass
|
||||
Baz._packed_, Baz._fields_ = True, layout
|
||||
self.assertEqual(ctypes.sizeof(Foo), 12)
|
||||
self.assertEqual(ctypes.sizeof(Bar), 12)
|
||||
self.assertEqual(ctypes.sizeof(Baz), 8)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_packed_struct_interop(self):
|
||||
@record
|
||||
class Baz:
|
||||
SIZE = 8
|
||||
a: Annotated[ctypes.c_int, 0, 30]
|
||||
b: Annotated[ctypes.c_int, 3, 30, 6]
|
||||
c: Annotated[ctypes.c_int, 7, 2, 4]
|
||||
d: Annotated[ctypes.c_int, 7, 2, 6]
|
||||
init_records()
|
||||
|
||||
class Baz(Struct): pass
|
||||
Baz._packed_ = True
|
||||
Baz._fields_ = [('a', ctypes.c_int, 30), ('b', ctypes.c_int, 30), ('c', ctypes.c_int, 2), ('d', ctypes.c_int, 2)]
|
||||
src = '''
|
||||
struct __attribute__((packed)) baz {
|
||||
int a:30;
|
||||
@@ -68,220 +37,70 @@ class TestAutogen(unittest.TestCase):
|
||||
return x.a + x.b + x.c + x.d;
|
||||
}
|
||||
'''
|
||||
dll = self.compile(src)
|
||||
b = Baz(0xAA000, 0x00BB0, 0, 1)
|
||||
@dll.bind
|
||||
def test(x:Baz) -> ctypes.c_int: ...
|
||||
self.assertEqual(test(b), b.a + b.b + b.c + b.d)
|
||||
args = ('-x', 'c', '-fPIC', '-shared')
|
||||
with tempfile.NamedTemporaryFile(suffix=".so") as f:
|
||||
subprocess.check_output(('clang',) + args + ('-', '-o', f.name), input=src.encode('utf-8'))
|
||||
b = Baz(0xAA000, 0x00BB0, 0, 1)
|
||||
test = ctypes.CDLL(f.name).test
|
||||
test.argtypes = [Baz]
|
||||
self.assertEqual(test(b), b.a + b.b + b.c + b.d)
|
||||
|
||||
# https://github.com/python/cpython/issues/90914
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_bitfield_interop(self):
|
||||
@record
|
||||
class Baz:
|
||||
SIZE = 1
|
||||
a: Annotated[ctypes.c_bool, 0, 1, 0]
|
||||
b: Annotated[ctypes.c_bool, 0, 1, 1]
|
||||
c: Annotated[ctypes.c_bool, 0, 1, 2]
|
||||
d: Annotated[ctypes.c_bool, 0, 1, 3]
|
||||
e: Annotated[ctypes.c_bool, 0, 1, 4]
|
||||
f: Annotated[ctypes.c_bool, 0, 1, 5]
|
||||
g: Annotated[ctypes.c_bool, 0, 1, 6]
|
||||
h: Annotated[ctypes.c_bool, 0, 1, 7]
|
||||
init_records()
|
||||
class Baz(Struct): pass
|
||||
Baz._fields_ = [(chr(ord('a') + i), ctypes.c_bool, 1) for i in range(8)]
|
||||
src = '''#include <stdbool.h>
|
||||
struct baz {
|
||||
bool a:1, b:1, c:1, d:1, e:1, f:1, g:1, h:1;
|
||||
bool a:1;
|
||||
bool b:1;
|
||||
bool c:1;
|
||||
bool d:1;
|
||||
bool e:1;
|
||||
bool f:1;
|
||||
bool g:1;
|
||||
bool h:1;
|
||||
};
|
||||
|
||||
int test(struct baz x) {
|
||||
return x.c;
|
||||
}
|
||||
'''
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(x:Baz) -> ctypes.c_int: ...
|
||||
for i in range(8): self.assertEqual(test(Baz(*(j==i for j in range(8)))), i==2)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_struct_interop(self):
|
||||
@record
|
||||
class Baz:
|
||||
SIZE = 32
|
||||
a: Annotated[ctypes.c_int, 0]
|
||||
b: Annotated[ctypes.c_int, 4]
|
||||
c: Annotated[ctypes.c_int, 8]
|
||||
d: Annotated[ctypes.c_int, 12]
|
||||
e: Annotated[ctypes.c_int, 16]
|
||||
f: Annotated[ctypes.c_int, 20]
|
||||
g: Annotated[ctypes.c_int, 24]
|
||||
h: Annotated[ctypes.c_int, 28]
|
||||
init_records()
|
||||
src = '''#include <stdio.h>
|
||||
struct baz {
|
||||
int a, b, c, d, e, f, g, h;
|
||||
};
|
||||
|
||||
struct baz test(struct baz x) {
|
||||
return (struct baz){x.h, x.g, x.f, x.e, x.d, x.c, x.b, x.a};
|
||||
}
|
||||
'''
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(x:Baz) -> Baz: ...
|
||||
self.assertEqual(bytes(test(Baz(*range(8)))), struct.pack("8i", *range(7, -1, -1)))
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_aos_interop(self):
|
||||
@record
|
||||
class Item:
|
||||
SIZE = 4
|
||||
val: Annotated[ctypes.c_int, 0]
|
||||
init_records()
|
||||
src = """
|
||||
struct item { int val; };
|
||||
int test(struct item arr[3]) {
|
||||
int ret = 0;
|
||||
for (int i = 0; i < 3; i++) ret += arr[i].val;
|
||||
return ret;
|
||||
}
|
||||
"""
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(arr:(Item * 3)) -> ctypes.c_int: ...
|
||||
self.assertEqual(test((Item * 3)(Item(10), Item(20), Item(30))), 60)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_soa_interop(self):
|
||||
@record
|
||||
class Row:
|
||||
SIZE = 16
|
||||
data: Annotated[ctypes.c_int * 3, 0]
|
||||
init_records()
|
||||
src = """
|
||||
struct row { int data[3]; };
|
||||
struct row test(struct row x) {
|
||||
return (struct row){{ x.data[2], x.data[1], x.data[0] }};
|
||||
}
|
||||
"""
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(x:Row) -> Row: ...
|
||||
r = test(Row((ctypes.c_int * 3)(10, 20, 30)))
|
||||
self.assertIsInstance(r, Row)
|
||||
self.assertEqual(r.data[0], 30)
|
||||
self.assertEqual(r.data[1], 20)
|
||||
self.assertEqual(r.data[2], 10)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_soa_ptr_interop(self):
|
||||
@record
|
||||
class Row:
|
||||
SIZE = 8
|
||||
data: Annotated[c.POINTER[ctypes.c_int], 0]
|
||||
init_records()
|
||||
src = """
|
||||
struct row { int *data; };
|
||||
int test(struct row x) {
|
||||
return x.data[2] + x.data[1] + x.data[0];
|
||||
}
|
||||
"""
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(x:Row) -> ctypes.c_int: ...
|
||||
assert test(Row((ctypes.c_int * 3)(10, 20, 30))) == 60
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_nested_struct_interop(self):
|
||||
@record
|
||||
class Inner:
|
||||
SIZE = 4
|
||||
a: Annotated[ctypes.c_int, 0]
|
||||
@record
|
||||
class Outer:
|
||||
SIZE = 8
|
||||
inner: Annotated[Inner, 0]
|
||||
b: Annotated[ctypes.c_int, 4]
|
||||
init_records()
|
||||
src = """
|
||||
struct i { int a; };
|
||||
struct o { struct i i; int b; };
|
||||
struct o test(struct o x) {
|
||||
return (struct o){(struct i){ x.b }, x.i.a };
|
||||
}
|
||||
"""
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(x:Outer) -> Outer: ...
|
||||
o = test(Outer(Inner(10), 20))
|
||||
self.assertEqual(o.inner.a, 20)
|
||||
self.assertEqual(o.b, 10)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_struct_pointer_interop(self):
|
||||
@record
|
||||
class Foo:
|
||||
SIZE = 8
|
||||
a: Annotated[ctypes.c_int, 0]
|
||||
b: Annotated[ctypes.c_int, 4]
|
||||
init_records()
|
||||
src = """
|
||||
struct foo { int a, b; };
|
||||
struct foo *test(struct foo *f) {
|
||||
int x = f->a;
|
||||
f->a = f->b;
|
||||
f->b = x;
|
||||
return f;
|
||||
}
|
||||
"""
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(f:ctypes.POINTER(Foo)) -> ctypes.POINTER(Foo): ...
|
||||
inp = ctypes.pointer(Foo(10, 20))
|
||||
out = test(inp)
|
||||
self.assertEqual(out.contents.a, 20)
|
||||
self.assertEqual(out.contents.b, 10)
|
||||
args = ('-x', 'c', '-fPIC', '-shared')
|
||||
with tempfile.NamedTemporaryFile(suffix=".so") as f:
|
||||
subprocess.check_output(('clang',) + args + ('-', '-o', f.name), input=src.encode('utf-8'))
|
||||
test = ctypes.CDLL(f.name).test
|
||||
test.argtypes = [Baz]
|
||||
for i in range(8): self.assertEqual(test(Baz(*(j==i for j in range(8)))), i==2)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_packed_structs(self):
|
||||
ns = self.run_gen("""
|
||||
typedef unsigned NvU32;
|
||||
typedef unsigned long NvU64;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
NvU32 version;
|
||||
NvU32 size;
|
||||
NvU64 gfwImageOffset;
|
||||
NvU32 gfwImageSize;
|
||||
NvU32 flags;
|
||||
} __attribute__((packed)) FWSECLIC_READ_VBIOS_DESC;
|
||||
|
||||
#define FWSECLIC_READ_VBIOS_STRUCT_FLAGS (2)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
NvU32 version;
|
||||
NvU32 size;
|
||||
NvU32 frtsRegionOffset4K;
|
||||
NvU32 frtsRegionSize;
|
||||
NvU32 frtsRegionMediaType;
|
||||
} __attribute__((packed)) FWSECLIC_FRTS_REGION_DESC;
|
||||
|
||||
#define FWSECLIC_FRTS_REGION_MEDIA_FB (2)
|
||||
#define FWSECLIC_FRTS_REGION_SIZE_1MB_IN_4K (0x100)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
FWSECLIC_READ_VBIOS_DESC readVbiosDesc;
|
||||
FWSECLIC_FRTS_REGION_DESC frtsRegionDesc;
|
||||
} __attribute__((packed)) FWSECLIC_FRTS_CMD;
|
||||
""")
|
||||
|
||||
FWSECLIC_READ_VBIOS_DESC = ns['FWSECLIC_READ_VBIOS_DESC']
|
||||
FWSECLIC_FRTS_REGION_DESC = ns['FWSECLIC_FRTS_REGION_DESC']
|
||||
FWSECLIC_FRTS_CMD = ns['FWSECLIC_FRTS_CMD']
|
||||
|
||||
NvU32 = ctypes.c_uint32
|
||||
NvU64 = ctypes.c_uint64
|
||||
class FWSECLIC_READ_VBIOS_DESC(Struct): pass
|
||||
FWSECLIC_READ_VBIOS_DESC._packed_ = True
|
||||
FWSECLIC_READ_VBIOS_DESC._fields_ = [
|
||||
('version', NvU32),
|
||||
('size', NvU32),
|
||||
('gfwImageOffset', NvU64),
|
||||
('gfwImageSize', NvU32),
|
||||
('flags', NvU32),
|
||||
]
|
||||
class FWSECLIC_FRTS_REGION_DESC(Struct): pass
|
||||
FWSECLIC_FRTS_REGION_DESC._packed_ = True
|
||||
FWSECLIC_FRTS_REGION_DESC._fields_ = [
|
||||
('version', NvU32),
|
||||
('size', NvU32),
|
||||
('frtsRegionOffset4K', NvU32),
|
||||
('frtsRegionSize', NvU32),
|
||||
('frtsRegionMediaType', NvU32),
|
||||
]
|
||||
class FWSECLIC_FRTS_CMD(Struct): pass
|
||||
FWSECLIC_FRTS_CMD._packed_ = True
|
||||
FWSECLIC_FRTS_CMD._fields_ = [
|
||||
('readVbiosDesc', FWSECLIC_READ_VBIOS_DESC),
|
||||
('frtsRegionDesc', FWSECLIC_FRTS_REGION_DESC),
|
||||
]
|
||||
read_vbios_desc = FWSECLIC_READ_VBIOS_DESC(version=0x1, size=ctypes.sizeof(FWSECLIC_READ_VBIOS_DESC), flags=2)
|
||||
frst_reg_desc = FWSECLIC_FRTS_REGION_DESC(version=0x1, size=ctypes.sizeof(FWSECLIC_FRTS_REGION_DESC),
|
||||
frtsRegionOffset4K=0xdead, frtsRegionSize=0x100, frtsRegionMediaType=2)
|
||||
@@ -292,36 +111,45 @@ typedef struct
|
||||
assert frts_cmd.readVbiosDesc.__class__ is FWSECLIC_READ_VBIOS_DESC
|
||||
assert frts_cmd.frtsRegionDesc.__class__ is FWSECLIC_FRTS_REGION_DESC
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
@unittest.skipIf(OSX, "can't find stdint?")
|
||||
def test_packed_fields(self):
|
||||
ns = self.run_gen("""#include <stdint.h>
|
||||
typedef struct die_info
|
||||
{
|
||||
uint16_t die_id;
|
||||
uint16_t die_offset; /* Points to the corresponding die_header structure */
|
||||
} die_info;
|
||||
uint8_t = ctypes.c_ubyte
|
||||
uint16_t = ctypes.c_ushort
|
||||
uint32_t = ctypes.c_uint32
|
||||
|
||||
typedef struct ip_discovery_header
|
||||
{
|
||||
uint32_t signature; /* Table Signature */
|
||||
uint16_t version; /* Table Version */
|
||||
uint16_t size; /* Table Size */
|
||||
uint32_t id; /* Table ID */
|
||||
uint16_t num_dies; /* Number of Dies */
|
||||
die_info die_info[16]; /* list die information for up to 16 dies */
|
||||
union {
|
||||
uint16_t padding[1]; /* version <= 3 */
|
||||
struct { /* version == 4 */
|
||||
uint8_t base_addr_64_bit : 1; /* ip structures are using 64 bit base address */
|
||||
uint8_t reserved : 7;
|
||||
uint8_t reserved2;
|
||||
};
|
||||
};
|
||||
} ip_discovery_header;
|
||||
""")
|
||||
|
||||
ip_discovery_header = ns['ip_discovery_header']
|
||||
class struct_die_info(Struct): pass
|
||||
struct_die_info._packed_ = True
|
||||
struct_die_info._fields_ = [
|
||||
('die_id', uint16_t),
|
||||
('die_offset', uint16_t),
|
||||
]
|
||||
die_info = struct_die_info
|
||||
class struct_ip_discovery_header(Struct): pass
|
||||
class struct_ip_discovery_header_0(ctypes.Union): pass
|
||||
class struct_ip_discovery_header_0_0(Struct): pass
|
||||
uint8_t = ctypes.c_ubyte
|
||||
struct_ip_discovery_header_0_0._fields_ = [
|
||||
('base_addr_64_bit', uint8_t,1),
|
||||
('reserved', uint8_t,7),
|
||||
('reserved2', uint8_t),
|
||||
]
|
||||
struct_ip_discovery_header_0._anonymous_ = ['_0']
|
||||
struct_ip_discovery_header_0._packed_ = True
|
||||
struct_ip_discovery_header_0._fields_ = [
|
||||
('padding', (uint16_t * 1)),
|
||||
('_0', struct_ip_discovery_header_0_0),
|
||||
]
|
||||
struct_ip_discovery_header._anonymous_ = ['_0']
|
||||
struct_ip_discovery_header._packed_ = True
|
||||
struct_ip_discovery_header._fields_ = [
|
||||
('signature', uint32_t),
|
||||
('version', uint16_t),
|
||||
('size', uint16_t),
|
||||
('id', uint32_t),
|
||||
('num_dies', uint16_t),
|
||||
('die_info', (die_info * 16)),
|
||||
('_0', struct_ip_discovery_header_0),
|
||||
]
|
||||
ip_discovery_header = struct_ip_discovery_header
|
||||
|
||||
hdr = b'IPDS\x04\x00|\x1d\x80\x1a\xffd\x01\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00' # noqa: E501
|
||||
ihdr = ip_discovery_header.from_buffer_copy(hdr)
|
||||
@@ -334,7 +162,7 @@ typedef struct ip_discovery_header
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_gen_from_header(self):
|
||||
namespace = self.run_gen("""
|
||||
header_content = """
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
@@ -353,34 +181,45 @@ typedef struct ip_discovery_header
|
||||
Color color;
|
||||
} Rectangle;
|
||||
|
||||
int add_points(Point a, Point b);""")
|
||||
int add_points(Point a, Point b);
|
||||
"""
|
||||
|
||||
self.assertIn('Point', namespace)
|
||||
self.assertIn('Color', namespace)
|
||||
self.assertIn('Rectangle', namespace)
|
||||
self.assertIn('RED', namespace)
|
||||
self.assertIn('GREEN', namespace)
|
||||
self.assertIn('BLUE', namespace)
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.h') as f:
|
||||
f.write(header_content)
|
||||
f.flush()
|
||||
|
||||
self.assertEqual(namespace['RED'], 0)
|
||||
self.assertEqual(namespace['GREEN'], 1)
|
||||
self.assertEqual(namespace['BLUE'], 2)
|
||||
generated_code = gen(name="test_header", dll=None, files=[f.name])
|
||||
|
||||
Point = namespace['Point']
|
||||
p = Point()
|
||||
self.assertTrue(hasattr(p, 'x'))
|
||||
self.assertTrue(hasattr(p, 'y'))
|
||||
namespace = {}
|
||||
exec(generated_code, namespace)
|
||||
|
||||
Rectangle = namespace['Rectangle']
|
||||
rect = Rectangle()
|
||||
self.assertTrue(hasattr(rect, 'origin'))
|
||||
self.assertTrue(hasattr(rect, 'width'))
|
||||
self.assertTrue(hasattr(rect, 'height'))
|
||||
self.assertTrue(hasattr(rect, 'color'))
|
||||
self.assertIn('Point', namespace)
|
||||
self.assertIn('Color', namespace)
|
||||
self.assertIn('Rectangle', namespace)
|
||||
self.assertIn('RED', namespace)
|
||||
self.assertIn('GREEN', namespace)
|
||||
self.assertIn('BLUE', namespace)
|
||||
|
||||
self.assertEqual(namespace['RED'], 0)
|
||||
self.assertEqual(namespace['GREEN'], 1)
|
||||
self.assertEqual(namespace['BLUE'], 2)
|
||||
|
||||
Point = namespace['Point']
|
||||
p = Point()
|
||||
self.assertIsInstance(p, Struct)
|
||||
self.assertTrue(hasattr(p, 'x'))
|
||||
self.assertTrue(hasattr(p, 'y'))
|
||||
|
||||
Rectangle = namespace['Rectangle']
|
||||
rect = Rectangle()
|
||||
self.assertTrue(hasattr(rect, 'origin'))
|
||||
self.assertTrue(hasattr(rect, 'width'))
|
||||
self.assertTrue(hasattr(rect, 'height'))
|
||||
self.assertTrue(hasattr(rect, 'color'))
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_struct_ordering(self):
|
||||
namespace = self.run_gen("""
|
||||
header_content = """
|
||||
struct A;
|
||||
struct C;
|
||||
typedef struct A A;
|
||||
@@ -396,119 +235,22 @@ typedef struct ip_discovery_header
|
||||
struct A {
|
||||
int x;
|
||||
struct B *b_ptr;
|
||||
};""")
|
||||
|
||||
self.assertIn('struct_A', namespace)
|
||||
self.assertIn('struct_B', namespace)
|
||||
self.assertIn('struct_C', namespace)
|
||||
A, B, C = namespace['A'], namespace['struct_B'], namespace['struct_C']
|
||||
a, b, c = A(), B(), C()
|
||||
self.assertTrue(hasattr(a, 'x'))
|
||||
self.assertTrue(hasattr(a, 'b_ptr'))
|
||||
self.assertTrue(hasattr(b, 'c_ptr'))
|
||||
self.assertTrue(hasattr(c, 'a_ptr'))
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_pointer_field_roundtrip(self):
|
||||
# This tests storing a pointer in a record struct field and passing it to C
|
||||
# Mimics how mesa.struct_lp_build_tgsi_params.mask is used
|
||||
from tinygrad.runtime.support.c import POINTER
|
||||
@record
|
||||
class Inner:
|
||||
SIZE = 8
|
||||
value: Annotated[ctypes.c_int, 0]
|
||||
flag: Annotated[ctypes.c_int, 4]
|
||||
@record
|
||||
class Outer:
|
||||
SIZE = 16
|
||||
x: Annotated[ctypes.c_int, 0]
|
||||
inner_ptr: Annotated[POINTER[Inner], 8]
|
||||
init_records()
|
||||
|
||||
src = """
|
||||
struct inner { int value; int flag; };
|
||||
struct outer { int x; struct inner *inner_ptr; };
|
||||
int test(struct inner *p) {
|
||||
return p->value + p->flag;
|
||||
}
|
||||
};
|
||||
"""
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def test(p:POINTER[Inner]) -> ctypes.c_int: ...
|
||||
|
||||
inner = Inner(value=42, flag=10)
|
||||
outer = Outer(x=1, inner_ptr=ctypes.pointer(inner))
|
||||
# Retrieve pointer from struct field and pass to C
|
||||
self.assertEqual(test(outer.inner_ptr), 52)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_pointer_field_loses_reference(self):
|
||||
# BUG: When a pointer is stored in a record struct field, only the address bytes are saved.
|
||||
# The pointer's _objects dict (which prevents GC of the pointed-to object) is lost.
|
||||
# This causes the pointed-to object to be garbage collected, leading to use-after-free.
|
||||
from tinygrad.runtime.support.c import POINTER
|
||||
@record
|
||||
class MaskContext:
|
||||
SIZE = 16
|
||||
value: Annotated[ctypes.c_int, 0]
|
||||
initialized: Annotated[ctypes.c_int, 4]
|
||||
ptr: Annotated[ctypes.c_void_p, 8]
|
||||
@record
|
||||
class Params:
|
||||
SIZE = 16
|
||||
x: Annotated[ctypes.c_int, 0]
|
||||
mask: Annotated[POINTER[MaskContext], 8]
|
||||
init_records()
|
||||
|
||||
src = """
|
||||
struct mask_ctx { int value; int initialized; void *ptr; };
|
||||
void mask_begin(struct mask_ctx *m, int val) { m->value = val; m->initialized = 1; }
|
||||
int mask_end(struct mask_ctx *m) { return m->value + m->initialized; }
|
||||
"""
|
||||
dll = self.compile(src)
|
||||
@dll.bind
|
||||
def mask_begin(m:POINTER[MaskContext], val:ctypes.c_int) -> None: ...
|
||||
@dll.bind
|
||||
def mask_end(m:POINTER[MaskContext]) -> ctypes.c_int: ...
|
||||
|
||||
# When MaskContext() is created inline, it gets garbage collected after the pointer
|
||||
# is stored because only the address bytes are saved, not the _objects reference.
|
||||
params = Params(x=1, mask=ctypes.pointer(MaskContext()))
|
||||
mask_begin(params.mask, 42)
|
||||
result = mask_end(params.mask)
|
||||
self.assertEqual(result, 43) # 42 + 1
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_anonymous_children(self):
|
||||
namespace = self.run_gen("""
|
||||
struct foo {
|
||||
struct {
|
||||
int a,b;
|
||||
} bar;
|
||||
};
|
||||
""")
|
||||
|
||||
self.assertIn('struct_foo', namespace)
|
||||
self.assertIn('struct_foo_bar', namespace)
|
||||
|
||||
@unittest.skipIf(WIN, "doesn't compile on windows")
|
||||
def test_enums(self):
|
||||
namespace = self.run_gen("""
|
||||
enum Foo { A, B, C };
|
||||
enum Bar { X, Y, Z };
|
||||
""")
|
||||
|
||||
assert namespace["A"] == 0
|
||||
assert namespace["B"] == 1
|
||||
assert namespace["C"] == 2
|
||||
assert namespace["X"] == 0
|
||||
assert namespace["Y"] == 1
|
||||
assert namespace["Z"] == 2
|
||||
assert namespace["enum_Foo"].get(0) == "A"
|
||||
assert namespace["enum_Foo"].get(1) == "B"
|
||||
assert namespace["enum_Foo"].get(2) == "C"
|
||||
assert namespace["enum_Bar"].get(0) == "X"
|
||||
assert namespace["enum_Bar"].get(1) == "Y"
|
||||
assert namespace["enum_Bar"].get(2) == "Z"
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.h') as f:
|
||||
f.write(header_content)
|
||||
f.flush()
|
||||
generated_code = gen(name="test_ordering", dll=None, files=[f.name])
|
||||
namespace = {}
|
||||
exec(generated_code, namespace)
|
||||
self.assertIn('struct_A', namespace)
|
||||
self.assertIn('struct_B', namespace)
|
||||
self.assertIn('struct_C', namespace)
|
||||
A, B, C = namespace['struct_A'], namespace['struct_B'], namespace['struct_C']
|
||||
a, b, c = A(), B(), C()
|
||||
self.assertTrue(hasattr(a, 'x'))
|
||||
self.assertTrue(hasattr(a, 'b_ptr'))
|
||||
self.assertTrue(hasattr(b, 'c_ptr'))
|
||||
self.assertTrue(hasattr(c, 'a_ptr'))
|
||||
|
||||
if __name__ == "__main__": unittest.main()
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import unittest
|
||||
import functools
|
||||
from tinygrad import Tensor, Variable, UOp, Context
|
||||
from tinygrad.uop.ops import KernelInfo
|
||||
from tinygrad import Tensor, Variable
|
||||
from tinygrad.engine.schedule import schedule_cache
|
||||
|
||||
def custom_set0_kernel(A:UOp, num:int) -> UOp:
|
||||
return A[0].set(num).sink(arg=KernelInfo(f"custom_set0_{num}"))
|
||||
|
||||
class TestScheduleCache(unittest.TestCase):
|
||||
def test_bound_variable_reuses_cache(self):
|
||||
schedule_cache.clear()
|
||||
@@ -31,33 +26,6 @@ class TestScheduleCache(unittest.TestCase):
|
||||
_, var_vals = t.schedule_with_vars()
|
||||
self.assertEqual(var_vals, {'pos': 42})
|
||||
|
||||
@Context(SPEC=0)
|
||||
def test_custom_kernel(self):
|
||||
for i in range(4):
|
||||
a = Tensor.empty(1)
|
||||
a = Tensor.custom_kernel(a, fxn=functools.partial(custom_set0_kernel, num=i))[0]
|
||||
a.realize()
|
||||
self.assertEqual(a.item(), i)
|
||||
|
||||
@Context(SPEC=0)
|
||||
def test_same_custom_function_reuses_cache(self):
|
||||
schedule_cache.clear()
|
||||
fxn = functools.partial(custom_set0_kernel, num=10)
|
||||
|
||||
# first run
|
||||
a = Tensor.empty(1)
|
||||
a = Tensor.custom_kernel(a, fxn=fxn)[0]
|
||||
a.realize()
|
||||
self.assertEqual(a.item(), 10)
|
||||
cache_size_after_first = len(schedule_cache)
|
||||
|
||||
# second run with same function should reuse cache
|
||||
b = Tensor.empty(1)
|
||||
b = Tensor.custom_kernel(b, fxn=fxn)[0]
|
||||
b.realize()
|
||||
self.assertEqual(b.item(), 10)
|
||||
self.assertEqual(len(schedule_cache), cache_size_after_first)
|
||||
|
||||
def test_simple(self):
|
||||
a = Tensor.ones(10).contiguous()
|
||||
b = Tensor.ones(10).contiguous()
|
||||
|
||||
@@ -126,14 +126,15 @@ class TransformerBlock:
|
||||
v = v.reshape(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2) # (B,KvH,T,Hd)
|
||||
if self.qk_norm == self.head_dim: q, k = self.attn_q_norm(q), self.attn_k_norm(k)
|
||||
|
||||
freqs_cis = precompute_freqs_cis(self.head_dim, self.max_context, self.rope_theta)[start_pos:start_pos+T]
|
||||
# TODO: make UOp have SupportsIndex
|
||||
freqs_cis = precompute_freqs_cis(self.head_dim, self.max_context, self.rope_theta)[start_pos:start_pos+T] # type: ignore
|
||||
q = apply_rope(q, freqs_cis)
|
||||
k = apply_rope(k, freqs_cis)
|
||||
|
||||
# TODO: remove these kv cache realizes
|
||||
if not hasattr(self, "cache_kv"):
|
||||
self.cache_kv = Tensor.zeros(2, B, self.n_kv_heads, self.max_context, self.head_dim, dtype=k.dtype, device=k.device).contiguous().realize()
|
||||
self.cache_kv[:, :, :, start_pos:start_pos+T, :].assign(Tensor.stack(k, v)).realize()
|
||||
self.cache_kv[:, :, :, start_pos:start_pos+T, :].assign(Tensor.stack(k, v)).realize() # type: ignore
|
||||
k = self.cache_kv[0, :, :, 0:start_pos+T, :]
|
||||
v = self.cache_kv[1, :, :, 0:start_pos+T, :]
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ def get_program(ast:UOp, renderer:Renderer, opts:list[Opt]|None=None) -> Program
|
||||
if opts is not None:
|
||||
assert ast.arg is None, "can't apply opts if sink has an arg"
|
||||
ast = ast.replace(arg=KernelInfo(opts_to_apply=tuple(opts)))
|
||||
if ast.arg is None and ast.op is Ops.SINK: ast = ast.replace(arg=KernelInfo())
|
||||
if ast.arg is None: ast = ast.replace(arg=KernelInfo())
|
||||
|
||||
# rewrite to prg
|
||||
if ast.op is Ops.PROGRAM: prg = ast
|
||||
|
||||
+1
-2
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
from typing import Final, ClassVar, Callable, Literal
|
||||
import math, struct, ctypes, functools
|
||||
from dataclasses import dataclass, fields
|
||||
from tinygrad.helpers import getenv, prod, round_up, next_power2, OSX
|
||||
from tinygrad.helpers import getenv, prod, round_up, next_power2
|
||||
from enum import Enum, auto
|
||||
|
||||
class InvalidType:
|
||||
@@ -105,7 +105,6 @@ class ImageDType(PtrDType):
|
||||
def pitch(self):
|
||||
if self._pitch != -1: return self._pitch
|
||||
imgw, imgh, itemsize_log = self.shape[1], self.shape[0], int(math.log2(self.itemsize))
|
||||
if OSX: return round_up(imgw, 256) * 4 * self.itemsize
|
||||
pitchalign = max(6, 11 - int(math.log2(imgh))) if imgh > 1 else 6
|
||||
align_up = max(1, (8 // itemsize_log + 1) - imgh // 32) if pitchalign == 6 else (2 ** (pitchalign - itemsize_log - 2))
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class CompiledRunner(Runner):
|
||||
def __call__(self, rawbufs:list[Buffer], var_vals:dict[str, int]|None=None, wait=False) -> float|None:
|
||||
if var_vals is None: var_vals = {}
|
||||
global_size, local_size = self.p.launch_dims(var_vals)
|
||||
if Device[self.p.device].renderer.has_local and local_size is None and all_int(self.p.global_size):
|
||||
if Device[self.p.device].renderer.has_local and local_size is None and all_int(self.p.global_size): # type: ignore[arg-type]
|
||||
local_size = optimize_local_size(self._prg, global_size, rawbufs)
|
||||
global_size = [g//l if g%l == 0 else g/l for g,l in zip(global_size, local_size)]
|
||||
self.p = replace(self.p, global_size=global_size, local_size=local_size)
|
||||
|
||||
@@ -9,10 +9,6 @@ from tinygrad.engine.realize import ExecItem
|
||||
|
||||
# **** schedule linearizer
|
||||
|
||||
def _unwrap_src(s: UOp) -> UOp:
|
||||
while len(s.src) and s.op not in {Ops.AFTER, Ops.BUFFER, Ops.MSELECT, Ops.MSTACK, Ops.BIND}: s = s.src[0]
|
||||
return s
|
||||
|
||||
def create_schedule(sched_sink:UOp) -> tuple[list[ExecItem], UOp]:
|
||||
with cpu_profile(TracingKey("toposort sched_sink")):
|
||||
# construct the KERNEL children graph based on assigns
|
||||
@@ -26,7 +22,6 @@ def create_schedule(sched_sink:UOp) -> tuple[list[ExecItem], UOp]:
|
||||
k = u.src[1]
|
||||
in_degree.setdefault(k, 0)
|
||||
for s in k.src[0].src if k.op is Ops.END else k.src:
|
||||
s = _unwrap_src(s)
|
||||
if s.op is Ops.AFTER:
|
||||
children.setdefault(s.src[1], []).append(k)
|
||||
in_degree[k] += 1
|
||||
@@ -54,7 +49,7 @@ def create_schedule(sched_sink:UOp) -> tuple[list[ExecItem], UOp]:
|
||||
if k.op is Ops.RANGE: schedule.append(k)
|
||||
elif k.op is Ops.KERNEL:
|
||||
ast = k.arg.ast
|
||||
buf_uops = tuple(_unwrap_src(s).buf_uop for s in k.src if s.op is not Ops.BIND)
|
||||
buf_uops = tuple(s.buf_uop for s in k.src if s.op is not Ops.BIND)
|
||||
bound_ranges = tuple(s for s in k.src if s.op is Ops.BIND and len(s.src) > 1 and s.src[1].op is Ops.RANGE)
|
||||
schedule.append((ast, buf_uops, k.arg.metadata, {}, bound_ranges))
|
||||
if rk.op is Ops.END: schedule.append(rk)
|
||||
|
||||
+7
-1
@@ -389,7 +389,7 @@ def fetch(url:str, name:pathlib.Path|str|None=None, subdir:str|None=None, gunzip
|
||||
fp = _ensure_downloads_dir() / (subdir or "") / ((name or hashlib.md5(url.encode('utf-8')).hexdigest()) + hh + (".gunzip" if gunzip else ""))
|
||||
if not fp.is_file() or not allow_caching:
|
||||
(_dir := fp.parent).mkdir(parents=True, exist_ok=True)
|
||||
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "tinygrad 0.12.0", **headers}), timeout=10) as r:
|
||||
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "tinygrad 0.11.0", **headers}), timeout=10) as r:
|
||||
assert r.status in {200, 206}, r.status
|
||||
length = int(r.headers.get('content-length', 0)) if not gunzip else None
|
||||
readfile = gzip.GzipFile(fileobj=r) if gunzip else r
|
||||
@@ -472,6 +472,12 @@ def mv_address(mv): return ctypes.addressof(ctypes.c_char.from_buffer(mv))
|
||||
def to_char_p_p(options: list[bytes], to_type=ctypes.c_char):
|
||||
return (ctypes.POINTER(to_type) * len(options))(*[ctypes.cast(ctypes.create_string_buffer(o), ctypes.POINTER(to_type)) for o in options])
|
||||
def charptr(s:str|bytes): return ctypes.cast(ctypes.c_char_p(s if isinstance(s, bytes) else s.encode()), ctypes.POINTER(ctypes.c_char))
|
||||
@functools.cache
|
||||
def init_c_struct_t(fields: tuple[tuple[str, type[ctypes._SimpleCData]], ...]):
|
||||
class CStruct(ctypes.Structure):
|
||||
_pack_, _fields_ = 1, fields
|
||||
return CStruct
|
||||
def init_c_var(ctypes_var, creat_cb): return (creat_cb(ctypes_var), ctypes_var)[1]
|
||||
def flat_mv(mv:memoryview): return mv if len(mv) == 0 else mv.cast("B", shape=(mv.nbytes,))
|
||||
|
||||
# *** tqdm
|
||||
|
||||
@@ -361,7 +361,6 @@ required_input_python_consts: dict[str, tuple[int, ...]] = {
|
||||
"Tile": (1,), "Range": (0,1,2), "Expand": (1,), "Reshape": (1,), "Squeeze": (1,), "Unsqueeze": (1,), "Trilu": (1,), "ConstantOfShape": (0,),
|
||||
"CumSum": (1,), "TopK": (1,), "Pad": (1,2,3), "MaxUnpool": (2,), "Dropout": (1,2), "CenterCropPad": (1,), "OneHot": (1,), "Compress": (1,),
|
||||
"ImageDecoder": (0,), "AffineGrid": (1,), "Resize": (1,2,3), "Upsample": (1,), "Split": (1,), "Slice": (1,2,3,4),
|
||||
"HannWindow": (0,), "HammingWindow": (0,), "BlackmanWindow": (0,),
|
||||
**{"Reduce"+r: (1,) for r in ("Max", "Min", "Sum", "Mean", "SumSquare", "Prod", "L1", "L2", "LogSum", "LogSumExp")},
|
||||
**{optim: (1,) for optim in ("Adam", "Adagrad", "Momentum")}
|
||||
}
|
||||
@@ -620,7 +619,6 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
def LeakyRelu(X:Tensor, alpha:float=0.01): return X.leaky_relu(alpha)
|
||||
def ThresholdedRelu(X:Tensor, alpha:float=1.0): return (X > alpha).where(X, 0)
|
||||
def LogSoftmax(x: Tensor, axis:int=-1): return x.log_softmax(axis)
|
||||
def Hardmax(x:Tensor, axis:int=-1): return x.argmax(axis).unsqueeze(axis)._one_hot_along_dim(x.shape[axis], dim=axis).cast(x.dtype)
|
||||
def Binarizer(x:Tensor, threshold:float=0.0): return (x > threshold).float()
|
||||
def Swish(x:Tensor, alpha:float=1.0): return x * (x * alpha).sigmoid()
|
||||
|
||||
@@ -949,9 +947,6 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
if axis is None: axis = [0,2,3]
|
||||
return (x - x.mean(axis, keepdim=True)) / (x.std(axis, keepdim=True, correction=0) + 1e-9)
|
||||
|
||||
def LpNormalization(x:Tensor, axis:int=-1, p:int=2):
|
||||
return x / (x.abs().sum(axis, keepdim=True) if p == 1 else x.square().sum(axis, keepdim=True).sqrt())
|
||||
|
||||
def OneHot(indices:Tensor, depth:float|int|list[int|float], values:Tensor, axis:int=-1):
|
||||
# Scalar or Rank 1 tensor containing exactly one element
|
||||
depth = int(_resolve_const(depth))
|
||||
@@ -978,15 +973,6 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
def dropout_6(data:Tensor, ratio:float=0.5, is_test=0): return dropout_7(data, ratio, training_mode=not is_test)
|
||||
Dropout = {OpSetId(Domain.ONNX, 6):dropout_6, OpSetId(Domain.ONNX, 7):dropout_7}
|
||||
|
||||
def _window(size, output_datatype, periodic, a):
|
||||
size = int(_resolve_const(size))
|
||||
N, n = (size if periodic else size - 1), Tensor.arange(size, requires_grad=False)
|
||||
w = a[0] - a[1] * (n * (2 * math.pi / N)).cos() + a[2] * (n * (4 * math.pi / N)).cos()
|
||||
return w.cast(dtype_fallback(OnnxDataType(output_datatype).to_dtype(), "window op"))
|
||||
def HannWindow(size, output_datatype:int=1, periodic:int=1): return _window(size, output_datatype, periodic, (0.5, 0.5, 0))
|
||||
def HammingWindow(size, output_datatype:int=1, periodic:int=1): return _window(size, output_datatype, periodic, (25/46, 21/46, 0))
|
||||
def BlackmanWindow(size, output_datatype:int=1, periodic:int=1): return _window(size, output_datatype, periodic, (0.42, 0.5, 0.08))
|
||||
|
||||
def LRN(x:Tensor, size:int, alpha:float=1e-4, beta:float=0.75, bias:float=1.0):
|
||||
pooled_x = (x**2).rearrange('b c h w -> b 1 c (h w)').pad((0,0,(size-1)//2, size//2)).avg_pool2d((size, 1), 1)
|
||||
return x / (pooled_x.reshape(x.shape) * alpha + bias).pow(beta)
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Callable, cast, TYPE_CHECKING
|
||||
import functools
|
||||
from dataclasses import dataclass, field
|
||||
from tinygrad.helpers import to_function_name, dedup, prod, DEBUG
|
||||
from tinygrad.uop.ops import Ops, UOp, sym_infer, sint, Variable, ssimplify, GroupOp, PatternMatcher, print_uops, KernelInfo
|
||||
from tinygrad.uop.ops import Ops, UOp, sym_infer, sint, Variable, ssimplify, GroupOp, PatternMatcher, print_uops
|
||||
from tinygrad.dtype import AddrSpace, PtrDType
|
||||
from tinygrad.codegen.opt.tc import TensorCore
|
||||
from tinygrad.codegen.opt import Opt
|
||||
@@ -38,7 +38,6 @@ class Estimates:
|
||||
elif u.op is Ops.IF:
|
||||
dont_count = dont_count.union(u.src[0].toposort())
|
||||
for u in uops:
|
||||
if u.op is Ops.SINK and isinstance(u.arg, KernelInfo) and u.arg.estimates is not None: return u.arg.estimates
|
||||
if u.op in {Ops.LOAD, Ops.STORE}:
|
||||
buf = u
|
||||
while len(buf.src): buf = buf.src[0]
|
||||
|
||||
@@ -46,16 +46,16 @@ def nir_instr(nc=1, bs=lambda: None, intrins=None, srcs=None, has_def=True, df=N
|
||||
def go(g): return g(**{nm: ba.arguments[nm] for nm in inspect.signature(g).parameters}) if callable(g) else g
|
||||
|
||||
instr = f(*args, **kwargs)
|
||||
if has_def: mesa.nir_def_init(instr.contents.instr, instr.contents._def, go(nc), go(bs))
|
||||
if has_def: mesa.nir_def_init(instr.contents.instr, getattr(instr.contents, "def"), go(nc), go(bs))
|
||||
for k, v in go(intrins or {}).items():
|
||||
idx = mesa.nir_intrinsic_infos[instr.contents.intrinsic].index_map[g(f"NIR_INTRINSIC_{k}")]
|
||||
idx = mesa.nir_intrinsic_infos[instr.contents.intrinsic.value].index_map[g(f"NIR_INTRINSIC_{k}")]
|
||||
assert idx > 0, "invalid intrinsic. mesa version mismatch?"
|
||||
instr.contents.const_index[idx - 1] = go(v)
|
||||
for i, src in enumerate(go(srcs or [])): ctypes.cast(instr.contents.src, ctypes.POINTER(mesa.nir_src))[i] = go(src)
|
||||
for k,v in {k:vcomp for k,v in contents.items() if (vcomp:=go(v)) is not None}.items(): setattr(instr.contents, k, go(v))
|
||||
mesa.nir_builder_instr_insert(ba.arguments['b'], instr.contents.instr)
|
||||
go(also)
|
||||
return instr.contents._def if has_def else (mesa.nir_def() if df is None else go(df))
|
||||
return getattr(instr.contents, "def") if has_def else (mesa.nir_def() if df is None else go(df))
|
||||
return wrapper
|
||||
return dec
|
||||
|
||||
@@ -73,7 +73,7 @@ def nimm_set(imm:mesa.nir_def, x, dtype:DType):
|
||||
|
||||
@nir_instr(nc=1, bs=lambda dtype: dtype.bitsize)
|
||||
def nimm(b:mesa.nir_builder, x, dtype:DType) -> mesa.nir_def:
|
||||
nimm_set((instr:=mesa.nir_load_const_instr_create(b.shader, 1, dtype.bitsize)).contents._def, x, dtype)
|
||||
nimm_set(getattr((instr:=mesa.nir_load_const_instr_create(b.shader, 1, dtype.bitsize)).contents, "def"), x, dtype)
|
||||
return instr
|
||||
@nir_instr(nc=1, bs=lambda dtype: dtype.bitsize)
|
||||
def nundef(b, dtype): return mesa.nir_undef_instr_create(b.shader, 1, dtype.bitsize)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user