OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_vera_bandwidth.c
22.9 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// Checks for VERA VRAM bandwidth accounting (src/vera_bandwidth.c).
2
//
3
// The module's whole job is to say what VERA's VRAM bus did in a frame, and
4
// almost all of that rests on one pure function turning a layer's mode into a
5
// fetch count. So the bulk of what follows is that function checked against the
6
// RTL's own decode tables, mode by mode -- because if those numbers are wrong
7
// every figure built on them is wrong too, and nothing else in the module would
8
// notice.
9
//
10
// The tables are quoted from X16Community/vera-module at tag v47.0.2, the pin
11
// tests/fetch_vera_rtl.py fetches. Run it and the citations below can be
12
// checked line by line.
13
//
14
// vera_bandwidth.c reaches for no emulator state, so this drives it directly:
15
// no CPU, no memory, no video, no SDL.
16
17
#include "vera_bandwidth.h"
18
#include "support/harness.h"
19
20
#include <stdio.h>
21
#include <string.h>
22
23
// Colour depth encodings, which are log2(bpp) throughout VERA.
24
#define BPP1 0
25
#define BPP2 1
26
#define BPP4 2
27
#define BPP8 3
28
29
static vera_bw_layer_t
30
tile_layer(uint8_t depth, uint8_t tile_width, bool scrolled)
31
{
32
	vera_bw_layer_t l;
33
	memset(&l, 0, sizeof l);
34
	l.enabled     = true;
35
	l.bitmap_mode = false;
36
	l.color_depth = depth;
37
	l.tile_width  = tile_width;
38
	l.scrolled    = scrolled;
39
	return l;
40
}
41
42
static vera_bw_layer_t
43
bitmap_layer(uint8_t depth, uint8_t tile_width)
44
{
45
	vera_bw_layer_t l;
46
	memset(&l, 0, sizeof l);
47
	l.enabled     = true;
48
	l.bitmap_mode = true;
49
	l.color_depth = depth;
50
	l.tile_width  = tile_width;
51
	return l;
52
}
53
54
static uint16_t
55
tile_fetches(uint8_t depth, uint8_t tile_width, bool scrolled)
56
{
57
	const vera_bw_layer_t l = tile_layer(depth, tile_width, scrolled);
58
	return vera_bandwidth_layer_line_fetches(&l);
59
}
60
61
static uint16_t
62
bitmap_fetches(uint8_t depth, uint8_t tile_width)
63
{
64
	const vera_bw_layer_t l = bitmap_layer(depth, tile_width);
65
	return vera_bandwidth_layer_line_fetches(&l);
66
}
67
68
static void
69
begin(void)
70
{
71
	vera_bandwidth_set_enabled(false);
72
	vera_bandwidth_configure(525);
73
	vera_bandwidth_set_enabled(true);
74
	vera_bandwidth_reset();
75
}
76
77
int
78
main(void)
79
{
80
	// ── The bus geometry these numbers all rest on ──────────────────────────
81
	{
82
		check_eq(VERA_BW_LINE_CLOCKS, 800,
83
		         "a scanline is 800 clocks: video_vga.v:27-31, 640+16+96+48");
84
		check_eq(VERA_BW_BYTES_PER_ACCESS, 4,
85
		         "VRAM is 32 bits wide, so an access is four bytes: main_ram.v:7-10");
86
		check_eq(VERA_BW_ACCESS_CLOCKS, 2,
87
		         "an access costs strobe plus ack: layer_renderer.v:250-271");
88
		check_eq(VERA_BW_LAYER_PIXELS, 640,
89
		         "the layer renderer always fills 640 pixels: layer_renderer.v:488");
90
	}
91
92
	// ── pixels_per_word, against layer_renderer.v:42-51 ─────────────────────
93
	{
94
		check_eq(vera_bandwidth_pixels_per_word(false, BPP1, 0), 8,
95
		         "1bpp 8px tile packs 8 pixels per word");
96
		check_eq(vera_bandwidth_pixels_per_word(false, BPP1, 1), 16,
97
		         "1bpp 16px tile packs 16");
98
		check_eq(vera_bandwidth_pixels_per_word(false, BPP2, 0), 8,
99
		         "2bpp 8px tile packs 8");
100
		check_eq(vera_bandwidth_pixels_per_word(false, BPP2, 1), 16,
101
		         "2bpp 16px tile packs 16");
102
		check_eq(vera_bandwidth_pixels_per_word(false, BPP4, 0), 8,
103
		         "4bpp tile packs 8 regardless of tile width");
104
		check_eq(vera_bandwidth_pixels_per_word(false, BPP4, 1), 8,
105
		         "4bpp 16px tile still packs 8");
106
		check_eq(vera_bandwidth_pixels_per_word(false, BPP8, 0), 4,
107
		         "8bpp tile packs 4");
108
		check_eq(vera_bandwidth_pixels_per_word(false, BPP8, 1), 4,
109
		         "8bpp 16px tile still packs 4");
110
111
		check_eq(vera_bandwidth_pixels_per_word(true, BPP1, 0), 32,
112
		         "1bpp bitmap packs 32 pixels per word -- a mode that only "
113
		         "exists from v47.0.2 (layer_renderer.v:48)");
114
		check_eq(vera_bandwidth_pixels_per_word(true, BPP2, 0), 16,
115
		         "2bpp bitmap packs 16");
116
		check_eq(vera_bandwidth_pixels_per_word(true, BPP4, 0), 8,
117
		         "4bpp bitmap packs 8");
118
		check_eq(vera_bandwidth_pixels_per_word(true, BPP8, 0), 4,
119
		         "8bpp bitmap packs 4");
120
	}
121
122
	// ── words_per_tile_line, against layer_renderer.v:68-77 ─────────────────
123
	{
124
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP1, 0), 1, "1bpp 8px tile row is one word");
125
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP1, 1), 1, "1bpp 16px tile row is one word");
126
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP2, 0), 1, "2bpp 8px tile row is one word");
127
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP2, 1), 1, "2bpp 16px tile row is one word");
128
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP4, 0), 1, "4bpp 8px tile row is one word");
129
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP4, 1), 2, "4bpp 16px tile row is two");
130
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP8, 0), 2, "8bpp 8px tile row is two");
131
		check_eq(vera_bandwidth_words_per_tile_line(false, BPP8, 1), 4, "8bpp 16px tile row is four");
132
	}
133
134
	// ── Tile-mode fetch counts per scanline ─────────────────────────────────
135
	//
136
	// 640 pixels / 8px tiles = 80 tiles, one map word per two tiles (40), plus
137
	// words_per_tile_line each. Unscrolled, so no extra tile.
138
	{
139
		check_eq(tile_fetches(BPP1, 0, false), 40 + 80,
140
		         "1bpp 8px tiles: 40 map + 80 data = 120 fetches a line");
141
		check_eq(tile_fetches(BPP2, 0, false), 40 + 80,
142
		         "2bpp 8px tiles cost the same as 1bpp: both are one word a tile");
143
		check_eq(tile_fetches(BPP4, 0, false), 40 + 80,
144
		         "4bpp 8px tiles too");
145
		check_eq(tile_fetches(BPP8, 0, false), 40 + 160,
146
		         "8bpp 8px tiles need two words a tile: 200");
147
148
		check_eq(tile_fetches(BPP1, 1, false), 20 + 40,
149
		         "1bpp 16px tiles halve the tile count: 60");
150
		check_eq(tile_fetches(BPP2, 1, false), 20 + 40, "2bpp 16px tiles: 60");
151
		check_eq(tile_fetches(BPP4, 1, false), 20 + 80,
152
		         "4bpp 16px tiles need two words a tile: 100");
153
		check_eq(tile_fetches(BPP8, 1, false), 20 + 160,
154
		         "8bpp 16px tiles need four: 180");
155
	}
156
157
	// ── The scroll tile ─────────────────────────────────────────────────────
158
	//
159
	// lb_wridx_start is 0 - subtile_hscroll (layer_renderer.v:499-501), so a
160
	// layer scrolled off a tile boundary renders one tile more than the screen
161
	// needs. It is the single most common configuration in a scrolling game, so
162
	// it is worth being explicit that it is not free.
163
	{
164
		check_eq(tile_fetches(BPP8, 0, true), 41 + 162,
165
		         "scrolling 8bpp 8px tiles off a boundary costs one more tile: 203");
166
		check_eq(tile_fetches(BPP8, 0, true) - tile_fetches(BPP8, 0, false), 3,
167
		         "which is three extra fetches: one map word and two data words");
168
		check_eq(tile_fetches(BPP1, 0, true), 41 + 81,
169
		         "and at 1bpp it is 122");
170
		check_eq(tile_fetches(BPP1, 0, true) - tile_fetches(BPP1, 0, false), 2,
171
		         "two extra fetches, because a 1bpp tile row is a single word");
172
	}
173
174
	// ── Bitmap fetch counts, and the surprise in them ───────────────────────
175
	{
176
		check_eq(bitmap_fetches(BPP1, 0), 20, "1bpp bitmap: 640/32 = 20 fetches");
177
		check_eq(bitmap_fetches(BPP2, 0), 40, "2bpp bitmap: 640/16 = 40");
178
		check_eq(bitmap_fetches(BPP4, 0), 80, "4bpp bitmap: 640/8 = 80");
179
		check_eq(bitmap_fetches(BPP8, 0), 160, "8bpp bitmap: 640/4 = 160");
180
181
		// The one people get wrong. layer_renderer has no scale input at all
182
		// (its port list, :3-33) -- DC_HSCALE is the composer's business when it
183
		// reads the line buffer. The renderer just fills to line_done at 640
184
		// (:488), so a 320-wide bitmap fetches exactly as much as a 640-wide
185
		// one and runs off the end of its own line doing it.
186
		check_eq(bitmap_fetches(BPP8, 1), bitmap_fetches(BPP8, 0),
187
		         "a 320-wide bitmap costs the same bus as a 640-wide one: "
188
		         "halving the width buys VRAM, not bandwidth");
189
		check_eq(bitmap_fetches(BPP4, 1), bitmap_fetches(BPP4, 0),
190
		         "and the same at 4bpp");
191
		check_eq(bitmap_fetches(BPP1, 1), bitmap_fetches(BPP1, 0),
192
		         "and at 1bpp");
193
	}
194
195
	// ── A disabled or absent layer costs nothing ────────────────────────────
196
	{
197
		vera_bw_layer_t off = tile_layer(BPP8, 0, true);
198
		off.enabled         = false;
199
		check_eq(vera_bandwidth_layer_line_fetches(&off), 0,
200
		         "a layer switched off in DC_VIDEO fetches nothing");
201
		check_eq(vera_bandwidth_layer_line_fetches(NULL), 0,
202
		         "and a null layer is not a crash");
203
	}
204
205
	// ── The worst case really does fill the scanline ────────────────────────
206
	//
207
	// This is the number that justifies reporting per-scanline pressure at all:
208
	// two 8bpp bitmap layers plus a full sprite line come to exactly H_TOTAL.
209
	{
210
		const uint32_t two_layers = 2u * bitmap_fetches(BPP8, 1) * VERA_BW_ACCESS_CLOCKS;
211
		check_eq(two_layers, 640,
212
		         "two 8bpp bitmap layers take 640 of the 800 clocks");
213
		check_eq(two_layers + 160, VERA_BW_LINE_CLOCKS,
214
		         "and 160 clocks of 4bpp sprites fills the scanline exactly");
215
	}
216
217
	// ── Accumulation over a frame ───────────────────────────────────────────
218
	{
219
		begin();
220
		const vera_bw_layer_t l0 = bitmap_layer(BPP8, 1); // 160 fetches a line
221
		const vera_bw_layer_t l1 = tile_layer(BPP1, 0, false); // 120 a line
222
223
		for (uint16_t y = 0; y < 480; y++)
224
			vera_bandwidth_line(y, &l0, &l1);
225
		vera_bandwidth_frame_end(1);
226
227
		vera_bw_frame_t f;
228
		check(vera_bandwidth_last_frame(&f), "a finished frame is published");
229
		check_eq(f.frame, 1, "carrying its ordinal");
230
		check_eq(f.lines_active, 480, "with every active line accounted");
231
		check_eq(f.layer_fetches[0], 160u * 480u, "layer 0 fetched 160 a line");
232
		check_eq(f.layer_fetches[1], 120u * 480u, "layer 1 fetched 120 a line");
233
		check_eq(f.layer_bytes[0], 160u * 480u * 4u,
234
		         "and bytes are just fetches times four");
235
		check_eq(f.peak_clocks, (160u + 120u) * VERA_BW_ACCESS_CLOCKS,
236
		         "the busiest line wanted 560 of its 800 clocks");
237
		check_eq(f.mean_clocks, 560, "and every line was the same, so the mean matches");
238
		check_eq(f.p95_clocks, 560, "as does the 95th percentile");
239
		check_eq(f.lines_over, 0, "nothing exceeded the scanline window");
240
		check_eq(f.layer_peak_fetches[0], 160, "layer 0's peak line is a full one");
241
	}
242
243
	// ── A re-rendered line records the last pass, not the sum ───────────────
244
	//
245
	// video.c re-renders a scanline when a register changes mid-line, and the
246
	// last pass is what stays on screen. Accumulating would report a line that
247
	// used twice the bandwidth it really did.
248
	{
249
		begin();
250
		const vera_bw_layer_t heavy = bitmap_layer(BPP8, 1);   // 160
251
		const vera_bw_layer_t light = bitmap_layer(BPP1, 0);   // 20
252
		vera_bw_layer_t       none  = bitmap_layer(BPP1, 0);
253
		none.enabled                = false;
254
255
		vera_bandwidth_line(100, &heavy, &none);
256
		vera_bandwidth_line(100, &light, &none); // the split re-renders it
257
		vera_bandwidth_frame_end(2);
258
259
		vera_bw_frame_t f;
260
		check(vera_bandwidth_last_frame(&f), "the frame published");
261
		check_eq(f.layer_fetches[0], 20,
262
		         "a re-rendered line keeps the last pass, not the sum of passes");
263
		check_eq(f.lines_active, 1, "and is still one line");
264
	}
265
266
	// ── The data port, which is the part a program controls ─────────────────
267
	{
268
		begin();
269
		for (int i = 0; i < 100; i++)
270
			vera_bandwidth_note_port(true, 1, 50);
271
		for (int i = 0; i < 30; i++)
272
			vera_bandwidth_note_port(false, 1, 50);
273
		vera_bandwidth_frame_end(3);
274
275
		vera_bw_frame_t f;
276
		check(vera_bandwidth_last_frame(&f), "the frame published");
277
		check_eq(f.port_writes, 100, "100 bytes written through $9F23");
278
		check_eq(f.port_reads, 30, "and 30 read back");
279
		check_eq(f.port_accesses, 130, "each byte costs one VRAM access");
280
		check_eq(f.port_vram_bytes, 130, "and an ordinary write moves one byte");
281
		// The port lands on a line even though no layer rendered it, because
282
		// the CPU outranks both layers on the bus (vram_if.v:142-157).
283
		check_eq(f.peak_line, 50, "port traffic marks the line it landed on");
284
		check_eq(f.lines_active, 1,
285
		         "and makes that line active even with both layers off -- a "
286
		         "program blasting VRAM through $9F23 is using the bus, "
287
		         "whatever the display is doing");
288
		check_eq(f.peak_clocks, 130u * VERA_BW_ACCESS_CLOCKS,
289
		         "costing two clocks an access like any other client");
290
	}
291
292
	// ── VERA FX, where one store is not one byte ────────────────────────────
293
	{
294
		begin();
295
		// A cache write is one store that puts four bytes into VRAM
296
		// (video.c fx_vram_cache_write x4).
297
		for (int i = 0; i < 10; i++) {
298
			vera_bandwidth_note_port(true, 4, 10);
299
			vera_bandwidth_note_fx_cache_write(10);
300
		}
301
		vera_bandwidth_frame_end(4);
302
303
		vera_bw_frame_t f;
304
		check(vera_bandwidth_last_frame(&f), "the frame published");
305
		check_eq(f.port_writes, 10, "ten CPU stores");
306
		check_eq(f.fx_cache_writes, 10, "all of them FX cache writes");
307
		check_eq(f.port_vram_bytes, 40,
308
		         "which moved forty bytes: FX multiplies a store fourfold");
309
	}
310
311
	// ── Affine mode costs a second fetch ────────────────────────────────────
312
	{
313
		begin();
314
		vera_bandwidth_note_port(false, 1, 20);
315
		vera_bandwidth_note_fx_affine(20);
316
		vera_bandwidth_frame_end(5);
317
318
		vera_bw_frame_t f;
319
		check(vera_bandwidth_last_frame(&f), "the frame published");
320
		check_eq(f.fx_affine_fetches, 1, "one affine read");
321
		check_eq(f.port_accesses, 2,
322
		         "costing two accesses: the map entry and then the tile");
323
	}
324
325
	// ── Lines over the window are counted, not clamped away ─────────────────
326
	{
327
		begin();
328
		const vera_bw_layer_t l0 = bitmap_layer(BPP8, 1); // 160 -> 320 clocks
329
		const vera_bw_layer_t l1 = bitmap_layer(BPP8, 1); // 160 -> 320 clocks
330
		vera_bandwidth_line(7, &l0, &l1);
331
		for (int i = 0; i < 100; i++) // 100 more accesses -> 200 more clocks
332
			vera_bandwidth_note_port(true, 1, 7);
333
		vera_bandwidth_frame_end(6);
334
335
		vera_bw_frame_t f;
336
		check(vera_bandwidth_last_frame(&f), "the frame published");
337
		check_eq(f.peak_clocks, 840, "layers and CPU together wanted 840 clocks");
338
		check(f.peak_clocks > VERA_BW_LINE_CLOCKS,
339
		      "which is more than the scanline has");
340
		check_eq(f.lines_over, 1,
341
		         "and that is reported rather than clamped, because it is the "
342
		         "whole point: sprites are last on the bus and get what is left");
343
	}
344
345
	// ── Percentiles ─────────────────────────────────────────────────────────
346
	{
347
		begin();
348
		vera_bw_layer_t none = bitmap_layer(BPP1, 0);
349
		none.enabled         = false;
350
		const vera_bw_layer_t light = bitmap_layer(BPP1, 0); // 20 -> 40 clocks
351
		const vera_bw_layer_t heavy = bitmap_layer(BPP8, 1); // 160 -> 320 clocks
352
353
		// 99 quiet lines and one loud one: the peak must show it and the mean
354
		// must not drown it.
355
		for (uint16_t y = 0; y < 99; y++)
356
			vera_bandwidth_line(y, &light, &none);
357
		vera_bandwidth_line(99, &heavy, &none);
358
		vera_bandwidth_frame_end(7);
359
360
		vera_bw_frame_t f;
361
		check(vera_bandwidth_last_frame(&f), "the frame published");
362
		check_eq(f.lines_active, 100, "a hundred lines rendered");
363
		check_eq(f.peak_clocks, 320, "the loud line is the peak");
364
		check_eq(f.peak_line, 99, "and is identified");
365
		check_eq(f.p95_clocks, 40,
366
		         "the 95th percentile ignores a single outlier, which is why "
367
		         "the peak is reported next to it and not instead of it");
368
		check(f.mean_clocks < 50, "and the mean stays near the quiet lines");
369
	}
370
371
	// ── Per-line detail is available for drawing ────────────────────────────
372
	{
373
		begin();
374
		const vera_bw_layer_t l0 = bitmap_layer(BPP4, 0); // 80
375
		vera_bw_layer_t       none = bitmap_layer(BPP1, 0);
376
		none.enabled               = false;
377
		vera_bandwidth_line(3, &l0, &none);
378
		vera_bandwidth_note_port(true, 1, 3);
379
		vera_bandwidth_frame_end(8);
380
381
		uint16_t              n     = 0;
382
		const vera_bw_line_t *lines = vera_bandwidth_last_lines(&n);
383
		check(lines != NULL, "per-line detail is published for the panel");
384
		check_eq(n, VERA_BW_LINES, "covering every active scanline");
385
		check(lines[3].rendered, "the line we drew is marked rendered");
386
		check_eq(lines[3].layer_fetches[0], 80, "with its fetch count");
387
		check_eq(lines[3].port_accesses, 1, "and the port access on it");
388
		check_eq(vera_bw_line_clocks(&lines[3]), 81u * VERA_BW_ACCESS_CLOCKS,
389
		         "line clocks are derived from both, so they cannot drift apart");
390
		check(!lines[4].rendered, "and one we did not is not");
391
	}
392
393
	// ── Layer and port traffic on one line must land on ONE line ────────────
394
	//
395
	// They are recorded from different places -- the renderer and the CPU's
396
	// store -- and if those two ever indexed the array differently the peak
397
	// figure would be built from halves of two different scanlines. Pinned
398
	// because that is exactly what went wrong once: the layer side was charged
399
	// to the scaled source row rather than the display line, so in a 320x240
400
	// mode it landed on line y/2 while the port traffic landed on y.
401
	{
402
		begin();
403
		const vera_bw_layer_t l0 = bitmap_layer(BPP4, 0); // 80 fetches
404
		vera_bw_layer_t       none = bitmap_layer(BPP1, 0);
405
		none.enabled               = false;
406
407
		vera_bandwidth_line(200, &l0, &none);
408
		for (int i = 0; i < 20; i++)
409
			vera_bandwidth_note_port(true, 1, 200);
410
		vera_bandwidth_frame_end(11);
411
412
		vera_bw_frame_t f;
413
		check(vera_bandwidth_last_frame(&f), "the frame published");
414
		check_eq(f.lines_active, 1,
415
		         "layer and port traffic on one scanline is one active line");
416
		check_eq(f.peak_line, 200, "and the peak is that line");
417
		check_eq(f.peak_clocks, (80u + 20u) * VERA_BW_ACCESS_CLOCKS,
418
		         "with both contributions summed into it");
419
	}
420
421
	// ── Sprite render time is NOT bus time ──────────────────────────────────
422
	//
423
	// The units trap this module exists on the right side of. sprite_trace
424
	// measures render_time_r, which ticks every clock including the ones
425
	// painting the line buffer with the bus idle; everything here is bus
426
	// occupancy. Subtracting one from the other is wrong by the duty cycle.
427
	{
428
		// 4bpp: one 32-bit word per 8 pixels, two clocks a fetch.
429
		check_eq(vera_bandwidth_sprite_bus_clocks(64, 0), 16,
430
		         "a 64-wide 4bpp sprite fetches 8 words: 16 bus clocks");
431
		check_eq(vera_bandwidth_sprite_bus_clocks(8, 0), 2,
432
		         "an 8-wide 4bpp sprite is one word");
433
		// 8bpp: one word per 4 pixels, so twice the bus for the same width.
434
		check_eq(vera_bandwidth_sprite_bus_clocks(64, 1), 32,
435
		         "the same sprite at 8bpp costs twice the bus");
436
		check_eq(vera_bandwidth_sprite_bus_clocks(8, 1), 4, "and an 8-wide one is two words");
437
		// A width that is not a whole number of words still fetches the last
438
		// partial one.
439
		check_eq(vera_bandwidth_sprite_bus_clocks(12, 0), 4,
440
		         "a 12-wide 4bpp sprite still pays for its partial last word");
441
442
		// The relationship that makes the conversion necessary. A 4bpp sprite
443
		// spends 2 bus clocks and 8 render clocks per word, so its render time
444
		// is five times its bus time. SPRITE_TRACE_LINE_BUDGET is 798 RENDER
445
		// clocks; the bus cost of filling a line with 4bpp sprites is nothing
446
		// like that number.
447
		const uint16_t words_in_a_full_line = 798 / 10; // 2 bus + 8 render
448
		const uint32_t bus = (uint32_t)words_in_a_full_line * VERA_BW_ACCESS_CLOCKS;
449
		check_eq(bus, 158,
450
		         "a full 4bpp sprite line is about 158 bus clocks, not 798 -- "
451
		         "which is why the two numbers must never be subtracted");
452
		check(bus < VERA_BW_LINE_CLOCKS / 4,
453
		      "sprite bus time is a small share of the scanline, however close "
454
		      "to its render-time ceiling the line runs");
455
456
		// The specific false alarm the conversion prevents: 700 render clocks
457
		// against 160 free bus clocks looks oversubscribed and is not.
458
		const uint16_t seventy_words = 700 / 10;
459
		check(vera_bandwidth_sprite_bus_clocks((uint16_t)(seventy_words * 8), 0) <= 160,
460
		      "700 render clocks of 4bpp sprites fit inside 160 free bus "
461
		      "clocks, so comparing render time against bus time would warn "
462
		      "about a line that is comfortably fine");
463
	}
464
465
	// ── Off means off ───────────────────────────────────────────────────────
466
	{
467
		vera_bandwidth_set_enabled(false);
468
		check(!vera_bandwidth_is_enabled(), "accounting is off");
469
470
		const vera_bw_layer_t l0 = bitmap_layer(BPP8, 1);
471
		vera_bandwidth_line(0, &l0, &l0);
472
		vera_bandwidth_note_port(true, 1, 0);
473
		vera_bandwidth_frame_end(9);
474
475
		vera_bw_frame_t f;
476
		check(!vera_bandwidth_last_frame(&f),
477
		      "and nothing is recorded while it is");
478
	}
479
480
	// ── Ownership, so two consumers cannot switch each other off ────────────
481
	{
482
		vera_bandwidth_set_enabled(false);
483
		vera_bandwidth_arm(VERA_BW_OWNER_UI, true);
484
		check(vera_bandwidth_is_enabled(), "the panel can switch accounting on");
485
486
		vera_bandwidth_arm(VERA_BW_OWNER_DAP, true);
487
		check(vera_bandwidth_owner_wants(VERA_BW_OWNER_DAP), "so can a DAP client");
488
489
		vera_bandwidth_arm(VERA_BW_OWNER_UI, false);
490
		check(vera_bandwidth_is_enabled(),
491
		      "closing the panel does not strand the client");
492
		check(!vera_bandwidth_owner_wants(VERA_BW_OWNER_UI),
493
		      "though the panel no longer wants it");
494
495
		vera_bandwidth_arm(VERA_BW_OWNER_DAP, false);
496
		check(!vera_bandwidth_is_enabled(),
497
		      "and it stops once nobody is asking");
498
	}
499
500
	// ── Configuration ───────────────────────────────────────────────────────
501
	{
502
		vera_bandwidth_configure(525);
503
		check_eq(vera_bandwidth_scanlines_per_frame(), 525, "VGA scans 525 lines");
504
		vera_bandwidth_configure(0);
505
		check_eq(vera_bandwidth_scanlines_per_frame(), 525,
506
		         "and a nonsense value is ignored rather than believed");
507
	}
508
509
	// ── Reset ───────────────────────────────────────────────────────────────
510
	{
511
		begin();
512
		const vera_bw_layer_t l0 = bitmap_layer(BPP8, 1);
513
		vera_bandwidth_line(0, &l0, &l0);
514
		vera_bandwidth_frame_end(10);
515
516
		vera_bw_frame_t f;
517
		check(vera_bandwidth_last_frame(&f), "a frame is there to forget");
518
		vera_bandwidth_reset();
519
		check(!vera_bandwidth_last_frame(&f), "reset forgets it");
520
		check_eq(vera_bandwidth_scanlines_per_frame(), 525,
521
		         "while leaving the machine configuration alone");
522
523
		vera_bandwidth_set_enabled(false);
524
	}
525
526
	return x16_test_summary("vera_bandwidth");
527
}