OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / tests / test_vera_layer_rows.c
7.7 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// The row of a layer that each display line shows.
2
//
3
// tests/test_vera_layer.c pins the layer registers and the base addresses they
4
// name. This pins the other half of the vertical path: given a display line
5
// and VSCROLL, which row of the tilemap is fetched and which line inside the
6
// tile is drawn.
7
//
8
// The emulator carries the answer as one number, the layer row, recorded per
9
// scanline for the debug views (video.c:1326-1328). The RTL carries it as two:
10
// a wrapped tile row and the line within the tile. This file derives the RTL's
11
// two from the emulator's one and compares them, over every display line
12
// rather than a chosen few, so an error that only shows up at a wrap boundary
13
// has nowhere to hide.
14
//
15
// Horizontal is still not covered. The RTL adds a pre-shifted HSCROLL to a
16
// per-line tile counter (layer_renderer.v:95) while video.c shifts after
17
// adding (video.c:574, 587); those differ on carry when HSCROLL is not tile
18
// aligned, and settling it needs rendered pixels, which means the framebuffer
19
// seam. Nothing here claims to reach it.
20
//
21
// ORACLE: B -- X16Community/vera-module Verilog, tag v47.0.2
22
// (45cc1f053376dae12173ea63612820e4d289c0da), modelled directly below from the
23
// quoted lines. layer_renderer.v is identical at v48.0.1 and at the tip.
24
25
#include "support/video_fixture.h"
26
#include "support/harness.h"
27
28
#include "video.h"
29
30
#include <stdio.h>
31
#include <stdint.h>
32
33
#define VERA_CTRL    0x05
34
#define VERA_DC_VIDEO 0x09
35
36
#define L0_CONFIG    0x0D
37
#define L0_VSCROLL_L 0x12
38
#define L0_VSCROLL_H 0x13
39
#define L0_TILEBASE  0x0F
40
41
#define DISPLAY_LINES 480
42
43
static void
44
step_frame(void)
45
{
46
	for (unsigned i = 0; i < 525; i++) {
47
		video_step(25.0f, 801.0f, false);
48
	}
49
}
50
51
// layer_renderer.v:81-82
52
//     wire [11:0] scrolled_line_idx = {3'b000, line_idx} + vscroll;
53
//     wire [7:0] vmap_idx = tile_height ? {scrolled_line_idx[11:4]} : scrolled_line_idx[10:3];
54
//
55
// layer_renderer.v:84-90
56
//     reg [7:0] wrapped_vmap_idx;
57
//     always @* case (map_height)
58
//         2'd0: wrapped_vmap_idx = {3'b0, vmap_idx[4:0]}; // 32
59
//         2'd1: wrapped_vmap_idx = {2'b0, vmap_idx[5:0]}; // 64
60
//         2'd2: wrapped_vmap_idx = {1'b0, vmap_idx[6:0]}; // 128
61
//         2'd3: wrapped_vmap_idx = {      vmap_idx[7:0]}; // 256
62
//     endcase
63
//
64
// The sum is twelve bits, so it wraps at 4096 pixel rows before anything else
65
// happens to it.
66
static unsigned
67
rtl_wrapped_vmap_idx(unsigned line_idx, unsigned vscroll, unsigned tile_height, unsigned map_height)
68
{
69
	const unsigned scrolled = (line_idx + vscroll) & 0xFFF;
70
	const unsigned vmap     = (tile_height ? (scrolled >> 4) : (scrolled >> 3)) & 0xFF;
71
72
	return vmap & ((1u << (5 + map_height)) - 1);
73
}
74
75
// The line within the tile is the low bits of the same twelve-bit sum: three
76
// for an 8-pixel tile, four for a 16-pixel one.
77
static unsigned
78
rtl_line_in_tile(unsigned line_idx, unsigned vscroll, unsigned tile_height)
79
{
80
	const unsigned scrolled = (line_idx + vscroll) & 0xFFF;
81
82
	return scrolled & (tile_height ? 0xFu : 0x7u);
83
}
84
85
// map_height and tile_height as the register fields, not as pixel counts.
86
static void
87
configure_layer0(unsigned map_height, unsigned tile_height, unsigned vscroll)
88
{
89
	video_write(VERA_CTRL, 0x00);
90
	video_write(VERA_DC_VIDEO, 0x31);  // VGA output, both layers on
91
92
	// 4bpp tile mode, so the layer is neither text nor bitmap and takes its
93
	// scroll from the registers below.
94
	video_write(L0_CONFIG, (uint8_t)((map_height << 6) | (2 << 4) | 0x02));
95
	video_write(L0_TILEBASE, (uint8_t)(0xFC | (tile_height << 1)));
96
	video_write(L0_VSCROLL_L, (uint8_t)(vscroll & 0xFF));
97
	video_write(L0_VSCROLL_H, (uint8_t)((vscroll >> 8) & 0x0F));
98
}
99
100
// The renderer takes the layer row from one register generation and the layout
101
// from another, so a change needs a couple of lines to settle. Two frames puts
102
// every line of the second one past that.
103
static void
104
settle(void)
105
{
106
	step_frame();
107
	step_frame();
108
}
109
110
static void
111
check_every_line_against_the_rtl(unsigned map_height, unsigned tile_height, unsigned vscroll)
112
{
113
	char what[128];
114
	char first[192];
115
116
	configure_layer0(map_height, tile_height, vscroll);
117
	settle();
118
119
	first[0] = '\0';
120
	unsigned mismatches = 0;
121
	unsigned examined   = 0;
122
123
	for (uint16_t line = 0; line < DISPLAY_LINES; line++) {
124
		uint16_t row    = 0;
125
		uint16_t eff_y  = 0;
126
127
		if (!video_get_layer_line_state(0, line, NULL, &eff_y, NULL, &row)) {
128
			continue;
129
		}
130
		examined++;
131
132
		const unsigned want_tile_row = rtl_wrapped_vmap_idx(eff_y, vscroll, tile_height, map_height);
133
		const unsigned want_in_tile  = rtl_line_in_tile(eff_y, vscroll, tile_height);
134
135
		// Deliberately not masked to the map height. video.c:587 shifts the row
136
		// down and puts it straight into the map index, so whatever wrapping
137
		// the map height calls for has to be in the row already. Masking here
138
		// would supply it and hide its absence.
139
		const unsigned got_tile_row = row >> (3 + tile_height);
140
		const unsigned got_in_tile  = row & ((1u << (3 + tile_height)) - 1);
141
142
		if (got_tile_row != want_tile_row || got_in_tile != want_in_tile) {
143
			mismatches++;
144
			if (first[0] == '\0') {
145
				snprintf(first, sizeof first,
146
				         "line %u (eff_y %u): tile row %u want %u, line in tile %u want %u",
147
				         line, eff_y, got_tile_row, want_tile_row, got_in_tile, want_in_tile);
148
			}
149
		}
150
	}
151
152
	if (first[0] != '\0') {
153
		printf("      %s\n", first);
154
	}
155
156
	// Without this the sweep would pass by examining nothing: an accessor that
157
	// reported every line as unrendered leaves no mismatches to count.
158
	snprintf(what, sizeof what,
159
	         "all %u display lines are recorded (map_height %u, tile_height %u, vscroll %u)",
160
	         DISPLAY_LINES, map_height, tile_height, vscroll);
161
	check_eq(examined, DISPLAY_LINES, what);
162
163
	snprintf(what, sizeof what,
164
	         "every row matches the RTL (map_height %u, tile_height %u, vscroll %u)",
165
	         map_height, tile_height, vscroll);
166
	check_eq(mismatches, 0, what);
167
}
168
169
// The composer's effective layer Y, which the sweep above feeds to the RTL
170
// model as line_idx. At unit vertical scale over a full-height window it is the
171
// display line itself; asserting it makes the sweep's input a known quantity
172
// rather than whatever the composer happened to produce.
173
static void
174
test_the_layer_y_is_the_display_line_at_unit_scale(void)
175
{
176
	video_reset();
177
	configure_layer0(0, 0, 0);
178
	settle();
179
180
	unsigned wrong = 0;
181
	for (uint16_t line = 0; line < DISPLAY_LINES; line++) {
182
		uint16_t eff_y = 0xFFFF;
183
		if (!video_get_layer_line_state(0, line, NULL, &eff_y, NULL, NULL)) {
184
			wrong++;
185
			continue;
186
		}
187
		if (eff_y != line) {
188
			wrong++;
189
		}
190
	}
191
	check_eq(wrong, 0, "the layer Y follows the display line one for one");
192
}
193
194
// Every map height against every tile height, with no scroll: the wrap point
195
// is then the map height alone.
196
static void
197
test_the_row_wraps_at_the_map_height(void)
198
{
199
	for (unsigned map_height = 0; map_height < 4; map_height++) {
200
		for (unsigned tile_height = 0; tile_height < 2; tile_height++) {
201
			video_reset();
202
			check_every_line_against_the_rtl(map_height, tile_height, 0);
203
		}
204
	}
205
}
206
207
// VSCROLL moves the start of the sweep. 4095 is the largest the twelve-bit
208
// register holds and puts the sum over the wrap for most of the frame; 300 and
209
// 7 land it mid-tile, where an off-by-one between the tile row and the line
210
// inside the tile shows up.
211
static void
212
test_vscroll_shifts_the_row(void)
213
{
214
	static const unsigned scrolls[] = { 1, 7, 300, 4095 };
215
216
	for (unsigned i = 0; i < sizeof scrolls / sizeof scrolls[0]; i++) {
217
		video_reset();
218
		check_every_line_against_the_rtl(0, 0, scrolls[i]);
219
220
		video_reset();
221
		check_every_line_against_the_rtl(3, 1, scrolls[i]);
222
	}
223
}
224
225
int
226
main(void)
227
{
228
	test_the_layer_y_is_the_display_line_at_unit_scale();
229
	test_the_row_wraps_at_the_map_height();
230
	test_vscroll_shifts_the_row();
231
	return x16_test_summary("vera_layer_rows");
232
}