Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
gte-precision.c
Go to the documentation of this file.
1// Precision tests: 44-bit MAC overflow detection, division table
2// spot-checks, RTPS IR3/FLAG.22 sf=0 anomaly.
3// These target the exact behaviors that cause subtle game glitches
4// when emulated imprecisely.
5
6// ==========================================================================
7// 44-bit MAC overflow detection (FLAG bits 25-30)
8// ==========================================================================
9// The GTE accumulator is 44 bits wide. Overflow is detected per-addition
10// in the chain, not on the final result. Two overflows that cancel out
11// will still both be flagged.
12
13// MAC1 positive overflow (FLAG.30): product exceeds +0x7FFFFFFFFFF
14// clang-format off
15
16CESTER_TEST(prec_mac1_positive_overflow, gte_tests,
17 // MVMVA with large matrix and large vector, sf=0 (no shift)
18 // R11=0x7FFF, V0.X=0x7FFF -> R11*VX = 0x3FFF0001
19 // With TR=0x7FFFFFFF and sf=0: TRX<<12 + R11*VX + R12*VY + R13*VZ
20 // TRX<<12 = 0x7FFFFFFF000 (43 bits) + 0x3FFF0001 = overflows 44-bit
21 cop2_putc(0, 0x00007fff); // R11=0x7FFF, R12=0
22 cop2_putc(1, 0x00000000);
23 cop2_putc(2, 0x00000000);
24 cop2_putc(3, 0x00000000);
25 cop2_putc(4, 0);
26 cop2_putc(5, 0x7fffffff); // TRX = max positive 32-bit
27 cop2_putc(6, 0);
28 cop2_putc(7, 0);
29 cop2_put(0, (0 << 16) | 0x7fff); // VX=0x7FFF, VY=0
30 cop2_put(1, 0);
33 uint32_t flag = gte_read_flag();
34 uint32_t f30 = (flag >> 30) & 1;
35 ramsyscall_printf("MAC1 pos overflow: FLAG=0x%08x F30=%u\n", flag, f30);
37)
38
39// MAC1 negative overflow (FLAG.27)
40CESTER_TEST(prec_mac1_negative_overflow, gte_tests,
41 cop2_putc(0, 0x00007fff); // R11=0x7FFF
42 cop2_putc(1, 0x00000000);
43 cop2_putc(2, 0x00000000);
44 cop2_putc(3, 0x00000000);
45 cop2_putc(4, 0);
46 cop2_putc(5, 0x80000000); // TRX = min negative 32-bit
47 cop2_putc(6, 0);
48 cop2_putc(7, 0);
49 cop2_put(0, (0 << 16) | 0x8000); // VX=-0x8000 (negative)
50 cop2_put(1, 0);
53 uint32_t flag = gte_read_flag();
54 uint32_t f27 = (flag >> 27) & 1;
55 ramsyscall_printf("MAC1 neg overflow: FLAG=0x%08x F27=%u\n", flag, f27);
57)
58
59// MAC2 overflow (FLAG.29 positive, FLAG.26 negative)
60CESTER_TEST(prec_mac2_overflow, gte_tests,
61 cop2_putc(0, 0x00000000);
62 cop2_putc(1, 0x7fff0000); // R21=0x7FFF (high16 of R13R21), R13=0
63 cop2_putc(2, 0x00000000);
64 cop2_putc(3, 0x00000000);
65 cop2_putc(4, 0);
66 cop2_putc(5, 0);
67 cop2_putc(6, 0x7fffffff); // TRY = max
68 cop2_putc(7, 0);
69 cop2_put(0, (0 << 16) | 0x7fff);
70 cop2_put(1, 0);
73 uint32_t flag = gte_read_flag();
74 uint32_t f29 = (flag >> 29) & 1;
75 ramsyscall_printf("MAC2 pos overflow: FLAG=0x%08x F29=%u\n", flag, f29);
77)
78
79// MAC3 overflow (FLAG.28 positive, FLAG.25 negative)
80CESTER_TEST(prec_mac3_overflow, gte_tests,
81 cop2_putc(0, 0x00000000);
82 cop2_putc(1, 0x00000000);
83 cop2_putc(2, 0x00000000);
84 cop2_putc(3, 0x00007fff); // R31=0x7FFF (high16 of R31R32)
85 cop2_putc(4, 0);
86 cop2_putc(5, 0);
87 cop2_putc(6, 0);
88 cop2_putc(7, 0x7fffffff); // TRZ = max
89 cop2_put(0, (0 << 16) | 0x7fff);
90 cop2_put(1, 0);
93 uint32_t flag = gte_read_flag();
94 uint32_t f28 = (flag >> 28) & 1;
95 ramsyscall_printf("MAC3 pos overflow: FLAG=0x%08x F28=%u\n", flag, f28);
97)
98
99// Two overflows that cancel: both positive and negative overflow
100// should be flagged even if the final result is in range
101CESTER_TEST(prec_mac_double_overflow, gte_tests,
102 // Use OP (cross product) sf=0 with values that cause intermediate
103 // overflow in both directions during the subtract
104 // MAC1 = R22*IR3 - R33*IR2
105 // Make R22*IR3 overflow positive, then R33*IR2 brings it back
106 cop2_putc(0, 0x00000000);
107 cop2_putc(2, 0x00007fff); // R22=0x7FFF
108 cop2_putc(4, 0x7fff); // R33=0x7FFF
109 cop2_put(9, 0);
110 cop2_put(10, 0x7fff); // IR2
111 cop2_put(11, 0x7fff); // IR3
113 cop2_cmd(COP2_OP_CP(0, 0)); // sf=0
114 int32_t mac1;
115 uint32_t flag;
116 cop2_get(25, mac1);
117 flag = gte_read_flag();
118 ramsyscall_printf("double overflow: MAC1=%d FLAG=0x%08x\n", mac1, flag);
119 // R22*IR3 = 0x7FFF*0x7FFF = 0x3FFF0001 (fits in 44-bit)
120 // Then subtract R33*IR2 = 0x7FFF*0x7FFF = 0x3FFF0001
121 // Result = 0, but check if intermediate overflow flagged
124)
125
126// ==========================================================================
127// Division table spot-checks
128// ==========================================================================
129// The UNR table has 257 entries. Test specific H/SZ3 pairs that exercise
130// known table entries and verify exact quotients.
131
132// Helper: run RTPS with given H and SZ3 (via VZ), return quotient via SX
133// Uses VX=0x1000, OFX=0 so SX = VX * (H/SZ3) = 0x1000 * quotient >> 16
134// Actually simpler: set IR1=0x1000 before RTPS, read MAC0 for DQA path,
135// or just check SX directly.
136
137// H/SZ3 = 1/1: quotient should be near 0x10000 (1.0 in 0.16 fixed)
138CESTER_TEST(prec_div_1_over_1, gte_tests,
141 cop2_putc(24, 0); // OFX=0
142 cop2_putc(25, 0);
143 cop2_putc(26, 1); // H=1
144 cop2_putc(27, 0);
145 cop2_putc(28, 0);
146 cop2_put(0, (0 << 16) | 0x1000); // VX=0x1000, VY=0
147 cop2_put(1, 1); // VZ=1 -> SZ3=1
150 uint32_t sxy2, flag;
152 flag = gte_read_flag();
153 int16_t sx = (int16_t)(sxy2 & 0xffff);
154 ramsyscall_printf("div 1/1: SX=%d FLAG=0x%08x\n", sx, flag);
155 // H=1, SZ3=1 -> H >= SZ3*2? 1 >= 2? No -> no overflow
156 // quotient = H*0x20000/SZ3 = 0x20000. Saturated to 0x1FFFF.
157 // SX = IR1 * quotient >> 16 = 0x1000 * 0x1FFFF >> 16 = 0x1FFF
158 // Then saturated to 0x3FF
159 uint32_t f17 = (flag >> 17) & 1;
160 cester_assert_uint_eq(0, f17); // no division overflow
161)
162
163// H/SZ3 = 100/1000: quotient = 0.1 in fixed point
164CESTER_TEST(prec_div_100_over_1000, gte_tests,
166 gte_set_translation(0, 0, 0);
167 cop2_putc(24, 0);
168 cop2_putc(25, 0);
169 cop2_putc(26, 100); // H=100
170 cop2_putc(27, 0);
171 cop2_putc(28, 0);
172 cop2_put(0, (0 << 16) | 1000); // VX=1000
173 cop2_put(1, 1000); // VZ=1000
175 cop2_cmd(COP2_RTPS(1, 0));
176 uint32_t sxy2;
177 cop2_get(14, sxy2);
178 int16_t sx = (int16_t)(sxy2 & 0xffff);
179 ramsyscall_printf("div 100/1000: SX=%d\n", sx);
180 // SX = 1000 * (100/1000) = 100 (roughly, depends on table rounding)
182)
183
184// The documented corner case: H=0xF015, SZ3=0x780B -> 0x20000 saturates to 0x1FFFF
185CESTER_TEST(prec_div_corner_f015_780b, gte_tests,
187 gte_set_translation(0, 0, 0);
188 cop2_putc(24, 0);
189 cop2_putc(25, 0);
190 cop2_putc(26, 0xf015); // H
191 cop2_putc(27, 0);
192 cop2_putc(28, 0);
193 cop2_put(0, (0 << 16) | 1); // VX=1 (minimal to see quotient effect)
194 cop2_put(1, 0x780b); // VZ = 0x780B
196 cop2_cmd(COP2_RTPS(1, 0));
197 uint32_t sxy2, flag;
198 cop2_get(14, sxy2);
199 flag = gte_read_flag();
200 int16_t sx = (int16_t)(sxy2 & 0xffff);
201 ramsyscall_printf("div F015/780B: SX=%d FLAG=0x%08x\n", sx, flag);
202 // This should NOT set FLAG.17 (division overflow)
203 uint32_t f17 = (flag >> 17) & 1;
205)
206
207// Large H, small SZ3 (just under overflow): H=0xFFFE, SZ3=0x8000
208CESTER_TEST(prec_div_large_h, gte_tests,
210 gte_set_translation(0, 0, 0);
211 cop2_putc(24, 0);
212 cop2_putc(25, 0);
213 cop2_putc(26, 0xfffe); // H near max
214 cop2_putc(27, 0);
215 cop2_putc(28, 0);
216 cop2_put(0, (0 << 16) | 1);
217 cop2_put(1, 0x7fff); // SZ3=0x7FFF -> H >= SZ3*2? 0xFFFE >= 0xFFFE -> yes, overflow
219 cop2_cmd(COP2_RTPS(1, 0));
220 uint32_t flag;
221 flag = gte_read_flag();
222 uint32_t f17 = (flag >> 17) & 1;
223 ramsyscall_printf("div large H: FLAG=0x%08x F17=%u\n", flag, f17);
224 cester_assert_uint_eq(1, f17); // H >= SZ3*2 is true (equal counts)
225)
226
227// SZ3=1 with moderate H (quotient near max)
228CESTER_TEST(prec_div_sz3_one, gte_tests,
230 gte_set_translation(0, 0, 0);
231 cop2_putc(24, 0);
232 cop2_putc(25, 0);
233 cop2_putc(26, 1); // H=1
234 cop2_putc(27, 0);
235 cop2_putc(28, 0);
236 cop2_put(0, (0 << 16) | 1);
237 cop2_put(1, 1); // SZ3=1
239 cop2_cmd(COP2_RTPS(1, 0));
240 uint32_t sxy2, flag;
241 int32_t ir1;
242 cop2_get(14, sxy2);
244 flag = gte_read_flag();
245 int16_t sx = (int16_t)(sxy2 & 0xffff);
246 ramsyscall_printf("div SZ3=1: SX=%d IR1=%d FLAG=0x%08x\n", sx, ir1, flag);
247 // H/SZ3 = 1/1 -> quotient saturates to 0x1FFFF
248 // SX = IR1 * 0x1FFFF >> 16 = 1 * 0x1FFFF >> 16 = 1
250)
251
252// ==========================================================================
253// RTPS IR3/FLAG.22 anomaly with sf=0
254// ==========================================================================
255// psx-spx: "When using RTP with sf=0, the IR3 saturation flag (FLAG.22)
256// gets set only if MAC3 SAR 12 exceeds -8000h..+7FFFh, although IR3 is
257// saturated when MAC3 exceeds -8000h..+7FFFh."
258//
259// Need MAC3 that is out of [-0x8000, 0x7FFF] range (so IR3 saturates)
260// but MAC3 >> 12 is in range (so FLAG.22 should NOT be set).
261
262CESTER_TEST(prec_rtps_sf0_ir3_flag_anomaly, gte_tests,
264 // TRZ such that MAC3 is just over 0x7FFF but MAC3>>12 is in range
265 // With identity rotation and VZ=0: MAC3 = TRZ << 12 (sf=0, no shift)
266 // Wait - with sf=0 the formula is: MAC3 = TRZ*0x1000 + R3x*V
267 // Actually let's think more carefully.
268 // sf=0: A3 returns the raw 44-bit value without >>12
269 // MAC3 = TRZ<<12 + R31*VX + R32*VY + R33*VZ (no shift applied)
270 // With identity: MAC3 = TRZ<<12 + VZ*0x1000
271 // We want MAC3 > 0x7FFF (IR3 saturates) but MAC3>>12 in [-0x8000,0x7FFF]
272 // MAC3 = 0x8000 -> MAC3>>12 = 0 (in range) -> FLAG.22 NOT set but IR3 saturated
273 cop2_putc(5, 0);
274 cop2_putc(6, 0);
275 cop2_putc(7, 0); // TRZ = 0
276 cop2_putc(24, 0);
277 cop2_putc(25, 0);
278 cop2_putc(26, 200);
279 cop2_putc(27, 0);
280 cop2_putc(28, 0);
281 // VZ = 8 -> MAC3 = 0 + 0x1000*8 = 0x8000 (just over 0x7FFF)
282 cop2_put(0, 0x00000000);
283 cop2_put(1, 8);
285 cop2_cmd(COP2_RTPS(0, 0)); // sf=0
286 int32_t mac3;
287 uint32_t ir3, flag;
288 cop2_get(27, mac3);
289 cop2_get(11, ir3);
290 flag = gte_read_flag();
291 uint32_t f22 = (flag >> 22) & 1;
292 ramsyscall_printf("sf=0 anomaly: MAC3=%d IR3=0x%04x FLAG=0x%08x F22=%u\n",
293 mac3, ir3 & 0xffff, flag, f22);
294 // MAC3 = 0x8000 -> out of [-0x8000, 0x7FFF] for IR3 (it equals -0x8000 boundary!)
295 // Hmm, 0x8000 = 32768 which is > 0x7FFF. IR3 should saturate to 0x7FFF.
296 // MAC3 >> 12 = 0x8000 >> 12 = 0 -> in range -> FLAG.22 should NOT be set.
297 // This is the anomaly: IR3 saturated but FLAG.22 not set.
299 cester_assert_uint_eq(0x7fff, ir3);
301 uint32_t f17 = (flag >> 17) & 1;
303)
304
305// Stronger test: MAC3 = 0x10000 -> well above 0x7FFF, but >>12 = 1 (in range)
306CESTER_TEST(prec_rtps_sf0_ir3_flag_strong, gte_tests,
308 cop2_putc(5, 0);
309 cop2_putc(6, 0);
310 cop2_putc(7, 0);
311 cop2_putc(24, 0);
312 cop2_putc(25, 0);
313 cop2_putc(26, 200);
314 cop2_putc(27, 0);
315 cop2_putc(28, 0);
316 // VZ = 16 -> MAC3 = 0x1000 * 16 = 0x10000 (65536, way above 0x7FFF)
317 cop2_put(0, 0x00000000);
318 cop2_put(1, 16);
321 int32_t mac3;
322 uint32_t ir3, flag;
325 flag = gte_read_flag();
326 uint32_t f22 = (flag >> 22) & 1;
327 ramsyscall_printf("sf=0 strong: MAC3=%d IR3=0x%04x FLAG=0x%08x F22=%u\n",
328 mac3, ir3 & 0xffff, flag, f22);
329 // MAC3 = 0x10000 -> IR3 saturated to 0x7FFF
331 // MAC3 >> 12 = 0x10000 >> 12 = 16 -> in range -> FLAG.22 NOT set
333)
334
335// Counter-test: MAC3 >> 12 exceeds range -> FLAG.22 SHOULD be set
336CESTER_TEST(prec_rtps_sf0_ir3_flag_set, gte_tests,
338 cop2_putc(5, 0);
339 cop2_putc(6, 0);
340 cop2_putc(7, 8); // TRZ = 8, so MAC3 = 8<<12 + VZ*0x1000
341 cop2_putc(24, 0);
342 cop2_putc(25, 0);
343 cop2_putc(26, 200);
344 cop2_putc(27, 0);
345 cop2_putc(28, 0);
346 // VZ = 0x7FF0 -> MAC3 = 8*4096 + 0x7FF0*0x1000 = 0x8000 + 0x7FF0000 = 0x7FF8000
347 // MAC3 >> 12 = 0x7FF8 -> in range? 0x7FF8 < 0x7FFF -> yes, still in range
348 // Need TRZ large enough: TRZ = 0x7FFF -> MAC3 = 0x7FFF<<12 = 0x7FFF000
349 // MAC3>>12 = 0x7FFF -> at boundary. With VZ=1: MAC3 = 0x7FFF000 + 0x1000 = 0x8000000
350 // MAC3>>12 = 0x8000 -> OUT of range -> FLAG.22 should be set
351 cop2_putc(7, 0x7fff);
352 cop2_put(0, 0x00000000);
353 cop2_put(1, 1);
355 cop2_cmd(COP2_RTPS(0, 0));
356 int32_t mac3;
357 uint32_t ir3, flag;
358 cop2_get(27, mac3);
359 cop2_get(11, ir3);
360 flag = gte_read_flag();
361 uint32_t f22 = (flag >> 22) & 1;
362 ramsyscall_printf("sf=0 flag set: MAC3=%d IR3=0x%04x FLAG=0x%08x F22=%u\n",
363 mac3, ir3 & 0xffff, flag, f22);
364 // MAC3>>12 = 0x8000 -> exceeds 0x7FFF -> FLAG.22 SHOULD be set
366)
#define COP2_OP_CP(sf, lm)
Definition cop2.h:136
#define cop2_cmd(op)
Definition cop2.h:175
#define COP2_V_V0
Definition cop2.h:70
#define cop2_put(reg, val)
Definition cop2.h:191
#define COP2_MX_RT
Definition cop2.h:64
#define cop2_putc(reg, val)
Definition cop2.h:205
#define COP2_RTPS(sf, lm)
Definition cop2.h:129
#define COP2_MVMVA(sf, mx, v, cv, lm)
Definition cop2.h:145
#define cop2_get(reg, dest)
Definition cop2.h:198
#define COP2_CV_TR
Definition cop2.h:76
int32_t mac1
Definition gte-depthcue.c:118
CESTER_TEST(prec_mac1_positive_overflow, gte_tests, cop2_putc(0, 0x00007fff);cop2_putc(1, 0x00000000);cop2_putc(2, 0x00000000);cop2_putc(3, 0x00000000);cop2_putc(4, 0);cop2_putc(5, 0x7fffffff);cop2_putc(6, 0);cop2_putc(7, 0);cop2_put(0,(0<< 16)|0x7fff);cop2_put(1, 0);gte_clear_flag();cop2_cmd(COP2_MVMVA(0, COP2_MX_RT, COP2_V_V0, COP2_CV_TR, 0));uint32_t flag=gte_read_flag();uint32_t f30=(flag > > 30) &1;ramsyscall_printf("MAC1 pos overflow: FLAG=0x%08x F30=%u\n", flag, f30);cester_assert_uint_eq(1, f30);) CESTER_TEST(prec_mac1_negative_overflow
cester_assert_int_eq(1, sx)
gte_set_identity_rotation()
uint32_t sxy2
Definition gte-precision.c:150
uint32_t f17
Definition gte-precision.c:159
ramsyscall_printf("MAC1 neg overflow: FLAG=0x%08x F27=%u\n", flag, f27)
int32_t ir1
Definition gte-precision.c:241
uint32_t f27
Definition gte-precision.c:54
gte_tests
Definition gte-precision.c:40
uint32_t flag
Definition gte-precision.c:53
int16_t sx
Definition gte-precision.c:153
cester_assert_uint_eq(1, f27)
gte_set_translation(0, 0, 0)
gte_clear_flag()
uint32_t f28
Definition gte-precision.c:94
uint32_t ir3
Definition gte-precision.c:322
int32_t mac3
Definition gte-precision.c:321
uint32_t f22
Definition gte-precision.c:326