diff --git a/CHANGES b/CHANGES index 8d6af7566..af4a2d7d7 100644 --- a/CHANGES +++ b/CHANGES @@ -96,6 +96,7 @@ Misc: - Core: Improve support for ROM patch cheats, supporting disabling overlapping patches - GB: Allow pausing event loop while CPU is blocked - GB: Add support for sleep and shutdown callbacks + - GB: Redo double speed emulation (closes mgba.io/i/1515) - GB Core: Return the current number of banks for ROM/SRAM, not theoretical max - GB I/O: Implement preliminary support for PCM12/PCM34 (closes mgba.io/i/1468) - GBA: Allow pausing event loop while CPU is blocked diff --git a/include/mgba/internal/sm83/sm83.h b/include/mgba/internal/sm83/sm83.h index 10bbf8bc4..86b4d374b 100644 --- a/include/mgba/internal/sm83/sm83.h +++ b/include/mgba/internal/sm83/sm83.h @@ -133,6 +133,7 @@ struct SM83Core { uint16_t index; + int tMultiplier; int32_t cycles; int32_t nextEvent; enum SM83ExecutionState executionState; diff --git a/src/gb/audio.c b/src/gb/audio.c index 126f4bf17..f20fa3fac 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -65,7 +65,7 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu if (style == GB_AUDIO_GBA) { audio->timingFactor = 4; } else { - audio->timingFactor = 1; + audio->timingFactor = 2; } audio->frameEvent.context = audio; @@ -339,7 +339,7 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) { if (audio->playingCh3) { audio->ch3.readable = audio->style != GB_AUDIO_DMG; // TODO: Where does this cycle delay come from? - mTimingSchedule(audio->timing, &audio->ch3Event, audio->timingFactor * 4 + 2 * (2048 - audio->ch3.rate)); + mTimingSchedule(audio->timing, &audio->ch3Event, audio->timingFactor * (4 + 2 * (2048 - audio->ch3.rate))); } *audio->nr52 &= ~0x0004; *audio->nr52 |= audio->playingCh3 << 2; @@ -477,11 +477,8 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t value) { audio->skipFrame = false; audio->frame = 7; - if (audio->p) { - unsigned timingFactor = 0x400 >> !audio->p->doubleSpeed; - if (audio->p->timer.internalDiv & timingFactor) { - audio->skipFrame = true; - } + if (audio->p && audio->p->timer.internalDiv & 0x400) { + audio->skipFrame = true; } } } @@ -914,7 +911,7 @@ static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesL audio->ch3.readable = true; if (audio->style == GB_AUDIO_DMG) { mTimingDeschedule(audio->timing, &audio->ch3Fade); - mTimingSchedule(timing, &audio->ch3Fade, 2 - cyclesLate); + mTimingSchedule(timing, &audio->ch3Fade, 4 - cyclesLate); } int cycles = 2 * (2048 - ch->rate); mTimingSchedule(timing, &audio->ch3Event, audio->timingFactor * cycles - cyclesLate); diff --git a/src/gb/gb.c b/src/gb/gb.c index fdc30724b..00eae92c0 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -742,7 +742,7 @@ void GBSetInterrupts(struct SM83Core* cpu, bool enable) { gb->memory.ime = false; GBUpdateIRQs(gb); } else { - mTimingSchedule(&gb->timing, &gb->eiPending, 4); + mTimingSchedule(&gb->timing, &gb->eiPending, 4 * cpu->tMultiplier); } } @@ -796,7 +796,7 @@ void GBStop(struct SM83Core* cpu) { struct GB* gb = (struct GB*) cpu->master; if (gb->model >= GB_MODEL_CGB && gb->memory.io[GB_REG_KEY1] & 1) { gb->doubleSpeed ^= 1; - gb->audio.timingFactor = gb->doubleSpeed + 1; + gb->cpu->tMultiplier = 2 - gb->doubleSpeed; gb->memory.io[GB_REG_KEY1] = 0; gb->memory.io[GB_REG_KEY1] |= gb->doubleSpeed << 7; } else { diff --git a/src/gb/io.c b/src/gb/io.c index 0e3a70eca..6cf77e4c8 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -417,15 +417,15 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { } return; case GB_REG_TIMA: - if (value && mTimingUntil(&gb->timing, &gb->timer.irq) > 1) { + if (value && mTimingUntil(&gb->timing, &gb->timer.irq) > 2 - (int) gb->doubleSpeed) { mTimingDeschedule(&gb->timing, &gb->timer.irq); } - if (mTimingUntil(&gb->timing, &gb->timer.irq) == -1) { + if (mTimingUntil(&gb->timing, &gb->timer.irq) == (int) gb->doubleSpeed - 2) { return; } break; case GB_REG_TMA: - if (mTimingUntil(&gb->timing, &gb->timer.irq) == -1) { + if (mTimingUntil(&gb->timing, &gb->timer.irq) == (int) gb->doubleSpeed - 2) { gb->memory.io[GB_REG_TIMA] = value; } break; diff --git a/src/gb/memory.c b/src/gb/memory.c index b7bd9c9d3..a65ea0e89 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -531,10 +531,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) { base &= 0xDFFF; } mTimingDeschedule(&gb->timing, &gb->memory.dmaEvent); - mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8); - if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) { - gb->cpu->nextEvent = gb->cpu->cycles + 8; - } + mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8 * (2 - gb->doubleSpeed)); gb->memory.dmaSource = base; gb->memory.dmaDest = 0; gb->memory.dmaRemaining = 0xA0; @@ -580,7 +577,7 @@ void _GBMemoryDMAService(struct mTiming* timing, void* context, uint32_t cyclesL ++gb->memory.dmaDest; gb->memory.dmaRemaining = dmaRemaining - 1; if (gb->memory.dmaRemaining) { - mTimingSchedule(timing, &gb->memory.dmaEvent, 4 - cyclesLate); + mTimingSchedule(timing, &gb->memory.dmaEvent, 4 * (2 - gb->doubleSpeed) - cyclesLate); } } @@ -594,7 +591,7 @@ void _GBMemoryHDMAService(struct mTiming* timing, void* context, uint32_t cycles --gb->memory.hdmaRemaining; if (gb->memory.hdmaRemaining) { mTimingDeschedule(timing, &gb->memory.hdmaEvent); - mTimingSchedule(timing, &gb->memory.hdmaEvent, 2 - cyclesLate); + mTimingSchedule(timing, &gb->memory.hdmaEvent, 4 - cyclesLate); } else { gb->cpuBlocked = false; gb->memory.io[GB_REG_HDMA1] = gb->memory.hdmaSource >> 8; diff --git a/src/gb/serialize.c b/src/gb/serialize.c index 056d1fd77..f9c37b48f 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -175,8 +175,6 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { gb->cpu->halted = GBSerializedCpuFlagsGetHalted(flags); gb->cpuBlocked = GBSerializedCpuFlagsGetBlocked(flags); - gb->audio.timingFactor = gb->doubleSpeed + 1; - LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles); LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent); gb->timing.root = NULL; diff --git a/src/gb/sio.c b/src/gb/sio.c index 78e3a92e1..d327b606d 100644 --- a/src/gb/sio.c +++ b/src/gb/sio.c @@ -77,7 +77,7 @@ void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesL sio->pendingSB = 0xFF; } } else { - mTimingSchedule(timing, &sio->event, sio->period); + mTimingSchedule(timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed)); } } @@ -93,7 +93,7 @@ void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) { if (GBRegisterSCIsEnable(sc)) { mTimingDeschedule(&sio->p->timing, &sio->event); if (GBRegisterSCIsShiftClock(sc)) { - mTimingSchedule(&sio->p->timing, &sio->event, sio->period); + mTimingSchedule(&sio->p->timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed)); sio->remainingBits = 8; } } diff --git a/src/gb/sio/lockstep.c b/src/gb/sio/lockstep.c index a9b3ed8fd..d739140e5 100644 --- a/src/gb/sio/lockstep.c +++ b/src/gb/sio/lockstep.c @@ -128,7 +128,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) { case TRANSFER_FINISHING: // Finish the transfer // We need to make sure the other GBs catch up so they don't get behind - node->nextEvent += node->d.p->period - 8; // Split the cycles to avoid waiting too long + node->nextEvent += node->d.p->period * (2 - node->d.p->p->doubleSpeed) - 8; // Split the cycles to avoid waiting too long #ifndef NDEBUG ATOMIC_ADD(node->p->d.transferId, 1); #endif @@ -208,7 +208,7 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, struct GBSIOLockstepNode* node = user; mLockstepLock(&node->p->d); if (node->p->d.attached < 2) { - mTimingSchedule(timing, &node->event, (GBSIOCyclesPerTransfer[0] >> 1) - cyclesLate); + mTimingSchedule(timing, &node->event, (GBSIOCyclesPerTransfer[0] >> 1) * (2 - node->d.p->p->doubleSpeed) - cyclesLate); mLockstepUnlock(&node->p->d); return; } diff --git a/src/gb/timer.c b/src/gb/timer.c index 1a99a06fd..0be048139 100644 --- a/src/gb/timer.c +++ b/src/gb/timer.c @@ -20,17 +20,18 @@ void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) { } static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) { - while (timer->nextDiv >= GB_DMG_DIV_PERIOD) { - timer->nextDiv -= GB_DMG_DIV_PERIOD; + int tMultiplier = 2 - timer->p->doubleSpeed; + while (timer->nextDiv >= GB_DMG_DIV_PERIOD * tMultiplier) { + timer->nextDiv -= GB_DMG_DIV_PERIOD * tMultiplier; // Make sure to trigger when the correct bit is a falling edge if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) { ++timer->p->memory.io[GB_REG_TIMA]; if (!timer->p->memory.io[GB_REG_TIMA]) { - mTimingSchedule(&timer->p->timing, &timer->irq, 7 - ((timer->p->cpu->executionState - cyclesLate) & 3)); + mTimingSchedule(&timer->p->timing, &timer->irq, 7 * tMultiplier - ((timer->p->cpu->executionState * tMultiplier - cyclesLate) & (3 * tMultiplier))); } } - unsigned timingFactor = 0x3FF >> !timer->p->doubleSpeed; + unsigned timingFactor = 0x1FF; if ((timer->internalDiv & timingFactor) == timingFactor) { GBAudioUpdateFrame(&timer->p->audio, &timer->p->timing); } @@ -52,7 +53,7 @@ void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) if (timaToGo < divsToGo) { divsToGo = timaToGo; } - timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo; + timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo * (2 - timer->p->doubleSpeed); mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate); } @@ -66,7 +67,7 @@ void GBTimerReset(struct GBTimer* timer) { timer->irq.callback = _GBTimerIRQ; timer->event.priority = 0x21; - timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences + timer->nextDiv = GB_DMG_DIV_PERIOD * 2; timer->timaPeriod = 1024 >> 4; } @@ -74,27 +75,27 @@ void GBTimerDivReset(struct GBTimer* timer) { timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); mTimingDeschedule(&timer->p->timing, &timer->event); _GBTimerDivIncrement(timer, 0); - if (((timer->internalDiv << 1) | ((timer->nextDiv >> 3) & 1)) & timer->timaPeriod) { + int tMultiplier = 2 - timer->p->doubleSpeed; + if (((timer->internalDiv << 1) | ((timer->nextDiv >> (4 - timer->p->doubleSpeed)) & 1)) & timer->timaPeriod) { ++timer->p->memory.io[GB_REG_TIMA]; if (!timer->p->memory.io[GB_REG_TIMA]) { - mTimingSchedule(&timer->p->timing, &timer->irq, 7 - (timer->p->cpu->executionState & 3)); + mTimingSchedule(&timer->p->timing, &timer->irq, (7 - (timer->p->cpu->executionState & 3)) * tMultiplier); } } - unsigned timingFactor = 0x400 >> !timer->p->doubleSpeed; - if (timer->internalDiv & timingFactor) { + if (timer->internalDiv & 0x200) { GBAudioUpdateFrame(&timer->p->audio, &timer->p->timing); } timer->p->memory.io[GB_REG_DIV] = 0; timer->internalDiv = 0; - timer->nextDiv = GB_DMG_DIV_PERIOD; - mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3)); + timer->nextDiv = GB_DMG_DIV_PERIOD * (2 - timer->p->doubleSpeed); + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3) * tMultiplier); } uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { if (GBRegisterTACIsRun(tac)) { timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); mTimingDeschedule(&timer->p->timing, &timer->event); - _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 2) & 3); + _GBTimerDivIncrement(timer, ((timer->p->cpu->executionState + 2) & 3) * (2 - timer->p->doubleSpeed)); switch (GBRegisterTACGetClock(tac)) { case 0: @@ -111,7 +112,7 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { break; } - timer->nextDiv += GB_DMG_DIV_PERIOD; + timer->nextDiv += GB_DMG_DIV_PERIOD * (2 - timer->p->doubleSpeed); mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); } else { timer->timaPeriod = 0; diff --git a/src/gb/video.c b/src/gb/video.c index 9826b601f..ccaae7717 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -253,7 +253,7 @@ void GBVideoSkipBIOS(struct GBVideo* video) { GBUpdateIRQs(video->p); video->p->memory.io[GB_REG_STAT] = video->stat; mTimingDeschedule(&video->p->timing, &video->modeEvent); - mTimingSchedule(&video->p->timing, &video->modeEvent, next); + mTimingSchedule(&video->p->timing, &video->modeEvent, next << 1); } void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { @@ -297,7 +297,7 @@ void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { GBUpdateIRQs(video->p); video->p->memory.io[GB_REG_STAT] = video->stat; - mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); + mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate); } void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { @@ -334,14 +334,14 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { GBUpdateIRQs(video->p); } video->p->memory.io[GB_REG_STAT] = video->stat; - mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); + mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate); } void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; _cleanOAM(video, video->ly); video->x = -(video->p->memory.io[GB_REG_SCX] & 7); - video->dotClock = mTimingCurrentTime(timing) - cyclesLate + 5 - (video->x << video->p->doubleSpeed); + video->dotClock = mTimingCurrentTime(timing) - cyclesLate + 10 - (video->x << 1); int32_t next = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 6 - video->x; video->mode = 3; video->modeEvent.callback = _endMode3; @@ -352,7 +352,7 @@ void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { GBUpdateIRQs(video->p); } video->p->memory.io[GB_REG_STAT] = video->stat; - mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); + mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate); } void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { @@ -375,18 +375,18 @@ void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { video->p->memory.io[GB_REG_STAT] = video->stat; // TODO: Cache SCX & 7 in case it changes int32_t next = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 6 - (video->p->memory.io[GB_REG_SCX] & 7); - mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); + mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate); } void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(cyclesLate); struct GBVideo* video = context; if (video->p->cpu->executionState != SM83_CORE_FETCH) { - mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3)); + mTimingSchedule(timing, &video->frameEvent, (4 - ((video->p->cpu->executionState + 1) & 3)) * (2 - video->p->doubleSpeed)); return; } if (!GBRegisterLCDCIsEnable(video->p->memory.io[GB_REG_LCDC])) { - mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); + mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH << 1); } --video->frameskipCounter; @@ -424,7 +424,7 @@ void GBVideoProcessDots(struct GBVideo* video, uint32_t cyclesLate) { return; } int oldX = video->x; - video->x = (int32_t) (mTimingCurrentTime(&video->p->timing) - cyclesLate - video->dotClock) >> video->p->doubleSpeed; + video->x = ((int32_t) (mTimingCurrentTime(&video->p->timing) - cyclesLate - video->dotClock)) >> 1; if (video->x > GB_VIDEO_HORIZONTAL_PIXELS) { video->x = GB_VIDEO_HORIZONTAL_PIXELS; } else if (video->x < 0) { @@ -444,7 +444,7 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { video->modeEvent.callback = _endMode2; int32_t next = GB_VIDEO_MODE_2_LENGTH - 5; // TODO: Why is this fudge factor needed? Might be related to T-cycles for load/store differing mTimingDeschedule(&video->p->timing, &video->modeEvent); - mTimingSchedule(&video->p->timing, &video->modeEvent, next << video->p->doubleSpeed); + mTimingSchedule(&video->p->timing, &video->modeEvent, next << 1); video->ly = 0; video->p->memory.io[GB_REG_LY] = 0; @@ -471,7 +471,7 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { mTimingDeschedule(&video->p->timing, &video->modeEvent); mTimingDeschedule(&video->p->timing, &video->frameEvent); - mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); + mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH << 1); } video->p->memory.io[GB_REG_STAT] = video->stat; } diff --git a/src/sm83/sm83.c b/src/sm83/sm83.c index 28e9e4bd3..b8c9287ec 100644 --- a/src/sm83/sm83.c +++ b/src/sm83/sm83.c @@ -61,6 +61,7 @@ void SM83Reset(struct SM83Core* cpu) { cpu->instruction = 0; + cpu->tMultiplier = 2; cpu->cycles = 0; cpu->nextEvent = 0; cpu->executionState = SM83_CORE_FETCH; @@ -102,7 +103,7 @@ static void _SM83InstructionIRQ(struct SM83Core* cpu) { } static void _SM83Step(struct SM83Core* cpu) { - ++cpu->cycles; + cpu->cycles += cpu->tMultiplier; enum SM83ExecutionState state = cpu->executionState; cpu->executionState = SM83_CORE_IDLE_0; switch (state) { @@ -147,23 +148,31 @@ static void _SM83Step(struct SM83Core* cpu) { } } +static inline bool _SM83TickInternal(struct SM83Core* cpu) { + bool running = true; + _SM83Step(cpu); + int t = cpu->tMultiplier; + if (cpu->cycles + t * 2 >= cpu->nextEvent) { + int32_t diff = cpu->nextEvent - cpu->cycles; + cpu->cycles = cpu->nextEvent; + cpu->executionState += diff >> (t - 1); // NB: This assumes tMultiplier is either 1 or 2 + cpu->irqh.processEvents(cpu); + cpu->cycles += (SM83_CORE_EXECUTE - cpu->executionState) * t; + running = false; + } else { + cpu->cycles += t * 2; + } + cpu->executionState = SM83_CORE_FETCH; + cpu->instruction(cpu); + cpu->cycles += t; + return running; +} + void SM83Tick(struct SM83Core* cpu) { while (cpu->cycles >= cpu->nextEvent) { cpu->irqh.processEvents(cpu); } - _SM83Step(cpu); - if (cpu->cycles + 2 >= cpu->nextEvent) { - int32_t diff = cpu->nextEvent - cpu->cycles; - cpu->cycles = cpu->nextEvent; - cpu->executionState += diff; - cpu->irqh.processEvents(cpu); - cpu->cycles += SM83_CORE_EXECUTE - cpu->executionState; - } else { - cpu->cycles += 2; - } - cpu->executionState = SM83_CORE_FETCH; - cpu->instruction(cpu); - ++cpu->cycles; + _SM83TickInternal(cpu); } void SM83Run(struct SM83Core* cpu) { @@ -173,19 +182,6 @@ void SM83Run(struct SM83Core* cpu) { cpu->irqh.processEvents(cpu); break; } - _SM83Step(cpu); - if (cpu->cycles + 2 >= cpu->nextEvent) { - int32_t diff = cpu->nextEvent - cpu->cycles; - cpu->cycles = cpu->nextEvent; - cpu->executionState += diff; - cpu->irqh.processEvents(cpu); - cpu->cycles += SM83_CORE_EXECUTE - cpu->executionState; - running = false; - } else { - cpu->cycles += 2; - } - cpu->executionState = SM83_CORE_FETCH; - cpu->instruction(cpu); - ++cpu->cycles; + running = _SM83TickInternal(cpu) && running; } }