Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
probe-common.h
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27#pragma once
28
29// Shared bare-metal helpers for the 573 VRAM test binaries.
30//
31// Each test binary is a standalone PS-EXE that initializes the GPU itself
32// (no PSYQo runtime, no snitch harness), runs one topic's worth of probes,
33// prints RESULT lines over Unirom's TTY, and idles. We deliberately stay
34// raw because the whole point of the suite is to characterize what the
35// silicon does with edge-case GP0/GP1 inputs without any library helpfully
36// clamping them on the way through.
37
38#include <stdint.h>
39
40#include "common/hardware/dma.h"
41#include "common/hardware/gpu.h"
43#include "common/hardware/irq.h"
46
47// Bring the GPU into a known polled-FIFO state. Modeled on the reset()
48// in tests/gpu/gpu.c so subsequent transfers do not hang on
49// bits that only advance under DMA.
50static inline void probeReset(void) {
51 IMASK = 0;
52 IREG = 0;
53 for (unsigned i = 0; i < 7; i++) {
54 DMA_CTRL[i].CHCR = 0;
55 DMA_CTRL[i].BCR = 0;
56 DMA_CTRL[i].MADR = 0;
57 }
58 DPCR = 0x800;
59 uint32_t dicr = DICR;
60 DICR = dicr;
61 DICR = 0;
62
63 // GP1(0x00): full GPU reset.
64 GPU_STATUS = 0x00000000;
65
66 // Restore a sane retail-shaped display mode. None of these settings are
67 // load-bearing for the probe itself - we just want the GPU out of any
68 // weird mode Unirom may have left it in.
69 struct DisplayModeConfig config = {
71 .vResolution = VR_240,
72 .videoMode = VM_NTSC,
73 .colorDepth = CD_15BITS,
74 .videoInterlace = VI_OFF,
75 .hResolutionExtended = HRE_NORMAL,
76 };
77 setDisplayMode(&config);
78 setHorizontalRange(0, 0xa00);
79 setVerticalRange(16, 255);
80 setDisplayArea(0, 0);
81 setDrawingArea(0, 0, 320, 240);
82
83 // GP1(0x04, 1): DMA direction = FIFO. This is what unblocks the
84 // status bits that gate VRAM transfers when we are writing GPU_DATA
85 // from the CPU. Without this the GP0(0xA0)/GP0(0xC0) handshake hangs.
86 sendGPUStatus(0x04000001);
87}
88
89// GP1(0x09) - on retail this is "Texture Disable"; on arcade boards with a
90// second VRAM bank it is repurposed as the upper-bank gate. The polarity is
91// what we are characterizing, so the helper deliberately does not name the
92// bit "enable" or "disable" - caller passes the literal value.
93static inline void gp1_09(uint32_t value) { sendGPUStatus(0x09000000 | (value & 0xff)); }
94
95// GP1(0x01): clear the GPU command FIFO. Note this only flushes the FIFO
96// behind GP0; it does NOT reset the GPU's internal register state. For
97// inter-iteration cleanup that returns the GPU to a fully known state,
98// prefer gpuFullResetWithGate() below, which does a real GP1(0x00).
99static inline void resetCommandBuffer(void) { sendGPUStatus(0x01000000); }
100
101// Full GPU reset between test iterations. GP1 (port 1) is unbuffered so
102// GP1(0x00) reaches the GPU immediately and clears every internal register
103// (texpage, drawing area, mode bits, etc) without touching VRAM contents.
104// We then restore just the two settings every test relies on:
105// - GP1(0x04, 1) FIFO mode so polled CPU transfers advance bit 25
106// - GP1(0x09) bank gate at the requested polarity
107// This is faster than full probeReset() and avoids re-touching display
108// timing registers that don't matter for the test outcome.
109static inline void gpuFullResetWithGate(uint32_t gate_value) {
110 sendGPUStatus(0x00000000); // GP1(0x00): full reset
111 sendGPUStatus(0x04000001); // GP1(0x04): FIFO mode
112 sendGPUStatus(0x09000000 | (gate_value & 0xff));
113}
114
115// Per psx-spx GPU section "Masking for COPY Commands parameters":
116// Xsiz_eff = ((Xsiz - 1) AND 3FFh) + 1 ;range 1..1024
117// Ysiz_eff = ((Ysiz - 1) AND 1FFh) + 1 ;range 1..512
118// These give the actual number of pixels/rows the GPU will transfer for
119// GP0(0xA0) CPU->VRAM, GP0(0xC0) VRAM->CPU, and GP0(0x80) VRAM->VRAM.
120// Sending more than this in the data phase overflows into the command
121// stream and crashes the GPU. Sending fewer stalls the GPU forever
122// waiting for the rest. Use these helpers to send exactly the right
123// number of words.
124static inline int copyWidthEff(int w) { return (((w - 1) & 0x3ff) + 1); }
125static inline int copyHeightEff(int h) { return (((h - 1) & 0x1ff) + 1); }
126
127// Per psx-spx, fast-fill (GP0 0x02) has different masking:
128// Ysiz_eff = Ysiz AND 1FFh ;range 0..1FFh, 0 = NO FILL
129// So multiples of 512 are silently rejected (Ysiz=0 effective).
130static inline int fastFillHeightEff(int h) {
131 int eff = h & 0x1ff;
132 return eff; // 0 means "no fill at all"
133}
134
135// Multi-word GPU commands need exactly one waitGPU() at the start; bit 26
136// goes LOW after the first word and only returns high after the entire
137// transfer completes, so polling between sub-words hangs forever. This
138// matches the pattern in tests/gpu/gpu.c::sendOnePolygon.
139//
140// All coordinate fields are passed through unmodified - we do NOT mask to
141// 16/9 bits because the masking behavior is part of what we are observing.
142
143// Write a single 16-bit pixel at (x, y). GP0(0xA0) consumes one full 32-bit
144// word per (1, 1) transfer; we set width=2 so the second pixel is filled
145// with a recognizable padding value, then do not care about it.
146static inline void writePixel(int16_t x, int16_t y, uint16_t value) {
147 waitGPU();
148 GPU_DATA = 0xa0000000;
149 GPU_DATA = ((uint32_t)(uint16_t)y << 16) | (uint32_t)(uint16_t)x;
150 GPU_DATA = ((uint32_t)(uint16_t)1 << 16) | (uint32_t)(uint16_t)2;
151 GPU_DATA = (uint32_t)value | ((uint32_t)0xdead << 16);
152}
153
154// Read a single 16-bit pixel at (x, y). After the GP0(0xC0) header words
155// we wait for status bit 27 ("Ready to send VRAM to CPU") before reading -
156// bit 26 is "ready to take a command word" which goes high BEFORE any
157// data is actually in the readback FIFO.
158static inline uint16_t readPixel(int16_t x, int16_t y) {
159 waitGPU();
160 GPU_DATA = 0xc0000000;
161 GPU_DATA = ((uint32_t)(uint16_t)y << 16) | (uint32_t)(uint16_t)x;
162 GPU_DATA = ((uint32_t)(uint16_t)1 << 16) | (uint32_t)(uint16_t)2;
163 while ((GPU_STATUS & 0x08000000) == 0) {
164 }
165 uint32_t word = GPU_DATA;
166 return (uint16_t)(word & 0xffff);
167}
168
169// Read N pixels from a horizontal strip starting at (x, y). |w| must be
170// even (so the readback word count is an integer). |dst| receives the
171// raw 16-bit words.
172static inline void readStrip(int16_t x, int16_t y, int16_t w, uint16_t* dst) {
173 waitGPU();
174 GPU_DATA = 0xc0000000;
175 GPU_DATA = ((uint32_t)(uint16_t)y << 16) | (uint32_t)(uint16_t)x;
176 GPU_DATA = ((uint32_t)(uint16_t)1 << 16) | (uint32_t)(uint16_t)w;
177 int words = (w + 1) >> 1;
178 for (int i = 0; i < words; i++) {
179 while ((GPU_STATUS & 0x08000000) == 0) {
180 }
181 uint32_t word = GPU_DATA;
182 dst[i * 2] = (uint16_t)(word & 0xffff);
183 if (i * 2 + 1 < w) dst[i * 2 + 1] = (uint16_t)(word >> 16);
184 }
185}
186
187// Pace large streamed payloads so the CPU does not outrun the GPU's
188// command-FIFO drain rate. Bit 25 ("DMA / Data Request") under DMA
189// direction = 1 (FIFO mode) means "FIFO has room". Without pacing,
190// transfers of more than a few hundred words deadlock or drop writes.
191static inline void streamPace(int idx) {
192 if ((idx & 7) == 0) {
193 while ((GPU_STATUS & 0x02000000) == 0) {
194 }
195 }
196}
197
198// Fill a rectangular region with a single 16-bit value via GP0(0xA0). Slow
199// but works at any (Y, H) including across Y=512 - we deliberately do NOT
200// use GP0(0x02) fast-fill here because that command's behavior is itself
201// what other tests are characterizing, and we need a known-good fill in
202// our test setup. Width must be even.
203static inline void fillRectViaUpload(int16_t x, int16_t y, int16_t w, int16_t h,
204 uint16_t value) {
205 waitGPU();
206 GPU_DATA = 0xa0000000;
207 GPU_DATA = ((uint32_t)(uint16_t)y << 16) | (uint32_t)(uint16_t)x;
208 GPU_DATA = ((uint32_t)(uint16_t)h << 16) | (uint32_t)(uint16_t)w;
209 uint32_t doubled = (uint32_t)value | ((uint32_t)value << 16);
210 int words = ((int)w * (int)h) >> 1;
211 for (int i = 0; i < words; i++) {
212 streamPace(i);
213 GPU_DATA = doubled;
214 }
215}
216
217// Fill the full Y=0..1023 range of a vertical column. GP0(0xA0) silently
218// caps height at 511 rows on at least some configurations (matches the
219// limit spicyjpeg observed for GP0(0x02) fast-fill); doing it as two
220// half-bank passes is the safe pattern.
221static inline void fillColumn(int16_t x, int16_t w, uint16_t value) {
222 fillRectViaUpload(x, 0, w, 256, value);
223 fillRectViaUpload(x, 256, w, 256, value);
224 fillRectViaUpload(x, 512, w, 256, value);
225 fillRectViaUpload(x, 768, w, 256, value);
226}
227
228// Compute a cheap 32-bit hash of a row of |n| 16-bit pixels. Useful for
229// quick "is this row what we expected" checks without dumping every byte.
230static inline uint32_t hashRow(const uint16_t* row, int n) {
231 uint32_t h = 0x811c9dc5u; // FNV-1a init
232 for (int i = 0; i < n; i++) {
233 h ^= row[i];
234 h *= 0x01000193u;
235 }
236 return h;
237}
238
239// Lightweight pass/fail accounting. Each test binary calls reportResult()
240// per observation, then reportSummary() at the end. We do not abort on
241// failure - characterization tests should record every observation, not
242// stop at the first surprise.
243typedef struct {
246 int info;
247} ProbeStats;
248
249static inline void probeStatsInit(ProbeStats* s) {
250 s->passed = 0;
251 s->failed = 0;
252 s->info = 0;
253}
254
255#define PROBE_PASS(stats, fmt, ...) \
256 do { \
257 (stats)->passed++; \
258 ramsyscall_printf("PASS " fmt "\n", ##__VA_ARGS__); \
259 } while (0)
260
261#define PROBE_FAIL(stats, fmt, ...) \
262 do { \
263 (stats)->failed++; \
264 ramsyscall_printf("FAIL " fmt "\n", ##__VA_ARGS__); \
265 } while (0)
266
267#define PROBE_INFO(stats, fmt, ...) \
268 do { \
269 (stats)->info++; \
270 ramsyscall_printf("INFO " fmt "\n", ##__VA_ARGS__); \
271 } while (0)
272
273#define PROBE_RESULT(fmt, ...) \
274 do { \
275 ramsyscall_printf("RESULT " fmt "\n", ##__VA_ARGS__); \
276 } while (0)
277
278static inline void probeStatsSummary(const ProbeStats* s, const char* name) {
279 ramsyscall_printf("SUMMARY name=%s passed=%d failed=%d info=%d\n", name, s->passed,
280 s->failed, s->info);
281 // Marker line for log-capture tools (psxup.py looks for this exact string
282 // to terminate its read loop).
283 ramsyscall_printf("=== Done ===\n");
284}
285
286// Under the emulator the exit code is the verdict. On hardware there is
287// nobody to exit to, so the probe parks once its output is out.
288static inline void probeExit(const ProbeStats* s) {
289 if (pcsx_present()) pcsx_exit(s->failed > 0 ? 1 : 0);
290 while (1) {
291 }
292}
uint32_t dicr
Definition cdlreadn.c:178
ramsyscall_printf("T21 phase=%s t=%u v=%02x cause=%u size=%u stat=%02x\n", phase, t/1000, response[0], cause, size,(nopCause==3 &&nopSize >=1) ? stat[0] :0xff)
@ VM_NTSC
Definition gpu.h:47
@ HRE_NORMAL
Definition gpu.h:62
@ VR_240
Definition gpu.h:42
@ HR_320
Definition gpu.h:36
@ VI_OFF
Definition gpu.h:57
@ CD_15BITS
Definition gpu.h:52
sendGPUStatus(0)
#define DMA_CTRL
Definition dma.h:36
int i
Definition gte-regio.c:299
#define DPCR
Definition hwregs.h:49
#define GPU_DATA
Definition hwregs.h:52
#define IMASK
Definition hwregs.h:47
#define GPU_STATUS
Definition hwregs.h:53
#define DICR
Definition hwregs.h:50
#define IREG
Definition hwregs.h:46
list w
Definition gentable.py:108
n
Definition gentable.py:95
list words
Definition mktest.py:123
char * s
Definition string.c:48
Definition gpu.h:66
enum HResolution hResolution
Definition gpu.h:67
Definition probe-common.h:243
int passed
Definition probe-common.h:244
int info
Definition probe-common.h:246
int failed
Definition probe-common.h:245
static int value
Definition syscalls.h:553