nx_simd_minmax_test.nx source
↩ module page · 146 lines · 5829 B
1// nx_simd_minmax_test.nx -- i16x16 per-lane and horizontal min/max.
2//
3// 4 ops: vmin / vmax (per-lane) and vreduce_min / vreduce_max
4// (horizontal scalar). Useful for: argmax-class ML reductions,
5// audio peak detect, image histogram extremes, clamping pipelines.
6
7import "nx_kernel_v2.nx"
8import "nx_log.nx"
9
10func pack4_i16(a: i64, b: i64, c: i64, d: i64) -> i64 {
11 return (a & 0xFFFF)
12 | ((b & 0xFFFF) << 16)
13 | ((c & 0xFFFF) << 32)
14 | ((d & 0xFFFF) << 48)
15}
16
17func t1_vmin_lanewise() -> i64 {
18 // a = [10, 20, 30, ..., 160], b = [5, 25, 5, 45, ...]
19 // pairwise min = mix.
20 let a_raw: *u8 = sys_mmap(64)
21 let b_raw: *u8 = sys_mmap(64)
22 let r_raw: *u8 = sys_mmap(64)
23 let a: *i64 = a_raw as *i64
24 let b: *i64 = b_raw as *i64
25 let r: *i64 = r_raw as *i64
26 a[0] = pack4_i16(10, 20, 30, 40)
27 a[1] = pack4_i16(50, 60, 70, 80)
28 a[2] = pack4_i16(90, 100, 110, 120)
29 a[3] = pack4_i16(130, 140, 150, 160)
30 b[0] = pack4_i16(5, 25, 5, 45)
31 b[1] = pack4_i16(45, 65, 65, 85)
32 b[2] = pack4_i16(85, 105, 105, 125)
33 b[3] = pack4_i16(125, 145, 145, 165)
34 let va: i64 = __simd_vload_i16_x16(a as *i64)
35 let vb: i64 = __simd_vload_i16_x16(b as *i64)
36 let vmin: i64 = __simd_vmin_i16_x16(va, vb)
37 __simd_vstore_i16_x16(vmin, r as *i64)
38 // expected lanes: min(10,5)=5 min(20,25)=20 min(30,5)=5 min(40,45)=40
39 if r[0] != pack4_i16(5, 20, 5, 40) { return 1 }
40 // min(50,45)=45 min(60,65)=60 min(70,65)=65 min(80,85)=80
41 if r[1] != pack4_i16(45, 60, 65, 80) { return 2 }
42 // min(90,85)=85 min(100,105)=100 min(110,105)=105 min(120,125)=120
43 if r[2] != pack4_i16(85, 100, 105, 120) { return 3 }
44 // min(130,125)=125 min(140,145)=140 min(150,145)=145 min(160,165)=160
45 if r[3] != pack4_i16(125, 140, 145, 160) { return 4 }
46 return 0
47}
48
49func t2_vmax_lanewise() -> i64 {
50 let a_raw: *u8 = sys_mmap(64)
51 let b_raw: *u8 = sys_mmap(64)
52 let r_raw: *u8 = sys_mmap(64)
53 let a: *i64 = a_raw as *i64
54 let b: *i64 = b_raw as *i64
55 let r: *i64 = r_raw as *i64
56 a[0] = pack4_i16(10, 20, 30, 40)
57 a[1] = pack4_i16(50, 60, 70, 80)
58 a[2] = pack4_i16(90, 100, 110, 120)
59 a[3] = pack4_i16(130, 140, 150, 160)
60 b[0] = pack4_i16(5, 25, 5, 45)
61 b[1] = pack4_i16(45, 65, 65, 85)
62 b[2] = pack4_i16(85, 105, 105, 125)
63 b[3] = pack4_i16(125, 145, 145, 165)
64 let va: i64 = __simd_vload_i16_x16(a as *i64)
65 let vb: i64 = __simd_vload_i16_x16(b as *i64)
66 let vmax: i64 = __simd_vmax_i16_x16(va, vb)
67 __simd_vstore_i16_x16(vmax, r as *i64)
68 if r[0] != pack4_i16(10, 25, 30, 45) { return 10 }
69 if r[1] != pack4_i16(50, 65, 70, 85) { return 11 }
70 if r[2] != pack4_i16(90, 105, 110, 125) { return 12 }
71 if r[3] != pack4_i16(130, 145, 150, 165) { return 13 }
72 return 0
73}
74
75func t3_vreduce_min() -> i64 {
76 let a_raw: *u8 = sys_mmap(64)
77 let a: *i64 = a_raw as *i64
78 a[0] = pack4_i16(100, 200, 300, 400)
79 a[1] = pack4_i16(500, 600, 700, 800)
80 a[2] = pack4_i16(50, 900, 1000, 1100) // 50 is the min
81 a[3] = pack4_i16(1200, 1300, 1400, 1500)
82 let v: i64 = __simd_vload_i16_x16(a as *i64)
83 let m: i64 = __simd_vreduce_min_i16_x16(v)
84 if m != 50 { return 20 }
85 return 0
86}
87
88func t4_vreduce_max() -> i64 {
89 let a_raw: *u8 = sys_mmap(64)
90 let a: *i64 = a_raw as *i64
91 a[0] = pack4_i16(100, 200, 300, 400)
92 a[1] = pack4_i16(500, 600, 700, 800)
93 a[2] = pack4_i16(50, 900, 1000, 1100)
94 a[3] = pack4_i16(1200, 1300, 1400, 9999) // 9999 is the max
95 let v: i64 = __simd_vload_i16_x16(a as *i64)
96 let m: i64 = __simd_vreduce_max_i16_x16(v)
97 if m != 9999 { return 30 }
98 return 0
99}
100
101func t5_vreduce_min_negative() -> i64 {
102 // Tests sign-extension: min across mixed-sign values.
103 let a_raw: *u8 = sys_mmap(64)
104 let a: *i64 = a_raw as *i64
105 a[0] = pack4_i16(100, -200, 300, -400)
106 a[1] = pack4_i16(500, -600, 700, -800)
107 a[2] = pack4_i16(50, -900, 1000, -1100)
108 a[3] = pack4_i16(1200, -1300, 1400, -1500)
109 let v: i64 = __simd_vload_i16_x16(a as *i64)
110 let m: i64 = __simd_vreduce_min_i16_x16(v)
111 if m != -1500 { return 40 }
112 return 0
113}
114
115func t6_vreduce_max_negative() -> i64 {
116 // All-negative vector -- max should still be sign-extended correctly.
117 let a_raw: *u8 = sys_mmap(64)
118 let a: *i64 = a_raw as *i64
119 a[0] = pack4_i16(-100, -200, -300, -400)
120 a[1] = pack4_i16(-500, -600, -700, -800)
121 a[2] = pack4_i16(-50, -900, -1000, -1100) // -50 is the max
122 a[3] = pack4_i16(-1200, -1300, -1400, -1500)
123 let v: i64 = __simd_vload_i16_x16(a as *i64)
124 let m: i64 = __simd_vreduce_max_i16_x16(v)
125 if m != -50 { return 50 }
126 return 0
127}
128
129func main() -> nx_exit {
130 println("=== nx_simd_minmax smoke (i16x16 min/max + reduce) ===" as *u8)
131 if t1_vmin_lanewise() != 0 { println("FAIL T1" as *u8); return 1 }
132 println("T1 vmin lanewise PASS 16 i16 pairwise minima" as *u8)
133 if t2_vmax_lanewise() != 0 { println("FAIL T2" as *u8); return 2 }
134 println("T2 vmax lanewise PASS 16 i16 pairwise maxima" as *u8)
135 if t3_vreduce_min() != 0 { println("FAIL T3" as *u8); return 3 }
136 println("T3 vreduce_min PASS min(100..1500 plus 50) = 50" as *u8)
137 if t4_vreduce_max() != 0 { println("FAIL T4" as *u8); return 4 }
138 println("T4 vreduce_max PASS max(...,9999) = 9999" as *u8)
139 if t5_vreduce_min_negative() != 0 { println("FAIL T5" as *u8); return 5 }
140 println("T5 vred_min mixed PASS min mixed-sign = -1500 (sign-ext correct)" as *u8)
141 if t6_vreduce_max_negative() != 0 { println("FAIL T6" as *u8); return 6 }
142 println("T6 vred_max negs PASS max all-negative = -50 (sign-ext correct)" as *u8)
143 println("" as *u8)
144 println("i16x16 min/max OPTIMAL on RV-V (vmin/vmax.vv + vred*) and x86 (vpminsw/vpmaxsw + cascade)." as *u8)
145 return 0
146}