update psx core to 0.9.38.7
This commit is contained in:
parent
29767ba522
commit
5cfc1dae2a
Binary file not shown.
|
@ -80,4 +80,12 @@
|
|||
[OK] psx/gpu : change to comments
|
||||
[OK] psx/memcard : change to debug output
|
||||
0.9.38.4 -> 0.9.38.6
|
||||
[OK] psx/gpu & gpu_sprite : Fixed GPU emulation timing bugs that caused graphical glitches in "Mr. Driller G".
|
||||
[OK] psx/gpu & gpu_sprite : Fixed GPU emulation timing bugs that caused graphical glitches in "Mr. Driller G".
|
||||
0.9.38.5 -> 0.9.38.7
|
||||
[OK] psx/cpu : Revisions to exception handling
|
||||
[OK] psx/cpu : Many revisions and cleanups to branch and exception handling in opcode implementations
|
||||
[OK] psx/dis : Just some basic disassembly changes
|
||||
[OK] psx/gte : Cleanup
|
||||
[OK] psx/psx : Cleanup
|
||||
[OK] psx/timer : Major functional changes
|
||||
[NO] psx/timer : Added loadstate sanity checks
|
File diff suppressed because it is too large
Load Diff
|
@ -1,263 +1,262 @@
|
|||
#ifndef __MDFN_PSX_CPU_H
|
||||
#define __MDFN_PSX_CPU_H
|
||||
|
||||
/*
|
||||
Load delay notes:
|
||||
|
||||
// Takes 1 less
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"or %0, %1, %1\n\t"
|
||||
|
||||
// cycle than this:
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"or %0, %1, %1\n\t"
|
||||
"nop\n\t"
|
||||
|
||||
|
||||
// Both of these
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"or %1, %0, %0\n\t"
|
||||
|
||||
// take same...(which is kind of odd).
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"or %1, %0, %0\n\t"
|
||||
"nop\n\t"
|
||||
*/
|
||||
|
||||
#include "gte.h"
|
||||
|
||||
namespace MDFN_IEN_PSX
|
||||
{
|
||||
|
||||
#define PS_CPU_EMULATE_ICACHE 1
|
||||
|
||||
class PS_CPU
|
||||
{
|
||||
public:
|
||||
|
||||
PS_CPU() MDFN_COLD;
|
||||
~PS_CPU() MDFN_COLD;
|
||||
|
||||
template<bool isReader>void SyncState(EW::NewState *ns);
|
||||
|
||||
// FAST_MAP_* enums are in BYTES(8-bit), not in 32-bit units("words" in MIPS context), but the sizes
|
||||
// will always be multiples of 4.
|
||||
enum { FAST_MAP_SHIFT = 16 };
|
||||
enum { FAST_MAP_PSIZE = 1 << FAST_MAP_SHIFT };
|
||||
|
||||
void SetFastMap(void *region_mem, uint32 region_address, uint32 region_size);
|
||||
|
||||
INLINE void SetEventNT(const pscpu_timestamp_t next_event_ts_arg)
|
||||
{
|
||||
next_event_ts = next_event_ts_arg;
|
||||
}
|
||||
|
||||
pscpu_timestamp_t Run(pscpu_timestamp_t timestamp_in, bool BIOSPrintMode, bool ILHMode);
|
||||
|
||||
void Power(void) MDFN_COLD;
|
||||
|
||||
// which ranges 0-5, inclusive
|
||||
void AssertIRQ(unsigned which, bool asserted);
|
||||
|
||||
void SetHalt(bool status);
|
||||
|
||||
// TODO eventually: factor BIU address decoding directly in the CPU core somehow without hurting speed.
|
||||
void SetBIU(uint32 val);
|
||||
uint32 GetBIU(void);
|
||||
|
||||
private:
|
||||
|
||||
uint32 GPR[32 + 1]; // GPR[32] Used as dummy in load delay simulation(indexing past the end of real GPR)
|
||||
|
||||
uint32 LO;
|
||||
uint32 HI;
|
||||
|
||||
|
||||
uint32 BACKED_PC;
|
||||
uint32 BACKED_new_PC;
|
||||
uint32 BACKED_new_PC_mask;
|
||||
|
||||
uint32 IPCache;
|
||||
void RecalcIPCache(void);
|
||||
bool Halted;
|
||||
|
||||
uint32 BACKED_LDWhich;
|
||||
uint32 BACKED_LDValue;
|
||||
uint32 LDAbsorb;
|
||||
|
||||
pscpu_timestamp_t next_event_ts;
|
||||
pscpu_timestamp_t gte_ts_done;
|
||||
pscpu_timestamp_t muldiv_ts_done;
|
||||
|
||||
uint32 BIU;
|
||||
|
||||
struct __ICache
|
||||
{
|
||||
uint32 TV;
|
||||
uint32 Data;
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
__ICache ICache[1024];
|
||||
uint32 ICache_Bulk[2048];
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
CP0REG_BPC = 3, // PC breakpoint address.
|
||||
CP0REG_BDA = 5, // Data load/store breakpoint address.
|
||||
CP0REG_TAR = 6, // Target address(???)
|
||||
CP0REG_DCIC = 7, // Cache control
|
||||
CP0REG_BDAM = 9, // Data load/store address mask.
|
||||
CP0REG_BPCM = 11, // PC breakpoint address mask.
|
||||
CP0REG_SR = 12,
|
||||
CP0REG_CAUSE = 13,
|
||||
CP0REG_EPC = 14,
|
||||
CP0REG_PRID = 15, // Product ID
|
||||
CP0REG_ERREG = 16
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
union
|
||||
{
|
||||
uint32 Regs[32];
|
||||
struct
|
||||
{
|
||||
uint32 Unused00;
|
||||
uint32 Unused01;
|
||||
uint32 Unused02;
|
||||
uint32 BPC; // RW
|
||||
uint32 Unused04;
|
||||
uint32 BDA; // RW
|
||||
uint32 TAR;
|
||||
uint32 DCIC; // RW
|
||||
uint32 Unused08;
|
||||
uint32 BDAM; // R/W
|
||||
uint32 Unused0A;
|
||||
uint32 BPCM; // R/W
|
||||
uint32 SR; // R/W
|
||||
uint32 CAUSE; // R/W(partial)
|
||||
uint32 EPC; // R
|
||||
uint32 PRID; // R
|
||||
uint32 ERREG; // ?(may not exist, test)
|
||||
};
|
||||
};
|
||||
} CP0;
|
||||
|
||||
#if 1
|
||||
//uint32 WrAbsorb;
|
||||
//uint8 WrAbsorbShift;
|
||||
|
||||
// On read:
|
||||
//WrAbsorb = 0;
|
||||
//WrAbsorbShift = 0;
|
||||
|
||||
// On write:
|
||||
//WrAbsorb >>= (WrAbsorbShift >> 2) & 8;
|
||||
//WrAbsorbShift -= (WrAbsorbShift >> 2) & 8;
|
||||
|
||||
//WrAbsorb |= (timestamp - pre_write_timestamp) << WrAbsorbShift;
|
||||
//WrAbsorbShift += 8;
|
||||
#endif
|
||||
|
||||
uint8 ReadAbsorb[0x20 + 1];
|
||||
uint8 ReadAbsorbWhich;
|
||||
uint8 ReadFudge;
|
||||
|
||||
//uint32 WriteAbsorb;
|
||||
//uint8 WriteAbsorbCount;
|
||||
//uint8 WriteAbsorbMonkey;
|
||||
uint8 MULT_Tab24[24];
|
||||
|
||||
MultiAccessSizeMem<1024, false> ScratchRAM;
|
||||
|
||||
//PS_GTE GTE;
|
||||
|
||||
uint8 *FastMap[1 << (32 - FAST_MAP_SHIFT)];
|
||||
uint8 DummyPage[FAST_MAP_PSIZE];
|
||||
|
||||
enum
|
||||
{
|
||||
EXCEPTION_INT = 0,
|
||||
EXCEPTION_MOD = 1,
|
||||
EXCEPTION_TLBL = 2,
|
||||
EXCEPTION_TLBS = 3,
|
||||
EXCEPTION_ADEL = 4, // Address error on load
|
||||
EXCEPTION_ADES = 5, // Address error on store
|
||||
EXCEPTION_IBE = 6, // Instruction bus error
|
||||
EXCEPTION_DBE = 7, // Data bus error
|
||||
EXCEPTION_SYSCALL = 8, // System call
|
||||
EXCEPTION_BP = 9, // Breakpoint
|
||||
EXCEPTION_RI = 10, // Reserved instruction
|
||||
EXCEPTION_COPU = 11, // Coprocessor unusable
|
||||
EXCEPTION_OV = 12 // Arithmetic overflow
|
||||
};
|
||||
|
||||
uint32 Exception(uint32 code, uint32 PC, const uint32 NPM) MDFN_WARN_UNUSED_RESULT;
|
||||
|
||||
template<bool DebugMode, bool BIOSPrintMode, bool ILHMode> pscpu_timestamp_t RunReal(pscpu_timestamp_t timestamp_in) NO_INLINE;
|
||||
|
||||
template<typename T> T PeekMemory(uint32 address) MDFN_COLD;
|
||||
template<typename T> void PokeMemory(uint32 address, T value) MDFN_COLD;
|
||||
template<typename T> T ReadMemory(pscpu_timestamp_t ×tamp, uint32 address, bool DS24 = false, bool LWC_timing = false);
|
||||
template<typename T> void WriteMemory(pscpu_timestamp_t ×tamp, uint32 address, uint32 value, bool DS24 = false);
|
||||
|
||||
|
||||
//
|
||||
// Mednafen debugger stuff follows:
|
||||
//
|
||||
public:
|
||||
void SetCPUHook(void (*cpuh)(const pscpu_timestamp_t timestamp, uint32 pc), void (*addbt)(uint32 from, uint32 to, bool exception));
|
||||
void CheckBreakpoints(void (*callback)(bool write, uint32 address, unsigned int len), uint32 instr);
|
||||
void* debug_GetScratchRAMPtr() { return ScratchRAM.data8; }
|
||||
void* debug_GetGPRPtr() { return GPR; }
|
||||
|
||||
enum
|
||||
{
|
||||
GSREG_GPR = 0,
|
||||
GSREG_PC = 32,
|
||||
GSREG_PC_NEXT,
|
||||
GSREG_IN_BD_SLOT,
|
||||
GSREG_LO,
|
||||
GSREG_HI,
|
||||
GSREG_SR,
|
||||
GSREG_CAUSE,
|
||||
GSREG_EPC,
|
||||
};
|
||||
|
||||
uint32 GetRegister(unsigned int which, char *special, const uint32 special_len);
|
||||
void SetRegister(unsigned int which, uint32 value);
|
||||
bool PeekCheckICache(uint32 PC, uint32 *iw);
|
||||
|
||||
uint8 PeekMem8(uint32 A);
|
||||
uint16 PeekMem16(uint32 A);
|
||||
uint32 PeekMem32(uint32 A);
|
||||
|
||||
void PokeMem8(uint32 A, uint8 V);
|
||||
void PokeMem16(uint32 A, uint16 V);
|
||||
void PokeMem32(uint32 A, uint32 V);
|
||||
|
||||
private:
|
||||
void (*CPUHook)(const pscpu_timestamp_t timestamp, uint32 pc);
|
||||
void (*ADDBT)(uint32 from, uint32 to, bool exception);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifndef __MDFN_PSX_CPU_H
|
||||
#define __MDFN_PSX_CPU_H
|
||||
|
||||
/*
|
||||
Load delay notes:
|
||||
|
||||
// Takes 1 less
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"or %0, %1, %1\n\t"
|
||||
|
||||
// cycle than this:
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"or %0, %1, %1\n\t"
|
||||
"nop\n\t"
|
||||
|
||||
|
||||
// Both of these
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"or %1, %0, %0\n\t"
|
||||
|
||||
// take same...(which is kind of odd).
|
||||
".set noreorder\n\t"
|
||||
".set nomacro\n\t"
|
||||
"lw %0, 0(%2)\n\t"
|
||||
"nop\n\t"
|
||||
"or %1, %0, %0\n\t"
|
||||
"nop\n\t"
|
||||
*/
|
||||
|
||||
#include "gte.h"
|
||||
|
||||
namespace MDFN_IEN_PSX
|
||||
{
|
||||
|
||||
#define PS_CPU_EMULATE_ICACHE 1
|
||||
|
||||
class PS_CPU
|
||||
{
|
||||
public:
|
||||
|
||||
PS_CPU() MDFN_COLD;
|
||||
~PS_CPU() MDFN_COLD;
|
||||
|
||||
template<bool isReader>void SyncState(EW::NewState *ns);
|
||||
|
||||
// FAST_MAP_* enums are in BYTES(8-bit), not in 32-bit units("words" in MIPS context), but the sizes
|
||||
// will always be multiples of 4.
|
||||
enum { FAST_MAP_SHIFT = 16 };
|
||||
enum { FAST_MAP_PSIZE = 1 << FAST_MAP_SHIFT };
|
||||
|
||||
void SetFastMap(void *region_mem, uint32 region_address, uint32 region_size);
|
||||
|
||||
INLINE void SetEventNT(const pscpu_timestamp_t next_event_ts_arg)
|
||||
{
|
||||
next_event_ts = next_event_ts_arg;
|
||||
}
|
||||
|
||||
pscpu_timestamp_t Run(pscpu_timestamp_t timestamp_in, bool BIOSPrintMode, bool ILHMode);
|
||||
|
||||
void Power(void) MDFN_COLD;
|
||||
|
||||
// which ranges 0-5, inclusive
|
||||
void AssertIRQ(unsigned which, bool asserted);
|
||||
|
||||
void SetHalt(bool status);
|
||||
|
||||
// TODO eventually: factor BIU address decoding directly in the CPU core somehow without hurting speed.
|
||||
void SetBIU(uint32 val);
|
||||
uint32 GetBIU(void);
|
||||
|
||||
private:
|
||||
|
||||
uint32 GPR[32 + 1]; // GPR[32] Used as dummy in load delay simulation(indexing past the end of real GPR)
|
||||
|
||||
uint32 LO;
|
||||
uint32 HI;
|
||||
|
||||
|
||||
uint32 BACKED_PC;
|
||||
uint32 BACKED_new_PC;
|
||||
uint32 BACKED_new_PC_mask;
|
||||
|
||||
uint32 IPCache;
|
||||
void RecalcIPCache(void);
|
||||
bool Halted;
|
||||
|
||||
uint32 BACKED_LDWhich;
|
||||
uint32 BACKED_LDValue;
|
||||
uint32 LDAbsorb;
|
||||
|
||||
pscpu_timestamp_t next_event_ts;
|
||||
pscpu_timestamp_t gte_ts_done;
|
||||
pscpu_timestamp_t muldiv_ts_done;
|
||||
|
||||
uint32 BIU;
|
||||
|
||||
struct __ICache
|
||||
{
|
||||
uint32 TV;
|
||||
uint32 Data;
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
__ICache ICache[1024];
|
||||
uint32 ICache_Bulk[2048];
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
CP0REG_BPC = 3, // PC breakpoint address.
|
||||
CP0REG_BDA = 5, // Data load/store breakpoint address.
|
||||
CP0REG_TAR = 6, // Target address(???)
|
||||
CP0REG_DCIC = 7, // Cache control
|
||||
CP0REG_BADVA = 8,
|
||||
CP0REG_BDAM = 9, // Data load/store address mask.
|
||||
CP0REG_BPCM = 11, // PC breakpoint address mask.
|
||||
CP0REG_SR = 12,
|
||||
CP0REG_CAUSE = 13,
|
||||
CP0REG_EPC = 14,
|
||||
CP0REG_PRID = 15 // Product ID
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
union
|
||||
{
|
||||
uint32 Regs[32];
|
||||
struct
|
||||
{
|
||||
uint32 Unused00;
|
||||
uint32 Unused01;
|
||||
uint32 Unused02;
|
||||
uint32 BPC; // RW
|
||||
uint32 Unused04;
|
||||
uint32 BDA; // RW
|
||||
uint32 TAR; // R
|
||||
uint32 DCIC; // RW
|
||||
uint32 BADVA; // R
|
||||
uint32 BDAM; // R/W
|
||||
uint32 Unused0A;
|
||||
uint32 BPCM; // R/W
|
||||
uint32 SR; // R/W
|
||||
uint32 CAUSE; // R/W(partial)
|
||||
uint32 EPC; // R
|
||||
uint32 PRID; // R
|
||||
};
|
||||
};
|
||||
} CP0;
|
||||
|
||||
#if 1
|
||||
//uint32 WrAbsorb;
|
||||
//uint8 WrAbsorbShift;
|
||||
|
||||
// On read:
|
||||
//WrAbsorb = 0;
|
||||
//WrAbsorbShift = 0;
|
||||
|
||||
// On write:
|
||||
//WrAbsorb >>= (WrAbsorbShift >> 2) & 8;
|
||||
//WrAbsorbShift -= (WrAbsorbShift >> 2) & 8;
|
||||
|
||||
//WrAbsorb |= (timestamp - pre_write_timestamp) << WrAbsorbShift;
|
||||
//WrAbsorbShift += 8;
|
||||
#endif
|
||||
|
||||
uint8 ReadAbsorb[0x20 + 1];
|
||||
uint8 ReadAbsorbWhich;
|
||||
uint8 ReadFudge;
|
||||
|
||||
//uint32 WriteAbsorb;
|
||||
//uint8 WriteAbsorbCount;
|
||||
//uint8 WriteAbsorbMonkey;
|
||||
uint8 MULT_Tab24[24];
|
||||
|
||||
MultiAccessSizeMem<1024, false> ScratchRAM;
|
||||
|
||||
//PS_GTE GTE;
|
||||
|
||||
uint8 *FastMap[1 << (32 - FAST_MAP_SHIFT)];
|
||||
uint8 DummyPage[FAST_MAP_PSIZE];
|
||||
|
||||
enum
|
||||
{
|
||||
EXCEPTION_INT = 0,
|
||||
EXCEPTION_MOD = 1,
|
||||
EXCEPTION_TLBL = 2,
|
||||
EXCEPTION_TLBS = 3,
|
||||
EXCEPTION_ADEL = 4, // Address error on load
|
||||
EXCEPTION_ADES = 5, // Address error on store
|
||||
EXCEPTION_IBE = 6, // Instruction bus error
|
||||
EXCEPTION_DBE = 7, // Data bus error
|
||||
EXCEPTION_SYSCALL = 8, // System call
|
||||
EXCEPTION_BP = 9, // Breakpoint
|
||||
EXCEPTION_RI = 10, // Reserved instruction
|
||||
EXCEPTION_COPU = 11, // Coprocessor unusable
|
||||
EXCEPTION_OV = 12 // Arithmetic overflow
|
||||
};
|
||||
|
||||
uint32 Exception(uint32 code, uint32 PC, const uint32 NP, const uint32 NPM, const uint32 instr) MDFN_WARN_UNUSED_RESULT;
|
||||
|
||||
template<bool DebugMode, bool BIOSPrintMode, bool ILHMode> pscpu_timestamp_t RunReal(pscpu_timestamp_t timestamp_in) NO_INLINE;
|
||||
|
||||
template<typename T> T PeekMemory(uint32 address) MDFN_COLD;
|
||||
template<typename T> void PokeMemory(uint32 address, T value) MDFN_COLD;
|
||||
template<typename T> T ReadMemory(pscpu_timestamp_t ×tamp, uint32 address, bool DS24 = false, bool LWC_timing = false);
|
||||
template<typename T> void WriteMemory(pscpu_timestamp_t ×tamp, uint32 address, uint32 value, bool DS24 = false);
|
||||
|
||||
|
||||
//
|
||||
// Mednafen debugger stuff follows:
|
||||
//
|
||||
public:
|
||||
void SetCPUHook(void(*cpuh)(const pscpu_timestamp_t timestamp, uint32 pc), void(*addbt)(uint32 from, uint32 to, bool exception));
|
||||
void CheckBreakpoints(void(*callback)(bool write, uint32 address, unsigned int len), uint32 instr);
|
||||
void* debug_GetScratchRAMPtr() { return ScratchRAM.data8; }
|
||||
void* debug_GetGPRPtr() { return GPR; }
|
||||
|
||||
enum
|
||||
{
|
||||
GSREG_GPR = 0,
|
||||
GSREG_PC = 32,
|
||||
GSREG_PC_NEXT,
|
||||
GSREG_IN_BD_SLOT,
|
||||
GSREG_LO,
|
||||
GSREG_HI,
|
||||
GSREG_SR,
|
||||
GSREG_CAUSE,
|
||||
GSREG_EPC,
|
||||
};
|
||||
|
||||
uint32 GetRegister(unsigned int which, char *special, const uint32 special_len);
|
||||
void SetRegister(unsigned int which, uint32 value);
|
||||
bool PeekCheckICache(uint32 PC, uint32 *iw);
|
||||
|
||||
uint8 PeekMem8(uint32 A);
|
||||
uint16 PeekMem16(uint32 A);
|
||||
uint32 PeekMem32(uint32 A);
|
||||
|
||||
void PokeMem8(uint32 A, uint8 V);
|
||||
void PokeMem16(uint32 A, uint16 V);
|
||||
void PokeMem32(uint32 A, uint32 V);
|
||||
|
||||
private:
|
||||
void(*CPUHook)(const pscpu_timestamp_t timestamp, uint32 pc);
|
||||
void(*ADDBT)(uint32 from, uint32 to, bool exception);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -128,13 +128,15 @@ struct OpEntry
|
|||
|
||||
#define MK_OP(mnemonic, format, op, func, extra_mask) { MASK_OP | (op ? 0 : MASK_FUNC) | extra_mask, ((unsigned)op << 26) | func, mnemonic, format }
|
||||
|
||||
#define MK_OP_REGIMM(mnemonic, regop) { MASK_OP | MASK_RT, (0x01U << 26) | (regop << 16), mnemonic, "s, p" }
|
||||
#define MK_OP_REGIMM(mnemonic, regop_mask, regop) { MASK_OP | (regop_mask << 16), (0x01U << 26) | (regop << 16), mnemonic, "s, p" }
|
||||
|
||||
|
||||
#define MK_COPZ(z) { MASK_OP | (0x1U << 25), (0x1U << 25) | ((0x10U | z) << 26), "cop" #z, "F" }
|
||||
#define MK_COP0_FUNC(mnemonic, func) { MASK_OP | (0x1U << 25) | MASK_FUNC, (0x10U << 26) | (0x1U << 25) | func, mnemonic, "" }
|
||||
|
||||
#define MK_COPZ_XFER(z, mnemonic, format, xf) { MASK_OP | (0x1FU << 21), ((0x10U | z) << 26) | (xf << 21), mnemonic, format }
|
||||
#define MK_COPZ_BCzx(z, x) { MASK_OP | (0x1BU << 21) | (0x01 << 16), ((0x10U | z) << 26) | (0x08 << 21) | (x << 16), (x ? "bc" #z "t" : "bc" #z "f"), "p" }
|
||||
#define MK_COPZ_BC(z) MK_COPZ_BCzx(z, 0), MK_COPZ_BCzx(z, 1)
|
||||
|
||||
#define MK_GTE(mnemonic, format, func) { MASK_OP | (0x1U << 25) | MASK_FUNC, (0x1U << 25) | (0x12U << 26) | func, mnemonic, format }
|
||||
|
||||
|
@ -180,10 +182,12 @@ static OpEntry ops[] =
|
|||
MK_OP("slt", "d, s, t", 0, 42, 0),
|
||||
MK_OP("sltu", "d, s, t", 0, 43, 0),
|
||||
|
||||
MK_OP_REGIMM("bgez", 0x01),
|
||||
MK_OP_REGIMM("bgezal", 0x11),
|
||||
MK_OP_REGIMM("bltz", 0x00),
|
||||
MK_OP_REGIMM("bltzal", 0x10),
|
||||
// keep *al before the non-linking versions, due to mask setup.
|
||||
MK_OP_REGIMM("bgezal", 0x1F, 0x11),
|
||||
MK_OP_REGIMM("bltzal", 0x1F, 0x10),
|
||||
|
||||
MK_OP_REGIMM("bgez", 0x01, 0x01),
|
||||
MK_OP_REGIMM("bltz", 0x00, 0x00),
|
||||
|
||||
|
||||
MK_OP("j", "P", 2, 0, 0),
|
||||
|
@ -225,6 +229,11 @@ static OpEntry ops[] =
|
|||
MK_COPZ_XFER(2, "ctc2", "t, G", 0x06),
|
||||
MK_COPZ_XFER(3, "ctc3", "t, ?", 0x06),
|
||||
|
||||
MK_COPZ_BC(0),
|
||||
MK_COPZ_BC(1),
|
||||
MK_COPZ_BC(2),
|
||||
MK_COPZ_BC(3),
|
||||
|
||||
// COP0 stuff here
|
||||
MK_COP0_FUNC("rfe", 0x10),
|
||||
|
||||
|
@ -316,8 +325,8 @@ EW_EXPORT s32 shock_Util_DisassembleMIPS(u32 PC, u32 instr, void* outbuf, s32 bu
|
|||
|
||||
static const char *cop0_names[32] =
|
||||
{
|
||||
"CPR0", "CPR1", "CPR2", "BPC", "CPR4", "BDA", "TAR", "DCIC", "CPR8", "BDAM", "CPR10", "BPCM", "SR", "CAUSE", "EPC", "PRID",
|
||||
"ERREG", "CPR17", "CPR18", "CPR19", "CPR20", "CPR21", "CPR22", "CPR23", "CPR24", "CPR25", "CPR26", "CPR27", "CPR28", "CPR29", "CPR30", "CPR31"
|
||||
"CPR0", "CPR1", "CPR2", "BPC", "CPR4", "BDA", "TAR", "DCIC", "BADVA", "BDAM", "CPR10", "BPCM", "SR", "CAUSE", "EPC", "PRID",
|
||||
"CPR16", "CPR17", "CPR18", "CPR19", "CPR20", "CPR21", "CPR22", "CPR23", "CPR24", "CPR25", "CPR26", "CPR27", "CPR28", "CPR29", "CPR30", "CPR31"
|
||||
};
|
||||
|
||||
static const char *gte_cr_names[32] =
|
||||
|
|
|
@ -796,22 +796,6 @@ static INLINE int32 Lm_G(unsigned int which, int32 value)
|
|||
// limit to 4096, not 4095
|
||||
static INLINE int32 Lm_H(int32 value)
|
||||
{
|
||||
#if 0
|
||||
if(FLAGS & (1 << 15))
|
||||
{
|
||||
value = 0;
|
||||
FLAGS |= 1 << 12;
|
||||
return value;
|
||||
}
|
||||
|
||||
if(FLAGS & (1 << 16))
|
||||
{
|
||||
value = 4096;
|
||||
FLAGS |= 1 << 12;
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(value < 0)
|
||||
{
|
||||
value = 0;
|
||||
|
|
|
@ -272,7 +272,6 @@ static void RebaseTS(const pscpu_timestamp_t timestamp)
|
|||
|
||||
void PSX_SetEventNT(const int type, const pscpu_timestamp_t next_timestamp)
|
||||
{
|
||||
assert(type > PSX_EVENT__SYNFIRST && type < PSX_EVENT__SYNLAST);
|
||||
event_list_entry *e = &events[type];
|
||||
|
||||
if(next_timestamp < e->event_time)
|
||||
|
|
|
@ -18,433 +18,441 @@
|
|||
#include <assert.h>
|
||||
#include "psx.h"
|
||||
#include "timer.h"
|
||||
|
||||
/*
|
||||
Notes(some of it may be incomplete or wrong in subtle ways)
|
||||
|
||||
Control bits:
|
||||
Lower 3 bits of mode, for timer1(when mode is | 0x100):
|
||||
0x1 = don't count while in vblank(except that the first count while in vblank does go through)
|
||||
0x3 = vblank going inactive triggers timer reset, then some interesting behavior where counting again is delayed...
|
||||
0x5 = vblank going inactive triggers timer reset, and only count within vblank.
|
||||
0x7 = Wait until vblank goes active then inactive, then start counting?
|
||||
For timer2:
|
||||
0x1 = timer stopped(TODO: confirm on real system)
|
||||
|
||||
Target mode enabled 0x008
|
||||
IRQ enable 0x010
|
||||
--?Affects 0x400 status flag?-- 0x020
|
||||
IRQ evaluation auto-reset 0x040
|
||||
--unknown-- 0x080
|
||||
Clock selection 0x100
|
||||
Divide by 8(timer 2 only?) 0x200
|
||||
|
||||
Counter:
|
||||
Reset to 0 on writes to the mode/status register.
|
||||
|
||||
Status flags:
|
||||
Unknown flag 0x0400
|
||||
Compare flag 0x0800
|
||||
Cleared on mode/status read.
|
||||
Set when: //ever Counter == 0(proooobably, need to investigate lower 3 bits in relation to this).
|
||||
|
||||
|
||||
Overflow/Carry flag 0x1000
|
||||
Cleared on mode/status read.
|
||||
Set when counter overflows from 0xFFFF->0.
|
||||
|
||||
Hidden flags:
|
||||
IRQ done
|
||||
Cleared on writes to the mode/status register, on writes to the count register, and apparently automatically when the counter
|
||||
increments if (Mode & 0x40) [Note: If target mode is enabled, and target is 0, IRQ done flag won't be automatically reset]
|
||||
|
||||
There seems to be a brief period(edge condition?) where, if count to target is enabled, you can (sometimes?) read the target value in the count
|
||||
register before it's reset to 0. I doubt any games rely on this, but who knows. Maybe a PSX equivalent of the PC Engine "Battle Royale"? ;)
|
||||
|
||||
When the counter == 0, the compare flag is set. An IRQ will be generated if (Mode & 0x10), and the hidden IRQ done flag will be set.
|
||||
*/
|
||||
|
||||
/*
|
||||
Dec. 26, 2011 Note
|
||||
Due to problems I've had with my GPU timing test program, timer2 appears to be unreliable(clocks are skipped?) when target mode is enabled and the full
|
||||
33MHz clock is used(rather than 33MHz / 8). TODO: Investigate further and confirm(or not).
|
||||
|
||||
Jan. 15, 2013 Note:
|
||||
Counters using GPU clock sources(hretrace,dot clock) reportedly will with a low probability return wrong count values on an actual PS1, so keep this in mind
|
||||
when writing test programs(IE keep reading the count value until two consecutive reads return the same value).
|
||||
*/
|
||||
|
||||
/*
|
||||
FIXME: Clock appropriately(and update events) when using SetRegister() via the debugger.
|
||||
|
||||
TODO: If we ever return randomish values to "simulate" open bus, remember to change the return type and such of the TIMER_Read() function to full 32-bit too.
|
||||
*/
|
||||
|
||||
namespace MDFN_IEN_PSX
|
||||
{
|
||||
|
||||
struct Timer
|
||||
{
|
||||
uint32 Mode;
|
||||
int32 Counter; // Only 16-bit, but 32-bit here for detecting counting past target.
|
||||
int32 Target;
|
||||
|
||||
int32 Div8Counter;
|
||||
|
||||
bool IRQDone;
|
||||
int32 DoZeCounting;
|
||||
};
|
||||
|
||||
static bool vblank;
|
||||
static bool hretrace;
|
||||
static Timer Timers[3];
|
||||
static pscpu_timestamp_t lastts;
|
||||
|
||||
static int32 CalcNextEvent(int32 next_event)
|
||||
{
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
int32 target;
|
||||
int32 count_delta;
|
||||
|
||||
if((i == 0 || i == 1) && (Timers[i].Mode & 0x100)) // If clocked by GPU, abort for this timer(will result in poor granularity for pixel-clock-derived timer IRQs, but whatever).
|
||||
continue;
|
||||
|
||||
if(!(Timers[i].Mode & 0x10)) // If IRQ is disabled, abort for this timer.
|
||||
continue;
|
||||
|
||||
if((Timers[i].Mode & 0x8) && (Timers[i].Counter == 0) && (Timers[i].Target == 0) && !Timers[i].IRQDone)
|
||||
{
|
||||
next_event = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
target = ((Timers[i].Mode & 0x8) && (Timers[i].Counter < Timers[i].Target)) ? Timers[i].Target : 0x10000;
|
||||
|
||||
count_delta = target - Timers[i].Counter;
|
||||
if(count_delta <= 0)
|
||||
{
|
||||
PSX_DBG(PSX_DBG_ERROR, "timer %d count_delta <= 0!!! %d %d\n", i, target, Timers[i].Counter);
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
int32 tmp_clocks;
|
||||
|
||||
if(Timers[i].DoZeCounting <= 0)
|
||||
continue;
|
||||
|
||||
if((i == 0x2) && (Timers[i].Mode & 0x1))
|
||||
continue;
|
||||
|
||||
if((i == 0x2) && (Timers[i].Mode & 0x200))
|
||||
{
|
||||
assert(Timers[i].Div8Counter >= 0 && Timers[i].Div8Counter < 8);
|
||||
tmp_clocks = ((count_delta - 1) * 8) + (8 - Timers[i].Div8Counter);
|
||||
}
|
||||
else
|
||||
tmp_clocks = count_delta;
|
||||
|
||||
assert(tmp_clocks > 0);
|
||||
|
||||
if(next_event > tmp_clocks)
|
||||
next_event = tmp_clocks;
|
||||
}
|
||||
}
|
||||
|
||||
return(next_event);
|
||||
}
|
||||
|
||||
static void ClockTimer(int i, uint32 clocks)
|
||||
{
|
||||
int32 before = Timers[i].Counter;
|
||||
int32 target = 0x10000;
|
||||
bool zero_tm = false;
|
||||
|
||||
if(Timers[i].DoZeCounting <= 0)
|
||||
clocks = 0;
|
||||
|
||||
if(i == 0x2)
|
||||
{
|
||||
uint32 d8_clocks;
|
||||
|
||||
Timers[i].Div8Counter += clocks;
|
||||
d8_clocks = Timers[i].Div8Counter >> 3;
|
||||
Timers[i].Div8Counter -= d8_clocks << 3;
|
||||
|
||||
if(Timers[i].Mode & 0x200) // Divide by 8, at least for timer 0x2
|
||||
clocks = d8_clocks;
|
||||
|
||||
if(Timers[i].Mode & 1)
|
||||
clocks = 0;
|
||||
}
|
||||
|
||||
if(Timers[i].Mode & 0x008)
|
||||
target = Timers[i].Target;
|
||||
|
||||
if(target == 0 && Timers[i].Counter == 0)
|
||||
zero_tm = true;
|
||||
else
|
||||
Timers[i].Counter += clocks;
|
||||
|
||||
if(clocks && (Timers[i].Mode & 0x40))
|
||||
Timers[i].IRQDone = false;
|
||||
|
||||
if((before < target && Timers[i].Counter >= target) || zero_tm || Timers[i].Counter > 0xFFFF)
|
||||
{
|
||||
#if 1
|
||||
if(Timers[i].Mode & 0x10)
|
||||
{
|
||||
if((Timers[i].Counter - target) > 3)
|
||||
PSX_WARNING("Timer %d IRQ trigger error: %d", i, Timers[i].Counter - target);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
Timers[i].Mode |= 0x0800;
|
||||
|
||||
if(Timers[i].Counter > 0xFFFF)
|
||||
{
|
||||
Timers[i].Counter -= 0x10000;
|
||||
|
||||
if(target == 0x10000)
|
||||
Timers[i].Mode |= 0x1000;
|
||||
|
||||
if(!target)
|
||||
Timers[i].Counter = 0;
|
||||
}
|
||||
|
||||
if(target)
|
||||
Timers[i].Counter -= (Timers[i].Counter / target) * target;
|
||||
|
||||
if((Timers[i].Mode & 0x10) && !Timers[i].IRQDone)
|
||||
{
|
||||
Timers[i].IRQDone = true;
|
||||
|
||||
IRQ_Assert(IRQ_TIMER_0 + i, true);
|
||||
IRQ_Assert(IRQ_TIMER_0 + i, false);
|
||||
}
|
||||
|
||||
if(Timers[i].Counter && (Timers[i].Mode & 0x40))
|
||||
Timers[i].IRQDone = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TIMER_SetVBlank(bool status)
|
||||
{
|
||||
switch(Timers[1].Mode & 0x7)
|
||||
{
|
||||
case 0x1:
|
||||
Timers[1].DoZeCounting = !status;
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
if(vblank && !status)
|
||||
Timers[1].Counter = 0;
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
Timers[1].DoZeCounting = status;
|
||||
if(vblank && !status)
|
||||
Timers[1].Counter = 0;
|
||||
break;
|
||||
|
||||
case 0x7:
|
||||
if(Timers[1].DoZeCounting == -1)
|
||||
{
|
||||
if(!vblank && status)
|
||||
Timers[1].DoZeCounting = 0;
|
||||
}
|
||||
else if(Timers[1].DoZeCounting == 0)
|
||||
{
|
||||
if(vblank && !status)
|
||||
Timers[1].DoZeCounting = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
vblank = status;
|
||||
}
|
||||
|
||||
void TIMER_SetHRetrace(bool status)
|
||||
{
|
||||
if(hretrace && !status)
|
||||
{
|
||||
if((Timers[0].Mode & 0x7) == 0x3)
|
||||
Timers[0].Counter = 0;
|
||||
}
|
||||
|
||||
hretrace = status;
|
||||
}
|
||||
|
||||
void TIMER_AddDotClocks(uint32 count)
|
||||
{
|
||||
if(Timers[0].Mode & 0x100)
|
||||
ClockTimer(0, count);
|
||||
}
|
||||
|
||||
void TIMER_ClockHRetrace(void)
|
||||
{
|
||||
if(Timers[1].Mode & 0x100)
|
||||
ClockTimer(1, 1);
|
||||
}
|
||||
|
||||
pscpu_timestamp_t TIMER_Update(const pscpu_timestamp_t timestamp)
|
||||
{
|
||||
int32 cpu_clocks = timestamp - lastts;
|
||||
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
uint32 timer_clocks = cpu_clocks;
|
||||
|
||||
if(Timers[i].Mode & 0x100)
|
||||
continue;
|
||||
|
||||
ClockTimer(i, timer_clocks);
|
||||
}
|
||||
|
||||
lastts = timestamp;
|
||||
|
||||
return(timestamp + CalcNextEvent(1024));
|
||||
}
|
||||
|
||||
static void CalcCountingStart(unsigned which)
|
||||
{
|
||||
Timers[which].DoZeCounting = true;
|
||||
|
||||
switch(which)
|
||||
{
|
||||
case 1:
|
||||
switch(Timers[which].Mode & 0x07)
|
||||
{
|
||||
case 0x1:
|
||||
Timers[which].DoZeCounting = !vblank;
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
Timers[which].DoZeCounting = vblank;
|
||||
break;
|
||||
|
||||
case 0x7:
|
||||
Timers[which].DoZeCounting = -1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void TIMER_Write(const pscpu_timestamp_t timestamp, uint32 A, uint16 V)
|
||||
{
|
||||
TIMER_Update(timestamp);
|
||||
|
||||
int which = (A >> 4) & 0x3;
|
||||
|
||||
V <<= (A & 3) * 8;
|
||||
|
||||
PSX_DBGINFO("[TIMER] Write: %08x %04x\n", A, V);
|
||||
|
||||
if(which >= 3)
|
||||
return;
|
||||
|
||||
// TODO: See if the "Timers[which].Counter" part of the IRQ if() statements below is what a real PSX does.
|
||||
switch(A & 0xC)
|
||||
{
|
||||
case 0x0: Timers[which].IRQDone = false;
|
||||
#if 1
|
||||
if(Timers[which].Counter && (V & 0xFFFF) == 0)
|
||||
{
|
||||
Timers[which].Mode |= 0x0800;
|
||||
if((Timers[which].Mode & 0x10) && !Timers[which].IRQDone)
|
||||
{
|
||||
Timers[which].IRQDone = true;
|
||||
IRQ_Assert(IRQ_TIMER_0 + which, true);
|
||||
IRQ_Assert(IRQ_TIMER_0 + which, false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Timers[which].Counter = V & 0xFFFF;
|
||||
break;
|
||||
|
||||
case 0x4: Timers[which].Mode = (V & 0x3FF) | (Timers[which].Mode & 0x1C00);
|
||||
Timers[which].IRQDone = false;
|
||||
#if 1
|
||||
if(Timers[which].Counter)
|
||||
{
|
||||
Timers[which].Mode |= 0x0800;
|
||||
if((Timers[which].Mode & 0x10) && !Timers[which].IRQDone)
|
||||
{
|
||||
Timers[which].IRQDone = true;
|
||||
IRQ_Assert(IRQ_TIMER_0 + which, true);
|
||||
IRQ_Assert(IRQ_TIMER_0 + which, false);
|
||||
}
|
||||
}
|
||||
Timers[which].Counter = 0;
|
||||
#endif
|
||||
CalcCountingStart(which); // Call after setting .Mode
|
||||
break;
|
||||
|
||||
case 0x8: Timers[which].Target = V & 0xFFFF;
|
||||
break;
|
||||
|
||||
case 0xC: // Open bus
|
||||
break;
|
||||
}
|
||||
|
||||
// TIMER_Update(timestamp);
|
||||
|
||||
PSX_SetEventNT(PSX_EVENT_TIMER, timestamp + CalcNextEvent(1024));
|
||||
}
|
||||
|
||||
uint16 TIMER_Read(const pscpu_timestamp_t timestamp, uint32 A)
|
||||
{
|
||||
uint16 ret = 0;
|
||||
int which = (A >> 4) & 0x3;
|
||||
|
||||
if(which >= 3)
|
||||
{
|
||||
PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A);
|
||||
|
||||
return(ret >> ((A & 3) * 8));
|
||||
}
|
||||
|
||||
TIMER_Update(timestamp);
|
||||
|
||||
switch(A & 0xC)
|
||||
{
|
||||
case 0x0: ret = Timers[which].Counter;
|
||||
break;
|
||||
|
||||
case 0x4: ret = Timers[which].Mode;
|
||||
Timers[which].Mode &= ~0x1800;
|
||||
break;
|
||||
|
||||
case 0x8: ret = Timers[which].Target;
|
||||
break;
|
||||
|
||||
case 0xC: PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A);
|
||||
break;
|
||||
}
|
||||
|
||||
return(ret >> ((A & 3) * 8));
|
||||
}
|
||||
|
||||
|
||||
void TIMER_ResetTS(void)
|
||||
{
|
||||
lastts = 0;
|
||||
}
|
||||
|
||||
|
||||
void TIMER_Power(void)
|
||||
{
|
||||
lastts = 0;
|
||||
|
||||
hretrace = false;
|
||||
vblank = false;
|
||||
memset(Timers, 0, sizeof(Timers));
|
||||
|
||||
/*
|
||||
Notes(some of it may be incomplete or wrong in subtle ways)
|
||||
|
||||
Control bits:
|
||||
Lower 3 bits of mode, for timer1(when mode is | 0x100):
|
||||
0x1 = don't count while in vblank(except that the first count while in vblank does go through)
|
||||
0x3 = vblank going inactive triggers timer reset, then some interesting behavior where counting again is delayed...
|
||||
0x5 = vblank going inactive triggers timer reset, and only count within vblank.
|
||||
0x7 = Wait until vblank goes active then inactive, then start counting?
|
||||
For timer2:
|
||||
0x1 = timer stopped(TODO: confirm on real system)
|
||||
|
||||
Target match counter reset enable 0x008
|
||||
Target match IRQ enable 0x010
|
||||
Overflow IRQ enable 0x020
|
||||
IRQ evaluation auto-reset 0x040
|
||||
--unknown-- 0x080
|
||||
Clock selection 0x100
|
||||
Divide by 8(timer 2 only?) 0x200
|
||||
|
||||
Counter:
|
||||
Reset to 0 on writes to the mode/status register.
|
||||
|
||||
Status flags:
|
||||
Current IRQ line status? 0x0400
|
||||
Compare flag 0x0800
|
||||
Cleared on mode/status read.
|
||||
Set repeatedly while counter == target.
|
||||
|
||||
Overflow/Carry flag 0x1000
|
||||
Cleared on mode/status read.
|
||||
Set when counter overflows from 0xFFFF->0.
|
||||
|
||||
Hidden flags:
|
||||
IRQ done
|
||||
Cleared on writes to the mode/status register, on writes to the count register, and apparently automatically when the counter
|
||||
increments if (Mode & 0x40) [Note: If target mode is enabled, and target is 0, IRQ done flag won't be automatically reset]
|
||||
|
||||
There seems to be a brief period(edge condition?) where, if target match reset mode is enabled, you can (sometimes?) read the target value in the count
|
||||
register before it's reset to 0. Currently not emulated; I doubt any games rely on this, but who knows. Maybe a PSX equivalent
|
||||
of the PC Engine "Battle Royale"? ;)
|
||||
|
||||
A timer is somewhat unreliable when target match reset mode is enabled and the 33MHz clock is used. Average 2.4 counts seem to be
|
||||
skipped for that timer every target match reset, but oddly subtracting only 2 from the desired target match value seems to effectively
|
||||
negate the loss...wonder if my test program is faulty in some way. Currently not emulated.
|
||||
|
||||
Counters using GPU clock sources(hretrace,dot clock) reportedly will with a low probability return wrong count values on an actual PS1,
|
||||
so keep this in mind when writing test programs(IE keep reading the count value until two consecutive reads return the same value).
|
||||
Currently not emulated.
|
||||
*/
|
||||
|
||||
/*
|
||||
FIXME: Clock appropriately(and update events) when using SetRegister() via the debugger.
|
||||
|
||||
TODO: If we ever return randomish values to "simulate" open bus, remember to change the return type and such of the TIMER_Read() function to full 32-bit too.
|
||||
*/
|
||||
|
||||
namespace MDFN_IEN_PSX
|
||||
{
|
||||
|
||||
struct Timer
|
||||
{
|
||||
uint32 Mode;
|
||||
uint32 Counter; // Only 16-bit, but 32-bit here for detecting counting past target.
|
||||
uint32 Target;
|
||||
|
||||
uint32 Div8Counter;
|
||||
|
||||
bool IRQDone;
|
||||
int32 DoZeCounting;
|
||||
};
|
||||
|
||||
static bool vblank;
|
||||
static bool hretrace;
|
||||
static Timer Timers[3];
|
||||
static pscpu_timestamp_t lastts;
|
||||
|
||||
static uint32 CalcNextEvent(void)
|
||||
{
|
||||
uint32 next_event = 1024; //
|
||||
|
||||
for(unsigned i = 0; i < 3; i++)
|
||||
{
|
||||
if(!(Timers[i].Mode & 0x30)) // If IRQ is disabled, abort for this timer(don't look at IRQDone for this test, or things will break since its resetting is deferred!).
|
||||
continue;
|
||||
|
||||
if((Timers[i].Mode & 0x8) && (Timers[i].Counter == 0) && (Timers[i].Target == 0) && !Timers[i].IRQDone)
|
||||
{
|
||||
next_event = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
if((i == 0 || i == 1) && (Timers[i].Mode & 0x100)) // If clocked by GPU, abort for this timer(will result in poor granularity for pixel-clock-derived timer IRQs, but whatever).
|
||||
continue;
|
||||
|
||||
if(Timers[i].DoZeCounting <= 0)
|
||||
continue;
|
||||
|
||||
if((i == 0x2) && (Timers[i].Mode & 0x1))
|
||||
continue;
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
const uint32 target = ((Timers[i].Mode & 0x18) && (Timers[i].Counter < Timers[i].Target)) ? Timers[i].Target : 0x10000;
|
||||
const uint32 count_delta = target - Timers[i].Counter;
|
||||
uint32 tmp_clocks;
|
||||
|
||||
if((i == 0x2) && (Timers[i].Mode & 0x200))
|
||||
tmp_clocks = (count_delta * 8) - Timers[i].Div8Counter;
|
||||
else
|
||||
tmp_clocks = count_delta;
|
||||
|
||||
if(next_event > tmp_clocks)
|
||||
next_event = tmp_clocks;
|
||||
}
|
||||
|
||||
return(next_event);
|
||||
}
|
||||
|
||||
static bool TimerMatch(unsigned i)
|
||||
{
|
||||
bool irq_exact = false;
|
||||
|
||||
Timers[i].Mode |= 0x0800;
|
||||
|
||||
if(Timers[i].Mode & 0x008)
|
||||
Timers[i].Counter %= std::max<uint32>(1, Timers[i].Target);
|
||||
|
||||
if((Timers[i].Mode & 0x10) && !Timers[i].IRQDone)
|
||||
{
|
||||
if(Timers[i].Counter == 0 || Timers[i].Counter == Timers[i].Target)
|
||||
irq_exact = true;
|
||||
|
||||
#if 1
|
||||
{
|
||||
const uint16 lateness = (Timers[i].Mode & 0x008) ? Timers[i].Counter : (Timers[i].Counter - Timers[i].Target);
|
||||
|
||||
if(lateness > ((i == 1 && (Timers[i].Mode & 0x100)) ? 0 : 3))
|
||||
PSX_DBG(PSX_DBG_WARNING, "[TIMER] Timer %d match IRQ trigger late: %u\n", i, lateness);
|
||||
}
|
||||
#endif
|
||||
|
||||
Timers[i].IRQDone = true;
|
||||
IRQ_Assert(IRQ_TIMER_0 + i, true);
|
||||
IRQ_Assert(IRQ_TIMER_0 + i, false);
|
||||
}
|
||||
|
||||
return irq_exact;
|
||||
}
|
||||
|
||||
static bool TimerOverflow(unsigned i)
|
||||
{
|
||||
bool irq_exact = false;
|
||||
|
||||
Timers[i].Mode |= 0x1000;
|
||||
Timers[i].Counter &= 0xFFFF;
|
||||
|
||||
if((Timers[i].Mode & 0x20) && !Timers[i].IRQDone)
|
||||
{
|
||||
if(Timers[i].Counter == 0)
|
||||
irq_exact = true;
|
||||
|
||||
#if 1
|
||||
if(Timers[i].Counter > ((i == 1 && (Timers[i].Mode & 0x100)) ? 0 : 3))
|
||||
PSX_DBG(PSX_DBG_WARNING, "[TIMER] Timer %d overflow IRQ trigger late: %u\n", i, Timers[i].Counter);
|
||||
#endif
|
||||
|
||||
Timers[i].IRQDone = true;
|
||||
IRQ_Assert(IRQ_TIMER_0 + i, true);
|
||||
IRQ_Assert(IRQ_TIMER_0 + i, false);
|
||||
}
|
||||
|
||||
return irq_exact;
|
||||
}
|
||||
|
||||
static void ClockTimer(int i, uint32 clocks)
|
||||
{
|
||||
if(Timers[i].DoZeCounting <= 0)
|
||||
clocks = 0;
|
||||
|
||||
if(i == 0x2)
|
||||
{
|
||||
uint32 d8_clocks;
|
||||
|
||||
Timers[i].Div8Counter += clocks;
|
||||
d8_clocks = Timers[i].Div8Counter >> 3;
|
||||
Timers[i].Div8Counter &= 0x7;
|
||||
|
||||
if(Timers[i].Mode & 0x200) // Divide by 8, at least for timer 0x2
|
||||
clocks = d8_clocks;
|
||||
|
||||
if(Timers[i].Mode & 1)
|
||||
clocks = 0;
|
||||
}
|
||||
|
||||
if((Timers[i].Mode & 0x008) && Timers[i].Target == 0 && Timers[i].Counter == 0)
|
||||
TimerMatch(i);
|
||||
else if(clocks)
|
||||
{
|
||||
uint32 before = Timers[i].Counter;
|
||||
|
||||
Timers[i].Counter += clocks;
|
||||
|
||||
if(Timers[i].Mode & 0x40)
|
||||
Timers[i].IRQDone = false;
|
||||
|
||||
bool irq_exact = false;
|
||||
|
||||
//
|
||||
// Target match handling
|
||||
//
|
||||
if((before < Timers[i].Target && Timers[i].Counter >= Timers[i].Target) || (Timers[i].Counter >= Timers[i].Target + 0x10000))
|
||||
irq_exact |= TimerMatch(i);
|
||||
|
||||
//
|
||||
// Overflow handling
|
||||
//
|
||||
if(Timers[i].Counter >= 0x10000)
|
||||
irq_exact |= TimerOverflow(i);
|
||||
|
||||
//
|
||||
if((Timers[i].Mode & 0x40) && !irq_exact)
|
||||
Timers[i].IRQDone = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TIMER_SetVBlank(bool status)
|
||||
{
|
||||
switch(Timers[1].Mode & 0x7)
|
||||
{
|
||||
case 0x1:
|
||||
Timers[1].DoZeCounting = !status;
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
if(vblank && !status)
|
||||
{
|
||||
Timers[1].Counter = 0;
|
||||
if(Timers[1].Counter == Timers[1].Target)
|
||||
TimerMatch(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
Timers[1].DoZeCounting = status;
|
||||
if(vblank && !status)
|
||||
{
|
||||
Timers[1].Counter = 0;
|
||||
if(Timers[1].Counter == Timers[1].Target)
|
||||
TimerMatch(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x7:
|
||||
if(Timers[1].DoZeCounting == -1)
|
||||
{
|
||||
if(!vblank && status)
|
||||
Timers[1].DoZeCounting = 0;
|
||||
}
|
||||
else if(Timers[1].DoZeCounting == 0)
|
||||
{
|
||||
if(vblank && !status)
|
||||
Timers[1].DoZeCounting = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
vblank = status;
|
||||
}
|
||||
|
||||
void TIMER_SetHRetrace(bool status)
|
||||
{
|
||||
if(hretrace && !status)
|
||||
{
|
||||
if((Timers[0].Mode & 0x7) == 0x3)
|
||||
{
|
||||
Timers[0].Counter = 0;
|
||||
|
||||
if(Timers[0].Counter == Timers[0].Target)
|
||||
TimerMatch(0);
|
||||
}
|
||||
}
|
||||
|
||||
hretrace = status;
|
||||
}
|
||||
|
||||
void TIMER_AddDotClocks(uint32 count)
|
||||
{
|
||||
if(Timers[0].Mode & 0x100)
|
||||
ClockTimer(0, count);
|
||||
}
|
||||
|
||||
void TIMER_ClockHRetrace(void)
|
||||
{
|
||||
if(Timers[1].Mode & 0x100)
|
||||
ClockTimer(1, 1);
|
||||
}
|
||||
|
||||
pscpu_timestamp_t TIMER_Update(const pscpu_timestamp_t timestamp)
|
||||
{
|
||||
int32 cpu_clocks = timestamp - lastts;
|
||||
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
uint32 timer_clocks = cpu_clocks;
|
||||
|
||||
if(Timers[i].Mode & 0x100)
|
||||
continue;
|
||||
|
||||
ClockTimer(i, timer_clocks);
|
||||
}
|
||||
|
||||
lastts = timestamp;
|
||||
|
||||
return(timestamp + CalcNextEvent());
|
||||
}
|
||||
|
||||
static void CalcCountingStart(unsigned which)
|
||||
{
|
||||
Timers[which].DoZeCounting = true;
|
||||
|
||||
switch(which)
|
||||
{
|
||||
case 1:
|
||||
switch(Timers[which].Mode & 0x07)
|
||||
{
|
||||
case 0x1:
|
||||
Timers[which].DoZeCounting = !vblank;
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
Timers[which].DoZeCounting = vblank;
|
||||
break;
|
||||
|
||||
case 0x7:
|
||||
Timers[which].DoZeCounting = -1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void TIMER_Write(const pscpu_timestamp_t timestamp, uint32 A, uint16 V)
|
||||
{
|
||||
TIMER_Update(timestamp);
|
||||
|
||||
int which = (A >> 4) & 0x3;
|
||||
|
||||
V <<= (A & 3) * 8;
|
||||
|
||||
PSX_DBGINFO("[TIMER] Write: %08x %04x\n", A, V);
|
||||
|
||||
if(which >= 3)
|
||||
return;
|
||||
|
||||
switch(A & 0xC)
|
||||
{
|
||||
case 0x0: Timers[which].IRQDone = false;
|
||||
Timers[which].Counter = V & 0xFFFF;
|
||||
break;
|
||||
|
||||
case 0x4: Timers[which].Mode = (V & 0x3FF) | (Timers[which].Mode & 0x1C00);
|
||||
Timers[which].IRQDone = false;
|
||||
Timers[which].Counter = 0;
|
||||
|
||||
CalcCountingStart(which); // Call after setting .Mode
|
||||
break;
|
||||
|
||||
case 0x8: Timers[which].Target = V & 0xFFFF;
|
||||
break;
|
||||
|
||||
case 0xC: // Open bus
|
||||
break;
|
||||
}
|
||||
|
||||
if(Timers[which].Counter == Timers[which].Target)
|
||||
TimerMatch(which);
|
||||
|
||||
PSX_SetEventNT(PSX_EVENT_TIMER, timestamp + CalcNextEvent());
|
||||
}
|
||||
|
||||
uint16 TIMER_Read(const pscpu_timestamp_t timestamp, uint32 A)
|
||||
{
|
||||
uint16 ret = 0;
|
||||
int which = (A >> 4) & 0x3;
|
||||
|
||||
if(which >= 3)
|
||||
{
|
||||
PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A);
|
||||
|
||||
return(ret >> ((A & 3) * 8));
|
||||
}
|
||||
|
||||
TIMER_Update(timestamp);
|
||||
|
||||
switch(A & 0xC)
|
||||
{
|
||||
case 0x0: ret = Timers[which].Counter;
|
||||
break;
|
||||
|
||||
case 0x4: ret = Timers[which].Mode;
|
||||
Timers[which].Mode &= ~0x1000;
|
||||
if(Timers[which].Counter != Timers[which].Target)
|
||||
Timers[which].Mode &= ~0x0800;
|
||||
break;
|
||||
|
||||
case 0x8: ret = Timers[which].Target;
|
||||
break;
|
||||
|
||||
case 0xC: PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A);
|
||||
break;
|
||||
}
|
||||
|
||||
return(ret >> ((A & 3) * 8));
|
||||
}
|
||||
|
||||
|
||||
void TIMER_ResetTS(void)
|
||||
{
|
||||
lastts = 0;
|
||||
}
|
||||
|
||||
|
||||
void TIMER_Power(void)
|
||||
{
|
||||
lastts = 0;
|
||||
|
||||
hretrace = false;
|
||||
vblank = false;
|
||||
memset(Timers, 0, sizeof(Timers));
|
||||
}
|
||||
|
||||
void TIMER_SyncState(bool isReader, EW::NewState *ns)
|
||||
|
@ -496,6 +504,9 @@ void TIMER_SetRegister(unsigned int which, uint32 value)
|
|||
break;
|
||||
}
|
||||
|
||||
if (Timers[tw].Counter == Timers[tw].Target)
|
||||
TimerMatch(tw);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue