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.
- {pyboy-2.3.0/pyboy.egg-info → pyboy-2.4.1}/PKG-INFO +3 -3
- {pyboy-2.3.0 → pyboy-2.4.1}/README.md +2 -2
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/__main__.py +1 -1
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/gameshark.py +4 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cpu.pxd +6 -3
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cpu.py +44 -45
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/lcd.pxd +2 -1
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/lcd.py +15 -4
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/mb.pxd +2 -1
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/mb.py +32 -20
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/opcodes.py +584 -517
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/opcodes_gen.py +132 -19
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/sound.pxd +4 -5
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/sound.py +11 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/timer.pxd +2 -2
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/timer.py +19 -18
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug.py +8 -2
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/pyboy.pxd +3 -3
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/pyboy.py +21 -24
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/utils.py +1 -1
- {pyboy-2.3.0 → pyboy-2.4.1/pyboy.egg-info}/PKG-INFO +3 -3
- {pyboy-2.3.0 → pyboy-2.4.1}/pyproject.toml +1 -1
- {pyboy-2.3.0 → pyboy-2.4.1}/LICENSE.md +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/MANIFEST.in +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/__init__.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/__init__.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/constants.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/gameshark.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/memory_scanner.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/memory_scanner.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/screen.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/screen.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/sprite.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/sprite.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tile.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tile.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tilemap.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/api/tilemap.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/conftest.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/__init__.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom_cgb.bin +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/bootrom_dmg.bin +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/__init__.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/base_mbc.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/base_mbc.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/cartridge.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/cartridge.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc1.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc1.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc2.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc2.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc3.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc3.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc5.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/mbc5.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/rtc.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/cartridge/rtc.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/interaction.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/interaction.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/opcodes.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/ram.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/core/ram.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/default_rom.gb +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/__init__.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/_logging.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/logging.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/logging/logging.pyx +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/__init__.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/auto_pause.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/auto_pause.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/base_plugin.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/base_plugin.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug_prompt.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/debug_prompt.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/disable_input.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/disable_input.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/font.txt +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_kirby_dream_land.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_kirby_dream_land.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_gen1.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_gen1.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_pinball.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_pokemon_pinball.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_super_mario_land.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_super_mario_land.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_tetris.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/game_wrapper_tetris.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/manager.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/manager.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/manager_gen.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/record_replay.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/record_replay.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/rewind.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/rewind.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screen_recorder.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screen_recorder.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screenshot_recorder.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/screenshot_recorder.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_null.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_null.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_open_gl.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_open_gl.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_sdl2.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/plugins/window_sdl2.py +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy/utils.pxd +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/SOURCES.txt +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/dependency_links.txt +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/entry_points.txt +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/not-zip-safe +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/requires.txt +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/pyboy.egg-info/top_level.txt +0 -0
- {pyboy-2.3.0 → pyboy-2.4.1}/setup.cfg +0 -0
- {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
|
+
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
|
|
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
|
|
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>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
#
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
from libc.stdint cimport int16_t, uint8_t, uint16_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
|
-
|
|
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.
|
|
97
|
-
f"Timer Intr.: {self.mb.timer.
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
294
|
+
cycles_target = max(
|
|
295
|
+
4,
|
|
298
296
|
min(
|
|
299
|
-
self.
|
|
300
|
-
self.
|
|
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
|
-
|
|
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.
|
|
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
|
|