OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_cpu_adc_sbc.c
5.0 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// CPU conformance: ADC and SBC in binary mode, and the flags they produce.
2
//
3
// Oracle: WDC W65C02S datasheet.
4
//
5
// The overflow flag is the interesting part and the one most often got wrong.
6
// V is about signed arithmetic: it is set when two operands of the same sign
7
// produce a result of the other sign, which is the only case where the signed
8
// answer cannot fit in eight bits. Carry, by contrast, is the unsigned answer
9
// not fitting. The two are independent, so each combination is checked.
10
//
11
// Decimal mode is deliberately absent; it has its own scenarios.
12
13
#include "support/cpu_fixture.h"
14
#include "support/harness.h"
15
16
#define PROG 0x0200
17
18
// CLC ; LDA #a ; ADC #b   (or SEC ; ... for carry in)
19
static void
20
add(uint8_t a, uint8_t b, bool carry_in)
21
{
22
	const uint8_t prog[] = { (uint8_t)(carry_in ? 0x38 : 0x18), 0xA9, a, 0x69, b };
23
	cpu_reset_to(CPU_65C02, PROG);
24
	cpu_load(PROG, prog, sizeof prog);
25
	cpu_steps(3);
26
}
27
28
// SEC ; LDA #a ; SBC #b   (carry set means "no borrow")
29
static void
30
sub(uint8_t a, uint8_t b, bool carry_in)
31
{
32
	const uint8_t prog[] = { (uint8_t)(carry_in ? 0x38 : 0x18), 0xA9, a, 0xE9, b };
33
	cpu_reset_to(CPU_65C02, PROG);
34
	cpu_load(PROG, prog, sizeof prog);
35
	cpu_steps(3);
36
}
37
38
static bool c(void) { return (regs.status & FLAG_CARRY) != 0; }
39
static bool v(void) { return (regs.status & FLAG_OVERFLOW) != 0; }
40
static bool z(void) { return (regs.status & FLAG_ZERO) != 0; }
41
static bool n(void) { return (regs.status & FLAG_SIGN) != 0; }
42
43
// ---- Addition --------------------------------------------------------------
44
45
static void
46
test_add_basic(void)
47
{
48
	add(0x01, 0x01, false);
49
	check_eq(regs.a, 0x02, "1 + 1 = 2");
50
	check(!c(), "1 + 1 does not carry");
51
	check(!v(), "1 + 1 does not overflow");
52
53
	add(0x01, 0x01, true);
54
	check_eq(regs.a, 0x03, "carry in is added");
55
56
	add(0xFF, 0x01, false);
57
	check_eq(regs.a, 0x00, "$FF + 1 wraps to zero");
58
	check(c(), "$FF + 1 carries");
59
	check(z(), "the wrapped result sets Z");
60
61
	add(0x7F, 0x01, false);
62
	check_eq(regs.a, 0x80, "$7F + 1 = $80");
63
	check(n(), "a result with bit 7 set is negative");
64
}
65
66
static void
67
test_add_overflow(void)
68
{
69
	// Same-signed operands crossing into the other sign: V set.
70
	add(0x50, 0x50, false); // 80 + 80 = 160, too big for a signed byte
71
	check_eq(regs.a, 0xA0, "$50 + $50 = $A0");
72
	check(v(), "positive + positive giving a negative sets V");
73
	check(!c(), "and it does not carry");
74
75
	add(0x90, 0x90, false); // -112 + -112 = -224, too small
76
	check_eq(regs.a, 0x20, "$90 + $90 = $20");
77
	check(v(), "negative + negative giving a positive sets V");
78
	check(c(), "and it does carry");
79
80
	// Opposite signs can never overflow: the result always fits.
81
	add(0x50, 0x90, false);
82
	check(!v(), "positive + negative never sets V");
83
	check(!c(), "$50 + $90 does not carry");
84
85
	add(0xD0, 0x50, false); // -48 + 80 = 32, fits
86
	check(!v(), "negative + positive never sets V");
87
	check(c(), "$D0 + $50 carries");
88
}
89
90
// ---- Subtraction -----------------------------------------------------------
91
// SBC subtracts the carry's complement, so carry set means "no borrow" and is
92
// the normal way to start a subtraction.
93
94
static void
95
test_sub_basic(void)
96
{
97
	sub(0x05, 0x03, true);
98
	check_eq(regs.a, 0x02, "5 - 3 = 2");
99
	check(c(), "a subtraction that does not borrow leaves C set");
100
	check(!v(), "5 - 3 does not overflow");
101
102
	sub(0x05, 0x05, true);
103
	check_eq(regs.a, 0x00, "5 - 5 = 0");
104
	check(z(), "an exactly zero result sets Z");
105
	check(c(), "and still does not borrow");
106
107
	sub(0x03, 0x05, true);
108
	check_eq(regs.a, 0xFE, "3 - 5 wraps to $FE");
109
	check(!c(), "a subtraction that borrows clears C");
110
	check(n(), "the wrapped result is negative");
111
112
	// Carry clear on entry costs an extra one: 5 - 3 - 1.
113
	sub(0x05, 0x03, false);
114
	check_eq(regs.a, 0x01, "a clear carry subtracts one more");
115
}
116
117
static void
118
test_sub_overflow(void)
119
{
120
	// Overflow in subtraction needs operands of opposite sign.
121
	sub(0x50, 0xB0, true); // 80 - (-80) = 160, too big
122
	check_eq(regs.a, 0xA0, "$50 - $B0 = $A0");
123
	check(v(), "positive - negative giving a negative sets V");
124
125
	sub(0xD0, 0x70, true); // -48 - 112 = -160, too small
126
	check_eq(regs.a, 0x60, "$D0 - $70 = $60");
127
	check(v(), "negative - positive giving a positive sets V");
128
129
	sub(0x50, 0x30, true);
130
	check(!v(), "same-signed operands do not overflow");
131
}
132
133
// ---- Timing ----------------------------------------------------------------
134
135
static void
136
test_timing(void)
137
{
138
	const uint8_t prog[] = { 0x18, 0xA9, 0x10, 0x69, 0x20 }; // CLC ; LDA # ; ADC #
139
	cpu_reset_to(CPU_65C02, PROG);
140
	cpu_load(PROG, prog, sizeof prog);
141
	cpu_steps(2);
142
	check_eq(cpu_step(), 2, "ADC immediate costs 2 cycles");
143
144
	// CLC ; LDA #$10 ; ADC $30
145
	const uint8_t zp[] = { 0x18, 0xA9, 0x10, 0x65, 0x30 };
146
	cpu_reset_to(CPU_65C02, PROG);
147
	cpu_load(PROG, zp, sizeof zp);
148
	cpu_mem[0x0030] = 0x05;
149
	cpu_steps(2);
150
	check_eq(cpu_step(), 3, "ADC zero page costs 3 cycles");
151
	check_eq(regs.a, 0x15, "ADC zero page adds the addressed byte");
152
}
153
154
int
155
main(void)
156
{
157
	test_add_basic();
158
	test_add_overflow();
159
	test_sub_basic();
160
	test_sub_overflow();
161
	test_timing();
162
	return x16_test_summary("cpu_adc_sbc");
163
}