OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_vera_debug_read.c
7.4 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// The value half of the debugOn contract, swept across VERA's register page.
2
//
3
// real_read6502() carries a debugOn flag to every device behind $9F00-$9FFF.
4
// tests/test_debugon_contract.c pins one half of what that flag means: a debug
5
// read must move no device state and spend no cycles. This file pins the other
6
// half, which nothing has been checking:
7
//
8
//     a debug read must return the same value a real read would.
9
//
10
// Both halves are needed and neither implies the other. A debug read that is
11
// perfectly free of side effects but reports a different byte is still a
12
// debugger lying about the machine, and it lies most convincingly in a memory
13
// view, where the number looks authoritative and there is nothing to compare it
14
// against.
15
//
16
// The existing contract test cannot see this. It drives recording fakes, which
17
// return synthetic values by construction, so value equality there would be a
18
// statement about the fake. This file links the real video.c instead, which is
19
// why it lives separately -- see tests/support/video_fixture.c for the seam.
20
//
21
// ORACLE: none, and none is needed. This is an internal consistency property
22
// between two paths through the same emulator, not a claim about hardware. The
23
// RTL has no notion of a debug read at all -- which is the point: whatever the
24
// hardware does, both paths must report it identically.
25
26
#include "support/video_fixture.h"
27
#include "support/harness.h"
28
29
#include "video.h"
30
31
#include <stdio.h>
32
#include <string.h>
33
34
#define VERA_ADDR_L 0x00
35
#define VERA_ADDR_M 0x01
36
#define VERA_ADDR_H 0x02
37
#define VERA_DATA0  0x03
38
#define VERA_DATA1  0x04
39
#define VERA_CTRL   0x05
40
41
// The registers that currently disagree: $9F29-$9F2C in DCSEL 2, 3, 5 and 6,
42
// wherever the register in that bank is write-only or unmapped. Stated as a
43
// number so the sweep fails if the set changes in either direction.
44
#define KNOWN_MISMATCHES 13
45
46
// Registers whose value legitimately changes between two consecutive reads, so
47
// that comparing a debug read against a following real read would be measuring
48
// the change rather than the contract.
49
// $9F23 and $9F24 are the data ports: a real read advances the address, so the
50
// second read of a pair sees the next byte by design. That is covered in
51
// tests/test_vera_video.c, and separately below with the address pinned.
52
static bool
53
reads_are_self_advancing(uint8_t reg)
54
{
55
	return reg == VERA_DATA0 || reg == VERA_DATA1;
56
}
57
58
// Put something distinguishable everywhere a register might source a value
59
// from, so a mismatch is visible rather than 0 == 0.
60
static void
61
prime(void)
62
{
63
	video_reset();
64
65
	// A run of VRAM under the data ports.
66
	video_write(VERA_CTRL, 0);
67
	video_write(VERA_ADDR_L, 0x00);
68
	video_write(VERA_ADDR_M, 0x10);
69
	video_write(VERA_ADDR_H, 0x10);       // increment 1
70
	for (unsigned i = 0; i < 64; i++) {
71
		video_write(VERA_DATA0, (uint8_t)(0x80 + i));
72
	}
73
74
	// Composer, layer and sprite registers across the banks that have them.
75
	for (unsigned dcsel = 0; dcsel < 8; dcsel++) {
76
		video_write(VERA_CTRL, (uint8_t)(dcsel << 1));
77
		for (uint8_t reg = 0x09; reg <= 0x0C; reg++) {
78
			// $9F2B and $9F2C in bank 5 are the FX fill length, which is
79
			// read-only; writing them only produces a warning.
80
			if (dcsel == 5 && reg >= 0x0B) {
81
				continue;
82
			}
83
			video_write(reg, (uint8_t)(0x30 + dcsel * 4 + (reg - 0x09)));
84
		}
85
	}
86
	video_write(VERA_CTRL, 0);
87
	for (uint8_t reg = 0x0D; reg <= 0x1A; reg++) {
88
		video_write(reg, (uint8_t)(0x40 + reg));
89
	}
90
}
91
92
// The sweep. For each register in each bank: read it as the debugger, then as
93
// the machine, and require the same answer.
94
//
95
// The debug read goes first deliberately. It is the one claiming to have no
96
// side effects, so if that half of the contract holds, both reads observe
97
// identical state and any difference is in the reporting.
98
static void
99
test_debug_and_real_reads_agree(void)
100
{
101
	static const unsigned banks[] = {0, 1, 2, 3, 5, 6, 63};
102
103
	unsigned mismatches = 0;
104
	char first[160];
105
	first[0] = '\0';
106
107
	for (unsigned b = 0; b < sizeof banks / sizeof banks[0]; b++) {
108
		for (uint8_t reg = 0x00; reg < 0x20; reg++) {
109
			if (reads_are_self_advancing(reg)) {
110
				continue;
111
			}
112
113
			prime();
114
			video_write(VERA_CTRL, (uint8_t)(banks[b] << 1));
115
116
			uint8_t as_debugger = video_read(reg, true);
117
			uint8_t as_machine  = video_read(reg, false);
118
119
			if (as_debugger != as_machine) {
120
				mismatches++;
121
				if (first[0] == '\0') {
122
					snprintf(first, sizeof first,
123
					         "$9F%02X with DCSEL %u: debugger sees 0x%02X, machine sees 0x%02X",
124
					         reg + 0x20, banks[b], as_debugger, as_machine);
125
				}
126
				printf("      mismatch: $9F%02X DCSEL %u  debug=0x%02X real=0x%02X\n",
127
				       reg + 0x20, banks[b], as_debugger, as_machine);
128
			}
129
		}
130
	}
131
132
	if (mismatches != 0) {
133
		printf("      first: %s\n", first);
134
	}
135
136
	// Recorded exactly, not as "some". A bare divergence marker would let a
137
	// fourteenth mismatch appear without anything noticing, which is how a
138
	// divergence register turns into a dumping ground. This fails on growth
139
	// and on unrecorded improvement alike, so either direction has to be a
140
	// deliberate edit -- the same rule the CPU baselines follow.
141
	check_eq(mismatches, KNOWN_MISMATCHES,
142
	         "the set of registers that disagree is unchanged");
143
144
	check_divergent(mismatches == 0,
145
	                "every VERA register reads the same to the debugger as to the machine",
146
	                "13 registers differ, all $9F29-$9F2C in banks where the register is "
147
	                "write-only or unmapped: video.c:2690 sends a debug read to the stored "
148
	                "composer byte, while a real read returns the version string");
149
}
150
151
// The data ports are excluded from the sweep because a real read advances the
152
// address. With the address pinned either side, the same rule applies to them:
153
// the byte reported must be the byte the machine would read.
154
static void
155
test_data_ports_report_the_same_byte(void)
156
{
157
	for (uint8_t port = VERA_DATA0; port <= VERA_DATA1; port++) {
158
		prime();
159
160
		// Point the selected register at a known byte with no increment, so
161
		// the real read cannot move it.
162
		uint8_t sel = (uint8_t)(port - VERA_DATA0);
163
		video_write(VERA_CTRL, sel);
164
		video_write(VERA_ADDR_L, 0x08);
165
		video_write(VERA_ADDR_M, 0x10);
166
		video_write(VERA_ADDR_H, 0x00);      // increment code 0: no movement
167
168
		uint8_t as_debugger = video_read(port, true);
169
		uint8_t as_machine  = video_read(port, false);
170
171
		check_eq(as_debugger, as_machine, "the data port reports the same byte to both");
172
		check_eq(as_machine, 0x88u, "and it is the byte that was written there");
173
	}
174
}
175
176
// The other half of the contract, restated here for the registers this file
177
// touches: having read as the debugger, the machine must still see what it
178
// would have seen. Without this, a passing sweep could be achieved by making
179
// the debug read perform the real read's side effects.
180
static void
181
test_a_debug_read_still_moves_nothing(void)
182
{
183
	prime();
184
	video_write(VERA_CTRL, 0);
185
	video_write(VERA_ADDR_L, 0x20);
186
	video_write(VERA_ADDR_M, 0x10);
187
	video_write(VERA_ADDR_H, 0x10);          // increment 1
188
189
	uint32_t before = video_get_address(0);
190
	video_read(VERA_DATA0, true);
191
	check_eq(video_get_address(0), before, "a debug read of the data port does not advance it");
192
193
	video_read(VERA_DATA0, false);
194
	check_eq(video_get_address(0), before + 1, "while a real read does");
195
}
196
197
int
198
main(void)
199
{
200
	test_debug_and_real_reads_agree();
201
	test_data_ports_report_the_same_byte();
202
	test_a_debug_read_still_moves_nothing();
203
	return x16_test_summary("vera_debug_read");
204
}