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

@ -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

View File

@ -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 &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);
//
// 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 &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);
//
// 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

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_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] =

View File

@ -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;

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)
{
assert(type > PSX_EVENT__SYNFIRST && type < PSX_EVENT__SYNLAST);
event_list_entry *e = &events[type];
if(next_timestamp < e->event_time)

View File

@ -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);
}