diff --git a/include/mgba/internal/ds/ds.h b/include/mgba/internal/ds/ds.h index d3be85859..84a7f4348 100644 --- a/include/mgba/internal/ds/ds.h +++ b/include/mgba/internal/ds/ds.h @@ -101,6 +101,8 @@ struct DS { struct mKeyCallback* keyCallback; struct mCoreCallbacks* coreCallbacks; + + struct mTimingEvent divEvent; }; struct DSCartridge { diff --git a/src/ds/ds.c b/src/ds/ds.c index b15584243..249467b71 100644 --- a/src/ds/ds.c +++ b/src/ds/ds.c @@ -77,6 +77,50 @@ static void _slice(struct mTiming* timing, void* context, uint32_t cyclesLate) { ds->earlyExit = true; } +static void _divide(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + struct DS* ds = context; + ds->memory.io9[DS9_REG_DIVCNT >> 1] &= ~0x8000; + int64_t numerator; + int64_t denominator; + LOAD_64LE(numerator, DS9_REG_DIV_NUMER_0, ds->memory.io9); + LOAD_64LE(denominator, DS9_REG_DIV_DENOM_0, ds->memory.io9); + bool max = false; + switch (ds->memory.io9[DS9_REG_DIVCNT >> 1] & 0x3) { + case 0: + denominator &= 0xFFFFFFFFLL; + case 1: + case 3: + numerator &= 0xFFFFFFFFLL; + if (numerator == INT32_MIN) { + max = true; + } + break; + } + if (numerator == INT64_MIN) { + max = true; + } + if (!denominator) { + ds->memory.io9[DS9_REG_DIVCNT >> 1] |= 0x4000; + STORE_64LE(numerator, DS9_REG_DIVREM_RESULT_0, ds->memory.io9); + numerator >>= 63LL; + numerator = -numerator; + STORE_64LE(numerator, DS9_REG_DIV_RESULT_0, ds->memory.io9); + return; + } + if (denominator == -1LL && max) { + ds->memory.io9[DS9_REG_DIVCNT >> 1] |= 0x4000; + STORE_64LE(numerator, DS9_REG_DIV_RESULT_0, ds->memory.io9); + return; + } + ds->memory.io9[DS9_REG_DIVCNT >> 1] &= ~0x4000; + int64_t result = numerator / denominator; + int64_t remainder = numerator % denominator; // TODO: defined behavior for negative denominator? + STORE_64LE(result, DS9_REG_DIV_RESULT_0, ds->memory.io9); + STORE_64LE(remainder, DS9_REG_DIVREM_RESULT_0, ds->memory.io9); +} + void DSCreate(struct DS* ds) { ds->d.id = DS_COMPONENT_MAGIC; ds->d.init = DSInit; @@ -131,6 +175,11 @@ static void DSInit(void* cpu, struct mCPUComponent* component) { ds->keyCallback = NULL; + ds->divEvent.name = "DS Hardware Divide"; + ds->divEvent.callback = _divide; + ds->divEvent.context = ds; + ds->divEvent.priority = 0x50; + mTimingInit(&ds->ds7.timing, &ds->ds7.cpu->cycles, &ds->ds7.cpu->nextEvent); mTimingInit(&ds->ds9.timing, &ds->ds9.cpu->cycles, &ds->ds9.cpu->nextEvent); } diff --git a/src/ds/io.c b/src/ds/io.c index d3c08e11b..33262574d 100644 --- a/src/ds/io.c +++ b/src/ds/io.c @@ -28,6 +28,12 @@ static void _DSHaltCNT(struct DSCommon* dscore, uint8_t value) { } } +static uint16_t _scheduleDiv(struct DS* ds, uint16_t control) { + mTimingDeschedule(&ds->ds9.timing, &ds->divEvent); + mTimingSchedule(&ds->ds9.timing, &ds->divEvent, (control & 3) ? 36 : 68); + return control | 0x8000; +} + static uint32_t DSIOWrite(struct DSCommon* dscore, uint32_t address, uint16_t value) { switch (address) { // Video @@ -291,6 +297,7 @@ void DS9IOInit(struct DS* ds) { void DS9IOWrite(struct DS* ds, uint32_t address, uint16_t value) { switch (address) { + // VRAM control case DS9_REG_VRAMCNT_A: case DS9_REG_VRAMCNT_C: case DS9_REG_VRAMCNT_E: @@ -300,6 +307,22 @@ void DS9IOWrite(struct DS* ds, uint32_t address, uint16_t value) { case DS9_REG_VRAMCNT_I: DSVideoConfigureVRAM(&ds->memory, address - DS9_REG_VRAMCNT_A, value >> 8); break; + + // Math + case DS9_REG_DIVCNT: + value = _scheduleDiv(ds, value); + break; + case DS9_REG_DIV_NUMER_0: + case DS9_REG_DIV_NUMER_1: + case DS9_REG_DIV_NUMER_2: + case DS9_REG_DIV_NUMER_3: + case DS9_REG_DIV_DENOM_0: + case DS9_REG_DIV_DENOM_1: + case DS9_REG_DIV_DENOM_2: + case DS9_REG_DIV_DENOM_3: + ds->memory.io9[DS9_REG_DIVCNT >> 1] = _scheduleDiv(ds, ds->memory.io9[DS9_REG_DIVCNT >> 1]); + break; + default: { uint32_t v2 = DSIOWrite(&ds->ds9, address, value);