mirror of
https://github.com/libretro/snes9x2005.git
synced 2024-12-03 14:40:52 +00:00
480 lines
12 KiB
C
480 lines
12 KiB
C
#include "../copyright"
|
|
|
|
#include "fxemu.h"
|
|
#include "fxinst.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
/* The FxChip Emulator's internal variables */
|
|
struct FxRegs_s GSU; // This will be initialized when loading a ROM
|
|
|
|
uint32_t(**fx_ppfFunctionTable)(uint32_t) = 0;
|
|
void (**fx_ppfPlotTable)() = 0;
|
|
void (**fx_ppfOpcodeTable)() = 0;
|
|
|
|
void FxCacheWriteAccess(uint16_t vAddress)
|
|
{
|
|
if ((vAddress & 0x00f) == 0x00f)
|
|
GSU.vCacheFlags |= 1 << ((vAddress & 0x1f0) >> 4);
|
|
}
|
|
|
|
void FxFlushCache()
|
|
{
|
|
GSU.vCacheFlags = 0;
|
|
GSU.vCacheBaseReg = 0;
|
|
GSU.bCacheActive = false;
|
|
}
|
|
|
|
void fx_flushCache()
|
|
{
|
|
GSU.vCacheFlags = 0;
|
|
GSU.bCacheActive = false;
|
|
}
|
|
|
|
|
|
void fx_updateRamBank(uint8_t Byte)
|
|
{
|
|
// Update BankReg and Bank pointer
|
|
GSU.vRamBankReg = (uint32_t)Byte & (FX_RAM_BANKS - 1);
|
|
GSU.pvRamBank = GSU.apvRamBank[Byte & 0x3];
|
|
}
|
|
|
|
|
|
static void fx_readRegisterSpace()
|
|
{
|
|
int i;
|
|
uint8_t* p;
|
|
static uint32_t avHeight[] = { 128, 160, 192, 256 };
|
|
static uint32_t avMult[] = { 16, 32, 32, 64 };
|
|
|
|
GSU.vErrorCode = 0;
|
|
|
|
/* Update R0-R15 */
|
|
p = GSU.pvRegisters;
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
GSU.avReg[i] = *p++;
|
|
GSU.avReg[i] += ((uint32_t)(*p++)) << 8;
|
|
}
|
|
|
|
/* Update other registers */
|
|
p = GSU.pvRegisters;
|
|
GSU.vStatusReg = (uint32_t)p[GSU_SFR];
|
|
GSU.vStatusReg |= ((uint32_t)p[GSU_SFR + 1]) << 8;
|
|
GSU.vPrgBankReg = (uint32_t)p[GSU_PBR];
|
|
GSU.vRomBankReg = (uint32_t)p[GSU_ROMBR];
|
|
GSU.vRamBankReg = ((uint32_t)p[GSU_RAMBR]) & (FX_RAM_BANKS - 1);
|
|
GSU.vCacheBaseReg = (uint32_t)p[GSU_CBR];
|
|
GSU.vCacheBaseReg |= ((uint32_t)p[GSU_CBR + 1]) << 8;
|
|
|
|
/* Update status register variables */
|
|
GSU.vZero = !(GSU.vStatusReg & FLG_Z);
|
|
GSU.vSign = (GSU.vStatusReg & FLG_S) << 12;
|
|
GSU.vOverflow = (GSU.vStatusReg & FLG_OV) << 16;
|
|
GSU.vCarry = (GSU.vStatusReg & FLG_CY) >> 2;
|
|
|
|
/* Set bank pointers */
|
|
GSU.pvRamBank = GSU.apvRamBank[GSU.vRamBankReg & 0x3];
|
|
GSU.pvRomBank = GSU.apvRomBank[GSU.vRomBankReg];
|
|
GSU.pvPrgBank = GSU.apvRomBank[GSU.vPrgBankReg];
|
|
|
|
/* Set screen pointers */
|
|
GSU.pvScreenBase = &GSU.pvRam[ USEX8(p[GSU_SCBR]) << 10 ];
|
|
i = (int)(!!(p[GSU_SCMR] & 0x04));
|
|
i |= ((int)(!!(p[GSU_SCMR] & 0x20))) << 1;
|
|
GSU.vScreenHeight = GSU.vScreenRealHeight = avHeight[i];
|
|
GSU.vMode = p[GSU_SCMR] & 0x03;
|
|
if (i == 3)
|
|
GSU.vScreenSize = (256 / 8) * (256 / 8) * 32;
|
|
else
|
|
GSU.vScreenSize = (GSU.vScreenHeight / 8) * (256 / 8) * avMult[GSU.vMode];
|
|
if (GSU.vPlotOptionReg & 0x10)
|
|
{
|
|
/* OBJ Mode (for drawing into sprites) */
|
|
GSU.vScreenHeight = 256;
|
|
}
|
|
if (GSU.pvScreenBase + GSU.vScreenSize > GSU.pvRam + (GSU.nRamBanks * 65536))
|
|
GSU.pvScreenBase = GSU.pvRam + (GSU.nRamBanks * 65536) - GSU.vScreenSize;
|
|
GSU.pfPlot = fx_apfPlotTable[GSU.vMode];
|
|
GSU.pfRpix = fx_apfPlotTable[GSU.vMode + 5];
|
|
|
|
fx_ppfOpcodeTable[0x04c] = GSU.pfPlot;
|
|
fx_ppfOpcodeTable[0x14c] = GSU.pfRpix;
|
|
fx_ppfOpcodeTable[0x24c] = GSU.pfPlot;
|
|
fx_ppfOpcodeTable[0x34c] = GSU.pfRpix;
|
|
|
|
fx_computeScreenPointers();
|
|
}
|
|
|
|
void fx_dirtySCBR()
|
|
{
|
|
GSU.vSCBRDirty = true;
|
|
}
|
|
|
|
void fx_computeScreenPointers()
|
|
{
|
|
if (GSU.vMode != GSU.vPrevMode ||
|
|
GSU.vPrevScreenHeight != GSU.vScreenHeight ||
|
|
GSU.vSCBRDirty)
|
|
{
|
|
int i;
|
|
|
|
GSU.vSCBRDirty = false;
|
|
|
|
/* Make a list of pointers to the start of each screen column */
|
|
switch (GSU.vScreenHeight)
|
|
{
|
|
case 128:
|
|
switch (GSU.vMode)
|
|
{
|
|
case 0:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 4);
|
|
GSU.x[i] = i << 8;
|
|
}
|
|
break;
|
|
case 1:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 5);
|
|
GSU.x[i] = i << 9;
|
|
}
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 6);
|
|
GSU.x[i] = i << 10;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 160:
|
|
switch (GSU.vMode)
|
|
{
|
|
case 0:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 4);
|
|
GSU.x[i] = (i << 8) + (i << 6);
|
|
}
|
|
break;
|
|
case 1:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 5);
|
|
GSU.x[i] = (i << 9) + (i << 7);
|
|
}
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 6);
|
|
GSU.x[i] = (i << 10) + (i << 8);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 192:
|
|
switch (GSU.vMode)
|
|
{
|
|
case 0:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 4);
|
|
GSU.x[i] = (i << 8) + (i << 7);
|
|
}
|
|
break;
|
|
case 1:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 5);
|
|
GSU.x[i] = (i << 9) + (i << 8);
|
|
}
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase + (i << 6);
|
|
GSU.x[i] = (i << 10) + (i << 9);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 256:
|
|
switch (GSU.vMode)
|
|
{
|
|
case 0:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase +
|
|
((i & 0x10) << 9) + ((i & 0xf) << 8);
|
|
GSU.x[i] = ((i & 0x10) << 8) + ((i & 0xf) << 4);
|
|
}
|
|
break;
|
|
case 1:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase +
|
|
((i & 0x10) << 10) + ((i & 0xf) << 9);
|
|
GSU.x[i] = ((i & 0x10) << 9) + ((i & 0xf) << 5);
|
|
}
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
GSU.apvScreen[i] = GSU.pvScreenBase +
|
|
((i & 0x10) << 11) + ((i & 0xf) << 10);
|
|
GSU.x[i] = ((i & 0x10) << 10) + ((i & 0xf) << 6);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
GSU.vPrevMode = GSU.vMode;
|
|
GSU.vPrevScreenHeight = GSU.vScreenHeight;
|
|
}
|
|
}
|
|
|
|
static void fx_writeRegisterSpace()
|
|
{
|
|
int i;
|
|
uint8_t* p;
|
|
|
|
p = GSU.pvRegisters;
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
*p++ = (uint8_t)GSU.avReg[i];
|
|
*p++ = (uint8_t)(GSU.avReg[i] >> 8);
|
|
}
|
|
|
|
/* Update status register */
|
|
if (USEX16(GSU.vZero) == 0) SF(Z);
|
|
else CF(Z);
|
|
if (GSU.vSign & 0x8000) SF(S);
|
|
else CF(S);
|
|
if (GSU.vOverflow >= 0x8000 || GSU.vOverflow < -0x8000) SF(OV);
|
|
else CF(OV);
|
|
if (GSU.vCarry) SF(CY);
|
|
else CF(CY);
|
|
|
|
p = GSU.pvRegisters;
|
|
p[GSU_SFR] = (uint8_t)GSU.vStatusReg;
|
|
p[GSU_SFR + 1] = (uint8_t)(GSU.vStatusReg >> 8);
|
|
p[GSU_PBR] = (uint8_t)GSU.vPrgBankReg;
|
|
p[GSU_ROMBR] = (uint8_t)GSU.vRomBankReg;
|
|
p[GSU_RAMBR] = (uint8_t)GSU.vRamBankReg;
|
|
p[GSU_CBR] = (uint8_t)GSU.vCacheBaseReg;
|
|
p[GSU_CBR + 1] = (uint8_t)(GSU.vCacheBaseReg >> 8);
|
|
}
|
|
|
|
/* Reset the FxChip */
|
|
void FxReset(struct FxInit_s* psFxInfo)
|
|
{
|
|
int i;
|
|
static uint32_t(**appfFunction[])(uint32_t) =
|
|
{
|
|
&fx_apfFunctionTable[0]
|
|
};
|
|
static void (**appfPlot[])() =
|
|
{
|
|
&fx_apfPlotTable[0]
|
|
};
|
|
static void (**appfOpcode[])() =
|
|
{
|
|
&fx_apfOpcodeTable[0]
|
|
};
|
|
|
|
/* Get function pointers for the current emulation mode */
|
|
fx_ppfFunctionTable = appfFunction[psFxInfo->vFlags & 0x3];
|
|
fx_ppfPlotTable = appfPlot[psFxInfo->vFlags & 0x3];
|
|
fx_ppfOpcodeTable = appfOpcode[psFxInfo->vFlags & 0x3];
|
|
|
|
/* Clear all internal variables */
|
|
memset((uint8_t*)&GSU, 0, sizeof(struct FxRegs_s));
|
|
|
|
/* Set default registers */
|
|
GSU.pvSreg = GSU.pvDreg = &R0;
|
|
|
|
/* Set RAM and ROM pointers */
|
|
GSU.pvRegisters = psFxInfo->pvRegisters;
|
|
GSU.nRamBanks = psFxInfo->nRamBanks;
|
|
GSU.pvRam = psFxInfo->pvRam;
|
|
GSU.nRomBanks = psFxInfo->nRomBanks;
|
|
GSU.pvRom = psFxInfo->pvRom;
|
|
GSU.vPrevScreenHeight = ~0;
|
|
GSU.vPrevMode = ~0;
|
|
|
|
/* The GSU can't access more than 2mb (16mbits) */
|
|
if (GSU.nRomBanks > 0x20)
|
|
GSU.nRomBanks = 0x20;
|
|
|
|
/* Clear FxChip register space */
|
|
memset(GSU.pvRegisters, 0, 0x300);
|
|
|
|
/* Set FxChip version Number */
|
|
GSU.pvRegisters[0x3b] = 0;
|
|
|
|
/* Make ROM bank table */
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
uint32_t b = i & 0x7f;
|
|
if (b >= 0x40)
|
|
{
|
|
if (GSU.nRomBanks > 1)
|
|
b %= GSU.nRomBanks;
|
|
else
|
|
b &= 1;
|
|
|
|
GSU.apvRomBank[i] = &GSU.pvRom[ b << 16 ];
|
|
}
|
|
else
|
|
{
|
|
b %= GSU.nRomBanks * 2;
|
|
GSU.apvRomBank[i] = &GSU.pvRom[(b << 16) + 0x200000];
|
|
}
|
|
}
|
|
|
|
/* Make RAM bank table */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
GSU.apvRamBank[i] = &GSU.pvRam[(i % GSU.nRamBanks) << 16];
|
|
GSU.apvRomBank[0x70 + i] = GSU.apvRamBank[i];
|
|
}
|
|
|
|
/* Start with a nop in the pipe */
|
|
GSU.vPipe = 0x01;
|
|
|
|
/* Set pointer to GSU cache */
|
|
GSU.pvCache = &GSU.pvRegisters[0x100];
|
|
|
|
fx_readRegisterSpace();
|
|
}
|
|
|
|
static bool fx_checkStartAddress()
|
|
{
|
|
/* Check if we start inside the cache */
|
|
if (GSU.bCacheActive && R15 >= GSU.vCacheBaseReg
|
|
&& R15 < (GSU.vCacheBaseReg + 512))
|
|
return true;
|
|
|
|
/* Check if we're in an unused area */
|
|
if (GSU.vPrgBankReg < 0x40 && R15 < 0x8000)
|
|
return false;
|
|
if (GSU.vPrgBankReg >= 0x60 && GSU.vPrgBankReg <= 0x6f)
|
|
return false;
|
|
if (GSU.vPrgBankReg >= 0x74)
|
|
return false;
|
|
|
|
/* Check if we're in RAM and the RAN flag is not set */
|
|
if (GSU.vPrgBankReg >= 0x70 && GSU.vPrgBankReg <= 0x73 && !(SCMR & (1 << 3)))
|
|
return false;
|
|
|
|
/* If not, we're in ROM, so check if the RON flag is set */
|
|
if (!(SCMR & (1 << 4)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Execute until the next stop instruction */
|
|
int FxEmulate(uint32_t nInstructions)
|
|
{
|
|
uint32_t vCount;
|
|
|
|
/* Read registers and initialize GSU session */
|
|
fx_readRegisterSpace();
|
|
|
|
/* Check if the start address is valid */
|
|
if (!fx_checkStartAddress())
|
|
{
|
|
CF(G);
|
|
fx_writeRegisterSpace();
|
|
return 0;
|
|
}
|
|
|
|
/* Execute GSU session */
|
|
CF(IRQ);
|
|
|
|
vCount = fx_ppfFunctionTable[FX_FUNCTION_RUN](nInstructions);
|
|
|
|
/* Store GSU registers */
|
|
fx_writeRegisterSpace();
|
|
|
|
/* Check for error code */
|
|
if (GSU.vErrorCode)
|
|
return GSU.vErrorCode;
|
|
else
|
|
return vCount;
|
|
}
|
|
|
|
/* Step by step execution */
|
|
int FxStepOver(uint32_t nInstructions)
|
|
{
|
|
uint32_t vCount;
|
|
fx_readRegisterSpace();
|
|
|
|
/* Check if the start address is valid */
|
|
if (!fx_checkStartAddress())
|
|
{
|
|
CF(G);
|
|
return 0;
|
|
}
|
|
|
|
if (PIPE >= 0xf0)
|
|
GSU.vStepPoint = USEX16(R15 + 3);
|
|
else if ((PIPE >= 0x05 && PIPE <= 0x0f) || (PIPE >= 0xa0 && PIPE <= 0xaf))
|
|
GSU.vStepPoint = USEX16(R15 + 2);
|
|
else
|
|
GSU.vStepPoint = USEX16(R15 + 1);
|
|
vCount = fx_ppfFunctionTable[FX_FUNCTION_STEP_OVER](nInstructions);
|
|
fx_writeRegisterSpace();
|
|
if (GSU.vErrorCode)
|
|
return GSU.vErrorCode;
|
|
else
|
|
return vCount;
|
|
}
|
|
|
|
/* Errors */
|
|
int FxGetErrorCode()
|
|
{
|
|
return GSU.vErrorCode;
|
|
}
|
|
|
|
int FxGetIllegalAddress()
|
|
{
|
|
return GSU.vIllegalAddress;
|
|
}
|
|
|
|
/* Access to internal registers */
|
|
uint32_t FxGetColorRegister()
|
|
{
|
|
return GSU.vColorReg & 0xff;
|
|
}
|
|
|
|
uint32_t FxGetPlotOptionRegister()
|
|
{
|
|
return GSU.vPlotOptionReg & 0x1f;
|
|
}
|
|
|
|
uint32_t FxGetSourceRegisterIndex()
|
|
{
|
|
return GSU.pvSreg - GSU.avReg;
|
|
}
|
|
|
|
uint32_t FxGetDestinationRegisterIndex()
|
|
{
|
|
return GSU.pvDreg - GSU.avReg;
|
|
}
|
|
|
|
uint8_t FxPipe()
|
|
{
|
|
return GSU.vPipe;
|
|
}
|