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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/components/CPU.js +1170 -0
  4. package/dist/components/CPU.js.map +1 -0
  5. package/dist/components/Cart.js +23 -0
  6. package/dist/components/Cart.js.map +1 -0
  7. package/dist/components/IO/Empty.js +19 -0
  8. package/dist/components/IO/Empty.js.map +1 -0
  9. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
  10. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
  11. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
  12. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
  13. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
  14. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
  15. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/GPIOCard.js +597 -0
  18. package/dist/components/IO/GPIOCard.js.map +1 -0
  19. package/dist/components/IO/InputBoard.js +19 -0
  20. package/dist/components/IO/InputBoard.js.map +1 -0
  21. package/dist/components/IO/LCDCard.js +19 -0
  22. package/dist/components/IO/LCDCard.js.map +1 -0
  23. package/dist/components/IO/RAMCard.js +63 -0
  24. package/dist/components/IO/RAMCard.js.map +1 -0
  25. package/dist/components/IO/RTCCard.js +483 -0
  26. package/dist/components/IO/RTCCard.js.map +1 -0
  27. package/dist/components/IO/SerialCard.js +282 -0
  28. package/dist/components/IO/SerialCard.js.map +1 -0
  29. package/dist/components/IO/SoundCard.js +620 -0
  30. package/dist/components/IO/SoundCard.js.map +1 -0
  31. package/dist/components/IO/StorageCard.js +428 -0
  32. package/dist/components/IO/StorageCard.js.map +1 -0
  33. package/dist/components/IO/VGACard.js +9 -0
  34. package/dist/components/IO/VGACard.js.map +1 -0
  35. package/dist/components/IO/VideoCard.js +623 -0
  36. package/dist/components/IO/VideoCard.js.map +1 -0
  37. package/dist/components/IO.js +3 -0
  38. package/dist/components/IO.js.map +1 -0
  39. package/dist/components/Machine.js +310 -0
  40. package/dist/components/Machine.js.map +1 -0
  41. package/dist/components/RAM.js +24 -0
  42. package/dist/components/RAM.js.map +1 -0
  43. package/dist/components/ROM.js +23 -0
  44. package/dist/components/ROM.js.map +1 -0
  45. package/dist/index.js +441 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/tests/CPU.test.js +1626 -0
  48. package/dist/tests/CPU.test.js.map +1 -0
  49. package/dist/tests/Cart.test.js +119 -0
  50. package/dist/tests/Cart.test.js.map +1 -0
  51. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
  52. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
  53. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
  54. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
  55. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
  56. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
  57. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
  58. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
  59. package/dist/tests/IO/GPIOCard.test.js +503 -0
  60. package/dist/tests/IO/GPIOCard.test.js.map +1 -0
  61. package/dist/tests/IO/RAMCard.test.js +229 -0
  62. package/dist/tests/IO/RAMCard.test.js.map +1 -0
  63. package/dist/tests/IO/RTCCard.test.js +177 -0
  64. package/dist/tests/IO/RTCCard.test.js.map +1 -0
  65. package/dist/tests/IO/SerialCard.test.js +423 -0
  66. package/dist/tests/IO/SerialCard.test.js.map +1 -0
  67. package/dist/tests/IO/SoundCard.test.js +528 -0
  68. package/dist/tests/IO/SoundCard.test.js.map +1 -0
  69. package/dist/tests/IO/StorageCard.test.js +647 -0
  70. package/dist/tests/IO/StorageCard.test.js.map +1 -0
  71. package/dist/tests/IO/VideoCard.test.js +549 -0
  72. package/dist/tests/IO/VideoCard.test.js.map +1 -0
  73. package/dist/tests/Machine.test.js +383 -0
  74. package/dist/tests/Machine.test.js.map +1 -0
  75. package/dist/tests/RAM.test.js +160 -0
  76. package/dist/tests/RAM.test.js.map +1 -0
  77. package/dist/tests/ROM.test.js +123 -0
  78. package/dist/tests/ROM.test.js.map +1 -0
  79. package/jest.config.cjs +9 -0
  80. package/package.json +43 -0
  81. package/src/components/CPU.ts +1371 -0
  82. package/src/components/Cart.ts +20 -0
  83. package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
  84. package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
  85. package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
  86. package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
  87. package/src/components/IO/GPIOCard.ts +677 -0
  88. package/src/components/IO/RAMCard.ts +68 -0
  89. package/src/components/IO/RTCCard.ts +518 -0
  90. package/src/components/IO/SerialCard.ts +335 -0
  91. package/src/components/IO/SoundCard.ts +711 -0
  92. package/src/components/IO/StorageCard.ts +473 -0
  93. package/src/components/IO/VideoCard.ts +730 -0
  94. package/src/components/IO.ts +11 -0
  95. package/src/components/Machine.ts +364 -0
  96. package/src/components/RAM.ts +23 -0
  97. package/src/components/ROM.ts +19 -0
  98. package/src/index.ts +474 -0
  99. package/src/tests/CPU.test.ts +2045 -0
  100. package/src/tests/Cart.test.ts +149 -0
  101. package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
  102. package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
  103. package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
  104. package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
  105. package/src/tests/IO/GPIOCard.test.ts +644 -0
  106. package/src/tests/IO/RAMCard.test.ts +284 -0
  107. package/src/tests/IO/RTCCard.test.ts +222 -0
  108. package/src/tests/IO/SerialCard.test.ts +530 -0
  109. package/src/tests/IO/SoundCard.test.ts +659 -0
  110. package/src/tests/IO/StorageCard.test.ts +787 -0
  111. package/src/tests/IO/VideoCard.test.ts +668 -0
  112. package/src/tests/Machine.test.ts +437 -0
  113. package/src/tests/RAM.test.ts +196 -0
  114. package/src/tests/ROM.test.ts +154 -0
  115. 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