OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_vera_reset.c
6.5 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// What a VERA reset leaves behind.
2
//
3
// video.c keeps three copies of the sprite attributes: the VRAM mirror the
4
// guest reads, a sprite_data[] shadow the renderer's raw source, and a
5
// sprite_properties[] array of values derived from that shadow. The hardware
6
// keeps one. Every extra copy is a chance for reset to clear some of them and
7
// leave the machine describing a state that no longer exists -- which has
8
// already happened once for layers, and is why video_reset_layer_pipeline()
9
// exists (video.c:255-260).
10
//
11
// This file checks that reset leaves nothing behind that still describes the
12
// pre-reset machine. Sprites are the subject because they are the only derived
13
// state with an observation channel that needs no framebuffer: a collision
14
// reaches ISR bits 7:4.
15
//
16
// ORACLE: B -- X16Community/vera-module Verilog, tag v47.0.2
17
// (45cc1f053376dae12173ea63612820e4d289c0da), for the claim that the hardware
18
// has nothing to go stale. sprite_renderer.v:73-85 decodes every attribute
19
// from sprite_attr, read from the attribute RAM as the sprite is rendered, and
20
// sprite_renderer.v:408-417 resets the renderer's own registers. There is no
21
// derived copy to refresh.
22
//
23
// One assertion needs no oracle at all and is marked as such below.
24
25
#include "support/video_fixture.h"
26
#include "support/harness.h"
27
28
#include "video.h"
29
30
#include <stdint.h>
31
32
#define VERA_CTRL 0x05
33
#define VERA_ISR  0x07
34
35
#define SPRITE_ATTR 0x1FC00
36
#define SPRITE_GFX  0x01000
37
38
#define ATTR_ZDEPTH 6
39
40
static void
41
step_frame(void)
42
{
43
	for (unsigned i = 0; i < 525; i++) {
44
		video_step(25.0f, 801.0f, false);
45
	}
46
}
47
48
// video_reset() fills VRAM with rand(), so the sprite bitmap has to be laid
49
// down after every reset or a collision would depend on the random contents
50
// rather than on what this file is measuring.
51
static void
52
opaque_graphics(void)
53
{
54
	for (unsigned i = 0; i < 16 * 8; i++) {
55
		video_space_write(SPRITE_GFX + i, 0x11);
56
	}
57
}
58
59
static void
60
place_sprite(unsigned n, uint8_t mask, uint8_t zdepth)
61
{
62
	const uint32_t attr = SPRITE_ATTR + n * 8;
63
64
	video_space_write(attr + 0, SPRITE_GFX >> 5);
65
	video_space_write(attr + 1, 0x00);
66
	video_space_write(attr + 2, 0x00);
67
	video_space_write(attr + 3, 0x00);
68
	video_space_write(attr + 4, 0x00);
69
	video_space_write(attr + 5, 0x00);
70
	video_space_write(attr + ATTR_ZDEPTH, (uint8_t)(mask | (zdepth << 2)));
71
	video_space_write(attr + 7, 0x50);
72
}
73
74
static void
75
enable_sprites(void)
76
{
77
	video_write(VERA_CTRL, 0x00);
78
	video_write(0x09, 0x41); // DCSEL 0, $9F29: sprites on, VGA output
79
}
80
81
// Two sprites on the same pixels with masks that share bits 6 and 4, so a
82
// collision reports 0x50 in ISR bits 7:4.
83
static void
84
arm_colliding_sprites(void)
85
{
86
	opaque_graphics();
87
	place_sprite(0, 0x70, 1);
88
	place_sprite(1, 0xD0, 1);
89
	enable_sprites();
90
}
91
92
static uint8_t
93
collision_after_a_frame(void)
94
{
95
	step_frame();
96
	return video_read(VERA_ISR, true) & 0xF0;
97
}
98
99
// The control. Everything below rests on a collision being visible when the
100
// sprites are there and absent when they are not, so prove the channel before
101
// using it to measure a reset.
102
static void
103
test_the_collision_channel_follows_the_attributes(void)
104
{
105
	video_reset();
106
	arm_colliding_sprites();
107
	check_eq(collision_after_a_frame(), 0x50, "two armed sprites collide");
108
109
	// A guest taking a sprite out of the picture is an ordinary attribute
110
	// write, and the renderer follows it.
111
	place_sprite(1, 0xD0, 0);
112
	check_eq(collision_after_a_frame(), 0x00, "and stop when one is given no z-depth");
113
}
114
115
// The renderer holds no attribute state of its own -- sprite_renderer.v:73-85
116
// decodes each one out of the attribute RAM as it goes -- so clearing the
117
// attributes is enough to stop a sprite, and a reset that clears them must
118
// stop it too.
119
static void
120
test_reset_stops_sprites_configured_before_it(void)
121
{
122
	video_reset();
123
	arm_colliding_sprites();
124
	check_eq(collision_after_a_frame(), 0x50, "sprites collide before the reset");
125
126
	video_reset();
127
	opaque_graphics();
128
	enable_sprites();
129
130
	check_divergent(collision_after_a_frame() == 0x00,
131
	                "a reset stops sprites that were running before it",
132
	                "video.c:339 clears sprite_data but never re-derives "
133
	                "sprite_properties, whose only writers are the two "
134
	                "video_space_write() paths at video.c:2396 and video.c:2425, so "
135
	                "the renderer keeps drawing the pre-reset sprites");
136
}
137
138
// ORACLE: none, and none is needed. Storing a byte that is already there is a
139
// no-op by definition, whatever the hardware does with it. This is the same
140
// statement as above with the reset taken out of it, so it holds even if the
141
// reset value is ever argued about.
142
static void
143
test_writing_a_byte_that_is_already_there_changes_nothing(void)
144
{
145
	video_reset();
146
	arm_colliding_sprites();
147
	step_frame();
148
149
	video_reset();
150
	opaque_graphics();
151
	enable_sprites();
152
153
	const uint8_t before = collision_after_a_frame();
154
155
	// sprite_data[] reads back as zero for both sprites after the reset, so
156
	// this stores the value already held.
157
	video_space_write(SPRITE_ATTR + 0 * 8 + ATTR_ZDEPTH, 0x00);
158
	video_space_write(SPRITE_ATTR + 1 * 8 + ATTR_ZDEPTH, 0x00);
159
160
	check_divergent(collision_after_a_frame() == before,
161
	                "rewriting an attribute byte with its own value does not move the machine",
162
	                "the write re-derives sprite_properties, which the reset did not, so "
163
	                "storing a zero over a zero is what actually stops the sprites");
164
}
165
166
// The frame accumulator is cleared (video.c:355), matching
167
// sprite_renderer.v:416. Recorded so the two halves of the reset are not
168
// confused: this one is right, the derived attributes are not.
169
static void
170
test_reset_clears_the_collision_accumulator(void)
171
{
172
	video_reset();
173
	arm_colliding_sprites();
174
175
	// Part of a frame, so collisions are accumulated but not yet latched.
176
	for (unsigned i = 0; i < 32; i++) {
177
		video_step(25.0f, 801.0f, false);
178
	}
179
180
	video_reset();
181
182
	// Reading ISR here would say nothing -- reset clears ISR itself, so the
183
	// nibble is zero whatever the accumulator holds. The accumulator is only
184
	// visible once a vblank latches it, so run a frame. The reset also turned
185
	// sprites off, so nothing new can accumulate during it and what latches is
186
	// exactly what the reset left behind.
187
	check_eq(collision_after_a_frame(), 0x00, "reset clears the pending collisions");
188
}
189
190
int
191
main(void)
192
{
193
	test_the_collision_channel_follows_the_attributes();
194
	test_reset_stops_sprites_configured_before_it();
195
	test_writing_a_byte_that_is_already_there_changes_nothing();
196
	test_reset_clears_the_collision_accumulator();
197
	return x16_test_summary("vera_reset");
198
}