OXYGENHUB
# 2980b7b6d7cd enhanced-x16-emulator / tests / test_cpu_smoke.c
5.9 KiB · C · last changed chore(): initial commit 26 days ago source blame history raw
1
// Checks that the CPU fixture itself works, before any conformance scenario
2
// relies on it.
3
//
4
// These are deliberately the least interesting assertions in the CPU track:
5
// the reset vector is honoured, an instruction executes, memory reads and
6
// writes reach the flat array, cycles accumulate, and a reset really does
7
// clear the previous scenario. If any of that is wrong then every later CPU
8
// result is meaningless, so it is worth stating plainly and separately.
9
10
#include "support/cpu_fixture.h"
11
#include "support/harness.h"
12
13
#define PROG 0x0200
14
15
static void
16
test_reset_vector(void)
17
{
18
	cpu_reset_to(CPU_65C02, PROG);
19
	check_eq(regs.pc, PROG, "reset takes PC from the vector at $FFFC");
20
	check_eq(clockticks6502, 0, "cycle counter starts at zero");
21
}
22
23
static void
24
test_executes_an_instruction(void)
25
{
26
	// LDA #$42
27
	const uint8_t prog[] = { 0xA9, 0x42 };
28
29
	cpu_reset_to(CPU_65C02, PROG);
30
	cpu_load(PROG, prog, sizeof prog);
31
	cpu_steps(1);
32
33
	check_eq(regs.a, 0x42, "LDA #$42 loads the accumulator");
34
	check_eq(regs.pc, PROG + 2, "PC advances past a two-byte instruction");
35
	check_eq(cpu_last_cycles(), 2, "LDA immediate costs two cycles");
36
}
37
38
static void
39
test_memory_round_trip(void)
40
{
41
	// LDA #$7E ; STA $0400
42
	const uint8_t prog[] = { 0xA9, 0x7E, 0x8D, 0x00, 0x04 };
43
44
	cpu_reset_to(CPU_65C02, PROG);
45
	cpu_load(PROG, prog, sizeof prog);
46
	cpu_steps(2);
47
48
	check_eq(cpu_mem[0x0400], 0x7E, "a store reaches the fixture's memory");
49
50
	// And the other direction: a seeded byte is visible to a load.
51
	cpu_reset_to(CPU_65C02, PROG);
52
	cpu_mem[0x0400] = 0x39;
53
	const uint8_t load[] = { 0xAD, 0x00, 0x04 }; // LDA $0400
54
	cpu_load(PROG, load, sizeof load);
55
	cpu_steps(1);
56
57
	check_eq(regs.a, 0x39, "a load sees a byte seeded by the test");
58
}
59
60
static void
61
test_reset_isolates_scenarios(void)
62
{
63
	const uint8_t prog[] = { 0xA9, 0x11, 0x8D, 0x00, 0x04 };
64
65
	cpu_reset_to(CPU_65C02, PROG);
66
	cpu_load(PROG, prog, sizeof prog);
67
	cpu_steps(2);
68
	check_eq(cpu_mem[0x0400], 0x11, "first scenario wrote to memory");
69
70
	cpu_reset_to(CPU_65C02, PROG);
71
	check_eq(cpu_mem[0x0400], 0x00, "reset clears memory from the last scenario");
72
	check_eq(cpu_mem[PROG], 0x00, "reset clears the previous program");
73
	check_eq(cpu_stop_count, 0, "reset clears the stop counter");
74
}
75
76
// Flags leak between scenarios unless the fixture clears them: reset6502() only
77
// ever ORs bits into the status register, so N, V, Z and C survive it. A test
78
// asserting "this instruction did not touch V" would otherwise pass or fail
79
// depending on which scenario ran before it.
80
static void
81
test_reset_clears_flags(void)
82
{
83
	// SEC ; LDA #$FF ; ADC #$FF -- leaves C, N and V set.
84
	const uint8_t dirty[] = { 0x38, 0xA9, 0xFF, 0x69, 0xFF };
85
	cpu_reset_to(CPU_65C02, PROG);
86
	cpu_load(PROG, dirty, sizeof dirty);
87
	cpu_steps(3);
88
	check(regs.status & FLAG_CARRY, "the dirtying scenario left C set");
89
	check(regs.status & FLAG_SIGN, "and N set");
90
91
	cpu_reset_to(CPU_65C02, PROG);
92
	check(!(regs.status & FLAG_CARRY), "reset clears C");
93
	check(!(regs.status & FLAG_SIGN), "reset clears N");
94
	check(!(regs.status & FLAG_OVERFLOW), "reset clears V");
95
	check(!(regs.status & FLAG_ZERO), "reset clears Z");
96
	check(regs.status & FLAG_INTERRUPT, "reset leaves interrupts disabled");
97
	check(!(regs.status & FLAG_DECIMAL), "reset clears decimal mode");
98
}
99
100
// penaltyd is set by the 65C816 direct-page addressing modes when the direct
101
// page is not page-aligned. It used not to be cleared per instruction, so one
102
// misaligned access added a cycle to every instruction thereafter -- which
103
// would show up as a scenario that passes alone and fails after another.
104
static void
105
test_direct_page_penalty_does_not_persist(void)
106
{
107
	// LDA #$01 ; XBA ; LDA #$34 ; TCD  -- direct page to $0134, misaligned.
108
	// Then LDA $10 (direct page, pays the penalty) and NOP (must not).
109
	const uint8_t prog[] = {
110
		0xA9, 0x01, 0xEB, 0xA9, 0x34, 0x5B, // set D = $0134
111
		0xA5, 0x10,                         // LDA $10
112
		0xEA,                               // NOP
113
	};
114
	cpu_reset_to(CPU_816_NATIVE_8, PROG);
115
	cpu_load(PROG, prog, sizeof prog);
116
	cpu_steps(4);
117
118
	uint32_t dp_cost  = cpu_step(); // LDA direct page, misaligned
119
	uint32_t nop_cost = cpu_step(); // NOP
120
121
	check_eq(nop_cost, 2, "a NOP after a misaligned direct-page access still costs 2");
122
	check(dp_cost > nop_cost, "and the direct-page access itself paid the penalty");
123
}
124
125
static void
126
test_cycles_are_per_run(void)
127
{
128
	const uint8_t prog[] = { 0xA9, 0x01, 0xA9, 0x02 }; // LDA #$01 ; LDA #$02
129
130
	cpu_reset_to(CPU_65C02, PROG);
131
	cpu_load(PROG, prog, sizeof prog);
132
133
	cpu_steps(1);
134
	check_eq(cpu_last_cycles(), 2, "cycles are counted for one step");
135
	cpu_steps(1);
136
	check_eq(cpu_last_cycles(), 2, "the count covers only the latest run");
137
	check_eq(clockticks6502, 4, "the total keeps accumulating");
138
}
139
140
static void
141
test_modes_select_register_width(void)
142
{
143
	// Naming and the width predicate are what the 65C816 scenarios branch on,
144
	// so an error here would quietly mis-label every one of them.
145
	check(cpu_mode_is_16bit(CPU_816_NATIVE_16), "native 16-bit mode reports 16-bit");
146
	check(!cpu_mode_is_16bit(CPU_65C02), "65C02 does not report 16-bit");
147
	check(!cpu_mode_is_16bit(CPU_816_EMU), "emulation mode does not report 16-bit");
148
149
	cpu_reset_to(CPU_816_NATIVE_16, PROG);
150
	check_eq(regs.e, 0, "native mode clears the emulation bit");
151
	check_eq(regs.status & (FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH), 0,
152
	         "native 16-bit clears both width flags");
153
154
	cpu_reset_to(CPU_816_NATIVE_8, PROG);
155
	check_eq(regs.status & (FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH),
156
	         FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH,
157
	         "native 8-bit sets both width flags");
158
159
	cpu_reset_to(CPU_816_EMU, PROG);
160
	check_eq(regs.e, 1, "emulation mode leaves the emulation bit set");
161
}
162
163
int
164
main(void)
165
{
166
	test_reset_vector();
167
	test_executes_an_instruction();
168
	test_memory_round_trip();
169
	test_reset_isolates_scenarios();
170
	test_reset_clears_flags();
171
	test_direct_page_penalty_does_not_persist();
172
	test_cycles_are_per_run();
173
	test_modes_select_register_width();
174
	return x16_test_summary("cpu_smoke");
175
}