@@ -4,7 +4,7 @@ use crate::{
4
4
bus:: Bus ,
5
5
ops:: { addressing_mode_to_size, is_official, lookup_opcode, OpName } ,
6
6
rom:: Rom ,
7
- utility:: addr_from,
7
+ utility:: { addr_from, is_addr_at_page_edge , is_page_cross } ,
8
8
} ;
9
9
10
10
/// CPU (Central Processing Unit)
@@ -33,6 +33,9 @@ pub struct Cpu {
33
33
34
34
/// Bus
35
35
bus : Bus ,
36
+
37
+ /// CPU Cycles
38
+ cycles : usize ,
36
39
}
37
40
38
41
#[ derive( Debug , PartialEq , Eq ) ]
@@ -113,6 +116,7 @@ impl Cpu {
113
116
y : 0 ,
114
117
status : DEFAULT_STATUS ,
115
118
bus : Bus :: new ( rom) ,
119
+ cycles : 0 ,
116
120
}
117
121
}
118
122
@@ -156,27 +160,49 @@ impl Cpu {
156
160
//
157
161
158
162
/// used for the "index indirect" and "indirect indexed" lookups
159
- fn mem_read_zero_page_wrapping ( & mut self , ptr : u8 ) -> u16 {
163
+ fn mem_read_zero_page_wrapping ( & mut self , ptr : u8 ) -> ( u16 , bool ) {
160
164
let lo = self . mem_read ( ptr as u16 ) ;
161
- let hi = self . mem_read ( ptr. wrapping_add ( 1 ) as u16 ) ;
162
- ( hi as u16 ) << 8 | ( lo as u16 )
165
+ let ( hi_ptr, is_page_crossed) = ptr. overflowing_add ( 1 ) ;
166
+ let hi = self . mem_read ( hi_ptr as u16 ) ;
167
+
168
+ let result = ( hi as u16 ) << 8 | ( lo as u16 ) ;
169
+
170
+ ( result, is_page_crossed)
163
171
}
164
172
165
- fn get_operand_address ( & mut self , mode : & AddressingMode ) -> u16 {
166
- match mode {
173
+ /// returns (u16: operand addresss, bool: is_page_crossed)
174
+ fn get_operand_address2 ( & mut self , mode : & AddressingMode , tick_on_page_cross : bool ) -> u16 {
175
+ let mut page_was_crossed = false ;
176
+ let addr = match mode {
167
177
AddressingMode :: Immediate => self . pc ,
168
178
AddressingMode :: ZeroPage => self . mem_read ( self . pc ) as u16 ,
169
179
AddressingMode :: ZeroPageX => self . mem_read ( self . pc ) . wrapping_add ( self . x ) as u16 ,
170
180
AddressingMode :: ZeroPageY => self . mem_read ( self . pc ) . wrapping_add ( self . y ) as u16 ,
171
181
AddressingMode :: Absolute => self . mem_read_u16 ( self . pc ) ,
172
- AddressingMode :: AbsoluteX => self . mem_read_u16 ( self . pc ) . wrapping_add ( self . x as u16 ) ,
173
- AddressingMode :: AbsoluteY => self . mem_read_u16 ( self . pc ) . wrapping_add ( self . y as u16 ) ,
174
- AddressingMode :: Indirect => {
182
+ AddressingMode :: AbsoluteX => {
183
+ let base = self . mem_read_u16 ( self . pc ) ;
184
+ let result = base. wrapping_add ( self . x as u16 ) ;
185
+
186
+ if is_page_cross ( base, result) {
187
+ page_was_crossed = true ;
188
+ }
189
+
190
+ result
191
+ }
192
+ AddressingMode :: AbsoluteY => {
175
193
let base = self . mem_read_u16 ( self . pc ) ;
194
+ let result = base. wrapping_add ( self . y as u16 ) ;
176
195
177
- let is_edge_of_page = base & 0x00ff == 0xff ;
196
+ if is_page_cross ( base, result) {
197
+ page_was_crossed = true ;
198
+ }
199
+
200
+ result
201
+ }
202
+ AddressingMode :: Indirect => {
203
+ let base = self . mem_read_u16 ( self . pc ) ;
178
204
179
- if is_edge_of_page {
205
+ if is_addr_at_page_edge ( base ) {
180
206
let first = self . mem_read ( base) ;
181
207
// Intentionally wrap incorrectly, to recreate buggy behavior from original 6502
182
208
let second = self . mem_read ( base & 0xff00 ) ;
@@ -189,16 +215,38 @@ impl Cpu {
189
215
// "Indexed indirect"
190
216
let base = self . mem_read ( self . pc ) ;
191
217
let target = base. wrapping_add ( self . x ) ; // indexed
192
- self . mem_read_zero_page_wrapping ( target) // indirect
218
+ let ( result, _) = self . mem_read_zero_page_wrapping ( target) ; // indirect
219
+
220
+ if is_addr_at_page_edge ( result) {
221
+ page_was_crossed = true ;
222
+ }
223
+
224
+ result
193
225
}
194
226
AddressingMode :: IndirectY => {
195
227
// "Indirect indexed"
196
228
let base = self . mem_read ( self . pc ) ;
197
- let target = self . mem_read_zero_page_wrapping ( base) ; // indirect
198
- target. wrapping_add ( self . y as u16 ) // indexed
229
+ let ( target, _) = self . mem_read_zero_page_wrapping ( base) ; // indirect
230
+ let result = target. wrapping_add ( self . y as u16 ) ; // indexed
231
+
232
+ if is_addr_at_page_edge ( base as u16 ) || is_addr_at_page_edge ( target) {
233
+ page_was_crossed = true ;
234
+ }
235
+
236
+ result
199
237
}
200
238
_ => panic ! ( "mode {:?} is not supported" , mode) ,
239
+ } ;
240
+
241
+ if tick_on_page_cross && page_was_crossed {
242
+ self . tick ( 1 )
201
243
}
244
+
245
+ addr
246
+ }
247
+
248
+ fn get_operand_address ( & mut self , mode : & AddressingMode ) -> u16 {
249
+ self . get_operand_address2 ( mode, false )
202
250
}
203
251
204
252
//
@@ -244,6 +292,9 @@ impl Cpu {
244
292
where
245
293
F : FnMut ( & mut Cpu ) ,
246
294
{
295
+ // This simulates the wait time for the PPU to start
296
+ self . tick ( 7 ) ;
297
+
247
298
loop {
248
299
if self . bus . poll_nmi_status ( ) {
249
300
self . interrupt_nmi ( ) ;
@@ -286,7 +337,7 @@ impl Cpu {
286
337
OpName :: LDX => self . ldx ( & mode) ,
287
338
OpName :: LDY => self . ldy ( & mode) ,
288
339
OpName :: LSR => self . lsr ( & mode) ,
289
- OpName :: NOP => self . nop ( ) ,
340
+ OpName :: NOP => self . nop ( & mode , cycles . 1 ) ,
290
341
OpName :: ORA => self . ora ( & mode) ,
291
342
OpName :: ROL => self . rol ( & mode) ,
292
343
OpName :: ROR => self . ror ( & mode) ,
@@ -347,12 +398,7 @@ impl Cpu {
347
398
OpName :: RRA => self . rra ( & mode) ,
348
399
}
349
400
350
- // TODO: Handle extra cycles behavior
351
- // (1) page wrapping behavior for some ops that adds +1 to the number of cycles
352
- // Memory page size is 256 bytes. For example, the range [0x0000 .. 0x00FF]- belongs to page 0, [0x0100 .. 0x01FF] belongs to page 1, etc.
353
- // It's enough to compare the upper byte of the addresses to see if they are on the same page.
354
- // (2) branching behavior that adds +1 or +2 to cycles
355
- self . bus . tick ( cycles. 0 as usize ) ;
401
+ self . tick ( cycles. 0 as usize ) ;
356
402
357
403
// some operations modify the pc, like JMP. We shouldn't override that.
358
404
if self . pc == saved_pc {
@@ -361,6 +407,11 @@ impl Cpu {
361
407
}
362
408
}
363
409
410
+ fn tick ( & mut self , cycles : usize ) {
411
+ self . cycles += cycles;
412
+ self . bus . tick ( cycles) ;
413
+ }
414
+
364
415
fn jsr ( & mut self , mode : & AddressingMode ) {
365
416
let jump_dest = self . get_operand_address ( mode) ;
366
417
// +2 for consumed u16 destination
@@ -446,7 +497,18 @@ impl Cpu {
446
497
let displacement = self . mem_read ( self . pc ) as i8 ;
447
498
448
499
if self . get_flag ( flag) == is_set {
449
- self . pc = ( self . pc as isize + 1 + displacement as isize ) as u16
500
+ let before = self . pc ;
501
+ let after = ( self . pc as isize + 1 + displacement as isize ) as u16 ;
502
+ self . pc = after;
503
+
504
+ // We set `before+2` so that we only check if we've moved to a DIFFERENT page given the branching scenario.
505
+ let before_plus = before + 2 ;
506
+ // This behavior is based on nestest.log line 1107
507
+ if is_page_cross ( before_plus, after) {
508
+ self . tick ( 2 ) ;
509
+ } else {
510
+ self . tick ( 1 ) ;
511
+ }
450
512
}
451
513
}
452
514
@@ -598,7 +660,7 @@ impl Cpu {
598
660
599
661
/// LDA (LoaD Accumulator)
600
662
fn lda ( & mut self , mode : & AddressingMode ) {
601
- let addr = self . get_operand_address ( mode) ;
663
+ let addr = self . get_operand_address2 ( mode, true ) ;
602
664
let param = self . mem_read ( addr) ;
603
665
604
666
self . a = param;
@@ -607,7 +669,7 @@ impl Cpu {
607
669
608
670
/// LDX (LoaD X register)
609
671
fn ldx ( & mut self , mode : & AddressingMode ) {
610
- let addr = self . get_operand_address ( mode) ;
672
+ let addr = self . get_operand_address2 ( mode, true ) ;
611
673
let param = self . mem_read ( addr) ;
612
674
613
675
self . x = param;
@@ -616,7 +678,7 @@ impl Cpu {
616
678
617
679
/// LDY (LoaD Y register)
618
680
fn ldy ( & mut self , mode : & AddressingMode ) {
619
- let addr = self . get_operand_address ( mode) ;
681
+ let addr = self . get_operand_address2 ( mode, true ) ;
620
682
let param = self . mem_read ( addr) ;
621
683
622
684
self . y = param;
@@ -646,7 +708,12 @@ impl Cpu {
646
708
}
647
709
648
710
/// NOP (No OPeration)
649
- fn nop ( & mut self ) { }
711
+ fn nop ( & mut self , mode : & AddressingMode , tick_on_page_cross : bool ) {
712
+ if tick_on_page_cross {
713
+ // Run this for the side-effect of possibly ticking one cycle
714
+ self . get_operand_address2 ( mode, tick_on_page_cross) ;
715
+ }
716
+ }
650
717
651
718
/// ORA (bitwise OR with Accumulator)
652
719
fn ora ( & mut self , mode : & AddressingMode ) {
@@ -847,6 +914,9 @@ impl Cpu {
847
914
pub fn trace ( & mut self ) -> String {
848
915
// C000 4C F5 C5 JMP $C5F5 A:00 X:00 Y:00 P:24 SP:FD PPU: 0, 21 CYC:7
849
916
917
+ let cpu_cycles = self . cycles ;
918
+ let ( ppu_frame, ppu_cycles) = self . bus . get_ppu_tick_status ( ) ;
919
+
850
920
let code = self . mem_read ( self . pc ) ;
851
921
let param1 = self . mem_read ( self . pc + 1 ) ;
852
922
let param2 = self . mem_read ( self . pc + 2 ) ;
@@ -904,7 +974,7 @@ impl Cpu {
904
974
}
905
975
AddressingMode :: IndirectX => {
906
976
let indexed = param1. wrapping_add ( self . x ) ;
907
- let indirect = self . mem_read_zero_page_wrapping ( indexed) ;
977
+ let ( indirect, _ ) = self . mem_read_zero_page_wrapping ( indexed) ;
908
978
let data = self . mem_read ( indirect) ;
909
979
910
980
format ! (
@@ -913,7 +983,7 @@ impl Cpu {
913
983
)
914
984
}
915
985
AddressingMode :: IndirectY => {
916
- let indirect = self . mem_read_zero_page_wrapping ( param1) ;
986
+ let ( indirect, _ ) = self . mem_read_zero_page_wrapping ( param1) ;
917
987
let indexed = indirect. wrapping_add ( self . y as u16 ) ;
918
988
let data = self . mem_read ( indexed) ;
919
989
format ! (
@@ -952,7 +1022,7 @@ impl Cpu {
952
1022
} ;
953
1023
954
1024
format ! (
955
- "{:04X} {:02X} {} {} {:>4} {:<28}A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X}" , // PPU:{:>3},{:>3} CYC:{:>4}
1025
+ "{:04X} {:02X} {} {} {:>4} {:<28}A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X} PPU:{:>3},{:>3} CYC:{}" ,
956
1026
self . pc,
957
1027
code,
958
1028
if size >= 2 {
@@ -972,9 +1042,9 @@ impl Cpu {
972
1042
self . y,
973
1043
self . status,
974
1044
self . sp,
975
- // 0 ,
976
- // 0 ,
977
- // 0
1045
+ ppu_frame ,
1046
+ ppu_cycles % 341 ,
1047
+ cpu_cycles ,
978
1048
)
979
1049
. to_string ( )
980
1050
}
@@ -1673,15 +1743,15 @@ mod tests {
1673
1743
result. push ( cpu. trace ( ) ) ;
1674
1744
} ) ;
1675
1745
assert_eq ! (
1676
- "0064 A2 01 LDX #$01 A:01 X:02 Y:03 P:24 SP:FD" ,
1746
+ "0064 A2 01 LDX #$01 A:01 X:02 Y:03 P:24 SP:FD PPU: 0, 21 CYC:7 " ,
1677
1747
result[ 0 ]
1678
1748
) ;
1679
1749
assert_eq ! (
1680
- "0066 CA DEX A:01 X:01 Y:03 P:24 SP:FD" ,
1750
+ "0066 CA DEX A:01 X:01 Y:03 P:24 SP:FD PPU: 0, 27 CYC:9 " ,
1681
1751
result[ 1 ]
1682
1752
) ;
1683
1753
assert_eq ! (
1684
- "0067 88 DEY A:01 X:00 Y:03 P:26 SP:FD" ,
1754
+ "0067 88 DEY A:01 X:00 Y:03 P:26 SP:FD PPU: 0, 33 CYC:11 " ,
1685
1755
result[ 2 ]
1686
1756
) ;
1687
1757
}
@@ -1710,7 +1780,7 @@ mod tests {
1710
1780
result. push ( cpu. trace ( ) ) ;
1711
1781
} ) ;
1712
1782
assert_eq ! (
1713
- "0064 11 33 ORA ($33),Y = 0400 @ 0400 = AA A:00 X:00 Y:00 P:24 SP:FD" ,
1783
+ "0064 11 33 ORA ($33),Y = 0400 @ 0400 = AA A:00 X:00 Y:00 P:24 SP:FD PPU: 0, 21 CYC:7 " ,
1714
1784
result[ 0 ]
1715
1785
) ;
1716
1786
}
@@ -1737,7 +1807,7 @@ mod tests {
1737
1807
} )
1738
1808
} ) ;
1739
1809
1740
- let expected = fs:: read ( "nestest_no_cycles .log" ) . unwrap ( ) ;
1810
+ let expected = fs:: read ( "nestest .log" ) . unwrap ( ) ;
1741
1811
1742
1812
let actual = match mutex_result. lock ( ) {
1743
1813
Ok ( m) => m,
0 commit comments