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