mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-12 07:23:45 +08:00
22a8575199
Keep official openpilot ancestry and dependency licenses. Store resources directly in Git, remove LFS setup requirements, and leave the downloadable big model outside the source tree.
1044 lines
41 KiB
Python
1044 lines
41 KiB
Python
"""Tests for VOPC instructions - vector compare operations.
|
|
|
|
Includes: v_cmp_class_f32, v_cmp_class_f16, v_cmp_eq_*, v_cmp_lt_*, v_cmp_gt_*
|
|
"""
|
|
import unittest
|
|
from test.amd.hw.helpers import *
|
|
|
|
VCC = 106 # SGPR index for VCC_LO
|
|
|
|
class TestCmpClass(unittest.TestCase):
|
|
"""Tests for V_CMP_CLASS_F32 float classification."""
|
|
|
|
def test_cmp_class_quiet_nan(self):
|
|
"""V_CMP_CLASS_F32 detects quiet NaN."""
|
|
quiet_nan = 0x7fc00000
|
|
instructions = [
|
|
s_mov_b32(s[0], quiet_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0b0000000010), # bit 1 = quiet NaN
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect quiet NaN")
|
|
|
|
def test_cmp_class_signaling_nan(self):
|
|
"""V_CMP_CLASS_F32 detects signaling NaN."""
|
|
signal_nan = 0x7f800001
|
|
instructions = [
|
|
s_mov_b32(s[0], signal_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0b0000000001), # bit 0 = signaling NaN
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect signaling NaN")
|
|
|
|
def test_cmp_class_positive_inf(self):
|
|
"""V_CMP_CLASS_F32 detects +inf."""
|
|
pos_inf = 0x7f800000
|
|
instructions = [
|
|
s_mov_b32(s[0], pos_inf),
|
|
s_mov_b32(s[1], 0b1000000000), # bit 9 = +inf
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], s[1]),
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect +inf")
|
|
|
|
def test_cmp_class_negative_inf(self):
|
|
"""V_CMP_CLASS_F32 detects -inf."""
|
|
neg_inf = 0xff800000
|
|
instructions = [
|
|
s_mov_b32(s[0], neg_inf),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0b0000000100), # bit 2 = -inf
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect -inf")
|
|
|
|
def test_cmp_class_normal_positive(self):
|
|
"""V_CMP_CLASS_F32 detects positive normal."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 1.0),
|
|
s_mov_b32(s[1], 0b0100000000), # bit 8 = positive normal
|
|
v_mov_b32_e32(v[1], s[1]),
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect positive normal")
|
|
|
|
def test_cmp_class_normal_negative(self):
|
|
"""V_CMP_CLASS_F32 detects negative normal."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], -1.0),
|
|
v_mov_b32_e32(v[1], 0b0000001000), # bit 3 = negative normal
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect negative normal")
|
|
|
|
def test_cmp_class_quiet_nan_not_signaling(self):
|
|
"""Quiet NaN does not match signaling NaN mask."""
|
|
quiet_nan = 0x7fc00000
|
|
instructions = [
|
|
s_mov_b32(s[0], quiet_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0b0000000001), # bit 0 = signaling NaN only
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "Quiet NaN should not match signaling mask")
|
|
|
|
def test_cmp_class_signaling_nan_not_quiet(self):
|
|
"""Signaling NaN does not match quiet NaN mask."""
|
|
signal_nan = 0x7f800001
|
|
instructions = [
|
|
s_mov_b32(s[0], signal_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0b0000000010), # bit 1 = quiet NaN only
|
|
v_cmp_class_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "Signaling NaN should not match quiet mask")
|
|
|
|
def test_v_cmp_lg_f32_nan(self):
|
|
"""v_cmp_lg_f32 is ordered not-equal (<>): NaN <> x should be False per IEEE 754."""
|
|
quiet_nan = 0x7fc00000
|
|
one_f32 = 0x3f800000 # 1.0f
|
|
instructions = [
|
|
s_mov_b32(s[0], quiet_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
s_mov_b32(s[1], one_f32),
|
|
v_mov_b32_e32(v[1], s[1]),
|
|
v_cmp_lg_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "v_cmp_lg_f32(NaN, 1.0) should be 0")
|
|
|
|
def test_v_cmp_neq_f32_nan(self):
|
|
"""v_cmp_neq_f32 is unordered not-equal (!=): NaN != x should be True per IEEE 754."""
|
|
quiet_nan = 0x7fc00000
|
|
one_f32 = 0x3f800000 # 1.0f
|
|
instructions = [
|
|
s_mov_b32(s[0], quiet_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
s_mov_b32(s[1], one_f32),
|
|
v_mov_b32_e32(v[1], s[1]),
|
|
v_cmp_neq_f32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "v_cmp_neq_f32(NaN, 1.0) should be 1")
|
|
|
|
def test_v_cmp_sets_vcc_bits(self):
|
|
"""V_CMP_EQ sets VCC bits based on per-lane comparison."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 5),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], s[0]),
|
|
v_cmp_eq_u32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=4)
|
|
self.assertEqual(st.vcc & 0xf, 0xf, "All lanes should match")
|
|
|
|
|
|
class TestCmpClassF16(unittest.TestCase):
|
|
"""Tests for V_CMP_CLASS_F16 float classification.
|
|
|
|
Class bit mapping:
|
|
bit 0 = signaling NaN
|
|
bit 1 = quiet NaN
|
|
bit 2 = -infinity
|
|
bit 3 = -normal
|
|
bit 4 = -denormal
|
|
bit 5 = -zero
|
|
bit 6 = +zero
|
|
bit 7 = +denormal
|
|
bit 8 = +normal
|
|
bit 9 = +infinity
|
|
"""
|
|
|
|
def test_cmp_class_f16_positive_zero(self):
|
|
"""V_CMP_CLASS_F16: +zero matches bit 6."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 0x0000), # f16 +0.0
|
|
v_mov_b32_e32(v[1], 0x40), # bit 6 = +zero
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect positive zero")
|
|
|
|
def test_cmp_class_f16_negative_zero(self):
|
|
"""V_CMP_CLASS_F16: -zero matches bit 5."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x8000), # f16 -0.0
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0x20), # bit 5 = -zero
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect negative zero")
|
|
|
|
def test_cmp_class_f16_positive_normal(self):
|
|
"""V_CMP_CLASS_F16: +1.0 (normal) matches bit 8."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x3c00), # f16 +1.0
|
|
s_mov_b32(s[1], 0x100), # bit 8 = +normal
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], s[1]),
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect positive normal")
|
|
|
|
def test_cmp_class_f16_negative_normal(self):
|
|
"""V_CMP_CLASS_F16: -1.0 (normal) matches bit 3."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0xbc00), # f16 -1.0
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0x08), # bit 3 = -normal
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect negative normal")
|
|
|
|
def test_cmp_class_f16_positive_infinity(self):
|
|
"""V_CMP_CLASS_F16: +inf matches bit 9."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x7c00), # f16 +inf
|
|
s_mov_b32(s[1], 0x200), # bit 9 = +inf
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], s[1]),
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect positive infinity")
|
|
|
|
def test_cmp_class_f16_negative_infinity(self):
|
|
"""V_CMP_CLASS_F16: -inf matches bit 2."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0xfc00), # f16 -inf
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0x04), # bit 2 = -inf
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect negative infinity")
|
|
|
|
def test_cmp_class_f16_quiet_nan(self):
|
|
"""V_CMP_CLASS_F16: quiet NaN matches bit 1."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x7e00), # f16 quiet NaN
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0x02), # bit 1 = quiet NaN
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect quiet NaN")
|
|
|
|
def test_cmp_class_f16_signaling_nan(self):
|
|
"""V_CMP_CLASS_F16: signaling NaN matches bit 0."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x7c01), # f16 signaling NaN
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0x01), # bit 0 = signaling NaN
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect signaling NaN")
|
|
|
|
def test_cmp_class_f16_positive_denormal(self):
|
|
"""V_CMP_CLASS_F16: positive denormal matches bit 7."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 1), # f16 +denormal (0x0001)
|
|
v_mov_b32_e32(v[1], 0x80), # bit 7 = +denormal
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect positive denormal")
|
|
|
|
def test_cmp_class_f16_negative_denormal(self):
|
|
"""V_CMP_CLASS_F16: negative denormal matches bit 4."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x8001), # f16 -denormal
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], 0x10), # bit 4 = -denormal
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Should detect negative denormal")
|
|
|
|
def test_cmp_class_f16_combined_mask_zeros(self):
|
|
"""V_CMP_CLASS_F16: mask 0x60 covers both +zero and -zero."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 0), # f16 +0.0
|
|
v_mov_b32_e32(v[1], 0x60), # bits 5 and 6 (+-zero)
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "VCC should be 1 for +zero with mask 0x60")
|
|
|
|
def test_cmp_class_f16_combined_mask_1f8(self):
|
|
"""V_CMP_CLASS_F16: mask 0x1f8 covers -normal,-denorm,-zero,+zero,+denorm,+normal.
|
|
|
|
This is the exact mask used in the f16 sin kernel at PC=46.
|
|
"""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 0), # f16 +0.0
|
|
s_mov_b32(s[0], 0x1f8),
|
|
v_mov_b32_e32(v[1], s[0]), # mask 0x1f8
|
|
v_cmp_class_f16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "VCC should be 1 for +zero with mask 0x1f8")
|
|
|
|
def test_cmp_class_f16_vop3_encoding(self):
|
|
"""V_CMP_CLASS_F16 in VOP3 encoding (v_cmp_class_f16_e64)."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 0), # f16 +0.0
|
|
s_mov_b32(s[0], 0x1f8), # class mask
|
|
v_cmp_class_f16_e64(VCC_LO, v[0], s[0]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "VCC should be 1 for +zero with VOP3 encoding")
|
|
|
|
def test_cmp_class_f16_vop3_normal_positive(self):
|
|
"""V_CMP_CLASS_F16 VOP3 encoding with +1.0 (normal)."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x3c00), # f16 +1.0
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
s_mov_b32(s[1], 0x1f8), # class mask
|
|
v_cmp_class_f16_e64(VCC_LO, v[0], s[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "VCC should be 1 for +1.0 (normal) with mask 0x1f8")
|
|
|
|
def test_cmp_class_f16_vop3_nan_fails_mask(self):
|
|
"""V_CMP_CLASS_F16 VOP3: NaN should NOT match mask 0x1f8 (no NaN bits set)."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x7e00), # f16 quiet NaN
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
s_mov_b32(s[1], 0x1f8), # class mask
|
|
v_cmp_class_f16_e64(VCC_LO, v[0], s[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "VCC should be 0 for NaN with mask 0x1f8 (no NaN bits)")
|
|
|
|
def test_cmp_class_f16_vop3_inf_fails_mask(self):
|
|
"""V_CMP_CLASS_F16 VOP3: +inf should NOT match mask 0x1f8 (no inf bits set)."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x7c00), # f16 +inf
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
s_mov_b32(s[1], 0x1f8), # class mask
|
|
v_cmp_class_f16_e64(VCC_LO, v[0], s[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "VCC should be 0 for +inf with mask 0x1f8 (no inf bits)")
|
|
|
|
|
|
class TestCmpInt(unittest.TestCase):
|
|
"""Tests for integer comparison operations."""
|
|
|
|
def test_v_cmp_eq_u32(self):
|
|
"""V_CMP_EQ_U32 sets VCC bits based on per-lane comparison."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 5),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], s[0]),
|
|
v_cmp_eq_u32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=4)
|
|
self.assertEqual(st.vcc & 0xf, 0xf, "All lanes should match")
|
|
|
|
def test_v_cmp_ne_u32_with_zero(self):
|
|
"""V_CMP_NE_U32: compare with zero, used for int->bool cast."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[1], 0),
|
|
v_cmp_eq_u32_e32(1, v[255]), # vcc = (lane == 1)
|
|
v_cndmask_b32_e64(v[1], v[1], 1, VCC_LO), # v1[lane1] = 1
|
|
v_cmp_ne_u32_e32(0, v[1]), # vcc = (0 != v1)
|
|
v_cndmask_b32_e64(v[0], 0, 1, VCC_LO), # v0 = vcc ? 1 : 0
|
|
]
|
|
st = run_program(instructions, n_lanes=2)
|
|
self.assertEqual(st.vgpr[0][0], 0, "lane 0: 0 != 0 should be false")
|
|
self.assertEqual(st.vgpr[1][0], 1, "lane 1: 0 != 1 should be true")
|
|
self.assertEqual(st.vcc & 0x3, 0x2, "VCC should be 0b10")
|
|
|
|
def test_v_cmp_ne_u32_all_nonzero(self):
|
|
"""V_CMP_NE_U32: all lanes have nonzero values."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[1], 5),
|
|
v_cmp_ne_u32_e32(0, v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=4)
|
|
self.assertEqual(st.vcc & 0xf, 0xf, "All lanes should be != 0")
|
|
|
|
def test_cmp_eq_u16_opsel_lo_lo(self):
|
|
"""V_CMP_EQ_U16 comparing lo halves."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x12340005), # lo=5, hi=0x1234
|
|
s_mov_b32(s[1], 0xABCD0005), # lo=5, hi=0xABCD
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], s[1]),
|
|
v_cmp_eq_u16_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Lo halves should be equal")
|
|
|
|
def test_cmp_eq_u16_opsel_hi_hi(self):
|
|
"""V_CMP_EQ_U16 comparing hi halves with VOP3 opsel."""
|
|
instructions = [
|
|
s_mov_b32(s[2], 0x00051234), # hi=5, lo=0x1234
|
|
v_mov_b32_e32(v[0], s[2]),
|
|
s_mov_b32(s[2], 0x0005ABCD), # hi=5, lo=0xABCD
|
|
v_mov_b32_e32(v[1], s[2]),
|
|
v_cmp_eq_u16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.sgpr[0] & 1, 1, "Hi halves should be equal: 5==5")
|
|
|
|
def test_cmp_eq_u16_opsel_hi_hi_equal(self):
|
|
"""V_CMP_EQ_U16 VOP3 with opsel=3 compares hi halves (equal case)."""
|
|
instructions = [
|
|
s_mov_b32(s[2], 0x12340005), # lo=5, hi=0x1234
|
|
v_mov_b32_e32(v[0], s[2]),
|
|
s_mov_b32(s[2], 0x12340009), # lo=9, hi=0x1234
|
|
v_mov_b32_e32(v[1], s[2]),
|
|
v_cmp_eq_u16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.sgpr[0] & 1, 1, "hi==hi should be true: 0x1234==0x1234")
|
|
|
|
def test_cmp_gt_u16_opsel_hi(self):
|
|
"""V_CMP_GT_U16 VOP3 with opsel=3 compares hi halves."""
|
|
instructions = [
|
|
s_mov_b32(s[2], 0x99990005), # lo=5, hi=0x9999
|
|
v_mov_b32_e32(v[0], s[2]),
|
|
s_mov_b32(s[2], 0x12340005), # lo=5, hi=0x1234
|
|
v_mov_b32_e32(v[1], s[2]),
|
|
v_cmp_gt_u16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.sgpr[0] & 1, 1, "hi>hi should be true: 0x9999>0x1234")
|
|
|
|
|
|
class TestCmpFloat(unittest.TestCase):
|
|
"""Tests for float comparison operations."""
|
|
|
|
def test_v_cmp_lt_f16_vsrc1_hi(self):
|
|
"""V_CMP_LT_F16 with both operands from high half using VOP3 opsel."""
|
|
instructions = [
|
|
s_mov_b32(s[2], 0x3c000000), # hi=1.0 (f16), lo=0
|
|
v_mov_b32_e32(v[0], s[2]),
|
|
s_mov_b32(s[2], 0x40000000), # hi=2.0 (f16), lo=0
|
|
v_mov_b32_e32(v[1], s[2]),
|
|
v_cmp_lt_f16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.sgpr[0] & 1, 1, "1.0 < 2.0 should be true")
|
|
|
|
def test_v_cmp_gt_f16_vsrc1_hi(self):
|
|
"""V_CMP_GT_F16 with both operands from high half using VOP3 opsel."""
|
|
instructions = [
|
|
s_mov_b32(s[2], 0x40000000), # hi=2.0 (f16), lo=0
|
|
v_mov_b32_e32(v[0], s[2]),
|
|
s_mov_b32(s[2], 0x3c000000), # hi=1.0 (f16), lo=0
|
|
v_mov_b32_e32(v[1], s[2]),
|
|
v_cmp_gt_f16_e64(vdst=s[0], src0=v[0], src1=v[1], opsel=3),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.sgpr[0] & 1, 1, "2.0 > 1.0 should be true")
|
|
|
|
def test_v_cmp_eq_f16_vsrc1_hi_equal(self):
|
|
"""v_cmp_eq_f16 with equal low and high halves."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x42004200), # hi=3.0 (0x4200), lo=3.0 (0x4200)
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_cmp_eq_f16_e32(v[0], v[0].h),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Expected vcc=1 (3.0 == 3.0)")
|
|
|
|
def test_v_cmp_neq_f16_vsrc1_hi(self):
|
|
"""v_cmp_neq_f16 with different low and high halves."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x40003c00), # hi=2.0 (0x4000), lo=1.0 (0x3c00)
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_cmp_lg_f16_e32(v[0], v[0].h),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Expected vcc=1 (1.0 != 2.0)")
|
|
|
|
def test_v_cmp_eq_f16_src0_hi(self):
|
|
"""v_cmp_eq_f16 with src0 from high half (true16 384+n encoding)."""
|
|
cmp = v_cmp_eq_f16_e32(v[0], v[1])
|
|
cmp._raw += 128 # src0 v[0] -> v[0].h, the dsl can't encode hi-half src0 yet
|
|
instructions = [
|
|
s_mov_b32(s[0], 0x42003c00), # hi=3.0, lo=1.0
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
s_mov_b32(s[0], 0x47004200), # hi=7.0, lo=3.0
|
|
v_mov_b32_e32(v[1], s[0]),
|
|
cmp,
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "Expected vcc=1 (v0.hi 3.0 == v1.lo 3.0)")
|
|
|
|
def test_v_cmp_nge_f16_inf_self(self):
|
|
"""v_cmp_nge_f16 comparing -inf with itself (unordered less than).
|
|
|
|
Regression test: -inf < -inf should be false (IEEE 754).
|
|
"""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0xFC00FC00), # both halves = -inf (0xFC00)
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_cmp_nge_f16_e32(v[0], v[0].h),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "Expected vcc=0 (-inf >= -inf)")
|
|
|
|
def test_v_cmp_f16_multilane(self):
|
|
"""v_cmp_lt_f16 with vsrc1=v128 across multiple lanes."""
|
|
instructions = [
|
|
# Lane 0: v0 = 0x40003c00 (hi=2.0, lo=1.0) -> 1.0 < 2.0 = true
|
|
# Lane 1: v0 = 0x3c004000 (hi=1.0, lo=2.0) -> 2.0 < 1.0 = false
|
|
v_mov_b32_e32(v[0], 0x40003c00), # default
|
|
v_cmp_eq_u32_e32(1, v[255]), # vcc = (lane == 1)
|
|
v_cndmask_b32_e64(v[0], v[0], 0x3c004000, SrcEnum.VCC_LO),
|
|
v_cmp_lt_f16_e32(v[0], v[0].h),
|
|
]
|
|
st = run_program(instructions, n_lanes=2)
|
|
self.assertEqual(st.vcc & 1, 1, "Lane 0: expected vcc=1 (1.0 < 2.0)")
|
|
self.assertEqual((st.vcc >> 1) & 1, 0, "Lane 1: expected vcc=0 (2.0 < 1.0)")
|
|
|
|
|
|
class TestVOP3VOPCModifiers(unittest.TestCase):
|
|
"""Tests for VOP3 VOPC with abs/neg modifiers."""
|
|
|
|
def test_v_cmp_ge_f32_abs_both(self):
|
|
"""v_cmp_ge_f32 with abs on both sources: abs(0.0) >= abs(-1.0) = false.
|
|
|
|
Regression test: int16 mod operation uses v_cmp_ge_f32 with abs modifiers.
|
|
"""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 0.0),
|
|
v_mov_b32_e32(v[1], -1.0),
|
|
# abs=0b11 means abs(src0) and abs(src1)
|
|
v_cmp_ge_f32_e64(VCC_LO, v[0], v[1], abs=0b11),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "abs(0.0) >= abs(-1.0) should be false")
|
|
|
|
def test_v_cmp_ge_f32_abs_negative_divisor(self):
|
|
"""v_cmp_ge_f32 with abs: remainder check for negative divisor.
|
|
|
|
Tests the exact comparison used in int16 mod: abs(rem_f) >= abs(div_f).
|
|
For 1 % -1: rem_f = 0.0, div_f = -1.0, so abs(0.0) >= abs(-1.0) = false.
|
|
"""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 0.0), # remainder as float
|
|
v_mov_b32_e32(v[1], -1.0), # divisor as float
|
|
v_cmp_ge_f32_e64(VCC_LO, v[0], v[1], abs=0b11),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "abs(0.0) >= abs(-1.0) should be false")
|
|
|
|
def test_v_cmp_ge_f32_abs_small_remainder(self):
|
|
"""v_cmp_ge_f32 with abs: abs(-0.5) >= abs(-3.0) = false."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], -0.5),
|
|
v_mov_b32_e32(v[1], -3.0),
|
|
v_cmp_ge_f32_e64(VCC_LO, v[0], v[1], abs=0b11),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "abs(-0.5) >= abs(-3.0) should be false")
|
|
|
|
def test_v_cmp_ge_f32_abs_equal(self):
|
|
"""v_cmp_ge_f32 with abs: abs(-1.0) >= abs(1.0) = true."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], -1.0),
|
|
v_mov_b32_e32(v[1], 1.0),
|
|
v_cmp_ge_f32_e64(VCC_LO, v[0], v[1], abs=0b11),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "abs(-1.0) >= abs(1.0) should be true")
|
|
|
|
|
|
class TestVOP3VOPC64Bit(unittest.TestCase):
|
|
"""Tests for VOP3 VOPC with 64-bit operands."""
|
|
|
|
def test_v_cmp_lt_f64_basic(self):
|
|
"""v_cmp_lt_f64: 0.0 < 1.0 = true."""
|
|
zero_f64 = f2i64(0.0)
|
|
one_f64 = f2i64(1.0)
|
|
instructions = [
|
|
s_mov_b32(s[0], zero_f64 & 0xffffffff),
|
|
s_mov_b32(s[1], zero_f64 >> 32),
|
|
s_mov_b32(s[2], one_f64 & 0xffffffff),
|
|
s_mov_b32(s[3], one_f64 >> 32),
|
|
v_cmp_lt_f64_e64(VCC_LO, s[0:1], s[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "0.0 < 1.0 should be true")
|
|
|
|
def test_v_cmp_lt_f64_negative(self):
|
|
"""v_cmp_lt_f64: -1.0 < 0.0 = true."""
|
|
neg_one_f64 = f2i64(-1.0)
|
|
zero_f64 = f2i64(0.0)
|
|
instructions = [
|
|
s_mov_b32(s[0], neg_one_f64 & 0xffffffff),
|
|
s_mov_b32(s[1], neg_one_f64 >> 32),
|
|
s_mov_b32(s[2], zero_f64 & 0xffffffff),
|
|
s_mov_b32(s[3], zero_f64 >> 32),
|
|
v_cmp_lt_f64_e64(VCC_LO, s[0:1], s[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "-1.0 < 0.0 should be true")
|
|
|
|
def test_v_cmp_lt_i64_signed(self):
|
|
"""v_cmp_lt_i64: 0 < -1 (signed) = false."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0),
|
|
s_mov_b32(s[1], 0), # s[0:1] = 0
|
|
s_mov_b32(s[2], 0xffffffff),
|
|
s_mov_b32(s[3], 0xffffffff), # s[2:3] = -1
|
|
v_cmp_lt_i64_e64(VCC_LO, s[0:1], s[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "0 < -1 (signed) should be false")
|
|
|
|
def test_v_cmp_lt_u64_unsigned(self):
|
|
"""v_cmp_lt_u64: 0 < 0xFFFFFFFFFFFFFFFF (unsigned) = true."""
|
|
instructions = [
|
|
s_mov_b32(s[0], 0),
|
|
s_mov_b32(s[1], 0), # s[0:1] = 0
|
|
s_mov_b32(s[2], 0xffffffff),
|
|
s_mov_b32(s[3], 0xffffffff), # s[2:3] = max uint64
|
|
v_cmp_lt_u64_e64(VCC_LO, s[0:1], s[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "0 < max_uint64 should be true")
|
|
|
|
|
|
class TestVOPCF64(unittest.TestCase):
|
|
"""Tests for VOPC (E32 encoding) with 64-bit float operands. Regression test for f64 compare bug."""
|
|
|
|
def test_v_cmp_lt_f64_e32_true(self):
|
|
"""v_cmp_lt_f64_e32: 2.0 < 3.0 = true."""
|
|
lo0, hi0 = f2i64(2.0) & 0xffffffff, f2i64(2.0) >> 32
|
|
lo1, hi1 = f2i64(3.0) & 0xffffffff, f2i64(3.0) >> 32
|
|
instructions = [
|
|
s_mov_b32(s[0], lo0), s_mov_b32(s[1], hi0),
|
|
s_mov_b32(s[2], lo1), s_mov_b32(s[3], hi1),
|
|
v_mov_b32_e32(v[0], s[0]), v_mov_b32_e32(v[1], s[1]),
|
|
v_mov_b32_e32(v[2], s[2]), v_mov_b32_e32(v[3], s[3]),
|
|
v_cmp_lt_f64_e32(v[0:1], v[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "2.0 < 3.0 should be true")
|
|
|
|
def test_v_cmp_lt_f64_e32_false(self):
|
|
"""v_cmp_lt_f64_e32: 3.0 < 2.0 = false."""
|
|
lo0, hi0 = f2i64(3.0) & 0xffffffff, f2i64(3.0) >> 32
|
|
lo1, hi1 = f2i64(2.0) & 0xffffffff, f2i64(2.0) >> 32
|
|
instructions = [
|
|
s_mov_b32(s[0], lo0), s_mov_b32(s[1], hi0),
|
|
s_mov_b32(s[2], lo1), s_mov_b32(s[3], hi1),
|
|
v_mov_b32_e32(v[0], s[0]), v_mov_b32_e32(v[1], s[1]),
|
|
v_mov_b32_e32(v[2], s[2]), v_mov_b32_e32(v[3], s[3]),
|
|
v_cmp_lt_f64_e32(v[0:1], v[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "3.0 < 2.0 should be false")
|
|
|
|
def test_v_cmp_nlt_f64_e32_true(self):
|
|
"""v_cmp_nlt_f64_e32: !(3.0 < 2.0) = true."""
|
|
lo0, hi0 = f2i64(3.0) & 0xffffffff, f2i64(3.0) >> 32
|
|
lo1, hi1 = f2i64(2.0) & 0xffffffff, f2i64(2.0) >> 32
|
|
instructions = [
|
|
s_mov_b32(s[0], lo0), s_mov_b32(s[1], hi0),
|
|
s_mov_b32(s[2], lo1), s_mov_b32(s[3], hi1),
|
|
v_mov_b32_e32(v[0], s[0]), v_mov_b32_e32(v[1], s[1]),
|
|
v_mov_b32_e32(v[2], s[2]), v_mov_b32_e32(v[3], s[3]),
|
|
v_cmp_nlt_f64_e32(v[0:1], v[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "!(3.0 < 2.0) should be true")
|
|
|
|
def test_v_cmp_nlt_f64_e32_false(self):
|
|
"""v_cmp_nlt_f64_e32: !(2.0 < 3.0) = false."""
|
|
lo0, hi0 = f2i64(2.0) & 0xffffffff, f2i64(2.0) >> 32
|
|
lo1, hi1 = f2i64(3.0) & 0xffffffff, f2i64(3.0) >> 32
|
|
instructions = [
|
|
s_mov_b32(s[0], lo0), s_mov_b32(s[1], hi0),
|
|
s_mov_b32(s[2], lo1), s_mov_b32(s[3], hi1),
|
|
v_mov_b32_e32(v[0], s[0]), v_mov_b32_e32(v[1], s[1]),
|
|
v_mov_b32_e32(v[2], s[2]), v_mov_b32_e32(v[3], s[3]),
|
|
v_cmp_nlt_f64_e32(v[0:1], v[2:3]),
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "!(2.0 < 3.0) should be false")
|
|
|
|
|
|
class TestCmpxExec(unittest.TestCase):
|
|
"""Tests for V_CMPX instructions that modify EXEC mask."""
|
|
|
|
def test_v_cmpx_ngt_f32_e64_all_true(self):
|
|
"""V_CMPX_NGT_F32_E64: all lanes pass (literal <= all values)."""
|
|
# 131072.0 = 0x48000000
|
|
# All values > 131072, so !(131072 > val) = true for all
|
|
instructions = [
|
|
s_mov_b32(EXEC_LO, 0x7), # 3 lanes active
|
|
v_mov_b32_e32(v[0], f2i(200000.0)), # lane 0
|
|
v_cmp_eq_u32_e32(1, v[255]),
|
|
v_cndmask_b32_e64(v[1], v[0], f2i(300000.0), VCC_LO), # lane 1
|
|
v_cmp_eq_u32_e32(2, v[255]),
|
|
v_cndmask_b32_e64(v[1], v[1], f2i(400000.0), VCC_LO), # lane 2
|
|
# Now v[1] has: lane0=200000, lane1=300000, lane2=400000
|
|
# Compare: !(131072.0 > v[1]) i.e., 131072.0 <= v[1]
|
|
v_cmpx_ngt_f32_e64(EXEC_LO, f2i(131072.0), v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=3)
|
|
# All values > 131072, so all lanes should remain active
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0x7, 0x7, "All 3 lanes should remain active")
|
|
|
|
def test_v_cmpx_ngt_f32_e64_some_false(self):
|
|
"""V_CMPX_NGT_F32_E64: some lanes fail (literal > some values)."""
|
|
instructions = [
|
|
s_mov_b32(EXEC_LO, 0x7), # 3 lanes active
|
|
v_mov_b32_e32(v[0], f2i(100000.0)), # lane 0: 131072 > 100000 = true, so !(true) = false
|
|
v_cmp_eq_u32_e32(1, v[255]),
|
|
v_cndmask_b32_e64(v[1], v[0], f2i(200000.0), VCC_LO), # lane 1: 131072 > 200000 = false, so !(false) = true
|
|
v_cmp_eq_u32_e32(2, v[255]),
|
|
v_cndmask_b32_e64(v[1], v[1], f2i(150000.0), VCC_LO), # lane 2: 131072 > 150000 = false, so !(false) = true
|
|
v_cmpx_ngt_f32_e64(EXEC_LO, f2i(131072.0), v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=3)
|
|
# lane 0: fail (100000 < 131072), lanes 1,2: pass
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0x7, 0x6, "Lanes 1,2 should be active, lane 0 inactive")
|
|
|
|
def test_v_cmpx_ngt_f32_e64_all_false(self):
|
|
"""V_CMPX_NGT_F32_E64: all lanes fail (literal > all values)."""
|
|
instructions = [
|
|
s_mov_b32(EXEC_LO, 0x7), # 3 lanes active
|
|
v_mov_b32_e32(v[0], f2i(100.0)), # all lanes have 100.0
|
|
# 131072 > 100 = true, so !(true) = false for all
|
|
v_cmpx_ngt_f32_e64(EXEC_LO, f2i(131072.0), v[0]),
|
|
]
|
|
st = run_program(instructions, n_lanes=3)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0x7, 0x0, "All lanes should be inactive")
|
|
|
|
def test_v_cmpx_ngt_f32_e64_large_values(self):
|
|
"""V_CMPX_NGT_F32_E64: test with values that trigger Payne-Hanek in sin().
|
|
|
|
This is a regression test for the sin(859240.0) bug.
|
|
Values 859240, 1000000, 100594688 should all pass !(131072 > val).
|
|
"""
|
|
instructions = [
|
|
s_mov_b32(EXEC_LO, 0x7), # 3 lanes active
|
|
v_mov_b32_e32(v[0], f2i(859240.0)), # lane 0
|
|
v_cmp_eq_u32_e32(1, v[255]),
|
|
v_cndmask_b32_e64(v[1], v[0], f2i(1000000.0), VCC_LO), # lane 1
|
|
v_cmp_eq_u32_e32(2, v[255]),
|
|
v_cndmask_b32_e64(v[1], v[1], f2i(100594688.0), VCC_LO), # lane 2
|
|
v_cmpx_ngt_f32_e64(EXEC_LO, f2i(131072.0), v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=3)
|
|
# All values > 131072, so !(131072 > val) = true for all
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0x7, 0x7, "All 3 lanes should remain active")
|
|
|
|
|
|
class TestVCCBehavior(unittest.TestCase):
|
|
"""Tests for VCC condition code behavior."""
|
|
|
|
def test_vcc_all_lanes_true(self):
|
|
"""VCC should have all bits set when all lanes compare true."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 5),
|
|
v_mov_b32_e32(v[1], 5),
|
|
v_cmp_eq_u32_e32(v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=32)
|
|
self.assertEqual(st.vcc, 0xFFFFFFFF, "All 32 lanes should be true")
|
|
|
|
def test_vcc_lane_dependent(self):
|
|
"""VCC should differ per lane based on lane_id comparison."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], 16),
|
|
v_cmp_lt_u32_e32(v[255], v[0]), # lanes 0-15 are < 16
|
|
]
|
|
st = run_program(instructions, n_lanes=32)
|
|
self.assertEqual(st.vcc & 0xFFFF, 0xFFFF, "Lanes 0-15 should be true")
|
|
self.assertEqual(st.vcc >> 16, 0x0000, "Lanes 16-31 should be false")
|
|
|
|
|
|
class TestCmpNge(unittest.TestCase):
|
|
"""Tests for V_CMP_NGE (not-greater-or-equal) with NaN semantics.
|
|
|
|
NGE = !(a >= b). With NaN inputs:
|
|
- If either input is NaN, a >= b is false, so !(false) = true
|
|
- This differs from a < b which returns false for NaN inputs
|
|
"""
|
|
|
|
def test_v_cmp_nge_f32_normal_values(self):
|
|
"""v_cmp_nge_f32: basic comparison with normal floats."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], f2i(1.0)),
|
|
v_mov_b32_e32(v[1], f2i(2.0)),
|
|
v_cmp_nge_f32_e32(v[0], v[1]), # !(1.0 >= 2.0) = !(false) = true
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "!(1.0 >= 2.0) should be true")
|
|
|
|
def test_v_cmp_nge_f32_equal_values(self):
|
|
"""v_cmp_nge_f32: equal values should return false."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], f2i(1.0)),
|
|
v_mov_b32_e32(v[1], f2i(1.0)),
|
|
v_cmp_nge_f32_e32(v[0], v[1]), # !(1.0 >= 1.0) = !(true) = false
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "!(1.0 >= 1.0) should be false")
|
|
|
|
def test_v_cmp_nge_f32_greater_value(self):
|
|
"""v_cmp_nge_f32: greater value should return false."""
|
|
instructions = [
|
|
v_mov_b32_e32(v[0], f2i(2.0)),
|
|
v_mov_b32_e32(v[1], f2i(1.0)),
|
|
v_cmp_nge_f32_e32(v[0], v[1]), # !(2.0 >= 1.0) = !(true) = false
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 0, "!(2.0 >= 1.0) should be false")
|
|
|
|
def test_v_cmp_nge_f32_neg_inf(self):
|
|
"""v_cmp_nge_f32: -inf compared to normal value."""
|
|
neg_inf = 0xff800000 # -inf
|
|
instructions = [
|
|
s_mov_b32(s[0], neg_inf),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], f2i(1.0)),
|
|
v_cmp_nge_f32_e32(v[0], v[1]), # !(-inf >= 1.0) = !(false) = true
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "!(-inf >= 1.0) should be true")
|
|
|
|
def test_v_cmp_nge_f32_clears_inactive_vcc_bits(self):
|
|
"""v_cmp_nge_f32 with partial EXEC clears inactive VCC bits (hardware behavior)."""
|
|
neg_inf = 0xff800000 # -inf
|
|
instructions = [
|
|
# Set VCC to all 1s first
|
|
s_mov_b32(VCC_LO, 0xFFFFFFFF),
|
|
# Set EXEC to only lane 0
|
|
s_mov_b32(EXEC_LO, 0x00000001),
|
|
# v0 = 1.0 for lane 0
|
|
v_mov_b32_e32(v[0], f2i(1.0)),
|
|
# Compare: !(-inf >= 1.0) = true for lane 0
|
|
v_cmp_nge_f32_e32(neg_inf, v[0]),
|
|
]
|
|
st = run_program(instructions, n_lanes=16)
|
|
# Hardware clears inactive lane bits, only active lane results remain
|
|
# Lane 0 result = 1 (true), lanes 1-15 = 0 (cleared)
|
|
self.assertEqual(st.vcc, 0x00000001, "VCC should only have active lane results")
|
|
|
|
def test_v_cmp_nge_f32_nan_src0(self):
|
|
"""v_cmp_nge_f32: NaN in src0 should return true (NaN >= x is false)."""
|
|
quiet_nan = 0x7fc00000
|
|
instructions = [
|
|
s_mov_b32(s[0], quiet_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], f2i(1.0)),
|
|
v_cmp_nge_f32_e32(v[0], v[1]), # !(NaN >= 1.0) = !(false) = true
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "!(NaN >= 1.0) should be true")
|
|
|
|
def test_v_cmp_nge_f32_nan_src1(self):
|
|
"""v_cmp_nge_f32: NaN in src1 should return true (x >= NaN is false)."""
|
|
quiet_nan = 0x7fc00000
|
|
instructions = [
|
|
s_mov_b32(s[0], quiet_nan),
|
|
v_mov_b32_e32(v[0], f2i(1.0)),
|
|
v_mov_b32_e32(v[1], s[0]),
|
|
v_cmp_nge_f32_e32(v[0], v[1]), # !(1.0 >= NaN) = !(false) = true
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "!(1.0 >= NaN) should be true")
|
|
|
|
def test_v_cmp_nge_f32_both_nan(self):
|
|
"""v_cmp_nge_f32: both NaN should return true."""
|
|
quiet_nan = 0x7fc00000
|
|
instructions = [
|
|
s_mov_b32(s[0], quiet_nan),
|
|
v_mov_b32_e32(v[0], s[0]),
|
|
v_mov_b32_e32(v[1], s[0]),
|
|
v_cmp_nge_f32_e32(v[0], v[1]), # !(NaN >= NaN) = !(false) = true
|
|
]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc & 1, 1, "!(NaN >= NaN) should be true")
|
|
|
|
|
|
class TestCmpxPartialWavefront(unittest.TestCase):
|
|
"""Tests for V_CMPX with partial wavefronts (fewer than 32 active lanes).
|
|
|
|
Regression tests for bug where v_cmpx incorrectly set EXEC bits for inactive
|
|
lanes when the wavefront had fewer than 32 lanes. This caused garbage data
|
|
from uninitialized lanes to corrupt memory writes.
|
|
"""
|
|
|
|
def test_v_cmpx_eq_u32_partial_wave_3_lanes(self):
|
|
"""V_CMPX_EQ_U32 with 3 active lanes should only affect those 3 lanes.
|
|
|
|
With n_lanes=3, initial EXEC=0x7. After v_cmpx comparing lane_id == 1,
|
|
only lane 1 should pass, so EXEC should become 0x2 (not have bits 3-31 set).
|
|
"""
|
|
instructions = [
|
|
v_cmpx_eq_u32_e32(1, v[255]), # EXEC = lanes where lane_id == 1
|
|
]
|
|
st = run_program(instructions, n_lanes=3)
|
|
# Only lane 1 should be active (bit 1 set)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0x2,
|
|
"Only lane 1 should be active after v_cmpx_eq_u32 with 3 lanes")
|
|
|
|
def test_v_cmpx_eq_u32_partial_wave_5_lanes(self):
|
|
"""V_CMPX_EQ_U32 with 5 active lanes."""
|
|
instructions = [
|
|
v_cmpx_eq_u32_e32(3, v[255]), # EXEC = lanes where lane_id == 3
|
|
]
|
|
st = run_program(instructions, n_lanes=5)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0x8,
|
|
"Only lane 3 should be active after v_cmpx_eq_u32 with 5 lanes")
|
|
|
|
def test_v_cmpx_lt_u32_partial_wave(self):
|
|
"""V_CMPX_LT_U32 with partial wavefront."""
|
|
# VOPC: src0 < vsrc1, so we need v_cmpx_gt_u32 to get lane_id < 2
|
|
instructions = [
|
|
v_cmpx_gt_u32_e32(2, v[255]), # EXEC = lanes where 2 > lane_id (i.e., lane_id < 2)
|
|
]
|
|
st = run_program(instructions, n_lanes=4)
|
|
# Lanes 0,1 should be active (bits 0,1 set = 0x3)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0x3,
|
|
"Only lanes 0,1 should be active after v_cmpx_gt_u32(2, lane_id) with 4 lanes")
|
|
|
|
def test_v_cmpx_ge_u32_partial_wave(self):
|
|
"""V_CMPX_GE_U32 with partial wavefront."""
|
|
# VOPC: src0 >= vsrc1, so v_cmpx_le_u32(1, lane_id) gives lane_id >= 2? No.
|
|
# v_cmpx_le_u32(src0, vsrc1) = src0 <= vsrc1 = 1 <= lane_id
|
|
instructions = [
|
|
v_cmpx_le_u32_e32(2, v[255]), # EXEC = lanes where 2 <= lane_id (i.e., lane_id >= 2)
|
|
]
|
|
st = run_program(instructions, n_lanes=4)
|
|
# Lanes 2,3 should be active (bits 2,3 set = 0xC)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0xC,
|
|
"Only lanes 2,3 should be active after v_cmpx_le_u32(2, lane_id) with 4 lanes")
|
|
|
|
def test_v_cmpx_ne_u32_partial_wave_all_pass(self):
|
|
"""V_CMPX_NE_U32 where all active lanes pass."""
|
|
instructions = [
|
|
v_cmpx_ne_u32_e32(99, v[255]), # EXEC = lanes where lane_id != 99
|
|
]
|
|
st = run_program(instructions, n_lanes=3)
|
|
# All 3 lanes should remain active (bits 0,1,2 set = 0x7)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0x7,
|
|
"All 3 lanes should remain active when all pass")
|
|
|
|
def test_v_cmpx_eq_u32_partial_wave_none_pass(self):
|
|
"""V_CMPX_EQ_U32 where no active lanes pass."""
|
|
instructions = [
|
|
v_cmpx_eq_u32_e32(99, v[255]), # EXEC = lanes where lane_id == 99
|
|
]
|
|
st = run_program(instructions, n_lanes=3)
|
|
# No lanes should be active
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0x0,
|
|
"No lanes should be active when none pass")
|
|
|
|
def test_v_cmpx_f32_partial_wave(self):
|
|
"""V_CMPX_GT_F32 with partial wavefront - float comparison."""
|
|
instructions = [
|
|
v_cvt_f32_u32_e32(v[0], v[255]), # v[0] = float(lane_id)
|
|
v_mov_b32_e32(v[1], f2i(0.5)), # v[1] = 0.5
|
|
v_cmpx_gt_f32_e32(v[0], v[1]), # EXEC = lanes where v[0] > 0.5
|
|
]
|
|
st = run_program(instructions, n_lanes=4)
|
|
# Lanes 1,2,3 have values > 0.5, lane 0 has 0.0
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0xE,
|
|
"Lanes 1,2,3 should be active (float > 0.5)")
|
|
|
|
def test_v_cmpx_e64_partial_wave(self):
|
|
"""V_CMPX_EQ_U32_E64 (VOP3 encoding) with partial wavefront."""
|
|
instructions = [
|
|
v_cmpx_eq_u32_e64(EXEC_LO, v[255], 2), # EXEC = lanes where lane_id == 2
|
|
]
|
|
st = run_program(instructions, n_lanes=4)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset] & 0xFFFFFFFF, 0x4,
|
|
"Only lane 2 should be active after v_cmpx_eq_u32_e64")
|
|
|
|
class TestClassDenormalRegressions(unittest.TestCase):
|
|
"""Regression tests: V_CMP_CLASS classifies denormals as DENORMAL (raw bits), not as zero class."""
|
|
|
|
def test_class_pos_denormal(self):
|
|
for bits in (0x00000001, 0x007FFFFF):
|
|
instructions = [v_mov_b32_e32(v[0], bits), v_mov_b32_e32(v[1], 0x80), v_cmp_class_f32_e64(VCC_LO, v[0], v[1])]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc, 1, f"bits=0x{bits:08x}") # n_lanes=1
|
|
# ...and it is not the zero class
|
|
instructions = [v_mov_b32_e32(v[0], bits), v_mov_b32_e32(v[1], 0x40), v_cmp_class_f32_e64(VCC_LO, v[0], v[1])]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc, 0, f"bits=0x{bits:08x}")
|
|
|
|
def test_class_neg_denormal(self):
|
|
instructions = [v_mov_b32_e32(v[0], 0x80000001), v_mov_b32_e32(v[1], 0x10), v_cmp_class_f32_e64(VCC_LO, v[0], v[1])]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc, 1) # n_lanes=1
|
|
instructions = [v_mov_b32_e32(v[0], 0x80000001), v_mov_b32_e32(v[1], 0x20), v_cmp_class_f32_e64(VCC_LO, v[0], v[1])]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc, 0) # not the negative-zero class
|
|
|
|
|
|
class TestIntCmpModRegressions(unittest.TestCase):
|
|
"""Regression tests: int compares (i32/u32) honor abs/neg as bit-level sign clear/flip (not integer abs/negate)."""
|
|
|
|
def test_cmp_i32_abs_neg_bit_level(self):
|
|
# abs(0x80000001) = 1 -> 1 > 1 is false (integer abs would give 2147483647 > 1)
|
|
instructions = [v_mov_b32_e32(v[0], 0x80000001), v_mov_b32_e32(v[1], 1), v_cmp_gt_i32_e64(VCC_LO, v[0], v[1], abs=1)]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc, 0)
|
|
# neg(0x80000001) flips the sign bit -> 1 > 2 is false (integer negate would give 2147483647 > 2)
|
|
instructions = [v_mov_b32_e32(v[0], 0x80000001), v_mov_b32_e32(v[1], 2), v_cmp_gt_i32_e64(VCC_LO, v[0], v[1], neg=1)]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc, 0)
|
|
|
|
def test_cmp_u32_abs_bit_level(self):
|
|
# abs(0x80000000) = 0 -> 0 < 1 is true
|
|
instructions = [v_mov_b32_e32(v[0], 0x80000000), v_mov_b32_e32(v[1], 1), v_cmp_lt_u32_e64(VCC_LO, v[0], v[1], abs=1)]
|
|
st = run_program(instructions, n_lanes=1)
|
|
self.assertEqual(st.vcc, 1) # n_lanes=1
|
|
|
|
|
|
class TestCmpxSdstRegressions(unittest.TestCase):
|
|
"""Regression tests: V_CMPX_*_E64 writes EXEC only, never SDST (hardware verified)."""
|
|
|
|
def test_cmpx_e64_no_sdst(self):
|
|
instructions = [
|
|
s_mov_b32(VCC_LO, 0), # preset VCC to 0
|
|
v_mov_b32_e32(v[0], 0x3F800000), v_mov_b32_e32(v[1], 0x40000000),
|
|
v_cmpx_lt_f32_e64(VCC_LO, v[0], v[1]), # 1.0 < 2.0
|
|
]
|
|
st = run_program(instructions, n_lanes=32)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset], 0xFFFFFFFF) # EXEC updated
|
|
self.assertEqual(st.vcc, 0) # but VCC untouched
|
|
|
|
def test_cmpx_e64_partial_exec(self):
|
|
instructions = [
|
|
s_mov_b32(EXEC_LO, 0x0F0F0F0F),
|
|
s_mov_b32(VCC_LO, 0xFFFFFFFF),
|
|
v_mov_b32_e32(v[0], 0), v_mov_b32_e32(v[1], 0x3F800000),
|
|
v_cmpx_lt_f32_e64(VCC_LO, v[0], v[1]),
|
|
]
|
|
st = run_program(instructions, n_lanes=32)
|
|
self.assertEqual(st.sgpr[EXEC_LO.offset], 0x0F0F0F0F) # EXEC = computed & old EXEC
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|