/******************************************************************************/ /* 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 #include #include 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 INLINE T PS_CPU::PeekMemory(uint32 address) { T ret; address &= addr_mask[address >> 29]; if(address >= 0x1F800000 && address <= 0x1F8003FF) return ScratchRAM->Read(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 void PS_CPU::PokeMemory(uint32 address, T value) { address &= addr_mask[address >> 29]; if(address >= 0x1F800000 && address <= 0x1F8003FF) return ScratchRAM->Write(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 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(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 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(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(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((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(&FMP[0x0]); case 0x4: if (!psx_gte_overclock) { timestamp++; } ICI[0x01].TV &= ~0x2; ICI[0x01].Data = MDFN_de32lsb(&FMP[0x4]); case 0x8: if (!psx_gte_overclock) { timestamp++; } ICI[0x02].TV &= ~0x2; ICI[0x02].Data = MDFN_de32lsb(&FMP[0x8]); case 0xC: if (!psx_gte_overclock) { timestamp++; } ICI[0x03].TV &= ~0x2; ICI[0x03].Data = MDFN_de32lsb(&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 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((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(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(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(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(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(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(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(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(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(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(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(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(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(timestamp, address & ~3) << 24); break; case 1: LDValue = (v & ~(0xFFFF << 16)) | (ReadMemory(timestamp, address & ~3) << 16); break; case 2: LDValue = (v & ~(0xFFFFFF << 8)) | (ReadMemory(timestamp, address & ~3, true) << 8); break; case 3: LDValue = (v & ~(0xFFFFFFFF << 0)) | (ReadMemory(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(timestamp, address & ~3, GPR[rt] >> 24); break; case 1: WriteMemory(timestamp, address & ~3, GPR[rt] >> 16); break; case 2: WriteMemory(timestamp, address & ~3, GPR[rt] >> 8, true); break; case 3: WriteMemory(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(timestamp, address); break; case 1: LDValue = (v & ~(0xFFFFFF)) | ReadMemory(timestamp, address, true); break; case 2: LDValue = (v & ~(0xFFFF)) | ReadMemory(timestamp, address); break; case 3: LDValue = (v & ~(0xFF)) | ReadMemory(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(timestamp, address, GPR[rt]); break; case 1: WriteMemory(timestamp, address, GPR[rt], true); break; case 2: WriteMemory(timestamp, address, GPR[rt]); break; case 3: WriteMemory(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(timestamp_in)); #ifdef DEBUG if(ILHMode) return(RunReal(timestamp_in)); if(BIOSPrintMode) return(RunReal(timestamp_in)); #endif return(RunReal(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(A); } uint16 PS_CPU::PeekMem16(uint32 A) { return PeekMemory(A); } uint32 PS_CPU::PeekMem32(uint32 A) { return PeekMemory(A); } void PS_CPU::PokeMem8(uint32 A, uint8 V) { PokeMemory(A, V); } void PS_CPU::PokeMem16(uint32 A, uint16 V) { PokeMemory(A, V); } void PS_CPU::PokeMem32(uint32 A, uint32 V) { PokeMemory(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