Gameboy Development Forum

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.

Ads

#1 2024-08-15 04:39:08

Finalspace
New member
Registered: 2024-08-15
Posts: 3

Interrupt handling not correct?

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:

Code:

// 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

 

Board footer

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson