forked from ulfalizer/nesalizer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcpu.cpp
More file actions
1781 lines (1460 loc) · 59.4 KB
/
cpu.cpp
File metadata and controls
1781 lines (1460 loc) · 59.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "common.h"
#include "apu.h"
#include "controller.h"
#include "cpu.h"
#include "mapper.h"
#include "ppu.h"
#ifdef RUN_TESTS
# include "test.h"
#endif
#include "rom.h"
#include "sdl_backend.h"
#include "timing.h"
#include <readline/history.h>
#include <readline/readline.h>
// Set true to break out of the CPU emulation loop
bool end_emulation;
#ifdef RUN_TESTS
// The system is soft-reset when this goes from 1 to 0. Used by test ROMs.
static unsigned ticks_till_reset;
// Set true to perform the reset at the next instruction boundary
static bool pending_reset;
#endif
// RAM, registers, status flags, and misc. variables {{{
static uint8_t ram[0x800];
// Possible optimization: Making some of the variables a natural size for the
// implementation architecture might be faster. CPU emulation is already
// relatively speedy though, and we wouldn't get automatic wrapping.
// Registers
static uint16_t pc;
static uint8_t a, s, x, y;
// Status flags
// The value the zero and negative flags are based on. Doing it like this turns
// the setting of these flags into a simple assignment in most cases.
//
// - !(zero_negative_flag & 0xFF) means the zero flag is set.
// - zero_negative_flag & 0x180 means the negative flag is set.
//
// Having zero_negative_flag & 0x100 also indicate that the negative flag is
// set allows the two flags to be set separately, which is required by the BIT
// instruction and when pulling flags from the stack.
static unsigned zero_negative_flag;
static bool carry_flag;
static bool irq_disable_flag;
static bool decimal_flag;
static bool overflow_flag;
// The byte after the opcode byte. Always fetched, so factoring out the fetch
// saves logic.
static uint8_t op_1;
// Used by the APU and DMA circuitry, parts of which tick at half the CPU
// frequency. Whether the initial tick is high or low seems to be random. The
// name apu_clk1 is from Visual 2A03.
static bool apu_clk1_is_high;
// Current CPU read/write and DMA state. Needed to get the timing for APU DMC
// sample loading right (tested by the sprdma_and_dmc_dma tests).
bool cpu_is_reading;
OAM_DMA_state oam_dma_state;
// }}}
// PPU and APU interface {{{
void tick() {
tick_ppu();
tick_ppu();
tick_ppu();
apu_clk1_is_high = !apu_clk1_is_high;
tick_apu(apu_clk1_is_high);
#ifdef RUN_TESTS
if (ticks_till_reset > 0 && --ticks_till_reset == 0)
pending_reset = true;
#endif
}
// Optimization for read/write ticks without visible side effects
static void read_tick() {
cpu_is_reading = true;
tick();
}
static void write_tick() {
cpu_is_reading = false;
tick();
}
// }}}
// Reading and writing {{{
// Forward declaration
static void poll_for_interrupt();
// Currently only used to implement open bus reads
uint8_t cpu_data_bus;
static uint8_t read(uint16_t addr) {
read_tick();
uint8_t res;
// Possible optimization: Checking for the most commonly read areas first
// might be faster here
switch (addr) {
case 0x0000 ... 0x1FFF: res = ram[addr & 0x07FF]; break;
case 0x2000 ... 0x3FFF: res = read_ppu_reg(addr & 7); break;
case 0x4015: res = read_apu_status(); break;
case 0x4016: res = read_controller(0); break;
case 0x4017: res = read_controller(1); break;
case 0x6000 ... 0x7FFF:
res = prg_ram ? prg_ram[addr & 0x1FFF] : cpu_data_bus;
break;
case 0x8000 ... 0xFFFF: res = PRG(addr); break;
default: res = cpu_data_bus; break;
}
cpu_data_bus = res;
return res;
}
static void write(uint8_t value, uint16_t addr) {
write_tick();
cpu_data_bus = value;
switch (addr) {
case 0x0000 ... 0x1FFF: ram[addr & 0x7FF] = value; break;
case 0x2000 ... 0x3FFF: write_ppu_reg(value, addr & 0x0007); break;
// Pulse 1
case 0x4000: write_pulse_reg_0(0, value); break;
case 0x4001: write_pulse_reg_1(0, value); break;
case 0x4002: write_pulse_reg_2(0, value); break;
case 0x4003: write_pulse_reg_3(0, value); break;
// Pulse 2
case 0x4004: write_pulse_reg_0(1, value); break;
case 0x4005: write_pulse_reg_1(1, value); break;
case 0x4006: write_pulse_reg_2(1, value); break;
case 0x4007: write_pulse_reg_3(1, value); break;
// Triangle
case 0x4008: write_triangle_reg_0(value); break;
case 0x400A: write_triangle_reg_1(value); break;
case 0x400B: write_triangle_reg_2(value); break;
// Noise
case 0x400C: write_noise_reg_0(value); break;
case 0x400E: write_noise_reg_1(value); break;
case 0x400F: write_noise_reg_2(value); break;
// DMC
case 0x4010: write_dmc_reg_0(value); break;
case 0x4011: write_dmc_reg_1(value); break;
case 0x4012: write_dmc_reg_2(value); break;
case 0x4013: write_dmc_reg_3(value); break;
// OAM DMA
case 0x4014:
{
LOG_PPU_MEM("Sprite DMA: Load 256 bytes from $%04x\n", 0x100 * value);
// We get either WDTTT... or WDDTTT... where W is the write cycle, D a
// dummy cycle, and T a transfer cycle (there's 512 of them). The extra
// dummy cycle occurs if the write cycle has apu_clk1 low.
// The current OAM DMA state influences the timing for APU DMC sample
// loads, so we need to keep track of it
oam_dma_state = OAM_DMA_IN_PROGRESS;
// Dummy cycles
if (!apu_clk1_is_high) tick();
tick();
unsigned const start_addr = 0x100*value;
for (size_t i = 0; i < 254; ++i) {
// Do it like this to get open bus right. Could be that it's not
// visible in any way though.
cpu_data_bus = read(start_addr + i);
tick();
write_oam_data_reg(cpu_data_bus);
}
cpu_data_bus = read(start_addr + 254);
oam_dma_state = OAM_DMA_IN_PROGRESS_3RD_TO_LAST_TICK;
tick();
write_oam_data_reg(cpu_data_bus);
oam_dma_state = OAM_DMA_IN_PROGRESS;
cpu_data_bus = read(start_addr + 255);
oam_dma_state = OAM_DMA_IN_PROGRESS_LAST_TICK;
tick();
write_oam_data_reg(cpu_data_bus);
oam_dma_state = OAM_DMA_NOT_IN_PROGRESS;
break;
}
case 0x4015: write_apu_status(value); break;
case 0x4016: write_controller_strobe(value & 1); break;
case 0x4017: write_frame_counter(value, apu_clk1_is_high); break;
case 0x6000 ... 0x7FFF:
#ifdef RUN_TESTS
// blargg's test ROMs write the test status to $6000 and a
// corresponding text string to $6004
if (addr == 0x6000) {
if (value < 0x80)
report_status_and_end_test(value, (char*)prg_ram + 4);
else if (value == 0x81)
// Wait 150 ms before resetting
ticks_till_reset = 0.15*ntsc_cpu_clock_rate;
}
#endif
// PRG RAM/SRAM/WRAM
if (prg_ram) prg_ram[addr & 0x1FFF] = value;
break;
case 0x8000 ... 0xFFFF:
write_mapper(value, addr);
break;
}
}
// }}}
// Core instruction logic, reused for different addressing modes {{{
// Forward declarations
static void and_(uint8_t);
static uint8_t lsr(uint8_t);
static void sbc(uint8_t);
static void adc(uint8_t arg) {
unsigned const sum = a + arg + carry_flag;
carry_flag = sum > 0xFF;
// The overflow flag is set when the sign of the addends is the same and
// differs from the sign of the sum
overflow_flag = ~(a ^ arg) & (a ^ sum) & 0x80;
zero_negative_flag = a /* (uint8_t) */ = sum;
}
// Unofficial
static void alr(uint8_t arg) {
a = lsr(a & arg);
}
// Unofficial
static void anc(uint8_t arg) {
and_(arg);
carry_flag = zero_negative_flag & 0x180;
}
// 'and' is an operator in C++, so we need the underscore
static void and_(uint8_t arg) {
zero_negative_flag = a = a & arg;
}
// Unofficial
static void arr(uint8_t arg) {
zero_negative_flag = a = (carry_flag << 7) | ((a & arg) >> 1);
carry_flag = a & 0x40;
overflow_flag = (a ^ (a << 1)) & 0x40;
}
static uint8_t asl(uint8_t arg) {
carry_flag = arg & 0x80;
return zero_negative_flag = (arg << 1) & 0xFF;
}
// Unofficial
static void atx(uint8_t arg) {
// Assume '(A | 0xFF) & arg' is calculated, which is the same as just 'arg':
// http://forums.nesdev.com/viewtopic.php?t=3831
zero_negative_flag = x = a = arg;
}
// Unofficial
static void axs(uint8_t arg) {
carry_flag = (a & x) >= arg;
zero_negative_flag = x /* (uint8_t) */ = (a & x) - arg;
}
static void bit(uint8_t arg) {
overflow_flag = arg & 0x40;
// Set the zero and negative flags separately by using bit 8 of
// zero_negative_flag for the negative flag
zero_negative_flag = ((arg << 1) & 0x100) | (a & arg);
}
// CMP, CPX, CPY
static void comp(uint8_t reg, uint8_t arg) {
carry_flag = reg >= arg;
zero_negative_flag = (reg - arg) & 0xFF;
}
// Unofficial
static uint8_t dcp(uint8_t arg) {
comp(a, --arg);
return arg;
}
static uint8_t dec(uint8_t arg) {
// Works without & 0xFF here since bit 8 (the additional negative flag bit)
// will only get set if arg is 0, in which case bit 7 gets set as well
return zero_negative_flag = arg - 1;
}
static void eor(uint8_t arg) {
zero_negative_flag = (a ^= arg);
}
static uint8_t inc(uint8_t arg) {
return zero_negative_flag = (arg + 1) & 0xFF;
}
// Unofficial
static void las(uint8_t arg) {
zero_negative_flag = a = x = s = arg & s;
}
// Unofficial
static void lax(uint8_t arg) {
zero_negative_flag = a = x = arg;
}
static void lda(uint8_t arg) { zero_negative_flag = a = arg; }
static void ldx(uint8_t arg) { zero_negative_flag = x = arg; }
static void ldy(uint8_t arg) { zero_negative_flag = y = arg; }
static uint8_t lsr(uint8_t arg) {
carry_flag = arg & 1;
return zero_negative_flag = arg >> 1;
}
static void ora(uint8_t arg) {
zero_negative_flag = (a |= arg);
}
// Unofficial
static uint8_t isc(uint8_t arg) {
sbc(++arg);
return arg;
}
// Unofficial
static uint8_t rla(uint8_t arg) {
bool const new_carry_flag = arg & 0x80;
and_(arg = (arg << 1) | carry_flag);
carry_flag = new_carry_flag;
return arg;
}
static uint8_t rol(uint8_t arg) {
bool const new_carry_flag = arg & 0x80;
zero_negative_flag = arg /* (uint8_t) */ = (arg << 1) | carry_flag;
carry_flag = new_carry_flag;
return arg;
}
static uint8_t ror(uint8_t arg) {
bool const new_carry_flag = arg & 1;
zero_negative_flag = arg = (carry_flag << 7) | (arg >> 1);
carry_flag = new_carry_flag;
return arg;
}
// Unofficial
static uint8_t rra(uint8_t arg) {
bool const new_carry_flag = arg & 1;
arg = (carry_flag << 7) | (arg >> 1);
carry_flag = new_carry_flag;
adc(arg);
return arg;
}
static void sbc(uint8_t arg) { adc(~arg); /* -arg - 1 */ }
// Unofficial
static uint8_t slo(uint8_t arg) {
carry_flag = arg & 0x80;
ora(arg <<= 1);
return arg;
}
// Unofficial
static uint8_t sre(uint8_t arg) {
carry_flag = arg & 1;
eor(arg >>= 1);
return arg;
}
// Unofficial
static void xaa(uint8_t arg) {
// http://visual6502.org/wiki/index.php?title=6502_Opcode_8B_%28XAA,_ANE%29
// Nestopia uses 0xEE as the magic constant.
zero_negative_flag = a = (a | 0xEE) & x & arg;
}
// Conditional branches
static void branch_if(bool cond) {
++pc;
if (cond) {
read(pc); // Dummy read
// TODO: Unsafe unsigned->signed conversion - likely to work in
// practice
uint16_t const new_pc = pc + (int8_t)op_1;
if ((pc ^ new_pc) & 0x100) { // Page crossing?
// Branch instructions perform additional interrupt polling during
// the fixup tick
poll_for_interrupt();
read((pc & 0xFF00) | (new_pc & 0x00FF)); // Dummy read
}
pc = new_pc;
}
}
// Stack manipulation
static void push(uint8_t value) {
write_tick();
ram[0x100 + s--] = value;
}
static uint8_t pull() {
read_tick();
return ram[0x100 + ++s];
}
static void push_flags(bool with_break_bit_set) {
// 7 6 5 4 3 2 1 0
// negative overflow 1 break decimal interrupt-disable zero carry
push(
(!!(zero_negative_flag & 0x180) << 7) |
(overflow_flag << 6) |
(1 << 5) |
(with_break_bit_set << 4) |
(decimal_flag << 3) |
(irq_disable_flag << 2) |
(!(zero_negative_flag & 0xFF) << 1) |
carry_flag);
}
static void pull_flags() {
uint8_t const flags = pull();
// flags & 0x82 pulls out the zero and negative flags.
//
// ^2 flips the zero flag, since we want 0 if it's set.
//
// << 1 moves the negative flag into the extra negative flag bit in
// zero_negative_flag, so that the negative and zero flags can
// be set separately. The zero flag moves to bit 3, which
// won't affect the result.
zero_negative_flag = ((flags & 0x82) ^ 2) << 1;
overflow_flag = flags & 0x40;
decimal_flag = flags & 0x08;
irq_disable_flag = flags & 0x04;
carry_flag = flags & 0x01;
}
// Helpers for read-modify-write instructions, which perform a dummy write-back
// of the unmodified value
#define RMW(fn, addr) \
do { \
uint16_t const addr_ = addr; \
uint8_t const val = read(addr_); \
write(val, addr_); /* Write back unmodified value */ \
poll_for_interrupt(); \
write(fn(val), addr_); \
} while(0)
// Optimized versions for zero page access, which never has side effects
#define ZERO_RMW(fn) \
do { \
++pc; \
read_tick(); /* Read effective address */ \
read_tick(); /* Write back unmodified value */ \
poll_for_interrupt(); \
write_tick(); \
ram[op_1] = fn(ram[op_1]); \
} while(0)
#define ZERO_X_RMW(fn) \
do { \
++pc; \
uint8_t const addr = op_1 + x; \
read_tick(); /* Read address and add x to it */ \
read_tick(); /* Read effective address */ \
read_tick(); /* Write back unmodified value */ \
poll_for_interrupt(); \
write_tick(); \
ram[addr] = fn(ram[addr]); \
} while(0)
// }}}
// Addressing modes {{{
// The _addr functions return addresses, the _op functions resolved operands
//
// Zero page addressing
//
static uint8_t get_zero_op() {
++pc;
poll_for_interrupt();
read_tick();
return ram[op_1];
}
static uint8_t get_zero_xy_op(uint8_t index) {
++pc;
read_tick(); // Read from address, add index
poll_for_interrupt();
read_tick();
return ram[(op_1 + index) & 0xFF];
}
// Writing zero page never has side effects, so we can optimize a bit
static void zero_write(uint8_t val) {
++pc;
poll_for_interrupt();
write_tick();
ram[op_1] = val;
}
static void zero_xy_write(uint8_t val, uint8_t index) {
++pc;
read_tick(); // Read from address and add x to it
poll_for_interrupt();
write_tick();
ram[(op_1 + index) & 0xFF] = val;
}
//
// Absolute addressing
//
static uint16_t get_abs_addr() {
++pc;
return (read(pc++) << 8) | op_1;
}
static uint8_t get_abs_op() {
uint16_t const addr = get_abs_addr();
poll_for_interrupt();
return read(addr);
}
static void abs_write(uint8_t val) {
uint16_t const addr = get_abs_addr();
poll_for_interrupt();
write(val, addr);
}
//
// Absolute,X/Y addressing
//
// Absolute,X/Y address fetching for write and read-modify-write instructions
static uint16_t get_abs_xy_addr_write(uint8_t index) {
uint16_t const addr = get_abs_addr();
read((addr & 0xFF00) | ((addr + index) & 0x00FF)); // Dummy read
return addr + index;
}
// Absolute,X/Y operand fetching for read instructions
static uint8_t get_abs_xy_op_read(uint8_t index) {
uint16_t const addr = get_abs_addr();
uint16_t const new_addr = addr + index;
if ((addr ^ new_addr) & 0x100) // Page crossing?
read(new_addr - 0x100); // Dummy read
poll_for_interrupt();
return read(new_addr);
}
// Instructions that use this always write the accumulator, so we can omit the
// 'val' argument
static void abs_xy_write_a(uint8_t index) {
uint16_t const addr = get_abs_xy_addr_write(index);
poll_for_interrupt();
write(a, addr);
}
//
// (Indirect,X) addressing
//
static uint16_t get_ind_x_addr() {
++pc;
read_tick(); // Read from address, add index
read_tick(); // Fetch effective address low
read_tick(); // Fetch effective address high
uint8_t const zero_addr = op_1 + x;
return (ram[(zero_addr + 1) & 0xFF] << 8) | ram[zero_addr];
}
static uint8_t get_ind_x_op() {
uint16_t const addr = get_ind_x_addr();
poll_for_interrupt();
return read(addr);
}
static void ind_x_write(uint8_t val) {
uint16_t const addr = get_ind_x_addr();
poll_for_interrupt();
write(val, addr);
}
//
// (Indirect),Y addressing
//
// (Indirect),Y helper for fetching the address from zero page
static uint16_t get_addr_from_zero_page() {
++pc;
read_tick(); // Fetch effective address low
read_tick(); // Fetch effective address high
return (ram[(op_1 + 1) & 0xFF] << 8) | ram[op_1];
}
// (Indirect),Y address fetching for write and read-modify-write instructions
static uint16_t get_ind_y_addr_write() {
uint16_t const addr = get_addr_from_zero_page();
read((addr & 0xFF00) | ((addr + y) & 0x00FF)); // Dummy read
return addr + y;
}
// (Indirect),Y operand fetching for read instructions
static uint8_t get_ind_y_op_read() {
uint16_t const addr = get_addr_from_zero_page();
uint16_t const new_addr = addr + y;
if ((addr ^ new_addr) & 0x100) // Page crossing?
read(new_addr - 0x100); // Dummy read
poll_for_interrupt();
return read(new_addr);
}
// Single caller, always writes accumulator
static void ind_y_write_a() {
uint16_t const addr = get_ind_y_addr_write();
poll_for_interrupt();
write(a, addr);
}
// Helper function for implementing the weird unofficial write instructions
// (AXA, XAS, TAS, SAY) that are influenced by the high byte of the address
// plus one (due to an internal bus conflict). In page crossings, the high byte
// of the target address is corrupted similarly to the value.
static void unoff_addr_write(uint16_t addr, uint8_t value, uint8_t index) {
uint16_t const new_addr = addr + index;
read((addr & 0xFF00) | (new_addr & 0x00FF)); // Dummy read
poll_for_interrupt();
write(value & ((addr >> 8) + 1),
((addr ^ new_addr) & 0x100) ?
(new_addr & (value << 8)) | (new_addr & 0x00FF) : // Page crossing
new_addr); // No page crossing
}
// }}}
// Opcodes {{{
enum {
// Implied
BRK = 0x00, CLC = 0x18, CLD = 0xD8, CLI = 0x58, CLV = 0xB8, DEX = 0xCA, DEY = 0x88,
INX = 0xE8, INY = 0xC8, NO0 = 0x1A, NO1 = 0x3A, NO2 = 0x5A, NO3 = 0x7A, NO4 = 0xDA,
NO5 = 0xFA, NOP = 0xEA, PHA = 0x48, PHP = 0x08, PLA = 0x68, PLP = 0x28, RTI = 0x40,
RTS = 0x60, SEC = 0x38, SED = 0xF8, SEI = 0x78, TAX = 0xAA, TAY = 0xA8, TSX = 0xBA,
TXA = 0x8A, TXS = 0x9A, TYA = 0x98,
// Accumulator
ASL_ACC = 0x0A, LSR_ACC = 0x4A, ROL_ACC = 0x2A, ROR_ACC = 0x6A,
// Immediate
ADC_IMM = 0x69, ALR_IMM = 0x4B, AN0_IMM = 0x0B, AN1_IMM = 0x2B, AND_IMM = 0x29,
ARR_IMM = 0x6B, ATX_IMM = 0xAB, AXS_IMM = 0xCB, CMP_IMM = 0xC9, CPX_IMM = 0xE0,
CPY_IMM = 0xC0, EOR_IMM = 0x49, LDA_IMM = 0xA9, LDX_IMM = 0xA2, LDY_IMM = 0xA0,
NO0_IMM = 0x80, NO1_IMM = 0x82, NO2_IMM = 0x89, NO3_IMM = 0xC2, NO4_IMM = 0xE2,
ORA_IMM = 0x09, SB2_IMM = 0xEB, SBC_IMM = 0xE9, XAA_IMM = 0x8B,
// Absolute
ADC_ABS = 0x6D, AND_ABS = 0x2D, ASL_ABS = 0x0E, BIT_ABS = 0x2C, CMP_ABS = 0xCD,
CPX_ABS = 0xEC, CPY_ABS = 0xCC, DCP_ABS = 0xCF, DEC_ABS = 0xCE, EOR_ABS = 0x4D,
INC_ABS = 0xEE, ISC_ABS = 0xEF, JMP_ABS = 0x4C, JSR_ABS = 0x20, LAX_ABS = 0xAF,
LDA_ABS = 0xAD, LDX_ABS = 0xAE, LDY_ABS = 0xAC, LSR_ABS = 0x4E, NOP_ABS = 0x0C,
ORA_ABS = 0x0D, RLA_ABS = 0x2F, ROL_ABS = 0x2E, ROR_ABS = 0x6E, RRA_ABS = 0x6F,
SAX_ABS = 0x8F, SBC_ABS = 0xED, SLO_ABS = 0x0F, SRE_ABS = 0x4F, STA_ABS = 0x8D,
STX_ABS = 0x8E, STY_ABS = 0x8C,
// Absolute,X
ADC_ABS_X = 0x7D, AND_ABS_X = 0x3D, ASL_ABS_X = 0x1E, CMP_ABS_X = 0xDD,
DCP_ABS_X = 0xDF, DEC_ABS_X = 0xDE, EOR_ABS_X = 0x5D, INC_ABS_X = 0xFE,
ISC_ABS_X = 0xFF, LDA_ABS_X = 0xBD, LDY_ABS_X = 0xBC, LSR_ABS_X = 0x5E,
ORA_ABS_X = 0x1D, NO0_ABS_X = 0x1C, NO1_ABS_X = 0x3C, NO2_ABS_X = 0x5C,
NO3_ABS_X = 0x7C, NO4_ABS_X = 0xDC, NO5_ABS_X = 0xFC, RLA_ABS_X = 0x3F,
ROL_ABS_X = 0x3E, ROR_ABS_X = 0x7E, RRA_ABS_X = 0x7F, SAY_ABS_X = 0x9C,
SBC_ABS_X = 0xFD, SLO_ABS_X = 0x1F, SRE_ABS_X = 0x5F, STA_ABS_X = 0x9D,
// Absolute,Y
ADC_ABS_Y = 0x79, AND_ABS_Y = 0x39, AXA_ABS_Y = 0x9F, CMP_ABS_Y = 0xD9,
DCP_ABS_Y = 0xDB, EOR_ABS_Y = 0x59, ISC_ABS_Y = 0xFB, LAS_ABS_Y = 0xBB,
LAX_ABS_Y = 0xBF, LDA_ABS_Y = 0xB9, LDX_ABS_Y = 0xBE, ORA_ABS_Y = 0x19,
RLA_ABS_Y = 0x3B, RRA_ABS_Y = 0x7B, SBC_ABS_Y = 0xF9, SLO_ABS_Y = 0x1B,
SRE_ABS_Y = 0x5B, STA_ABS_Y = 0x99, TAS_ABS_Y = 0x9B, XAS_ABS_Y = 0x9E,
// Zero page
ADC_ZERO = 0x65, AND_ZERO = 0x25, BIT_ZERO = 0x24, CMP_ZERO = 0xC5,
CPX_ZERO = 0xE4, CPY_ZERO = 0xC4, DCP_ZERO = 0xC7, EOR_ZERO = 0x45,
ISC_ZERO = 0xE7, LAX_ZERO = 0xA7, LDA_ZERO = 0xA5, LDX_ZERO = 0xA6,
LDY_ZERO = 0xA4, NO0_ZERO = 0x04, NO1_ZERO = 0x44, NO2_ZERO = 0x64,
ORA_ZERO = 0x05, RLA_ZERO = 0x27, RRA_ZERO = 0x67, SBC_ZERO = 0xE5,
SLO_ZERO = 0x07, SRE_ZERO = 0x47, ASL_ZERO = 0x06, LSR_ZERO = 0x46,
ROL_ZERO = 0x26, ROR_ZERO = 0x66, INC_ZERO = 0xE6, DEC_ZERO = 0xC6,
SAX_ZERO = 0x87, STA_ZERO = 0x85, STX_ZERO = 0x86, STY_ZERO = 0x84,
// Zero page,X
ADC_ZERO_X = 0x75, AND_ZERO_X = 0x35, ASL_ZERO_X = 0x16, CMP_ZERO_X = 0xD5,
DCP_ZERO_X = 0xD7, DEC_ZERO_X = 0xD6, EOR_ZERO_X = 0x55, INC_ZERO_X = 0xF6,
ISC_ZERO_X = 0xF7, LDA_ZERO_X = 0xB5, LDY_ZERO_X = 0xB4, LSR_ZERO_X = 0x56,
NO0_ZERO_X = 0x14, NO1_ZERO_X = 0x34, NO2_ZERO_X = 0x54, NO3_ZERO_X = 0x74,
NO4_ZERO_X = 0xD4, NO5_ZERO_X = 0xF4, ORA_ZERO_X = 0x15, RLA_ZERO_X = 0x37,
ROL_ZERO_X = 0x36, ROR_ZERO_X = 0x76, RRA_ZERO_X = 0x77, SBC_ZERO_X = 0xF5,
SLO_ZERO_X = 0x17, SRE_ZERO_X = 0x57, STA_ZERO_X = 0x95, STY_ZERO_X = 0x94,
// Zero page,Y
LAX_ZERO_Y = 0xB7, LDX_ZERO_Y = 0xB6, SAX_ZERO_Y = 0x97, STX_ZERO_Y = 0x96,
// (Indirect,X)
ADC_IND_X = 0x61, AND_IND_X = 0x21, CMP_IND_X = 0xC1, DCP_IND_X = 0xC3,
EOR_IND_X = 0x41, ISC_IND_X = 0xE3, LAX_IND_X = 0xA3, LDA_IND_X = 0xA1,
ORA_IND_X = 0x01, RLA_IND_X = 0x23, RRA_IND_X = 0x63, SAX_IND_X = 0x83,
SBC_IND_X = 0xE1, SLO_IND_X = 0x03, SRE_IND_X = 0x43, STA_IND_X = 0x81,
// (Indirect),Y
ADC_IND_Y = 0x71, AND_IND_Y = 0x31, AXA_IND_Y = 0x93, CMP_IND_Y = 0xD1,
DCP_IND_Y = 0xD3, EOR_IND_Y = 0x51, ISC_IND_Y = 0xF3, LAX_IND_Y = 0xB3,
LDA_IND_Y = 0xB1, ORA_IND_Y = 0x11, RLA_IND_Y = 0x33, RRA_IND_Y = 0x73,
SBC_IND_Y = 0xF1, SLO_IND_Y = 0x13, SRE_IND_Y = 0x53, STA_IND_Y = 0x91,
// Relative (branch instructions)
BCC = 0x90, BCS = 0xB0, BEQ = 0xF0, BMI = 0x30, BNE = 0xD0, BPL = 0x10,
BVC = 0x50, BVS = 0x70,
// Indirect (indirect jump)
JMP_IND = 0x6C,
// KIL instructions
KI0 = 0x02, KI1 = 0x12, KI2 = 0x22, KI3 = 0x32, KI4 = 0x42, KI5 = 0x52,
KI6 = 0x62, KI7 = 0x72, KI8 = 0x92, KI9 = 0xB2, K10 = 0xD2, K11 = 0xF2
};
// }}}
// Interrupts {{{
// Interrupt variables. 'true' means asserted.
// IRQ from mapper hardware on the cart
bool cart_irq;
// IRQ from the DMC channel
// Set by the last sample byte being loaded, unless inhibited or looping is set
// Cleared by
// * the reset signal,
// * writing $4015,
// * and clearing the IRQ enable flag in $4010
bool dmc_irq;
// IRQ from the frame counter
// Set by the frame counter in 4-step mode, unless inhibited
// Cleared by (derived from Visual 2A03)
// * the reset signal,
// * setting the inhibit IRQ flag,
// * and reading $4015
bool frame_irq;
// The OR of the above IRQ sources. Updated in update_irq_status().
static bool irq_line;
// Set true when a falling edge occurs on the NMI input
bool nmi_asserted;
// Set true if interrupt polling detects a pending IRQ or NMI. The next
// "instruction" executed is the interrupt sequence in that case.
static bool pending_irq;
static bool pending_nmi;
enum Interrupt_type { Int_BRK = 0, Int_IRQ, Int_NMI, Int_reset };
static void do_interrupt(Interrupt_type type) {
static uint16_t const vector_addr[] =
{ 0xFFFE, // Int_BRK
0xFFFE, // Int_IRQ
0xFFFA }; // Int_NMI
uint16_t vec_addr;
// Two dummy reads
if (type != Int_BRK) {
// For BRK, these have already been done
read(pc);
read(pc);
}
if (type == Int_reset) {
// Push operations, writes inhibited
read_tick();
read_tick();
read_tick();
s -= 3;
vec_addr = 0xFFFC;
}
else {
push(pc >> 8);
push(pc & 0xFF);
// Interrupt glitch. An NMI asserted here can override BRK and IRQ.
if (nmi_asserted) {
nmi_asserted = false;
vec_addr = 0xFFFA;
}
else
vec_addr = vector_addr[(unsigned)type];
push_flags(type == Int_BRK);
}
irq_disable_flag = true;
// No interrupt polling happens here; the first instruction of the
// interrupt handler always executes before another interrupt is serviced
pc = read(vec_addr);
pc |= (read(vec_addr + 1) << 8);
}
// The interrupt lines are polled at the end of the second-to-last tick for
// most instructions, making interrupts a bit less straightforward to implement
// compared to if the polling was done at the very end. For addressing modes
// where all instructions poll interrupts in the same location we do the
// polling in the addressing mode routine to save code. Same goes for
// read-modify-write instructions, which all poll in the same location.
static void poll_for_interrupt() {
// If both NMI and IRQ have been asserted, the IRQ assertion is lost at
// this polling point (as if IRQ had never been asserted). IRQ might still
// be detected at the next polling point.
//
// This behavior has been confirmed in Visual 6502.
if (nmi_asserted) {
nmi_asserted = false;
pending_nmi = true;
}
else if (irq_line && !irq_disable_flag)
pending_irq = true;
}
void update_irq_status() {
irq_line = cart_irq || dmc_irq || frame_irq;
}
// Probably no nice way to initialize this statically. Designated initializers
// are only supported for C in GCC.
static bool polls_irq_after_first_cycle[256];
static void init_interrupts() {
#define P(opcode) polls_irq_after_first_cycle[opcode] = true;
// For CLI and SEI, the flag changes after the interrupt is polled, so both
// can safely go in this list (the polling is influenced by the current
// flag status like for the rest)
P(ADC_IMM) P(ALR_IMM) P(AN0_IMM) P(AN1_IMM) P(AND_IMM) P(ARR_IMM) P(ASL_ACC)
P(BCC) P(ATX_IMM) P(AXS_IMM) P(BCS) P(BEQ) P(BMI) P(BNE)
P(BPL) P(BVC) P(BVS) P(CLC) P(CLD) P(CLI) P(CLV)
P(CMP_IMM) P(CPX_IMM) P(CPY_IMM) P(DEX) P(DEY) P(EOR_IMM) P(INX)
P(INY) P(LDA_IMM) P(LDX_IMM) P(LDY_IMM) P(LSR_ACC) P(NO0) P(NO0_IMM)
P(NO1_IMM) P(NO2_IMM) P(NO3_IMM) P(NO4_IMM) P(NO1) P(NO2) P(NO3)
P(NO4) P(NO5) P(NOP) P(ORA_IMM) P(ROL_ACC) P(ROR_ACC) P(SB2_IMM)
P(SBC_IMM) P(SEC) P(SED) P(SEI) P(TAX) P(TAY) P(TSX)
P(TXA) P(TXS) P(TYA) P(XAA_IMM)
#undef P
}
// }}}
// Main CPU loop {{{
#ifdef LOG_INSTRUCTIONS
static void log_instruction();
#endif
static void set_cpu_cold_boot_state();
#ifdef RUN_TESTS
// We currently only reset for test ROMs
static void reset_cpu();
#endif
void run() {
end_emulation = false;
set_apu_cold_boot_state();
set_cpu_cold_boot_state();
set_ppu_cold_boot_state();
init_timing();
do_interrupt(Int_reset);
while (!end_emulation) {
#ifdef RUN_TESTS
if (pending_reset) {
pending_reset = false;
// Reset the APU and PPU first since they should tick during the
// CPU's reset sequence
reset_apu();
reset_ppu();
reset_cpu();
}
#endif
#ifdef LOG_INSTRUCTIONS
log_instruction();
#endif
if (pending_nmi) {
do_interrupt(Int_NMI);
pending_nmi = false;
continue;
}
if (pending_irq) {
do_interrupt(Int_IRQ);
pending_irq = false;
continue;
}
uint8_t const opcode = read(pc++);
if (polls_irq_after_first_cycle[opcode])
poll_for_interrupt();
op_1 = read(pc);
// http://eli.thegreenplace.net/2012/07/12/computed-goto-for-efficient-dispatch-tables/
// could possibly speed this up a bit (also,
// https://www.cs.tcd.ie/David.Gregg/papers/toplas05.pdf). CPU
// emulation seems to account for less than 5% of the runtime though,
// so it might not be worth uglifying the code for.
switch (opcode) {
//
// Accumulator or implied addressing
//
case BRK:
++pc;
do_interrupt(Int_BRK);
break;
case RTI:
{
read_tick(); // Corresponds to incrementing s
pull_flags();
pc = pull();
poll_for_interrupt();
pc |= (pull() << 8);
}
break;
case RTS:
{
read_tick(); // Corresponds to incrementing s
uint8_t const pc_low = pull();
pc = ((pull() << 8) | pc_low) + 1;
poll_for_interrupt();
read_tick(); // Increment PC
}
break;