mirror of
https://github.com/libretro/beetle-psx-libretro.git
synced 2024-11-23 16:59:49 +00:00
3959 lines
82 KiB
C++
3959 lines
82 KiB
C++
/******************************************************************************/
|
|
/* Mednafen Sony PS1 Emulation Module */
|
|
/******************************************************************************/
|
|
/* cpu.cpp:
|
|
** Copyright (C) 2011-2016 Mednafen Team
|
|
**
|
|
** This program is free software; you can redistribute it and/or
|
|
** modify it under the terms of the GNU General Public License
|
|
** as published by the Free Software Foundation; either version 2
|
|
** of the License, or (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software Foundation, Inc.,
|
|
** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#pragma GCC optimize ("unroll-loops")
|
|
|
|
#include "psx.h"
|
|
#include "cpu.h"
|
|
|
|
#include "../state_helpers.h"
|
|
#include "../math_ops.h"
|
|
#include "../mednafen.h"
|
|
#include "../mednafen-endian.h"
|
|
|
|
// iCB: PGXP STUFF
|
|
#include "../pgxp/pgxp_cpu.h"
|
|
#include "../pgxp/pgxp_gte.h"
|
|
#include "../pgxp/pgxp_main.h"
|
|
int pgxpMode = PGXP_GetModes();
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
#include <lightrec.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
|
|
enum DYNAREC prev_dynarec;
|
|
bool prev_invalidate;
|
|
extern bool psx_dynarec_invalidate;
|
|
extern uint8 psx_mmap;
|
|
static struct lightrec_state *lightrec_state;
|
|
#endif
|
|
|
|
extern bool psx_gte_overclock;
|
|
pscpu_timestamp_t PS_CPU::next_event_ts;
|
|
uint32 PS_CPU::IPCache;
|
|
uint32 PS_CPU::BIU;
|
|
bool PS_CPU::Halted;
|
|
struct PS_CPU::CP0 PS_CPU::CP0;
|
|
char PS_CPU::cache_buf[64 * 1024];
|
|
|
|
#if 0
|
|
#define EXP_ILL_CHECK(n) {n;}
|
|
#else
|
|
#define EXP_ILL_CHECK(n) {}
|
|
#endif
|
|
|
|
#define BIU_ENABLE_ICACHE_S1 0x00000800 // Enable I-cache, set 1
|
|
#define BIU_ICACHE_FSIZE_MASK 0x00000300 // I-cache fill size mask; 0x000 = 2 words, 0x100 = 4 words, 0x200 = 8 words, 0x300 = 16 words
|
|
#define BIU_ENABLE_DCACHE 0x00000080 // Enable D-cache
|
|
#define BIU_DCACHE_SCRATCHPAD 0x00000008 // Enable scratchpad RAM mode of D-cache
|
|
#define BIU_TAG_TEST_MODE 0x00000004 // Enable TAG test mode(IsC must be set to 1 as well presumably?)
|
|
#define BIU_INVALIDATE_MODE 0x00000002 // Enable Invalidate mode(IsC must be set to 1 as well presumably?)
|
|
#define BIU_LOCK_MODE 0x00000001 // Enable Lock mode(IsC must be set to 1 as well presumably?)
|
|
|
|
|
|
#if NOT_LIBRETRO
|
|
namespace MDFN_IEN_PSX
|
|
{
|
|
#endif
|
|
|
|
|
|
PS_CPU::PS_CPU()
|
|
{
|
|
//printf("%zu\n", (size_t)((uintptr_t)ICache - (uintptr_t)this));
|
|
|
|
addr_mask[0] = 0xFFFFFFFF;
|
|
addr_mask[1] = 0xFFFFFFFF;
|
|
addr_mask[2] = 0xFFFFFFFF;
|
|
addr_mask[3] = 0xFFFFFFFF;
|
|
addr_mask[4] = 0x7FFFFFFF;
|
|
addr_mask[5] = 0x1FFFFFFF;
|
|
addr_mask[6] = 0xFFFFFFFF;
|
|
addr_mask[7] = 0xFFFFFFFF;
|
|
|
|
Halted = false;
|
|
|
|
memset(FastMap, 0, sizeof(FastMap));
|
|
memset(DummyPage, 0xFF, sizeof(DummyPage)); // 0xFF to trigger an illegal instruction exception, so we'll know what's up when debugging.
|
|
|
|
for(uint64 a = 0x00000000; a < (1ULL << 32); a += FAST_MAP_PSIZE)
|
|
SetFastMap(DummyPage, a, FAST_MAP_PSIZE);
|
|
|
|
CPUHook = NULL;
|
|
ADDBT = NULL;
|
|
|
|
GTE_Init();
|
|
|
|
for(unsigned i = 0; i < 24; i++)
|
|
{
|
|
uint8 v = 7;
|
|
|
|
if(i < 12)
|
|
v += 4;
|
|
|
|
if(i < 21)
|
|
v += 3;
|
|
|
|
MULT_Tab24[i] = v;
|
|
}
|
|
}
|
|
|
|
PS_CPU::~PS_CPU()
|
|
{
|
|
#ifdef HAVE_LIGHTREC
|
|
if (lightrec_state)
|
|
lightrec_plugin_shutdown();
|
|
#endif
|
|
|
|
}
|
|
|
|
void PS_CPU::SetFastMap(void *region_mem, uint32 region_address, uint32 region_size)
|
|
{
|
|
// FAST_MAP_SHIFT
|
|
// FAST_MAP_PSIZE
|
|
|
|
for(uint64 A = region_address; A < (uint64)region_address + region_size; A += FAST_MAP_PSIZE)
|
|
{
|
|
FastMap[A >> FAST_MAP_SHIFT] = ((uintptr_t)region_mem - region_address);
|
|
}
|
|
}
|
|
|
|
INLINE void PS_CPU::RecalcIPCache(void)
|
|
{
|
|
IPCache = 0;
|
|
|
|
if((CP0.SR & CP0.CAUSE & 0xFF00) && (CP0.SR & 1))
|
|
IPCache = 0x80;
|
|
|
|
if(Halted)
|
|
IPCache = 0x80;
|
|
}
|
|
|
|
void PS_CPU::SetHalt(bool status)
|
|
{
|
|
Halted = status;
|
|
RecalcIPCache();
|
|
}
|
|
|
|
const uint8_t *PSX_LoadExpansion1(void);
|
|
|
|
void PS_CPU::Power(void)
|
|
{
|
|
assert(sizeof(ICache) == sizeof(ICache_Bulk));
|
|
|
|
memset(GPR, 0, sizeof(GPR));
|
|
memset(&CP0, 0, sizeof(CP0));
|
|
LO = 0;
|
|
HI = 0;
|
|
|
|
gte_ts_done = 0;
|
|
muldiv_ts_done = 0;
|
|
|
|
BACKED_PC = 0xBFC00000;
|
|
BACKED_new_PC = BACKED_PC + 4;
|
|
BDBT = 0;
|
|
|
|
BACKED_LDWhich = 0x20;
|
|
BACKED_LDValue = 0;
|
|
LDAbsorb = 0;
|
|
memset(ReadAbsorb, 0, sizeof(ReadAbsorb));
|
|
ReadAbsorbWhich = 0;
|
|
ReadFudge = 0;
|
|
|
|
CP0.SR |= (1 << 22); // BEV
|
|
CP0.SR |= (1 << 21); // TS
|
|
|
|
CP0.PRID = 0x2;
|
|
|
|
RecalcIPCache();
|
|
|
|
|
|
BIU = 0;
|
|
|
|
memset(ScratchRAM->data8, 0, 1024);
|
|
|
|
PGXP_Init();
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
prev_dynarec = psx_dynarec;
|
|
prev_invalidate = psx_dynarec_invalidate;
|
|
pgxpMode = PGXP_GetModes();
|
|
if(psx_dynarec != DYNAREC_DISABLED)
|
|
lightrec_plugin_init();
|
|
#endif
|
|
|
|
// Not quite sure about these poweron/reset values:
|
|
for(unsigned i = 0; i < 1024; i++)
|
|
{
|
|
ICache[i].TV = 0x2 | ((BIU & 0x800) ? 0x0 : 0x1);
|
|
ICache[i].Data = 0;
|
|
}
|
|
|
|
GTE_Power();
|
|
}
|
|
|
|
int PS_CPU::StateAction(StateMem *sm, const unsigned load, const bool data_only)
|
|
{
|
|
uint32 OPM = BDBT;
|
|
|
|
SFORMAT StateRegs[] =
|
|
{
|
|
SFARRAY32(GPR, 32),
|
|
SFVAR(LO),
|
|
SFVAR(HI),
|
|
SFVAR(BACKED_PC),
|
|
SFVAR(BACKED_new_PC),
|
|
SFVARN(OPM, "BACKED_new_PC_mask"),
|
|
|
|
SFVAR(IPCache),
|
|
SFVAR(Halted),
|
|
|
|
SFVAR(BACKED_LDWhich),
|
|
SFVAR(BACKED_LDValue),
|
|
SFVAR(LDAbsorb),
|
|
|
|
SFVAR(next_event_ts),
|
|
SFVAR(gte_ts_done),
|
|
SFVAR(muldiv_ts_done),
|
|
|
|
SFVAR(BIU),
|
|
SFVAR(ICache_Bulk),
|
|
|
|
SFVAR(CP0.Regs),
|
|
|
|
SFARRAY(ReadAbsorb, 0x20),
|
|
SFVARN(ReadAbsorb[0x20], "ReadAbsorbDummy"),
|
|
SFVAR(ReadAbsorbWhich),
|
|
SFVAR(ReadFudge),
|
|
|
|
SFARRAYN(ScratchRAM->data8, 1024, "ScratchRAM.data8"),
|
|
|
|
SFEND
|
|
};
|
|
int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "CPU");
|
|
|
|
ret &= GTE_StateAction(sm, load, data_only);
|
|
|
|
if(load)
|
|
{
|
|
#ifdef HAVE_LIGHTREC
|
|
if(psx_dynarec != DYNAREC_DISABLED) {
|
|
if(lightrec_state)
|
|
lightrec_invalidate_all(lightrec_state);
|
|
else
|
|
lightrec_plugin_init();
|
|
}
|
|
#endif
|
|
if(load < 0x939)
|
|
{
|
|
//
|
|
// For compatibility with pre-0.9.39 save states.
|
|
//
|
|
uint32 NOPM = ~OPM;
|
|
|
|
//printf("Old: new_PC=0x%08x, new_PC_mask=0x%08x\n", BACKED_new_PC, OPM);
|
|
|
|
BDBT = ((NOPM << 1) | (NOPM >> 1)) & 0x3;
|
|
|
|
BACKED_new_PC = (BACKED_PC & OPM) + BACKED_new_PC;
|
|
}
|
|
else
|
|
BDBT = OPM;
|
|
|
|
ReadAbsorbWhich &= 0x1F;
|
|
BACKED_LDWhich %= 0x21;
|
|
|
|
//printf("PC=0x%08x, new_PC=0x%08x, BDBT=0x%02x\n", BACKED_PC, BACKED_new_PC, BDBT);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void PS_CPU::AssertIRQ(unsigned which, bool asserted)
|
|
{
|
|
assert(which <= 5);
|
|
|
|
CP0.CAUSE &= ~(1 << (10 + which));
|
|
|
|
if(asserted)
|
|
CP0.CAUSE |= 1 << (10 + which);
|
|
|
|
RecalcIPCache();
|
|
}
|
|
|
|
void PS_CPU::SetBIU(uint32 val)
|
|
{
|
|
const uint32 old_BIU = BIU;
|
|
|
|
BIU = val & ~(0x440);
|
|
|
|
if((BIU ^ old_BIU) & 0x800)
|
|
{
|
|
if(BIU & 0x800) // ICache enabled
|
|
{
|
|
for(unsigned i = 0; i < 1024; i++)
|
|
ICache[i].TV &= ~0x1;
|
|
}
|
|
else // ICache disabled
|
|
{
|
|
for(unsigned i = 0; i < 1024; i++)
|
|
ICache[i].TV |= 0x1;
|
|
}
|
|
}
|
|
|
|
PSX_DBG(PSX_DBG_SPARSE, "[CPU] Set BIU=0x%08x\n", BIU);
|
|
}
|
|
|
|
uint32 PS_CPU::GetBIU(void)
|
|
{
|
|
return BIU;
|
|
}
|
|
|
|
template<typename T>
|
|
INLINE T PS_CPU::PeekMemory(uint32 address)
|
|
{
|
|
T ret;
|
|
address &= addr_mask[address >> 29];
|
|
|
|
if(address >= 0x1F800000 && address <= 0x1F8003FF)
|
|
return ScratchRAM->Read<T>(address & 0x3FF);
|
|
|
|
//assert(!(CP0.SR & 0x10000));
|
|
|
|
if(sizeof(T) == 1)
|
|
ret = PSX_MemPeek8(address);
|
|
else if(sizeof(T) == 2)
|
|
ret = PSX_MemPeek16(address);
|
|
else
|
|
ret = PSX_MemPeek32(address);
|
|
|
|
return(ret);
|
|
}
|
|
|
|
template<typename T>
|
|
void PS_CPU::PokeMemory(uint32 address, T value)
|
|
{
|
|
address &= addr_mask[address >> 29];
|
|
|
|
if(address >= 0x1F800000 && address <= 0x1F8003FF)
|
|
return ScratchRAM->Write<T>(address & 0x3FF, value);
|
|
|
|
if(sizeof(T) == 1)
|
|
PSX_MemPoke8(address, value);
|
|
else if(sizeof(T) == 2)
|
|
PSX_MemPoke16(address, value);
|
|
else
|
|
PSX_MemPoke32(address, value);
|
|
}
|
|
|
|
template<typename T>
|
|
INLINE T PS_CPU::ReadMemory(pscpu_timestamp_t ×tamp, uint32 address, bool DS24, bool LWC_timing)
|
|
{
|
|
T ret;
|
|
|
|
ReadAbsorb[ReadAbsorbWhich] = 0;
|
|
ReadAbsorbWhich = 0;
|
|
|
|
#if 0
|
|
if(MDFN_UNLIKELY(CP0.SR & 0x10000))
|
|
{
|
|
uint32 tmp = 0; // TODO(someday): = open bus
|
|
|
|
LDAbsorb = 0;
|
|
|
|
if(BIU & (BIU_TAG_TEST_MODE | BIU_INVALIDATE_MODE | BIU_LOCK_MODE))
|
|
{
|
|
if(BIU & BIU_TAG_TEST_MODE)
|
|
{
|
|
const __ICache* const ICI = &ICache[((address & 0xFF0) >> 2)];
|
|
|
|
tmp &= ~0x3F; // bits 0-3 validity, bit 4 tag compare match, bit 5 forced to 0(lock bit apparently unimplemented in the PS1).
|
|
|
|
//
|
|
// Get validity bits.
|
|
//
|
|
for(unsigned i = 0; i < 4; i++)
|
|
tmp |= (!(ICI[i].TV & 0x02)) << i;
|
|
|
|
//
|
|
// Tag compare.
|
|
//
|
|
if(!((address ^ ICI[0].TV) & 0xFFFFF000))
|
|
tmp |= 0x10;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tmp = ICache[(address & 0xFFC) >> 2].Data;
|
|
}
|
|
|
|
return tmp >> ((address & 0x3) * 8);
|
|
}
|
|
#endif
|
|
//
|
|
//
|
|
//
|
|
|
|
address &= addr_mask[address >> 29];
|
|
|
|
if(address >= 0x1F800000 && address <= 0x1F8003FF)
|
|
{
|
|
LDAbsorb = 0;
|
|
|
|
if(DS24)
|
|
return ScratchRAM->ReadU24(address & 0x3FF);
|
|
else
|
|
return ScratchRAM->Read<T>(address & 0x3FF);
|
|
}
|
|
|
|
timestamp += (ReadFudge >> 4) & 2;
|
|
|
|
//assert(!(CP0.SR & 0x10000));
|
|
|
|
pscpu_timestamp_t lts = timestamp;
|
|
|
|
if(sizeof(T) == 1)
|
|
ret = PSX_MemRead8(lts, address);
|
|
else if(sizeof(T) == 2)
|
|
ret = PSX_MemRead16(lts, address);
|
|
else
|
|
{
|
|
if(DS24)
|
|
ret = PSX_MemRead24(lts, address) & 0xFFFFFF;
|
|
else
|
|
ret = PSX_MemRead32(lts, address);
|
|
}
|
|
|
|
if(LWC_timing)
|
|
lts += 1;
|
|
else
|
|
lts += 2;
|
|
|
|
LDAbsorb = (lts - timestamp);
|
|
timestamp = lts;
|
|
|
|
return(ret);
|
|
}
|
|
|
|
template<typename T>
|
|
INLINE void PS_CPU::WriteMemory(pscpu_timestamp_t ×tamp, uint32 address, uint32 value, bool DS24)
|
|
{
|
|
if(MDFN_LIKELY(!(CP0.SR & 0x10000)))
|
|
{
|
|
address &= addr_mask[address >> 29];
|
|
|
|
if(address >= 0x1F800000 && address <= 0x1F8003FF)
|
|
{
|
|
if(DS24)
|
|
ScratchRAM->WriteU24(address & 0x3FF, value);
|
|
else
|
|
ScratchRAM->Write<T>(address & 0x3FF, value);
|
|
|
|
return;
|
|
}
|
|
|
|
if(sizeof(T) == 1)
|
|
PSX_MemWrite8(timestamp, address, value);
|
|
else if(sizeof(T) == 2)
|
|
PSX_MemWrite16(timestamp, address, value);
|
|
else
|
|
{
|
|
if(DS24)
|
|
PSX_MemWrite24(timestamp, address, value);
|
|
else
|
|
PSX_MemWrite32(timestamp, address, value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(BIU & BIU_ENABLE_ICACHE_S1) // Instruction cache is enabled/active
|
|
{
|
|
if(BIU & (BIU_TAG_TEST_MODE | BIU_INVALIDATE_MODE | BIU_LOCK_MODE))
|
|
{
|
|
const uint8 valid_bits = (BIU & BIU_TAG_TEST_MODE) ? ((value << ((address & 0x3) * 8)) & 0x0F) : 0x00;
|
|
__ICache* const ICI = &ICache[((address & 0xFF0) >> 2)];
|
|
|
|
//
|
|
// Set validity bits and tag.
|
|
//
|
|
for(unsigned i = 0; i < 4; i++)
|
|
ICI[i].TV = ((valid_bits & (1U << i)) ? 0x00 : 0x02) | (address & 0xFFFFFFF0) | (i << 2);
|
|
}
|
|
else
|
|
{
|
|
ICache[(address & 0xFFC) >> 2].Data = value << ((address & 0x3) * 8);
|
|
}
|
|
}
|
|
|
|
if((BIU & 0x081) == 0x080) // Writes to the scratchpad(TODO test)
|
|
{
|
|
if(DS24)
|
|
ScratchRAM->WriteU24(address & 0x3FF, value);
|
|
else
|
|
ScratchRAM->Write<T>(address & 0x3FF, value);
|
|
}
|
|
//printf("IsC WRITE%d 0x%08x 0x%08x -- CP0.SR=0x%08x\n", (int)sizeof(T), address, value, CP0.SR);
|
|
}
|
|
}
|
|
|
|
//
|
|
// ICache emulation here is not very accurate. More accurate emulation had about a 6% performance penalty for simple
|
|
// code that just looped infinitely, with no tangible known benefit for commercially-released games.
|
|
//
|
|
// We do emulate the tag test mode functionality in regards to loading custom tag, valid bits, and instruction word data, as it could
|
|
// hypothetically be useful for homebrew. However, due to inaccuracies, it's possible to load a tag for an address in the non-cached
|
|
// 0xA0000000-0xBFFFFFFF range, jump to the address, and have it execute out of instruction cache, which is wrong and not possible on a PS1.
|
|
//
|
|
// The other major inaccuracy here is how the region 0x80000000-0x9FFFFFFF is handled. On a PS1, when fetching instruction word data
|
|
// from this region, the upper bit is forced to 0 before the tag compare(though the tag compare IS a full 20 bit compare),
|
|
// and this address with the upper bit set to 0 is also written to the tag on cache miss. We don't do the address masking here,
|
|
// so in addition to the tag test mode implications, a program that jumps from somewhere in 0x00000000-0x1FFFFFFF to the corresponding
|
|
// location in 0x80000000-0x9FFFFFFF will always cause a cache miss in Mednafen.
|
|
//
|
|
// On a PS1, icache miss fill size can be programmed to 2, 4, 8, or 16 words(though 4 words is the nominally-correct setting). We always emulate the cache
|
|
// miss fill size as 4-words. Fill size of 8-words and 16-words are buggy on a PS1, and will write the wrong instruction data values(or read from the wrong
|
|
// addresses?) to cache when a cache miss occurs on an address that isn't aligned to a 4-word boundary.
|
|
// Fill size of 2-words seems to work on a PS1, and even behaves as if the line size is 2 words in regards to clearing
|
|
// the valid bits(when the tag matches, of course), but is obviously not very efficient unless running code that's just endless branching.
|
|
//
|
|
INLINE uint32 PS_CPU::ReadInstruction(pscpu_timestamp_t ×tamp, uint32 address)
|
|
{
|
|
uint32 instr;
|
|
|
|
instr = ICache[(address & 0xFFC) >> 2].Data;
|
|
|
|
if(ICache[(address & 0xFFC) >> 2].TV != address)
|
|
{
|
|
ReadAbsorb[ReadAbsorbWhich] = 0;
|
|
ReadAbsorbWhich = 0;
|
|
|
|
if(address >= 0xA0000000 || !(BIU & 0x800))
|
|
{
|
|
instr = MDFN_de32lsb<true>((uint8*)(FastMap[address >> FAST_MAP_SHIFT] + address));
|
|
|
|
if (!psx_gte_overclock) {
|
|
timestamp += 4; // Approximate best-case cache-disabled time, per PS1 tests(executing out of 0xA0000000+); it can be 5 in *some* sequences of code(like a lot of sequential "nop"s, probably other simple instructions too).
|
|
}
|
|
}
|
|
else
|
|
{
|
|
__ICache *ICI = &ICache[((address & 0xFF0) >> 2)];
|
|
const uint8 *FMP = (uint8*)(FastMap[(address & 0xFFFFFFF0) >> FAST_MAP_SHIFT] + (address & 0xFFFFFFF0));
|
|
|
|
// | 0x2 to simulate (in)validity bits.
|
|
ICI[0x00].TV = (address & 0xFFFFFFF0) | 0x0 | 0x2;
|
|
ICI[0x01].TV = (address & 0xFFFFFFF0) | 0x4 | 0x2;
|
|
ICI[0x02].TV = (address & 0xFFFFFFF0) | 0x8 | 0x2;
|
|
ICI[0x03].TV = (address & 0xFFFFFFF0) | 0xC | 0x2;
|
|
|
|
if (!psx_gte_overclock) {
|
|
timestamp += 3;
|
|
}
|
|
|
|
switch(address & 0xC)
|
|
{
|
|
case 0x0:
|
|
if (!psx_gte_overclock) {
|
|
timestamp++;
|
|
}
|
|
ICI[0x00].TV &= ~0x2;
|
|
ICI[0x00].Data = MDFN_de32lsb<true>(&FMP[0x0]);
|
|
case 0x4:
|
|
if (!psx_gte_overclock) {
|
|
timestamp++;
|
|
}
|
|
ICI[0x01].TV &= ~0x2;
|
|
ICI[0x01].Data = MDFN_de32lsb<true>(&FMP[0x4]);
|
|
case 0x8:
|
|
if (!psx_gte_overclock) {
|
|
timestamp++;
|
|
}
|
|
ICI[0x02].TV &= ~0x2;
|
|
ICI[0x02].Data = MDFN_de32lsb<true>(&FMP[0x8]);
|
|
case 0xC:
|
|
if (!psx_gte_overclock) {
|
|
timestamp++;
|
|
}
|
|
ICI[0x03].TV &= ~0x2;
|
|
ICI[0x03].Data = MDFN_de32lsb<true>(&FMP[0xC]);
|
|
break;
|
|
}
|
|
instr = ICache[(address & 0xFFC) >> 2].Data;
|
|
}
|
|
}
|
|
|
|
return instr;
|
|
}
|
|
|
|
uint32 NO_INLINE PS_CPU::Exception(uint32 code, uint32 PC, const uint32 NP, const uint32 instr)
|
|
{
|
|
uint32 handler = 0x80000080;
|
|
|
|
assert(code < 16);
|
|
|
|
#ifdef DEBUG
|
|
if(code != EXCEPTION_INT && code != EXCEPTION_BP && code != EXCEPTION_SYSCALL)
|
|
{
|
|
static const char* exmne[16] =
|
|
{
|
|
"INT", "MOD", "TLBL", "TLBS", "ADEL", "ADES", "IBE", "DBE", "SYSCALL", "BP", "RI", "COPU", "OV", NULL, NULL, NULL
|
|
};
|
|
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] Exception %s(0x%02x) @ PC=0x%08x(NP=0x%08x, BDBT=0x%02x), Instr=0x%08x, IPCache=0x%02x, CAUSE=0x%08x, SR=0x%08x, IRQC_Status=0x%04x, IRQC_Mask=0x%04x\n",
|
|
exmne[code], code, PC, NP, BDBT, instr, IPCache, CP0.CAUSE, CP0.SR, IRQ_GetRegister(IRQ_GSREG_STATUS, NULL, 0), IRQ_GetRegister(IRQ_GSREG_MASK, NULL, 0));
|
|
}
|
|
#endif
|
|
|
|
if(CP0.SR & (1 << 22)) // BEV
|
|
handler = 0xBFC00180;
|
|
|
|
CP0.EPC = PC;
|
|
if(BDBT & 2)
|
|
{
|
|
CP0.EPC -= 4;
|
|
CP0.TAR = NP;
|
|
}
|
|
|
|
if(ADDBT)
|
|
ADDBT(PC, handler, true);
|
|
|
|
// "Push" IEc and KUc(so that the new IEc and KUc are 0)
|
|
CP0.SR = (CP0.SR & ~0x3F) | ((CP0.SR << 2) & 0x3F);
|
|
|
|
// Setup cause register
|
|
CP0.CAUSE &= 0x0000FF00;
|
|
CP0.CAUSE |= code << 2;
|
|
|
|
CP0.CAUSE |= BDBT << 30;
|
|
CP0.CAUSE |= (instr << 2) & (0x3 << 28); // CE
|
|
|
|
//
|
|
//
|
|
//
|
|
RecalcIPCache();
|
|
|
|
BDBT = 0;
|
|
|
|
return(handler);
|
|
}
|
|
|
|
#define BACKING_TO_ACTIVE \
|
|
PC = BACKED_PC; \
|
|
new_PC = BACKED_new_PC; \
|
|
LDWhich = BACKED_LDWhich; \
|
|
LDValue = BACKED_LDValue;
|
|
|
|
#define ACTIVE_TO_BACKING \
|
|
BACKED_PC = PC; \
|
|
BACKED_new_PC = new_PC; \
|
|
BACKED_LDWhich = LDWhich; \
|
|
BACKED_LDValue = LDValue;
|
|
|
|
//
|
|
// Should come before DO_LDS() so the EXP_ILL_CHECK() emulator debugging macro in GPR_DEP() will work properly.
|
|
//
|
|
#define GPR_DEPRES_BEGIN { uint8 back = ReadAbsorb[0];
|
|
#define GPR_DEP(n) { unsigned tn = (n); ReadAbsorb[tn] = 0; EXP_ILL_CHECK(if(LDWhich > 0 && LDWhich < 0x20 && LDWhich == tn) { PSX_DBG(PSX_DBG_WARNING, "[CPU] Instruction at PC=0x%08x in load delay slot has dependency on load target register(0x%02x): SR=0x%08x\n", PC, LDWhich, CP0.SR); }) }
|
|
#define GPR_RES(n) { unsigned tn = (n); ReadAbsorb[tn] = 0; }
|
|
#define GPR_DEPRES_END ReadAbsorb[0] = back; }
|
|
|
|
template<bool DebugMode, bool BIOSPrintMode, bool ILHMode>
|
|
pscpu_timestamp_t PS_CPU::RunReal(pscpu_timestamp_t timestamp_in)
|
|
{
|
|
pscpu_timestamp_t timestamp = timestamp_in;
|
|
|
|
uint32 PC;
|
|
uint32 new_PC;
|
|
uint32 LDWhich;
|
|
uint32 LDValue;
|
|
|
|
//printf("%d %d\n", gte_ts_done, muldiv_ts_done);
|
|
|
|
gte_ts_done += timestamp;
|
|
muldiv_ts_done += timestamp;
|
|
|
|
BACKING_TO_ACTIVE;
|
|
|
|
#if defined(HAVE_LIGHTREC) && defined(LIGHTREC_DEBUG)
|
|
u32 oldpc = PC;
|
|
#endif
|
|
|
|
do
|
|
{
|
|
//printf("Running: %d %d\n", timestamp, next_event_ts);
|
|
while(MDFN_LIKELY(timestamp < next_event_ts))
|
|
{
|
|
uint32 instr;
|
|
uint32 opf;
|
|
|
|
// Zero must be zero...until the Master Plan is enacted.
|
|
GPR[0] = 0;
|
|
|
|
#ifdef DEBUG
|
|
if(DebugMode && CPUHook)
|
|
{
|
|
ACTIVE_TO_BACKING;
|
|
|
|
// For save states in step mode.
|
|
gte_ts_done -= timestamp;
|
|
muldiv_ts_done -= timestamp;
|
|
|
|
CPUHook(timestamp, PC);
|
|
|
|
// For save states in step mode.
|
|
gte_ts_done += timestamp;
|
|
muldiv_ts_done += timestamp;
|
|
|
|
BACKING_TO_ACTIVE;
|
|
}
|
|
#endif
|
|
|
|
#if NOT_LIBRETRO
|
|
if(BIOSPrintMode)
|
|
{
|
|
if(PC == 0xB0)
|
|
{
|
|
if(MDFN_UNLIKELY(GPR[9] == 0x3D))
|
|
{
|
|
PSX_DBG_BIOS_PUTC(GPR[4]);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Instruction fetch
|
|
//
|
|
if(MDFN_UNLIKELY(PC & 0x3))
|
|
{
|
|
// This will block interrupt processing, but since we're going more for keeping broken homebrew/hacks from working
|
|
// than super-duper-accurate pipeline emulation, it shouldn't be a problem.
|
|
CP0.BADA = PC;
|
|
new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, 0);
|
|
goto OpDone;
|
|
}
|
|
|
|
instr = ReadInstruction(timestamp, PC);
|
|
|
|
|
|
//
|
|
// Instruction decode
|
|
//
|
|
opf = instr & 0x3F;
|
|
|
|
if(instr & (0x3F << 26))
|
|
opf = 0x40 | (instr >> 26);
|
|
|
|
opf |= IPCache;
|
|
|
|
if(ReadAbsorb[ReadAbsorbWhich])
|
|
ReadAbsorb[ReadAbsorbWhich]--;
|
|
else
|
|
timestamp++;
|
|
|
|
#define DO_LDS() { GPR[LDWhich] = LDValue; ReadAbsorb[LDWhich] = LDAbsorb; ReadFudge = LDWhich; ReadAbsorbWhich |= LDWhich & 0x1F; LDWhich = 0x20; }
|
|
#define BEGIN_OPF(name) { op_##name:
|
|
#define END_OPF goto OpDone; }
|
|
|
|
#ifdef DEBUG
|
|
#define DEBUG_ADDBT() if(DebugMode && ADDBT) { ADDBT(PC, new_PC, false); }
|
|
#define DEBUG_ILH() \
|
|
if(ILHMode) \
|
|
{ \
|
|
if(old_PC == (((new_PC - 4) & mask) + offset)) \
|
|
{ \
|
|
if(MDFN_densb<uint32, true>((uint8*)(FastMap[PC >> FAST_MAP_SHIFT] + PC)) == 0) \
|
|
{ \
|
|
if(next_event_ts > timestamp) /* Necessary since next_event_ts might be set to something like "0" to force a call to the event handler. */ \
|
|
{ \
|
|
timestamp = next_event_ts; \
|
|
} \
|
|
} \
|
|
} \
|
|
}
|
|
#else
|
|
#define DEBUG_ADDBT()
|
|
#define DEBUG_ILH()
|
|
#endif
|
|
|
|
#define DO_BRANCH(arg_cond, arg_offset, arg_mask, arg_dolink, arg_linkreg)\
|
|
{ \
|
|
const bool cond = (arg_cond); \
|
|
const uint32 offset = (arg_offset); \
|
|
const uint32 mask = (arg_mask); \
|
|
const uint32 old_PC = PC; \
|
|
\
|
|
EXP_ILL_CHECK(if(BDBT) { PSX_DBG(PSX_DBG_WARNING, "[CPU] Branch instruction at PC=0x%08x in branch delay slot: SR=0x%08x\n", PC, CP0.SR);}) \
|
|
\
|
|
PC = new_PC; \
|
|
new_PC += 4; \
|
|
BDBT = 2; \
|
|
\
|
|
if(arg_dolink) \
|
|
GPR[(arg_linkreg)] = new_PC; \
|
|
\
|
|
if(cond) \
|
|
{ \
|
|
DEBUG_ILH() \
|
|
new_PC = ((new_PC - 4) & mask) + offset; \
|
|
BDBT = 3; \
|
|
DEBUG_ADDBT() \
|
|
} \
|
|
\
|
|
goto SkipNPCStuff; \
|
|
}
|
|
|
|
#define ITYPE uint32 rs MDFN_NOWARN_UNUSED = (instr >> 21) & 0x1F; uint32 rt MDFN_NOWARN_UNUSED = (instr >> 16) & 0x1F; uint32 immediate = (int32)(int16)(instr & 0xFFFF); /*printf(" rs=%02x(%08x), rt=%02x(%08x), immediate=(%08x) ", rs, GPR[rs], rt, GPR[rt], immediate);*/
|
|
#define ITYPE_ZE uint32 rs MDFN_NOWARN_UNUSED = (instr >> 21) & 0x1F; uint32 rt MDFN_NOWARN_UNUSED = (instr >> 16) & 0x1F; uint32 immediate = instr & 0xFFFF; /*printf(" rs=%02x(%08x), rt=%02x(%08x), immediate=(%08x) ", rs, GPR[rs], rt, GPR[rt], immediate);*/
|
|
#define JTYPE uint32 target = instr & ((1 << 26) - 1); /*printf(" target=(%08x) ", target);*/
|
|
#define RTYPE uint32 rs MDFN_NOWARN_UNUSED = (instr >> 21) & 0x1F; uint32 rt MDFN_NOWARN_UNUSED = (instr >> 16) & 0x1F; uint32 rd MDFN_NOWARN_UNUSED = (instr >> 11) & 0x1F; uint32 shamt MDFN_NOWARN_UNUSED = (instr >> 6) & 0x1F; /*printf(" rs=%02x(%08x), rt=%02x(%08x), rd=%02x(%08x) ", rs, GPR[rs], rt, GPR[rt], rd, GPR[rd]);*/
|
|
|
|
#if HAVE_COMPUTED_GOTO
|
|
#if 0
|
|
//
|
|
// These truncated 32-bit table values apparently can't be calculated at compile/link time by gcc on x86_64, so gcc inserts initialization code, but
|
|
// the compare for the initialization code path is placed sub-optimally(near where the table value is used instead of at the beginning of the function).
|
|
//
|
|
#define CGBEGIN static const uint32 const op_goto_table[256] = {
|
|
#define CGE(l) (uint32)(uintptr_t)&&l,
|
|
#define CGEND }; goto *(void*)(uintptr_t)op_goto_table[opf];
|
|
#else
|
|
#define CGBEGIN static const void *const op_goto_table[256] = {
|
|
#define CGE(l) &&l,
|
|
#define CGEND }; goto *op_goto_table[opf];
|
|
#endif
|
|
#else
|
|
/* (uint8) cast for cheaper alternative to generated branch+compare bounds check instructions, but still more
|
|
expensive than computed goto which needs no masking nor bounds checking.
|
|
*/
|
|
#define CGBEGIN { enum { CGESB = 1 + __COUNTER__ }; switch((uint8)opf) {
|
|
#define CGE(l) case __COUNTER__ - CGESB: goto l;
|
|
#define CGEND } }
|
|
#endif
|
|
|
|
CGBEGIN
|
|
CGE(op_SLL) CGE(op_ILL) CGE(op_SRL) CGE(op_SRA) CGE(op_SLLV) CGE(op_ILL) CGE(op_SRLV) CGE(op_SRAV)
|
|
CGE(op_JR) CGE(op_JALR) CGE(op_ILL) CGE(op_ILL) CGE(op_SYSCALL) CGE(op_BREAK) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_MFHI) CGE(op_MTHI) CGE(op_MFLO) CGE(op_MTLO) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_MULT) CGE(op_MULTU) CGE(op_DIV) CGE(op_DIVU) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_ADD) CGE(op_ADDU) CGE(op_SUB) CGE(op_SUBU) CGE(op_AND) CGE(op_OR) CGE(op_XOR) CGE(op_NOR)
|
|
CGE(op_ILL) CGE(op_ILL) CGE(op_SLT) CGE(op_SLTU) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
|
|
CGE(op_ILL) CGE(op_BCOND) CGE(op_J) CGE(op_JAL) CGE(op_BEQ) CGE(op_BNE) CGE(op_BLEZ) CGE(op_BGTZ)
|
|
CGE(op_ADDI) CGE(op_ADDIU) CGE(op_SLTI) CGE(op_SLTIU) CGE(op_ANDI) CGE(op_ORI) CGE(op_XORI) CGE(op_LUI)
|
|
CGE(op_COP0) CGE(op_COP13) CGE(op_COP2) CGE(op_COP13) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_LB) CGE(op_LH) CGE(op_LWL) CGE(op_LW) CGE(op_LBU) CGE(op_LHU) CGE(op_LWR) CGE(op_ILL)
|
|
CGE(op_SB) CGE(op_SH) CGE(op_SWL) CGE(op_SW) CGE(op_ILL) CGE(op_ILL) CGE(op_SWR) CGE(op_ILL)
|
|
CGE(op_LWC013) CGE(op_LWC013) CGE(op_LWC2) CGE(op_LWC013) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
CGE(op_SWC013) CGE(op_SWC013) CGE(op_SWC2) CGE(op_SWC013) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL)
|
|
|
|
// Interrupt portion of this table is constructed so that an interrupt won't be taken when the PC is pointing to a GTE instruction,
|
|
// to avoid problems caused by pipeline vs coprocessor nuances that aren't emulated.
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
|
|
CGE(op_ILL) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_COP2) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT)
|
|
CGEND
|
|
|
|
{
|
|
BEGIN_OPF(ILL);
|
|
DO_LDS();
|
|
new_PC = Exception(EXCEPTION_RI, PC, new_PC, instr);
|
|
END_OPF;
|
|
|
|
//
|
|
// ADD - Add Word
|
|
//
|
|
BEGIN_OPF(ADD);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] + GPR[rt];
|
|
bool ep = ((~(GPR[rs] ^ GPR[rt])) & (GPR[rs] ^ result)) & 0x80000000;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_ADD(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
if(MDFN_UNLIKELY(ep))
|
|
{
|
|
new_PC = Exception(EXCEPTION_OV, PC, new_PC, instr);
|
|
}
|
|
else
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// ADDI - Add Immediate Word
|
|
//
|
|
BEGIN_OPF(ADDI);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] + immediate;
|
|
bool ep = ((~(GPR[rs] ^ immediate)) & (GPR[rs] ^ result)) & 0x80000000;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_ADDI(instr, result, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
if(MDFN_UNLIKELY(ep))
|
|
{
|
|
new_PC = Exception(EXCEPTION_OV, PC, new_PC, instr);
|
|
}
|
|
else
|
|
GPR[rt] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// ADDIU - Add Immediate Unsigned Word
|
|
//
|
|
BEGIN_OPF(ADDIU);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] + immediate;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_ADDIU(instr, result, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rt] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// ADDU - Add Unsigned Word
|
|
//
|
|
BEGIN_OPF(ADDU);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] + GPR[rt];
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_ADDU(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// AND - And
|
|
//
|
|
BEGIN_OPF(AND);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] & GPR[rt];
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_AND(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// ANDI - And Immediate
|
|
//
|
|
BEGIN_OPF(ANDI);
|
|
ITYPE_ZE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] & immediate;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_ANDI(instr, result, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rt] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// BEQ - Branch on Equal
|
|
//
|
|
BEGIN_OPF(BEQ);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
const bool result = (GPR[rs] == GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, false, 0);
|
|
END_OPF;
|
|
|
|
// Bah, why does MIPS encoding have to be funky like this. :(
|
|
// Handles BGEZ, BGEZAL, BLTZ, BLTZAL
|
|
BEGIN_OPF(BCOND);
|
|
const uint32 tv = GPR[(instr >> 21) & 0x1F];
|
|
const uint32 riv = (instr >> 16) & 0x1F;
|
|
const uint32 immediate = (int32)(int16)(instr & 0xFFFF);
|
|
const bool result = (int32)(tv ^ (riv << 31)) < 0;
|
|
const uint32 link = ((riv & 0x1E) == 0x10) ? 31 : 0;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP((instr >> 21) & 0x1F);
|
|
GPR_RES(link);
|
|
GPR_DEPRES_END
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, true, link);
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// BGTZ - Branch on Greater than Zero
|
|
//
|
|
BEGIN_OPF(BGTZ);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
const bool result = (int32)GPR[rs] > 0;
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, false, 0);
|
|
END_OPF;
|
|
|
|
//
|
|
// BLEZ - Branch on Less Than or Equal to Zero
|
|
//
|
|
BEGIN_OPF(BLEZ);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
const bool result = (int32)GPR[rs] <= 0;
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, false, 0);
|
|
END_OPF;
|
|
|
|
//
|
|
// BNE - Branch on Not Equal
|
|
//
|
|
BEGIN_OPF(BNE);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
const bool result = GPR[rs] != GPR[rt];
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, false, 0);
|
|
END_OPF;
|
|
|
|
//
|
|
// BREAK - Breakpoint
|
|
//
|
|
BEGIN_OPF(BREAK);
|
|
DO_LDS();
|
|
new_PC = Exception(EXCEPTION_BP, PC, new_PC, instr);
|
|
END_OPF;
|
|
|
|
// Cop "instructions": CFCz(no CP0), COPz, CTCz(no CP0), LWCz(no CP0), MFCz, MTCz, SWCz(no CP0)
|
|
//
|
|
// COP0 instructions
|
|
//
|
|
BEGIN_OPF(COP0);
|
|
const uint32 sub_op = (instr >> 21) & 0x1F;
|
|
const uint32 rt = (instr >> 16) & 0x1F;
|
|
const uint32 rd = (instr >> 11) & 0x1F;
|
|
const uint32 val = GPR[rt];
|
|
|
|
switch(sub_op)
|
|
{
|
|
default:
|
|
DO_LDS();
|
|
break;
|
|
|
|
case 0x02:
|
|
case 0x06:
|
|
DO_LDS();
|
|
new_PC = Exception(EXCEPTION_RI, PC, new_PC, instr);
|
|
break;
|
|
|
|
case 0x00: // MFC0 - Move from Coprocessor
|
|
switch(rd)
|
|
{
|
|
case 0x00:
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x04:
|
|
case 0x0A:
|
|
DO_LDS();
|
|
new_PC = Exception(EXCEPTION_RI, PC, new_PC, instr);
|
|
break;
|
|
|
|
case 0x03:
|
|
case 0x05:
|
|
case 0x06:
|
|
case 0x07:
|
|
case 0x08:
|
|
case 0x09:
|
|
case 0x0B:
|
|
case 0x0C:
|
|
case 0x0D:
|
|
case 0x0E:
|
|
case 0x0F:
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
LDAbsorb = 0;
|
|
LDWhich = rt;
|
|
LDValue = CP0.Regs[rd];
|
|
break;
|
|
|
|
default:
|
|
// Tested to be rather NOPish
|
|
DO_LDS();
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] MFC0 from unmapped CP0 register %u.\n", rd);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x04: // MTC0 - Move to Coprocessor
|
|
DO_LDS();
|
|
switch(rd)
|
|
{
|
|
case 0x00:
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x04:
|
|
case 0x0A:
|
|
new_PC = Exception(EXCEPTION_RI, PC, new_PC, instr);
|
|
break;
|
|
|
|
case CP0REG_BPC:
|
|
CP0.BPC = val;
|
|
break;
|
|
|
|
case CP0REG_BDA:
|
|
CP0.BDA = val;
|
|
break;
|
|
|
|
case CP0REG_DCIC:
|
|
#ifdef DEBUG
|
|
if(val)
|
|
{
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] Non-zero write to DCIC: 0x%08x\n", val);
|
|
}
|
|
#endif
|
|
CP0.DCIC = val & 0xFF80003F;
|
|
break;
|
|
|
|
case CP0REG_BDAM:
|
|
CP0.BDAM = val;
|
|
break;
|
|
|
|
case CP0REG_BPCM:
|
|
CP0.BPCM = val;
|
|
break;
|
|
|
|
case CP0REG_CAUSE:
|
|
CP0.CAUSE &= ~(0x3 << 8);
|
|
CP0.CAUSE |= val & (0x3 << 8);
|
|
RecalcIPCache();
|
|
break;
|
|
|
|
case CP0REG_SR:
|
|
CP0.SR = val & ~( (0x3 << 26) | (0x3 << 23) | (0x3 << 6));
|
|
RecalcIPCache();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x08: // BC
|
|
case 0x0C:
|
|
DO_LDS();
|
|
{
|
|
const uint32 immediate = (int32)(int16)(instr & 0xFFFF);
|
|
const bool result = (false == (bool)(instr & (1U << 16)));
|
|
|
|
#ifdef DEBUG
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] BC0x instruction(0x%08x) @ PC=0x%08x\n", instr, PC);
|
|
#endif
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, false, 0);
|
|
}
|
|
break;
|
|
|
|
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
|
|
case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F:
|
|
DO_LDS();
|
|
{
|
|
const uint32 cp0_op = instr & 0x1F; // Not 0x3F
|
|
|
|
if(MDFN_LIKELY(cp0_op == 0x10)) // RFE
|
|
{
|
|
// "Pop"
|
|
CP0.SR = (CP0.SR & ~0x0F) | ((CP0.SR >> 2) & 0x0F);
|
|
RecalcIPCache();
|
|
}
|
|
else if(cp0_op == 0x01 || cp0_op == 0x02 || cp0_op == 0x06 || cp0_op == 0x08) // TLBR, TLBWI, TLBWR, TLBP
|
|
{
|
|
new_PC = Exception(EXCEPTION_RI, PC, new_PC, instr);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
END_OPF;
|
|
|
|
//
|
|
// COP2
|
|
//
|
|
BEGIN_OPF(COP2);
|
|
const uint32 sub_op = (instr >> 21) & 0x1F;
|
|
const uint32 rt = (instr >> 16) & 0x1F;
|
|
const uint32 rd = (instr >> 11) & 0x1F;
|
|
const uint32 val = GPR[rt];
|
|
|
|
if(MDFN_UNLIKELY(!(CP0.SR & (1U << (28 + 2)))))
|
|
{
|
|
DO_LDS();
|
|
new_PC = Exception(EXCEPTION_COPU, PC, new_PC, instr);
|
|
}
|
|
else switch(sub_op)
|
|
{
|
|
default:
|
|
DO_LDS();
|
|
break;
|
|
|
|
case 0x00: // MFC2 - Move from Coprocessor
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
if(timestamp < gte_ts_done)
|
|
{
|
|
LDAbsorb = gte_ts_done - timestamp;
|
|
timestamp = gte_ts_done;
|
|
}
|
|
else
|
|
LDAbsorb = 0;
|
|
|
|
LDWhich = rt;
|
|
LDValue = GTE_ReadDR(rd);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_GTE)
|
|
PGXP_GTE_MFC2(instr, LDValue, LDValue);
|
|
|
|
break;
|
|
|
|
case 0x04: // MTC2 - Move to Coprocessor
|
|
DO_LDS();
|
|
|
|
if(timestamp < gte_ts_done)
|
|
timestamp = gte_ts_done;
|
|
|
|
GTE_WriteDR(rd, val);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_GTE)
|
|
PGXP_GTE_MTC2(instr, val, val);
|
|
|
|
break;
|
|
|
|
case 0x02: // CFC2
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
if(timestamp < gte_ts_done)
|
|
{
|
|
LDAbsorb = gte_ts_done - timestamp;
|
|
timestamp = gte_ts_done;
|
|
}
|
|
else
|
|
LDAbsorb = 0;
|
|
|
|
LDWhich = rt;
|
|
LDValue = GTE_ReadCR(rd);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_GTE)
|
|
PGXP_GTE_CFC2(instr, LDValue, LDValue);
|
|
|
|
break;
|
|
|
|
case 0x06: // CTC2
|
|
DO_LDS();
|
|
|
|
if(timestamp < gte_ts_done)
|
|
timestamp = gte_ts_done;
|
|
|
|
GTE_WriteCR(rd, val);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_GTE)
|
|
PGXP_GTE_CTC2(instr, val, val);
|
|
|
|
break;
|
|
|
|
case 0x08:
|
|
case 0x0C:
|
|
DO_LDS();
|
|
{
|
|
const uint32 immediate = (int32)(int16)(instr & 0xFFFF);
|
|
const bool result = (false == (bool)(instr & (1U << 16)));
|
|
|
|
#ifdef DEBUG
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] BC2x instruction(0x%08x) @ PC=0x%08x\n", instr, PC);
|
|
#endif
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, false, 0);
|
|
}
|
|
break;
|
|
|
|
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
|
|
case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F:
|
|
DO_LDS();
|
|
|
|
if(timestamp < gte_ts_done)
|
|
timestamp = gte_ts_done;
|
|
gte_ts_done = timestamp + GTE_Instruction(instr);
|
|
break;
|
|
}
|
|
END_OPF;
|
|
|
|
//
|
|
// COP1, COP3
|
|
//
|
|
BEGIN_OPF(COP13);
|
|
DO_LDS();
|
|
|
|
if(!(CP0.SR & (1U << (28 + ((instr >> 26) & 0x3)))))
|
|
{
|
|
new_PC = Exception(EXCEPTION_COPU, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
const uint32 sub_op = (instr >> 21) & 0x1F;
|
|
|
|
#ifdef DEBUG
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] COP%u instruction(0x%08x) @ PC=0x%08x\n", (instr >> 26) & 0x3, instr, PC);
|
|
#endif
|
|
|
|
if(sub_op == 0x08 || sub_op == 0x0C)
|
|
{
|
|
const uint32 immediate = (int32)(int16)(instr & 0xFFFF);
|
|
const bool result = (false == (bool)(instr & (1U << 16)));
|
|
|
|
DO_BRANCH(result, (immediate << 2), ~0U, false, 0);
|
|
}
|
|
}
|
|
END_OPF;
|
|
|
|
//
|
|
// LWC0, LWC1, LWC3
|
|
//
|
|
BEGIN_OPF(LWC013);
|
|
ITYPE;
|
|
const uint32 address = GPR[rs] + immediate;
|
|
|
|
DO_LDS();
|
|
|
|
if(!(CP0.SR & (1U << (28 + ((instr >> 26) & 0x3)))))
|
|
{
|
|
new_PC = Exception(EXCEPTION_COPU, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
if(MDFN_UNLIKELY(address & 3))
|
|
{
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] LWC%u instruction(0x%08x) @ PC=0x%08x\n", (instr >> 26) & 0x3, instr, PC);
|
|
#endif
|
|
|
|
ReadMemory<uint32>(timestamp, address, false, true);
|
|
}
|
|
}
|
|
END_OPF;
|
|
|
|
//
|
|
// LWC2
|
|
//
|
|
BEGIN_OPF(LWC2);
|
|
ITYPE;
|
|
const uint32 address = GPR[rs] + immediate;
|
|
|
|
DO_LDS();
|
|
|
|
if(MDFN_UNLIKELY(address & 3))
|
|
{
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
if(timestamp < gte_ts_done)
|
|
timestamp = gte_ts_done;
|
|
|
|
uint32_t value = ReadMemory<uint32>(timestamp, address, false, true);
|
|
GTE_WriteDR(rt, value);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_GTE)
|
|
PGXP_GTE_LWC2(instr, value, address);
|
|
|
|
}
|
|
// GTE stuff here
|
|
END_OPF;
|
|
|
|
//
|
|
// SWC0, SWC1, SCW3
|
|
//
|
|
BEGIN_OPF(SWC013);
|
|
ITYPE;
|
|
const uint32 address = GPR[rs] + immediate;
|
|
|
|
DO_LDS();
|
|
|
|
if(!(CP0.SR & (1U << (28 + ((instr >> 26) & 0x3)))))
|
|
{
|
|
new_PC = Exception(EXCEPTION_COPU, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
if(MDFN_UNLIKELY(address & 0x3))
|
|
{
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADES, PC, new_PC, instr);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
{
|
|
PSX_DBG(PSX_DBG_WARNING, "[CPU] SWC%u instruction(0x%08x) @ PC=0x%08x\n", (instr >> 26) & 0x3, instr, PC);
|
|
//WriteMemory<uint32>(timestamp, address, SOMETHING);
|
|
}
|
|
#endif
|
|
}
|
|
END_OPF;
|
|
|
|
//
|
|
// SWC2
|
|
//
|
|
BEGIN_OPF(SWC2);
|
|
ITYPE;
|
|
const uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(address & 0x3))
|
|
{
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADES, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
if(timestamp < gte_ts_done)
|
|
timestamp = gte_ts_done;
|
|
|
|
WriteMemory<uint32>(timestamp, address, GTE_ReadDR(rt));
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_GTE)
|
|
PGXP_GTE_SWC2(instr, GTE_ReadDR(rt), address);
|
|
}
|
|
DO_LDS();
|
|
END_OPF;
|
|
|
|
//
|
|
// DIV - Divide Word
|
|
//
|
|
BEGIN_OPF(DIV);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
if(!GPR[rt])
|
|
{
|
|
if(GPR[rs] & 0x80000000)
|
|
LO = 1;
|
|
else
|
|
LO = 0xFFFFFFFF;
|
|
|
|
HI = GPR[rs];
|
|
}
|
|
else if(GPR[rs] == 0x80000000 && GPR[rt] == 0xFFFFFFFF)
|
|
{
|
|
LO = 0x80000000;
|
|
HI = 0;
|
|
}
|
|
else
|
|
{
|
|
LO = (int32)GPR[rs] / (int32)GPR[rt];
|
|
HI = (int32)GPR[rs] % (int32)GPR[rt];
|
|
}
|
|
muldiv_ts_done = timestamp + 37;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_DIV(instr, HI, LO, GPR[rs], GPR[rt]);
|
|
DO_LDS();
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// DIVU - Divide Unsigned Word
|
|
//
|
|
BEGIN_OPF(DIVU);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
if(!GPR[rt])
|
|
{
|
|
LO = 0xFFFFFFFF;
|
|
HI = GPR[rs];
|
|
}
|
|
else
|
|
{
|
|
LO = GPR[rs] / GPR[rt];
|
|
HI = GPR[rs] % GPR[rt];
|
|
}
|
|
muldiv_ts_done = timestamp + 37;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_DIVU(instr, HI, LO, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
END_OPF;
|
|
|
|
//
|
|
// J - Jump
|
|
//
|
|
BEGIN_OPF(J);
|
|
JTYPE;
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(true, target << 2, 0xF0000000, false, 0);
|
|
END_OPF;
|
|
|
|
//
|
|
// JAL - Jump and Link
|
|
//
|
|
BEGIN_OPF(JAL);
|
|
JTYPE;
|
|
|
|
//GPR_DEPRES_BEGIN
|
|
GPR_RES(31);
|
|
//GPR_DEPRES_END
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(true, target << 2, 0xF0000000, true, 31);
|
|
END_OPF;
|
|
|
|
//
|
|
// JALR - Jump and Link Register
|
|
//
|
|
BEGIN_OPF(JALR);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 tmp = GPR[rs];
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(true, tmp, 0, true, rd);
|
|
END_OPF;
|
|
|
|
//
|
|
// JR - Jump Register
|
|
//
|
|
BEGIN_OPF(JR);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 bt = GPR[rs];
|
|
|
|
DO_LDS();
|
|
|
|
DO_BRANCH(true, bt, 0, false, 0);
|
|
END_OPF;
|
|
|
|
//
|
|
// LUI - Load Upper Immediate
|
|
//
|
|
BEGIN_OPF(LUI);
|
|
ITYPE_ZE; // Actually, probably would be sign-extending...if we were emulating a 64-bit MIPS chip :b
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rt] = immediate << 16;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_LUI(instr, GPR[rt]);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// MFHI - Move from HI
|
|
//
|
|
BEGIN_OPF(MFHI);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
DO_LDS();
|
|
|
|
if(timestamp < muldiv_ts_done)
|
|
{
|
|
if(timestamp == muldiv_ts_done - 1)
|
|
muldiv_ts_done--;
|
|
else
|
|
{
|
|
do
|
|
{
|
|
if(ReadAbsorb[ReadAbsorbWhich])
|
|
ReadAbsorb[ReadAbsorbWhich]--;
|
|
timestamp++;
|
|
} while(timestamp < muldiv_ts_done);
|
|
}
|
|
}
|
|
|
|
GPR[rd] = HI;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_MFHI(instr, GPR[rd], HI);
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// MFLO - Move from LO
|
|
//
|
|
BEGIN_OPF(MFLO);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
DO_LDS();
|
|
|
|
if(timestamp < muldiv_ts_done)
|
|
{
|
|
if(timestamp == muldiv_ts_done - 1)
|
|
muldiv_ts_done--;
|
|
else
|
|
{
|
|
do
|
|
{
|
|
if(ReadAbsorb[ReadAbsorbWhich])
|
|
ReadAbsorb[ReadAbsorbWhich]--;
|
|
timestamp++;
|
|
} while(timestamp < muldiv_ts_done);
|
|
}
|
|
}
|
|
|
|
GPR[rd] = LO;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_MFLO(instr, GPR[rd], LO);
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// MTHI - Move to HI
|
|
//
|
|
BEGIN_OPF(MTHI);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
HI = GPR[rs];
|
|
|
|
DO_LDS();
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_MTHI(instr, HI, GPR[rs]);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// MTLO - Move to LO
|
|
//
|
|
BEGIN_OPF(MTLO);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
LO = GPR[rs];
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_MTLO(instr, LO, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// MULT - Multiply Word
|
|
//
|
|
BEGIN_OPF(MULT);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint64 result;
|
|
|
|
result = (int64)(int32)GPR[rs] * (int32)GPR[rt];
|
|
muldiv_ts_done = timestamp + MULT_Tab24[MDFN_lzcount32((GPR[rs] ^ ((int32)GPR[rs] >> 31)) | 0x400)];
|
|
DO_LDS();
|
|
|
|
LO = result;
|
|
HI = result >> 32;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_MULT(instr, HI, LO, GPR[rs], GPR[rt]);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// MULTU - Multiply Unsigned Word
|
|
//
|
|
BEGIN_OPF(MULTU);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint64 result;
|
|
|
|
result = (uint64)GPR[rs] * GPR[rt];
|
|
muldiv_ts_done = timestamp + MULT_Tab24[MDFN_lzcount32(GPR[rs] | 0x400)];
|
|
DO_LDS();
|
|
|
|
LO = result;
|
|
HI = result >> 32;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_MULTU(instr, HI, LO, GPR[rs], GPR[rt]);
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// NOR - NOR
|
|
//
|
|
BEGIN_OPF(NOR);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = ~(GPR[rs] | GPR[rt]);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_NOR(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// OR - OR
|
|
//
|
|
BEGIN_OPF(OR);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] | GPR[rt];
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_OR(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// ORI - OR Immediate
|
|
//
|
|
BEGIN_OPF(ORI);
|
|
ITYPE_ZE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] | immediate;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_ORI(instr, result, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rt] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SLL - Shift Word Left Logical
|
|
//
|
|
BEGIN_OPF(SLL); // SLL
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rt] << shamt;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SLL(instr, result, GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SLLV - Shift Word Left Logical Variable
|
|
//
|
|
BEGIN_OPF(SLLV);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rt] << (GPR[rs] & 0x1F);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SLLV(instr, result, GPR[rt], GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// SLT - Set on Less Than
|
|
//
|
|
BEGIN_OPF(SLT);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = (bool)((int32)GPR[rs] < (int32)GPR[rt]);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SLT(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SLTI - Set on Less Than Immediate
|
|
//
|
|
BEGIN_OPF(SLTI);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = (bool)((int32)GPR[rs] < (int32)immediate);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SLTI(instr, result, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rt] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SLTIU - Set on Less Than Immediate, Unsigned
|
|
//
|
|
BEGIN_OPF(SLTIU);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = (bool)(GPR[rs] < (uint32)immediate);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SLTIU(instr, result, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rt] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SLTU - Set on Less Than, Unsigned
|
|
//
|
|
BEGIN_OPF(SLTU);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = (bool)(GPR[rs] < GPR[rt]);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SLTU(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SRA - Shift Word Right Arithmetic
|
|
//
|
|
BEGIN_OPF(SRA);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = ((int32)GPR[rt]) >> shamt;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SRA(instr, result, GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SRAV - Shift Word Right Arithmetic Variable
|
|
//
|
|
BEGIN_OPF(SRAV);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = ((int32)GPR[rt]) >> (GPR[rs] & 0x1F);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SRAV(instr, result, GPR[rt], GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SRL - Shift Word Right Logical
|
|
//
|
|
BEGIN_OPF(SRL);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rt] >> shamt;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SRL(instr, result, GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// SRLV - Shift Word Right Logical Variable
|
|
//
|
|
BEGIN_OPF(SRLV);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rt] >> (GPR[rs] & 0x1F);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SRLV(instr, result, GPR[rt], GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SUB - Subtract Word
|
|
//
|
|
BEGIN_OPF(SUB);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] - GPR[rt];
|
|
bool ep = (((GPR[rs] ^ GPR[rt])) & (GPR[rs] ^ result)) & 0x80000000;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SUB(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
if(MDFN_UNLIKELY(ep))
|
|
{
|
|
new_PC = Exception(EXCEPTION_OV, PC, new_PC, instr);
|
|
}
|
|
else
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SUBU - Subtract Unsigned Word
|
|
//
|
|
BEGIN_OPF(SUBU);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] - GPR[rt];
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_SUBU(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// SYSCALL
|
|
//
|
|
BEGIN_OPF(SYSCALL);
|
|
DO_LDS();
|
|
|
|
new_PC = Exception(EXCEPTION_SYSCALL, PC, new_PC, instr);
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// XOR
|
|
//
|
|
BEGIN_OPF(XOR);
|
|
RTYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_RES(rd);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] ^ GPR[rt];
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_XOR(instr, result, GPR[rs], GPR[rt]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rd] = result;
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// XORI - Exclusive OR Immediate
|
|
//
|
|
BEGIN_OPF(XORI);
|
|
ITYPE_ZE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_RES(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 result = GPR[rs] ^ immediate;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_CPU)
|
|
PGXP_CPU_XORI(instr, result, GPR[rs]);
|
|
|
|
DO_LDS();
|
|
|
|
GPR[rt] = result;
|
|
END_OPF;
|
|
|
|
//
|
|
// Memory access instructions(besides the coprocessor ones) follow:
|
|
//
|
|
|
|
//
|
|
// LB - Load Byte
|
|
//
|
|
BEGIN_OPF(LB);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
LDWhich = rt;
|
|
LDValue = (int32)ReadMemory<int8>(timestamp, address);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LB(instr, LDValue, address);
|
|
END_OPF;
|
|
|
|
//
|
|
// LBU - Load Byte Unsigned
|
|
//
|
|
BEGIN_OPF(LBU);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
LDWhich = rt;
|
|
LDValue = ReadMemory<uint8>(timestamp, address);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LBU(instr, LDValue, address);
|
|
END_OPF;
|
|
|
|
//
|
|
// LH - Load Halfword
|
|
//
|
|
BEGIN_OPF(LH);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(address & 1))
|
|
{
|
|
DO_LDS();
|
|
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
LDWhich = rt;
|
|
LDValue = (int32)ReadMemory<int16>(timestamp, address);
|
|
}
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LH(instr, LDValue, address);
|
|
END_OPF;
|
|
|
|
//
|
|
// LHU - Load Halfword Unsigned
|
|
//
|
|
BEGIN_OPF(LHU);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(address & 1))
|
|
{
|
|
DO_LDS();
|
|
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
LDWhich = rt;
|
|
LDValue = ReadMemory<uint16>(timestamp, address);
|
|
}
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LHU(instr, LDValue, address);
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// LW - Load Word
|
|
//
|
|
BEGIN_OPF(LW);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(address & 3))
|
|
{
|
|
DO_LDS();
|
|
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, instr);
|
|
}
|
|
else
|
|
{
|
|
if(MDFN_UNLIKELY(LDWhich == rt))
|
|
LDWhich = 0;
|
|
|
|
DO_LDS();
|
|
|
|
LDWhich = rt;
|
|
LDValue = ReadMemory<uint32>(timestamp, address);
|
|
}
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LW(instr, LDValue, address);
|
|
END_OPF;
|
|
|
|
//
|
|
// SB - Store Byte
|
|
//
|
|
BEGIN_OPF(SB);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
WriteMemory<uint8>(timestamp, address, GPR[rt]);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_SB(instr, GPR[rt], address);
|
|
|
|
DO_LDS();
|
|
END_OPF;
|
|
|
|
//
|
|
// SH - Store Halfword
|
|
//
|
|
BEGIN_OPF(SH);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(address & 0x1))
|
|
{
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADES, PC, new_PC, instr);
|
|
}
|
|
else
|
|
WriteMemory<uint16>(timestamp, address, GPR[rt]);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_SH(instr, GPR[rt], address);
|
|
|
|
DO_LDS();
|
|
END_OPF;
|
|
|
|
//
|
|
// SW - Store Word
|
|
//
|
|
BEGIN_OPF(SW);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
if(MDFN_UNLIKELY(address & 0x3))
|
|
{
|
|
CP0.BADA = address;
|
|
new_PC = Exception(EXCEPTION_ADES, PC, new_PC, instr);
|
|
}
|
|
else
|
|
WriteMemory<uint32>(timestamp, address, GPR[rt]);
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_SW(instr, GPR[rt], address);
|
|
|
|
DO_LDS();
|
|
END_OPF;
|
|
|
|
// LWL and LWR load delay slot tomfoolery appears to apply even to MFC0! (and probably MFCn and CFCn as well, though they weren't explicitly tested)
|
|
|
|
//
|
|
// LWL - Load Word Left
|
|
//
|
|
BEGIN_OPF(LWL);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
//GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
uint32 v = GPR[rt];
|
|
|
|
if(LDWhich == rt)
|
|
{
|
|
v = LDValue;
|
|
ReadFudge = 0;
|
|
}
|
|
else
|
|
{
|
|
DO_LDS();
|
|
}
|
|
|
|
LDWhich = rt;
|
|
switch(address & 0x3)
|
|
{
|
|
case 0: LDValue = (v & ~(0xFF << 24)) | (ReadMemory<uint8>(timestamp, address & ~3) << 24);
|
|
break;
|
|
|
|
case 1: LDValue = (v & ~(0xFFFF << 16)) | (ReadMemory<uint16>(timestamp, address & ~3) << 16);
|
|
break;
|
|
|
|
case 2: LDValue = (v & ~(0xFFFFFF << 8)) | (ReadMemory<uint32>(timestamp, address & ~3, true) << 8);
|
|
break;
|
|
|
|
case 3: LDValue = (v & ~(0xFFFFFFFF << 0)) | (ReadMemory<uint32>(timestamp, address & ~3) << 0);
|
|
break;
|
|
}
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LWL(instr, LDValue, address);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// SWL - Store Word Left
|
|
//
|
|
BEGIN_OPF(SWL);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
switch(address & 0x3)
|
|
{
|
|
case 0: WriteMemory<uint8>(timestamp, address & ~3, GPR[rt] >> 24);
|
|
break;
|
|
|
|
case 1: WriteMemory<uint16>(timestamp, address & ~3, GPR[rt] >> 16);
|
|
break;
|
|
|
|
case 2: WriteMemory<uint32>(timestamp, address & ~3, GPR[rt] >> 8, true);
|
|
break;
|
|
|
|
case 3: WriteMemory<uint32>(timestamp, address & ~3, GPR[rt] >> 0);
|
|
break;
|
|
}
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_SWL(instr, GPR[rt], address);
|
|
|
|
DO_LDS();
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// LWR - Load Word Right
|
|
//
|
|
BEGIN_OPF(LWR);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
//GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
uint32 v = GPR[rt];
|
|
|
|
if(LDWhich == rt)
|
|
{
|
|
v = LDValue;
|
|
ReadFudge = 0;
|
|
}
|
|
else
|
|
{
|
|
DO_LDS();
|
|
}
|
|
|
|
LDWhich = rt;
|
|
switch(address & 0x3)
|
|
{
|
|
case 0: LDValue = (v & ~(0xFFFFFFFF)) | ReadMemory<uint32>(timestamp, address);
|
|
break;
|
|
|
|
case 1: LDValue = (v & ~(0xFFFFFF)) | ReadMemory<uint32>(timestamp, address, true);
|
|
break;
|
|
|
|
case 2: LDValue = (v & ~(0xFFFF)) | ReadMemory<uint16>(timestamp, address);
|
|
break;
|
|
|
|
case 3: LDValue = (v & ~(0xFF)) | ReadMemory<uint8>(timestamp, address);
|
|
break;
|
|
}
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LWR(instr, LDValue, address);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// SWR - Store Word Right
|
|
//
|
|
BEGIN_OPF(SWR);
|
|
ITYPE;
|
|
|
|
GPR_DEPRES_BEGIN
|
|
GPR_DEP(rs);
|
|
GPR_DEP(rt);
|
|
GPR_DEPRES_END
|
|
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
switch(address & 0x3)
|
|
{
|
|
case 0: WriteMemory<uint32>(timestamp, address, GPR[rt]);
|
|
break;
|
|
|
|
case 1: WriteMemory<uint32>(timestamp, address, GPR[rt], true);
|
|
break;
|
|
|
|
case 2: WriteMemory<uint16>(timestamp, address, GPR[rt]);
|
|
break;
|
|
|
|
case 3: WriteMemory<uint8>(timestamp, address, GPR[rt]);
|
|
break;
|
|
}
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_SWR(instr, GPR[rt], address);
|
|
|
|
|
|
DO_LDS();
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// Mednafen special instruction
|
|
//
|
|
BEGIN_OPF(INTERRUPT);
|
|
if(Halted)
|
|
{
|
|
goto SkipNPCStuff;
|
|
}
|
|
else
|
|
{
|
|
DO_LDS();
|
|
|
|
new_PC = Exception(EXCEPTION_INT, PC, new_PC, instr);
|
|
}
|
|
END_OPF;
|
|
}
|
|
|
|
OpDone: ;
|
|
PC = new_PC;
|
|
new_PC = new_PC + 4;
|
|
BDBT = 0;
|
|
|
|
SkipNPCStuff: ;
|
|
|
|
//printf("\n");
|
|
}
|
|
#if defined(HAVE_LIGHTREC) && defined(LIGHTREC_DEBUG)
|
|
if (timestamp >= 0 && PC != oldpc)
|
|
print_for_big_ass_debugger(timestamp, PC);
|
|
#endif
|
|
} while(MDFN_LIKELY(PSX_EventHandler(timestamp)));
|
|
|
|
if(gte_ts_done > 0)
|
|
gte_ts_done -= timestamp;
|
|
|
|
if(muldiv_ts_done > 0)
|
|
muldiv_ts_done -= timestamp;
|
|
|
|
ACTIVE_TO_BACKING;
|
|
|
|
return(timestamp);
|
|
}
|
|
|
|
pscpu_timestamp_t PS_CPU::Run(pscpu_timestamp_t timestamp_in, bool BIOSPrintMode, bool ILHMode)
|
|
{
|
|
#ifdef HAVE_LIGHTREC
|
|
//track options changing
|
|
if(MDFN_UNLIKELY(psx_dynarec != prev_dynarec || pgxpMode != PGXP_GetModes()) ||
|
|
prev_invalidate != psx_dynarec_invalidate)
|
|
{
|
|
//init lightrec when changing dynarec, invalidate, or PGXP option, cleans entire state if already running
|
|
if(psx_dynarec != DYNAREC_DISABLED)
|
|
{
|
|
lightrec_plugin_init();
|
|
}
|
|
prev_dynarec = psx_dynarec;
|
|
pgxpMode = PGXP_GetModes();
|
|
prev_invalidate = psx_dynarec_invalidate;
|
|
}
|
|
|
|
if(psx_dynarec != DYNAREC_DISABLED)
|
|
return(lightrec_plugin_execute(timestamp_in));
|
|
#endif
|
|
if(CPUHook || ADDBT)
|
|
return(RunReal<true, true, false>(timestamp_in));
|
|
#ifdef DEBUG
|
|
if(ILHMode)
|
|
return(RunReal<false, false, true>(timestamp_in));
|
|
if(BIOSPrintMode)
|
|
return(RunReal<false, true, false>(timestamp_in));
|
|
#endif
|
|
return(RunReal<false, false, false>(timestamp_in));
|
|
}
|
|
|
|
void PS_CPU::SetCPUHook(void (*cpuh)(const pscpu_timestamp_t timestamp, uint32 pc), void (*addbt)(uint32 from, uint32 to, bool exception))
|
|
{
|
|
ADDBT = addbt;
|
|
CPUHook = cpuh;
|
|
}
|
|
|
|
uint32 PS_CPU::GetRegister(unsigned int which, char *special, const uint32 special_len)
|
|
{
|
|
uint32 ret = 0;
|
|
|
|
if(which >= GSREG_GPR && which < (GSREG_GPR + 32))
|
|
ret = GPR[which];
|
|
else switch(which)
|
|
{
|
|
case GSREG_PC:
|
|
ret = BACKED_PC;
|
|
break;
|
|
|
|
case GSREG_PC_NEXT:
|
|
ret = BACKED_new_PC;
|
|
break;
|
|
|
|
case GSREG_IN_BD_SLOT:
|
|
ret = BDBT;
|
|
break;
|
|
|
|
case GSREG_LO:
|
|
ret = LO;
|
|
break;
|
|
|
|
case GSREG_HI:
|
|
ret = HI;
|
|
break;
|
|
|
|
case GSREG_BPC:
|
|
ret = CP0.BPC;
|
|
break;
|
|
|
|
case GSREG_BDA:
|
|
ret = CP0.BDA;
|
|
break;
|
|
|
|
case GSREG_TAR:
|
|
ret = CP0.TAR;
|
|
break;
|
|
|
|
case GSREG_DCIC:
|
|
ret = CP0.DCIC;
|
|
break;
|
|
|
|
case GSREG_BADA:
|
|
ret = CP0.BADA;
|
|
break;
|
|
|
|
case GSREG_BDAM:
|
|
ret = CP0.BDAM;
|
|
break;
|
|
|
|
case GSREG_BPCM:
|
|
ret = CP0.BPCM;
|
|
break;
|
|
|
|
case GSREG_SR:
|
|
ret = CP0.SR;
|
|
break;
|
|
|
|
case GSREG_CAUSE:
|
|
ret = CP0.CAUSE;
|
|
#ifdef NOT_LIBRETRO
|
|
if(special)
|
|
{
|
|
trio_snprintf(special, special_len, "BD: %u, BT: %u, CE: %u, IP: 0x%02x, Sw: %u, ExcCode: 0x%01x",
|
|
(ret >> 31) & 1, (ret >> 30) & 1, (ret >> 28) & 3, (ret >> 10) & 0x3F, (ret >> 8) & 3, (ret >> 2) & 0xF);
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case GSREG_EPC:
|
|
ret = CP0.EPC;
|
|
break;
|
|
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
void PS_CPU::SetRegister(unsigned int which, uint32 value)
|
|
{
|
|
if(which >= GSREG_GPR && which < (GSREG_GPR + 32))
|
|
{
|
|
if(which != (GSREG_GPR + 0))
|
|
GPR[which] = value;
|
|
}
|
|
else switch(which)
|
|
{
|
|
case GSREG_PC:
|
|
BACKED_PC = value;
|
|
break;
|
|
|
|
case GSREG_PC_NEXT:
|
|
BACKED_new_PC = value;
|
|
break;
|
|
|
|
case GSREG_IN_BD_SLOT:
|
|
BDBT = value & 0x3;
|
|
break;
|
|
|
|
case GSREG_LO:
|
|
LO = value;
|
|
break;
|
|
|
|
case GSREG_HI:
|
|
HI = value;
|
|
break;
|
|
|
|
case GSREG_SR:
|
|
CP0.SR = value; // TODO: mask
|
|
break;
|
|
|
|
case GSREG_CAUSE:
|
|
CP0.CAUSE = value;
|
|
break;
|
|
|
|
case GSREG_EPC:
|
|
CP0.EPC = value & ~0x3U;
|
|
break;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
bool PS_CPU::PeekCheckICache(uint32 PC, uint32 *iw)
|
|
{
|
|
if(ICache[(PC & 0xFFC) >> 2].TV == PC)
|
|
{
|
|
*iw = ICache[(PC & 0xFFC) >> 2].Data;
|
|
return(true);
|
|
}
|
|
|
|
return(false);
|
|
}
|
|
|
|
|
|
uint8 PS_CPU::PeekMem8(uint32 A)
|
|
{
|
|
return PeekMemory<uint8>(A);
|
|
}
|
|
|
|
uint16 PS_CPU::PeekMem16(uint32 A)
|
|
{
|
|
return PeekMemory<uint16>(A);
|
|
}
|
|
|
|
uint32 PS_CPU::PeekMem32(uint32 A)
|
|
{
|
|
return PeekMemory<uint32>(A);
|
|
}
|
|
|
|
void PS_CPU::PokeMem8(uint32 A, uint8 V)
|
|
{
|
|
PokeMemory<uint8>(A, V);
|
|
}
|
|
|
|
void PS_CPU::PokeMem16(uint32 A, uint16 V)
|
|
{
|
|
PokeMemory<uint16>(A, V);
|
|
}
|
|
|
|
void PS_CPU::PokeMem32(uint32 A, uint32 V)
|
|
{
|
|
PokeMemory<uint32>(A, V);
|
|
}
|
|
|
|
#undef BEGIN_OPF
|
|
#undef END_OPF
|
|
#undef MK_OPF
|
|
|
|
#define MK_OPF(op, funct) ((op) ? (0x40 | (op)) : (funct))
|
|
#define BEGIN_OPF(op, funct) case MK_OPF(op, funct): {
|
|
#define END_OPF } break;
|
|
|
|
// FIXME: should we breakpoint on an illegal address? And with LWC2/SWC2 if CP2 isn't enabled?
|
|
void PS_CPU::CheckBreakpoints(void (*callback)(bool write, uint32 address, unsigned int len), uint32 instr)
|
|
{
|
|
uint32 opf;
|
|
|
|
opf = instr & 0x3F;
|
|
|
|
if(instr & (0x3F << 26))
|
|
opf = 0x40 | (instr >> 26);
|
|
|
|
|
|
switch(opf)
|
|
{
|
|
default:
|
|
break;
|
|
|
|
//
|
|
// LB - Load Byte
|
|
//
|
|
BEGIN_OPF(0x20, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(false, address, 1);
|
|
END_OPF;
|
|
|
|
//
|
|
// LBU - Load Byte Unsigned
|
|
//
|
|
BEGIN_OPF(0x24, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(false, address, 1);
|
|
END_OPF;
|
|
|
|
//
|
|
// LH - Load Halfword
|
|
//
|
|
BEGIN_OPF(0x21, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(false, address, 2);
|
|
END_OPF;
|
|
|
|
//
|
|
// LHU - Load Halfword Unsigned
|
|
//
|
|
BEGIN_OPF(0x25, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(false, address, 2);
|
|
END_OPF;
|
|
|
|
|
|
//
|
|
// LW - Load Word
|
|
//
|
|
BEGIN_OPF(0x23, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(false, address, 4);
|
|
END_OPF;
|
|
|
|
//
|
|
// SB - Store Byte
|
|
//
|
|
BEGIN_OPF(0x28, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(true, address, 1);
|
|
END_OPF;
|
|
|
|
//
|
|
// SH - Store Halfword
|
|
//
|
|
BEGIN_OPF(0x29, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(true, address, 2);
|
|
END_OPF;
|
|
|
|
//
|
|
// SW - Store Word
|
|
//
|
|
BEGIN_OPF(0x2B, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(true, address, 4);
|
|
END_OPF;
|
|
|
|
//
|
|
// LWL - Load Word Left
|
|
//
|
|
BEGIN_OPF(0x22, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
do
|
|
{
|
|
callback(false, address, 1);
|
|
} while((address--) & 0x3);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// SWL - Store Word Left
|
|
//
|
|
BEGIN_OPF(0x2A, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
do
|
|
{
|
|
callback(true, address, 1);
|
|
} while((address--) & 0x3);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// LWR - Load Word Right
|
|
//
|
|
BEGIN_OPF(0x26, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
do
|
|
{
|
|
callback(false, address, 1);
|
|
} while((++address) & 0x3);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// SWR - Store Word Right
|
|
//
|
|
BEGIN_OPF(0x2E, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
do
|
|
{
|
|
callback(true, address, 1);
|
|
} while((++address) & 0x3);
|
|
|
|
END_OPF;
|
|
|
|
//
|
|
// LWC2
|
|
//
|
|
BEGIN_OPF(0x32, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(false, address, 4);
|
|
END_OPF;
|
|
|
|
//
|
|
// SWC2
|
|
//
|
|
BEGIN_OPF(0x3A, 0);
|
|
ITYPE;
|
|
uint32 address = GPR[rs] + immediate;
|
|
|
|
callback(true, address, 4);
|
|
END_OPF;
|
|
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
#define ARRAY_SIZE(x) (sizeof(x) ? sizeof(x) / sizeof((x)[0]) : 0)
|
|
|
|
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
# define LE32TOH(x) __builtin_bswap32(x)
|
|
# define HTOLE32(x) __builtin_bswap32(x)
|
|
# define LE16TOH(x) __builtin_bswap16(x)
|
|
# define HTOLE16(x) __builtin_bswap16(x)
|
|
#else
|
|
# define LE32TOH(x) (x)
|
|
# define HTOLE32(x) (x)
|
|
# define LE16TOH(x) (x)
|
|
# define HTOLE16(x) (x)
|
|
#endif
|
|
|
|
static inline u32 kunseg(u32 addr)
|
|
{
|
|
if (MDFN_UNLIKELY(addr >= 0xa0000000))
|
|
return addr - 0xa0000000;
|
|
else
|
|
return addr &~ 0x80000000;
|
|
}
|
|
|
|
enum opcodes {
|
|
OP_SPECIAL = 0x00,
|
|
OP_REGIMM = 0x01,
|
|
OP_J = 0x02,
|
|
OP_JAL = 0x03,
|
|
OP_BEQ = 0x04,
|
|
OP_BNE = 0x05,
|
|
OP_BLEZ = 0x06,
|
|
OP_BGTZ = 0x07,
|
|
OP_ADDI = 0x08,
|
|
OP_ADDIU = 0x09,
|
|
OP_SLTI = 0x0a,
|
|
OP_SLTIU = 0x0b,
|
|
OP_ANDI = 0x0c,
|
|
OP_ORI = 0x0d,
|
|
OP_XORI = 0x0e,
|
|
OP_LUI = 0x0f,
|
|
OP_CP0 = 0x10,
|
|
OP_CP2 = 0x12,
|
|
OP_LB = 0x20,
|
|
OP_LH = 0x21,
|
|
OP_LWL = 0x22,
|
|
OP_LW = 0x23,
|
|
OP_LBU = 0x24,
|
|
OP_LHU = 0x25,
|
|
OP_LWR = 0x26,
|
|
OP_SB = 0x28,
|
|
OP_SH = 0x29,
|
|
OP_SWL = 0x2a,
|
|
OP_SW = 0x2b,
|
|
OP_SWR = 0x2e,
|
|
OP_LWC2 = 0x32,
|
|
OP_SWC2 = 0x3a,
|
|
};
|
|
|
|
static char *name = (char*) "beetle_psx_libretro";
|
|
|
|
#ifdef LIGHTREC_DEBUG
|
|
u32 lightrec_begin_cycles = 0;
|
|
|
|
u32 hash_calculate(const void *buffer, u32 count)
|
|
{
|
|
unsigned int i;
|
|
u32 *data = (u32 *) buffer;
|
|
u32 hash = 0xffffffff;
|
|
|
|
count /= 4;
|
|
for(i = 0; i < count; ++i) {
|
|
hash += data[i];
|
|
hash += (hash << 10);
|
|
hash ^= (hash >> 6);
|
|
}
|
|
|
|
hash += (hash << 3);
|
|
hash ^= (hash >> 11);
|
|
hash += (hash << 15);
|
|
return hash;
|
|
}
|
|
|
|
void PS_CPU::print_for_big_ass_debugger(int32_t timestamp, uint32_t PC)
|
|
{
|
|
uint8_t *psxM = (uint8_t *) MainRAM->data8;
|
|
uint8_t *psxR = (uint8_t *) BIOSROM->data8;
|
|
uint8_t *psxH = (uint8_t *) ScratchRAM->data8;
|
|
|
|
unsigned int i;
|
|
|
|
printf("CYCLE 0x%08x PC 0x%08x", timestamp, PC);
|
|
|
|
#ifdef LIGHTREC_VERY_DEBUG
|
|
printf(" RAM 0x%08x SCRATCH 0x%08x",
|
|
hash_calculate(psxM, 0x200000),
|
|
hash_calculate(psxH, 0x400));
|
|
#endif
|
|
|
|
printf(" CP0 0x%08x",
|
|
hash_calculate(&CP0.Regs,
|
|
sizeof(CP0.Regs)));
|
|
|
|
#ifdef LIGHTREC_VERY_DEBUG
|
|
for (i = 0; i < 32; i++)
|
|
printf(" GPR[%i] 0x%08x", i, GPR[i]);
|
|
printf(" LO 0x%08x", LO);
|
|
printf(" HI 0x%08x", HI);
|
|
#else
|
|
printf(" GPR 0x%08x", hash_calculate(&GPR, 32*sizeof(uint32_t)));
|
|
#endif
|
|
printf("\n");
|
|
}
|
|
#endif /* LIGHTREC_DEBUG */
|
|
|
|
u32 PS_CPU::cop_mfc(struct lightrec_state *state, u32 op, u8 reg)
|
|
{
|
|
return CP0.Regs[reg];
|
|
}
|
|
|
|
u32 PS_CPU::cop_cfc(struct lightrec_state *state, u32 op, u8 reg)
|
|
{
|
|
return CP0.Regs[reg];
|
|
}
|
|
|
|
u32 PS_CPU::cop2_mfc(struct lightrec_state *state, u32 op, u8 reg)
|
|
{
|
|
return GTE_ReadDR(reg);
|
|
}
|
|
|
|
u32 PS_CPU::pgxp_cop2_mfc(struct lightrec_state *state, u32 op, u8 reg)
|
|
{
|
|
u32 r = GTE_ReadDR(reg);
|
|
|
|
if((op >> 26) == OP_CP2)
|
|
PGXP_GTE_MFC2(op, r, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
u32 PS_CPU::cop2_cfc(struct lightrec_state *state, u32 op, u8 reg)
|
|
{
|
|
return GTE_ReadCR(reg);
|
|
}
|
|
|
|
u32 PS_CPU::pgxp_cop2_cfc(struct lightrec_state *state, u32 op, u8 reg)
|
|
{
|
|
u32 r = GTE_ReadCR(reg);
|
|
|
|
PGXP_GTE_CFC2(op, r, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
void PS_CPU::cop_mtc_ctc(struct lightrec_state *state,
|
|
u8 reg, u32 value)
|
|
{
|
|
switch (reg) {
|
|
case 1:
|
|
case 4:
|
|
case 8:
|
|
case 14:
|
|
case 15:
|
|
/* Those registers are read-only */
|
|
break;
|
|
case 12: /* Status */
|
|
if ((CP0.SR & ~value) & (1 << 16)) {
|
|
memcpy(MainRAM->data8, cache_buf, sizeof(cache_buf));
|
|
lightrec_invalidate_all(state);
|
|
} else if ((~CP0.SR & value) & (1 << 16)) {
|
|
memcpy(cache_buf, MainRAM->data8, sizeof(cache_buf));
|
|
}
|
|
|
|
CP0.SR = value & ~( (0x3 << 26) | (0x3 << 23) | (0x3 << 6));
|
|
RecalcIPCache();
|
|
lightrec_set_exit_flags(state,
|
|
LIGHTREC_EXIT_CHECK_INTERRUPT);
|
|
break;
|
|
case 13: /* Cause */
|
|
CP0.CAUSE &= ~0x0300;
|
|
CP0.CAUSE |= value & 0x0300;
|
|
RecalcIPCache();
|
|
lightrec_set_exit_flags(state,
|
|
LIGHTREC_EXIT_CHECK_INTERRUPT);
|
|
break;
|
|
default:
|
|
CP0.Regs[reg] = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PS_CPU::cop_mtc(struct lightrec_state *state, u32 op, u8 reg, u32 value)
|
|
{
|
|
cop_mtc_ctc(state, reg, value);
|
|
}
|
|
|
|
void PS_CPU::cop_ctc(struct lightrec_state *state, u32 op, u8 reg, u32 value)
|
|
{
|
|
cop_mtc_ctc(state, reg, value);
|
|
}
|
|
|
|
void PS_CPU::cop2_mtc(struct lightrec_state *state, u32 op, u8 reg, u32 value)
|
|
{
|
|
GTE_WriteDR(reg, value);
|
|
}
|
|
|
|
void PS_CPU::pgxp_cop2_mtc(struct lightrec_state *state, u32 op, u8 reg, u32 value)
|
|
{
|
|
GTE_WriteDR(reg, value);
|
|
if((op >> 26) == OP_CP2)
|
|
PGXP_GTE_MTC2(op, value, value);
|
|
}
|
|
|
|
void PS_CPU::cop2_ctc(struct lightrec_state *state, u32 op, u8 reg, u32 value)
|
|
{
|
|
GTE_WriteCR(reg, value);
|
|
}
|
|
|
|
void PS_CPU::pgxp_cop2_ctc(struct lightrec_state *state, u32 op, u8 reg, u32 value)
|
|
{
|
|
GTE_WriteCR(reg, value);
|
|
PGXP_GTE_CTC2(op, value, value);
|
|
}
|
|
|
|
static bool cp2_ops[0x40] = {0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,
|
|
1,1,1,1,1,0,1,0,0,0,0,1,1,0,1,0,
|
|
1,0,0,0,0,0,0,0,1,1,1,0,0,1,1,0,
|
|
1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1};
|
|
|
|
static void cop_op(struct lightrec_state *state, u32 func)
|
|
{
|
|
MDFND_DispMessage(3, RETRO_LOG_WARN,
|
|
RETRO_MESSAGE_TARGET_LOG, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"Access to invalid co-processor 0");
|
|
}
|
|
|
|
static void cop2_op(struct lightrec_state *state, u32 func)
|
|
{
|
|
if (MDFN_UNLIKELY(!cp2_ops[func & 0x3f]))
|
|
{
|
|
MDFN_DispMessage(3, RETRO_LOG_WARN,
|
|
RETRO_MESSAGE_TARGET_LOG, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"Invalid CP2 function %u\n", func);
|
|
}
|
|
else
|
|
GTE_Instruction(func);
|
|
}
|
|
|
|
void PS_CPU::reset_target_cycle_count(struct lightrec_state *state, pscpu_timestamp_t timestamp){
|
|
if (timestamp >= next_event_ts)
|
|
lightrec_set_exit_flags(state, LIGHTREC_EXIT_CHECK_INTERRUPT);
|
|
}
|
|
|
|
void PS_CPU::hw_write_byte(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u8 val)
|
|
{
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
PSX_MemWrite8(timestamp, mem, val);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
}
|
|
|
|
void PS_CPU::pgxp_nonhw_write_byte(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u8 val)
|
|
{
|
|
*(u8 *)host = val;
|
|
PGXP_CPU_SB(opcode, val, mem);
|
|
|
|
if (!psx_dynarec_invalidate)
|
|
lightrec_invalidate(state, mem, 1);
|
|
}
|
|
|
|
void PS_CPU::pgxp_hw_write_byte(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u8 val)
|
|
{
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
u32 kmem = kunseg(mem);
|
|
|
|
PSX_MemWrite8(timestamp, kmem, val);
|
|
|
|
PGXP_CPU_SB(opcode, val, mem);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
}
|
|
|
|
void PS_CPU::hw_write_half(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u16 val)
|
|
{
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
PSX_MemWrite16(timestamp, mem, val);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
}
|
|
|
|
void PS_CPU::pgxp_nonhw_write_half(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u16 val)
|
|
{
|
|
*(u16 *)host = HTOLE16(val);
|
|
PGXP_CPU_SH(opcode, val, mem);
|
|
|
|
if (!psx_dynarec_invalidate)
|
|
lightrec_invalidate(state, mem, 2);
|
|
}
|
|
|
|
void PS_CPU::pgxp_hw_write_half(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u16 val)
|
|
{
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
u32 kmem = kunseg(mem);
|
|
|
|
PSX_MemWrite16(timestamp, kmem, val);
|
|
|
|
PGXP_CPU_SH(opcode, val, mem);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
}
|
|
|
|
void PS_CPU::hw_write_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u32 val)
|
|
{
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
PSX_MemWrite32(timestamp, mem, val);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
}
|
|
|
|
void PS_CPU::pgxp_nonhw_write_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u32 val)
|
|
{
|
|
*(u32 *)host = HTOLE32(val);
|
|
|
|
switch (opcode >> 26){
|
|
case 0x2A:
|
|
PGXP_CPU_SWL(opcode, val, mem + (opcode & 0x3));
|
|
break;
|
|
case 0x2B:
|
|
PGXP_CPU_SW(opcode, val, mem);
|
|
break;
|
|
case 0x2E:
|
|
PGXP_CPU_SWR(opcode, val, mem + (opcode & 0x3));
|
|
break;
|
|
case 0x3A:
|
|
PGXP_GTE_SWC2(opcode, val, mem);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!psx_dynarec_invalidate)
|
|
lightrec_invalidate(state, mem, 4);
|
|
}
|
|
|
|
void PS_CPU::pgxp_hw_write_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u32 val)
|
|
{
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
u32 kmem = kunseg(mem);
|
|
|
|
PSX_MemWrite32(timestamp, kmem, val);
|
|
|
|
switch (opcode >> 26){
|
|
case OP_SWL:
|
|
PGXP_CPU_SWL(opcode, val, mem + (opcode & 0x3));
|
|
break;
|
|
case OP_SW:
|
|
PGXP_CPU_SW(opcode, val, mem);
|
|
break;
|
|
case OP_SWR:
|
|
PGXP_CPU_SWR(opcode, val, mem + (opcode & 0x3));
|
|
break;
|
|
case OP_SWC2:
|
|
PGXP_GTE_SWC2(opcode, val, mem);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
}
|
|
|
|
u8 PS_CPU::hw_read_byte(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u8 val;
|
|
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
val = PSX_MemRead8(timestamp, mem);
|
|
|
|
/* Calling PSX_MemRead* might update timestamp - Make sure
|
|
* here that state->current_cycle stays in sync. */
|
|
lightrec_reset_cycle_count(lightrec_state, timestamp);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
|
|
return val;
|
|
}
|
|
|
|
u8 PS_CPU::pgxp_nonhw_read_byte(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u8 val = *(u8 *)host;
|
|
|
|
if((opcode >> 26) == OP_LB)
|
|
PGXP_CPU_LB(opcode, val, mem);
|
|
else
|
|
PGXP_CPU_LBU(opcode, val, mem);
|
|
|
|
return val;
|
|
}
|
|
|
|
u8 PS_CPU::pgxp_hw_read_byte(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u8 val;
|
|
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
u32 kmem = kunseg(mem);
|
|
|
|
val = PSX_MemRead8(timestamp, kmem);
|
|
|
|
if((opcode >> 26) == OP_LB)
|
|
PGXP_CPU_LB(opcode, val, mem);
|
|
else
|
|
PGXP_CPU_LBU(opcode, val, mem);
|
|
|
|
/* Calling PSX_MemRead* might update timestamp - Make sure
|
|
* here that state->current_cycle stays in sync. */
|
|
lightrec_reset_cycle_count(lightrec_state, timestamp);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
|
|
return val;
|
|
}
|
|
|
|
u16 PS_CPU::hw_read_half(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u16 val;
|
|
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
val = PSX_MemRead16(timestamp, mem);
|
|
|
|
/* Calling PSX_MemRead* might update timestamp - Make sure
|
|
* here that state->current_cycle stays in sync. */
|
|
lightrec_reset_cycle_count(lightrec_state, timestamp);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
|
|
return val;
|
|
}
|
|
|
|
u16 PS_CPU::pgxp_nonhw_read_half(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u16 val = LE16TOH(*(u16 *)host);
|
|
|
|
if((opcode >> 26) == OP_LH)
|
|
PGXP_CPU_LH(opcode, val, mem);
|
|
else
|
|
PGXP_CPU_LHU(opcode, val, mem);
|
|
|
|
return val;
|
|
}
|
|
|
|
u16 PS_CPU::pgxp_hw_read_half(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u16 val;
|
|
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
u32 kmem = kunseg(mem);
|
|
|
|
val = PSX_MemRead16(timestamp, kmem);
|
|
|
|
if((opcode >> 26) == OP_LH)
|
|
PGXP_CPU_LH(opcode, val, mem);
|
|
else
|
|
PGXP_CPU_LHU(opcode, val, mem);
|
|
|
|
/* Calling PSX_MemRead* might update timestamp - Make sure
|
|
* here that state->current_cycle stays in sync. */
|
|
lightrec_reset_cycle_count(lightrec_state, timestamp);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
|
|
return val;
|
|
}
|
|
|
|
u32 PS_CPU::hw_read_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u32 val;
|
|
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
val = PSX_MemRead32(timestamp, mem);
|
|
|
|
/* Calling PSX_MemRead* might update timestamp - Make sure
|
|
* here that state->current_cycle stays in sync. */
|
|
lightrec_reset_cycle_count(lightrec_state, timestamp);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
|
|
return val;
|
|
}
|
|
|
|
u32 PS_CPU::pgxp_nonhw_read_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u32 val = LE32TOH(*(u32 *)host);
|
|
|
|
switch (opcode >> 26){
|
|
case OP_LWL:
|
|
//TODO: OR with masked register
|
|
PGXP_CPU_LWL(opcode, val << (24-(opcode & 0x3)*8), mem + (opcode & 0x3));
|
|
break;
|
|
case OP_LW:
|
|
PGXP_CPU_LW(opcode, val, mem);
|
|
break;
|
|
case OP_LWR:
|
|
//TODO: OR with masked register
|
|
PGXP_CPU_LWR(opcode, val >> ((opcode & 0x3)*8), mem + (opcode & 0x3));
|
|
break;
|
|
case OP_LWC2:
|
|
PGXP_GTE_LWC2(opcode, val, mem);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
u32 PS_CPU::pgxp_hw_read_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
u32 val;
|
|
|
|
pscpu_timestamp_t timestamp = lightrec_current_cycle_count(state);
|
|
|
|
u32 kmem = kunseg(mem);
|
|
|
|
val = PSX_MemRead32(timestamp, kmem);
|
|
|
|
switch (opcode >> 26){
|
|
case OP_LWL:
|
|
//TODO: OR with masked register
|
|
PGXP_CPU_LWL(opcode, val << (24-(opcode & 0x3)*8), mem + (opcode & 0x3));
|
|
break;
|
|
case OP_LW:
|
|
PGXP_CPU_LW(opcode, val, mem);
|
|
break;
|
|
case OP_LWR:
|
|
//TODO: OR with masked register
|
|
PGXP_CPU_LWR(opcode, val >> ((opcode & 0x3)*8), mem + (opcode & 0x3));
|
|
break;
|
|
case OP_LWC2:
|
|
PGXP_GTE_LWC2(opcode, val, mem);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Calling PSX_MemRead* might update timestamp - Make sure
|
|
* here that state->current_cycle stays in sync. */
|
|
lightrec_reset_cycle_count(lightrec_state, timestamp);
|
|
|
|
reset_target_cycle_count(state, timestamp);
|
|
|
|
return val;
|
|
}
|
|
|
|
struct lightrec_mem_map_ops PS_CPU::pgxp_nonhw_regs_ops = {
|
|
.sb = pgxp_nonhw_write_byte,
|
|
.sh = pgxp_nonhw_write_half,
|
|
.sw = pgxp_nonhw_write_word,
|
|
.lb = pgxp_nonhw_read_byte,
|
|
.lh = pgxp_nonhw_read_half,
|
|
.lw = pgxp_nonhw_read_word,
|
|
};
|
|
|
|
struct lightrec_mem_map_ops PS_CPU::pgxp_hw_regs_ops = {
|
|
.sb = pgxp_hw_write_byte,
|
|
.sh = pgxp_hw_write_half,
|
|
.sw = pgxp_hw_write_word,
|
|
.lb = pgxp_hw_read_byte,
|
|
.lh = pgxp_hw_read_half,
|
|
.lw = pgxp_hw_read_word,
|
|
};
|
|
|
|
struct lightrec_mem_map_ops PS_CPU::hw_regs_ops = {
|
|
.sb = hw_write_byte,
|
|
.sh = hw_write_half,
|
|
.sw = hw_write_word,
|
|
.lb = hw_read_byte,
|
|
.lh = hw_read_half,
|
|
.lw = hw_read_word,
|
|
};
|
|
|
|
u32 PS_CPU::cache_ctrl_read_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem)
|
|
{
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_LW(opcode, BIU, mem);
|
|
|
|
return BIU;
|
|
}
|
|
|
|
void PS_CPU::cache_ctrl_write_word(struct lightrec_state *state,
|
|
u32 opcode, void *host, u32 mem, u32 val)
|
|
{
|
|
BIU = val;
|
|
|
|
if (PGXP_GetModes() & PGXP_MODE_MEMORY)
|
|
PGXP_CPU_SW(opcode, BIU, mem);
|
|
}
|
|
|
|
struct lightrec_mem_map_ops PS_CPU::cache_ctrl_ops = {
|
|
.sb = NULL,
|
|
.sh = NULL,
|
|
.sw = cache_ctrl_write_word,
|
|
.lb = NULL,
|
|
.lh = NULL,
|
|
.lw = cache_ctrl_read_word,
|
|
};
|
|
|
|
struct lightrec_mem_map PS_CPU::lightrec_map[] = {
|
|
[PSX_MAP_KERNEL_USER_RAM] = {
|
|
/* Kernel and user memory */
|
|
.pc = 0x00000000,
|
|
.length = 0x200000,
|
|
},
|
|
[PSX_MAP_BIOS] = {
|
|
/* BIOS */
|
|
.pc = 0x1fc00000,
|
|
.length = 0x80000,
|
|
},
|
|
[PSX_MAP_SCRATCH_PAD] = {
|
|
/* Scratch pad */
|
|
.pc = 0x1f800000,
|
|
.length = 0x400,
|
|
},
|
|
[PSX_MAP_PARALLEL_PORT] = {
|
|
/* Parallel port */
|
|
.pc = 0x1f000000,
|
|
.length = 0x800000,
|
|
},
|
|
[PSX_MAP_HW_REGISTERS] = {
|
|
/* Hardware registers */
|
|
.pc = 0x1f801000,
|
|
.length = 0x2000,
|
|
},
|
|
[PSX_MAP_CACHE_CONTROL] = {
|
|
/* Cache control */
|
|
.pc = 0x5ffe0130,
|
|
.length = 4,
|
|
.address = NULL,
|
|
.ops = &cache_ctrl_ops,
|
|
},
|
|
|
|
/* Mirrors of the kernel/user memory */
|
|
{
|
|
.pc = 0x00200000,
|
|
.length = 0x200000,
|
|
.address = NULL,
|
|
.ops = NULL,
|
|
.mirror_of = &lightrec_map[PSX_MAP_KERNEL_USER_RAM],
|
|
},
|
|
{
|
|
.pc = 0x00400000,
|
|
.length = 0x200000,
|
|
.address = NULL,
|
|
.ops = NULL,
|
|
.mirror_of = &lightrec_map[PSX_MAP_KERNEL_USER_RAM],
|
|
},
|
|
{
|
|
.pc = 0x00600000,
|
|
.length = 0x200000,
|
|
.address = NULL,
|
|
.ops = NULL,
|
|
.mirror_of = &lightrec_map[PSX_MAP_KERNEL_USER_RAM],
|
|
},
|
|
};
|
|
|
|
struct lightrec_ops PS_CPU::ops = {
|
|
.cop0_ops = {
|
|
.mfc = cop_mfc,
|
|
.cfc = cop_cfc,
|
|
.mtc = cop_mtc,
|
|
.ctc = cop_ctc,
|
|
.op = cop_op,
|
|
},
|
|
.cop2_ops = {
|
|
.mfc = cop2_mfc,
|
|
.cfc = cop2_cfc,
|
|
.mtc = cop2_mtc,
|
|
.ctc = cop2_ctc,
|
|
.op = cop2_op,
|
|
},
|
|
};
|
|
|
|
struct lightrec_ops PS_CPU::pgxp_ops = {
|
|
.cop0_ops = {
|
|
.mfc = cop_mfc,
|
|
.cfc = cop_cfc,
|
|
.mtc = cop_mtc,
|
|
.ctc = cop_ctc,
|
|
.op = cop_op,
|
|
},
|
|
.cop2_ops = {
|
|
.mfc = pgxp_cop2_mfc,
|
|
.cfc = pgxp_cop2_cfc,
|
|
.mtc = pgxp_cop2_mtc,
|
|
.ctc = pgxp_cop2_ctc,
|
|
.op = cop2_op,
|
|
},
|
|
};
|
|
|
|
int PS_CPU::lightrec_plugin_init()
|
|
{
|
|
struct lightrec_ops *cop_ops;
|
|
uint8_t *psxM = (uint8_t *) MainRAM->data8;
|
|
uint8_t *psxR = (uint8_t *) BIOSROM->data8;
|
|
uint8_t *psxH = (uint8_t *) ScratchRAM->data8;
|
|
uint8_t *psxP = (uint8_t *) PSX_LoadExpansion1();
|
|
|
|
if(lightrec_state)
|
|
lightrec_destroy(lightrec_state);
|
|
else{
|
|
log_cb(RETRO_LOG_INFO, "Lightrec map addresses: M=0x%lx, P=0x%lx, R=0x%lx, H=0x%lx\n",
|
|
(uintptr_t) psxM,
|
|
(uintptr_t) psxP,
|
|
(uintptr_t) psxR,
|
|
(uintptr_t) psxH);
|
|
}
|
|
|
|
lightrec_map[PSX_MAP_KERNEL_USER_RAM].address = psxM;
|
|
|
|
if(psx_mmap == 4){
|
|
lightrec_map[PSX_MAP_MIRROR1].address = psxM + 0x200000;
|
|
lightrec_map[PSX_MAP_MIRROR2].address = psxM + 0x400000;
|
|
lightrec_map[PSX_MAP_MIRROR3].address = psxM + 0x600000;
|
|
}
|
|
|
|
lightrec_map[PSX_MAP_BIOS].address = psxR;
|
|
lightrec_map[PSX_MAP_SCRATCH_PAD].address = psxH;
|
|
lightrec_map[PSX_MAP_PARALLEL_PORT].address = psxP;
|
|
|
|
if (PGXP_GetModes() & (PGXP_MODE_MEMORY | PGXP_MODE_GTE)){
|
|
lightrec_map[PSX_MAP_HW_REGISTERS].ops = &pgxp_hw_regs_ops;
|
|
lightrec_map[PSX_MAP_KERNEL_USER_RAM].ops = &pgxp_nonhw_regs_ops;
|
|
lightrec_map[PSX_MAP_BIOS].ops = &pgxp_nonhw_regs_ops;
|
|
lightrec_map[PSX_MAP_SCRATCH_PAD].ops = &pgxp_nonhw_regs_ops;
|
|
|
|
cop_ops = &pgxp_ops;
|
|
} else {
|
|
lightrec_map[PSX_MAP_HW_REGISTERS].ops = &hw_regs_ops;
|
|
lightrec_map[PSX_MAP_KERNEL_USER_RAM].ops = NULL;
|
|
lightrec_map[PSX_MAP_BIOS].ops = NULL;
|
|
lightrec_map[PSX_MAP_SCRATCH_PAD].ops = NULL;
|
|
|
|
cop_ops = &ops;
|
|
}
|
|
|
|
lightrec_state = lightrec_init(name,
|
|
lightrec_map, ARRAY_SIZE(lightrec_map), cop_ops);
|
|
|
|
lightrec_set_invalidate_mode(lightrec_state, psx_dynarec_invalidate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t PS_CPU::lightrec_plugin_execute(int32_t timestamp)
|
|
{
|
|
uint32_t GPRL[34];
|
|
|
|
uint32_t PC;
|
|
uint32_t new_PC;
|
|
uint32_t new_PC_mask;
|
|
uint32_t LDWhich;
|
|
uint32_t LDValue;
|
|
|
|
BACKING_TO_ACTIVE;
|
|
|
|
u32 flags;
|
|
|
|
do {
|
|
#ifdef LIGHTREC_DEBUG
|
|
u32 oldpc = PC;
|
|
#endif
|
|
memcpy(&GPRL,&GPR,32*sizeof(uint32_t));
|
|
GPRL[32] = LO;
|
|
GPRL[33] = HI;
|
|
lightrec_restore_registers(lightrec_state, GPRL);
|
|
lightrec_reset_cycle_count(lightrec_state, timestamp);
|
|
|
|
if (psx_dynarec == DYNAREC_EXECUTE)
|
|
PC = lightrec_execute(lightrec_state, PC, next_event_ts);
|
|
else if (psx_dynarec == DYNAREC_EXECUTE_ONE)
|
|
PC = lightrec_execute_one(lightrec_state,PC);
|
|
else if (psx_dynarec == DYNAREC_RUN_INTERPRETER)
|
|
PC = lightrec_run_interpreter(lightrec_state,PC);
|
|
|
|
timestamp = lightrec_current_cycle_count(
|
|
lightrec_state);
|
|
|
|
lightrec_dump_registers(lightrec_state, GPRL);
|
|
memcpy(&GPR,&GPRL,32*sizeof(uint32_t));
|
|
LO = GPRL[32];
|
|
HI = GPRL[33];
|
|
|
|
flags = lightrec_exit_flags(lightrec_state);
|
|
|
|
if (flags & LIGHTREC_EXIT_SEGFAULT) {
|
|
log_cb(RETRO_LOG_ERROR, "Exiting at cycle 0x%08x\n",
|
|
timestamp);
|
|
exit(1);
|
|
}
|
|
|
|
if (flags & LIGHTREC_EXIT_SYSCALL)
|
|
PC = Exception(EXCEPTION_SYSCALL, PC, PC, 0);
|
|
|
|
#ifdef LIGHTREC_DEBUG
|
|
if (timestamp >= lightrec_begin_cycles && PC != oldpc){
|
|
print_for_big_ass_debugger(timestamp, PC);
|
|
}
|
|
#endif
|
|
if ((CP0.SR & CP0.CAUSE & 0xFF00) && (CP0.SR & 1)) {
|
|
/* Handle software interrupts */
|
|
PC = Exception(EXCEPTION_INT, PC, PC, 0);
|
|
}
|
|
} while(MDFN_LIKELY(PSX_EventHandler(timestamp)));
|
|
|
|
ACTIVE_TO_BACKING;
|
|
|
|
return timestamp;
|
|
}
|
|
|
|
void PS_CPU::lightrec_plugin_clear(u32 addr, u32 size)
|
|
{
|
|
if (lightrec_state) /* size * 4: uses DMA units */
|
|
lightrec_invalidate(lightrec_state, addr, size * 4);
|
|
}
|
|
|
|
void PS_CPU::lightrec_plugin_shutdown(void)
|
|
{
|
|
log_cb(RETRO_LOG_INFO,"Lightrec memory usage: %u KiB, average IPI: %.2f\n",
|
|
lightrec_get_total_mem_usage()/1024,
|
|
lightrec_get_average_ipi());
|
|
lightrec_destroy(lightrec_state);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if NOT_LIBRETRO
|
|
}
|
|
#endif
|