Commit b015dccc65 for qemu.org
commit b015dccc65da20041ed1f694c948f360ec3a65ec
Author: Brian Cain <brian.cain@oss.qualcomm.com>
Date: Tue Sep 15 09:30:40 2026 -0700
tests/tcg/hexagon: add HVX vwhist tests
Test all eight vwhist instruction variants and the four Q-masked forms.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
diff --git a/tests/tcg/hexagon/hex_test.h b/tests/tcg/hexagon/hex_test.h
index f86e6e1a69..9315fe463a 100644
--- a/tests/tcg/hexagon/hex_test.h
+++ b/tests/tcg/hexagon/hex_test.h
@@ -21,6 +21,16 @@
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+static inline void __check16(int line, uint16_t val, uint16_t expect)
+{
+ if (val != expect) {
+ printf("ERROR at line %d: 0x%04x != 0x%04x\n", line, val, expect);
+ err++;
+ }
+}
+
+#define check16(RES, EXP) __check16(__LINE__, RES, EXP)
+
static inline void __check32(int line, uint32_t val, uint32_t expect)
{
if (val != expect) {
diff --git a/tests/tcg/hexagon/meson.build b/tests/tcg/hexagon/meson.build
index ff3c095d9c..1cffc63e09 100644
--- a/tests/tcg/hexagon/meson.build
+++ b/tests/tcg/hexagon/meson.build
@@ -94,6 +94,7 @@ tests += {
'test_vminh.S': {'cflags': asmflags},
'test_vpmpyh.S': {'cflags': asmflags},
'test_vspliceb.S': {'cflags': asmflags},
+ 'test_vwhist.c': {'cflags': [cflags, '-mhvx']},
'unaligned_pc.c': {'cflags': cflags},
'unaligned_data.c': {'cflags': cflags},
'usr.c': {
diff --git a/tests/tcg/hexagon/test_vwhist.c b/tests/tcg/hexagon/test_vwhist.c
new file mode 100644
index 0000000000..32dedb0a8b
--- /dev/null
+++ b/tests/tcg/hexagon/test_vwhist.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * Test HVX weighted histogram instructions (vwhist128/vwhist256 variants).
+ *
+ * Each halfword in the input vector (loaded via Vx.tmp) contains a bucket
+ * index (low byte) and a weight (high byte). The instruction accumulates
+ * the weight into the appropriate element of the destination vector.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+int err;
+
+#include "hex_test.h"
+
+#define MAX_VEC_SIZE_BYTES 128
+
+typedef union {
+ uint32_t uw[MAX_VEC_SIZE_BYTES / 4];
+ uint16_t uh[MAX_VEC_SIZE_BYTES / 2];
+ uint8_t ub[MAX_VEC_SIZE_BYTES];
+} MMVector;
+
+/*
+ * Build input vector for vwhist256: each halfword is (weight << 8 | bucket).
+ * We put bucket=0, weight=1 in every slot so that v0.uh[0] gets incremented
+ * by 1 for each of the 64 halfwords -> v0.uh[0] should equal 64.
+ */
+static MMVector input __attribute__((aligned(MAX_VEC_SIZE_BYTES)));
+static MMVector zero_vec __attribute__((aligned(MAX_VEC_SIZE_BYTES)));
+static MMVector result __attribute__((aligned(MAX_VEC_SIZE_BYTES)));
+
+/*
+ * Test vwhist256: 256-bin histogram with 16-bit accumulation.
+ * Each halfword of the input: low byte = bucket, high byte = weight.
+ * bucket bits [7:3] -> vindex (which vector register), [2:0] + lane -> element.
+ * All entries have bucket=0 weight=1, so v0.uh[0..7] get incremented.
+ */
+static void test_vwhist256(void)
+{
+ int i;
+
+ /* Set all input halfwords to bucket=0, weight=1 */
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0x0100; /* weight=1, bucket=0 */
+ }
+
+ asm volatile(
+ /* Zero out v0 (the target for bucket 0) */
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist256\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result)
+ : "v0", "v12", "memory");
+
+ /*
+ * 64 input halfwords all targeting bucket 0.
+ * elindex = (i & ~7) | (bucket & 7) = i & ~7 (since bucket=0).
+ * So elements 0,8,16,24,32,40,48,56 each get 8 increments.
+ */
+ for (i = 0; i < 64; i++) {
+ uint16_t expected = ((i & 7) == 0) ? 8 : 0;
+ check16(result.uh[i], expected);
+ }
+}
+
+/*
+ * Test vwhist256:sat -- saturating variant.
+ * Use weight=0xFF to test that saturation to 0xFFFF works.
+ */
+static void test_vwhist256_sat(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0xFF00; /* weight=0xFF, bucket=0 */
+ }
+
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist256:sat\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result)
+ : "v0", "v12", "memory");
+
+ /*
+ * Same distribution as vwhist256: elements 0,8,16,...,56.
+ * Each gets 8 * 0xFF = 2040 = 0x7F8 (within uint16 range).
+ */
+ for (i = 0; i < 64; i++) {
+ uint16_t expected = ((i & 7) == 0) ? 8 * 0xFF : 0;
+ check16(result.uh[i], expected);
+ }
+}
+
+/*
+ * Test vwhist128: 128-bin histogram with 32-bit accumulation.
+ * bucket bits [7:3] -> vindex, [2:1] + lane -> element (word granularity).
+ */
+static void test_vwhist128(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0x0100; /* weight=1, bucket=0 */
+ }
+
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist128\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result)
+ : "v0", "v12", "memory");
+
+ /*
+ * 128-bin: 64 halfwords all targeting bucket 0.
+ * bucket 0 -> vindex=0, elindex based on (i>>1)&(~3) | (bucket>>1)&3.
+ * With bucket=0, elindex = (i>>1)&(~3).
+ * For i=0,1: elindex=0; i=2,3: elindex=0; ... up to i=6,7: elindex=0
+ * i ranges 0..63. (i>>1) ranges 0..31.
+ * (i>>1)&(~3) = 0,0,0,0,4,4,4,4,8,...
+ * So elements 0,4,8,12,16,20,24,28 each get 8 increments.
+ */
+ for (i = 0; i < 32; i++) {
+ uint32_t expected = ((i & 3) == 0) ? 8 : 0;
+ check32(result.uw[i], expected);
+ }
+}
+
+/*
+ * Test vwhist128(#0) -- masked variant.
+ * Only processes elements where (bucket & 1) == mode.
+ */
+static void test_vwhist128m(void)
+{
+ int i;
+
+ /* All bucket=0, weight=1. bucket&1==0, so mode=0 matches all. */
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0x0100; /* weight=1, bucket=0 */
+ }
+
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist128(#0)\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result)
+ : "v0", "v12", "memory");
+
+ /* Same distribution as vwhist128 since all buckets have bit0=0 */
+ for (i = 0; i < 32; i++) {
+ uint32_t expected = ((i & 3) == 0) ? 8 : 0;
+ check32(result.uw[i], expected);
+ }
+
+ /* Now test with mode=1: bucket=0 has bit0=0, so nothing should match */
+ memset(&result, 0, sizeof(result));
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist128(#1)\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result)
+ : "v0", "v12", "memory");
+
+ for (i = 0; i < 32; i++) {
+ check32(result.uw[i], 0);
+ }
+}
+
+static MMVector ones_vec __attribute__((aligned(MAX_VEC_SIZE_BYTES)));
+
+/*
+ * Test vwhist256(Qv4) -- Q-masked vwhist256.
+ * Set Q0 to all-ones so all elements pass the mask -> same as vwhist256.
+ */
+static void test_vwhist256q(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0x0100; /* weight=1, bucket=0 */
+ }
+
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "v1 = vmem(%[ones] + #0)\n\t"
+ "q0 = vcmp.eq(v1.b, v1.b)\n\t" /* all-ones Q0 */
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist256(q0)\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result), [ones] "r"(&ones_vec)
+ : "v0", "v1", "v12", "q0", "memory");
+
+ for (i = 0; i < 64; i++) {
+ uint16_t expected = ((i & 7) == 0) ? 8 : 0;
+ check16(result.uh[i], expected);
+ }
+}
+
+/*
+ * Test vwhist256(Qv4):sat -- Q-masked saturating vwhist256.
+ */
+static void test_vwhist256q_sat(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0xFF00; /* weight=0xFF, bucket=0 */
+ }
+
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "v1 = vmem(%[ones] + #0)\n\t"
+ "q0 = vcmp.eq(v1.b, v1.b)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist256(q0):sat\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result), [ones] "r"(&ones_vec)
+ : "v0", "v1", "v12", "q0", "memory");
+
+ for (i = 0; i < 64; i++) {
+ uint16_t expected = ((i & 7) == 0) ? 8 * 0xFF : 0;
+ check16(result.uh[i], expected);
+ }
+}
+
+/*
+ * Test vwhist128(Qv4) -- Q-masked vwhist128.
+ */
+static void test_vwhist128q(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0x0100; /* weight=1, bucket=0 */
+ }
+
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "v1 = vmem(%[ones] + #0)\n\t"
+ "q0 = vcmp.eq(v1.b, v1.b)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist128(q0)\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result), [ones] "r"(&ones_vec)
+ : "v0", "v1", "v12", "q0", "memory");
+
+ for (i = 0; i < 32; i++) {
+ uint32_t expected = ((i & 3) == 0) ? 8 : 0;
+ check32(result.uw[i], expected);
+ }
+}
+
+/*
+ * Test vwhist128(Qv4,#0) -- Q-masked mode vwhist128.
+ */
+static void test_vwhist128qm(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) {
+ input.uh[i] = 0x0100; /* weight=1, bucket=0 */
+ }
+
+ asm volatile(
+ "v0 = vmem(%[zero] + #0)\n\t"
+ "v1 = vmem(%[ones] + #0)\n\t"
+ "q0 = vcmp.eq(v1.b, v1.b)\n\t"
+ "{\n\t"
+ " v12.tmp = vmem(%[inp] + #0)\n\t"
+ " vwhist128(q0,#0)\n\t"
+ "}\n\t"
+ "vmem(%[out] + #0) = v0\n\t"
+ :
+ : [inp] "r"(&input), [zero] "r"(&zero_vec),
+ [out] "r"(&result), [ones] "r"(&ones_vec)
+ : "v0", "v1", "v12", "q0", "memory");
+
+ /* bucket=0, bit0=0, mode=0 matches -> same as vwhist128 */
+ for (i = 0; i < 32; i++) {
+ uint32_t expected = ((i & 3) == 0) ? 8 : 0;
+ check32(result.uw[i], expected);
+ }
+}
+
+int main(void)
+{
+ memset(&zero_vec, 0, sizeof(zero_vec));
+ memset(&ones_vec, 0xff, sizeof(ones_vec));
+
+ test_vwhist256();
+ test_vwhist256_sat();
+ test_vwhist128();
+ test_vwhist128m();
+ test_vwhist256q();
+ test_vwhist256q_sat();
+ test_vwhist128q();
+ test_vwhist128qm();
+
+ puts(err ? "FAIL" : "PASS");
+ return err ? 1 : 0;
+}