Discussion about software development for the old-school Gameboys, ranging from the "Gray brick" to Gameboy Color
(Launched in 2008)
You are not logged in.
I wrote a DMG gameboy emulator in C99 which plays a lot of games just fine, but there are a few games that won't work at all or has some major issues:
- Alley Way (D-Pad input not working at all, but Start/Select A/B works just as fine)
- Duck Tales (Sound are too fast and input handling breaks when you are on a ladder or vine - you can't jump of from this) -> But on real hardware or BGB, this works just fine by pressing left/right and then the jump key.
From looking at other emulators and reading the documentations, i think my interrupt handling is partly incorrect or i miss something... HALT BUG maybe?
In my current implementation interrupts are dispatched BEFORE the instruction is fetched/executed or BEFORE the CPU was halted - not after.
Other emulators does it differently, reading the next op-code but don't increment PC and then check for interrupts and maybe dispatch them or handle HALT/HALT-BUG/STOP. Only when a normal execution should be done, the instruction is decoded and executed and THEN the interrupts are dispatched when needed.
My Emulator Step looks like this:
// Executes one instruction or a NOP FGB_API bool fgbTick(fgbGameboy *gameboy) { // ... initial stuff // // Joypad State Change // if (gameboy->joypad.isStateChanged) { gameboy->joypad.isStateChanged = false; gameboy->joypad.currentState = gameboy->joypad.requestedState; fgb__JoypadInterrupt(gameboy, &gameboy->interrupts, &gameboy->joypad); } // // Interrupts // // NOTE(final): Handle interrupts when master interrupt is enabled if (gameboy->interrupts.masterEnable) { if (fgb__HasAnyInterrupts(&gameboy->interrupts)) { if (!fgb__InterruptsHandle(gameboy)) { FGB__Failure(gameboy, fgb__SystemName_Core, "Failed handling interrupts"); return false; } } gameboy->interrupts.isIMEPending = false; } // NOTE(final): Enable master interrupt, when master enable pending was set, so interrupts will be handled on the next tick if (gameboy->interrupts.isIMEPending) { gameboy->interrupts.masterEnable = true; } // ... other stuff if (!gameboy->cpu.state.isHalted) { // // Fetch Instruction // if (!fgb__FetchInstruction(gameboy)) { FGB__Failure(gameboy, fgb__SystemName_Core, "Failed to fetch instruction at address '$%04X'", cur->startPC); return false; } // // Fetch Data // const char *instructionName = fgbGetInstructionName(cur->instruction.type); const char *addressingModeName = fgbGetAddressingModeName(cur->instruction.mode); uint16_t fetchAddress = cpu->registers.pc; if (!fgb__FetchData(gameboy)) { fgb__PrintCurrentInstruction(gameboy, cur, &startRegs, startFrameIndex, "FETCH", fgb__PrintInstructionFlags_Console); FGB__Failure(gameboy, fgb__SystemName_Core, "Failed to fetch data for instruction '%s', mode '%s' at address '$%04X'", instructionName, addressingModeName, fetchAddress); return false; } #if FGB_CPU_INSTRUCTION_TICK_LOGGING fgb__PrintCurrentInstruction(gameboy, cur, &startRegs, startFrameIndex, "", fgb__PrintInstructionFlags_Console); #endif // // Execute // if (!fgb__ExecuteInstruction(gameboy)) { fgb__PrintCurrentInstruction(gameboy, cur, &startRegs, startFrameIndex, "EXEC", fgb__PrintInstructionFlags_Console); FGB__Failure(gameboy, fgb__SystemName_Core, "Failed to execute instruction '%s', mode '%s'", instructionName, addressingModeName); return false; } // ... assert cycle counts } else { // Halted: NOP fgb__CPUTick4(gameboy); // NOTE(final): Reset halted state, when any interrupt was requested if (gameboy->interrupts.request.u8 != 0) { cpu->state.isHalted = false; } } // Put the machine to a faulted state when there is an infinite loop detected and track the error if ((cpu->state.lastPC == regs->pc && cpu->state.lastSP == regs->sp) && (!gameboy->interrupts.masterEnable && !gameboy->interrupts.enable.u8)) { gameboy->state = fgbState_Error; gameboy->error.type = fgbErrorType_InfiniteLoop; } // Remember last PC and SP, so we can detect infinite loops cpu->state.lastPC = regs->pc; cpu->state.lastSP = regs->sp; // ... Save external RAM, if needed return true; }
Anyone can tell me how interrupts are properly handled inside in the main Tick() function?
Note that, CPUTick4() simulates the actual realtime bus and updates the Timer, PPU, APU, directly etc.
I don't just simply count the cycles and then eventually, update the components at the very end - i do it in while the instruction is executed. Oh and very importantly: FetchInstruction() increments the PC register and in case of a CB instruction, it also reads the next op-code for the CB as well. So the function does two things, Fetch Op-Codes and then decodes them.
Thanks in advance,
Finalspace
Last edited by Finalspace (2024-08-15 04:47:04)
Offline