DS: Implement hardware DIV
Vicki Pfau vi@endrift.com
Fri, 17 Feb 2017 12:18:22 -0800
3 files changed,
74 insertions(+),
0 deletions(-)
M
include/mgba/internal/ds/ds.h
→
include/mgba/internal/ds/ds.h
@@ -101,6 +101,8 @@ struct VFile* bios9Vf;
struct mKeyCallback* keyCallback; struct mCoreCallbacks* coreCallbacks; + + struct mTimingEvent divEvent; }; struct DSCartridge {
M
src/ds/ds.c
→
src/ds/ds.c
@@ -77,6 +77,50 @@ ds->sliceStart = mTimingCurrentTime(timing);
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;@@ -130,6 +174,11 @@
ds->romVf = NULL; 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);
M
src/ds/io.c
→
src/ds/io.c
@@ -28,6 +28,12 @@ break;
} } +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 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 @@ // Fall through
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);