update psx core to 0.9.38.7

This commit is contained in:
zeromus 2015-10-02 00:52:56 -05:00
parent 29767ba522
commit 5cfc1dae2a
8 changed files with 2539 additions and 2420 deletions

Binary file not shown.

View File

@ -81,3 +81,11 @@
[OK] psx/memcard : change to debug output [OK] psx/memcard : change to debug output
0.9.38.4 -> 0.9.38.6 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

View File

@ -2,40 +2,40 @@
#define __MDFN_PSX_CPU_H #define __MDFN_PSX_CPU_H
/* /*
Load delay notes: Load delay notes:
// Takes 1 less // Takes 1 less
".set noreorder\n\t" ".set noreorder\n\t"
".set nomacro\n\t" ".set nomacro\n\t"
"lw %0, 0(%2)\n\t" "lw %0, 0(%2)\n\t"
"nop\n\t" "nop\n\t"
"nop\n\t" "nop\n\t"
"or %0, %1, %1\n\t" "or %0, %1, %1\n\t"
// cycle than this: // cycle than this:
".set noreorder\n\t" ".set noreorder\n\t"
".set nomacro\n\t" ".set nomacro\n\t"
"lw %0, 0(%2)\n\t" "lw %0, 0(%2)\n\t"
"nop\n\t" "nop\n\t"
"or %0, %1, %1\n\t" "or %0, %1, %1\n\t"
"nop\n\t" "nop\n\t"
// Both of these // Both of these
".set noreorder\n\t" ".set noreorder\n\t"
".set nomacro\n\t" ".set nomacro\n\t"
"lw %0, 0(%2)\n\t" "lw %0, 0(%2)\n\t"
"nop\n\t" "nop\n\t"
"nop\n\t" "nop\n\t"
"or %1, %0, %0\n\t" "or %1, %0, %0\n\t"
// take same...(which is kind of odd). // take same...(which is kind of odd).
".set noreorder\n\t" ".set noreorder\n\t"
".set nomacro\n\t" ".set nomacro\n\t"
"lw %0, 0(%2)\n\t" "lw %0, 0(%2)\n\t"
"nop\n\t" "nop\n\t"
"or %1, %0, %0\n\t" "or %1, %0, %0\n\t"
"nop\n\t" "nop\n\t"
*/ */
#include "gte.h" #include "gte.h"
@ -45,218 +45,217 @@ namespace MDFN_IEN_PSX
#define PS_CPU_EMULATE_ICACHE 1 #define PS_CPU_EMULATE_ICACHE 1
class PS_CPU class PS_CPU
{ {
public: public:
PS_CPU() MDFN_COLD; PS_CPU() MDFN_COLD;
~PS_CPU() MDFN_COLD; ~PS_CPU() MDFN_COLD;
template<bool isReader>void SyncState(EW::NewState *ns); 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 // 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. // will always be multiples of 4.
enum { FAST_MAP_SHIFT = 16 }; enum { FAST_MAP_SHIFT = 16 };
enum { FAST_MAP_PSIZE = 1 << FAST_MAP_SHIFT }; enum { FAST_MAP_PSIZE = 1 << FAST_MAP_SHIFT };
void SetFastMap(void *region_mem, uint32 region_address, uint32 region_size); void SetFastMap(void *region_mem, uint32 region_address, uint32 region_size);
INLINE void SetEventNT(const pscpu_timestamp_t next_event_ts_arg) INLINE void SetEventNT(const pscpu_timestamp_t next_event_ts_arg)
{ {
next_event_ts = next_event_ts_arg; next_event_ts = next_event_ts_arg;
} }
pscpu_timestamp_t Run(pscpu_timestamp_t timestamp_in, bool BIOSPrintMode, bool ILHMode); pscpu_timestamp_t Run(pscpu_timestamp_t timestamp_in, bool BIOSPrintMode, bool ILHMode);
void Power(void) MDFN_COLD; void Power(void) MDFN_COLD;
// which ranges 0-5, inclusive // which ranges 0-5, inclusive
void AssertIRQ(unsigned which, bool asserted); void AssertIRQ(unsigned which, bool asserted);
void SetHalt(bool status); void SetHalt(bool status);
// TODO eventually: factor BIU address decoding directly in the CPU core somehow without hurting speed. // TODO eventually: factor BIU address decoding directly in the CPU core somehow without hurting speed.
void SetBIU(uint32 val); void SetBIU(uint32 val);
uint32 GetBIU(void); uint32 GetBIU(void);
private: private:
uint32 GPR[32 + 1]; // GPR[32] Used as dummy in load delay simulation(indexing past the end of real GPR) uint32 GPR[32 + 1]; // GPR[32] Used as dummy in load delay simulation(indexing past the end of real GPR)
uint32 LO; uint32 LO;
uint32 HI; uint32 HI;
uint32 BACKED_PC; uint32 BACKED_PC;
uint32 BACKED_new_PC; uint32 BACKED_new_PC;
uint32 BACKED_new_PC_mask; uint32 BACKED_new_PC_mask;
uint32 IPCache; uint32 IPCache;
void RecalcIPCache(void); void RecalcIPCache(void);
bool Halted; bool Halted;
uint32 BACKED_LDWhich; uint32 BACKED_LDWhich;
uint32 BACKED_LDValue; uint32 BACKED_LDValue;
uint32 LDAbsorb; uint32 LDAbsorb;
pscpu_timestamp_t next_event_ts; pscpu_timestamp_t next_event_ts;
pscpu_timestamp_t gte_ts_done; pscpu_timestamp_t gte_ts_done;
pscpu_timestamp_t muldiv_ts_done; pscpu_timestamp_t muldiv_ts_done;
uint32 BIU; uint32 BIU;
struct __ICache struct __ICache
{ {
uint32 TV; uint32 TV;
uint32 Data; uint32 Data;
}; };
union union
{ {
__ICache ICache[1024]; __ICache ICache[1024];
uint32 ICache_Bulk[2048]; uint32 ICache_Bulk[2048];
}; };
enum enum
{ {
CP0REG_BPC = 3, // PC breakpoint address. CP0REG_BPC = 3, // PC breakpoint address.
CP0REG_BDA = 5, // Data load/store breakpoint address. CP0REG_BDA = 5, // Data load/store breakpoint address.
CP0REG_TAR = 6, // Target address(???) CP0REG_TAR = 6, // Target address(???)
CP0REG_DCIC = 7, // Cache control CP0REG_DCIC = 7, // Cache control
CP0REG_BDAM = 9, // Data load/store address mask. CP0REG_BADVA = 8,
CP0REG_BPCM = 11, // PC breakpoint address mask. CP0REG_BDAM = 9, // Data load/store address mask.
CP0REG_SR = 12, CP0REG_BPCM = 11, // PC breakpoint address mask.
CP0REG_CAUSE = 13, CP0REG_SR = 12,
CP0REG_EPC = 14, CP0REG_CAUSE = 13,
CP0REG_PRID = 15, // Product ID CP0REG_EPC = 14,
CP0REG_ERREG = 16 CP0REG_PRID = 15 // Product ID
}; };
struct struct
{ {
union union
{ {
uint32 Regs[32]; uint32 Regs[32];
struct struct
{ {
uint32 Unused00; uint32 Unused00;
uint32 Unused01; uint32 Unused01;
uint32 Unused02; uint32 Unused02;
uint32 BPC; // RW uint32 BPC; // RW
uint32 Unused04; uint32 Unused04;
uint32 BDA; // RW uint32 BDA; // RW
uint32 TAR; uint32 TAR; // R
uint32 DCIC; // RW uint32 DCIC; // RW
uint32 Unused08; uint32 BADVA; // R
uint32 BDAM; // R/W uint32 BDAM; // R/W
uint32 Unused0A; uint32 Unused0A;
uint32 BPCM; // R/W uint32 BPCM; // R/W
uint32 SR; // R/W uint32 SR; // R/W
uint32 CAUSE; // R/W(partial) uint32 CAUSE; // R/W(partial)
uint32 EPC; // R uint32 EPC; // R
uint32 PRID; // R uint32 PRID; // R
uint32 ERREG; // ?(may not exist, test) };
}; };
}; } CP0;
} CP0;
#if 1 #if 1
//uint32 WrAbsorb; //uint32 WrAbsorb;
//uint8 WrAbsorbShift; //uint8 WrAbsorbShift;
// On read: // On read:
//WrAbsorb = 0; //WrAbsorb = 0;
//WrAbsorbShift = 0; //WrAbsorbShift = 0;
// On write: // On write:
//WrAbsorb >>= (WrAbsorbShift >> 2) & 8; //WrAbsorb >>= (WrAbsorbShift >> 2) & 8;
//WrAbsorbShift -= (WrAbsorbShift >> 2) & 8; //WrAbsorbShift -= (WrAbsorbShift >> 2) & 8;
//WrAbsorb |= (timestamp - pre_write_timestamp) << WrAbsorbShift; //WrAbsorb |= (timestamp - pre_write_timestamp) << WrAbsorbShift;
//WrAbsorbShift += 8; //WrAbsorbShift += 8;
#endif #endif
uint8 ReadAbsorb[0x20 + 1]; uint8 ReadAbsorb[0x20 + 1];
uint8 ReadAbsorbWhich; uint8 ReadAbsorbWhich;
uint8 ReadFudge; uint8 ReadFudge;
//uint32 WriteAbsorb; //uint32 WriteAbsorb;
//uint8 WriteAbsorbCount; //uint8 WriteAbsorbCount;
//uint8 WriteAbsorbMonkey; //uint8 WriteAbsorbMonkey;
uint8 MULT_Tab24[24]; uint8 MULT_Tab24[24];
MultiAccessSizeMem<1024, false> ScratchRAM; MultiAccessSizeMem<1024, false> ScratchRAM;
//PS_GTE GTE; //PS_GTE GTE;
uint8 *FastMap[1 << (32 - FAST_MAP_SHIFT)]; uint8 *FastMap[1 << (32 - FAST_MAP_SHIFT)];
uint8 DummyPage[FAST_MAP_PSIZE]; uint8 DummyPage[FAST_MAP_PSIZE];
enum enum
{ {
EXCEPTION_INT = 0, EXCEPTION_INT = 0,
EXCEPTION_MOD = 1, EXCEPTION_MOD = 1,
EXCEPTION_TLBL = 2, EXCEPTION_TLBL = 2,
EXCEPTION_TLBS = 3, EXCEPTION_TLBS = 3,
EXCEPTION_ADEL = 4, // Address error on load EXCEPTION_ADEL = 4, // Address error on load
EXCEPTION_ADES = 5, // Address error on store EXCEPTION_ADES = 5, // Address error on store
EXCEPTION_IBE = 6, // Instruction bus error EXCEPTION_IBE = 6, // Instruction bus error
EXCEPTION_DBE = 7, // Data bus error EXCEPTION_DBE = 7, // Data bus error
EXCEPTION_SYSCALL = 8, // System call EXCEPTION_SYSCALL = 8, // System call
EXCEPTION_BP = 9, // Breakpoint EXCEPTION_BP = 9, // Breakpoint
EXCEPTION_RI = 10, // Reserved instruction EXCEPTION_RI = 10, // Reserved instruction
EXCEPTION_COPU = 11, // Coprocessor unusable EXCEPTION_COPU = 11, // Coprocessor unusable
EXCEPTION_OV = 12 // Arithmetic overflow EXCEPTION_OV = 12 // Arithmetic overflow
}; };
uint32 Exception(uint32 code, uint32 PC, const uint32 NPM) MDFN_WARN_UNUSED_RESULT; 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<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> T PeekMemory(uint32 address) MDFN_COLD;
template<typename T> void PokeMemory(uint32 address, T value) MDFN_COLD; template<typename T> void PokeMemory(uint32 address, T value) MDFN_COLD;
template<typename T> T ReadMemory(pscpu_timestamp_t &timestamp, uint32 address, bool DS24 = false, bool LWC_timing = false); template<typename T> T ReadMemory(pscpu_timestamp_t &timestamp, uint32 address, bool DS24 = false, bool LWC_timing = false);
template<typename T> void WriteMemory(pscpu_timestamp_t &timestamp, uint32 address, uint32 value, bool DS24 = false); template<typename T> void WriteMemory(pscpu_timestamp_t &timestamp, uint32 address, uint32 value, bool DS24 = false);
// //
// Mednafen debugger stuff follows: // Mednafen debugger stuff follows:
// //
public: public:
void SetCPUHook(void (*cpuh)(const pscpu_timestamp_t timestamp, uint32 pc), void (*addbt)(uint32 from, uint32 to, bool exception)); 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 CheckBreakpoints(void(*callback)(bool write, uint32 address, unsigned int len), uint32 instr);
void* debug_GetScratchRAMPtr() { return ScratchRAM.data8; } void* debug_GetScratchRAMPtr() { return ScratchRAM.data8; }
void* debug_GetGPRPtr() { return GPR; } void* debug_GetGPRPtr() { return GPR; }
enum enum
{ {
GSREG_GPR = 0, GSREG_GPR = 0,
GSREG_PC = 32, GSREG_PC = 32,
GSREG_PC_NEXT, GSREG_PC_NEXT,
GSREG_IN_BD_SLOT, GSREG_IN_BD_SLOT,
GSREG_LO, GSREG_LO,
GSREG_HI, GSREG_HI,
GSREG_SR, GSREG_SR,
GSREG_CAUSE, GSREG_CAUSE,
GSREG_EPC, GSREG_EPC,
}; };
uint32 GetRegister(unsigned int which, char *special, const uint32 special_len); uint32 GetRegister(unsigned int which, char *special, const uint32 special_len);
void SetRegister(unsigned int which, uint32 value); void SetRegister(unsigned int which, uint32 value);
bool PeekCheckICache(uint32 PC, uint32 *iw); bool PeekCheckICache(uint32 PC, uint32 *iw);
uint8 PeekMem8(uint32 A); uint8 PeekMem8(uint32 A);
uint16 PeekMem16(uint32 A); uint16 PeekMem16(uint32 A);
uint32 PeekMem32(uint32 A); uint32 PeekMem32(uint32 A);
void PokeMem8(uint32 A, uint8 V); void PokeMem8(uint32 A, uint8 V);
void PokeMem16(uint32 A, uint16 V); void PokeMem16(uint32 A, uint16 V);
void PokeMem32(uint32 A, uint32 V); void PokeMem32(uint32 A, uint32 V);
private: private:
void (*CPUHook)(const pscpu_timestamp_t timestamp, uint32 pc); void(*CPUHook)(const pscpu_timestamp_t timestamp, uint32 pc);
void (*ADDBT)(uint32 from, uint32 to, bool exception); void(*ADDBT)(uint32 from, uint32 to, bool exception);
}; };
} }

View File

@ -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(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_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_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_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 } #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("slt", "d, s, t", 0, 42, 0),
MK_OP("sltu", "d, s, t", 0, 43, 0), MK_OP("sltu", "d, s, t", 0, 43, 0),
MK_OP_REGIMM("bgez", 0x01), // keep *al before the non-linking versions, due to mask setup.
MK_OP_REGIMM("bgezal", 0x11), MK_OP_REGIMM("bgezal", 0x1F, 0x11),
MK_OP_REGIMM("bltz", 0x00), MK_OP_REGIMM("bltzal", 0x1F, 0x10),
MK_OP_REGIMM("bltzal", 0x10),
MK_OP_REGIMM("bgez", 0x01, 0x01),
MK_OP_REGIMM("bltz", 0x00, 0x00),
MK_OP("j", "P", 2, 0, 0), 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(2, "ctc2", "t, G", 0x06),
MK_COPZ_XFER(3, "ctc3", "t, ?", 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 // COP0 stuff here
MK_COP0_FUNC("rfe", 0x10), 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] = static const char *cop0_names[32] =
{ {
"CPR0", "CPR1", "CPR2", "BPC", "CPR4", "BDA", "TAR", "DCIC", "CPR8", "BDAM", "CPR10", "BPCM", "SR", "CAUSE", "EPC", "PRID", "CPR0", "CPR1", "CPR2", "BPC", "CPR4", "BDA", "TAR", "DCIC", "BADVA", "BDAM", "CPR10", "BPCM", "SR", "CAUSE", "EPC", "PRID",
"ERREG", "CPR17", "CPR18", "CPR19", "CPR20", "CPR21", "CPR22", "CPR23", "CPR24", "CPR25", "CPR26", "CPR27", "CPR28", "CPR29", "CPR30", "CPR31" "CPR16", "CPR17", "CPR18", "CPR19", "CPR20", "CPR21", "CPR22", "CPR23", "CPR24", "CPR25", "CPR26", "CPR27", "CPR28", "CPR29", "CPR30", "CPR31"
}; };
static const char *gte_cr_names[32] = static const char *gte_cr_names[32] =

View File

@ -796,22 +796,6 @@ static INLINE int32 Lm_G(unsigned int which, int32 value)
// limit to 4096, not 4095 // limit to 4096, not 4095
static INLINE int32 Lm_H(int32 value) 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) if(value < 0)
{ {
value = 0; value = 0;

View File

@ -272,7 +272,6 @@ static void RebaseTS(const pscpu_timestamp_t timestamp)
void PSX_SetEventNT(const int type, const pscpu_timestamp_t next_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]; event_list_entry *e = &events[type];
if(next_timestamp < e->event_time) if(next_timestamp < e->event_time)

View File

@ -31,23 +31,22 @@
For timer2: For timer2:
0x1 = timer stopped(TODO: confirm on real system) 0x1 = timer stopped(TODO: confirm on real system)
Target mode enabled 0x008 Target match counter reset enable 0x008
IRQ enable 0x010 Target match IRQ enable 0x010
--?Affects 0x400 status flag?-- 0x020 Overflow IRQ enable 0x020
IRQ evaluation auto-reset 0x040 IRQ evaluation auto-reset 0x040
--unknown-- 0x080 --unknown-- 0x080
Clock selection 0x100 Clock selection 0x100
Divide by 8(timer 2 only?) 0x200 Divide by 8(timer 2 only?) 0x200
Counter: Counter:
Reset to 0 on writes to the mode/status register. Reset to 0 on writes to the mode/status register.
Status flags: Status flags:
Unknown flag 0x0400 Current IRQ line status? 0x0400
Compare flag 0x0800 Compare flag 0x0800
Cleared on mode/status read. Cleared on mode/status read.
Set when: //ever Counter == 0(proooobably, need to investigate lower 3 bits in relation to this). Set repeatedly while counter == target.
Overflow/Carry flag 0x1000 Overflow/Carry flag 0x1000
Cleared on mode/status read. Cleared on mode/status read.
@ -58,20 +57,17 @@
Cleared on writes to the mode/status register, on writes to the count register, and apparently automatically when the counter 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] 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 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. I doubt any games rely on this, but who knows. Maybe a PSX equivalent of the PC Engine "Battle Royale"? ;) 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"? ;)
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. 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,
Dec. 26, 2011 Note so keep this in mind when writing test programs(IE keep reading the count value until two consecutive reads return the same value).
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 Currently not emulated.
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).
*/ */
/* /*
@ -86,10 +82,10 @@ namespace MDFN_IEN_PSX
struct Timer struct Timer
{ {
uint32 Mode; uint32 Mode;
int32 Counter; // Only 16-bit, but 32-bit here for detecting counting past target. uint32 Counter; // Only 16-bit, but 32-bit here for detecting counting past target.
int32 Target; uint32 Target;
int32 Div8Counter; uint32 Div8Counter;
bool IRQDone; bool IRQDone;
int32 DoZeCounting; int32 DoZeCounting;
@ -100,17 +96,13 @@ static bool hretrace;
static Timer Timers[3]; static Timer Timers[3];
static pscpu_timestamp_t lastts; static pscpu_timestamp_t lastts;
static int32 CalcNextEvent(int32 next_event) static uint32 CalcNextEvent(void)
{ {
for(int i = 0; i < 3; i++) uint32 next_event = 1024; //
for(unsigned i = 0; i < 3; i++)
{ {
int32 target; 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!).
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; continue;
if((Timers[i].Mode & 0x8) && (Timers[i].Counter == 0) && (Timers[i].Target == 0) && !Timers[i].IRQDone) if((Timers[i].Mode & 0x8) && (Timers[i].Counter == 0) && (Timers[i].Target == 0) && !Timers[i].IRQDone)
@ -119,48 +111,94 @@ static int32 CalcNextEvent(int32 next_event)
continue; continue;
} }
target = ((Timers[i].Mode & 0x8) && (Timers[i].Counter < Timers[i].Target)) ? Timers[i].Target : 0x10000; //
//
count_delta = target - Timers[i].Counter; 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).
if(count_delta <= 0)
{
PSX_DBG(PSX_DBG_ERROR, "timer %d count_delta <= 0!!! %d %d\n", i, target, Timers[i].Counter);
continue; continue;
}
{ if(Timers[i].DoZeCounting <= 0)
int32 tmp_clocks; continue;
if(Timers[i].DoZeCounting <= 0) if((i == 0x2) && (Timers[i].Mode & 0x1))
continue; 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)) if((i == 0x2) && (Timers[i].Mode & 0x200))
{ tmp_clocks = (count_delta * 8) - Timers[i].Div8Counter;
assert(Timers[i].Div8Counter >= 0 && Timers[i].Div8Counter < 8); else
tmp_clocks = ((count_delta - 1) * 8) + (8 - Timers[i].Div8Counter); tmp_clocks = count_delta;
}
else
tmp_clocks = count_delta;
assert(tmp_clocks > 0); if(next_event > tmp_clocks)
next_event = tmp_clocks;
if(next_event > tmp_clocks)
next_event = tmp_clocks;
}
} }
return(next_event); 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) static void ClockTimer(int i, uint32 clocks)
{ {
int32 before = Timers[i].Counter;
int32 target = 0x10000;
bool zero_tm = false;
if(Timers[i].DoZeCounting <= 0) if(Timers[i].DoZeCounting <= 0)
clocks = 0; clocks = 0;
@ -170,7 +208,7 @@ static void ClockTimer(int i, uint32 clocks)
Timers[i].Div8Counter += clocks; Timers[i].Div8Counter += clocks;
d8_clocks = Timers[i].Div8Counter >> 3; d8_clocks = Timers[i].Div8Counter >> 3;
Timers[i].Div8Counter -= d8_clocks << 3; Timers[i].Div8Counter &= 0x7;
if(Timers[i].Mode & 0x200) // Divide by 8, at least for timer 0x2 if(Timers[i].Mode & 0x200) // Divide by 8, at least for timer 0x2
clocks = d8_clocks; clocks = d8_clocks;
@ -179,57 +217,35 @@ static void ClockTimer(int i, uint32 clocks)
clocks = 0; clocks = 0;
} }
if(Timers[i].Mode & 0x008) if((Timers[i].Mode & 0x008) && Timers[i].Target == 0 && Timers[i].Counter == 0)
target = Timers[i].Target; TimerMatch(i);
else if(clocks)
{
uint32 before = Timers[i].Counter;
if(target == 0 && Timers[i].Counter == 0)
zero_tm = true;
else
Timers[i].Counter += clocks; Timers[i].Counter += clocks;
if(clocks && (Timers[i].Mode & 0x40)) if(Timers[i].Mode & 0x40)
Timers[i].IRQDone = false; Timers[i].IRQDone = false;
if((before < target && Timers[i].Counter >= target) || zero_tm || Timers[i].Counter > 0xFFFF) bool irq_exact = false;
{
#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 //
// 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);
Timers[i].Mode |= 0x0800; //
if((Timers[i].Mode & 0x40) && !irq_exact)
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; Timers[i].IRQDone = false;
} }
} }
void TIMER_SetVBlank(bool status) void TIMER_SetVBlank(bool status)
@ -242,13 +258,21 @@ void TIMER_SetVBlank(bool status)
case 0x3: case 0x3:
if(vblank && !status) if(vblank && !status)
{
Timers[1].Counter = 0; Timers[1].Counter = 0;
if(Timers[1].Counter == Timers[1].Target)
TimerMatch(1);
}
break; break;
case 0x5: case 0x5:
Timers[1].DoZeCounting = status; Timers[1].DoZeCounting = status;
if(vblank && !status) if(vblank && !status)
{
Timers[1].Counter = 0; Timers[1].Counter = 0;
if(Timers[1].Counter == Timers[1].Target)
TimerMatch(1);
}
break; break;
case 0x7: case 0x7:
@ -272,7 +296,12 @@ void TIMER_SetHRetrace(bool status)
if(hretrace && !status) if(hretrace && !status)
{ {
if((Timers[0].Mode & 0x7) == 0x3) if((Timers[0].Mode & 0x7) == 0x3)
{
Timers[0].Counter = 0; Timers[0].Counter = 0;
if(Timers[0].Counter == Timers[0].Target)
TimerMatch(0);
}
} }
hretrace = status; hretrace = status;
@ -306,7 +335,7 @@ pscpu_timestamp_t TIMER_Update(const pscpu_timestamp_t timestamp)
lastts = timestamp; lastts = timestamp;
return(timestamp + CalcNextEvent(1024)); return(timestamp + CalcNextEvent());
} }
static void CalcCountingStart(unsigned which) static void CalcCountingStart(unsigned which)
@ -349,40 +378,16 @@ void TIMER_Write(const pscpu_timestamp_t timestamp, uint32 A, uint16 V)
if(which >= 3) if(which >= 3)
return; return;
// TODO: See if the "Timers[which].Counter" part of the IRQ if() statements below is what a real PSX does.
switch(A & 0xC) switch(A & 0xC)
{ {
case 0x0: Timers[which].IRQDone = false; 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; Timers[which].Counter = V & 0xFFFF;
break; break;
case 0x4: Timers[which].Mode = (V & 0x3FF) | (Timers[which].Mode & 0x1C00); case 0x4: Timers[which].Mode = (V & 0x3FF) | (Timers[which].Mode & 0x1C00);
Timers[which].IRQDone = false; 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; Timers[which].Counter = 0;
#endif
CalcCountingStart(which); // Call after setting .Mode CalcCountingStart(which); // Call after setting .Mode
break; break;
@ -393,9 +398,10 @@ void TIMER_Write(const pscpu_timestamp_t timestamp, uint32 A, uint16 V)
break; break;
} }
// TIMER_Update(timestamp); if(Timers[which].Counter == Timers[which].Target)
TimerMatch(which);
PSX_SetEventNT(PSX_EVENT_TIMER, timestamp + CalcNextEvent(1024)); PSX_SetEventNT(PSX_EVENT_TIMER, timestamp + CalcNextEvent());
} }
uint16 TIMER_Read(const pscpu_timestamp_t timestamp, uint32 A) uint16 TIMER_Read(const pscpu_timestamp_t timestamp, uint32 A)
@ -418,7 +424,9 @@ uint16 TIMER_Read(const pscpu_timestamp_t timestamp, uint32 A)
break; break;
case 0x4: ret = Timers[which].Mode; case 0x4: ret = Timers[which].Mode;
Timers[which].Mode &= ~0x1800; Timers[which].Mode &= ~0x1000;
if(Timers[which].Counter != Timers[which].Target)
Timers[which].Mode &= ~0x0800;
break; break;
case 0x8: ret = Timers[which].Target; case 0x8: ret = Timers[which].Target;
@ -496,6 +504,9 @@ void TIMER_SetRegister(unsigned int which, uint32 value)
break; break;
} }
if (Timers[tw].Counter == Timers[tw].Target)
TimerMatch(tw);
} }