OXYGENHUB
# 9120e87480a6 enhanced-x16-emulator / tests / test_vera_layer.c
9.1 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// VERA's layer registers, and the addresses derived from them.
2
//
3
// $9F2D-$9F33 (layer 0) and $9F34-$9F3A (layer 1) carry the map and tile base
4
// addresses, the map and tile geometry, and the scroll offsets. Every one of
5
// them is a shift or a bit field, which is the kind of arithmetic that goes
6
// wrong quietly: a wrong shift puts the tilemap somewhere plausible.
7
//
8
// Two things are checked. The registers themselves -- what a write stores and
9
// what a read reports -- and the two base addresses the emulator derives from
10
// them, observed through video_is_tilemap_address() and
11
// video_is_tiledata_address(). Those two are what the debugger uses to label
12
// VRAM, so a wrong base mislabels a memory view as well as rendering wrongly.
13
//
14
// Not covered: the horizontal tile-column arithmetic and the per-row strides
15
// within a tile. Both are only visible in rendered pixels, and video.c's
16
// framebuffer has no accessor -- that seam belongs to the backend work, not
17
// here. Nothing below claims to reach them.
18
//
19
// ORACLE: B -- X16Community/vera-module Verilog, tag v47.0.2
20
// (45cc1f053376dae12173ea63612820e4d289c0da), quoted inline.
21
// layer_renderer.v is identical at v48.0.1 and at the tip: its last change was
22
// a37697832a (2024-01-17), which v48.0.1 already contains.
23
24
#include "support/video_fixture.h"
25
#include "support/harness.h"
26
27
#include "video.h"
28
29
#include <stdint.h>
30
31
#define L0_CONFIG   0x0D
32
#define L0_MAPBASE  0x0E
33
#define L0_TILEBASE 0x0F
34
#define L0_HSCROLL_L 0x10
35
#define L0_HSCROLL_H 0x11
36
#define L0_VSCROLL_L 0x12
37
#define L0_VSCROLL_H 0x13
38
39
// $9F34-$9F3A repeat the same seven registers for layer 1.
40
#define LAYER_STRIDE 7
41
42
static uint8_t
43
reg_of(unsigned layer, uint8_t l0_reg)
44
{
45
	return (uint8_t)(l0_reg + layer * LAYER_STRIDE);
46
}
47
48
static void
49
write_layer(unsigned layer, uint8_t l0_reg, uint8_t value)
50
{
51
	video_write(reg_of(layer, l0_reg), value);
52
}
53
54
static uint8_t
55
read_layer(unsigned layer, uint8_t l0_reg)
56
{
57
	return video_read(reg_of(layer, l0_reg), false);
58
}
59
60
// top.v:211-217 (layer 0; :219-225 repeat it for layer 1)
61
//     5'h0D: rddata = {l0_map_height_r, l0_map_width_r, l0_attr_mode_r, l0_bitmap_mode_r, l0_color_depth_r};
62
//     5'h0E: rddata = l0_map_baseaddr_r;
63
//     5'h0F: rddata = {l0_tile_baseaddr_r[7:2], l0_tile_height_r, l0_tile_width_r};
64
//     5'h10: rddata = l0_hscroll_r[7:0];
65
//     5'h12: rddata = l0_vscroll_r[7:0];
66
//
67
// Every bit of these five has a field behind it, so each one round-trips whole.
68
static void
69
test_the_full_width_layer_registers_round_trip(void)
70
{
71
	static const uint8_t regs[] = {
72
		L0_CONFIG, L0_MAPBASE, L0_TILEBASE, L0_HSCROLL_L, L0_VSCROLL_L
73
	};
74
75
	video_reset();
76
77
	for (unsigned layer = 0; layer < 2; layer++) {
78
		for (unsigned i = 0; i < sizeof regs / sizeof regs[0]; i++) {
79
			// A pattern with both nibbles populated and no byte repeated, so a
80
			// register wired to its neighbour shows up.
81
			const uint8_t v = (uint8_t)(0x9C + layer * 0x10 + i);
82
83
			write_layer(layer, regs[i], v);
84
			check_eq(read_layer(layer, regs[i]), v, "a layer register reports what was written");
85
		}
86
	}
87
}
88
89
// top.v:215,217
90
//     5'h11: rddata = {4'b0, l0_hscroll_r[11:8]};
91
//     5'h13: rddata = {4'b0, l0_vscroll_r[11:8]};
92
//
93
// top.v:392-400
94
//     if (do_write && access_addr == 5'h11) begin
95
//         l0_hscroll_next[11:8] = write_data[3:0];
96
//     end
97
//     ...
98
//     if (do_write && access_addr == 5'h13) begin
99
//         l0_vscroll_next[11:8] = write_data[3:0];
100
//     end
101
//
102
// The scroll registers are twelve bits, so the high byte of each holds four.
103
// The other four have nowhere to be stored and read back as zero.
104
static void
105
test_the_scroll_high_bytes_are_four_bits_wide(void)
106
{
107
	static const uint8_t highs[] = { L0_HSCROLL_H, L0_VSCROLL_H };
108
109
	video_reset();
110
111
	for (unsigned layer = 0; layer < 2; layer++) {
112
		for (unsigned i = 0; i < sizeof highs / sizeof highs[0]; i++) {
113
			write_layer(layer, highs[i], 0x0A);
114
			check_eq(read_layer(layer, highs[i]), 0x0A, "the low four bits are stored");
115
116
			write_layer(layer, highs[i], 0xFF);
117
			check_divergent(read_layer(layer, highs[i]) == 0x0F,
118
			                "the top four bits of a scroll high byte read as zero",
119
			                "video.c:2733 and video.c:2741 return the stored byte whole; "
120
			                "top.v:215 and top.v:217 read {4'b0, scroll[11:8]} and the write "
121
			                "at top.v:392-400 takes only write_data[3:0]");
122
		}
123
	}
124
}
125
126
// Each layer has its own copy of all seven. Written separately so a shared
127
// index or a wrong stride between the two banks shows up as a bleed.
128
static void
129
test_the_two_layers_are_independent(void)
130
{
131
	video_reset();
132
133
	write_layer(0, L0_CONFIG, 0x39);
134
	write_layer(1, L0_CONFIG, 0xC6);
135
	check_eq(read_layer(0, L0_CONFIG), 0x39, "layer 0 keeps its own config");
136
	check_eq(read_layer(1, L0_CONFIG), 0xC6, "layer 1 keeps its own");
137
138
	write_layer(0, L0_MAPBASE, 0x11);
139
	write_layer(1, L0_MAPBASE, 0x22);
140
	check_eq(read_layer(0, L0_MAPBASE), 0x11, "and its own map base");
141
	check_eq(read_layer(1, L0_MAPBASE), 0x22, "separately from the other");
142
}
143
144
// Put both layers on the same geometry and base, so the two ranges coincide
145
// and a boundary is unambiguous. video_is_tilemap_address() answers for either
146
// layer, so overlapping them deliberately is what makes the edges testable.
147
static void
148
set_both_layers(uint8_t config, uint8_t mapbase, uint8_t tilebase)
149
{
150
	for (unsigned layer = 0; layer < 2; layer++) {
151
		write_layer(layer, L0_CONFIG, config);
152
		write_layer(layer, L0_MAPBASE, mapbase);
153
		write_layer(layer, L0_TILEBASE, tilebase);
154
	}
155
}
156
157
// layer_renderer.v:107
158
//     wire [14:0] map_addr = {map_baseaddr, 7'b0} + map_idx[15:1];
159
//
160
// The register lands in the top eight bits of a 15-bit word address over
161
// 32-bit words, so the byte address it names is the register times 512.
162
static void
163
test_mapbase_places_the_tilemap_at_512_byte_units(void)
164
{
165
	video_reset();
166
	set_both_layers(0x00, 0x20, 0xFC);        // 32x32 map at 0x20 * 512
167
168
	check(!video_is_tilemap_address(0x3FFF), "below the base is not tilemap");
169
	check(video_is_tilemap_address(0x4000), "the base itself is");
170
	check(video_is_tilemap_address(0x47FF), "and so is the last byte of a 32x32 map");
171
	check(!video_is_tilemap_address(0x4800), "one past the end is not");
172
173
	// A different register value must move it by the same unit, or the shift is
174
	// right only at zero.
175
	set_both_layers(0x00, 0x21, 0xFC);
176
	check(video_is_tilemap_address(0x4200), "the next register value is 512 bytes further on");
177
	check(!video_is_tilemap_address(0x41FF), "and the byte below it is outside");
178
}
179
180
// layer_renderer.v:100-103
181
//     2'd0: map_idx = {3'b0, wrapped_vmap_idx, scrolled_htile_cnt[4:0]}; // 32
182
//     2'd1: map_idx = {2'b0, wrapped_vmap_idx, scrolled_htile_cnt[5:0]}; // 64
183
//     2'd2: map_idx = {1'b0, wrapped_vmap_idx, scrolled_htile_cnt[6:0]}; // 128
184
//     2'd3: map_idx = {      wrapped_vmap_idx, scrolled_htile_cnt[7:0]}; // 256
185
//
186
// layer_renderer.v:86-89 masks the row index to the map height the same way.
187
// Both dimensions are 32 << the field, and an entry is two bytes, so the map
188
// occupies 2 << (mapw_log2 + maph_log2) bytes.
189
static void
190
test_the_map_extent_follows_width_and_height(void)
191
{
192
	video_reset();
193
194
	for (unsigned mh = 0; mh < 4; mh++) {
195
		for (unsigned mw = 0; mw < 4; mw++) {
196
			const uint8_t config = (uint8_t)((mh << 6) | (mw << 4));
197
			const uint32_t bytes = 2u << ((5 + mw) + (5 + mh));
198
199
			set_both_layers(config, 0x00, 0xFC);
200
201
			check(video_is_tilemap_address((int)(bytes - 1)),
202
			      "the last byte of the map is inside it");
203
204
			// 256x256 fills the whole 128K, so there is no byte past the end to
205
			// look at and the check would be measuring the address space.
206
			if (bytes < 0x20000u) {
207
				check(!video_is_tilemap_address((int)bytes),
208
				      "and the byte after it is outside");
209
			}
210
		}
211
	}
212
}
213
214
// top.v:382-388
215
//     if (do_write && access_addr == 5'h0F) begin
216
//         l0_tile_baseaddr_next[7:2] = write_data[7:2];
217
//         l0_tile_baseaddr_next[1:0] = 0;
218
//
219
//         l0_tile_height_next = write_data[1];
220
//         l0_tile_width_next  = write_data[0];
221
//     end
222
//
223
// layer_renderer.v:188
224
//     wire [14:0] tile_addr = {tile_baseaddr, 7'b0} + tile_addr_xbpp;
225
//
226
// Bits 1 and 0 are the tile size, not address bits, and the base they leave
227
// behind is forced even to 2K.
228
static void
229
test_tilebase_ignores_the_two_size_bits(void)
230
{
231
	video_reset();
232
	set_both_layers(0x00, 0xF0, 0x00);
233
	check(video_is_tiledata_address(0x0000), "tile data starts at zero with base zero");
234
235
	// Both size bits set. The base must not move with them.
236
	set_both_layers(0x00, 0xF0, 0x03);
237
	check(video_is_tiledata_address(0x0000), "the tile size bits do not move the base");
238
239
	// The first value that does move it, and by 2K rather than 512 bytes,
240
	// because the two bits below it are not part of the field.
241
	set_both_layers(0x00, 0xF0, 0x04);
242
	check(!video_is_tiledata_address(0x07FF), "the first base step clears the 2K below it");
243
	check(video_is_tiledata_address(0x0800), "and lands 2K up");
244
}
245
246
int
247
main(void)
248
{
249
	test_the_full_width_layer_registers_round_trip();
250
	test_the_scroll_high_bytes_are_four_bits_wide();
251
	test_the_two_layers_are_independent();
252
	test_mapbase_places_the_tilemap_at_512_byte_units();
253
	test_the_map_extent_follows_width_and_height();
254
	test_tilebase_ignores_the_two_size_bits();
255
	return x16_test_summary("vera_layer");
256
}