ac6502 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/components/CPU.js +1170 -0
- package/dist/components/CPU.js.map +1 -0
- package/dist/components/Cart.js +23 -0
- package/dist/components/Cart.js.map +1 -0
- package/dist/components/IO/Empty.js +19 -0
- package/dist/components/IO/Empty.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
- package/dist/components/IO/GPIOCard.js +597 -0
- package/dist/components/IO/GPIOCard.js.map +1 -0
- package/dist/components/IO/InputBoard.js +19 -0
- package/dist/components/IO/InputBoard.js.map +1 -0
- package/dist/components/IO/LCDCard.js +19 -0
- package/dist/components/IO/LCDCard.js.map +1 -0
- package/dist/components/IO/RAMCard.js +63 -0
- package/dist/components/IO/RAMCard.js.map +1 -0
- package/dist/components/IO/RTCCard.js +483 -0
- package/dist/components/IO/RTCCard.js.map +1 -0
- package/dist/components/IO/SerialCard.js +282 -0
- package/dist/components/IO/SerialCard.js.map +1 -0
- package/dist/components/IO/SoundCard.js +620 -0
- package/dist/components/IO/SoundCard.js.map +1 -0
- package/dist/components/IO/StorageCard.js +428 -0
- package/dist/components/IO/StorageCard.js.map +1 -0
- package/dist/components/IO/VGACard.js +9 -0
- package/dist/components/IO/VGACard.js.map +1 -0
- package/dist/components/IO/VideoCard.js +623 -0
- package/dist/components/IO/VideoCard.js.map +1 -0
- package/dist/components/IO.js +3 -0
- package/dist/components/IO.js.map +1 -0
- package/dist/components/Machine.js +310 -0
- package/dist/components/Machine.js.map +1 -0
- package/dist/components/RAM.js +24 -0
- package/dist/components/RAM.js.map +1 -0
- package/dist/components/ROM.js +23 -0
- package/dist/components/ROM.js.map +1 -0
- package/dist/index.js +441 -0
- package/dist/index.js.map +1 -0
- package/dist/tests/CPU.test.js +1626 -0
- package/dist/tests/CPU.test.js.map +1 -0
- package/dist/tests/Cart.test.js +119 -0
- package/dist/tests/Cart.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOCard.test.js +503 -0
- package/dist/tests/IO/GPIOCard.test.js.map +1 -0
- package/dist/tests/IO/RAMCard.test.js +229 -0
- package/dist/tests/IO/RAMCard.test.js.map +1 -0
- package/dist/tests/IO/RTCCard.test.js +177 -0
- package/dist/tests/IO/RTCCard.test.js.map +1 -0
- package/dist/tests/IO/SerialCard.test.js +423 -0
- package/dist/tests/IO/SerialCard.test.js.map +1 -0
- package/dist/tests/IO/SoundCard.test.js +528 -0
- package/dist/tests/IO/SoundCard.test.js.map +1 -0
- package/dist/tests/IO/StorageCard.test.js +647 -0
- package/dist/tests/IO/StorageCard.test.js.map +1 -0
- package/dist/tests/IO/VideoCard.test.js +549 -0
- package/dist/tests/IO/VideoCard.test.js.map +1 -0
- package/dist/tests/Machine.test.js +383 -0
- package/dist/tests/Machine.test.js.map +1 -0
- package/dist/tests/RAM.test.js +160 -0
- package/dist/tests/RAM.test.js.map +1 -0
- package/dist/tests/ROM.test.js +123 -0
- package/dist/tests/ROM.test.js.map +1 -0
- package/jest.config.cjs +9 -0
- package/package.json +43 -0
- package/src/components/CPU.ts +1371 -0
- package/src/components/Cart.ts +20 -0
- package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
- package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
- package/src/components/IO/GPIOCard.ts +677 -0
- package/src/components/IO/RAMCard.ts +68 -0
- package/src/components/IO/RTCCard.ts +518 -0
- package/src/components/IO/SerialCard.ts +335 -0
- package/src/components/IO/SoundCard.ts +711 -0
- package/src/components/IO/StorageCard.ts +473 -0
- package/src/components/IO/VideoCard.ts +730 -0
- package/src/components/IO.ts +11 -0
- package/src/components/Machine.ts +364 -0
- package/src/components/RAM.ts +23 -0
- package/src/components/ROM.ts +19 -0
- package/src/index.ts +474 -0
- package/src/tests/CPU.test.ts +2045 -0
- package/src/tests/Cart.test.ts +149 -0
- package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
- package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
- package/src/tests/IO/GPIOCard.test.ts +644 -0
- package/src/tests/IO/RAMCard.test.ts +284 -0
- package/src/tests/IO/RTCCard.test.ts +222 -0
- package/src/tests/IO/SerialCard.test.ts +530 -0
- package/src/tests/IO/SoundCard.test.ts +659 -0
- package/src/tests/IO/StorageCard.test.ts +787 -0
- package/src/tests/IO/VideoCard.test.ts +668 -0
- package/src/tests/Machine.test.ts +437 -0
- package/src/tests/RAM.test.ts +196 -0
- package/src/tests/ROM.test.ts +154 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,1626 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const CPU_1 = require("../components/CPU");
|
|
4
|
+
describe('CPU', () => {
|
|
5
|
+
let memory;
|
|
6
|
+
let cpu;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
memory = new Array(0x10000).fill(0);
|
|
9
|
+
const read = (address) => {
|
|
10
|
+
return memory[address & 0xFFFF] || 0;
|
|
11
|
+
};
|
|
12
|
+
const write = (address, data) => {
|
|
13
|
+
memory[address & 0xFFFF] = data & 0xFF;
|
|
14
|
+
};
|
|
15
|
+
cpu = new CPU_1.CPU(read, write);
|
|
16
|
+
});
|
|
17
|
+
describe('Initialization', () => {
|
|
18
|
+
test('should create a new CPU instance', () => {
|
|
19
|
+
expect(cpu).not.toBeNull();
|
|
20
|
+
});
|
|
21
|
+
test('should initialize with default register values', () => {
|
|
22
|
+
expect(cpu.a).toBe(0x00);
|
|
23
|
+
expect(cpu.x).toBe(0x00);
|
|
24
|
+
expect(cpu.y).toBe(0x00);
|
|
25
|
+
expect(cpu.pc).toBe(0x0000);
|
|
26
|
+
expect(cpu.sp).toBe(0xFD);
|
|
27
|
+
expect(cpu.st).toBe(CPU_1.CPU.U);
|
|
28
|
+
expect(cpu.cyclesRem).toBe(0);
|
|
29
|
+
expect(cpu.cycles).toBe(0);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('Reset', () => {
|
|
33
|
+
test('should reset CPU and load PC from reset vector', () => {
|
|
34
|
+
memory[0xFFFC] = 0x00;
|
|
35
|
+
memory[0xFFFD] = 0x80;
|
|
36
|
+
cpu.reset();
|
|
37
|
+
expect(cpu.pc).toBe(0x8000);
|
|
38
|
+
expect(cpu.a).toBe(0x00);
|
|
39
|
+
expect(cpu.x).toBe(0x00);
|
|
40
|
+
expect(cpu.y).toBe(0x00);
|
|
41
|
+
expect(cpu.sp).toBe(0xFD);
|
|
42
|
+
expect(cpu.st).toBe(CPU_1.CPU.U);
|
|
43
|
+
expect(cpu.cyclesRem).toBe(7);
|
|
44
|
+
});
|
|
45
|
+
test('should add 7 cycles on reset', () => {
|
|
46
|
+
memory[0xFFFC] = 0x00;
|
|
47
|
+
memory[0xFFFD] = 0x80;
|
|
48
|
+
const initialCycles = cpu.cycles;
|
|
49
|
+
cpu.reset();
|
|
50
|
+
expect(cpu.cycles).toBe(initialCycles + 7);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('Status Flags', () => {
|
|
54
|
+
test('should set carry flag (C)', () => {
|
|
55
|
+
// SEC - Set Carry Flag (0x38)
|
|
56
|
+
memory[0x8000] = 0x38;
|
|
57
|
+
memory[0xFFFC] = 0x00;
|
|
58
|
+
memory[0xFFFD] = 0x80;
|
|
59
|
+
cpu.reset();
|
|
60
|
+
cpu.step();
|
|
61
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
62
|
+
});
|
|
63
|
+
test('should clear carry flag (C)', () => {
|
|
64
|
+
// CLC - Clear Carry Flag (0x18)
|
|
65
|
+
memory[0x8000] = 0x38; // SEC
|
|
66
|
+
memory[0x8001] = 0x18; // CLC
|
|
67
|
+
memory[0xFFFC] = 0x00;
|
|
68
|
+
memory[0xFFFD] = 0x80;
|
|
69
|
+
cpu.reset();
|
|
70
|
+
cpu.step(); // SEC
|
|
71
|
+
cpu.step(); // CLC
|
|
72
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
test('should set interrupt disable flag (I)', () => {
|
|
75
|
+
// SEI - Set Interrupt Disable (0x78)
|
|
76
|
+
memory[0x8000] = 0x78;
|
|
77
|
+
memory[0xFFFC] = 0x00;
|
|
78
|
+
memory[0xFFFD] = 0x80;
|
|
79
|
+
cpu.reset();
|
|
80
|
+
cpu.step();
|
|
81
|
+
expect(cpu.st & CPU_1.CPU.I).toBe(CPU_1.CPU.I);
|
|
82
|
+
});
|
|
83
|
+
test('should clear interrupt disable flag (I)', () => {
|
|
84
|
+
// CLI - Clear Interrupt Disable (0x58)
|
|
85
|
+
memory[0x8000] = 0x78; // SEI
|
|
86
|
+
memory[0x8001] = 0x58; // CLI
|
|
87
|
+
memory[0xFFFC] = 0x00;
|
|
88
|
+
memory[0xFFFD] = 0x80;
|
|
89
|
+
cpu.reset();
|
|
90
|
+
cpu.step(); // SEI
|
|
91
|
+
cpu.step(); // CLI
|
|
92
|
+
expect(cpu.st & CPU_1.CPU.I).toBe(0);
|
|
93
|
+
});
|
|
94
|
+
test('should set decimal mode flag (D)', () => {
|
|
95
|
+
// SED - Set Decimal Mode (0xF8)
|
|
96
|
+
memory[0x8000] = 0xF8;
|
|
97
|
+
memory[0xFFFC] = 0x00;
|
|
98
|
+
memory[0xFFFD] = 0x80;
|
|
99
|
+
cpu.reset();
|
|
100
|
+
cpu.step();
|
|
101
|
+
expect(cpu.st & CPU_1.CPU.D).toBe(CPU_1.CPU.D);
|
|
102
|
+
});
|
|
103
|
+
test('should clear decimal mode flag (D)', () => {
|
|
104
|
+
// CLD - Clear Decimal Mode (0xD8)
|
|
105
|
+
memory[0x8000] = 0xF8; // SED
|
|
106
|
+
memory[0x8001] = 0xD8; // CLD
|
|
107
|
+
memory[0xFFFC] = 0x00;
|
|
108
|
+
memory[0xFFFD] = 0x80;
|
|
109
|
+
cpu.reset();
|
|
110
|
+
cpu.step(); // SED
|
|
111
|
+
cpu.step(); // CLD
|
|
112
|
+
expect(cpu.st & CPU_1.CPU.D).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
test('should clear overflow flag (V)', () => {
|
|
115
|
+
// CLV - Clear Overflow Flag (0xB8)
|
|
116
|
+
memory[0x8000] = 0xB8;
|
|
117
|
+
memory[0xFFFC] = 0x00;
|
|
118
|
+
memory[0xFFFD] = 0x80;
|
|
119
|
+
cpu.reset();
|
|
120
|
+
cpu.st |= CPU_1.CPU.V;
|
|
121
|
+
cpu.step();
|
|
122
|
+
expect(cpu.st & CPU_1.CPU.V).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('Load Instructions', () => {
|
|
126
|
+
test('LDA immediate should load accumulator', () => {
|
|
127
|
+
// LDA #$42
|
|
128
|
+
memory[0x8000] = 0xA9; // LDA immediate
|
|
129
|
+
memory[0x8001] = 0x42;
|
|
130
|
+
memory[0xFFFC] = 0x00;
|
|
131
|
+
memory[0xFFFD] = 0x80;
|
|
132
|
+
cpu.reset();
|
|
133
|
+
cpu.step();
|
|
134
|
+
expect(cpu.a).toBe(0x42);
|
|
135
|
+
});
|
|
136
|
+
test('LDA should set zero flag when loading zero', () => {
|
|
137
|
+
// LDA #$00
|
|
138
|
+
memory[0x8000] = 0xA9; // LDA immediate
|
|
139
|
+
memory[0x8001] = 0x00;
|
|
140
|
+
memory[0xFFFC] = 0x00;
|
|
141
|
+
memory[0xFFFD] = 0x80;
|
|
142
|
+
cpu.reset();
|
|
143
|
+
cpu.step();
|
|
144
|
+
expect(cpu.a).toBe(0x00);
|
|
145
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
146
|
+
});
|
|
147
|
+
test('LDA should set negative flag when loading negative value', () => {
|
|
148
|
+
// LDA #$80
|
|
149
|
+
memory[0x8000] = 0xA9; // LDA immediate
|
|
150
|
+
memory[0x8001] = 0x80;
|
|
151
|
+
memory[0xFFFC] = 0x00;
|
|
152
|
+
memory[0xFFFD] = 0x80;
|
|
153
|
+
cpu.reset();
|
|
154
|
+
cpu.step();
|
|
155
|
+
expect(cpu.a).toBe(0x80);
|
|
156
|
+
expect(cpu.st & CPU_1.CPU.N).toBe(CPU_1.CPU.N);
|
|
157
|
+
});
|
|
158
|
+
test('LDX immediate should load X register', () => {
|
|
159
|
+
// LDX #$55
|
|
160
|
+
memory[0x8000] = 0xA2; // LDX immediate
|
|
161
|
+
memory[0x8001] = 0x55;
|
|
162
|
+
memory[0xFFFC] = 0x00;
|
|
163
|
+
memory[0xFFFD] = 0x80;
|
|
164
|
+
cpu.reset();
|
|
165
|
+
cpu.step();
|
|
166
|
+
expect(cpu.x).toBe(0x55);
|
|
167
|
+
});
|
|
168
|
+
test('LDY immediate should load Y register', () => {
|
|
169
|
+
// LDY #$66
|
|
170
|
+
memory[0x8000] = 0xA0; // LDY immediate
|
|
171
|
+
memory[0x8001] = 0x66;
|
|
172
|
+
memory[0xFFFC] = 0x00;
|
|
173
|
+
memory[0xFFFD] = 0x80;
|
|
174
|
+
cpu.reset();
|
|
175
|
+
cpu.step();
|
|
176
|
+
expect(cpu.y).toBe(0x66);
|
|
177
|
+
});
|
|
178
|
+
test('LDA zero page should load from zero page', () => {
|
|
179
|
+
memory[0x0010] = 0x77;
|
|
180
|
+
memory[0x8000] = 0xA5; // LDA zero page
|
|
181
|
+
memory[0x8001] = 0x10;
|
|
182
|
+
memory[0xFFFC] = 0x00;
|
|
183
|
+
memory[0xFFFD] = 0x80;
|
|
184
|
+
cpu.reset();
|
|
185
|
+
cpu.step();
|
|
186
|
+
expect(cpu.a).toBe(0x77);
|
|
187
|
+
});
|
|
188
|
+
test('LDA absolute should load from absolute address', () => {
|
|
189
|
+
memory[0x1234] = 0x88;
|
|
190
|
+
memory[0x8000] = 0xAD; // LDA absolute
|
|
191
|
+
memory[0x8001] = 0x34;
|
|
192
|
+
memory[0x8002] = 0x12;
|
|
193
|
+
memory[0xFFFC] = 0x00;
|
|
194
|
+
memory[0xFFFD] = 0x80;
|
|
195
|
+
cpu.reset();
|
|
196
|
+
cpu.step();
|
|
197
|
+
expect(cpu.a).toBe(0x88);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('Store Instructions', () => {
|
|
201
|
+
test('STA zero page should store accumulator', () => {
|
|
202
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
203
|
+
memory[0x8001] = 0x42;
|
|
204
|
+
memory[0x8002] = 0x85; // STA $10
|
|
205
|
+
memory[0x8003] = 0x10;
|
|
206
|
+
memory[0xFFFC] = 0x00;
|
|
207
|
+
memory[0xFFFD] = 0x80;
|
|
208
|
+
cpu.reset();
|
|
209
|
+
cpu.step(); // LDA
|
|
210
|
+
cpu.step(); // STA
|
|
211
|
+
expect(memory[0x0010]).toBe(0x42);
|
|
212
|
+
});
|
|
213
|
+
test('STX zero page should store X register', () => {
|
|
214
|
+
memory[0x8000] = 0xA2; // LDX #$55
|
|
215
|
+
memory[0x8001] = 0x55;
|
|
216
|
+
memory[0x8002] = 0x86; // STX $20
|
|
217
|
+
memory[0x8003] = 0x20;
|
|
218
|
+
memory[0xFFFC] = 0x00;
|
|
219
|
+
memory[0xFFFD] = 0x80;
|
|
220
|
+
cpu.reset();
|
|
221
|
+
cpu.step(); // LDX
|
|
222
|
+
cpu.step(); // STX
|
|
223
|
+
expect(memory[0x0020]).toBe(0x55);
|
|
224
|
+
});
|
|
225
|
+
test('STY zero page should store Y register', () => {
|
|
226
|
+
memory[0x8000] = 0xA0; // LDY #$66
|
|
227
|
+
memory[0x8001] = 0x66;
|
|
228
|
+
memory[0x8002] = 0x84; // STY $30
|
|
229
|
+
memory[0x8003] = 0x30;
|
|
230
|
+
memory[0xFFFC] = 0x00;
|
|
231
|
+
memory[0xFFFD] = 0x80;
|
|
232
|
+
cpu.reset();
|
|
233
|
+
cpu.step(); // LDY
|
|
234
|
+
cpu.step(); // STY
|
|
235
|
+
expect(memory[0x0030]).toBe(0x66);
|
|
236
|
+
});
|
|
237
|
+
test('STA absolute should store to absolute address', () => {
|
|
238
|
+
memory[0x8000] = 0xA9; // LDA #$99
|
|
239
|
+
memory[0x8001] = 0x99;
|
|
240
|
+
memory[0x8002] = 0x8D; // STA $1234
|
|
241
|
+
memory[0x8003] = 0x34;
|
|
242
|
+
memory[0x8004] = 0x12;
|
|
243
|
+
memory[0xFFFC] = 0x00;
|
|
244
|
+
memory[0xFFFD] = 0x80;
|
|
245
|
+
cpu.reset();
|
|
246
|
+
cpu.step(); // LDA
|
|
247
|
+
cpu.step(); // STA
|
|
248
|
+
expect(memory[0x1234]).toBe(0x99);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe('Transfer Instructions', () => {
|
|
252
|
+
test('TAX should transfer A to X', () => {
|
|
253
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
254
|
+
memory[0x8001] = 0x42;
|
|
255
|
+
memory[0x8002] = 0xAA; // TAX
|
|
256
|
+
memory[0xFFFC] = 0x00;
|
|
257
|
+
memory[0xFFFD] = 0x80;
|
|
258
|
+
cpu.reset();
|
|
259
|
+
cpu.step(); // LDA
|
|
260
|
+
cpu.step(); // TAX
|
|
261
|
+
expect(cpu.x).toBe(0x42);
|
|
262
|
+
});
|
|
263
|
+
test('TAY should transfer A to Y', () => {
|
|
264
|
+
memory[0x8000] = 0xA9; // LDA #$55
|
|
265
|
+
memory[0x8001] = 0x55;
|
|
266
|
+
memory[0x8002] = 0xA8; // TAY
|
|
267
|
+
memory[0xFFFC] = 0x00;
|
|
268
|
+
memory[0xFFFD] = 0x80;
|
|
269
|
+
cpu.reset();
|
|
270
|
+
cpu.step(); // LDA
|
|
271
|
+
cpu.step(); // TAY
|
|
272
|
+
expect(cpu.y).toBe(0x55);
|
|
273
|
+
});
|
|
274
|
+
test('TXA should transfer X to A', () => {
|
|
275
|
+
memory[0x8000] = 0xA2; // LDX #$66
|
|
276
|
+
memory[0x8001] = 0x66;
|
|
277
|
+
memory[0x8002] = 0x8A; // TXA
|
|
278
|
+
memory[0xFFFC] = 0x00;
|
|
279
|
+
memory[0xFFFD] = 0x80;
|
|
280
|
+
cpu.reset();
|
|
281
|
+
cpu.step(); // LDX
|
|
282
|
+
cpu.step(); // TXA
|
|
283
|
+
expect(cpu.a).toBe(0x66);
|
|
284
|
+
});
|
|
285
|
+
test('TYA should transfer Y to A', () => {
|
|
286
|
+
memory[0x8000] = 0xA0; // LDY #$77
|
|
287
|
+
memory[0x8001] = 0x77;
|
|
288
|
+
memory[0x8002] = 0x98; // TYA
|
|
289
|
+
memory[0xFFFC] = 0x00;
|
|
290
|
+
memory[0xFFFD] = 0x80;
|
|
291
|
+
cpu.reset();
|
|
292
|
+
cpu.step(); // LDY
|
|
293
|
+
cpu.step(); // TYA
|
|
294
|
+
expect(cpu.a).toBe(0x77);
|
|
295
|
+
});
|
|
296
|
+
test('TSX should transfer SP to X', () => {
|
|
297
|
+
memory[0x8000] = 0xBA; // TSX
|
|
298
|
+
memory[0xFFFC] = 0x00;
|
|
299
|
+
memory[0xFFFD] = 0x80;
|
|
300
|
+
cpu.reset();
|
|
301
|
+
cpu.sp = 0xAB;
|
|
302
|
+
cpu.step(); // TSX
|
|
303
|
+
expect(cpu.x).toBe(0xAB);
|
|
304
|
+
});
|
|
305
|
+
test('TXS should transfer X to SP', () => {
|
|
306
|
+
memory[0x8000] = 0xA2; // LDX #$CD
|
|
307
|
+
memory[0x8001] = 0xCD;
|
|
308
|
+
memory[0x8002] = 0x9A; // TXS
|
|
309
|
+
memory[0xFFFC] = 0x00;
|
|
310
|
+
memory[0xFFFD] = 0x80;
|
|
311
|
+
cpu.reset();
|
|
312
|
+
cpu.step(); // LDX
|
|
313
|
+
cpu.step(); // TXS
|
|
314
|
+
expect(cpu.sp).toBe(0xCD);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
describe('Increment and Decrement', () => {
|
|
318
|
+
test('INX should increment X register', () => {
|
|
319
|
+
memory[0x8000] = 0xA2; // LDX #$10
|
|
320
|
+
memory[0x8001] = 0x10;
|
|
321
|
+
memory[0x8002] = 0xE8; // INX
|
|
322
|
+
memory[0xFFFC] = 0x00;
|
|
323
|
+
memory[0xFFFD] = 0x80;
|
|
324
|
+
cpu.reset();
|
|
325
|
+
cpu.step(); // LDX
|
|
326
|
+
cpu.step(); // INX
|
|
327
|
+
expect(cpu.x).toBe(0x11);
|
|
328
|
+
});
|
|
329
|
+
test('INX should wrap around from 0xFF to 0x00', () => {
|
|
330
|
+
memory[0x8000] = 0xA2; // LDX #$FF
|
|
331
|
+
memory[0x8001] = 0xFF;
|
|
332
|
+
memory[0x8002] = 0xE8; // INX
|
|
333
|
+
memory[0xFFFC] = 0x00;
|
|
334
|
+
memory[0xFFFD] = 0x80;
|
|
335
|
+
cpu.reset();
|
|
336
|
+
cpu.step(); // LDX
|
|
337
|
+
cpu.step(); // INX
|
|
338
|
+
expect(cpu.x).toBe(0x00);
|
|
339
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
340
|
+
});
|
|
341
|
+
test('INY should increment Y register', () => {
|
|
342
|
+
memory[0x8000] = 0xA0; // LDY #$20
|
|
343
|
+
memory[0x8001] = 0x20;
|
|
344
|
+
memory[0x8002] = 0xC8; // INY
|
|
345
|
+
memory[0xFFFC] = 0x00;
|
|
346
|
+
memory[0xFFFD] = 0x80;
|
|
347
|
+
cpu.reset();
|
|
348
|
+
cpu.step(); // LDY
|
|
349
|
+
cpu.step(); // INY
|
|
350
|
+
expect(cpu.y).toBe(0x21);
|
|
351
|
+
});
|
|
352
|
+
test('DEX should decrement X register', () => {
|
|
353
|
+
memory[0x8000] = 0xA2; // LDX #$10
|
|
354
|
+
memory[0x8001] = 0x10;
|
|
355
|
+
memory[0x8002] = 0xCA; // DEX
|
|
356
|
+
memory[0xFFFC] = 0x00;
|
|
357
|
+
memory[0xFFFD] = 0x80;
|
|
358
|
+
cpu.reset();
|
|
359
|
+
cpu.step(); // LDX
|
|
360
|
+
cpu.step(); // DEX
|
|
361
|
+
expect(cpu.x).toBe(0x0F);
|
|
362
|
+
});
|
|
363
|
+
test('DEX should wrap around from 0x00 to 0xFF', () => {
|
|
364
|
+
memory[0x8000] = 0xA2; // LDX #$00
|
|
365
|
+
memory[0x8001] = 0x00;
|
|
366
|
+
memory[0x8002] = 0xCA; // DEX
|
|
367
|
+
memory[0xFFFC] = 0x00;
|
|
368
|
+
memory[0xFFFD] = 0x80;
|
|
369
|
+
cpu.reset();
|
|
370
|
+
cpu.step(); // LDX
|
|
371
|
+
cpu.step(); // DEX
|
|
372
|
+
expect(cpu.x).toBe(0xFF);
|
|
373
|
+
expect(cpu.st & CPU_1.CPU.N).toBe(CPU_1.CPU.N);
|
|
374
|
+
});
|
|
375
|
+
test('DEY should decrement Y register', () => {
|
|
376
|
+
memory[0x8000] = 0xA0; // LDY #$20
|
|
377
|
+
memory[0x8001] = 0x20;
|
|
378
|
+
memory[0x8002] = 0x88; // DEY
|
|
379
|
+
memory[0xFFFC] = 0x00;
|
|
380
|
+
memory[0xFFFD] = 0x80;
|
|
381
|
+
cpu.reset();
|
|
382
|
+
cpu.step(); // LDY
|
|
383
|
+
cpu.step(); // DEY
|
|
384
|
+
expect(cpu.y).toBe(0x1F);
|
|
385
|
+
});
|
|
386
|
+
test('INC zero page should increment memory', () => {
|
|
387
|
+
memory[0x0010] = 0x42;
|
|
388
|
+
memory[0x8000] = 0xE6; // INC $10
|
|
389
|
+
memory[0x8001] = 0x10;
|
|
390
|
+
memory[0xFFFC] = 0x00;
|
|
391
|
+
memory[0xFFFD] = 0x80;
|
|
392
|
+
cpu.reset();
|
|
393
|
+
cpu.step(); // INC
|
|
394
|
+
expect(memory[0x0010]).toBe(0x43);
|
|
395
|
+
});
|
|
396
|
+
test('DEC zero page should decrement memory', () => {
|
|
397
|
+
memory[0x0010] = 0x42;
|
|
398
|
+
memory[0x8000] = 0xC6; // DEC $10
|
|
399
|
+
memory[0x8001] = 0x10;
|
|
400
|
+
memory[0xFFFC] = 0x00;
|
|
401
|
+
memory[0xFFFD] = 0x80;
|
|
402
|
+
cpu.reset();
|
|
403
|
+
cpu.step(); // DEC
|
|
404
|
+
expect(memory[0x0010]).toBe(0x41);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
describe('Arithmetic Operations', () => {
|
|
408
|
+
test('ADC should add with carry', () => {
|
|
409
|
+
memory[0x8000] = 0xA9; // LDA #$10
|
|
410
|
+
memory[0x8001] = 0x10;
|
|
411
|
+
memory[0x8002] = 0x69; // ADC #$20
|
|
412
|
+
memory[0x8003] = 0x20;
|
|
413
|
+
memory[0xFFFC] = 0x00;
|
|
414
|
+
memory[0xFFFD] = 0x80;
|
|
415
|
+
cpu.reset();
|
|
416
|
+
cpu.step(); // LDA
|
|
417
|
+
cpu.step(); // ADC
|
|
418
|
+
expect(cpu.a).toBe(0x30);
|
|
419
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(0);
|
|
420
|
+
});
|
|
421
|
+
test('ADC should set carry flag on overflow', () => {
|
|
422
|
+
memory[0x8000] = 0xA9; // LDA #$FF
|
|
423
|
+
memory[0x8001] = 0xFF;
|
|
424
|
+
memory[0x8002] = 0x69; // ADC #$02
|
|
425
|
+
memory[0x8003] = 0x02;
|
|
426
|
+
memory[0xFFFC] = 0x00;
|
|
427
|
+
memory[0xFFFD] = 0x80;
|
|
428
|
+
cpu.reset();
|
|
429
|
+
cpu.step(); // LDA
|
|
430
|
+
cpu.step(); // ADC
|
|
431
|
+
expect(cpu.a).toBe(0x01);
|
|
432
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
433
|
+
});
|
|
434
|
+
test('ADC should add carry flag to result', () => {
|
|
435
|
+
memory[0x8000] = 0x38; // SEC (set carry)
|
|
436
|
+
memory[0x8001] = 0xA9; // LDA #$10
|
|
437
|
+
memory[0x8002] = 0x10;
|
|
438
|
+
memory[0x8003] = 0x69; // ADC #$20
|
|
439
|
+
memory[0x8004] = 0x20;
|
|
440
|
+
memory[0xFFFC] = 0x00;
|
|
441
|
+
memory[0xFFFD] = 0x80;
|
|
442
|
+
cpu.reset();
|
|
443
|
+
cpu.step(); // SEC
|
|
444
|
+
cpu.step(); // LDA
|
|
445
|
+
cpu.step(); // ADC
|
|
446
|
+
expect(cpu.a).toBe(0x31);
|
|
447
|
+
});
|
|
448
|
+
test('SBC should subtract with borrow', () => {
|
|
449
|
+
memory[0x8000] = 0x38; // SEC (required for SBC)
|
|
450
|
+
memory[0x8001] = 0xA9; // LDA #$30
|
|
451
|
+
memory[0x8002] = 0x30;
|
|
452
|
+
memory[0x8003] = 0xE9; // SBC #$10
|
|
453
|
+
memory[0x8004] = 0x10;
|
|
454
|
+
memory[0xFFFC] = 0x00;
|
|
455
|
+
memory[0xFFFD] = 0x80;
|
|
456
|
+
cpu.reset();
|
|
457
|
+
cpu.step(); // SEC
|
|
458
|
+
cpu.step(); // LDA
|
|
459
|
+
cpu.step(); // SBC
|
|
460
|
+
expect(cpu.a).toBe(0x20);
|
|
461
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
462
|
+
});
|
|
463
|
+
test('SBC should handle underflow', () => {
|
|
464
|
+
memory[0x8000] = 0x38; // SEC
|
|
465
|
+
memory[0x8001] = 0xA9; // LDA #$10
|
|
466
|
+
memory[0x8002] = 0x10;
|
|
467
|
+
memory[0x8003] = 0xE9; // SBC #$20
|
|
468
|
+
memory[0x8004] = 0x20;
|
|
469
|
+
memory[0xFFFC] = 0x00;
|
|
470
|
+
memory[0xFFFD] = 0x80;
|
|
471
|
+
cpu.reset();
|
|
472
|
+
cpu.step(); // SEC
|
|
473
|
+
cpu.step(); // LDA
|
|
474
|
+
cpu.step(); // SBC
|
|
475
|
+
expect(cpu.a).toBe(0xF0);
|
|
476
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(0);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
describe('Logical Operations', () => {
|
|
480
|
+
test('AND should perform bitwise AND', () => {
|
|
481
|
+
memory[0x8000] = 0xA9; // LDA #$FF
|
|
482
|
+
memory[0x8001] = 0xFF;
|
|
483
|
+
memory[0x8002] = 0x29; // AND #$0F
|
|
484
|
+
memory[0x8003] = 0x0F;
|
|
485
|
+
memory[0xFFFC] = 0x00;
|
|
486
|
+
memory[0xFFFD] = 0x80;
|
|
487
|
+
cpu.reset();
|
|
488
|
+
cpu.step(); // LDA
|
|
489
|
+
cpu.step(); // AND
|
|
490
|
+
expect(cpu.a).toBe(0x0F);
|
|
491
|
+
});
|
|
492
|
+
test('ORA should perform bitwise OR', () => {
|
|
493
|
+
memory[0x8000] = 0xA9; // LDA #$0F
|
|
494
|
+
memory[0x8001] = 0x0F;
|
|
495
|
+
memory[0x8002] = 0x09; // ORA #$F0
|
|
496
|
+
memory[0x8003] = 0xF0;
|
|
497
|
+
memory[0xFFFC] = 0x00;
|
|
498
|
+
memory[0xFFFD] = 0x80;
|
|
499
|
+
cpu.reset();
|
|
500
|
+
cpu.step(); // LDA
|
|
501
|
+
cpu.step(); // ORA
|
|
502
|
+
expect(cpu.a).toBe(0xFF);
|
|
503
|
+
});
|
|
504
|
+
test('EOR should perform bitwise XOR', () => {
|
|
505
|
+
memory[0x8000] = 0xA9; // LDA #$FF
|
|
506
|
+
memory[0x8001] = 0xFF;
|
|
507
|
+
memory[0x8002] = 0x49; // EOR #$0F
|
|
508
|
+
memory[0x8003] = 0x0F;
|
|
509
|
+
memory[0xFFFC] = 0x00;
|
|
510
|
+
memory[0xFFFD] = 0x80;
|
|
511
|
+
cpu.reset();
|
|
512
|
+
cpu.step(); // LDA
|
|
513
|
+
cpu.step(); // EOR
|
|
514
|
+
expect(cpu.a).toBe(0xF0);
|
|
515
|
+
});
|
|
516
|
+
test('BIT should test bits', () => {
|
|
517
|
+
memory[0x0010] = 0xC0; // Bits 7 and 6 set
|
|
518
|
+
memory[0x8000] = 0xA9; // LDA #$C0
|
|
519
|
+
memory[0x8001] = 0xC0;
|
|
520
|
+
memory[0x8002] = 0x24; // BIT $10
|
|
521
|
+
memory[0x8003] = 0x10;
|
|
522
|
+
memory[0xFFFC] = 0x00;
|
|
523
|
+
memory[0xFFFD] = 0x80;
|
|
524
|
+
cpu.reset();
|
|
525
|
+
cpu.step(); // LDA
|
|
526
|
+
cpu.step(); // BIT
|
|
527
|
+
expect(cpu.st & CPU_1.CPU.N).toBe(CPU_1.CPU.N); // Bit 7
|
|
528
|
+
expect(cpu.st & CPU_1.CPU.V).toBe(CPU_1.CPU.V); // Bit 6
|
|
529
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(0); // Result not zero
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
describe('Shift and Rotate Operations', () => {
|
|
533
|
+
test('ASL accumulator should shift left', () => {
|
|
534
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
535
|
+
memory[0x8001] = 0x42;
|
|
536
|
+
memory[0x8002] = 0x0A; // ASL A
|
|
537
|
+
memory[0xFFFC] = 0x00;
|
|
538
|
+
memory[0xFFFD] = 0x80;
|
|
539
|
+
cpu.reset();
|
|
540
|
+
cpu.step(); // LDA
|
|
541
|
+
cpu.step(); // ASL
|
|
542
|
+
expect(cpu.a).toBe(0x84);
|
|
543
|
+
});
|
|
544
|
+
test('ASL should set carry flag on bit 7', () => {
|
|
545
|
+
memory[0x8000] = 0xA9; // LDA #$80
|
|
546
|
+
memory[0x8001] = 0x80;
|
|
547
|
+
memory[0x8002] = 0x0A; // ASL A
|
|
548
|
+
memory[0xFFFC] = 0x00;
|
|
549
|
+
memory[0xFFFD] = 0x80;
|
|
550
|
+
cpu.reset();
|
|
551
|
+
cpu.step(); // LDA
|
|
552
|
+
cpu.step(); // ASL
|
|
553
|
+
expect(cpu.a).toBe(0x00);
|
|
554
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
555
|
+
});
|
|
556
|
+
test('LSR accumulator should shift right', () => {
|
|
557
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
558
|
+
memory[0x8001] = 0x42;
|
|
559
|
+
memory[0x8002] = 0x4A; // LSR A
|
|
560
|
+
memory[0xFFFC] = 0x00;
|
|
561
|
+
memory[0xFFFD] = 0x80;
|
|
562
|
+
cpu.reset();
|
|
563
|
+
cpu.step(); // LDA
|
|
564
|
+
cpu.step(); // LSR
|
|
565
|
+
expect(cpu.a).toBe(0x21);
|
|
566
|
+
});
|
|
567
|
+
test('LSR should set carry flag on bit 0', () => {
|
|
568
|
+
memory[0x8000] = 0xA9; // LDA #$01
|
|
569
|
+
memory[0x8001] = 0x01;
|
|
570
|
+
memory[0x8002] = 0x4A; // LSR A
|
|
571
|
+
memory[0xFFFC] = 0x00;
|
|
572
|
+
memory[0xFFFD] = 0x80;
|
|
573
|
+
cpu.reset();
|
|
574
|
+
cpu.step(); // LDA
|
|
575
|
+
cpu.step(); // LSR
|
|
576
|
+
expect(cpu.a).toBe(0x00);
|
|
577
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
578
|
+
});
|
|
579
|
+
test('ROL accumulator should rotate left through carry', () => {
|
|
580
|
+
memory[0x8000] = 0x38; // SEC
|
|
581
|
+
memory[0x8001] = 0xA9; // LDA #$42
|
|
582
|
+
memory[0x8002] = 0x42;
|
|
583
|
+
memory[0x8003] = 0x2A; // ROL A
|
|
584
|
+
memory[0xFFFC] = 0x00;
|
|
585
|
+
memory[0xFFFD] = 0x80;
|
|
586
|
+
cpu.reset();
|
|
587
|
+
cpu.step(); // SEC
|
|
588
|
+
cpu.step(); // LDA
|
|
589
|
+
cpu.step(); // ROL
|
|
590
|
+
expect(cpu.a).toBe(0x85); // 0x42 << 1 | 1
|
|
591
|
+
});
|
|
592
|
+
test('ROR accumulator should rotate right through carry', () => {
|
|
593
|
+
memory[0x8000] = 0x38; // SEC
|
|
594
|
+
memory[0x8001] = 0xA9; // LDA #$42
|
|
595
|
+
memory[0x8002] = 0x42;
|
|
596
|
+
memory[0x8003] = 0x6A; // ROR A
|
|
597
|
+
memory[0xFFFC] = 0x00;
|
|
598
|
+
memory[0xFFFD] = 0x80;
|
|
599
|
+
cpu.reset();
|
|
600
|
+
cpu.step(); // SEC
|
|
601
|
+
cpu.step(); // LDA
|
|
602
|
+
cpu.step(); // ROR
|
|
603
|
+
expect(cpu.a).toBe(0xA1); // 0x80 | (0x42 >> 1)
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
describe('Compare Operations', () => {
|
|
607
|
+
test('CMP should set carry when A >= operand', () => {
|
|
608
|
+
memory[0x8000] = 0xA9; // LDA #$50
|
|
609
|
+
memory[0x8001] = 0x50;
|
|
610
|
+
memory[0x8002] = 0xC9; // CMP #$30
|
|
611
|
+
memory[0x8003] = 0x30;
|
|
612
|
+
memory[0xFFFC] = 0x00;
|
|
613
|
+
memory[0xFFFD] = 0x80;
|
|
614
|
+
cpu.reset();
|
|
615
|
+
cpu.step(); // LDA
|
|
616
|
+
cpu.step(); // CMP
|
|
617
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
618
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(0);
|
|
619
|
+
});
|
|
620
|
+
test('CMP should set zero flag when A == operand', () => {
|
|
621
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
622
|
+
memory[0x8001] = 0x42;
|
|
623
|
+
memory[0x8002] = 0xC9; // CMP #$42
|
|
624
|
+
memory[0x8003] = 0x42;
|
|
625
|
+
memory[0xFFFC] = 0x00;
|
|
626
|
+
memory[0xFFFD] = 0x80;
|
|
627
|
+
cpu.reset();
|
|
628
|
+
cpu.step(); // LDA
|
|
629
|
+
cpu.step(); // CMP
|
|
630
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
631
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
632
|
+
});
|
|
633
|
+
test('CPX should compare X register', () => {
|
|
634
|
+
memory[0x8000] = 0xA2; // LDX #$50
|
|
635
|
+
memory[0x8001] = 0x50;
|
|
636
|
+
memory[0x8002] = 0xE0; // CPX #$50
|
|
637
|
+
memory[0x8003] = 0x50;
|
|
638
|
+
memory[0xFFFC] = 0x00;
|
|
639
|
+
memory[0xFFFD] = 0x80;
|
|
640
|
+
cpu.reset();
|
|
641
|
+
cpu.step(); // LDX
|
|
642
|
+
cpu.step(); // CPX
|
|
643
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
644
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
645
|
+
});
|
|
646
|
+
test('CPY should compare Y register', () => {
|
|
647
|
+
memory[0x8000] = 0xA0; // LDY #$50
|
|
648
|
+
memory[0x8001] = 0x50;
|
|
649
|
+
memory[0x8002] = 0xC0; // CPY #$30
|
|
650
|
+
memory[0x8003] = 0x30;
|
|
651
|
+
memory[0xFFFC] = 0x00;
|
|
652
|
+
memory[0xFFFD] = 0x80;
|
|
653
|
+
cpu.reset();
|
|
654
|
+
cpu.step(); // LDY
|
|
655
|
+
cpu.step(); // CPY
|
|
656
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
describe('Branch Operations', () => {
|
|
660
|
+
test('BEQ should branch when zero flag is set', () => {
|
|
661
|
+
memory[0x8000] = 0xA9; // LDA #$00
|
|
662
|
+
memory[0x8001] = 0x00;
|
|
663
|
+
memory[0x8002] = 0xF0; // BEQ +2
|
|
664
|
+
memory[0x8003] = 0x02;
|
|
665
|
+
memory[0x8004] = 0xA9; // LDA #$FF (should be skipped)
|
|
666
|
+
memory[0x8005] = 0xFF;
|
|
667
|
+
memory[0x8006] = 0xEA; // NOP
|
|
668
|
+
memory[0xFFFC] = 0x00;
|
|
669
|
+
memory[0xFFFD] = 0x80;
|
|
670
|
+
cpu.reset();
|
|
671
|
+
cpu.step(); // LDA
|
|
672
|
+
cpu.step(); // BEQ
|
|
673
|
+
expect(cpu.pc).toBe(0x8006);
|
|
674
|
+
});
|
|
675
|
+
test('BNE should branch when zero flag is clear', () => {
|
|
676
|
+
memory[0x8000] = 0xA9; // LDA #$01
|
|
677
|
+
memory[0x8001] = 0x01;
|
|
678
|
+
memory[0x8002] = 0xD0; // BNE +2
|
|
679
|
+
memory[0x8003] = 0x02;
|
|
680
|
+
memory[0x8004] = 0xA9; // LDA #$FF (should be skipped)
|
|
681
|
+
memory[0x8005] = 0xFF;
|
|
682
|
+
memory[0x8006] = 0xEA; // NOP
|
|
683
|
+
memory[0xFFFC] = 0x00;
|
|
684
|
+
memory[0xFFFD] = 0x80;
|
|
685
|
+
cpu.reset();
|
|
686
|
+
cpu.step(); // LDA
|
|
687
|
+
cpu.step(); // BNE
|
|
688
|
+
expect(cpu.pc).toBe(0x8006);
|
|
689
|
+
});
|
|
690
|
+
test('BCS should branch when carry flag is set', () => {
|
|
691
|
+
memory[0x8000] = 0x38; // SEC
|
|
692
|
+
memory[0x8001] = 0xB0; // BCS +2
|
|
693
|
+
memory[0x8002] = 0x02;
|
|
694
|
+
memory[0x8003] = 0xA9; // LDA #$FF (should be skipped)
|
|
695
|
+
memory[0x8004] = 0xFF;
|
|
696
|
+
memory[0x8005] = 0xEA; // NOP
|
|
697
|
+
memory[0xFFFC] = 0x00;
|
|
698
|
+
memory[0xFFFD] = 0x80;
|
|
699
|
+
cpu.reset();
|
|
700
|
+
cpu.step(); // SEC
|
|
701
|
+
cpu.step(); // BCS
|
|
702
|
+
expect(cpu.pc).toBe(0x8005);
|
|
703
|
+
});
|
|
704
|
+
test('BCC should branch when carry flag is clear', () => {
|
|
705
|
+
memory[0x8000] = 0x18; // CLC
|
|
706
|
+
memory[0x8001] = 0x90; // BCC +2
|
|
707
|
+
memory[0x8002] = 0x02;
|
|
708
|
+
memory[0x8003] = 0xA9; // LDA #$FF (should be skipped)
|
|
709
|
+
memory[0x8004] = 0xFF;
|
|
710
|
+
memory[0x8005] = 0xEA; // NOP
|
|
711
|
+
memory[0xFFFC] = 0x00;
|
|
712
|
+
memory[0xFFFD] = 0x80;
|
|
713
|
+
cpu.reset();
|
|
714
|
+
cpu.step(); // CLC
|
|
715
|
+
cpu.step(); // BCC
|
|
716
|
+
expect(cpu.pc).toBe(0x8005);
|
|
717
|
+
});
|
|
718
|
+
test('BMI should branch when negative flag is set', () => {
|
|
719
|
+
memory[0x8000] = 0xA9; // LDA #$80
|
|
720
|
+
memory[0x8001] = 0x80;
|
|
721
|
+
memory[0x8002] = 0x30; // BMI +2
|
|
722
|
+
memory[0x8003] = 0x02;
|
|
723
|
+
memory[0x8004] = 0xA9; // LDA #$FF (should be skipped)
|
|
724
|
+
memory[0x8005] = 0xFF;
|
|
725
|
+
memory[0x8006] = 0xEA; // NOP
|
|
726
|
+
memory[0xFFFC] = 0x00;
|
|
727
|
+
memory[0xFFFD] = 0x80;
|
|
728
|
+
cpu.reset();
|
|
729
|
+
cpu.step(); // LDA
|
|
730
|
+
cpu.step(); // BMI
|
|
731
|
+
expect(cpu.pc).toBe(0x8006);
|
|
732
|
+
});
|
|
733
|
+
test('BPL should branch when negative flag is clear', () => {
|
|
734
|
+
memory[0x8000] = 0xA9; // LDA #$01
|
|
735
|
+
memory[0x8001] = 0x01;
|
|
736
|
+
memory[0x8002] = 0x10; // BPL +2
|
|
737
|
+
memory[0x8003] = 0x02;
|
|
738
|
+
memory[0x8004] = 0xA9; // LDA #$FF (should be skipped)
|
|
739
|
+
memory[0x8005] = 0xFF;
|
|
740
|
+
memory[0x8006] = 0xEA; // NOP
|
|
741
|
+
memory[0xFFFC] = 0x00;
|
|
742
|
+
memory[0xFFFD] = 0x80;
|
|
743
|
+
cpu.reset();
|
|
744
|
+
cpu.step(); // LDA
|
|
745
|
+
cpu.step(); // BPL
|
|
746
|
+
expect(cpu.pc).toBe(0x8006);
|
|
747
|
+
});
|
|
748
|
+
test('BVS should branch when overflow flag is set', () => {
|
|
749
|
+
memory[0x8000] = 0xA9; // LDA #$7F
|
|
750
|
+
memory[0x8001] = 0x7F;
|
|
751
|
+
memory[0x8002] = 0x69; // ADC #$01 (causes overflow)
|
|
752
|
+
memory[0x8003] = 0x01;
|
|
753
|
+
memory[0x8004] = 0x70; // BVS +2
|
|
754
|
+
memory[0x8005] = 0x02;
|
|
755
|
+
memory[0x8006] = 0xA9; // LDA #$FF (should be skipped)
|
|
756
|
+
memory[0x8007] = 0xFF;
|
|
757
|
+
memory[0x8008] = 0xEA; // NOP
|
|
758
|
+
memory[0xFFFC] = 0x00;
|
|
759
|
+
memory[0xFFFD] = 0x80;
|
|
760
|
+
cpu.reset();
|
|
761
|
+
cpu.step(); // LDA
|
|
762
|
+
cpu.step(); // ADC
|
|
763
|
+
cpu.step(); // BVS
|
|
764
|
+
expect(cpu.pc).toBe(0x8008);
|
|
765
|
+
});
|
|
766
|
+
test('BVC should branch when overflow flag is clear', () => {
|
|
767
|
+
memory[0x8000] = 0xB8; // CLV
|
|
768
|
+
memory[0x8001] = 0x50; // BVC +2
|
|
769
|
+
memory[0x8002] = 0x02;
|
|
770
|
+
memory[0x8003] = 0xA9; // LDA #$FF (should be skipped)
|
|
771
|
+
memory[0x8004] = 0xFF;
|
|
772
|
+
memory[0x8005] = 0xEA; // NOP
|
|
773
|
+
memory[0xFFFC] = 0x00;
|
|
774
|
+
memory[0xFFFD] = 0x80;
|
|
775
|
+
cpu.reset();
|
|
776
|
+
cpu.step(); // CLV
|
|
777
|
+
cpu.step(); // BVC
|
|
778
|
+
expect(cpu.pc).toBe(0x8005);
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
describe('Jump and Subroutine Operations', () => {
|
|
782
|
+
test('JMP absolute should jump to address', () => {
|
|
783
|
+
memory[0x8000] = 0x4C; // JMP $9000
|
|
784
|
+
memory[0x8001] = 0x00;
|
|
785
|
+
memory[0x8002] = 0x90;
|
|
786
|
+
memory[0xFFFC] = 0x00;
|
|
787
|
+
memory[0xFFFD] = 0x80;
|
|
788
|
+
cpu.reset();
|
|
789
|
+
cpu.step(); // JMP
|
|
790
|
+
expect(cpu.pc).toBe(0x9000);
|
|
791
|
+
});
|
|
792
|
+
test('JSR should jump to subroutine and save return address', () => {
|
|
793
|
+
memory[0x8000] = 0x20; // JSR $9000
|
|
794
|
+
memory[0x8001] = 0x00;
|
|
795
|
+
memory[0x8002] = 0x90;
|
|
796
|
+
memory[0xFFFC] = 0x00;
|
|
797
|
+
memory[0xFFFD] = 0x80;
|
|
798
|
+
cpu.reset();
|
|
799
|
+
const oldSP = cpu.sp;
|
|
800
|
+
cpu.step(); // JSR
|
|
801
|
+
expect(cpu.pc).toBe(0x9000);
|
|
802
|
+
expect(cpu.sp).toBe(oldSP - 2);
|
|
803
|
+
});
|
|
804
|
+
test('RTS should return from subroutine', () => {
|
|
805
|
+
memory[0x8000] = 0x20; // JSR $9000
|
|
806
|
+
memory[0x8001] = 0x00;
|
|
807
|
+
memory[0x8002] = 0x90;
|
|
808
|
+
memory[0x8003] = 0xEA; // NOP (return here)
|
|
809
|
+
memory[0x9000] = 0x60; // RTS
|
|
810
|
+
memory[0xFFFC] = 0x00;
|
|
811
|
+
memory[0xFFFD] = 0x80;
|
|
812
|
+
cpu.reset();
|
|
813
|
+
cpu.step(); // JSR
|
|
814
|
+
cpu.step(); // RTS
|
|
815
|
+
expect(cpu.pc).toBe(0x8003);
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
describe('Stack Operations', () => {
|
|
819
|
+
test('PHA should push accumulator to stack', () => {
|
|
820
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
821
|
+
memory[0x8001] = 0x42;
|
|
822
|
+
memory[0x8002] = 0x48; // PHA
|
|
823
|
+
memory[0xFFFC] = 0x00;
|
|
824
|
+
memory[0xFFFD] = 0x80;
|
|
825
|
+
cpu.reset();
|
|
826
|
+
const oldSP = cpu.sp;
|
|
827
|
+
cpu.step(); // LDA
|
|
828
|
+
cpu.step(); // PHA
|
|
829
|
+
expect(memory[0x0100 + oldSP]).toBe(0x42);
|
|
830
|
+
expect(cpu.sp).toBe(oldSP - 1);
|
|
831
|
+
});
|
|
832
|
+
test('PLA should pull accumulator from stack', () => {
|
|
833
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
834
|
+
memory[0x8001] = 0x42;
|
|
835
|
+
memory[0x8002] = 0x48; // PHA
|
|
836
|
+
memory[0x8003] = 0xA9; // LDA #$00
|
|
837
|
+
memory[0x8004] = 0x00;
|
|
838
|
+
memory[0x8005] = 0x68; // PLA
|
|
839
|
+
memory[0xFFFC] = 0x00;
|
|
840
|
+
memory[0xFFFD] = 0x80;
|
|
841
|
+
cpu.reset();
|
|
842
|
+
cpu.step(); // LDA #$42
|
|
843
|
+
cpu.step(); // PHA
|
|
844
|
+
cpu.step(); // LDA #$00
|
|
845
|
+
cpu.step(); // PLA
|
|
846
|
+
expect(cpu.a).toBe(0x42);
|
|
847
|
+
});
|
|
848
|
+
test('PHP should push processor status to stack', () => {
|
|
849
|
+
memory[0x8000] = 0x38; // SEC
|
|
850
|
+
memory[0x8001] = 0x08; // PHP
|
|
851
|
+
memory[0xFFFC] = 0x00;
|
|
852
|
+
memory[0xFFFD] = 0x80;
|
|
853
|
+
cpu.reset();
|
|
854
|
+
const oldSP = cpu.sp;
|
|
855
|
+
cpu.step(); // SEC
|
|
856
|
+
cpu.step(); // PHP
|
|
857
|
+
const pushedStatus = memory[0x0100 + oldSP];
|
|
858
|
+
expect(pushedStatus & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
859
|
+
expect(cpu.sp).toBe(oldSP - 1);
|
|
860
|
+
});
|
|
861
|
+
test('PLP should pull processor status from stack', () => {
|
|
862
|
+
memory[0x8000] = 0x38; // SEC
|
|
863
|
+
memory[0x8001] = 0x08; // PHP
|
|
864
|
+
memory[0x8002] = 0x18; // CLC
|
|
865
|
+
memory[0x8003] = 0x28; // PLP
|
|
866
|
+
memory[0xFFFC] = 0x00;
|
|
867
|
+
memory[0xFFFD] = 0x80;
|
|
868
|
+
cpu.reset();
|
|
869
|
+
cpu.step(); // SEC
|
|
870
|
+
cpu.step(); // PHP
|
|
871
|
+
cpu.step(); // CLC
|
|
872
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(0);
|
|
873
|
+
cpu.step(); // PLP
|
|
874
|
+
expect(cpu.st & CPU_1.CPU.C).toBe(CPU_1.CPU.C);
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
describe('Interrupt Operations', () => {
|
|
878
|
+
test('IRQ should trigger interrupt when I flag is clear', () => {
|
|
879
|
+
memory[0xFFFE] = 0x00;
|
|
880
|
+
memory[0xFFFF] = 0x90;
|
|
881
|
+
memory[0xFFFC] = 0x00;
|
|
882
|
+
memory[0xFFFD] = 0x80;
|
|
883
|
+
cpu.reset();
|
|
884
|
+
cpu.irq();
|
|
885
|
+
expect(cpu.pc).toBe(0x9000);
|
|
886
|
+
expect(cpu.st & CPU_1.CPU.I).toBe(CPU_1.CPU.I);
|
|
887
|
+
});
|
|
888
|
+
test('IRQ should not trigger when I flag is set', () => {
|
|
889
|
+
memory[0xFFFE] = 0x00;
|
|
890
|
+
memory[0xFFFF] = 0x90;
|
|
891
|
+
memory[0xFFFC] = 0x00;
|
|
892
|
+
memory[0xFFFD] = 0x80;
|
|
893
|
+
memory[0x8000] = 0x78; // SEI
|
|
894
|
+
cpu.reset();
|
|
895
|
+
cpu.step(); // SEI
|
|
896
|
+
const oldPC = cpu.pc;
|
|
897
|
+
cpu.irq();
|
|
898
|
+
expect(cpu.pc).toBe(oldPC);
|
|
899
|
+
});
|
|
900
|
+
test('NMI should always trigger interrupt', () => {
|
|
901
|
+
memory[0xFFFA] = 0x00;
|
|
902
|
+
memory[0xFFFB] = 0x90;
|
|
903
|
+
memory[0xFFFC] = 0x00;
|
|
904
|
+
memory[0xFFFD] = 0x80;
|
|
905
|
+
memory[0x8000] = 0x78; // SEI
|
|
906
|
+
cpu.reset();
|
|
907
|
+
cpu.step(); // SEI
|
|
908
|
+
cpu.nmi();
|
|
909
|
+
expect(cpu.pc).toBe(0x9000);
|
|
910
|
+
expect(cpu.st & CPU_1.CPU.I).toBe(CPU_1.CPU.I);
|
|
911
|
+
});
|
|
912
|
+
test('BRK should trigger software interrupt', () => {
|
|
913
|
+
memory[0xFFFE] = 0x00;
|
|
914
|
+
memory[0xFFFF] = 0x90;
|
|
915
|
+
memory[0xFFFC] = 0x00;
|
|
916
|
+
memory[0xFFFD] = 0x80;
|
|
917
|
+
memory[0x8000] = 0x00; // BRK
|
|
918
|
+
cpu.reset();
|
|
919
|
+
cpu.step(); // BRK
|
|
920
|
+
expect(cpu.pc).toBe(0x9000);
|
|
921
|
+
expect(cpu.st & CPU_1.CPU.I).toBe(CPU_1.CPU.I);
|
|
922
|
+
});
|
|
923
|
+
test('RTI should return from interrupt', () => {
|
|
924
|
+
memory[0xFFFE] = 0x00;
|
|
925
|
+
memory[0xFFFF] = 0x90;
|
|
926
|
+
memory[0xFFFC] = 0x00;
|
|
927
|
+
memory[0xFFFD] = 0x80;
|
|
928
|
+
memory[0x9000] = 0x40; // RTI
|
|
929
|
+
cpu.reset();
|
|
930
|
+
const returnPC = cpu.pc;
|
|
931
|
+
cpu.irq();
|
|
932
|
+
cpu.step(); // RTI
|
|
933
|
+
expect(cpu.pc).toBe(returnPC);
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
describe('Cycle Counting', () => {
|
|
937
|
+
test('should count cycles correctly', () => {
|
|
938
|
+
memory[0xFFFC] = 0x00;
|
|
939
|
+
memory[0xFFFD] = 0x80;
|
|
940
|
+
memory[0x8000] = 0xA9; // LDA #$42 (2 cycles)
|
|
941
|
+
memory[0x8001] = 0x42;
|
|
942
|
+
cpu.reset();
|
|
943
|
+
const cyclesBeforeStep = cpu.cycles;
|
|
944
|
+
cpu.step();
|
|
945
|
+
expect(cpu.cycles).toBeGreaterThan(cyclesBeforeStep);
|
|
946
|
+
});
|
|
947
|
+
test('step should return number of cycles executed', () => {
|
|
948
|
+
memory[0xFFFC] = 0x00;
|
|
949
|
+
memory[0xFFFD] = 0x80;
|
|
950
|
+
memory[0x8000] = 0xA9; // LDA #$42 (2 cycles)
|
|
951
|
+
memory[0x8001] = 0x42;
|
|
952
|
+
cpu.reset();
|
|
953
|
+
const cyclesTaken = cpu.step();
|
|
954
|
+
expect(cyclesTaken).toBe(2);
|
|
955
|
+
});
|
|
956
|
+
test('tick should decrement cyclesRem', () => {
|
|
957
|
+
memory[0xFFFC] = 0x00;
|
|
958
|
+
memory[0xFFFD] = 0x80;
|
|
959
|
+
memory[0x8000] = 0xEA; // NOP
|
|
960
|
+
cpu.reset();
|
|
961
|
+
cpu.tick(); // Start executing NOP
|
|
962
|
+
expect(cpu.cyclesRem).toBeGreaterThan(0);
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
describe('Addressing Modes', () => {
|
|
966
|
+
test('zero page X indexed should work correctly', () => {
|
|
967
|
+
memory[0x0015] = 0x99; // Target location ($10 + $05)
|
|
968
|
+
memory[0x8000] = 0xA2; // LDX #$05
|
|
969
|
+
memory[0x8001] = 0x05;
|
|
970
|
+
memory[0x8002] = 0xB5; // LDA $10,X
|
|
971
|
+
memory[0x8003] = 0x10;
|
|
972
|
+
memory[0xFFFC] = 0x00;
|
|
973
|
+
memory[0xFFFD] = 0x80;
|
|
974
|
+
cpu.reset();
|
|
975
|
+
cpu.step(); // LDX
|
|
976
|
+
cpu.step(); // LDA
|
|
977
|
+
expect(cpu.a).toBe(0x99);
|
|
978
|
+
});
|
|
979
|
+
test('zero page Y indexed should work correctly', () => {
|
|
980
|
+
memory[0x0025] = 0x88; // Target location ($20 + $05)
|
|
981
|
+
memory[0x8000] = 0xA0; // LDY #$05
|
|
982
|
+
memory[0x8001] = 0x05;
|
|
983
|
+
memory[0x8002] = 0xB6; // LDX $20,Y
|
|
984
|
+
memory[0x8003] = 0x20;
|
|
985
|
+
memory[0xFFFC] = 0x00;
|
|
986
|
+
memory[0xFFFD] = 0x80;
|
|
987
|
+
cpu.reset();
|
|
988
|
+
cpu.step(); // LDY
|
|
989
|
+
cpu.step(); // LDX
|
|
990
|
+
expect(cpu.x).toBe(0x88);
|
|
991
|
+
});
|
|
992
|
+
test('absolute X indexed should work correctly', () => {
|
|
993
|
+
memory[0x1235] = 0x77; // Target location ($1230 + $05)
|
|
994
|
+
memory[0x8000] = 0xA2; // LDX #$05
|
|
995
|
+
memory[0x8001] = 0x05;
|
|
996
|
+
memory[0x8002] = 0xBD; // LDA $1230,X
|
|
997
|
+
memory[0x8003] = 0x30;
|
|
998
|
+
memory[0x8004] = 0x12;
|
|
999
|
+
memory[0xFFFC] = 0x00;
|
|
1000
|
+
memory[0xFFFD] = 0x80;
|
|
1001
|
+
cpu.reset();
|
|
1002
|
+
cpu.step(); // LDX
|
|
1003
|
+
cpu.step(); // LDA
|
|
1004
|
+
expect(cpu.a).toBe(0x77);
|
|
1005
|
+
});
|
|
1006
|
+
test('absolute Y indexed should work correctly', () => {
|
|
1007
|
+
memory[0x1245] = 0x66; // Target location ($1240 + $05)
|
|
1008
|
+
memory[0x8000] = 0xA0; // LDY #$05
|
|
1009
|
+
memory[0x8001] = 0x05;
|
|
1010
|
+
memory[0x8002] = 0xB9; // LDA $1240,Y
|
|
1011
|
+
memory[0x8003] = 0x40;
|
|
1012
|
+
memory[0x8004] = 0x12;
|
|
1013
|
+
memory[0xFFFC] = 0x00;
|
|
1014
|
+
memory[0xFFFD] = 0x80;
|
|
1015
|
+
cpu.reset();
|
|
1016
|
+
cpu.step(); // LDY
|
|
1017
|
+
cpu.step(); // LDA
|
|
1018
|
+
expect(cpu.a).toBe(0x66);
|
|
1019
|
+
});
|
|
1020
|
+
test('indirect addressing should work correctly', () => {
|
|
1021
|
+
memory[0x0120] = 0x00; // Low byte of target address
|
|
1022
|
+
memory[0x0121] = 0x90; // High byte of target address
|
|
1023
|
+
memory[0x8000] = 0x6C; // JMP ($0120)
|
|
1024
|
+
memory[0x8001] = 0x20;
|
|
1025
|
+
memory[0x8002] = 0x01;
|
|
1026
|
+
memory[0xFFFC] = 0x00;
|
|
1027
|
+
memory[0xFFFD] = 0x80;
|
|
1028
|
+
cpu.reset();
|
|
1029
|
+
cpu.step(); // JMP
|
|
1030
|
+
expect(cpu.pc).toBe(0x9000);
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
describe('Edge Cases', () => {
|
|
1034
|
+
test('should handle PC wraparound', () => {
|
|
1035
|
+
memory[0xFFFC] = 0xFE;
|
|
1036
|
+
memory[0xFFFD] = 0xFF;
|
|
1037
|
+
memory[0xFFFE] = 0xEA; // NOP
|
|
1038
|
+
memory[0xFFFF] = 0xEA; // NOP
|
|
1039
|
+
cpu.reset();
|
|
1040
|
+
expect(cpu.pc).toBe(0xFFFE);
|
|
1041
|
+
cpu.step(); // Execute NOP at 0xFFFE
|
|
1042
|
+
expect(cpu.pc).toBe(0xFFFF);
|
|
1043
|
+
cpu.step(); // Execute NOP at 0xFFFF
|
|
1044
|
+
expect(cpu.pc).toBe(0x0000); // Should wrap around
|
|
1045
|
+
});
|
|
1046
|
+
test('should handle SP wraparound on underflow', () => {
|
|
1047
|
+
cpu.sp = 0x00;
|
|
1048
|
+
memory[0x8000] = 0xA9; // LDA #$42
|
|
1049
|
+
memory[0x8001] = 0x42;
|
|
1050
|
+
memory[0x8002] = 0x48; // PHA
|
|
1051
|
+
memory[0xFFFC] = 0x00;
|
|
1052
|
+
memory[0xFFFD] = 0x80;
|
|
1053
|
+
cpu.reset();
|
|
1054
|
+
cpu.sp = 0x00;
|
|
1055
|
+
cpu.step(); // LDA
|
|
1056
|
+
cpu.step(); // PHA
|
|
1057
|
+
expect(cpu.sp).toBe(0xFF);
|
|
1058
|
+
});
|
|
1059
|
+
test('should handle zero page wraparound', () => {
|
|
1060
|
+
memory[0x0000] = 0x55; // Wrapped address
|
|
1061
|
+
memory[0x8000] = 0xA2; // LDX #$05
|
|
1062
|
+
memory[0x8001] = 0x05;
|
|
1063
|
+
memory[0x8002] = 0xB5; // LDA $FB,X ($FB + $05 = $100, wraps to $00)
|
|
1064
|
+
memory[0x8003] = 0xFB;
|
|
1065
|
+
memory[0xFFFC] = 0x00;
|
|
1066
|
+
memory[0xFFFD] = 0x80;
|
|
1067
|
+
cpu.reset();
|
|
1068
|
+
cpu.step(); // LDX
|
|
1069
|
+
cpu.step(); // LDA
|
|
1070
|
+
expect(cpu.a).toBe(0x55);
|
|
1071
|
+
});
|
|
1072
|
+
test('NOP should do nothing', () => {
|
|
1073
|
+
memory[0x8000] = 0xEA; // NOP
|
|
1074
|
+
memory[0xFFFC] = 0x00;
|
|
1075
|
+
memory[0xFFFD] = 0x80;
|
|
1076
|
+
cpu.reset();
|
|
1077
|
+
const oldA = cpu.a;
|
|
1078
|
+
const oldX = cpu.x;
|
|
1079
|
+
const oldY = cpu.y;
|
|
1080
|
+
const oldST = cpu.st;
|
|
1081
|
+
cpu.step(); // NOP
|
|
1082
|
+
expect(cpu.a).toBe(oldA);
|
|
1083
|
+
expect(cpu.x).toBe(oldX);
|
|
1084
|
+
expect(cpu.y).toBe(oldY);
|
|
1085
|
+
expect(cpu.st).toBe(oldST);
|
|
1086
|
+
});
|
|
1087
|
+
test('should handle negative branch offsets', () => {
|
|
1088
|
+
memory[0x8000] = 0xA9; // LDA #$00
|
|
1089
|
+
memory[0x8001] = 0x00;
|
|
1090
|
+
memory[0x8002] = 0xF0; // BEQ -4 (branch back to LDA)
|
|
1091
|
+
memory[0x8003] = 0xFC; // -4 in signed byte
|
|
1092
|
+
memory[0xFFFC] = 0x00;
|
|
1093
|
+
memory[0xFFFD] = 0x80;
|
|
1094
|
+
cpu.reset();
|
|
1095
|
+
cpu.step(); // LDA
|
|
1096
|
+
cpu.step(); // BEQ
|
|
1097
|
+
expect(cpu.pc).toBe(0x8000);
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
describe('65C02 Instructions', () => {
|
|
1101
|
+
describe('BRA - Branch Always', () => {
|
|
1102
|
+
test('should always branch forward', () => {
|
|
1103
|
+
memory[0x8000] = 0x80; // BRA +4
|
|
1104
|
+
memory[0x8001] = 0x04;
|
|
1105
|
+
memory[0x8002] = 0xEA; // NOP (should be skipped)
|
|
1106
|
+
memory[0x8003] = 0xEA; // NOP (should be skipped)
|
|
1107
|
+
memory[0x8004] = 0xEA; // NOP (should be skipped)
|
|
1108
|
+
memory[0x8005] = 0xEA; // NOP (should be skipped)
|
|
1109
|
+
memory[0x8006] = 0xA9; // LDA #$AA
|
|
1110
|
+
memory[0x8007] = 0xAA;
|
|
1111
|
+
memory[0xFFFC] = 0x00;
|
|
1112
|
+
memory[0xFFFD] = 0x80;
|
|
1113
|
+
cpu.reset();
|
|
1114
|
+
cpu.step(); // BRA
|
|
1115
|
+
cpu.step(); // LDA
|
|
1116
|
+
expect(cpu.a).toBe(0xAA);
|
|
1117
|
+
expect(cpu.pc).toBe(0x8008);
|
|
1118
|
+
});
|
|
1119
|
+
test('should always branch backward', () => {
|
|
1120
|
+
memory[0x8000] = 0xA9; // LDA #$55
|
|
1121
|
+
memory[0x8001] = 0x55;
|
|
1122
|
+
memory[0x8002] = 0x80; // BRA -4 (back to LDA)
|
|
1123
|
+
memory[0x8003] = 0xFC; // -4 in signed byte
|
|
1124
|
+
memory[0xFFFC] = 0x00;
|
|
1125
|
+
memory[0xFFFD] = 0x80;
|
|
1126
|
+
cpu.reset();
|
|
1127
|
+
cpu.step(); // LDA (first time)
|
|
1128
|
+
cpu.step(); // BRA
|
|
1129
|
+
expect(cpu.pc).toBe(0x8000);
|
|
1130
|
+
expect(cpu.a).toBe(0x55);
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
describe('PHX/PLX - Push/Pull X Register', () => {
|
|
1134
|
+
test('PHX should push X register to stack', () => {
|
|
1135
|
+
memory[0x8000] = 0xDA; // PHX
|
|
1136
|
+
memory[0xFFFC] = 0x00;
|
|
1137
|
+
memory[0xFFFD] = 0x80;
|
|
1138
|
+
cpu.reset();
|
|
1139
|
+
cpu.x = 0x42;
|
|
1140
|
+
const initialSP = cpu.sp;
|
|
1141
|
+
cpu.step();
|
|
1142
|
+
expect(memory[0x0100 + initialSP]).toBe(0x42);
|
|
1143
|
+
expect(cpu.sp).toBe(initialSP - 1);
|
|
1144
|
+
});
|
|
1145
|
+
test('PLX should pull X register from stack', () => {
|
|
1146
|
+
memory[0x8000] = 0xFA; // PLX
|
|
1147
|
+
memory[0xFFFC] = 0x00;
|
|
1148
|
+
memory[0xFFFD] = 0x80;
|
|
1149
|
+
cpu.reset();
|
|
1150
|
+
memory[0x0100 + cpu.sp + 1] = 0x77;
|
|
1151
|
+
cpu.step();
|
|
1152
|
+
expect(cpu.x).toBe(0x77);
|
|
1153
|
+
});
|
|
1154
|
+
test('PLX should set zero flag when pulling zero', () => {
|
|
1155
|
+
memory[0x8000] = 0xFA; // PLX
|
|
1156
|
+
memory[0xFFFC] = 0x00;
|
|
1157
|
+
memory[0xFFFD] = 0x80;
|
|
1158
|
+
cpu.reset();
|
|
1159
|
+
memory[0x0100 + cpu.sp + 1] = 0x00;
|
|
1160
|
+
cpu.step();
|
|
1161
|
+
expect(cpu.x).toBe(0x00);
|
|
1162
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
1163
|
+
});
|
|
1164
|
+
test('PLX should set negative flag when pulling negative value', () => {
|
|
1165
|
+
memory[0x8000] = 0xFA; // PLX
|
|
1166
|
+
memory[0xFFFC] = 0x00;
|
|
1167
|
+
memory[0xFFFD] = 0x80;
|
|
1168
|
+
cpu.reset();
|
|
1169
|
+
memory[0x0100 + cpu.sp + 1] = 0x80;
|
|
1170
|
+
cpu.step();
|
|
1171
|
+
expect(cpu.x).toBe(0x80);
|
|
1172
|
+
expect(cpu.st & CPU_1.CPU.N).toBe(CPU_1.CPU.N);
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
describe('PHY/PLY - Push/Pull Y Register', () => {
|
|
1176
|
+
test('PHY should push Y register to stack', () => {
|
|
1177
|
+
memory[0x8000] = 0x5A; // PHY
|
|
1178
|
+
memory[0xFFFC] = 0x00;
|
|
1179
|
+
memory[0xFFFD] = 0x80;
|
|
1180
|
+
cpu.reset();
|
|
1181
|
+
cpu.y = 0x33;
|
|
1182
|
+
const initialSP = cpu.sp;
|
|
1183
|
+
cpu.step();
|
|
1184
|
+
expect(memory[0x0100 + initialSP]).toBe(0x33);
|
|
1185
|
+
expect(cpu.sp).toBe(initialSP - 1);
|
|
1186
|
+
});
|
|
1187
|
+
test('PLY should pull Y register from stack', () => {
|
|
1188
|
+
memory[0x8000] = 0x7A; // PLY
|
|
1189
|
+
memory[0xFFFC] = 0x00;
|
|
1190
|
+
memory[0xFFFD] = 0x80;
|
|
1191
|
+
cpu.reset();
|
|
1192
|
+
memory[0x0100 + cpu.sp + 1] = 0x99;
|
|
1193
|
+
cpu.step();
|
|
1194
|
+
expect(cpu.y).toBe(0x99);
|
|
1195
|
+
});
|
|
1196
|
+
test('PLY should set flags correctly', () => {
|
|
1197
|
+
memory[0x8000] = 0x7A; // PLY
|
|
1198
|
+
memory[0xFFFC] = 0x00;
|
|
1199
|
+
memory[0xFFFD] = 0x80;
|
|
1200
|
+
cpu.reset();
|
|
1201
|
+
memory[0x0100 + cpu.sp + 1] = 0x80;
|
|
1202
|
+
cpu.step();
|
|
1203
|
+
expect(cpu.y).toBe(0x80);
|
|
1204
|
+
expect(cpu.st & CPU_1.CPU.N).toBe(CPU_1.CPU.N);
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
describe('STZ - Store Zero', () => {
|
|
1208
|
+
test('STZ zero page should store zero', () => {
|
|
1209
|
+
memory[0x8000] = 0x64; // STZ $10
|
|
1210
|
+
memory[0x8001] = 0x10;
|
|
1211
|
+
memory[0xFFFC] = 0x00;
|
|
1212
|
+
memory[0xFFFD] = 0x80;
|
|
1213
|
+
memory[0x0010] = 0xFF; // Set to non-zero first
|
|
1214
|
+
cpu.reset();
|
|
1215
|
+
cpu.step();
|
|
1216
|
+
expect(memory[0x0010]).toBe(0x00);
|
|
1217
|
+
});
|
|
1218
|
+
test('STZ zero page,X should store zero with X offset', () => {
|
|
1219
|
+
memory[0x8000] = 0x74; // STZ $10,X
|
|
1220
|
+
memory[0x8001] = 0x10;
|
|
1221
|
+
memory[0xFFFC] = 0x00;
|
|
1222
|
+
memory[0xFFFD] = 0x80;
|
|
1223
|
+
memory[0x0015] = 0xFF;
|
|
1224
|
+
cpu.reset();
|
|
1225
|
+
cpu.x = 0x05;
|
|
1226
|
+
cpu.step();
|
|
1227
|
+
expect(memory[0x0015]).toBe(0x00);
|
|
1228
|
+
});
|
|
1229
|
+
test('STZ absolute should store zero', () => {
|
|
1230
|
+
memory[0x8000] = 0x9C; // STZ $1234
|
|
1231
|
+
memory[0x8001] = 0x34;
|
|
1232
|
+
memory[0x8002] = 0x12;
|
|
1233
|
+
memory[0xFFFC] = 0x00;
|
|
1234
|
+
memory[0xFFFD] = 0x80;
|
|
1235
|
+
memory[0x1234] = 0xFF;
|
|
1236
|
+
cpu.reset();
|
|
1237
|
+
cpu.step();
|
|
1238
|
+
expect(memory[0x1234]).toBe(0x00);
|
|
1239
|
+
});
|
|
1240
|
+
test('STZ absolute,X should store zero with X offset', () => {
|
|
1241
|
+
memory[0x8000] = 0x9E; // STZ $1234,X
|
|
1242
|
+
memory[0x8001] = 0x34;
|
|
1243
|
+
memory[0x8002] = 0x12;
|
|
1244
|
+
memory[0xFFFC] = 0x00;
|
|
1245
|
+
memory[0xFFFD] = 0x80;
|
|
1246
|
+
memory[0x1239] = 0xFF;
|
|
1247
|
+
cpu.reset();
|
|
1248
|
+
cpu.x = 0x05;
|
|
1249
|
+
cpu.step();
|
|
1250
|
+
expect(memory[0x1239]).toBe(0x00);
|
|
1251
|
+
});
|
|
1252
|
+
});
|
|
1253
|
+
describe('TRB - Test and Reset Bits', () => {
|
|
1254
|
+
test('TRB zero page should reset bits and test', () => {
|
|
1255
|
+
memory[0x8000] = 0x14; // TRB $10
|
|
1256
|
+
memory[0x8001] = 0x10;
|
|
1257
|
+
memory[0xFFFC] = 0x00;
|
|
1258
|
+
memory[0xFFFD] = 0x80;
|
|
1259
|
+
memory[0x0010] = 0xFF;
|
|
1260
|
+
cpu.reset();
|
|
1261
|
+
cpu.a = 0x0F; // Lower 4 bits
|
|
1262
|
+
cpu.step();
|
|
1263
|
+
expect(memory[0x0010]).toBe(0xF0); // Lower 4 bits cleared
|
|
1264
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(0); // Not zero because A & memory was non-zero
|
|
1265
|
+
});
|
|
1266
|
+
test('TRB should set zero flag when A & memory is zero', () => {
|
|
1267
|
+
memory[0x8000] = 0x14; // TRB $10
|
|
1268
|
+
memory[0x8001] = 0x10;
|
|
1269
|
+
memory[0xFFFC] = 0x00;
|
|
1270
|
+
memory[0xFFFD] = 0x80;
|
|
1271
|
+
memory[0x0010] = 0xF0;
|
|
1272
|
+
cpu.reset();
|
|
1273
|
+
cpu.a = 0x0F;
|
|
1274
|
+
cpu.step();
|
|
1275
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
1276
|
+
});
|
|
1277
|
+
test('TRB absolute should work', () => {
|
|
1278
|
+
memory[0x8000] = 0x1C; // TRB $1234
|
|
1279
|
+
memory[0x8001] = 0x34;
|
|
1280
|
+
memory[0x8002] = 0x12;
|
|
1281
|
+
memory[0xFFFC] = 0x00;
|
|
1282
|
+
memory[0xFFFD] = 0x80;
|
|
1283
|
+
memory[0x1234] = 0xAA; // 10101010
|
|
1284
|
+
cpu.reset();
|
|
1285
|
+
cpu.a = 0x55; // 01010101
|
|
1286
|
+
cpu.step();
|
|
1287
|
+
expect(memory[0x1234]).toBe(0xAA); // No bits cleared since no overlap
|
|
1288
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
1289
|
+
});
|
|
1290
|
+
});
|
|
1291
|
+
describe('TSB - Test and Set Bits', () => {
|
|
1292
|
+
test('TSB zero page should set bits and test', () => {
|
|
1293
|
+
memory[0x8000] = 0x04; // TSB $10
|
|
1294
|
+
memory[0x8001] = 0x10;
|
|
1295
|
+
memory[0xFFFC] = 0x00;
|
|
1296
|
+
memory[0xFFFD] = 0x80;
|
|
1297
|
+
memory[0x0010] = 0xF0;
|
|
1298
|
+
cpu.reset();
|
|
1299
|
+
cpu.a = 0x0F;
|
|
1300
|
+
cpu.step();
|
|
1301
|
+
expect(memory[0x0010]).toBe(0xFF); // All bits set
|
|
1302
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z); // Was zero because A & original memory was zero
|
|
1303
|
+
});
|
|
1304
|
+
test('TSB should not set zero flag when A & memory is non-zero', () => {
|
|
1305
|
+
memory[0x8000] = 0x04; // TSB $10
|
|
1306
|
+
memory[0x8001] = 0x10;
|
|
1307
|
+
memory[0xFFFC] = 0x00;
|
|
1308
|
+
memory[0xFFFD] = 0x80;
|
|
1309
|
+
memory[0x0010] = 0xFF;
|
|
1310
|
+
cpu.reset();
|
|
1311
|
+
cpu.a = 0x0F;
|
|
1312
|
+
cpu.step();
|
|
1313
|
+
expect(memory[0x0010]).toBe(0xFF);
|
|
1314
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(0);
|
|
1315
|
+
});
|
|
1316
|
+
test('TSB absolute should work', () => {
|
|
1317
|
+
memory[0x8000] = 0x0C; // TSB $1234
|
|
1318
|
+
memory[0x8001] = 0x34;
|
|
1319
|
+
memory[0x8002] = 0x12;
|
|
1320
|
+
memory[0xFFFC] = 0x00;
|
|
1321
|
+
memory[0xFFFD] = 0x80;
|
|
1322
|
+
memory[0x1234] = 0x00;
|
|
1323
|
+
cpu.reset();
|
|
1324
|
+
cpu.a = 0x42;
|
|
1325
|
+
cpu.step();
|
|
1326
|
+
expect(memory[0x1234]).toBe(0x42);
|
|
1327
|
+
});
|
|
1328
|
+
});
|
|
1329
|
+
describe('BIT Immediate', () => {
|
|
1330
|
+
test('BIT immediate should test bits without affecting N and V flags', () => {
|
|
1331
|
+
memory[0x8000] = 0x89; // BIT #$42
|
|
1332
|
+
memory[0x8001] = 0x42;
|
|
1333
|
+
memory[0xFFFC] = 0x00;
|
|
1334
|
+
memory[0xFFFD] = 0x80;
|
|
1335
|
+
cpu.reset();
|
|
1336
|
+
cpu.a = 0x42;
|
|
1337
|
+
cpu.step();
|
|
1338
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(0); // Not zero because 0x42 & 0x42 = 0x42
|
|
1339
|
+
});
|
|
1340
|
+
test('BIT immediate should set zero flag when result is zero', () => {
|
|
1341
|
+
memory[0x8000] = 0x89; // BIT #$0F
|
|
1342
|
+
memory[0x8001] = 0x0F;
|
|
1343
|
+
memory[0xFFFC] = 0x00;
|
|
1344
|
+
memory[0xFFFD] = 0x80;
|
|
1345
|
+
cpu.reset();
|
|
1346
|
+
cpu.a = 0xF0;
|
|
1347
|
+
cpu.step();
|
|
1348
|
+
expect(cpu.st & CPU_1.CPU.Z).toBe(CPU_1.CPU.Z);
|
|
1349
|
+
});
|
|
1350
|
+
});
|
|
1351
|
+
describe('JMP (addr,X) - Indexed Indirect', () => {
|
|
1352
|
+
test('JMP (addr,X) should jump to address in table indexed by X', () => {
|
|
1353
|
+
memory[0x8000] = 0x7C; // JMP ($1000,X)
|
|
1354
|
+
memory[0x8001] = 0x00;
|
|
1355
|
+
memory[0x8002] = 0x10;
|
|
1356
|
+
memory[0xFFFC] = 0x00;
|
|
1357
|
+
memory[0xFFFD] = 0x80;
|
|
1358
|
+
// Jump table at $1005 (when X=5) contains address $2000
|
|
1359
|
+
memory[0x1005] = 0x00;
|
|
1360
|
+
memory[0x1006] = 0x20;
|
|
1361
|
+
cpu.reset();
|
|
1362
|
+
cpu.x = 0x05;
|
|
1363
|
+
cpu.step();
|
|
1364
|
+
expect(cpu.pc).toBe(0x2000);
|
|
1365
|
+
});
|
|
1366
|
+
test('JMP (addr,X) should work with different X values', () => {
|
|
1367
|
+
memory[0x8000] = 0x7C; // JMP ($1000,X)
|
|
1368
|
+
memory[0x8001] = 0x00;
|
|
1369
|
+
memory[0x8002] = 0x10;
|
|
1370
|
+
memory[0xFFFC] = 0x00;
|
|
1371
|
+
memory[0xFFFD] = 0x80;
|
|
1372
|
+
memory[0x100A] = 0x50;
|
|
1373
|
+
memory[0x100B] = 0x30;
|
|
1374
|
+
cpu.reset();
|
|
1375
|
+
cpu.x = 0x0A;
|
|
1376
|
+
cpu.step();
|
|
1377
|
+
expect(cpu.pc).toBe(0x3050);
|
|
1378
|
+
});
|
|
1379
|
+
});
|
|
1380
|
+
});
|
|
1381
|
+
describe('WDC 65C02 Instructions', () => {
|
|
1382
|
+
describe('RMB/SMB - Reset/Set Memory Bit', () => {
|
|
1383
|
+
test('RMB0 should clear bit 0', () => {
|
|
1384
|
+
memory[0x8000] = 0x07; // RMB0 $10
|
|
1385
|
+
memory[0x8001] = 0x10;
|
|
1386
|
+
memory[0xFFFC] = 0x00;
|
|
1387
|
+
memory[0xFFFD] = 0x80;
|
|
1388
|
+
memory[0x0010] = 0xFF;
|
|
1389
|
+
cpu.reset();
|
|
1390
|
+
cpu.step();
|
|
1391
|
+
expect(memory[0x0010]).toBe(0xFE); // 11111110
|
|
1392
|
+
});
|
|
1393
|
+
test('RMB7 should clear bit 7', () => {
|
|
1394
|
+
memory[0x8000] = 0x77; // RMB7 $10
|
|
1395
|
+
memory[0x8001] = 0x10;
|
|
1396
|
+
memory[0xFFFC] = 0x00;
|
|
1397
|
+
memory[0xFFFD] = 0x80;
|
|
1398
|
+
memory[0x0010] = 0xFF;
|
|
1399
|
+
cpu.reset();
|
|
1400
|
+
cpu.step();
|
|
1401
|
+
expect(memory[0x0010]).toBe(0x7F); // 01111111
|
|
1402
|
+
});
|
|
1403
|
+
test('SMB0 should set bit 0', () => {
|
|
1404
|
+
memory[0x8000] = 0x87; // SMB0 $10
|
|
1405
|
+
memory[0x8001] = 0x10;
|
|
1406
|
+
memory[0xFFFC] = 0x00;
|
|
1407
|
+
memory[0xFFFD] = 0x80;
|
|
1408
|
+
memory[0x0010] = 0x00;
|
|
1409
|
+
cpu.reset();
|
|
1410
|
+
cpu.step();
|
|
1411
|
+
expect(memory[0x0010]).toBe(0x01);
|
|
1412
|
+
});
|
|
1413
|
+
test('SMB7 should set bit 7', () => {
|
|
1414
|
+
memory[0x8000] = 0xF7; // SMB7 $10
|
|
1415
|
+
memory[0x8001] = 0x10;
|
|
1416
|
+
memory[0xFFFC] = 0x00;
|
|
1417
|
+
memory[0xFFFD] = 0x80;
|
|
1418
|
+
memory[0x0010] = 0x00;
|
|
1419
|
+
cpu.reset();
|
|
1420
|
+
cpu.step();
|
|
1421
|
+
expect(memory[0x0010]).toBe(0x80);
|
|
1422
|
+
});
|
|
1423
|
+
test('SMB3 should set bit 3', () => {
|
|
1424
|
+
memory[0x8000] = 0xB7; // SMB3 $10
|
|
1425
|
+
memory[0x8001] = 0x10;
|
|
1426
|
+
memory[0xFFFC] = 0x00;
|
|
1427
|
+
memory[0xFFFD] = 0x80;
|
|
1428
|
+
memory[0x0010] = 0x00;
|
|
1429
|
+
cpu.reset();
|
|
1430
|
+
cpu.step();
|
|
1431
|
+
expect(memory[0x0010]).toBe(0x08); // 00001000
|
|
1432
|
+
});
|
|
1433
|
+
test('RMB4 should clear bit 4', () => {
|
|
1434
|
+
memory[0x8000] = 0x47; // RMB4 $10
|
|
1435
|
+
memory[0x8001] = 0x10;
|
|
1436
|
+
memory[0xFFFC] = 0x00;
|
|
1437
|
+
memory[0xFFFD] = 0x80;
|
|
1438
|
+
memory[0x0010] = 0xFF;
|
|
1439
|
+
cpu.reset();
|
|
1440
|
+
cpu.step();
|
|
1441
|
+
expect(memory[0x0010]).toBe(0xEF); // 11101111
|
|
1442
|
+
});
|
|
1443
|
+
});
|
|
1444
|
+
describe('BBR/BBS - Branch on Bit Reset/Set', () => {
|
|
1445
|
+
test('BBR0 should branch when bit 0 is clear', () => {
|
|
1446
|
+
memory[0x8000] = 0x0F; // BBR0 $10, +4
|
|
1447
|
+
memory[0x8001] = 0x10;
|
|
1448
|
+
memory[0x8002] = 0x04;
|
|
1449
|
+
memory[0xFFFC] = 0x00;
|
|
1450
|
+
memory[0xFFFD] = 0x80;
|
|
1451
|
+
memory[0x0010] = 0xFE; // Bit 0 is clear
|
|
1452
|
+
cpu.reset();
|
|
1453
|
+
cpu.step();
|
|
1454
|
+
expect(cpu.pc).toBe(0x8007); // Branched forward 4 bytes
|
|
1455
|
+
});
|
|
1456
|
+
test('BBR0 should not branch when bit 0 is set', () => {
|
|
1457
|
+
memory[0x8000] = 0x0F; // BBR0 $10, +4
|
|
1458
|
+
memory[0x8001] = 0x10;
|
|
1459
|
+
memory[0x8002] = 0x04;
|
|
1460
|
+
memory[0xFFFC] = 0x00;
|
|
1461
|
+
memory[0xFFFD] = 0x80;
|
|
1462
|
+
memory[0x0010] = 0x01; // Bit 0 is set
|
|
1463
|
+
cpu.reset();
|
|
1464
|
+
cpu.step();
|
|
1465
|
+
expect(cpu.pc).toBe(0x8003); // Did not branch
|
|
1466
|
+
});
|
|
1467
|
+
test('BBS7 should branch when bit 7 is set', () => {
|
|
1468
|
+
memory[0x8000] = 0xFF; // BBS7 $10, +4
|
|
1469
|
+
memory[0x8001] = 0x10;
|
|
1470
|
+
memory[0x8002] = 0x04;
|
|
1471
|
+
memory[0xFFFC] = 0x00;
|
|
1472
|
+
memory[0xFFFD] = 0x80;
|
|
1473
|
+
memory[0x0010] = 0x80; // Bit 7 is set
|
|
1474
|
+
cpu.reset();
|
|
1475
|
+
cpu.step();
|
|
1476
|
+
expect(cpu.pc).toBe(0x8007); // Branched
|
|
1477
|
+
});
|
|
1478
|
+
test('BBS7 should not branch when bit 7 is clear', () => {
|
|
1479
|
+
memory[0x8000] = 0xFF; // BBS7 $10, +4
|
|
1480
|
+
memory[0x8001] = 0x10;
|
|
1481
|
+
memory[0x8002] = 0x04;
|
|
1482
|
+
memory[0xFFFC] = 0x00;
|
|
1483
|
+
memory[0xFFFD] = 0x80;
|
|
1484
|
+
memory[0x0010] = 0x7F; // Bit 7 is clear
|
|
1485
|
+
cpu.reset();
|
|
1486
|
+
cpu.step();
|
|
1487
|
+
expect(cpu.pc).toBe(0x8003); // Did not branch
|
|
1488
|
+
});
|
|
1489
|
+
test('BBS3 should branch when bit 3 is set', () => {
|
|
1490
|
+
memory[0x8000] = 0xBF; // BBS3 $10, +2
|
|
1491
|
+
memory[0x8001] = 0x10;
|
|
1492
|
+
memory[0x8002] = 0x02;
|
|
1493
|
+
memory[0xFFFC] = 0x00;
|
|
1494
|
+
memory[0xFFFD] = 0x80;
|
|
1495
|
+
memory[0x0010] = 0x08; // Bit 3 is set
|
|
1496
|
+
cpu.reset();
|
|
1497
|
+
cpu.step();
|
|
1498
|
+
expect(cpu.pc).toBe(0x8005); // Branched
|
|
1499
|
+
});
|
|
1500
|
+
test('BBR4 should branch backward when bit 4 is clear', () => {
|
|
1501
|
+
memory[0x8000] = 0xA9; // LDA #$55
|
|
1502
|
+
memory[0x8001] = 0x55;
|
|
1503
|
+
memory[0x8002] = 0x4F; // BBR4 $10, -5
|
|
1504
|
+
memory[0x8003] = 0x10;
|
|
1505
|
+
memory[0x8004] = 0xFB; // -5 in signed byte
|
|
1506
|
+
memory[0xFFFC] = 0x00;
|
|
1507
|
+
memory[0xFFFD] = 0x80;
|
|
1508
|
+
memory[0x0010] = 0xEF; // Bit 4 is clear
|
|
1509
|
+
cpu.reset();
|
|
1510
|
+
cpu.step(); // LDA
|
|
1511
|
+
cpu.step(); // BBR4
|
|
1512
|
+
expect(cpu.pc).toBe(0x8000); // Branched back
|
|
1513
|
+
});
|
|
1514
|
+
});
|
|
1515
|
+
describe('WAI - Wait for Interrupt', () => {
|
|
1516
|
+
test('WAI should execute and consume cycles', () => {
|
|
1517
|
+
memory[0x8000] = 0xCB; // WAI
|
|
1518
|
+
memory[0xFFFC] = 0x00;
|
|
1519
|
+
memory[0xFFFD] = 0x80;
|
|
1520
|
+
cpu.reset();
|
|
1521
|
+
const initialCycles = cpu.cycles;
|
|
1522
|
+
cpu.step();
|
|
1523
|
+
// WAI executes and consumes cycles (instruction completes)
|
|
1524
|
+
expect(cpu.cycles).toBeGreaterThan(initialCycles);
|
|
1525
|
+
expect(cpu.pc).toBe(0x8001); // PC advanced past WAI
|
|
1526
|
+
});
|
|
1527
|
+
});
|
|
1528
|
+
describe('STP - Stop the Processor', () => {
|
|
1529
|
+
test('STP should execute and consume cycles', () => {
|
|
1530
|
+
memory[0x8000] = 0xDB; // STP
|
|
1531
|
+
memory[0xFFFC] = 0x00;
|
|
1532
|
+
memory[0xFFFD] = 0x80;
|
|
1533
|
+
cpu.reset();
|
|
1534
|
+
const initialCycles = cpu.cycles;
|
|
1535
|
+
cpu.step();
|
|
1536
|
+
// STP executes and consumes cycles (instruction completes)
|
|
1537
|
+
expect(cpu.cycles).toBeGreaterThan(initialCycles);
|
|
1538
|
+
expect(cpu.pc).toBe(0x8001); // PC advanced past STP
|
|
1539
|
+
});
|
|
1540
|
+
});
|
|
1541
|
+
});
|
|
1542
|
+
describe('Complex Programs', () => {
|
|
1543
|
+
test('should execute a simple loop correctly', () => {
|
|
1544
|
+
// Loop that adds 1 to accumulator 5 times
|
|
1545
|
+
memory[0x8000] = 0xA9; // LDA #$00
|
|
1546
|
+
memory[0x8001] = 0x00;
|
|
1547
|
+
memory[0x8002] = 0xA2; // LDX #$05
|
|
1548
|
+
memory[0x8003] = 0x05;
|
|
1549
|
+
memory[0x8004] = 0x18; // loop: CLC
|
|
1550
|
+
memory[0x8005] = 0x69; // ADC #$01
|
|
1551
|
+
memory[0x8006] = 0x01;
|
|
1552
|
+
memory[0x8007] = 0xCA; // DEX
|
|
1553
|
+
memory[0x8008] = 0xD0; // BNE loop
|
|
1554
|
+
memory[0x8009] = 0xFA; // -6
|
|
1555
|
+
memory[0xFFFC] = 0x00;
|
|
1556
|
+
memory[0xFFFD] = 0x80;
|
|
1557
|
+
cpu.reset();
|
|
1558
|
+
cpu.step(); // LDA
|
|
1559
|
+
cpu.step(); // LDX
|
|
1560
|
+
// Execute loop 5 times
|
|
1561
|
+
for (let i = 0; i < 5; i++) {
|
|
1562
|
+
cpu.step(); // CLC
|
|
1563
|
+
cpu.step(); // ADC
|
|
1564
|
+
cpu.step(); // DEX
|
|
1565
|
+
cpu.step(); // BNE (or fall through on last iteration)
|
|
1566
|
+
}
|
|
1567
|
+
expect(cpu.a).toBe(0x05);
|
|
1568
|
+
expect(cpu.x).toBe(0x00);
|
|
1569
|
+
});
|
|
1570
|
+
test('should use 65C02 instructions in a program', () => {
|
|
1571
|
+
// Use STZ and BRA in a program
|
|
1572
|
+
memory[0x8000] = 0x9C; // STZ $1234 - Clear memory location
|
|
1573
|
+
memory[0x8001] = 0x34;
|
|
1574
|
+
memory[0x8002] = 0x12;
|
|
1575
|
+
memory[0x8003] = 0xA9; // LDA #$42
|
|
1576
|
+
memory[0x8004] = 0x42;
|
|
1577
|
+
memory[0x8005] = 0x8D; // STA $1234
|
|
1578
|
+
memory[0x8006] = 0x34;
|
|
1579
|
+
memory[0x8007] = 0x12;
|
|
1580
|
+
memory[0x8008] = 0x80; // BRA +2
|
|
1581
|
+
memory[0x8009] = 0x02;
|
|
1582
|
+
memory[0x800A] = 0xEA; // NOP (skipped)
|
|
1583
|
+
memory[0x800B] = 0xEA; // NOP (skipped)
|
|
1584
|
+
memory[0x800C] = 0xAD; // LDA $1234
|
|
1585
|
+
memory[0x800D] = 0x34;
|
|
1586
|
+
memory[0x800E] = 0x12;
|
|
1587
|
+
memory[0xFFFC] = 0x00;
|
|
1588
|
+
memory[0xFFFD] = 0x80;
|
|
1589
|
+
memory[0x1234] = 0xFF; // Initially set
|
|
1590
|
+
cpu.reset();
|
|
1591
|
+
cpu.step(); // STZ
|
|
1592
|
+
expect(memory[0x1234]).toBe(0x00);
|
|
1593
|
+
cpu.step(); // LDA
|
|
1594
|
+
cpu.step(); // STA
|
|
1595
|
+
expect(memory[0x1234]).toBe(0x42);
|
|
1596
|
+
cpu.step(); // BRA
|
|
1597
|
+
expect(cpu.pc).toBe(0x800C);
|
|
1598
|
+
cpu.step(); // LDA
|
|
1599
|
+
expect(cpu.a).toBe(0x42);
|
|
1600
|
+
});
|
|
1601
|
+
test('should use WDC 65C02 bit manipulation in a program', () => {
|
|
1602
|
+
// Set bit 3, then test it and branch
|
|
1603
|
+
memory[0x8000] = 0xB7; // SMB3 $10
|
|
1604
|
+
memory[0x8001] = 0x10;
|
|
1605
|
+
memory[0x8002] = 0xBF; // BBS3 $10, +2
|
|
1606
|
+
memory[0x8003] = 0x10;
|
|
1607
|
+
memory[0x8004] = 0x02;
|
|
1608
|
+
memory[0x8005] = 0xA9; // LDA #$00 (should be skipped)
|
|
1609
|
+
memory[0x8006] = 0x00;
|
|
1610
|
+
memory[0x8007] = 0xA9; // LDA #$FF (should execute)
|
|
1611
|
+
memory[0x8008] = 0xFF;
|
|
1612
|
+
memory[0xFFFC] = 0x00;
|
|
1613
|
+
memory[0xFFFD] = 0x80;
|
|
1614
|
+
memory[0x0010] = 0x00;
|
|
1615
|
+
cpu.reset();
|
|
1616
|
+
cpu.step(); // SMB3
|
|
1617
|
+
expect(memory[0x0010]).toBe(0x08);
|
|
1618
|
+
cpu.step(); // BBS3
|
|
1619
|
+
expect(cpu.pc).toBe(0x8007); // Branched (0x8005 + 2)
|
|
1620
|
+
cpu.step(); // LDA #$FF
|
|
1621
|
+
expect(cpu.a).toBe(0xFF);
|
|
1622
|
+
});
|
|
1623
|
+
});
|
|
1624
|
+
});
|
|
1625
|
+
// TODO: Move to using https://github.com/SingleStepTests/65x02/tree/main/6502 test suite for more comprehensive testing of CPU behavior
|
|
1626
|
+
//# sourceMappingURL=CPU.test.js.map
|