OXYGENHUB
# 2980b7b6d7cd enhanced-x16-emulator / tests / test_vera_pcm.c
17.2 KiB · C · last changed chore(): initial commit 26 days ago source blame history raw
1
// VERA PCM channel: the FIFO the CPU feeds and the two registers that drive it.
2
//
3
// ORACLE (strength B): the VERA Verilog. VERA is an FPGA, so the RTL is not a
4
// description of the hardware -- it IS the hardware. Documentation and the
5
// emulator's own C are both downstream of it and can drift from it.
6
//
7
//   repo:   https://github.com/X16Community/vera-module
8
//   tag:    v47.0.2   (commit 45cc1f05)
9
//   files:  fpga/source/audio/audio_fifo.v
10
//           fpga/source/audio/pcm.v
11
//           fpga/source/top.v                          (register decode)
12
//
13
// v47.0.2 because that is what this emulator reports itself to be, from
14
// VERA_VERSION_MAJOR/MINOR/PATCH in video.c. Guest software reads those bytes
15
// back and adapts to them, so the version the emulator claims is the version it
16
// has to behave like, whatever revision any particular physical board carries.
17
// tests/check_vera_version.py fails if the two are changed apart.
18
//
19
// The audio RTL happens to be byte-identical in v48.0.1, so nothing here turns
20
// on the choice; the two differ only in the version registers and a bus
21
// write-address register. Line numbers in top.v do differ between them, and
22
// these are v47.0.2's.
23
//
24
// Use this repo and no other. X16Community/vera-module is a fork of
25
// fvdhoef/vera-module, but the two diverged at the v0.7 release in 2019 and are
26
// now different chips: the parent has no audio RTL of any kind -- no audio
27
// directory, no PCM, no FIFO, and only eight bus registers. Audio was added for
28
// production hardware and exists solely in the fork.
29
//
30
// Every expectation below quotes the RTL it rests on, so a reader can check the
31
// claim here rather than take it on trust or re-derive it from the repo. An
32
// assertion with no quoted RTL does not belong in this file.
33
//
34
// Three behaviours diverge from the RTL and are marked with check_divergent().
35
// Those assert what the HARDWARE does: they print loudly and do not fail the
36
// build. If one starts passing, the emulator has been fixed and the marker must
37
// be deleted rather than left lying.
38
//
39
// Pin a commit, but check the history before calling something a divergence.
40
// Two of the three below look like emulator inventions against the pinned RTL
41
// and are not: the feature was real, sat on the RTL trunk for five weeks in
42
// 2023, and was reverted as an accidental merge before any release. A single
43
// snapshot would have recorded that as "no hardware counterpart", which is
44
// wrong and would have justified deleting working code. VERA firmware is
45
// field-flashable, so what matters is what shipped, not what main holds today.
46
//
47
// vera_pcm.c includes only its own header and libc, so this links it directly
48
// with no fixture and no SDL.
49
50
#include "support/harness.h"
51
52
#include "vera_pcm.h"
53
54
#include <string.h>
55
56
// audio_fifo.v:20   reg [7:0] mem_r [4095:0];                 // 4096 cells
57
// audio_fifo.v:22   wire [11:0] wridx_next = wridx_r + 12'd1;
58
// audio_fifo.v:26   assign empty = (wridx_r == rdidx_r);
59
// audio_fifo.v:27   assign full  = (wridx_next == rdidx_r);
60
//
61
// 4096 cells exist, but `full` asserts one short of them: the write pointer is
62
// never allowed to catch the read pointer, because wridx == rdidx already means
63
// empty. So the usable depth is 4095.
64
#define USABLE (PCM_FIFO_SIZE - 1)
65
66
static struct pcm_debug_state
67
state(void)
68
{
69
	struct pcm_debug_state s;
70
	pcm_debug_get_state(&s);
71
	return s;
72
}
73
74
static void
75
fill(unsigned count, uint8_t first)
76
{
77
	for (unsigned i = 0; i < count; i++) {
78
		pcm_write_fifo((uint8_t)(first + i));
79
	}
80
}
81
82
// audio_fifo.v:31-34   if (rst) begin wridx_r <= 0; rdidx_r <= 0; rddata <= 0; end
83
// top.v:540-544        audio_pcm_sample_rate_r <= 0; ... audio_pcm_volume_r <= 0;
84
static void
85
test_reset_clears_pointers_and_registers(void)
86
{
87
	pcm_reset();
88
	fill(100, 0x10);
89
	pcm_write_ctrl(0x2F);
90
	pcm_write_rate(64);
91
92
	pcm_reset();
93
	struct pcm_debug_state s = state();
94
	check_eq(s.fifo_cnt, 0u, "reset empties the FIFO");
95
	check_eq(s.fifo_rdidx, 0u, "reset clears the read pointer");
96
	check_eq(s.fifo_wridx, 0u, "reset clears the write pointer");
97
	check_eq(s.ctrl, 0u, "reset clears the control register");
98
	check_eq(s.rate, 0u, "reset clears the rate register");
99
	check_eq(s.phase, 0u, "reset clears the rate accumulator");
100
}
101
102
// audio_fifo.v:37-40   if (wr_en && !full) begin
103
//                          mem_r[wridx_r] <= wrdata;
104
//                          wridx_r <= wridx_next;
105
//                      end
106
//
107
// The write is gated on !full with no else branch, so a byte arriving at a full
108
// FIFO is dropped: nothing wraps, nothing is overwritten, no error is raised.
109
static void
110
test_fifo_depth_is_one_less_than_its_memory(void)
111
{
112
	pcm_reset();
113
	fill(USABLE, 0);
114
	check_eq(state().fifo_cnt, (unsigned)USABLE,
115
	         "the FIFO fills to 4095, one short of its 4096 cells");
116
117
	pcm_write_fifo(0xAA);
118
	check_eq(state().fifo_cnt, (unsigned)USABLE,
119
	         "a write to a full FIFO is dropped");
120
}
121
122
// top.v:227   5'h1B: rddata = {audio_fifo_full, audio_fifo_empty,
123
//                              audio_mode_16bit_r, audio_mode_stereo_r,
124
//                              audio_pcm_volume_r};
125
//
126
// Bits 7 and 6 of a read are the live FIFO flags from audio_fifo.v:26-27, not
127
// stored register bits.
128
static void
129
test_full_and_empty_status_bits(void)
130
{
131
	pcm_reset();
132
	check(pcm_read_ctrl() & 0x40, "an empty FIFO reports empty");
133
	check(!(pcm_read_ctrl() & 0x80), "an empty FIFO does not report full");
134
135
	pcm_write_fifo(0x01);
136
	check(!(pcm_read_ctrl() & 0x40), "one byte is not empty");
137
	check(!(pcm_read_ctrl() & 0x80), "one byte is not full");
138
139
	fill(USABLE - 1, 0);
140
	check(pcm_read_ctrl() & 0x80, "4095 bytes reports full");
141
	check(!(pcm_read_ctrl() & 0x40), "a full FIFO does not report empty");
142
}
143
144
// audio_fifo.v:24   wire [11:0] fifo_count = wridx_r - rdidx_r;
145
// audio_fifo.v:28   assign almost_empty = fifo_count < 12'd1024;
146
//
147
// A strict less-than against a literal 1024, on a real subtraction-based count
148
// rather than a pointer-MSB approximation. 1023 asserts and 1024 does not, and
149
// that edge is what drives the AFLOW interrupt.
150
static void
151
test_almost_empty_is_strictly_below_1024(void)
152
{
153
	pcm_reset();
154
	check(pcm_is_fifo_almost_empty(), "an empty FIFO is almost empty");
155
156
	fill(1023, 0);
157
	check(pcm_is_fifo_almost_empty(), "1023 bytes is almost empty");
158
	check(state().almost_empty, "and the debug view agrees");
159
160
	pcm_write_fifo(0);
161
	check_eq(state().fifo_cnt, 1024u, "1024 bytes queued");
162
	check(!pcm_is_fifo_almost_empty(), "1024 bytes is not almost empty");
163
	check(!state().almost_empty, "and the debug view agrees");
164
}
165
166
// top.v:473-477   if (do_write && access_addr == 5'h1B) begin
167
//                     audio_fifo_reset_next  = write_data[7];
168
//                     audio_mode_16bit_next  = write_data[5];
169
//                     audio_mode_stereo_next = write_data[4];
170
//                     audio_pcm_volume_next  = write_data[3:0];
171
//
172
// Bit 5 is the 16-bit flag, bit 4 selects stereo, bits 3:0 are volume. Those
173
// six bits are the whole of the stored state, and a read returns them under the
174
// two status bits.
175
static void
176
test_ctrl_stores_mode_and_volume_only(void)
177
{
178
	pcm_reset();
179
	pcm_write_ctrl(0x3F);
180
	check_eq(state().ctrl, 0x3Fu, "16-bit, stereo and full volume are stored");
181
182
	pcm_reset();
183
	pcm_write_ctrl(0x15);
184
	check_eq(state().ctrl, 0x15u, "stereo and volume 5 are stored");
185
}
186
187
// top.v:474   audio_fifo_reset_next = write_data[7];
188
// top.v:320   audio_fifo_reset_next = 0;          // default, every cycle
189
// pcm.v:31    wire audio_fifo_reset = rst || fifo_reset;
190
//
191
// Bit 7 is a one-shot command rather than a stored setting: the default
192
// assignment clears it every cycle, so it pulses for the write cycle only and
193
// resets both FIFO pointers via audio_fifo.v:32-33.
194
static void
195
test_ctrl_bit7_resets_the_fifo(void)
196
{
197
	pcm_reset();
198
	fill(50, 0);
199
	pcm_write_ctrl(0x80);
200
	check_eq(state().fifo_cnt, 0u, "bit 7 empties the FIFO");
201
	check_eq(state().ctrl, 0u, "bit 7 itself is not stored");
202
}
203
204
// DIVERGENCE 1 -- AUDIO_RATE is stored raw on hardware; the emulator folds it.
205
//
206
// top.v:139       reg [7:0] audio_pcm_sample_rate_r, audio_pcm_sample_rate_next;
207
// top.v:479-480   if (do_write && access_addr == 5'h1C) begin
208
//                     audio_pcm_sample_rate_next = write_data;
209
// top.v:228       5'h1C: rddata = audio_pcm_sample_rate_r;
210
// pcm.v:67        sr_accum_r <= sr_accum_r + sample_rate;
211
// pcm.v:72        wire new_sample = next_sample_r && (sr_accum7_r != sr_accum_r[7]);
212
//
213
// All 8 bits are stored unchanged -- no fold, no mask, no clamp -- read back
214
// unchanged, and added whole to the rate accumulator. A new sample fires when
215
// bit 7 of that accumulator flips, so a LARGER value plays FASTER, all the way
216
// to 255.
217
//
218
// This is a leftover from the loop feature, not a reading of the hardware. No
219
// RTL revision has ever transformed this register:
220
//
221
//   2020-03-08  1b402ea2 (original PCM)  sr_accum_r <= sr_accum_r + sample_rate;
222
//   2023-08-12  70a03e00 (loop era)      5'h1C: audio_pcm_sample_rate_next = write_data;
223
//   v47.0.2     45cc1f05 (this oracle)   same, top.v:480
224
//
225
// The original emulator code by VERA's designer was `rate = val`, matching all
226
// three. Upstream #116 then added a loop feature triggered by writing
227
// AUDIO_RATE > 128, and masked to recover the rate: `loop = (val > 128);
228
// rate = loop ? (val & 0x7f) : val`. When #159 moved that trigger onto
229
// AUDIO_CTRL bits 6 and 7 -- correctly, to match the RTL of the day -- it left
230
// a transform behind on the rate register and changed it to `256 - val`.
231
//
232
// loop is now assigned only in pcm_write_ctrl, so nothing reads loop state out
233
// of this register. The fold survives with no purpose beyond corrupting rates
234
// above 128: 200 becomes 56, playing at roughly a quarter of the hardware speed
235
// and reading back as 56. Values up to and including 128 are unaffected, and
236
// 128 is the fold's fixed point, so the boundary itself is invisible -- which
237
// is why this has gone unnoticed.
238
static void
239
test_rate_register_stores_the_raw_value(void)
240
{
241
	pcm_reset();
242
243
	pcm_write_rate(0);
244
	check_eq(pcm_read_rate(), 0u, "rate 0 is stored as 0");
245
	pcm_write_rate(64);
246
	check_eq(pcm_read_rate(), 64u, "rate 64 is stored as 64");
247
	pcm_write_rate(128);
248
	check_eq(pcm_read_rate(), 128u, "rate 128 is stored as 128");
249
250
	pcm_write_rate(129);
251
	check_divergent(pcm_read_rate() == 129, "rate 129 is stored as 129",
252
	                "emulator folds to 127; top.v:480 stores write_data unchanged");
253
254
	pcm_write_rate(200);
255
	check_divergent(pcm_read_rate() == 200, "rate 200 is stored as 200",
256
	                "emulator folds to 56; on hardware 200 plays faster than 128, not slower");
257
258
	pcm_write_rate(255);
259
	check_divergent(pcm_read_rate() == 255, "rate 255 is stored as 255",
260
	                "emulator folds to 1, near-silence, where hardware runs near maximum");
261
}
262
263
// DIVERGENCE 2 -- AUDIO_CTRL bit 6 does nothing on any released hardware.
264
//
265
// top.v:473-477 decodes only bits 7, 5, 4 and 3:0 of a write to 0x1B; bit 6 is
266
// never read. Searching top.v for write_data[6] finds one use, at top.v:340,
267
// for sprites_enabled on an unrelated register. audio_fifo.v assigns rdidx_r in
268
// exactly two places, :33 (reset, together with wridx_r) and :44 (increment on
269
// read), so nothing rewinds the read pointer alone.
270
//
271
// The emulator treats bit 6 as a "restart" that rewinds the read pointer while
272
// keeping the queued data. That was NOT invented: the RTL briefly carried the
273
// same feature, and the emulator implemented it faithfully.
274
//
275
//   2023-08-09  VERA v0.3.1 released -- no loop or restart
276
//   2023-08-12  vera-module 70a03e00 adds fifo_restart and fifo_loop, decoding
277
//                 audio_fifo_reset_next   = write_data[7:6] == 2'b10;
278
//                 audio_fifo_restart_next = write_data[6];
279
//                 audio_fifo_loop_next    = write_data[7:6] == 2'b11;
280
//               and an rd_rst input on the FIFO that clears rdidx_r alone
281
//   2023-08-13  emulator #159 matches that decode exactly, one day later
282
//   2023-09-16  vera-module b1ada295 reverts it: "Was accidentally merged in"
283
//   2023-11-20  v0.3.2, then v47.0.2 and v48.0.1 -- none contain fifo_loop or
284
//               fifo_restart
285
//
286
// So the feature existed on the RTL trunk for five weeks, by mistake, and is in
287
// no released bitstream. The emulator tracked the addition and not the revert.
288
// Recorded rather than removed, since software written in the meantime may use
289
// it -- but no real machine has ever had it.
290
static void
291
test_ctrl_bit6_is_ignored(void)
292
{
293
	// The read pointer has to be somewhere other than zero for this to mean
294
	// anything: rewinding to zero from zero is indistinguishable from doing
295
	// nothing, so render first to advance it.
296
	pcm_reset();
297
	fill(100, 0x40);
298
	int16_t buf[128];
299
	pcm_write_rate(128);
300
	pcm_render(buf, 20);
301
302
	unsigned rd_before = state().fifo_rdidx;
303
	unsigned cnt_before = state().fifo_cnt;
304
	check(rd_before > 0, "the read pointer has advanced");
305
306
	pcm_write_ctrl(0x40);
307
	check_divergent(state().fifo_rdidx == rd_before,
308
	                "bit 6 leaves the read pointer alone",
309
	                "emulator rewinds it to 0; bit 6 of 0x1B is not decoded in top.v");
310
	check_divergent(state().fifo_cnt == cnt_before,
311
	                "bit 6 leaves the queued count alone",
312
	                "emulator requeues everything written; there is no restart in the RTL");
313
}
314
315
// DIVERGENCE 3 -- there is no loop mode on any released hardware.
316
//
317
// The string "loop" does not occur anywhere in top.v, audio.v, pcm.v or
318
// audio_fifo.v at the pinned commit. There is no loop register, no loop input
319
// on the pcm module (pcm.v:8-20), and no re-read path in the FIFO.
320
//
321
// The emulator reads bits 7 and 6 set together as loop mode and, in that case,
322
// deliberately does not reset the FIFO. On the pinned RTL bit 7 resets it
323
// regardless of bit 6, because bit 6 is not decoded at all.
324
//
325
// Same history as divergence 2: the reverted RTL had
326
//     if (fifo_empty && fifo_loop) fifo_restart_next = 1;
327
// which is precisely the emulator's `if (loop && fifo_cnt == 0) fifo_restart()`.
328
// The emulator is a faithful implementation of a design that was accidentally
329
// merged and then reverted before any release.
330
static void
331
test_no_loop_mode(void)
332
{
333
	pcm_reset();
334
	fill(50, 0);
335
	pcm_write_ctrl(0xC0);
336
	check_divergent(state().fifo_cnt == 0,
337
	                "bit 7 resets the FIFO even with bit 6 set",
338
	                "emulator reads bits 7+6 as loop mode and keeps the data; no loop in the RTL");
339
}
340
341
// audio_fifo.v:17-18   reg [11:0] wridx_r = 0;  reg [11:0] rdidx_r = 0;
342
// audio_fifo.v:22-23   wridx_next = wridx_r + 12'd1;  rdidx_next = rdidx_r + 12'd1;
343
//
344
// The pointers are 12 bits and wrap mod 4096 by natural overflow, so the RTL
345
// needs no explicit bounds check and neither should the emulator.
346
static void
347
test_pointers_wrap_within_the_ring(void)
348
{
349
	pcm_reset();
350
	fill(USABLE, 0);
351
352
	int16_t buf[4096];
353
	pcm_write_rate(128);
354
	pcm_render(buf, 2000);
355
356
	unsigned before = state().fifo_cnt;
357
	check(before < (unsigned)USABLE, "rendering consumed part of the queue");
358
359
	fill(100, 0x80);
360
	struct pcm_debug_state s = state();
361
	check_eq(s.fifo_cnt, before + 100, "writes after the wrap are queued");
362
	check(s.fifo_wridx < PCM_FIFO_SIZE, "the write pointer stays in the ring");
363
	check(s.fifo_rdidx < PCM_FIFO_SIZE, "the read pointer stays in the ring");
364
}
365
366
// audio_fifo.v:42   if (rd_en && !empty) begin
367
//
368
// Reads are gated on !empty, so an empty FIFO advances nothing. The debug peek
369
// has no RTL counterpart -- it exists only for the debugger -- so what is
370
// pinned here is that it disturbs none of the state the RTL does define.
371
static void
372
test_debug_peek_consumes_nothing(void)
373
{
374
	pcm_reset();
375
	fill(10, 0x30);
376
377
	uint8_t seen[10];
378
	unsigned got = pcm_debug_peek_fifo(seen, 0, sizeof seen);
379
	check_eq(got, 10u, "peeking returns what was queued");
380
	check_eq(seen[0], 0x30u, "peeking starts at the read pointer");
381
	check_eq(seen[9], 0x39u, "and reads forward in order");
382
	check_eq(state().fifo_cnt, 10u, "peeking consumes nothing");
383
	check_eq(state().fifo_rdidx, 0u, "and does not move the read pointer");
384
385
	got = pcm_debug_peek_fifo(seen, 8, sizeof seen);
386
	check_eq(got, 2u, "peeking past the end stops at the queued data");
387
	check_eq(seen[0], 0x38u, "the offset is applied from the read pointer");
388
389
	check_eq(pcm_debug_peek_fifo(seen, 10, 4), 0u,
390
	         "an offset at the end of the queue returns nothing");
391
}
392
393
// pcm.v:60    sr_accum_r <= 0;                       // and the sample registers
394
// pcm.v:160   right_output_next = mode_stereo ? right_sample : left_sample;
395
//
396
// With nothing queued the sample state machine never leaves IDLE and the output
397
// registers hold. From reset those registers are zero, so rendering an empty
398
// FIFO produces silence rather than stale buffer contents.
399
static void
400
test_empty_fifo_renders_silence(void)
401
{
402
	pcm_reset();
403
	int16_t buf[64];
404
	memset(buf, 0x7F, sizeof buf);
405
	pcm_render(buf, 32);
406
	bool silent = true;
407
	for (int i = 0; i < 64; i++) {
408
		if (buf[i] != 0) {
409
			silent = false;
410
		}
411
	}
412
	check(silent, "rendering an empty FIFO writes silence");
413
}
414
415
int
416
main(void)
417
{
418
	test_reset_clears_pointers_and_registers();
419
	test_fifo_depth_is_one_less_than_its_memory();
420
	test_full_and_empty_status_bits();
421
	test_almost_empty_is_strictly_below_1024();
422
	test_ctrl_stores_mode_and_volume_only();
423
	test_ctrl_bit7_resets_the_fifo();
424
	test_rate_register_stores_the_raw_value();
425
	test_ctrl_bit6_is_ignored();
426
	test_no_loop_mode();
427
	test_pointers_wrap_within_the_ring();
428
	test_debug_peek_consumes_nothing();
429
	test_empty_fifo_renders_silence();
430
	return x16_test_summary("vera_pcm");
431
}