From 208f110fa89a6cbd124fe4f9670b5935a4f5740b Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 23 Sep 2024 17:17:38 +0200 Subject: [PATCH 01/51] Change TEST_CI to TEST_VERBOSE_IMAGES and invert semantic --- .github/workflows/pr-test.yml | 4 ++-- tests/test_acid_cgb.py | 2 +- tests/test_acid_dmg.py | 2 +- tests/test_basics.py | 2 +- tests/test_breakpoints.py | 2 +- tests/test_magen.py | 2 +- tests/test_mooneye.py | 2 +- tests/test_replay.py | 2 +- tests/test_rtc3test.py | 2 +- tests/test_samesuite.py | 2 +- tests/test_shonumi.py | 2 +- tests/test_which.py | 2 +- tests/test_whichboot.py | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index fa4875550..4d0ec332c 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -46,7 +46,7 @@ jobs: - name: Run PyTest env: PYTEST_SECRETS_KEY: ${{ secrets.PYTEST_SECRETS_KEY }} - TEST_CI: 1 + TEST_VERBOSE_IMAGES: 0 TEST_NO_UI: 1 run: | python -m pytest tests/ -n auto -v @@ -111,7 +111,7 @@ jobs: - name: Run PyTest env: PYTEST_SECRETS_KEY: ${{ secrets.PYTEST_SECRETS_KEY }} - TEST_CI: 1 + TEST_VERBOSE_IMAGES: 0 TEST_NO_UI: 1 run: | pypy3 -m pytest tests/ -n auto -v diff --git a/tests/test_acid_cgb.py b/tests/test_acid_cgb.py index 41f21c1c8..1a5e65db7 100644 --- a/tests/test_acid_cgb.py +++ b/tests/test_acid_cgb.py @@ -29,7 +29,7 @@ def test_cgb_acid(cgb_acid_file): # Converting to RGB as ImageChops.difference cannot handle Alpha: https://github.com/python-pillow/Pillow/issues/4849 old_image = PIL.Image.open(png_path).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_acid_dmg.py b/tests/test_acid_dmg.py index adf942af1..08694a0a9 100644 --- a/tests/test_acid_dmg.py +++ b/tests/test_acid_dmg.py @@ -30,7 +30,7 @@ def test_dmg_acid(cgb, dmg_acid_file): # Converting to RGB as ImageChops.difference cannot handle Alpha: https://github.com/python-pillow/Pillow/issues/4849 old_image = PIL.Image.open(png_path).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_basics.py b/tests/test_basics.py index 751f4ad85..612214cec 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -284,7 +284,7 @@ def test_all_modes(cgb, _bootrom, frames, rom, any_rom_cgb, boot_cgb_rom): old_image = PIL.Image.open(png_buf).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_breakpoints.py b/tests/test_breakpoints.py index 10e75b121..52f2512fc 100644 --- a/tests/test_breakpoints.py +++ b/tests/test_breakpoints.py @@ -254,7 +254,7 @@ def test_data_hooking_failure(default_rom): image1 = pyboy1.screen.image.convert("RGB") image2 = pyboy2.screen.image.convert("RGB") diff = PIL.ImageChops.difference(image1, image2) - if not diff.getbbox() and not os.environ.get("TEST_CI"): + if not diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image1.show() image2.show() diff.show() diff --git a/tests/test_magen.py b/tests/test_magen.py index e3999bd9f..cee03f386 100644 --- a/tests/test_magen.py +++ b/tests/test_magen.py @@ -29,7 +29,7 @@ def test_magen_test(magen_test_file): # Converting to RGB as ImageChops.difference cannot handle Alpha: https://github.com/python-pillow/Pillow/issues/4849 old_image = PIL.Image.open(png_path).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_mooneye.py b/tests/test_mooneye.py index c5bf9b705..a70655181 100644 --- a/tests/test_mooneye.py +++ b/tests/test_mooneye.py @@ -185,7 +185,7 @@ def test_mooneye(clean, rom, mooneye_dir, default_rom): else: diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_replay.py b/tests/test_replay.py index d855def23..5ccebe3e6 100644 --- a/tests/test_replay.py +++ b/tests/test_replay.py @@ -31,7 +31,7 @@ def verify_screen_image_np(pyboy, saved_array): match = np.all(np.frombuffer(saved_array, dtype=np.uint8).reshape(144, 160, 3) == pyboy.screen.ndarray) - if not match and not os.environ.get("TEST_CI"): + if not match and os.environ.get("TEST_VERBOSE_IMAGES"): from PIL import Image original = Image.frombytes("RGB", (160, 144), np.frombuffer(saved_array, dtype=np.uint8).reshape(144, 160, 3)) original.show() diff --git a/tests/test_rtc3test.py b/tests/test_rtc3test.py index 3118d6536..eb66feb0c 100644 --- a/tests/test_rtc3test.py +++ b/tests/test_rtc3test.py @@ -45,7 +45,7 @@ def test_rtc3test(subtest, rtc3test_file): # Converting to RGB as ImageChops.difference cannot handle Alpha: https://github.com/python-pillow/Pillow/issues/4849 old_image = PIL.Image.open(png_path).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_samesuite.py b/tests/test_samesuite.py index 16e0e5bba..edf36170c 100644 --- a/tests/test_samesuite.py +++ b/tests/test_samesuite.py @@ -155,7 +155,7 @@ def test_samesuite(clean, gb_type, rom, samesuite_dir, boot_cgb_rom, boot_rom, d old_image = PIL.Image.open(png_path).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_shonumi.py b/tests/test_shonumi.py index 01adfe01b..e9aaa6e48 100644 --- a/tests/test_shonumi.py +++ b/tests/test_shonumi.py @@ -37,7 +37,7 @@ def test_shonumi(rom, shonumi_dir): old_image = old_image.resize(image.size, resample=PIL.Image.Dither.NONE) diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_which.py b/tests/test_which.py index afa67f46e..2a358afe4 100644 --- a/tests/test_which.py +++ b/tests/test_which.py @@ -30,7 +30,7 @@ def test_which(cgb, which_file): # Converting to RGB as ImageChops.difference cannot handle Alpha: https://github.com/python-pillow/Pillow/issues/4849 old_image = PIL.Image.open(png_path).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() diff --git a/tests/test_whichboot.py b/tests/test_whichboot.py index 34d5bdb19..deee0e46b 100644 --- a/tests/test_whichboot.py +++ b/tests/test_whichboot.py @@ -30,7 +30,7 @@ def test_which(cgb, whichboot_file): # Converting to RGB as ImageChops.difference cannot handle Alpha: https://github.com/python-pillow/Pillow/issues/4849 old_image = PIL.Image.open(png_path).convert("RGB") diff = PIL.ImageChops.difference(image.convert("RGB"), old_image) - if diff.getbbox() and not os.environ.get("TEST_CI"): + if diff.getbbox() and os.environ.get("TEST_VERBOSE_IMAGES"): image.show() old_image.show() diff.show() From 9c8a7eea9badfc28cb06fde3789b150e85e0a97d Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Fri, 20 Sep 2024 21:30:39 +0200 Subject: [PATCH 02/51] Try to quit debug window better --- pyboy/plugins/debug.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyboy/plugins/debug.py b/pyboy/plugins/debug.py index 012bbbca6..25520cad7 100644 --- a/pyboy/plugins/debug.py +++ b/pyboy/plugins/debug.py @@ -205,8 +205,14 @@ def handle_events(self, events): return events def stop(self): - if self.sdl2_event_pump: - sdl2.SDL_Quit() + self.tile1.stop() + self.tile2.stop() + self.tiledata0.stop() + if self.cgb: + self.tiledata1.stop() + self.sprite.stop() + self.spriteview.stop() + self.memory.stop() def enabled(self): if self.pyboy_argv.get("debug"): From 9407c384243cb51a55937d8a5fb5ce60c53ad169 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 16 Sep 2024 21:09:54 +0200 Subject: [PATCH 03/51] Remove redundant definitions in sound.pxd --- pyboy/core/sound.pxd | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyboy/core/sound.pxd b/pyboy/core/sound.pxd index 322ef25df..5a13b8e2f 100644 --- a/pyboy/core/sound.pxd +++ b/pyboy/core/sound.pxd @@ -91,11 +91,7 @@ cdef class SweepChannel(ToneChannel): cdef bint sweepenable # Internal sweep enable flag cdef int shadow # Shadow copy of period register for ignoring writes to sndper - cdef uint8_t getreg(self, uint8_t) noexcept nogil - cdef void setreg(self, uint8_t, uint8_t) noexcept nogil cdef bint sweep(self, bint) noexcept nogil - cdef void trigger(self) noexcept nogil - cdef void tickframe(self) noexcept nogil cdef class WaveChannel: From 6e6e0d8056dd9edc691cdbf9530603b924bb122e Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Tue, 17 Sep 2024 17:24:45 +0100 Subject: [PATCH 04/51] Restructure CPU interrupt handling --- pyboy/core/cpu.py | 56 +++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 288e16efd..a24d36e11 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -133,46 +133,40 @@ def check_interrupts(self): # Interrupt already queued. This happens only when using a debugger. return False - if (self.interrupts_flag_register & 0b11111) & (self.interrupts_enabled_register & 0b11111): - if self.handle_interrupt(INTR_VBLANK, 0x0040): - self.interrupt_queued = True - elif self.handle_interrupt(INTR_LCDC, 0x0048): - self.interrupt_queued = True - elif self.handle_interrupt(INTR_TIMER, 0x0050): - self.interrupt_queued = True - elif self.handle_interrupt(INTR_SERIAL, 0x0058): - self.interrupt_queued = True - elif self.handle_interrupt(INTR_HIGHTOLOW, 0x0060): - self.interrupt_queued = True - else: - logger.error("No interrupt triggered, but it should!") - self.interrupt_queued = False - return True - else: - self.interrupt_queued = False - return False - - def handle_interrupt(self, flag, addr): - if (self.interrupts_enabled_register & flag) and (self.interrupts_flag_register & flag): + raised_and_enabled = (self.interrupts_flag_register & 0b11111) & (self.interrupts_enabled_register & 0b11111) + if raised_and_enabled: # Clear interrupt flag if self.halted: self.PC += 1 # Escape HALT on return self.PC &= 0xFFFF - # Handle interrupt vectors if self.interrupt_master_enable: - self.interrupts_flag_register ^= flag # Remove flag - self.mb.setitem((self.SP - 1) & 0xFFFF, self.PC >> 8) # High - self.mb.setitem((self.SP - 2) & 0xFFFF, self.PC & 0xFF) # Low - self.SP -= 2 - self.SP &= 0xFFFF - - self.PC = addr - self.interrupt_master_enable = False - + if raised_and_enabled & INTR_VBLANK: + self.handle_interrupt(INTR_VBLANK, 0x0040) + elif raised_and_enabled & INTR_LCDC: + self.handle_interrupt(INTR_LCDC, 0x0048) + elif raised_and_enabled & INTR_TIMER: + self.handle_interrupt(INTR_TIMER, 0x0050) + elif raised_and_enabled & INTR_SERIAL: + self.handle_interrupt(INTR_SERIAL, 0x0058) + elif raised_and_enabled & INTR_HIGHTOLOW: + self.handle_interrupt(INTR_HIGHTOLOW, 0x0060) + self.interrupt_queued = True return True + else: + self.interrupt_queued = False return False + def handle_interrupt(self, flag, addr): + self.interrupts_flag_register ^= flag # Remove flag + self.mb.setitem((self.SP - 1) & 0xFFFF, self.PC >> 8) # High + self.mb.setitem((self.SP - 2) & 0xFFFF, self.PC & 0xFF) # Low + self.SP -= 2 + self.SP &= 0xFFFF + + self.PC = addr + self.interrupt_master_enable = False + def fetch_and_execute(self): opcode = self.mb.getitem(self.PC) if opcode == 0xCB: # Extension code From 2d0620836224563fc7f13f96052f926d55f79848 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 16 Sep 2024 21:09:29 +0200 Subject: [PATCH 05/51] Simplify CGB bank read in MB getitem --- pyboy/core/mb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 78264a527..b717b9065 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -350,8 +350,7 @@ def getitem(self, i): bank_offset = 0 if self.cgb and 0xD000 <= i: # Find which bank to read from at FF70 - bank = self.getitem(0xFF70) - bank &= 0b111 + bank = self.ram.non_io_internal_ram1[0xFF70 - 0xFF4C] & 0b111 if bank == 0x0: bank = 0x01 bank_offset = (bank-1) * 0x1000 From de5002720ac45f2d01ef93dd370385f886353653 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 28 Jul 2024 21:34:52 -0700 Subject: [PATCH 06/51] Fix blargg tests for CPU without is_stuck --- tests/test_blargg.py | 124 +++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/tests/test_blargg.py b/tests/test_blargg.py index eac9731a7..420a93ce9 100644 --- a/tests/test_blargg.py +++ b/tests/test_blargg.py @@ -20,7 +20,7 @@ blargg_json = "tests/test_results/blargg.json" -def run_rom(rom): +def run_rom(rom, max_frames): pyboy = PyBoy(str(rom), window="null", cgb="cgb" in rom, sound_emulated=True) pyboy.set_emulation_speed(0) t = time.time() @@ -31,7 +31,7 @@ def run_rom(rom): result += b t = time.time() - if pyboy._is_cpu_stuck(): + if pyboy._is_cpu_stuck() or pyboy.frame_count > max_frames: break pyboy.tick(10, False) @@ -51,69 +51,69 @@ def run_rom(rom): @pytest.mark.parametrize( - "test_rom", [ - "cgb_sound/cgb_sound.gb", - "cgb_sound/rom_singles/01-registers.gb", - "cgb_sound/rom_singles/02-len ctr.gb", - "cgb_sound/rom_singles/03-trigger.gb", - "cgb_sound/rom_singles/04-sweep.gb", - "cgb_sound/rom_singles/05-sweep details.gb", - "cgb_sound/rom_singles/06-overflow on trigger.gb", - "cgb_sound/rom_singles/07-len sweep period sync.gb", - "cgb_sound/rom_singles/08-len ctr during power.gb", - "cgb_sound/rom_singles/09-wave read while on.gb", - "cgb_sound/rom_singles/10-wave trigger while on.gb", - "cgb_sound/rom_singles/11-regs after power.gb", - "cgb_sound/rom_singles/12-wave.gb", - "cpu_instrs/cpu_instrs.gb", - "cpu_instrs/individual/01-special.gb", - "cpu_instrs/individual/02-interrupts.gb", - "cpu_instrs/individual/03-op sp,hl.gb", - "cpu_instrs/individual/04-op r,imm.gb", - "cpu_instrs/individual/05-op rp.gb", - "cpu_instrs/individual/06-ld r,r.gb", - "cpu_instrs/individual/07-jr,jp,call,ret,rst.gb", - "cpu_instrs/individual/08-misc instrs.gb", - "cpu_instrs/individual/09-op r,r.gb", - "cpu_instrs/individual/10-bit ops.gb", - "cpu_instrs/individual/11-op a,(hl).gb", - "dmg_sound/dmg_sound.gb", - "dmg_sound/rom_singles/01-registers.gb", - "dmg_sound/rom_singles/02-len ctr.gb", - "dmg_sound/rom_singles/03-trigger.gb", - "dmg_sound/rom_singles/04-sweep.gb", - "dmg_sound/rom_singles/05-sweep details.gb", - "dmg_sound/rom_singles/06-overflow on trigger.gb", - "dmg_sound/rom_singles/07-len sweep period sync.gb", - "dmg_sound/rom_singles/08-len ctr during power.gb", - "dmg_sound/rom_singles/09-wave read while on.gb", - "dmg_sound/rom_singles/10-wave trigger while on.gb", - "dmg_sound/rom_singles/11-regs after power.gb", - "dmg_sound/rom_singles/12-wave write while on.gb", - "instr_timing/instr_timing.gb", - "interrupt_time/interrupt_time.gb", - "mem_timing/individual/01-read_timing.gb", - "mem_timing/individual/02-write_timing.gb", - "mem_timing/individual/03-modify_timing.gb", - "mem_timing/mem_timing.gb", - "mem_timing-2/mem_timing.gb", - "mem_timing-2/rom_singles/01-read_timing.gb", - "mem_timing-2/rom_singles/02-write_timing.gb", - "mem_timing-2/rom_singles/03-modify_timing.gb", - "oam_bug/oam_bug.gb", - "oam_bug/rom_singles/1-lcd_sync.gb", - "oam_bug/rom_singles/2-causes.gb", - "oam_bug/rom_singles/3-non_causes.gb", - "oam_bug/rom_singles/4-scanline_timing.gb", - "oam_bug/rom_singles/5-timing_bug.gb", - "oam_bug/rom_singles/6-timing_no_bug.gb", - "oam_bug/rom_singles/7-timing_effect.gb", - "oam_bug/rom_singles/8-instr_effect.gb", + "test_rom, max_frames", [ + ("cgb_sound/cgb_sound.gb", 4_000), + ("cgb_sound/rom_singles/01-registers.gb", 700), + ("cgb_sound/rom_singles/02-len ctr.gb", 700), + ("cgb_sound/rom_singles/03-trigger.gb", 700), + ("cgb_sound/rom_singles/04-sweep.gb", 700), + ("cgb_sound/rom_singles/05-sweep details.gb", 700), + ("cgb_sound/rom_singles/06-overflow on trigger.gb", 700), + ("cgb_sound/rom_singles/07-len sweep period sync.gb", 700), + ("cgb_sound/rom_singles/08-len ctr during power.gb", 700), + ("cgb_sound/rom_singles/09-wave read while on.gb", 700), + ("cgb_sound/rom_singles/10-wave trigger while on.gb", 700), + ("cgb_sound/rom_singles/11-regs after power.gb", 700), + ("cgb_sound/rom_singles/12-wave.gb", 700), + ("cpu_instrs/cpu_instrs.gb", 4_000), + ("cpu_instrs/individual/01-special.gb", 700), + ("cpu_instrs/individual/02-interrupts.gb", 700), + ("cpu_instrs/individual/03-op sp,hl.gb", 700), + ("cpu_instrs/individual/04-op r,imm.gb", 700), + ("cpu_instrs/individual/05-op rp.gb", 700), + ("cpu_instrs/individual/06-ld r,r.gb", 700), + ("cpu_instrs/individual/07-jr,jp,call,ret,rst.gb", 700), + ("cpu_instrs/individual/08-misc instrs.gb", 700), + ("cpu_instrs/individual/09-op r,r.gb", 700), + ("cpu_instrs/individual/10-bit ops.gb", 2_000), + ("cpu_instrs/individual/11-op a,(hl).gb", 2_000), + ("dmg_sound/dmg_sound.gb", 700), + ("dmg_sound/rom_singles/01-registers.gb", 700), + ("dmg_sound/rom_singles/02-len ctr.gb", 700), + ("dmg_sound/rom_singles/03-trigger.gb", 700), + ("dmg_sound/rom_singles/04-sweep.gb", 700), + ("dmg_sound/rom_singles/05-sweep details.gb", 700), + ("dmg_sound/rom_singles/06-overflow on trigger.gb", 700), + ("dmg_sound/rom_singles/07-len sweep period sync.gb", 700), + ("dmg_sound/rom_singles/08-len ctr during power.gb", 700), + ("dmg_sound/rom_singles/09-wave read while on.gb", 700), + ("dmg_sound/rom_singles/10-wave trigger while on.gb", 700), + ("dmg_sound/rom_singles/11-regs after power.gb", 700), + ("dmg_sound/rom_singles/12-wave write while on.gb", 700), + ("instr_timing/instr_timing.gb", 2_000), + ("interrupt_time/interrupt_time.gb", 700), + ("mem_timing/individual/01-read_timing.gb", 700), + ("mem_timing/individual/02-write_timing.gb", 700), + ("mem_timing/individual/03-modify_timing.gb", 700), + ("mem_timing/mem_timing.gb", 700), + ("mem_timing-2/mem_timing.gb", 700), + ("mem_timing-2/rom_singles/01-read_timing.gb", 700), + ("mem_timing-2/rom_singles/02-write_timing.gb", 700), + ("mem_timing-2/rom_singles/03-modify_timing.gb", 700), + ("oam_bug/oam_bug.gb", 2_000), + ("oam_bug/rom_singles/1-lcd_sync.gb", 700), + ("oam_bug/rom_singles/2-causes.gb", 700), + ("oam_bug/rom_singles/3-non_causes.gb", 700), + ("oam_bug/rom_singles/4-scanline_timing.gb", 700), + ("oam_bug/rom_singles/5-timing_bug.gb", 700), + ("oam_bug/rom_singles/6-timing_no_bug.gb", 700), + ("oam_bug/rom_singles/7-timing_effect.gb", 700), + ("oam_bug/rom_singles/8-instr_effect.gb", 700), ] ) -def test_blarggs(test_rom, blargg_dir): +def test_blarggs(test_rom, max_frames, blargg_dir): rom = str(blargg_dir / Path(test_rom)) - result = run_rom(rom) + result = run_rom(rom, max_frames) if os.path.isfile(blargg_json): with open(blargg_json, "r") as f: From f1faa97ed30748234da328a0b68470461a862118 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 29 Sep 2024 17:23:02 +0200 Subject: [PATCH 07/51] Add places to bail in MB and opcodes but not CPU --- pyboy/core/cpu.pxd | 2 +- pyboy/core/mb.py | 7 +++++++ pyboy/core/opcodes.py | 3 +++ pyboy/core/opcodes_gen.py | 9 +++++++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pyboy/core/cpu.pxd b/pyboy/core/cpu.pxd index f1cf0d393..6b803d76a 100644 --- a/pyboy/core/cpu.pxd +++ b/pyboy/core/cpu.pxd @@ -26,7 +26,7 @@ cdef uint8_t INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW cdef class CPU: cdef bint is_stuck - cdef bint interrupt_master_enable, interrupt_queued, halted, stopped + cdef bint interrupt_master_enable, interrupt_queued, halted, stopped, bail cdef uint8_t interrupts_flag, interrupts_enabled, interrupts_flag_register, interrupts_enabled_register diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index b717b9065..78ead0825 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -441,9 +441,11 @@ def setitem(self, i, value): if 0x0000 <= i < 0x4000: # 16kB ROM bank #0 # Doesn't change the data. This is for MBC commands self.cartridge.setitem(i, value) + self.cpu.bail = True elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank # Doesn't change the data. This is for MBC commands self.cartridge.setitem(i, value) + self.cpu.bail = True elif 0x8000 <= i < 0xA000: # 8kB Video RAM if not self.cgb or self.lcd.vbk.active_bank == 0: self.lcd.VRAM0[i - 0x8000] = value @@ -525,13 +527,16 @@ def setitem(self, i, value): self.lcd.WX = value else: self.ram.io_ports[i - 0xFF00] = value + self.cpu.bail = True elif 0xFF4C <= i < 0xFF80: # Empty but unusable for I/O if self.bootrom_enabled and i == 0xFF50 and (value == 0x1 or value == 0x11): logger.debug("Bootrom disabled!") self.bootrom_enabled = False + self.cpu.bail = True # CGB registers elif self.cgb and i == 0xFF4D: self.key1 = value + self.cpu.bail = True elif self.cgb and i == 0xFF4F: self.lcd.vbk.set(value) elif self.cgb and i == 0xFF51: @@ -544,6 +549,7 @@ def setitem(self, i, value): self.hdma.hdma4 = value # & 0xF0 elif self.cgb and i == 0xFF55: self.hdma.set_hdma5(value, self) + self.cpu.bail = True elif self.cgb and i == 0xFF68: self.lcd.bcps.set(value) elif self.cgb and i == 0xFF69: @@ -562,6 +568,7 @@ def setitem(self, i, value): self.ram.internal_ram1[i - 0xFF80] = value elif i == 0xFFFF: # Interrupt Enable Register self.cpu.interrupts_enabled_register = value + self.cpu.bail = True # else: # logger.critical("Memory access violation. Tried to write: 0x%0.2x to 0x%0.4x", value, i) diff --git a/pyboy/core/opcodes.py b/pyboy/core/opcodes.py index 743036227..d90ede52e 100644 --- a/pyboy/core/opcodes.py +++ b/pyboy/core/opcodes.py @@ -12,6 +12,7 @@ FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8) def BRK(cpu): + cpu.bail = True cpu.mb.breakpoint_singlestep = 1 cpu.mb.breakpoint_singlestep_latch = 0 # NOTE: We do not increment PC @@ -2332,6 +2333,7 @@ def RET_D8(cpu): # D8 RET C def RETI_D9(cpu): # D9 RETI cpu.interrupt_master_enable = True + cpu.bail = (cpu.interrupts_flag_register & 0b11111) & (cpu.interrupts_enabled_register & 0b11111) cpu.PC = cpu.mb.getitem((cpu.SP + 1) & 0xFFFF) << 8 # High cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 @@ -2591,6 +2593,7 @@ def LD_FA(cpu, v): # FA LD A,(a16) def EI_FB(cpu): # FB EI cpu.interrupt_master_enable = True + cpu.bail = (cpu.interrupts_flag_register & 0b11111) & (cpu.interrupts_enabled_register & 0b11111) cpu.PC += 1 cpu.PC &= 0xFFFF return 4 diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index 279e94462..333ceb231 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -26,6 +26,7 @@ FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8) def BRK(cpu): + cpu.bail = True cpu.mb.breakpoint_singlestep = 1 cpu.mb.breakpoint_singlestep_latch = 0 # NOTE: We do not increment PC @@ -470,7 +471,10 @@ def CB(self): def EI(self): code = Code(self.name.split()[0], self.opcode, self.name, 0, self.length, self.cycles) - code.addline("cpu.interrupt_master_enable = True") + code.addlines([ + "cpu.interrupt_master_enable = True", + "cpu.bail = (cpu.interrupts_flag_register & 0b11111) & (cpu.interrupts_enabled_register & 0b11111)", + ]) return code.getcode() def DI(self): @@ -1001,8 +1005,9 @@ def RET(self): def RETI(self): code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles, branch_op=True) - code.addline("cpu.interrupt_master_enable = True") code.addlines([ + "cpu.interrupt_master_enable = True", + "cpu.bail = (cpu.interrupts_flag_register & 0b11111) & (cpu.interrupts_enabled_register & 0b11111)", "cpu.PC = cpu.mb.getitem((cpu.SP + 1) & 0xFFFF) << 8 # High", "cpu.PC |= cpu.mb.getitem(cpu.SP) # Low", "cpu.SP += 2", From b32e6274989f838658a597e8efb498fac926dd6b Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 29 Sep 2024 17:26:47 +0200 Subject: [PATCH 08/51] Implement cycles target for CPU with support to bail --- pyboy/core/cpu.pxd | 7 ++++-- pyboy/core/cpu.py | 55 ++++++++++++++++++++++++++-------------------- pyboy/core/mb.pxd | 3 ++- pyboy/core/mb.py | 18 ++++++++------- pyboy/utils.py | 2 +- 5 files changed, 49 insertions(+), 36 deletions(-) diff --git a/pyboy/core/cpu.pxd b/pyboy/core/cpu.pxd index 6b803d76a..c7d58703e 100644 --- a/pyboy/core/cpu.pxd +++ b/pyboy/core/cpu.pxd @@ -4,7 +4,7 @@ # -from libc.stdint cimport int16_t, uint8_t, uint16_t, uint32_t, uint64_t +from libc.stdint cimport int16_t, uint8_t, uint16_t, int64_t cimport pyboy.core.mb from pyboy.utils cimport IntIOInterface @@ -30,13 +30,16 @@ cdef class CPU: cdef uint8_t interrupts_flag, interrupts_enabled, interrupts_flag_register, interrupts_enabled_register + cdef int64_t cycles + cdef inline int check_interrupts(self) noexcept nogil cdef void set_interruptflag(self, int) noexcept nogil cdef bint handle_interrupt(self, uint8_t, uint16_t) noexcept nogil @cython.locals(opcode=uint16_t) cdef inline uint8_t fetch_and_execute(self) noexcept nogil - cdef int tick(self) noexcept nogil + @cython.locals(_cycles0=int64_t) + cdef int tick(self, int64_t) noexcept nogil cdef void save_state(self, IntIOInterface) noexcept cdef void load_state(self, IntIOInterface, int) noexcept diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index a24d36e11..90c72ef97 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -39,6 +39,7 @@ def __init__(self, mb): self.halted = False self.stopped = False self.is_stuck = False + self.cycles = 0 def save_state(self, f): for n in [self.A, self.F, self.B, self.C, self.D, self.E]: @@ -53,6 +54,7 @@ def save_state(self, f): f.write(self.interrupts_enabled_register) f.write(self.interrupt_queued) f.write(self.interrupts_flag_register) + f.write_64bit(self.cycles) def load_state(self, f, state_version): self.A, self.F, self.B, self.C, self.D, self.E = [f.read() for _ in range(6)] @@ -69,6 +71,8 @@ def load_state(self, f, state_version): if state_version >= 8: self.interrupt_queued = f.read() self.interrupts_flag_register = f.read() + if state_version >= 12: + self.cycles = f.read_64bit() logger.debug("State loaded: %s", self.dump_state("")) def dump_state(self, sym_label): @@ -103,30 +107,33 @@ def dump_state(self, sym_label): def set_interruptflag(self, flag): self.interrupts_flag_register |= flag - def tick(self): - if self.check_interrupts(): - self.halted = False - # TODO: We return with the cycles it took to handle the interrupt - return 0 - - if self.halted and self.interrupt_queued: - # GBCPUman.pdf page 20 - # WARNING: The instruction immediately following the HALT instruction is "skipped" when interrupts are - # disabled (DI) on the GB,GBP, and SGB. - self.halted = False - self.PC += 1 - self.PC &= 0xFFFF - elif self.halted: - return 4 # TODO: Number of cycles for a HALT in effect? - - old_pc = self.PC # If the PC doesn't change, we're likely stuck - old_sp = self.SP # Sometimes a RET can go to the same PC, so we check the SP too. - cycles = self.fetch_and_execute() - 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: - logger.debug("CPU is stuck: %s", self.dump_state("")) - self.is_stuck = True - self.interrupt_queued = False - return cycles + def tick(self, cycles_target): + _cycles0 = self.cycles + _target = _cycles0 + cycles_target + self.bail = False + while self.cycles < _target: + if self.check_interrupts(): + self.halted = False + # TODO: We return with the cycles it took to handle the interrupt + break + # return cycles + + if self.halted and self.interrupt_queued: + # GBCPUman.pdf page 20 + # WARNING: The instruction immediately following the HALT instruction is "skipped" when interrupts are + # disabled (DI) on the GB,GBP, and SGB. + self.halted = False + self.PC += 1 + self.PC &= 0xFFFF + elif self.halted: + self.cycles += cycles_target # TODO: Number of cycles for a HALT in effect? + break + + self.interrupt_queued = False + + self.cycles += self.fetch_and_execute() + if self.bail: # Possible cycles-target changes + break def check_interrupts(self): if self.interrupt_queued: diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index c43f7fb18..065c12b6d 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -21,6 +21,7 @@ from pyboy.utils cimport IntIOInterface, WindowEvent cdef Logger logger +cdef int64_t MAX_CYCLES cdef uint16_t STAT, LY, LYC cdef short VBLANK, LCDC, TIMER, SERIAL, HIGHTOLOW cdef int INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW @@ -59,7 +60,7 @@ cdef class Motherboard: cdef void buttonevent(self, WindowEvent) noexcept cdef void stop(self, bint) noexcept - @cython.locals(cycles=int64_t, mode0_cycles=int64_t, breakpoint_index=int64_t) + @cython.locals(cycles=int64_t, cycles_target=int64_t, mode0_cycles=int64_t, breakpoint_index=int64_t) cdef bint tick(self) noexcept nogil cdef void switch_speed(self) noexcept nogil diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 78ead0825..bc78e3c09 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -15,6 +15,8 @@ logger = pyboy.logging.get_logger(__name__) +MAX_CYCLES = 1 << 16 + class Motherboard: def __init__( @@ -275,12 +277,10 @@ def processing_frame(self): def tick(self): while self.processing_frame(): + _cycles0 = self.cpu.cycles if self.cgb and self.hdma.transfer_active and self.lcd._STAT._mode & 0b11 == 0: - cycles = self.hdma.tick(self) + self.cpu.cycles = self.cpu.cycles + self.hdma.tick(self) else: - cycles = self.cpu.tick() - - if self.cpu.halted: # Fast-forward to next interrupt: # As we are halted, we are guaranteed, that our state # cannot be altered by other factors than time. @@ -288,13 +288,12 @@ def tick(self): # it gets triggered mid-frame or by next frame # Serial is not implemented, so this isn't a concern - # Help Cython with types - mode0_cycles = 1 << 32 + mode0_cycles = MAX_CYCLES if self.cgb and self.hdma.transfer_active: mode0_cycles = self.lcd.cycles_to_mode0() - cycles = max( - 0, + cycles_target = max( + 4, min( self.lcd.cycles_to_interrupt(), self.timer.cycles_to_interrupt(), @@ -302,10 +301,13 @@ def tick(self): mode0_cycles ) ) + self.cpu.tick(cycles_target) #TODO: Support General Purpose DMA # https://gbdev.io/pandocs/CGB_Registers.html#bit-7--0---general-purpose-dma + cycles = self.cpu.cycles - _cycles0 + # TODO: Unify interface sclock = self.sound.clock if self.cgb and self.double_speed: diff --git a/pyboy/utils.py b/pyboy/utils.py index 8dee8a1bb..168844edc 100644 --- a/pyboy/utils.py +++ b/pyboy/utils.py @@ -5,7 +5,7 @@ __all__ = ["WindowEvent", "dec_to_bcd", "bcd_to_dec"] -STATE_VERSION = 11 +STATE_VERSION = 12 ############################################################## # Buffer classes From 4b6a0531510c12038ed354e24f717de4a9ce9036 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 29 Sep 2024 17:12:40 +0200 Subject: [PATCH 09/51] Refactor LCD and Timer cycles_to_interrupts --- pyboy/core/cpu.pxd | 2 +- pyboy/core/cpu.py | 4 ++-- pyboy/core/lcd.pxd | 3 ++- pyboy/core/lcd.py | 16 ++++++++++++---- pyboy/core/mb.py | 10 ++++++---- pyboy/core/timer.pxd | 4 ++-- pyboy/core/timer.py | 37 +++++++++++++++++++------------------ 7 files changed, 44 insertions(+), 32 deletions(-) diff --git a/pyboy/core/cpu.pxd b/pyboy/core/cpu.pxd index c7d58703e..c53e51f16 100644 --- a/pyboy/core/cpu.pxd +++ b/pyboy/core/cpu.pxd @@ -4,7 +4,7 @@ # -from libc.stdint cimport int16_t, uint8_t, uint16_t, int64_t +from libc.stdint cimport int16_t, int64_t, uint8_t, uint16_t, int64_t cimport pyboy.core.mb from pyboy.utils cimport IntIOInterface diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 90c72ef97..69525155d 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -97,8 +97,8 @@ def dump_state(self, sym_label): f"Interrupts - IME: {self.mb.cpu.interrupt_master_enable}, " f"IE: {self.mb.cpu.interrupts_enabled_register:08b}, " f"IF: {self.mb.cpu.interrupts_flag_register:08b}\n" - f"LCD Intr.: {self.mb.lcd.cycles_to_interrupt()}, LY:{self.mb.lcd.LY}, LYC:{self.mb.lcd.LYC}\n" - f"Timer Intr.: {self.mb.timer.cycles_to_interrupt()}\n" + f"LCD Intr.: {self.mb.lcd._cycles_to_interrupt}, LY:{self.mb.lcd.LY}, LYC:{self.mb.lcd.LYC}\n" + f"Timer Intr.: {self.mb.timer._cycles_to_interrupt}\n" f"halted:{self.halted}, " f"interrupt_queued:{self.interrupt_queued}, " f"stopped:{self.stopped}\n" diff --git a/pyboy/core/lcd.pxd b/pyboy/core/lcd.pxd index 4b4ed074a..c431aa981 100644 --- a/pyboy/core/lcd.pxd +++ b/pyboy/core/lcd.pxd @@ -48,10 +48,11 @@ cdef class LCD: cdef PaletteRegister OBP1 cdef Renderer renderer cdef uint8_t[144][5] _scanlineparameters + cdef uint64_t last_cycles + cdef int64_t _cycles_to_interrupt @cython.locals(interrupt_flag=uint8_t,bx=int,by=int,wx=int,wy=int) cdef uint8_t tick(self, int) noexcept nogil - cdef int64_t cycles_to_interrupt(self) noexcept nogil cdef void set_lcdc(self, uint8_t) noexcept nogil cdef uint8_t get_lcdc(self) noexcept nogil diff --git a/pyboy/core/lcd.py b/pyboy/core/lcd.py index 171d114c2..769d7eb99 100644 --- a/pyboy/core/lcd.py +++ b/pyboy/core/lcd.py @@ -58,6 +58,8 @@ def __init__(self, cgb, cartridge_cgb, color_palette, cgb_color_palette, randomi self.double_speed = False self.cgb = cgb self._scanlineparameters = [[0, 0, 0, 0, 0] for _ in range(ROWS)] + self.last_cycles = 0 + self._cycles_to_interrupt = 0 if self.cgb: # Setting for both modes, even though CGB is ignoring them. BGP[0] used in scanline_blank. @@ -103,9 +105,6 @@ def get_stat(self): def set_stat(self, value): self._STAT.set(value) - def cycles_to_interrupt(self): - return self.clock_target - self.clock - def cycles_to_mode0(self): multiplier = 2 if self.double_speed else 1 mode2 = 80 * multiplier @@ -130,7 +129,12 @@ def cycles_to_mode0(self): # logger.critical("Unsupported STAT mode: %d", mode) # return 0 - def tick(self, cycles): + def tick(self, _cycles): + cycles = _cycles - self.last_cycles + if cycles == 0: + return False + self.last_cycles = _cycles + interrupt_flag = 0 self.clock += cycles @@ -205,6 +209,7 @@ def tick(self, cycles): # Renderer self.renderer.blank_screen(self) + self._cycles_to_interrupt = self.clock_target - self.clock return interrupt_flag def save_state(self, f): @@ -239,6 +244,7 @@ def save_state(self, f): # CGB f.write(self.cgb) f.write(self.double_speed) + f.write_64bit(self.last_cycles) f.write_64bit(self.clock) f.write_64bit(self.clock_target) f.write(self.next_stat_mode) @@ -293,6 +299,8 @@ def load_state(self, f, state_version): self.cgb = _cgb self.double_speed = f.read() + if state_version >= 12: + self.last_cycles = f.read_64bit() self.clock = f.read_64bit() self.clock_target = f.read_64bit() self.next_stat_mode = f.read() diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index bc78e3c09..4fac92bc4 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -295,8 +295,8 @@ def tick(self): cycles_target = max( 4, min( - self.lcd.cycles_to_interrupt(), - self.timer.cycles_to_interrupt(), + self.timer._cycles_to_interrupt, + self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled. # self.serial.cycles_to_interrupt(), mode0_cycles ) @@ -315,10 +315,10 @@ def tick(self): else: self.sound.clock = sclock + cycles - if self.timer.tick(cycles): + if self.timer.tick(self.cpu.cycles): self.cpu.set_interruptflag(INTR_TIMER) - lcd_interrupt = self.lcd.tick(cycles) + lcd_interrupt = self.lcd.tick(self.cpu.cycles) if lcd_interrupt: self.cpu.set_interruptflag(lcd_interrupt) @@ -365,6 +365,7 @@ def getitem(self, i): elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O return self.ram.non_io_internal_ram0[i - 0xFEA0] elif 0xFF00 <= i < 0xFF4C: # I/O ports + assert not self.timer.tick(self.cpu.cycles) if i == 0xFF04: return self.timer.DIV elif i == 0xFF05: @@ -478,6 +479,7 @@ def setitem(self, i, value): elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O self.ram.non_io_internal_ram0[i - 0xFEA0] = value elif 0xFF00 <= i < 0xFF4C: # I/O ports + assert not self.timer.tick(self.cpu.cycles) if i == 0xFF00: self.ram.io_ports[i - 0xFF00] = self.interaction.pull(value) elif i == 0xFF01: diff --git a/pyboy/core/timer.pxd b/pyboy/core/timer.pxd index 37d75b997..b444294e8 100644 --- a/pyboy/core/timer.pxd +++ b/pyboy/core/timer.pxd @@ -18,12 +18,12 @@ cdef class Timer: cdef uint64_t DIV, TIMA, TMA, TAC cdef uint16_t DIV_counter, TIMA_counter cdef uint64_t[4] dividers + cdef int64_t _cycles_to_interrupt + cdef uint64_t last_cycles cdef void reset(self) noexcept nogil @cython.locals(divider=cython.int) cdef bint tick(self, uint64_t) noexcept nogil - @cython.locals(divider=cython.int, cyclesleft=cython.uint) - cdef int64_t cycles_to_interrupt(self) noexcept nogil cdef void save_state(self, IntIOInterface) noexcept cdef void load_state(self, IntIOInterface, int) noexcept diff --git a/pyboy/core/timer.py b/pyboy/core/timer.py index 0f89226f8..16d5cf2b2 100644 --- a/pyboy/core/timer.py +++ b/pyboy/core/timer.py @@ -25,7 +25,9 @@ def __init__(self): self.TIMA_counter = 0 self.TMA = 0 self.TAC = 0 - self.dividers = [1024, 16, 64, 256] + self.dividers = [10, 4, 6, 8] + self._cycles_to_interrupt = 0 + self.last_cycles = 0 def reset(self): # TODO: Should probably one be DIV=0, but this makes a bunch of mooneye tests pass @@ -33,39 +35,35 @@ def reset(self): self.TIMA_counter = 0 self.DIV = 0 - def tick(self, cycles): + def tick(self, _cycles): + cycles = _cycles - self.last_cycles + if cycles == 0: + return False + self.last_cycles = _cycles + self.DIV_counter += cycles self.DIV += (self.DIV_counter >> 8) # Add overflown bits to DIV self.DIV_counter &= 0xFF # Remove the overflown bits self.DIV &= 0xFF if self.TAC & 0b100 == 0: # Check if timer is not enabled + self._cycles_to_interrupt = 1 << 16 return False self.TIMA_counter += cycles divider = self.dividers[self.TAC & 0b11] - if self.TIMA_counter >= divider: - self.TIMA_counter -= divider # Keeps possible remainder + ret = False + while self.TIMA_counter >= (1 << divider): + self.TIMA_counter -= (1 << divider) # Keeps possible remainder self.TIMA += 1 if self.TIMA > 0xFF: self.TIMA = self.TMA self.TIMA &= 0xFF - return True - - return False - - def cycles_to_interrupt(self): - if self.TAC & 0b100 == 0: # Check if timer is not enabled - # Large enough, that 'calculate_cycles' will choose 'x' - return 1 << 16 - - divider = self.dividers[self.TAC & 0b11] - - cyclesleft = ((0x100 - self.TIMA) * divider) - self.TIMA_counter - - return cyclesleft + ret = True + self._cycles_to_interrupt = ((0x100 - self.TIMA) << divider) - self.TIMA_counter + return ret def save_state(self, f): f.write(self.DIV) @@ -74,6 +72,7 @@ def save_state(self, f): f.write_16bit(self.TIMA_counter) f.write(self.TMA) f.write(self.TAC) + f.write_64bit(self.last_cycles) def load_state(self, f, state_version): self.DIV = f.read() @@ -82,3 +81,5 @@ def load_state(self, f, state_version): self.TIMA_counter = f.read_16bit() self.TMA = f.read() self.TAC = f.read() + if state_version >= 12: + self.last_cycles = f.read_64bit() From cecef342427fb99419c471ddb40480817896f2e3 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 29 Sep 2024 17:18:48 +0200 Subject: [PATCH 10/51] Implement LCD cycles to frame --- pyboy/core/lcd.pxd | 2 +- pyboy/core/lcd.py | 3 +++ pyboy/core/mb.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyboy/core/lcd.pxd b/pyboy/core/lcd.pxd index c431aa981..08c769fdc 100644 --- a/pyboy/core/lcd.pxd +++ b/pyboy/core/lcd.pxd @@ -49,7 +49,7 @@ cdef class LCD: cdef Renderer renderer cdef uint8_t[144][5] _scanlineparameters cdef uint64_t last_cycles - cdef int64_t _cycles_to_interrupt + cdef int64_t _cycles_to_interrupt, _cycles_to_frame @cython.locals(interrupt_flag=uint8_t,bx=int,by=int,wx=int,wy=int) cdef uint8_t tick(self, int) noexcept nogil diff --git a/pyboy/core/lcd.py b/pyboy/core/lcd.py index 769d7eb99..ffba52543 100644 --- a/pyboy/core/lcd.py +++ b/pyboy/core/lcd.py @@ -60,6 +60,7 @@ def __init__(self, cgb, cartridge_cgb, color_palette, cgb_color_palette, randomi self._scanlineparameters = [[0, 0, 0, 0, 0] for _ in range(ROWS)] self.last_cycles = 0 self._cycles_to_interrupt = 0 + self._cycles_to_frame = FRAME_CYCLES if self.cgb: # Setting for both modes, even though CGB is ignoring them. BGP[0] used in scanline_blank. @@ -210,6 +211,7 @@ def tick(self, _cycles): self.renderer.blank_screen(self) self._cycles_to_interrupt = self.clock_target - self.clock + self._cycles_to_frame = self.clock - FRAME_CYCLES return interrupt_flag def save_state(self, f): @@ -303,6 +305,7 @@ def load_state(self, f, state_version): self.last_cycles = f.read_64bit() self.clock = f.read_64bit() self.clock_target = f.read_64bit() + self._cycles_to_frame = self.clock - FRAME_CYCLES self.next_stat_mode = f.read() if self.cgb: diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 4fac92bc4..71c54cd33 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -297,6 +297,7 @@ def tick(self): min( self.timer._cycles_to_interrupt, self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled. + self.lcd._cycles_to_frame, # self.serial.cycles_to_interrupt(), mode0_cycles ) From 4310f988ab39d03a157bc95ff4116cce3bced9d3 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Tue, 17 Sep 2024 18:12:32 +0100 Subject: [PATCH 11/51] Only pre-check interrupts and halt in CPU tick --- pyboy/core/cpu.py | 36 +++++++++++++++++------------------- pyboy/core/opcodes.py | 1 + pyboy/core/opcodes_gen.py | 1 + 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 69525155d..af86cf099 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -110,27 +110,25 @@ def set_interruptflag(self, flag): def tick(self, cycles_target): _cycles0 = self.cycles _target = _cycles0 + cycles_target - self.bail = False - while self.cycles < _target: - if self.check_interrupts(): - self.halted = False - # TODO: We return with the cycles it took to handle the interrupt - break - # return cycles - - if self.halted and self.interrupt_queued: - # GBCPUman.pdf page 20 - # WARNING: The instruction immediately following the HALT instruction is "skipped" when interrupts are - # disabled (DI) on the GB,GBP, and SGB. - self.halted = False - self.PC += 1 - self.PC &= 0xFFFF - elif self.halted: - self.cycles += cycles_target # TODO: Number of cycles for a HALT in effect? - break - self.interrupt_queued = False + if self.check_interrupts(): + self.halted = False + # TODO: Cycles it took to handle the interrupt + + if self.halted and self.interrupt_queued: + # GBCPUman.pdf page 20 + # WARNING: The instruction immediately following the HALT instruction is "skipped" when interrupts are + # disabled (DI) on the GB,GBP, and SGB. + self.halted = False + self.PC += 1 + self.PC &= 0xFFFF + elif self.halted: + self.cycles += cycles_target # TODO: Number of cycles for a HALT in effect? + self.interrupt_queued = False + self.bail = False + while self.cycles < _target: + # TODO: cpu-stuck check for blargg tests? self.cycles += self.fetch_and_execute() if self.bail: # Possible cycles-target changes break diff --git a/pyboy/core/opcodes.py b/pyboy/core/opcodes.py index d90ede52e..2c8f3e783 100644 --- a/pyboy/core/opcodes.py +++ b/pyboy/core/opcodes.py @@ -1088,6 +1088,7 @@ def LD_75(cpu): # 75 LD (HL),L def HALT_76(cpu): # 76 HALT cpu.halted = True + cpu.bail = True return 4 diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index 333ceb231..00c6a9906 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -460,6 +460,7 @@ def HALT(self): # TODO: Implement HALT bug. code.addlines([ "cpu.halted = True", + "cpu.bail = True", "return " + self.cycles[0], ]) return code.getcode() From 5f7fcaf1ee9f63af87c258ab5b796882ef0516b0 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 16 Sep 2024 21:08:45 +0200 Subject: [PATCH 12/51] Refactor sound ticking to have a tick method --- pyboy/core/mb.py | 13 ++++--------- pyboy/core/sound.pxd | 5 ++++- pyboy/core/sound.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 71c54cd33..5fa028be3 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -277,7 +277,6 @@ def processing_frame(self): def tick(self): while self.processing_frame(): - _cycles0 = self.cpu.cycles if self.cgb and self.hdma.transfer_active and self.lcd._STAT._mode & 0b11 == 0: self.cpu.cycles = self.cpu.cycles + self.hdma.tick(self) else: @@ -298,6 +297,7 @@ def tick(self): self.timer._cycles_to_interrupt, self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled. self.lcd._cycles_to_frame, + self.sound._cycles_to_interrupt, # TODO: Not implemented # self.serial.cycles_to_interrupt(), mode0_cycles ) @@ -307,14 +307,7 @@ def tick(self): #TODO: Support General Purpose DMA # https://gbdev.io/pandocs/CGB_Registers.html#bit-7--0---general-purpose-dma - cycles = self.cpu.cycles - _cycles0 - - # TODO: Unify interface - sclock = self.sound.clock - if self.cgb and self.double_speed: - self.sound.clock = sclock + cycles//2 - else: - self.sound.clock = sclock + cycles + self.sound.tick(self.cpu.cycles, self.double_speed) if self.timer.tick(self.cpu.cycles): self.cpu.set_interruptflag(INTR_TIMER) @@ -378,6 +371,7 @@ def getitem(self, i): elif i == 0xFF0F: return self.cpu.interrupts_flag_register elif 0xFF10 <= i < 0xFF40: + self.sound.tick(self.cpu.cycles, self.double_speed) return self.sound.get(i - 0xFF10) elif i == 0xFF40: return self.lcd.get_lcdc() @@ -499,6 +493,7 @@ def setitem(self, i, value): elif i == 0xFF0F: self.cpu.interrupts_flag_register = value elif 0xFF10 <= i < 0xFF40: + self.sound.tick(self.cpu.cycles, self.double_speed) self.sound.set(i - 0xFF10, value) elif i == 0xFF40: self.lcd.set_lcdc(value) diff --git a/pyboy/core/sound.pxd b/pyboy/core/sound.pxd index 5a13b8e2f..7b1afde74 100644 --- a/pyboy/core/sound.pxd +++ b/pyboy/core/sound.pxd @@ -4,7 +4,7 @@ # cimport cython -from libc.stdint cimport uint8_t, uint16_t, uint64_t +from libc.stdint cimport int64_t, uint8_t, uint16_t, uint64_t from pyboy.logging.logging cimport Logger from pyboy.utils cimport IntIOInterface @@ -28,6 +28,8 @@ cdef class Sound: cdef object audiobuffer cdef object audiobuffer_p + cdef uint64_t last_cycles + cdef int64_t _cycles_to_interrupt cdef int clock cdef bint poweron @@ -42,6 +44,7 @@ cdef class Sound: cdef uint8_t get(self, uint8_t) noexcept nogil cdef void set(self, uint8_t, uint8_t) noexcept nogil + cdef void tick(self, int64_t, bint) noexcept nogil @cython.locals(nsamples=int, sample=int, i=int) cdef void sync(self) noexcept with gil # TODO: nogil @cython.locals(queued_time=int, samples_per_frame=int) diff --git a/pyboy/core/sound.py b/pyboy/core/sound.py index b69de626c..cd40b2905 100644 --- a/pyboy/core/sound.py +++ b/pyboy/core/sound.py @@ -59,6 +59,8 @@ def __init__(self, enabled, emulate): self.audiobuffer = array("b", [0] * 4096) # Over 2 frames self.audiobuffer_p = c_void_p(self.audiobuffer.buffer_info()[0]) + self.last_cycles = 0 + self._cycles_to_interrupt = 1 << 16 self.clock = 0 self.poweron = True @@ -150,6 +152,15 @@ def set(self, offset, value): else: raise IndexError(f"Attempted to write register {offset} in sound memory") + def tick(self, _cycles, double_speed): + cycles = _cycles - self.last_cycles + self.last_cycles = _cycles + + if double_speed: + self.clock += cycles // 2 + else: + self.clock += cycles + def sync(self): """Run the audio for the number of clock cycles stored in self.clock""" if not self.emulate: From 0c15c6803f79ebd469d4c017fb6de63dfc89b49d Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Wed, 25 Sep 2024 20:33:01 +0200 Subject: [PATCH 13/51] Fix CPU clock cycles for CB 46, CB 4E, CB 56, CB 5E, CB 66, CB 6E, CB 76, CB 7E --- pyboy/core/opcodes.py | 16 ++++++++-------- pyboy/core/opcodes_gen.py | 6 ++++++ tests/test_results/blargg.json | 2 +- tests/test_results/cgb_whichboot.gb.png | Bin 2243 -> 2361 bytes tests/test_results/dmg_whichboot.gb.png | Bin 2225 -> 2337 bytes 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pyboy/core/opcodes.py b/pyboy/core/opcodes.py index 2c8f3e783..75703064c 100644 --- a/pyboy/core/opcodes.py +++ b/pyboy/core/opcodes.py @@ -3587,7 +3587,7 @@ def BIT_146(cpu): # 146 BIT 0,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_147(cpu): # 147 BIT 0,A @@ -3675,7 +3675,7 @@ def BIT_14E(cpu): # 14E BIT 1,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_14F(cpu): # 14F BIT 1,A @@ -3763,7 +3763,7 @@ def BIT_156(cpu): # 156 BIT 2,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_157(cpu): # 157 BIT 2,A @@ -3851,7 +3851,7 @@ def BIT_15E(cpu): # 15E BIT 3,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_15F(cpu): # 15F BIT 3,A @@ -3939,7 +3939,7 @@ def BIT_166(cpu): # 166 BIT 4,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_167(cpu): # 167 BIT 4,A @@ -4027,7 +4027,7 @@ def BIT_16E(cpu): # 16E BIT 5,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_16F(cpu): # 16F BIT 5,A @@ -4115,7 +4115,7 @@ def BIT_176(cpu): # 176 BIT 6,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_177(cpu): # 177 BIT 6,A @@ -4203,7 +4203,7 @@ def BIT_17E(cpu): # 17E BIT 7,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + return 12 def BIT_17F(cpu): # 17F BIT 7,A diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index 00c6a9906..cd6c07b7a 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -1170,6 +1170,12 @@ def BIT(self): left = Literal(r0) right = Operand(r1) code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles) + + if self.opcode in [0x146, 0x14E, 0x156, 0x15E, 0x166, 0x16E, 0x176, 0x17E]: + # FIX: Corrent cycle count is 12, not 16! + code.cycles = ("12", ) + + code.addline("t = %s & (1 << %s)" % (right.get, left.get)) code.addlines(self.handleflags8bit(left.get, right.get, None, False)) diff --git a/tests/test_results/blargg.json b/tests/test_results/blargg.json index d82a5c967..5e153d5eb 100644 --- a/tests/test_results/blargg.json +++ b/tests/test_results/blargg.json @@ -24,7 +24,7 @@ "blargg/dmg_sound/rom_singles/10-wave trigger while on.gb": "10-wave trigger while on\n\n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99", "blargg/dmg_sound/rom_singles/11-regs after power.gb": "11-regs after power\n\n\nPowering off should clear NR13\n\nFailed #3\n", "blargg/dmg_sound/rom_singles/12-wave write while on.gb": "12-wave write while on\n\n00 11 22 33 44 F7 66 77 88 99 AA BB CC DD EE FF \n00 11 22 F7 44 55 66 77 88 99 AA BB CC DD EE FF \n00 F7 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \nF7 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 A", - "blargg/instr_timing/instr_timing.gb": "instr_timing\n\n01:255-3 02:254-2 03:254-2 06:254-2 09:254-2 0A:254-2 0B:254-2 0E:254-2 11:255-3 12:254-2 13:254-2 16:254-2 18:255-3 19:254-2 1A:254-2 1B:254-2 1E:254-2 20:254-2 20:255-3 21:255-3 22:254-2 23:254-2 26:254-2 28:254-2 28:255-3 29:254-2 2A:254-2 2B:254-2 2E:254-2 30:254-2 30:255-3 31:255-3 32:254-2 33:254-2 34:255-3 35:255-3 36:255-3 38:254-2 38:255-3 39:254-2 3A:254-2 3B:254-2 3E:254-2 46:254-2 4E:254-2 56:254-2 5E:254-2 66:254-2 6E:254-2 70:254-2 71:254-2 72:254-2 73:254-2 74:254-2 75:254-2 77:254-2 7E:254-2 86:254-2 8E:254-2 96:254-2 9E:254-2 A6:254-2 AE:254-2 B6:254-2 BE:254-2 C0:254-2 C1:255-3 C2:255-3 C4:255-3 C4:2-6 C6:254-2 C8:254-2 CA:255-3 CC:255-3 CC:2-6 CD:2-6 CE:254-2 D0:254-2 D1:255-3 D2:255-3 D4:255-3 D4:2-6 D6:254-2 D8:254-2 DA:255-3 DC:255-3 DC:2-6 DE:254-2 E0:255-3 E1:255-3 E2:254-2 E6:254-2 EE:254-2 F0:255-3 F1:255-3 F2:254-2 F6:254-2 F8:255-3 F9:254-2 FE:254-2 CB 00:254-2 CB 01:254-2 CB 02:254-2 CB 03:254-2 CB 04:254-2 CB 05:254-2 CB 07:254-2 CB 08:254-2 CB 09:254-2 CB 0A:254-2 CB 0B:254-2 CB 0C:254-2 CB 0D:254-2 CB 0F:254-2 CB 10:254-2 CB 11:254-2 CB 12:254-2 CB 13:254-2 CB 14:254-2 CB 15:254-2 CB 17:254-2 CB 18:254-2 CB 19:254-2 CB 1A:254-2 CB 1B:254-2 CB 1C:254-2 CB 1D:254-2 CB 1F:254-2 CB 20:254-2 CB 21:254-2 CB 22:254-2 CB 23:254-2 CB 24:254-2 CB 25:254-2 CB 27:254-2 CB 28:254-2 CB 29:254-2 CB 2A:254-2 CB 2B:254-2 CB 2C:254-2 CB 2D:254-2 CB 2F:254-2 CB 30:254-2 CB 31:254-2 CB 32:254-2 CB 33:254-2 CB 34:254-2 CB 35:254-2 CB 37:254-2 CB 38:254-2 CB 39:254-2 CB 3A:254-2 CB 3B:254-2 CB 3C:254-2 CB 3D:254-2 CB 3F:254-2 CB 40:254-2 CB 41:254-2 CB 42:254-2 CB 43:254-2 CB 44:254-2 CB 45:254-2 CB 46:4-3 CB 47:254-2 CB 48:254-2 CB 49:254-2 CB 4A:254-2 CB 4B:254-2 CB 4C:254-2 CB 4D:254-2 CB 4E:4-3 CB 4F:254-2 CB 50:254-2 CB 51:254-2 CB 52:254-2 CB 53:254-2 CB 54:254-2 CB 55:254-2 CB 56:4-3 CB 57:254-2 CB 58:254-2 CB 59:254-2 CB 5A:254-2 CB 5B:254-2 CB 5C:254-2 CB 5D:254-2 CB 5E:4-3 CB 5F:254-2 CB 60:254-2 CB 61:254-2 CB 62:254-2 CB 63:254-2 CB 64:254-2 CB 65:254-2 CB 66:4-3 CB 67:254-2 CB 68:254-2 CB 69:254-2 CB 6A:254-2 CB 6B:254-2 CB 6C:254-2 CB 6D:254-2 CB 6E:4-3 CB 6F:254-2 CB 70:254-2 CB 71:254-2 CB 72:254-2 CB 73:254-2 CB 74:254-2 CB 75:254-2 CB 76:4-3 CB 77:254-2 CB 78:254-2 CB 79:254-2 CB 7A:254-2 CB 7B:254-2 CB 7C:254-2 CB 7D:254-2 CB 7E:4-3 CB 7F:254-2 CB 80:254-2 CB 81:254-2 CB 82:254-2 CB 83:254-2 CB 84:254-2 CB 85:254-2 CB 87:254-2 CB 88:254-2 CB 89:254-2 CB 8A:254-2 CB 8B:254-2 CB 8C:254-2 CB 8D:254-2 CB 8F:254-2 CB 90:254-2 CB 91:254-2 CB 92:254-2 CB 93:254-2 CB 94:254-2 CB 95:254-2 CB 97:254-2 CB 98:254-2 CB 99:254-2 CB 9A:254-2 CB 9B:254-2 CB 9C:254-2 CB 9D:254-2 CB 9F:254-2 CB A0:254-2 CB A1:254-2 CB A2:254-2 CB A3:254-2 CB A4:254-2 CB A5:254-2 CB A7:254-2 CB A8:254-2 CB A9:254-2 CB AA:254-2 CB AB:254-2 CB AC:254-2 CB AD:254-2 CB AF:254-2 CB B0:254-2 CB B1:254-2 CB B2:254-2 CB B3:254-2 CB B4:254-2 CB B5:254-2 CB B7:254-2 CB B8:254-2 CB B9:254-2 CB BA:254-2 CB BB:254-2 CB BC:254-2 CB BD:254-2 CB BF:254-2 CB C0:254-2 CB C1:254-2 CB C2:254-2 CB C3:254-2 CB C4:254-2 CB C5:254-2 CB C7:254-2 CB C8:254-2 CB C9:254-2 CB CA:254-2 CB CB:254-2 CB CC:254-2 CB CD:254-2 CB CF:254-2 CB D0:254-2 CB D1:254-2 CB D2:254-2 CB D3:254-2 CB D4:254-2 CB D5:254-2 CB D7:254-2 CB D8:254-2 CB D9:254-2 CB DA:254-2 CB DB:254-2 CB DC:254-2 CB DD:254-2 CB DF:254-2 CB E0:254-2 CB E1:254-2 CB E2:254-2 CB E3:254-2 CB E4:254-2 CB E5:254-2 CB E7:254-2 CB E8:254-2 CB E9:254-2 CB EA:254-2 CB EB:254-2 CB EC:254-2 CB ED:254-2 CB EF:254-2 CB F0:254-2 CB F1:254-2 CB F2:254-2 CB F3:254-2 CB F4:254-2 CB F5:254-2 CB F7:254-2 CB F8:254-2 CB F9:254-2 CB FA:254-2 CB FB:254-2 CB FC:254-2 CB FD:254-2 CB FF:254-2 \nFailed\n", + "blargg/instr_timing/instr_timing.gb": "instr_timing\n\n\nPassed\n", "blargg/interrupt_time/interrupt_time.gb": "interrupt time\n\n00 00 FC \n00 08 04 \n00 00 FC \n00 08 04 \n550B72D0 \nFailed\n", "blargg/mem_timing/individual/01-read_timing.gb": "01-read_timing\n\nF0:2-3 FA:2-4 CB 46:2-3 CB 4E:2-3 CB 56:2-3 CB 5E:2-3 CB 66:2-3 CB 6E:2-3 CB 76:2-3 CB 7E:2-3 \nFailed\n", "blargg/mem_timing/individual/02-write_timing.gb": "02-write_timing\n\n36:2-3 E0:2-3 EA:2-4 \nFailed\n", diff --git a/tests/test_results/cgb_whichboot.gb.png b/tests/test_results/cgb_whichboot.gb.png index 9edda33da10ab291af965807a5c1af627b3ca44e..b25f3c39dc979d00125d78e1b2635ac7f77efe42 100644 GIT binary patch literal 2361 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 Hu0mjf1rdqp delta 2224 zcmV;h2v7H+60s4G8Gix*0002j_tyXb2zW_EK~#90?VZt@<0=S%&H3j2U%BTZyBSvj z0fDIHznZZYi6jV;CfDclDapC~lv1wiDy2M@zOL*3n{KbE`(TYjIrXUq%Aw_BqChxu zm_qvF0zVxh?a$;#@g30oeM@t|w6wxLvHEsQ9}HSgss6+eet-S>{O2UMIGl`5c<3pW z={Hy7E7e z>$;pYsm~Ao+2E%S$nO>W1T4eh>kf6ma(`Fq;`;uW9G(0gT8>EmBMJ-kIia7lzlamO zP(twM3Kr#sihp#-HGc)QBJ`)_(1Xs%A^Kaj_fQ)h&G*$o2U9-%Em{t-tnl+`8mSz< z{#NZEKpwn>)0XZ!G3Cr4(qWi1PPRV?@?a;!WAcgNtO9N~-d9W^D^mbi-`hz<>7>@N| zSJZjoB3S7^jHTnQU<|`NxM}~`6%~qn>b>ciIr2D6f28nRd^>$1WOvDvD z+;YRd<*Agol)EXJ2OrMwZL>9;Jd(e&L!?Pxa(@}U$WKAcK(ycVB@s^lrllZ;Ppr@f zS+{lhCIJg%O$W6y3C2{ArKq@pHkQU;37_blEYO6X&nHdm#D4YQLlx|N)H?6)ex`+@6O#x^>b^ac~1 zYJaqzp#0{99-b&ze_|f|c=@i%VRF0z>iaKOK>4IQqCf-|_!hJxrY8~fO*)BJ=0SM{ z6FwH4g&Cjh1m8u2ou$HW;^#q=2TPTE%c%7VI*6p1c9h?S39X4_Ip-EIJy}MBw zMc1D)t*g)z^8`axgKm~uDk8XgE=z=+8A3?ZE1|XyIpVihci`8 zy8goHJj@W*$)FZTbg~*0{F#Qq)Dj6HdVbG&BUcJ)+65Bk%x-t9j2!F!_et(w0 zFmE%7qM1=~tMOT@=?t8ySTPSy3<-TsR-Q~cXcySG*F>zr3sxF7V#c~9EXYb5>zK4| ziM*>aZ7gNpx+T)Uxe`kmZuxO8m7Vh7FJ#`vK|cV$|48d9n6z=`!G|+`e{c<_T-bCb z9F}(N6M1N3zJoYc;0DtSjMRJoH)?X@z=IVamEY)WzLt-rS<1?$-VaoXk zf8T61ZZN-B@UYy*JOFI4t266F<1W@GrbPPn3@)0e=Yc;3f#- z=aI>aI6fyUt-As)&&gUm?)WNP(j;A)k$AAsR%POW#tUIYg+yy^=6>hFoZ7n^XsWn`Hh~FdG=5~Qy+{)>djP;RS&F?9$eoIW3PHM zP!DcS=R0m%XO^vkI7|*9Uw=CE3ialJi+;RBZ{xSBW9E)w=%SmCCJ#Kg-b}gZz9iQS zKyPSR9m$$?RyD!(X84vJ-Bn_h2TSGH=HK)6Z!1j3U1)JlJeaKq_kZ{O3*sIyIWzi{ ziTdg=+c{ZOb$kTeoV_5 zw$BVb&pFXV0?2M-y)XN{KbW7VV)`6uNR0j7A8ekd9&FvR>HC9SO>$Ul zAN}St(8eZts+D14Qh)O>j@qick$&+ELt=d6`-9&a_Z3bKAd|9JJvdRYUp+VxaZZqx zfE-m1PI|tg9{kN@J@|0O?+*q$aH==MD4J+btjK`&a3bCxjFI)-03zNWOq2CpR}Avt z$=S^Mt}9dJLaY7ytnc~}_x@lhS>H8b-L1;3@0vjUJ{YT;N`KqE`KtP^lngizN027< zAKirI$A)Q}C~w|64buc!w)wHN)GwXizW+?V&L4LX9D^*U>A>#2wd(IJCm5I8W<~Li z;yeK7ni%w(ZT$Z8qIETY(26&pG^tnTkGnYQ=D}~#?~QMb<$IHK`7!2E{qkClKF8R2 z%g8X{GkBC?Uw_4`Kk7Sx?Na$x?X;dEIr}nRgYV<)j~okXuy$!MC7oX^uBL;p`EeI# zY+&^rHYPmNF*ep4c=tm3 zVfijb+%Nny-+hJ5gAd2?wpmQhkjZg}F+aAb>+}Id)qfxLwNavT{Sk3onEW2rR;BAz zt}wreKGOPhzh+~nwe&%Z&Vn(v}9IfRD%`S~Ac$Rd0SYgr=z0000 Date: Wed, 25 Sep 2024 21:39:06 +0200 Subject: [PATCH 14/51] Fix memory timings, supporting sub-opcode timing --- pyboy/core/cpu.py | 2 +- pyboy/core/mb.py | 10 +- pyboy/core/opcodes.py | 1097 +++++++++++++++++--------------- pyboy/core/opcodes_gen.py | 138 +++- tests/test_results/blargg.json | 16 +- 5 files changed, 715 insertions(+), 548 deletions(-) diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index af86cf099..e6e0d0a90 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -129,7 +129,7 @@ def tick(self, cycles_target): self.bail = False while self.cycles < _target: # TODO: cpu-stuck check for blargg tests? - self.cycles += self.fetch_and_execute() + self.fetch_and_execute() if self.bail: # Possible cycles-target changes break diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 5fa028be3..6090d7a8d 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -359,7 +359,10 @@ def getitem(self, i): elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O return self.ram.non_io_internal_ram0[i - 0xFEA0] elif 0xFF00 <= i < 0xFF4C: # I/O ports - assert not self.timer.tick(self.cpu.cycles) + # NOTE: A bit ad-hoc, but interrupts can occur right between writes + if self.timer.tick(self.cpu.cycles): + self.cpu.set_interruptflag(INTR_TIMER) + if i == 0xFF04: return self.timer.DIV elif i == 0xFF05: @@ -474,7 +477,10 @@ def setitem(self, i, value): elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O self.ram.non_io_internal_ram0[i - 0xFEA0] = value elif 0xFF00 <= i < 0xFF4C: # I/O ports - assert not self.timer.tick(self.cpu.cycles) + # NOTE: A bit ad-hoc, but interrupts can occur right between writes + if self.timer.tick(self.cpu.cycles): + self.cpu.set_interruptflag(INTR_TIMER) + if i == 0xFF00: self.ram.io_ports[i - 0xFF00] = self.interaction.pull(value) elif i == 0xFF01: diff --git a/pyboy/core/opcodes.py b/pyboy/core/opcodes.py index 75703064c..21fed5341 100644 --- a/pyboy/core/opcodes.py +++ b/pyboy/core/opcodes.py @@ -21,7 +21,7 @@ def BRK(cpu): def NOP_00(cpu): # 00 NOP cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_01(cpu, v): # 01 LD BC,d16 @@ -29,14 +29,14 @@ def LD_01(cpu, v): # 01 LD BC,d16 cpu.C = v & 0x00FF cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def LD_02(cpu): # 02 LD (BC),A cpu.mb.setitem(((cpu.B << 8) + cpu.C), cpu.A) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_03(cpu): # 03 INC BC @@ -47,7 +47,7 @@ def INC_03(cpu): # 03 INC BC cpu.C = t & 0x00FF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_04(cpu): # 04 INC B @@ -61,7 +61,7 @@ def INC_04(cpu): # 04 INC B cpu.B = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def DEC_05(cpu): # 05 DEC B @@ -75,14 +75,14 @@ def DEC_05(cpu): # 05 DEC B cpu.B = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_06(cpu, v): # 06 LD B,d8 cpu.B = v cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLCA_07(cpu): # 07 RLCA @@ -95,7 +95,7 @@ def RLCA_07(cpu): # 07 RLCA cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_08(cpu, v): # 08 LD (a16),SP @@ -103,7 +103,7 @@ def LD_08(cpu, v): # 08 LD (a16),SP cpu.mb.setitem(v+1, cpu.SP >> 8) cpu.PC += 3 cpu.PC &= 0xFFFF - return 20 + cpu.cycles += 20 def ADD_09(cpu): # 09 ADD HL,BC @@ -117,14 +117,14 @@ def ADD_09(cpu): # 09 ADD HL,BC cpu.HL = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_0A(cpu): # 0A LD A,(BC) cpu.A = cpu.mb.getitem(((cpu.B << 8) + cpu.C)) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def DEC_0B(cpu): # 0B DEC BC @@ -135,7 +135,7 @@ def DEC_0B(cpu): # 0B DEC BC cpu.C = t & 0x00FF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_0C(cpu): # 0C INC C @@ -149,7 +149,7 @@ def INC_0C(cpu): # 0C INC C cpu.C = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def DEC_0D(cpu): # 0D DEC C @@ -163,14 +163,14 @@ def DEC_0D(cpu): # 0D DEC C cpu.C = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_0E(cpu, v): # 0E LD C,d8 cpu.C = v cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRCA_0F(cpu): # 0F RRCA @@ -183,7 +183,7 @@ def RRCA_0F(cpu): # 0F RRCA cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def STOP_10(cpu, v): # 10 STOP 0 @@ -192,7 +192,7 @@ def STOP_10(cpu, v): # 10 STOP 0 cpu.mb.setitem(0xFF04, 0) cpu.PC += 2 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_11(cpu, v): # 11 LD DE,d16 @@ -200,14 +200,14 @@ def LD_11(cpu, v): # 11 LD DE,d16 cpu.E = v & 0x00FF cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def LD_12(cpu): # 12 LD (DE),A cpu.mb.setitem(((cpu.D << 8) + cpu.E), cpu.A) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_13(cpu): # 13 INC DE @@ -218,7 +218,7 @@ def INC_13(cpu): # 13 INC DE cpu.E = t & 0x00FF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_14(cpu): # 14 INC D @@ -232,7 +232,7 @@ def INC_14(cpu): # 14 INC D cpu.D = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def DEC_15(cpu): # 15 DEC D @@ -246,14 +246,14 @@ def DEC_15(cpu): # 15 DEC D cpu.D = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_16(cpu, v): # 16 LD D,d8 cpu.D = v cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLA_17(cpu): # 17 RLA @@ -266,13 +266,13 @@ def RLA_17(cpu): # 17 RLA cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def JR_18(cpu, v): # 18 JR r8 cpu.PC += 2 + ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def ADD_19(cpu): # 19 ADD HL,DE @@ -286,14 +286,14 @@ def ADD_19(cpu): # 19 ADD HL,DE cpu.HL = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_1A(cpu): # 1A LD A,(DE) cpu.A = cpu.mb.getitem(((cpu.D << 8) + cpu.E)) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def DEC_1B(cpu): # 1B DEC DE @@ -304,7 +304,7 @@ def DEC_1B(cpu): # 1B DEC DE cpu.E = t & 0x00FF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_1C(cpu): # 1C INC E @@ -318,7 +318,7 @@ def INC_1C(cpu): # 1C INC E cpu.E = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def DEC_1D(cpu): # 1D DEC E @@ -332,14 +332,14 @@ def DEC_1D(cpu): # 1D DEC E cpu.E = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_1E(cpu, v): # 1E LD E,d8 cpu.E = v cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRA_1F(cpu): # 1F RRA @@ -352,7 +352,7 @@ def RRA_1F(cpu): # 1F RRA cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def JR_20(cpu, v): # 20 JR NZ,r8 @@ -360,17 +360,17 @@ def JR_20(cpu, v): # 20 JR NZ,r8 if ((cpu.F & (1 << FLAGZ)) == 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 else: cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_21(cpu, v): # 21 LD HL,d16 cpu.HL = v cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def LD_22(cpu): # 22 LD (HL+),A @@ -379,7 +379,7 @@ def LD_22(cpu): # 22 LD (HL+),A cpu.HL &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_23(cpu): # 23 INC HL @@ -389,7 +389,7 @@ def INC_23(cpu): # 23 INC HL cpu.HL = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_24(cpu): # 24 INC H @@ -403,7 +403,7 @@ def INC_24(cpu): # 24 INC H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def DEC_25(cpu): # 25 DEC H @@ -417,14 +417,14 @@ def DEC_25(cpu): # 25 DEC H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_26(cpu, v): # 26 LD H,d8 cpu.HL = (cpu.HL & 0x00FF) | (v << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def DAA_27(cpu): # 27 DAA @@ -447,7 +447,7 @@ def DAA_27(cpu): # 27 DAA cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def JR_28(cpu, v): # 28 JR Z,r8 @@ -455,10 +455,10 @@ def JR_28(cpu, v): # 28 JR Z,r8 if ((cpu.F & (1 << FLAGZ)) != 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 else: cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def ADD_29(cpu): # 29 ADD HL,HL @@ -472,7 +472,7 @@ def ADD_29(cpu): # 29 ADD HL,HL cpu.HL = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_2A(cpu): # 2A LD A,(HL+) @@ -481,7 +481,7 @@ def LD_2A(cpu): # 2A LD A,(HL+) cpu.HL &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def DEC_2B(cpu): # 2B DEC HL @@ -491,7 +491,7 @@ def DEC_2B(cpu): # 2B DEC HL cpu.HL = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_2C(cpu): # 2C INC L @@ -505,7 +505,7 @@ def INC_2C(cpu): # 2C INC L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def DEC_2D(cpu): # 2D DEC L @@ -519,14 +519,14 @@ def DEC_2D(cpu): # 2D DEC L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_2E(cpu, v): # 2E LD L,d8 cpu.HL = (cpu.HL & 0xFF00) | (v & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def CPL_2F(cpu): # 2F CPL @@ -536,7 +536,7 @@ def CPL_2F(cpu): # 2F CPL cpu.F |= flag cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def JR_30(cpu, v): # 30 JR NC,r8 @@ -544,17 +544,17 @@ def JR_30(cpu, v): # 30 JR NC,r8 if ((cpu.F & (1 << FLAGC)) == 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 else: cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_31(cpu, v): # 31 LD SP,d16 cpu.SP = v cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def LD_32(cpu): # 32 LD (HL-),A @@ -563,7 +563,7 @@ def LD_32(cpu): # 32 LD (HL-),A cpu.HL &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_33(cpu): # 33 INC SP @@ -573,7 +573,7 @@ def INC_33(cpu): # 33 INC SP cpu.SP = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_34(cpu): # 34 INC (HL) @@ -584,10 +584,11 @@ def INC_34(cpu): # 34 INC (HL) cpu.F &= 0b00010000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 1 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def DEC_35(cpu): # 35 DEC (HL) @@ -598,17 +599,19 @@ def DEC_35(cpu): # 35 DEC (HL) cpu.F &= 0b00010000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 1 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def LD_36(cpu, v): # 36 LD (HL),d8 + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, v) cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def SCF_37(cpu): # 37 SCF @@ -617,7 +620,7 @@ def SCF_37(cpu): # 37 SCF cpu.F |= flag cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def JR_38(cpu, v): # 38 JR C,r8 @@ -625,10 +628,10 @@ def JR_38(cpu, v): # 38 JR C,r8 if ((cpu.F & (1 << FLAGC)) != 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 else: cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def ADD_39(cpu): # 39 ADD HL,SP @@ -642,7 +645,7 @@ def ADD_39(cpu): # 39 ADD HL,SP cpu.HL = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_3A(cpu): # 3A LD A,(HL-) @@ -651,7 +654,7 @@ def LD_3A(cpu): # 3A LD A,(HL-) cpu.HL &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def DEC_3B(cpu): # 3B DEC SP @@ -661,7 +664,7 @@ def DEC_3B(cpu): # 3B DEC SP cpu.SP = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def INC_3C(cpu): # 3C INC A @@ -675,7 +678,7 @@ def INC_3C(cpu): # 3C INC A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def DEC_3D(cpu): # 3D DEC A @@ -689,14 +692,14 @@ def DEC_3D(cpu): # 3D DEC A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_3E(cpu, v): # 3E LD A,d8 cpu.A = v cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def CCF_3F(cpu): # 3F CCF @@ -705,454 +708,454 @@ def CCF_3F(cpu): # 3F CCF cpu.F |= flag cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_40(cpu): # 40 LD B,B cpu.B = cpu.B cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_41(cpu): # 41 LD B,C cpu.B = cpu.C cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_42(cpu): # 42 LD B,D cpu.B = cpu.D cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_43(cpu): # 43 LD B,E cpu.B = cpu.E cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_44(cpu): # 44 LD B,H cpu.B = (cpu.HL >> 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_45(cpu): # 45 LD B,L cpu.B = (cpu.HL & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_46(cpu): # 46 LD B,(HL) cpu.B = cpu.mb.getitem(cpu.HL) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_47(cpu): # 47 LD B,A cpu.B = cpu.A cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_48(cpu): # 48 LD C,B cpu.C = cpu.B cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_49(cpu): # 49 LD C,C cpu.C = cpu.C cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_4A(cpu): # 4A LD C,D cpu.C = cpu.D cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_4B(cpu): # 4B LD C,E cpu.C = cpu.E cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_4C(cpu): # 4C LD C,H cpu.C = (cpu.HL >> 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_4D(cpu): # 4D LD C,L cpu.C = (cpu.HL & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_4E(cpu): # 4E LD C,(HL) cpu.C = cpu.mb.getitem(cpu.HL) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_4F(cpu): # 4F LD C,A cpu.C = cpu.A cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_50(cpu): # 50 LD D,B cpu.D = cpu.B cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_51(cpu): # 51 LD D,C cpu.D = cpu.C cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_52(cpu): # 52 LD D,D cpu.D = cpu.D cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_53(cpu): # 53 LD D,E cpu.D = cpu.E cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_54(cpu): # 54 LD D,H cpu.D = (cpu.HL >> 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_55(cpu): # 55 LD D,L cpu.D = (cpu.HL & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_56(cpu): # 56 LD D,(HL) cpu.D = cpu.mb.getitem(cpu.HL) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_57(cpu): # 57 LD D,A cpu.D = cpu.A cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_58(cpu): # 58 LD E,B cpu.E = cpu.B cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_59(cpu): # 59 LD E,C cpu.E = cpu.C cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_5A(cpu): # 5A LD E,D cpu.E = cpu.D cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_5B(cpu): # 5B LD E,E cpu.E = cpu.E cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_5C(cpu): # 5C LD E,H cpu.E = (cpu.HL >> 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_5D(cpu): # 5D LD E,L cpu.E = (cpu.HL & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_5E(cpu): # 5E LD E,(HL) cpu.E = cpu.mb.getitem(cpu.HL) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_5F(cpu): # 5F LD E,A cpu.E = cpu.A cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_60(cpu): # 60 LD H,B cpu.HL = (cpu.HL & 0x00FF) | (cpu.B << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_61(cpu): # 61 LD H,C cpu.HL = (cpu.HL & 0x00FF) | (cpu.C << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_62(cpu): # 62 LD H,D cpu.HL = (cpu.HL & 0x00FF) | (cpu.D << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_63(cpu): # 63 LD H,E cpu.HL = (cpu.HL & 0x00FF) | (cpu.E << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_64(cpu): # 64 LD H,H cpu.HL = (cpu.HL & 0x00FF) | ((cpu.HL >> 8) << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_65(cpu): # 65 LD H,L cpu.HL = (cpu.HL & 0x00FF) | ((cpu.HL & 0xFF) << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_66(cpu): # 66 LD H,(HL) cpu.HL = (cpu.HL & 0x00FF) | (cpu.mb.getitem(cpu.HL) << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_67(cpu): # 67 LD H,A cpu.HL = (cpu.HL & 0x00FF) | (cpu.A << 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_68(cpu): # 68 LD L,B cpu.HL = (cpu.HL & 0xFF00) | (cpu.B & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_69(cpu): # 69 LD L,C cpu.HL = (cpu.HL & 0xFF00) | (cpu.C & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_6A(cpu): # 6A LD L,D cpu.HL = (cpu.HL & 0xFF00) | (cpu.D & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_6B(cpu): # 6B LD L,E cpu.HL = (cpu.HL & 0xFF00) | (cpu.E & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_6C(cpu): # 6C LD L,H cpu.HL = (cpu.HL & 0xFF00) | ((cpu.HL >> 8) & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_6D(cpu): # 6D LD L,L cpu.HL = (cpu.HL & 0xFF00) | ((cpu.HL & 0xFF) & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_6E(cpu): # 6E LD L,(HL) cpu.HL = (cpu.HL & 0xFF00) | (cpu.mb.getitem(cpu.HL) & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_6F(cpu): # 6F LD L,A cpu.HL = (cpu.HL & 0xFF00) | (cpu.A & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_70(cpu): # 70 LD (HL),B cpu.mb.setitem(cpu.HL, cpu.B) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_71(cpu): # 71 LD (HL),C cpu.mb.setitem(cpu.HL, cpu.C) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_72(cpu): # 72 LD (HL),D cpu.mb.setitem(cpu.HL, cpu.D) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_73(cpu): # 73 LD (HL),E cpu.mb.setitem(cpu.HL, cpu.E) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_74(cpu): # 74 LD (HL),H cpu.mb.setitem(cpu.HL, (cpu.HL >> 8)) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_75(cpu): # 75 LD (HL),L cpu.mb.setitem(cpu.HL, (cpu.HL & 0xFF)) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def HALT_76(cpu): # 76 HALT cpu.halted = True cpu.bail = True - return 4 + cpu.cycles += 4 def LD_77(cpu): # 77 LD (HL),A cpu.mb.setitem(cpu.HL, cpu.A) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_78(cpu): # 78 LD A,B cpu.A = cpu.B cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_79(cpu): # 79 LD A,C cpu.A = cpu.C cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_7A(cpu): # 7A LD A,D cpu.A = cpu.D cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_7B(cpu): # 7B LD A,E cpu.A = cpu.E cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_7C(cpu): # 7C LD A,H cpu.A = (cpu.HL >> 8) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_7D(cpu): # 7D LD A,L cpu.A = (cpu.HL & 0xFF) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def LD_7E(cpu): # 7E LD A,(HL) cpu.A = cpu.mb.getitem(cpu.HL) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_7F(cpu): # 7F LD A,A cpu.A = cpu.A cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADD_80(cpu): # 80 ADD A,B @@ -1167,7 +1170,7 @@ def ADD_80(cpu): # 80 ADD A,B cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADD_81(cpu): # 81 ADD A,C @@ -1182,7 +1185,7 @@ def ADD_81(cpu): # 81 ADD A,C cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADD_82(cpu): # 82 ADD A,D @@ -1197,7 +1200,7 @@ def ADD_82(cpu): # 82 ADD A,D cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADD_83(cpu): # 83 ADD A,E @@ -1212,7 +1215,7 @@ def ADD_83(cpu): # 83 ADD A,E cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADD_84(cpu): # 84 ADD A,H @@ -1227,7 +1230,7 @@ def ADD_84(cpu): # 84 ADD A,H cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADD_85(cpu): # 85 ADD A,L @@ -1242,7 +1245,7 @@ def ADD_85(cpu): # 85 ADD A,L cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADD_86(cpu): # 86 ADD A,(HL) @@ -1257,7 +1260,7 @@ def ADD_86(cpu): # 86 ADD A,(HL) cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def ADD_87(cpu): # 87 ADD A,A @@ -1272,7 +1275,7 @@ def ADD_87(cpu): # 87 ADD A,A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADC_88(cpu): # 88 ADC A,B @@ -1287,7 +1290,7 @@ def ADC_88(cpu): # 88 ADC A,B cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADC_89(cpu): # 89 ADC A,C @@ -1302,7 +1305,7 @@ def ADC_89(cpu): # 89 ADC A,C cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADC_8A(cpu): # 8A ADC A,D @@ -1317,7 +1320,7 @@ def ADC_8A(cpu): # 8A ADC A,D cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADC_8B(cpu): # 8B ADC A,E @@ -1332,7 +1335,7 @@ def ADC_8B(cpu): # 8B ADC A,E cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADC_8C(cpu): # 8C ADC A,H @@ -1347,7 +1350,7 @@ def ADC_8C(cpu): # 8C ADC A,H cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADC_8D(cpu): # 8D ADC A,L @@ -1362,7 +1365,7 @@ def ADC_8D(cpu): # 8D ADC A,L cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def ADC_8E(cpu): # 8E ADC A,(HL) @@ -1377,7 +1380,7 @@ def ADC_8E(cpu): # 8E ADC A,(HL) cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def ADC_8F(cpu): # 8F ADC A,A @@ -1392,7 +1395,7 @@ def ADC_8F(cpu): # 8F ADC A,A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SUB_90(cpu): # 90 SUB B @@ -1407,7 +1410,7 @@ def SUB_90(cpu): # 90 SUB B cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SUB_91(cpu): # 91 SUB C @@ -1422,7 +1425,7 @@ def SUB_91(cpu): # 91 SUB C cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SUB_92(cpu): # 92 SUB D @@ -1437,7 +1440,7 @@ def SUB_92(cpu): # 92 SUB D cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SUB_93(cpu): # 93 SUB E @@ -1452,7 +1455,7 @@ def SUB_93(cpu): # 93 SUB E cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SUB_94(cpu): # 94 SUB H @@ -1467,7 +1470,7 @@ def SUB_94(cpu): # 94 SUB H cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SUB_95(cpu): # 95 SUB L @@ -1482,7 +1485,7 @@ def SUB_95(cpu): # 95 SUB L cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SUB_96(cpu): # 96 SUB (HL) @@ -1497,7 +1500,7 @@ def SUB_96(cpu): # 96 SUB (HL) cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SUB_97(cpu): # 97 SUB A @@ -1512,7 +1515,7 @@ def SUB_97(cpu): # 97 SUB A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SBC_98(cpu): # 98 SBC A,B @@ -1527,7 +1530,7 @@ def SBC_98(cpu): # 98 SBC A,B cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SBC_99(cpu): # 99 SBC A,C @@ -1542,7 +1545,7 @@ def SBC_99(cpu): # 99 SBC A,C cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SBC_9A(cpu): # 9A SBC A,D @@ -1557,7 +1560,7 @@ def SBC_9A(cpu): # 9A SBC A,D cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SBC_9B(cpu): # 9B SBC A,E @@ -1572,7 +1575,7 @@ def SBC_9B(cpu): # 9B SBC A,E cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SBC_9C(cpu): # 9C SBC A,H @@ -1587,7 +1590,7 @@ def SBC_9C(cpu): # 9C SBC A,H cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SBC_9D(cpu): # 9D SBC A,L @@ -1602,7 +1605,7 @@ def SBC_9D(cpu): # 9D SBC A,L cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def SBC_9E(cpu): # 9E SBC A,(HL) @@ -1617,7 +1620,7 @@ def SBC_9E(cpu): # 9E SBC A,(HL) cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SBC_9F(cpu): # 9F SBC A,A @@ -1632,7 +1635,7 @@ def SBC_9F(cpu): # 9F SBC A,A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def AND_A0(cpu): # A0 AND B @@ -1645,7 +1648,7 @@ def AND_A0(cpu): # A0 AND B cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def AND_A1(cpu): # A1 AND C @@ -1658,7 +1661,7 @@ def AND_A1(cpu): # A1 AND C cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def AND_A2(cpu): # A2 AND D @@ -1671,7 +1674,7 @@ def AND_A2(cpu): # A2 AND D cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def AND_A3(cpu): # A3 AND E @@ -1684,7 +1687,7 @@ def AND_A3(cpu): # A3 AND E cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def AND_A4(cpu): # A4 AND H @@ -1697,7 +1700,7 @@ def AND_A4(cpu): # A4 AND H cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def AND_A5(cpu): # A5 AND L @@ -1710,7 +1713,7 @@ def AND_A5(cpu): # A5 AND L cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def AND_A6(cpu): # A6 AND (HL) @@ -1723,7 +1726,7 @@ def AND_A6(cpu): # A6 AND (HL) cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def AND_A7(cpu): # A7 AND A @@ -1736,7 +1739,7 @@ def AND_A7(cpu): # A7 AND A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def XOR_A8(cpu): # A8 XOR B @@ -1749,7 +1752,7 @@ def XOR_A8(cpu): # A8 XOR B cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def XOR_A9(cpu): # A9 XOR C @@ -1762,7 +1765,7 @@ def XOR_A9(cpu): # A9 XOR C cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def XOR_AA(cpu): # AA XOR D @@ -1775,7 +1778,7 @@ def XOR_AA(cpu): # AA XOR D cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def XOR_AB(cpu): # AB XOR E @@ -1788,7 +1791,7 @@ def XOR_AB(cpu): # AB XOR E cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def XOR_AC(cpu): # AC XOR H @@ -1801,7 +1804,7 @@ def XOR_AC(cpu): # AC XOR H cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def XOR_AD(cpu): # AD XOR L @@ -1814,7 +1817,7 @@ def XOR_AD(cpu): # AD XOR L cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def XOR_AE(cpu): # AE XOR (HL) @@ -1827,7 +1830,7 @@ def XOR_AE(cpu): # AE XOR (HL) cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def XOR_AF(cpu): # AF XOR A @@ -1840,7 +1843,7 @@ def XOR_AF(cpu): # AF XOR A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def OR_B0(cpu): # B0 OR B @@ -1853,7 +1856,7 @@ def OR_B0(cpu): # B0 OR B cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def OR_B1(cpu): # B1 OR C @@ -1866,7 +1869,7 @@ def OR_B1(cpu): # B1 OR C cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def OR_B2(cpu): # B2 OR D @@ -1879,7 +1882,7 @@ def OR_B2(cpu): # B2 OR D cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def OR_B3(cpu): # B3 OR E @@ -1892,7 +1895,7 @@ def OR_B3(cpu): # B3 OR E cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def OR_B4(cpu): # B4 OR H @@ -1905,7 +1908,7 @@ def OR_B4(cpu): # B4 OR H cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def OR_B5(cpu): # B5 OR L @@ -1918,7 +1921,7 @@ def OR_B5(cpu): # B5 OR L cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def OR_B6(cpu): # B6 OR (HL) @@ -1931,7 +1934,7 @@ def OR_B6(cpu): # B6 OR (HL) cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def OR_B7(cpu): # B7 OR A @@ -1944,7 +1947,7 @@ def OR_B7(cpu): # B7 OR A cpu.A = t cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_B8(cpu): # B8 CP B @@ -1958,7 +1961,7 @@ def CP_B8(cpu): # B8 CP B t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_B9(cpu): # B9 CP C @@ -1972,7 +1975,7 @@ def CP_B9(cpu): # B9 CP C t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_BA(cpu): # BA CP D @@ -1986,7 +1989,7 @@ def CP_BA(cpu): # BA CP D t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_BB(cpu): # BB CP E @@ -2000,7 +2003,7 @@ def CP_BB(cpu): # BB CP E t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_BC(cpu): # BC CP H @@ -2014,7 +2017,7 @@ def CP_BC(cpu): # BC CP H t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_BD(cpu): # BD CP L @@ -2028,7 +2031,7 @@ def CP_BD(cpu): # BD CP L t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_BE(cpu): # BE CP (HL) @@ -2042,7 +2045,7 @@ def CP_BE(cpu): # BE CP (HL) t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def CP_BF(cpu): # BF CP A @@ -2056,7 +2059,7 @@ def CP_BF(cpu): # BF CP A t &= 0xFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def RET_C0(cpu): # C0 RET NZ @@ -2065,11 +2068,11 @@ def RET_C0(cpu): # C0 RET NZ cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF - return 20 + cpu.cycles += 20 else: cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def POP_C1(cpu): # C1 POP BC @@ -2079,22 +2082,22 @@ def POP_C1(cpu): # C1 POP BC cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def JP_C2(cpu, v): # C2 JP NZ,a16 if ((cpu.F & (1 << FLAGZ)) == 0): cpu.PC = v - return 16 + cpu.cycles += 16 else: cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def JP_C3(cpu, v): # C3 JP a16 cpu.PC = v - return 16 + cpu.cycles += 16 def CALL_C4(cpu, v): # C4 CALL NZ,a16 @@ -2106,9 +2109,9 @@ def CALL_C4(cpu, v): # C4 CALL NZ,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v - return 24 + cpu.cycles += 24 else: - return 12 + cpu.cycles += 12 def PUSH_C5(cpu): # C5 PUSH BC @@ -2118,7 +2121,7 @@ def PUSH_C5(cpu): # C5 PUSH BC cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 16 def ADD_C6(cpu, v): # C6 ADD A,d8 @@ -2133,7 +2136,7 @@ def ADD_C6(cpu, v): # C6 ADD A,d8 cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_C7(cpu): # C7 RST 00H @@ -2144,7 +2147,7 @@ def RST_C7(cpu): # C7 RST 00H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 0 - return 16 + cpu.cycles += 16 def RET_C8(cpu): # C8 RET Z @@ -2153,11 +2156,11 @@ def RET_C8(cpu): # C8 RET Z cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF - return 20 + cpu.cycles += 20 else: cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RET_C9(cpu): # C9 RET @@ -2165,24 +2168,24 @@ def RET_C9(cpu): # C9 RET cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF - return 16 + cpu.cycles += 16 def JP_CA(cpu, v): # CA JP Z,a16 if ((cpu.F & (1 << FLAGZ)) != 0): cpu.PC = v - return 16 + cpu.cycles += 16 else: cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def PREFIX_CB(cpu): # CB PREFIX CB logger.critical('CB cannot be called!') cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CALL_CC(cpu, v): # CC CALL Z,a16 @@ -2194,9 +2197,9 @@ def CALL_CC(cpu, v): # CC CALL Z,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v - return 24 + cpu.cycles += 24 else: - return 12 + cpu.cycles += 12 def CALL_CD(cpu, v): # CD CALL a16 @@ -2207,7 +2210,7 @@ def CALL_CD(cpu, v): # CD CALL a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v - return 24 + cpu.cycles += 24 def ADC_CE(cpu, v): # CE ADC A,d8 @@ -2222,7 +2225,7 @@ def ADC_CE(cpu, v): # CE ADC A,d8 cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_CF(cpu): # CF RST 08H @@ -2233,7 +2236,7 @@ def RST_CF(cpu): # CF RST 08H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 8 - return 16 + cpu.cycles += 16 def RET_D0(cpu): # D0 RET NC @@ -2242,11 +2245,11 @@ def RET_D0(cpu): # D0 RET NC cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF - return 20 + cpu.cycles += 20 else: cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def POP_D1(cpu): # D1 POP DE @@ -2256,17 +2259,17 @@ def POP_D1(cpu): # D1 POP DE cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def JP_D2(cpu, v): # D2 JP NC,a16 if ((cpu.F & (1 << FLAGC)) == 0): cpu.PC = v - return 16 + cpu.cycles += 16 else: cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def CALL_D4(cpu, v): # D4 CALL NC,a16 @@ -2278,9 +2281,9 @@ def CALL_D4(cpu, v): # D4 CALL NC,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v - return 24 + cpu.cycles += 24 else: - return 12 + cpu.cycles += 12 def PUSH_D5(cpu): # D5 PUSH DE @@ -2290,7 +2293,7 @@ def PUSH_D5(cpu): # D5 PUSH DE cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 16 def SUB_D6(cpu, v): # D6 SUB d8 @@ -2305,7 +2308,7 @@ def SUB_D6(cpu, v): # D6 SUB d8 cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_D7(cpu): # D7 RST 10H @@ -2316,7 +2319,7 @@ def RST_D7(cpu): # D7 RST 10H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 16 - return 16 + cpu.cycles += 16 def RET_D8(cpu): # D8 RET C @@ -2325,11 +2328,11 @@ def RET_D8(cpu): # D8 RET C cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF - return 20 + cpu.cycles += 20 else: cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RETI_D9(cpu): # D9 RETI @@ -2339,17 +2342,17 @@ def RETI_D9(cpu): # D9 RETI cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF - return 16 + cpu.cycles += 16 def JP_DA(cpu, v): # DA JP C,a16 if ((cpu.F & (1 << FLAGC)) != 0): cpu.PC = v - return 16 + cpu.cycles += 16 else: cpu.PC += 3 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def CALL_DC(cpu, v): # DC CALL C,a16 @@ -2361,9 +2364,9 @@ def CALL_DC(cpu, v): # DC CALL C,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v - return 24 + cpu.cycles += 24 else: - return 12 + cpu.cycles += 12 def SBC_DE(cpu, v): # DE SBC A,d8 @@ -2378,7 +2381,7 @@ def SBC_DE(cpu, v): # DE SBC A,d8 cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_DF(cpu): # DF RST 18H @@ -2389,14 +2392,15 @@ def RST_DF(cpu): # DF RST 18H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 24 - return 16 + cpu.cycles += 16 def LDH_E0(cpu, v): # E0 LDH (a8),A + cpu.cycles += 4 cpu.mb.setitem(v + 0xFF00, cpu.A) cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def POP_E1(cpu): # E1 POP HL @@ -2405,14 +2409,14 @@ def POP_E1(cpu): # E1 POP HL cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def LD_E2(cpu): # E2 LD (C),A cpu.mb.setitem(0xFF00 + cpu.C, cpu.A) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def PUSH_E5(cpu): # E5 PUSH HL @@ -2422,7 +2426,7 @@ def PUSH_E5(cpu): # E5 PUSH HL cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 16 def AND_E6(cpu, v): # E6 AND d8 @@ -2435,7 +2439,7 @@ def AND_E6(cpu, v): # E6 AND d8 cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_E7(cpu): # E7 RST 20H @@ -2446,7 +2450,7 @@ def RST_E7(cpu): # E7 RST 20H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 32 - return 16 + cpu.cycles += 16 def ADD_E8(cpu, v): # E8 ADD SP,r8 @@ -2460,19 +2464,20 @@ def ADD_E8(cpu, v): # E8 ADD SP,r8 cpu.SP = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 16 def JP_E9(cpu): # E9 JP (HL) cpu.PC = cpu.HL - return 4 + cpu.cycles += 4 def LD_EA(cpu, v): # EA LD (a16),A + cpu.cycles += 8 cpu.mb.setitem(v, cpu.A) cpu.PC += 3 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def XOR_EE(cpu, v): # EE XOR d8 @@ -2485,7 +2490,7 @@ def XOR_EE(cpu, v): # EE XOR d8 cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_EF(cpu): # EF RST 28H @@ -2496,14 +2501,15 @@ def RST_EF(cpu): # EF RST 28H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 40 - return 16 + cpu.cycles += 16 def LDH_F0(cpu, v): # F0 LDH A,(a8) + cpu.cycles += 4 cpu.A = cpu.mb.getitem(v + 0xFF00) cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def POP_F1(cpu): # F1 POP AF @@ -2513,21 +2519,21 @@ def POP_F1(cpu): # F1 POP AF cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def LD_F2(cpu): # F2 LD A,(C) cpu.A = cpu.mb.getitem(0xFF00 + cpu.C) cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def DI_F3(cpu): # F3 DI cpu.interrupt_master_enable = False cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def PUSH_F5(cpu): # F5 PUSH AF @@ -2537,7 +2543,7 @@ def PUSH_F5(cpu): # F5 PUSH AF cpu.SP &= 0xFFFF cpu.PC += 1 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 16 def OR_F6(cpu, v): # F6 OR d8 @@ -2550,7 +2556,7 @@ def OR_F6(cpu, v): # F6 OR d8 cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_F7(cpu): # F7 RST 30H @@ -2561,7 +2567,7 @@ def RST_F7(cpu): # F7 RST 30H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 48 - return 16 + cpu.cycles += 16 def LD_F8(cpu, v): # F8 LD HL,SP+r8 @@ -2575,21 +2581,22 @@ def LD_F8(cpu, v): # F8 LD HL,SP+r8 cpu.HL &= 0xFFFF cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 12 def LD_F9(cpu): # F9 LD SP,HL cpu.SP = cpu.HL cpu.PC += 1 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def LD_FA(cpu, v): # FA LD A,(a16) + cpu.cycles += 8 cpu.A = cpu.mb.getitem(v) cpu.PC += 3 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def EI_FB(cpu): # FB EI @@ -2597,7 +2604,7 @@ def EI_FB(cpu): # FB EI cpu.bail = (cpu.interrupts_flag_register & 0b11111) & (cpu.interrupts_enabled_register & 0b11111) cpu.PC += 1 cpu.PC &= 0xFFFF - return 4 + cpu.cycles += 4 def CP_FE(cpu, v): # FE CP d8 @@ -2611,7 +2618,7 @@ def CP_FE(cpu, v): # FE CP d8 t &= 0xFF cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RST_FF(cpu): # FF RST 38H @@ -2622,7 +2629,7 @@ def RST_FF(cpu): # FF RST 38H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 56 - return 16 + cpu.cycles += 16 def RLC_100(cpu): # 100 RLC B @@ -2636,7 +2643,7 @@ def RLC_100(cpu): # 100 RLC B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLC_101(cpu): # 101 RLC C @@ -2650,7 +2657,7 @@ def RLC_101(cpu): # 101 RLC C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLC_102(cpu): # 102 RLC D @@ -2664,7 +2671,7 @@ def RLC_102(cpu): # 102 RLC D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLC_103(cpu): # 103 RLC E @@ -2678,7 +2685,7 @@ def RLC_103(cpu): # 103 RLC E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLC_104(cpu): # 104 RLC H @@ -2692,7 +2699,7 @@ def RLC_104(cpu): # 104 RLC H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLC_105(cpu): # 105 RLC L @@ -2706,10 +2713,11 @@ def RLC_105(cpu): # 105 RLC L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RLC_106(cpu): # 106 RLC (HL) + cpu.cycles += 4 t = (cpu.mb.getitem(cpu.HL) << 1) + (cpu.mb.getitem(cpu.HL) >> 7) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -2717,10 +2725,11 @@ def RLC_106(cpu): # 106 RLC (HL) cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RLC_107(cpu): # 107 RLC A @@ -2734,7 +2743,7 @@ def RLC_107(cpu): # 107 RLC A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRC_108(cpu): # 108 RRC B @@ -2748,7 +2757,7 @@ def RRC_108(cpu): # 108 RRC B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRC_109(cpu): # 109 RRC C @@ -2762,7 +2771,7 @@ def RRC_109(cpu): # 109 RRC C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRC_10A(cpu): # 10A RRC D @@ -2776,7 +2785,7 @@ def RRC_10A(cpu): # 10A RRC D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRC_10B(cpu): # 10B RRC E @@ -2790,7 +2799,7 @@ def RRC_10B(cpu): # 10B RRC E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRC_10C(cpu): # 10C RRC H @@ -2804,7 +2813,7 @@ def RRC_10C(cpu): # 10C RRC H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRC_10D(cpu): # 10D RRC L @@ -2818,10 +2827,11 @@ def RRC_10D(cpu): # 10D RRC L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RRC_10E(cpu): # 10E RRC (HL) + cpu.cycles += 4 t = (cpu.mb.getitem(cpu.HL) >> 1) + ((cpu.mb.getitem(cpu.HL) & 1) << 7) + ((cpu.mb.getitem(cpu.HL) & 1) << 8) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -2829,10 +2839,11 @@ def RRC_10E(cpu): # 10E RRC (HL) cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RRC_10F(cpu): # 10F RRC A @@ -2846,7 +2857,7 @@ def RRC_10F(cpu): # 10F RRC A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RL_110(cpu): # 110 RL B @@ -2860,7 +2871,7 @@ def RL_110(cpu): # 110 RL B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RL_111(cpu): # 111 RL C @@ -2874,7 +2885,7 @@ def RL_111(cpu): # 111 RL C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RL_112(cpu): # 112 RL D @@ -2888,7 +2899,7 @@ def RL_112(cpu): # 112 RL D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RL_113(cpu): # 113 RL E @@ -2902,7 +2913,7 @@ def RL_113(cpu): # 113 RL E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RL_114(cpu): # 114 RL H @@ -2916,7 +2927,7 @@ def RL_114(cpu): # 114 RL H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RL_115(cpu): # 115 RL L @@ -2930,10 +2941,11 @@ def RL_115(cpu): # 115 RL L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RL_116(cpu): # 116 RL (HL) + cpu.cycles += 4 t = (cpu.mb.getitem(cpu.HL) << 1) + ((cpu.F & (1 << FLAGC)) != 0) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -2941,10 +2953,11 @@ def RL_116(cpu): # 116 RL (HL) cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RL_117(cpu): # 117 RL A @@ -2958,7 +2971,7 @@ def RL_117(cpu): # 117 RL A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RR_118(cpu): # 118 RR B @@ -2972,7 +2985,7 @@ def RR_118(cpu): # 118 RR B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RR_119(cpu): # 119 RR C @@ -2986,7 +2999,7 @@ def RR_119(cpu): # 119 RR C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RR_11A(cpu): # 11A RR D @@ -3000,7 +3013,7 @@ def RR_11A(cpu): # 11A RR D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RR_11B(cpu): # 11B RR E @@ -3014,7 +3027,7 @@ def RR_11B(cpu): # 11B RR E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RR_11C(cpu): # 11C RR H @@ -3028,7 +3041,7 @@ def RR_11C(cpu): # 11C RR H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RR_11D(cpu): # 11D RR L @@ -3042,10 +3055,11 @@ def RR_11D(cpu): # 11D RR L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RR_11E(cpu): # 11E RR (HL) + cpu.cycles += 4 t = (cpu.mb.getitem(cpu.HL) >> 1) + (((cpu.F & (1 << FLAGC)) != 0) << 7) + ((cpu.mb.getitem(cpu.HL) & 1) << 8) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3053,10 +3067,11 @@ def RR_11E(cpu): # 11E RR (HL) cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RR_11F(cpu): # 11F RR A @@ -3070,7 +3085,7 @@ def RR_11F(cpu): # 11F RR A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SLA_120(cpu): # 120 SLA B @@ -3084,7 +3099,7 @@ def SLA_120(cpu): # 120 SLA B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SLA_121(cpu): # 121 SLA C @@ -3098,7 +3113,7 @@ def SLA_121(cpu): # 121 SLA C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SLA_122(cpu): # 122 SLA D @@ -3112,7 +3127,7 @@ def SLA_122(cpu): # 122 SLA D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SLA_123(cpu): # 123 SLA E @@ -3126,7 +3141,7 @@ def SLA_123(cpu): # 123 SLA E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SLA_124(cpu): # 124 SLA H @@ -3140,7 +3155,7 @@ def SLA_124(cpu): # 124 SLA H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SLA_125(cpu): # 125 SLA L @@ -3154,10 +3169,11 @@ def SLA_125(cpu): # 125 SLA L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SLA_126(cpu): # 126 SLA (HL) + cpu.cycles += 4 t = (cpu.mb.getitem(cpu.HL) << 1) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3165,10 +3181,11 @@ def SLA_126(cpu): # 126 SLA (HL) cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SLA_127(cpu): # 127 SLA A @@ -3182,7 +3199,7 @@ def SLA_127(cpu): # 127 SLA A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRA_128(cpu): # 128 SRA B @@ -3196,7 +3213,7 @@ def SRA_128(cpu): # 128 SRA B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRA_129(cpu): # 129 SRA C @@ -3210,7 +3227,7 @@ def SRA_129(cpu): # 129 SRA C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRA_12A(cpu): # 12A SRA D @@ -3224,7 +3241,7 @@ def SRA_12A(cpu): # 12A SRA D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRA_12B(cpu): # 12B SRA E @@ -3238,7 +3255,7 @@ def SRA_12B(cpu): # 12B SRA E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRA_12C(cpu): # 12C SRA H @@ -3252,7 +3269,7 @@ def SRA_12C(cpu): # 12C SRA H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRA_12D(cpu): # 12D SRA L @@ -3266,10 +3283,11 @@ def SRA_12D(cpu): # 12D SRA L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRA_12E(cpu): # 12E SRA (HL) + cpu.cycles += 4 t = ((cpu.mb.getitem(cpu.HL) >> 1) | (cpu.mb.getitem(cpu.HL) & 0x80)) + ((cpu.mb.getitem(cpu.HL) & 1) << 8) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3277,10 +3295,11 @@ def SRA_12E(cpu): # 12E SRA (HL) cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SRA_12F(cpu): # 12F SRA A @@ -3294,7 +3313,7 @@ def SRA_12F(cpu): # 12F SRA A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SWAP_130(cpu): # 130 SWAP B @@ -3307,7 +3326,7 @@ def SWAP_130(cpu): # 130 SWAP B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SWAP_131(cpu): # 131 SWAP C @@ -3320,7 +3339,7 @@ def SWAP_131(cpu): # 131 SWAP C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SWAP_132(cpu): # 132 SWAP D @@ -3333,7 +3352,7 @@ def SWAP_132(cpu): # 132 SWAP D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SWAP_133(cpu): # 133 SWAP E @@ -3346,7 +3365,7 @@ def SWAP_133(cpu): # 133 SWAP E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SWAP_134(cpu): # 134 SWAP H @@ -3359,7 +3378,7 @@ def SWAP_134(cpu): # 134 SWAP H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SWAP_135(cpu): # 135 SWAP L @@ -3372,20 +3391,22 @@ def SWAP_135(cpu): # 135 SWAP L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SWAP_136(cpu): # 136 SWAP (HL) + cpu.cycles += 4 t = ((cpu.mb.getitem(cpu.HL) & 0xF0) >> 4) | ((cpu.mb.getitem(cpu.HL) & 0x0F) << 4) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SWAP_137(cpu): # 137 SWAP A @@ -3398,7 +3419,7 @@ def SWAP_137(cpu): # 137 SWAP A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRL_138(cpu): # 138 SRL B @@ -3412,7 +3433,7 @@ def SRL_138(cpu): # 138 SRL B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRL_139(cpu): # 139 SRL C @@ -3426,7 +3447,7 @@ def SRL_139(cpu): # 139 SRL C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRL_13A(cpu): # 13A SRL D @@ -3440,7 +3461,7 @@ def SRL_13A(cpu): # 13A SRL D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRL_13B(cpu): # 13B SRL E @@ -3454,7 +3475,7 @@ def SRL_13B(cpu): # 13B SRL E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRL_13C(cpu): # 13C SRL H @@ -3468,7 +3489,7 @@ def SRL_13C(cpu): # 13C SRL H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRL_13D(cpu): # 13D SRL L @@ -3482,10 +3503,11 @@ def SRL_13D(cpu): # 13D SRL L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SRL_13E(cpu): # 13E SRL (HL) + cpu.cycles += 4 t = (cpu.mb.getitem(cpu.HL) >> 1) + ((cpu.mb.getitem(cpu.HL) & 1) << 8) flag = 0b00000000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3493,10 +3515,11 @@ def SRL_13E(cpu): # 13E SRL (HL) cpu.F &= 0b00000000 cpu.F |= flag t &= 0xFF + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SRL_13F(cpu): # 13F SRL A @@ -3510,7 +3533,7 @@ def SRL_13F(cpu): # 13F SRL A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_140(cpu): # 140 BIT 0,B @@ -3521,7 +3544,7 @@ def BIT_140(cpu): # 140 BIT 0,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_141(cpu): # 141 BIT 0,C @@ -3532,7 +3555,7 @@ def BIT_141(cpu): # 141 BIT 0,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_142(cpu): # 142 BIT 0,D @@ -3543,7 +3566,7 @@ def BIT_142(cpu): # 142 BIT 0,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_143(cpu): # 143 BIT 0,E @@ -3554,7 +3577,7 @@ def BIT_143(cpu): # 143 BIT 0,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_144(cpu): # 144 BIT 0,H @@ -3565,7 +3588,7 @@ def BIT_144(cpu): # 144 BIT 0,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_145(cpu): # 145 BIT 0,L @@ -3576,10 +3599,11 @@ def BIT_145(cpu): # 145 BIT 0,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_146(cpu): # 146 BIT 0,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 0) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3587,7 +3611,7 @@ def BIT_146(cpu): # 146 BIT 0,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_147(cpu): # 147 BIT 0,A @@ -3598,7 +3622,7 @@ def BIT_147(cpu): # 147 BIT 0,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_148(cpu): # 148 BIT 1,B @@ -3609,7 +3633,7 @@ def BIT_148(cpu): # 148 BIT 1,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_149(cpu): # 149 BIT 1,C @@ -3620,7 +3644,7 @@ def BIT_149(cpu): # 149 BIT 1,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_14A(cpu): # 14A BIT 1,D @@ -3631,7 +3655,7 @@ def BIT_14A(cpu): # 14A BIT 1,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_14B(cpu): # 14B BIT 1,E @@ -3642,7 +3666,7 @@ def BIT_14B(cpu): # 14B BIT 1,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_14C(cpu): # 14C BIT 1,H @@ -3653,7 +3677,7 @@ def BIT_14C(cpu): # 14C BIT 1,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_14D(cpu): # 14D BIT 1,L @@ -3664,10 +3688,11 @@ def BIT_14D(cpu): # 14D BIT 1,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_14E(cpu): # 14E BIT 1,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 1) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3675,7 +3700,7 @@ def BIT_14E(cpu): # 14E BIT 1,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_14F(cpu): # 14F BIT 1,A @@ -3686,7 +3711,7 @@ def BIT_14F(cpu): # 14F BIT 1,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_150(cpu): # 150 BIT 2,B @@ -3697,7 +3722,7 @@ def BIT_150(cpu): # 150 BIT 2,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_151(cpu): # 151 BIT 2,C @@ -3708,7 +3733,7 @@ def BIT_151(cpu): # 151 BIT 2,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_152(cpu): # 152 BIT 2,D @@ -3719,7 +3744,7 @@ def BIT_152(cpu): # 152 BIT 2,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_153(cpu): # 153 BIT 2,E @@ -3730,7 +3755,7 @@ def BIT_153(cpu): # 153 BIT 2,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_154(cpu): # 154 BIT 2,H @@ -3741,7 +3766,7 @@ def BIT_154(cpu): # 154 BIT 2,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_155(cpu): # 155 BIT 2,L @@ -3752,10 +3777,11 @@ def BIT_155(cpu): # 155 BIT 2,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_156(cpu): # 156 BIT 2,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 2) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3763,7 +3789,7 @@ def BIT_156(cpu): # 156 BIT 2,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_157(cpu): # 157 BIT 2,A @@ -3774,7 +3800,7 @@ def BIT_157(cpu): # 157 BIT 2,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_158(cpu): # 158 BIT 3,B @@ -3785,7 +3811,7 @@ def BIT_158(cpu): # 158 BIT 3,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_159(cpu): # 159 BIT 3,C @@ -3796,7 +3822,7 @@ def BIT_159(cpu): # 159 BIT 3,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_15A(cpu): # 15A BIT 3,D @@ -3807,7 +3833,7 @@ def BIT_15A(cpu): # 15A BIT 3,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_15B(cpu): # 15B BIT 3,E @@ -3818,7 +3844,7 @@ def BIT_15B(cpu): # 15B BIT 3,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_15C(cpu): # 15C BIT 3,H @@ -3829,7 +3855,7 @@ def BIT_15C(cpu): # 15C BIT 3,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_15D(cpu): # 15D BIT 3,L @@ -3840,10 +3866,11 @@ def BIT_15D(cpu): # 15D BIT 3,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_15E(cpu): # 15E BIT 3,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 3) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3851,7 +3878,7 @@ def BIT_15E(cpu): # 15E BIT 3,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_15F(cpu): # 15F BIT 3,A @@ -3862,7 +3889,7 @@ def BIT_15F(cpu): # 15F BIT 3,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_160(cpu): # 160 BIT 4,B @@ -3873,7 +3900,7 @@ def BIT_160(cpu): # 160 BIT 4,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_161(cpu): # 161 BIT 4,C @@ -3884,7 +3911,7 @@ def BIT_161(cpu): # 161 BIT 4,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_162(cpu): # 162 BIT 4,D @@ -3895,7 +3922,7 @@ def BIT_162(cpu): # 162 BIT 4,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_163(cpu): # 163 BIT 4,E @@ -3906,7 +3933,7 @@ def BIT_163(cpu): # 163 BIT 4,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_164(cpu): # 164 BIT 4,H @@ -3917,7 +3944,7 @@ def BIT_164(cpu): # 164 BIT 4,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_165(cpu): # 165 BIT 4,L @@ -3928,10 +3955,11 @@ def BIT_165(cpu): # 165 BIT 4,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_166(cpu): # 166 BIT 4,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 4) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -3939,7 +3967,7 @@ def BIT_166(cpu): # 166 BIT 4,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_167(cpu): # 167 BIT 4,A @@ -3950,7 +3978,7 @@ def BIT_167(cpu): # 167 BIT 4,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_168(cpu): # 168 BIT 5,B @@ -3961,7 +3989,7 @@ def BIT_168(cpu): # 168 BIT 5,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_169(cpu): # 169 BIT 5,C @@ -3972,7 +4000,7 @@ def BIT_169(cpu): # 169 BIT 5,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_16A(cpu): # 16A BIT 5,D @@ -3983,7 +4011,7 @@ def BIT_16A(cpu): # 16A BIT 5,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_16B(cpu): # 16B BIT 5,E @@ -3994,7 +4022,7 @@ def BIT_16B(cpu): # 16B BIT 5,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_16C(cpu): # 16C BIT 5,H @@ -4005,7 +4033,7 @@ def BIT_16C(cpu): # 16C BIT 5,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_16D(cpu): # 16D BIT 5,L @@ -4016,10 +4044,11 @@ def BIT_16D(cpu): # 16D BIT 5,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_16E(cpu): # 16E BIT 5,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 5) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -4027,7 +4056,7 @@ def BIT_16E(cpu): # 16E BIT 5,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_16F(cpu): # 16F BIT 5,A @@ -4038,7 +4067,7 @@ def BIT_16F(cpu): # 16F BIT 5,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_170(cpu): # 170 BIT 6,B @@ -4049,7 +4078,7 @@ def BIT_170(cpu): # 170 BIT 6,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_171(cpu): # 171 BIT 6,C @@ -4060,7 +4089,7 @@ def BIT_171(cpu): # 171 BIT 6,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_172(cpu): # 172 BIT 6,D @@ -4071,7 +4100,7 @@ def BIT_172(cpu): # 172 BIT 6,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_173(cpu): # 173 BIT 6,E @@ -4082,7 +4111,7 @@ def BIT_173(cpu): # 173 BIT 6,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_174(cpu): # 174 BIT 6,H @@ -4093,7 +4122,7 @@ def BIT_174(cpu): # 174 BIT 6,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_175(cpu): # 175 BIT 6,L @@ -4104,10 +4133,11 @@ def BIT_175(cpu): # 175 BIT 6,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_176(cpu): # 176 BIT 6,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 6) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -4115,7 +4145,7 @@ def BIT_176(cpu): # 176 BIT 6,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_177(cpu): # 177 BIT 6,A @@ -4126,7 +4156,7 @@ def BIT_177(cpu): # 177 BIT 6,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_178(cpu): # 178 BIT 7,B @@ -4137,7 +4167,7 @@ def BIT_178(cpu): # 178 BIT 7,B cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_179(cpu): # 179 BIT 7,C @@ -4148,7 +4178,7 @@ def BIT_179(cpu): # 179 BIT 7,C cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_17A(cpu): # 17A BIT 7,D @@ -4159,7 +4189,7 @@ def BIT_17A(cpu): # 17A BIT 7,D cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_17B(cpu): # 17B BIT 7,E @@ -4170,7 +4200,7 @@ def BIT_17B(cpu): # 17B BIT 7,E cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_17C(cpu): # 17C BIT 7,H @@ -4181,7 +4211,7 @@ def BIT_17C(cpu): # 17C BIT 7,H cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_17D(cpu): # 17D BIT 7,L @@ -4192,10 +4222,11 @@ def BIT_17D(cpu): # 17D BIT 7,L cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def BIT_17E(cpu): # 17E BIT 7,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & (1 << 7) flag = 0b00100000 flag += ((t & 0xFF) == 0) << FLAGZ @@ -4203,7 +4234,7 @@ def BIT_17E(cpu): # 17E BIT 7,(HL) cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 12 + cpu.cycles += 8 def BIT_17F(cpu): # 17F BIT 7,A @@ -4214,7 +4245,7 @@ def BIT_17F(cpu): # 17F BIT 7,A cpu.F |= flag cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_180(cpu): # 180 RES 0,B @@ -4222,7 +4253,7 @@ def RES_180(cpu): # 180 RES 0,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_181(cpu): # 181 RES 0,C @@ -4230,7 +4261,7 @@ def RES_181(cpu): # 181 RES 0,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_182(cpu): # 182 RES 0,D @@ -4238,7 +4269,7 @@ def RES_182(cpu): # 182 RES 0,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_183(cpu): # 183 RES 0,E @@ -4246,7 +4277,7 @@ def RES_183(cpu): # 183 RES 0,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_184(cpu): # 184 RES 0,H @@ -4254,7 +4285,7 @@ def RES_184(cpu): # 184 RES 0,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_185(cpu): # 185 RES 0,L @@ -4262,15 +4293,17 @@ def RES_185(cpu): # 185 RES 0,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_186(cpu): # 186 RES 0,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 0) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_187(cpu): # 187 RES 0,A @@ -4278,7 +4311,7 @@ def RES_187(cpu): # 187 RES 0,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_188(cpu): # 188 RES 1,B @@ -4286,7 +4319,7 @@ def RES_188(cpu): # 188 RES 1,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_189(cpu): # 189 RES 1,C @@ -4294,7 +4327,7 @@ def RES_189(cpu): # 189 RES 1,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_18A(cpu): # 18A RES 1,D @@ -4302,7 +4335,7 @@ def RES_18A(cpu): # 18A RES 1,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_18B(cpu): # 18B RES 1,E @@ -4310,7 +4343,7 @@ def RES_18B(cpu): # 18B RES 1,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_18C(cpu): # 18C RES 1,H @@ -4318,7 +4351,7 @@ def RES_18C(cpu): # 18C RES 1,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_18D(cpu): # 18D RES 1,L @@ -4326,15 +4359,17 @@ def RES_18D(cpu): # 18D RES 1,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_18E(cpu): # 18E RES 1,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 1) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_18F(cpu): # 18F RES 1,A @@ -4342,7 +4377,7 @@ def RES_18F(cpu): # 18F RES 1,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_190(cpu): # 190 RES 2,B @@ -4350,7 +4385,7 @@ def RES_190(cpu): # 190 RES 2,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_191(cpu): # 191 RES 2,C @@ -4358,7 +4393,7 @@ def RES_191(cpu): # 191 RES 2,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_192(cpu): # 192 RES 2,D @@ -4366,7 +4401,7 @@ def RES_192(cpu): # 192 RES 2,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_193(cpu): # 193 RES 2,E @@ -4374,7 +4409,7 @@ def RES_193(cpu): # 193 RES 2,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_194(cpu): # 194 RES 2,H @@ -4382,7 +4417,7 @@ def RES_194(cpu): # 194 RES 2,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_195(cpu): # 195 RES 2,L @@ -4390,15 +4425,17 @@ def RES_195(cpu): # 195 RES 2,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_196(cpu): # 196 RES 2,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 2) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_197(cpu): # 197 RES 2,A @@ -4406,7 +4443,7 @@ def RES_197(cpu): # 197 RES 2,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_198(cpu): # 198 RES 3,B @@ -4414,7 +4451,7 @@ def RES_198(cpu): # 198 RES 3,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_199(cpu): # 199 RES 3,C @@ -4422,7 +4459,7 @@ def RES_199(cpu): # 199 RES 3,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_19A(cpu): # 19A RES 3,D @@ -4430,7 +4467,7 @@ def RES_19A(cpu): # 19A RES 3,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_19B(cpu): # 19B RES 3,E @@ -4438,7 +4475,7 @@ def RES_19B(cpu): # 19B RES 3,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_19C(cpu): # 19C RES 3,H @@ -4446,7 +4483,7 @@ def RES_19C(cpu): # 19C RES 3,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_19D(cpu): # 19D RES 3,L @@ -4454,15 +4491,17 @@ def RES_19D(cpu): # 19D RES 3,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_19E(cpu): # 19E RES 3,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 3) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_19F(cpu): # 19F RES 3,A @@ -4470,7 +4509,7 @@ def RES_19F(cpu): # 19F RES 3,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A0(cpu): # 1A0 RES 4,B @@ -4478,7 +4517,7 @@ def RES_1A0(cpu): # 1A0 RES 4,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A1(cpu): # 1A1 RES 4,C @@ -4486,7 +4525,7 @@ def RES_1A1(cpu): # 1A1 RES 4,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A2(cpu): # 1A2 RES 4,D @@ -4494,7 +4533,7 @@ def RES_1A2(cpu): # 1A2 RES 4,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A3(cpu): # 1A3 RES 4,E @@ -4502,7 +4541,7 @@ def RES_1A3(cpu): # 1A3 RES 4,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A4(cpu): # 1A4 RES 4,H @@ -4510,7 +4549,7 @@ def RES_1A4(cpu): # 1A4 RES 4,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A5(cpu): # 1A5 RES 4,L @@ -4518,15 +4557,17 @@ def RES_1A5(cpu): # 1A5 RES 4,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A6(cpu): # 1A6 RES 4,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 4) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_1A7(cpu): # 1A7 RES 4,A @@ -4534,7 +4575,7 @@ def RES_1A7(cpu): # 1A7 RES 4,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A8(cpu): # 1A8 RES 5,B @@ -4542,7 +4583,7 @@ def RES_1A8(cpu): # 1A8 RES 5,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1A9(cpu): # 1A9 RES 5,C @@ -4550,7 +4591,7 @@ def RES_1A9(cpu): # 1A9 RES 5,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1AA(cpu): # 1AA RES 5,D @@ -4558,7 +4599,7 @@ def RES_1AA(cpu): # 1AA RES 5,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1AB(cpu): # 1AB RES 5,E @@ -4566,7 +4607,7 @@ def RES_1AB(cpu): # 1AB RES 5,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1AC(cpu): # 1AC RES 5,H @@ -4574,7 +4615,7 @@ def RES_1AC(cpu): # 1AC RES 5,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1AD(cpu): # 1AD RES 5,L @@ -4582,15 +4623,17 @@ def RES_1AD(cpu): # 1AD RES 5,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1AE(cpu): # 1AE RES 5,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 5) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_1AF(cpu): # 1AF RES 5,A @@ -4598,7 +4641,7 @@ def RES_1AF(cpu): # 1AF RES 5,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B0(cpu): # 1B0 RES 6,B @@ -4606,7 +4649,7 @@ def RES_1B0(cpu): # 1B0 RES 6,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B1(cpu): # 1B1 RES 6,C @@ -4614,7 +4657,7 @@ def RES_1B1(cpu): # 1B1 RES 6,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B2(cpu): # 1B2 RES 6,D @@ -4622,7 +4665,7 @@ def RES_1B2(cpu): # 1B2 RES 6,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B3(cpu): # 1B3 RES 6,E @@ -4630,7 +4673,7 @@ def RES_1B3(cpu): # 1B3 RES 6,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B4(cpu): # 1B4 RES 6,H @@ -4638,7 +4681,7 @@ def RES_1B4(cpu): # 1B4 RES 6,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B5(cpu): # 1B5 RES 6,L @@ -4646,15 +4689,17 @@ def RES_1B5(cpu): # 1B5 RES 6,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B6(cpu): # 1B6 RES 6,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 6) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_1B7(cpu): # 1B7 RES 6,A @@ -4662,7 +4707,7 @@ def RES_1B7(cpu): # 1B7 RES 6,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B8(cpu): # 1B8 RES 7,B @@ -4670,7 +4715,7 @@ def RES_1B8(cpu): # 1B8 RES 7,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1B9(cpu): # 1B9 RES 7,C @@ -4678,7 +4723,7 @@ def RES_1B9(cpu): # 1B9 RES 7,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1BA(cpu): # 1BA RES 7,D @@ -4686,7 +4731,7 @@ def RES_1BA(cpu): # 1BA RES 7,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1BB(cpu): # 1BB RES 7,E @@ -4694,7 +4739,7 @@ def RES_1BB(cpu): # 1BB RES 7,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1BC(cpu): # 1BC RES 7,H @@ -4702,7 +4747,7 @@ def RES_1BC(cpu): # 1BC RES 7,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1BD(cpu): # 1BD RES 7,L @@ -4710,15 +4755,17 @@ def RES_1BD(cpu): # 1BD RES 7,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def RES_1BE(cpu): # 1BE RES 7,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) & ~(1 << 7) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def RES_1BF(cpu): # 1BF RES 7,A @@ -4726,7 +4773,7 @@ def RES_1BF(cpu): # 1BF RES 7,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C0(cpu): # 1C0 SET 0,B @@ -4734,7 +4781,7 @@ def SET_1C0(cpu): # 1C0 SET 0,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C1(cpu): # 1C1 SET 0,C @@ -4742,7 +4789,7 @@ def SET_1C1(cpu): # 1C1 SET 0,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C2(cpu): # 1C2 SET 0,D @@ -4750,7 +4797,7 @@ def SET_1C2(cpu): # 1C2 SET 0,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C3(cpu): # 1C3 SET 0,E @@ -4758,7 +4805,7 @@ def SET_1C3(cpu): # 1C3 SET 0,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C4(cpu): # 1C4 SET 0,H @@ -4766,7 +4813,7 @@ def SET_1C4(cpu): # 1C4 SET 0,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C5(cpu): # 1C5 SET 0,L @@ -4774,15 +4821,17 @@ def SET_1C5(cpu): # 1C5 SET 0,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C6(cpu): # 1C6 SET 0,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 0) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1C7(cpu): # 1C7 SET 0,A @@ -4790,7 +4839,7 @@ def SET_1C7(cpu): # 1C7 SET 0,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C8(cpu): # 1C8 SET 1,B @@ -4798,7 +4847,7 @@ def SET_1C8(cpu): # 1C8 SET 1,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1C9(cpu): # 1C9 SET 1,C @@ -4806,7 +4855,7 @@ def SET_1C9(cpu): # 1C9 SET 1,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1CA(cpu): # 1CA SET 1,D @@ -4814,7 +4863,7 @@ def SET_1CA(cpu): # 1CA SET 1,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1CB(cpu): # 1CB SET 1,E @@ -4822,7 +4871,7 @@ def SET_1CB(cpu): # 1CB SET 1,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1CC(cpu): # 1CC SET 1,H @@ -4830,7 +4879,7 @@ def SET_1CC(cpu): # 1CC SET 1,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1CD(cpu): # 1CD SET 1,L @@ -4838,15 +4887,17 @@ def SET_1CD(cpu): # 1CD SET 1,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1CE(cpu): # 1CE SET 1,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 1) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1CF(cpu): # 1CF SET 1,A @@ -4854,7 +4905,7 @@ def SET_1CF(cpu): # 1CF SET 1,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D0(cpu): # 1D0 SET 2,B @@ -4862,7 +4913,7 @@ def SET_1D0(cpu): # 1D0 SET 2,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D1(cpu): # 1D1 SET 2,C @@ -4870,7 +4921,7 @@ def SET_1D1(cpu): # 1D1 SET 2,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D2(cpu): # 1D2 SET 2,D @@ -4878,7 +4929,7 @@ def SET_1D2(cpu): # 1D2 SET 2,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D3(cpu): # 1D3 SET 2,E @@ -4886,7 +4937,7 @@ def SET_1D3(cpu): # 1D3 SET 2,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D4(cpu): # 1D4 SET 2,H @@ -4894,7 +4945,7 @@ def SET_1D4(cpu): # 1D4 SET 2,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D5(cpu): # 1D5 SET 2,L @@ -4902,15 +4953,17 @@ def SET_1D5(cpu): # 1D5 SET 2,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D6(cpu): # 1D6 SET 2,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 2) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1D7(cpu): # 1D7 SET 2,A @@ -4918,7 +4971,7 @@ def SET_1D7(cpu): # 1D7 SET 2,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D8(cpu): # 1D8 SET 3,B @@ -4926,7 +4979,7 @@ def SET_1D8(cpu): # 1D8 SET 3,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1D9(cpu): # 1D9 SET 3,C @@ -4934,7 +4987,7 @@ def SET_1D9(cpu): # 1D9 SET 3,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1DA(cpu): # 1DA SET 3,D @@ -4942,7 +4995,7 @@ def SET_1DA(cpu): # 1DA SET 3,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1DB(cpu): # 1DB SET 3,E @@ -4950,7 +5003,7 @@ def SET_1DB(cpu): # 1DB SET 3,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1DC(cpu): # 1DC SET 3,H @@ -4958,7 +5011,7 @@ def SET_1DC(cpu): # 1DC SET 3,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1DD(cpu): # 1DD SET 3,L @@ -4966,15 +5019,17 @@ def SET_1DD(cpu): # 1DD SET 3,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1DE(cpu): # 1DE SET 3,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 3) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1DF(cpu): # 1DF SET 3,A @@ -4982,7 +5037,7 @@ def SET_1DF(cpu): # 1DF SET 3,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E0(cpu): # 1E0 SET 4,B @@ -4990,7 +5045,7 @@ def SET_1E0(cpu): # 1E0 SET 4,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E1(cpu): # 1E1 SET 4,C @@ -4998,7 +5053,7 @@ def SET_1E1(cpu): # 1E1 SET 4,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E2(cpu): # 1E2 SET 4,D @@ -5006,7 +5061,7 @@ def SET_1E2(cpu): # 1E2 SET 4,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E3(cpu): # 1E3 SET 4,E @@ -5014,7 +5069,7 @@ def SET_1E3(cpu): # 1E3 SET 4,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E4(cpu): # 1E4 SET 4,H @@ -5022,7 +5077,7 @@ def SET_1E4(cpu): # 1E4 SET 4,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E5(cpu): # 1E5 SET 4,L @@ -5030,15 +5085,17 @@ def SET_1E5(cpu): # 1E5 SET 4,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E6(cpu): # 1E6 SET 4,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 4) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1E7(cpu): # 1E7 SET 4,A @@ -5046,7 +5103,7 @@ def SET_1E7(cpu): # 1E7 SET 4,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E8(cpu): # 1E8 SET 5,B @@ -5054,7 +5111,7 @@ def SET_1E8(cpu): # 1E8 SET 5,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1E9(cpu): # 1E9 SET 5,C @@ -5062,7 +5119,7 @@ def SET_1E9(cpu): # 1E9 SET 5,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1EA(cpu): # 1EA SET 5,D @@ -5070,7 +5127,7 @@ def SET_1EA(cpu): # 1EA SET 5,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1EB(cpu): # 1EB SET 5,E @@ -5078,7 +5135,7 @@ def SET_1EB(cpu): # 1EB SET 5,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1EC(cpu): # 1EC SET 5,H @@ -5086,7 +5143,7 @@ def SET_1EC(cpu): # 1EC SET 5,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1ED(cpu): # 1ED SET 5,L @@ -5094,15 +5151,17 @@ def SET_1ED(cpu): # 1ED SET 5,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1EE(cpu): # 1EE SET 5,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 5) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1EF(cpu): # 1EF SET 5,A @@ -5110,7 +5169,7 @@ def SET_1EF(cpu): # 1EF SET 5,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F0(cpu): # 1F0 SET 6,B @@ -5118,7 +5177,7 @@ def SET_1F0(cpu): # 1F0 SET 6,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F1(cpu): # 1F1 SET 6,C @@ -5126,7 +5185,7 @@ def SET_1F1(cpu): # 1F1 SET 6,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F2(cpu): # 1F2 SET 6,D @@ -5134,7 +5193,7 @@ def SET_1F2(cpu): # 1F2 SET 6,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F3(cpu): # 1F3 SET 6,E @@ -5142,7 +5201,7 @@ def SET_1F3(cpu): # 1F3 SET 6,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F4(cpu): # 1F4 SET 6,H @@ -5150,7 +5209,7 @@ def SET_1F4(cpu): # 1F4 SET 6,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F5(cpu): # 1F5 SET 6,L @@ -5158,15 +5217,17 @@ def SET_1F5(cpu): # 1F5 SET 6,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F6(cpu): # 1F6 SET 6,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 6) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1F7(cpu): # 1F7 SET 6,A @@ -5174,7 +5235,7 @@ def SET_1F7(cpu): # 1F7 SET 6,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F8(cpu): # 1F8 SET 7,B @@ -5182,7 +5243,7 @@ def SET_1F8(cpu): # 1F8 SET 7,B cpu.B = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1F9(cpu): # 1F9 SET 7,C @@ -5190,7 +5251,7 @@ def SET_1F9(cpu): # 1F9 SET 7,C cpu.C = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1FA(cpu): # 1FA SET 7,D @@ -5198,7 +5259,7 @@ def SET_1FA(cpu): # 1FA SET 7,D cpu.D = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1FB(cpu): # 1FB SET 7,E @@ -5206,7 +5267,7 @@ def SET_1FB(cpu): # 1FB SET 7,E cpu.E = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1FC(cpu): # 1FC SET 7,H @@ -5214,7 +5275,7 @@ def SET_1FC(cpu): # 1FC SET 7,H cpu.HL = (cpu.HL & 0x00FF) | (t << 8) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1FD(cpu): # 1FD SET 7,L @@ -5222,15 +5283,17 @@ def SET_1FD(cpu): # 1FD SET 7,L cpu.HL = (cpu.HL & 0xFF00) | (t & 0xFF) cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def SET_1FE(cpu): # 1FE SET 7,(HL) + cpu.cycles += 4 t = cpu.mb.getitem(cpu.HL) | (1 << 7) + cpu.cycles += 4 cpu.mb.setitem(cpu.HL, t) cpu.PC += 2 cpu.PC &= 0xFFFF - return 16 + cpu.cycles += 8 def SET_1FF(cpu): # 1FF SET 7,A @@ -5238,7 +5301,7 @@ def SET_1FF(cpu): # 1FF SET 7,A cpu.A = t cpu.PC += 2 cpu.PC &= 0xFFFF - return 8 + cpu.cycles += 8 def no_opcode(cpu): diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index cd6c07b7a..e5c750ee5 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -265,7 +265,7 @@ def getcode(self): if not self.branch_op: self.lines.append("cpu.PC += %d" % self.length) self.lines.append("cpu.PC &= 0xFFFF") - self.lines.append("return " + self.cycles[0]) # Choose the 0th cycle count + self.lines.append("cpu.cycles += " + self.cycles[0]) # Choose the 0th cycle count code += "\n\t".join(self.lines) @@ -461,7 +461,7 @@ def HALT(self): code.addlines([ "cpu.halted = True", "cpu.bail = True", - "return " + self.cycles[0], + "cpu.cycles += " + self.cycles[0], ]) return code.getcode() @@ -557,6 +557,24 @@ def LD(self): code = Code( self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) + + # These opcodes can be observed reading mid-cycle + if self.opcode == 0x36: + code.addline("cpu.cycles += 4") + code.cycles = (str(int(code.cycles[0]) - 4), ) + elif self.opcode == 0xE0: + code.addline("cpu.cycles += 4") + code.cycles = (str(int(code.cycles[0]) - 4), ) + elif self.opcode == 0xEA: + code.addline("cpu.cycles += 8") + code.cycles = (str(int(code.cycles[0]) - 8), ) + elif self.opcode == 0xF0: + code.addline("cpu.cycles += 4") + code.cycles = (str(int(code.cycles[0]) - 4), ) + elif self.opcode == 0xFA: + code.addline("cpu.cycles += 8") + code.cycles = (str(int(code.cycles[0]) - 8), ) + if self.is16bit and left.immediate and left.pointer: code.addline(left.set % ("%s & 0xFF" % right.get)) a, b = left.set.split(",") @@ -662,6 +680,13 @@ def INC(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "+")) + + if self.opcode == 0x34: + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(-1, "cpu.cycles += 4") # Inject before read + code.cycles = ("8", ) # 12 - 4 + return code.getcode() def DEC(self): @@ -673,6 +698,13 @@ def DEC(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "-")) + + if self.opcode == 0x35: + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(-1, "cpu.cycles += 4") # Inject before write + code.cycles = ("8", ) # 12 - 4 + return code.getcode() def ADC(self): @@ -858,16 +890,16 @@ def JP(self): self.name.split()[0], self.opcode, self.name, right.immediate, self.length, self.cycles, branch_op=True ) if left is None: - code.addlines(["cpu.PC = %s" % ("v" if right.immediate else r_code), "return " + self.cycles[0]]) + code.addlines(["cpu.PC = %s" % ("v" if right.immediate else r_code), "cpu.cycles += " + self.cycles[0]]) else: code.addlines([ "if %s:" % l_code, "\tcpu.PC = %s" % ("v" if right.immediate else r_code), - "\treturn " + self.cycles[0], + "\tcpu.cycles += " + self.cycles[0], "else:", "\tcpu.PC += %s" % self.length, "\tcpu.PC &= 0xFFFF", - "\treturn " + self.cycles[1], + "\tcpu.cycles += " + self.cycles[1], ]) return code.getcode() @@ -897,7 +929,7 @@ def JR(self): code.addlines([ "cpu.PC += %d + " % self.length + inline_signed_int8("v"), "cpu.PC &= 0xFFFF", - "return " + self.cycles[0], + "cpu.cycles += " + self.cycles[0], ]) else: code.addlines([ @@ -905,10 +937,10 @@ def JR(self): "if %s:" % l_code, "\tcpu.PC += " + inline_signed_int8("v"), "\tcpu.PC &= 0xFFFF", - "\treturn " + self.cycles[0], + "\tcpu.cycles += " + self.cycles[0], "else:", "\tcpu.PC &= 0xFFFF", - "\treturn " + self.cycles[1], + "\tcpu.cycles += " + self.cycles[1], ]) return code.getcode() @@ -948,7 +980,7 @@ def CALL(self): "cpu.SP -= 2", "cpu.SP &= 0xFFFF", "cpu.PC = %s" % ("v" if right.immediate else right.get), - "return " + self.cycles[0], + "cpu.cycles += " + self.cycles[0], ]) else: code.addlines([ @@ -958,9 +990,9 @@ def CALL(self): "\tcpu.SP -= 2", "\tcpu.SP &= 0xFFFF", "\tcpu.PC = %s" % ("v" if right.immediate else right.get), - "\treturn " + self.cycles[0], + "\tcpu.cycles += " + self.cycles[0], "else:", - "\treturn " + self.cycles[1], + "\tcpu.cycles += " + self.cycles[1], ]) return code.getcode() @@ -986,7 +1018,7 @@ def RET(self): "cpu.PC |= cpu.mb.getitem(cpu.SP) # Low", "cpu.SP += 2", "cpu.SP &= 0xFFFF", - "return " + self.cycles[0], + "cpu.cycles += " + self.cycles[0], ]) else: code.addlines([ @@ -995,11 +1027,11 @@ def RET(self): "\tcpu.PC |= cpu.mb.getitem(cpu.SP) # Low", "\tcpu.SP += 2", "\tcpu.SP &= 0xFFFF", - "\treturn " + self.cycles[0], + "\tcpu.cycles += " + self.cycles[0], "else:", "\tcpu.PC += %s" % self.length, "\tcpu.PC &= 0xFFFF", - "\treturn " + self.cycles[1], + "\tcpu.cycles += " + self.cycles[1], ]) return code.getcode() @@ -1013,7 +1045,7 @@ def RETI(self): "cpu.PC |= cpu.mb.getitem(cpu.SP) # Low", "cpu.SP += 2", "cpu.SP &= 0xFFFF", - "return " + self.cycles[0], + "cpu.cycles += " + self.cycles[0], ]) return code.getcode() @@ -1036,7 +1068,7 @@ def RST(self): code.addlines([ "cpu.PC = %s" % (right.code), - "return " + self.cycles[0], + "cpu.cycles += " + self.cycles[0], ]) return code.getcode() @@ -1055,6 +1087,14 @@ def rotateleft(self, name, left, throughcarry=False): code.addlines(self.handleflags8bit(left.get, None, None, throughcarry)) code.addline("t &= 0xFF") left.assign = True + + if left.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(left.set % "t") return code @@ -1092,6 +1132,14 @@ def rotateright(self, name, left, throughcarry=False): code.addline("t = (%s >> 1) + ((%s & 1) << 7)" % (left.get, left.get) + " + ((%s & 1) << 8)" % (left.get)) code.addlines(self.handleflags8bit(left.get, None, None, throughcarry)) code.addline("t &= 0xFF") + + if left.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(left.set % "t") return code @@ -1124,6 +1172,14 @@ def SLA(self): code.addline("t = (%s << 1)" % left.get) code.addlines(self.handleflags8bit(left.get, None, None, False)) code.addline("t &= 0xFF") + + if left.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(left.set % "t") return code.getcode() @@ -1137,6 +1193,14 @@ def SRA(self): code.addline("t = ((%s >> 1) | (%s & 0x80)) + ((%s & 1) << 8)" % (left.get, left.get, left.get)) code.addlines(self.handleflags8bit(left.get, None, None, False)) code.addline("t &= 0xFF") + + if left.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(left.set % "t") return code.getcode() @@ -1148,6 +1212,14 @@ def SRL(self): code.addline("t = (%s >> 1) + ((%s & 1) << 8)" % (left.get, left.get)) code.addlines(self.handleflags8bit(left.get, None, None, False)) code.addline("t &= 0xFF") + + if left.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(left.set % "t") return code.getcode() @@ -1158,6 +1230,14 @@ def SWAP(self): code.addline("t = ((%s & 0xF0) >> 4) | ((%s & 0x0F) << 4)" % (left.get, left.get)) code.addlines(self.handleflags8bit(left.get, None, None, False)) code.addline("t &= 0xFF") + + if left.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(left.set % "t") return code.getcode() @@ -1171,10 +1251,12 @@ def BIT(self): right = Operand(r1) code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles) - if self.opcode in [0x146, 0x14E, 0x156, 0x15E, 0x166, 0x16E, 0x176, 0x17E]: - # FIX: Corrent cycle count is 12, not 16! - code.cycles = ("12", ) - + # FIX: Correct cycle count is 12, not 16! + if right.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 12 - 4 code.addline("t = %s & (1 << %s)" % (right.get, left.get)) code.addlines(self.handleflags8bit(left.get, right.get, None, False)) @@ -1188,6 +1270,14 @@ def RES(self): code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles) code.addline("t = %s & ~(1 << %s)" % (right.get, left.get)) + + if right.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(right.set % "t") return code.getcode() @@ -1197,6 +1287,14 @@ def SET(self): right = Operand(r1) code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles) code.addline("t = %s | (1 << %s)" % (right.get, left.get)) + + if right.operand == "(HL)": + # HACK: Offset the timing by 4 cycles + # TODO: Probably should be generalized + code.lines.insert(0, "cpu.cycles += 4") # Inject before read + code.addline("cpu.cycles += 4") + code.cycles = ("8", ) # 16 - 4 - 4 + code.addline(right.set % "t") return code.getcode() diff --git a/tests/test_results/blargg.json b/tests/test_results/blargg.json index 5e153d5eb..67385b2d1 100644 --- a/tests/test_results/blargg.json +++ b/tests/test_results/blargg.json @@ -26,14 +26,14 @@ "blargg/dmg_sound/rom_singles/12-wave write while on.gb": "12-wave write while on\n\n00 11 22 33 44 F7 66 77 88 99 AA BB CC DD EE FF \n00 11 22 F7 44 55 66 77 88 99 AA BB CC DD EE FF \n00 F7 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \nF7 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 A", "blargg/instr_timing/instr_timing.gb": "instr_timing\n\n\nPassed\n", "blargg/interrupt_time/interrupt_time.gb": "interrupt time\n\n00 00 FC \n00 08 04 \n00 00 FC \n00 08 04 \n550B72D0 \nFailed\n", - "blargg/mem_timing/individual/01-read_timing.gb": "01-read_timing\n\nF0:2-3 FA:2-4 CB 46:2-3 CB 4E:2-3 CB 56:2-3 CB 5E:2-3 CB 66:2-3 CB 6E:2-3 CB 76:2-3 CB 7E:2-3 \nFailed\n", - "blargg/mem_timing/individual/02-write_timing.gb": "02-write_timing\n\n36:2-3 E0:2-3 EA:2-4 \nFailed\n", - "blargg/mem_timing/individual/03-modify_timing.gb": "03-modify_timing\n\n35:0/0-2/3\n34:0/0-2/3\nCB 06:0/0-3/4\nCB 0E:0/0-3/4\nCB 16:0/0-3/4\nCB 1E:0/0-3/4\nCB 26:0/0-3/4\nCB 2E:0/0-3/4\nCB 36:0/0-3/4\nCB 3E:0/0-3/4\nCB 86:0/0-3/4\nCB 8E:0/0-3/4\nCB 96:0/0-3/4\nCB 9E:0/0-3/4\nCB A6:0/0-3/4\nCB AE:0/0-3/4\nCB B6:0/0-3/4\nCB BE:0/0-3/4\nCB C6:0/0-3/4\nCB CE:0/0-3/4\nCB D6:0/0-3/4\nCB DE:0/0-3/4\nCB E6:0/0-3/4\nCB EE:0/0-3/4\nCB F6:0/0-3/4\nCB FE:0/0-3/4\n\nFailed\n", - "blargg/mem_timing/mem_timing.gb": "mem_timing\n\n01:01 02:01 03:01 \n\nFailed 3 tests.\n", - "blargg/mem_timing-2/mem_timing.gb": "mem_timing\n\n01:01 02:01 03:01 \n\nRun failed tests\nindividually for\nmore details.\n\nFailed\n", - "blargg/mem_timing-2/rom_singles/01-read_timing.gb": "01-read_timing\n\nF0:2-3 FA:2-4 CB 46:2-3 CB 4E:2-3 CB 56:2-3 CB 5E:2-3 CB 66:2-3 CB 6E:2-3 CB 76:2-3 CB 7E:2-3 \nFailed\n", - "blargg/mem_timing-2/rom_singles/02-write_timing.gb": "02-write_timing\n\n36:2-3 E0:2-3 EA:2-4 \nFailed\n", - "blargg/mem_timing-2/rom_singles/03-modify_timing.gb": "03-modify_timing\n\n35:0/0-2/3\n34:0/0-2/3\nCB 06:0/0-3/4\nCB 0E:0/0-3/4\nCB 16:0/0-3/4\nCB 1E:0/0-3/4\nCB 26:0/0-3/4\nCB 2E:0/0-3/4\nCB 36:0/0-3/4\nCB 3E:0/0-3/4\nCB 86:0/0-3/4\nCB 8E:0/0-3/4\nCB 96:0/0-3/4\nCB 9E:0/0-3/4\nCB A6:0/0-3/4\nCB AE:0/0-3/4\nCB B6:0/0-3/4\nC", + "blargg/mem_timing/individual/01-read_timing.gb": "01-read_timing\n\n\nPassed\n", + "blargg/mem_timing/individual/02-write_timing.gb": "02-write_timing\n\n\nPassed\n", + "blargg/mem_timing/individual/03-modify_timing.gb": "03-modify_timing\n\n\nPassed\n", + "blargg/mem_timing/mem_timing.gb": "mem_timing\n\n01:ok 02:ok 03:ok \n\nPassed all tests\n", + "blargg/mem_timing-2/mem_timing.gb": "mem_timing\n\n01:ok 02:ok 03:ok \n\nPassed\n", + "blargg/mem_timing-2/rom_singles/01-read_timing.gb": "01-read_timing\n\n\nPassed\n", + "blargg/mem_timing-2/rom_singles/02-write_timing.gb": "02-write_timing\n\n\nPassed\n", + "blargg/mem_timing-2/rom_singles/03-modify_timing.gb": "03-modify_timing\n\n\nPassed\n", "blargg/oam_bug/oam_bug.gb": "oam_bug\n\n01:03 02:02 03:ok 04:03 05:02 06:ok 07:01 08:02 \n\nRun failed tests\nindividually for\nmore details.\n\nFailed\n", "blargg/oam_bug/rom_singles/1-lcd_sync.gb": "1-lcd_sync\n\n\nTurning LCD on starts too early in scanline\n\nFailed #3\n", "blargg/oam_bug/rom_singles/2-causes.gb": "2-causes\n\n\nLD DE,$FE00 : INC DE\n\nFailed #2\n", From b95c2cb9da350e25eea690ef673b747b5437d4fc Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Wed, 25 Sep 2024 21:44:11 +0200 Subject: [PATCH 15/51] Fix import order in opcodes_gen.py --- pyboy/core/opcodes_gen.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index e5c750ee5..ecce26f78 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -35,11 +35,14 @@ def BRK(cpu): """ cimports = """ -from . cimport cpu cimport cython from libc.stdint cimport uint8_t, uint16_t, uint32_t from pyboy.logging.logging cimport Logger + +from . cimport cpu + + cdef Logger logger cdef uint16_t FLAGC, FLAGH, FLAGN, FLAGZ From 3292f7a27f4f29af7a73db86f7d6c666bf4b6b2f Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 29 Sep 2024 20:27:50 +0200 Subject: [PATCH 16/51] Defer post-tick and reduce time keeping on tick --- pyboy/__main__.py | 2 +- pyboy/pyboy.pxd | 6 +++--- pyboy/pyboy.py | 35 ++++++++++++++++------------------- tests/test_basics.py | 6 ++++++ 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/pyboy/__main__.py b/pyboy/__main__.py index 8c5209fd4..c3feff9de 100644 --- a/pyboy/__main__.py +++ b/pyboy/__main__.py @@ -169,7 +169,7 @@ def main(): pyboy.load_state(f) render = not argv.no_renderer - while pyboy._tick(render): + while pyboy.tick(): pass pyboy.stop() diff --git a/pyboy/pyboy.pxd b/pyboy/pyboy.pxd index 700647b68..f28edd4f3 100644 --- a/pyboy/pyboy.pxd +++ b/pyboy/pyboy.pxd @@ -43,9 +43,8 @@ cdef class PyBoy: cdef readonly str gamerom cdef readonly bint paused - cdef double avg_pre cdef double avg_tick - cdef double avg_post + cdef double avg_emu cdef readonly list events cdef list queued_input @@ -72,7 +71,7 @@ cdef class PyBoy: @cython.locals(t_start=int64_t, t_pre=int64_t, t_tick=int64_t, t_post=int64_t, nsecs=int64_t) cpdef bint _tick(self, bint) noexcept - @cython.locals(running=bint) + @cython.locals(running=bint, factor=double) cpdef bint tick(self, count=*, render=*) noexcept cpdef void stop(self, save=*) noexcept cpdef int save_state(self, object) except -1 @@ -84,6 +83,7 @@ cdef class PyBoy: cpdef void _unpause(self) noexcept cdef void _update_window_title(self) noexcept cdef void _post_tick(self) noexcept + cdef void _post_handle_events(self) noexcept cdef dict _hooks cdef object symbols_file diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index 5824fcdc3..4c806a61d 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -160,9 +160,8 @@ def __init__( raise KeyError(f"Unknown keyword argument: {k}") # Performance measures - self.avg_pre = 0 self.avg_tick = 0 - self.avg_post = 0 + self.avg_emu = 0 # Absolute frame count of the emulation self.frame_count = 0 @@ -378,9 +377,7 @@ def _tick(self, render): if self.stopped: return False - t_start = time.perf_counter_ns() self._handle_events(self.events) - t_pre = time.perf_counter_ns() if not self.paused: self.gameshark.tick() self.__rendering(render) @@ -407,18 +404,7 @@ def _tick(self, render): self.mb.breakpoint_singlestep = self.mb.breakpoint_singlestep_latch self.frame_count += 1 - t_tick = time.perf_counter_ns() - self._post_tick() - t_post = time.perf_counter_ns() - - nsecs = t_pre - t_start - self.avg_pre = 0.9 * self.avg_pre + (0.1*nsecs/1_000_000_000) - - nsecs = t_tick - t_pre - self.avg_tick = 0.9 * self.avg_tick + (0.1*nsecs/1_000_000_000) - - nsecs = t_post - t_tick - self.avg_post = 0.9 * self.avg_post + (0.1*nsecs/1_000_000_000) + self._post_handle_events() return not self.quitting @@ -467,11 +453,22 @@ def tick(self, count=1, render=True): False if emulation has ended otherwise True """ + _count = count running = False + t_start = time.perf_counter_ns() while count != 0: _render = render and count == 1 # Only render on last tick to improve performance running = self._tick(_render) count -= 1 + t_tick = time.perf_counter_ns() + self._post_tick() + t_post = time.perf_counter_ns() + + if _count > 0: + nsecs = t_tick - t_start + self.avg_tick = 0.9 * (self.avg_tick / _count) + (0.1*nsecs/1_000_000_000) + nsecs = t_post - t_start + self.avg_emu = 0.9 * (self.avg_emu / _count) + (0.1*nsecs/1_000_000_000) return running def _handle_events(self, events): @@ -538,6 +535,7 @@ def _post_tick(self): self._plugin_manager.post_tick() self._plugin_manager.frame_limiter(self.target_emulationspeed) + def _post_handle_events(self): # Prepare an empty list, as the API might be used to send in events between ticks self.events = [] while self.queued_input and self.frame_count == self.queued_input[0][0]: @@ -545,9 +543,8 @@ def _post_tick(self): self.events.append(WindowEvent(_event)) def _update_window_title(self): - avg_emu = self.avg_pre + self.avg_tick + self.avg_post - self.window_title = f"CPU/frame: {(self.avg_pre + self.avg_tick) / SPF * 100:0.2f}%" - self.window_title += f' Emulation: x{(round(SPF / avg_emu) if avg_emu > 0 else "INF")}' + self.window_title = f"CPU/frame: {(self.avg_tick) / SPF * 100:0.2f}%" + self.window_title += f' Emulation: x{(round(SPF / self.avg_emu) if self.avg_emu > 0 else "INF")}' if self.paused: self.window_title += "[PAUSED]" self.window_title += self._plugin_manager.window_title() diff --git a/tests/test_basics.py b/tests/test_basics.py index 612214cec..88b1c862a 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -47,6 +47,12 @@ def test_log_level_critical(default_rom, capsys): assert captured.out == "" +def test_tick_zero(default_rom): + pyboy = PyBoy(default_rom, window="null") + # Not permitted, but shouldn't crash the emulator either + pyboy.tick(0) + + def test_register_file(default_rom): pyboy = PyBoy(default_rom, window="null") pyboy.set_emulation_speed(0) From 0724cb01203b1046869dcd1e7eb240b9d27f3d87 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sat, 14 Sep 2024 20:45:26 +0200 Subject: [PATCH 17/51] Update whichboot pytest --- tests/test_results/cgb_whichboot.gb.png | Bin 2361 -> 2472 bytes tests/test_results/dmg_whichboot.gb.png | Bin 2337 -> 2471 bytes tests/test_whichboot.py | 7 +++---- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_results/cgb_whichboot.gb.png b/tests/test_results/cgb_whichboot.gb.png index b25f3c39dc979d00125d78e1b2635ac7f77efe42..57c8411db1b9eea5915860959b4f53fff6d0921a 100644 GIT binary patch delta 2462 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 delta 2350 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 diff --git a/tests/test_whichboot.py b/tests/test_whichboot.py index deee0e46b..31fa35040 100644 --- a/tests/test_whichboot.py +++ b/tests/test_whichboot.py @@ -15,11 +15,10 @@ @pytest.mark.parametrize("cgb", [False, True]) -def test_which(cgb, whichboot_file): - pyboy = PyBoy(whichboot_file, window="null", cgb=cgb) +def test_which(cgb, whichboot_file, boot_rom, boot_cgb_rom): + pyboy = PyBoy(whichboot_file, window="null", cgb=cgb, bootrom=boot_cgb_rom if cgb else boot_rom) pyboy.set_emulation_speed(0) - pyboy.tick(59, True) - pyboy.tick(25, True) + pyboy.tick(1000, True) png_path = Path(f"tests/test_results/{'cgb' if cgb else 'dmg'}_{os.path.basename(whichboot_file)}.png") image = pyboy.screen.image From 5e8cf7ede858375fc36e67e7e074828743777c02 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 26 Sep 2024 16:25:12 +0200 Subject: [PATCH 18/51] Saving SameSuite and Blargg results for sound, although they are still wrong --- .../apu/channel_1/channel_1_align.gb.png | Bin 804 -> 894 bytes .../apu/channel_1/channel_1_align_cpu.gb.png | Bin 777 -> 874 bytes .../apu/channel_1/channel_1_delay.gb.png | Bin 784 -> 896 bytes .../apu/channel_1/channel_1_duty.gb.png | Bin 1438 -> 1571 bytes .../apu/channel_1/channel_1_duty_delay.gb.png | Bin 1382 -> 1430 bytes ...annel_1_extra_length_clocking-cgb0B.gb.png | Bin 751 -> 853 bytes .../channel_1/channel_1_freq_change.gb.png | Bin 1590 -> 1666 bytes .../channel_1_freq_change_timing-A.gb.png | Bin 601 -> 691 bytes ...channel_1_freq_change_timing-cgb0BC.gb.png | Bin 601 -> 691 bytes .../channel_1_freq_change_timing-cgbDE.gb.png | Bin 596 -> 681 bytes .../channel_1/channel_1_nrx2_glitch.gb.png | Bin 532 -> 620 bytes .../channel_1_nrx2_speed_change.gb.png | Bin 968 -> 1016 bytes .../apu/channel_1/channel_1_restart.gb.png | Bin 1550 -> 1564 bytes .../channel_1_restart_nrx2_glitch.gb.png | Bin 552 -> 660 bytes .../apu/channel_1/channel_1_stop_div.gb.png | Bin 1118 -> 1248 bytes .../channel_1/channel_1_stop_restart.gb.png | Bin 1485 -> 1514 bytes .../apu/channel_1/channel_1_sweep.gb.png | Bin 1472 -> 1585 bytes .../channel_1/channel_1_sweep_restart.gb.png | Bin 2078 -> 2288 bytes .../channel_1_sweep_restart_2.gb.png | Bin 1328 -> 1484 bytes .../apu/channel_1/channel_1_volume.gb.png | Bin 1376 -> 1545 bytes .../apu/channel_1/channel_1_volume_div.gb.png | Bin 968 -> 1043 bytes .../apu/channel_2/channel_2_align.gb.png | Bin 804 -> 894 bytes .../apu/channel_2/channel_2_align_cpu.gb.png | Bin 777 -> 874 bytes .../apu/channel_2/channel_2_delay.gb.png | Bin 784 -> 896 bytes .../apu/channel_2/channel_2_duty.gb.png | Bin 1438 -> 1571 bytes .../apu/channel_2/channel_2_duty_delay.gb.png | Bin 1382 -> 1430 bytes ...annel_2_extra_length_clocking-cgb0B.gb.png | Bin 792 -> 909 bytes .../channel_2/channel_2_freq_change.gb.png | Bin 1590 -> 1666 bytes .../channel_2/channel_2_nrx2_glitch.gb.png | Bin 532 -> 620 bytes .../channel_2_nrx2_speed_change.gb.png | Bin 968 -> 1016 bytes .../apu/channel_2/channel_2_restart.gb.png | Bin 1550 -> 1564 bytes .../channel_2_restart_nrx2_glitch.gb.png | Bin 552 -> 660 bytes .../apu/channel_2/channel_2_stop_div.gb.png | Bin 1128 -> 1252 bytes .../channel_2/channel_2_stop_restart.gb.png | Bin 1485 -> 1514 bytes .../apu/channel_2/channel_2_volume.gb.png | Bin 517 -> 614 bytes .../apu/channel_2/channel_2_volume_div.gb.png | Bin 817 -> 879 bytes .../apu/channel_3/channel_3_and_glitch.gb.png | Bin 827 -> 926 bytes .../apu/channel_3/channel_3_delay.gb.png | Bin 568 -> 671 bytes ...hannel_3_extra_length_clocking-cgb0.gb.png | Bin 897 -> 1015 bytes ...hannel_3_extra_length_clocking-cgbB.gb.png | Bin 872 -> 970 bytes .../channel_3/channel_3_first_sample.gb.png | Bin 510 -> 600 bytes .../channel_3_freq_change_delay.gb.png | Bin 576 -> 705 bytes .../channel_3/channel_3_restart_delay.gb.png | Bin 482 -> 565 bytes .../channel_3_restart_during_delay.gb.png | Bin 1092 -> 1261 bytes .../channel_3_restart_stop_delay.gb.png | Bin 513 -> 600 bytes .../channel_3/channel_3_shift_delay.gb.png | Bin 482 -> 565 bytes .../channel_3_shift_skip_delay.gb.png | Bin 510 -> 600 bytes .../apu/channel_3/channel_3_stop_delay.gb.png | Bin 479 -> 546 bytes .../apu/channel_3/channel_3_stop_div.gb.png | Bin 1254 -> 1338 bytes .../channel_3_wave_ram_dac_on_rw.gb.png | Bin 1480 -> 1731 bytes .../channel_3_wave_ram_locked_write.gb.png | Bin 2749 -> 3248 bytes .../channel_3/channel_3_wave_ram_sync.gb.png | Bin 810 -> 982 bytes .../apu/channel_4/channel_4_align.gb.png | Bin 886 -> 953 bytes .../apu/channel_4/channel_4_delay.gb.png | Bin 728 -> 856 bytes .../channel_4_equivalent_frequencies.gb.png | Bin 1443 -> 1558 bytes ...annel_4_extra_length_clocking-cgb0B.gb.png | Bin 777 -> 855 bytes .../channel_4/channel_4_freq_change.gb.png | Bin 950 -> 1014 bytes .../channel_4_frequency_alignment.gb.png | Bin 1573 -> 1577 bytes .../apu/channel_4/channel_4_lfsr.gb.png | Bin 1365 -> 1451 bytes .../apu/channel_4/channel_4_lfsr15.gb.png | Bin 1398 -> 1513 bytes .../apu/channel_4/channel_4_lfsr_15_7.gb.png | Bin 1721 -> 1833 bytes .../apu/channel_4/channel_4_lfsr_7_15.gb.png | Bin 1721 -> 1809 bytes .../channel_4/channel_4_lfsr_restart.gb.png | Bin 1365 -> 1453 bytes .../channel_4_lfsr_restart_fast.gb.png | Bin 1365 -> 1453 bytes .../apu/channel_4/channel_4_volume_div.gb.png | Bin 895 -> 974 bytes .../apu/div_trigger_volume_10.gb.png | Bin 653 -> 714 bytes .../SameSuite/apu/div_write_trigger.gb.png | Bin 1695 -> 1908 bytes .../SameSuite/apu/div_write_trigger_10.gb.png | Bin 1634 -> 1857 bytes .../apu/div_write_trigger_volume.gb.png | Bin 653 -> 714 bytes .../apu/div_write_trigger_volume_10.gb.png | Bin 653 -> 714 bytes tests/test_results/blargg.json | 6 +++--- 71 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_align.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_align.gb.png index e6601e48a50ced9b870491c13de4b93c204d530b..e1323bb000d3957bb2f8352948ce2706fa974e09 100644 GIT binary patch literal 894 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V`_hk3UQB-#RZ z-!gr6@6g#Q`&|njJSv-VAbNVS%95jJ!*gFm8m%c=y7k(%(%hv}y07nAm;3p}`u=mb z&q=PGJ->e6wrf#qw@wN2%3eDq=+x$Z^R3sqx4x~|a?jZQ{-62$_m|f2Fv~sF{VV-o zE9Zn2s~N9Fo&WFG>|Uk4bV_Ngx90SRx!LD`{Q9@&?VPCBKj&QB_4DWR?9ZLe3$_P& zS=s%HIve=kwpEYCf&WtG9`IulnpQ zv(VQmxikL%6*%;IXBGFqf=SUI`sdF7E0!ZaLF0GB{*1cFX_um`_iyu`zxeBXgM9gd zqRsbL^4(;AbN=N`_L@6?{++rMFL}N6Z^__1{EhK;nPsbVr5wl#7uikxH`s5>-)1+lpS7LeV@mCT z4}a=vPd)$nZq9aoiFxnuZ_hp+KiB;89+1Spy|>T3?RS5@SMvG%P5dt^9o+bKGWDJN zeeO?A_=f7|v5f0)#IJlX@eT981wSR{Ki^$`&o1G{>ie00VhSqi>QiTYtlU@q{BO*L zpD+F>+Ka!@J6A6I&*IvPS8Ug1q!#?xaW(OG!=uO6e7D)}{J&+l;OGCj2p1g#x@g{Z z{uy)c-|K$+cSjj8{LWhB%THLb{0(!J+yue%%x%x(VF72KQ3p~F)Gkr>dhWCBJAY}; zcwYH+|Nno*N{d)LrZ990DFia9cs00iPS9W!Qgs;qf-CmlDPOac;gEhRFkdlvy85}S Ib4q9e00~s3KmY&$ literal 804 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V1*PZ!6KiaBp*KP+0UAi}n= zI6m;cZ~V01hc%s5R~~&NQdU+!@v!*q6y=X1(m`Igx3xCq-VXATef{-~VQBc7^7F^1 z1oarUObFsqv@x{nFnrd2e8;*Lrq8jT{vQhWbDwzg=a1L%4)a_O7MyEJ?m2#axr0ZX z>*CMPvyB@+3Oe2RZY%cYciowP%ez5N6k4o0b z_ixy&(|-C<{?u*u!!K{D$;N*=erSUHsryB7i}s({>9YN~d-S5W%`a6z>SH3OsoIxE zPMbb;Qv0WG2j@!W{@s0kHn-Og)&&a2(<`yg}CbDnkS^)uuj_T-$s zeC|QbKhBmEd7G>M^0M#Q{b;&*-S~a?z7zf17HxU9S6^!0{^Iy8tADxt$=bca&NtQM z9rv|&B6r?2Z?)z&l0^Rsw^4FXc5uW4q jcFl_uEV$Z62Ju<`fB&Bci?6#jfKtDwtDnm{r-UW|#J7N6 diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_align_cpu.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_align_cpu.gb.png index 82a17ed6dc8cfa3b270df2f0ce3a450d2fc5593c..10551d8219cac0d8caaae3bd96a06831abd311ed 100644 GIT binary patch literal 874 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V`_M8De(BCQwG zub5WlpG#YAzd||vgoC4>9`kdLiLVawZ@s8KyT@ns)@$2xvqMWi-`ckJt=!+Q7b|~T zE-wB2`Mmzxt=n|GLqoS-Tea%d&EvaMBlE6(dwj9#{GXc7_4jI~S4p$kobvvqf8f2E z!=iU=J9JKeeQr{*WcsRAxwlunGTT%8Rx;jx-uC&Mff92r?)v%j`SfSP?2h`Oq0>)4 zU3MWg@>S8!82_p-*WI7Lecp5J?Dez1cC9nNpC5a@{QB?klzVm$?tK1T9T|GP_&3m2 zeZzeD2`iT89RBmZU7&9M?(GlE)pxSi-~M?s|KK;q&Q1J3nx9=SU3=};*RA!xWY*TV zSN?98Jo~R-=_$K}dcX5_4<0}351;n$)Q;TSyI=p+HjMwjuHo(1`=4HIKK_@veQ*D> z{@368++RO?KHvUp@P~E(?d(`*%b%UUiT}p8)0#Dto`bCLxT5*Hp?1UHjkOM+7X!_A zxye4K=HCvx$DbR%F{;mhfB$>+arwE^=WOS<`1$AK-uvg@_Pf8{d*n~>H%43j6DKso z4_vH_pE>_6*OYnxCx#z*Je5oCq}>DlS>J6G{LO#QJ@uZ|->!borZYcm7To7Hlb>t< z^n26Yqd!+&mOm~Pd-uV-U}h2d`K7n)7X0|pb@>}(fBie=ZTuN^Ccu!<0Q&a4{Ppx2 zxeIm=o`Ay5^!Z^9V8B$JvRm++QSze=P;~SCrnk&Osq!D#&sNJ#SpNIX+*iAI{??pP zd8Y2~hxx*Sst$`-Jf<*o3Mm9KsdzQGa8A%*6dL}5e_0RxpS?EA+ItBw4>5SU`njxg HN@xNAGYple literal 777 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V0qPZ!6KiaBp*2Npe65MWi@ z8b0NnT5Q?3=BO?X_0<^{*-Cfq|L{R)`5mz|E_K%<+j3VX-rm+B5%E6#bV$6Rz0G`O zrRQ^kx;my@l6~xSZl|5j<9(B)&hVf7@Acj?#&zDg+vWcmi}bnLibduW>&4G!QoQ53 z`1A3-GY?dFYrg$GPx@P}e(~a0e{*KO(r&0(yXJM&{N3wbpY}Q(`*Q=o+^qBUWd>h+ z*FN5NDenKWJ+%gNCgukHoTSg~T&r>abYW+m`E#MaU;pHIZ>cqy3sS#+YHH-G;_0cC zk(oO+^W|-3+`P75ySBjkYTRi%i=`%ayMo?7C~N&$b0=;^?aBU6TZ<&Vi+(=*`Fi=q z(1-D#OSjpbIe$~s-nM=z|GN2tGv&`WKfJ+yxpV#Zt@`?(JonA74T%5!1?UW!-wzJ{ zjGFnx=7wEG!M@sbzQ_G(`Dbsl^PhWu(tq-~f*&iwZ20F*&@L~quQswjEL&Dy_sx38 zy?Q3aH_f*E(>|Zs_V4HJ7YBdd44d*;?R8(F;`xo7zcPPn@9!=Ptrz%T5_9AJG9&5h ztl8fMW8O4x{Vj3GPGatM{CubcHot7C y-SGTi(LCeV_y4;bEco->*n+EVal!~8_A@RM`dVYMbGa@kiF>;GxvXEaktG3V{=i+PJ2B-$R% zOv@L1$D3dFZNb8Wni--`qPL%)TybE^{TZLcu6IU9W#`UX7Us3H%IETxwDtdfUaS0U z8U1?g*41AVCDzT$muQ>)`tRXCk3V~#kJ*1KDtqg-AiMc%rv#n4d3#MhF_H5l( zv+n25Ykk|VRX>xTe}9+W>BrI6-=E(e9=NZaas8)>b&P-Vm{hV%4@A9wwO{Z-hWyeg zM!vzCPp$S;zU`T>e}4A)m0RoPZr*xN`uN5z*Y?RhwzwB#UjOEg@%8oF%>N&G=r?z( z`TYJMubnY^;jP!APCqUBY4!4|{e7!^yY%e6<~)b5#=kP)F~5FR-unNUubcUIB;2!8 z*ndXzwt@X$fkW>%{}TK2Rwe)7v-mG!Ir0-Uem6YhpS_*`#LI8bici=j)cd`k`n%!u z{xkp1S@>Q)`}N=S>G#~LkN@ z`< zt@oAB`}zF&@7G((YToa${rs`k;djB!pHH`Xy*WH@6aS0f4LO!i_;!@u^M7`iar5W@ zPh<;TA7+txJO37Y&GFhb|1<42@=M=mFIj$;EB@ct|Dxr~zUVjT#JeeYHMnq2&|nl& sby&pWF@>R1NFk6(WmFImAiucJ8Lwv!J1Kbtn6nr>UHx3vIVCg!0H2VRHUIzs literal 784 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V1_PZ!6KiaBp@2Ieg`U|*+C>1F?UD=R3M(+LGzbk%jef{(OgYz5@Ucb{{u#Jaj zJD-dbhvJ=2p;uhKX#NWPoPFK+%eRoHe;-HWu3w)hF)fvIW@=>I^L0}C=lA(dRo>~h zZbsho{k!LV{LZ%eD8%T(^yL!kZ@b&o*1fIT#oxBOX!e(1{&u$Q>*wt(=|6jW<@OhO z&#$jr{ySmU3%eg68!p#=IDF-s)n@*4{!izZp0vB6{jZ`fYGdZCOV!IWZ^>{f&bi6{ zoLkno`t#)zuXe~-8ruI?Tm5+N=J}@e#>cETo-TiT*8D^7=ksrxPoH@;Z9n_ro&3wX zzy7{;mfvzl{5;zVt#xzfyIED9dYfBU{`*1Hz5IWVkBH8he{1)%OMkv>np}Tz@7en? zd$r#etUP%BwRzv_ZwG&#x%%Q*WY_p@jmD3pJ%@x+!Ncf{pVWd_?`UUzCVzg zyQAs8WMu7+XCIZz{8WCV8MJ?QsUf!0=70|gE@^4N1o7cwnEXZeZfNaWk4z2)78&qol`;+0H5k} A8~^|S diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_duty.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_duty.gb.png index 66d4896a20611fbdc6cf48724e78ba0128fd356c..254772b1a2ec0a27c1dcd43ad1475c483a1de313 100644 GIT binary patch literal 1571 zcma)6eKga17~fh9Wu}}$Yj-I{r??&0?atfQQWSAh*J~Jxyk%{LVYalq7gu??xvWk} zEJN-%g_*iuimb3!i6N}8mX~d2X7@*(?m73b`^WeDobU6T@AvsU&-e3rGJbV)($z86 z0f9ieN1Po!0DB8qxmudQp2Nn|Kp>6DBaR17#{VfUIi6Q(V{qu8m%D!Xa1r6*BTRc@ zw)UEgb8cZ(yO}m4dl+q5m!{Vl24|o5DKNyvC+zLou#qtoz;v-@Cnu?w4MMW}e!2q3 zrSD6H;}&9V_Z{my5f~v@Bz;jSS}Y|S0-&1Q@`dA6kl_|hl(RXE8-5ch3I{R9cG2Ml zXSBE3={5DsuZrs}_^y5P_ba3yl=AYlCr!3ru#)&>+UZB$w zoA+s(6G4(VG&Ng8n@(BeJSAt()FxC(N~Xz8M~BHBjG1}!@(Uz`J( zK89g6f0!=wgLMvH-hQDAEEXz2JE4D9D*(J#5b(gs>yS|w3K5$-n~%ty0^imYd^_V{ z>6X%G-~*X<(7z~|B1k1As{>KZFBH*aR2c%=s<)E!E1%jp zVtITZiRlhAlg&{)0glW~Ux#E;8igLwLoBH%e!e9SLbONqqo4-LZkoolD;qk6m}fB4!$vj?0_F^u)5r#yy>SRFu=t`Zht_5X@U1#pDpZ&K#?!D*U-~Ij0_kMoA-_1OYJ!zbIC%&^SR@0J#sdjSgWw1Qcana ztbu)H{mmk)^6;AiC&F0YkP_GKX}QytTus0)CEglYnphZZQY zaXc>Q4iw}F!TY`g`bMyN*();v_sPjQ%I&dRG_@Q*sfI}SJD@yVxW26%nA|KxnJsqe z9|`Cr$HVKUvQP{fsD)#>lio<6)#t7i^?_UAxvbT*bC+W zo{Q3t9;Kw^O***9((@+cj3w!Q^3~2h04%nQ212~-20{cru47W&!t=~WfgJKk(`Zc zsjQ$-DLg~%112R$d1Yu{9)56TSc)PkLhn*jD-QCZkCwBbvkx(YTYuZ+Y~ZFf=}K9j zc$6BU=uNIiXKv%LF=ma}>8bQuJKb%ZEab2fp}AM0V%jU5PsIKOoSf?7(l;0-*l3!_KG`(I$~S*cyh;> zm{;j{FnHa@-@(VTKto5cCuByJ`-P?av9C0=&X#}G`oPvul+&S2uFpl8j+cByFLw?e z|LeQ&GYf6|H0+s;U%y2>oBj-hfXnGEoSQ4a(sB%aTQW%q+uAV_%on~dL7jhV;{Hsn zz$mJyGEp)=x-V%9Co1hB)6wOkSLy`4mmbnDCf$JM3sL7?ca88!+7;w1w6#do-9*E~ z^R=SG!@72p*B};Us?^shzZfr3*}FAe!-vHtn=&>ekY&_42PM;9cz1CAPj4UFgaUtf z2-_*Rbj{V;kInl5F_^WPWD#>=DHp0vYwVR%H%3qAv3bq-L2hv|#X-=%={EdgN!4p9)|?`uyM+i-9G9zT-+c;dz*zamgA{(8n-eH zDQxWljy||;w{;Z71GO-nmKW|+f(z7*uxdb7;ud{ubLg$$VogZ%O_@%_vClI0iyF77 zZj#zqZCK0rc-;?<{8wtE`vmtYyPLZ@>pZRg&J6SrhrgZR=HmW$!+AP#B5pl<++|7g N?|Ngf9vn0-?H?kX+sXg{ diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_duty_delay.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_duty_delay.gb.png index b9f7a35f2489f3a61768f44225439c56ebce7bd5..46c209328d3ee040edad58b1c95c88cc5bdce8a4 100644 GIT binary patch literal 1430 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 O{Pe>5xwGByw7&oiZQD2i diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_extra_length_clocking-cgb0B.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_extra_length_clocking-cgb0B.gb.png index 4770cfb19f4469d292e5182f51a976d9431dd34e..9b6c97eddfd90881efced1aee59302e23ec9e3be 100644 GIT binary patch literal 853 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V{AhklP7B-$<> ze53nn@6_3I|8A5t?hB5Idcj@fm&7x-PV}Vn#brycMNLm#xpazt{OL_sp51$Wc89;_ z)7yr6H;wFXmv8&`@87;(#%JEocyHe4>~8yR`}r@7@tacXY8cb?bE-dHyLL8zzWvpx z*Dp)7p57Ncb!JE1>*IgV&$MmdR-0RUzNT95?awd$$KPhZezyJm|47@Y6A~`}|91SW zyI;1(TYuNj+Ryyr4@@`SN&YuW{Pv%JKeumfep9RT!Tz3?W@_ZLpi|dB+}fsmt^U&a z+{)rNbJuUJ`)RTNn@)CJ?Z1%kb!oPi>A%)&5Aw2{e|~;?+qY7?`S#_zzE>H)yL|j& zU*XAVKfm?GRLgJMT6XN+clrHi;_c&qWgDNp*#G;j@%KG^8FtqHKV4@$oBn+Hf7biA zO#io>v9r_vd+FKy>VKN&8m`s9-}Sn|WhYRb6-eF5T_;Nq6wbe>9M3-It#$POA3q}g zN&eyXQA!{rf+@|GD#yJ^o{I|GjxNU+(W)zvlh?w{v@~+ut`! zH*ffP;cr8>VxA$}#d`asefPKjKYa5~?X$c57B_yY8NIJ_n0LZ=9dqBozW(`ttrFJu zSKHs+e189*QU{<<+kZ=c$k6}yuiN@bo2UJWjs6EqlwR7VBz c2gqO6?JqlHH`)bo0J95&r>mdKI;Vst05Yz5$N&HU literal 751 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 i2@VbEaktG3V{A*sR9}5)OeY z&xpO#VPxv5dcNGwc7Bzpr=o)!vVnf4$3Y?rr;HcWNL15Ge2Juar#gIsLS#wDr8@@ux+V zv0tvPw~s1cQ)%+Q=WqV=?cv{ct@D4r{iE*bqSB@Mr$3d{z3%z#U(feKX8qmwG83+B zmpT0B{d9qI&)@0(xU01NfqXUh@3+6_GEXYv`*AGa#vRB3sn)N3{cigMW6Pgkr++@r kv4q9Lt6`KND-8ZI{YpLd@4DbzP_kt3boFyt=akR{04oJE4*&oF literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|S$Sr;B4q#hkY{FJ`@R5OD|$ zey6YYf7+j%cg+GvA{M%?zPawJ$sEapCCMHyGGnh-&hr<4RvCBwYvbp??<%wHe>Q)< zog#Dm?dHc8_vXY{z54lfmj3$x8(-%ZZN2CJ@4=sM?6=nMzis>FXMMSy+kxw=_cNSV zEx1$qAW@<%vEpR-L(MS%i^r?9{yqHp@bbJ>c5nZEDYcqAEmcxa=XBA%>MJ$-tMA?N zuPdyZ?Q?lcZP~14vnQY4mH&ym`q$65;x+dwj#bT$S^Ir*{G9uG75o3?eP4X8n!m65 zN%p0m3)6NB$JrKtlrqm>o_BHP=X=esuQl&y&!1dgnN_>_b9+61+5VmHKQ!(EaktG3V{A*sR9}5)OeY z&xpO#VPxv5dcNGwc7Bzpr=o)!vVnf4$3Y?rr;HcWNL15Ge2Juar#gIsLS#wDr8@@ux+V zv0tvPw~s1cQ)%+Q=WqV=?cv{ct@D4r{iE*bqSB@Mr$3d{z3%z#U(feKX8qmwG83+B zmpT0B{d9qI&)@0(xU01NfqXUh@3+6_GEXYv`*AGa#vRB3sn)N3{cigMW6Pgkr++@r kv4q9Lt6`KND-8ZI{YpLd@4DbzP_kt3boFyt=akR{04oJE4*&oF literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|S$Sr;B4q#hkY{FJ`@R5OD|$ zey6YYf7+j%cg+GvA{M%?zPawJ$sEapCCMHyGGnh-&hr<4RvCBwYvbp??<%wHe>Q)< zog#Dm?dHc8_vXY{z54lfmj3$x8(-%ZZN2CJ@4=sM?6=nMzis>FXMMSy+kxw=_cNSV zEx1$qAW@<%vEpR-L(MS%i^r?9{yqHp@bbJ>c5nZEDYcqAEmcxa=XBA%>MJ$-tMA?N zuPdyZ?Q?lcZP~14vnQY4mH&ym`q$65;x+dwj#bT$S^Ir*{G9uG75o3?eP4X8n!m65 zN%p0m3)6NB$JrKtlrqm>o_BHP=X=esuQl&y&!1dgnN_>_b9+61+5VmHKQ!(EaktG3V{Ai++zBBpNPC zZeAa8Z|$C(?@8O;CP;1Wm@j?Kb4S9A`5iwuygafo_x84Rug#9_s95uQ*IT*2zb@{p zJ%90+|BU+G`*cnp{jGcY)5Bk1A4@)ezAg9moZ=qCWApDnm$?78#{Bu7h}-k!*T!o< z`+j~?{k-V&{nyspKK`@5{Jqh=cE-XQ?{(iIHFzZv)UJmL63U%?lC-kN>SWbLE1dG}a9SMJ;Y{o{F#B`h9Z g4Wk5EVNl0h@OgLHX=bHqz$D1v>FVdQ&MBb@0OQ#+EdT%j literal 596 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|S$^r;B4q#hkY{1G`omFgOIt z-V=+RlXvaAqQ^>OX4c&0+n?98siTn!JkFI~2Bj8HS3j3^P6EaktG3V_q@4j0OA`Tay zhi%LFmSz?$>G-SJgRN2fh~*?zjo(|V3Jw-^MrN;lY;o;Ojb-2C2=RWK|CPD-zkj{C z?|u38{qoP^^XsCs*XG_%OG{g8YHWPIroOWF{oniRjQ{WYyua?x)Stp!Hn-gW@_)F; z;_)i2;r6z>dyXIe|IN_2SmyYd8s7JtzV&<-Uw%3BTGaK&C02K1&Hw-Ue7kGDMa|`X z+xutE{QR@#Z`m%H=bwL8U0i%V{>|@yPbY7B|NCy~=k@ma@)v5X^5rL73Er0Y@2g_V z_u|iDe;zAsPx#C~YdgQkDZ2$fOYYezteowC@cfg`-+O-=GHEhS5>gl?$O(g$_nrLB VRC@()H33r*gQu&X%Q~loCIAr}*SP=y literal 532 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Vo2PZ!6KiaBp@9?WWX5OKH| z@vFOTPP}z&OP7kTSgK2rWxDCraHE2g3!a!AyRq$U*@oQP63N%T{ykqBdwhLq?EgJQ zDRt-m+C z_mAWDeyc}zwfCwE?tT!NJMX)kOnFtpx~Iq6EE*rP^a)$|Js4$R1j28|=+$x^S?l8d PfnwX!)z4*}Q$iB}(WdQ2 diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_nrx2_speed_change.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_nrx2_speed_change.gb.png index 955eced02c658132647fd047b512a08ed4abfd90..62cddba8d9aff7fbe6e0eae9410a2433348a9015 100644 GIT binary patch literal 1016 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-A8q*mRQa#?gF7M>f-f2N{-RA4|KK_3B^RePx>vqrIyv^?V_gcxehyN~r|NCrLjotmNc5NGjx4&IH zB`9zE?ZbSROJjH5&HFz2-}m>|&q<$;jMiUjvuE=&>&IWK?#9gD|6OnT>BFyM^`@Ww zYWef}=eq_x&ZmnHEUqj*l59$YroX*c(Z^SOs_Wu7G*7F}K9TuHo>|ABU zK1aN)?#~;U4M#1f{n=Pk!kF${6E+EGWay)PtImO<^DN;Ci5>Fh3r*t z8nU0;PTtNu&pMyaV~XX18WSKL@@MCRm)6PWi}!xYeeVB1Wxd6La=Iko3!p|~B|FM}3cJGf@ey8sX&1d#A{TZ|R&%!-W_gqP0 z{2sOIV}ow!^M>>D-V5o(m-6jM&*ND7XVZJm%)&m^dL4x8HRR9EW)=$lyYY;&x#FCD zs1b0lZGF$Fnfmkch4oLO=Omw$KU4p<;rsji8^ty}x9^K?nmRun7S>yLKlr(B`X)zQ<+R(3Ps`soR6nn}+_O7+USKMph0nW!W!dMSx$BWC;Tj5oTpm-HgqAkAXbwf@75jwsnSM`gckBS>Xa-ML KKbLh*2~7ZRrt1;_ literal 968 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Rrvr;B4q#hkZu11H^95MVtT zwr<5?&*`f2A!;HDkx7A-6|eqD9gbeRIX6PBXpTT~jDv)*OmfYNZ4(!*bN>_`9V>r! zlKsj5mOqUI9z4J8&oGbm;G-!`&K!y@>up3US|lEC%l&y`Tkgv}##wvj`38UWsAGz_ zesy12?Dc0Jd_U9dDy%~{*7rP3kDOL}-}%_S_nCQDDr{#jdo@utdrrj1Pu0gI zXG?3os#>C)U;kj2-K^Tsd3F(VCguj+a(XKhH)rPqYm@E4+cIX_)NZi47%5+4p06C| zxAxqpcfTI&-f+KWLB8v)D;H;N-eX(xsjDvbp3IU??NU9XG?UMjn^yFvo$Nm&o@B?R zy-&Dl?(^zj52B2BwEjN$=mPs{;rH_w%@(&jb=z5g`Kjqo<=;2ouVD_Pkm=RYeRn;{k%b;|!rr@+7WVk_6VPviOI{K@ux_r6Jt>VIC9 zCGPb(DE+r-qc!W?T7$CXigtI{tF1%#>DvGOz33sctoDyN#$G-aV#-^9e z>8CLCYO=;3uiJU){jOyfD74$zQ z@jT`Fk$e7USB4wZZa54w_z!bI;SYbyBaRy^IK diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_restart.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_restart.gb.png index 8999fc17c481cc818569b296fc7d2a26b067109e..0c220dc537142609fd106d63d58d20cd42a1a733 100644 GIT binary patch delta 1558 zcmYL~e>~H99LM*)m95gYN8K^qFxM$cp&zGjX?D^Hp_Dt7Wu?d|i$%%oyHio=80zN7 znttI#wseVpm|ulhqg;^W z&go@`Yb3t`r%hbMLWDLEfYABJP#+zPCpBS+(9g?V(Msne90_&!J|!|x?G>MR;G4dUd9R#N*B@JG9>r>`DZ{Kh z#;&q1UB5=20l$Sa$QnakTsEu+z=e7>40Qd_5NrEHXf0nUqF24xxQ<~u4BlcKpDwq2 z^UQu^1M2!cr{brS`{7ePShOH{4B_k<(GLjR+6kw_A)jiAw2POJGstp}f_AWQS{}}j z$mD>;Y47o@mM;*sUmzaSj#_JHXXaHZn>MsJkV?@2lqq9z6XIco)YlK9s$k&$u9;zw zxUqni9q+pfJ=5D74!b$`0W0f+L@b3W*tsnwa^{(9nYNG@&KW&Br3!xYxJwtS&Uugk zsfsz<$_5Lbs*f5bdWp+YnHdC!n?8+ds=c?+)+6O)NEIoB7%m*qT-=q^x$fj`xWg|O zyNkvNnP3fS_joKXIAwE? zm_F_|i(#I(E9FPfjYOS~E&``m=oXm9$aAaL|6F z09CTYXkSD$MWAAm)X1&P^OVAWDZ8 z;g%-j43uQ{CO=K-54FIF?4Um?o`{S;y$^LEX0}Z~rLtbIkpnq8xOt6egzi?7iJ)9{NdY9%GAJLyfJA1 z4ovPU@D?fGw||f&kvGYvTcdj%%NhYyPV)m&GEVaMWNzFdeSv?E%kMEqO}bs-8S-jU zrnkU2v=wg3mz@HLlMmi9NU$vLx^lx?vqLGX+Z;vW)M0$RqW0VRNO;z}s@lgJpf_<0{P#1y+Io>2%*;B&Fs7nRHj`gk7G8%14l;{2uOgNNn- z#VHgKBz7-b2@?N~47!smn5Fche8mK>=)Ggf8G(;T%0Ex x`Lm9jAZNe{c!?mm4Gk(4&*!5@KrND5mW%Ez!C;SAeu94hgvU-Fw-Q%s!awqnDeM3M delta 1544 zcmYLJdpy%?9A1fEmmO2eEzXL3I;E1Z%Gk7tpTm0y=5*H!M>I@0CT z(5H=rFny5Q)>78Uny@J?YZrIx{G2+U^ZxPv@&56ApZEDb&vOf&3C}jAErdZwI3a|= zbl>{8?>UZ~+XVgdq03hr{ecF`jjNorXsnHvAyUA_x%Dmk7p`ipyVbF@QZzJHHw5@65jy7M1 zo{oa?k8rdDd$L*w4XDn)$^yG=uMX9lC84PL$05+DRmW(K*w;XZMVng>Wjvy4`~s6F zTc$lAZ%)!5*vs%r=oX|ri$7nAkdhWXv1I{(@w8;5L|b1~eM7Ip%c9HmxvEdY?K{2( z9QKnJ>^Wke@8TJHJcy@q>==#hE1wBVkQRij)^r(z0{uX8e%|cs3mT<#;p2R!2srBU zuH>4Cc8~36AA>s`=ixc7oNnr_Aig{CYx1c$0Wo=NBCGR^@%(_=xA*B9$wkfJC5?pe zaMxtgV|#{;0gxFZ_c=P2CN6te+Y{Vz*~xcplo?`c$*3!s4Beg_Jn~C12{A3nuQ@ zJBZ9JLGJ<8y&U{7aaM_`u8jqyC)R*~*vpp~xUN){bA zNeK5V$+$T}$4vfF0WUBJHbNR#f=lQqT_;k^4Q{b>rr1T~kd>FFb~ z&k?B$@AikB+;VRg=$315c0gAUDe|k zc``=i6lysqAo}ws&^lLC>94R)u&5(94iN>Zu%eHoA4*I1 zb`~8W<`jJkL}Zo+Nkb;?GuO4);Aut)5SUO-Zq7v|Zky+?`sFn_=FN5j+!GxpqCXjW zHU85yg|>Qwdm4MwrQMYe*VQKpU%;tLoRHPJ*c<$Xs%>G@-g3;l_qt;(2->3R7S3Ky&N6WZ(?n7d^EGAr zpaAd+DC6*yKi2Nt1Kt^(+c$h)&or7YGr6Vnyy_5rzz`z2a@HNgjA&DE4$ll>BlbQw z6-ABeSO=I&id(6tPGvOMnkm=1BFziEkmey{q>Wg|mlA}cxwfAQENhi~C*R+EIAW5P z4dpr|R-rseBA;Ff8DB$;c+o;t0Fz>P%Q6ezq!>1DLRaLMYb1U%-eEe7^f^z+}3 z3LMqmt)h*kp~gn6`RhyNy}wPS&IUIWsWqA$#W|mroMVieYU6@V{E+72^+#|8FRKC= zjN*nw)*-Z7ZRjb?t7t(Y?E}ijCEsK9(EO)2tIV_?_h)mN10purB_E->E^|8pg`}R_ zfo6)D{ncE-U1Ua{2)}6YC>J@9Xisr&B<&b}Q}Z&ocDH!yf2y|P7#7g#A76K(V0#DT znQdWc1|v=W)AI_$DLTgFOeuZ12~9hXY@EaktG3V{Ai(ZcdL>w*# z&y4r^ulx6=wSQ^U+PRF&?|NKsYS?*F@L;a9-nppkx4-wLJ$rTId+l7?Prsht+LrtJ z(ze|4>-KWb>hJwYT)UO8{qUJHYqqAPJ)8Ob`TF;B|G%~SY+wHG`ttWi^-hiM)vN0n z;$|~=-sSmFU~#AB*x~=*%#1g`|9vK=y*#e^*w@XEKU!q3{r# z@ce!2URs*p^2@)MU;b$^*YCZ#?XO=qx8;V{Yu?^=H`e_A`S^A9HLKqKt}CxyYx<4x zY<>2>PmERjzCZpM{@VB(W2bGZ^t1SQrwUth`GfQGK5>}+U*Nu>)*%R}^#AsswfEPT zS)JSec>T5XH_V@W@1K4CEc%0S<>R{Cf0B(;7(6*Aj1uI8!Ik`-vBpzA&q#a>Ol=IF Lu6{1-oD!M literal 552 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|VoGPZ!6KiaBp@Ud(DXU~sr- z^{u;ZPW)`!DV{5DN3=iMGv{+k@~I`_&WG}ndr~9w_@|{xCciP=S2yQ*WzDSTKTl6| zfA-$EKDS{1#@}CmIoy8z{@twokNOQ;?xRd2)UC2nrLx-`>>l+1VJvX;%1 zsCk_%+=N4$xNFI^Cecz^w0zaOOGMj6b4z?hV-ZOtmj7cvJkRsP=a=W6AD*96Qxe>< zC$R_w!kwHLpYF(Rhq#!J9Nsum$VMPs`pNOJnH60yPf_|uC=PWgP>MOKsm`(vH&KBz z^d=W{a=F*Y>#gC!@~QLB)^SDkdw>l0Tow*7l6_*nBQrkxQYVv}(RgkFZc8NXRDfqSF8}4QsF-d}pI|zU$s$>(4$P5Y2?$V_(<5S)VBp0UP zXZ;^KOPmZa>$eeo3;c4te8lWODufa4hj(z#bw*2Q*@>Fn@5fcm4Zb0>NsP<3O|ICs z81}<?PAZ~Cz5=vX-VBSr>ozCR4B$*HIM#M zp-k6xS1>?hlKu3QSOmJ#W{Eb>WIV1nZ}3>opf~N|#f=!^m5iUxNyKHQYg#=oyNk#q zozn*0v%vav^uG*X2`S!^(GPP5rs;Xx`X;gL%)mwaLpA}r0n@DLV=+{gf=_L;Nd5H` zab6`&#oNo*b3U#jQd!Z^K9BC*H>X$)o;AYhQcZ(7m^pDEe(b!x^=n)h$yLkt6C~m1 z-E@j2q&^LusUC2yV_8~iqTbv!)J1i}rl36l`o&f?B+oz}*VuHeRM9L|J_PZjx+6tG z=V0FICz|TZc&-N76W``F940zjE%tiwIfQNRDZ7B2Kc!`R&kX4*0FMLGQuaxsqIv*A zS-;qyC0ldeHL%9cZzUELFAwk8vYH`0b*F1#up?U53MMi;=2Ou(X$rfKV6!qo62rGF zw$u7)KC*4lvHh3|Rl=jph&J+Drsb;|c&6oj#m(FQ1g19Y?Yi|9wX#!eU3(N_ps}JO zU@9cdFufnct_4!4tzf0Z*mt8ul8Ya%1$Tj`%Sd>&;LQr*#d;nbfi_#fu=4eNoi9tv z_UJd>S74bFwVK7c%xkp^YiZ0xEn6*cAhp1}LwNa0KB75$y4;)-RctWLQ7o(pB|X%h z|MwCy7Rw|V762xR8;9S0YgQ_ik&tu2;|8V))Ltzb<%R!__7UdFo>r=adBqK~>up(Y zI}iOEi|(c#J)sV S{wa>*xe(+_De=O%Le781jz+)$ literal 1118 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Seyr;B4q#hkZuZckdQAmX|) zyUy@m^ry{dnoZL)99Ws+wq@H&-qG4M<-T?u|1D#D|NVCL{f&1cn7&>7drbX~ zVZMC)`su3i>%+IH-ifaGI?a3fwk30Kp0$1*d}IH<(}C@WxTnM&+5O!4{>7rQ+_LGxy}J zTf3hh=G}bl`i-*4+4g<~(^eVpO#TB0hIw7bGQP0m@?Xs|FWjDVu`+w*T+#R^ zxk9BM_!b}EeDV7JrF?wht-mL&Y3_CO-ypkj!?|^5x9{QSYi}yJ5OF%%d&`_{F$VJU zGPms6_ATh=x5$#J8C&eZ5TK2pCUpZ*~Ge`B+@2e}pZa;4|&efSPpwS2^>4+W zE$00Gw^x6P|NHaBtgmnP}4Fu!(?x9p)v z)IYl$?;0}P!heS@RXzQFmHX4hC!?b0S^Y@KYJVEFqS^o4@~=TR{?*R)mDQQIoKNh0 z{nyPmPds|3qAj(4(_gjw=Jn@(KX{?Br9T@e*=`kK<1eejSNh@PPYr>spY48pt2BNx zdEu1r2e~S%#r|Hs$9_BRRQ-RMc|E7^>hYhCPI&d*?#CJR8D-mlvXp1$uWa@zTrH#}YaT-G@yGywpJwJF&E diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_stop_restart.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_stop_restart.gb.png index 005fdf1f3095699cd6fda0a3ed2754a1e265a582..c0bcf5904e91479e417f5fd489c74389b6a462d6 100644 GIT binary patch literal 1514 zcmX|>dsNbA7{_6xxJ*RnrWrb%Ewv`*W-5v|PGxpcr_Q``C@t*h4i3sRjfkna zOz~7=PJx;8mWHU!dyy7i%byGH%0Tgw0=gf$?T`0)&-=XRea`23zVGw=;_vINx6^bd z1On0XId(Vz+)KcftD_CtoEjn>0@14SIqVgbcwbr+n=L^jwHIS}GZ7Y~ukz1bi_36v z8#i{AB7Vp#rI#;spCx|Kp2!V2Zr^T%Pi#D!X+vFAhc&+C1n0;AcEIrq_c%zrj!h|O z*ccM$!j+1i$*^bI0DDV09o`CLL*u{j7}$cE>~TRC1T4^BJ3OQL!JKcik@yVzbmW*T zfv0?abr@5};>?P-V;UYn9GtL9MVjYygzFb^Y;fi|ryo|!21h0xYc|O8b82ZqvQjWA ztqtMZ<-21`xNP!zPmA`R>dm77E!sbsIcR(Ax!- zmmAo85XoV6g?ix5;7HPH7c}eY`T`|9k|&PEZG71U9oE-5^fZ%Rli-~~whXBd>4bP@ z2SG@9M4|;Cp$UT7s8Ke}Vl1xvS3xoWz9 zTZ#udP(GybP|8uSBs52rBY=ayb?tAxe5M%I&EA!x9F`C3XUK*$wf)shZe5xN7aB%V zg7*_8dE;^o@Xcy@PUSAg+(Qt*C$jbh<)xYqpl{EOr+(aOk)*^x$U zn7S{J4k}gb+qFuxiv*Z^lN~M5mg9dJ$YXd`X2Es8r-8SI2T-AP;(Y1E@!>XFZLy#O z#TSDx-lSz#D-O=QgjuUTv|*T!B~)yopf4z%Qf7CoRIc8b!j8{W2>aAI6Z8dmswtSe z<@o=VjLJ>}rV6L&nan8aNCR=WYDmA6Cj1SIP)HF*WZQELV+EDl_|qhd(Yd8CR}t+e zgS?#{Md9^;>`w#3lJxbelhQ7ihEzLo{$?b3bH+Ys%~%XsxK3>$c9*`?3YT?Muewbo zas^2UXP1W})W2f$#rgHUi(yw;UD4ThLAZ5AKoZwX5Y_y~%OTvgq$!&82=<;m0v#Ng zf7k!y=X)NIlt2KNVeXb;_D&-*<__#iZ`n(Zz6{-TqK6`M+ zfYBUlke5717&z(-IkgtWO76*|M=2M;w@-_8wTE=EXhFF~+g#ZD!i@>~2M+kZhL%)j z@*2O2R6%oHFU?aG=E{gk31p*?CoSO5omRjWWn!=;g0ixBil^!xU<+r$Y;Gb|+)Wvq z{OO7~UT@f4Bx_2C*1l4QoS!wfL~rs;rmUOlO;`I5!J7cti!BS2Oy=FKpxQ~=KHznn zI`Az{L>|M5Zmw*!u1#+@Xo}ehwtOg`lx8A1`%HiXi_tE)PycTMOwftl-V{#?{;?M#W+BI2 tYn#$|DJqlf<3&5FnRnpK*nPESP2cob@aStE$`_pB5T7Hyhk1A+;~#MD{h9y( literal 1485 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 TyMy}*00000NkvXXu0mjft5fSG diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_sweep_restart.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_sweep_restart.gb.png index f28a3f1971a231860136de59fa4a712a316558db..e51dc2d4f708f6b2ca07d2b506f998405cd71967 100644 GIT binary patch literal 2288 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 C15YFX diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_sweep_restart_2.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_sweep_restart_2.gb.png index 3b7ee8746ba3acf26fd70c5c7476cafa6f835eb9..1980e20aedd433d99994ba27ccb52d64cca33d13 100644 GIT binary patch literal 1484 zcmb7^ZB)`%7{Fni)RT!iTX~97b5>SPd$FQYkg55yiCHJjhTuGA9W@F;QxRC{vO0>U z(_9voBWI~(sidTMN<)XmswtGiza&M<6pjAzuKQ!1oz7|dun+g%b3Z)Kz0dFY-FqfF zYPZ9}WeZ_2m_vA2a16B8K%>OY2AbKeBo+*|pe#H%Xy1wI$xDfpi+)a_(yD|~ln2_S ztHD@Hkc5`{g`JDNr+IiHJ}v;aE0s%k4vC34LXdvB3X`DjSpD*^j`M8iQldz9wb!@E zXRtE4p5b6d0#gIr*z{qGR~mx+_=Sp08ZR2$#@jC<>&V_*sz4q*yCNt;Nzj;c(lG;`)09y}bRbV!_|7m!60&21x8b0G7M&8{aKQuMn=(#(vx+RTy> z7r5;H>B7E_rd4ll&rpH&6Ox+=;?uQKythSvoI$PV<(c?@m^Ph1$Kp~s9FFjTinj!~%84??R3v8``$gpG^sQwn{yf=`u(A#GrxX`pvXNm( z-3Vi5o$R9LQjdDSJhz8erd2a_7vS|oeiYK~$~UEm=iULFkr~yy`x)3n&vg&HYzIWj zGy|8}65n!d^R*Qm;j~Koa&D2n?|6Khc0nClLKg^os?_kAq&(>j(lEH2zO#vn-1_zP zOP;gUU1CZ@yk}d9Xwy5qS#4;r?QsPA;U0@CQGR3@J@*~R(yI&#wENv{3UrN^TN0Zq zl6}U+5w*)bYFy-9#^8aCn9ys*=??oXI>>nz6wg2!QKOX!Ji51!tdF8eFsMc(XgG9wJb>YscW_-j`jz@2of^a+9vH&5!Q0 zvVBO-M>%m&$GTAVLP-|EF;37&3UFu8%!}p&txzdgb2JC#ggc%aRTyS)t7&8~suQhc zLpa9=X4)=)aZ4n%c!fS{o;K0>H%9LYB3pfky2?jEI(|Zdf~^)PS;nyznnMkDOCCAz zsXoKhgpYXgm4rRG6A>dkDhJUXLq}#5mSg?!y%Fq6IbX{B5sVK+K^*^`jc-5vz&x0Z zi06}y&zfnUpsABDO%qh3oaVfA3wUbddeyTr=&8NGBBYbJqv;I@wu<@0Ix4b52(DYi z{Em5Nc#}`=^2N*q!1BXjXIkDVy;R%o>xxr!k=_G<++lD4SB`73vhNZW{0(~OAs_DW?s$mrxdI`+I=>X<8uCX7JbsyQx&Jr3$*&B zVH`Xp&YY!{cRHNK@FY19ZUF2ev1%b3PX7miCW(F28OU;xS#k39r@G!zva)?N6sU7g zGqB=4!c0`M(%N9(t)%hp++}OIBP#&Pi#F~Y-H%C`Pxp^Sox7HN1f>%m*=jyAk$vjb)MJ}ojx;MB zo8EG_JtqY_A#MC1?iHx?{1-WUKkwdi7;9HxlOO%RmY2Riy}hyBl1_j=X&5X#Br3QG HM>_d8Z(af1 literal 1328 zcma)6ZB&wH7^W|2iPLRoGFw4ZCpR+Dob)lpnV7Lsb8D5U;nAe6FvO3Pe8Cyn$1FcH zCt_5qbvkG=%TWzMFjGRQwmjG@6ohCQ@)hwSh z#QF8w!-jFuoUL&OS^GWw1?tv^u`JuV{5TJecEf>xaevAe6Vi-a-Db==qK}dH-%sgm zOx~+7$&*VPrTkNNN}OdM`W5Z0i3K;;{$hJ`!?5w5^UStvu>XBC*6;XuU>5|t{ylVRL36p|1$xiiW@g)JW2|I_ow_Rl& zzA7r~Cm(D`-uozVs}*E~0ZlZElw};GHT9h%-5J(LeIg!64!@VP{2p~X2%ttf@r=?h zZui#7ZRVxUbK^46KtsnWJWTW8`4uHwQ=nn@4r$rb%gmM`=*#YAgkdKN(NvCF-`x5J zA@-+53_l&Boc2eZ?1NX@))=JtqaWrT>Zr5tvoAL@o#%qMF?-~@s)*L5HZYS8Rbv2x zTYG>!Htl_trJ{H|*T>aGA01w6`aKr`^*k!B42{?jlhK1zpNT0<2d}-%QFEyHV=+o0 zvV>OTkeM^U&FH7wGhTlbIhE<_{;?ZQu7Qw!(XGl61ipAETCE=$b6FuQ&%;r8@OQc> z@Mx4ls`bK}rCP6iU}lk;BDl?;w1kfOj5d0CCsg~!^-{33ta$+*s;4pHXE)NihH&e6 zEV9<>i0^>|0+CtzQZ$%Zu2@W<`R*BGm#ehM)~$Z*JWeooZYMkWuK6^KaRN zD90h;!01#=X+^t%$)5x0Uc60j{!N%pNFAGCehQGqbfobFsV-gLlLKg;fq`gIYnQ#R zTMTE-Rukv=q=`1RP3#YI;&daDV1?jo)HHDoVOXMsuS>K@kvfQi49>T)uC$$AK`2=Q z!7ClFrQBIfyaZ)XbC4EAVERqJK*0PlL-Ob3PX>mvH zycPaC4dbq?w4h+crVXB(ml5ww;l4OGn51Om`XS4HeziTT30HX5&43JeXE|Xv2eJoW j&&$71PAu<@|KG=sc5+3%394p{cN3E0lZe+hWgh((<6ObG diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_volume.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_volume.gb.png index e662d1623910b43b69943a95d9408156e5206814..5628be2bbfd5a641dfe15b55a76bbff49ea7b768 100644 GIT binary patch literal 1545 zcmb7EX;hMF7==hNCj+K+%=olLGb_=P+LV5w$Ho+MqwF{gi8Wf5TVt9j2szrg9hcO! z%w<%dkua||vWe_3D;_hcdz;sKvacmS&}wX*LRayH0x zn_u@?ltv*ydCCPA%@8z=Cp&?rMSZPkPfgVbs=|7*Y^_fz)8p{|62kRQ?zPefv6eMz zwuUq1t?iM>9%dzNktJ@>t30sB5KMO@SvC0QmU0yqUV)uNJ8$D`ns&c^(^8F1ya!3Yf5qF^4`m8@;6ywSodn zYW9Tp%n&+E59nQ|2FtU!$Ht~&C7ImkjiDOy0i_BO&56U;-0&9;*6(6ua`)kW zYCa|^?8=CUEP1>eq&?a;I@Rpk&}bPdZ-3P%Pdq{lgvdK+{93MErdJS#N+xL9MD0E2?9JlaG$@<%uw_P0lJ&8vqa%yDDX*wxfb>N8Nv$ zokgzQGHD%vj7Ft7oi?f@ze2%Bv3?y4j*9x(n<-Zu2&dlGop1zZ8z7OiR(;kUAk-HI z=bBKU1-1Xu6p4%{A5C4a3`ZwC7@m1;xWcD}9$;>t9x3d)WW159$vA_^YHFYnKKRL# z_zZ#))rGG54dU1_Iv7Mou!2rxLLq=U0NF`zy1S&iZ`+BDrxOY645QVn{Vk@oiOld* zplLqAp@LkJHDzOKQ^$}mF-%%yb)ZeWBd1aIE+15#p%A$?&t@h` z;|B$TuE1kJly||h9+<^|e$kgzYkLZH`Gy9wyBcb~)87kg3yO#@oP$~Plk{B&7g)c` zV+KbTSqrRmoQhxhN+kVy0{4Q>kJ+@)A!SAr?h9eT?}=WcJbw8s$-$U8R`#=b~5urH+B+oRBrG+usOarH5VA>36KcZ(9)c`!fe-lSN%@QhSG=Kjmb?CdHjo^iKVH3rhG y7E&9ZXC*nk`wabn$9pa3=|J9WmPd;rZG@R-u@g#6Z8oq#7v>`^kQrRO?d0a3plbF%OP_(liK|!XT$=n?+7E3siM>PB z9dE3UUo0!YzqrenzuzmIU&M7$-blm=_D{~bhQWK(Ex8wSu<=gs!M1m>3&HdeB2Fu) zMrcE4H(aJ2BcjlvNm6qFSS2XNOAxow$?FZx2QGIPg@IDF3LLbB$Wj2{rIoc~Dp=Qg zr9Man^>{;ON$pdRAmBugm8BJ}RCpalczML83rNL~ zun}z$2|yaI)e;&#;Ti(^%6Mk~J-{r4AwWENmAl=>Aq1BO#`8h^hulAI*ZM|i@L=qK!aMtTVc07p8k$CT6q0)<@ zhSHU#b_}UUUq7lRM$Km{U&}J{?Znm{8TngO;J_Zf*;$vYodSw=yHY}TZNXW-%&Lk7 z`H^f&VR-K}pq+b1C#;waOY;9Au4H;eA|y_^Ef!$rL5t#8V)T`mD@wbSn`%pv0<+Aj zgsF_?yP|WupE2d#ZQ0!N4H{-m+R^3*mK6<^yEYlOE0ra-yu&#`z*G}EK20k8%BXam zld6vFZ$2=2@)v@fYVH1(Dxo6L0K6jurveMABQe0#F>`IcLS0uQQeZ1(n$2hcyELQi zW~myZg^KAN$QVT)X8Bw@y__^(^hAY7pV))Y5}4&OSs>8E zlW3TB`>Hkw29lZyTE6;ouft-e$dH{lvWcO&Y}1NQ?@ov=CYze*r7_+Eub7tp_u}x} zXA_<+q2R8o$wGv@7k`>D1rEA}6yGRY9D?h{)Y#CWutK31vJf@94n(w1a#WyKO@@w} ziSs^v*^ET8t}FXnTj7&|kCKl@3j{|#KKtR`pp)q{K}%j^(9xEeeF(D2*bP+*jj}p? z%5z~5xkZi|O$1AYttcG?c@L|zWtT!p^?8m;4rVAZu~NZYydKRBd-LtItRx(Y{gkn` z=exSHI2MfAo5=(u{kPt6zcZWA=!wm;W7N9G9^DLHppGZZV^_0i49I=qpb6lxTs_r{ xdRo*MNa{+&KlkZfy>>J=PsBFNsrsK|1Woh4JkVrdr(6Cxg9C{Hzx$DM{{=AAy|VxS diff --git a/tests/test_results/SameSuite/apu/channel_1/channel_1_volume_div.gb.png b/tests/test_results/SameSuite/apu/channel_1/channel_1_volume_div.gb.png index 290698e6687d268ab6ec84def3f7ea585e9ac8a1..1f34b7ecd485fadbabe4d1558e298457b35f3107 100644 GIT binary patch literal 1043 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-P9ICviKmoe++kslf>Qs+IdoV9G{l%lUQmSx8MdGh)7 z#zL9rTMK2%kH0>yer^5L=arVnqaSZ|`D<|gvki~4=Kud@OQ!_IRqpw-r~J>|J^t(B z&t+;pHQH`>PinpDtN(vKYhBf6wSTGom;b?LPK7{oW(%IfXU}U~xD~Fs)aP=D*G|5B zM&G)gi!Z;t^33O&$~k_^?-qW&zASUsoX3e0de1Ae|H!^-x7_o%V5#l0@4SzJy5^jJ zzPg3)aKO3epHnaS+wYe+{_)jZyS~TocKpizS{Zll3Y`1ft-TCc(`)5yIlfR2Q?#lXP@f`6zq4$^YTl|ledJy=IUByd& z!V0z816CWpZnRqfgb(zu{!Y)YJrEzV9%$|g{vQc@?Gm1UeO-8)Cp4*q2zo$-S?fUySFPpsW z+wc50s=xNYkMB*_-~BDvy|lEJU;a_``utCOAKaVHSd_>1p=0gp+l#+`&CcoFzV7+f zS!^FNetgS%Uwd}(*F9mI?Gm~_e5{=HmGQd0{cU!i*BkqlB%hOkj zviFqM_SbH?_xdYi`7>Q$T)*nS{+e0n>YwbH+t=mw=QwSfcJlqS^I<^K{?r|)ny30> z^DA(G#=Ha>w%1M}@K=Mj?YE0n>#wulnYmpizjw7gFfKf<^xx?VJ@t8Z&)4iL?`K6n zUi-Ro?Xubf)z71wuA0APwzHBK%l}&LbgAmi;;$g}Q|umiVROirx&vQ7eS4#pl0EZJ z-p`!S^Vu(%-<^4#$oYD2ocsih+5=PNerN0jx%p}SQ9;!P7htku@|eOPG`yHs>JKkt V%k#=j=LTkW22WQ%mvv4FO#oHN?lu4b literal 968 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Rrvr;B4q#hkZu0?TeIh_D9w zZOeG?dr#+Yz^Q{yK{`B!a_4Iv9?xBp#q;oJrgKl>yyp`P&lmS>47J_6Cr0n1{q)nR znt)ukqlki`y=@+hbd@Mpb-B%7wEW)kRsOg6-K>|^wF=~IzdfaJ_uXkb!@jmUhTWL| z^z-tKHeH)OiT|v)f523JxwO{t>dpCc{ZF0#crWMro?3%B({e9;^0k|LI6PgilK1Jo zN&25d*2_&7=knctH+)l&J_ z*q=u`?#s-1_W4QH9`oPVe*Vc^bF6xE?B}n`Uwrbx;}QJt`BKTlaBUpOA1PzU{}#)sMnpdbEn+n`P#m(?=D)?l5=#qwD;pplj4>B zXKC)S{2lmn*8Og|T=7ZF*P8qT^)9=AdRTXJt=6S!RR`1G=CysjHT#s^jzjDAw5;Fo zU-{VHsw(y+#7b9^_X4$8&+ruYryYl_uamnp396y=Xeo&T_Y?)>EBWEw) z?}XnE1pYkxEB)=@2Vk(+u4*dN(fjg9Rcy cLz{RmFI|+)=pK751e8NPUHx3vIVCg!08*sfKL7v# diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_align.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_align.gb.png index e6601e48a50ced9b870491c13de4b93c204d530b..e1323bb000d3957bb2f8352948ce2706fa974e09 100644 GIT binary patch literal 894 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V`_hk3UQB-#RZ z-!gr6@6g#Q`&|njJSv-VAbNVS%95jJ!*gFm8m%c=y7k(%(%hv}y07nAm;3p}`u=mb z&q=PGJ->e6wrf#qw@wN2%3eDq=+x$Z^R3sqx4x~|a?jZQ{-62$_m|f2Fv~sF{VV-o zE9Zn2s~N9Fo&WFG>|Uk4bV_Ngx90SRx!LD`{Q9@&?VPCBKj&QB_4DWR?9ZLe3$_P& zS=s%HIve=kwpEYCf&WtG9`IulnpQ zv(VQmxikL%6*%;IXBGFqf=SUI`sdF7E0!ZaLF0GB{*1cFX_um`_iyu`zxeBXgM9gd zqRsbL^4(;AbN=N`_L@6?{++rMFL}N6Z^__1{EhK;nPsbVr5wl#7uikxH`s5>-)1+lpS7LeV@mCT z4}a=vPd)$nZq9aoiFxnuZ_hp+KiB;89+1Spy|>T3?RS5@SMvG%P5dt^9o+bKGWDJN zeeO?A_=f7|v5f0)#IJlX@eT981wSR{Ki^$`&o1G{>ie00VhSqi>QiTYtlU@q{BO*L zpD+F>+Ka!@J6A6I&*IvPS8Ug1q!#?xaW(OG!=uO6e7D)}{J&+l;OGCj2p1g#x@g{Z z{uy)c-|K$+cSjj8{LWhB%THLb{0(!J+yue%%x%x(VF72KQ3p~F)Gkr>dhWCBJAY}; zcwYH+|Nno*N{d)LrZ990DFia9cs00iPS9W!Qgs;qf-CmlDPOac;gEhRFkdlvy85}S Ib4q9e00~s3KmY&$ literal 804 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V1*PZ!6KiaBp*KP+0UAi}n= zI6m;cZ~V01hc%s5R~~&NQdU+!@v!*q6y=X1(m`Igx3xCq-VXATef{-~VQBc7^7F^1 z1oarUObFsqv@x{nFnrd2e8;*Lrq8jT{vQhWbDwzg=a1L%4)a_O7MyEJ?m2#axr0ZX z>*CMPvyB@+3Oe2RZY%cYciowP%ez5N6k4o0b z_ixy&(|-C<{?u*u!!K{D$;N*=erSUHsryB7i}s({>9YN~d-S5W%`a6z>SH3OsoIxE zPMbb;Qv0WG2j@!W{@s0kHn-Og)&&a2(<`yg}CbDnkS^)uuj_T-$s zeC|QbKhBmEd7G>M^0M#Q{b;&*-S~a?z7zf17HxU9S6^!0{^Iy8tADxt$=bca&NtQM z9rv|&B6r?2Z?)z&l0^Rsw^4FXc5uW4q jcFl_uEV$Z62Ju<`fB&Bci?6#jfKtDwtDnm{r-UW|#J7N6 diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_align_cpu.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_align_cpu.gb.png index 82a17ed6dc8cfa3b270df2f0ce3a450d2fc5593c..10551d8219cac0d8caaae3bd96a06831abd311ed 100644 GIT binary patch literal 874 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V`_M8De(BCQwG zub5WlpG#YAzd||vgoC4>9`kdLiLVawZ@s8KyT@ns)@$2xvqMWi-`ckJt=!+Q7b|~T zE-wB2`Mmzxt=n|GLqoS-Tea%d&EvaMBlE6(dwj9#{GXc7_4jI~S4p$kobvvqf8f2E z!=iU=J9JKeeQr{*WcsRAxwlunGTT%8Rx;jx-uC&Mff92r?)v%j`SfSP?2h`Oq0>)4 zU3MWg@>S8!82_p-*WI7Lecp5J?Dez1cC9nNpC5a@{QB?klzVm$?tK1T9T|GP_&3m2 zeZzeD2`iT89RBmZU7&9M?(GlE)pxSi-~M?s|KK;q&Q1J3nx9=SU3=};*RA!xWY*TV zSN?98Jo~R-=_$K}dcX5_4<0}351;n$)Q;TSyI=p+HjMwjuHo(1`=4HIKK_@veQ*D> z{@368++RO?KHvUp@P~E(?d(`*%b%UUiT}p8)0#Dto`bCLxT5*Hp?1UHjkOM+7X!_A zxye4K=HCvx$DbR%F{;mhfB$>+arwE^=WOS<`1$AK-uvg@_Pf8{d*n~>H%43j6DKso z4_vH_pE>_6*OYnxCx#z*Je5oCq}>DlS>J6G{LO#QJ@uZ|->!borZYcm7To7Hlb>t< z^n26Yqd!+&mOm~Pd-uV-U}h2d`K7n)7X0|pb@>}(fBie=ZTuN^Ccu!<0Q&a4{Ppx2 zxeIm=o`Ay5^!Z^9V8B$JvRm++QSze=P;~SCrnk&Osq!D#&sNJ#SpNIX+*iAI{??pP zd8Y2~hxx*Sst$`-Jf<*o3Mm9KsdzQGa8A%*6dL}5e_0RxpS?EA+ItBw4>5SU`njxg HN@xNAGYple literal 777 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V0qPZ!6KiaBp*2Npe65MWi@ z8b0NnT5Q?3=BO?X_0<^{*-Cfq|L{R)`5mz|E_K%<+j3VX-rm+B5%E6#bV$6Rz0G`O zrRQ^kx;my@l6~xSZl|5j<9(B)&hVf7@Acj?#&zDg+vWcmi}bnLibduW>&4G!QoQ53 z`1A3-GY?dFYrg$GPx@P}e(~a0e{*KO(r&0(yXJM&{N3wbpY}Q(`*Q=o+^qBUWd>h+ z*FN5NDenKWJ+%gNCgukHoTSg~T&r>abYW+m`E#MaU;pHIZ>cqy3sS#+YHH-G;_0cC zk(oO+^W|-3+`P75ySBjkYTRi%i=`%ayMo?7C~N&$b0=;^?aBU6TZ<&Vi+(=*`Fi=q z(1-D#OSjpbIe$~s-nM=z|GN2tGv&`WKfJ+yxpV#Zt@`?(JonA74T%5!1?UW!-wzJ{ zjGFnx=7wEG!M@sbzQ_G(`Dbsl^PhWu(tq-~f*&iwZ20F*&@L~quQswjEL&Dy_sx38 zy?Q3aH_f*E(>|Zs_V4HJ7YBdd44d*;?R8(F;`xo7zcPPn@9!=Ptrz%T5_9AJG9&5h ztl8fMW8O4x{Vj3GPGatM{CubcHot7C y-SGTi(LCeV_y4;bEco->*n+EVal!~8_A@RM`dVYMbGa@kiF>;GxvXEaktG3V{=i+PJ2B-$R% zOv@L1$D3dFZNb8Wni--`qPL%)TybE^{TZLcu6IU9W#`UX7Us3H%IETxwDtdfUaS0U z8U1?g*41AVCDzT$muQ>)`tRXCk3V~#kJ*1KDtqg-AiMc%rv#n4d3#MhF_H5l( zv+n25Ykk|VRX>xTe}9+W>BrI6-=E(e9=NZaas8)>b&P-Vm{hV%4@A9wwO{Z-hWyeg zM!vzCPp$S;zU`T>e}4A)m0RoPZr*xN`uN5z*Y?RhwzwB#UjOEg@%8oF%>N&G=r?z( z`TYJMubnY^;jP!APCqUBY4!4|{e7!^yY%e6<~)b5#=kP)F~5FR-unNUubcUIB;2!8 z*ndXzwt@X$fkW>%{}TK2Rwe)7v-mG!Ir0-Uem6YhpS_*`#LI8bici=j)cd`k`n%!u z{xkp1S@>Q)`}N=S>G#~LkN@ z`< zt@oAB`}zF&@7G((YToa${rs`k;djB!pHH`Xy*WH@6aS0f4LO!i_;!@u^M7`iar5W@ zPh<;TA7+txJO37Y&GFhb|1<42@=M=mFIj$;EB@ct|Dxr~zUVjT#JeeYHMnq2&|nl& sby&pWF@>R1NFk6(WmFImAiucJ8Lwv!J1Kbtn6nr>UHx3vIVCg!0H2VRHUIzs literal 784 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V1_PZ!6KiaBp@2Ieg`U|*+C>1F?UD=R3M(+LGzbk%jef{(OgYz5@Ucb{{u#Jaj zJD-dbhvJ=2p;uhKX#NWPoPFK+%eRoHe;-HWu3w)hF)fvIW@=>I^L0}C=lA(dRo>~h zZbsho{k!LV{LZ%eD8%T(^yL!kZ@b&o*1fIT#oxBOX!e(1{&u$Q>*wt(=|6jW<@OhO z&#$jr{ySmU3%eg68!p#=IDF-s)n@*4{!izZp0vB6{jZ`fYGdZCOV!IWZ^>{f&bi6{ zoLkno`t#)zuXe~-8ruI?Tm5+N=J}@e#>cETo-TiT*8D^7=ksrxPoH@;Z9n_ro&3wX zzy7{;mfvzl{5;zVt#xzfyIED9dYfBU{`*1Hz5IWVkBH8he{1)%OMkv>np}Tz@7en? zd$r#etUP%BwRzv_ZwG&#x%%Q*WY_p@jmD3pJ%@x+!Ncf{pVWd_?`UUzCVzg zyQAs8WMu7+XCIZz{8WCV8MJ?QsUf!0=70|gE@^4N1o7cwnEXZeZfNaWk4z2)78&qol`;+0H5k} A8~^|S diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_duty.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_duty.gb.png index 66d4896a20611fbdc6cf48724e78ba0128fd356c..254772b1a2ec0a27c1dcd43ad1475c483a1de313 100644 GIT binary patch literal 1571 zcma)6eKga17~fh9Wu}}$Yj-I{r??&0?atfQQWSAh*J~Jxyk%{LVYalq7gu??xvWk} zEJN-%g_*iuimb3!i6N}8mX~d2X7@*(?m73b`^WeDobU6T@AvsU&-e3rGJbV)($z86 z0f9ieN1Po!0DB8qxmudQp2Nn|Kp>6DBaR17#{VfUIi6Q(V{qu8m%D!Xa1r6*BTRc@ zw)UEgb8cZ(yO}m4dl+q5m!{Vl24|o5DKNyvC+zLou#qtoz;v-@Cnu?w4MMW}e!2q3 zrSD6H;}&9V_Z{my5f~v@Bz;jSS}Y|S0-&1Q@`dA6kl_|hl(RXE8-5ch3I{R9cG2Ml zXSBE3={5DsuZrs}_^y5P_ba3yl=AYlCr!3ru#)&>+UZB$w zoA+s(6G4(VG&Ng8n@(BeJSAt()FxC(N~Xz8M~BHBjG1}!@(Uz`J( zK89g6f0!=wgLMvH-hQDAEEXz2JE4D9D*(J#5b(gs>yS|w3K5$-n~%ty0^imYd^_V{ z>6X%G-~*X<(7z~|B1k1As{>KZFBH*aR2c%=s<)E!E1%jp zVtITZiRlhAlg&{)0glW~Ux#E;8igLwLoBH%e!e9SLbONqqo4-LZkoolD;qk6m}fB4!$vj?0_F^u)5r#yy>SRFu=t`Zht_5X@U1#pDpZ&K#?!D*U-~Ij0_kMoA-_1OYJ!zbIC%&^SR@0J#sdjSgWw1Qcana ztbu)H{mmk)^6;AiC&F0YkP_GKX}QytTus0)CEglYnphZZQY zaXc>Q4iw}F!TY`g`bMyN*();v_sPjQ%I&dRG_@Q*sfI}SJD@yVxW26%nA|KxnJsqe z9|`Cr$HVKUvQP{fsD)#>lio<6)#t7i^?_UAxvbT*bC+W zo{Q3t9;Kw^O***9((@+cj3w!Q^3~2h04%nQ212~-20{cru47W&!t=~WfgJKk(`Zc zsjQ$-DLg~%112R$d1Yu{9)56TSc)PkLhn*jD-QCZkCwBbvkx(YTYuZ+Y~ZFf=}K9j zc$6BU=uNIiXKv%LF=ma}>8bQuJKb%ZEab2fp}AM0V%jU5PsIKOoSf?7(l;0-*l3!_KG`(I$~S*cyh;> zm{;j{FnHa@-@(VTKto5cCuByJ`-P?av9C0=&X#}G`oPvul+&S2uFpl8j+cByFLw?e z|LeQ&GYf6|H0+s;U%y2>oBj-hfXnGEoSQ4a(sB%aTQW%q+uAV_%on~dL7jhV;{Hsn zz$mJyGEp)=x-V%9Co1hB)6wOkSLy`4mmbnDCf$JM3sL7?ca88!+7;w1w6#do-9*E~ z^R=SG!@72p*B};Us?^shzZfr3*}FAe!-vHtn=&>ekY&_42PM;9cz1CAPj4UFgaUtf z2-_*Rbj{V;kInl5F_^WPWD#>=DHp0vYwVR%H%3qAv3bq-L2hv|#X-=%={EdgN!4p9)|?`uyM+i-9G9zT-+c;dz*zamgA{(8n-eH zDQxWljy||;w{;Z71GO-nmKW|+f(z7*uxdb7;ud{ubLg$$VogZ%O_@%_vClI0iyF77 zZj#zqZCK0rc-;?<{8wtE`vmtYyPLZ@>pZRg&J6SrhrgZR=HmW$!+AP#B5pl<++|7g N?|Ngf9vn0-?H?kX+sXg{ diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_duty_delay.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_duty_delay.gb.png index b9f7a35f2489f3a61768f44225439c56ebce7bd5..46c209328d3ee040edad58b1c95c88cc5bdce8a4 100644 GIT binary patch literal 1430 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 O{Pe>5xwGByw7&oiZQD2i diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_extra_length_clocking-cgb0B.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_extra_length_clocking-cgb0B.gb.png index d6e02f5e8ac561d23750a7921a2426bfb7a2bd31..276a0db09332c307624c5c60e2b605d17185811e 100644 GIT binary patch literal 909 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V{Az)7Yz`!LN~o>MbNnlZJ;&t|!nJlFY%YqIJ>-fOq^J&y3!46i>{xMuQuJG;#DnoCQ* z?|r-Y_qRLulIQs?U;W!}`Q=CVQ}^fFUx|8cz_avEWlNBk=K6iMl5G#4?Ys9k`uy!p z^XHxZQuA#4^mq5G=KcNk@A}!BPtAI^q|ZOU`S-oTy~(!U4q*m|wL{!RZ){;zlK3isUDz4vncto1=&J7e_1 z-}4t!$4?JfrryWZDv+1;AQ zub3?_vMq*v%YVz?@76!Kmr?iU>i#C#|MmU5`7-41f{FS+r4F0;cJkerce(2M>xSrm z7i;Y1uYYGf|9p4-^uPORfBxo+IQ@pZwK{)${EwWqZ=ZkvzU6zqZT{(fwf8>X_69P~ z*VWd)SpLBCw?+2vpOxO{6Hiz6zj=Os@3q|j5BHVK`}^;&V*K$au8)Gb0(&2SSE??v zxBqscJ7<6X``-<7i{IIPyTw0q?|bID({-=ci~p|u?Yxe8+re$y>wlFV(7Y3M|NZY0 z``Z5>uQT3%JNMt^J$Lsr#=kvW|L;-Ur(B5$C5J^U9#a@Pg%kprRJb*0|V22PZ!6KiaBp@#!gyfAmZ@w z$Sd7L_YyYGWM5_c(naZk|H+3n{$)F?UTw@YYMXM&#CM^AZ?Mrn*!sEaXf6gD?z4!dR`t0{L zJr{pkeTaUV98qK3v-QK`x9&@m?A|YD_j~W>x1~2(pMU@BRI|{3UcR5^ z+#2~eHT8DO?r;89G{5{#+?IN~(^LNiKIcC_`TN0h?ccP1N%HG&y0)(0>UYBP`E%C$ zU%VPq@m%xPSN=7JfeJv1*bmr6)NU6zWOII_!THZcIk|gnLhQoQ?L_9UsX1P>PwJlC z{%?%^hJBA`)XN;-SbISGjQmf#9X8*$MZf=67Ux+%Rr~Maxj#S63|U|K8|eLyt6zL` zy#DO-#dGtUzr8s2^G{09_y13CFBYF^|EV-_cI9Ftx2<*CpWgfVU;g&zn)u)ED);lZ zod-JR@8=t$&-&jqpXPV0{C)e|L9kE#_w)aI^7C1bpL|5^`afo#@h1)xu(cT*%xQec n!;_w1!F*U!;@qfEdcuzSk7rf$; zLWnVj5JLELJ26euQ!KS>@f`I2^nYIV5?&Jn;9dNv4*G3-a;GO)0 z5W+mqA%y3|OR-OExBXI|X__8=YH{@b^;2ZU0YZL42;qLeKV|4NP2qmOKRUg8d#zvU zQ;YY|+xYc*ecIpkdR>g?FZ2iWqx*pASkm#7+O>EN`u%!*K#=kg8)oYw1jeH{1;2D+ z3O?-==3S2u2!B%W0S(vdHN+Snue5rr#nHFxhxY*)>9ut60rNgNXZ8W@m6?x>^E@xc z^B4O4`tUmf#_;cLJkrh!4s+J@HePDi;^_VByRL#0`F_s43P!KhckuyZ8cqRrLOmcz z!3P8>U3|biVe_^Z9}qqDfG`;DeLx#`Sr0e3-n|c)e}8VNz19!CPVe3aw4a^&=IvTM z2mMK^VfGSNd_d{}p+=Vxu-5O0o)K^?Jw6~v`6?gKV?EsHdiOpcBeieb-xqAJERXeY zqw9yiFBrX6-{rnw=d(Bn>0XYtX6XP}x+WmW2Ycz}1Co4jgl;~deXsHX(fiZ8_W{8- zav#vfJb%`+p>NcyeLy=^*V=3S(EHWn1A>%~_<+;{LJfI6AXD(zdVD~Taw;FtV?7)C zHoe>j+&j|3cOmxy?UmuNo^5o!<#M?^?eB8AEXG5qEJ8lrejLo?ubypmed{6;9}uJz z6Ywadg$KYAU46i<{h5NlN8dUHckuy__=W%>@PC{T^d0pseL(O{>&z$j0d34>J=@^= zRv%IB1GfH%wV?mJera)IwY7fufFPy&ZJFuHII;+V@sRs~Ou@eOty3^QAV@it59qO; z4gG&y-WSZs>6_N{>GqtE5X#liYkGTSd8~)?uD2Yj=WFy4!|wa6amlwk_#XJlDl|?rIS4dbJDD#Rp8&Dc&CJrR$TD zY-Te8`p}^0oVig1%s4R-50F(!My9$KB`L}5ZdrQ z^T>U`o<5dGAFrog;^VdIttXRvOMBZ#e8BX6slC)M|C~~Lt)FLo>-kgb@c}_f7aJxe ztWDh8?e^GCJs{MO8_}&ssyzmU5r3@Kx1K+XLXQs!QcmRqQqPAE_;FfCPVwQ(@jmms z%?Gr+vmS8ib3FKXHtw>XuTlS&=Y-^J?eB3B(5m;oh-lS!^#Qkx5&)L&w`Cq_+t2Yf zg0<=y0Re<`y)PJGFQ@VW)jpb9@6pF=)ysXr(HVWFk^6v4{XF=1J@p=aynj|bKA>37 zD!4$()V*xE$^k50Z5W%l)C0m6FrvkJrr-jlg#^Gmzy~~$-lLD#s>cTuGx|)^#Rtr{ zw^YEc>Y5}YugiPTh3N}-Y@m3 z#m}I(@yq4%w7<*cvKY@_9`&u~4t? z{Qd?kE*=v3_ND%%ZF?3mrFJcT2EF}Gwf0&+&-&)`SKC=G*ph%L(RK3V+Nvi3{pnlJ zA3h*xi4h;LMQ`ICeY{qE>-ob6Yy=;$Eq#isVAu}VZyWq^B9KA?f)o-Eq>z9hg#-jC zBp^s30YM502vSHukU|226cP}mkboeC1OzE0AV?trLCRm|GXpX(DN6eQ0000psbK*zGP`)12QaQIh&MoemowD;qiFnoEQ3K^bAQ4_{;f6&iOcwlv2()rDS2B zIF5r3Pg{Bt1Nck$zteXVX90G{ub*xJU<)KTILZVc$8rAo1b>1cV;3EoU+&F-XxM!Q zljnB3(PwYBTh+sH6+Vp-AFs7Gc?w&4Civ80X}6-6;zI`q$4Z%WE}^s!FIH^eXHZ>E zu|$XALkCAk#x6QEe|sH_^`?(sDW&K0c`!Vm&okEY^AJB*hhId`kn~LOtyhTM`7=70 zpNLAQ)Pr` zI(!-3XcxLWe|sGayuic^=AqU-m1bu!d7L~6+MZ8i%Ez~Fo9bYG=E818!SC^_$rwqt z{8WpN1pgp^r4B|t*dr!p@Q9}x#SH#DA38WXvNIh_9)B;p(9QYobTHPBwV0^~Pt=bS z>bv-UaIuzvzl1;YUi1hDaDunWg>sq#-Y}nFWOoZp>R@Dd8)RoX82q(-cRCnYjENbn z^3$P{Cp(wpQx49+a+?|??N${0g?#AX;8>}HQ4jWrVrk{ze#z;3ij!CEEta_5`Ov}9 zk&SgQ9e;Y+RbGs5u7gqE{b5YZ;EDIBvzsS7yYtDTn8BzAZ^Wk+krGF?^d#uu@F){J z4!T*OcRgd~8~!64EYVJu=ouY6sFeX1$k;`1#V3y!9Sp36dhN8H!F1^4$~3ul#|+*D({@wL?at|f=`}Z--`zNuwMTre>W-pTg-X;s(d=U-|xh{-|tlq#~{CY^@YF7 z^fCUfI+)W6yA@UO`PB=%6?Mh89v^cZd>HQ%`PB@Ebk`|vlg)Kb+_>=%?x1zRuva&!w!GvNmN!xTlz46TxrsU)6ql@{6 z@q-I3AHN?Q9C$A}Ix@Z=yp^ui@=*_73~y_jxelJzg-&$sG~dY#HrK(_AZb^l{C{kP z-HN*6Yx`@?mm|v?U9`LC!->8Q);7pG0oHtV0<8IRWO<{DcK12`V#8pAtP^0(H&2+7 zZ~mY|N^i2O(f=*;@!&$s*Y=lS#D@-!j_mCF!Q^qGYqflBf35lM>cQ6Sv4*#`ZGjG^ zb1kU{SI>s}?prhX7^n!fY>qx>aDS8Hjej`XG->(eCaai69dsrK;0JGgeRR;Kvw4`2 zT7I+{3b1Mpca?nT;1_(kFHKwTqR;%N z0d#J5z!4~&S%CXT?EWT;UxKb%jk@B?f#T$9)D<5OI$rRjEz#$yn}9IL(0{?u!B@ua z>)DJBJ_P!F8AW~fWB<{h+(i%CbeR!;^;1>P2lzAu(r!gv@wNSZ>vxmRTfX?y3%J5t zugwj9ZVi4yJ9V0+uzIS z;KB9DEwuT%oDL>9nUT}{(SQEt?~CoAO&1>FS3gzte1J~_B<)tz6<^z5=-}v(w+6op z()Sy`L&%%dgJs9l(ivvWSNB(bl9P&Y;n6dlJd1t)V1umr-V&zeYx@fw932@t*vGCI zI(QAPrh_k+;Hv>&J_`;cI5?2t;6Q?d0|^ceBse&b;NU=lg98Z;4k;u!IFR7rK!Sq< i2@VbEaktG3V_q@4j0OA`Tay zhi%LFmSz?$>G-SJgRN2fh~*?zjo(|V3Jw-^MrN;lY;o;Ojb-2C2=RWK|CPD-zkj{C z?|u38{qoP^^XsCs*XG_%OG{g8YHWPIroOWF{oniRjQ{WYyua?x)Stp!Hn-gW@_)F; z;_)i2;r6z>dyXIe|IN_2SmyYd8s7JtzV&<-Uw%3BTGaK&C02K1&Hw-Ue7kGDMa|`X z+xutE{QR@#Z`m%H=bwL8U0i%V{>|@yPbY7B|NCy~=k@ma@)v5X^5rL73Er0Y@2g_V z_u|iDe;zAsPx#C~YdgQkDZ2$fOYYezteowC@cfg`-+O-=GHEhS5>gl?$O(g$_nrLB VRC@()H33r*gQu&X%Q~loCIAr}*SP=y literal 532 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Vo2PZ!6KiaBp@9?WWX5OKH| z@vFOTPP}z&OP7kTSgK2rWxDCraHE2g3!a!AyRq$U*@oQP63N%T{ykqBdwhLq?EgJQ zDRt-m+C z_mAWDeyc}zwfCwE?tT!NJMX)kOnFtpx~Iq6EE*rP^a)$|Js4$R1j28|=+$x^S?l8d PfnwX!)z4*}Q$iB}(WdQ2 diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_nrx2_speed_change.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_nrx2_speed_change.gb.png index 955eced02c658132647fd047b512a08ed4abfd90..62cddba8d9aff7fbe6e0eae9410a2433348a9015 100644 GIT binary patch literal 1016 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-A8q*mRQa#?gF7M>f-f2N{-RA4|KK_3B^RePx>vqrIyv^?V_gcxehyN~r|NCrLjotmNc5NGjx4&IH zB`9zE?ZbSROJjH5&HFz2-}m>|&q<$;jMiUjvuE=&>&IWK?#9gD|6OnT>BFyM^`@Ww zYWef}=eq_x&ZmnHEUqj*l59$YroX*c(Z^SOs_Wu7G*7F}K9TuHo>|ABU zK1aN)?#~;U4M#1f{n=Pk!kF${6E+EGWay)PtImO<^DN;Ci5>Fh3r*t z8nU0;PTtNu&pMyaV~XX18WSKL@@MCRm)6PWi}!xYeeVB1Wxd6La=Iko3!p|~B|FM}3cJGf@ey8sX&1d#A{TZ|R&%!-W_gqP0 z{2sOIV}ow!^M>>D-V5o(m-6jM&*ND7XVZJm%)&m^dL4x8HRR9EW)=$lyYY;&x#FCD zs1b0lZGF$Fnfmkch4oLO=Omw$KU4p<;rsji8^ty}x9^K?nmRun7S>yLKlr(B`X)zQ<+R(3Ps`soR6nn}+_O7+USKMph0nW!W!dMSx$BWC;Tj5oTpm-HgqAkAXbwf@75jwsnSM`gckBS>Xa-ML KKbLh*2~7ZRrt1;_ literal 968 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Rrvr;B4q#hkZu11H^95MVtT zwr<5?&*`f2A!;HDkx7A-6|eqD9gbeRIX6PBXpTT~jDv)*OmfYNZ4(!*bN>_`9V>r! zlKsj5mOqUI9z4J8&oGbm;G-!`&K!y@>up3US|lEC%l&y`Tkgv}##wvj`38UWsAGz_ zesy12?Dc0Jd_U9dDy%~{*7rP3kDOL}-}%_S_nCQDDr{#jdo@utdrrj1Pu0gI zXG?3os#>C)U;kj2-K^Tsd3F(VCguj+a(XKhH)rPqYm@E4+cIX_)NZi47%5+4p06C| zxAxqpcfTI&-f+KWLB8v)D;H;N-eX(xsjDvbp3IU??NU9XG?UMjn^yFvo$Nm&o@B?R zy-&Dl?(^zj52B2BwEjN$=mPs{;rH_w%@(&jb=z5g`Kjqo<=;2ouVD_Pkm=RYeRn;{k%b;|!rr@+7WVk_6VPviOI{K@ux_r6Jt>VIC9 zCGPb(DE+r-qc!W?T7$CXigtI{tF1%#>DvGOz33sctoDyN#$G-aV#-^9e z>8CLCYO=;3uiJU){jOyfD74$zQ z@jT`Fk$e7USB4wZZa54w_z!bI;SYbyBaRy^IK diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_restart.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_restart.gb.png index 8999fc17c481cc818569b296fc7d2a26b067109e..0c220dc537142609fd106d63d58d20cd42a1a733 100644 GIT binary patch delta 1558 zcmYL~e>~H99LM*)m95gYN8K^qFxM$cp&zGjX?D^Hp_Dt7Wu?d|i$%%oyHio=80zN7 znttI#wseVpm|ulhqg;^W z&go@`Yb3t`r%hbMLWDLEfYABJP#+zPCpBS+(9g?V(Msne90_&!J|!|x?G>MR;G4dUd9R#N*B@JG9>r>`DZ{Kh z#;&q1UB5=20l$Sa$QnakTsEu+z=e7>40Qd_5NrEHXf0nUqF24xxQ<~u4BlcKpDwq2 z^UQu^1M2!cr{brS`{7ePShOH{4B_k<(GLjR+6kw_A)jiAw2POJGstp}f_AWQS{}}j z$mD>;Y47o@mM;*sUmzaSj#_JHXXaHZn>MsJkV?@2lqq9z6XIco)YlK9s$k&$u9;zw zxUqni9q+pfJ=5D74!b$`0W0f+L@b3W*tsnwa^{(9nYNG@&KW&Br3!xYxJwtS&Uugk zsfsz<$_5Lbs*f5bdWp+YnHdC!n?8+ds=c?+)+6O)NEIoB7%m*qT-=q^x$fj`xWg|O zyNkvNnP3fS_joKXIAwE? zm_F_|i(#I(E9FPfjYOS~E&``m=oXm9$aAaL|6F z09CTYXkSD$MWAAm)X1&P^OVAWDZ8 z;g%-j43uQ{CO=K-54FIF?4Um?o`{S;y$^LEX0}Z~rLtbIkpnq8xOt6egzi?7iJ)9{NdY9%GAJLyfJA1 z4ovPU@D?fGw||f&kvGYvTcdj%%NhYyPV)m&GEVaMWNzFdeSv?E%kMEqO}bs-8S-jU zrnkU2v=wg3mz@HLlMmi9NU$vLx^lx?vqLGX+Z;vW)M0$RqW0VRNO;z}s@lgJpf_<0{P#1y+Io>2%*;B&Fs7nRHj`gk7G8%14l;{2uOgNNn- z#VHgKBz7-b2@?N~47!smn5Fche8mK>=)Ggf8G(;T%0Ex x`Lm9jAZNe{c!?mm4Gk(4&*!5@KrND5mW%Ez!C;SAeu94hgvU-Fw-Q%s!awqnDeM3M delta 1544 zcmYLJdpy%?9A1fEmmO2eEzXL3I;E1Z%Gk7tpTm0y=5*H!M>I@0CT z(5H=rFny5Q)>78Uny@J?YZrIx{G2+U^ZxPv@&56ApZEDb&vOf&3C}jAErdZwI3a|= zbl>{8?>UZ~+XVgdq03hr{ecF`jjNorXsnHvAyUA_x%Dmk7p`ipyVbF@QZzJHHw5@65jy7M1 zo{oa?k8rdDd$L*w4XDn)$^yG=uMX9lC84PL$05+DRmW(K*w;XZMVng>Wjvy4`~s6F zTc$lAZ%)!5*vs%r=oX|ri$7nAkdhWXv1I{(@w8;5L|b1~eM7Ip%c9HmxvEdY?K{2( z9QKnJ>^Wke@8TJHJcy@q>==#hE1wBVkQRij)^r(z0{uX8e%|cs3mT<#;p2R!2srBU zuH>4Cc8~36AA>s`=ixc7oNnr_Aig{CYx1c$0Wo=NBCGR^@%(_=xA*B9$wkfJC5?pe zaMxtgV|#{;0gxFZ_c=P2CN6te+Y{Vz*~xcplo?`c$*3!s4Beg_Jn~C12{A3nuQ@ zJBZ9JLGJ<8y&U{7aaM_`u8jqyC)R*~*vpp~xUN){bA zNeK5V$+$T}$4vfF0WUBJHbNR#f=lQqT_;k^4Q{b>rr1T~kd>FFb~ z&k?B$@AikB+;VRg=$315c0gAUDe|k zc``=i6lysqAo}ws&^lLC>94R)u&5(94iN>Zu%eHoA4*I1 zb`~8W<`jJkL}Zo+Nkb;?GuO4);Aut)5SUO-Zq7v|Zky+?`sFn_=FN5j+!GxpqCXjW zHU85yg|>Qwdm4MwrQMYe*VQKpU%;tLoRHPJ*c<$Xs%>G@-g3;l_qt;(2->3R7S3Ky&N6WZ(?n7d^EGAr zpaAd+DC6*yKi2Nt1Kt^(+c$h)&or7YGr6Vnyy_5rzz`z2a@HNgjA&DE4$ll>BlbQw z6-ABeSO=I&id(6tPGvOMnkm=1BFziEkmey{q>Wg|mlA}cxwfAQENhi~C*R+EIAW5P z4dpr|R-rseBA;Ff8DB$;c+o;t0Fz>P%Q6ezq!>1DLRaLMYb1U%-eEe7^f^z+}3 z3LMqmt)h*kp~gn6`RhyNy}wPS&IUIWsWqA$#W|mroMVieYU6@V{E+72^+#|8FRKC= zjN*nw)*-Z7ZRjb?t7t(Y?E}ijCEsK9(EO)2tIV_?_h)mN10purB_E->E^|8pg`}R_ zfo6)D{ncE-U1Ua{2)}6YC>J@9Xisr&B<&b}Q}Z&ocDH!yf2y|P7#7g#A76K(V0#DT znQdWc1|v=W)AI_$DLTgFOeuZ12~9hXY@EaktG3V{Ai(ZcdL>w*# z&y4r^ulx6=wSQ^U+PRF&?|NKsYS?*F@L;a9-nppkx4-wLJ$rTId+l7?Prsht+LrtJ z(ze|4>-KWb>hJwYT)UO8{qUJHYqqAPJ)8Ob`TF;B|G%~SY+wHG`ttWi^-hiM)vN0n z;$|~=-sSmFU~#AB*x~=*%#1g`|9vK=y*#e^*w@XEKU!q3{r# z@ce!2URs*p^2@)MU;b$^*YCZ#?XO=qx8;V{Yu?^=H`e_A`S^A9HLKqKt}CxyYx<4x zY<>2>PmERjzCZpM{@VB(W2bGZ^t1SQrwUth`GfQGK5>}+U*Nu>)*%R}^#AsswfEPT zS)JSec>T5XH_V@W@1K4CEc%0S<>R{Cf0B(;7(6*Aj1uI8!Ik`-vBpzA&q#a>Ol=IF Lu6{1-oD!M literal 552 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 diff --git a/tests/test_results/SameSuite/apu/channel_2/channel_2_stop_restart.gb.png b/tests/test_results/SameSuite/apu/channel_2/channel_2_stop_restart.gb.png index 005fdf1f3095699cd6fda0a3ed2754a1e265a582..c0bcf5904e91479e417f5fd489c74389b6a462d6 100644 GIT binary patch literal 1514 zcmX|>dsNbA7{_6xxJ*RnrWrb%Ewv`*W-5v|PGxpcr_Q``C@t*h4i3sRjfkna zOz~7=PJx;8mWHU!dyy7i%byGH%0Tgw0=gf$?T`0)&-=XRea`23zVGw=;_vINx6^bd z1On0XId(Vz+)KcftD_CtoEjn>0@14SIqVgbcwbr+n=L^jwHIS}GZ7Y~ukz1bi_36v z8#i{AB7Vp#rI#;spCx|Kp2!V2Zr^T%Pi#D!X+vFAhc&+C1n0;AcEIrq_c%zrj!h|O z*ccM$!j+1i$*^bI0DDV09o`CLL*u{j7}$cE>~TRC1T4^BJ3OQL!JKcik@yVzbmW*T zfv0?abr@5};>?P-V;UYn9GtL9MVjYygzFb^Y;fi|ryo|!21h0xYc|O8b82ZqvQjWA ztqtMZ<-21`xNP!zPmA`R>dm77E!sbsIcR(Ax!- zmmAo85XoV6g?ix5;7HPH7c}eY`T`|9k|&PEZG71U9oE-5^fZ%Rli-~~whXBd>4bP@ z2SG@9M4|;Cp$UT7s8Ke}Vl1xvS3xoWz9 zTZ#udP(GybP|8uSBs52rBY=ayb?tAxe5M%I&EA!x9F`C3XUK*$wf)shZe5xN7aB%V zg7*_8dE;^o@Xcy@PUSAg+(Qt*C$jbh<)xYqpl{EOr+(aOk)*^x$U zn7S{J4k}gb+qFuxiv*Z^lN~M5mg9dJ$YXd`X2Es8r-8SI2T-AP;(Y1E@!>XFZLy#O z#TSDx-lSz#D-O=QgjuUTv|*T!B~)yopf4z%Qf7CoRIc8b!j8{W2>aAI6Z8dmswtSe z<@o=VjLJ>}rV6L&nan8aNCR=WYDmA6Cj1SIP)HF*WZQELV+EDl_|qhd(Yd8CR}t+e zgS?#{Md9^;>`w#3lJxbelhQ7ihEzLo{$?b3bH+Ys%~%XsxK3>$c9*`?3YT?Muewbo zas^2UXP1W})W2f$#rgHUi(yw;UD4ThLAZ5AKoZwX5Y_y~%OTvgq$!&82=<;m0v#Ng zf7k!y=X)NIlt2KNVeXb;_D&-*<__#iZ`n(Zz6{-TqK6`M+ zfYBUlke5717&z(-IkgtWO76*|M=2M;w@-_8wTE=EXhFF~+g#ZD!i@>~2M+kZhL%)j z@*2O2R6%oHFU?aG=E{gk31p*?CoSO5omRjWWn!=;g0ixBil^!xU<+r$Y;Gb|+)Wvq z{OO7~UT@f4Bx_2C*1l4QoS!wfL~rs;rmUOlO;`I5!J7cti!BS2Oy=FKpxQ~=KHznn zI`Az{L>|M5Zmw*!u1#+@Xo}ehwtOg`lx8A1`%HiXi_tE)PycTMOwftl-V{#?{;?M#W+BI2 tYn#$|DJqlf<3&5FnRnpK*nPESP2cob@aStE$`_pB5T7Hyhk1A+;~#MD{h9y( literal 1485 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 PBQbco`njxgN@xNAF~x!T literal 817 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V0!PZ!6KiaBp@2NumXkYGrh za#HkRUP0v7#&7bhj!D6qZC~sBT{3zOOLfh8IiYyo^Gb~~pXU_knabJsJ)ZGj@_1qS zl9J0m4TI+Oehs;zywvY=d&Y{m-um+? zXU+0OazA^2{(Q}}?q}Jag;1kEL5yD4{4h#y^0oRWk1cCHpILf+#e3bSsdA5(Z-33q z>zm&hKKuUfX}{mpTt8THvvkAj2me3jeQ&<^{BvabwS!OBecD>~{J45v@q@b)g~R_` zT{q+I=EBXT1wXr|E`N|w*OTKHX*=n=Ty)zb&PDfj|ITvW-9Baad4J32U!BfeRSoUC ze!H}7@z0Ir#T`{kYCcvZZ;b7q(ykG&tM>j~?dqSm^{wOr)9V6eKF@H;oy%Dh6?<{r z-kp4Hza{$iC)r5p+p|0^yS-j(d1jY;Tjs1XcdySj$Bmy-^Z`s z`+v10p1u9c)ps5**VWhWz4d1YQ_|lrkGH*kAbqx6mS=wbv-bx-O5XhOQBgZj;eOAG zwZ`EEd%qw28Fz5~{-(Y25@Og7Ki|#QwmT+a>(hF>W@CdnjSqQv(i1G04@*j%J5a#Z dHu8kt@xNc`dGA!&g}b1v;OXk;vd$@?2>`2EaktG3V`_hk1(}Bv>!Z zOp6!1#~WYv?f5me#vUvs_PzExZAt%>cweyuwFO#J_8 zOQ-bj{rmA&mHx#4pI?TadY*K^zta0Z)128%LR%vaM7@4wD);SMn3v{dlc_;@-Q}^> zM?ZhAy>7epTEA`f>rdN{73!?r+JD@lul#zw^FFh3nf9_T+E=4Bm&WK#pIsiYb}NwX z_qG4|r)F(^dhg?k^1bzQ4DHQ%4xgQwCegP0^k#B$o7Z8bG~FMO)=cAsJVZM*mV6K~6BK#j@&{_@Rr=Q`CZ&~A=UO<4=t{do1jtbu&8V% z^S9Qw{bw1UUjOv0!R6@lhR@}0^PRQZA@jlurF8aUx zr@!}eU;bEK>wfO|^M=3se%`8S+p=ZfmuflP`frvmyq`7L@t>F#d#)k-_q>zxZ+W(v zzUSF-KxRVt&(PunU%qEu{;7QKFVdQ&MBb@0Kjgh1ONa4 literal 827 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|PUYr;B4q#hkaZ9~P}v5MkR` z9DlL>^7N(lI}QhQWNxu&Jo|3F@Q_L{VVg_PjSyMnXhLPTOGPx`RT{}Q~$Z` zSFZC)d;a>ay2AHG2^L!)q(-jV8uv`?mq>4Y7vH7+hu8M+e*fT4;e_}v$DeL)Ho3U> zocfh(?wg-kKYQE!@Jr6*OG0z!A6_jQeb0I8mT6NSPk;Tb`%sAYoi~-g6Ra*S&nWr# z&F;q62YYK;1-{kBbA7AzFLr3U+kc6D`>fQ68hM`Mb~F5J?!ULQIT2`AFY|Qer^BDV9W2Ou-~2g0 zYl5tJ`sd1&3D%!V?Bbm(ZO^|Bt}U>PzWVk=!_QA%tAAybt*F`iaqp9#%f5Zfo*;eS z{q~7Ev-_L*ztw(dh}qeE`=8V&rbexJfA{p%;vkhRchJIERX-9 z@3Vhi?2hKU7Y&2$tUZM9zOHe~tu@Ksm;CS4z8B#}MjZ83u@zV1Pg=x?ueqza;|6z=7{!Xyi_o4l<{rrQ01~RONM+Whe?UmP3hlmz|wV+Jl>FVdQ&MBb@ E0H_3WJpcdz diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_delay.gb.png index 3d42c450c02b2ed5c9547477c003d259c026bade..3f8ee77d5f11189c08bfffd0dd9c692ba520a3b1 100644 GIT binary patch literal 671 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V{Ai&?h~7#spu zz7d~WUtD*sT7s`wYWl%zchYRx{|H2KFssaJ^H7MD)y&F+R5zS-}SZl_O{$}md7L>Uw?0WY;*kkXLH&&{{4FM$KLll>i$Rf z8RiwgkDuRrU;Ensitl=lYb8EJ-g_nA@ZPs!i5#27^O_yU5AXkGc1+^+u0F%Qhi{Y5 z@A&_FPwlxmpUs}<-1)Wm^i!j4xw~VhpMHAymHA*F^j;}ix@&IzLgIbm?ce>F$u Vwh70%Re=eQ!PC{xWt~$(69E3T6CeNp literal 568 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|VoKPZ!6KiaBp@I(FSQU~mY` zej!?G@q5#&L#h>T^rAdwJo$X@)Wj(>XZW|cPP2TzE%)@vZMntg)cfwI_pkizk?=P; zCHDI9>g%O{U!SgTe~DWM4fZPE!( diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_extra_length_clocking-cgb0.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_extra_length_clocking-cgb0.gb.png index a486d6c0dd5dd5989eb6b1ea0c6396e173b29ead..284dd155d280e3e6ebb31eee194cf74d0b5581a1 100644 GIT binary patch literal 1015 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-> z_v&u4*c5sG_@0n;tSthwxMvnG@nZhw8(6(jsM~Ml)@zxwvNWF_zi#6D_1*U4?|)e? zopSS|YV{}Y;_Jtr@9Y0_uBLj?{159MfBQ2jbJi=1K7Q^WIs5)I>i#V_UifEU=CwM; zPv@VuM`g>lEu6A{eZaFXkKN_B-%o!lm9=*EGn>i#<4^ysy7f40cCCqfUG=~HH*Y%D zDtc)?E!#b{Q0D%m`P$Dd`hc91|DFB`X(v?Io#)yy?fxdFZO^~2Vfb|V=lY%EQf-MJ zXD``uearJ(ac8eb?eE@Q!hFJh{(r5dQ!;0HY1-F&elLD6alh(gbv?uSXYa4?-F^V5 zapAj_pV=*Md;MSkQ|syb*!+q25Qoyo6Qn)b7tmP)(+tNQ8vhIwA4y!XGYH8t`da5@6^pL1v>lD?}li*)@{kr&rZav*Pr;@@b%!OHM z&(F{FH}l`P7n;3#?Vqimrr%(<`D{1sPxj9rb`NfbMcvc>H($8*xBrdke_DLgG!z24 nJf<)SEp2eo9Ay#;hb!`OojYB>&G!)lW@rXaS3j3^P6b*0|T>%r;B4q#hkaZE{ZKukYL-o zdHsvSFHdh0pZ9dbpOla=t8+1uHC+}ngm^WVR?cg0s+Wj$>@&;Q`5%N^3~*k(O- zexUX9)uh@a>*-4l*qpy?V!GPu^G0RW%O-0duc>U*o0xy<$E*i6Ommx(>vkW0s%IP_jr%*l!w*kuMgL}@z|U# z9o{~7f9S$Ona{PKS&NG2_S((XXFp>4d3&P7we9z7-hMj$A@1qz^vm}f(?379Z`;S% zc&2}@^WzOQpMO7_vV{M9^Xax#>{*fbYz#kJKl$9gDe3sz8v!RdpG{0ZAD;AR!H*RW zN`6#6x%v5P^cQQJnv<*A{Lb5)|Fiq2{Jn!`XZ)=FY(J0v`Na!wnt#fEdfA0F7n)W~B^e-`Wuj|-c6JnPwN^{QoB{*C8qcuYxg{RAG(b|5ve3bFq8bo!4=7&JlKveg_Tr-+fp#eIECb)B88c?RoeA&|%Z} zo8%6DeW3s3_>@agvhFUCa>ln+&)!LBCD)KB3P65v6y`S(hI^ zuY4Y))gmjd(Dp~iPw{sT=1qF_z&ZPfY4Wr0XMqNtU;h05qW7}bzdm4o7R%pO{7c>{ rWw!1AP)&B1g9Rcy?xO@!7(Cavm}IT1<0QTel(jrv{an^LB{Ts5KC_r} diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_extra_length_clocking-cgbB.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_extra_length_clocking-cgbB.gb.png index 6f909c62fed8dd413243fefcfe1779a2c993232f..1db7e34b7156ff1901b53c14ff46b5b67fc6af69 100644 GIT binary patch literal 970 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-mFv-C(=_Io}lf{C|tTH{Vj5?^x|yz0UXF zoS*$cUXsT@uJ^pWbG}6Vvbws+U%QH{$`}0k`f2;FugecM+Iju_Gr?l5-{seJ`|Evw zz595w=3LFn`fnE#{P&rEzxdLcK#iQ{#^fcjp_9F&-wfLE4EGj zcUNW;zr}px@@KUNmhzuHf9!Vgr}a-y*K0qxZg+0pGJLjiQuGg(U!F6NycJ=)wyl;Qn0>h&6 z^Ebw)i+?(QV|?B^S3bU0KH%QxhCe|-2i%zbJEP`h?UV0M@7Ku})ZKks_}l9B=aTUA zzz|8OetY@HB(vW+eSd!aQ9RFl?biR*GlFdRHU6JH&;0G#+mrgS&S-wM<^NH%Z|`dP zwckG7{`8G8e8&H%`%wv%=b6jq<}R%_U;g`iXWgyY_v;JoKdpbXz@4!($YGJl1PxXd guTdt!aQMd>EVH|d|67naFpn~Ly85}Sb4q9e04p%UN&o-= literal 872 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|T?Zr;B4q#hkaZq9-j;5MkYz zwC%;ClFet@FS0OaT)dX7a`w-rj}c9s{n8~JhI*%kjHab_8t&UztNh3NqMY`b{i^a^ z%lvbePun&9^rkbP_n0$$UjKZ##A6fR=wL(5Pv63~U0!eMzp*qSfBH}H&-I7IZ?0*V z|M+@W-sQ7@o`3rPna@vmuJM&WoqrbF>74D>pWt`BGU)UEv-VCLx3A~lnr_#2|LQ*X zk9$9C`TorRiS+k<3p$#wA1qyP^PbEP-A_kVe;+B!>J(jnz4EI=(X+~X&muoF-Br3j z<)i7rmCSRSUjO;n@Y%UaPe4Du&UOEx%l&I!w!b|;&+2{n&qKd#&cEJu&-VVF+n)}{ z-9MMVFYWZbo7JDbI?Qs;j+<-!emnc6yBHar11Ih^@I0o?)98dZrQNz z^^}QmSE}FsT=!=?-zUY{@kbl0^;^%?{58tkW`6%;`HA@}n?I-L*Phy&XIfqP_v@95 zpPz?Mo^)vW&n??4yDHv-1k$fHXP=$B#Cco1M3Qakmp?PDcig)@Q{T^UZ}Q&De+uQ| z*k{`Zvp=7#aDQKvY>pkntAt7?o2~n1mmTG~-n@Q>zFy?{Z%bc2s8Xi(3A z%De5d^y?LLN*4qd%(a&*sPj0_`snHTHSE`CnaA+&OQ`!{WugDAJ4#7DDmLwo3jg}% zZuZ~2pPyJ+)F1lp!;u1%|1tagY(dGm4Kkn4{a-bOdr^V~SKH!I0x1kWi%&Xex33^S RDhZT>JYD@<);T3K0RT!2o^1dC diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_first_sample.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_first_sample.gb.png index f2ca901cf18a6578bf2960124fa5b04e8564b2c8..4e03aa5746b589ed920b3b7f5a1413c42f9f03ff 100644 GIT binary patch literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V`d$GpW3A`S=N zWvt^Uf4s%En{sjn|@%7p^fp_SB*;dG5a7zc!aXpOs$x z`+DlW+}lsH>)@@&AwRaf$6) zzdt|!?0eVWdb>NRH9XAwPDlS`{Bf7X<5glq@wxW%6+g1_lY0tfj!Qh2x@Y;j^Y#CA z*4NKi=4a14`*#1W@3m{T<{jVne*U@t?SG3aA6I0q|F@*-huE3d6<`1J>nko{@$hOG eC8!n#|Cp}5X;^Bx=N>4b*0|VnxPZ!6KiaBquIp#GRh&VXL zeC?9yyS$5gRfp4bM_qqo?dOkGBK)0niqGZVp4Oau``MiQIq~b?Op=fS?83{1OVhy(C7dF diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_freq_change_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_freq_change_delay.gb.png index 6f0152dd6f41239ffdaf40da7e4e1b0d21341db1..f0b6a956039c387850d3791504cf33cd1d9d261e 100644 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V{AKtCl%5r>P{ z)3)7sv-W1r`S#0fAtzki*9)KX{P7^rwIl9DK;pOB``dGGr$2kOE%$cak5|X@*IHk@ zGwb&K_xtCpe_i#p=Jl?7bI+gI_vZIL{nJLq#^=wQf4=*j|MaGqvwgp}KU;tE|KH#J z|DT@Ov!=H8|FxR5XV+(k8|EKwSpO+xKZD$C2G70H4>sw9U(flWcw#Ty6n3fpI!SC|7QQ3_b*0|OJgr;B4q#hkY{1O0A0h&Ws< zysBGO829$e;i*$^HBQsw_*r!B`Uj)H1LAJmB#O`F-agixd;8fO{Wc-+$NoJ#AMyX+l2prmcdlPQS&{Jn^55UBch|qotex9q zSl4Gi*KX?W-yg5uXPEE(U`O$T)X2W#IZZG1eue$+Kgs@X5&QR#4?ll>SAX#M6Z!hZ z`|tjHEM*(F{`$K9lRyLh9RK&2ec!tKRbQjuewUH?e(Ly=zZ|yj4*RQ{#Xo*o_^RXF zo9{9)=679}f4~3F@FVdQ&MBb@0EgZOhyVZp diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_restart_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_restart_delay.gb.png index 259d9f5ae1129bcd939ef163ab4e68056aa13ee1..c4ee58a79b7a58b437a5a612e57ce764aaabc526 100644 GIT binary patch literal 565 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-9&ED@(+2_wp-+Jwq`TffOpYK)9`@Q+I zjr_a+JJ0OD_h;kxd&djUoH=u^IxTHpW$o`jFMh?}H~zotv;DtcQ-8{G)okzo#sA?R zi^r?HhU0~IYL6fO|IN^N^R-zs*FU`bZC>I3XS>$jto|Q$z0~%O`Tt*^kN5e@%-iz* w?K{mMGw1BMKiio@lW~%e!YDzlFjzVNsqrq~OHr@8fysfv)78&qol`;+09$>z#{d8T literal 482 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|VnqPZ!6KiaBqud1oCl5MekN zI79nYfz{eg9La5e!pz#1WuRKI!l+P&W+Umt%xr+EJv%lg>L^nY`H zE0~ wg7v4Fi}k0kv1okE(kE=;_h6JkL?HZT`629bQfO!RHc;Goy85}Sb4q9e09Z!NU;qFB diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_restart_during_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_restart_during_delay.gb.png index 9a1241f816ea9ff8e709856cbc6e6c7035c67b4f..02ebf2603415a445b0160363f813ce72a8a398d7 100644 GIT binary patch literal 1261 zcmeH{-A`Hv0L3qzTCMFMT3x%;uPo`hty?>dsbK}y&Q+~dJE^Tm)T#?E>cq+n%JqUZ z*u+{(O*@^NfDcpS+=W_2@e6h9YK*i5rk4uTk0D-_sY41_E?4FsvBxFnoZrKFI}fM+ zYHGrfL+>5}0N_YcVto2O+xPvz-)}!fikme6;M1QJf8knX+omb6d?WH$-LmLK_K(a& z=bhwl-kHYfC*mU?kVJ^9^;Tz)3Ru@(D+NCml15#V>aFkvj3kCeku%P21>L*n_(Vu* zsB@^B|3W+xD{-jKPa4~59IM^!X^7r_9RiP^yLInyCu-)iM_w;=RI}cOU|V8-Z`EM87d}jLz&H%M}R}kWdnmn;YG=W;{jKD za?>Dk>+7&n!ZOTn)=+rx@wqZ4+VGb+O67Ll$@?1b;DgUh!4n2D(<*&J%(Uw1f>o;F zI+(IO%F{e_Zg)4*3UH6-aN{C~53G)4EXMFPdX}I%;<2;#7lkx7=X|J$-RQ+}N<~h` zP+QfOj@)BSV~4C4tjG;J1vV{RIuJJIt5MdWtb0bb{dDHam>^CxZ{{=3O9owPT3BDK7ujWQ#>f_z9bN2t z?V6o|)ijU3LfLkOv)a_;td^~lci!kFC~5$W&#|g)B2-Q#?=85O9W4i=u}1-pHx%}U8*-1HbXYE1pe|qVw^d|WS4!9v=}ZuyKH1_eSuI( zid7y$U28Q9Jk%wvr3dwHtq`6TnrZLX9wi9>*MG9Wtl(~?#(L`ac5wg3fTS-|<7F3b G*8U4Ocu6e) literal 1092 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|N`Ur;B4q#hkaZ6DKWJ5O7P( z>Yt`PWA{YmCzIaruhw9@#b+^VX3f6u-WzkJ=5Ly?mecb2l%N$JnosBO=e%FPZu&2= zNBsT$md_=UITWAEI9WWdP(2pmcm44`)ewPtk3aiA%vIyAv5Max%P@z#>1ZbFaf$2o zc8)D)w4$u`#m;V&3jf{YeR`A5(|f6rXQs{a@6}IUcf9^hVP&yW54 zqT<`ZM>0Eoqn01p#Fw9z^F#K`%PaGX()<1A7NtM_-PrED{K$T>^L7z)&uIPH9Cmc_ z$LY7(wkeZ;HPy#- z=N!9p{JU&xv6$@BLJP@1f3hb%+glU;ck{=zt!{sNXG}kI=KIr}gXa|3i~H_mtG)Ep zTzmhre~YE*jF0E6zb{%W+vFbgYg5|t1(649&vZQ4`n}XID*pd;*{Q2H2Y=bv^gHYA zuls5hWeo2HbFMvR(LWs&(|7%TfBBM62mhRN{4u5WgWb)|md;d)UZ4NG`o-FLb>*UW{q28;T|8L(>tDyVSC;vrxna@U=j`ME*Zle5(jQ!kHgf$D zA1iFGWkWqPhaz|yZn`5J@bqFmz#N?eLrp; zTmNp^`{~Dje>=FescPHH=wFBCOnYEg{&~l~!kV;o>z7SgCU$%C&*P8VU!A|U+(2pf z{?k3{o3G2Jd`>jg-OQXPGK=}9P>jay8Ou^vf8VuRw{FsX$@eAyZ1gKuAB|6$dH!f+ zP+fdl#61C-ef!zd;?ojqeT3gH`P!cOOdhE2Z`!VXvp*`$dVJ}rr+tZq-q~p{*;BTD z`50~a{ptOzkH3vR@6VU7*?srj_v)UHb^Gt^8wkeZ#*3}p<{`$Q9 zZFBjai~9CwcWvB%*C)cS{Qc(gHH*H#eawII|Ht1CZvI-oO@IHL`XBMU|9<$$uyi*k v6bk6@H63-_V8J2Ae(;e(#Hb)5K%U#%RmUDxwp#oiR62ON`njxgN@xNA@E{T} diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_restart_stop_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_restart_stop_delay.gb.png index 3eeb53263c5d92a60143a7b5715e3ba941f08213..87ed4935413b12719ac278bb478d8187cdf2b7bc 100644 GIT binary patch literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V{|z^vO2A`TbL z7e;USzU-=>eV5OiBMTEAMZd6Iz;An&qfuXM*52=SeaYv~+%1$jZuj@gSNGTFO#7dG z`~Lg>dHw09@BY=BetP@onK!fF@4X&nZ2b9oo$-1*`?Xu&r9J<-_^11~{dNC;MxQ&A z`}X)}_s=tD#?POZWY^AU|8#8~!@qncm9MD{*=u)~oBf%xedf%^7HQAy_7s2Xdj0`s+7)^N)Y6`demaym^~G-@FfRW`8=pKH*37`G564ITTeLf|yiB e32KGGS80aA%&9ZJA7lll7zR&QKbLh*2~7Zkro)5) literal 513 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Vm;PZ!6KiaBquIc6O;5OHu^ z`K8Nlj{j=C7#}HiuLTRA7;X;Qmn!q;K*pM_-)iN}ztzTP|9$wU>g$e@xi|j5{#U#y z_jcv`ZMppF$vr!-%_{CQ?3*3G@n7AgInU20*Us&c{$_ute$S1^ckiuTKiTlu^B*tY zt=zM_|NlScJ<|>pes6p}=Xv>@rl%r*!+xJXDZKo}!QU0%erpxXaSoevppfY}r;K{R eD1(SVsAb$;6Z6ZpIcGB{YCT>3T-G@yGywo(7TMwe diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_shift_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_shift_delay.gb.png index 259d9f5ae1129bcd939ef163ab4e68056aa13ee1..c4ee58a79b7a58b437a5a612e57ce764aaabc526 100644 GIT binary patch literal 565 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-9&ED@(+2_wp-+Jwq`TffOpYK)9`@Q+I zjr_a+JJ0OD_h;kxd&djUoH=u^IxTHpW$o`jFMh?}H~zotv;DtcQ-8{G)okzo#sA?R zi^r?HhU0~IYL6fO|IN^N^R-zs*FU`bZC>I3XS>$jto|Q$z0~%O`Tt*^kN5e@%-iz* w?K{mMGw1BMKiio@lW~%e!YDzlFjzVNsqrq~OHr@8fysfv)78&qol`;+09$>z#{d8T literal 482 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|VnqPZ!6KiaBqud1oCl5MekN zI79nYfz{eg9La5e!pz#1WuRKI!l+P&W+Umt%xr+EJv%lg>L^nY`H zE0~ wg7v4Fi}k0kv1okE(kE=;_h6JkL?HZT`629bQfO!RHc;Goy85}Sb4q9e09Z!NU;qFB diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_shift_skip_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_shift_skip_delay.gb.png index f2ca901cf18a6578bf2960124fa5b04e8564b2c8..4e03aa5746b589ed920b3b7f5a1413c42f9f03ff 100644 GIT binary patch literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V`d$GpW3A`S=N zWvt^Uf4s%En{sjn|@%7p^fp_SB*;dG5a7zc!aXpOs$x z`+DlW+}lsH>)@@&AwRaf$6) zzdt|!?0eVWdb>NRH9XAwPDlS`{Bf7X<5glq@wxW%6+g1_lY0tfj!Qh2x@Y;j^Y#CA z*4NKi=4a14`*#1W@3m{T<{jVne*U@t?SG3aA6I0q|F@*-huE3d6<`1J>nko{@$hOG eC8!n#|Cp}5X;^Bx=N>4b*0|VnxPZ!6KiaBquIp#GRh&VXL zeC?9yyS$5gRfp4bM_qqo?dOkGBK)0niqGZVp4Oau``MiQIq~b?Op=fS?83{1OVhy(C7dF diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_stop_delay.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_stop_delay.gb.png index 2de2506731240d73107cf0cdbfae60205b01fb5a..d315c994644e4d95e462c8c7ee4e62e5f933574d 100644 GIT binary patch literal 546 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-+qKn?~h(~)ApR=rfZ){wC{oe1GxW9jH-ZB0B=FY$0@7EWf zi_AWI=8R=}TH3!~|2AFQWxe~|{JH;A{{Q`EFISf;ceo*Z=hga#|Go`N>Uay*e*HR+ zZGFk?nK!G?pQ)|5ely{{P?7nnHn3d>&p6OISt;YKDOwqkVKV VN6(Lb*0|VnSPZ!6KiaBquZOl4iAj06N z`Q(3f{af!u4)NVz3sYvUOnI5e6MZ5(t?zM!e&6F6GugktU;g}J?eRa4KUb%HyO+Io zW@Pr>bP0l+XkK_sQ(Z7{@j9a!8%0<2I!sYIW4sW?tqo2K;nt%WCT8Iyk}0bh;@u(%JEXTryUv zdFeFMZCusJzC(6ZRNOhTc&C9Wip)nmlTx5bR% zo`6DXcLasElo3iIRg8&a&xD9~Y{u}*rq|E(DHXXbKgRW)uuCmMee)v+|CNK|rH`C5 zIFXb%6{1eC>N9IefAqx8L zZpWPdg}O>C#tSW#D-EJ4hM%4GgN)XmrsNR;h@kmqH#@$#F_&P?zCnP}bdp*9mNv;N zXJcE7EgKe--T@{nM}u3RY=lvGjhdM|ve@ey<1XU4+`yX=t8IL?5XG3fuf^J!W`4Y6 zm2^H`orj@d7Sk%1Pv%hOOniL~%ix3qL^}!s#eivfVPTXx~@R~}+EU|_o z%0NU8Y!003i10g4pCV#mC=dbLB*@yYU#j2X7>!G6{+UsCnbrS;v2*1lqu~7>$}Qwvn*y(@F;!DpTT3HB z2pYhJj)HO@3IN572CqMlAY{A!)z*o~jN#5b*hjiq+Xf<*C&wJHl zX5x4?U6ihg5*y^1T+$GI1iDV!3^P?hTInTgSQLx|e&E}&Sp_E^iO{(r`F3fIg_scP z7y=kK+lvWDC9{CvU3FnFM_F30&vkY0DT}Dxt@BFCOH+xZ+JbIh3QYFD- zWZ!k4gyl~x>d1oH;~n&przRZ3fkqsr2C>#Ay7|7d97!SOcj+Dm$DvOHE&LmqIvcsi z#7dcN)6*Mu!m?wQf29US$_CW;^J~EEj^jT&fc%o;w@)E6FEQ( cUnEu^{Ii%WW!_be^C!BbrlckJoyxEK2Xpe04FCWD literal 1254 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|U!iPZ!6KiaBp*2Toh9AmCDZ z?)D1JA^yAl^%cklycUL^m6^}$#Cc~Ld9JjamHs#)S;xNDV`r6{mU#As~ zwWg{J*vKZT$DV--WN1r%cd2wNXv;b;YFJ+jP43Ox^T# zOLy+=KI!SN1Me&E-*;ud-tB*HUr+zl`P8z-;C%6=zm0452VFb!*?<2|zsC<28~xLq zJxOoU=~J(t|N8X0boJZ#T~|$S?O%R8X7$4V=C#w3Lkb%wG5bw9z3I*Gcl$r>d;f8h z?w830(&EmC>(#$lH-9}+damJ;WT$QO{w@2DT#F5fF{^C;SW@`q(VNvF@xCwr=zUFo zv47cpR`ou!b@t^~k6-&aeTine6=y>1^&OoN*G(_JmTkYAyfQB8(~s+Ux&L>S-|i2t z-*m#p`(^j^f4TdXW#0N;D|Te<+1Rh_yE6ZIKRQ#{*K^`t|LfPYw5#5har}R&w%XP5 z-hx-YUO%eiX5GsT`tr3j{`;%lU+2qBu8RK1_vOz9P1#m=dDGXss_r#cEB@l+ihQqm zTR!yFvLkLvm(Blf3H`VD+xwiaFG?rBjn_Z1?f>QeB3^sHzt7Jd44-BZyEwO6Y-7x7 z_xgf8+iUCVpPTH}y*S^RJ^taBIO!>T2iMo{jdKmJoA+Vma?f3vM)qsoKgsObRvz{4 zn*I5M=9lOG4=`0u4t{59x^iyF%bgo0OJ2BDUtBBGEYlx5&D`nUrr^CHYENUbC&qo7 zH>G&3>#uA7X21SAcN4RBRnh_RzuT`Z-<`p4m3B$jlyP#S#A6wsSDa6Gzqs7})OBO*N+>X_;@E`TH6-a^wkTeD_==2+a?8cdi3k; zed2E~PVn3D<@bX-KegOLtnT0N`ua)WlGFWL_1v|!2bR9PWtVqxx}NS*kn$JW=MRd$ zoaDaZXKg_Z>ydQ*ZFcQ{t8c#kwSE8hy({wH{ui?ax#M5)-UG!OR?6S4|9qcayjEs@ zw&@qgH!718f7`Dy|FZb^b4!Ui-tn`2Skbzf9G9##={K<6AwHvNGOmnu;7u9VdLRrW@~3`9-+eL Z>L*G5pKxP&GN>eD@O1TaS?83{1OVBIXgmM_ diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_wave_ram_dac_on_rw.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_wave_ram_dac_on_rw.gb.png index a84ad439131ee5fbcc1d076dbc599350ce86f327..1263c3b37b6c9f8a96b2d8c03020a930ab37e5b2 100644 GIT binary patch literal 1731 zcmeHIYf};k6xDUYrm1zU&E8_`I=-dym0N-}Dp{-KBVQ=kq|s=_x`Y}JVVt(jS86G= zY2ZetN#JHGX@ZY3HB8o2P#MrskyO+{5#+t>e%X)v2lm6gckaxYGjq?GGq>RBLw4UR3 zgP|KTq2}orlh~&bZ=|--z(v4CL_QV{69mb}4o-|@FgYJ-h%2rhPX((aG+_{%L|NFM zj3Y`Y?at5FWd4r>n^Myakv45U1c1%`DX8m-DM9e8rlbeY50!#_XTAB&e&TUm(}|ge z{9l9l_%$d(}2V%I{H$fR~l)e2bq?caFlYWxfBf&BvF_bG}wNgUoozSknO*>6wAW^SXeM zUEhp0I}^hVP08B?iG8U)RFzKmm@UfE84QP)G((UFYt9;A5$Nm4BUIrgoDcOmo16h# zOLfMvIwCy<0~2`B>gHq?2W!C6{-!w!I9$b&dO%%&QcwTlNVGX!V2x4(3(c4GWHcJ> zPLjky9JMqAhKm^OV7tp}gxE;Yaw~JXI#`dR@?E_7yM-C^2E5vuFA-+$D}+5nUWOxk zd^KlQ&b}LkYQ;*9D&F7_j8a>l8LA|9cSJ^lcpNCUJ*5L@Y_C+5ky>f&;i zYy^_1=o4kL)*B?WEh+OUFP!=02lMNGn8em=Otjdi;a$XUGTwMArH?MqSU4tg1A-@D z_I6gCPE3{JB4zU{lFCbR5@pLBP*aLqKQ^7MwBy({&`uBKUN_JyW0FPwb*j<4u;MY% zT^65_SDmgM*30$#6!+HVE{{}`@oDf}|h ziPNg5!ahv!E{xEip4()oY<|qh(6-IuG%y>2P_M=($j)Cu2`BtD5Wad7Iu6pMvv9?Bo(F zz<0f2b3lMK`D5AM+`uNzaMwJcyP{AK6@YAk24==-rYLHukdhk=YGROFf2G?cimG6K z(yZL~T5>!SSt{M96JoEY+x%?SXfNGq=0RPTOn>zTLS!=9tsDp8uEs)S%LAeW+w| VRv(l=wLB-Qh_I+o_Sv+8zW^sSXXyX{ literal 1480 zcmd^9YgZBm07YksW|5&`q_|>gx_s5CnFd(%QD=#%rMNOZIn#8eXp|3-He1cdG;`Bb z#790tQVca8BY~5fQw+pMG;~6lA{e3&qVjO=>}T}h-h0lyU+#x{Zr*ux;7)U>IRF6I zdG?H7=$6)Q;Q`Qiiva?S007uleAe&Og$$CsD(dR&=eu*UiUn95A}lR(^@o3)IUX;I zz_T5u;K^Om5c$K;E%Wz*%-RcYPnjv;v~!SY`P+%z!iM+^x_>vlzUNa)-5a4W?g31R%{D9=Y-ey8BT4DikH z8fP`o=aIdO^QMPGoAAU#h+KQ+<2<+Tjw{nFW5I#KC5Y^toCz2ezcam|p?7xNHnG6- z#*`y`epnAr-WOyzmn=7NkY5_=#cxsvu+Zj0`+y>zYTe=hoV6v-F_x>Kl-xTrSHk_GTR|ult0ADms4Rw+-RA(Eo^p|z1{#GI9bDU4fD>c zSy-HzXsm{>HyFT=-6oxL>S59iovzcCN|uYRGgGfAm1bq2HEA+I2DFRFd%V7;AeGg; z>QvFQ2L9H(=y|hu5GqP@R4&dq1_96-;`+03uCvmEJ&>>a?4tv`l;3j&v^X zE9$KrV_(n8FrA-%%;9iX>?B+bDeAhy$zy>2j390Jh1X19R4L=J*h{!H+&s0zr>32{ z80qGo#1$yITTS_sc=BOkxZ*sr=0!q8Hmxnbmw^mLwqI?^VTEIJ=oNxENP;U1GTHHM zKfZDTE;)niXRN&o@(ENGB!e*e1h zIWWdE3>8E5LJjHoI+GaAN-)3t0uO(bcu<^Z5Xvf2=ItJYXG52@5n8R%OT!+CP<1mw zP0;)uoAtB&XDCcS`x3O^O-a^wwu6+{rFc0;jX~qy?iocF?$~tvhFBh|ztz}x`Z|Y4 z|5yTTby}5!xK1wp)uTnI*sgp04GFDYKZ0e2UjwEZT$0SVAHWV5oyMvL)b62aG6d6- zbKfPacnQ?Dy>dN-RVD&?wT_#TM!*`=`<0zE5Py2*I;_4z&+h9!>Q1~Nk%P(#dLAL1 zOWBQujHIh&(^x3G=Skse`U{!m0ty)+2R)wwQz>h{@5eabyD>6$N8*%}DlRsPj=}`b zN^pu%MSfhqtU=kV`ruF;BnU$oB@R*~zefMG%NlHy13_ff5Y$-_RLQC~H&Dbs(*OTb zGwYZgrveT&QN4S6^|TgMcP)edrEtTS!>EqQQm&A-x!W)^&q)Sz;s_?Ugx5NSsOSdt z&l<#yMi57hY48MX=no;k&Dd>ZLtE=jA~!LC&E4_r`e-`JD;m8PE!@}Yk;p*J4# znBtbKz3Jn6HVwd-C!``Rmkr`~Hph3YeikBK)v#}u_uDCt4Hw*Dt7Wg^Ckp&o;)5}% smL5Goo69B^ZpPaoM!8$zCu$4qN#iEFbSB#*|2_0sf3#oQ>6jn?1zpkBg8%>k diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_wave_ram_locked_write.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_wave_ram_locked_write.gb.png index 7310a68b8dcd0e72c7f779250a07dd1468b55cbd..1c8338256444206264f8fd1afe26e1a04b3f1e8b 100644 GIT binary patch literal 3248 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 diff --git a/tests/test_results/SameSuite/apu/channel_3/channel_3_wave_ram_sync.gb.png b/tests/test_results/SameSuite/apu/channel_3/channel_3_wave_ram_sync.gb.png index cfbb95cf6bc6b70f53af95486c457ea311e59800..0e22fcadb3b3bc8b4960de55bc9dd94cccc83e90 100644 GIT binary patch literal 982 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-M{TmXv^$5cwlU;Q#=Pdm%Pfp6A&Amd&=_plpq(EKh-wM^Y+FqnIe{)y4O5Td7J+8 zpmWQ&-jiOx_wl#-x;LtIZHl(1*lXFp+~WyYk=CHO^jY?vFSqTKl$K?7b);6LzLDI1 z{`pi(&84sB-21UQ*S;_L$(;44E&5hJ&Xb?G>AeNt$ElBI^mlZ8{<&wzp@-~ax+6Z?F=@$c*z5j&f{{gYZ;e%0>vb%n3B8lPW%UUNPF?*Drg*}t#-y7lq< zyUq9Bz4-ZfKJ&gq#&4~ye?7PV_5CpOmFmyOIh-UH1h8pLVdP3}7-dp79InLQSh#HN U!4H8yfccfd)78&qol`;+0AXy&Jpcdz literal 810 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V1XPZ!6KiaBp@UMyT}z`$_v zh1$}KOnC=QFEZ%`{Y>yX?4cmQT)5}WZzUr!qwEc}-$j#U+8$=7Nwi5k?tJ`o@nz?y zC-mRUcl))^|NPtEb%iPKg1`H(ul)Dx{9Qlgn`M@txPLDf4#`_@`|ING`tW&b4fXHi z8rG#I?64`|P;B`yZRrc4i}@Gle_gwy@PEPf{Lt;Y?PF_Mm)l3*Uzpi`is$J$PqR&C zm6o?}S^qomOFaFw+q3L>ehJ^6r<|9~_}TID{m;+-cj9ac?tHY?-`jAC=O*{bbDyGC zoyxiFD4xA_U47xUq9tc6>w~A=Ui;7l! z9}#b^S-y^2AO0g>YUaCq#g?tRGkOfIEuVS>o!e$vui0{SzGd}w`TwuBzxdQ!^X~nL zpNh-eA7A^stSIs7{Y`~``!+p)`Pnx5di~G$4{rZ`mvg1+U-bFsZ~rZPocHHEn+5Y> vNr`g@3fS6=4dygHEaktG3V{whk3INBv=E2 z-%3`Rf7*1f|D5Q;9kD@&)=n;7;&J$CnDDNqk0&pBt=+13I>=iye12+V*5?=Vzn{x~ z{^i=!8C@TmuG5CKmGLa68Ham%Hyn;Z~w>h^Xo60^WppR=RMcIzJ0#p=VuK!Yj%sht$HK- z{n~c7Gqah6N>52VtB-f8u-)DNz+b(RrJBWFBcIP>isb|SQ|CX&WUZB-|Lxn2==!!j zmI*gj+h_94WX?N((}?}e^XuhpZ|4?9P6L@N`1$F5*$w;RyUVxB<(tdo)lQFJ{~l-u z(AV<6#O~zO_is=58TW63WkT-u;2jhHHn_|LT75#MAo|AJjg|{)^dVM00J^v1H{)4G z_VfAcf8YC9{#+I4#&S!; zspD(;W;1Kaf49it+wt%7wgb-^KJDFOsSxU&H=U-M;zH&N3d(tG#3+U3{RRt~Onw(Eh$Z z(Dk1#f=u1by65`6ZuXj|7c3wApZ8u+=YAjmOy+x4b|(Mk3;fKuCo@5#_yEw!r6Aw? zpWmJxd1}UN!844VR)4Z*uD$tvPs@j@Gr(Z!|Hom*u5wD|L+7V&%tgB{&bMjLb9+#; u_`dz0^i~&5g+MNkDNI638-^3}N_b*0|T?Ir;B4q#hkZuVv8Ot2(T$` z4d3!UEp}RMK)@AN#Iuv@R$Pu^y~tod%y#oZadADGXn zwf*g-se5{o^t9B-=ku=KUimFb|I^(!R;FQ0&la!Mm>s_07t40-ORo#77k-;xGj07; zpdn}c&%eL#8s3`!xpZ5dMd9@9f2DQ)@-h3}RP5%lAKu8nEO^`Zt^E3*ln zwx`xW=J$h>KcjZWt^ZoQ{`%{~UZ))^K_cI#zC3n5jsNUzcK>tF6PEiM|4umH%)IG2 zr(C-I8TX1qedpZkn_hob7wD=r`1R+8U4_xbYx`|$KlJUhY<(Hrf6hHR@R#p3U{J~= z3xB!(sk%<@R_I@sKQDF^)cB}M{btSnF7oD0^VVu9pnj`-d7t#-!h5#Lz0$7A-WS%i z$9St{{QHG_=kI(R_3~J}u-Z?XCH#|Sc_|fKvuN}Mf{kyZa1AXAO68j$?||}?r>mdKI;Vst0Dhmxk^lez diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_delay.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_delay.gb.png index e2236b163195a1d1e9883394c4b755891f4fef7d..62eb513118ef8660e6c55228be9c418ee43afb87 100644 GIT binary patch literal 856 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V{=hk3UQB-#RZ z-_lk4E|z!g_eM5B^M%q&_M}+4J4MCRhlbm-BwoA}m3`S{YtX4jhQ7hG|9-i?{oC2< zZP(6jKl;mnr~AD*&*8gY|4p2+e(lz0CC2X|OJu4FZAy>|6_#f{tUnoE5yhj>M{nXljb z==JvB&uq6|dp&P$>1Xf97SpcX`fAws`0m&88SBql_c>et(!L5*AEP(@sPUR>QF_x) zmtOX-ug$){Cq4V_pG!c8S)VoFDZhR{@t);_gU{zO9}cN&$|t(d zzyDNbL+!n&hw(pc6avpNcHRQ=`M2%2J7xJm@chL-yZT?n4&}erT%ON-&(8R_^~7IYEO_NY!Bxi^mj(P9cR+K}3LD$v>m_e^1TnBl5r;!{F)a=d#Wzp$P!r C+j#;2 literal 728 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V21PZ!6KiaBp@2IjRWFfat( z_|hfkr|)f_!J)l6bg}62&--*P=f9&Z1>t$Z;PwA`m>%Tt!#UHC485QrAcKq#$#;;%VjjZEl{f&QZf9!_P z?!W7ner4NN_;+G$!R*MppK1-}{7%@l<-g{y$L(v@{YWL&S*RM0r^-WLP9aaA)H|Tfrg#5`>)z(|e|IYlKaG$;W z#R<_n=kj9i9~RwNZu^!iPk!!~oR<%OKUjTz`oy?-UFGZQ|JeP=^4~vSQsUfn-U7BZ rV}m)34|#af6D*hy1H}%EK%rXZX35y2E9CA8f>NxftDnm{r-UW|j}l7S diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_equivalent_frequencies.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_equivalent_frequencies.gb.png index 6d032ba3ff6d7cb45c7f0da664a4083674c685cc..ddd9971ee3f62829433e7b17158765a55af6b51f 100644 GIT binary patch literal 1558 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 diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_extra_length_clocking-cgb0B.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_extra_length_clocking-cgb0B.gb.png index afdd0f77c8cc1f858602c65d6bc607335d06904d..adf5da7d33c7b9c56588556a5ba35e2008e3a41c 100644 GIT binary patch literal 855 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-EaktG3V{==)PMHBCZ!- zhb`-P9_F*{(?koyR!;tzb^_NX{Wx%C>BLR9^Su1N-mA`D8-DfV;;q-j&)3x&u3Ne4 z?9a6~-Jc)d8vVY~LdLI8mao0r{`8;P&%y8a`d<$D`dW6?s#m+`>o3c+vfftu=UVCZ zGy3yWf7-l$KE3b$u5*9B{*+H&{o017V%k}T$;a+bS+(lgt&<{&_8w@^9bImGwW&`O<%_S-)!4^uH=zAM=Ik&h&vZ%>n- z8~;Bo@8#b4a}A@j)r+6W&;S3U*5UKb%HItxxpmz4ZrjE?9XtJe)3-cw>@_)}O_x;#>=DvCS{__p$<|6lg z*X(&%T#{Q?TXVVs91Q*QtN;GF^fCJS%>8!zUvGbK^oQN`XV+hEvpa0OzjdvB_51hb z<|lWbe_v$(V5xw-Vci9-@67+sf8ITZQGac9%00;so1X!lu=TXv1Bo|3{@ne}82__w z7cg*uP6v8-`fq>F`)?1||9`aaX`aLmmj)Nk2^x$-st$`-Jf<*o3Mm9Ksd$YF;t!Bt ZoPFoBWDkA4Sq98744$rjF6*2Ung9jle4YRR literal 777 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|V0qPZ!6KiaBp@UMzZ~AmNZ$ z@T=?OZdHA6{TW6&8cC~Si*4?Qn*Dqn)}OkhZOWx>xeGVtMti;Ne7^a^`m}wL^X9K- zpLKhBs^=ANaS*_WO=GFYA|`T;2Th7w32VgHJ*~{JDPjc1c$5gXvv{ z&zk?3-P*QxPUf`l$)BIzoq7CPsc!nU(!Upeu0H+p&nN5RKNlwbeo^y3XZ^gNn^iyF zeWrglpl9QsX&euP>Za|Uv~5k@d8>NwxT4Lw53@h}JZWld>?6BGyKnnnK7UkI>~8hb z?#9oLH9ne8kqLRXbm7zxTJ$@^b!F_4RzuUe8}rdG2%g$GG}>dZa)}( zZSUisN1qmHezvK%)0=+!-LJ*ZmuLREb39SP?t5YO-k;Z>w_Da6ooIJy*Mn2PfZASv zt(trxQR39ss=s$$?4BRr^SGjTiPZ7J^1p(gzuIkeKX&@*rrrB$Ri0(P{k><;@#xpl zoj)&sZ`gbO`@Q5>jGa~9yv6TbIi7E>;`*1ay!^rcvVGr!H^@!UuzkQk>pHXD!HM5Y z_r@E@U1HvLd&|Df>}#%X|8KNB^H%JAyQ(?&tLIn0U_5as|mhSHC^;{r2Lr`^?+! zU$2R`eHC5h@SSwm@+wC{JBU7!1J!gPcJZV$@g2WVHdHRR~W$` zQ9l>xnmzLG-h96My`gVM?dyH)Yf69ayfOLwYewy7UmNC}f!M$F_ix6zO~2<|XBJw@ zZX!PyL5H1r&1gCEDudl>JFk~NcN|}6`(W>FYu4HJTi9z#sw4ia*e`r3nZN8?rL96B z(2lR!^FIB*{<>!0Z@CTAW|RwlxwS`b!ivN8({{>8&fCfHux8hL#%#ZXf6u;V?EDpI zn^3jyxSXCXFsx=gV|ruSey`^PkNtt-&&Rnsg&Y>KOweFd@nRTG%oX=5|J*-XouC#0 P%+Cy-u6{1-oD!M<3d8E} literal 950 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Rrdr;B4q#hkZuV#{tTh_D6v zZO?dr_Lfdzq4xpo;`Q-e(G<( zox64Uv$HiXSH8EqTXi$Ic;cT4#d2+lRY96dpS@kV@zy=5yHoY&FVqa!KHc`~SN%!< zr_P%h>w50o_2vH=FNw1rp2-bV{cU%^Io~qz-gD=_r=5MdS8j*)AO7_D2QPlQnD$}6 zQ#;4hMfG~va`|bhchG9e7#+6wx18n`N?y7>(Y78mi{lOu3UVj z#ItAG`@K)z0To?7`EJq0-7hX*KX~!QUz_irrUpqJFI*vUyin@)tM%=#viF`?e)(L( z-k(>O8`^fIp1*cEd*=D`yPL~zeU)A|>7k|B)_I7RHXZ=r~ng7qa-u}}& zclFP!%75Q5{_}s@f92p)W2KKDZ6x|i4d(9Ie=m0LiLiZp_MFhOin5E5eUtzFn9PiW zUoTus*OHxkF#PoUxf82DKYaCI_0!kO&9n1+-Z=ifY9{Wkc_hI>Ef&pUYSph?jNs~=fEFBMM(QeW!+xu0w1pCiBAVE&7YpO-Gb zw6VBzpMCmwnI}8mzWJ$qzx&{IK$Mf`#tug!9eJMNjmlr!Ch1q<3*XI25lPe6gq0 zK<0Hq#XT#DdE5EgjK%-hrGAQh@Aj-h_K($LAz79BC46=2%7G_-zkU$=_vzBo4W$KM z?;b=6ujA9RuiJnB^!a=5zh9j<%lz|Fb&t8XtmT2?C8wTu=RR7~{IKSA_U6N%cdmba zkdZT-T|8#d>?doRUp`4FEqGt^k~3)ew{z{Qo2xJX{_D1QcfwDdf9XpKM0nf}4soK6 YS-8#o*vssTTR{2I)78&qol`;+0Ktafvj6}9 diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_frequency_alignment.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_frequency_alignment.gb.png index 751a11b294ef672ee6236e8bf3f9a10f1a4eef57..5d9ee0dec89633c72e68a0c1fecb1e9679243fbf 100644 GIT binary patch delta 1571 zcmY+FeKga19LG1qNE(VM{7jv5@8K>FjadC=aucOnouZm0ZfGaVLvpnTY`MC1tgP-) zB6%qEz$C6aYRN-2G1jPYDVCXaFl&BTyDRSIo^#K+|9t=Xe7+B#*ZcGSe5+PJSbcRp z%Nz!SahqbfU-t3rvHQ^?g_}S+F+L)sst(?Y`es2V5yj95)C&os%820PJ;%6`YWG~5 zj%78);z;4z0pDlus?vCYp_|H?01>H@5J9Bj`SFv5-cJns=mw{}!%xN3u{=pgc zfUmuh#vc88!KR}D>z1p{FPy3i@EobyYB*&0?Nn&bSTf%xE|ZYVA4w$WY^uBR?pmJA z+qx8N@lit)-2OP=dqF7IB+jekO&Je<;l=nJOIrS zFu;C)F48p8Q{v4;yUDoY>%K}ZxzmQ+MGOFC@lXagb08Ei2$-m@ut(u&)J^{D^?xC{ zaX8oYg$X2{SRBm(_7Rqy;c`ewt1gMDFKWDAxL_zWJ2!LbTwym^1uCD`<*d~~mj$bm zdmHnTd(LUtkG)wg@R{?|@E!Ib=6?T6#H7nsFS3SB+XgdWxe{Hyx#x+SUgp`M}>efwCYv2YBRX$b6;W zE6v z!~nLaPo~p`b?9c{0;M&k-p{KYv2C6$C6r_si5we;w*WSYsf5_LQupS~Ms}asE|9}Y zc&JNKWHQ>0$Pkg|Q-|rGAtxk^Y}~ePyNtBQ3dQz&!f1P@Dq`gbi%goJXiz3ugjvq6 z?e|HMbe>Xb*jK#g_~((QMLjsZsC85-Fh$WCzA}8w(Qd=^X%5DSD~o)xXQS!Y^GM%P zexRA$2vLSoXxOQEDWx=7YiU`L={J8(5P}*iE?fY#W*3zfj%CDGIECY&+P>lYvZ-)I z6ziMU1f;E90XvptUs86ZDvqj)C{vEhYTif%3z4U(tsLauL#vWe`TYy~b~jLPCRv<13{Ksy;_Soaq7% zoanzx5)(t)zC+#Y?rC^oN2ZfvJO-zj69d$1NzmNk?Q=@#Wk{GPP^?m9KTpZu0<+VO zY>{|B(arilga?@Db9UG-npt5PzSDFNvx)C9ODG3%x@;l~jD38pX8A%U!z+G7Ab8i8 zW)vx1;{BkrJD>S{EJkP)xfv}PrmHtPuNVQ@O`fOm=vv@_lNu)+1k2;&Q2_@>D}_`M zOA+XX#|9tGb&krdEM*iBD0p_I{!bAuLC37U63e6S3W9|^nYA(xSOuh(3qrVqtYRU; zL!MRLj_^<_x?~!Lq4OcZqKEOS^mpD3@-zZw25zeYi4*INMTNmK5g6!P9*dVU;HA?RCTssS!Zp_Z!c>#*oxK-b#5c~8m)Uxn4elV zH>O{=nH!6QVzTTYY%h>VFdr zYgD$R=v21lip%s2GDHhf2(*#jfK56*+Z^i&*y%J)i&b%<4n79|FQMn=2oJ9Fhl~1p zg0DPt@&4rx!C6%=c_xTQUszMlDiI=bJblLDeo({c9U-+>4!_=x_)R@y6__TF_7w{| z5o;Q}98t|gE)v^TyZVSkk%nFT)i%VI(IoMB~d delta 1567 zcmZ8idsLEV9By?SD>S;I>GGSAwlXhi>N5O1rD9%|nPy^|6prR?N*5=Id}~;ySV~qW z4DBSQnR&mIsTAdnK#@&lZYsB-|w9F_x#@Hd4A7%D@`Am7Hol5 zu7Hn3D_OC^WIn+6KzKU)ZE?))$z7&dee99b4VO=bHQ>h(qRJpl(yB|5zdqV=ZBb3E z3JqHokg`pEv7_Zstt80TQ~qHFEOU*OF)3K<^!Z6uFX=R8n%t-QpuNTnI9f>T=MAt29Wb(1*C`i|s{MWK! zEpvQ93l7`Y`fF{9<6(I4Ui!RdNl2P_W63)8iXxO{5`6Q5H_QdE%0~{X#4|^+naZg* z@rD2RnR%XSke9v-Z7FW>3wiZBLgYw(M`zYIB)E0AX?gP=iUbu;bp+B!KUpO7FCwia zU&nr;d@cPh=X@uou+vjx0@4%#)V*)+iqMno1im0z?SqUE+_ir};C3PAT*63Q1{vQE zz_yi*OGx*ovLu{QR5{A}!H}*n1Bezlc}$YcR3F$qHJPanKy3U5;9AiH{p$S2P;WoB zq@KpYSaUu-Pkwe<6QftX=-Y9yVg~!DD~HjpP@eMwOs!x!`6yXE~YxSe4Dt>yszfnAt2fO zO?)6eEaMVB#ysIY?{SUQ>P+Pzmjym;4Ae;A@)l%VrAoL$ZZITHI*!S#GxjOIPB(B2`nRVm1$NtznoHkD07C9Xx% z@K=K@C#CM?lNtxHrF4Rh>~Q(v;DkP-A;HM@EW+5AxDRNL%9gdx^iz9_It&W2(^z~;d z+|JbPe%Y)Z^turd%K*~!=K_Jeyj#b}wjlu3X2HPkK;cNYM9af!b=&W^J5Tcw zJt>%+e4J#WvFqvXNOj=uIy|=mC8yQ-xd~+)gtZD{^RAGbGnGjj6rjZx`>F_Hdj=r< zNUm9{C?zu%#!&7qM4`%v)}z?E|GF_u@iH>c6SRzBh|rv}7e~}{1tu+B9-E$KiK%+g zqF4P3AIe8~HM`D<8-v*Q7`X1&kA^Hl0Bq~%G9m3Myy)v(D_jz)aJJk^E0QMIe-Xx+c-;48J?kyOwj4S?W^_QxR?>2opWcU2y&M+M`^GDJRtb%TK;;0~-=T6!y$-!UXG;(N71mBpV&jQ%%WPi3> zH_H-d>1|!T+w1!+reKd?i*i#`yqlQjBy;YfmZ`t0D0NzN9=YNzw{^?OGCFenww8ti zqF-CFm_t~YHdELApY{q=Nwim{v!VWg7!j+y1m1CR_&m{Ke0crvLx| diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr.gb.png index 9a4ca6a49e32492a2c7e26722fe85115e61d6546..92e5341cd4868bca8bd294f8e41614fe89745295 100644 GIT binary patch literal 1451 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 diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr15.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr15.gb.png index 8ea5179f988465f679ca53748ce0978a86a8a019..101ebe4dd41095968bdab8aed94b837e5afd4f92 100644 GIT binary patch literal 1513 zcmb7^do{tVO!xo(xc8oW&$-{n=YGyj^ElTM)Ynh+U~N|r00Y$+K6`a+y?CLByM8r2NQ06(jzo~b9|uPF_vnRqK+0YJT^ZR zW4R>k$yoi$NvE$SRZ)pFFlcHn4mz7ZRx~oONJ}&&_)!zQY=`gPe5Q)G?G1CP^no7M zSm3j!B(IfHD-!`ydSs>yor8{a;Q0f<$4-m(xI(SQ!uu9eWmrq3uNVtk5R)Y`?3_2vo))` zqeQ+ySyp5Dh*F;}o~69jFSteNnxZ)gmR{@Yu(H8K^8m7AKvi?XpiS4nzf+G0~CX93j;1iv>fQ zC+UW!isWz{Q5F%<{sGZ7xH3T>zbY4lWf=wPGA8I!7IcG$h;~SS*dEPe#00a!X4NM_sU$N-*S_SIsodoQchpbx@-aJ1 zGS{=%5^>!AJ3J{0S;7@x16OxiYoS)EcF@-k`__&GxNg3!N$z; z@h0WIhV!ZSf`pyh?k*87S()y`-Hy2^x+ijn5PUV>@m@e&k5g7RFgMX75;$;xbD^<pmwb zVdvB?JvT#COj6gR93!_!W9@S{k zBYZ&AOd$x-Xx4aaK?dq=#G&0aD$b5i|j;B3n^pz zK7GI-t71--VuYe@cqcRRXqjoP6T$JOhrb~N!T)24YI^k(jwtL~hE_}YfpjZtg^fX@ i6Od?bi1{DO0^%>KTkxe{(rn-_T*J-flrzhTnD`F>7y=yt literal 1398 zcma)6dsLEV7-zLXCPJ-a5h>bQ7iWcL3W8+w64J2?WnPcQMAHh<3e(Ucqswt#=ydU@ z37KeRNR~L!Ep>FGa+VxZOwGPVCW(j&pQeu3$FkPhUpwc${PjHV@Akae+qY8f=3?i< zU@$vcP{0o8E`|2lIkTV>rk+s4VBb(^0Y8SMm*|D`!y0E~;NE8}_m_U^3#?G%>z$7F z^H$7K``wwY01|QsBGzJKSI$=%CxcH67-<0-y=h4U*j2%roK2DY;Lv>&qv;`0-%AE3 zOZq1T%H;D&zV%TW*q?o^ za9D`f7yB0mp6T2jgU}pH!C!W%Q0u^@ zz%FNLH2d7q_^F|wtv9uLQ;9-(S`^+_M%!L@cE@8CDfP`5x3zGmtfjGLz60;lOW_Ec z%U4j=bUP3GzHT*zqfw|qK-*T%4Qn@2@vsNBSu5^*I$CuV^T*p&osJWt|vpK z1^suK33`ERCqFt11b|XhKB`s1m*^cbigvNDQXL%}{o&1ErW=nl!_esqh6Mxs9;}bk zknI}dluRbYqc0RZIo<(|g8*}!Q#=gB`v5r`&NKKBenA>->D_0uaz9 z7Nr#nd;Ps8pZqztac?CuJSN$zUaEp0u-GJDl|NU!A2w}ndtk_UD~Q>1@zOgwf|?Ku z4$$&3+qOMtQiw!x%PE(Nq5M3Mu{%-TDeA$qRUg1lrioFg z{L!N-S{3$yZQmk2{n_G7mNuJqWLM}o-D?Hme7@dP?4HDzExVX!W5_vNM!;m2Cr^6a zyV3X3#&DH@snU0~5lN8f;}dbWk;XW6+ce1bjQ6DQOzR}Q!B(P?Z#}!wap;Jg-ut)D zyK>1Cb5r~H{gb5hrHy-ndw`)Jw=QYl7`8%VSuI;a6r01A*CYJl`^llxE;Hg=!5RmF zeFOhGi2bmMIz@|xjG}LAHq@F&-#JEo#(NF!(VbRv3peDK>{f62?zlp(8=Y1t6cc4) z|MU&ST3l)h=b?wEi%fUY@_4M&Y97UX5b%VkLZuz*RSE_k%3@NU4;U;82!H>{}4hb7W(DRnn(2@3k}dp)BW{ zGgJ(_IXcA^tMneC%FK?CFDwI<=k}35nf9!jpM@uR)c6)qmhqF-Ru8QTKwbWMKB6dc z(Bq?$8O~;6Uc<62=WWhiis)x^H3o!}Lah$FVXi_AGO3A0>dV5Uqen_>L*P|6<<$uJ z?lbTbMm4{=xfzc^7uw}DCWY29C|y^qiq!tGzc!4l>h^BZ&h205-Oaon-{O?36pD_$ zVv(7{LG5uTI4@SyajSA9QPnsB!Q5s*gy|T-$4Lq9JgDTnu>Tp!*6DSMNXOnoXc!M} zo!~p9`fhJ!VW1dQH!}P_P11rVV0vwF8*EWX)c;&hc@C))hR_CLto1*o1#S(v>Bl(n EH-I~~>;M1& diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr_15_7.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr_15_7.gb.png index 9401ace66f035a22502919ca8cbf2bce41740dce..dc1a67d258941bc275b5ab63986dd86e9ccd566a 100644 GIT binary patch delta 1829 zcmV+=2io|#4XF;08Gi-<0057*V1NJs2JlHlK~#90?VVwA>naR|ap!K)*+SOjY?k-q zm5ibS!Iqq3h4amHreG9gMaJb6?38oP+qUJD(#y}bZMksfaap&nYnH6*+O5|=5$z>9 zP9;XIdd(4g^!YD2=e#USO8-YArIgYq%jNf8`CYrn_9Z4IE`POht6p=&9v%OGe?CP8 zg^)A(K|4U^h6zZZR1jkrqASL&yIorIa3z$IBJ6EK7Pk9?wQ+A1|!y`f{bL z>)NgNu$T3EWPjMUty`~?$Lhb1Q;AWlKBe?huScfa?bfZ=$%lyl^g5tbhqL;R?%x_e zt37o<0_7tz%;+S9%t!SW{HGCb!Mp{ZWKSKCK%ovOKRtA-ova&~1xB|=&tiM$^$Gu7biiC0jtLoo>j4QA>VO1_iw>w0 zR+mS0K=gVzv^`qKL--HZ0cQt&r?GzhR{!BTpj8L+)X@;@@#M&W&_U5UBwPp7cD3@m zb{2c>XMe5SYlrS1?Z4F?t^>}lgIW8RApDmcvHyq;$n}7vh06%oYByq!z5+P{UT_3# zO(qEO=2AM~j5?TS|7ib1>VW8x)_VF@|H0vh$A9{L!IC37;FbDE`_FyBoP>m%gt+Vr zM&CwAT-=vq)vjzpTDT@4laK7hO$TK1kt4Y2fPeCL>VVV%sRIr|9dL3TP+l1k_XSHL zJ|ECCej#;02|@?6+M{*A=>1a%Bv3x019CkeX#rmksN3-tJXinJ0SS~#>40b*46UQh z*gsqcjLzOWg~l5G=>5ZWKv{>_*K4)+)X^e7pU~WYZ4&ayGWw5b$&B{s_)`ZYP^t-d zlz-a9gODDsI^bw~-h!`VkB+~K4!E~HA%(AcPY7BEjBbyJKU@d2j+~qT*+9o1t^-OA zttS(oiFm%C)m{=&$27+;x(+CbXditNW&QrB0}?3i+cHaLADM)Z`3SEAN+R0Neha1! zNT7U`4jA1YtpiTE9&q-NGx`M1`u(}RCx4_kQ@b5s`T2md9Wgv)F4tYvo=$`hS7^{&i!?R#a8Am8i1>w7|^&Fr&VGKPx|n8gf7){WX1qU&(V9S$L8AMA$_QASNQ@wD(6cKNgG=dv5!bWPe_Q`>9T1Im zq(+Ai?Z37E)Aj{R$3}I;=3oa(4+&F^0sZc#yfjiUn{rjJ?x7wvaK%n z+M)eN+gsyTorL7d)iJ{Tet&zF5F&wZ%hYzVZUz|xeg0N^Yy8|_R71!fxG$JMxzxVk z*>$w&_S69nU>&(951%=H)Byz%pHCP)e$)YtIu_bKdiVOA`K60bKK5P6=(*Y%C>VTd)TEyoI zTH|N6x1Rqg?+MYIISD~eBCL}DtN-Xph}GUz2OPP|2$8sN%REw+{cr^22uR4saNQS7 z$X>om2aK-6q5DVJht~noBY(9ud{+O1e|>lzP}W6!K40%VBdkaC^#72Ki`D^Wp9Naa zUlK8X)By<;R~beoF4qH+5#T~JZ^66;S3jtBf`oW;DIIV|9nACmBgPM|16oH~u={4v zcN%p3Ty#KvOy9!Kd|$Bq%!u#XmFE?4J)!md-E=_xN9L$~^gqI_{(sNj|G!jW@31V} zmu#oZLPl`kmf56PfLWaos?~4XgdVSE)yW@p&Ow+TM zb?dsm*sbf@t=B(C?0bF|A6xYv{?FcjcpcD+_4KX&&)z>=2h2tt%xW)*_?`}Q|B^#G zZq8U8P;z7zMtiJ1b$>t-8~KN13w^xTzIJTSB&Jqw)zcntFTc}Px!X<_T8t)ONi>~Y zqxKQMfZ6S>2}m7~V96P)1NPdD=uouB+ROToIv|OSRR^rjf(aSLI)3rtL<9;GkU(Jq z5-3bS0)+`kpfCXm6eb{n!UQBxn1BQd6Oce*0um@pKmvscNFJat0SOc)Ac689Uarch Te92Sk00000NkvXXu0mjfgZQPj delta 1716 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 delta 1716 zcmV;l221&o4!I4G8Gix*0002j_tyXb27pOKK~#90?Ojop;~)$p`}T_M73NIsW!aDH z_)$zKCb$t)`f@-e0tbpFNhib@ACE^!@OV68jHAls@OHb!U)*lDVo3-=O48Q$rgTXP zC%#ob9AEm7_Ge%$f^=N06^S}Ns&(aC^`r6sRz5~7fIn9YAb)(p1TT&}!P~ZN+jfZ* z!Ud7L=%shjoA<8Dx*nkI@@8Mp{)7;o&*zrl`F!qmqsn(!uh6u$y(wMtNj}JoG_OE1 zWBwnHj^&g(F4js1bbM6n%D3wG=3DjC2QQA1HfdiXWFMaGxrl3^=YBfP!|O`b5$h9$ zp-YfA->To6Z-3QKAG|sueehI1*s7mC_zlq3Gwb;D_~5iEZEJ`RmXF`>clqr7elM1! z0vdg7cl#hS*xKJ7ADk`)F+q`^GPC%o*2GWGp2fvlZ#{7R)t{^SQI5MSu62d%$wKG+JY{(srmCu@JR8hM8xi z)yK!z{dY@DA3RWxyMC>#p0KRvZ~15gil)a~|41Lq`@x9NmtK8F7HcbfeWGybj6u`m zt)D)4bwv8$sdmv;J@mnYfi|AF&qYt_gS+*3>5s3wgW<6V((zHPi7#E!aj_OnkGKAw z=b~FU1b+xl-7eg}514Oc^e33vodTUcnAx2Mkv^C{_zci|Cz>8B;C!(66)mSjd(Qb= ze6S$2`8+FMI;`WPT30?=f9Zo4$4DQ{`@w+NVvI+gCoR{UdP%WX4)<7>WM^;T%h#%O zL}Fc%w(rC*_UOX?(g&}O=kJevzAAjtPmi6Y7@E%WVe?NCW7{nKTY=r>n zZ@$U$i2t7nW7APQe_m@6+apVbZhg>Z5yU6JG!F3LoXpsK)_}~@}fr4oF!PatJ zd!a8xYu{VX*ZE*;f4$qc!m0<(2TO_f{ax?ps6l)%-2GtbfR2x9O?+=X*7m*i&<8J$ zQ-6=mZm;)!c|RBsa6Wi5`aD4+U!QTT9&7vF<3k_3IwF`4mJ*QrwchP}>w)vZ-fM%k zzg9io<7w5?`8!!5F|1ND#0P(D22ARM&Ha@=r~R+TSzA(A;YT-L=*>^NnZ?CgH2s~s z=yc*{g++dF+f{n=^}bYMU6PFkg8m*Kyninjw7FBDX9i1LoYtJdQ2P6SCrfCJeBv6! zUSBI0iyU5ZKiHcuB_KZ7%7^p8Xt1tNXyfVbgXPfa{~&#g58{K(;~8I9+Lkz4d~iz3 z;$m&uS{5JGy7JNVTlKj6;BE3J7_k#i9?t{^0fR&G?|LhEw`S%t=Yk%Yu z*C0CkU^$%BN22Ms>T&nM*7@zN#|kw4Ry_lriDNRIzFm3@zb+s@lmabKfQi^rj(TRGwbt??CT+mryron^>_ZE<1J_FwMpp^wD3jJ zhG9$4^z;7*FAhEzy*i@vbJ21L@_(GOl|Q?FI3H{U^}j#C$(b5{FGJ4Y4n5xELm&Kx z@2$sseCUG*C&0UXGhZu9MPxeaoca>&)B)>&dMCiUeKWr?c~;Jmh~XWu4ybnmyxX_( z>4R5Cqz_(t7fm1h23Gx6J??Aa`I%b0W`mkElg^mg+s}6g);L9-6MQxuY=6Jre!j`d zTB)G&@B;ya;%rOPd_JCf2F%_*?*}iA{^z2JIS?oRoh-IrZy)acU^L%3d;0^PiLs9X>iytNZS*UAkSqGz`2F{T`Lp0tpx+1o z{QNlRAx0zi`dayTao?!vryJ1Q?WGHM1($(Otd;eXDLuZwMCEDg2r zi=Qf%qywbmqgoT+ss~NKl}{hMI*PNcWk%}4OYR3-`Bpt>`px{K z;KjiNFAgSnaWKJ)gDnYO98B=yV1gG16TCQ>;KjiNFAgSnar^^)v+FMCS`L~30000< KMNUMnLSTXgwvYb+ diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr_restart.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr_restart.gb.png index 640db3d070608a3d30476206e7b81277ea748fc0..73ad2b7b71c3891f8ee498c715d97c8821ea3268 100644 GIT binary patch literal 1453 zcmZuxZB&wH7=Fh!3`Rz@A8nM$9|R1(wRlmRhI*Gg$K zV`*Vcbf!^>A45HYTABG(jgtZ<8lokbC@CWHvX@rt&wf15^W#4EeP8!=-PiMT%&v$P zj%yqN09X+f8AgKMQs{6IUqN?49fb`54w+G5A^R95lQ+|-YOHg3;$8V2(FI|r+}m&@ zG&(^#;9xEqamT;Z)9&n?kr39cz8=Vs3Yp(7P^cu;3bUx~rx+dvDP(xvm4F@`OLSzQ7t9di;#GJosdd17jr^2c##% zNSVVp_!9i}QTA2E0ev1}C9`-p5*+8kp|N!8>{(M%aJ?fCC-R9x+YH0#l+zuQyeSmJmFm<=E2r_jmWNd zlK$F>>~$2_-QdoB^ArEM4}7O}av9IUp7-Fpqsr1*!P%9&k$JweO@o-2Z0Fqb?^@Ps zpPqm}uLDx{EDwjHGlIDpQAnO!*O#^zpllM9oj3taR~y8FFE$?Dk&E%|DX<(@`IlCk zD)*k~9~&vq(pB8XB&K2v71W^efGe8cs+A?WNZj;`xQnp$s==Age0AIF~3?%@S~x z?Q$R8?P#QHn{o9nMMHnV`|%^IUCSE%>b4wBzn z2+M9Mr)GE;83DaF$z?Lo8E(LJ!cI+1T52uDlGgb&+o0n0@XNAZPDJ-Zy)xUzrXs5~ z`OrfQO7fPtUs~?&SO0h5VZppf{4n5F3PhWeuHSLJf5M51`-7A64D}2KXpxQ2rOuNi zS_21`-byf`=rrSn)I|@LbxCa-4Cf9R#L}7o$%} z3kS&vwT3*O#x%lXPwx4K%v%LjX|uql1vaPzXgeTXn4Lro0e9;jt(P-C*A(O%K>7h^ z%x}-mlz1C{EOtq!@-1wvK%3PHM0`^5qji|onRytSQw*I@Jy1Zvvd3S#`+4|pd*kma z=}97DBW)(r**pj>uOJ#p?lKM7*0)K+eiwRXz2~SmgDQze@_Mgy7!_0O(HzPvJbds* zoZ$cr0`eX{XqK*FNA|nx4F-|PzV&F19xM$J^`C8$Jy}QV7HKNZL1t%s{&_x;UwtLQ znXpeW#iGaeXNgoSdPMh#4E#VSwGN6;S6cnE!50=CmG?)4CKHTBB8J6V;X6$}UI?$! zS5uIUitN$x2S%*9?x@&yg2>}U)gw63-m2tbwI6k)9BgT-0R6Me+(Gj=)R zk+CrB1-m79XKdtcAjHuDLD~?^4TW)>9Z_X5?EeiFN}eshP2DiBt%ts0AS!%Um@t&W F`3DHs;f??R literal 1365 zcma)6YgE!_7$-dgPvg?5vjF8%XQeGKd8uPwC@pU^XKJMuQrnqnUx>ZHZbra!`J#Z#(CG-{;%!d48AoIT9NaiCFBs z7y^MH$WdYOwtCTaPdGW)UWj&93xRxrBZr0V1t}IVjX+Js4!rxSGc*|d^=FX`ft|N+J)=6&gRnMg+qeNeQzz|@Av-gRRl8#hT?JfvpXmD z0!1U~3Gyn!0R|h6_3%G=@sr6K@pZ@)vINW8%aJF_hz0pvKSjEHvVZp+;}1aUE+yb_ z8c|>Kby=>6>gW$?dSd&}7f2_Hjw|7)E5~Kmd$wyRHhhu%cFO8?r8QLOT z?py7&>X8rf8xn<+?WP{U#;NKI_@q04!;Hvp$V6Qt5Bc14Tua~QZdo>+ghFWmA{*YQgNGf?gMRA15~pr+D6FnK7o_fKR1qdnU|-dsUwr z)!LcUN_@}u-D5zu9p*xGZOIYZgCT*k3 zs$Yj>H^P&W#kNeXmmr%t-8W9}9?q~cu6a&nU#ed9zzPzkEk*TX;_Z_Dn?CX?c5Z|A zc)*4;>Z0<}X&M?3fmXLg@H@%kc@z>^gvR6VT0dImccGD7mF&hZ9}ay)h$;tCHDHd( z7D7bERdJjUOtC;k1I|1+hJTwdPy7&^m3ks>kA0LOY-c8-PegiUb1hQf(bzV%@X-{* zJTLe7M| z+waVZJnOn_m4AzB(DGm&wMi(ENOsOq@EYY$J-(97K?Cm=RLrNM@-`z88^IKqKp>!c z6TDrV3!gUSf`D|ZJq}q_+AImDR>tD1u_{kyt1`CZ)Mku0h@_zA2GPf(deek#oktvD z&9Q3~2|?^{?U{!qnrVhFJ*VEav_$tYm24gMC_K+RW(N1*^(+KL0JXvPvat tWZUGxKNAV3sd?VF(0cxI90vD4&$;;+$u4TNXjE!nm*nu6u;wi^;eYOOo@oF8 diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr_restart_fast.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_lfsr_restart_fast.gb.png index 640db3d070608a3d30476206e7b81277ea748fc0..73ad2b7b71c3891f8ee498c715d97c8821ea3268 100644 GIT binary patch literal 1453 zcmZuxZB&wH7=Fh!3`Rz@A8nM$9|R1(wRlmRhI*Gg$K zV`*Vcbf!^>A45HYTABG(jgtZ<8lokbC@CWHvX@rt&wf15^W#4EeP8!=-PiMT%&v$P zj%yqN09X+f8AgKMQs{6IUqN?49fb`54w+G5A^R95lQ+|-YOHg3;$8V2(FI|r+}m&@ zG&(^#;9xEqamT;Z)9&n?kr39cz8=Vs3Yp(7P^cu;3bUx~rx+dvDP(xvm4F@`OLSzQ7t9di;#GJosdd17jr^2c##% zNSVVp_!9i}QTA2E0ev1}C9`-p5*+8kp|N!8>{(M%aJ?fCC-R9x+YH0#l+zuQyeSmJmFm<=E2r_jmWNd zlK$F>>~$2_-QdoB^ArEM4}7O}av9IUp7-Fpqsr1*!P%9&k$JweO@o-2Z0Fqb?^@Ps zpPqm}uLDx{EDwjHGlIDpQAnO!*O#^zpllM9oj3taR~y8FFE$?Dk&E%|DX<(@`IlCk zD)*k~9~&vq(pB8XB&K2v71W^efGe8cs+A?WNZj;`xQnp$s==Age0AIF~3?%@S~x z?Q$R8?P#QHn{o9nMMHnV`|%^IUCSE%>b4wBzn z2+M9Mr)GE;83DaF$z?Lo8E(LJ!cI+1T52uDlGgb&+o0n0@XNAZPDJ-Zy)xUzrXs5~ z`OrfQO7fPtUs~?&SO0h5VZppf{4n5F3PhWeuHSLJf5M51`-7A64D}2KXpxQ2rOuNi zS_21`-byf`=rrSn)I|@LbxCa-4Cf9R#L}7o$%} z3kS&vwT3*O#x%lXPwx4K%v%LjX|uql1vaPzXgeTXn4Lro0e9;jt(P-C*A(O%K>7h^ z%x}-mlz1C{EOtq!@-1wvK%3PHM0`^5qji|onRytSQw*I@Jy1Zvvd3S#`+4|pd*kma z=}97DBW)(r**pj>uOJ#p?lKM7*0)K+eiwRXz2~SmgDQze@_Mgy7!_0O(HzPvJbds* zoZ$cr0`eX{XqK*FNA|nx4F-|PzV&F19xM$J^`C8$Jy}QV7HKNZL1t%s{&_x;UwtLQ znXpeW#iGaeXNgoSdPMh#4E#VSwGN6;S6cnE!50=CmG?)4CKHTBB8J6V;X6$}UI?$! zS5uIUitN$x2S%*9?x@&yg2>}U)gw63-m2tbwI6k)9BgT-0R6Me+(Gj=)R zk+CrB1-m79XKdtcAjHuDLD~?^4TW)>9Z_X5?EeiFN}eshP2DiBt%ts0AS!%Um@t&W F`3DHs;f??R literal 1365 zcma)6YgE!_7$-dgPvg?5vjF8%XQeGKd8uPwC@pU^XKJMuQrnqnUx>ZHZbra!`J#Z#(CG-{;%!d48AoIT9NaiCFBs z7y^MH$WdYOwtCTaPdGW)UWj&93xRxrBZr0V1t}IVjX+Js4!rxSGc*|d^=FX`ft|N+J)=6&gRnMg+qeNeQzz|@Av-gRRl8#hT?JfvpXmD z0!1U~3Gyn!0R|h6_3%G=@sr6K@pZ@)vINW8%aJF_hz0pvKSjEHvVZp+;}1aUE+yb_ z8c|>Kby=>6>gW$?dSd&}7f2_Hjw|7)E5~Kmd$wyRHhhu%cFO8?r8QLOT z?py7&>X8rf8xn<+?WP{U#;NKI_@q04!;Hvp$V6Qt5Bc14Tua~QZdo>+ghFWmA{*YQgNGf?gMRA15~pr+D6FnK7o_fKR1qdnU|-dsUwr z)!LcUN_@}u-D5zu9p*xGZOIYZgCT*k3 zs$Yj>H^P&W#kNeXmmr%t-8W9}9?q~cu6a&nU#ed9zzPzkEk*TX;_Z_Dn?CX?c5Z|A zc)*4;>Z0<}X&M?3fmXLg@H@%kc@z>^gvR6VT0dImccGD7mF&hZ9}ay)h$;tCHDHd( z7D7bERdJjUOtC;k1I|1+hJTwdPy7&^m3ks>kA0LOY-c8-PegiUb1hQf(bzV%@X-{* zJTLe7M| z+waVZJnOn_m4AzB(DGm&wMi(ENOsOq@EYY$J-(97K?Cm=RLrNM@-`z88^IKqKp>!c z6TDrV3!gUSf`D|ZJq}q_+AImDR>tD1u_{kyt1`CZ)Mku0h@_zA2GPf(deek#oktvD z&9Q3~2|?^{?U{!qnrVhFJ*VEav_$tYm24gMC_K+RW(N1*^(+KL0JXvPvat tWZUGxKNAV3sd?VF(0cxI90vD4&$;;+$u4TNXjE!nm*nu6u;wi^;eYOOo@oF8 diff --git a/tests/test_results/SameSuite/apu/channel_4/channel_4_volume_div.gb.png b/tests/test_results/SameSuite/apu/channel_4/channel_4_volume_div.gb.png index bc1c2885e398bb63fd88684d79dc14358a016283..5961675095043a2ce527b79e78324ab5082cf506 100644 GIT binary patch literal 974 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-w# zm2B_$-e!LLyJ`vt^N)5jQ{DN>kz8~2PV=X7%+FbRCF-?B-_|LgUK{o$&)xI+`{%8R z60hzU@a%qly1l0Ur}x^eaeC8(yfn970}|@84FxpZ{tUAy(}*Z&JTTrP8N z%lx;irv&9~zkU8@X4Rd%?el%#|NEOF(H8%HN}@#B^{2nj&t^X5{`pyh%Uq+j$}egy z=Qmez{>z^1z2VR7Pe9R`P+8NN%zEE-fV})?`_DIg{eAq`sgIwSmwQ+LOwD`yM&gb)(V!5Ek?0+qr z3Hy}(CubR-F8&$(tig`|#5A?^18ZyhpUh>nw}vV+0n#CVstMFzJ zju)24{;RP2xA(!$m;Be3uKnu%eebOK{~7mg)2p|+|D^ih!_^kD(Yg-=zq?uWKbwAj zgnghD$j}jw|P)B-}^)QQ=q&N`<$1Beb=h*$xP5FJ}^`6dB$F#w(@Vj$D?0= zIt~i-y-y!UEZtK#*|5Iv>F%`Ly`}StP3JP(?YgfTvG1kKhns&5elGv#?-A8syyYH9 z{Y>U_Uo6~T+vfA_*qm>559C^Nc9m5xpEEw6zCAqOX`9~U?|SpsewuSyrXW3@fBFVA uT^>{Zi@Hx?5?b2eqNxzbb*0|T?0r;B4q#hkZu?iSrv5Mi6B zzCL6B>_3~nG-*0t=vQqzUa_^>l`p<`nY4!bDU%}-&nx*@KieFasJ{An`uE?q|DM>Xm;5jNc|T{v?d9?g_qv-NUTIpMIcwQV<$k4n#j0+% z=gZ6Qud?B{>#>%Ycb@N(%lE>%1y3uouhksiQ)@71UT%=pqR+N_tTsM14sp-+jLetQ z7w3vEJyKhJ&u)g_7qQ-Rx7iQxY!#Sy{&~oU8oSGP!uHP5vd>nRZ~NJ|FUIcu1Czp( z7`dYx6HMdPR)4&<=I6YhUT3@$uX=IG+1$Uxu5WsJ!w)-&zFLF175nbT{++P4=3mj2 z=%+_NeLGn2rupU1J8G~;iTg6KhsR0xhsf#NDi!&<{N1aRYBR-auD#2O zSpR<6M*h4F+VT&@w$&fm5qkbU-O~?e>^e2r~i2J)*z$v#dY(xeA!fV@Xzhv z4>rqK+IzWG|GSt`DSzj~KMRrDwi}}~Eq_m*Tl5vI2;e0iSd{`<|M-!7a!eN9cn zn~){?Uj_eWzq>kdckss!ITfJ%aXX25+xgp)<$2Bn!}>EYVE+BuWiV%_&(z=ftAWmY z-~8QJ?qc(k=wA<%B_&&?+5O1b%lkXw_XB}H$A61{JNN-=&+@;u1$N>6@tZy|ZVH~b z?*_!IH_Z<#etP&Rrtr#SYy<*^@MqOON!eBS!_{W|0Sdp_6i`!nsMzQ~U4 z+keR)$QD*uxtZ}@+3wBrKNe)?C--bz_gdm{amDiD{)+!8`|tN#KL4G)|9-q}{=c8k z%Z=+E6)dmK1u84Fkh%BXZ~66y1r_^W+X|F5#z;q%KqwhAlT z*Cf{c5;*kQ^0(lhr%Lh%pS?H9=l3`zccI1#NWW66KJeBw`dh;@{d4l~8$SPd{Ad2- zx8i==*PYFOeonT&eD2TZbN9at{&4c!_X;kv|ErfjxIgQ7wDzBA@0llUVpl1hZ2Q3M z#@mgy3x0Zk{?@SM4ddDEpO0HF0P<7rf$aD{!FIuW=Ax6n?;57B`+ahc^@Hs5w?SG> z=AQ%7R=>jw`$fL{pP4S`!8u_HgXbuL90q?`le7QL4%@G40!*U}p00i_>zopr0E$FD A2LJ#7 literal 653 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Qftr;B4q#hkY{FXk-@5OE01 zcqabS;%DTugFK2W)+QUgdKcv%ZV@^;mcyLy^rks}%M%}$Sm~ZFy7KYsQxo6Q_Log! zZ8xRc5TJY9aj72-dPKB)ssJZXZjD8%~$y_`@H=o{(Wc8-2PNuFsHWQ z?x+91@2<`-%C4*Zspmg+f6hHSi*t56?DoeUd@lTb&D&=C%sTOB#}jt@3;j6zy#EgS zZ`&WTwgs&Bi&MTo3%@R3$XfGj573w-`8o5p^Y>L(CB&WX=PhJ9&MBi_Fz3JsV|*9q XJ!9$dB+Viel*~L`{an^LB{Ts5dekrr diff --git a/tests/test_results/SameSuite/apu/div_write_trigger.gb.png b/tests/test_results/SameSuite/apu/div_write_trigger.gb.png index 6338b9a902cce4984b85577cb1f0920290a70308..5ee42b547ecd9227c7f492d05a2611a5b5d20d30 100644 GIT binary patch literal 1908 zcmbW2Yh2P<8plP}z?v3I%}W-x)->jrm3d1=EE~hpyc9ZWWn-u8KxXv^Zh5;e{zhbAeF=M!X>^6y881AT0NT8JN8B1(Adj51=KYG zdwlrw?_tz$oK9HVp&DPlVS?0qj+UlQI$aLud950#5kbA3#2MFjN>5ZV%JGwHV9`hb zBW0q151KAf)a=DaQnbl3(^!1Ag<#qmuWskk1WxhLf*7Opc1J|5ox{~Rrb~OQMZM)? zas|cYX^~L&1JN`2bAky$@0lR!kX}*#sQ6Tsq#=n|geg`qB>~Q>qTwwsJzBR@6RJC5 zljn{t*IUBiX@#`#;;TWgT_3@}+Vn8yb4Fvk5t5*dWhJOxKazXb?I6PbRtq3`DQdiv z4tXX|>xabD05_@Ml~8cpcw%bf$kga+Sha2Bw(W_RYxjmKC2@UP=05oa>@M^11Ty| zT&^dwB^Bu71-N!VmdS|T5Fw;@fKji-!tQV*{Q5H}of8I9_gr%1u%sIBS^a{~oF#*~@#-u0Ch&gY5*r{N`j)MYSsWr@u=MH{zt z%v#C@Ts=8?UK_?GB2)V3>ng=~WHR+3+N!5i3f?DqAC)YSk8_OSlaRud_-y!G(=g!s_8cAq|K$hUd>HhnV|lQ_iu4z(TrwAM(e31%?gI4a4;!4A8u36<>T`sBg{b0} zRXj_GQ#M&+1ab+atlIskwj_veiukLq?8ritqvqPJGI_04%mYE6K?b;3OhLpg>ZUEt zQhU{5@cRX@i~*0l+OnLD5U7GzH7_NK9I6bY(C>?=`u`A-(~jezj z?wkhy*Xv>2onn!qEcK}-y>)$-h}2gDJS*^BLcI!MpLN~Vp?_SWwNm*ng2;qE>O->> zl*<9@st&CG*m{Evp%0IorneU-MD|6q0NN%oc3VInAs~A&SU>i}8c?w98ROnKk?nJm z{vN~>Mm~Jdo*z?l+0RKj%&|!X^2hCfnswvxR`23B##-a(#2JBD9BIx6LM6Wn21U4I zGGiJCN~6s!2(g(TVQ6y82V?dOnL}g)SWjB{D8V_};d}tD-*>Kpqt_)-&MVA={HlDI z=ioNR?tD##+-J3oc*5Cq(1Ti7Z#sA*Hh3Rl7-Fw@`^gByUFS(4f=9@PuD0@W(-vZ- zuW8Z{ya=Nwq2*IHBMn~N1N!^4>QBN69QhR_?~(~zwVS}pTvWH=bfny0-}p5S6OeRo z11UYAqBtISPn=)m!dB@>=aog>s2!DvRK@!!L2SyJ05j|(DCeI`3Jz7J;>N%^9t%y= zR$usc=fI%TWm`<_M8w_gc~2eI?bVQuFpFKdv*Vl&c{A-hR`7l$2x4B++f{|0kJ*dl zjZl!0YY|y4o{G)Qi*kd(Al&5j!n)2}rhyXh$8D#%FJ0@BG43*LYhd1XVX3i?-;p@g!C#~iL-hYbpcE98f8AGQcfGEOpoemIopg_$Ui0B+V(L<9Q2 zZ;^4c>sdx3oXwdBsOITrm6Bb5%JR!>femhI!8y^V`ypj@5Bzy+;Ua{sZZTskbfn-M zxNIw0*6>K&_|&Uy4F+1VZD?XaLyA>_#Il&Zp}f>Y<0OXnCb3VbuUF@rzVRmit!?bf zu)3jn+iRdb+WnYsLCM=W29)fr=G$mUznwpm);N#VKEGKTWqYl+aoQ5%-+k8jY~%m4 bK7zmdb{4`zC;l7wO^s0JLjve$<8uBBb&K4S literal 1695 zcmah~X;f3!7LK*%^2A)!SU`wD#YzQUkf}0+po~(PEZT%Y5=8QF0wQDN0>#J!WDu)NUD#=krOG^v&>UvHM_TD-Qi!jb6L0T zx4M2Cy@B2u^x?yS+ZAaYi`_+D*H6^VY>k9?qRgp}>D`8aFgZ&!e)0xM-7uoM+?Eg| zK8D{FA9JD7^0aS)`();PX7TcK1Ajy4N`RBZ2nbS?g0hq6v{+{>fIGkM;+r1~ujy|{ z^{fNiS-d~tZ0uJ~cz0#?v+~9vA?goqyPM7$H9PEBD*O0ybx!@m+mG*JAQZ0|S9LdQ zDUvfj;ZTuWliY_KKRUf$mJ*!o!QJK^BF~Ck^t0x}&)qO*ndJ{AZJpkp$qlXDKd>>Q zj7LLurf2S=apFgI0z+Z)60Dk(ei@U$Bo0r@+Dc&apDeCI%RK3fI^bc$J}cH_&B%Wj z;Jfq(*_UaPHo=c8Zmf!5KP1p@6tuP!i%#dSdHnfR>#6d@Lyjzs z`P!&viG|Ph!J}CI)ok69&u5LLsV}mG{iWjuhiYPe&!d0wjeM+#>|}zkif!}$=X?%> zdD{$>9ok^U9{~8WE^0r(3nQeoVDAyS8WoOarduZ%#EGejc}E`B^Q$VO7ju=mAoRVKXc6t94NY z4z10;`qsaN&TB=cMfgzXVf%d9J^OXY;_fJ_jsDui_bI*YSx{-`wO8w$X)Yp#_y0qQD#QU;SUN3azscsOZtJxBOkm9#r9CctsC^!bxxEG^n zfcWI;;AZY(d$cktw_nKf0u3%lAFyv?Zd;{@K3|*SfaU?+?#U{*^7Do+IxaU!Tab_R z#?}_#T~(H}Pz=HUk)AlV8s?BG-k6!N6!IVO*j0-B9YjtDB+Gl=kDv7u7EH^@4GS)` zIQ7TMe^1)lnqaW-JnrtAyERi5x%M=-s z2aUr{j0oPN(N8SP4s-gX{?cPu?PxM==SfI8qAjJkHcKZH(%knanJXWbWv=)J%S}Vi zTJ+JVn0mmATbyyKKx?tVh+A2N|FM~&F<=tI4Uyo-mO2ZND!YA?ojWUUS4etqJs3=K z;k^AZqUnw)n1nk<<5avCpI1r8DfK|)%Mv~Pq)R?g8WM4=BHb{4?Zc|zrHbW{VwK0Y z-i#0aGIlTf98)3kEa@3)0KwlK4Amsf3wF`>!NZAn#oq6WG@sxA@%y%Dd540*TSmkfvnZsqa5AGe=MdUk@)y#rT$Rzp+t-kOc7Pv!q7(RrLAcf+X3r+rpn zR~(ow6x!}K@x6%Z)w-y}7|m^Bi!MsnB;v4gM=zrF`k>c$8D+ea|56$?YUIdzlPJ)# z?g5WxsdXLmK(;ll()zzoFFk@PfZ!l${2gx-7wHDtFp!ODUV@Bv+#oe4a9yI zMF`F#`V>C7~;gH0m>mQGgr@u#|8|BJh`iomh diff --git a/tests/test_results/SameSuite/apu/div_write_trigger_10.gb.png b/tests/test_results/SameSuite/apu/div_write_trigger_10.gb.png index cb6b4821bb89a2d9f3e03f7f2a285c60c6140f83..c62355ca7be4ec9725c3ffb119d021ef84f5623d 100644 GIT binary patch literal 1857 zcmbVNZ8RHL8b%Q9(TNt-&d{*gN$Cfx*%lQ^#v+1JomSe=(ymg%8pc+TG$}fYMmlIs zQR#GQN)cOaq9O6IZBbKFA5E4LyY@4e@|=e_ql&wZZf+@h1= z=pEZX-)>@JvI7$waLTwpFxG7=OXEso#!*a6fL54*uTB$c7Vcjx6T4d9zz8mQk_wbC zpUTT*MLoLNe$OpO%u4Q=G4ogGn&)96?9!t#oSc=)tXOJH{Sbvo?89V)yGhCTWpe~L z*4xz~2TP~nMcXEA^~Cj`_&6{P?Y0?M0^QrD)X0WQulGi$tFsN{sz#rpvS$@t+a_{{ zXTH_U?)ql#bsmpLzLHPt6Jw!&d3SErwXP!dLx@|KEv;9$bJ|u`TH`n)UZSl)27gX0WM14TSI_{Byjlf$JuY;&zEztAVO>>CMADQ;+zpKA0T92Qzf5^F3$~amc1RAo&p~A z=$i-~imYw%d=_=JmRHA2AiW6VB$+Xw(+6@bu4x}0`XX%(fcqmj6M4@-S%1m|^|OE4 zk51@pJs9$D?j+^Cyg`S9i#q&vJOx^DM|Y9 z>{w2xf^!U863YilMtYN}=XsU)AEy^DBAB6Tl(JF2WZx;dPmin#4X<--tYDYi;(xxJ zpcQ7SS#WwNesGlnBNjW^ENVnTL{kxn@XD*;!6~(`%M9@Jxcy;rN>9fc<#r1P$(|&k z5I+}Pij*5#H8Ib97lYcHK*2O7g4wT4nJHhRK(H78EG*9F{Zh>bu-uQF1X*>~{m+6Q z6>6Up_Q>X@NGL^(?(Vp%pkicYd28+*O`ix{UraAu#xHC}^;<=VtNtJuy&fwbWaJ&i zlD>PKzOd&-;n?vYwNJJ!_%i!8Bz^!V~mvu=c@y`^5<)L*_VFYXS3d*rHoCp znv*}qXzN7;gwe)0M(T$Ffk0rxqoaK-l`hQEoy2hA1}lsTp+a5t9F74L)$&AV))PzX zBlQ?no^*K2hP?R<;r?I1{YP7aBG80X#%>xeqVj$h7Zm)-vGVy54e=kkLMIGLNI1fP zI+N4=oMzEN6uVaeHX#RtROP2Y_p+eZhXnd``Z2EjM z73-0u^(LbF&!|m-r}2|H?TYmUqdb$H#|MFuVg+Di@Az?$+iqs9yKl<{isz{p8w^>r)DWklZc%6*1LSBRWFz_%vx2!#-R>x4hTj9PE-$hKRC5oM7|>Qa z`y~C>sRlklo0Hf#3-D*;fqtO09Q~W$^Z^j~-MOsIN1ltOI--l7Qjdmn_nqum-Mi4n zcL`is>~2|v9H_{{1n)vjGO f8TS82z3=MUmUq;ap!I^-9p!tY?k}r zsYaCmgTMilbDoI$VPnf+pDI5g#`t_b1Jm>Qj4=+3p9+7)yTp{h58(r$yxZ6|MZ^&4 zg2){J_1N5yj@vhu0|Z{X86aLA3mLI|(d>tK4lUgzIN#ZMi7 z#k(X&27U}rGnm}f@OG1L13#9B4|dMWr|2GCK4gjYBsp&IWBHjg81Xm6;e(wEKG-=A zAM9PxRNqZLk$+FA2Ma*NACE`w3jP$HUURP8=84q%&~1u{F%9u~`anqM(H2h7NASsH zloKYpU-G$x(cbm0>-oX~^DZ?f$R%$n7e;1;nj{=sZs=y70D9H{@2Vc$dd+A{J@{^3 zJglwBZ(k3-Tig5ic(V`YJK{Qn8~DiGd3w#cfo;7b(iT2)dtM|)Ulh7s!{|Zi0mY&@ z0Y8NA`h2i>mPS2zMczDv%Mr2h&~harJ}?d+?0;M{eK5^+KIy@aD8`#!6BJEL6-JKVS(HDho*D!hzdO)#g z9)G|O;qCQc!$X$1ENXln_+EbE3`YD7arj{8n(2dSuJcI`ek_lAuya{{H|Z3`DU#&H z-I{a;*RR*^&RcwN{QEx*vCAYM+0vmmk~;wK{k*Z7#3!x@Ci$>T{q@s$iw{26-uf~y z9|2$0lj8*4dBY6O zbu2KUrW!xn}xc+6dno_^~|d!QK_S-~SwbFByD3ctPIcgU`0eUxBBH;D5KM zr|!<%pQ1^W>DB}XX5DHI0K9>J>&Hi_E#i8!;79SUem@wz7Ty=_T=;yja~}0z?~0rI zU;&8u2KCyzd3w#cn)V_-SRV*!k9zIhd@>p3go*9W^}&S$=3Qz|kWb!_;2cxGM3)oU z#OIj8iTEgf`|ru3$HjQh_e|{`hkp-tF3&z#Ea{)gZ|{S#mXu}re6WbqwkkhQ%zK?( zk|V3+lMfmcU%y^^H*fdBxn1TM-DQh<>hApZJ{WwN@A~*?Wt(q}63^p$*WgF-s0Tae z?eB|*wvcBZ3_dvU!O(?bcRiRM*j&CZS|28ld#(rPPFar8UH0&i>O_%H4}bRHg9|5= zcd1Fh96nkTtlLxS!FTgHrf{N{;@|p@j^tPukJIC5T%*3*8ix;dF8E+OFP8K*dHCQ9 z?Y^E_>cNLnnNRlOU6LbPgik)mP<#U)Pblwp*%rGgw{NxiS&3pf_ERh zJKy!TSrIJreCZQ$y=(BJcz=66*zmRB$$^5umACugpP!$m(Li$)`@U_KM=p7N&k!R4 zKb(gTb}snf$-DqW{QLV`Z>ouh51!0ZhN4T#NmUzE5Zj)<@w9Y4T`_k@Kx}^lU2E1+P9Ac z`|98&b0NXbg#X!7ZU7TNU(Du!On#QI~Nk{ aT>k-+X3dBmVg>*J0000tP&Rrtr#SYy<*^@MqOON!eBS!_{W|0Sdp_6i`!nsMzQ~U4 z+keR)$QD*uxtZ}@+3wBrKNe)?C--bz_gdm{amDiD{)+!8`|tN#KL4G)|9-q}{=c8k z%Z=+E6)dmK1u84Fkh%BXZ~66y1r_^W+X|F5#z;q%KqwhAlT z*Cf{c5;*kQ^0(lhr%Lh%pS?H9=l3`zccI1#NWW66KJeBw`dh;@{d4l~8$SPd{Ad2- zx8i==*PYFOeonT&eD2TZbN9at{&4c!_X;kv|ErfjxIgQ7wDzBA@0llUVpl1hZ2Q3M z#@mgy3x0Zk{?@SM4ddDEpO0HF0P<7rf$aD{!FIuW=Ax6n?;57B`+ahc^@Hs5w?SG> z=AQ%7R=>jw`$fL{pP4S`!8u_HgXbuL90q?`le7QL4%@G40!*U}p00i_>zopr0E$FD A2LJ#7 literal 653 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Qftr;B4q#hkY{FXk-@5OE01 zcqabS;%DTugFK2W)+QUgdKcv%ZV@^;mcyLy^rks}%M%}$Sm~ZFy7KYsQxo6Q_Log! zZ8xRc5TJY9aj72-dPKB)ssJZXZjD8%~$y_`@H=o{(Wc8-2PNuFsHWQ z?x+91@2<`-%C4*Zspmg+f6hHSi*t56?DoeUd@lTb&D&=C%sTOB#}jt@3;j6zy#EgS zZ`&WTwgs&Bi&MTo3%@R3$XfGj573w-`8o5p^Y>L(CB&WX=PhJ9&MBi_Fz3JsV|*9q XJ!9$dB+Viel*~L`{an^LB{Ts5dekrr diff --git a/tests/test_results/SameSuite/apu/div_write_trigger_volume_10.gb.png b/tests/test_results/SameSuite/apu/div_write_trigger_volume_10.gb.png index ccfcd887aa58296553c3c2c4aa50808a6d4db717..6067c133f0db135c4f51ee10e1d4db2265182b7e 100644 GIT binary patch literal 714 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62OE&=-tP&Rrtr#SYy<*^@MqOON!eBS!_{W|0Sdp_6i`!nsMzQ~U4 z+keR)$QD*uxtZ}@+3wBrKNe)?C--bz_gdm{amDiD{)+!8`|tN#KL4G)|9-q}{=c8k z%Z=+E6)dmK1u84Fkh%BXZ~66y1r_^W+X|F5#z;q%KqwhAlT z*Cf{c5;*kQ^0(lhr%Lh%pS?H9=l3`zccI1#NWW66KJeBw`dh;@{d4l~8$SPd{Ad2- zx8i==*PYFOeonT&eD2TZbN9at{&4c!_X;kv|ErfjxIgQ7wDzBA@0llUVpl1hZ2Q3M z#@mgy3x0Zk{?@SM4ddDEpO0HF0P<7rf$aD{!FIuW=Ax6n?;57B`+ahc^@Hs5w?SG> z=AQ%7R=>jw`$fL{pP4S`!8u_HgXbuL90q?`le7QL4%@G40!*U}p00i_>zopr0E$FD A2LJ#7 literal 653 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Qftr;B4q#hkY{FXk-@5OE01 zcqabS;%DTugFK2W)+QUgdKcv%ZV@^;mcyLy^rks}%M%}$Sm~ZFy7KYsQxo6Q_Log! zZ8xRc5TJY9aj72-dPKB)ssJZXZjD8%~$y_`@H=o{(Wc8-2PNuFsHWQ z?x+91@2<`-%C4*Zspmg+f6hHSi*t56?DoeUd@lTb&D&=C%sTOB#}jt@3;j6zy#EgS zZ`&WTwgs&Bi&MTo3%@R3$XfGj573w-`8o5p^Y>L(CB&WX=PhJ9&MBi_Fz3JsV|*9q XJ!9$dB+Viel*~L`{an^LB{Ts5dekrr diff --git a/tests/test_results/blargg.json b/tests/test_results/blargg.json index 67385b2d1..8b2e1c0bd 100644 --- a/tests/test_results/blargg.json +++ b/tests/test_results/blargg.json @@ -20,10 +20,10 @@ "blargg/dmg_sound/rom_singles/06-overflow on trigger.gb": "06-overflow on trigger\n\n7FFF 7FFF 7FFF 7FFF 7FFF 7FFF 7FFF \n8D7112A4 \nFailed\n", "blargg/dmg_sound/rom_singles/07-len sweep period sync.gb": "07-len sweep period sync\n\n\nLength period is wrong\n\nFailed #2\n", "blargg/dmg_sound/rom_singles/08-len ctr during power.gb": "08-len ctr during power\n\n00 00 00 00 2144DF1C \nFailed\n", - "blargg/dmg_sound/rom_singles/09-wave read while on.gb": "09-wave read while on\n\n55 33 11 00 00 FF FF 00 11 11 33 88 BB CC 44 55 BB 11 DD DD 44 33 BB BB DD 77 11 CC CC 33 33 00 EE CC BB 88 77 77 77 88 99 BB DD 00 33 77 BB 00 55 DD 33 AA 99 11 99 22 BB 55 FF AA 55 11 DD 77 44 22 00 11 22 0652FAE5 \nFailed\n", + "blargg/dmg_sound/rom_singles/09-wave read while on.gb": "09-wave read while on\n\n88 33 11 00 FF FF FF 00 11 44 66 99 CC DD 55 AA AA FF 55 22 99 AA AA 33 CC 66 77 CC CC 33 FF CC 99 99 55 55 33 33 33 44 55 77 CC FF 22 66 CC 33 EE 44 11 88 FF 77 FF 88 11 BB 55 00 BB 33 CC 99 88 44 22 00 FF D075E68F \nFailed\n", "blargg/dmg_sound/rom_singles/10-wave trigger while on.gb": "10-wave trigger while on\n\n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99", "blargg/dmg_sound/rom_singles/11-regs after power.gb": "11-regs after power\n\n\nPowering off should clear NR13\n\nFailed #3\n", - "blargg/dmg_sound/rom_singles/12-wave write while on.gb": "12-wave write while on\n\n00 11 22 33 44 F7 66 77 88 99 AA BB CC DD EE FF \n00 11 22 F7 44 55 66 77 88 99 AA BB CC DD EE FF \n00 F7 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \nF7 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 A", + "blargg/dmg_sound/rom_singles/12-wave write while on.gb": "12-wave write while on\n\n00 11 22 33 44 F7 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 F7 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 F7 55 66 77 88 99 AA BB CC DD EE FF \n00 F7 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \nF7 11 22 33 44 55 66 77 88 99 A", "blargg/instr_timing/instr_timing.gb": "instr_timing\n\n\nPassed\n", "blargg/interrupt_time/interrupt_time.gb": "interrupt time\n\n00 00 FC \n00 08 04 \n00 00 FC \n00 08 04 \n550B72D0 \nFailed\n", "blargg/mem_timing/individual/01-read_timing.gb": "01-read_timing\n\n\nPassed\n", @@ -52,7 +52,7 @@ "blargg/cgb_sound/rom_singles/06-overflow on trigger.gb": "06-overflow on trigger\n\n7FFF 7FFF 7FFF 7FFF 7FFF 7FFF 7FFF \n8D7112A4 \nFailed\n", "blargg/cgb_sound/rom_singles/07-len sweep period sync.gb": "07-len sweep period sync\n\n\nLength period is wrong\n\nFailed #2\n", "blargg/cgb_sound/rom_singles/08-len ctr during power.gb": "08-len ctr during power\n\n00 00 00 00 2144DF1C \nFailed\n", - "blargg/cgb_sound/rom_singles/09-wave read while on.gb": "09-wave read while on\n\n55 33 11 22 22 11 22 33 44 66 66 BB EE 22 22 77 11 77 88 55 CC CC CC 55 55 88 88 22 88 44 55 EE FF DD CC 99 99 88 88 AA BB DD 22 55 AA EE 22 77 CC 22 88 66 DD DD 55 EE 77 BB 00 66 11 DD 66 33 00 00 DD CC DD CCD5FE43 \nFailed\n", + "blargg/cgb_sound/rom_singles/09-wave read while on.gb": "09-wave read while on\n\n55 33 11 11 00 FF FF FF 00 33 55 88 99 DD 11 AA AA 55 BB 22 99 11 11 99 22 BB FF AA 55 11 11 AA 77 77 66 33 22 22 22 33 44 66 88 BB EE 22 66 BB 00 88 55 CC 33 BB 33 CC 77 11 BB 11 CC 88 44 11 EE CC AA 99 AA B7836723 \nFailed\n", "blargg/cgb_sound/rom_singles/10-wave trigger while on.gb": "10-wave trigger while on\n\n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 55 66 77 88 99", "blargg/cgb_sound/rom_singles/11-regs after power.gb": "11-regs after power\n\n\nPowering off should clear NR13\n\nFailed #3\n", "blargg/cgb_sound/rom_singles/12-wave.gb": "12-wave\n\n\nTimer period or phase resetting is wrong\n\nFailed #2\n" From 3451731fd72ee7f317d310751f6b64c63d34d9cc Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 26 Sep 2024 16:28:54 +0200 Subject: [PATCH 19/51] Saving Blargg interrupt time results as they depend on sound, and are still wrong --- tests/test_results/blargg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_results/blargg.json b/tests/test_results/blargg.json index 8b2e1c0bd..3a2ef5e18 100644 --- a/tests/test_results/blargg.json +++ b/tests/test_results/blargg.json @@ -25,7 +25,7 @@ "blargg/dmg_sound/rom_singles/11-regs after power.gb": "11-regs after power\n\n\nPowering off should clear NR13\n\nFailed #3\n", "blargg/dmg_sound/rom_singles/12-wave write while on.gb": "12-wave write while on\n\n00 11 22 33 44 F7 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 44 F7 66 77 88 99 AA BB CC DD EE FF \n00 11 22 33 F7 55 66 77 88 99 AA BB CC DD EE FF \n00 F7 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \nF7 11 22 33 44 55 66 77 88 99 A", "blargg/instr_timing/instr_timing.gb": "instr_timing\n\n\nPassed\n", - "blargg/interrupt_time/interrupt_time.gb": "interrupt time\n\n00 00 FC \n00 08 04 \n00 00 FC \n00 08 04 \n550B72D0 \nFailed\n", + "blargg/interrupt_time/interrupt_time.gb": "interrupt time\n\n00 00 00 \n00 08 08 \n00 00 00 \n00 08 08 \n5DDD9187 \nFailed\n", "blargg/mem_timing/individual/01-read_timing.gb": "01-read_timing\n\n\nPassed\n", "blargg/mem_timing/individual/02-write_timing.gb": "02-write_timing\n\n\nPassed\n", "blargg/mem_timing/individual/03-modify_timing.gb": "03-modify_timing\n\n\nPassed\n", From 64598396dce26c17e80422b061045d8611d4a236 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 26 Sep 2024 16:30:30 +0200 Subject: [PATCH 20/51] Saving Pokemon Pinball test even though it broke because of timing changes --- tests/test_game_wrapper_pokemon_pinball.py | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/test_game_wrapper_pokemon_pinball.py b/tests/test_game_wrapper_pokemon_pinball.py index bc693f647..c5f142111 100644 --- a/tests/test_game_wrapper_pokemon_pinball.py +++ b/tests/test_game_wrapper_pokemon_pinball.py @@ -67,37 +67,37 @@ def test_pokemon_catch_mode(pokemon_pinball_rom): pokemon_pinball.start_game(stage=Stage.RED_BOTTOM, timer_div=0x00) pyboy.button_press("a") pyboy.button_press("left") - pyboy.tick(50) + pyboy.tick(50, False) pokemon_pinball.start_catch_mode() - pyboy.tick(270) + pyboy.tick(270, False) pyboy.button_release("left") pyboy.button_release("a") pyboy.button("select") - pyboy.tick(20) + pyboy.tick(20, False) pyboy.button_press("left") pyboy.button_press("a") - pyboy.tick(500) + pyboy.tick(500, False) pyboy.button_release("left") - pyboy.tick(21) + pyboy.tick(21, False) pyboy.button_press("left") - pyboy.tick(100) + pyboy.tick(100, False) pyboy.button_release("a") - pyboy.tick(31) + pyboy.tick(31, False) pyboy.button_press("a") - pyboy.tick(200) + pyboy.tick(200, False) pyboy.button_release("left") - pyboy.tick(31) + pyboy.tick(31, False) pyboy.button_press("left") - pyboy.tick(200) + pyboy.tick(200, False) pyboy.button_release("left") - pyboy.tick(31) + pyboy.tick(31, False) pyboy.button_press("left") - pyboy.tick(400) + pyboy.tick(400, False) # NOTE: This sequence broke because of changed instruction timings - assert pokemon_pinball.score == 15635100 - assert pokemon_pinball.has_pokemon(Pokemon.BULBASAUR) - assert pokemon_pinball.has_pokemon(Pokemon.CHARMANDER) == False - assert pokemon_pinball.get_unique_pokemon_caught() == 1 + assert pokemon_pinball.score == 9030100 + assert not pokemon_pinball.has_pokemon(Pokemon.BULBASAUR) + assert not pokemon_pinball.has_pokemon(Pokemon.CHARMANDER) + assert pokemon_pinball.get_unique_pokemon_caught() == 0 pyboy.stop(False) From a6a895a21e498eabade1caac30be0cb53ec83284 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 26 Sep 2024 16:40:01 +0200 Subject: [PATCH 21/51] Saving Tetris example as timings (and randomness) have changed --- extras/examples/gamewrapper_tetris.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/examples/gamewrapper_tetris.py b/extras/examples/gamewrapper_tetris.py index f0cb1a4e3..a419cf26c 100644 --- a/extras/examples/gamewrapper_tetris.py +++ b/extras/examples/gamewrapper_tetris.py @@ -29,7 +29,7 @@ tetris.start_game(timer_div=0x00) # The timer_div works like a random seed in Tetris tetromino_at_0x00 = tetris.next_tetromino() -assert tetromino_at_0x00 == "Z", tetris.next_tetromino() +assert tetromino_at_0x00 == "O", tetris.next_tetromino() assert tetris.score == 0 assert tetris.level == 0 assert tetris.lines == 0 From 779aaaeb027f1ff6388f0dbe61a5ecca85d93382 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 25 Mar 2024 22:44:12 +0100 Subject: [PATCH 22/51] Ignore SDL2 warning in pytest --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e79828f92..2f1c119a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,3 +82,8 @@ split_penalty_for_added_line_split = 30 split_before_logical_operator = false split_before_bitwise_operator = false arithmetic_precedence_indication = true + +[tool.pytest.ini_options] +filterwarnings = [ + "ignore:.*Using SDL2 binaries from pysdl2-dll.*", +] From 1a638ef08bdfe46b168f397f7e2bbf1da04be470 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Wed, 20 Mar 2024 19:20:01 +0100 Subject: [PATCH 23/51] wip jit --- pyboy/core/cpu.py | 14 +++++ pyboy/core/opcodes_gen.py | 109 ++++++++++++++++++++------------------ pyboy/core/test_func.pyx | 5 ++ 3 files changed, 76 insertions(+), 52 deletions(-) create mode 100644 pyboy/core/test_func.pyx diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index e6e0d0a90..132d10cf2 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -5,6 +5,8 @@ import array +import cython + from pyboy import utils from . import opcodes @@ -57,6 +59,8 @@ def save_state(self, f): f.write_64bit(self.cycles) def load_state(self, f, state_version): + test = cython.inline("print(self.A)", self=self) + test() self.A, self.F, self.B, self.C, self.D, self.E = [f.read() for _ in range(6)] self.HL = f.read_16bit() self.SP = f.read_16bit() @@ -107,6 +111,16 @@ def dump_state(self, sym_label): def set_interruptflag(self, flag): self.interrupts_flag_register |= flag + # def jit_analyze(self): + # pass + + # def jit(self): + # code = self.jit_table[bank][pc] + # if code: + # return code() + # # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 + # self.jit_analyze() + def tick(self, cycles_target): _cycles0 = self.cycles _target = _cycles0 + cycles_target diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index ecce26f78..b57eb3240 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -264,13 +264,7 @@ def getcode(self): "def %s_%0.2X(cpu, v): # %0.2X %s" % (self.function_name, self.opcode, self.opcode, self.name) ][self.takes_immediate] code += "\n\t" - - if not self.branch_op: - self.lines.append("cpu.PC += %d" % self.length) - self.lines.append("cpu.PC &= 0xFFFF") - self.lines.append("cpu.cycles += " + self.cycles[0]) # Choose the 0th cycle count - - code += "\n\t".join(self.lines) + code += self._code_body(code) pxd = [ "cdef uint8_t %s_%0.2X(cpu.CPU) noexcept nogil # %0.2X %s" % @@ -288,6 +282,14 @@ def getcode(self): return (pxd, code) + def _code_body(self): + if not self.branch_op: + self.lines.append("cpu.PC += %d" % self.length) + self.lines.append("cpu.PC &= 0xFFFF") + self.lines.append("cpu.cycles += " + self.cycles[0]) # Choose the 0th cycle count + + return "\n\t".join(self.lines) + class OpcodeData: def __init__(self, opcode, name, length, cycles, bit16, flag_z, flag_n, flag_h, flag_c): @@ -354,7 +356,7 @@ def __init__(self, opcode, name, length, cycles, bit16, flag_z, flag_n, flag_h, # yapf: enable def createfunction(self): - text = self.functionhandlers[self.name.split()[0]]() + text = self.functionhandlers[self.name.split()[0]]().getcode() # Compensate for CB operations being "2 bytes long" if self.opcode > 0xFF: self.length -= 1 @@ -455,7 +457,7 @@ def handleflags8bit(self, r0, r1, op, carry=False): # def NOP(self): code = Code(self.name.split()[0], self.opcode, self.name, 0, self.length, self.cycles) - return code.getcode() + return code def HALT(self): code = Code(self.name.split()[0], self.opcode, self.name, 0, self.length, self.cycles, branch_op=True) @@ -466,12 +468,12 @@ def HALT(self): "cpu.bail = True", "cpu.cycles += " + self.cycles[0], ]) - return code.getcode() + return code def CB(self): code = Code(self.name.split()[0], self.opcode, self.name, 0, self.length, self.cycles) code.addline("logger.critical('CB cannot be called!')") - return code.getcode() + return code def EI(self): code = Code(self.name.split()[0], self.opcode, self.name, 0, self.length, self.cycles) @@ -479,12 +481,12 @@ def EI(self): "cpu.interrupt_master_enable = True", "cpu.bail = (cpu.interrupts_flag_register & 0b11111) & (cpu.interrupts_enabled_register & 0b11111)", ]) - return code.getcode() + return code def DI(self): code = Code(self.name.split()[0], self.opcode, self.name, 0, self.length, self.cycles) code.addline("cpu.interrupt_master_enable = False") - return code.getcode() + return code def STOP(self): code = Code(self.name.split()[0], self.opcode, self.name, True, self.length, self.cycles) @@ -494,7 +496,7 @@ def STOP(self): " cpu.mb.setitem(0xFF04, 0)", ]) # code.addLine("raise Exception('STOP not implemented!')") - return code.getcode() + return code def DAA(self): left = Operand("A") @@ -521,12 +523,12 @@ def DAA(self): "t &= 0xFF", left.set % "t", ]) - return code.getcode() + return code def SCF(self): code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles) code.addlines(self.handleflags8bit(None, None, None)) - return code.getcode() + return code def CCF(self): code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles) @@ -535,14 +537,14 @@ def CCF(self): "cpu.F &= 0b10000000", "cpu.F |= flag", ]) - return code.getcode() + return code def CPL(self): left = Operand("A") code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles) code.addline(left.set % ("(~%s) & 0xFF" % left.get)) code.addlines(self.handleflags8bit(None, None, None)) - return code.getcode() + return code ################################################################### # @@ -603,7 +605,7 @@ def LD(self): code.addlines(self.handleflags16bit_E8_F8("cpu.SP", "v", "+", False)) code.addline("cpu.HL &= 0xFFFF") - return code.getcode() + return code def LDH(self): return self.LD() @@ -656,7 +658,7 @@ def ADD(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "+")) - return code.getcode() + return code def SUB(self): if self.name.find(",") > 0: @@ -672,7 +674,7 @@ def SUB(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "-")) - return code.getcode() + return code def INC(self): r0 = self.name.split()[1] @@ -690,7 +692,7 @@ def INC(self): code.lines.insert(-1, "cpu.cycles += 4") # Inject before read code.cycles = ("8", ) # 12 - 4 - return code.getcode() + return code def DEC(self): r0 = self.name.split()[1] @@ -708,7 +710,7 @@ def DEC(self): code.lines.insert(-1, "cpu.cycles += 4") # Inject before write code.cycles = ("8", ) # 12 - 4 - return code.getcode() + return code def ADC(self): if self.name.find(",") > 0: @@ -724,7 +726,7 @@ def ADC(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "+", carry=True)) - return code.getcode() + return code def SBC(self): if self.name.find(",") > 0: @@ -740,7 +742,7 @@ def SBC(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "-", carry=True)) - return code.getcode() + return code def AND(self): if self.name.find(",") > 0: @@ -756,7 +758,7 @@ def AND(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "&")) - return code.getcode() + return code def OR(self): if self.name.find(",") > 0: @@ -772,7 +774,7 @@ def OR(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "|")) - return code.getcode() + return code def XOR(self): if self.name.find(",") > 0: @@ -788,7 +790,7 @@ def XOR(self): self.name.split()[0], self.opcode, self.name, left.immediate or right.immediate, self.length, self.cycles ) code.addlines(self.ALU(left, right, "^")) - return code.getcode() + return code def CP(self): r1 = self.name.split()[1] @@ -801,7 +803,7 @@ def CP(self): # CP is equal to SUB, but without saving the result. # Therefore; we discard the last instruction. code.addlines(self.ALU(left, right, "-")[:-1]) - return code.getcode() + return code ################################################################### # @@ -831,7 +833,7 @@ def PUSH(self): code.addline("cpu.SP -= 2") code.addline("cpu.SP &= 0xFFFF") - return code.getcode() + return code def POP(self): r0 = self.name.split()[1] @@ -859,7 +861,7 @@ def POP(self): code.addline("cpu.SP += 2") code.addline("cpu.SP &= 0xFFFF") - return code.getcode() + return code ################################################################### # @@ -905,7 +907,7 @@ def JP(self): "\tcpu.cycles += " + self.cycles[1], ]) - return code.getcode() + return code def JR(self): if self.name.find(",") > 0: @@ -946,7 +948,7 @@ def JR(self): "\tcpu.cycles += " + self.cycles[1], ]) - return code.getcode() + return code def CALL(self): if self.name.find(",") > 0: @@ -998,7 +1000,7 @@ def CALL(self): "\tcpu.cycles += " + self.cycles[1], ]) - return code.getcode() + return code def RET(self): if self.name == "RET": @@ -1037,7 +1039,7 @@ def RET(self): "\tcpu.cycles += " + self.cycles[1], ]) - return code.getcode() + return code def RETI(self): code = Code(self.name.split()[0], self.opcode, self.name, False, self.length, self.cycles, branch_op=True) @@ -1051,7 +1053,7 @@ def RETI(self): "cpu.cycles += " + self.cycles[0], ]) - return code.getcode() + return code def RST(self): r1 = self.name.split()[1] @@ -1074,7 +1076,7 @@ def RST(self): "cpu.cycles += " + self.cycles[0], ]) - return code.getcode() + return code ################################################################### # @@ -1104,24 +1106,24 @@ def rotateleft(self, name, left, throughcarry=False): def RLA(self): left = Operand("A") code = self.rotateleft(self.name.split()[0], left, throughcarry=True) - return code.getcode() + return code def RLCA(self): left = Operand("A") code = self.rotateleft(self.name.split()[0], left) - return code.getcode() + return code def RLC(self): r0 = self.name.split()[1] left = Operand(r0) code = self.rotateleft(self.name.split()[0], left) - return code.getcode() + return code def RL(self): r0 = self.name.split()[1] left = Operand(r0) code = self.rotateleft(self.name.split()[0], left, throughcarry=True) - return code.getcode() + return code def rotateright(self, name, left, throughcarry=False): code = Code(name, self.opcode, self.name, False, self.length, self.cycles) @@ -1149,24 +1151,24 @@ def rotateright(self, name, left, throughcarry=False): def RRA(self): left = Operand("A") code = self.rotateright(self.name.split()[0], left, throughcarry=True) - return code.getcode() + return code def RRCA(self): left = Operand("A") code = self.rotateright(self.name.split()[0], left) - return code.getcode() + return code def RRC(self): r0 = self.name.split()[1] left = Operand(r0) code = self.rotateright(self.name.split()[0], left) - return code.getcode() + return code def RR(self): r0 = self.name.split()[1] left = Operand(r0) code = self.rotateright(self.name.split()[0], left, throughcarry=True) - return code.getcode() + return code def SLA(self): r0 = self.name.split()[1] @@ -1184,7 +1186,7 @@ def SLA(self): code.cycles = ("8", ) # 16 - 4 - 4 code.addline(left.set % "t") - return code.getcode() + return code def SRA(self): r0 = self.name.split()[1] @@ -1205,7 +1207,7 @@ def SRA(self): code.cycles = ("8", ) # 16 - 4 - 4 code.addline(left.set % "t") - return code.getcode() + return code def SRL(self): r0 = self.name.split()[1] @@ -1224,7 +1226,7 @@ def SRL(self): code.cycles = ("8", ) # 16 - 4 - 4 code.addline(left.set % "t") - return code.getcode() + return code def SWAP(self): r0 = self.name.split()[1] @@ -1242,7 +1244,7 @@ def SWAP(self): code.cycles = ("8", ) # 16 - 4 - 4 code.addline(left.set % "t") - return code.getcode() + return code ################################################################### # @@ -1264,7 +1266,7 @@ def BIT(self): code.addline("t = %s & (1 << %s)" % (right.get, left.get)) code.addlines(self.handleflags8bit(left.get, right.get, None, False)) - return code.getcode() + return code def RES(self): r0, r1 = self.name.split()[1].split(",") @@ -1282,7 +1284,7 @@ def RES(self): code.cycles = ("8", ) # 16 - 4 - 4 code.addline(right.set % "t") - return code.getcode() + return code def SET(self): r0, r1 = self.name.split()[1].split(",") @@ -1299,7 +1301,7 @@ def SET(self): code.cycles = ("8", ) # 16 - 4 - 4 code.addline(right.set % "t") - return code.getcode() + return code def update(): @@ -1309,6 +1311,8 @@ def update(): parser = MyHTMLParser() parser.feed(html) + breakpoint() + opcodefunctions = map(lambda x: (None, None) if x is None else x.createfunction(), opcodes) with open(destination, "w") as f, open(pxd_destination, "w") as f_pxd: @@ -1318,6 +1322,7 @@ def update(): f_pxd.write(cimports) lookuplist = [] for lookuptuple, code in opcodefunctions: + breakpoint() lookuplist.append(lookuptuple) if code is None: diff --git a/pyboy/core/test_func.pyx b/pyboy/core/test_func.pyx new file mode 100644 index 000000000..a12725357 --- /dev/null +++ b/pyboy/core/test_func.pyx @@ -0,0 +1,5 @@ +cimport cpu + + +cdef test_func(cpu.CPU _cpu): + return _cpu.A \ No newline at end of file From 363f7c25919775b0a95f42af12f9d7c064cc27db Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 21 Mar 2024 23:47:39 +0100 Subject: [PATCH 24/51] wip working compile --- pyboy/core/cpu.pxd | 2 + pyboy/core/cpu.py | 10 ++- pyboy/core/mb.pxd | 3 + pyboy/core/mb.py | 150 +++++++++++++++++++++++++++++++++++++- pyboy/core/opcodes.py | 4 + pyboy/core/opcodes_gen.py | 37 ++++++---- pyboy/core/test_func.pyx | 5 -- 7 files changed, 189 insertions(+), 22 deletions(-) delete mode 100644 pyboy/core/test_func.pyx diff --git a/pyboy/core/cpu.pxd b/pyboy/core/cpu.pxd index c53e51f16..a6667b535 100644 --- a/pyboy/core/cpu.pxd +++ b/pyboy/core/cpu.pxd @@ -27,12 +27,14 @@ cdef uint8_t INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW cdef class CPU: cdef bint is_stuck cdef bint interrupt_master_enable, interrupt_queued, halted, stopped, bail + cdef bint jit_jump cdef uint8_t interrupts_flag, interrupts_enabled, interrupts_flag_register, interrupts_enabled_register cdef int64_t cycles cdef inline int check_interrupts(self) noexcept nogil + cdef inline int pending_interrupt(self) noexcept nogil cdef void set_interruptflag(self, int) noexcept nogil cdef bint handle_interrupt(self, uint8_t, uint16_t) noexcept nogil diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 132d10cf2..27ba77d76 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -43,6 +43,8 @@ def __init__(self, mb): self.is_stuck = False self.cycles = 0 + self.jit_jump = False + def save_state(self, f): for n in [self.A, self.F, self.B, self.C, self.D, self.E]: f.write(n & 0xFF) @@ -59,8 +61,6 @@ def save_state(self, f): f.write_64bit(self.cycles) def load_state(self, f, state_version): - test = cython.inline("print(self.A)", self=self) - test() self.A, self.F, self.B, self.C, self.D, self.E = [f.read() for _ in range(6)] self.HL = f.read_16bit() self.SP = f.read_16bit() @@ -85,7 +85,7 @@ def dump_state(self, sym_label): ] # Max 3 length, then we don't need to backtrack opcode = opcode_data[0] - opcode_length = opcodes.OPCODE_LENGTHS[opcode] + opcode_length = opcodes.get_length(opcode) opcode_str = f"Opcode: [{opcodes.CPU_COMMANDS[opcode]}]" if opcode == 0xCB: opcode_str += f" {opcodes.CPU_COMMANDS[opcode_data[1]+0x100]}" @@ -147,6 +147,10 @@ def tick(self, cycles_target): if self.bail: # Possible cycles-target changes break + def pending_interrupt(self): + return self.interrupt_queued or (self.interrupts_flag_register & + 0b11111) & (self.interrupts_enabled_register & 0b11111) + def check_interrupts(self): if self.interrupt_queued: # Interrupt already queued. This happens only when using a debugger. diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 065c12b6d..8f5f59952 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -58,6 +58,9 @@ cdef class Motherboard: cdef inline bint processing_frame(self) noexcept nogil + cdef dict jit_table + cdef int jit(self, int) noexcept with gil + cdef void buttonevent(self, WindowEvent) noexcept cdef void stop(self, bint) noexcept @cython.locals(cycles=int64_t, cycles_target=int64_t, mode0_cycles=int64_t, breakpoint_index=int64_t) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 6090d7a8d..89a103be7 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -3,10 +3,21 @@ # GitHub: https://github.com/Baekalfen/PyBoy # +import hashlib +import importlib +import os +from distutils.command.build_ext import build_ext +from distutils.core import Distribution, Extension +from importlib.machinery import ExtensionFileLoader + +from Cython.Build import cythonize + from pyboy import utils +# from pyboy.core.opcodes import OPCODE_LENGTHS +from pyboy.core.opcodes_gen import opcodes as opcodes_gen from pyboy.utils import STATE_VERSION -from . import bootrom, cartridge, cpu, interaction, lcd, ram, sound, timer +from . import bootrom, cartridge, cpu, interaction, lcd, opcodes, ram, sound, timer INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW = [1 << x for x in range(5)] OPCODE_BRK = 0xDB @@ -18,6 +29,12 @@ MAX_CYCLES = 1 << 16 +class CodeBlock: + def __init__(self, body, eligible=True): + self.body = body + self.eligible = eligible + + class Motherboard: def __init__( self, @@ -83,6 +100,8 @@ def __init__( self.breakpoint_singlestep_latch = False self.breakpoint_waiting = -1 + self.jit_table = {} + def switch_speed(self): bit0 = self.key1 & 0b1 if bit0 == 1: @@ -270,6 +289,130 @@ def load_state(self, f): # Coordinator # + # TODO: Taken from https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L95 + def _get_build_extension(self): + dist = Distribution() + # Ensure the build respects distutils configuration by parsing + # the configuration files + config_files = dist.find_config_files() + dist.parse_config_files(config_files) + build_extension = build_ext(dist) + build_extension.finalize_options() + return build_extension + + def jit_compile(self, code_text): + # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 + m = hashlib.sha1() + m.update(code_text.encode()) + _hash = m.digest().hex() + jit_file = os.path.splitext(self.cartridge.filename + )[0].replace(".", "_") + "_jit_" + _hash + ".pyx" # Generate name + with open(jit_file, "w") as f: + f.write(code_text) + + module_name = "jit" + _hash + cythonize_files = [ + Extension( + module_name, #src.split(".")[0].replace(os.sep, "."), + [jit_file], + extra_compile_args=["-O3"], + # include_dirs=[np.get_include()], + ) + ] + build_extension = self._get_build_extension() + build_extension.extensions = cythonize( + [*cythonize_files], + nthreads=1, + annotate=False, + gdb_debug=False, + language_level=3, + compiler_directives={ + "boundscheck": False, + "cdivision": True, + "cdivision_warnings": False, + "infer_types": True, + "initializedcheck": False, + "nonecheck": False, + "overflowcheck": False, + # "profile" : True, + "wraparound": False, + "legacy_implicit_noexcept": True, + }, + ) + build_extension.inplace = True + # build_extension.build_temp = "./"# os.path.dirname(jit_file) + build_extension.run() + + module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' + spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, module_path)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + module.execute(self.cpu) + # module = load_dynamic(module_name, module_path) + exit(1) + code_body = lambda x: x + code = CodeBlock(code_body) + return code + + def jit_emit_code(self, code_block): + preamble = """ +cimport pyboy + +from libc.stdint cimport uint8_t, uint16_t, uint32_t +cimport cython +from pyboy.core cimport cpu as _cpu + +cdef uint8_t FLAGC = 4 +cdef uint8_t FLAGH = 5 +cdef uint8_t FLAGN = 6 +cdef uint8_t FLAGZ = 7 + +""" + code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef uint8_t t\n\tcdef uint16_t v\n\tcdef int cycles = 0" + for opcode, length, literal1, literal2 in code_block: + opcode_handler = opcodes_gen[opcode] + opcode_name = opcode_handler.name.split()[0] + code_text += "\n\t" + "# " + opcode_handler.name + "\n\t" + if length == 2: + v = literal1 + code_text += "v = " + str(v) + "\n\t" + elif length == 3: + v = (literal2 << 8) + literal1 + code_text += "v = " + str(v) + "\n\t" + + code_text += opcode_handler.functionhandlers[opcode_name]()._code_body().replace("return", "cycles +=") + + code_text += "\n\treturn cycles" + # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op + # if .getitem in code, commit timer.tick(cycles); cycles = 0 + return code_text + + def jit_analyze(self): + boundary_instruction = [0x20] # jumps, rets, rst + code_block = [] + pc = self.cpu.PC + while self.getitem(pc) not in boundary_instruction: + instruction = self.getitem(pc) + instruction_length = opcodes.get_length(instruction) + code_block.append((instruction, instruction_length, self.getitem(pc + 1), self.getitem(pc + 2))) + pc += instruction_length + + # if len(code_block) < 10: + # return CodeBlock(None, eligible=False) + + code_text = self.jit_emit_code(code_block) + return self.jit_compile(code_text) + + def jit(self, cycles): + code = self.jit_table.get(self.cpu.PC) # Bank collision! + if not code: + code = self.jit_analyze() + self.jit_table[self.cpu.PC] = code # Bank collision! + if code.eligible and code.cycles <= cycles: + return code.body() + return 0 + def processing_frame(self): b = (not self.lcd.frame_done) self.lcd.frame_done = False # Clear vblank flag for next iteration @@ -304,6 +447,11 @@ def tick(self): ) self.cpu.tick(cycles_target) + if self.cpu.jit_jump: + if not self.cpu.pending_interrupt(): + cycles = self.jit(cycles) + self.cpu.jit_jump = False + #TODO: Support General Purpose DMA # https://gbdev.io/pandocs/CGB_Registers.html#bit-7--0---general-purpose-dma diff --git a/pyboy/core/opcodes.py b/pyboy/core/opcodes.py index 21fed5341..b1a992be6 100644 --- a/pyboy/core/opcodes.py +++ b/pyboy/core/opcodes.py @@ -11,6 +11,9 @@ FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8) +def get_length(opcode): + return OPCODE_LENGTHS[opcode] + def BRK(cpu): cpu.bail = True cpu.mb.breakpoint_singlestep = 1 @@ -360,6 +363,7 @@ def JR_20(cpu, v): # 20 JR NZ,r8 if ((cpu.F & (1 << FLAGZ)) == 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 12 else: cpu.PC &= 0xFFFF diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index b57eb3240..1a0bacff4 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -3,6 +3,7 @@ # GitHub: https://github.com/Baekalfen/PyBoy # +import os import re from html.parser import HTMLParser from urllib.request import urlopen @@ -25,6 +26,9 @@ FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8) +def get_length(opcode): + return OPCODE_LENGTHS[opcode] + def BRK(cpu): cpu.bail = True cpu.mb.breakpoint_singlestep = 1 @@ -47,6 +51,7 @@ def BRK(cpu): cdef uint16_t FLAGC, FLAGH, FLAGN, FLAGZ cdef uint8_t[512] OPCODE_LENGTHS +cdef uint8_t get_length(int) noexcept nogil @cython.locals(v=cython.int, a=cython.int, b=cython.int, pc=cython.ushort) cdef int execute_opcode(cpu.CPU, uint16_t) noexcept nogil @@ -264,7 +269,7 @@ def getcode(self): "def %s_%0.2X(cpu, v): # %0.2X %s" % (self.function_name, self.opcode, self.opcode, self.name) ][self.takes_immediate] code += "\n\t" - code += self._code_body(code) + code += self._code_body() pxd = [ "cdef uint8_t %s_%0.2X(cpu.CPU) noexcept nogil # %0.2X %s" % @@ -985,6 +990,7 @@ def CALL(self): "cpu.SP -= 2", "cpu.SP &= 0xFFFF", "cpu.PC = %s" % ("v" if right.immediate else right.get), + "cpu.jit_jump = True", "cpu.cycles += " + self.cycles[0], ]) else: @@ -995,6 +1001,7 @@ def CALL(self): "\tcpu.SP -= 2", "\tcpu.SP &= 0xFFFF", "\tcpu.PC = %s" % ("v" if right.immediate else right.get), + "\tcpu.jit_jump = True", "\tcpu.cycles += " + self.cycles[0], "else:", "\tcpu.cycles += " + self.cycles[1], @@ -1073,6 +1080,7 @@ def RST(self): code.addlines([ "cpu.PC = %s" % (right.code), + "cpu.jit_jump = True", "cpu.cycles += " + self.cycles[0], ]) @@ -1305,14 +1313,6 @@ def SET(self): def update(): - response = urlopen("http://pastraiser.com/cpu/gameboy/gameboy_opcodes.html") - html = response.read().replace(b" ", b"
").decode() - - parser = MyHTMLParser() - parser.feed(html) - - breakpoint() - opcodefunctions = map(lambda x: (None, None) if x is None else x.createfunction(), opcodes) with open(destination, "w") as f, open(pxd_destination, "w") as f_pxd: @@ -1389,10 +1389,21 @@ def execute_opcode(cpu, opcode): def load(): - # if os.path.exists(destination): - # return - update() + cache = "cache.html" + if not os.path.isfile(cache): + response = urlopen("http://pastraiser.com/cpu/gameboy/gameboy_opcodes.html") + html = response.read().replace(b" ", b"
").decode() + + with open(cache, "w") as f: + f.write(html) + else: + with open(cache, "r") as f: + html = f.read() + + parser = MyHTMLParser() + parser.feed(html) +load() if __name__ == "__main__": - load() + update() diff --git a/pyboy/core/test_func.pyx b/pyboy/core/test_func.pyx deleted file mode 100644 index a12725357..000000000 --- a/pyboy/core/test_func.pyx +++ /dev/null @@ -1,5 +0,0 @@ -cimport cpu - - -cdef test_func(cpu.CPU _cpu): - return _cpu.A \ No newline at end of file From b29ee4be870aaff7040f3ace7de2e3a314491deb Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Fri, 22 Mar 2024 19:12:05 +0100 Subject: [PATCH 25/51] jit default_rom working --- pyboy/core/mb.pxd | 1 + pyboy/core/mb.py | 153 +++++++++++++++++++++++++++++++++------------- setup.py | 2 +- 3 files changed, 111 insertions(+), 45 deletions(-) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 8f5f59952..06ba4cab3 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -60,6 +60,7 @@ cdef class Motherboard: cdef dict jit_table cdef int jit(self, int) noexcept with gil + cdef void clear_jit_table(self) noexcept with gil cdef void buttonevent(self, WindowEvent) noexcept cdef void stop(self, bint) noexcept diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 89a103be7..a1dd3289c 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -32,6 +32,7 @@ class CodeBlock: def __init__(self, body, eligible=True): self.body = body + # self.cycles = cycles self.eligible = eligible @@ -305,54 +306,57 @@ def jit_compile(self, code_text): m = hashlib.sha1() m.update(code_text.encode()) _hash = m.digest().hex() - jit_file = os.path.splitext(self.cartridge.filename - )[0].replace(".", "_") + "_jit_" + _hash + ".pyx" # Generate name - with open(jit_file, "w") as f: - f.write(code_text) module_name = "jit" + _hash - cythonize_files = [ - Extension( - module_name, #src.split(".")[0].replace(os.sep, "."), - [jit_file], - extra_compile_args=["-O3"], - # include_dirs=[np.get_include()], + module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' + + if not os.path.exists(module_path): + logger.debug("Compiling JIT block: %s", module_path) + jit_file = os.path.splitext(self.cartridge.filename + )[0].replace(".", "_") + "_jit_" + _hash + ".pyx" # Generate name + with open(jit_file, "w") as f: + f.write(code_text) + + cythonize_files = [ + Extension( + module_name, #src.split(".")[0].replace(os.sep, "."), + [jit_file], + extra_compile_args=["-O3"], + # include_dirs=[np.get_include()], + ) + ] + build_extension = self._get_build_extension() + build_extension.extensions = cythonize( + [*cythonize_files], + nthreads=1, + annotate=False, + gdb_debug=False, + language_level=3, + compiler_directives={ + "boundscheck": False, + "cdivision": True, + "cdivision_warnings": False, + "infer_types": True, + "initializedcheck": False, + "nonecheck": False, + "overflowcheck": False, + # "profile" : True, + "wraparound": False, + "legacy_implicit_noexcept": True, + }, ) - ] - build_extension = self._get_build_extension() - build_extension.extensions = cythonize( - [*cythonize_files], - nthreads=1, - annotate=False, - gdb_debug=False, - language_level=3, - compiler_directives={ - "boundscheck": False, - "cdivision": True, - "cdivision_warnings": False, - "infer_types": True, - "initializedcheck": False, - "nonecheck": False, - "overflowcheck": False, - # "profile" : True, - "wraparound": False, - "legacy_implicit_noexcept": True, - }, - ) - build_extension.inplace = True - # build_extension.build_temp = "./"# os.path.dirname(jit_file) - build_extension.run() + build_extension.inplace = True + # build_extension.build_temp = "./"# os.path.dirname(jit_file) + build_extension.run() + else: + logger.debug("JIT block already compiled: %s", module_path) - module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, module_path)) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - module.execute(self.cpu) - # module = load_dynamic(module_name, module_path) - exit(1) - code_body = lambda x: x - code = CodeBlock(code_body) + # module.execute(self.cpu) + code = CodeBlock(module.execute) return code def jit_emit_code(self, code_block): @@ -389,7 +393,61 @@ def jit_emit_code(self, code_block): return code_text def jit_analyze(self): - boundary_instruction = [0x20] # jumps, rets, rst + boundary_instruction = [ + # JR + 0x20, + 0x30, + + # JR + 0x18, + 0x28, + 0x38, + + # RET + 0xC0, + 0xD0, + + # JP + 0xC2, + 0xD2, + + # JP + 0xC3, + + # CALL + 0xC4, + 0xD4, + + # RST + 0xC7, + 0xD7, + 0xE7, + 0xF7, + + # RET + 0xC8, + 0xD8, + 0xC9, # RET + 0xD9, # RETI + 0xE9, # JP + + # JP + 0xCA, + 0xDA, + + # CALL + 0xCC, + 0xDC, + + # CALL + 0xDD, + + # RST + 0xCF, + 0xDF, + 0xEF, + 0xFF, + ] code_block = [] pc = self.cpu.PC while self.getitem(pc) not in boundary_instruction: @@ -398,11 +456,13 @@ def jit_analyze(self): code_block.append((instruction, instruction_length, self.getitem(pc + 1), self.getitem(pc + 2))) pc += instruction_length - # if len(code_block) < 10: + # if len(code_block) < 3: # return CodeBlock(None, eligible=False) code_text = self.jit_emit_code(code_block) - return self.jit_compile(code_text) + code = self.jit_compile(code_text) + code.cycles = (pc - self.cpu.PC) * 4 # TODO + return code def jit(self, cycles): code = self.jit_table.get(self.cpu.PC) # Bank collision! @@ -410,9 +470,13 @@ def jit(self, cycles): code = self.jit_analyze() self.jit_table[self.cpu.PC] = code # Bank collision! if code.eligible and code.cycles <= cycles: - return code.body() + logger.info("Executing JIT block: PC=%x, cycles=%s", self.cpu.PC, code.cycles) + return code.body(self.cpu) return 0 + def clear_jit_table(self): + self.jit_table = {} + def processing_frame(self): b = (not self.lcd.frame_done) self.lcd.frame_done = False # Clear vblank flag for next iteration @@ -687,6 +751,7 @@ def setitem(self, i, value): logger.debug("Bootrom disabled!") self.bootrom_enabled = False self.cpu.bail = True + self.clear_jit_table() # CGB registers elif self.cgb and i == 0xFF4D: self.key1 = value diff --git a/setup.py b/setup.py index 48c85323e..afc550ec5 100644 --- a/setup.py +++ b/setup.py @@ -94,7 +94,7 @@ def prep_pxd_py_files(): # We also yield the py_files that have a .pxd file, as we feed these into the cythonize call. for root, dirs, files in os.walk(ROOT_DIR): for f in files: - if os.path.splitext(f)[1] in [".py", ".pyx"] and f not in ignore_py_files: + if os.path.splitext(f)[1] in [".py", ".pyx"] and f not in ignore_py_files and "_jit_" not in f: yield os.path.join(root, f) if os.path.splitext(f)[1] == ".pxd": py_file = os.path.join(root, os.path.splitext(f)[0]) + ".py" From c11cc49188a50706b0c926265ae4bb821b599dd0 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Fri, 22 Mar 2024 19:23:07 +0100 Subject: [PATCH 26/51] CB support --- pyboy/core/mb.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index a1dd3289c..922273beb 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -307,7 +307,7 @@ def jit_compile(self, code_text): m.update(code_text.encode()) _hash = m.digest().hex() - module_name = "jit" + _hash + module_name = "jit_" + _hash module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' if not os.path.exists(module_path): @@ -451,10 +451,14 @@ def jit_analyze(self): code_block = [] pc = self.cpu.PC while self.getitem(pc) not in boundary_instruction: - instruction = self.getitem(pc) - instruction_length = opcodes.get_length(instruction) - code_block.append((instruction, instruction_length, self.getitem(pc + 1), self.getitem(pc + 2))) - pc += instruction_length + opcode = self.getitem(pc) + if opcode == 0xCB: # Extension code + pc += 1 + opcode = self.getitem(pc) + opcode += 0x100 # Internally shifting look-up table + opcode_length = opcodes.get_length(opcode) + code_block.append((opcode, opcode_length, self.getitem(pc + 1), self.getitem(pc + 2))) + pc += opcode_length # if len(code_block) < 3: # return CodeBlock(None, eligible=False) From 9efdd96728bd338cc6f7e620eb7075c8a7ce2a20 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Fri, 22 Mar 2024 22:57:28 +0100 Subject: [PATCH 27/51] Clearing bootrom --- Makefile | 6 +++- pyboy/core/cpu.pxd | 2 +- pyboy/core/cpu.py | 29 +++++++++--------- pyboy/core/mb.pxd | 5 +++- pyboy/core/mb.py | 55 +++++++++++++++++++++++------------ pyboy/core/opcodes_gen.py | 6 ++++ pyboy/plugins/debug_prompt.py | 2 +- 7 files changed, 69 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index ffa7f541a..4573c5e01 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,11 @@ build: cd ${ROOT_DIR}/extras/bootrom && $(MAKE) CFLAGS=$(CFLAGS) ${PY} setup.py build_ext -j $(shell getconf _NPROCESSORS_ONLN) --inplace -clean: + +clean_jit: + find . -type f -name "*jit_*" -delete + +clean: clean_jit @echo "Cleaning..." cd ${ROOT_DIR}/extras/default_rom && $(MAKE) clean cd ${ROOT_DIR}/extras/bootrom && $(MAKE) clean diff --git a/pyboy/core/cpu.pxd b/pyboy/core/cpu.pxd index a6667b535..7eb28e591 100644 --- a/pyboy/core/cpu.pxd +++ b/pyboy/core/cpu.pxd @@ -55,4 +55,4 @@ cdef class CPU: cdef pyboy.core.mb.Motherboard mb - cdef dump_state(self, str) noexcept with gil + cdef void dump_state(CPU) noexcept with gil diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 27ba77d76..028cd8de9 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -77,9 +77,10 @@ def load_state(self, f, state_version): self.interrupts_flag_register = f.read() if state_version >= 12: self.cycles = f.read_64bit() - logger.debug("State loaded: %s", self.dump_state("")) + # logger.debug("State loaded: %s", self.dump_state("")) - def dump_state(self, sym_label): + def dump_state(self): + sym_label = "" opcode_data = [ self.mb.getitem(self.mb.cpu.PC + n) for n in range(3) ] # Max 3 length, then we don't need to backtrack @@ -92,20 +93,20 @@ def dump_state(self, sym_label): else: opcode_str += " " + " ".join(f"{d:02X}" for d in opcode_data[1:opcode_length]) - return ( - "\n" + print( + # "\n" f"A: {self.mb.cpu.A:02X}, F: {self.mb.cpu.F:02X}, B: {self.mb.cpu.B:02X}, " f"C: {self.mb.cpu.C:02X}, D: {self.mb.cpu.D:02X}, E: {self.mb.cpu.E:02X}, " - f"HL: {self.mb.cpu.HL:04X}, SP: {self.mb.cpu.SP:04X}, PC: {self.mb.cpu.PC:04X} ({sym_label})\n" - f"{opcode_str} " - f"Interrupts - IME: {self.mb.cpu.interrupt_master_enable}, " - f"IE: {self.mb.cpu.interrupts_enabled_register:08b}, " - f"IF: {self.mb.cpu.interrupts_flag_register:08b}\n" - f"LCD Intr.: {self.mb.lcd._cycles_to_interrupt}, LY:{self.mb.lcd.LY}, LYC:{self.mb.lcd.LYC}\n" - f"Timer Intr.: {self.mb.timer._cycles_to_interrupt}\n" - f"halted:{self.halted}, " - f"interrupt_queued:{self.interrupt_queued}, " - f"stopped:{self.stopped}\n" + f"HL: {self.mb.cpu.HL:04X}, SP: {self.mb.cpu.SP:04X}, PC: {self.mb.cpu.PC:04X} ({sym_label})" #\n" + # f"{opcode_str} " + # f"Interrupts - IME: {self.mb.cpu.interrupt_master_enable}, " + # f"IE: {self.mb.cpu.interrupts_enabled_register:08b}, " + # f"IF: {self.mb.cpu.interrupts_flag_register:08b}\n" + # f"LCD Intr.: {self.mb.lcd.cycles_to_interrupt()}, LY:{self.mb.lcd.LY}, LYC:{self.mb.lcd.LYC}\n" + # f"Timer Intr.: {self.mb.timer.cycles_to_interrupt()}\n" + # f"halted:{self.halted}, " + # f"interrupt_queued:{self.interrupt_queued}, " + # f"stopped:{self.stopped}\n" ) def set_interruptflag(self, flag): diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 06ba4cab3..8ccd8e594 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -59,7 +59,10 @@ cdef class Motherboard: cdef inline bint processing_frame(self) noexcept nogil cdef dict jit_table - cdef int jit(self, int) noexcept with gil + cdef object jit_compile(self, str) noexcept with gil + cdef object jit_emit_code(self, object) noexcept with gil + cdef object jit_analyze(self) noexcept with gil + cdef int64_t jit(self, int64_t) noexcept with gil cdef void clear_jit_table(self) noexcept with gil cdef void buttonevent(self, WindowEvent) noexcept diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 922273beb..285c9e8a6 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -311,7 +311,7 @@ def jit_compile(self, code_text): module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' if not os.path.exists(module_path): - logger.debug("Compiling JIT block: %s", module_path) + logger.info("Compiling JIT block: %s", module_path) jit_file = os.path.splitext(self.cartridge.filename )[0].replace(".", "_") + "_jit_" + _hash + ".pyx" # Generate name with open(jit_file, "w") as f: @@ -349,7 +349,7 @@ def jit_compile(self, code_text): # build_extension.build_temp = "./"# os.path.dirname(jit_file) build_extension.run() else: - logger.debug("JIT block already compiled: %s", module_path) + logger.info("JIT block already compiled: %s", module_path) spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, module_path)) module = importlib.util.module_from_spec(spec) @@ -363,7 +363,7 @@ def jit_emit_code(self, code_block): preamble = """ cimport pyboy -from libc.stdint cimport uint8_t, uint16_t, uint32_t +from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t cimport cython from pyboy.core cimport cpu as _cpu @@ -373,7 +373,7 @@ def jit_emit_code(self, code_block): cdef uint8_t FLAGZ = 7 """ - code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef uint8_t t\n\tcdef uint16_t v\n\tcdef int cycles = 0" + code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef uint16_t t\n\tcdef int16_t v\n\tcdef int cycles = 0" for opcode, length, literal1, literal2 in code_block: opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] @@ -386,6 +386,7 @@ def jit_emit_code(self, code_block): code_text += "v = " + str(v) + "\n\t" code_text += opcode_handler.functionhandlers[opcode_name]()._code_body().replace("return", "cycles +=") + code_text += "\n\tcpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcycles = 0" code_text += "\n\treturn cycles" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op @@ -450,7 +451,7 @@ def jit_analyze(self): ] code_block = [] pc = self.cpu.PC - while self.getitem(pc) not in boundary_instruction: + while True: opcode = self.getitem(pc) if opcode == 0xCB: # Extension code pc += 1 @@ -459,9 +460,13 @@ def jit_analyze(self): opcode_length = opcodes.get_length(opcode) code_block.append((opcode, opcode_length, self.getitem(pc + 1), self.getitem(pc + 2))) pc += opcode_length + if opcode in boundary_instruction: + break - # if len(code_block) < 3: - # return CodeBlock(None, eligible=False) + if len(code_block) < 3: + code = CodeBlock(None, eligible=False) + code.cycles = 0 + return code code_text = self.jit_emit_code(code_block) code = self.jit_compile(code_text) @@ -469,13 +474,19 @@ def jit_analyze(self): return code def jit(self, cycles): - code = self.jit_table.get(self.cpu.PC) # Bank collision! + block_id = self.cpu.PC << 8 + self.cartridge.rombank_selected + code = self.jit_table.get(block_id) # Bank collision! if not code: code = self.jit_analyze() - self.jit_table[self.cpu.PC] = code # Bank collision! - if code.eligible and code.cycles <= cycles: - logger.info("Executing JIT block: PC=%x, cycles=%s", self.cpu.PC, code.cycles) - return code.body(self.cpu) + self.jit_table[block_id] = code # Bank collision! + if code.eligible: + if code.cycles <= cycles: + logger.debug("Executing JIT block: PC=%x, code.cycles=%s, cycles=%d", self.cpu.PC, code.cycles, cycles) + cycles = code.body(self.cpu) + logger.debug("After block: PC=%x, jit_jump=%d", self.cpu.PC, self.cpu.jit_jump) + return cycles + else: + logger.debug("Too narrow to execute JIT block %s, %d", code.cycles, cycles) return 0 def clear_jit_table(self): @@ -513,12 +524,20 @@ def tick(self): mode0_cycles ) ) - self.cpu.tick(cycles_target) - - if self.cpu.jit_jump: - if not self.cpu.pending_interrupt(): - cycles = self.jit(cycles) - self.cpu.jit_jump = False + # cycles_target = 4 + + # Inject special opcode instead? ~~Long immediate as identifier~~ + # Special opcode cannot be more than 1 byte, to avoid jumps to sub-parts of the jit block + # Compile in other thread, acquire memory lock between frames + if self.cpu.jit_jump: + self.cpu.jit_jump = False + if not self.cpu_pending_interrupt() and self.cpu.PC < 0x8000: # and cycles_target > 0: + cycles = self.jit(cycles_target) + # cycles = self.jit(0xFFFF) + else: + cycles = self.cpu.tick(cycles_target) + else: + cycles = self.cpu.tick(cycles_target) #TODO: Support General Purpose DMA # https://gbdev.io/pandocs/CGB_Registers.html#bit-7--0---general-purpose-dma diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index 1a0bacff4..b38609c2b 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -905,6 +905,7 @@ def JP(self): code.addlines([ "if %s:" % l_code, "\tcpu.PC = %s" % ("v" if right.immediate else r_code), + "\tcpu.jit_jump = True", "\tcpu.cycles += " + self.cycles[0], "else:", "\tcpu.PC += %s" % self.length, @@ -939,6 +940,7 @@ def JR(self): code.addlines([ "cpu.PC += %d + " % self.length + inline_signed_int8("v"), "cpu.PC &= 0xFFFF", + "cpu.jit_jump = True", "cpu.cycles += " + self.cycles[0], ]) else: @@ -947,6 +949,7 @@ def JR(self): "if %s:" % l_code, "\tcpu.PC += " + inline_signed_int8("v"), "\tcpu.PC &= 0xFFFF", + "\tcpu.jit_jump = True", "\tcpu.cycles += " + self.cycles[0], "else:", "\tcpu.PC &= 0xFFFF", @@ -1030,6 +1033,7 @@ def RET(self): "cpu.PC |= cpu.mb.getitem(cpu.SP) # Low", "cpu.SP += 2", "cpu.SP &= 0xFFFF", + "cpu.jit_jump = True", "cpu.cycles += " + self.cycles[0], ]) else: @@ -1039,6 +1043,7 @@ def RET(self): "\tcpu.PC |= cpu.mb.getitem(cpu.SP) # Low", "\tcpu.SP += 2", "\tcpu.SP &= 0xFFFF", + "\tcpu.jit_jump = True", "\tcpu.cycles += " + self.cycles[0], "else:", "\tcpu.PC += %s" % self.length, @@ -1057,6 +1062,7 @@ def RETI(self): "cpu.PC |= cpu.mb.getitem(cpu.SP) # Low", "cpu.SP += 2", "cpu.SP &= 0xFFFF", + "cpu.jit_jump = True", "cpu.cycles += " + self.cycles[0], ]) diff --git a/pyboy/plugins/debug_prompt.py b/pyboy/plugins/debug_prompt.py index b57e63f1e..144c16980 100644 --- a/pyboy/plugins/debug_prompt.py +++ b/pyboy/plugins/debug_prompt.py @@ -65,7 +65,7 @@ def handle_breakpoint(self): sym_label = self.rom_symbols.get(bank, {}).get(self.mb.cpu.PC, "") # Print state - print(self.mb.cpu.dump_state(sym_label)) + self.mb.cpu.dump_state() # REPL cmd = input() From 0da9f93c6b57a79b45f42450a4a0f546e15a81b2 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 25 Mar 2024 22:45:07 +0100 Subject: [PATCH 28/51] Much better, but not quite there --- pyboy/__main__.py | 1 + pyboy/core/mb.pxd | 5 +++ pyboy/core/mb.py | 49 ++++++++++++++++++++++++----- pyboy/core/opcodes.pxd | 3 ++ pyboy/core/opcodes.py | 66 +++++++++++++++++++++++++++++++++++++++ pyboy/core/opcodes_gen.py | 38 ++++++++++++++++------ pyboy/pyboy.pxd | 2 ++ pyboy/pyboy.py | 5 +++ tests/test_jit.py | 27 ++++++++++++++++ 9 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 tests/test_jit.py diff --git a/pyboy/__main__.py b/pyboy/__main__.py index c3feff9de..e5d9df765 100644 --- a/pyboy/__main__.py +++ b/pyboy/__main__.py @@ -87,6 +87,7 @@ def valid_file_path(path): parser.add_argument("-s", "--scale", default=defaults["scale"], type=int, help="The scaling multiplier for the window") parser.add_argument("--sound", action="store_true", help="Enable sound (beta)") parser.add_argument("--no-renderer", action="store_true", help="Disable rendering (internal use)") +parser.add_argument("--jit", action="store_true", help="Enable JIT compile (beta)") parser.add_argument( "--gameshark", type=str, diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 8ccd8e594..068b75877 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -15,9 +15,12 @@ cimport pyboy.core.lcd cimport pyboy.core.ram cimport pyboy.core.sound cimport pyboy.core.timer +# cimport pyboy.core.opcodes from pyboy.logging.logging cimport Logger from pyboy.utils cimport IntIOInterface, WindowEvent +from . cimport opcodes + cdef Logger logger @@ -40,6 +43,7 @@ cdef class Motherboard: cdef bint bootrom_enabled cdef char[1024] serialbuffer cdef uint16_t serialbuffer_count + cdef uint64_t _cycles # CGB cdef HDMA hdma @@ -59,6 +63,7 @@ cdef class Motherboard: cdef inline bint processing_frame(self) noexcept nogil cdef dict jit_table + cdef bint jit_enabled cdef object jit_compile(self, str) noexcept with gil cdef object jit_emit_code(self, object) noexcept with gil cdef object jit_analyze(self) noexcept with gil diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 285c9e8a6..c1bb8866a 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -13,7 +13,6 @@ from Cython.Build import cythonize from pyboy import utils -# from pyboy.core.opcodes import OPCODE_LENGTHS from pyboy.core.opcodes_gen import opcodes as opcodes_gen from pyboy.utils import STATE_VERSION @@ -46,6 +45,7 @@ def __init__( sound_enabled, sound_emulated, cgb, + jit_enabled, randomize=False, ): if bootrom_file is not None: @@ -101,7 +101,9 @@ def __init__( self.breakpoint_singlestep_latch = False self.breakpoint_waiting = -1 + self.jit_enabled = jit_enabled self.jit_table = {} + self._cycles = 0 def switch_speed(self): bit0 = self.key1 & 0b1 @@ -386,7 +388,10 @@ def jit_emit_code(self, code_block): code_text += "v = " + str(v) + "\n\t" code_text += opcode_handler.functionhandlers[opcode_name]()._code_body().replace("return", "cycles +=") - code_text += "\n\tcpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcycles = 0" + # code_text += "\n\tcpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcycles = 0" + # if "setitem" in code_text: + # code_text.replace() + # code_text += "\n\tif" code_text += "\n\treturn cycles" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op @@ -448,16 +453,23 @@ def jit_analyze(self): 0xDF, 0xEF, 0xFF, + 0x76, # HALT + 0x10, # STOP ] code_block = [] pc = self.cpu.PC + block_max_cycles = 0 while True: opcode = self.getitem(pc) if opcode == 0xCB: # Extension code pc += 1 opcode = self.getitem(pc) opcode += 0x100 # Internally shifting look-up table - opcode_length = opcodes.get_length(opcode) + # opcode_length = opcodes.get_length(opcode) + # opcode_max_cycles = opcodes.get_max_cycles(opcode) + opcode_length = opcodes.OPCODE_LENGTHS[opcode] + opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] + block_max_cycles += opcode_max_cycles code_block.append((opcode, opcode_length, self.getitem(pc + 1), self.getitem(pc + 2))) pc += opcode_length if opcode in boundary_instruction: @@ -470,7 +482,7 @@ def jit_analyze(self): code_text = self.jit_emit_code(code_block) code = self.jit_compile(code_text) - code.cycles = (pc - self.cpu.PC) * 4 # TODO + code.cycles = block_max_cycles # (pc - self.cpu.PC) * 4 # TODO return code def jit(self, cycles): @@ -498,8 +510,18 @@ def processing_frame(self): return b def tick(self): + # Threading by 'LD_21(cpu, v, cycles_left, cycles_acc)' + # cpu.HL = v + # cpu.PC += 3 + # cpu.PC &= 0xFFFF + # if (cycles_left < 12): + # return cpu.next_opcode(cpu, cycles_left, cycles_acc + 12) + # else: + # return 12 + while self.processing_frame(): if self.cgb and self.hdma.transfer_active and self.lcd._STAT._mode & 0b11 == 0: + self.cpu.jit_jump = False self.cpu.cycles = self.cpu.cycles + self.hdma.tick(self) else: # Fast-forward to next interrupt: @@ -514,7 +536,7 @@ def tick(self): mode0_cycles = self.lcd.cycles_to_mode0() cycles_target = max( - 4, + 0, #4, min( self.timer._cycles_to_interrupt, self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled. @@ -522,14 +544,14 @@ def tick(self): self.sound._cycles_to_interrupt, # TODO: Not implemented # self.serial.cycles_to_interrupt(), mode0_cycles - ) + ) # - 24 # TODO: Avoid overshooting? ) # cycles_target = 4 # Inject special opcode instead? ~~Long immediate as identifier~~ # Special opcode cannot be more than 1 byte, to avoid jumps to sub-parts of the jit block # Compile in other thread, acquire memory lock between frames - if self.cpu.jit_jump: + if self.jit_enabled and self.cpu.jit_jump: self.cpu.jit_jump = False if not self.cpu_pending_interrupt() and self.cpu.PC < 0x8000: # and cycles_target > 0: cycles = self.jit(cycles_target) @@ -551,6 +573,8 @@ def tick(self): if lcd_interrupt: self.cpu.set_interruptflag(lcd_interrupt) + self._cycles += cycles + if self.breakpoint_singlestep: break @@ -677,11 +701,21 @@ def setitem(self, i, value): if 0x0000 <= i < 0x4000: # 16kB ROM bank #0 # Doesn't change the data. This is for MBC commands self.cartridge.setitem(i, value) +<<<<<<< HEAD self.cpu.bail = True + return 1 elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank # Doesn't change the data. This is for MBC commands self.cartridge.setitem(i, value) self.cpu.bail = True + return 1 +======= + self.cpu.bail = True # TODO: This is not something to bail for in non-jit mode + elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank + # Doesn't change the data. This is for MBC commands + self.cartridge.setitem(i, value) + self.cpu.bail = True # TODO: This is not something to bail for in non-jit mode +>>>>>>> f44ecdec (fixup! Much better, but not quite there) elif 0x8000 <= i < 0xA000: # 8kB Video RAM if not self.cgb or self.lcd.vbk.active_bank == 0: self.lcd.VRAM0[i - 0x8000] = value @@ -806,6 +840,7 @@ def setitem(self, i, value): self.lcd.renderer.clear_spritecache1() else: self.ram.non_io_internal_ram1[i - 0xFF4C] = value + return 3 elif 0xFF80 <= i < 0xFFFF: # Internal RAM self.ram.internal_ram1[i - 0xFF80] = value elif i == 0xFFFF: # Interrupt Enable Register diff --git a/pyboy/core/opcodes.pxd b/pyboy/core/opcodes.pxd index e14b47bea..d0af7016d 100644 --- a/pyboy/core/opcodes.pxd +++ b/pyboy/core/opcodes.pxd @@ -15,6 +15,9 @@ cdef Logger logger cdef uint16_t FLAGC, FLAGH, FLAGN, FLAGZ cdef uint8_t[512] OPCODE_LENGTHS +cdef uint8_t get_length(int) noexcept nogil +cdef uint8_t[512] OPCODE_MAX_CYCLES +cdef uint8_t get_max_cycles(int) noexcept nogil @cython.locals(v=cython.int, a=cython.int, b=cython.int, pc=cython.ushort) cdef int execute_opcode(cpu.CPU, uint16_t) noexcept nogil diff --git a/pyboy/core/opcodes.py b/pyboy/core/opcodes.py index b1a992be6..ec55f29ff 100644 --- a/pyboy/core/opcodes.py +++ b/pyboy/core/opcodes.py @@ -14,6 +14,9 @@ def get_length(opcode): return OPCODE_LENGTHS[opcode] +def get_max_cycles(opcode): + return OPCODE_MAX_CYCLES[opcode] + def BRK(cpu): cpu.bail = True cpu.mb.breakpoint_singlestep = 1 @@ -275,6 +278,7 @@ def RLA_17(cpu): # 17 RLA def JR_18(cpu, v): # 18 JR r8 cpu.PC += 2 + ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 12 @@ -459,6 +463,7 @@ def JR_28(cpu, v): # 28 JR Z,r8 if ((cpu.F & (1 << FLAGZ)) != 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 12 else: cpu.PC &= 0xFFFF @@ -548,6 +553,7 @@ def JR_30(cpu, v): # 30 JR NC,r8 if ((cpu.F & (1 << FLAGC)) == 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 12 else: cpu.PC &= 0xFFFF @@ -632,6 +638,7 @@ def JR_38(cpu, v): # 38 JR C,r8 if ((cpu.F & (1 << FLAGC)) != 0): cpu.PC += ((v ^ 0x80) - 0x80) cpu.PC &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 12 else: cpu.PC &= 0xFFFF @@ -2072,6 +2079,7 @@ def RET_C0(cpu): # C0 RET NZ cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 20 else: cpu.PC += 1 @@ -2092,6 +2100,7 @@ def POP_C1(cpu): # C1 POP BC def JP_C2(cpu, v): # C2 JP NZ,a16 if ((cpu.F & (1 << FLAGZ)) == 0): cpu.PC = v + cpu.jit_jump = True cpu.cycles += 16 else: cpu.PC += 3 @@ -2113,6 +2122,7 @@ def CALL_C4(cpu, v): # C4 CALL NZ,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v + cpu.jit_jump = True cpu.cycles += 24 else: cpu.cycles += 12 @@ -2151,6 +2161,7 @@ def RST_C7(cpu): # C7 RST 00H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 0 + cpu.jit_jump = True cpu.cycles += 16 @@ -2160,6 +2171,7 @@ def RET_C8(cpu): # C8 RET Z cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 20 else: cpu.PC += 1 @@ -2172,12 +2184,14 @@ def RET_C9(cpu): # C9 RET cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 16 def JP_CA(cpu, v): # CA JP Z,a16 if ((cpu.F & (1 << FLAGZ)) != 0): cpu.PC = v + cpu.jit_jump = True cpu.cycles += 16 else: cpu.PC += 3 @@ -2201,6 +2215,7 @@ def CALL_CC(cpu, v): # CC CALL Z,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v + cpu.jit_jump = True cpu.cycles += 24 else: cpu.cycles += 12 @@ -2214,6 +2229,7 @@ def CALL_CD(cpu, v): # CD CALL a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v + cpu.jit_jump = True cpu.cycles += 24 @@ -2240,6 +2256,7 @@ def RST_CF(cpu): # CF RST 08H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 8 + cpu.jit_jump = True cpu.cycles += 16 @@ -2249,6 +2266,7 @@ def RET_D0(cpu): # D0 RET NC cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 20 else: cpu.PC += 1 @@ -2269,6 +2287,7 @@ def POP_D1(cpu): # D1 POP DE def JP_D2(cpu, v): # D2 JP NC,a16 if ((cpu.F & (1 << FLAGC)) == 0): cpu.PC = v + cpu.jit_jump = True cpu.cycles += 16 else: cpu.PC += 3 @@ -2285,6 +2304,7 @@ def CALL_D4(cpu, v): # D4 CALL NC,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v + cpu.jit_jump = True cpu.cycles += 24 else: cpu.cycles += 12 @@ -2323,6 +2343,7 @@ def RST_D7(cpu): # D7 RST 10H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 16 + cpu.jit_jump = True cpu.cycles += 16 @@ -2332,6 +2353,7 @@ def RET_D8(cpu): # D8 RET C cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 20 else: cpu.PC += 1 @@ -2346,12 +2368,14 @@ def RETI_D9(cpu): # D9 RETI cpu.PC |= cpu.mb.getitem(cpu.SP) # Low cpu.SP += 2 cpu.SP &= 0xFFFF + cpu.jit_jump = True cpu.cycles += 16 def JP_DA(cpu, v): # DA JP C,a16 if ((cpu.F & (1 << FLAGC)) != 0): cpu.PC = v + cpu.jit_jump = True cpu.cycles += 16 else: cpu.PC += 3 @@ -2368,6 +2392,7 @@ def CALL_DC(cpu, v): # DC CALL C,a16 cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = v + cpu.jit_jump = True cpu.cycles += 24 else: cpu.cycles += 12 @@ -2396,6 +2421,7 @@ def RST_DF(cpu): # DF RST 18H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 24 + cpu.jit_jump = True cpu.cycles += 16 @@ -2454,6 +2480,7 @@ def RST_E7(cpu): # E7 RST 20H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 32 + cpu.jit_jump = True cpu.cycles += 16 @@ -2505,6 +2532,7 @@ def RST_EF(cpu): # EF RST 28H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 40 + cpu.jit_jump = True cpu.cycles += 16 @@ -2571,6 +2599,7 @@ def RST_F7(cpu): # F7 RST 30H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 48 + cpu.jit_jump = True cpu.cycles += 16 @@ -2633,6 +2662,7 @@ def RST_FF(cpu): # FF RST 38H cpu.SP -= 2 cpu.SP &= 0xFFFF cpu.PC = 56 + cpu.jit_jump = True cpu.cycles += 16 @@ -6353,6 +6383,42 @@ def execute_opcode(cpu, opcode): return SET_1FF(cpu) +OPCODE_MAX_CYCLES = array.array("B", [ + 4, 12, 8, 8, 4, 4, 8, 4, 20, 8, 8, 8, 4, 4, 8, 4, + 4, 12, 8, 8, 4, 4, 8, 4, 12, 8, 8, 8, 4, 4, 8, 4, + 12, 12, 8, 8, 4, 4, 8, 4, 12, 8, 8, 8, 4, 4, 8, 4, + 12, 12, 8, 8, 12, 12, 12, 4, 12, 8, 8, 8, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 8, 8, 8, 8, 8, 8, 4, 8, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 20, 12, 16, 16, 24, 16, 8, 16, 20, 16, 16, 4, 24, 24, 8, 16, + 20, 12, 16, 0, 24, 16, 8, 16, 20, 16, 16, 0, 24, 0, 8, 16, + 12, 12, 8, 0, 0, 16, 8, 16, 16, 4, 16, 0, 0, 0, 8, 16, + 12, 12, 8, 4, 0, 16, 8, 16, 12, 8, 16, 4, 0, 0, 8, 16, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + ]) + + OPCODE_LENGTHS = array.array("B", [ 1, 3, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 2, 1, 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, diff --git a/pyboy/core/opcodes_gen.py b/pyboy/core/opcodes_gen.py index b38609c2b..17e001db2 100644 --- a/pyboy/core/opcodes_gen.py +++ b/pyboy/core/opcodes_gen.py @@ -29,6 +29,9 @@ def get_length(opcode): return OPCODE_LENGTHS[opcode] +def get_max_cycles(opcode): + return OPCODE_MAX_CYCLES[opcode] + def BRK(cpu): cpu.bail = True cpu.mb.breakpoint_singlestep = 1 @@ -52,6 +55,8 @@ def BRK(cpu): cdef uint16_t FLAGC, FLAGH, FLAGN, FLAGZ cdef uint8_t[512] OPCODE_LENGTHS cdef uint8_t get_length(int) noexcept nogil +cdef uint8_t[512] OPCODE_MAX_CYCLES +cdef uint8_t get_max_cycles(int) noexcept nogil @cython.locals(v=cython.int, a=cython.int, b=cython.int, pc=cython.ushort) cdef int execute_opcode(cpu.CPU, uint16_t) noexcept nogil @@ -365,7 +370,7 @@ def createfunction(self): # Compensate for CB operations being "2 bytes long" if self.opcode > 0xFF: self.length -= 1 - return (self.length, "%s_%0.2X" % (self.name.split()[0], self.opcode), self.name), text + return (self.length, self.cycles, "%s_%0.2X" % (self.name.split()[0], self.opcode), self.name), text # Special carry and half-carry for E8 and F8: # http://forums.nesdev.com/viewtopic.php?p=42138 @@ -1328,7 +1333,6 @@ def update(): f_pxd.write(cimports) lookuplist = [] for lookuptuple, code in opcodefunctions: - breakpoint() lookuplist.append(lookuptuple) if code is None: @@ -1342,7 +1346,7 @@ def update(): # We create a new opcode to use as a software breakpoint instruction. # I hope the irony of the opcode number is not lost. - lookuplist[0xDB] = (1, "BRK", "Breakpoint/Illegal opcode") + lookuplist[0xDB] = (1, (0, ), "BRK", "Breakpoint/Illegal opcode") f.write("def no_opcode(cpu):\n return 0\n\n\n") @@ -1367,17 +1371,32 @@ def execute_opcode(cpu, opcode): indent = 4 for i, t in enumerate(lookuplist): - t = t if t is not None else (0, "no_opcode", "") + t = t if t is not None else (0, (0, ), "no_opcode", "") + length, cycles, name, text = t f.write( " "*indent + ("if" if i == 0 else "elif") + " opcode == 0x%0.2X:\n"%i + " " * (indent+4) + "return " + - str(t[1]).replace("'", "") + ("(cpu)" if t[0] <= 1 else "(cpu, v)") + "\n" + str(name).replace("'", "") + ("(cpu)" if length <= 1 else "(cpu, v)") + "\n" ) f.write("\n\n") + f.write('OPCODE_MAX_CYCLES = array.array("B", [\n ') + for i, t in enumerate(lookuplist): + t = t if t is not None else (0, (0, ), "no_opcode", "") + length, cycles, name, text = t + f.write(str(cycles[0]).rjust(2).replace("'", "") + ",") + if (i+1) % 16 == 0: + f.write("\n" + " "*4) + else: + f.write(" ") + + f.write("])\n") + f.write("\n\n") + f.write('OPCODE_LENGTHS = array.array("B", [\n ') for i, t in enumerate(lookuplist): - t = t if t is not None else (0, "no_opcode", "") - f.write(str(t[0]).replace("'", "") + ",") + t = t if t is not None else (0, (0, ), "no_opcode", "") + length, cycles, name, text = t + f.write(str(length).replace("'", "") + ",") if (i+1) % 16 == 0: f.write("\n" + " "*4) else: @@ -1388,8 +1407,9 @@ def execute_opcode(cpu, opcode): f.write("\n\n") f.write("CPU_COMMANDS = [\n ") for _, t in enumerate(lookuplist): - t = t if t is not None else (0, "no_opcode", "") - f.write(f"\"{t[2]}\",\n" + " "*4) + t = t if t is not None else (0, (0, ), "no_opcode", "") + length, cycles, name, text = t + f.write(f"\"{text}\",\n" + " "*4) f.write("]\n") diff --git a/pyboy/pyboy.pxd b/pyboy/pyboy.pxd index f28edd4f3..2da18b95e 100644 --- a/pyboy/pyboy.pxd +++ b/pyboy/pyboy.pxd @@ -99,3 +99,5 @@ cdef class PyBoy: cpdef object get_sprite(self, int) noexcept cpdef list get_sprite_by_tile_identifier(self, list, on_screen=*) noexcept cpdef object get_tile(self, int) noexcept + + cpdef int64_t _cycles(self) diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index 4c806a61d..685508fdb 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -53,6 +53,7 @@ def __init__( cgb=None, gameshark=None, log_level=defaults["log_level"], + jit=False, **kwargs ): """ @@ -144,6 +145,7 @@ def __init__( sound, sound_emulated, cgb, + jit, randomize=randomize, ) @@ -1303,6 +1305,9 @@ def rtc_lock_experimental(self, enable): else: raise Exception("There's no RTC for this cartridge type") + def _cycles(self): + return self.mb._cycles + class PyBoyRegisterFile: """ diff --git a/tests/test_jit.py b/tests/test_jit.py new file mode 100644 index 000000000..3cbd1cf7f --- /dev/null +++ b/tests/test_jit.py @@ -0,0 +1,27 @@ +# +# License: See LICENSE.md file +# GitHub: https://github.com/Baekalfen/PyBoy +# + +from pyboy import PyBoy + + +def test_jit_tick(default_rom): + pyboy_jit = PyBoy(default_rom, window="null", jit=True, debug=False, log_level="DEBUG") + pyboy = PyBoy(default_rom, window="null", jit=False, debug=False) + pyboy_jit.set_emulation_speed(0) + pyboy.set_emulation_speed(0) + + for tick in range(60 * 10): + pyboy_jit.tick() + pyboy.tick() + + fail = False + for addr in range(0xFFFF): + a = pyboy.memory[addr] + b = pyboy_jit.memory[addr] + if a != b: + print(f"{tick=} address: 0x{addr:04x} {a}!={b}") + fail = True + assert pyboy._cycles() == pyboy_jit._cycles(), pyboy._cycles() - pyboy_jit._cycles() + assert not fail From 8ef82cfa501bc7a176b72296d8e77271cd966264 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 25 Mar 2024 23:56:25 +0100 Subject: [PATCH 29/51] Added bail-early for setitem, but makes little difference --- pyboy/core/mb.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index c1bb8866a..68152f5ca 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -375,7 +375,7 @@ def jit_emit_code(self, code_block): cdef uint8_t FLAGZ = 7 """ - code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef uint16_t t\n\tcdef int16_t v\n\tcdef int cycles = 0" + code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef uint16_t t\n\tcdef uint16_t tr\n\tcdef int16_t v\n\tcdef int cycles = 0" for opcode, length, literal1, literal2 in code_block: opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] @@ -387,11 +387,16 @@ def jit_emit_code(self, code_block): v = (literal2 << 8) + literal1 code_text += "v = " + str(v) + "\n\t" - code_text += opcode_handler.functionhandlers[opcode_name]()._code_body().replace("return", "cycles +=") + tmp_code = opcode_handler.functionhandlers[opcode_name]()._code_body() + tmp_code = tmp_code.replace("return", "cycles +=") + if "tr = " in tmp_code: + # if "(" in opcode_handler.name.split(',')[0]: #"setitem" in tmp_code: + # breakpoint() + # Return early on state-altering writes + tmp_code += "\n\tif tr: return cycles" + code_text += tmp_code + # TODO: Shouldn't this work? # code_text += "\n\tcpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcycles = 0" - # if "setitem" in code_text: - # code_text.replace() - # code_text += "\n\tif" code_text += "\n\treturn cycles" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op From 1dc8c71d0219e9833f09c9b88f2dc716346898ce Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Wed, 27 Mar 2024 17:21:44 +0100 Subject: [PATCH 30/51] Working well now (all pytests cleared 2:32:00) --- pyboy/core/cpu.py | 16 +++++++--------- pyboy/core/lcd.py | 5 ++++- pyboy/core/mb.pxd | 1 + pyboy/core/mb.py | 26 +++++++++++++++++--------- pyboy/pyboy.pxd | 1 + pyboy/pyboy.py | 22 ++++++++++++++++++++++ tests/test_jit.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 97 insertions(+), 20 deletions(-) diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 028cd8de9..1b02f4d73 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -81,9 +81,7 @@ def load_state(self, f, state_version): def dump_state(self): sym_label = "" - opcode_data = [ - self.mb.getitem(self.mb.cpu.PC + n) for n in range(3) - ] # Max 3 length, then we don't need to backtrack + opcode_data = [self.mb.getitem(self.PC + n) for n in range(3)] # Max 3 length, then we don't need to backtrack opcode = opcode_data[0] opcode_length = opcodes.get_length(opcode) @@ -95,13 +93,13 @@ def dump_state(self): print( # "\n" - f"A: {self.mb.cpu.A:02X}, F: {self.mb.cpu.F:02X}, B: {self.mb.cpu.B:02X}, " - f"C: {self.mb.cpu.C:02X}, D: {self.mb.cpu.D:02X}, E: {self.mb.cpu.E:02X}, " - f"HL: {self.mb.cpu.HL:04X}, SP: {self.mb.cpu.SP:04X}, PC: {self.mb.cpu.PC:04X} ({sym_label})" #\n" + f"A: {self.A:02X}, F: {self.F:02X}, B: {self.B:02X}, " + f"C: {self.C:02X}, D: {self.D:02X}, E: {self.E:02X}, " + f"HL: {self.HL:04X}, SP: {self.SP:04X}, PC: {self.PC:04X} ({sym_label})" #\n" # f"{opcode_str} " - # f"Interrupts - IME: {self.mb.cpu.interrupt_master_enable}, " - # f"IE: {self.mb.cpu.interrupts_enabled_register:08b}, " - # f"IF: {self.mb.cpu.interrupts_flag_register:08b}\n" + # f"Interrupts - IME: {self.interrupt_master_enable}, " + # f"IE: {self.interrupts_enabled_register:08b}, " + # f"IF: {self.interrupts_flag_register:08b}\n" # f"LCD Intr.: {self.mb.lcd.cycles_to_interrupt()}, LY:{self.mb.lcd.LY}, LYC:{self.mb.lcd.LYC}\n" # f"Timer Intr.: {self.mb.timer.cycles_to_interrupt()}\n" # f"halted:{self.halted}, " diff --git a/pyboy/core/lcd.py b/pyboy/core/lcd.py index ffba52543..6ec2af8e8 100644 --- a/pyboy/core/lcd.py +++ b/pyboy/core/lcd.py @@ -139,6 +139,9 @@ def tick(self, _cycles): interrupt_flag = 0 self.clock += cycles + if cycles > 28: + logger.error("lcd tick cycles=%d", cycles) + if self._LCDC.lcd_enable: if self.clock >= self.clock_target: # Change to next mode @@ -203,7 +206,7 @@ def tick(self, _cycles): self.next_stat_mode = 2 else: # See also `self.set_lcdc` - if self.clock >= FRAME_CYCLES: + if self.clock >= FRAME_CYCLES: # TODO: clock_target? self.frame_done = True self.clock %= FRAME_CYCLES diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 068b75877..dbaca2d67 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -67,6 +67,7 @@ cdef class Motherboard: cdef object jit_compile(self, str) noexcept with gil cdef object jit_emit_code(self, object) noexcept with gil cdef object jit_analyze(self) noexcept with gil + @cython.locals(block_id=int64_t) cdef int64_t jit(self, int64_t) noexcept with gil cdef void clear_jit_table(self) noexcept with gil diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 68152f5ca..cbe54153c 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -375,7 +375,7 @@ def jit_emit_code(self, code_block): cdef uint8_t FLAGZ = 7 """ - code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef uint16_t t\n\tcdef uint16_t tr\n\tcdef int16_t v\n\tcdef int cycles = 0" + code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0" for opcode, length, literal1, literal2 in code_block: opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] @@ -451,7 +451,7 @@ def jit_analyze(self): 0xDC, # CALL - 0xDD, + 0xCD, # RST 0xCF, @@ -460,6 +460,7 @@ def jit_analyze(self): 0xFF, 0x76, # HALT 0x10, # STOP + 0xF0, # LDH A,(a8) Used often for VBLANK check ] code_block = [] pc = self.cpu.PC @@ -470,8 +471,6 @@ def jit_analyze(self): pc += 1 opcode = self.getitem(pc) opcode += 0x100 # Internally shifting look-up table - # opcode_length = opcodes.get_length(opcode) - # opcode_max_cycles = opcodes.get_max_cycles(opcode) opcode_length = opcodes.OPCODE_LENGTHS[opcode] opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] block_max_cycles += opcode_max_cycles @@ -480,7 +479,7 @@ def jit_analyze(self): if opcode in boundary_instruction: break - if len(code_block) < 3: + if len(code_block) < 10: code = CodeBlock(None, eligible=False) code.cycles = 0 return code @@ -491,16 +490,21 @@ def jit_analyze(self): return code def jit(self, cycles): - block_id = self.cpu.PC << 8 + self.cartridge.rombank_selected - code = self.jit_table.get(block_id) # Bank collision! + if self.bootrom_enabled: + block_id = (self.cpu.PC << 8) | 0xFF + else: + block_id = (self.cpu.PC << 8) | self.cartridge.rombank_selected + code = self.jit_table.get(block_id) if not code: code = self.jit_analyze() - self.jit_table[block_id] = code # Bank collision! + self.jit_table[block_id] = code if code.eligible: if code.cycles <= cycles: logger.debug("Executing JIT block: PC=%x, code.cycles=%s, cycles=%d", self.cpu.PC, code.cycles, cycles) cycles = code.body(self.cpu) - logger.debug("After block: PC=%x, jit_jump=%d", self.cpu.PC, self.cpu.jit_jump) + logger.debug( + "After block: PC=%x, jit_jump=%d, actual cycles=%d", self.cpu.PC, self.cpu.jit_jump, cycles + ) return cycles else: logger.debug("Too narrow to execute JIT block %s, %d", code.cycles, cycles) @@ -561,6 +565,10 @@ def tick(self): if not self.cpu_pending_interrupt() and self.cpu.PC < 0x8000: # and cycles_target > 0: cycles = self.jit(cycles_target) # cycles = self.jit(0xFFFF) + # WARN: Shouldn't we just do a loop and come back with self.cpu.jit_jump==False + if cycles == 0: # If nothing was jit'ed + self.cpu.jit_jump = False + cycles = self.cpu.tick(4) # NOTE: cycles? else: cycles = self.cpu.tick(cycles_target) else: diff --git a/pyboy/pyboy.pxd b/pyboy/pyboy.pxd index 2da18b95e..b41afd0fa 100644 --- a/pyboy/pyboy.pxd +++ b/pyboy/pyboy.pxd @@ -69,6 +69,7 @@ cdef class PyBoy: cdef list recorded_input cdef list external_input + cpdef tuple _single_step(self) noexcept @cython.locals(t_start=int64_t, t_pre=int64_t, t_tick=int64_t, t_post=int64_t, nsecs=int64_t) cpdef bint _tick(self, bint) noexcept @cython.locals(running=bint, factor=double) diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index 685508fdb..5a10a9d9b 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -375,6 +375,28 @@ def __init__( self.initialized = True + def _single_step(self): + self.mb.breakpoint_singlestep = True + self.mb.breakpoint_singlestep_latch = True + self.mb.tick() + return ( + self.mb.cpu.A, + self.mb.cpu.F, + self.mb.cpu.B, + self.mb.cpu.C, + self.mb.cpu.D, + self.mb.cpu.E, + self.mb.cpu.HL, + self.mb.cpu.SP, + self.mb.cpu.PC, + # TODO: Could be moved to test + self.mb.getitem(self.mb.cpu.PC + 1), + self.mb.getitem(self.mb.cpu.PC + 2), + self.mb.getitem(self.mb.cpu.HL), + self.mb.getitem(self.mb.cpu.HL + 1), + # Interrupts? + ) + def _tick(self, render): if self.stopped: return False diff --git a/tests/test_jit.py b/tests/test_jit.py index 3cbd1cf7f..2e5288d38 100644 --- a/tests/test_jit.py +++ b/tests/test_jit.py @@ -7,8 +7,8 @@ def test_jit_tick(default_rom): - pyboy_jit = PyBoy(default_rom, window="null", jit=True, debug=False, log_level="DEBUG") pyboy = PyBoy(default_rom, window="null", jit=False, debug=False) + pyboy_jit = PyBoy(default_rom, window="null", jit=True, debug=False, log_level="DEBUG") pyboy_jit.set_emulation_speed(0) pyboy.set_emulation_speed(0) @@ -25,3 +25,47 @@ def test_jit_tick(default_rom): fail = True assert pyboy._cycles() == pyboy_jit._cycles(), pyboy._cycles() - pyboy_jit._cycles() assert not fail + + +# def test_jit_LY(default_rom): +# pyboy_jit = PyBoy(default_rom, window="null", jit=True, debug=False, log_level="ERROR") + +# asm = """ +# spin: +# ld A, (LY) +# nop +# nop +# nop +# nop +# nop +# nop +# nop +# nop +# nop +# jp spin +# """ + +# for tick in range(60 * 10): +# pyboy_jit.tick() + + +def test_jit_single_step(default_rom): + pyboy = PyBoy(default_rom, window="null", jit=False, debug=False) + pyboy_jit = PyBoy(default_rom, window="null", jit=True, debug=False, log_level="DEBUG") + pyboy_jit.set_emulation_speed(0) + pyboy.set_emulation_speed(0) + + for step in range(1_000_000): + registers_jit = pyboy_jit._single_step() + registers = pyboy._single_step() + + if registers_jit != registers: + print("Fixing cycles?", pyboy._cycles(), pyboy_jit._cycles(), registers, registers_jit) + # breakpoint() + while pyboy._cycles() < pyboy_jit._cycles(): + registers = pyboy._single_step() + assert registers == registers_jit + assert pyboy._cycles() == pyboy_jit._cycles() + else: + print("Matching cycles:", pyboy._cycles(), pyboy_jit._cycles(), registers, registers_jit) + # breakpoint() From 425bc67878dbd9db1d0f6143395638c4e0c6b98c Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Fri, 29 Mar 2024 23:52:53 +0100 Subject: [PATCH 31/51] Functionally sound with dlopen --- pyboy/core/lcd.py | 4 +- pyboy/core/mb.pxd | 33 ++++++++++++--- pyboy/core/mb.py | 103 +++++++++++++++++++++++++--------------------- 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/pyboy/core/lcd.py b/pyboy/core/lcd.py index 6ec2af8e8..fc548499c 100644 --- a/pyboy/core/lcd.py +++ b/pyboy/core/lcd.py @@ -139,8 +139,8 @@ def tick(self, _cycles): interrupt_flag = 0 self.clock += cycles - if cycles > 28: - logger.error("lcd tick cycles=%d", cycles) + # if cycles > 28: + # logger.error("lcd tick cycles=%d", cycles) if self._LCDC.lcd_enable: if self.clock >= self.clock_target: diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index dbaca2d67..3f0f2e358 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -3,6 +3,8 @@ # GitHub: https://github.com/Baekalfen/PyBoy # +from posix cimport dlfcn + from libc.stdint cimport int64_t, uint8_t, uint16_t, uint32_t, uint64_t import cython @@ -30,6 +32,7 @@ cdef short VBLANK, LCDC, TIMER, SERIAL, HIGHTOLOW cdef int INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW cdef int STATE_VERSION +ctypedef int(*f_type)(pyboy.core.cpu.CPU) noexcept nogil cdef class Motherboard: cdef pyboy.core.interaction.Interaction interaction @@ -62,14 +65,34 @@ cdef class Motherboard: cdef inline bint processing_frame(self) noexcept nogil - cdef dict jit_table + cdef f_type[0xFFFFFF] jit_array + cdef int[0xFFFFFF] jit_cycles + + cdef inline int jit_load(self, str module_path, int block_id, int block_max_cycles): + # logger.debug("JIT LOAD %d", block_id) + cdef void* handle = dlfcn.dlopen(module_path.encode(), dlfcn.RTLD_NOW | dlfcn.RTLD_GLOBAL) # RTLD_LAZY? + if (handle == NULL): + return -1 + dlfcn.dlerror() # Clear error + + cdef f_type execute = dlfcn.dlsym(handle, b"execute") + if (execute == NULL): + print(dlfcn.dlerror()) + + self.jit_array[block_id] = execute + self.jit_cycles[block_id] = block_max_cycles + # execute(self.cpu) + + cdef inline int jit_execute(self, int block_id) noexcept nogil: + # logger.debug("JIT EXECUTE %d", block_id) + return self.jit_array[block_id](self.cpu) + cdef bint jit_enabled - cdef object jit_compile(self, str) noexcept with gil + cdef str jit_compile(self, str) noexcept with gil cdef object jit_emit_code(self, object) noexcept with gil - cdef object jit_analyze(self) noexcept with gil + cdef int jit_analyze(self, int) noexcept with gil @cython.locals(block_id=int64_t) - cdef int64_t jit(self, int64_t) noexcept with gil - cdef void clear_jit_table(self) noexcept with gil + cdef int64_t jit(self, int64_t) noexcept nogil cdef void buttonevent(self, WindowEvent) noexcept cdef void stop(self, bint) noexcept diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index cbe54153c..9d5716b3b 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -28,13 +28,6 @@ MAX_CYCLES = 1 << 16 -class CodeBlock: - def __init__(self, body, eligible=True): - self.body = body - # self.cycles = cycles - self.eligible = eligible - - class Motherboard: def __init__( self, @@ -102,9 +95,12 @@ def __init__( self.breakpoint_waiting = -1 self.jit_enabled = jit_enabled - self.jit_table = {} self._cycles = 0 + def __cinit__(self): + for n in range(0xFFFFFF): + self.jit_cycles[n] = 0 + def switch_speed(self): bit0 = self.key1 & 0b1 if bit0 == 1: @@ -313,12 +309,16 @@ def jit_compile(self, code_text): module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' if not os.path.exists(module_path): - logger.info("Compiling JIT block: %s", module_path) - jit_file = os.path.splitext(self.cartridge.filename - )[0].replace(".", "_") + "_jit_" + _hash + ".pyx" # Generate name + # logger.info("Compiling JIT block: %s", module_path) + file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name + jit_file = file_base + ".pyx" with open(jit_file, "w") as f: f.write(code_text) + jit_file_pxd = file_base + ".pxd" + with open(jit_file_pxd, "w") as f: + f.write("from pyboy.core cimport cpu as _cpu\ncdef public int execute(void* __cpu) noexcept with gil") + cythonize_files = [ Extension( module_name, #src.split(".")[0].replace(os.sep, "."), @@ -350,16 +350,21 @@ def jit_compile(self, code_text): build_extension.inplace = True # build_extension.build_temp = "./"# os.path.dirname(jit_file) build_extension.run() - else: - logger.info("JIT block already compiled: %s", module_path) + # else: + # logger.info("JIT block already compiled: %s", module_path) - spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, module_path)) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) + return module_path - # module.execute(self.cpu) - code = CodeBlock(module.execute) - return code + # def jit_load(self, module_path): + # # spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, module_path)) + # # module = importlib.util.module_from_spec(spec) + # # spec.loader.exec_module(module) + # self.jit_load(module_path) + # # self.test_func = module.execute + + # # module.execute(self.cpu) + # # code = CodeBlock(module.execute) + # # return code def jit_emit_code(self, code_block): preamble = """ @@ -375,7 +380,7 @@ def jit_emit_code(self, code_block): cdef uint8_t FLAGZ = 7 """ - code_text = preamble + "cpdef int execute(_cpu.CPU cpu):\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0" + code_text = preamble + "cdef public int execute(void* __cpu) noexcept with gil:\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0\n\tcdef _cpu.CPU cpu = <_cpu.CPU> __cpu" for opcode, length, literal1, literal2 in code_block: opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] @@ -403,7 +408,7 @@ def jit_emit_code(self, code_block): # if .getitem in code, commit timer.tick(cycles); cycles = 0 return code_text - def jit_analyze(self): + def jit_analyze(self, block_id): boundary_instruction = [ # JR 0x20, @@ -480,38 +485,43 @@ def jit_analyze(self): break if len(code_block) < 10: - code = CodeBlock(None, eligible=False) - code.cycles = 0 - return code + return -1 code_text = self.jit_emit_code(code_block) - code = self.jit_compile(code_text) - code.cycles = block_max_cycles # (pc - self.cpu.PC) * 4 # TODO - return code + module_path = self.jit_compile(code_text) + self.jit_load(module_path, block_id, block_max_cycles) + # code = CodeBlock(block_id) + # code.cycles = block_max_cycles + # return block_id + return 0 - def jit(self, cycles): + def jit(self, max_cycles): if self.bootrom_enabled: block_id = (self.cpu.PC << 8) | 0xFF else: block_id = (self.cpu.PC << 8) | self.cartridge.rombank_selected - code = self.jit_table.get(block_id) - if not code: - code = self.jit_analyze() - self.jit_table[block_id] = code - if code.eligible: - if code.cycles <= cycles: - logger.debug("Executing JIT block: PC=%x, code.cycles=%s, cycles=%d", self.cpu.PC, code.cycles, cycles) - cycles = code.body(self.cpu) - logger.debug( - "After block: PC=%x, jit_jump=%d, actual cycles=%d", self.cpu.PC, self.cpu.jit_jump, cycles - ) - return cycles - else: - logger.debug("Too narrow to execute JIT block %s, %d", code.cycles, cycles) - return 0 - def clear_jit_table(self): - self.jit_table = {} + block_max_cycles = self.jit_cycles[block_id] + if block_max_cycles == 0 and self.jit_analyze(block_id): + self.jit_cycles[block_id] = -1 # Don't retry + + if block_max_cycles <= 0 or block_max_cycles > max_cycles: + return 0 + + return self.jit_execute(block_id) + + # if code.eligible: + # # if <= cycles: + # return self.jit_execute(block_id, code.cycles) + # logger.debug("Executing JIT block: PC=%x, code.cycles=%s, cycles=%d", self.cpu.PC, code.cycles, cycles) + # cycles = code.body(self.cpu) + # logger.debug( + # "After block: PC=%x, jit_jump=%d, actual cycles=%d", self.cpu.PC, self.cpu.jit_jump, cycles + # ) + # return cycles + # else: + # logger.debug("Too narrow to execute JIT block %s, %d", code.cycles, cycles) + return 0 def processing_frame(self): b = (not self.lcd.frame_done) @@ -821,7 +831,8 @@ def setitem(self, i, value): logger.debug("Bootrom disabled!") self.bootrom_enabled = False self.cpu.bail = True - self.clear_jit_table() + for n in range(0xFFFFFF): + self.jit_cycles[n] = 0 # CGB registers elif self.cgb and i == 0xFF4D: self.key1 = value From e952aa54c7397f685064b87ee20d6ae467fee5c5 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 31 Mar 2024 23:21:20 +0200 Subject: [PATCH 32/51] No GIL working --- pyboy/core/cpu.pxd | 1 - pyboy/core/cpu.py | 4 ---- pyboy/core/mb.pxd | 1 + pyboy/core/mb.py | 28 ++++++++++++++++++++++++++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/pyboy/core/cpu.pxd b/pyboy/core/cpu.pxd index 7eb28e591..1b8a0cb03 100644 --- a/pyboy/core/cpu.pxd +++ b/pyboy/core/cpu.pxd @@ -34,7 +34,6 @@ cdef class CPU: cdef int64_t cycles cdef inline int check_interrupts(self) noexcept nogil - cdef inline int pending_interrupt(self) noexcept nogil cdef void set_interruptflag(self, int) noexcept nogil cdef bint handle_interrupt(self, uint8_t, uint16_t) noexcept nogil diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 1b02f4d73..24f14be53 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -146,10 +146,6 @@ def tick(self, cycles_target): if self.bail: # Possible cycles-target changes break - def pending_interrupt(self): - return self.interrupt_queued or (self.interrupts_flag_register & - 0b11111) & (self.interrupts_enabled_register & 0b11111) - def check_interrupts(self): if self.interrupt_queued: # Interrupt already queued. This happens only when using a debugger. diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 3f0f2e358..654a9d285 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -93,6 +93,7 @@ cdef class Motherboard: cdef int jit_analyze(self, int) noexcept with gil @cython.locals(block_id=int64_t) cdef int64_t jit(self, int64_t) noexcept nogil + cdef inline int cpu_pending_interrupt(self) noexcept nogil cdef void buttonevent(self, WindowEvent) noexcept cdef void stop(self, bint) noexcept diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 9d5716b3b..feb308c84 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -11,6 +11,15 @@ from importlib.machinery import ExtensionFileLoader from Cython.Build import cythonize +from Cython.Compiler.Nodes import CFuncDefNode + + +# HACK: Disable this check to allow usage of CPU outside of GIL +def patched_validate_type_visibility(self, type, pos, env): + pass + + +CFuncDefNode._validate_type_visibility = patched_validate_type_visibility from pyboy import utils from pyboy.core.opcodes_gen import opcodes as opcodes_gen @@ -27,6 +36,12 @@ MAX_CYCLES = 1 << 16 +try: + import cython + cython_compiled = cython.compiled +except ImportError: + cython_compiled = False + class Motherboard: def __init__( @@ -96,6 +111,8 @@ def __init__( self.jit_enabled = jit_enabled self._cycles = 0 + # if not cython_compiled: + # self.jit_cycles = [0] * 0xFFFFFF def __cinit__(self): for n in range(0xFFFFFF): @@ -317,7 +334,7 @@ def jit_compile(self, code_text): jit_file_pxd = file_base + ".pxd" with open(jit_file_pxd, "w") as f: - f.write("from pyboy.core cimport cpu as _cpu\ncdef public int execute(void* __cpu) noexcept with gil") + f.write("from pyboy.core cimport cpu as _cpu\ncdef public int execute(_cpu.CPU __cpu) noexcept nogil") cythonize_files = [ Extension( @@ -380,7 +397,8 @@ def jit_emit_code(self, code_block): cdef uint8_t FLAGZ = 7 """ - code_text = preamble + "cdef public int execute(void* __cpu) noexcept with gil:\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0\n\tcdef _cpu.CPU cpu = <_cpu.CPU> __cpu" + code_text = preamble + "cdef public int execute(_cpu.CPU cpu) noexcept nogil:" + code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0" for opcode, length, literal1, literal2 in code_block: opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] @@ -487,6 +505,8 @@ def jit_analyze(self, block_id): if len(code_block) < 10: return -1 + logger.debug("Code block size: %d", len(code_block)) + code_text = self.jit_emit_code(code_block) module_path = self.jit_compile(code_text) self.jit_load(module_path, block_id, block_max_cycles) @@ -528,6 +548,10 @@ def processing_frame(self): self.lcd.frame_done = False # Clear vblank flag for next iteration return b + def cpu_pending_interrupt(self): + return self.cpu.interrupt_queued or (self.cpu.interrupts_flag_register & + 0b11111) & (self.cpu.interrupts_enabled_register & 0b11111) + def tick(self): # Threading by 'LD_21(cpu, v, cycles_left, cycles_acc)' # cpu.HL = v From aea78c1f19aa6f285907607b6f5f01bbebebda55 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 1 Apr 2024 17:23:12 +0100 Subject: [PATCH 33/51] Attempts at general improvements --- pyboy/core/mb.pxd | 2 +- pyboy/core/mb.py | 100 ++++++++++++++++++++++++++++++---------------- setup.py | 2 +- tests/test_jit.py | 16 +++++++- 4 files changed, 81 insertions(+), 39 deletions(-) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 654a9d285..013f5eb17 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -68,7 +68,7 @@ cdef class Motherboard: cdef f_type[0xFFFFFF] jit_array cdef int[0xFFFFFF] jit_cycles - cdef inline int jit_load(self, str module_path, int block_id, int block_max_cycles): + cdef inline int jit_load(self, str module_path, int block_id, int block_max_cycles) except -1 with gil: # logger.debug("JIT LOAD %d", block_id) cdef void* handle = dlfcn.dlopen(module_path.encode(), dlfcn.RTLD_NOW | dlfcn.RTLD_GLOBAL) # RTLD_LAZY? if (handle == NULL): diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index feb308c84..0da00553e 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -3,6 +3,7 @@ # GitHub: https://github.com/Baekalfen/PyBoy # +import array import hashlib import importlib import os @@ -37,10 +38,10 @@ def patched_validate_type_visibility(self, type, pos, env): MAX_CYCLES = 1 << 16 try: - import cython - cython_compiled = cython.compiled + from cython import compiled + cythonmode = compiled except ImportError: - cython_compiled = False + cythonmode = False class Motherboard: @@ -111,8 +112,9 @@ def __init__( self.jit_enabled = jit_enabled self._cycles = 0 - # if not cython_compiled: - # self.jit_cycles = [0] * 0xFFFFFF + + # if not cythonmode: + # self.jit_cycles = array.array("I", [0] * 100) # 0xFFFFFF def __cinit__(self): for n in range(0xFFFFFF): @@ -340,7 +342,7 @@ def jit_compile(self, code_text): Extension( module_name, #src.split(".")[0].replace(os.sep, "."), [jit_file], - extra_compile_args=["-O3"], + extra_compile_args=["-O3", "-march=native", "-mtune=native"], # include_dirs=[np.get_include()], ) ] @@ -384,6 +386,23 @@ def jit_compile(self, code_text): # # return code def jit_emit_code(self, code_block): + flush_instructions = [ + # Anything loading from a 16bit address pointer + 0x0A, + 0x1A, + 0x2A, + 0x3A, + 0x46, + 0x56, + 0x66, + 0x4E, + 0x5E, + 0x6E, + 0x7E, + 0xF0, # LDH A,(a8) Used often for VBLANK check + 0xF2, # LDH A,(C) + 0xFA, + ] preamble = """ cimport pyboy @@ -399,27 +418,36 @@ def jit_emit_code(self, code_block): """ code_text = preamble + "cdef public int execute(_cpu.CPU cpu) noexcept nogil:" code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0" - for opcode, length, literal1, literal2 in code_block: + for i, (opcode, length, pc, literal1, literal2) in enumerate(code_block): opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] - code_text += "\n\t" + "# " + opcode_handler.name + "\n\t" + code_text += "\n\t" + "# " + opcode_handler.name + f" (PC: 0x{pc:04x})\n\t" if length == 2: v = literal1 - code_text += "v = " + str(v) + "\n\t" + code_text += f"v = 0x{v:02x} # {v}\n\t" elif length == 3: v = (literal2 << 8) + literal1 - code_text += "v = " + str(v) + "\n\t" + code_text += f"v = 0x{v:04x} # {v}\n\t" tmp_code = opcode_handler.functionhandlers[opcode_name]()._code_body() tmp_code = tmp_code.replace("return", "cycles +=") - if "tr = " in tmp_code: - # if "(" in opcode_handler.name.split(',')[0]: #"setitem" in tmp_code: - # breakpoint() + if "if" in tmp_code: + # Return early on jump + tmp_code = tmp_code.replace("else:", "\treturn cycles\n\telse:") + elif "tr = " in tmp_code: # Return early on state-altering writes tmp_code += "\n\tif tr: return cycles" + elif opcode in flush_instructions: + # TODO: Add condition, if the flush is not necessary + # Statically determine from literal, or dynamically with 'if' + snippet = "# Perform cycles flush\n\t" + if i == 0: # First operation will always have cycles = 0 + snippet += "# [Flush skipped for first instruction in block]\n\t" + else: + snippet += "cpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcycles = 0\n\t" + tmp_code = snippet + tmp_code + code_text += tmp_code - # TODO: Shouldn't this work? - # code_text += "\n\tcpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcycles = 0" code_text += "\n\treturn cycles" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op @@ -429,28 +457,28 @@ def jit_emit_code(self, code_block): def jit_analyze(self, block_id): boundary_instruction = [ # JR - 0x20, - 0x30, + # 0x20, + # 0x30, # JR - 0x18, - 0x28, - 0x38, + 0x18, # Unconditional + # 0x28, + # 0x38, # RET 0xC0, 0xD0, # JP - 0xC2, - 0xD2, + # 0xC2, + # 0xD2, # JP 0xC3, # CALL - 0xC4, - 0xD4, + # 0xC4, + # 0xD4, # RST 0xC7, @@ -466,12 +494,12 @@ def jit_analyze(self, block_id): 0xE9, # JP # JP - 0xCA, - 0xDA, + # 0xCA, + # 0xDA, # CALL - 0xCC, - 0xDC, + # 0xCC, + # 0xDC, # CALL 0xCD, @@ -483,7 +511,9 @@ def jit_analyze(self, block_id): 0xFF, 0x76, # HALT 0x10, # STOP - 0xF0, # LDH A,(a8) Used often for VBLANK check + + # REQUIRED?! + # 0xF0, # LDH A,(a8) Used often for VBLANK check ] code_block = [] pc = self.cpu.PC @@ -497,7 +527,7 @@ def jit_analyze(self, block_id): opcode_length = opcodes.OPCODE_LENGTHS[opcode] opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] block_max_cycles += opcode_max_cycles - code_block.append((opcode, opcode_length, self.getitem(pc + 1), self.getitem(pc + 2))) + code_block.append((opcode, opcode_length, pc, self.getitem(pc + 1), self.getitem(pc + 2))) pc += opcode_length if opcode in boundary_instruction: break @@ -516,10 +546,11 @@ def jit_analyze(self, block_id): return 0 def jit(self, max_cycles): + block_id = (self.cpu.PC << 8) if self.bootrom_enabled: - block_id = (self.cpu.PC << 8) | 0xFF + block_id |= 0xFF else: - block_id = (self.cpu.PC << 8) | self.cartridge.rombank_selected + block_id |= self.cartridge.rombank_selected block_max_cycles = self.jit_cycles[block_id] if block_max_cycles == 0 and self.jit_analyze(block_id): @@ -855,8 +886,8 @@ def setitem(self, i, value): logger.debug("Bootrom disabled!") self.bootrom_enabled = False self.cpu.bail = True - for n in range(0xFFFFFF): - self.jit_cycles[n] = 0 + if self.jit_enabled: + self.jit._jit_clear() # CGB registers elif self.cgb and i == 0xFF4D: self.key1 = value @@ -888,7 +919,6 @@ def setitem(self, i, value): self.lcd.renderer.clear_spritecache1() else: self.ram.non_io_internal_ram1[i - 0xFF4C] = value - return 3 elif 0xFF80 <= i < 0xFFFF: # Internal RAM self.ram.internal_ram1[i - 0xFF80] = value elif i == 0xFFFF: # Interrupt Enable Register diff --git a/setup.py b/setup.py index afc550ec5..782349624 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ def initialize_options(self): self.distribution.ext_modules = cythonize( [*cythonize_files], nthreads=thread_count, - annotate=False, + annotate=True, gdb_debug=False, language_level=3, compiler_directives={ diff --git a/tests/test_jit.py b/tests/test_jit.py index 2e5288d38..586559580 100644 --- a/tests/test_jit.py +++ b/tests/test_jit.py @@ -49,6 +49,11 @@ def test_jit_tick(default_rom): # pyboy_jit.tick() +def _registers(registers): + A, F, B, C, D, E, HL, SP, PC, PC_1, PC_2, HL_0, HL_1 = registers + return f"{A=:02x}, {F=:02x}, {B=:02x}, {C=:02x}, {D=:02x}, {E=:02x}, {HL=:04x}, {SP=:04x}, {PC=:04x}, {PC_1=:04x}, {PC_2=:04x}, {HL_0=:02x}, {HL_1=:02x}" + + def test_jit_single_step(default_rom): pyboy = PyBoy(default_rom, window="null", jit=False, debug=False) pyboy_jit = PyBoy(default_rom, window="null", jit=True, debug=False, log_level="DEBUG") @@ -60,12 +65,19 @@ def test_jit_single_step(default_rom): registers = pyboy._single_step() if registers_jit != registers: - print("Fixing cycles?", pyboy._cycles(), pyboy_jit._cycles(), registers, registers_jit) + print( + "Fixing cycles?", pyboy._cycles(), pyboy_jit._cycles(), "\n", _registers(registers), "\n", + _registers(registers_jit) + ) # breakpoint() while pyboy._cycles() < pyboy_jit._cycles(): registers = pyboy._single_step() + print("Inc non-jit", "\n", _registers(registers)) assert registers == registers_jit assert pyboy._cycles() == pyboy_jit._cycles() else: - print("Matching cycles:", pyboy._cycles(), pyboy_jit._cycles(), registers, registers_jit) + print( + "Matching cycles:", pyboy._cycles(), pyboy_jit._cycles(), "\n", _registers(registers), "\n", + _registers(registers_jit) + ) # breakpoint() From 773c0973d22bf71a9e5511e7fdcc32b4acc2a937 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Tue, 2 Apr 2024 23:01:16 +0200 Subject: [PATCH 34/51] Working hybrid PyPy/Cython JIT. Flushing on getitem, minimized boundary instructions --- pyboy/core/cartridge/base_mbc.pxd | 2 +- pyboy/core/lcd.pxd | 2 +- pyboy/core/mb.pxd | 8 +- pyboy/core/mb.py | 239 ++++++++++++++---------------- 4 files changed, 118 insertions(+), 133 deletions(-) diff --git a/pyboy/core/cartridge/base_mbc.pxd b/pyboy/core/cartridge/base_mbc.pxd index 5b8842795..758914914 100644 --- a/pyboy/core/cartridge/base_mbc.pxd +++ b/pyboy/core/cartridge/base_mbc.pxd @@ -36,7 +36,7 @@ cdef class BaseMBC: cdef void save_ram(self, IntIOInterface) noexcept cdef void load_ram(self, IntIOInterface) noexcept cdef void init_rambanks(self, uint8_t) noexcept - cdef str getgamename(self, uint8_t[:,:]) noexcept + cdef str getgamename(self, uint8_t[:,:]) cdef uint8_t getitem(self, uint16_t) noexcept nogil cdef void setitem(self, uint16_t, uint8_t) noexcept nogil diff --git a/pyboy/core/lcd.pxd b/pyboy/core/lcd.pxd index 08c769fdc..9949d23f8 100644 --- a/pyboy/core/lcd.pxd +++ b/pyboy/core/lcd.pxd @@ -113,7 +113,7 @@ cdef class LCDCRegister: cdef uint16_t backgroundmap_offset cdef uint16_t windowmap_offset - cpdef int _get_sprite_height(self) + cpdef int _get_sprite_height(self) noexcept cdef class Renderer: cdef uint8_t[:] _tilecache0_state, _tilecache1_state, _spritecache0_state, _spritecache1_state diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 013f5eb17..0b5ebfb5d 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -68,7 +68,7 @@ cdef class Motherboard: cdef f_type[0xFFFFFF] jit_array cdef int[0xFFFFFF] jit_cycles - cdef inline int jit_load(self, str module_path, int block_id, int block_max_cycles) except -1 with gil: + cdef inline int jit_load(self, str module_name, str module_path, str file_base, int block_id, int block_max_cycles) except -1 with gil: # logger.debug("JIT LOAD %d", block_id) cdef void* handle = dlfcn.dlopen(module_path.encode(), dlfcn.RTLD_NOW | dlfcn.RTLD_GLOBAL) # RTLD_LAZY? if (handle == NULL): @@ -88,8 +88,10 @@ cdef class Motherboard: return self.jit_array[block_id](self.cpu) cdef bint jit_enabled - cdef str jit_compile(self, str) noexcept with gil - cdef object jit_emit_code(self, object) noexcept with gil + cdef tuple jit_get_module_name(self, str) with gil + cdef void jit_gen_files(self, str, str) noexcept with gil + cdef void jit_compile(self, str, str, str) noexcept with gil + cdef object jit_emit_code(self, object) with gil cdef int jit_analyze(self, int) noexcept with gil @cython.locals(block_id=int64_t) cdef int64_t jit(self, int64_t) noexcept nogil diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 0da00553e..71a6b4757 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -43,6 +43,16 @@ def patched_validate_type_visibility(self, type, pos, env): except ImportError: cythonmode = False +JIT_EXTENSION = ".pyx" if cythonmode else ".py" +JIT_PREAMBLE = """ +cimport pyboy + +from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t +cimport cython +from pyboy.core cimport cpu as _cpu + +""" + class Motherboard: def __init__( @@ -112,11 +122,9 @@ def __init__( self.jit_enabled = jit_enabled self._cycles = 0 + self._jit_clear() - # if not cythonmode: - # self.jit_cycles = array.array("I", [0] * 100) # 0xFFFFFF - - def __cinit__(self): + def _jit_clear(self): for n in range(0xFFFFFF): self.jit_cycles[n] = 0 @@ -318,8 +326,7 @@ def _get_build_extension(self): build_extension.finalize_options() return build_extension - def jit_compile(self, code_text): - # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 + def jit_get_module_name(self, code_text): m = hashlib.sha1() m.update(code_text.encode()) _hash = m.digest().hex() @@ -327,64 +334,57 @@ def jit_compile(self, code_text): module_name = "jit_" + _hash module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' - if not os.path.exists(module_path): + file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name + return module_name, file_base, module_path + + def jit_gen_files(self, code_text, file_base): + # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 + + if not os.path.exists(file_base + JIT_EXTENSION): # logger.info("Compiling JIT block: %s", module_path) - file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name - jit_file = file_base + ".pyx" - with open(jit_file, "w") as f: + with open(file_base + JIT_EXTENSION, "w") as f: f.write(code_text) - jit_file_pxd = file_base + ".pxd" - with open(jit_file_pxd, "w") as f: - f.write("from pyboy.core cimport cpu as _cpu\ncdef public int execute(_cpu.CPU __cpu) noexcept nogil") - - cythonize_files = [ - Extension( - module_name, #src.split(".")[0].replace(os.sep, "."), - [jit_file], - extra_compile_args=["-O3", "-march=native", "-mtune=native"], - # include_dirs=[np.get_include()], - ) - ] - build_extension = self._get_build_extension() - build_extension.extensions = cythonize( - [*cythonize_files], - nthreads=1, - annotate=False, - gdb_debug=False, - language_level=3, - compiler_directives={ - "boundscheck": False, - "cdivision": True, - "cdivision_warnings": False, - "infer_types": True, - "initializedcheck": False, - "nonecheck": False, - "overflowcheck": False, - # "profile" : True, - "wraparound": False, - "legacy_implicit_noexcept": True, - }, + if cythonmode: + jit_file_pxd = file_base + ".pxd" + with open(jit_file_pxd, "w") as f: + f.write("from pyboy.core cimport cpu as _cpu\n") + f.write("cdef public int execute(_cpu.CPU __cpu) noexcept nogil") + + def jit_compile(self, module_name, file_base, module_path): + cythonize_files = [ + Extension( + module_name, + [file_base + JIT_EXTENSION], + extra_compile_args=["-O3", "-march=native", "-mtune=native"], ) - build_extension.inplace = True - # build_extension.build_temp = "./"# os.path.dirname(jit_file) - build_extension.run() + ] + build_extension = self._get_build_extension() + build_extension.extensions = cythonize( + [*cythonize_files], + nthreads=1, + annotate=False, + gdb_debug=True, + language_level=3, + compiler_directives={ + "boundscheck": False, + "cdivision": True, + "cdivision_warnings": False, + "infer_types": True, + "initializedcheck": False, + "nonecheck": False, + "overflowcheck": False, + # "profile" : True, + "wraparound": False, + "legacy_implicit_noexcept": True, + }, + ) + build_extension.inplace = True + # build_extension.build_temp = "./"# os.path.dirname(jit_file) + build_extension.run() # else: # logger.info("JIT block already compiled: %s", module_path) - return module_path - - # def jit_load(self, module_path): - # # spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, module_path)) - # # module = importlib.util.module_from_spec(spec) - # # spec.loader.exec_module(module) - # self.jit_load(module_path) - # # self.test_func = module.execute - - # # module.execute(self.cpu) - # # code = CodeBlock(module.execute) - # # return code - def jit_emit_code(self, code_block): flush_instructions = [ # Anything loading from a 16bit address pointer @@ -403,21 +403,22 @@ def jit_emit_code(self, code_block): 0xF2, # LDH A,(C) 0xFA, ] - preamble = """ -cimport pyboy - -from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t -cimport cython -from pyboy.core cimport cpu as _cpu -cdef uint8_t FLAGC = 4 -cdef uint8_t FLAGH = 5 -cdef uint8_t FLAGN = 6 -cdef uint8_t FLAGZ = 7 + code_text = "" + if not cythonmode: + code_text += "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" + code_text += "def execute(cpu):\n\t" + code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0\n\tcycles = 0" + else: + code_text += JIT_PREAMBLE + code_text += "cdef public int execute(_cpu.CPU cpu) noexcept nogil:" + code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0" + code_text += """ +\tcdef uint16_t FLAGC = 4 +\tcdef uint16_t FLAGH = 5 +\tcdef uint16_t FLAGN = 6 +\tcdef uint16_t FLAGZ = 7""" -""" - code_text = preamble + "cdef public int execute(_cpu.CPU cpu) noexcept nogil:" - code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0" for i, (opcode, length, pc, literal1, literal2) in enumerate(code_block): opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] @@ -444,7 +445,7 @@ def jit_emit_code(self, code_block): if i == 0: # First operation will always have cycles = 0 snippet += "# [Flush skipped for first instruction in block]\n\t" else: - snippet += "cpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcycles = 0\n\t" + snippet += "cpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcpu.mb._cycles = cpu.mb._cycles + cycles\n\tcycles = 0\n\t" tmp_code = snippet + tmp_code code_text += tmp_code @@ -456,53 +457,22 @@ def jit_emit_code(self, code_block): def jit_analyze(self, block_id): boundary_instruction = [ - # JR - # 0x20, - # 0x30, - - # JR - 0x18, # Unconditional - # 0x28, - # 0x38, + 0x18, # JR r8 # RET 0xC0, 0xD0, - - # JP - # 0xC2, - # 0xD2, - - # JP - 0xC3, - - # CALL - # 0xC4, - # 0xD4, + 0xC3, # JP a16 # RST 0xC7, 0xD7, 0xE7, 0xF7, - - # RET - 0xC8, - 0xD8, 0xC9, # RET 0xD9, # RETI - 0xE9, # JP - - # JP - # 0xCA, - # 0xDA, - - # CALL - # 0xCC, - # 0xDC, - - # CALL - 0xCD, + 0xE9, # JP (HL) + 0xCD, # CALL a16 # RST 0xCF, @@ -511,9 +481,6 @@ def jit_analyze(self, block_id): 0xFF, 0x76, # HALT 0x10, # STOP - - # REQUIRED?! - # 0xF0, # LDH A,(a8) Used often for VBLANK check ] code_block = [] pc = self.cpu.PC @@ -535,14 +502,15 @@ def jit_analyze(self, block_id): if len(code_block) < 10: return -1 - logger.debug("Code block size: %d", len(code_block)) + logger.debug("Code block size: %d, block cycles: %d", len(code_block), block_max_cycles) code_text = self.jit_emit_code(code_block) - module_path = self.jit_compile(code_text) - self.jit_load(module_path, block_id, block_max_cycles) - # code = CodeBlock(block_id) - # code.cycles = block_max_cycles - # return block_id + module_name, file_base, module_path = self.jit_get_module_name(code_text) + self.jit_gen_files(code_text, file_base) + if cythonmode: + self.jit_compile(module_name, file_base, module_path) + self.jit_load(module_name, module_path, file_base, block_id, block_max_cycles) + return 0 def jit(self, max_cycles): @@ -561,19 +529,6 @@ def jit(self, max_cycles): return self.jit_execute(block_id) - # if code.eligible: - # # if <= cycles: - # return self.jit_execute(block_id, code.cycles) - # logger.debug("Executing JIT block: PC=%x, code.cycles=%s, cycles=%d", self.cpu.PC, code.cycles, cycles) - # cycles = code.body(self.cpu) - # logger.debug( - # "After block: PC=%x, jit_jump=%d, actual cycles=%d", self.cpu.PC, self.cpu.jit_jump, cycles - # ) - # return cycles - # else: - # logger.debug("Too narrow to execute JIT block %s, %d", code.cycles, cycles) - return 0 - def processing_frame(self): b = (not self.lcd.frame_done) self.lcd.frame_done = False # Clear vblank flag for next iteration @@ -1040,3 +995,31 @@ def tick(self, mb): self.hdma5 -= 1 return 206 # TODO: adjust for double speed + + +# Unfortunately CPython/PyPy code has to be hidden in an exec call to +# prevent Cython from trying to parse it. This block provides the +# functions that are otherwise implemented as inlined cdefs in the pxd +if not cythonmode: + exec( + """ +def _jit_load(self, module_name, module_path, file_base, block_id, block_max_cycles): + # spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, file_base + JIT_EXTENSION)) + spec = importlib.util.spec_from_file_location(module_name, file_base + JIT_EXTENSION) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + self.jit_array[block_id] = module.execute + self.jit_cycles[block_id] = block_max_cycles + +def _jit_clear(self): + self.jit_cycles = [0] * 0xFFFFFF + self.jit_array = [None] * 0xFFFFFF + +def _jit_execute(self, block_id): + return self.jit_array[block_id](self.cpu) + +Motherboard.jit_load = _jit_load +Motherboard._jit_clear = _jit_clear +Motherboard.jit_execute = _jit_execute +""", globals(), locals() + ) From d00da42033d5e6937e80ead1cd7b522037f1826a Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Tue, 17 Sep 2024 22:23:58 +0200 Subject: [PATCH 35/51] minimum 4 cycles --- pyboy/core/mb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 71a6b4757..e299561a5 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -565,7 +565,7 @@ def tick(self): mode0_cycles = self.lcd.cycles_to_mode0() cycles_target = max( - 0, #4, + 4, #0, #4, min( self.timer._cycles_to_interrupt, self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled. From ba2d0b73cfc8c68d7b43e76f9546cc4dec29d381 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Wed, 18 Sep 2024 23:00:53 +0100 Subject: [PATCH 36/51] JIT: Change streamline all to cpu.cycles and convert LCD to cpu.cycles --- pyboy/core/mb.pxd | 1 - pyboy/core/mb.py | 34 +++++++++++++++------------------- pyboy/pyboy.py | 2 +- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 0b5ebfb5d..40abede91 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -46,7 +46,6 @@ cdef class Motherboard: cdef bint bootrom_enabled cdef char[1024] serialbuffer cdef uint16_t serialbuffer_count - cdef uint64_t _cycles # CGB cdef HDMA hdma diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index e299561a5..535186177 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -121,7 +121,6 @@ def __init__( self.breakpoint_waiting = -1 self.jit_enabled = jit_enabled - self._cycles = 0 self._jit_clear() def _jit_clear(self): @@ -408,11 +407,11 @@ def jit_emit_code(self, code_block): if not cythonmode: code_text += "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" code_text += "def execute(cpu):\n\t" - code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0\n\tcycles = 0" + code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0" else: code_text += JIT_PREAMBLE - code_text += "cdef public int execute(_cpu.CPU cpu) noexcept nogil:" - code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v\n\tcdef int cycles = 0" + code_text += "cdef public void execute(_cpu.CPU cpu) noexcept nogil:" + code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v" code_text += """ \tcdef uint16_t FLAGC = 4 \tcdef uint16_t FLAGH = 5 @@ -431,13 +430,13 @@ def jit_emit_code(self, code_block): code_text += f"v = 0x{v:04x} # {v}\n\t" tmp_code = opcode_handler.functionhandlers[opcode_name]()._code_body() - tmp_code = tmp_code.replace("return", "cycles +=") + tmp_code = tmp_code.replace("return", "cpu.cycles +=") if "if" in tmp_code: # Return early on jump - tmp_code = tmp_code.replace("else:", "\treturn cycles\n\telse:") - elif "tr = " in tmp_code: + tmp_code = tmp_code.replace("else:", "\treturn\n\telse:") + elif "tr = " in tmp_code: # TODO: Replace with cpu._bail # Return early on state-altering writes - tmp_code += "\n\tif tr: return cycles" + tmp_code += "\n\tif tr: return" elif opcode in flush_instructions: # TODO: Add condition, if the flush is not necessary # Statically determine from literal, or dynamically with 'if' @@ -445,12 +444,12 @@ def jit_emit_code(self, code_block): if i == 0: # First operation will always have cycles = 0 snippet += "# [Flush skipped for first instruction in block]\n\t" else: - snippet += "cpu.mb.timer.tick(cycles)\n\tcpu.mb.lcd.tick(cycles)\n\tcpu.mb._cycles = cpu.mb._cycles + cycles\n\tcycles = 0\n\t" + snippet += "cpu.mb.timer.tick(cpu.cycles)\n\tcpu.mb.lcd.tick(cpu.cycles)\n\t" tmp_code = snippet + tmp_code code_text += tmp_code - code_text += "\n\treturn cycles" + code_text += "\n\treturn" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op # if .getitem in code, commit timer.tick(cycles); cycles = 0 return code_text @@ -547,7 +546,6 @@ def tick(self): # return cpu.next_opcode(cpu, cycles_left, cycles_acc + 12) # else: # return 12 - while self.processing_frame(): if self.cgb and self.hdma.transfer_active and self.lcd._STAT._mode & 0b11 == 0: self.cpu.jit_jump = False @@ -584,15 +582,15 @@ def tick(self): self.cpu.jit_jump = False if not self.cpu_pending_interrupt() and self.cpu.PC < 0x8000: # and cycles_target > 0: cycles = self.jit(cycles_target) - # cycles = self.jit(0xFFFF) - # WARN: Shouldn't we just do a loop and come back with self.cpu.jit_jump==False + # self.cpu.cycles += cycles # TODO: Should be done in emitted code + if cycles == 0: # If nothing was jit'ed - self.cpu.jit_jump = False - cycles = self.cpu.tick(4) # NOTE: cycles? + self.cpu.jit_jump = False # NOTE: Necessary? + self.cpu.tick(cycles_target) else: - cycles = self.cpu.tick(cycles_target) + self.cpu.tick(cycles_target) else: - cycles = self.cpu.tick(cycles_target) + self.cpu.tick(cycles_target) #TODO: Support General Purpose DMA # https://gbdev.io/pandocs/CGB_Registers.html#bit-7--0---general-purpose-dma @@ -606,8 +604,6 @@ def tick(self): if lcd_interrupt: self.cpu.set_interruptflag(lcd_interrupt) - self._cycles += cycles - if self.breakpoint_singlestep: break diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index 5a10a9d9b..5469e529b 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -1328,7 +1328,7 @@ def rtc_lock_experimental(self, enable): raise Exception("There's no RTC for this cartridge type") def _cycles(self): - return self.mb._cycles + return self.mb.cpu.cycles class PyBoyRegisterFile: From 133b78cbd4044db11ac3dc32b448cf0ce7c79645 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Wed, 18 Sep 2024 23:01:42 +0100 Subject: [PATCH 37/51] Correct EXT_SUFFIX to be dynamic --- pyboy/core/mb.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 535186177..bee42f145 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -7,6 +7,7 @@ import hashlib import importlib import os +import sysconfig from distutils.command.build_ext import build_ext from distutils.core import Distribution, Extension from importlib.machinery import ExtensionFileLoader @@ -43,6 +44,7 @@ def patched_validate_type_visibility(self, type, pos, env): except ImportError: cythonmode = False +EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX") JIT_EXTENSION = ".pyx" if cythonmode else ".py" JIT_PREAMBLE = """ cimport pyboy @@ -331,7 +333,7 @@ def jit_get_module_name(self, code_text): _hash = m.digest().hex() module_name = "jit_" + _hash - module_path = module_name + ".cpython-311-darwin.so" #os.path.splitext(jit_file)[0] + '.so' + module_path = module_name + EXT_SUFFIX #os.path.splitext(jit_file)[0] + '.so' file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name return module_name, file_base, module_path From 921d0031c4e7f0a27bb5440538150f14ee2516f9 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 19 Sep 2024 16:32:53 +0200 Subject: [PATCH 38/51] JIT general clean-up --- pyboy/core/mb.pxd | 4 ++-- pyboy/core/mb.py | 46 ++++++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 40abede91..46a2a8953 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -91,7 +91,7 @@ cdef class Motherboard: cdef void jit_gen_files(self, str, str) noexcept with gil cdef void jit_compile(self, str, str, str) noexcept with gil cdef object jit_emit_code(self, object) with gil - cdef int jit_analyze(self, int) noexcept with gil + cdef bint jit_analyze(self, int) noexcept with gil @cython.locals(block_id=int64_t) cdef int64_t jit(self, int64_t) noexcept nogil cdef inline int cpu_pending_interrupt(self) noexcept nogil @@ -124,7 +124,7 @@ cdef class HDMA: cdef uint16_t curr_dst cdef void set_hdma5(self, uint8_t, Motherboard) noexcept nogil - cdef int tick(self, Motherboard) noexcept nogil + cdef int64_t tick(self, Motherboard) noexcept nogil cdef void save_state(self, IntIOInterface) noexcept cdef void load_state(self, IntIOInterface, int) noexcept diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index bee42f145..556490110 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -388,7 +388,7 @@ def jit_compile(self, module_name, file_base, module_path): def jit_emit_code(self, code_block): flush_instructions = [ - # Anything loading from a 16bit address pointer + # Anything loading from a 16bit address pointer, as it could be special registers 0x0A, 0x1A, 0x2A, @@ -423,7 +423,7 @@ def jit_emit_code(self, code_block): for i, (opcode, length, pc, literal1, literal2) in enumerate(code_block): opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] - code_text += "\n\t" + "# " + opcode_handler.name + f" (PC: 0x{pc:04x})\n\t" + code_text += "\n\t\n\t" + "# " + opcode_handler.name + f" (PC: 0x{pc:04x})\n\t" if length == 2: v = literal1 code_text += f"v = 0x{v:02x} # {v}\n\t" @@ -487,6 +487,8 @@ def jit_analyze(self, block_id): pc = self.cpu.PC block_max_cycles = 0 while True: + # for _ in range(200): + # while block_max_cycles < 200: opcode = self.getitem(pc) if opcode == 0xCB: # Extension code pc += 1 @@ -500,8 +502,8 @@ def jit_analyze(self, block_id): if opcode in boundary_instruction: break - if len(code_block) < 10: - return -1 + if len(code_block) < 25: + return False logger.debug("Code block size: %d, block cycles: %d", len(code_block), block_max_cycles) @@ -510,11 +512,12 @@ def jit_analyze(self, block_id): self.jit_gen_files(code_text, file_base) if cythonmode: self.jit_compile(module_name, file_base, module_path) + logger.debug("Loading: %s %x %d", file_base, block_id, block_max_cycles) self.jit_load(module_name, module_path, file_base, block_id, block_max_cycles) - return 0 + return True - def jit(self, max_cycles): + def jit(self, cycles_target): block_id = (self.cpu.PC << 8) if self.bootrom_enabled: block_id |= 0xFF @@ -522,13 +525,19 @@ def jit(self, max_cycles): block_id |= self.cartridge.rombank_selected block_max_cycles = self.jit_cycles[block_id] - if block_max_cycles == 0 and self.jit_analyze(block_id): - self.jit_cycles[block_id] = -1 # Don't retry - if block_max_cycles <= 0 or block_max_cycles > max_cycles: - return 0 + # Hot path + if 0 < block_max_cycles < cycles_target: + # logger.critical("Execute block: %d of %d id:%x", block_max_cycles, cycles_target, block_id) + self.jit_execute(block_id) + return True + + if block_max_cycles == 0 and not self.jit_analyze(block_id): + self.jit_cycles[block_id] = -1 # Don't retry - return self.jit_execute(block_id) + # if not block_max_cycles == -1: + # logger.debug("Skipping block: %d of %d id:%x", block_max_cycles, cycles_target, block_id) + return False def processing_frame(self): b = (not self.lcd.frame_done) @@ -565,7 +574,7 @@ def tick(self): mode0_cycles = self.lcd.cycles_to_mode0() cycles_target = max( - 4, #0, #4, + 4, min( self.timer._cycles_to_interrupt, self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled. @@ -573,23 +582,16 @@ def tick(self): self.sound._cycles_to_interrupt, # TODO: Not implemented # self.serial.cycles_to_interrupt(), mode0_cycles - ) # - 24 # TODO: Avoid overshooting? + ) ) - # cycles_target = 4 # Inject special opcode instead? ~~Long immediate as identifier~~ # Special opcode cannot be more than 1 byte, to avoid jumps to sub-parts of the jit block # Compile in other thread, acquire memory lock between frames if self.jit_enabled and self.cpu.jit_jump: self.cpu.jit_jump = False - if not self.cpu_pending_interrupt() and self.cpu.PC < 0x8000: # and cycles_target > 0: - cycles = self.jit(cycles_target) - # self.cpu.cycles += cycles # TODO: Should be done in emitted code - - if cycles == 0: # If nothing was jit'ed - self.cpu.jit_jump = False # NOTE: Necessary? - self.cpu.tick(cycles_target) - else: + if not self.cpu_pending_interrupt() and self.cpu.PC < 0x8000 and not self.jit(cycles_target): + # If nothing was run with jit self.cpu.tick(cycles_target) else: self.cpu.tick(cycles_target) From 790e7a4576ee619acb5dd8aa9da54d4d02936a21 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sat, 21 Sep 2024 15:57:46 +0200 Subject: [PATCH 39/51] WIP: make JIT block aware of cycles_target --- pyboy/core/cpu.py | 2 +- pyboy/core/mb.pxd | 6 ++--- pyboy/core/mb.py | 57 ++++++++++++++++++++++++----------------------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 24f14be53..23523ed58 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -125,8 +125,8 @@ def tick(self, cycles_target): _target = _cycles0 + cycles_target if self.check_interrupts(): + # TODO: Cycles it took to handle the interrupt? self.halted = False - # TODO: Cycles it took to handle the interrupt if self.halted and self.interrupt_queued: # GBCPUman.pdf page 20 diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 46a2a8953..64cb23bd7 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -32,7 +32,7 @@ cdef short VBLANK, LCDC, TIMER, SERIAL, HIGHTOLOW cdef int INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW cdef int STATE_VERSION -ctypedef int(*f_type)(pyboy.core.cpu.CPU) noexcept nogil +ctypedef int(*f_type)(pyboy.core.cpu.CPU, int64_t) noexcept nogil cdef class Motherboard: cdef pyboy.core.interaction.Interaction interaction @@ -82,9 +82,9 @@ cdef class Motherboard: self.jit_cycles[block_id] = block_max_cycles # execute(self.cpu) - cdef inline int jit_execute(self, int block_id) noexcept nogil: + cdef inline int jit_execute(self, int block_id, int64_t cycles_target) noexcept nogil: # logger.debug("JIT EXECUTE %d", block_id) - return self.jit_array[block_id](self.cpu) + return self.jit_array[block_id](self.cpu, cycles_target) cdef bint jit_enabled cdef tuple jit_get_module_name(self, str) with gil diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 556490110..ba4f1c9c3 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -49,7 +49,7 @@ def patched_validate_type_visibility(self, type, pos, env): JIT_PREAMBLE = """ cimport pyboy -from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t +from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t, int64_t cimport cython from pyboy.core cimport cpu as _cpu @@ -335,6 +335,7 @@ def jit_get_module_name(self, code_text): module_name = "jit_" + _hash module_path = module_name + EXT_SUFFIX #os.path.splitext(jit_file)[0] + '.so' + # logger.debug("%s %s", self.cartridge.filename, _hash) file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name return module_name, file_base, module_path @@ -350,7 +351,7 @@ def jit_gen_files(self, code_text, file_base): jit_file_pxd = file_base + ".pxd" with open(jit_file_pxd, "w") as f: f.write("from pyboy.core cimport cpu as _cpu\n") - f.write("cdef public int execute(_cpu.CPU __cpu) noexcept nogil") + f.write("cdef public int execute(_cpu.CPU __cpu, int64_t cycles_target) noexcept nogil") def jit_compile(self, module_name, file_base, module_path): cythonize_files = [ @@ -408,11 +409,11 @@ def jit_emit_code(self, code_block): code_text = "" if not cythonmode: code_text += "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" - code_text += "def execute(cpu):\n\t" + code_text += "def execute(cpu, cycles_target):\n\t" code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0" else: code_text += JIT_PREAMBLE - code_text += "cdef public void execute(_cpu.CPU cpu) noexcept nogil:" + code_text += "cdef public void execute(_cpu.CPU cpu, int64_t cycles_target) noexcept nogil:" code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v" code_text += """ \tcdef uint16_t FLAGC = 4 @@ -451,6 +452,8 @@ def jit_emit_code(self, code_block): code_text += tmp_code + # code_text += "\n\tif cpu.cycles > cycles_target: return" + code_text += "\n\treturn" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op # if .getitem in code, commit timer.tick(cycles); cycles = 0 @@ -518,26 +521,14 @@ def jit_analyze(self, block_id): return True def jit(self, cycles_target): - block_id = (self.cpu.PC << 8) - if self.bootrom_enabled: - block_id |= 0xFF - else: - block_id |= self.cartridge.rombank_selected - - block_max_cycles = self.jit_cycles[block_id] - - # Hot path - if 0 < block_max_cycles < cycles_target: - # logger.critical("Execute block: %d of %d id:%x", block_max_cycles, cycles_target, block_id) - self.jit_execute(block_id) - return True + pass - if block_max_cycles == 0 and not self.jit_analyze(block_id): - self.jit_cycles[block_id] = -1 # Don't retry + # if block_max_cycles == 0 and not self.jit_analyze(block_id): + # self.jit_cycles[block_id] = -1 # Don't retry - # if not block_max_cycles == -1: - # logger.debug("Skipping block: %d of %d id:%x", block_max_cycles, cycles_target, block_id) - return False + # # if not block_max_cycles == -1: + # # logger.debug("Skipping block: %d of %d id:%x", block_max_cycles, cycles_target, block_id) + # return False def processing_frame(self): b = (not self.lcd.frame_done) @@ -588,10 +579,20 @@ def tick(self): # Inject special opcode instead? ~~Long immediate as identifier~~ # Special opcode cannot be more than 1 byte, to avoid jumps to sub-parts of the jit block # Compile in other thread, acquire memory lock between frames - if self.jit_enabled and self.cpu.jit_jump: - self.cpu.jit_jump = False - if not self.cpu_pending_interrupt() and self.cpu.PC < 0x8000 and not self.jit(cycles_target): - # If nothing was run with jit + if self.jit_enabled and self.cpu.PC < 0x8000 and not self.bootrom_enabled: + block_id = (self.cpu.PC << 8) + block_id |= self.cartridge.rombank_selected + + block_max_cycles = self.jit.cycles[block_id] + + # Hot path + if 0 < block_max_cycles and not self.cpu_pending_interrupt(): + self.jit.execute(block_id, cycles_target) + else: + if self.cpu.jit_jump and block_max_cycles == 0: + self.jit.offload(block_id, cycles_target, self.cpu.interrupt_master_enable) + + self.cpu.jit_jump = False self.cpu.tick(cycles_target) else: self.cpu.tick(cycles_target) @@ -1015,8 +1016,8 @@ def _jit_clear(self): self.jit_cycles = [0] * 0xFFFFFF self.jit_array = [None] * 0xFFFFFF -def _jit_execute(self, block_id): - return self.jit_array[block_id](self.cpu) +def _jit_execute(self, block_id, cycles_target): + return self.jit_array[block_id](self.cpu, cycles_target) Motherboard.jit_load = _jit_load Motherboard._jit_clear = _jit_clear From 3a9a1cb4346dfaa46fff774f2d4f5ffb50a1a27f Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 23 Sep 2024 14:32:12 +0200 Subject: [PATCH 40/51] wip note about jit blocks --- pyboy/core/mb.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index ba4f1c9c3..6f8eca0ec 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -523,6 +523,11 @@ def jit_analyze(self, block_id): def jit(self, cycles_target): pass + # TODO: Send cycles_target and which interrupt to jit_analyze. Track interrupt enable and flags on JIT block? + # Interrupts are likely to hit the same rythm -- sync on halt, do hblank, do vblank, etc. + # JIT interrupt routines and just straight to them? + # Predict which interrupt and inline interrupt vector? + # if block_max_cycles == 0 and not self.jit_analyze(block_id): # self.jit_cycles[block_id] = -1 # Don't retry From c37c940c508fdf415a357ecc4c1d987b177a41ab Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 26 Sep 2024 19:33:55 +0200 Subject: [PATCH 41/51] wip jit_analyze account for cycles_target and master interrupt enable --- pyboy/core/mb.pxd | 2 +- pyboy/core/mb.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 64cb23bd7..12e1a6d9a 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -91,7 +91,7 @@ cdef class Motherboard: cdef void jit_gen_files(self, str, str) noexcept with gil cdef void jit_compile(self, str, str, str) noexcept with gil cdef object jit_emit_code(self, object) with gil - cdef bint jit_analyze(self, int) noexcept with gil + cdef bint jit_analyze(self, int, int64_t, bint) noexcept with gil @cython.locals(block_id=int64_t) cdef int64_t jit(self, int64_t) noexcept nogil cdef inline int cpu_pending_interrupt(self) noexcept nogil diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 6f8eca0ec..2cbb9dd05 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -459,7 +459,7 @@ def jit_emit_code(self, code_block): # if .getitem in code, commit timer.tick(cycles); cycles = 0 return code_text - def jit_analyze(self, block_id): + def jit_analyze(self, block_id, cycles_target, interrupt_master_enable): boundary_instruction = [ 0x18, # JR r8 @@ -485,6 +485,7 @@ def jit_analyze(self, block_id): 0xFF, 0x76, # HALT 0x10, # STOP + 0xFB, # EI ] code_block = [] pc = self.cpu.PC @@ -499,14 +500,18 @@ def jit_analyze(self, block_id): opcode += 0x100 # Internally shifting look-up table opcode_length = opcodes.OPCODE_LENGTHS[opcode] opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] + if (not interrupt_master_enable) and (block_max_cycles + opcode_max_cycles > cycles_target): + break block_max_cycles += opcode_max_cycles code_block.append((opcode, opcode_length, pc, self.getitem(pc + 1), self.getitem(pc + 2))) pc += opcode_length if opcode in boundary_instruction: break - if len(code_block) < 25: + if block_max_cycles < 100: return False + # if len(code_block) < 25: + # return False logger.debug("Code block size: %d, block cycles: %d", len(code_block), block_max_cycles) From d7cbb7270469de84935442f3ac4f1d8aa78f9a43 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 26 Sep 2024 22:08:01 +0200 Subject: [PATCH 42/51] Cleanup and first attempt at collecting jit blocks for analysis --- pyboy/core/cpu.py | 10 ----- pyboy/core/mb.pxd | 6 ++- pyboy/core/mb.py | 96 +++++++++++++++++++++++------------------------ 3 files changed, 50 insertions(+), 62 deletions(-) diff --git a/pyboy/core/cpu.py b/pyboy/core/cpu.py index 23523ed58..7632850aa 100644 --- a/pyboy/core/cpu.py +++ b/pyboy/core/cpu.py @@ -110,16 +110,6 @@ def dump_state(self): def set_interruptflag(self, flag): self.interrupts_flag_register |= flag - # def jit_analyze(self): - # pass - - # def jit(self): - # code = self.jit_table[bank][pc] - # if code: - # return code() - # # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 - # self.jit_analyze() - def tick(self, cycles_target): _cycles0 = self.cycles _target = _cycles0 + cycles_target diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 12e1a6d9a..a16e0c2d3 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -46,6 +46,7 @@ cdef class Motherboard: cdef bint bootrom_enabled cdef char[1024] serialbuffer cdef uint16_t serialbuffer_count + cdef dict jit_queue # CGB cdef HDMA hdma @@ -92,8 +93,9 @@ cdef class Motherboard: cdef void jit_compile(self, str, str, str) noexcept with gil cdef object jit_emit_code(self, object) with gil cdef bint jit_analyze(self, int, int64_t, bint) noexcept with gil - @cython.locals(block_id=int64_t) - cdef int64_t jit(self, int64_t) noexcept nogil + cdef void jit_offload(self, int, int64_t, bint) noexcept with gil + @cython.locals(block_id=int64_t, cycles_target=int64_t, interrupt_master_enable=bint, count=int64_t) + cdef void jit_process(self) noexcept with gil cdef inline int cpu_pending_interrupt(self) noexcept nogil cdef void buttonevent(self, WindowEvent) noexcept diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 2cbb9dd05..bbde0a893 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -5,6 +5,7 @@ import array import hashlib +import heapq import importlib import os import sysconfig @@ -126,6 +127,7 @@ def __init__( self._jit_clear() def _jit_clear(self): + self.jit_queue = {} for n in range(0xFFFFFF): self.jit_cycles[n] = 0 @@ -388,24 +390,6 @@ def jit_compile(self, module_name, file_base, module_path): # logger.info("JIT block already compiled: %s", module_path) def jit_emit_code(self, code_block): - flush_instructions = [ - # Anything loading from a 16bit address pointer, as it could be special registers - 0x0A, - 0x1A, - 0x2A, - 0x3A, - 0x46, - 0x56, - 0x66, - 0x4E, - 0x5E, - 0x6E, - 0x7E, - 0xF0, # LDH A,(a8) Used often for VBLANK check - 0xF2, # LDH A,(C) - 0xFA, - ] - code_text = "" if not cythonmode: code_text += "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" @@ -440,25 +424,22 @@ def jit_emit_code(self, code_block): elif "tr = " in tmp_code: # TODO: Replace with cpu._bail # Return early on state-altering writes tmp_code += "\n\tif tr: return" - elif opcode in flush_instructions: - # TODO: Add condition, if the flush is not necessary - # Statically determine from literal, or dynamically with 'if' - snippet = "# Perform cycles flush\n\t" - if i == 0: # First operation will always have cycles = 0 - snippet += "# [Flush skipped for first instruction in block]\n\t" - else: - snippet += "cpu.mb.timer.tick(cpu.cycles)\n\tcpu.mb.lcd.tick(cpu.cycles)\n\t" - tmp_code = snippet + tmp_code - code_text += tmp_code - # code_text += "\n\tif cpu.cycles > cycles_target: return" - code_text += "\n\treturn" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op # if .getitem in code, commit timer.tick(cycles); cycles = 0 return code_text + def getitem_bank(self, bank, i): + if 0x0000 <= i < 0x4000: # 16kB ROM bank #0 + if bank == 0xFF and (i <= 0xFF or (self.cgb and 0x200 <= i < 0x900)): + return self.bootrom.getitem(i) + else: + return self.cartridge.rombanks[0, i] # TODO: Actually self.cartridge.rombank_selected_low + elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank + return self.cartridge.rombanks[bank, i - 0x4000] + def jit_analyze(self, block_id, cycles_target, interrupt_master_enable): boundary_instruction = [ 0x18, # JR r8 @@ -488,22 +469,28 @@ def jit_analyze(self, block_id, cycles_target, interrupt_master_enable): 0xFB, # EI ] code_block = [] - pc = self.cpu.PC + pc = block_id >> 8 + assert pc < 0x8000 + rom_bank = block_id & 0xFF + block_max_cycles = 0 while True: # for _ in range(200): # while block_max_cycles < 200: - opcode = self.getitem(pc) + opcode = self.getitem_bank(rom_bank, pc) if opcode == 0xCB: # Extension code pc += 1 - opcode = self.getitem(pc) + opcode = self.getitem_bank(rom_bank, pc) opcode += 0x100 # Internally shifting look-up table opcode_length = opcodes.OPCODE_LENGTHS[opcode] opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] - if (not interrupt_master_enable) and (block_max_cycles + opcode_max_cycles > cycles_target): + # if (not interrupt_master_enable) and (block_max_cycles + opcode_max_cycles > cycles_target): + if (block_max_cycles + opcode_max_cycles > cycles_target): break block_max_cycles += opcode_max_cycles - code_block.append((opcode, opcode_length, pc, self.getitem(pc + 1), self.getitem(pc + 2))) + code_block.append( + (opcode, opcode_length, pc, self.getitem_bank(rom_bank, pc + 1), self.getitem_bank(rom_bank, pc + 2)) + ) pc += opcode_length if opcode in boundary_instruction: break @@ -525,20 +512,33 @@ def jit_analyze(self, block_id, cycles_target, interrupt_master_enable): return True - def jit(self, cycles_target): - pass + def jit_offload(self, block_id, cycles_target, interrupt_master_enable): + if cycles_target < 200: + return + if self.jit_queue.get(block_id) is None: + self.jit_queue[block_id] = [] + self.jit_queue[block_id].append((cycles_target, interrupt_master_enable)) + + def jit_process(self): # TODO: Send cycles_target and which interrupt to jit_analyze. Track interrupt enable and flags on JIT block? # Interrupts are likely to hit the same rythm -- sync on halt, do hblank, do vblank, etc. # JIT interrupt routines and just straight to them? # Predict which interrupt and inline interrupt vector? - # if block_max_cycles == 0 and not self.jit_analyze(block_id): - # self.jit_cycles[block_id] = -1 # Don't retry + priority_list = [] + for k, v in self.jit_queue.items(): + priority_list.append((k, len(v))) # block_id, number of hits + + # Pick the 10 most frequent + for block_id, count in sorted(priority_list, key=lambda x: x[1], reverse=True)[:10]: + cycles_target, interrupt_master_enable = self.jit_queue[block_id][ + 0] # TODO: Currently just picking the first entry! + logger.critical("analyze: %x, %d, %d", block_id, cycles_target, interrupt_master_enable) + if not self.jit_analyze(block_id, cycles_target, interrupt_master_enable): + self.jit_cycles[block_id] = -1 # Don't retry - # # if not block_max_cycles == -1: - # # logger.debug("Skipping block: %d of %d id:%x", block_max_cycles, cycles_target, block_id) - # return False + self.jit_queue = {} # Throw the rest away to not grow the list indefinitely. Maybe there's a better way. def processing_frame(self): b = (not self.lcd.frame_done) @@ -550,14 +550,6 @@ def cpu_pending_interrupt(self): 0b11111) & (self.cpu.interrupts_enabled_register & 0b11111) def tick(self): - # Threading by 'LD_21(cpu, v, cycles_left, cycles_acc)' - # cpu.HL = v - # cpu.PC += 3 - # cpu.PC &= 0xFFFF - # if (cycles_left < 12): - # return cpu.next_opcode(cpu, cycles_left, cycles_acc + 12) - # else: - # return 12 while self.processing_frame(): if self.cgb and self.hdma.transfer_active and self.lcd._STAT._mode & 0b11 == 0: self.cpu.jit_jump = False @@ -605,6 +597,7 @@ def tick(self): self.cpu.jit_jump = False self.cpu.tick(cycles_target) else: + self.cpu.jit_jump = False self.cpu.tick(cycles_target) #TODO: Support General Purpose DMA @@ -625,6 +618,8 @@ def tick(self): # TODO: Move SDL2 sync to plugin self.sound.sync() + self.jit_process() + return self.breakpoint_singlestep ################################################################### @@ -1023,6 +1018,7 @@ def _jit_load(self, module_name, module_path, file_base, block_id, block_max_cyc self.jit_cycles[block_id] = block_max_cycles def _jit_clear(self): + self.jit_queue = {} self.jit_cycles = [0] * 0xFFFFFF self.jit_array = [None] * 0xFFFFFF From 3db44f8eb482a2c33da9ea1c5c58545b1912e995 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Thu, 26 Sep 2024 22:34:55 +0200 Subject: [PATCH 43/51] fixup --- pyboy/core/mb.pxd | 1 + pyboy/core/mb.py | 15 ++------------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index a16e0c2d3..5bd3fef6e 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -92,6 +92,7 @@ cdef class Motherboard: cdef void jit_gen_files(self, str, str) noexcept with gil cdef void jit_compile(self, str, str, str) noexcept with gil cdef object jit_emit_code(self, object) with gil + @cython.locals(block_max_cycles=int64_t) cdef bint jit_analyze(self, int, int64_t, bint) noexcept with gil cdef void jit_offload(self, int, int64_t, bint) noexcept with gil @cython.locals(block_id=int64_t, cycles_target=int64_t, interrupt_master_enable=bint, count=int64_t) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index bbde0a893..a84644b7b 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -417,13 +417,12 @@ def jit_emit_code(self, code_block): code_text += f"v = 0x{v:04x} # {v}\n\t" tmp_code = opcode_handler.functionhandlers[opcode_name]()._code_body() - tmp_code = tmp_code.replace("return", "cpu.cycles +=") if "if" in tmp_code: # Return early on jump tmp_code = tmp_code.replace("else:", "\treturn\n\telse:") - elif "tr = " in tmp_code: # TODO: Replace with cpu._bail + elif "cpu.mb.setitem" in tmp_code: # Return early on state-altering writes - tmp_code += "\n\tif tr: return" + tmp_code += "\n\tif cpu.bail: return" code_text += tmp_code code_text += "\n\treturn" @@ -740,21 +739,11 @@ def setitem(self, i, value): if 0x0000 <= i < 0x4000: # 16kB ROM bank #0 # Doesn't change the data. This is for MBC commands self.cartridge.setitem(i, value) -<<<<<<< HEAD - self.cpu.bail = True - return 1 - elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank - # Doesn't change the data. This is for MBC commands - self.cartridge.setitem(i, value) - self.cpu.bail = True - return 1 -======= self.cpu.bail = True # TODO: This is not something to bail for in non-jit mode elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank # Doesn't change the data. This is for MBC commands self.cartridge.setitem(i, value) self.cpu.bail = True # TODO: This is not something to bail for in non-jit mode ->>>>>>> f44ecdec (fixup! Much better, but not quite there) elif 0x8000 <= i < 0xA000: # 8kB Video RAM if not self.cgb or self.lcd.vbk.active_bank == 0: self.lcd.VRAM0[i - 0x8000] = value From 95120009a2a9efacda12efa3864f557815ec3587 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Fri, 27 Sep 2024 15:47:38 +0200 Subject: [PATCH 44/51] Bulk compilation working, no loading on start --- pyboy/core/mb.pxd | 25 ++++++++------ pyboy/core/mb.py | 88 ++++++++++++++++++++++++++++------------------- 2 files changed, 67 insertions(+), 46 deletions(-) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 5bd3fef6e..5cca3c3e6 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -68,20 +68,23 @@ cdef class Motherboard: cdef f_type[0xFFFFFF] jit_array cdef int[0xFFFFFF] jit_cycles - cdef inline int jit_load(self, str module_name, str module_path, str file_base, int block_id, int block_max_cycles) except -1 with gil: + cdef inline int jit_load(self, str module_name, str module_path, str file_base, list block_manifest) except -1 with gil: # logger.debug("JIT LOAD %d", block_id) cdef void* handle = dlfcn.dlopen(module_path.encode(), dlfcn.RTLD_NOW | dlfcn.RTLD_GLOBAL) # RTLD_LAZY? if (handle == NULL): return -1 dlfcn.dlerror() # Clear error - cdef f_type execute = dlfcn.dlsym(handle, b"execute") - if (execute == NULL): - print(dlfcn.dlerror()) + cdef f_type execute; + for func_name, block_id, block_max_cycles in block_manifest: + execute = dlfcn.dlsym(handle, func_name.encode()) + if (execute == NULL): + print(dlfcn.dlerror()) - self.jit_array[block_id] = execute - self.jit_cycles[block_id] = block_max_cycles - # execute(self.cpu) + # block_id = int(func_name.split('_')[-1], 16) + + self.jit_array[block_id] = execute + self.jit_cycles[block_id] = block_max_cycles cdef inline int jit_execute(self, int block_id, int64_t cycles_target) noexcept nogil: # logger.debug("JIT EXECUTE %d", block_id) @@ -89,11 +92,11 @@ cdef class Motherboard: cdef bint jit_enabled cdef tuple jit_get_module_name(self, str) with gil - cdef void jit_gen_files(self, str, str) noexcept with gil + cdef void jit_gen_files(self, str, str, list) noexcept with gil cdef void jit_compile(self, str, str, str) noexcept with gil - cdef object jit_emit_code(self, object) with gil - @cython.locals(block_max_cycles=int64_t) - cdef bint jit_analyze(self, int, int64_t, bint) noexcept with gil + cdef object jit_emit_code(self, object, str) with gil + # @cython.locals(block_max_cycles=int64_t) + # cdef bint jit_analyze(self, int, int64_t, bint) noexcept with gil cdef void jit_offload(self, int, int64_t, bint) noexcept with gil @cython.locals(block_id=int64_t, cycles_target=int64_t, interrupt_master_enable=bint, count=int64_t) cdef void jit_process(self) noexcept with gil diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index a84644b7b..36cfcbe2b 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -47,7 +47,11 @@ def patched_validate_type_visibility(self, type, pos, env): EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX") JIT_EXTENSION = ".pyx" if cythonmode else ".py" -JIT_PREAMBLE = """ + +if not cythonmode: + JIT_PREAMBLE = "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" +else: + JIT_PREAMBLE = """ cimport pyboy from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t, int64_t @@ -341,7 +345,7 @@ def jit_get_module_name(self, code_text): file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name return module_name, file_base, module_path - def jit_gen_files(self, code_text, file_base): + def jit_gen_files(self, code_text, file_base, block_manifest): # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 if not os.path.exists(file_base + JIT_EXTENSION): @@ -353,7 +357,8 @@ def jit_gen_files(self, code_text, file_base): jit_file_pxd = file_base + ".pxd" with open(jit_file_pxd, "w") as f: f.write("from pyboy.core cimport cpu as _cpu\n") - f.write("cdef public int execute(_cpu.CPU __cpu, int64_t cycles_target) noexcept nogil") + for func_name in block_manifest: + f.write(f"cdef public int {func_name}(_cpu.CPU __cpu, int64_t cycles_target) noexcept nogil\n") def jit_compile(self, module_name, file_base, module_path): cythonize_files = [ @@ -389,15 +394,13 @@ def jit_compile(self, module_name, file_base, module_path): # else: # logger.info("JIT block already compiled: %s", module_path) - def jit_emit_code(self, code_block): + def jit_emit_code(self, code_block, func_name): code_text = "" if not cythonmode: - code_text += "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" - code_text += "def execute(cpu, cycles_target):\n\t" + code_text += f"def {func_name}(cpu, cycles_target):\n\t" code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0" else: - code_text += JIT_PREAMBLE - code_text += "cdef public void execute(_cpu.CPU cpu, int64_t cycles_target) noexcept nogil:" + code_text += f"cdef public void {func_name}(_cpu.CPU cpu, int64_t cycles_target) noexcept nogil:" code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v" code_text += """ \tcdef uint16_t FLAGC = 4 @@ -425,7 +428,7 @@ def jit_emit_code(self, code_block): tmp_code += "\n\tif cpu.bail: return" code_text += tmp_code - code_text += "\n\treturn" + code_text += "\n\treturn\n\n" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op # if .getitem in code, commit timer.tick(cycles); cycles = 0 return code_text @@ -439,7 +442,7 @@ def getitem_bank(self, bank, i): elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank return self.cartridge.rombanks[bank, i - 0x4000] - def jit_analyze(self, block_id, cycles_target, interrupt_master_enable): + def jit_collect_block(self, block_id, cycles_target): boundary_instruction = [ 0x18, # JR r8 @@ -494,22 +497,7 @@ def jit_analyze(self, block_id, cycles_target, interrupt_master_enable): if opcode in boundary_instruction: break - if block_max_cycles < 100: - return False - # if len(code_block) < 25: - # return False - - logger.debug("Code block size: %d, block cycles: %d", len(code_block), block_max_cycles) - - code_text = self.jit_emit_code(code_block) - module_name, file_base, module_path = self.jit_get_module_name(code_text) - self.jit_gen_files(code_text, file_base) - if cythonmode: - self.jit_compile(module_name, file_base, module_path) - logger.debug("Loading: %s %x %d", file_base, block_id, block_max_cycles) - self.jit_load(module_name, module_path, file_base, block_id, block_max_cycles) - - return True + return code_block, block_max_cycles def jit_offload(self, block_id, cycles_target, interrupt_master_enable): if cycles_target < 200: @@ -529,13 +517,43 @@ def jit_process(self): for k, v in self.jit_queue.items(): priority_list.append((k, len(v))) # block_id, number of hits + block_manifest = [] + code_text = JIT_PREAMBLE # Pick the 10 most frequent - for block_id, count in sorted(priority_list, key=lambda x: x[1], reverse=True)[:10]: - cycles_target, interrupt_master_enable = self.jit_queue[block_id][ - 0] # TODO: Currently just picking the first entry! + for block_id, count in sorted(priority_list, key=lambda x: x[1], reverse=True)[:50]: + + # TODO: Currently just picking the first entry! + cycles_target, interrupt_master_enable = self.jit_queue[block_id][0] + logger.critical("analyze: %x, %d, %d", block_id, cycles_target, interrupt_master_enable) - if not self.jit_analyze(block_id, cycles_target, interrupt_master_enable): + + code_block, block_max_cycles = self.jit_collect_block(block_id, cycles_target) + + if block_max_cycles < 100: self.jit_cycles[block_id] = -1 # Don't retry + continue + + # if len(code_block) < 25: + # continue + + func_name = f"block_{block_id:08x}" + + logger.debug("Code block size: %d, block cycles: %d", len(code_block), block_max_cycles) + code_text += self.jit_emit_code(code_block, func_name) + + block_manifest.append((func_name, block_id, block_max_cycles)) + + if not block_manifest: + return + + code_text = "block_manifest = " + str(block_manifest) + "\n\n" + code_text + + module_name, file_base, module_path = self.jit_get_module_name(code_text) + self.jit_gen_files(code_text, file_base, block_manifest) + if cythonmode: + self.jit_compile(module_name, file_base, module_path) + # logger.debug("Loading: %s %x %d", file_base, block_id, block_max_cycles) + self.jit_load(module_name, module_path, file_base, block_manifest) self.jit_queue = {} # Throw the rest away to not grow the list indefinitely. Maybe there's a better way. @@ -617,8 +635,6 @@ def tick(self): # TODO: Move SDL2 sync to plugin self.sound.sync() - self.jit_process() - return self.breakpoint_singlestep ################################################################### @@ -998,13 +1014,15 @@ def tick(self, mb): if not cythonmode: exec( """ -def _jit_load(self, module_name, module_path, file_base, block_id, block_max_cycles): +def _jit_load(self, module_name, module_path, file_base, block_manifest): # spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, file_base + JIT_EXTENSION)) spec = importlib.util.spec_from_file_location(module_name, file_base + JIT_EXTENSION) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - self.jit_array[block_id] = module.execute - self.jit_cycles[block_id] = block_max_cycles + + for func_name, block_id, block_max_cycles in block_manifest: + self.jit_array[block_id] = getattr(module, func_name) + self.jit_cycles[block_id] = block_max_cycles def _jit_clear(self): self.jit_queue = {} From 0293fe5dd5c2fbb2b042073457eac982b3264c1e Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sat, 28 Sep 2024 20:43:05 +0200 Subject: [PATCH 45/51] Implement init loading of already JITed blocks --- pyboy/core/mb.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 36cfcbe2b..5817ef3ef 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -52,10 +52,7 @@ def patched_validate_type_visibility(self, type, pos, env): JIT_PREAMBLE = "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" else: JIT_PREAMBLE = """ -cimport pyboy - from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t, int64_t -cimport cython from pyboy.core cimport cpu as _cpu """ @@ -129,6 +126,7 @@ def __init__( self.jit_enabled = jit_enabled self._jit_clear() + self.jit_init_load() def _jit_clear(self): self.jit_queue = {} @@ -401,7 +399,7 @@ def jit_emit_code(self, code_block, func_name): code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0" else: code_text += f"cdef public void {func_name}(_cpu.CPU cpu, int64_t cycles_target) noexcept nogil:" - code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef uint16_t tr\n\tcdef int v" + code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef int v" code_text += """ \tcdef uint16_t FLAGC = 4 \tcdef uint16_t FLAGH = 5 @@ -507,6 +505,25 @@ def jit_offload(self, block_id, cycles_target, interrupt_master_enable): self.jit_queue[block_id] = [] self.jit_queue[block_id].append((cycles_target, interrupt_master_enable)) + def jit_init_load(self): + # breakpoint() + logger.critical("initload") + for module_path in os.listdir(): + logger.critical("file: %s", module_path) + if module_path.startswith("jit_") and module_path.endswith(EXT_SUFFIX): + logger.critical("match") + module_name = module_path.split(".")[0] + file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_" + module_name + + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + if getattr(module, "cartridge") == self.cartridge.gamename: + block_manifest = getattr(module, "block_manifest") + self.jit_load(module_name, module_path, file_base, block_manifest) + del spec, module + def jit_process(self): # TODO: Send cycles_target and which interrupt to jit_analyze. Track interrupt enable and flags on JIT block? # Interrupts are likely to hit the same rythm -- sync on halt, do hblank, do vblank, etc. @@ -543,10 +560,13 @@ def jit_process(self): block_manifest.append((func_name, block_id, block_max_cycles)) + self.jit_queue = {} # Throw the rest away to not grow the list indefinitely. Maybe there's a better way. if not block_manifest: return - code_text = "block_manifest = " + str(block_manifest) + "\n\n" + code_text + code_text = "block_manifest = " + str( + block_manifest + ) + "\n" + f"cartridge = '{self.cartridge.gamename}'\n\n" + code_text module_name, file_base, module_path = self.jit_get_module_name(code_text) self.jit_gen_files(code_text, file_base, block_manifest) @@ -555,8 +575,6 @@ def jit_process(self): # logger.debug("Loading: %s %x %d", file_base, block_id, block_max_cycles) self.jit_load(module_name, module_path, file_base, block_manifest) - self.jit_queue = {} # Throw the rest away to not grow the list indefinitely. Maybe there's a better way. - def processing_frame(self): b = (not self.lcd.frame_done) self.lcd.frame_done = False # Clear vblank flag for next iteration From b074f86c69d2f118f67e1676f26f75bccb3764ca Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sat, 28 Sep 2024 21:20:59 +0200 Subject: [PATCH 46/51] Move JIT into its own file and run in a thread --- pyboy/core/jit.pxd | 70 ++++++++ pyboy/core/jit.py | 392 +++++++++++++++++++++++++++++++++++++++++++++ pyboy/core/mb.pxd | 46 +----- pyboy/core/mb.py | 343 +-------------------------------------- pyboy/pyboy.py | 2 + 5 files changed, 473 insertions(+), 380 deletions(-) create mode 100644 pyboy/core/jit.pxd create mode 100644 pyboy/core/jit.py diff --git a/pyboy/core/jit.pxd b/pyboy/core/jit.pxd new file mode 100644 index 000000000..419f1338f --- /dev/null +++ b/pyboy/core/jit.pxd @@ -0,0 +1,70 @@ +# +# License: See LICENSE.md file +# GitHub: https://github.com/Baekalfen/PyBoy +# + +from posix cimport dlfcn + +cimport cython +from libc.stdint cimport int64_t, uint8_t, uint16_t, uint32_t, uint64_t + +cimport pyboy +cimport pyboy.core.cartridge.base_mbc +cimport pyboy.core.cpu +from pyboy.logging.logging cimport Logger + +from . cimport opcodes + + +cdef Logger logger +ctypedef int(*f_type)(pyboy.core.cpu.CPU, int64_t) noexcept nogil + +cdef class JIT: + cdef pyboy.core.cpu.CPU cpu + cdef pyboy.core.cartridge.base_mbc.BaseMBC cartridge + cdef dict queue + cdef bint thread_stop + cdef object thread_queue + cdef object thread + + cdef f_type[0xFFFFFF] array + cdef int[0xFFFFFF] cycles + + cdef inline int load(self, str module_name, str module_path, str file_base, list block_manifest) except -1 with gil: + # logger.debug("JIT LOAD %d", block_id) + cdef void* handle = dlfcn.dlopen(module_path.encode(), dlfcn.RTLD_NOW | dlfcn.RTLD_GLOBAL) # RTLD_LAZY? + if (handle == NULL): + return -1 + dlfcn.dlerror() # Clear error + + cdef f_type execute + for func_name, block_id, block_max_cycles in block_manifest: + execute = dlfcn.dlsym(handle, func_name.encode()) + if (execute == NULL): + print(dlfcn.dlerror()) + + # block_id = int(func_name.split('_')[-1], 16) + + self.array[block_id] = execute + self.cycles[block_id] = block_max_cycles + + cdef inline int execute(self, int block_id, int64_t cycles_target) noexcept nogil: + # logger.debug("JIT EXECUTE %d", block_id) + return self.array[block_id](self.cpu, cycles_target) + + cdef void stop(self) noexcept with gil + + cdef uint8_t getitem_bank(self, uint8_t, uint16_t) noexcept nogil + + cdef void _jit_clear(self) noexcept with gil + cdef tuple get_module_name(self, str) with gil + cdef void gen_files(self, str, str, list) noexcept with gil + cdef void compile(self, str, str, str) noexcept with gil + cdef object emit_code(self, object, str) with gil + # @cython.locals(block_max_cycles=int64_t) + # cdef bint analyze(self, int, int64_t, bint) noexcept with gil + cdef void offload(self, int, int64_t, bint) noexcept with gil + @cython.locals(block_id=int64_t, cycles_target=int64_t, interrupt_master_enable=bint, count=int64_t) + cdef void process(self) noexcept with gil + +cpdef void threaded_processor(JIT) noexcept with gil diff --git a/pyboy/core/jit.py b/pyboy/core/jit.py new file mode 100644 index 000000000..4ce405564 --- /dev/null +++ b/pyboy/core/jit.py @@ -0,0 +1,392 @@ +# +# License: See LICENSE.md file +# GitHub: https://github.com/Baekalfen/PyBoy +# + +import hashlib +import importlib +import os +import queue +import sysconfig +import threading +import time +from distutils.command.build_ext import build_ext +from distutils.core import Distribution, Extension +from importlib.machinery import ExtensionFileLoader + +from Cython.Build import cythonize +from Cython.Compiler.Nodes import CFuncDefNode + + +# HACK: Disable this check to allow usage of CPU outside of GIL +def patched_validate_type_visibility(self, type, pos, env): + pass + + +CFuncDefNode._validate_type_visibility = patched_validate_type_visibility + +import pyboy +from pyboy import utils +from pyboy.core.opcodes_gen import opcodes as opcodes_gen + +from . import opcodes + +logger = pyboy.logging.get_logger(__name__) + +try: + from cython import compiled + cythonmode = compiled +except ImportError: + cythonmode = False + +EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX") +JIT_EXTENSION = ".pyx" if cythonmode else ".py" + +if not cythonmode: + JIT_PREAMBLE = "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" +else: + JIT_PREAMBLE = """ +from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t, int64_t +from pyboy.core cimport cpu as _cpu + +""" + + +def threaded_processor(jit): + while not jit.thread_stop: + while not jit.thread_queue.empty(): + message = jit.thread_queue.get() + block_id, cycles_target, interrupt_master_enable = message + + if jit.queue.get(block_id) is None: + jit.queue[block_id] = [] + jit.queue[block_id].append((cycles_target, interrupt_master_enable)) + logger.critical("PROCESSING!") + jit.process() + time.sleep(0.1) + + +class JIT: + def __init__(self, cpu, cartridge): + self.cpu = cpu + self.cartridge = cartridge + self._jit_clear() + self.init_load() + + self.thread_queue = queue.Queue() + self.thread = threading.Thread(target=threaded_processor, args=(self, )) + self.thread_stop = False + self.thread.start() + + def stop(self): + self.thread_stop = True + + def _jit_clear(self): + self.queue = {} + for n in range(0xFFFFFF): + self.cycles[n] = 0 + + # TODO: Taken from https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L95 + def _get_build_extension(self): + dist = Distribution() + # Ensure the build respects distutils configuration by parsing + # the configuration files + config_files = dist.find_config_files() + dist.parse_config_files(config_files) + build_extension = build_ext(dist) + build_extension.finalize_options() + return build_extension + + def get_module_name(self, code_text): + m = hashlib.sha1() + m.update(code_text.encode()) + _hash = m.digest().hex() + + module_name = "jit_" + _hash + module_path = module_name + EXT_SUFFIX #os.path.splitext(jit_file)[0] + '.so' + + # logger.debug("%s %s", self.cartridge.filename, _hash) + file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name + return module_name, file_base, module_path + + def gen_files(self, code_text, file_base, block_manifest): + # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 + + if not os.path.exists(file_base + JIT_EXTENSION): + # logger.info("Compiling JIT block: %s", module_path) + with open(file_base + JIT_EXTENSION, "w") as f: + f.write(code_text) + + if cythonmode: + jit_file_pxd = file_base + ".pxd" + with open(jit_file_pxd, "w") as f: + f.write("from pyboy.core cimport cpu as _cpu\nfrom libc.stdint cimport int64_t\n\n") + for func_name, _, _ in block_manifest: + f.write(f"cdef public int {func_name}(_cpu.CPU __cpu, int64_t cycles_target) noexcept nogil\n") + + def compile(self, module_name, file_base, module_path): + cythonize_files = [ + Extension( + module_name, + [file_base + JIT_EXTENSION], + extra_compile_args=["-O3", "-march=native", "-mtune=native"], + ) + ] + build_extension = self._get_build_extension() + build_extension.extensions = cythonize( + [*cythonize_files], + nthreads=1, + annotate=False, + gdb_debug=True, + language_level=3, + compiler_directives={ + "boundscheck": False, + "cdivision": True, + "cdivision_warnings": False, + "infer_types": True, + "initializedcheck": False, + "nonecheck": False, + "overflowcheck": False, + # "profile" : True, + "wraparound": False, + "legacy_implicit_noexcept": True, + }, + ) + build_extension.inplace = True + # build_extension.build_temp = "./"# os.path.dirname(jit_file) + build_extension.run() + + def emit_code(self, code_block, func_name): + # TODO: Detect loops + # TODO: memory address (eliminate bail, know if it's a RAM or ROM write, interpolate specific memory pointer and bypass getitem) + # TODO: Easy to detect high RAM LDH + # TODO: Detect memcpy loop and replace. We don't need to LD A, (HL+) and then LD (DE), A. For each byte. This means to allow relative jumps within the block + # TODO: Invalidate JIT block on breakpoint + + code_text = "" + if not cythonmode: + code_text += f"def {func_name}(cpu, cycles_target):\n\t" + code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0" + else: + code_text += f"cdef public void {func_name}(_cpu.CPU cpu, int64_t cycles_target) noexcept nogil:" + code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef int v" + code_text += """ +\tcdef uint16_t FLAGC = 4 +\tcdef uint16_t FLAGH = 5 +\tcdef uint16_t FLAGN = 6 +\tcdef uint16_t FLAGZ = 7""" + + for i, (opcode, length, pc, literal1, literal2) in enumerate(code_block): + opcode_handler = opcodes_gen[opcode] + opcode_name = opcode_handler.name.split()[0] + code_text += "\n\t\n\t" + "# " + opcode_handler.name + f" (PC: 0x{pc:04x})\n\t" + if length == 2: + v = literal1 + code_text += f"v = 0x{v:02x} # {v}\n\t" + elif length == 3: + v = (literal2 << 8) + literal1 + code_text += f"v = 0x{v:04x} # {v}\n\t" + + tmp_code = opcode_handler.functionhandlers[opcode_name]()._code_body() + if "if" in tmp_code: + # Return early on jump + tmp_code = tmp_code.replace("else:", "\treturn\n\telse:") + elif "cpu.mb.setitem" in tmp_code: + # Return early on state-altering writes + tmp_code += "\n\tif cpu.bail: return" + code_text += tmp_code + + code_text += "\n\treturn\n\n" + # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op + # if .getitem in code, commit timer.tick(cycles); cycles = 0 + return code_text + + def getitem_bank(self, bank, i): + if 0x0000 <= i < 0x4000: # 16kB ROM bank #0 + return self.cartridge.rombanks[0, i] # TODO: Actually self.cartridge.rombank_selected_low + elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank + return self.cartridge.rombanks[bank, i - 0x4000] + + def collect_block(self, block_id, cycles_target): + boundary_instruction = [ + 0x18, # JR r8 + + # RET + 0xC0, + 0xD0, + 0xC3, # JP a16 + + # RST + 0xC7, + 0xD7, + 0xE7, + 0xF7, + 0xC9, # RET + 0xD9, # RETI + 0xE9, # JP (HL) + 0xCD, # CALL a16 + + # RST + 0xCF, + 0xDF, + 0xEF, + 0xFF, + 0x76, # HALT + 0x10, # STOP + 0xFB, # EI + 0xDB, # Breakpoint/hook + ] + code_block = [] + pc = block_id >> 8 + assert pc < 0x8000 + rom_bank = block_id & 0xFF + + block_max_cycles = 0 + while True: + # for _ in range(200): + # while block_max_cycles < 200: + opcode = self.getitem_bank(rom_bank, pc) + if opcode == 0xCB: # Extension code + pc += 1 + opcode = self.getitem_bank(rom_bank, pc) + opcode += 0x100 # Internally shifting look-up table + opcode_length = opcodes.OPCODE_LENGTHS[opcode] + opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] + # if (not interrupt_master_enable) and (block_max_cycles + opcode_max_cycles > cycles_target): + if (block_max_cycles + opcode_max_cycles > cycles_target): + break + block_max_cycles += opcode_max_cycles + code_block.append( + (opcode, opcode_length, pc, self.getitem_bank(rom_bank, pc + 1), self.getitem_bank(rom_bank, pc + 2)) + ) + pc += opcode_length + if opcode in boundary_instruction: + break + + return code_block, block_max_cycles + + def invalidate(self, bank, address): + # Invalidate any JIT block that crosses this bank and adress. + # Used when adding breakpoints and hooks + pass + + def offload(self, block_id, cycles_target, interrupt_master_enable): + if cycles_target < 200: + return + + self.thread_queue.put((block_id, cycles_target, interrupt_master_enable)) + + def init_load(self): + # breakpoint() + logger.critical("initload") + for module_path in os.listdir(): + # logger.critical("file: %s", module_path) + if module_path.startswith("jit_") and module_path.endswith(EXT_SUFFIX): + # logger.critical("match") + module_name = module_path.split(".")[0] + file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_" + module_name + + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + if getattr(module, "cartridge") == self.cartridge.gamename: + block_manifest = getattr(module, "block_manifest") + no_blocks = len(block_manifest) + logger.critical("Loading %d precompiled blocks from %s", no_blocks, file_base) + self.load(module_name, module_path, file_base, block_manifest) + del spec, module + + def process(self): + # TODO: Send cycles_target and which interrupt to jit_analyze. Track interrupt enable and flags on JIT block? + # Interrupts are likely to hit the same rythm -- sync on halt, do hblank, do vblank, etc. + # JIT interrupt routines and just straight to them? + # Predict which interrupt and inline interrupt vector? + + priority_list = [] + for k, v in self.queue.items(): + priority_list.append((k, len(v))) # block_id, number of hits + + block_manifest = [] + code_text = JIT_PREAMBLE + # Pick the 10 most frequent + + no_blocks = 0 + for block_id, count in sorted(priority_list, key=lambda x: x[1], reverse=True): + # if no_blocks >= 500: + # break + # TODO: Currently just picking the first entry! + cycles_target, interrupt_master_enable = self.queue[block_id][0] + + # logger.critical("analyze: %x, %d, %d", block_id, cycles_target, interrupt_master_enable) + + code_block, block_max_cycles = self.collect_block(block_id, cycles_target) + + if block_max_cycles < 100: + self.cycles[block_id] = -1 # Don't retry + continue + + no_blocks += 1 + + # if len(code_block) < 25: + # continue + + func_name = f"block_{block_id:08x}" + + # logger.debug("Code block size: %d, block cycles: %d", len(code_block), block_max_cycles) + code_text += self.emit_code(code_block, func_name) + + block_manifest.append((func_name, block_id, block_max_cycles)) + + if no_blocks < 20: + temp_queue = {} + for _, block_id, _ in block_manifest: + temp_queue[block_id] = self.queue[block_id] + self.queue = temp_queue # Clear all unwanted blocks and wait for more blocks to come in + return + else: + self.queue = {} # Throw the rest away to not grow the list indefinitely. Maybe there's a better way. + + logger.critical("processing: %d blocks", no_blocks) + + code_text = "block_manifest = " + str( + block_manifest + ) + "\n" + f"cartridge = '{self.cartridge.gamename}'\n\n" + code_text + + module_name, file_base, module_path = self.get_module_name(code_text) + self.gen_files(code_text, file_base, block_manifest) + if cythonmode: + self.compile(module_name, file_base, module_path) + # logger.debug("Loading: %s %x %d", file_base, block_id, block_max_cycles) + self.load(module_name, module_path, file_base, block_manifest) + + +# Unfortunately CPython/PyPy code has to be hidden in an exec call to +# prevent Cython from trying to parse it. This block provides the +# functions that are otherwise implemented as inlined cdefs in the pxd +if not cythonmode: + exec( + """ +def _load(self, module_name, module_path, file_base, block_manifest): + # spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, file_base + JIT_EXTENSION)) + spec = importlib.util.spec_from_file_location(module_name, file_base + JIT_EXTENSION) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for func_name, block_id, block_max_cycles in block_manifest: + self.array[block_id] = getattr(module, func_name) + self.cycles[block_id] = block_max_cycles + +def _jit_clear(self): + self.queue = {} + self.cycles = [0] * 0xFFFFFF + self.array = [None] * 0xFFFFFF + +def _execute(self, block_id, cycles_target): + return self.array[block_id](self.cpu, cycles_target) + +JIT.load = _load +JIT._jit_clear = _jit_clear +JIT.execute = _execute +""", globals(), locals() + ) diff --git a/pyboy/core/mb.pxd b/pyboy/core/mb.pxd index 5cca3c3e6..77bd9679f 100644 --- a/pyboy/core/mb.pxd +++ b/pyboy/core/mb.pxd @@ -3,8 +3,6 @@ # GitHub: https://github.com/Baekalfen/PyBoy # -from posix cimport dlfcn - from libc.stdint cimport int64_t, uint8_t, uint16_t, uint32_t, uint64_t import cython @@ -13,16 +11,14 @@ cimport pyboy.core.bootrom cimport pyboy.core.cartridge.base_mbc cimport pyboy.core.cpu cimport pyboy.core.interaction +cimport pyboy.core.jit cimport pyboy.core.lcd cimport pyboy.core.ram cimport pyboy.core.sound cimport pyboy.core.timer -# cimport pyboy.core.opcodes from pyboy.logging.logging cimport Logger from pyboy.utils cimport IntIOInterface, WindowEvent -from . cimport opcodes - cdef Logger logger @@ -32,8 +28,6 @@ cdef short VBLANK, LCDC, TIMER, SERIAL, HIGHTOLOW cdef int INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW cdef int STATE_VERSION -ctypedef int(*f_type)(pyboy.core.cpu.CPU, int64_t) noexcept nogil - cdef class Motherboard: cdef pyboy.core.interaction.Interaction interaction cdef pyboy.core.bootrom.BootROM bootrom @@ -42,11 +36,12 @@ cdef class Motherboard: cdef pyboy.core.cpu.CPU cpu cdef pyboy.core.timer.Timer timer cdef pyboy.core.sound.Sound sound + cdef bint jit_enabled + cdef pyboy.core.jit.JIT jit cdef pyboy.core.cartridge.base_mbc.BaseMBC cartridge cdef bint bootrom_enabled cdef char[1024] serialbuffer cdef uint16_t serialbuffer_count - cdef dict jit_queue # CGB cdef HDMA hdma @@ -65,41 +60,6 @@ cdef class Motherboard: cdef inline bint processing_frame(self) noexcept nogil - cdef f_type[0xFFFFFF] jit_array - cdef int[0xFFFFFF] jit_cycles - - cdef inline int jit_load(self, str module_name, str module_path, str file_base, list block_manifest) except -1 with gil: - # logger.debug("JIT LOAD %d", block_id) - cdef void* handle = dlfcn.dlopen(module_path.encode(), dlfcn.RTLD_NOW | dlfcn.RTLD_GLOBAL) # RTLD_LAZY? - if (handle == NULL): - return -1 - dlfcn.dlerror() # Clear error - - cdef f_type execute; - for func_name, block_id, block_max_cycles in block_manifest: - execute = dlfcn.dlsym(handle, func_name.encode()) - if (execute == NULL): - print(dlfcn.dlerror()) - - # block_id = int(func_name.split('_')[-1], 16) - - self.jit_array[block_id] = execute - self.jit_cycles[block_id] = block_max_cycles - - cdef inline int jit_execute(self, int block_id, int64_t cycles_target) noexcept nogil: - # logger.debug("JIT EXECUTE %d", block_id) - return self.jit_array[block_id](self.cpu, cycles_target) - - cdef bint jit_enabled - cdef tuple jit_get_module_name(self, str) with gil - cdef void jit_gen_files(self, str, str, list) noexcept with gil - cdef void jit_compile(self, str, str, str) noexcept with gil - cdef object jit_emit_code(self, object, str) with gil - # @cython.locals(block_max_cycles=int64_t) - # cdef bint jit_analyze(self, int, int64_t, bint) noexcept with gil - cdef void jit_offload(self, int, int64_t, bint) noexcept with gil - @cython.locals(block_id=int64_t, cycles_target=int64_t, interrupt_master_enable=bint, count=int64_t) - cdef void jit_process(self) noexcept with gil cdef inline int cpu_pending_interrupt(self) noexcept nogil cdef void buttonevent(self, WindowEvent) noexcept diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index 5817ef3ef..ed3a39b64 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -3,32 +3,9 @@ # GitHub: https://github.com/Baekalfen/PyBoy # -import array -import hashlib -import heapq -import importlib -import os -import sysconfig -from distutils.command.build_ext import build_ext -from distutils.core import Distribution, Extension -from importlib.machinery import ExtensionFileLoader - -from Cython.Build import cythonize -from Cython.Compiler.Nodes import CFuncDefNode - - -# HACK: Disable this check to allow usage of CPU outside of GIL -def patched_validate_type_visibility(self, type, pos, env): - pass - - -CFuncDefNode._validate_type_visibility = patched_validate_type_visibility - -from pyboy import utils -from pyboy.core.opcodes_gen import opcodes as opcodes_gen from pyboy.utils import STATE_VERSION -from . import bootrom, cartridge, cpu, interaction, lcd, opcodes, ram, sound, timer +from . import bootrom, cartridge, cpu, interaction, jit, lcd, ram, sound, timer INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW = [1 << x for x in range(5)] OPCODE_BRK = 0xDB @@ -39,24 +16,6 @@ def patched_validate_type_visibility(self, type, pos, env): MAX_CYCLES = 1 << 16 -try: - from cython import compiled - cythonmode = compiled -except ImportError: - cythonmode = False - -EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX") -JIT_EXTENSION = ".pyx" if cythonmode else ".py" - -if not cythonmode: - JIT_PREAMBLE = "FLAGC, FLAGH, FLAGN, FLAGZ = range(4, 8)\n\n" -else: - JIT_PREAMBLE = """ -from libc.stdint cimport uint8_t, uint16_t, int16_t, uint32_t, int64_t -from pyboy.core cimport cpu as _cpu - -""" - class Motherboard: def __init__( @@ -85,6 +44,9 @@ def __init__( self.bootrom = bootrom.BootROM(bootrom_file, cgb) self.ram = ram.RAM(cgb, randomize=randomize) self.cpu = cpu.CPU(self) + self.jit_enabled = jit_enabled + if self.jit_enabled: + self.jit = jit.JIT(self.cpu, self.cartridge) if cgb: self.lcd = lcd.CGBLCD( @@ -124,15 +86,6 @@ def __init__( self.breakpoint_singlestep_latch = False self.breakpoint_waiting = -1 - self.jit_enabled = jit_enabled - self._jit_clear() - self.jit_init_load() - - def _jit_clear(self): - self.jit_queue = {} - for n in range(0xFFFFFF): - self.jit_cycles[n] = 0 - def switch_speed(self): bit0 = self.key1 & 0b1 if bit0 == 1: @@ -255,6 +208,8 @@ def buttonevent(self, key): self.cpu.set_interruptflag(INTR_HIGHTOLOW) def stop(self, save): + if self.jit_enabled: + self.jit.stop() self.sound.stop() if save: self.cartridge.stop() @@ -320,261 +275,6 @@ def load_state(self, f): # Coordinator # - # TODO: Taken from https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L95 - def _get_build_extension(self): - dist = Distribution() - # Ensure the build respects distutils configuration by parsing - # the configuration files - config_files = dist.find_config_files() - dist.parse_config_files(config_files) - build_extension = build_ext(dist) - build_extension.finalize_options() - return build_extension - - def jit_get_module_name(self, code_text): - m = hashlib.sha1() - m.update(code_text.encode()) - _hash = m.digest().hex() - - module_name = "jit_" + _hash - module_path = module_name + EXT_SUFFIX #os.path.splitext(jit_file)[0] + '.so' - - # logger.debug("%s %s", self.cartridge.filename, _hash) - file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_jit_" + _hash # Generate name - return module_name, file_base, module_path - - def jit_gen_files(self, code_text, file_base, block_manifest): - # https://github.com/cython/cython/blob/4e0eee43210d6b7822859f3001202910888644af/Cython/Build/Inline.py#L141 - - if not os.path.exists(file_base + JIT_EXTENSION): - # logger.info("Compiling JIT block: %s", module_path) - with open(file_base + JIT_EXTENSION, "w") as f: - f.write(code_text) - - if cythonmode: - jit_file_pxd = file_base + ".pxd" - with open(jit_file_pxd, "w") as f: - f.write("from pyboy.core cimport cpu as _cpu\n") - for func_name in block_manifest: - f.write(f"cdef public int {func_name}(_cpu.CPU __cpu, int64_t cycles_target) noexcept nogil\n") - - def jit_compile(self, module_name, file_base, module_path): - cythonize_files = [ - Extension( - module_name, - [file_base + JIT_EXTENSION], - extra_compile_args=["-O3", "-march=native", "-mtune=native"], - ) - ] - build_extension = self._get_build_extension() - build_extension.extensions = cythonize( - [*cythonize_files], - nthreads=1, - annotate=False, - gdb_debug=True, - language_level=3, - compiler_directives={ - "boundscheck": False, - "cdivision": True, - "cdivision_warnings": False, - "infer_types": True, - "initializedcheck": False, - "nonecheck": False, - "overflowcheck": False, - # "profile" : True, - "wraparound": False, - "legacy_implicit_noexcept": True, - }, - ) - build_extension.inplace = True - # build_extension.build_temp = "./"# os.path.dirname(jit_file) - build_extension.run() - # else: - # logger.info("JIT block already compiled: %s", module_path) - - def jit_emit_code(self, code_block, func_name): - code_text = "" - if not cythonmode: - code_text += f"def {func_name}(cpu, cycles_target):\n\t" - code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0" - else: - code_text += f"cdef public void {func_name}(_cpu.CPU cpu, int64_t cycles_target) noexcept nogil:" - code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef int v" - code_text += """ -\tcdef uint16_t FLAGC = 4 -\tcdef uint16_t FLAGH = 5 -\tcdef uint16_t FLAGN = 6 -\tcdef uint16_t FLAGZ = 7""" - - for i, (opcode, length, pc, literal1, literal2) in enumerate(code_block): - opcode_handler = opcodes_gen[opcode] - opcode_name = opcode_handler.name.split()[0] - code_text += "\n\t\n\t" + "# " + opcode_handler.name + f" (PC: 0x{pc:04x})\n\t" - if length == 2: - v = literal1 - code_text += f"v = 0x{v:02x} # {v}\n\t" - elif length == 3: - v = (literal2 << 8) + literal1 - code_text += f"v = 0x{v:04x} # {v}\n\t" - - tmp_code = opcode_handler.functionhandlers[opcode_name]()._code_body() - if "if" in tmp_code: - # Return early on jump - tmp_code = tmp_code.replace("else:", "\treturn\n\telse:") - elif "cpu.mb.setitem" in tmp_code: - # Return early on state-altering writes - tmp_code += "\n\tif cpu.bail: return" - code_text += tmp_code - - code_text += "\n\treturn\n\n" - # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op - # if .getitem in code, commit timer.tick(cycles); cycles = 0 - return code_text - - def getitem_bank(self, bank, i): - if 0x0000 <= i < 0x4000: # 16kB ROM bank #0 - if bank == 0xFF and (i <= 0xFF or (self.cgb and 0x200 <= i < 0x900)): - return self.bootrom.getitem(i) - else: - return self.cartridge.rombanks[0, i] # TODO: Actually self.cartridge.rombank_selected_low - elif 0x4000 <= i < 0x8000: # 16kB switchable ROM bank - return self.cartridge.rombanks[bank, i - 0x4000] - - def jit_collect_block(self, block_id, cycles_target): - boundary_instruction = [ - 0x18, # JR r8 - - # RET - 0xC0, - 0xD0, - 0xC3, # JP a16 - - # RST - 0xC7, - 0xD7, - 0xE7, - 0xF7, - 0xC9, # RET - 0xD9, # RETI - 0xE9, # JP (HL) - 0xCD, # CALL a16 - - # RST - 0xCF, - 0xDF, - 0xEF, - 0xFF, - 0x76, # HALT - 0x10, # STOP - 0xFB, # EI - ] - code_block = [] - pc = block_id >> 8 - assert pc < 0x8000 - rom_bank = block_id & 0xFF - - block_max_cycles = 0 - while True: - # for _ in range(200): - # while block_max_cycles < 200: - opcode = self.getitem_bank(rom_bank, pc) - if opcode == 0xCB: # Extension code - pc += 1 - opcode = self.getitem_bank(rom_bank, pc) - opcode += 0x100 # Internally shifting look-up table - opcode_length = opcodes.OPCODE_LENGTHS[opcode] - opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] - # if (not interrupt_master_enable) and (block_max_cycles + opcode_max_cycles > cycles_target): - if (block_max_cycles + opcode_max_cycles > cycles_target): - break - block_max_cycles += opcode_max_cycles - code_block.append( - (opcode, opcode_length, pc, self.getitem_bank(rom_bank, pc + 1), self.getitem_bank(rom_bank, pc + 2)) - ) - pc += opcode_length - if opcode in boundary_instruction: - break - - return code_block, block_max_cycles - - def jit_offload(self, block_id, cycles_target, interrupt_master_enable): - if cycles_target < 200: - return - - if self.jit_queue.get(block_id) is None: - self.jit_queue[block_id] = [] - self.jit_queue[block_id].append((cycles_target, interrupt_master_enable)) - - def jit_init_load(self): - # breakpoint() - logger.critical("initload") - for module_path in os.listdir(): - logger.critical("file: %s", module_path) - if module_path.startswith("jit_") and module_path.endswith(EXT_SUFFIX): - logger.critical("match") - module_name = module_path.split(".")[0] - file_base = os.path.splitext(self.cartridge.filename)[0].replace(".", "_") + "_" + module_name - - spec = importlib.util.spec_from_file_location(module_name, module_path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - if getattr(module, "cartridge") == self.cartridge.gamename: - block_manifest = getattr(module, "block_manifest") - self.jit_load(module_name, module_path, file_base, block_manifest) - del spec, module - - def jit_process(self): - # TODO: Send cycles_target and which interrupt to jit_analyze. Track interrupt enable and flags on JIT block? - # Interrupts are likely to hit the same rythm -- sync on halt, do hblank, do vblank, etc. - # JIT interrupt routines and just straight to them? - # Predict which interrupt and inline interrupt vector? - - priority_list = [] - for k, v in self.jit_queue.items(): - priority_list.append((k, len(v))) # block_id, number of hits - - block_manifest = [] - code_text = JIT_PREAMBLE - # Pick the 10 most frequent - for block_id, count in sorted(priority_list, key=lambda x: x[1], reverse=True)[:50]: - - # TODO: Currently just picking the first entry! - cycles_target, interrupt_master_enable = self.jit_queue[block_id][0] - - logger.critical("analyze: %x, %d, %d", block_id, cycles_target, interrupt_master_enable) - - code_block, block_max_cycles = self.jit_collect_block(block_id, cycles_target) - - if block_max_cycles < 100: - self.jit_cycles[block_id] = -1 # Don't retry - continue - - # if len(code_block) < 25: - # continue - - func_name = f"block_{block_id:08x}" - - logger.debug("Code block size: %d, block cycles: %d", len(code_block), block_max_cycles) - code_text += self.jit_emit_code(code_block, func_name) - - block_manifest.append((func_name, block_id, block_max_cycles)) - - self.jit_queue = {} # Throw the rest away to not grow the list indefinitely. Maybe there's a better way. - if not block_manifest: - return - - code_text = "block_manifest = " + str( - block_manifest - ) + "\n" + f"cartridge = '{self.cartridge.gamename}'\n\n" + code_text - - module_name, file_base, module_path = self.jit_get_module_name(code_text) - self.jit_gen_files(code_text, file_base, block_manifest) - if cythonmode: - self.jit_compile(module_name, file_base, module_path) - # logger.debug("Loading: %s %x %d", file_base, block_id, block_max_cycles) - self.jit_load(module_name, module_path, file_base, block_manifest) - def processing_frame(self): b = (not self.lcd.frame_done) self.lcd.frame_done = False # Clear vblank flag for next iteration @@ -1024,34 +724,3 @@ def tick(self, mb): self.hdma5 -= 1 return 206 # TODO: adjust for double speed - - -# Unfortunately CPython/PyPy code has to be hidden in an exec call to -# prevent Cython from trying to parse it. This block provides the -# functions that are otherwise implemented as inlined cdefs in the pxd -if not cythonmode: - exec( - """ -def _jit_load(self, module_name, module_path, file_base, block_manifest): - # spec = importlib.util.spec_from_file_location(module_name, loader=ExtensionFileLoader(module_name, file_base + JIT_EXTENSION)) - spec = importlib.util.spec_from_file_location(module_name, file_base + JIT_EXTENSION) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - for func_name, block_id, block_max_cycles in block_manifest: - self.jit_array[block_id] = getattr(module, func_name) - self.jit_cycles[block_id] = block_max_cycles - -def _jit_clear(self): - self.jit_queue = {} - self.jit_cycles = [0] * 0xFFFFFF - self.jit_array = [None] * 0xFFFFFF - -def _jit_execute(self, block_id, cycles_target): - return self.jit_array[block_id](self.cpu, cycles_target) - -Motherboard.jit_load = _jit_load -Motherboard._jit_clear = _jit_clear -Motherboard.jit_execute = _jit_execute -""", globals(), locals() - ) diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index 5469e529b..ec747921f 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -8,7 +8,9 @@ import heapq import os +import queue import re +import threading import time import numpy as np From 9ccc8b9b8e8ad200ee3daddf9a808798693afb65 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Mon, 30 Sep 2024 22:42:31 +0200 Subject: [PATCH 47/51] fixup! Move JIT into its own file and run in a thread --- pyboy/core/jit.pxd | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyboy/core/jit.pxd b/pyboy/core/jit.pxd index 419f1338f..8e3317a70 100644 --- a/pyboy/core/jit.pxd +++ b/pyboy/core/jit.pxd @@ -43,8 +43,6 @@ cdef class JIT: if (execute == NULL): print(dlfcn.dlerror()) - # block_id = int(func_name.split('_')[-1], 16) - self.array[block_id] = execute self.cycles[block_id] = block_max_cycles From b29ec5529cd311223bba47cff545c891ea500a85 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Tue, 1 Oct 2024 16:31:09 +0200 Subject: [PATCH 48/51] Correcting boundary instructions. Several were missing --- pyboy/core/jit.py | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/pyboy/core/jit.py b/pyboy/core/jit.py index 4ce405564..9b0816216 100644 --- a/pyboy/core/jit.py +++ b/pyboy/core/jit.py @@ -209,28 +209,36 @@ def getitem_bank(self, bank, i): def collect_block(self, block_id, cycles_target): boundary_instruction = [ - 0x18, # JR r8 - - # RET - 0xC0, - 0xD0, - 0xC3, # JP a16 - - # RST - 0xC7, - 0xD7, - 0xE7, - 0xF7, + 0xC7, # RST 00H + 0xCF, # RST 08H + 0xD7, # RST 10H + 0xDF, # RST 18H + 0xE7, # RST 20H + 0xEF, # RST 28H + 0xF7, # RST 30H + 0xFF, # RST 38H + 0xC4, # CALL NZ,a16 + 0xCC, # CALL Z,a16 + 0xCD, # CALL a16 + 0xD4, # CALL NC,a16 + 0xDC, # CALL C,a16 + 0xC0, # RET NZ + 0xC8, # RET Z 0xC9, # RET + 0xD0, # RET NC + 0xD8, # RET C 0xD9, # RETI + 0x18, # JR r8 + 0x20, # JR NZ,r8 + 0x28, # JR Z,r8 + 0x30, # JR NC,r8 + 0x38, # JR C,r8 + 0xC2, # JP NZ,a16 + 0xC3, # JP a16 + 0xCA, # JP Z,a16 + 0xD2, # JP NC,a16 + 0xDA, # JP C,a16 0xE9, # JP (HL) - 0xCD, # CALL a16 - - # RST - 0xCF, - 0xDF, - 0xEF, - 0xFF, 0x76, # HALT 0x10, # STOP 0xFB, # EI From e12bf554b9429f4b8d0c62d2ada995bfa6ee4107 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 6 Oct 2024 21:57:34 +0200 Subject: [PATCH 49/51] First steps in transforming JIT code. While loops. --- pyboy/core/jit.py | 236 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 180 insertions(+), 56 deletions(-) diff --git a/pyboy/core/jit.py b/pyboy/core/jit.py index 9b0816216..fd5a60602 100644 --- a/pyboy/core/jit.py +++ b/pyboy/core/jit.py @@ -51,6 +51,48 @@ def patched_validate_type_visibility(self, type, pos, env): """ +# NOTE: print('\n'.join([f"0x{i:02X}, # {x}" for i,x in enumerate(pyboy.core.opcodes.CPU_COMMANDS) if "JR" in x])) +jr_instruction = [ + 0x18, # JR r8 + 0x20, # JR NZ,r8 + 0x28, # JR Z,r8 + 0x30, # JR NC,r8 + 0x38, # JR C,r8 +] +jp_instruction = [ + 0xC2, # JP NZ,a16 + 0xC3, # JP a16 + 0xCA, # JP Z,a16 + 0xD2, # JP NC,a16 + 0xDA, # JP C,a16 +] +boundary_instruction = [ + 0xC7, # RST 00H + 0xCF, # RST 08H + 0xD7, # RST 10H + 0xDF, # RST 18H + 0xE7, # RST 20H + 0xEF, # RST 28H + 0xF7, # RST 30H + 0xFF, # RST 38H + 0xC4, # CALL NZ,a16 + 0xCC, # CALL Z,a16 + 0xCD, # CALL a16 + 0xD4, # CALL NC,a16 + 0xDC, # CALL C,a16 + 0xC0, # RET NZ + 0xC8, # RET Z + 0xC9, # RET + 0xD0, # RET NC + 0xD8, # RET C + 0xD9, # RETI + 0xE9, # JP (HL) + 0x76, # HALT + 0x10, # STOP + 0xFB, # EI + 0xDB, # Breakpoint/hook +] + def threaded_processor(jit): while not jit.thread_stop: @@ -166,39 +208,63 @@ def emit_code(self, code_block, func_name): code_text = "" if not cythonmode: code_text += f"def {func_name}(cpu, cycles_target):\n\t" - code_text += "flag = 0\n\tt = 0\n\ttr = 0\n\tv = 0" + code_text += "flag = 0\n\tt = 0\n\tv = 0\n\t_cycles0 = cpu.cycles\n\t_target = _cycles0 + cycles_target" else: code_text += f"cdef public void {func_name}(_cpu.CPU cpu, int64_t cycles_target) noexcept nogil:" - code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef int v" + code_text += "\n\tcdef uint8_t flag\n\tcdef int t\n\tcdef int v\n\tcdef int64_t _cycles0 = cpu.cycles\n\tcdef int64_t _target = _cycles0 + cycles_target" code_text += """ \tcdef uint16_t FLAGC = 4 \tcdef uint16_t FLAGH = 5 \tcdef uint16_t FLAGN = 6 \tcdef uint16_t FLAGZ = 7""" - for i, (opcode, length, pc, literal1, literal2) in enumerate(code_block): + def emit_opcode(indent, opcode, length, pc, literal1, literal2): opcode_handler = opcodes_gen[opcode] opcode_name = opcode_handler.name.split()[0] - code_text += "\n\t\n\t" + "# " + opcode_handler.name + f" (PC: 0x{pc:04x})\n\t" + preamble = f"\n\t\n\t" + "# " + opcode_handler.name + f" (PC: 0x{pc:04x})\n\t" if length == 2: v = literal1 - code_text += f"v = 0x{v:02x} # {v}\n\t" + preamble += f"v = 0x{v:02x} # {v}\n\t" elif length == 3: v = (literal2 << 8) + literal1 - code_text += f"v = 0x{v:04x} # {v}\n\t" + preamble += f"v = 0x{v:04x} # {v}\n\t" tmp_code = opcode_handler.functionhandlers[opcode_name]()._code_body() if "if" in tmp_code: # Return early on jump - tmp_code = tmp_code.replace("else:", "\treturn\n\telse:") + tmp_code = tmp_code.replace("else:", f"\treturn\n\telse:") elif "cpu.mb.setitem" in tmp_code: # Return early on state-altering writes - tmp_code += "\n\tif cpu.bail: return" - code_text += tmp_code + tmp_code += f"\n\tif cpu.bail: return" + return (preamble + tmp_code).replace("\t", indent) + + for i, (opcode, length, pc, literal1, literal2) in enumerate(code_block): + if opcode < 0x200: # Regular opcode + code_text += emit_opcode("\t", opcode, length, pc, literal1, literal2) + elif opcode == 0x200: # Loop body + loop_body_cycles, jump_to, jump_from, _block = length, pc, literal1, literal2 + # breakpoint() + code_text += f"\n\n\twhile True: # Loop body (PC: 0x{jump_to:04X} to 0x{jump_from:04X})" + for i, (opcode, length, pc, literal1, literal2) in enumerate(_block[:-1]): + code_text += emit_opcode("\t\t", opcode, length, pc, literal1, literal2) + + # Loop condition + opcode, length, pc, literal1, literal2 = _block[-1] + loop_condition = emit_opcode("\t\t", opcode, length, pc, literal1, literal2) + loop_condition = loop_condition.replace( + "return", + f'if cpu.cycles + {loop_body_cycles} < _target:\n\t\t\t\t\tcpu.jit_jump=False;continue\n\t\t\t\telse:\n\t\t\t\t\tcpu.jit_jump=False;return' + ) + loop_condition += "\n\t\tbreak" + code_text += loop_condition + elif opcode == 0x201: # Remainder of block + remainder_cycles = length + code_text += f'\n\tif cpu.cycles + {remainder_cycles} < _target: return' code_text += "\n\treturn\n\n" # opcodes[7].functionhandlers[opcodes[7].name.split()[0]]().branch_op # if .getitem in code, commit timer.tick(cycles); cycles = 0 + return code_text def getitem_bank(self, bank, i): @@ -208,55 +274,21 @@ def getitem_bank(self, bank, i): return self.cartridge.rombanks[bank, i - 0x4000] def collect_block(self, block_id, cycles_target): - boundary_instruction = [ - 0xC7, # RST 00H - 0xCF, # RST 08H - 0xD7, # RST 10H - 0xDF, # RST 18H - 0xE7, # RST 20H - 0xEF, # RST 28H - 0xF7, # RST 30H - 0xFF, # RST 38H - 0xC4, # CALL NZ,a16 - 0xCC, # CALL Z,a16 - 0xCD, # CALL a16 - 0xD4, # CALL NC,a16 - 0xDC, # CALL C,a16 - 0xC0, # RET NZ - 0xC8, # RET Z - 0xC9, # RET - 0xD0, # RET NC - 0xD8, # RET C - 0xD9, # RETI - 0x18, # JR r8 - 0x20, # JR NZ,r8 - 0x28, # JR Z,r8 - 0x30, # JR NC,r8 - 0x38, # JR C,r8 - 0xC2, # JP NZ,a16 - 0xC3, # JP a16 - 0xCA, # JP Z,a16 - 0xD2, # JP NC,a16 - 0xDA, # JP C,a16 - 0xE9, # JP (HL) - 0x76, # HALT - 0x10, # STOP - 0xFB, # EI - 0xDB, # Breakpoint/hook - ] code_block = [] - pc = block_id >> 8 - assert pc < 0x8000 + PC = block_id >> 8 + _PC = PC + assert PC < 0x8000 rom_bank = block_id & 0xFF + has_internal_jump = False block_max_cycles = 0 while True: # for _ in range(200): # while block_max_cycles < 200: - opcode = self.getitem_bank(rom_bank, pc) + opcode = self.getitem_bank(rom_bank, PC) if opcode == 0xCB: # Extension code - pc += 1 - opcode = self.getitem_bank(rom_bank, pc) + PC += 1 + opcode = self.getitem_bank(rom_bank, PC) opcode += 0x100 # Internally shifting look-up table opcode_length = opcodes.OPCODE_LENGTHS[opcode] opcode_max_cycles = opcodes.OPCODE_MAX_CYCLES[opcode] @@ -264,14 +296,105 @@ def collect_block(self, block_id, cycles_target): if (block_max_cycles + opcode_max_cycles > cycles_target): break block_max_cycles += opcode_max_cycles - code_block.append( - (opcode, opcode_length, pc, self.getitem_bank(rom_bank, pc + 1), self.getitem_bank(rom_bank, pc + 2)) - ) - pc += opcode_length + l1, l2 = self.getitem_bank(rom_bank, PC + 1), self.getitem_bank(rom_bank, PC + 2) + code_block.append((opcode, opcode_length, PC, l1, l2)) + PC += opcode_length + + is_jr = opcode in jr_instruction + is_jp = opcode in jp_instruction + if opcode in boundary_instruction: break + elif is_jr or is_jp: + # We assume it's the ending instruction? Or is the validation at the top? + if not has_internal_jump: + if is_jr: + jump_to = PC + ((l1 ^ 0x80) - 0x80) + else: + jump_to = ((l2 << 8) | l1) + + if _PC <= jump_to < PC: # Detect internal jump + has_internal_jump = True + else: + # The jump is to somewhere else + break + else: + # Expected jump away + # TODO: Just one loop? + break + + return code_block, block_max_cycles, has_internal_jump + + def print_block(self, code_block): + def opcode_translate(opcode): + if opcode == 0x200: + return "loop block" + else: + return pyboy.core.opcodes.CPU_COMMANDS[opcode] + + print( + "\n".join( + f"0x{opcode:02X} {opcode_translate(opcode)}\tlen: {opcode_length}\tPC: {pc:04X}\tlit1: {l1:02X}\tlit2: {l2:02X}\tlit: {(l2<<8) | l1:04X}\t r8: {pc + ((l1 ^ 0x80) - 0x80):04X}" + for opcode, opcode_length, pc, l1, l2 in code_block + ) + ) - return code_block, block_max_cycles + def check_no_overlap(self, ranges): + if len(ranges) == 1: + return True + + # Sort the ranges by the starting points + ranges.sort(key=lambda x: x[0]) + + # Traverse through the ranges to check for overlap + for i in range(1, len(ranges)): + # If the start of the current range is less than the end of the previous range, there's an overlap + if ranges[i][0] < ranges[i - 1][1]: + return False + + return True + + def optimize_block(self, raw_code_block, raw_block_max_cycles, has_internal_jump): + if not has_internal_jump: + return raw_code_block + + # _, _, PC, _, _ = raw_code_block[0] + jumps = [] + for opcode, opcode_length, PC, l1, l2 in raw_code_block: + is_jr = opcode in jr_instruction + is_jp = opcode in jp_instruction + if is_jp or is_jr: + if is_jr: + jump_to = PC + ((l1 ^ 0x80) - 0x80) + elif is_jp: + jump_to = ((l2 << 8) | l1) + + jumps.append((jump_to, PC)) # Sorted as (start, end) + + if not self.check_no_overlap(jumps): + return raw_code_block + + new_block = [] + _block = [] + current_jump = jumps.pop() + for i, (opcode, opcode_length, pc, l1, l2) in enumerate(raw_code_block): + if current_jump and current_jump[0] <= pc < current_jump[1]: + # Collect body + _block.append((opcode, opcode_length, pc, l1, l2)) + elif current_jump and pc == current_jump[1]: + # Add loop block + _block.append((opcode, opcode_length, pc, l1, l2)) + loop_body_cycles = sum(opcodes.OPCODE_MAX_CYCLES[opcode] for opcode, _, _, _, _, in _block) + new_block.append((0x200, loop_body_cycles, current_jump[0], current_jump[1], _block)) + _block = [] + current_jump = jumps.pop() if jumps else None + + remainder_cycles = sum(opcodes.OPCODE_MAX_CYCLES[opcode] for opcode, _, _, _, _, in raw_code_block[i:]) + new_block.append((0x201, remainder_cycles, None, None, None)) + else: + # Add regular opcode + new_block.append((opcode, opcode_length, pc, l1, l2)) + return new_block def invalidate(self, bank, address): # Invalidate any JIT block that crosses this bank and adress. @@ -328,7 +451,8 @@ def process(self): # logger.critical("analyze: %x, %d, %d", block_id, cycles_target, interrupt_master_enable) - code_block, block_max_cycles = self.collect_block(block_id, cycles_target) + raw_code_block, block_max_cycles, has_internal_jump = self.collect_block(block_id, cycles_target) + code_block = self.optimize_block(raw_code_block, block_max_cycles, has_internal_jump) if block_max_cycles < 100: self.cycles[block_id] = -1 # Don't retry From d50193d2f73f6cc2ddf93f1ba33b89a76f67078d Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 6 Oct 2024 22:28:53 +0200 Subject: [PATCH 50/51] Remove bootrom clearing JIT --- pyboy/core/mb.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index ed3a39b64..a044e3eb6 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -569,9 +569,6 @@ def setitem(self, i, value): if self.bootrom_enabled and i == 0xFF50 and (value == 0x1 or value == 0x11): logger.debug("Bootrom disabled!") self.bootrom_enabled = False - self.cpu.bail = True - if self.jit_enabled: - self.jit._jit_clear() # CGB registers elif self.cgb and i == 0xFF4D: self.key1 = value From 0247bec39747382f899c35251d76861e12e5c641 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 6 Oct 2024 22:34:48 +0200 Subject: [PATCH 51/51] setup.py enable wraparound --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 782349624..cb47db52d 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ def initialize_options(self): "nonecheck": False, "overflowcheck": False, # "profile" : True, - "wraparound": False, + # "wraparound": False, "legacy_implicit_noexcept": True, }, )