OXYGENHUB
# 2980b7b6d7cd enhanced-x16-emulator / tests / test_cpu_steprun.c
5.2 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Regression: single-stepping must reproduce running.
2
//
3
// The emulator runs through exec6502() and the debugger steps through
4
// step6502(). They are separate loops over the same tables, and everything a
5
// debugger tells you rests on them agreeing: if stepping through code produces
6
// a different machine than letting it run, then every value the debugger shows
7
// is a value the program would not otherwise have had.
8
//
9
// Each scenario runs the same program from the same starting state both ways
10
// and compares the whole visible machine -- registers, memory and the cycle
11
// counter. Cycle counts are included deliberately: they are the part most
12
// likely to drift, and the part a debugger hook can disturb without changing
13
// anything else.
14
15
#include "support/cpu_fixture.h"
16
#include "support/harness.h"
17
18
#include <stdio.h>
19
#include <string.h>
20
21
#define PROG 0x0200
22
#define SCRATCH 0x0400
23
24
// A snapshot of everything a scenario can observe.
25
typedef struct {
26
	uint16_t a, x, y, pc, sp, dp;
27
	uint8_t  status, k, db;
28
	uint32_t cycles;
29
	uint8_t  scratch[16];
30
} machine_t;
31
32
static machine_t
33
snapshot(void)
34
{
35
	machine_t m;
36
	m.a      = cpu_a();
37
	m.x      = cpu_x();
38
	m.y      = cpu_y();
39
	m.pc     = regs.pc;
40
	m.sp     = regs.sp;
41
	m.dp     = regs.dp;
42
	m.status = regs.status;
43
	m.k      = regs.k;
44
	m.db     = regs.db;
45
	m.cycles = clockticks6502;
46
	for (int i = 0; i < 16; i++) {
47
		m.scratch[i] = cpu_mem[SCRATCH + i];
48
	}
49
	return m;
50
}
51
52
static void
53
compare(machine_t stepped, machine_t ran, const char *what)
54
{
55
	char label[128];
56
57
#define FIELD(f, name)                                                        \
58
	snprintf(label, sizeof label, "%s: %s matches", what, name);              \
59
	check_eq(ran.f, stepped.f, label)
60
61
	FIELD(a, "A");
62
	FIELD(x, "X");
63
	FIELD(y, "Y");
64
	FIELD(pc, "PC");
65
	FIELD(sp, "SP");
66
	FIELD(status, "status");
67
	FIELD(cycles, "cycle count");
68
#undef FIELD
69
70
	bool same_memory = true;
71
	for (int i = 0; i < 16; i++) {
72
		if (stepped.scratch[i] != ran.scratch[i]) {
73
			same_memory = false;
74
		}
75
	}
76
	snprintf(label, sizeof label, "%s: memory matches", what);
77
	check(same_memory, label);
78
}
79
80
// Run `steps` instructions one at a time, then the same program for the same
81
// number of cycles in one go, and compare.
82
static void
83
both_ways(cpu_mode_t mode, const uint8_t *prog, uint16_t len, int steps,
84
          const char *what)
85
{
86
	cpu_reset_to(mode, PROG);
87
	cpu_load(PROG, prog, len);
88
	cpu_steps(steps);
89
	machine_t stepped = snapshot();
90
91
	cpu_reset_to(mode, PROG);
92
	cpu_load(PROG, prog, len);
93
	cpu_exec(stepped.cycles);
94
	machine_t ran = snapshot();
95
96
	compare(stepped, ran, what);
97
}
98
99
#define BOTH_WAYS(mode, prog, steps, what) \
100
	both_ways((mode), (prog), (uint16_t)sizeof(prog), (steps), (what))
101
102
// ---- Straight-line arithmetic ----------------------------------------------
103
104
static void
105
test_arithmetic(void)
106
{
107
	// CLC ; LDA #$40 ; ADC #$40 ; STA $0400 ; LDX #$05 ; DEX ; STX $0401
108
	const uint8_t prog[] = {
109
		0x18, 0xA9, 0x40, 0x69, 0x40, 0x8D, 0x00, 0x04,
110
		0xA2, 0x05, 0xCA, 0x8E, 0x01, 0x04,
111
	};
112
	BOTH_WAYS(CPU_65C02, prog, 7, "arithmetic and stores");
113
}
114
115
// ---- Taken branches, which carry their own cycle penalties -----------------
116
117
static void
118
test_branches(void)
119
{
120
	// LDX #$03 ; loop: DEX ; BNE loop ; STX $0400
121
	const uint8_t prog[] = {
122
		0xA2, 0x03,
123
		0xCA,             // loop
124
		0xD0, 0xFD,       // BNE loop
125
		0x8E, 0x00, 0x04,
126
	};
127
	BOTH_WAYS(CPU_65C02, prog, 8, "a loop with taken branches");
128
}
129
130
// ---- Subroutine call and return --------------------------------------------
131
132
static void
133
test_subroutine(void)
134
{
135
	// JSR $0210 ; STA $0400 ; (at $0210) LDA #$7B ; RTS
136
	uint8_t prog[0x14];
137
	memset(prog, 0xEA, sizeof prog); // NOP filler
138
	prog[0x00] = 0x20; prog[0x01] = 0x10; prog[0x02] = 0x02; // JSR $0210
139
	prog[0x03] = 0x8D; prog[0x04] = 0x00; prog[0x05] = 0x04; // STA $0400
140
	prog[0x10] = 0xA9; prog[0x11] = 0x7B;                    // LDA #$7B
141
	prog[0x12] = 0x60;                                       // RTS
142
	BOTH_WAYS(CPU_65C02, prog, 4, "a subroutine call and return");
143
}
144
145
// ---- Page-crossing indexed reads, which cost an extra cycle ----------------
146
147
static void
148
test_page_cross(void)
149
{
150
	// LDX #$01 ; LDA $04FF,X ; STA $0400
151
	const uint8_t prog[] = {
152
		0xA2, 0x01, 0xBD, 0xFF, 0x04, 0x8D, 0x00, 0x04,
153
	};
154
	BOTH_WAYS(CPU_65C02, prog, 3, "an indexed read across a page");
155
}
156
157
// ---- 65C816 native mode, including a misaligned direct page -----------------
158
// The direct-page penalty is applied by one loop and not the other, so this is
159
// where the two paths are most likely to disagree.
160
161
static void
162
test_native_direct_page(void)
163
{
164
	// LDA #$01 ; XBA ; LDA #$34 ; TCD  -- direct page to $0134, misaligned
165
	// LDA $10 ; STA $0400
166
	const uint8_t prog[] = {
167
		0xA9, 0x01, 0xEB, 0xA9, 0x34, 0x5B,
168
		0xA5, 0x10,
169
		0x8D, 0x00, 0x04,
170
	};
171
	BOTH_WAYS(CPU_816_NATIVE_8, prog, 6, "a misaligned direct page in native mode");
172
}
173
174
static void
175
test_native_aligned_direct_page(void)
176
{
177
	// The same shape with an aligned direct page ($0100), which pays no penalty.
178
	const uint8_t prog[] = {
179
		0xA9, 0x01, 0xEB, 0xA9, 0x00, 0x5B,
180
		0xA5, 0x10,
181
		0x8D, 0x00, 0x04,
182
	};
183
	BOTH_WAYS(CPU_816_NATIVE_8, prog, 6, "an aligned direct page in native mode");
184
}
185
186
int
187
main(void)
188
{
189
	test_arithmetic();
190
	test_branches();
191
	test_subroutine();
192
	test_page_cross();
193
	test_native_aligned_direct_page();
194
	test_native_direct_page();
195
	return x16_test_summary("cpu_steprun");
196
}