Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
spu-reverb.c
Go to the documentation of this file.
1// ============================================================================
2// Validate reverb work-area state
3// ============================================================================
4//
5// Tier 1.5 oracle: this file reads the SPU reverb work area from SPU RAM after
6// a deterministic stimulus. The normal capture buffers at 0x000/0x400/0x800/
7// 0xC00 are dry pre-reverb mirrors, so they cannot see the final reverb mix.
8// The work area is still a real hardware state oracle: it captures the IIR,
9// comb, and APF delay-line state that the reverb unit writes while running.
10//
11// Tier 2 oracle, later: Pixel's I2S logic-analyzer rig will capture BCLK/LRCK/
12// DATA from the SPU's digital audio output. That gives the post-mix, post-reverb
13// final audio stream. These tests do not depend on that rig; the I2S path should
14// slot in later as a separate final-mix comparison, not replace this autonomous
15// SPU-RAM state test.
16//
17// The expected files are intentionally not doc-derived. Run with SPU_DUMP=true
18// on real silicon to create the .test.rev files, then enable
19// SPU_REVERB_GOLDENS=true once those hardware captures have been checked in.
20
21#ifndef SPU_REVERB_HELPERS_DEFINED
22#define SPU_REVERB_HELPERS_DEFINED
23
24#define SPU_REVERB_ROOM_SIZE 0x26c0u
25#define SPU_REVERB_ROOM_BASE (0x80000u - SPU_REVERB_ROOM_SIZE)
26#define SPU_REVERB_ROOM_BASE_REG (SPU_REVERB_ROOM_BASE >> 3)
27
28#define SPU_REVERB_INPUT_IMPULSE 1u
29#define SPU_REVERB_INPUT_STEADY_SINE 2u
30#define SPU_REVERB_PRESET_ROOM 1u
31
32// One positive ADPCM impulse, followed by a silent loop. The first block has no
33// loop flag; the second block marks the silent loop point, and the last block
34// ends/repeats back to that silence. This gives the reverb a clean transient
35// without continuously re-triggering the impulse.
36static const uint8_t kAdpcmImpulseThenSilence[64] __attribute__((aligned(4))) = {
37 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
38 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
39 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
40 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
41 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
42 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
44 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45};
46
48 uint32_t magic; // 'REVR'
49 uint32_t length; // sizeof(struct SPUReverbCaptureHeader)
50 uint32_t preset; // SPU_REVERB_PRESET_*
51 uint32_t input; // SPU_REVERB_INPUT_*
52 uint32_t workBase; // byte address in SPU RAM
53 uint32_t workBytes; // bytes captured after this header
54 uint32_t syncWindows; // count of bit11 high->low waits after key-on
56};
57
58static const uint16_t kReverbRoomPreset[32] = {
59 0x007d, 0x005b, 0x6d80, 0x54b8, 0xbed0, 0x0000, 0x0000, 0xba80,
60 0x5800, 0x5300, 0x04d6, 0x0333, 0x03f0, 0x0227, 0x0374, 0x01ef,
61 0x0334, 0x01b5, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
62 0x0000, 0x0000, 0x01b4, 0x0136, 0x00b8, 0x005c, 0x8000, 0x8000,
63};
64
65static void spu_reverb_apply_room_preset(void) {
66 volatile uint16_t *regs = (volatile uint16_t *)SPU_REVERB;
67 for (unsigned i = 0; i < 32; i++) regs[i] = kReverbRoomPreset[i];
68}
69
70static void spu_zero_reverb_work_area(void) {
71 for (unsigned off = 0; off < SPU_REVERB_ROOM_SIZE; off += sizeof(s_reverb_work)) {
72 unsigned chunk = SPU_REVERB_ROOM_SIZE - off;
73 if (chunk > sizeof(s_reverb_work)) chunk = sizeof(s_reverb_work);
74 for (unsigned i = 0; i < chunk; i++) s_reverb_work[i] = 0;
75 spu_write_sync(SPU_REVERB_ROOM_BASE + off, s_reverb_work, chunk);
76 }
77}
78
79static void spu_wait_reverb_windows(unsigned windows) {
80 for (unsigned i = 0; i < windows; i++) spu_wait_status_bit11_flip();
81}
82
83static void run_reverb_room_work_area(const uint8_t *sample64, uint32_t inputKind, unsigned syncWindows) {
84 (void)inputKind;
86
87 for (int i = 0; i < 64; i++) s_upload[i] = sample64[i];
88 for (int i = 64; i < 128; i++) s_upload[i] = 0xaa;
89 spu_write_sync(SPU_UPLOAD_ADDR, s_upload, 128);
90
91 // Disable reverb writes while clearing the work area, then program mBASE.
92 SPU_CTRL = 0x8000 | 0x4000;
93 spu_zero_reverb_work_area();
95 spu_reverb_apply_room_preset();
96
97 SPU_VOICES[1].volumeLeft = 0x3fff;
98 SPU_VOICES[1].volumeRight = 0x3fff;
101 SPU_REVERB_LEFT = 0;
103 SPU_REVERB_EN_LOW = 1u << 1;
105
106 // Master reverb enable gates writes into the work area. Output volume is
107 // left at zero: this test observes SPU RAM state, not audible final mix.
108 SPU_CTRL = 0x8000 | 0x4000 | 0x0080;
109
110 // Align the stimulus to the same capture-half edge used by the dry tests.
111 // With zero input and a zeroed work area, the pre-key-on reverb ticks leave
112 // the work area unchanged.
113 spu_wait_status_bit11_flip();
114 spu_voice1_keyon(SPU_UPLOAD_ADDR, 0x1000);
115 spu_wait_reverb_windows(syncWindows);
116
118 SPU_KEY_OFF_LOW = 0xffff;
119 SPU_KEY_OFF_HIGH = 0xffff;
122 muteSpu();
123}
124
125#ifdef SPU_DUMP
126// File-scope (main-RAM .bss) header buffer: PCwrite must read its source from a
127// stable RAM address. A stack local got captured as live stack contents (saved
128// registers) rather than the initialized struct on real hardware, so the header
129// is built into this static instead.
130static struct SPUReverbCaptureHeader s_reverb_hdr;
131static void spu_dump_reverb(const char *name, uint32_t inputKind, unsigned syncWindows) {
132 s_reverb_hdr.magic = 0x52564552u;
133 s_reverb_hdr.length = sizeof(struct SPUReverbCaptureHeader);
134 s_reverb_hdr.preset = SPU_REVERB_PRESET_ROOM;
135 s_reverb_hdr.input = inputKind;
136 s_reverb_hdr.workBase = SPU_REVERB_ROOM_BASE;
137 s_reverb_hdr.workBytes = SPU_REVERB_ROOM_SIZE;
138 s_reverb_hdr.syncWindows = syncWindows;
139 s_reverb_hdr.reserved = 0;
140 if (!is_pcdrv_init) {
141 PCinit();
142 is_pcdrv_init = 1;
143 }
144 int fd = PCcreat(name, 0);
145 if (fd < 0) return;
146 PCwrite(fd, &s_reverb_hdr, sizeof(s_reverb_hdr));
147 PCwrite(fd, s_reverb_work, SPU_REVERB_ROOM_SIZE);
148 PCclose(fd);
149}
150#define SPU_ASSERT_REVERB_GOLDEN(NAME, INPUT, WINDOWS) spu_dump_reverb(#NAME ".test.rev", INPUT, WINDOWS)
151#else
152static int spu_compare_reverb_golden(const char *name, const void *capture, const uint8_t *goldenFile,
153 uint32_t inputKind, unsigned syncWindows) {
154 const struct SPUReverbCaptureHeader *hdr = (const struct SPUReverbCaptureHeader *)goldenFile;
155 const uint8_t *expected = goldenFile + hdr->length;
156 if (hdr->magic != 0x52564552u || hdr->length != sizeof(struct SPUReverbCaptureHeader) ||
157 hdr->preset != SPU_REVERB_PRESET_ROOM || hdr->input != inputKind ||
159 hdr->syncWindows != syncWindows) {
160 ramsyscall_printf("%s reverb golden header mismatch\n", name);
161 return 1;
162 }
163 const uint8_t *got = (const uint8_t *)capture;
164 for (unsigned i = 0; i < SPU_REVERB_ROOM_SIZE; i++) {
165 if (got[i] != expected[i]) {
166 ramsyscall_printf("%s reverb mismatch at +0x%04x: got 0x%02x, want 0x%02x\n",
167 name, i, got[i], expected[i]);
168 return i + 1;
169 }
170 }
171 return 0;
172}
173#endif
174
175#ifdef SPU_REVERB_GOLDENS_AVAILABLE
176#define INCLUDE_REVERB_GOLDEN(NAME) \
177 asm( \
178 ".pushsection .rodata\n" \
179 ".global " #NAME "\n" \
180 ".align 2\n" \
181 #NAME ":\n" \
182 ".incbin \"" #NAME ".test.rev\"\n" \
183 ".popsection"); \
184 extern const uint8_t NAME[]
185
186INCLUDE_REVERB_GOLDEN(room_impulse);
187INCLUDE_REVERB_GOLDEN(room_steady_sine);
188#define SPU_ASSERT_REVERB_GOLDEN(NAME, INPUT, WINDOWS) \
189 cester_assert_int_eq(0, spu_compare_reverb_golden(#NAME, s_reverb_work, NAME, INPUT, WINDOWS))
190#define SPU_REVERB_TEST CESTER_TEST
191#elif defined(SPU_DUMP)
192#define SPU_REVERB_TEST CESTER_TEST
193#else
194#define SPU_ASSERT_REVERB_GOLDEN(NAME, INPUT, WINDOWS) ((void)0)
195#define SPU_REVERB_TEST CESTER_SKIP_TEST
196#endif
197
198#endif
199
200SPU_REVERB_TEST(reverb_room_impulse_work_area, spu_tests,
201 run_reverb_room_work_area(kAdpcmImpulseThenSilence, SPU_REVERB_INPUT_IMPULSE, 8);
203)
204
205SPU_REVERB_TEST(reverb_room_steady_sine_work_area, spu_tests,
206 run_reverb_room_work_area(kAdpcmSine, SPU_REVERB_INPUT_STEADY_SINE, 32);
208)
__attribute__((weak))
Definition clz.c:56
#define SPU_VOICES
Definition spu.h:42
#define SPU_REVERB_EN_HIGH
Definition spu.h:97
#define SPU_VOL_MAIN_LEFT
Definition spu.h:84
#define SPU_REVERB_ADDR
Definition spu.h:98
#define SPU_REVERB_RIGHT
Definition spu.h:87
#define SPU_KEY_OFF_LOW
Definition spu.h:90
#define SPU_KEY_OFF_HIGH
Definition spu.h:91
#define SPU_CTRL
Definition spu.h:104
#define SPU_VOL_MAIN_RIGHT
Definition spu.h:85
#define SPU_REVERB_EN_LOW
Definition spu.h:96
#define SPU_REVERB
Definition spu.h:82
#define SPU_REVERB_LEFT
Definition spu.h:86
ramsyscall_printf("=== e01_kseg1_reads_no_fill ===\n")
int i
Definition gte-regio.c:297
int off
Definition mkimage.py:49
spu_tests
Definition spu-adpcm-edge.c:189
#define SPU_UPLOAD_ADDR
Definition spu-capamp.c:31
muteSpu()
#define SPU_REVERB_INPUT_IMPULSE
Definition spu-reverb.c:28
#define SPU_REVERB_ROOM_BASE_REG
Definition spu-reverb.c:26
#define SPU_REVERB_INPUT_STEADY_SINE
Definition spu-reverb.c:29
#define SPU_REVERB_ROOM_BASE
Definition spu-reverb.c:25
#define SPU_REVERB_ROOM_SIZE
Definition spu-reverb.c:24
#define SPU_REVERB_TEST
Definition spu-reverb.c:195
#define SPU_REVERB_PRESET_ROOM
Definition spu-reverb.c:30
#define SPU_ASSERT_REVERB_GOLDEN(NAME, INPUT, WINDOWS)
Definition spu-reverb.c:194
spu_write_sync(0x1000, s_upload, 128)
spu_reset_quiet()
spu_read_sync(0x1000, s_readback, 0x80)
Definition spu-reverb.c:47
uint32_t workBytes
Definition spu-reverb.c:53
uint32_t length
Definition spu-reverb.c:49
uint32_t syncWindows
Definition spu-reverb.c:54
uint32_t magic
Definition spu-reverb.c:48
uint32_t input
Definition spu-reverb.c:51
uint32_t preset
Definition spu-reverb.c:50
uint32_t workBase
Definition spu-reverb.c:52
uint32_t reserved
Definition spu-reverb.c:55
void void(ptr, size)
void uint32_t(classId, spec)