pyboy 2.3.0__tar.gz → 2.4.1__tar.gz

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 (116) hide show
  1. {pyboy-2.3.0/pyboy.egg-info → pyboy-2.4.1}/PKG-INFO +3 -3
  2. {pyboy-2.3.0 → pyboy-2.4.1}/README.md +2 -2
  3. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/__main__.py +1 -1
  4. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/gameshark.py +4 -0
  5. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cpu.pxd +6 -3
  6. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cpu.py +44 -45
  7. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/lcd.pxd +2 -1
  8. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/lcd.py +15 -4
  9. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/mb.pxd +2 -1
  10. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/mb.py +32 -20
  11. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/opcodes.py +584 -517
  12. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/opcodes_gen.py +132 -19
  13. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/sound.pxd +4 -5
  14. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/sound.py +11 -0
  15. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/timer.pxd +2 -2
  16. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/timer.py +19 -18
  17. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug.py +8 -2
  18. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/pyboy.pxd +3 -3
  19. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/pyboy.py +21 -24
  20. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/utils.py +1 -1
  21. {pyboy-2.3.0 → pyboy-2.4.1/pyboy.egg-info}/PKG-INFO +3 -3
  22. {pyboy-2.3.0 → pyboy-2.4.1}/pyproject.toml +1 -1
  23. {pyboy-2.3.0 → pyboy-2.4.1}/LICENSE.md +0 -0
  24. {pyboy-2.3.0 → pyboy-2.4.1}/MANIFEST.in +0 -0
  25. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/__init__.py +0 -0
  26. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/__init__.py +0 -0
  27. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/constants.py +0 -0
  28. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/gameshark.pxd +0 -0
  29. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/memory_scanner.pxd +0 -0
  30. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/memory_scanner.py +0 -0
  31. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/screen.pxd +0 -0
  32. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/screen.py +0 -0
  33. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/sprite.pxd +0 -0
  34. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/sprite.py +0 -0
  35. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tile.pxd +0 -0
  36. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tile.py +0 -0
  37. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tilemap.pxd +0 -0
  38. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tilemap.py +0 -0
  39. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/conftest.py +0 -0
  40. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/__init__.py +0 -0
  41. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom.pxd +0 -0
  42. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom.py +0 -0
  43. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom_cgb.bin +0 -0
  44. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom_dmg.bin +0 -0
  45. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/__init__.py +0 -0
  46. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/base_mbc.pxd +0 -0
  47. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/base_mbc.py +0 -0
  48. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/cartridge.pxd +0 -0
  49. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/cartridge.py +0 -0
  50. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc1.pxd +0 -0
  51. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc1.py +0 -0
  52. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc2.pxd +0 -0
  53. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc2.py +0 -0
  54. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc3.pxd +0 -0
  55. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc3.py +0 -0
  56. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc5.pxd +0 -0
  57. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc5.py +0 -0
  58. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/rtc.pxd +0 -0
  59. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/rtc.py +0 -0
  60. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/interaction.pxd +0 -0
  61. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/interaction.py +0 -0
  62. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/opcodes.pxd +0 -0
  63. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/ram.pxd +0 -0
  64. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/ram.py +0 -0
  65. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/default_rom.gb +0 -0
  66. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/__init__.py +0 -0
  67. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/_logging.py +0 -0
  68. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/logging.pxd +0 -0
  69. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/logging.pyx +0 -0
  70. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/__init__.py +0 -0
  71. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/auto_pause.pxd +0 -0
  72. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/auto_pause.py +0 -0
  73. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/base_plugin.pxd +0 -0
  74. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/base_plugin.py +0 -0
  75. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug.pxd +0 -0
  76. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug_prompt.pxd +0 -0
  77. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug_prompt.py +0 -0
  78. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/disable_input.pxd +0 -0
  79. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/disable_input.py +0 -0
  80. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/font.txt +0 -0
  81. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_kirby_dream_land.pxd +0 -0
  82. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_kirby_dream_land.py +0 -0
  83. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_gen1.pxd +0 -0
  84. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_gen1.py +0 -0
  85. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_pinball.pxd +0 -0
  86. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_pinball.py +0 -0
  87. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_super_mario_land.pxd +0 -0
  88. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_super_mario_land.py +0 -0
  89. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_tetris.pxd +0 -0
  90. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_tetris.py +0 -0
  91. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/manager.pxd +0 -0
  92. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/manager.py +0 -0
  93. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/manager_gen.py +0 -0
  94. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/record_replay.pxd +0 -0
  95. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/record_replay.py +0 -0
  96. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/rewind.pxd +0 -0
  97. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/rewind.py +0 -0
  98. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screen_recorder.pxd +0 -0
  99. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screen_recorder.py +0 -0
  100. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screenshot_recorder.pxd +0 -0
  101. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screenshot_recorder.py +0 -0
  102. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_null.pxd +0 -0
  103. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_null.py +0 -0
  104. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_open_gl.pxd +0 -0
  105. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_open_gl.py +0 -0
  106. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_sdl2.pxd +0 -0
  107. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_sdl2.py +0 -0
  108. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/utils.pxd +0 -0
  109. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/SOURCES.txt +0 -0
  110. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/dependency_links.txt +0 -0
  111. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/entry_points.txt +0 -0
  112. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/not-zip-safe +0 -0
  113. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/requires.txt +0 -0
  114. {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/top_level.txt +0 -0
  115. {pyboy-2.3.0 → pyboy-2.4.1}/setup.cfg +0 -0
  116. {pyboy-2.3.0 → pyboy-2.4.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyboy
3
- Version: 2.3.0
3
+ Version: 2.4.1
4
4
  Summary: Game Boy emulator written in Python
5
5
  Author-email: Mads Ynddal <mads@ynddal.dk>
6
6
  License: GNU Lesser General Public License
@@ -354,14 +354,14 @@ for _ in range(target):
354
354
 
355
355
  ```python
356
356
  for _ in range(target//15):
357
- pyboy.tick(15, True)
357
+ pyboy.tick(15)
358
358
 
359
359
  ```
360
360
  </td>
361
361
  <td >
362
362
 
363
363
  ```python
364
- pyboy.tick(target)
364
+ pyboy.tick(target, False)
365
365
 
366
366
  ```
367
367
  </td>
@@ -163,14 +163,14 @@ for _ in range(target):
163
163
 
164
164
  ```python
165
165
  for _ in range(target//15):
166
- pyboy.tick(15, True)
166
+ pyboy.tick(15)
167
167
 
168
168
  ```
169
169
  </td>
170
170
  <td >
171
171
 
172
172
  ```python
173
- pyboy.tick(target)
173
+ pyboy.tick(target, False)
174
174
 
175
175
  ```
176
176
  </td>
@@ -169,7 +169,7 @@ See "pyboy --help" for how to enable rewind and other awesome features!
169
169
  pyboy.load_state(f)
170
170
 
171
171
  render = not argv.no_renderer
172
- while pyboy._tick(render):
172
+ while pyboy.tick():
173
173
  pass
174
174
 
175
175
  pyboy.stop()
@@ -7,6 +7,10 @@ from pyboy.logging import get_logger
7
7
 
8
8
  logger = get_logger(__name__)
9
9
 
10
+ __pdoc__ = {
11
+ "GameShark.tick": False,
12
+ }
13
+
10
14
 
11
15
  class GameShark:
12
16
  def __init__(self, memory):
@@ -4,7 +4,7 @@
4
4
  #
5
5
 
6
6
 
7
- from libc.stdint cimport int16_t, uint8_t, uint16_t, uint32_t, uint64_t
7
+ from libc.stdint cimport int16_t, int64_t, uint8_t, uint16_t, int64_t
8
8
 
9
9
  cimport pyboy.core.mb
10
10
  from pyboy.utils cimport IntIOInterface
@@ -26,17 +26,20 @@ cdef uint8_t INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW
26
26
 
27
27
  cdef class CPU:
28
28
  cdef bint is_stuck
29
- cdef bint interrupt_master_enable, interrupt_queued, halted, stopped
29
+ cdef bint interrupt_master_enable, interrupt_queued, halted, stopped, bail
30
30
 
31
31
  cdef uint8_t interrupts_flag, interrupts_enabled, interrupts_flag_register, interrupts_enabled_register
32
32
 
33
+ cdef int64_t cycles
34
+
33
35
  cdef inline int check_interrupts(self) noexcept nogil
34
36
  cdef void set_interruptflag(self, int) noexcept nogil
35
37
  cdef bint handle_interrupt(self, uint8_t, uint16_t) noexcept nogil
36
38
 
37
39
  @cython.locals(opcode=uint16_t)
38
40
  cdef inline uint8_t fetch_and_execute(self) noexcept nogil
39
- cdef int tick(self) noexcept nogil
41
+ @cython.locals(_cycles0=int64_t)
42
+ cdef int tick(self, int64_t) noexcept nogil
40
43
  cdef void save_state(self, IntIOInterface) noexcept
41
44
  cdef void load_state(self, IntIOInterface, int) noexcept
42
45
 
@@ -39,6 +39,7 @@ class CPU:
39
39
  self.halted = False
40
40
  self.stopped = False
41
41
  self.is_stuck = False
42
+ self.cycles = 0
42
43
 
43
44
  def save_state(self, f):
44
45
  for n in [self.A, self.F, self.B, self.C, self.D, self.E]:
@@ -53,6 +54,7 @@ class CPU:
53
54
  f.write(self.interrupts_enabled_register)
54
55
  f.write(self.interrupt_queued)
55
56
  f.write(self.interrupts_flag_register)
57
+ f.write_64bit(self.cycles)
56
58
 
57
59
  def load_state(self, f, state_version):
58
60
  self.A, self.F, self.B, self.C, self.D, self.E = [f.read() for _ in range(6)]
@@ -69,6 +71,8 @@ class CPU:
69
71
  if state_version >= 8:
70
72
  self.interrupt_queued = f.read()
71
73
  self.interrupts_flag_register = f.read()
74
+ if state_version >= 12:
75
+ self.cycles = f.read_64bit()
72
76
  logger.debug("State loaded: %s", self.dump_state(""))
73
77
 
74
78
  def dump_state(self, sym_label):
@@ -93,8 +97,8 @@ class CPU:
93
97
  f"Interrupts - IME: {self.mb.cpu.interrupt_master_enable}, "
94
98
  f"IE: {self.mb.cpu.interrupts_enabled_register:08b}, "
95
99
  f"IF: {self.mb.cpu.interrupts_flag_register:08b}\n"
96
- f"LCD Intr.: {self.mb.lcd.cycles_to_interrupt()}, LY:{self.mb.lcd.LY}, LYC:{self.mb.lcd.LYC}\n"
97
- f"Timer Intr.: {self.mb.timer.cycles_to_interrupt()}\n"
100
+ f"LCD Intr.: {self.mb.lcd._cycles_to_interrupt}, LY:{self.mb.lcd.LY}, LYC:{self.mb.lcd.LYC}\n"
101
+ f"Timer Intr.: {self.mb.timer._cycles_to_interrupt}\n"
98
102
  f"halted:{self.halted}, "
99
103
  f"interrupt_queued:{self.interrupt_queued}, "
100
104
  f"stopped:{self.stopped}\n"
@@ -103,11 +107,13 @@ class CPU:
103
107
  def set_interruptflag(self, flag):
104
108
  self.interrupts_flag_register |= flag
105
109
 
106
- def tick(self):
110
+ def tick(self, cycles_target):
111
+ _cycles0 = self.cycles
112
+ _target = _cycles0 + cycles_target
113
+
107
114
  if self.check_interrupts():
108
115
  self.halted = False
109
- # TODO: We return with the cycles it took to handle the interrupt
110
- return 0
116
+ # TODO: Cycles it took to handle the interrupt
111
117
 
112
118
  if self.halted and self.interrupt_queued:
113
119
  # GBCPUman.pdf page 20
@@ -117,62 +123,55 @@ class CPU:
117
123
  self.PC += 1
118
124
  self.PC &= 0xFFFF
119
125
  elif self.halted:
120
- return 4 # TODO: Number of cycles for a HALT in effect?
121
-
122
- old_pc = self.PC # If the PC doesn't change, we're likely stuck
123
- old_sp = self.SP # Sometimes a RET can go to the same PC, so we check the SP too.
124
- cycles = self.fetch_and_execute()
125
- if not self.halted and old_pc == self.PC and old_sp == self.SP and not self.is_stuck and not self.mb.breakpoint_singlestep:
126
- logger.debug("CPU is stuck: %s", self.dump_state(""))
127
- self.is_stuck = True
126
+ self.cycles += cycles_target # TODO: Number of cycles for a HALT in effect?
128
127
  self.interrupt_queued = False
129
- return cycles
128
+
129
+ self.bail = False
130
+ while self.cycles < _target:
131
+ # TODO: cpu-stuck check for blargg tests?
132
+ self.fetch_and_execute()
133
+ if self.bail: # Possible cycles-target changes
134
+ break
130
135
 
131
136
  def check_interrupts(self):
132
137
  if self.interrupt_queued:
133
138
  # Interrupt already queued. This happens only when using a debugger.
134
139
  return False
135
140
 
136
- if (self.interrupts_flag_register & 0b11111) & (self.interrupts_enabled_register & 0b11111):
137
- if self.handle_interrupt(INTR_VBLANK, 0x0040):
138
- self.interrupt_queued = True
139
- elif self.handle_interrupt(INTR_LCDC, 0x0048):
140
- self.interrupt_queued = True
141
- elif self.handle_interrupt(INTR_TIMER, 0x0050):
142
- self.interrupt_queued = True
143
- elif self.handle_interrupt(INTR_SERIAL, 0x0058):
144
- self.interrupt_queued = True
145
- elif self.handle_interrupt(INTR_HIGHTOLOW, 0x0060):
146
- self.interrupt_queued = True
147
- else:
148
- logger.error("No interrupt triggered, but it should!")
149
- self.interrupt_queued = False
150
- return True
151
- else:
152
- self.interrupt_queued = False
153
- return False
154
-
155
- def handle_interrupt(self, flag, addr):
156
- if (self.interrupts_enabled_register & flag) and (self.interrupts_flag_register & flag):
141
+ raised_and_enabled = (self.interrupts_flag_register & 0b11111) & (self.interrupts_enabled_register & 0b11111)
142
+ if raised_and_enabled:
157
143
  # Clear interrupt flag
158
144
  if self.halted:
159
145
  self.PC += 1 # Escape HALT on return
160
146
  self.PC &= 0xFFFF
161
147
 
162
- # Handle interrupt vectors
163
148
  if self.interrupt_master_enable:
164
- self.interrupts_flag_register ^= flag # Remove flag
165
- self.mb.setitem((self.SP - 1) & 0xFFFF, self.PC >> 8) # High
166
- self.mb.setitem((self.SP - 2) & 0xFFFF, self.PC & 0xFF) # Low
167
- self.SP -= 2
168
- self.SP &= 0xFFFF
169
-
170
- self.PC = addr
171
- self.interrupt_master_enable = False
172
-
149
+ if raised_and_enabled & INTR_VBLANK:
150
+ self.handle_interrupt(INTR_VBLANK, 0x0040)
151
+ elif raised_and_enabled & INTR_LCDC:
152
+ self.handle_interrupt(INTR_LCDC, 0x0048)
153
+ elif raised_and_enabled & INTR_TIMER:
154
+ self.handle_interrupt(INTR_TIMER, 0x0050)
155
+ elif raised_and_enabled & INTR_SERIAL:
156
+ self.handle_interrupt(INTR_SERIAL, 0x0058)
157
+ elif raised_and_enabled & INTR_HIGHTOLOW:
158
+ self.handle_interrupt(INTR_HIGHTOLOW, 0x0060)
159
+ self.interrupt_queued = True
173
160
  return True
161
+ else:
162
+ self.interrupt_queued = False
174
163
  return False
175
164
 
165
+ def handle_interrupt(self, flag, addr):
166
+ self.interrupts_flag_register ^= flag # Remove flag
167
+ self.mb.setitem((self.SP - 1) & 0xFFFF, self.PC >> 8) # High
168
+ self.mb.setitem((self.SP - 2) & 0xFFFF, self.PC & 0xFF) # Low
169
+ self.SP -= 2
170
+ self.SP &= 0xFFFF
171
+
172
+ self.PC = addr
173
+ self.interrupt_master_enable = False
174
+
176
175
  def fetch_and_execute(self):
177
176
  opcode = self.mb.getitem(self.PC)
178
177
  if opcode == 0xCB: # Extension code
@@ -48,10 +48,11 @@ cdef class LCD:
48
48
  cdef PaletteRegister OBP1
49
49
  cdef Renderer renderer
50
50
  cdef uint8_t[144][5] _scanlineparameters
51
+ cdef uint64_t last_cycles
52
+ cdef int64_t _cycles_to_interrupt, _cycles_to_frame
51
53
 
52
54
  @cython.locals(interrupt_flag=uint8_t,bx=int,by=int,wx=int,wy=int)
53
55
  cdef uint8_t tick(self, int) noexcept nogil
54
- cdef int64_t cycles_to_interrupt(self) noexcept nogil
55
56
 
56
57
  cdef void set_lcdc(self, uint8_t) noexcept nogil
57
58
  cdef uint8_t get_lcdc(self) noexcept nogil
@@ -58,6 +58,9 @@ class LCD:
58
58
  self.double_speed = False
59
59
  self.cgb = cgb
60
60
  self._scanlineparameters = [[0, 0, 0, 0, 0] for _ in range(ROWS)]
61
+ self.last_cycles = 0
62
+ self._cycles_to_interrupt = 0
63
+ self._cycles_to_frame = FRAME_CYCLES
61
64
 
62
65
  if self.cgb:
63
66
  # Setting for both modes, even though CGB is ignoring them. BGP[0] used in scanline_blank.
@@ -103,9 +106,6 @@ class LCD:
103
106
  def set_stat(self, value):
104
107
  self._STAT.set(value)
105
108
 
106
- def cycles_to_interrupt(self):
107
- return self.clock_target - self.clock
108
-
109
109
  def cycles_to_mode0(self):
110
110
  multiplier = 2 if self.double_speed else 1
111
111
  mode2 = 80 * multiplier
@@ -130,7 +130,12 @@ class LCD:
130
130
  # logger.critical("Unsupported STAT mode: %d", mode)
131
131
  # return 0
132
132
 
133
- def tick(self, cycles):
133
+ def tick(self, _cycles):
134
+ cycles = _cycles - self.last_cycles
135
+ if cycles == 0:
136
+ return False
137
+ self.last_cycles = _cycles
138
+
134
139
  interrupt_flag = 0
135
140
  self.clock += cycles
136
141
 
@@ -205,6 +210,8 @@ class LCD:
205
210
  # Renderer
206
211
  self.renderer.blank_screen(self)
207
212
 
213
+ self._cycles_to_interrupt = self.clock_target - self.clock
214
+ self._cycles_to_frame = self.clock - FRAME_CYCLES
208
215
  return interrupt_flag
209
216
 
210
217
  def save_state(self, f):
@@ -239,6 +246,7 @@ class LCD:
239
246
  # CGB
240
247
  f.write(self.cgb)
241
248
  f.write(self.double_speed)
249
+ f.write_64bit(self.last_cycles)
242
250
  f.write_64bit(self.clock)
243
251
  f.write_64bit(self.clock_target)
244
252
  f.write(self.next_stat_mode)
@@ -293,8 +301,11 @@ class LCD:
293
301
  self.cgb = _cgb
294
302
  self.double_speed = f.read()
295
303
 
304
+ if state_version >= 12:
305
+ self.last_cycles = f.read_64bit()
296
306
  self.clock = f.read_64bit()
297
307
  self.clock_target = f.read_64bit()
308
+ self._cycles_to_frame = self.clock - FRAME_CYCLES
298
309
  self.next_stat_mode = f.read()
299
310
 
300
311
  if self.cgb:
@@ -21,6 +21,7 @@ from pyboy.utils cimport IntIOInterface, WindowEvent
21
21
 
22
22
  cdef Logger logger
23
23
 
24
+ cdef int64_t MAX_CYCLES
24
25
  cdef uint16_t STAT, LY, LYC
25
26
  cdef short VBLANK, LCDC, TIMER, SERIAL, HIGHTOLOW
26
27
  cdef int INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW
@@ -59,7 +60,7 @@ cdef class Motherboard:
59
60
 
60
61
  cdef void buttonevent(self, WindowEvent) noexcept
61
62
  cdef void stop(self, bint) noexcept
62
- @cython.locals(cycles=int64_t, mode0_cycles=int64_t, breakpoint_index=int64_t)
63
+ @cython.locals(cycles=int64_t, cycles_target=int64_t, mode0_cycles=int64_t, breakpoint_index=int64_t)
63
64
  cdef bint tick(self) noexcept nogil
64
65
 
65
66
  cdef void switch_speed(self) noexcept nogil
@@ -15,6 +15,8 @@ import pyboy
15
15
 
16
16
  logger = pyboy.logging.get_logger(__name__)
17
17
 
18
+ MAX_CYCLES = 1 << 16
19
+
18
20
 
19
21
  class Motherboard:
20
22
  def __init__(
@@ -276,11 +278,8 @@ class Motherboard:
276
278
  def tick(self):
277
279
  while self.processing_frame():
278
280
  if self.cgb and self.hdma.transfer_active and self.lcd._STAT._mode & 0b11 == 0:
279
- cycles = self.hdma.tick(self)
281
+ self.cpu.cycles = self.cpu.cycles + self.hdma.tick(self)
280
282
  else:
281
- cycles = self.cpu.tick()
282
-
283
- if self.cpu.halted:
284
283
  # Fast-forward to next interrupt:
285
284
  # As we are halted, we are guaranteed, that our state
286
285
  # cannot be altered by other factors than time.
@@ -288,35 +287,32 @@ class Motherboard:
288
287
  # it gets triggered mid-frame or by next frame
289
288
  # Serial is not implemented, so this isn't a concern
290
289
 
291
- # Help Cython with types
292
- mode0_cycles = 1 << 32
290
+ mode0_cycles = MAX_CYCLES
293
291
  if self.cgb and self.hdma.transfer_active:
294
292
  mode0_cycles = self.lcd.cycles_to_mode0()
295
293
 
296
- cycles = max(
297
- 0,
294
+ cycles_target = max(
295
+ 4,
298
296
  min(
299
- self.lcd.cycles_to_interrupt(),
300
- self.timer.cycles_to_interrupt(),
297
+ self.timer._cycles_to_interrupt,
298
+ self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled.
299
+ self.lcd._cycles_to_frame,
300
+ self.sound._cycles_to_interrupt, # TODO: Not implemented
301
301
  # self.serial.cycles_to_interrupt(),
302
302
  mode0_cycles
303
303
  )
304
304
  )
305
+ self.cpu.tick(cycles_target)
305
306
 
306
307
  #TODO: Support General Purpose DMA
307
308
  # https://gbdev.io/pandocs/CGB_Registers.html#bit-7--0---general-purpose-dma
308
309
 
309
- # TODO: Unify interface
310
- sclock = self.sound.clock
311
- if self.cgb and self.double_speed:
312
- self.sound.clock = sclock + cycles//2
313
- else:
314
- self.sound.clock = sclock + cycles
310
+ self.sound.tick(self.cpu.cycles, self.double_speed)
315
311
 
316
- if self.timer.tick(cycles):
312
+ if self.timer.tick(self.cpu.cycles):
317
313
  self.cpu.set_interruptflag(INTR_TIMER)
318
314
 
319
- lcd_interrupt = self.lcd.tick(cycles)
315
+ lcd_interrupt = self.lcd.tick(self.cpu.cycles)
320
316
  if lcd_interrupt:
321
317
  self.cpu.set_interruptflag(lcd_interrupt)
322
318
 
@@ -350,8 +346,7 @@ class Motherboard:
350
346
  bank_offset = 0
351
347
  if self.cgb and 0xD000 <= i:
352
348
  # Find which bank to read from at FF70
353
- bank = self.getitem(0xFF70)
354
- bank &= 0b111
349
+ bank = self.ram.non_io_internal_ram1[0xFF70 - 0xFF4C] & 0b111
355
350
  if bank == 0x0:
356
351
  bank = 0x01
357
352
  bank_offset = (bank-1) * 0x1000
@@ -364,6 +359,10 @@ class Motherboard:
364
359
  elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O
365
360
  return self.ram.non_io_internal_ram0[i - 0xFEA0]
366
361
  elif 0xFF00 <= i < 0xFF4C: # I/O ports
362
+ # NOTE: A bit ad-hoc, but interrupts can occur right between writes
363
+ if self.timer.tick(self.cpu.cycles):
364
+ self.cpu.set_interruptflag(INTR_TIMER)
365
+
367
366
  if i == 0xFF04:
368
367
  return self.timer.DIV
369
368
  elif i == 0xFF05:
@@ -375,6 +374,7 @@ class Motherboard:
375
374
  elif i == 0xFF0F:
376
375
  return self.cpu.interrupts_flag_register
377
376
  elif 0xFF10 <= i < 0xFF40:
377
+ self.sound.tick(self.cpu.cycles, self.double_speed)
378
378
  return self.sound.get(i - 0xFF10)
379
379
  elif i == 0xFF40:
380
380
  return self.lcd.get_lcdc()
@@ -442,9 +442,11 @@ class Motherboard:
442
442
  if 0x0000 <= i < 0x4000: # 16kB ROM bank #0
443
443
  # Doesn't change the data. This is for MBC commands
444
444
  self.cartridge.setitem(i, value)
445
+ self.cpu.bail = True
445
446
  elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank
446
447
  # Doesn't change the data. This is for MBC commands
447
448
  self.cartridge.setitem(i, value)
449
+ self.cpu.bail = True
448
450
  elif 0x8000 <= i < 0xA000: # 8kB Video RAM
449
451
  if not self.cgb or self.lcd.vbk.active_bank == 0:
450
452
  self.lcd.VRAM0[i - 0x8000] = value
@@ -475,6 +477,10 @@ class Motherboard:
475
477
  elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O
476
478
  self.ram.non_io_internal_ram0[i - 0xFEA0] = value
477
479
  elif 0xFF00 <= i < 0xFF4C: # I/O ports
480
+ # NOTE: A bit ad-hoc, but interrupts can occur right between writes
481
+ if self.timer.tick(self.cpu.cycles):
482
+ self.cpu.set_interruptflag(INTR_TIMER)
483
+
478
484
  if i == 0xFF00:
479
485
  self.ram.io_ports[i - 0xFF00] = self.interaction.pull(value)
480
486
  elif i == 0xFF01:
@@ -493,6 +499,7 @@ class Motherboard:
493
499
  elif i == 0xFF0F:
494
500
  self.cpu.interrupts_flag_register = value
495
501
  elif 0xFF10 <= i < 0xFF40:
502
+ self.sound.tick(self.cpu.cycles, self.double_speed)
496
503
  self.sound.set(i - 0xFF10, value)
497
504
  elif i == 0xFF40:
498
505
  self.lcd.set_lcdc(value)
@@ -526,13 +533,16 @@ class Motherboard:
526
533
  self.lcd.WX = value
527
534
  else:
528
535
  self.ram.io_ports[i - 0xFF00] = value
536
+ self.cpu.bail = True
529
537
  elif 0xFF4C <= i < 0xFF80: # Empty but unusable for I/O
530
538
  if self.bootrom_enabled and i == 0xFF50 and (value == 0x1 or value == 0x11):
531
539
  logger.debug("Bootrom disabled!")
532
540
  self.bootrom_enabled = False
541
+ self.cpu.bail = True
533
542
  # CGB registers
534
543
  elif self.cgb and i == 0xFF4D:
535
544
  self.key1 = value
545
+ self.cpu.bail = True
536
546
  elif self.cgb and i == 0xFF4F:
537
547
  self.lcd.vbk.set(value)
538
548
  elif self.cgb and i == 0xFF51:
@@ -545,6 +555,7 @@ class Motherboard:
545
555
  self.hdma.hdma4 = value # & 0xF0
546
556
  elif self.cgb and i == 0xFF55:
547
557
  self.hdma.set_hdma5(value, self)
558
+ self.cpu.bail = True
548
559
  elif self.cgb and i == 0xFF68:
549
560
  self.lcd.bcps.set(value)
550
561
  elif self.cgb and i == 0xFF69:
@@ -563,6 +574,7 @@ class Motherboard:
563
574
  self.ram.internal_ram1[i - 0xFF80] = value
564
575
  elif i == 0xFFFF: # Interrupt Enable Register
565
576
  self.cpu.interrupts_enabled_register = value
577
+ self.cpu.bail = True
566
578
  # else:
567
579
  # logger.critical("Memory access violation. Tried to write: 0x%0.2x to 0x%0.4x", value, i)
568
580