Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

3315 lines
119 KiB
Plaintext

// Copyright (c) Advanced Micro Devices, Inc.
// SPDX-License-Identifier: MIT
/**
* GPU Mega Kernel Unit Test
*
* This file contains a comprehensive unit test kernel that exercises
* most of the instructions/features available on AMD Instinct GPUs.
*
* SUPPORTED ARCHITECTURES:
* ========================
* - MI250/MI250X (CDNA2, gfx90a):
* - FP32/FP64/FP16/BF16, INT32 operations
* - Hardware FP64 atomics (add, min, max)
* - MFMA matrix operations (FP16/BF16/INT8)
* - Unified s_waitcnt instruction
*
* - MI300/MI300X/MI325X (CDNA3, gfx942):
* - All MI250 features
* - FP8/BF8 conversions (cvt_pk_fp8/bf8, CDNA3 FNUZ-style packed formats)
* - TF32 MFMA, Packed FP32
* - Sparse MFMA (FP16/BF16/FP8/INT8)
* - Async LDS (global_load_async_to_lds - if available)
* - Unified s_waitcnt instruction
*
* - MI350X/MI355X (CDNA4, gfx950):
* - All MI300 features
* - FP6/FP4 conversions and MFMA
* - Scaled FP8 conversions
* - Async LDS operations
* - Unified s_waitcnt instruction
*
* - Strix / Strix Halo / Krackan (RDNA 3.5 gfx115x) APU iGPU:
* - Wave32 native, WMMA (FP16/BF16/INT8), VOPD same-wave dual-issue VALU
* - No CDNA-style FP8/BF8 cvt builtins; scalar FP in scalar unit; s_singleuse_vdst hint
*
* - RDNA4 Consumer (gfx1200/gfx1201 - RX 9060 Series, RX 9070 Series):
* - FP8/BF8 OCP format conversions
* - WMMA matrix operations (FP16/BF16/INT8)
* - Async LDS operations
* - AI inference accelerators
* - Unified s_waitcnt instruction (still available)
*
* Features tested:
* - Warp/Wave operations (shuffle, ballot, permute)
* - FP16/BF16 packed operations
* - FP32/FP64 operations
* - Atomic operations (global, LDS, flat)
* - LDS operations (sync and async)
* - Memory operations (VMEM comprehensive tests)
* - Transcendental functions
* - Integer operations
* - DOT product operations
* - MFMA operations (CDNA) / WMMA operations (RDNA4 / RDNA 3.5 where supported)
*
* Copyright (c) 2026 - For testing purposes
*/
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <hip/hip_bf16.h>
#include <hip/hip_fp16.h>
#include <hip/hip_runtime.h>
#include "atomic_global_buffers.h"
#include "mega_kernel_device_arch.h"
#include "mega_kernel_types.h"
// ============================================================================
// Wait Instruction Compatibility Macros
// ============================================================================
//
// WAITCNT INSTRUCTION CHANGES ON RDNA4 / GFX12+ (e.g. gfx1200/gfx1201):
// ====================================================================
// The unified s_waitcnt instruction is REMOVED on RDNA4 consumer GPUs and replaced
// with split wait instructions (load/store/kmcnt/dscnt/expcnt families).
//
// OLD (pre-GFX12 / unified wait): NEW (GFX12 RDNA4, e.g. gfx1201):
// --------------------- -----------------------
// s_waitcnt vmcnt(0) --> s_wait_loadcnt 0 + s_wait_storecnt 0
// s_waitcnt lgkmcnt(0) --> s_wait_kmcnt 0 + s_wait_dscnt 0
// s_waitcnt expcnt(0) --> s_wait_expcnt 0
//
// BUILTIN EQUIVALENTS:
// --------------------
// __builtin_amdgcn_s_waitcnt(0) --> Old unified wait
// __builtin_amdgcn_s_wait_loadcnt(0) --> Wait for vector memory loads
// __builtin_amdgcn_s_wait_storecnt(0) --> Wait for vector memory stores
// __builtin_amdgcn_s_wait_kmcnt(0) --> Wait for scalar memory
// __builtin_amdgcn_s_wait_dscnt(0) --> Wait for LDS operations
// __builtin_amdgcn_s_wait_expcnt(0) --> Wait for exports
//
// USAGE PATTERNS:
// ---------------
// 1. Wait for all vector memory (load + store):
// WAITCNT_VMEM_ALL()
//
// 2. Wait for LDS + scalar memory:
// WAITCNT_LDS_SCALAR()
//
// 3. Wait for everything:
// WAITCNT_ALL()
//
// 4. Inline assembly wait for vector memory:
// ASM_WAITCNT_VMEM
//
// 5. Inline assembly wait for LDS:
// ASM_WAITCNT_LDS
//
#if 0 // defined() placeholder for future architecture
// GFX12+ ONLY: s_waitcnt is REMOVED, use split wait instructions
# define WAITCNT_VMEM_ALL() \
do \
{ \
__builtin_amdgcn_s_wait_loadcnt(0); \
__builtin_amdgcn_s_wait_storecnt(0); \
} while(0)
// Wait for LDS + scalar memory (kmcnt + dscnt)
# define WAITCNT_LDS_SCALAR() \
do \
{ \
__builtin_amdgcn_s_wait_kmcnt(0); \
__builtin_amdgcn_s_wait_dscnt(0); \
} while(0)
// Wait for all outstanding operations: VMEM (load/store) + scalar + LDS
# define WAITCNT_ALL() \
do \
{ \
__builtin_amdgcn_s_wait_loadcnt(0); \
__builtin_amdgcn_s_wait_storecnt(0); \
__builtin_amdgcn_s_wait_kmcnt(0); \
__builtin_amdgcn_s_wait_dscnt(0); \
} while(0)
// Inline assembly versions for GFX12+ (split waits with 0x0 syntax)
# define ASM_WAITCNT_VMEM "s_wait_loadcnt 0x0\n\ts_wait_storecnt 0x0"
# define ASM_WAITCNT_LDS "s_wait_kmcnt 0x0\n\ts_wait_dscnt 0x0"
# define ASM_WAITCNT_ALL \
"s_wait_loadcnt 0x0\n\ts_wait_storecnt 0x0\n\ts_wait_kmcnt 0x0\n\ts_wait_dscnt " \
"0x0"
#else
// All other architectures (CDNA, RDNA 3.5, and pre-GFX12 RDNA): unified s_waitcnt
# define WAITCNT_VMEM_ALL() __builtin_amdgcn_s_waitcnt(0)
# define WAITCNT_LDS_SCALAR() __builtin_amdgcn_s_waitcnt(0)
# define WAITCNT_ALL() __builtin_amdgcn_s_waitcnt(0)
// Inline assembly versions for architectures with unified s_waitcnt
# define ASM_WAITCNT_VMEM "s_waitcnt vmcnt(0)"
# define ASM_WAITCNT_LDS "s_waitcnt lgkmcnt(0)"
# define ASM_WAITCNT_ALL "s_waitcnt vmcnt(0) lgkmcnt(0)"
#endif
// Helper: Tolerance-based float comparison
__device__ inline bool
approx_equal_f32(float a, float b, float tol = 1e-5f)
{
return fabsf(a - b) < tol * (fabsf(a) + fabsf(b) + 1.0f);
}
__device__ inline bool
approx_equal_f64(double a, double b, double tol = 1e-10)
{
return fabs(a - b) < tol * (fabs(a) + fabs(b) + 1.0);
}
// ============================================================================
// Section 1: Warp/Wave Operations
// ============================================================================
__device__ void
test_warp_shuffle(TestResults* results, int lane_id)
{
int value = lane_id * 10;
// Test __shfl - broadcast from lane 0
int shuffled = __shfl(value, 0, warpSize);
bool passed = (shuffled == 0); // lane 0 has value 0
// Test __shfl_xor - butterfly reduction pattern
int xor_result = __shfl_xor(value, 1, warpSize);
passed = passed && (xor_result == ((lane_id ^ 1) * 10));
// Test __shfl_up
int up_result = __shfl_up(value, 1, warpSize);
if(lane_id > 0)
{
passed = passed && (up_result == (lane_id - 1) * 10);
}
// Test __shfl_down
int down_result = __shfl_down(value, 1, warpSize);
if(lane_id < warpSize - 1)
{
passed = passed && (down_result == (lane_id + 1) * 10);
}
if(lane_id == 0)
{
atomicAdd(&results->warp_shuffle_passed, passed ? 1 : 0);
}
}
__device__ void
test_warp_ballot(TestResults* results, int lane_id)
{
// Test ballot - all even lanes vote true
unsigned long long ballot_result = __ballot(lane_id % 2 == 0);
// Expected: 0x5555555555555555 for wave64, 0x55555555 for wave32
unsigned long long expected =
(warpSize == 64) ? 0x5555555555555555ULL : 0x55555555ULL;
bool passed = (ballot_result == expected);
// Test __any
int any_result = __any(lane_id == 0);
passed = passed && (any_result != 0);
// Test __all
int all_result = __all(lane_id >= 0);
passed = passed && (all_result != 0);
if(lane_id == 0)
{
atomicAdd(&results->warp_ballot_passed, passed ? 1 : 0);
}
}
__device__ void
test_warp_permute(TestResults* results, int lane_id)
{
int value = lane_id * 7;
// Test ds_bpermute - read from specific lane
int target_lane = (lane_id + 1) % warpSize;
int permuted = __builtin_amdgcn_ds_bpermute(target_lane << 2, value);
bool passed = (permuted == target_lane * 7);
// Test ds_permute
int permuted2 = __builtin_amdgcn_ds_permute(lane_id << 2, value);
passed = passed && (permuted2 == lane_id * 7);
if(lane_id == 0)
{
atomicAdd(&results->warp_permute_passed, passed ? 1 : 0);
}
}
__device__ void
test_warp_reduce(TestResults* results, int lane_id)
{
// Warp-level sum reduction using shuffle
int value = 1; // Each lane contributes 1
for(int offset = warpSize / 2; offset > 0; offset /= 2)
{
value += __shfl_xor(value, offset, warpSize);
}
// All lanes should have the sum (warpSize)
bool passed = (value == warpSize);
if(lane_id == 0)
{
atomicAdd(&results->warp_reduce_passed, passed ? 1 : 0);
}
}
// ============================================================================
// Section 2: FP8/BF8 Conversion Operations (CDNA / RDNA4; RDNA 3.5 gfx115x bypassed)
// ============================================================================
__device__ void
test_fp8_conversions(TestResults* results, int lane_id)
{
bool passed = true;
#if defined(__gfx950__) // MI350 (gfx950/CDNA4): scaled FP8 E4M3 conversions
float test_val = 1.5f;
// Convert float to FP8 and back
uint32_t fp8_packed = 0;
// Use builtin to convert F32 to packed FP8
fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false);
// Convert back to F32
float2 converted;
asm volatile("v_cvt_pk_f32_fp8 %0, %1" : "=v"(converted) : "v"(fp8_packed));
// Check if round-trip is approximately correct (FP8 has limited precision)
passed = passed && (fabsf(converted.x - test_val) < 0.1f);
#elif defined(__gfx1200__) || defined(__gfx1201__)
// RDNA4 Consumer (RX 9060 / RX 9070 families): FP8 OCP format support
float test_val = 1.5f;
uint32_t fp8_packed = 0;
// Use builtin for FP8 conversion (OCP format on RDNA4)
fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false);
// Verify non-zero output
passed = (fp8_packed != 0);
#elif ARCH_RDNA35_APU
// RDNA 3.5 APU (Strix): NO FP8 conversion builtins - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->fp8_convert_passed, -1);
}
return;
#elif defined(__gfx942__)
// MI300 (gfx942/CDNA3): FP8 E4M3 via cvt_pk_fp8_f32 (FNUZ-style packed FP8)
float test_val = 1.5f;
uint32_t fp8_packed = 0;
fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false);
// Simplified test - verify conversion produces non-zero output
passed = (fp8_packed != 0);
#elif defined(__gfx90a__)
// MI250 (gfx90a/CDNA2): NO FP8 support - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->fp8_convert_passed, -1);
}
return;
#else
// Fallback for unknown architectures - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->fp8_convert_passed, -1);
}
return;
#endif
if(lane_id == 0)
{
atomicAdd(&results->fp8_convert_passed, passed ? 1 : 0);
}
}
__device__ void
test_bf8_conversions(TestResults* results, int lane_id)
{
bool passed = true;
#if defined(__gfx950__) // MI350 (gfx950/CDNA4): BF8 E5M2 conversions
float test_val = 2.0f;
uint32_t bf8_packed = 0;
// Convert float to BF8
bf8_packed = __builtin_amdgcn_cvt_pk_bf8_f32(test_val, test_val, bf8_packed, false);
// Verify non-zero result
passed = (bf8_packed != 0);
#elif defined(__gfx1200__) || defined(__gfx1201__)
// RDNA4 Consumer (RX 9060 / RX 9070 families): BF8 OCP format support
float test_val = 2.0f;
uint32_t bf8_packed = 0;
// Convert float to BF8
bf8_packed = __builtin_amdgcn_cvt_pk_bf8_f32(test_val, test_val, bf8_packed, false);
// Verify non-zero result
passed = (bf8_packed != 0);
#elif ARCH_RDNA35_APU
// RDNA 3.5 APU (Strix): NO BF8 conversion builtins - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->bf8_convert_passed, -1);
}
return;
#elif defined(__gfx942__)
// MI300 (gfx942/CDNA3): BF8 E5M2 support via cvt_pk_bf8_f32
float test_val = 2.0f;
uint32_t bf8_packed = 0;
bf8_packed = __builtin_amdgcn_cvt_pk_bf8_f32(test_val, test_val, bf8_packed, false);
// Verify non-zero result
passed = (bf8_packed != 0);
#elif defined(__gfx90a__)
// MI250 (gfx90a/CDNA2): NO BF8 support - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->bf8_convert_passed, -1);
}
return;
#else
// Fallback for unknown architectures - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->bf8_convert_passed, -1);
}
return;
#endif
if(lane_id == 0)
{
atomicAdd(&results->bf8_convert_passed, passed ? 1 : 0);
}
}
// ============================================================================
// Section 3: FP16/BF16 Operations
// ============================================================================
__device__ void
test_fp16_operations(TestResults* results, int lane_id)
{
_Float16 a = (_Float16) 1.5f;
_Float16 b = (_Float16) 2.5f;
// Arithmetic
_Float16 sum = a + b;
_Float16 diff = b - a;
_Float16 prod = a * b;
_Float16 quot = b / a;
bool passed = true;
passed = passed && (fabsf((float) sum - 4.0f) < 0.01f);
passed = passed && (fabsf((float) diff - 1.0f) < 0.01f);
passed = passed && (fabsf((float) prod - 3.75f) < 0.01f);
passed = passed && (fabsf((float) quot - 1.666f) < 0.01f);
if(lane_id == 0)
{
atomicAdd(&results->fp16_convert_passed, passed ? 1 : 0);
}
}
__device__ void
test_bf16_operations(TestResults* results, int lane_id)
{
__hip_bfloat16 a = __float2bfloat16(1.5f);
__hip_bfloat16 b = __float2bfloat16(2.5f);
// Convert back and check
float fa = __bfloat162float(a);
float fb = __bfloat162float(b);
bool passed = true;
passed = passed && (fabsf(fa - 1.5f) < 0.01f);
passed = passed && (fabsf(fb - 2.5f) < 0.01f);
if(lane_id == 0)
{
atomicAdd(&results->bf16_convert_passed, passed ? 1 : 0);
}
}
// ============================================================================
// Section 4: FP32/FP64 Arithmetic Operations
// ============================================================================
__device__ void
test_fp32_arithmetic(TestResults* results, int lane_id)
{
float a = 3.14159f;
float b = 2.71828f;
float sum = a + b;
float diff = a - b;
float prod = a * b;
(void) prod; // Used to verify computation happens
// FMA operation
float fma_result = __fmaf_rn(a, b, 1.0f);
float expected_fma = a * b + 1.0f;
bool passed = true;
passed = passed && approx_equal_f32(sum, 5.85987f);
passed = passed && approx_equal_f32(diff, 0.42331f);
passed = passed && approx_equal_f32(fma_result, expected_fma);
// Test special values
float inf_val = __int_as_float(0x7F800000); // +Inf
float nan_val = __int_as_float(0x7FC00000); // NaN
passed = passed && isinf(inf_val);
passed = passed && isnan(nan_val);
if(lane_id == 0)
{
atomicAdd(&results->fp32_arith_passed, passed ? 1 : 0);
}
}
__device__ void
test_fp64_arithmetic(TestResults* results, int lane_id)
{
double a = 3.141592653589793;
double b = 2.718281828459045;
double sum = a + b;
double diff = a - b;
(void) (a * b); // Verify mul happens
(void) (a / b); // Verify div happens
// FMA operation
double fma_result = __fma_rn(a, b, 1.0);
double expected_fma = a * b + 1.0;
bool passed = true;
passed = passed && approx_equal_f64(sum, 5.859874482048838);
passed = passed && approx_equal_f64(diff, 0.423310825130748);
passed = passed && approx_equal_f64(fma_result, expected_fma);
if(lane_id == 0)
{
atomicAdd(&results->fp64_arith_passed, passed ? 1 : 0);
}
}
// ============================================================================
// Section 5: Integer Operations
// ============================================================================
__device__ void
test_int32_operations(TestResults* results, int lane_id)
{
int a = 12345;
int b = 6789;
int sum = a + b;
int diff = a - b;
int prod = a * b;
int quot = a / b;
int rem = a % b;
// Bit operations - verify they execute
volatile int and_val = a & b;
volatile int or_val = a | b;
volatile int xor_val = a ^ b;
volatile int not_val = ~a;
int shl = a << 4;
volatile int shr = a >> 4;
// Population count and leading zeros - verify they execute
volatile int popcount = __popc(a);
volatile int clz_val = __clz(a);
// Use volatile results to prevent optimization
(void) and_val;
(void) or_val;
(void) xor_val;
(void) not_val;
(void) shr;
(void) popcount;
(void) clz_val;
bool passed = true;
passed = passed && (sum == 19134);
passed = passed && (diff == 5556);
passed = passed && (prod == 83810205);
passed = passed && (quot == 1);
passed = passed && (rem == 5556);
passed = passed && (shl == 197520);
if(lane_id == 0)
{
atomicAdd(&results->int32_arith_passed, passed ? 1 : 0);
}
}
__device__ void
test_int64_operations(TestResults* results, int lane_id)
{
// Use simpler values that are less likely to have precision issues
long long a = 100000000000LL; // 100 billion
long long b = 12345678LL;
// Basic arithmetic
long long sum = a + b;
long long diff = a - b;
long long prod = (long long) lane_id * 1000000LL;
bool passed = true;
// Check sum: 100000000000 + 12345678 = 100012345678
passed = passed && (sum == 100012345678LL);
// Check diff: 100000000000 - 12345678 = 99987654322
passed = passed && (diff == 99987654322LL);
// Check that product is non-negative (basic sanity)
passed = passed && (prod >= 0);
// Use volatile to prevent optimization
volatile long long sink = sum + diff + prod;
(void) sink;
if(lane_id == 0)
{
atomicAdd(&results->int64_arith_passed, passed ? 1 : 0);
}
}
// ============================================================================
// Section 6: Atomic Operations (HW FP64 atomics: CDNA2+ gfx90a/gfx942/gfx950;
// bypassed on RDNA consumer gfx12 and RDNA 3.5 gfx115x — no global/DS FP64 atomics)
// ============================================================================
__device__ void
test_atomic_add_f32(float* global_mem, TestResults* results, int lane_id)
{
// Each lane atomically adds 1.0
(void) atomicAdd(global_mem, 1.0f); // Ignore old value
__syncthreads();
if(lane_id == 0)
{
// After all threads in block add, value should be >= blockDim.x
bool passed = (*global_mem >= (float) blockDim.x);
atomicAdd(&results->atomic_add_f32_passed, passed ? 1 : 0);
}
}
__device__ void
test_atomic_add_f64([[maybe_unused]] double* global_mem, TestResults* results,
int lane_id)
{
#if defined(__gfx90a__) || defined(__gfx942__) || \
defined(__gfx950__) // CDNA architectures: Each lane atomically adds 1.0 using
// hardware FP64 atomics
(void) atomicAdd(global_mem, 1.0); // Ignore old value
__syncthreads();
if(lane_id == 0)
{
bool passed = (*global_mem >= (double) blockDim.x);
atomicAdd(&results->atomic_add_f64_passed, passed ? 1 : 0);
}
#else
// RDNA4 consumer GPUs don't have hardware FP64 atomics - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->atomic_add_f64_passed, -1);
}
#endif
}
__device__ void
test_atomic_min_max_f32(TestResults* results, int lane_id)
{
__shared__ unsigned int shared_atomic_min_max_f32;
float my_val = (float) lane_id + 1.0f;
// ---------------------------------------------------------------------
// Atomic MIN test: all lanes compete to set the minimum
// Use shared memory so each block gets an independent scratch value.
if(threadIdx.x == 0)
{
shared_atomic_min_max_f32 = __float_as_uint(1.0e9f);
}
__syncthreads();
(void) atomicMin(&shared_atomic_min_max_f32, __float_as_uint(my_val));
__syncthreads();
if(lane_id == 0)
{
// Minimum should be 1.0 (from lane 0)
float min_val = __uint_as_float(shared_atomic_min_max_f32);
bool min_passed = fabsf(min_val - 1.0f) < 1.0e-6f;
atomicAdd(&results->atomic_min_f32_passed, min_passed ? 1 : 0);
}
// ---------------------------------------------------------------------
// Atomic MAX test: all threads in block compete to set the maximum
// Reuse the same per-block shared scratch value for the max phase.
if(threadIdx.x == 0)
{
shared_atomic_min_max_f32 = __float_as_uint(0.0f);
}
__syncthreads();
(void) atomicMax(&shared_atomic_min_max_f32, __float_as_uint(my_val));
__syncthreads();
if(threadIdx.x == 0)
{
float max_val = __uint_as_float(shared_atomic_min_max_f32);
// All threads in block participate with my_val = lane_id + 1
// Since lane_id = threadIdx.x % warpSize, and warpSize = 32:
// Maximum my_val across all threads is warpSize (when lane_id = 31)
// Verify max_val is at least 1.0 and at most warpSize
bool max_passed = (max_val >= 1.0f) && (max_val <= (float) warpSize + 0.1f);
atomicAdd(&results->atomic_max_f32_passed, max_passed ? 1 : 0);
}
}
__device__ void
test_atomic_int(int* global_mem, TestResults* results, int lane_id)
{
// Test various integer atomics - use volatile to ensure execution
(void) atomicAdd(global_mem, 1);
(void) atomicSub(global_mem + 1, 1);
(void) atomicAnd(global_mem + 2, 0xFF);
(void) atomicOr(global_mem + 3, lane_id);
(void) atomicXor(global_mem + 4, 1);
(void) atomicMin(global_mem + 5, lane_id);
(void) atomicMax(global_mem + 6, lane_id);
__syncthreads();
if(lane_id == 0)
{
bool passed = true; // Verify no crash and results make sense
passed = passed && (global_mem[0] >= (int) blockDim.x); // All lanes added 1
atomicAdd(&results->atomic_int_passed, passed ? 1 : 0);
}
}
// ============================================================================
// Section 6.5: Atomic Operations - Comprehensive Inline ASM Tests
// ============================================================================
// Global buffer sizes and slot layout must match main.cpp / atomic_global_buffers.h.
//
// AMD GPU ATOMIC INSTRUCTION TYPES:
// ==================================
// This section tests all atomic operation types using inline assembly to
// verify the hardware atomic paths are functioning correctly.
//
// ATOMIC INSTRUCTION CATEGORIES:
// ------------------------------
//
// 1. GLOBAL ATOMICS (global_atomic_*):
// Integer: global_atomic_add, global_atomic_sub, global_atomic_and,
// global_atomic_or, global_atomic_xor, global_atomic_swap,
// global_atomic_smin, global_atomic_smax, global_atomic_umin,
// global_atomic_umax, global_atomic_inc, global_atomic_dec
// Float: global_atomic_add_f32, global_atomic_add_f64,
// global_atomic_min_f64, global_atomic_max_f64
// Packed: global_atomic_pk_add_f16, global_atomic_pk_add_bf16
// CAS: global_atomic_cmpswap, global_atomic_cmpswap_x2
//
// 2. FLAT ATOMICS (flat_atomic_*):
// Same operations as global, but with unified addressing
//
// 3. DS (LDS) ATOMICS (ds_*):
// Integer: ds_add_u32/u64, ds_sub_u32, ds_and_b32/b64, ds_or_b32/b64,
// ds_xor_b32/b64, ds_min_i32/i64/u32/u64, ds_max_i32/i64/u32/u64,
// ds_inc_u32, ds_dec_u32
// Float: ds_add_f32/f64, ds_min_f32/f64, ds_max_f32/f64
// CAS: ds_cmpst_b32/b64
//
// ARCHITECTURE ATOMIC SUPPORT:
// ----------------------------
// | Atomic Type | gfx90a | gfx942 | gfx950 | gfx115x | gfx1200/gfx1201 |
// |--------------------------|--------|--------|--------|---------|-----------------|
// | INT32/INT64 atomics | ✅ | ✅ | ✅ | ✅ | ✅ |
// | FP32 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ |
// | FP64 atomic add/min/max | ✅ | ✅ | ✅ | ❌ | ❌ |
// | Packed FP16 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ |
// | Packed BF16 atomic add | ❌ | ✅ | ✅ | ✅ | ✅ |
// | DS FP32 atomics | ✅ | ✅ | ✅ | ✅ | ✅ |
// | DS FP64 atomics | ✅ | ✅ | ✅ | ❌ | ❌ |
//
// NAMING CONVENTION:
// ------------------
// CDNA (gfx9x): global_atomic_add, flat_atomic_add (HIP atomics in this sample for gfx115x too)
// RDNA4 (gfx120x): global_atomic_add_u32, flat_atomic_add_u32 (explicit type suffix in asm paths)
//
// Shared memory for DS atomic tests
__shared__ int ds_atomic_int_test[64];
__shared__ float ds_atomic_f32_test[64];
__shared__ double ds_atomic_f64_test[32];
__device__ void
test_atomic_operations_asm([[maybe_unused]] int* global_int_ptr,
[[maybe_unused]] float* global_f32_ptr,
[[maybe_unused]] double* global_f64_ptr,
[[maybe_unused]] TestResults* results,
[[maybe_unused]] int lane_id, [[maybe_unused]] int tid)
{
bool global_int_passed = true;
bool global_f32_passed = true;
[[maybe_unused]] bool global_f64_passed = true;
bool flat_int_passed = true;
bool ds_int_passed = true;
bool ds_f32_passed = true;
[[maybe_unused]] bool ds_f64_passed = true;
bool pk_f16_passed = true;
bool pk_bf16_passed = true;
bool cas_passed = true;
// ========================================================================
// TEST 1: GLOBAL INTEGER ATOMICS
// ========================================================================
// global_atomic_add/sub/and/or/xor/min/max/swap
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU
// CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs)
{
int old_val = atomicAdd(&global_int_ptr[tid % 64], 1);
global_int_passed = (old_val >= 0);
atomicMax(&global_int_ptr[(tid % 64) + 64], tid);
}
#elif defined(__gfx1200__) || \
defined(__gfx1201__) // RDNA4: global_atomic_add_u32 vdst:opt, vaddr, vdata, off
// [th:TH_ATOMIC_RETURN]
{
int old_val = 0;
int add_val = 1;
// RDNA4 uses explicit type suffix and different return syntax
asm volatile("global_atomic_add_u32 %0, %1, %2, off "
"th:TH_ATOMIC_RETURN\n\t" ASM_WAITCNT_VMEM
: "=v"(old_val)
: "v"(&global_int_ptr[tid % 64]), "v"(add_val)
: "memory");
global_int_passed = (old_val >= 0);
// global_atomic_max_i32 (signed max)
int max_val = tid;
asm volatile("global_atomic_max_i32 %0, %1, off\n\t" ASM_WAITCNT_VMEM
:
: "v"(&global_int_ptr[(tid % 64) + 64]), "v"(max_val)
: "memory");
}
#else
global_int_passed = true; // Unknown arch - skip
#endif
// ========================================================================
// TEST 2: GLOBAL FP32 ATOMICS
// ========================================================================
// global_atomic_add_f32 - supported on all target architectures
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU
// CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs)
{
float old_val = atomicAdd(&global_f32_ptr[tid % 64], 1.0f);
global_f32_passed = global_f32_passed && isfinite(old_val);
}
#elif defined(__gfx1200__) || \
defined(__gfx1201__) // RDNA4: global_atomic_add_f32 vdst:opt, vaddr, vdata, off
{
float old_val = 0.0f;
float add_val = 1.0f;
asm volatile("global_atomic_add_f32 %0, %1, %2, off "
"th:TH_ATOMIC_RETURN\n\t" ASM_WAITCNT_VMEM
: "=v"(old_val)
: "v"(&global_f32_ptr[tid % 64]), "v"(add_val)
: "memory");
global_f32_passed = global_f32_passed && isfinite(old_val);
}
#else
global_f32_passed = true;
#endif
// ========================================================================
// TEST 3: GLOBAL FP64 ATOMICS (CDNA2+ only; gfx90a/gfx942/gfx950)
// ========================================================================
// global_atomic_add_f64, global_atomic_min_f64, global_atomic_max_f64
// Available on: gfx90a, gfx942, gfx950,
// NOT available on: gfx1200, gfx1201 (RDNA4 consumer)
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__)
// CDNA: Use HIP atomics for FP64 (inline ASM too complex)
{
double old_val = unsafeAtomicAdd(&global_f64_ptr[tid % 32], 1.0);
global_f64_passed = global_f64_passed && isfinite(old_val);
// Min/max also via HIP
unsafeAtomicMin(&global_f64_ptr[(tid % 32) + 32], 1.0);
}
#elif 0 // defined() placeholder for future architecture
// Future arch: FP64 atomics via global_atomic_add_f64
{
double old_val = 0.0;
double add_val = 1.0;
asm volatile("global_atomic_add_f64 %0, %1, %2, off "
"th:TH_ATOMIC_RETURN\n\t" ASM_WAITCNT_VMEM
: "=v"(old_val)
: "v"(&global_f64_ptr[tid % 32]), "v"(add_val)
: "memory");
global_f64_passed = global_f64_passed && isfinite(old_val);
}
#elif defined(__gfx1200__) || defined(__gfx1201__)
// RDNA4 consumer (gfx12): no hardware FP64 global atomics — bypass
if(lane_id == 0 && tid < 32)
{
atomicMin(&results->atomic_global_f64_passed, -1);
}
global_f64_passed = true; // Don't count as failure
#elif ARCH_RDNA35_APU
// RDNA 3.5 APU (gfx115x): no hardware FP64 global atomics — bypass
if(lane_id == 0 && tid < 32)
{
atomicMin(&results->atomic_global_f64_passed, -1);
}
global_f64_passed = true; // Don't count as failure
#else
global_f64_passed = true;
#endif
// ========================================================================
// TEST 4: FLAT INTEGER ATOMICS
// ========================================================================
// flat_atomic_add/sub/min/max - unified addressing
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU
// CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs)
{
int old_val = atomicAdd(&global_int_ptr[(tid % 64) + 128], 1);
flat_int_passed = (old_val >= 0);
}
#elif defined(__gfx1200__) || \
defined(__gfx1201__) // RDNA4: flat_atomic_add_u32 vdst:opt, vaddr, vdata
{
int old_val = 0;
int add_val = 1;
asm volatile(
"flat_atomic_add_u32 %0, %1, %2 th:TH_ATOMIC_RETURN\n\t" ASM_WAITCNT_ALL
: "=v"(old_val)
: "v"(&global_int_ptr[(tid % 64) + 128]), "v"(add_val)
: "memory");
flat_int_passed = (old_val >= 0);
}
#else
flat_int_passed = true;
#endif
// ========================================================================
// TEST 5: DS (LDS) INTEGER ATOMICS
// ========================================================================
// ds_add_u32, ds_min_i32, ds_max_u32, ds_and_b32, ds_or_b32
// Same syntax on all architectures
//
{
// Initialize LDS
if(tid < 64)
{
ds_atomic_int_test[tid] = 0;
}
__syncthreads();
// Use HIP atomics for LDS instead of inline ASM to avoid pointer issues
int old_val = atomicAdd(&ds_atomic_int_test[tid % 64], 1);
ds_int_passed = (old_val >= 0);
atomicMin(&ds_atomic_int_test[tid % 64], tid);
atomicMax((unsigned int*) &ds_atomic_int_test[tid % 64], (unsigned int) tid);
__syncthreads();
}
// ========================================================================
// TEST 6: DS (LDS) FP32 ATOMICS
// ========================================================================
// ds_add_f32, ds_min_f32, ds_max_f32
//
{
if(tid < 64)
{
ds_atomic_f32_test[tid] = 0.0f;
}
__syncthreads();
// Use HIP atomics for LDS FP32
atomicAdd(&ds_atomic_f32_test[tid % 64], 1.0f);
// Min/max via reinterpret cast
float min_f32_val = (float) tid * 0.1f;
atomicMin((unsigned int*) &ds_atomic_f32_test[tid % 64],
__float_as_uint(min_f32_val));
__syncthreads();
ds_f32_passed = !isnan(ds_atomic_f32_test[tid % 64]);
}
// ========================================================================
// TEST 7: DS (LDS) FP64 ATOMICS (CDNA2+ only; gfx90a/gfx942/gfx950)
// ========================================================================
// ds_add_f64, ds_min_f64, ds_max_f64
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__)
{
if(tid < 32)
{
ds_atomic_f64_test[tid] = 0.0;
}
__syncthreads();
// Use unsafeAtomicAdd for LDS FP64 (CDNA architectures)
unsafeAtomicAdd(&ds_atomic_f64_test[tid % 32], 1.0);
__syncthreads();
ds_f64_passed = !isnan(ds_atomic_f64_test[tid % 32]);
}
#else
// RDNA consumer (gfx12 + gfx115x APU): no DS FP64 atomics — bypass
if(lane_id == 0 && tid < 32)
{
atomicMin(&results->atomic_ds_f64_passed, -1);
}
ds_f64_passed = true;
#endif
// ========================================================================
// TEST 8: PACKED FP16 ATOMICS (gfx90a+ CDNA; RDNA 3.5 gfx115x via HIP; gfx12 asm)
// ========================================================================
// global_atomic_pk_add_f16 - Atomically add two FP16 values
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU
// CDNA and RDNA 3.5 APU: Use HIP atomics for packed FP16
{
// Use regular atomicAdd on reinterpreted pointer
unsigned int packed_f16 = 0x3C003C00;
unsigned int old_packed =
atomicAdd((unsigned int*) &global_f32_ptr[(tid % 64) + 64], packed_f16);
pk_f16_passed = true;
(void) old_packed;
}
#elif defined(__gfx1200__) || defined(__gfx1201__) // RDNA4: global_atomic_pk_add_f16
{
unsigned int packed_f16 = 0x3C003C00;
unsigned int old_packed = 0;
asm volatile("global_atomic_pk_add_f16 %0, %1, %2, off "
"th:TH_ATOMIC_RETURN\n\t" ASM_WAITCNT_VMEM
: "=v"(old_packed)
: "v"((unsigned int*) &global_f32_ptr[(tid % 64) + 64]),
"v"(packed_f16)
: "memory");
pk_f16_passed = true;
}
#else
// Packed FP16 global atomic not available for this ISA — bypass (not a failure).
if(lane_id == 0)
{
atomicMin(&results->atomic_pk_f16_passed, -1);
}
pk_f16_passed = false;
#endif
// ========================================================================
// TEST 9: PACKED BF16 ATOMICS (gfx942+, gfx115x, gfx1200/gfx1201)
// ========================================================================
// global_atomic_pk_add_bf16 - Atomically add two BF16 values
// Available on: gfx942, gfx950, gfx115x, gfx1200, gfx1201
// NOT available on: gfx90a
//
#if defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU
// CDNA3+ and RDNA 3.5 APU: Use HIP atomics for packed BF16
{
unsigned int packed_bf16 = 0x3F803F80;
unsigned int old_packed =
atomicAdd((unsigned int*) &global_f32_ptr[(tid % 64) + 128], packed_bf16);
pk_bf16_passed = true;
(void) old_packed;
}
#elif defined(__gfx1200__) || defined(__gfx1201__) // RDNA4: global_atomic_pk_add_bf16
{
unsigned int packed_bf16 = 0x3F803F80;
unsigned int old_packed = 0;
asm volatile("global_atomic_pk_add_bf16 %0, %1, %2, off "
"th:TH_ATOMIC_RETURN\n\t" ASM_WAITCNT_VMEM
: "=v"(old_packed)
: "v"((unsigned int*) &global_f32_ptr[(tid % 64) + 128]),
"v"(packed_bf16)
: "memory");
pk_bf16_passed = true;
}
#elif defined(__gfx90a__)
// MI200: No packed BF16 atomics — bypass (atomicAdd skipped for gfx90a below).
if(lane_id == 0)
{
atomicMin(&results->atomic_pk_bf16_passed, -1);
}
pk_bf16_passed = false;
#else
if(lane_id == 0)
{
atomicMin(&results->atomic_pk_bf16_passed, -1);
}
pk_bf16_passed = false;
#endif
// ========================================================================
// TEST 10: COMPARE-AND-SWAP (CAS) ATOMICS
// ========================================================================
// global_atomic_cmpswap - Atomic compare and swap
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU
// CDNA and RDNA 3.5 APU: Use HIP atomicCAS
{
int compare_val = 0;
int swap_val = tid + 1;
int old_val = atomicCAS(&global_int_ptr[(tid % 64) + 192], compare_val, swap_val);
cas_passed = true;
(void) old_val;
}
#elif defined(__gfx1200__) || defined(__gfx1201__)
{
int compare_val = 0;
int swap_val = tid + 1;
int old_val = 0;
long long cas_data = ((long long) swap_val << 32) | (unsigned int) compare_val;
asm volatile("global_atomic_cmpswap_b32 %0, %1, %2, off "
"th:TH_ATOMIC_RETURN\n\t" ASM_WAITCNT_VMEM
: "=v"(old_val)
: "v"(&global_int_ptr[(tid % 64) + 192]), "v"(cas_data)
: "memory");
cas_passed = true;
}
#else
// CAS path not implemented for this ISA — bypass.
if(lane_id == 0)
{
atomicMin(&results->atomic_cas_passed, -1);
}
cas_passed = false;
#endif
// Record results
if(lane_id == 0)
{
atomicAdd(&results->atomic_global_int_passed, global_int_passed ? 1 : 0);
atomicAdd(&results->atomic_global_f32_passed, global_f32_passed ? 1 : 0);
#if !defined(__gfx1200__) && !defined(__gfx1201__) && !ARCH_RDNA35_APU
atomicAdd(&results->atomic_global_f64_passed, global_f64_passed ? 1 : 0);
#endif
atomicAdd(&results->atomic_flat_int_passed, flat_int_passed ? 1 : 0);
atomicAdd(&results->atomic_ds_int_passed, ds_int_passed ? 1 : 0);
atomicAdd(&results->atomic_ds_f32_passed, ds_f32_passed ? 1 : 0);
#if !defined(__gfx1200__) && !defined(__gfx1201__) && !ARCH_RDNA35_APU
atomicAdd(&results->atomic_ds_f64_passed, ds_f64_passed ? 1 : 0);
#endif
atomicAdd(&results->atomic_pk_f16_passed, pk_f16_passed ? 1 : 0);
#if !defined(__gfx90a__)
atomicAdd(&results->atomic_pk_bf16_passed, pk_bf16_passed ? 1 : 0);
#endif
atomicAdd(&results->atomic_cas_passed, cas_passed ? 1 : 0);
}
}
// ============================================================================
// Section 7: LDS (Shared Memory) Operations
// ============================================================================
// Use extern shared memory for dynamic sizing based on block size
extern __shared__ char shared_mem[];
__device__ void
test_lds_operations(TestResults* results, int lane_id, int tid)
{
// Dynamically allocate LDS arrays from shared memory
// Layout: [float array][int array][double array][atomic counter]
float* lds_float = (float*) shared_mem;
int* lds_int = (int*) &lds_float[blockDim.x];
double* lds_double = (double*) &lds_int[blockDim.x];
// Use a separate int at the end for atomic counter (after double array)
int* lds_atomic_counter = (int*) &lds_double[blockDim.x / 2];
// Write to LDS - now safe for any block size
lds_float[tid] = (float) tid * 1.5f;
lds_int[tid] = tid * 3;
// For double, only use if we have enough threads (avoid wasting memory for small
// blocks)
if(static_cast<unsigned>(tid) < blockDim.x / 2u)
{
lds_double[tid] = (double) tid * 2.5;
}
__syncthreads();
// Read back and verify - track separately from atomic test
bool lds_rw_passed = true;
lds_rw_passed = lds_rw_passed && approx_equal_f32(lds_float[tid], (float) tid * 1.5f);
lds_rw_passed = lds_rw_passed && (lds_int[tid] == tid * 3);
if(static_cast<unsigned>(tid) < blockDim.x / 2u)
{
lds_rw_passed =
lds_rw_passed && approx_equal_f64(lds_double[tid], (double) tid * 2.5);
}
// LDS atomic test - use dedicated counter separate from data arrays
if(tid == 0)
{
*lds_atomic_counter = 0;
}
__syncthreads();
atomicAdd(lds_atomic_counter, 1);
__syncthreads();
// Check atomic result - separate from read/write verification
bool lds_atomic_test_passed = (*lds_atomic_counter >= (int) blockDim.x);
if(lane_id == 0)
{
// Report LDS read/write tests
atomicAdd(&results->lds_load_passed, lds_rw_passed ? 1 : 0);
atomicAdd(&results->lds_store_passed, lds_rw_passed ? 1 : 0);
// Report LDS atomic test separately
atomicAdd(&results->lds_atomic_passed, lds_atomic_test_passed ? 1 : 0);
}
}
// ============================================================================
// Section 8: Transcendental Functions
// ============================================================================
__device__ void
test_transcendental(TestResults* results, int lane_id)
{
float x = 1.0f;
// Sine/Cosine
float sin_val = sinf(x);
float cos_val = cosf(x);
bool sin_pass = approx_equal_f32(sin_val, 0.8414709848f, 1e-4f);
bool cos_pass = approx_equal_f32(cos_val, 0.5403023059f, 1e-4f);
// Exponential and logarithm
float exp_val = expf(x);
float log_val = logf(exp_val);
bool exp_pass = approx_equal_f32(exp_val, 2.718281828f, 1e-4f);
bool log_pass = approx_equal_f32(log_val, x, 1e-4f);
// Square root
float sqrt_val = sqrtf(4.0f);
bool sqrt_pass = approx_equal_f32(sqrt_val, 2.0f);
// Reciprocal
float rcp_val = 1.0f / x;
bool rcp_pass = approx_equal_f32(rcp_val, 1.0f);
// Reciprocal square root
float rsq_val = rsqrtf(4.0f);
bool rsq_pass = approx_equal_f32(rsq_val, 0.5f);
if(lane_id == 0)
{
atomicAdd(&results->trans_sin_passed, sin_pass ? 1 : 0);
atomicAdd(&results->trans_cos_passed, cos_pass ? 1 : 0);
atomicAdd(&results->trans_exp_passed, exp_pass ? 1 : 0);
atomicAdd(&results->trans_log_passed, log_pass ? 1 : 0);
atomicAdd(&results->trans_sqrt_passed, sqrt_pass ? 1 : 0);
atomicAdd(&results->trans_rcp_passed, rcp_pass ? 1 : 0);
atomicAdd(&results->trans_rsq_passed, rsq_pass ? 1 : 0);
}
}
// ============================================================================
// Section 9: Memory Operations
// ============================================================================
__device__ void
test_global_memory(float* input, float* output, TestResults* results, int lane_id,
int tid)
{
// Coalesced load
float val = input[tid];
// Some computation
val = val * 2.0f + 1.0f;
// Coalesced store
output[tid] = val;
__threadfence();
if(lane_id == 0)
{
atomicAdd(&results->global_load_passed, 1);
atomicAdd(&results->global_store_passed, 1);
}
}
// ============================================================================
// Section 9.5: VMEM (Vector Memory) Operations - Comprehensive Memory Tests
// ============================================================================
//
// AMD GPU MEMORY INSTRUCTION TYPES:
// ==================================
// This section tests all vector memory (VMEM) instruction types using inline
// assembly to ensure the hardware memory paths are exercised.
//
// MEMORY INSTRUCTION CATEGORIES:
// ------------------------------
//
// 1. FLAT MEMORY (flat_*):
// - Unified addressing mode for global/LDS/scratch
// - Uses 64-bit virtual addresses
// - Instructions: flat_load_b32, flat_load_b64, flat_load_b128
// flat_store_b32, flat_store_b64, flat_store_b128
// - Data widths: b8 (byte), b16 (short), b32 (dword), b64, b128
//
// 2. GLOBAL MEMORY (global_*):
// - Direct global memory access with SGPR base + VGPR offset
// - Instructions: global_load_b32, global_load_b64, global_load_b128
// global_store_b32, global_store_b64, global_store_b128
// - Async variants: global_load_async_to_lds_b* (gfx950+)
//
// 3. BUFFER MEMORY (buffer_*):
// - Descriptor-based memory access (128-bit resource descriptor)
// - Instructions: buffer_load_dword, buffer_load_dwordx2/x3/x4
// buffer_store_dword, buffer_store_dwordx2/x3/x4
// - Format variants: buffer_load_format_x/xy/xyz/xyzw
//
// 4. SCRATCH MEMORY (scratch_*):
// - Private per-thread stack memory
// - Instructions: scratch_load_b32, scratch_load_b64
// scratch_store_b32, scratch_store_b64
//
// 5. LDS (Local Data Share) (ds_*):
// - Shared memory within workgroup
// - Instructions: ds_read_b32, ds_read_b64, ds_read_b128
// ds_write_b32, ds_write_b64, ds_write_b128
// - Atomic variants: ds_add_u32, ds_min_i32, ds_max_u32, etc.
//
// ARCHITECTURE DIFFERENCES:
// -------------------------
// | Instruction Type | gfx90a | gfx942 | gfx950 | gfx115x | gfx1200/gfx1201 |
// |-----------------------|--------|--------|--------|---------|-----------------|
// | flat_load/store_b* | ✓ | ✓ | ✓ | ✓ | ✓ |
// | global_load/store_b* | ✓ | ✓ | ✓ | ✓ | ✓ |
// | buffer_load/store_* | ✓ | ✓ | ✓ | ✓ | ✓ |
// | scratch_load/store_* | ✓ | ✓ | ✓ | ✓ | ✓ |
// | ds_read/write_* | ✓ | ✓ | ✓ | ✓ | ✓ |
// | global_load_async_* | ✗ | ⚠ | ✓ | ⚠ | ✓ |
// | tensor_load_to_lds | ✗ | ✗ | ✗ | ✗ | ✓ |
//
// NAMING CONVENTION CHANGES (GFX12-class RDNA — gfx120x and RDNA 3.5 gfx115x in this sample):
// -----------------------------------------------------------------------------------------
// CDNA (gfx9x): RDNA4 / RDNA 3.5 APU (flat/global *_b32 style):
// flat_load_dword flat_load_b32
// flat_load_dwordx2 flat_load_b64
// flat_load_dwordx4 flat_load_b128
// global_load_dword global_load_b32
// buffer_load_dword buffer_load_b32
//
// Shared memory for LDS tests
__shared__ int vmem_lds_test_buffer[256];
__device__ void
test_vmem_operations([[maybe_unused]] float* global_ptr,
[[maybe_unused]] TestResults* results, [[maybe_unused]] int lane_id,
[[maybe_unused]] int tid, [[maybe_unused]] int buffer_size,
[[maybe_unused]] hipTextureObject_t tex_obj,
[[maybe_unused]] hipSurfaceObject_t surf_obj)
{
// Host pass of hipcc has no target arch macros; device pass uses in_bounds in guarded VMEM.
[[maybe_unused]] const bool in_bounds = (buffer_size > 0) && (tid < buffer_size);
bool flat_passed = true;
bool global_passed = true;
bool buffer_passed = true;
bool scratch_passed = true;
bool lds_passed = true;
// ========================================================================
// TEST 1: FLAT MEMORY OPERATIONS
// ========================================================================
// Flat addressing uses unified virtual addresses that can point to
// global memory, LDS, or scratch space. The hardware determines the
// actual memory space based on the address range.
//
// CDNA (gfx9x) syntax: flat_load_dword, flat_store_dword
// RDNA4 (gfx12) syntax: flat_load_b32, flat_store_b32
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__)
// CDNA architectures: flat_load_dword / flat_store_dword
if(in_bounds)
{
float test_val = (float) tid + 1.0f;
float loaded_val = 0.0f;
// Write test value to global memory first (using regular store)
global_ptr[tid] = test_val;
__threadfence();
// Flat load: Load from flat address space
// flat_load_dword vdst, vaddr [offset:N] [glc] [slc]
asm volatile("flat_load_dword %0, %1\n\t" ASM_WAITCNT_VMEM
: "=v"(loaded_val)
: "v"(&global_ptr[tid])
: "memory");
flat_passed = (fabsf(loaded_val - test_val) < 0.001f);
// Flat store: Store to flat address space
float store_val = test_val * 2.0f;
asm volatile("flat_store_dword %0, %1\n\t" ASM_WAITCNT_VMEM
:
: "v"(&global_ptr[tid]), "v"(store_val)
: "memory");
// Verify the store
__threadfence();
flat_passed = flat_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f);
}
#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU // GFX12-style names: flat_load_b32 / flat_store_b32 (gfx12 + gfx115x)
if(in_bounds)
{
float test_val = (float) tid + 1.0f;
float loaded_val = 0.0f;
global_ptr[tid] = test_val;
__threadfence();
// RDNA4 uses flat_load_b32 instead of flat_load_dword
asm volatile("flat_load_b32 %0, %1\n\t" ASM_WAITCNT_VMEM
: "=v"(loaded_val)
: "v"(&global_ptr[tid])
: "memory");
flat_passed = (fabsf(loaded_val - test_val) < 0.001f);
float store_val = test_val * 2.0f;
asm volatile("flat_store_b32 %0, %1\n\t" ASM_WAITCNT_VMEM
:
: "v"(&global_ptr[tid]), "v"(store_val)
: "memory");
__threadfence();
flat_passed = flat_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f);
}
#else
flat_passed = true; // Unknown arch - skip
#endif
// ========================================================================
// TEST 2: GLOBAL MEMORY OPERATIONS
// ========================================================================
// Global memory uses SGPR base address + VGPR offset for addressing.
// More efficient than flat when the memory type is known.
//
// CDNA (gfx9x) syntax: global_load_dword, global_store_dword
// RDNA4 (gfx12) syntax: global_load_b32, global_store_b32
//
#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__)
// CDNA: global_load_dword / global_store_dword
if(in_bounds)
{
float test_val = (float) (tid * 3) + 0.5f;
float loaded_val = 0.0f;
global_ptr[tid] = test_val;
__threadfence();
// Global load with VGPR address (off variant)
// global_load_dword vdst, vaddr, off [offset:N]
asm volatile("global_load_dword %0, %1, off\n\t" ASM_WAITCNT_VMEM
: "=v"(loaded_val)
: "v"(&global_ptr[tid])
: "memory");
global_passed = (fabsf(loaded_val - test_val) < 0.001f);
float store_val = test_val + 100.0f;
asm volatile("global_store_dword %0, %1, off\n\t" ASM_WAITCNT_VMEM
:
: "v"(&global_ptr[tid]), "v"(store_val)
: "memory");
__threadfence();
global_passed = global_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f);
}
#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU // GFX12-style names: global_load_b32 / global_store_b32 (gfx12 + gfx115x)
if(in_bounds)
{
float test_val = (float) (tid * 3) + 0.5f;
float loaded_val = 0.0f;
global_ptr[tid] = test_val;
__threadfence();
// RDNA4 global memory syntax
asm volatile("global_load_b32 %0, %1, off\n\t" ASM_WAITCNT_VMEM
: "=v"(loaded_val)
: "v"(&global_ptr[tid])
: "memory");
global_passed = (fabsf(loaded_val - test_val) < 0.001f);
float store_val = test_val + 100.0f;
asm volatile("global_store_b32 %0, %1, off\n\t" ASM_WAITCNT_VMEM
:
: "v"(&global_ptr[tid]), "v"(store_val)
: "memory");
__threadfence();
global_passed = global_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f);
}
#else
global_passed = true;
#endif
// ========================================================================
// TEST 3: SCRATCH MEMORY OPERATIONS
// ========================================================================
// Scratch memory is private per-thread stack memory.
// Automatically allocated by the compiler for local variables.
//
// Note: Direct scratch ASM is complex due to compiler-managed offsets.
// We test scratch implicitly via local variable spilling.
//
{
// Force compiler to use scratch by creating large local array
volatile int local_array[8];
for(int i = 0; i < 8; i++)
{
local_array[i] = tid * 10 + i;
}
__threadfence_block();
// Read back and verify
int sum = 0;
for(int i = 0; i < 8; i++)
{
sum += local_array[i];
}
// Expected sum: sum(tid*10 + i) for i=0..7 = 8*tid*10 + 28
int expected = 8 * tid * 10 + 28;
scratch_passed = (sum == expected);
}
// ========================================================================
// TEST 4: LDS (LOCAL DATA SHARE) OPERATIONS
// ========================================================================
// LDS is shared memory within a workgroup.
//
// Note: Inline ASM for DS operations has pointer issues on some architectures.
// We use regular LDS operations via shared memory instead.
//
{
// Initialize LDS (block-local indices; every block must clear all slots)
if(threadIdx.x < 256)
{
vmem_lds_test_buffer[threadIdx.x] = 0;
}
__syncthreads();
int lds_val = tid * 7 + 3;
// Write to LDS using regular shared memory access
vmem_lds_test_buffer[tid % 256] = lds_val;
__syncthreads();
// Read from LDS
int lds_read = vmem_lds_test_buffer[tid % 256];
lds_passed = (lds_read == lds_val);
// Test reading from adjacent location
if(tid % 2 == 0 && tid + 1 < 256)
{
int read_val0 = vmem_lds_test_buffer[tid];
int read_val1 = vmem_lds_test_buffer[tid + 1];
// Verify both reads
lds_passed = lds_passed && (read_val0 == (tid * 7 + 3));
lds_passed = lds_passed && (read_val1 == ((tid + 1) * 7 + 3));
}
__syncthreads();
}
// ========================================================================
// TEST 5: BUFFER MEMORY OPERATIONS (descriptor-based)
// ========================================================================
// Buffer operations use a 128-bit resource descriptor for memory access.
// More complex to set up but provides additional features like bounds
// checking, format conversion, and swizzling.
//
// Note: Buffer descriptors require SGPR setup which is complex in inline ASM.
// We test buffer operations implicitly via the compiler's generated code
// for texture/buffer access patterns.
//
{
// Buffer operations are typically generated by the compiler for
// structured buffer access. We verify basic functionality via
// the global memory tests above, which use similar hardware paths.
buffer_passed = true; // Implicit test via global operations
}
#if ARCH_RDNA35_APU
bool tex_load_passed = true;
bool tex_store_passed = true;
// ========================================================================
// TEST 6: TEXTURE LOAD (Strix / RDNA 3.5) - exercises INSTS_TEX_LOAD
// ========================================================================
// Use tex1Dfetch to emit image_load (TEX load).
//
if(tex_obj != 0 && buffer_size > 0)
{
int idx = tid % buffer_size;
float fetched = tex1Dfetch<float>(tex_obj, idx);
tex_load_passed = (fabsf(fetched - (float) idx) < 0.001f);
}
else
{
tex_load_passed = true; // Bypassed when no texture object
}
// ========================================================================
// TEST 7: TEXTURE STORE (Strix / RDNA 3.5) - exercises INSTS_TEX_STORE
// ========================================================================
// Use surf1Dwrite to emit image_store (TEX store).
//
if(surf_obj != 0 && buffer_size > 0)
{
int idx = tid % buffer_size;
float store_val = (float) tid + 1000.0f;
surf1Dwrite(store_val, surf_obj, idx);
tex_store_passed = true; // Write issued successfully
}
else
{
tex_store_passed = true; // Bypassed when no surface object
}
#endif
// Record results
if(lane_id == 0)
{
atomicAdd(&results->vmem_flat_passed, flat_passed ? 1 : 0);
atomicAdd(&results->vmem_global_passed, global_passed ? 1 : 0);
atomicAdd(&results->vmem_buffer_passed, buffer_passed ? 1 : 0);
atomicAdd(&results->vmem_scratch_passed, scratch_passed ? 1 : 0);
atomicAdd(&results->vmem_lds_passed, lds_passed ? 1 : 0);
#if ARCH_RDNA35_APU
atomicAdd(&results->vmem_tex_load_passed, tex_load_passed ? 1 : 0);
atomicAdd(&results->vmem_tex_store_passed, tex_store_passed ? 1 : 0);
#else
// TEX load/store paths are gfx115x-only; mark N/A from every block (not tid==0 only).
atomicMin(&results->vmem_tex_load_passed, -1);
atomicMin(&results->vmem_tex_store_passed, -1);
#endif
}
}
// ============================================================================
// Section 10: DOT Product Operations (CDNA: DOT4/DOT8 asm; RDNA: IU-style opcodes / software sanity)
// ============================================================================
__device__ void
test_dot_products(TestResults* results, int lane_id)
{
bool passed = true;
// DOT4/DOT8 Product Instructions:
// All CDNA architectures (MI250/MI300/MI350) support:
// - V_DOT4_U32_U8: 4-element unsigned INT8 dot product
// - V_DOT4_I32_I8: 4-element signed INT8 dot product
// - V_DOT8_U32_U4: 8-element unsigned INT4 dot product
// - V_DOT8_I32_I4: 8-element signed INT4 dot product
//
// RDNA4 and RDNA 3.5 APUs use different opcode names:
// - V_DOT4_U32_U8 -> V_DOT4_U32_IU8
// - V_DOT4_I32_I8 -> V_DOT4_I32_IU8
// - V_DOT8_U32_U4 -> V_DOT8_U32_IU4
// - V_DOT8_I32_I4 -> V_DOT8_I32_IU4
#if 0 // defined() placeholder for future architecture
// Future arch: use new IU suffix opcodes
unsigned int a = 0x01020304; // [1, 2, 3, 4] packed as 4 unsigned bytes
unsigned int b = 0x01010101; // [1, 1, 1, 1] packed as 4 unsigned bytes
// v_dot4_u32_iu8 is the GFX12 IU-suffixed variant
int dot4_result;
asm volatile("v_dot4_u32_iu8 %0, %1, %2, %3"
: "=v"(dot4_result)
: "v"(a), "v"(b), "v"(0));
// Expected: 1*1 + 2*1 + 3*1 + 4*1 = 10
passed = (dot4_result == 10);
#elif defined(__gfx950__) || defined(__gfx942__) || defined(__gfx90a__)
// All CDNA architectures: Use V_DOT4_U32_U8 (4-element unsigned INT8 dot product)
unsigned int a = 0x01020304; // [1, 2, 3, 4] packed as 4 unsigned bytes
unsigned int b = 0x01010101; // [1, 1, 1, 1] packed as 4 unsigned bytes
// v_dot4_u32_u8 is available on gfx90a, gfx942, and gfx950
int dot4_result;
asm volatile("v_dot4_u32_u8 %0, %1, %2, %3"
: "=v"(dot4_result)
: "v"(a), "v"(b), "v"(0));
// Expected: 1*1 + 2*1 + 3*1 + 4*1 = 10
passed = (dot4_result == 10);
#elif defined(__gfx1200__) || defined(__gfx1201__)
// RDNA4 consumer (gfx120x): ISA uses IU-suffixed DOT4 encodings; use software dot sanity here
unsigned int a = 0x01020304; // [1, 2, 3, 4] packed as 4 unsigned bytes
unsigned int b = 0x01010101; // [1, 1, 1, 1] packed as 4 unsigned bytes
int a0 = (a >> 0) & 0xFF, a1 = (a >> 8) & 0xFF, a2 = (a >> 16) & 0xFF,
a3 = (a >> 24) & 0xFF;
int b0 = (b >> 0) & 0xFF, b1 = (b >> 8) & 0xFF, b2 = (b >> 16) & 0xFF,
b3 = (b >> 24) & 0xFF;
int dot4_result = a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3;
passed = (dot4_result == 10);
#elif ARCH_RDNA35_APU
// RDNA 3.5 APU (gfx115x): DOT4 family per RDNA 3.5 ISA; same software sanity path as gfx120x here
unsigned int a = 0x01020304;
unsigned int b = 0x01010101;
int a0 = (a >> 0) & 0xFF, a1 = (a >> 8) & 0xFF, a2 = (a >> 16) & 0xFF,
a3 = (a >> 24) & 0xFF;
int b0 = (b >> 0) & 0xFF, b1 = (b >> 8) & 0xFF, b2 = (b >> 16) & 0xFF,
b3 = (b >> 24) & 0xFF;
int dot4_result = a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3;
passed = (dot4_result == 10);
#else
// Fallback for other architectures - software emulation
unsigned int a = 0x01020304;
unsigned int b = 0x01010101;
int a0 = (a >> 0) & 0xFF, a1 = (a >> 8) & 0xFF, a2 = (a >> 16) & 0xFF,
a3 = (a >> 24) & 0xFF;
int b0 = (b >> 0) & 0xFF, b1 = (b >> 8) & 0xFF, b2 = (b >> 16) & 0xFF,
b3 = (b >> 24) & 0xFF;
int dot4_result = a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3;
passed = (dot4_result == 10);
#endif
// Software-emulated DOT8 test: 8 x 8-bit dot product.
// a8 = [1, 2, 3, 4, 5, 6, 7, 8], b8 = [1, 1, 1, 1, 1, 1, 1, 1]
// Expected dot product = 1+2+3+4+5+6+7+8 = 36.
unsigned long long a8 = 0x0807060504030201ULL;
unsigned long long b8 = 0x0101010101010101ULL;
int dot8_result = 0;
#pragma unroll
for(int i = 0; i < 8; ++i)
{
int ai = (int) ((a8 >> (8 * i)) & 0xFF);
int bi = (int) ((b8 >> (8 * i)) & 0xFF);
dot8_result += ai * bi;
}
int dot8_passed = (dot8_result == 36);
if(lane_id == 0)
{
atomicAdd(&results->dot4_passed, passed ? 1 : 0);
atomicAdd(&results->dot8_passed, dot8_passed ? 1 : 0);
}
}
// ============================================================================
// Section 10.3: MFMA (Matrix Fused Multiply-Add) Operations (gfx90a+)
// ============================================================================
//
// MFMA INSTRUCTIONS - CDNA Matrix Cores:
// For maintained MFMA / roofline coverage, see rocprofiler-compute `src/roofline/benchmark/`
// (the older rocm-amdgpu-bench kernels repo is deprecated).
//
// This test includes BOTH inline assembly AND HIP builtin versions to ensure
// comprehensive coverage of MFMA instructions.
//
// MI250 (gfx90a/CDNA2):
// - v_mfma_f32_32x32x2f32 / __builtin_amdgcn_mfma_f32_32x32x2f32 (FP32: 4096 ops)
// - v_mfma_f64_16x16x4f64 / __builtin_amdgcn_mfma_f64_16x16x4f64 (FP64: 2048 ops)
// - v_mfma_f32_32x32x8f16 / __builtin_amdgcn_mfma_f32_32x32x8f16 (FP16: 16384 ops)
// - v_mfma_f32_32x32x4bf16 / __builtin_amdgcn_mfma_f32_32x32x4bf16 (BF16: 8192 ops)
// - v_mfma_i32_32x32x8i8 / __builtin_amdgcn_mfma_i32_32x32x8i8 (INT8: 16384 ops)
//
// MI300 (gfx942/CDNA3) - doubled K dimensions + FP8:
// - v_mfma_f32_32x32x8bf16_1k / __builtin_amdgcn_mfma_f32_32x32x8bf16_1k (BF16: 16384
// ops)
// - v_mfma_i32_32x32x16_i8 / __builtin_amdgcn_mfma_i32_32x32x16_i8 (INT8: 32768
// ops)
// - v_mfma_f32_32x32x16_fp8 / __builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8 (FP8: 32768
// ops)
//
// MI350 (gfx950/CDNA4) - scaled F8F6F4:
// - v_mfma_scale_f32_32x32x64_f8f6f4 / __builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4
// (FP8/BF8/FP6/FP4: 131072 ops)
//
// Vector types for MFMA operations (layout matches CDNA MFMA builtins)
using int32_16vec = __attribute__((__vector_size__(16 * sizeof(int)))) int;
using f32_16vec = __attribute__((__vector_size__(16 * sizeof(float)))) float;
using f64_4vec = __attribute__((__vector_size__(4 * sizeof(double)))) double;
using bf16_2vec = __attribute__((__vector_size__(2 * sizeof(short)))) short;
using bf16_4vec = __attribute__((__vector_size__(4 * sizeof(short)))) short;
using f16_4vec = __attribute__((__vector_size__(4 * sizeof(_Float16)))) _Float16;
using int32_8vec = __attribute__((__vector_size__(8 * sizeof(int)))) int;
/** MFMA sanity: all lanes finite (FP paths) and at least one nonzero accumulator lane. */
__device__ inline bool
mfma_acc_f32_nonempty_finite(const f32_16vec& r)
{
bool any_nonzero = false;
for(int i = 0; i < 16; i++)
{
if(!isfinite(r[i]))
return false;
if(r[i] != 0.0f)
any_nonzero = true;
}
return any_nonzero;
}
__device__ inline bool
mfma_acc_f64_nonempty_finite(const f64_4vec& r)
{
bool any_nonzero = false;
for(int i = 0; i < 4; i++)
{
if(!isfinite(r[i]))
return false;
if(r[i] != 0.0)
any_nonzero = true;
}
return any_nonzero;
}
__device__ inline bool
mfma_acc_i32_nonempty(const int32_16vec& r)
{
bool any_nonzero = false;
for(int i = 0; i < 16; i++)
{
if(r[i] != 0)
any_nonzero = true;
}
return any_nonzero;
}
/** Pack operand bits for MFMA FP8 builtins that take IEEE binary64 slots at the API. */
__device__ inline double
mfma_fp8_packed_operand_u64(uint64_t bits)
{
double d;
memcpy(&d, &bits, sizeof(bits));
return d;
}
// mfma_mode: 0=both (ASM+builtin), 1=asm only, 2=builtin only
__device__ void
test_mfma_operations([[maybe_unused]] TestResults* results, [[maybe_unused]] int lane_id,
[[maybe_unused]] int mfma_mode)
{
#if defined(__gfx90a__)
// ========================================================================
// MI250 (gfx90a/CDNA2) MFMA Tests
// mfma_mode: 0=both, 1=asm only, 2=builtin only
// Note: MFMA inline ASM requires complex accumulator register constraints.
// We use builtins as the primary test method since they properly handle
// the register allocation. ASM option is preserved for compatibility.
// ========================================================================
bool f32_passed = true;
bool f64_passed = true;
bool f16_passed = true;
bool bf16_passed = true;
bool i8_passed = true;
// ---- FP32 MFMA: v_mfma_f32_32x32x2f32 (4096 ops) ----
{
if(mfma_mode != 1)
{
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x2f32(1.0f, 1.0f, result, 0, 0, 0);
f32_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f32_passed = true;
}
}
// ---- FP64 MFMA: v_mfma_f64_16x16x4f64 (2048 ops) ----
{
if(mfma_mode != 1)
{
f64_4vec result = { 0 };
result = __builtin_amdgcn_mfma_f64_16x16x4f64(1.0, 1.0, result, 0, 0, 0);
f64_passed = mfma_acc_f64_nonempty_finite(result);
}
else
{
f64_passed = true;
}
}
// ---- FP16 MFMA: v_mfma_f32_32x32x8f16 (16384 ops) ----
{
if(mfma_mode != 1)
{
const _Float16 one_h = (_Float16)1.0f;
f16_4vec a = { one_h, one_h, one_h, one_h };
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0);
f16_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f16_passed = true;
}
}
// ---- BF16 MFMA: v_mfma_f32_32x32x4bf16 (8192 ops) ----
{
if(mfma_mode != 1)
{
const short bf16_one = (short) (0x3f800000u >> 16);
bf16_2vec a = { bf16_one, bf16_one };
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x4bf16(a, a, result, 0, 0, 0);
bf16_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
bf16_passed = true;
}
}
// ---- INT8 MFMA: v_mfma_i32_32x32x8i8 (16384 ops) ----
{
if(mfma_mode != 1)
{
const int i8_pack = 0x01010101;
int32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_i32_32x32x8i8(i8_pack, i8_pack, result,
0, 0, 0);
i8_passed = mfma_acc_i32_nonempty(result);
}
else
{
i8_passed = true;
}
}
if(lane_id == 0)
{
atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0);
atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0);
atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0);
atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0);
atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0);
atomicMin(&results->mfma_f8_passed, -1);
}
#elif defined(__gfx942__) || defined(__gfx940__) || defined(__gfx941__)
// ========================================================================
// MI300 (gfx942/CDNA3) MFMA Tests - Enhanced with FP8 support
// mfma_mode: 0=both, 1=asm only, 2=builtin only
// ========================================================================
bool f32_passed = true;
bool f64_passed = true;
bool f16_passed = true;
bool bf16_passed = true;
bool i8_passed = true;
bool f8_passed = true;
// ---- FP32 MFMA: v_mfma_f32_32x32x2f32 (4096 ops) ----
{
if(mfma_mode != 1)
{
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x2f32(1.0f, 1.0f, result, 0, 0, 0);
f32_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f32_passed = true;
}
}
// ---- FP64 MFMA: v_mfma_f64_16x16x4f64 (2048 ops) ----
{
if(mfma_mode != 1)
{
f64_4vec result = { 0 };
result = __builtin_amdgcn_mfma_f64_16x16x4f64(1.0, 1.0, result, 0, 0, 0);
f64_passed = mfma_acc_f64_nonempty_finite(result);
}
else
{
f64_passed = true;
}
}
// ---- FP16 MFMA: v_mfma_f32_32x32x8f16 (16384 ops) ----
{
if(mfma_mode != 1)
{
const _Float16 one_h = (_Float16)1.0f;
f16_4vec a = { one_h, one_h, one_h, one_h };
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0);
f16_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f16_passed = true;
}
}
// ---- BF16 MFMA: v_mfma_f32_32x32x8bf16_1k (16384 ops) - MI300 enhanced ----
{
if(mfma_mode != 1)
{
const short bf16_one = (short) (0x3f800000u >> 16);
bf16_4vec a = { bf16_one, bf16_one, bf16_one, bf16_one };
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x8bf16_1k(a, a, result, 0, 0, 0);
bf16_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
bf16_passed = true;
}
}
// ---- INT8 MFMA: v_mfma_i32_32x32x16_i8 (32768 ops) - MI300 doubled K ----
{
if(mfma_mode != 1)
{
const long long i8_pack = 0x0101010101010101LL;
int32_16vec result = { 0 };
result =
__builtin_amdgcn_mfma_i32_32x32x16_i8(i8_pack, i8_pack, result, 0, 0, 0);
i8_passed = mfma_acc_i32_nonempty(result);
}
else
{
i8_passed = true;
}
}
// ---- FP8 MFMA: v_mfma_f32_32x32x16_fp8_fp8 (32768 ops) - MI300 new ----
{
if(mfma_mode != 1)
{
const uint64_t fp8_bits =
(uint64_t) (unsigned) (lane_id + 1)
| ((uint64_t) (unsigned) (lane_id + 1) << 32);
const double fp8_a = mfma_fp8_packed_operand_u64(fp8_bits);
const double fp8_b = mfma_fp8_packed_operand_u64(fp8_bits);
f32_16vec result = { 0 };
result =
__builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8(fp8_a, fp8_b, result, 0, 0, 0);
f8_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f8_passed = true;
}
}
if(lane_id == 0)
{
atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0);
atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0);
atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0);
atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0);
atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0);
atomicAdd(&results->mfma_f8_passed, f8_passed ? 1 : 0);
}
#elif defined(__gfx950__)
// ========================================================================
// MI350 (gfx950/CDNA4) MFMA Tests - Adds scaled F8F6F4
// mfma_mode: 0=both, 1=asm only, 2=builtin only
// ========================================================================
bool f32_passed = true;
bool f64_passed = true;
bool f16_passed = true;
bool bf16_passed = true;
bool i8_passed = true;
bool f8_passed = true;
// ---- FP32 MFMA: v_mfma_f32_32x32x2f32 ----
{
if(mfma_mode != 1)
{
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x2f32(1.0f, 1.0f, result, 0, 0, 0);
f32_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f32_passed = true;
}
}
// ---- FP64 MFMA: v_mfma_f64_16x16x4f64 ----
{
if(mfma_mode != 1)
{
f64_4vec result = { 0 };
result = __builtin_amdgcn_mfma_f64_16x16x4f64(1.0, 1.0, result, 0, 0, 0);
f64_passed = mfma_acc_f64_nonempty_finite(result);
}
else
{
f64_passed = true;
}
}
// ---- FP16 MFMA: v_mfma_f32_32x32x8f16 ----
{
if(mfma_mode != 1)
{
const _Float16 one_h = (_Float16)1.0f;
f16_4vec a = { one_h, one_h, one_h, one_h };
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0);
f16_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f16_passed = true;
}
}
// ---- BF16 MFMA: v_mfma_f32_32x32x8bf16_1k ----
{
if(mfma_mode != 1)
{
const short bf16_one = (short) (0x3f800000u >> 16);
bf16_4vec a = { bf16_one, bf16_one, bf16_one, bf16_one };
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_f32_32x32x8bf16_1k(a, a, result, 0, 0, 0);
bf16_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
bf16_passed = true;
}
}
// ---- INT8 MFMA: v_mfma_i32_32x32x16_i8 ----
{
if(mfma_mode != 1)
{
const long long i8_pack = 0x0101010101010101LL;
int32_16vec result = { 0 };
result =
__builtin_amdgcn_mfma_i32_32x32x16_i8(i8_pack, i8_pack, result, 0, 0, 0);
i8_passed = mfma_acc_i32_nonempty(result);
}
else
{
i8_passed = true;
}
}
// ---- Scaled F8F6F4 MFMA: v_mfma_scale_f32_32x32x64_f8f6f4 (131072 ops) ----
{
if(mfma_mode != 1)
{
const int pkt = 0x01010101 * (lane_id + 1);
int32_8vec a = { pkt, pkt, pkt, pkt, pkt, pkt, pkt, pkt };
f32_16vec result = { 0 };
result = __builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4(a, a, result,
0, 0, 0, 0, 0, 0);
f8_passed = mfma_acc_f32_nonempty_finite(result);
}
else
{
f8_passed = true;
}
}
if(lane_id == 0)
{
atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0);
atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0);
atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0);
atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0);
atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0);
atomicAdd(&results->mfma_f8_passed, f8_passed ? 1 : 0);
}
#else
// MFMA not supported on RDNA4 consumer GPUs - mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->mfma_f32_passed, -1);
atomicMin(&results->mfma_f64_passed, -1);
atomicMin(&results->mfma_f16_passed, -1);
atomicMin(&results->mfma_bf16_passed, -1);
atomicMin(&results->mfma_i8_passed, -1);
atomicMin(&results->mfma_f8_passed, -1);
}
#endif
}
// ============================================================================
// Section 10.5: Dual-Issue VALU Instructions (gfx942+)
// ============================================================================
//
// DUAL-ISSUE VALU FEATURE (gfx942+):
// ===================================
// AMD GPUs support dual-issue VALU in different forms:
//
// TWO TYPES OF DUAL-ISSUE:
// ------------------------
// 1. SAME-WAVE CO-ISSUE (VOPD/VOPD3) - MI300 (gfx942), MI350 (gfx950):
// - Single instruction encodes two VALU operations (X and Y opcodes)
// - X opcode executes on Core MACC, Y opcode executes on Side MACC
// - Example: VOPD encoding packs v_add_f32 + v_mul_f32 in one instruction
// - VOPD3 extends this with 3 source operands per instruction
// - Consumer RDNA (gfx115x / gfx120x) may expose related VOPD-style dual-op patterns
// where the ISA and compiler support them; this sample does not claim perf verification.
//
// 2. CROSS-WAVE CO-ISSUE - MI350 (gfx950) ONLY:
// - Two waves each contribute one VALU instruction
// - Instructions execute simultaneously on different MACC units
// - One instruction uses DP-MACC (Arbiter-B), other uses Core/Side-MACC (Arbiter-A)
// - Requires dual VALU arbiters (new hardware in gfx950+)
//
// DUAL-ISSUE REQUIREMENTS:
// ------------------------
// For cross-wave co-issue to occur, BOTH instructions must satisfy:
// - Both waves must be Wave32 (NOT Wave64)
// - Both must be single-cycle VALU instructions
// - One uses DP-MACC (FP64/DGEMM), other uses Core/Side MACC
// - Neither instruction uses DPP (Data-Parallel Primitives)
// - Non-DPFP instruction is NOT VOPD or VOPD3
// - Neither is a Transcendental (sin, cos, exp, log, etc.) or WMMA instruction
// - Source operands must not conflict (no register bank conflicts)
//
// INSTRUCTIONS THAT CAN CO-EXECUTE WITH DP-MACC (Arbiter-B):
// ----------------------------------------------------------
// - Single-cycle FP32/FP16/BF16 operations (v_add_f32, v_mul_f32, v_fma_f32, etc.)
// - Single-cycle integer operations (v_add_i32, v_mul_lo_u32, etc.)
// - Comparison operations
// - Bitwise operations
//
// INSTRUCTIONS THAT BLOCK DUAL-ISSUE:
// -----------------------------------
// - VOPD/VOPD3 instructions (already dual-op)
// - DPP instructions (data-parallel primitives)
// - DGEMM/SGEMM (matrix multiply)
// - XDL/WMMA instructions
// - TMACC instructions
// - Multi-cycle instructions (V_SWAP*, V_MQSAD*, V_MUL_U64, etc.)
// - Transcendental operations (v_sin_f32, v_cos_f32, v_exp_f32, etc.)
// - PIPEFLUSH operations
//
// HOW TO MAXIMIZE DUAL-ISSUE IN YOUR CODE:
// ----------------------------------------
// 1. Mix FP64 operations with FP32/FP16 operations
// Example: While one wave does FP64 accumulation, another does FP32 normalization
//
// 2. Avoid back-to-back transcendentals - they block co-execution
// Bad: sin(x); cos(x); // Neither can dual-issue
// Good: sin(x); a + b; // a+b might co-issue with sin from another wave
//
// 3. Use Wave32 dispatch for maximum dual-issue opportunity
//
// 4. Minimize register conflicts between concurrent operations
//
// DUAL-ISSUE PERFORMANCE TEST:
// ----------------------------
// The following test demonstrates instruction patterns that enable dual-issue.
// On MI350, mixing FP64 and FP32 ops from different waves can achieve 2x IPC.
//
// NOTE: Actual dual-issue happens at hardware scheduling level between waves.
// This test provides instruction patterns that are dual-issue eligible.
//
__device__ void
test_dual_issue_patterns(TestResults* results, int lane_id, int tid)
{
(void) lane_id;
// This function demonstrates dual-issue eligible instruction patterns
// for MI350 (gfx950). The patterns are designed to maximize throughput.
#if defined(__gfx950__)
// ========================================================================
// PATTERN 1: FP64 + FP32 Co-execution
// ========================================================================
// FP64 ops use DP-MACC (Arbiter-B), FP32 ops use Core MACC (Arbiter-A)
// These can potentially co-execute with FP64 ops from another wave
double dp_accum = 1.0;
float sp_accum = 1.0f;
// Mix of FP64 and FP32 operations - eligible for cross-wave dual-issue
// When wave A executes FP64 FMA, wave B's FP32 add can co-execute
# pragma unroll 4
for(int i = 0; i < 4; i++)
{
dp_accum = dp_accum * 1.00001 + 0.00001; // DP-MACC (Arbiter-B)
sp_accum = sp_accum * 1.001f + 0.001f; // Core MACC (Arbiter-A)
}
// ========================================================================
// PATTERN 2: Integer + FP32 Co-execution
// ========================================================================
// Integer VALU ops and FP32 ops can potentially co-execute
int int_accum = lane_id;
float fp_accum = (float) lane_id;
# pragma unroll 4
for(int i = 0; i < 4; i++)
{
int_accum = int_accum * 3 + 1; // Integer MACC
fp_accum = fp_accum * 1.5f + 0.5f; // FP32 MACC
}
// ========================================================================
// PATTERN 3: Avoid Dual-Issue Blockers
// ========================================================================
// Transcendentals CANNOT co-execute - avoid clustering them
// BAD PATTERN (blocks dual-issue):
// float s = sinf(x);
// float c = cosf(x); // Both use Trans pipe, no co-execution
// BETTER PATTERN (allows some dual-issue):
float x = (float) lane_id * 0.1f;
float s = sinf(x); // Trans pipe - no co-execute
float y = x * 2.0f + 1.0f; // Core MACC - might co-execute with other wave
float c = cosf(x); // Trans pipe - no co-execute
float z = y * 0.5f - 0.5f; // Core MACC - might co-execute with other wave
// Use results to prevent optimization
volatile float sink =
(float) dp_accum + sp_accum + (float) int_accum + fp_accum + s + c + y + z;
(void) sink;
#elif defined(__gfx942__)
// ========================================================================
// MI300 (gfx942): Same-Wave Co-Issue via VOPD/VOPD3
// ========================================================================
// MI300 supports VOPD encoding which executes 2 operations from the SAME
// wave in parallel. X instruction uses Core MACC, Y instruction uses Side MACC.
//
// VOPD Operations (executed as dual ops in one instruction):
// - V_DUAL_FMAC_F32, V_DUAL_MUL_F32, V_DUAL_ADD_F32, V_DUAL_SUB_F32
// - V_DUAL_MOV_B32, V_DUAL_CNDMASK_B32
// - V_DUAL_MAX_NUM_F32, V_DUAL_MIN_NUM_F32
// - V_DUAL_ADD_NC_U32, V_DUAL_LSHLREV_B32, etc.
//
// VOPD3 extends this with 3 source operands per instruction.
// Pattern: FP32 operations that compiler can potentially encode as VOPD
float a = (float) lane_id * 0.1f;
float b = (float) lane_id * 0.2f;
float c = 1.0f;
float d = 2.0f;
// These paired operations may be encoded as VOPD by the compiler
# pragma unroll 4
for(int i = 0; i < 4; i++)
{
// Pair 1: Two independent FMAs - potential VOPD
c = c * a + 0.001f; // V_DUAL_FMAC_F32 (X op - Core MACC)
d = d * b + 0.002f; // V_DUAL_FMAC_F32 (Y op - Side MACC)
}
// Pattern: Mixed integer and FP32 - potential VOPD
int e = lane_id;
float f = (float) lane_id;
# pragma unroll 4
for(int i = 0; i < 4; i++)
{
e = e + 1; // V_DUAL_ADD_NC_U32 (X op)
f = f + 1.0f; // V_DUAL_ADD_F32 (Y op)
}
// Use results to prevent optimization
volatile float sink = c + d + f + (float) e;
(void) sink;
#elif defined(__gfx90a__)
// MI250 (gfx90a/CDNA2): Limited dual-issue capability
// Basic operations for compatibility
float accum = 1.0f;
# pragma unroll 4
for(int i = 0; i < 4; i++)
{
accum = accum * 1.001f + 0.001f;
}
volatile float sink = accum;
(void) sink;
#else
// Other architectures - basic fallback
volatile float sink = 1.0f;
(void) sink;
#endif
// Note: Dual-issue is a hardware scheduling decision.
// This test provides dual-issue ELIGIBLE patterns.
// Actual dual-issue depends on wave scheduling and register availability.
// Dual-issue is a scheduler/performance property: we emit eligible instruction
// patterns but do not verify co-issue in hardware (would need perf counters).
if(tid == 0)
{
results->dual_issue_passed = -1;
}
}
// ============================================================================
// Section 11: Lane/Thread Info Operations
// ============================================================================
__device__ void
test_lane_thread_info(TestResults* results, int lane_id)
{
// Get lane ID using builtin
unsigned int hw_lane_id;
if(warpSize == 32)
{
hw_lane_id = __builtin_amdgcn_mbcnt_lo(-1, 0);
}
else
{
hw_lane_id = __builtin_amdgcn_mbcnt_hi(-1, __builtin_amdgcn_mbcnt_lo(-1, 0));
}
bool passed = (hw_lane_id == (unsigned) lane_id);
// Test wavefront size
int ws = __builtin_amdgcn_wavefrontsize();
passed = passed && (ws == 32 || ws == 64);
if(lane_id == 0)
{
atomicAdd(&results->lane_id_passed, passed ? 1 : 0);
atomicAdd(&results->wavefront_passed, 1);
}
}
// ============================================================================
// Section 12: Packed FP16/BF16 Operations
// ============================================================================
__device__ void
test_packed_operations(TestResults* results, int lane_id)
{
bool f16_passed = true;
bool bf16_passed = true;
// ========================================================================
// TEST 1: Packed FP16 Operations
// ========================================================================
// Create packed FP16 values (2 x FP16 in 32 bits)
__half2 h2_a = __float2half2_rn(1.5f);
__half2 h2_b = __float2half2_rn(2.0f);
// Packed add
__half2 h2_sum = __hadd2(h2_a, h2_b);
// Extract and verify
float low = __low2float(h2_sum);
float high = __high2float(h2_sum);
f16_passed = f16_passed && approx_equal_f32(low, 3.5f, 0.01f);
f16_passed = f16_passed && approx_equal_f32(high, 3.5f, 0.01f);
// Packed multiply
__half2 h2_prod = __hmul2(h2_a, h2_b);
low = __low2float(h2_prod);
f16_passed = f16_passed && approx_equal_f32(low, 3.0f, 0.01f);
// ========================================================================
// TEST 2: Packed BF16 Operations
// ========================================================================
// Create packed BF16 values (2 x BF16 in 32 bits)
// BF16 has same exponent range as FP32 but reduced mantissa precision
// Convert floats to BF16
__hip_bfloat16 bf16_a = __float2bfloat16(1.5f);
__hip_bfloat16 bf16_b = __float2bfloat16(2.5f);
// Create packed BF16 (using __hip_bfloat162 if available)
#if defined(__HIP_PLATFORM_AMD__)
// Pack two BF16 values
__hip_bfloat162 bf16_2_a;
bf16_2_a.x = bf16_a;
bf16_2_a.y = bf16_a;
__hip_bfloat162 bf16_2_b;
bf16_2_b.x = bf16_b;
bf16_2_b.y = bf16_b;
// Packed BF16 add (if available)
__hip_bfloat162 bf16_2_sum = __hadd2(bf16_2_a, bf16_2_b);
// Convert back to float and verify
float bf16_low = __bfloat162float(bf16_2_sum.x);
float bf16_high = __bfloat162float(bf16_2_sum.y);
// Expected: 1.5 + 2.5 = 4.0 (with BF16 precision tolerance)
bf16_passed = bf16_passed && approx_equal_f32(bf16_low, 4.0f, 0.02f);
bf16_passed = bf16_passed && approx_equal_f32(bf16_high, 4.0f, 0.02f);
// Packed BF16 multiply
__hip_bfloat162 bf16_2_prod = __hmul2(bf16_2_a, bf16_2_b);
bf16_low = __bfloat162float(bf16_2_prod.x);
// Expected: 1.5 * 2.5 = 3.75 (with BF16 precision tolerance)
bf16_passed = bf16_passed && approx_equal_f32(bf16_low, 3.75f, 0.02f);
#else
// Fallback: Manual BF16 packed operations
// Convert to BF16, do arithmetic, convert back
float a_val = __bfloat162float(bf16_a);
float b_val = __bfloat162float(bf16_b);
float sum_val = a_val + b_val;
float prod_val = a_val * b_val;
bf16_passed = bf16_passed && approx_equal_f32(sum_val, 4.0f, 0.02f);
bf16_passed = bf16_passed && approx_equal_f32(prod_val, 3.75f, 0.02f);
#endif
if(lane_id == 0)
{
atomicAdd(&results->pk_f16_passed, f16_passed ? 1 : 0);
atomicAdd(&results->pk_bf16_passed, bf16_passed ? 1 : 0);
atomicAdd(&results->pk_add_passed, (f16_passed && bf16_passed) ? 1 : 0);
}
}
// ============================================================================
// Section 13: Async LDS Operations (MI350/RDNA4 specific - global_load/store_async)
// ============================================================================
//
// Async LDS Support by Architecture:
// - MI250 (gfx90a): NO async LDS support
// - MI300 (gfx942): LIMITED async LDS support (may not have all builtins)
// - MI350 (gfx950): FULL async LDS support (global_load/store_async_to/from_lds)
// - RDNA4: FULL async LDS support + DS atomic async barriers
//
// GFX12+ / MI350-SPECIFIC DS ATOMIC BARRIER BUILTINS:
// --------------------------------------------
// These are used for synchronization with async LDS operations:
// - __builtin_amdgcn_ds_atomic_async_barrier_arrive_b64: Signal barrier arrival
// - __builtin_amdgcn_ds_atomic_barrier_arrive_rtn_b64: Signal barrier with return value
//
// USAGE PATTERN:
// 1. Issue async LDS load/store operations
// 2. Signal barrier arrival using ds_atomic_async_barrier_arrive
// 3. Wait for barrier completion (all threads arrived)
// 4. Use s_wait_asynccnt + s_wait_dscnt to ensure completion
//
// Shared memory for async LDS test
__shared__ int async_lds_buffer[64];
#if 0 // defined() placeholder for future architecture
__shared__ long async_barrier_counter; // Placeholder type for DS atomic barrier experiments
#else
__shared__ long long async_barrier_counter;
#endif
__device__ void
test_async_lds_operations([[maybe_unused]] float* global_src, float* global_dst,
TestResults* results, int lane_id, int tid)
{
bool passed = true;
#if __has_builtin(__builtin_amdgcn_global_load_async_to_lds_b32)
// MI350 (gfx950) / RDNA4: Full async LDS load/store support
// global_load_async_to_lds_b8/b32/b64/b128: Load from global to LDS asynchronously
// global_store_async_from_lds_b8/b32/b64/b128: Store from LDS to global
// asynchronously
// Initialize global source with test pattern
if(tid < 64)
{
// First, copy test data to global memory
global_src[tid] = (float) (tid * 10 + 1);
}
// Initialize async barrier counter per block for barrier experiments
if(threadIdx.x == 0)
{
async_barrier_counter = 0;
}
__syncthreads();
// Perform async load from global to LDS
// Only first thread in each block issues the async load for this test
if(threadIdx.x == 0)
{
// Cast pointers to appropriate address spaces
// address_space(1) = global, address_space(3) = local/LDS
typedef int __attribute__((ext_vector_type(4))) vint4;
// Load 16 bytes (4 ints) asynchronously from global to LDS
__builtin_amdgcn_global_load_async_to_lds_b128(
((__attribute__((address_space(1))) vint4*) global_src),
((__attribute__((address_space(3))) vint4*) async_lds_buffer), 0 /* offset */,
0 /* cache policy */);
}
# if 0 // defined() placeholder for future architecture
// GFX12+/MI350: Use DS atomic async barrier for synchronization
// This signals that this thread has reached the barrier point
// The barrier counter is atomically incremented
if (__has_builtin(__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64)) {
__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64(
((__attribute__((address_space(3))) long*)&async_barrier_counter));
}
// Wait for async operations to complete (s_wait_asynccnt on targets that expose it)
__builtin_amdgcn_s_wait_asynccnt(0); // Wait for async copy operations
# else
// Other architectures: Use unified s_waitcnt
WAITCNT_ALL(); // Wait for all memory operations
# endif
__syncthreads();
// Verify the data was loaded correctly to LDS
if(tid < 4)
{
int expected = (int) (tid * 10 + 1);
// Note: We loaded floats but stored as ints, reinterpret
float* lds_as_float = (float*) async_lds_buffer;
passed = passed && approx_equal_f32(lds_as_float[tid], (float) expected, 0.1f);
}
__syncthreads();
// Now test async store from LDS to global
if(tid == 0)
{
// Modify LDS data
async_lds_buffer[0] = 12345;
async_lds_buffer[1] = 67890;
async_lds_buffer[2] = 11111;
async_lds_buffer[3] = 22222;
}
__syncthreads();
if(tid == 0)
{
typedef int __attribute__((ext_vector_type(4))) vint4;
// Store 16 bytes from LDS to global asynchronously
__builtin_amdgcn_global_store_async_from_lds_b128(
((__attribute__((address_space(1))) vint4*) (global_dst)),
((__attribute__((address_space(3))) vint4*) async_lds_buffer), 0 /* offset */,
0 /* cache policy */);
}
# if 0 // defined() placeholder for future architecture
// : Use DS atomic async barrier for store synchronization
if (__has_builtin(__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64)) {
__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64(
((__attribute__((address_space(3))) long*)&async_barrier_counter));
}
// Wait for async store operations to complete
__builtin_amdgcn_s_wait_asynccnt(0); // Wait for async copy operations
# else
// Other architectures: Use unified s_waitcnt
WAITCNT_ALL();
# endif
__syncthreads();
// Verify the store (on subsequent kernel or by reading back)
// For now, just check that no crash occurred
passed = passed && true;
#else
// Async LDS builtins not available - mark as bypassed
(void) global_src;
(void) global_dst;
(void) tid;
(void) passed;
if(lane_id == 0)
{
atomicMin(&results->async_lds_load_passed, -1);
atomicMin(&results->async_lds_store_passed, -1);
}
return;
#endif
if(lane_id == 0)
{
atomicAdd(&results->async_lds_load_passed, passed ? 1 : 0);
atomicAdd(&results->async_lds_store_passed, passed ? 1 : 0);
}
}
// ============================================================================
// Section 15.5: WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4 / RDNA 3.5)
// ============================================================================
//
// WMMA INSTRUCTIONS - RDNA4 Matrix Cores:
// =======================================
// Reference: https://gpuopen.com/learn/using_matrix_core_amd_rdna4/
//
// RDNA4 (gfx12/) features 3rd-generation Matrix Cores with significantly
// improved GEMM performance compared to RDNA3:
//
// PERFORMANCE COMPARISON (FLOPS/clock/CU):
// ----------------------------------------
// | Format | RDNA2 (RX 6950) | RDNA3 (RX 7900) | RDNA4 (RX 9070) |
// |--------|-----------------|-----------------|-----------------|
// | FP16 | 256 | 512 | 1024 |
// | BF16 | N/A | 512 | 1024 |
// | I8 | 512 | 512 | 2048 |
//
// WMMA INTRINSIC FORMAT:
// ----------------------
// GFX12 (gfx1200/gfx1201/gfx1150/gfx1151): __builtin_amdgcn_wmma_*_16x16x16_*_w32_gfx12
// (3-arg)
// Pre-gfx12 WMMA: __builtin_amdgcn_wmma_* (no _gfx12 suffix; different signatures per
// BuiltinsAMDGPU.td)
//
// Available for gfx12 (RDNA4 / RDNA 3.5): _w32_gfx12 suffix, 3 args (a, b, c)
// - __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12 (FP16 → FP32)
// - __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12 (BF16 → FP32)
// - __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12 (INT8 → INT32)
//
// Available for older WMMA entry points only (no suffix): 8-arg FP16/BF16, 7-arg INT8
// - __builtin_amdgcn_wmma_f32_16x16x32_f16, __builtin_amdgcn_wmma_f32_16x16x32_bf16
// - __builtin_amdgcn_wmma_i32_16x16x64_iu8
//
// MATRIX LAYOUT IN VGPR:
// ----------------------
// WMMA operates on 16x16 matrices distributed across a Wave32:
// - Each lane holds 8 elements (8 * 32 = 256 = 16 * 16 elements)
// - Matrix A is column-major (transposed)
// - Matrices B, C, D are row-major
// - laneWrapped = lane_id % 16 (column index within matrix; lane_id = threadIdx.x % warpSize)
// - laneGroup = lane_id / 16 (upper/lower half of matrix)
//
// WMMA USAGE PATTERN:
// -------------------
// 1. Load matrix A (column-major) into fragment
// 2. Load matrix B (row-major) into fragment
// 3. Execute WMMA intrinsic with all lanes in wavefront
// 4. Store result matrix D (row-major) from fragment
//
// IMPORTANT NOTES:
// ----------------
// - RDNA4 uses Wave32 ONLY for WMMA (no Wave64 WMMA)
// - VGPR layout changed vs RDNA3 - NOT backward compatible
// - All 32 lanes must participate in the WMMA operation
// - Matrix dimensions are fixed at 16x16
//
// Vector types for WMMA operations (RDNA4)
// Fragment types: 8 elements per lane for 16x16 matrix
#if defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU
using wmma_f16_frag = __attribute__((__vector_size__(8 * sizeof(_Float16)))) _Float16;
using wmma_bf16_frag = __attribute__((__vector_size__(8 * sizeof(short)))) short;
using wmma_f32_frag = __attribute__((__vector_size__(8 * sizeof(float)))) float;
using wmma_i32_frag = __attribute__((__vector_size__(8 * sizeof(int)))) int;
using wmma_i8_frag = __attribute__((
__vector_size__(2 * sizeof(int)))) int; // 2 packed i32 for 8 i8 elements
__device__ inline bool
wmma_frag_f32_nonempty_finite(const wmma_f32_frag& c)
{
bool any_nonzero = false;
for(int i = 0; i < 8; i++)
{
if(!isfinite(c[i]))
return false;
if(c[i] != 0.0f)
any_nonzero = true;
}
return any_nonzero;
}
__device__ inline bool
wmma_frag_i32_nonempty(const wmma_i32_frag& c)
{
bool any_nonzero = false;
for(int i = 0; i < 8; i++)
{
if(c[i] != 0)
any_nonzero = true;
}
return any_nonzero;
}
#endif
__device__ void
test_wmma_operations([[maybe_unused]] TestResults* results, [[maybe_unused]] int lane_id,
[[maybe_unused]] int tid)
{
#if 0 // defined() placeholder for future architecture
// ========================================================================
// () WMMA Tests - use builtins (no _gfx12 suffix)
// BuiltinsAMDGPU.td: __builtin_amdgcn_wmma_f32_16x16x32_f16, _bf16, i32_16x16x64_iu8
// ========================================================================
bool f16_passed = true;
bool bf16_passed = true;
bool i8_passed = true;
const int laneWrapped = tid % 16;
const int laneGroup = tid / 16;
// FP16/BF16: 16 elements per lane for A and B; INT8: 8 int (32 i8) per lane
using wmma_f16_frag_ = __attribute__((__vector_size__(16 * sizeof(_Float16)))) _Float16;
using wmma_bf16_frag_ = __attribute__((__vector_size__(16 * sizeof(short)))) short;
using wmma_i8_frag_ = __attribute__((__vector_size__(8 * sizeof(int)))) int;
// ---- FP16: __builtin_amdgcn_wmma_f32_16x16x32_f16(transA, A_16, transB, B_16, scale, C_8, bool, bool) ----
{
wmma_f16_frag_ a_frag;
wmma_f16_frag_ b_frag;
wmma_f32_frag c_frag = {0};
for (int ele = 0; ele < 16; ele++) {
a_frag[ele] = (_Float16)((laneWrapped + (ele + laneGroup * 16) % 32) % 16 + 1) * 0.1f;
b_frag[ele] = (_Float16)(((ele + laneGroup * 16) % 32 + laneWrapped) % 16 + 1) * 0.1f;
}
for (int i = 0; i < 8; i++) c_frag[i] = 0.0f;
# if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x32_f16)
c_frag = __builtin_amdgcn_wmma_f32_16x16x32_f16(false, a_frag, false, b_frag, (short)0, c_frag, false, false);
# endif
volatile float sink = 0;
for (int i = 0; i < 8; i++) sink += c_frag[i];
f16_passed = (sink != 0.0f || tid >= 32);
(void)sink;
}
// ---- BF16: __builtin_amdgcn_wmma_f32_16x16x32_bf16(transA, A_16, transB, B_16, scale, C_8, bool, bool) ----
{
wmma_bf16_frag_ a_frag;
wmma_bf16_frag_ b_frag;
wmma_f32_frag c_frag = {0};
for (int ele = 0; ele < 16; ele++) {
float va = (float)((laneWrapped + ele) % 16 + 1) * 0.1f;
float vb = (float)((ele + laneWrapped) % 16 + 1) * 0.1f;
a_frag[ele] = (short)(*(unsigned int*)&va >> 16);
b_frag[ele] = (short)(*(unsigned int*)&vb >> 16);
}
for (int i = 0; i < 8; i++) c_frag[i] = 0.0f;
# if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x32_bf16)
c_frag = __builtin_amdgcn_wmma_f32_16x16x32_bf16(false, a_frag, false, b_frag, (short)0, c_frag, false, false);
# endif
volatile float sink = 0;
for (int i = 0; i < 8; i++) sink += c_frag[i];
bf16_passed = (sink != 0.0f || tid >= 32);
(void)sink;
}
// ---- INT8: __builtin_amdgcn_wmma_i32_16x16x64_iu8(negA, A_8i, negB, B_8i, C_8i, bool, bool) ----
{
wmma_i8_frag_ a_frag = {0};
wmma_i8_frag_ b_frag = {0};
wmma_i32_frag c_frag = {0};
for (int ele = 0; ele < 8; ele++) {
unsigned int pa = 0, pb = 0;
for (int i = 0; i < 4; i++) {
int ia = (laneWrapped + ele * 4 + i) % 16 + 1;
int ib = (ele * 4 + i + laneWrapped) % 16 + 1;
pa |= (ia & 0xFF) << (i * 8);
pb |= (ib & 0xFF) << (i * 8);
}
a_frag[ele] = (int)pa;
b_frag[ele] = (int)pb;
}
# if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x64_iu8)
c_frag = __builtin_amdgcn_wmma_i32_16x16x64_iu8(false, a_frag, false, b_frag, c_frag, false, false);
# endif
volatile int sink = 0;
for (int i = 0; i < 8; i++) sink += c_frag[i];
i8_passed = (sink != 0 || tid >= 32);
(void)sink;
}
if (lane_id == 0) {
atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0);
atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0);
atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0);
}
#elif ARCH_RDNA35_APU
// ========================================================================
// RDNA 3.5 APU (gfx115x - Strix) WMMA Tests
// Uses GFX11 WMMA builtins (wmma-256b-insts) with _Vector<16> inputs
// These are DIFFERENT from GFX12 builtins which use _Vector<8> inputs
// ========================================================================
bool f16_passed = true;
bool bf16_passed = true;
bool i8_passed = true;
const int laneWrapped = lane_id % 16;
const int laneGroup = lane_id / 16;
// GFX11 WMMA uses 16-element input vectors (wmma-256b-insts)
using wmma_f16_frag_gfx11 =
__attribute__((__vector_size__(16 * sizeof(_Float16)))) _Float16;
using wmma_bf16_frag_gfx11 =
__attribute__((__vector_size__(16 * sizeof(short)))) short;
using wmma_i8_frag_gfx11 = __attribute__((
__vector_size__(4 * sizeof(int)))) int; // 4 x int32 for 16 x int8 packed
// ---- FP16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_f16_w32 ----
{
wmma_f16_frag_gfx11 a_frag;
wmma_f16_frag_gfx11 b_frag;
wmma_f32_frag c_frag = { 0 };
for(int ele = 0; ele < 16; ele++)
{
a_frag[ele] =
(_Float16) ((laneWrapped + ele + laneGroup * 8) % 16 + 1) * 0.1f;
b_frag[ele] =
(_Float16) ((ele + laneGroup * 8 + laneWrapped) % 16 + 1) * 0.1f;
}
for(int i = 0; i < 8; i++)
c_frag[i] = 0.0f;
# if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_f16_w32)
c_frag = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a_frag, b_frag, c_frag);
# endif
f16_passed = wmma_frag_f32_nonempty_finite(c_frag);
}
// ---- BF16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32 ----
{
wmma_bf16_frag_gfx11 a_frag;
wmma_bf16_frag_gfx11 b_frag;
wmma_f32_frag c_frag = { 0 };
for(int ele = 0; ele < 16; ele++)
{
float val = (float) ((laneWrapped + ele) % 16 + 1) * 0.1f;
a_frag[ele] = (short) (*(unsigned int*) &val >> 16);
b_frag[ele] = (short) (*(unsigned int*) &val >> 16);
}
for(int i = 0; i < 8; i++)
c_frag[i] = 0.0f;
# if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32)
c_frag = __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32(a_frag, b_frag, c_frag);
# endif
bf16_passed = wmma_frag_f32_nonempty_finite(c_frag);
}
// ---- INT8 WMMA: __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32 ----
{
wmma_i8_frag_gfx11 a_frag; // 4 x int32 for 16 x int8 packed
wmma_i8_frag_gfx11 b_frag;
wmma_i32_frag c_frag = { 0 };
// Pack 4 INT8 values into each of 4 int32 elements (16 total)
for(int ele = 0; ele < 4; ele++)
{
unsigned int packed_a = 0;
unsigned int packed_b = 0;
for(int i = 0; i < 4; i++)
{
int val_a = ((laneWrapped + ele * 4 + i) % 16 + 1);
int val_b = ((ele * 4 + i + laneWrapped) % 16 + 1);
packed_a |= (val_a & 0xFF) << (i * 8);
packed_b |= (val_b & 0xFF) << (i * 8);
}
a_frag[ele] = (int) packed_a;
b_frag[ele] = (int) packed_b;
}
for(int i = 0; i < 8; i++)
c_frag[i] = 0;
# if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32)
c_frag = __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32(false, a_frag, false, b_frag,
c_frag, false);
# endif
i8_passed = wmma_frag_i32_nonempty(c_frag);
}
if(lane_id == 0)
{
atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0);
atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0);
atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0);
}
#elif defined(__gfx1200__) || defined(__gfx1201__)
// ========================================================================
// RDNA4 Consumer (gfx1200/gfx1201 - RX 9060 / RX 9070 families) WMMA Tests
// Uses GFX12 WMMA builtins (wmma-128b-insts) with _Vector<8> inputs
// ========================================================================
bool f16_passed = true;
bool bf16_passed = true;
bool i8_passed = true;
const int laneWrapped = lane_id % 16;
const int laneGroup = lane_id / 16;
const int WMMA_DATA_WIDTH = 8;
// ---- FP16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12 ----
{
wmma_f16_frag a_frag;
wmma_f16_frag b_frag;
wmma_f32_frag c_frag = { 0 };
for(int ele = 0; ele < WMMA_DATA_WIDTH; ele++)
{
a_frag[ele] =
(_Float16) ((laneWrapped + ele + laneGroup * WMMA_DATA_WIDTH) % 16 + 1) *
0.1f;
b_frag[ele] =
(_Float16) ((ele + laneGroup * WMMA_DATA_WIDTH + laneWrapped) % 16 + 1) *
0.1f;
}
for(int i = 0; i < 8; i++)
c_frag[i] = 0.0f;
# if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12)
c_frag = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12(a_frag, b_frag, c_frag);
# endif
f16_passed = wmma_frag_f32_nonempty_finite(c_frag);
}
// ---- BF16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12 ----
{
wmma_bf16_frag a_frag;
wmma_bf16_frag b_frag;
wmma_f32_frag c_frag = { 0 };
for(int ele = 0; ele < WMMA_DATA_WIDTH; ele++)
{
float val = (float) ((laneWrapped + ele) % 16 + 1) * 0.1f;
a_frag[ele] = (short) (*(unsigned int*) &val >> 16);
b_frag[ele] = (short) (*(unsigned int*) &val >> 16);
}
for(int i = 0; i < 8; i++)
c_frag[i] = 0.0f;
# if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12)
c_frag =
__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12(a_frag, b_frag, c_frag);
# endif
bf16_passed = wmma_frag_f32_nonempty_finite(c_frag);
}
// ---- INT8 WMMA: __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12 ----
{
wmma_i8_frag a_frag; // 2 x int32 for 8 x int8 packed
wmma_i8_frag b_frag;
wmma_i32_frag c_frag = { 0 };
// Pack 4 INT8 values into each of 2 int32 elements
for(int ele = 0; ele < 2; ele++)
{
unsigned int packed_a = 0;
unsigned int packed_b = 0;
for(int i = 0; i < 4; i++)
{
int val_a = ((laneWrapped + ele * 4 + i) % 16 + 1);
int val_b = ((ele * 4 + i + laneWrapped) % 16 + 1);
packed_a |= (val_a & 0xFF) << (i * 8);
packed_b |= (val_b & 0xFF) << (i * 8);
}
a_frag[ele] = (int) packed_a;
b_frag[ele] = (int) packed_b;
}
for(int i = 0; i < 8; i++)
c_frag[i] = 0;
# if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12)
c_frag = __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12(false, a_frag, false,
b_frag, c_frag, false);
# endif
i8_passed = wmma_frag_i32_nonempty(c_frag);
}
if(lane_id == 0)
{
atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0);
atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0);
atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0);
}
#else
// WMMA not available on CDNA architectures (use MFMA instead)
// Mark as bypassed
if(lane_id == 0)
{
atomicMin(&results->wmma_f16_passed, -1);
atomicMin(&results->wmma_bf16_passed, -1);
atomicMin(&results->wmma_i8_passed, -1);
}
#endif
}
// ============================================================================
// Section 16: RDNA4 Consumer-Specific Features (RX 9060 / RX 9070 families / gfx1200/gfx1201)
// ============================================================================
//
// RDNA4 CONSUMER GPU FEATURES (RX 9070 XT, RX 9070, RX 9060 series):
// ==================================================================
//
// ARCHITECTURE OVERVIEW:
// ----------------------
// - GFX12 architecture (gfx1200/gfx1201)
// - WGP (Workgroup Processor) based - 2 CUs per WGP
// - Wave32 native, Wave64 emulated
// - FP8/BF8 OCP (Open Compute Platform) format support
//
// KEY DIFFERENCES FROM CDNA (MI series):
// --------------------------------------
// - No hardware FP64 atomics (limited FP64 in general)
// - Graphics-focused instruction set (more pixel/vertex ops)
// - Different memory hierarchy (infinity cache)
// - WGP instead of CU-centric design
//
// RDNA4 SPECIFIC INSTRUCTIONS:
// ----------------------------
// - FP8/BF8 conversions (OCP format)
// - Async LDS operations
//
// This test verifies RDNA4-specific features when running on consumer GPUs.
__device__ void
test_rdna4_features([[maybe_unused]] TestResults* results, [[maybe_unused]] int lane_id)
{
#if defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU
bool passed = true;
// Test 1: Wave32 operations (RDNA4 is Wave32 native)
// Wave32 ballot mask test
uint32_t ballot_mask = __ballot(1);
passed = passed && (ballot_mask != 0);
// Test 2: Subgroup operations
int lane_val = lane_id;
int first_active = __shfl(lane_val, 0);
passed = passed && (first_active == 0);
// Test 3: FP16 packed operations (RDNA4 has optimized FP16)
half2 h2_val = __float2half2_rn(1.5f);
float converted = __half2float(__low2half(h2_val));
passed = passed && (fabsf(converted - 1.5f) < 0.01f);
// Test 4: WGP local barrier (implicit in __syncthreads on RDNA4)
__syncthreads();
if(lane_id == 0)
{
atomicAdd(&results->lane_id_passed, passed ? 1 : 0);
}
#else
// Not RDNA4 - skip
(void) results;
(void) lane_id;
#endif
}
// ============================================================================
// Main Mega Kernel
// ============================================================================
extern "C" __global__ void
mega_kernel(
TestResults* results, float* global_float, double* global_double, int* global_int,
float* input_buffer, float* output_buffer,
float* async_lds_src, // Source buffer for async LDS load test
float* async_lds_dst, // Destination buffer for async LDS store test
int buffer_size,
int mfma_mode, // 0=both, 1=asm only, 2=builtin only
hipTextureObject_t tex_obj, // Texture for TEX load test (Strix); 0 if not used
hipSurfaceObject_t surf_obj // Surface for TEX store test (Strix); 0 if not used
)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int lane_id = threadIdx.x % warpSize;
// Use active flag instead of early return to avoid __syncthreads() deadlock
// When buffer_size is not a multiple of blockDim.x, some threads in the last
// block would return early while others hit barriers, causing deadlock
bool active = (tid < buffer_size);
// results (and other global buffers) are initialized on the host before launch
// (main.cpp). Do not memset here: with grid_size > 1, only tid==0 would run it and
// __syncthreads() is block-local, so other blocks could start updating results via
// atomics before memset completes.
// =========== Run all tests ===========
// 1. Warp Operations
test_warp_shuffle(results, lane_id);
test_warp_ballot(results, lane_id);
test_warp_permute(results, lane_id);
test_warp_reduce(results, lane_id);
// 2. FP8/BF8 Conversions
test_fp8_conversions(results, lane_id);
test_bf8_conversions(results, lane_id);
// 3. FP16/BF16 Operations
test_fp16_operations(results, lane_id);
test_bf16_operations(results, lane_id);
// 4. FP32/FP64 Arithmetic
test_fp32_arithmetic(results, lane_id);
test_fp64_arithmetic(results, lane_id);
// 5. Integer Operations
test_int32_operations(results, lane_id);
test_int64_operations(results, lane_id);
// 6. Atomic Operations
test_atomic_add_f32(global_float, results, lane_id);
__syncthreads();
test_atomic_add_f64(global_double, results, lane_id);
__syncthreads();
test_atomic_min_max_f32(results, lane_id);
__syncthreads();
test_atomic_int(global_int, results, lane_id);
__syncthreads();
// 6.5. Comprehensive Atomic Operations (Inline ASM)
test_atomic_operations_asm(global_int, global_float, global_double, results, lane_id,
tid);
__syncthreads();
// 7. LDS Operations
test_lds_operations(results, lane_id, threadIdx.x);
// 8. Transcendental Functions
test_transcendental(results, lane_id);
// 9. Memory Operations (guard with active flag to avoid out-of-bounds access)
if(active)
{
test_global_memory(input_buffer, output_buffer, results, lane_id, tid);
}
// 9.5. VMEM (Vector Memory) Comprehensive Tests
// Always entered so all threads hit __syncthreads inside; global_ptr uses tid only when
// tid < buffer_size (see in_bounds in test_vmem_operations).
test_vmem_operations(input_buffer, results, lane_id, tid, buffer_size, tex_obj,
surf_obj);
__syncthreads();
// 10. DOT Products
test_dot_products(results, lane_id);
// 10.3 MFMA (Matrix Fused Multiply-Add) Operations
test_mfma_operations(results, lane_id, mfma_mode);
// 10.5 Dual-Issue VALU Patterns (gfx942+)
test_dual_issue_patterns(results, lane_id, tid);
// 11. Lane/Thread Info
test_lane_thread_info(results, lane_id);
// 12. Packed Operations
test_packed_operations(results, lane_id);
// 13. Async LDS Operations (MI350/RDNA4)
test_async_lds_operations(async_lds_src, async_lds_dst, results, lane_id, tid);
__syncthreads();
// 14. RDNA4 Consumer-Specific Features (RX 9060 / RX 9070 families)
test_rdna4_features(results, lane_id);
__syncthreads();
// 15. WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4 / RDNA 3.5)
test_wmma_operations(results, lane_id, tid);
__syncthreads();
}