OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / tests / support / cpu_fixture.c
6.3 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
#include "cpu_fixture.h"
2
3
#include <string.h>
4
5
// exec6502() accumulates into clockgoal6502, and neither reset6502() nor the
6
// header exposes it. Left alone it would carry over between scenarios, so a
7
// run following another run would stop at the wrong point.
8
extern uint32_t clockgoal6502;
9
10
uint8_t  cpu_mem[CPU_MEM_SIZE];
11
uint32_t cpu_stop_count;
12
uint32_t cpu_vector_pulls;
13
14
static uint32_t cycles_before_run;
15
16
// Clearing 16 MB per reset would dominate everything: the ProcessorTests runner
17
// resets once per case and there are tens of thousands of them. Bank 0 is
18
// cleared outright, because it is small and every 65C02 test lives there. Above
19
// bank 0 only the pages actually touched are cleared, tracked here.
20
//
21
// A test that scribbles over more than DIRTY_MAX pages sets dirty_all instead
22
// and pays for a full clear, which is the right trade for the few that load
23
// whole images.
24
#define DIRTY_PAGE_SHIFT 8
25
#define DIRTY_MAX        256
26
27
static uint32_t dirty_pages[DIRTY_MAX];
28
static int      dirty_count;
29
static bool     dirty_all;
30
31
static void
32
mark_dirty(uint32_t addr)
33
{
34
	if (dirty_all || addr < 0x10000) {
35
		return;  // bank 0 is always cleared
36
	}
37
	uint32_t page = addr >> DIRTY_PAGE_SHIFT;
38
	for (int i = 0; i < dirty_count; i++) {
39
		if (dirty_pages[i] == page) {
40
			return;
41
		}
42
	}
43
	if (dirty_count == DIRTY_MAX) {
44
		dirty_all = true;
45
		return;
46
	}
47
	dirty_pages[dirty_count++] = page;
48
}
49
50
static void
51
clear_memory(void)
52
{
53
	if (dirty_all) {
54
		memset(cpu_mem, 0, CPU_MEM_SIZE);
55
	} else {
56
		for (int i = 0; i < dirty_count; i++) {
57
			memset(&cpu_mem[dirty_pages[i] << DIRTY_PAGE_SHIFT], 0,
58
			       1u << DIRTY_PAGE_SHIFT);
59
		}
60
		memset(cpu_mem, 0, 0x10000);
61
	}
62
	dirty_count = 0;
63
	dirty_all   = false;
64
}
65
66
void
67
cpu_seed(uint32_t addr, uint8_t value)
68
{
69
	addr &= CPU_MEM_SIZE - 1;
70
	mark_dirty(addr);
71
	cpu_mem[addr] = value;
72
}
73
74
// ---- What fake6502.c expects the rest of the emulator to provide ------------
75
// Flat 24-bit memory: no I/O, and no banking beyond the 65C816's own, which is
76
// just the top eight address lines. A test that cares about the emulator's
77
// banking scheme belongs with memory.c, not here.
78
79
void (*cpu_bus_read_hook)(uint32_t addr, uint8_t value);
80
void (*cpu_bus_write_hook)(uint32_t addr, uint8_t value);
81
82
// A 65C02 has sixteen address lines and no bank pins, so its addresses wrap
83
// inside 64K. The core computes effective addresses in 24 bits for the 65C816's
84
// benefit and indexing can carry past $FFFF, which on a 65C02 must come back to
85
// $0000 rather than reaching bank 1.
86
static uint32_t
87
cpu_bus_address(uint16_t address, uint8_t bank)
88
{
89
	if (!regs.is65c816) {
90
		return address;
91
	}
92
	return ((uint32_t)bank << 16) | address;
93
}
94
95
uint8_t
96
read6502(uint16_t address, uint8_t bank)
97
{
98
	uint32_t full  = cpu_bus_address(address, bank);
99
	uint8_t  value = cpu_mem[full];
100
	if (cpu_bus_read_hook) {
101
		cpu_bus_read_hook(full, value);
102
	}
103
	return value;
104
}
105
106
void
107
write6502(uint16_t address, uint8_t bank, uint8_t value)
108
{
109
	uint32_t full = cpu_bus_address(address, bank);
110
	mark_dirty(full);
111
	cpu_mem[full] = value;
112
	if (cpu_bus_write_hook) {
113
		cpu_bus_write_hook(full, value);
114
	}
115
}
116
117
// STP halts the CPU until reset. Counted rather than ignored so a test can tell
118
// a halt from a stall.
119
void
120
stop6502(uint16_t address, uint8_t bank)
121
{
122
	(void)address;
123
	(void)bank;
124
	cpu_stop_count++;
125
}
126
127
// Asserted while the CPU fetches an interrupt vector.
128
void
129
vp6502(void)
130
{
131
	cpu_vector_pulls++;
132
}
133
134
uint8_t
135
memory_get_ram_bank(void)
136
{
137
	return 0;
138
}
139
140
uint8_t
141
memory_get_rom_bank(void)
142
{
143
	return 0;
144
}
145
146
// ---- Fixture ---------------------------------------------------------------
147
148
const char *
149
cpu_mode_name(cpu_mode_t mode)
150
{
151
	switch (mode) {
152
		case CPU_65C02:          return "65C02";
153
		case CPU_816_EMU:        return "65C816 emulation";
154
		case CPU_816_NATIVE_8:   return "65C816 native, 8-bit";
155
		case CPU_816_NATIVE_16:  return "65C816 native, 16-bit";
156
	}
157
	return "?";
158
}
159
160
bool
161
cpu_mode_is_16bit(cpu_mode_t mode)
162
{
163
	return mode == CPU_816_NATIVE_16;
164
}
165
166
void
167
cpu_reset_to(cpu_mode_t mode, uint16_t start)
168
{
169
	clear_memory();
170
	cpu_stop_count   = 0;
171
	cpu_vector_pulls = 0;
172
173
	// reset6502() only ever sets bits in the status register -- it ORs in the
174
	// constant and width bits and calls setinterrupt()/cleardecimal() -- so
175
	// N, V, Z and C would otherwise survive from the previous scenario and
176
	// make a test's result depend on what ran before it.
177
	regs.status = 0;
178
179
	// reset6502() reads the vector from $FFFC/$FFFD, so the entry point has to
180
	// be in place before the reset rather than assigned to regs.pc after it.
181
	cpu_mem[0xFFFC] = (uint8_t)(start & 0xFF);
182
	cpu_mem[0xFFFD] = (uint8_t)(start >> 8);
183
184
	reset6502(mode != CPU_65C02);
185
186
	// Emulation mode forces 8-bit registers whatever these bits say, so the
187
	// width flags are only meaningful once E is clear.
188
	switch (mode) {
189
		case CPU_65C02:
190
		case CPU_816_EMU:
191
			break;
192
		case CPU_816_NATIVE_8:
193
			regs.e      = 0;
194
			regs.status |= FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH;
195
			break;
196
		case CPU_816_NATIVE_16:
197
			regs.e      = 0;
198
			regs.status &= (uint8_t) ~(FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH);
199
			break;
200
	}
201
202
	clockticks6502    = 0;
203
	clockgoal6502     = 0;
204
	cycles_before_run = 0;
205
}
206
207
uint16_t
208
cpu_load(uint16_t addr, const uint8_t *bytes, uint16_t len)
209
{
210
	memcpy(&cpu_mem[addr], bytes, len);
211
	return (uint16_t)(addr + len);
212
}
213
214
void
215
cpu_steps(int count)
216
{
217
	cycles_before_run = clockticks6502;
218
	for (int i = 0; i < count; i++) {
219
		step6502();
220
	}
221
}
222
223
uint32_t
224
cpu_step(void)
225
{
226
	cycles_before_run = clockticks6502;
227
	step6502();
228
	return clockticks6502 - cycles_before_run;
229
}
230
231
void
232
cpu_exec(uint32_t ticks)
233
{
234
	cycles_before_run = clockticks6502;
235
	exec6502(ticks);
236
}
237
238
uint32_t
239
cpu_last_cycles(void)
240
{
241
	return clockticks6502 - cycles_before_run;
242
}
243
244
// The register names are not as symmetric as they look: regs.a is the low byte
245
// of the accumulator while regs.x and regs.y are the whole 16-bit registers,
246
// so reading regs.a in a 16-bit scenario would silently discard the high byte
247
// and report a false pass. These read the width the CPU is actually using.
248
249
uint16_t
250
cpu_a(void)
251
{
252
	return memory_16bit_width() ? regs.c : regs.a;
253
}
254
255
uint16_t
256
cpu_x(void)
257
{
258
	return index_16bit_width() ? regs.x : regs.xl;
259
}
260
261
uint16_t
262
cpu_y(void)
263
{
264
	return index_16bit_width() ? regs.y : regs.yl;
265
}
266
267
bool
268
memory_16bit_width(void)
269
{
270
	return !regs.e && !(regs.status & FLAG_MEMORY_WIDTH);
271
}
272
273
bool
274
index_16bit_width(void)
275
{
276
	return !regs.e && !(regs.status & FLAG_INDEX_WIDTH);
277
}