mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-18 22:35:39 +00:00
CPU: Implement cop0 execution and data breakpoints
This commit is contained in:
parent
22fdd80cc3
commit
442b801d39
|
@ -23,7 +23,6 @@ static void SetPC(u32 new_pc);
|
||||||
static void UpdateLoadDelay();
|
static void UpdateLoadDelay();
|
||||||
static void Branch(u32 target);
|
static void Branch(u32 target);
|
||||||
static void FlushPipeline();
|
static void FlushPipeline();
|
||||||
static void UpdateDebugDispatcherFlag();
|
|
||||||
|
|
||||||
State g_state;
|
State g_state;
|
||||||
bool g_using_interpreter = false;
|
bool g_using_interpreter = false;
|
||||||
|
@ -227,34 +226,13 @@ ALWAYS_INLINE_RELEASE void Branch(u32 target)
|
||||||
g_state.branch_was_taken = true;
|
g_state.branch_was_taken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE static u32 GetExceptionVector(Exception excode)
|
ALWAYS_INLINE static u32 GetExceptionVector(bool debug_exception = false)
|
||||||
{
|
{
|
||||||
const u32 base = g_state.cop0_regs.sr.BEV ? UINT32_C(0xbfc00100) : UINT32_C(0x80000000);
|
const u32 base = g_state.cop0_regs.sr.BEV ? UINT32_C(0xbfc00100) : UINT32_C(0x80000000);
|
||||||
|
return base | (debug_exception ? UINT32_C(0x00000040) : UINT32_C(0x00000080));
|
||||||
#if 0
|
|
||||||
// apparently this isn't correct...
|
|
||||||
switch (excode)
|
|
||||||
{
|
|
||||||
case Exception::BP:
|
|
||||||
return base | UINT32_C(0x00000040);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return base | UINT32_C(0x00000080);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return base | UINT32_C(0x00000080);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RaiseException(Exception excode)
|
ALWAYS_INLINE_RELEASE static void RaiseException(u32 CAUSE_bits, u32 EPC, u32 vector)
|
||||||
{
|
|
||||||
RaiseException(Cop0Registers::CAUSE::MakeValueForException(excode, g_state.current_instruction_in_branch_delay_slot,
|
|
||||||
g_state.current_instruction_was_branch_taken,
|
|
||||||
g_state.current_instruction.cop.cop_n),
|
|
||||||
g_state.current_instruction_pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RaiseException(u32 CAUSE_bits, u32 EPC)
|
|
||||||
{
|
{
|
||||||
g_state.cop0_regs.EPC = EPC;
|
g_state.cop0_regs.EPC = EPC;
|
||||||
g_state.cop0_regs.cause.bits = (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::EXCEPTION_WRITE_MASK) |
|
g_state.cop0_regs.cause.bits = (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::EXCEPTION_WRITE_MASK) |
|
||||||
|
@ -264,10 +242,10 @@ void RaiseException(u32 CAUSE_bits, u32 EPC)
|
||||||
if (g_state.cop0_regs.cause.Excode != Exception::INT && g_state.cop0_regs.cause.Excode != Exception::Syscall &&
|
if (g_state.cop0_regs.cause.Excode != Exception::INT && g_state.cop0_regs.cause.Excode != Exception::Syscall &&
|
||||||
g_state.cop0_regs.cause.Excode != Exception::BP)
|
g_state.cop0_regs.cause.Excode != Exception::BP)
|
||||||
{
|
{
|
||||||
Log_DebugPrintf("Exception %u at 0x%08X (epc=0x%08X, BD=%s, CE=%u)",
|
Log_DevPrintf("Exception %u at 0x%08X (epc=0x%08X, BD=%s, CE=%u)",
|
||||||
static_cast<u8>(g_state.cop0_regs.cause.Excode.GetValue()), g_state.current_instruction_pc,
|
static_cast<u8>(g_state.cop0_regs.cause.Excode.GetValue()), g_state.current_instruction_pc,
|
||||||
g_state.cop0_regs.EPC, g_state.cop0_regs.cause.BD ? "true" : "false",
|
g_state.cop0_regs.EPC, g_state.cop0_regs.cause.BD ? "true" : "false",
|
||||||
g_state.cop0_regs.cause.CE.GetValue());
|
g_state.cop0_regs.cause.CE.GetValue());
|
||||||
DisassembleAndPrint(g_state.current_instruction_pc, 4, 0);
|
DisassembleAndPrint(g_state.current_instruction_pc, 4, 0);
|
||||||
if (s_trace_to_log)
|
if (s_trace_to_log)
|
||||||
{
|
{
|
||||||
|
@ -291,11 +269,36 @@ void RaiseException(u32 CAUSE_bits, u32 EPC)
|
||||||
g_state.cop0_regs.sr.mode_bits <<= 2;
|
g_state.cop0_regs.sr.mode_bits <<= 2;
|
||||||
|
|
||||||
// flush the pipeline - we don't want to execute the previously fetched instruction
|
// flush the pipeline - we don't want to execute the previously fetched instruction
|
||||||
g_state.regs.npc = GetExceptionVector(g_state.cop0_regs.cause.Excode);
|
g_state.regs.npc = vector;
|
||||||
g_state.exception_raised = true;
|
g_state.exception_raised = true;
|
||||||
FlushPipeline();
|
FlushPipeline();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE_RELEASE static void DispatchCop0Breakpoint()
|
||||||
|
{
|
||||||
|
// When a breakpoint address match occurs the PSX jumps to 80000040h (ie. unlike normal exceptions, not to 80000080h).
|
||||||
|
// The Excode value in the CAUSE register is set to 09h (same as BREAK opcode), and EPC contains the return address,
|
||||||
|
// as usually. One of the first things to be done in the exception handler is to disable breakpoints (eg. if the
|
||||||
|
// any-jump break is enabled, then it must be disabled BEFORE jumping from 80000040h to the actual exception handler).
|
||||||
|
RaiseException(Cop0Registers::CAUSE::MakeValueForException(
|
||||||
|
Exception::BP, g_state.current_instruction_in_branch_delay_slot,
|
||||||
|
g_state.current_instruction_was_branch_taken, g_state.current_instruction.cop.cop_n),
|
||||||
|
g_state.current_instruction_pc, GetExceptionVector(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RaiseException(u32 CAUSE_bits, u32 EPC)
|
||||||
|
{
|
||||||
|
RaiseException(CAUSE_bits, EPC, GetExceptionVector());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RaiseException(Exception excode)
|
||||||
|
{
|
||||||
|
RaiseException(Cop0Registers::CAUSE::MakeValueForException(excode, g_state.current_instruction_in_branch_delay_slot,
|
||||||
|
g_state.current_instruction_was_branch_taken,
|
||||||
|
g_state.current_instruction.cop.cop_n),
|
||||||
|
g_state.current_instruction_pc, GetExceptionVector());
|
||||||
|
}
|
||||||
|
|
||||||
void SetExternalInterrupt(u8 bit)
|
void SetExternalInterrupt(u8 bit)
|
||||||
{
|
{
|
||||||
g_state.cop0_regs.cause.Ip |= static_cast<u8>(1u << bit);
|
g_state.cop0_regs.cause.Ip |= static_cast<u8>(1u << bit);
|
||||||
|
@ -466,6 +469,7 @@ ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value)
|
||||||
g_state.cop0_regs.dcic.bits =
|
g_state.cop0_regs.dcic.bits =
|
||||||
(g_state.cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK);
|
(g_state.cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK);
|
||||||
Log_WarningPrintf("COP0 DCIC <- %08X (now %08X)", value, g_state.cop0_regs.dcic.bits);
|
Log_WarningPrintf("COP0 DCIC <- %08X (now %08X)", value, g_state.cop0_regs.dcic.bits);
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -488,12 +492,83 @@ ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log_DevPrintf("Unknown COP0 reg %u", ZeroExtend32(static_cast<u8>(reg)));
|
Log_WarningPrintf("Unknown COP0 reg write %u (%08X)", ZeroExtend32(static_cast<u8>(reg)), value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PrintInstruction(u32 bits, u32 pc, Registers* regs)
|
ALWAYS_INLINE_RELEASE void Cop0ExecutionBreakpointCheck()
|
||||||
|
{
|
||||||
|
if (!g_state.cop0_regs.dcic.ExecutionBreakpointsEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const u32 pc = g_state.current_instruction_pc;
|
||||||
|
const u32 bpc = g_state.cop0_regs.BPC;
|
||||||
|
const u32 bpcm = g_state.cop0_regs.BPCM;
|
||||||
|
|
||||||
|
// Break condition is "((PC XOR BPC) AND BPCM)=0".
|
||||||
|
if (bpcm == 0 || ((pc ^ bpc) & bpcm) != 0u)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log_DevPrintf("Cop0 execution breakpoint at %08X", pc);
|
||||||
|
g_state.cop0_regs.dcic.status_any_break = true;
|
||||||
|
g_state.cop0_regs.dcic.status_bpc_code_break = true;
|
||||||
|
DispatchCop0Breakpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<MemoryAccessType type>
|
||||||
|
ALWAYS_INLINE_RELEASE void Cop0DataBreakpointCheck(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
if constexpr (type == MemoryAccessType::Read)
|
||||||
|
{
|
||||||
|
if (!g_state.cop0_regs.dcic.DataReadBreakpointsEnabled())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!g_state.cop0_regs.dcic.DataWriteBreakpointsEnabled())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break condition is "((addr XOR BDA) AND BDAM)=0".
|
||||||
|
const u32 bda = g_state.cop0_regs.BDA;
|
||||||
|
const u32 bdam = g_state.cop0_regs.BDAM;
|
||||||
|
if (bdam == 0 || ((address ^ bda) & bdam) != 0u)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log_DevPrintf("Cop0 data breakpoint for %08X at %08X", address, g_state.current_instruction_pc);
|
||||||
|
|
||||||
|
g_state.cop0_regs.dcic.status_any_break = true;
|
||||||
|
g_state.cop0_regs.dcic.status_bda_data_break = true;
|
||||||
|
if constexpr (type == MemoryAccessType::Read)
|
||||||
|
g_state.cop0_regs.dcic.status_bda_data_read_break = true;
|
||||||
|
else
|
||||||
|
g_state.cop0_regs.dcic.status_bda_data_write_break = true;
|
||||||
|
|
||||||
|
DispatchCop0Breakpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TracePrintInstruction()
|
||||||
|
{
|
||||||
|
const u32 pc = g_state.current_instruction_pc;
|
||||||
|
const u32 bits = g_state.current_instruction.bits;
|
||||||
|
|
||||||
|
TinyString instr;
|
||||||
|
TinyString comment;
|
||||||
|
DisassembleInstruction(&instr, pc, bits);
|
||||||
|
DisassembleInstructionComment(&comment, pc, bits, &g_state.regs);
|
||||||
|
if (!comment.IsEmpty())
|
||||||
|
{
|
||||||
|
for (u32 i = instr.GetLength(); i < 30; i++)
|
||||||
|
instr.AppendCharacter(' ');
|
||||||
|
instr.AppendString("; ");
|
||||||
|
instr.AppendString(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintInstruction(u32 bits, u32 pc, Registers* regs, const char* prefix)
|
||||||
{
|
{
|
||||||
TinyString instr;
|
TinyString instr;
|
||||||
TinyString comment;
|
TinyString comment;
|
||||||
|
@ -507,7 +582,7 @@ static void PrintInstruction(u32 bits, u32 pc, Registers* regs)
|
||||||
instr.AppendString(comment);
|
instr.AppendString(comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
|
Log_DevPrintf("%s%08x: %08x %s", prefix, pc, bits, instr.GetCharArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LogInstruction(u32 bits, u32 pc, Registers* regs)
|
static void LogInstruction(u32 bits, u32 pc, Registers* regs)
|
||||||
|
@ -537,11 +612,11 @@ ALWAYS_INLINE static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u3
|
||||||
return (((new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0;
|
return (((new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisassembleAndPrint(u32 addr)
|
void DisassembleAndPrint(u32 addr, const char* prefix)
|
||||||
{
|
{
|
||||||
u32 bits = 0;
|
u32 bits = 0;
|
||||||
SafeReadMemoryWord(addr, &bits);
|
SafeReadMemoryWord(addr, &bits);
|
||||||
PrintInstruction(bits, addr, &g_state.regs);
|
PrintInstruction(bits, addr, &g_state.regs, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instructions_after /* = 0 */)
|
void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instructions_after /* = 0 */)
|
||||||
|
@ -549,21 +624,19 @@ void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instru
|
||||||
u32 disasm_addr = addr - (instructions_before * sizeof(u32));
|
u32 disasm_addr = addr - (instructions_before * sizeof(u32));
|
||||||
for (u32 i = 0; i < instructions_before; i++)
|
for (u32 i = 0; i < instructions_before; i++)
|
||||||
{
|
{
|
||||||
DisassembleAndPrint(disasm_addr);
|
DisassembleAndPrint(disasm_addr, "");
|
||||||
disasm_addr += sizeof(u32);
|
disasm_addr += sizeof(u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::printf("----> ");
|
|
||||||
|
|
||||||
// <= to include the instruction itself
|
// <= to include the instruction itself
|
||||||
for (u32 i = 0; i <= instructions_after; i++)
|
for (u32 i = 0; i <= instructions_after; i++)
|
||||||
{
|
{
|
||||||
DisassembleAndPrint(disasm_addr);
|
DisassembleAndPrint(disasm_addr, (i == 0) ? "---->" : "");
|
||||||
disasm_addr += sizeof(u32);
|
disasm_addr += sizeof(u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PGXPMode pgxp_mode>
|
template<PGXPMode pgxp_mode, bool debug>
|
||||||
ALWAYS_INLINE_RELEASE static void ExecuteInstruction()
|
ALWAYS_INLINE_RELEASE static void ExecuteInstruction()
|
||||||
{
|
{
|
||||||
restart_instruction:
|
restart_instruction:
|
||||||
|
@ -579,7 +652,7 @@ restart_instruction:
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
if (TRACE_EXECUTION)
|
if (TRACE_EXECUTION)
|
||||||
PrintInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs);
|
TracePrintInstruction();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Skip nops. Makes PGXP-CPU quicker, but also the regular interpreter.
|
// Skip nops. Makes PGXP-CPU quicker, but also the regular interpreter.
|
||||||
|
@ -1059,6 +1132,9 @@ restart_instruction:
|
||||||
case InstructionOp::lb:
|
case InstructionOp::lb:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Read>(addr);
|
||||||
|
|
||||||
u8 value;
|
u8 value;
|
||||||
if (!ReadMemoryByte(addr, &value))
|
if (!ReadMemoryByte(addr, &value))
|
||||||
return;
|
return;
|
||||||
|
@ -1075,6 +1151,9 @@ restart_instruction:
|
||||||
case InstructionOp::lh:
|
case InstructionOp::lh:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Read>(addr);
|
||||||
|
|
||||||
u16 value;
|
u16 value;
|
||||||
if (!ReadMemoryHalfWord(addr, &value))
|
if (!ReadMemoryHalfWord(addr, &value))
|
||||||
return;
|
return;
|
||||||
|
@ -1090,6 +1169,9 @@ restart_instruction:
|
||||||
case InstructionOp::lw:
|
case InstructionOp::lw:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Read>(addr);
|
||||||
|
|
||||||
u32 value;
|
u32 value;
|
||||||
if (!ReadMemoryWord(addr, &value))
|
if (!ReadMemoryWord(addr, &value))
|
||||||
return;
|
return;
|
||||||
|
@ -1104,6 +1186,9 @@ restart_instruction:
|
||||||
case InstructionOp::lbu:
|
case InstructionOp::lbu:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Read>(addr);
|
||||||
|
|
||||||
u8 value;
|
u8 value;
|
||||||
if (!ReadMemoryByte(addr, &value))
|
if (!ReadMemoryByte(addr, &value))
|
||||||
return;
|
return;
|
||||||
|
@ -1119,6 +1204,9 @@ restart_instruction:
|
||||||
case InstructionOp::lhu:
|
case InstructionOp::lhu:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Read>(addr);
|
||||||
|
|
||||||
u16 value;
|
u16 value;
|
||||||
if (!ReadMemoryHalfWord(addr, &value))
|
if (!ReadMemoryHalfWord(addr, &value))
|
||||||
return;
|
return;
|
||||||
|
@ -1136,6 +1224,9 @@ restart_instruction:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3);
|
const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3);
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Read>(addr);
|
||||||
|
|
||||||
u32 aligned_value;
|
u32 aligned_value;
|
||||||
if (!ReadMemoryWord(aligned_addr, &aligned_value))
|
if (!ReadMemoryWord(aligned_addr, &aligned_value))
|
||||||
return;
|
return;
|
||||||
|
@ -1165,6 +1256,9 @@ restart_instruction:
|
||||||
case InstructionOp::sb:
|
case InstructionOp::sb:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Write>(addr);
|
||||||
|
|
||||||
const u8 value = Truncate8(ReadReg(inst.i.rt));
|
const u8 value = Truncate8(ReadReg(inst.i.rt));
|
||||||
WriteMemoryByte(addr, value);
|
WriteMemoryByte(addr, value);
|
||||||
|
|
||||||
|
@ -1176,6 +1270,9 @@ restart_instruction:
|
||||||
case InstructionOp::sh:
|
case InstructionOp::sh:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Write>(addr);
|
||||||
|
|
||||||
const u16 value = Truncate16(ReadReg(inst.i.rt));
|
const u16 value = Truncate16(ReadReg(inst.i.rt));
|
||||||
WriteMemoryHalfWord(addr, value);
|
WriteMemoryHalfWord(addr, value);
|
||||||
|
|
||||||
|
@ -1187,6 +1284,9 @@ restart_instruction:
|
||||||
case InstructionOp::sw:
|
case InstructionOp::sw:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Write>(addr);
|
||||||
|
|
||||||
const u32 value = ReadReg(inst.i.rt);
|
const u32 value = ReadReg(inst.i.rt);
|
||||||
WriteMemoryWord(addr, value);
|
WriteMemoryWord(addr, value);
|
||||||
|
|
||||||
|
@ -1200,6 +1300,9 @@ restart_instruction:
|
||||||
{
|
{
|
||||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||||
const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3);
|
const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3);
|
||||||
|
if constexpr (debug)
|
||||||
|
Cop0DataBreakpointCheck<MemoryAccessType::Write>(aligned_addr);
|
||||||
|
|
||||||
const u32 reg_value = ReadReg(inst.i.rt);
|
const u32 reg_value = ReadReg(inst.i.rt);
|
||||||
const u8 shift = (Truncate8(addr) & u8(3)) * u8(8);
|
const u8 shift = (Truncate8(addr) & u8(3)) * u8(8);
|
||||||
u32 mem_value;
|
u32 mem_value;
|
||||||
|
@ -1508,16 +1611,20 @@ void DispatchInterrupt()
|
||||||
g_state.regs.pc);
|
g_state.regs.pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void UpdateDebugDispatcherFlag()
|
void UpdateDebugDispatcherFlag()
|
||||||
{
|
{
|
||||||
const bool has_any_breakpoints = !s_breakpoints.empty();
|
const bool has_any_breakpoints = !s_breakpoints.empty();
|
||||||
|
|
||||||
// TODO: cop0 breakpoints
|
// TODO: cop0 breakpoints
|
||||||
|
const auto& dcic = g_state.cop0_regs.dcic;
|
||||||
|
const bool has_cop0_breakpoints =
|
||||||
|
dcic.super_master_enable_1 && dcic.super_master_enable_2 && dcic.execution_breakpoint_enable;
|
||||||
|
|
||||||
const bool use_debug_dispatcher = has_any_breakpoints || s_trace_to_log;
|
const bool use_debug_dispatcher = has_any_breakpoints || has_cop0_breakpoints || s_trace_to_log;
|
||||||
if (use_debug_dispatcher == g_state.use_debug_dispatcher)
|
if (use_debug_dispatcher == g_state.use_debug_dispatcher)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Log_DevPrintf("%s debug dispatcher", use_debug_dispatcher ? "Now using" : "No longer using");
|
||||||
g_state.use_debug_dispatcher = use_debug_dispatcher;
|
g_state.use_debug_dispatcher = use_debug_dispatcher;
|
||||||
ForceDispatcherExit();
|
ForceDispatcherExit();
|
||||||
}
|
}
|
||||||
|
@ -1694,7 +1801,7 @@ bool AddStepOutBreakpoint(u32 max_instructions_to_search)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ALWAYS_INLINE_RELEASE bool BreakpointCheck()
|
ALWAYS_INLINE_RELEASE static bool BreakpointCheck()
|
||||||
{
|
{
|
||||||
const u32 pc = g_state.regs.pc;
|
const u32 pc = g_state.regs.pc;
|
||||||
|
|
||||||
|
@ -1779,6 +1886,8 @@ static void ExecuteImpl()
|
||||||
|
|
||||||
if constexpr (debug)
|
if constexpr (debug)
|
||||||
{
|
{
|
||||||
|
Cop0ExecutionBreakpointCheck();
|
||||||
|
|
||||||
if (BreakpointCheck())
|
if (BreakpointCheck())
|
||||||
{
|
{
|
||||||
// continue is measurably faster than break on msvc for some reason
|
// continue is measurably faster than break on msvc for some reason
|
||||||
|
@ -1818,7 +1927,7 @@ static void ExecuteImpl()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// execute the instruction we previously fetched
|
// execute the instruction we previously fetched
|
||||||
ExecuteInstruction<pgxp_mode>();
|
ExecuteInstruction<pgxp_mode, debug>();
|
||||||
|
|
||||||
// next load delay
|
// next load delay
|
||||||
UpdateLoadDelay();
|
UpdateLoadDelay();
|
||||||
|
@ -1891,7 +2000,7 @@ void InterpretCachedBlock(const CodeBlock& block)
|
||||||
g_state.regs.npc += 4;
|
g_state.regs.npc += 4;
|
||||||
|
|
||||||
// execute the instruction we previously fetched
|
// execute the instruction we previously fetched
|
||||||
ExecuteInstruction<pgxp_mode>();
|
ExecuteInstruction<pgxp_mode, false>();
|
||||||
|
|
||||||
// next load delay
|
// next load delay
|
||||||
UpdateLoadDelay();
|
UpdateLoadDelay();
|
||||||
|
@ -1944,7 +2053,7 @@ void InterpretUncachedBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute the instruction we previously fetched
|
// execute the instruction we previously fetched
|
||||||
ExecuteInstruction<pgxp_mode>();
|
ExecuteInstruction<pgxp_mode, false>();
|
||||||
|
|
||||||
// next load delay
|
// next load delay
|
||||||
UpdateLoadDelay();
|
UpdateLoadDelay();
|
||||||
|
@ -1969,13 +2078,13 @@ namespace Recompiler::Thunks {
|
||||||
|
|
||||||
bool InterpretInstruction()
|
bool InterpretInstruction()
|
||||||
{
|
{
|
||||||
ExecuteInstruction<PGXPMode::Disabled>();
|
ExecuteInstruction<PGXPMode::Disabled, false>();
|
||||||
return g_state.exception_raised;
|
return g_state.exception_raised;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InterpretInstructionPGXP()
|
bool InterpretInstructionPGXP()
|
||||||
{
|
{
|
||||||
ExecuteInstruction<PGXPMode::Memory>();
|
ExecuteInstruction<PGXPMode::Memory, false>();
|
||||||
return g_state.exception_raised;
|
return g_state.exception_raised;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ ALWAYS_INLINE void CheckForPendingInterrupt()
|
||||||
}
|
}
|
||||||
|
|
||||||
void DispatchInterrupt();
|
void DispatchInterrupt();
|
||||||
|
void UpdateDebugDispatcherFlag();
|
||||||
|
|
||||||
// icache stuff
|
// icache stuff
|
||||||
ALWAYS_INLINE bool IsCachedAddress(VirtualMemoryAddress address)
|
ALWAYS_INLINE bool IsCachedAddress(VirtualMemoryAddress address)
|
||||||
|
|
|
@ -2494,25 +2494,55 @@ bool CodeGenerator::Compile_cop0(const CodeBlockInstruction& cbi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cbi.instruction.cop.CommonOp() == CopCommonInstruction::mtcn &&
|
if (cbi.instruction.cop.CommonOp() == CopCommonInstruction::mtcn)
|
||||||
(reg == Cop0Reg::CAUSE || reg == Cop0Reg::SR))
|
|
||||||
{
|
{
|
||||||
// Emit an interrupt check on load of CAUSE/SR.
|
if (reg == Cop0Reg::CAUSE || reg == Cop0Reg::SR)
|
||||||
Value sr_value = m_register_cache.AllocateScratch(RegSize_32);
|
{
|
||||||
Value cause_value = m_register_cache.AllocateScratch(RegSize_32);
|
// Emit an interrupt check on load of CAUSE/SR.
|
||||||
|
Value sr_value = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
|
Value cause_value = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
|
|
||||||
// m_cop0_regs.sr.IEc && ((m_cop0_regs.cause.Ip & m_cop0_regs.sr.Im) != 0)
|
// m_cop0_regs.sr.IEc && ((m_cop0_regs.cause.Ip & m_cop0_regs.sr.Im) != 0)
|
||||||
LabelType no_interrupt;
|
LabelType no_interrupt;
|
||||||
EmitLoadCPUStructField(sr_value.host_reg, sr_value.size, offsetof(State, cop0_regs.sr.bits));
|
EmitLoadCPUStructField(sr_value.host_reg, sr_value.size, offsetof(State, cop0_regs.sr.bits));
|
||||||
EmitLoadCPUStructField(cause_value.host_reg, cause_value.size, offsetof(State, cop0_regs.cause.bits));
|
EmitLoadCPUStructField(cause_value.host_reg, cause_value.size, offsetof(State, cop0_regs.cause.bits));
|
||||||
EmitBranchIfBitClear(sr_value.host_reg, sr_value.size, 0, &no_interrupt);
|
EmitBranchIfBitClear(sr_value.host_reg, sr_value.size, 0, &no_interrupt);
|
||||||
m_register_cache.InhibitAllocation();
|
m_register_cache.InhibitAllocation();
|
||||||
EmitAnd(sr_value.host_reg, sr_value.host_reg, cause_value);
|
EmitAnd(sr_value.host_reg, sr_value.host_reg, cause_value);
|
||||||
EmitTest(sr_value.host_reg, Value::FromConstantU32(0xFF00));
|
EmitTest(sr_value.host_reg, Value::FromConstantU32(0xFF00));
|
||||||
EmitConditionalBranch(Condition::Zero, false, &no_interrupt);
|
EmitConditionalBranch(Condition::Zero, false, &no_interrupt);
|
||||||
EmitStoreCPUStructField(offsetof(State, downcount), Value::FromConstantU32(0));
|
EmitStoreCPUStructField(offsetof(State, downcount), Value::FromConstantU32(0));
|
||||||
EmitBindLabel(&no_interrupt);
|
EmitBindLabel(&no_interrupt);
|
||||||
m_register_cache.UninhibitAllocation();
|
m_register_cache.UninhibitAllocation();
|
||||||
|
}
|
||||||
|
else if (reg == Cop0Reg::DCIC)
|
||||||
|
{
|
||||||
|
Value dcic_value = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
|
m_register_cache.InhibitAllocation();
|
||||||
|
|
||||||
|
// if ((dcic & master_enable_bits) != master_enable_bits) goto not_enabled;
|
||||||
|
LabelType not_enabled;
|
||||||
|
EmitLoadCPUStructField(dcic_value.host_reg, dcic_value.size, offsetof(State, cop0_regs.dcic.bits));
|
||||||
|
EmitAnd(dcic_value.host_reg, dcic_value.host_reg,
|
||||||
|
Value::FromConstantU32(Cop0Registers::DCIC::MASTER_ENABLE_BITS));
|
||||||
|
EmitConditionalBranch(Condition::NotEqual, false, dcic_value.host_reg,
|
||||||
|
Value::FromConstantU32(Cop0Registers::DCIC::MASTER_ENABLE_BITS), ¬_enabled);
|
||||||
|
|
||||||
|
// if ((dcic & breakpoint_bits) == 0) goto not_enabled;
|
||||||
|
EmitLoadCPUStructField(dcic_value.host_reg, dcic_value.size, offsetof(State, cop0_regs.dcic.bits));
|
||||||
|
EmitTest(dcic_value.host_reg, Value::FromConstantU32(Cop0Registers::DCIC::ANY_BREAKPOINTS_ENABLED_BITS));
|
||||||
|
EmitConditionalBranch(Condition::Zero, false, ¬_enabled);
|
||||||
|
|
||||||
|
m_register_cache.UninhibitAllocation();
|
||||||
|
|
||||||
|
// exit block early if enabled
|
||||||
|
m_register_cache.PushState();
|
||||||
|
EmitFunctionCall(nullptr, &CPU::UpdateDebugDispatcherFlag);
|
||||||
|
EmitExceptionExit();
|
||||||
|
m_register_cache.PopState();
|
||||||
|
|
||||||
|
EmitBindLabel(¬_enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstructionEpilogue(cbi);
|
InstructionEpilogue(cbi);
|
||||||
|
|
|
@ -283,8 +283,7 @@ struct Registers
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<VirtualMemoryAddress> GetLoadStoreEffectiveAddress(const Instruction& instruction,
|
std::optional<VirtualMemoryAddress> GetLoadStoreEffectiveAddress(const Instruction& instruction, const Registers* regs);
|
||||||
const Registers* regs);
|
|
||||||
|
|
||||||
enum class Cop0Reg : u8
|
enum class Cop0Reg : u8
|
||||||
{
|
{
|
||||||
|
@ -402,6 +401,27 @@ struct Cop0Registers
|
||||||
BitField<u32, bool, 31, 1> super_master_enable_2;
|
BitField<u32, bool, 31, 1> super_master_enable_2;
|
||||||
|
|
||||||
static constexpr u32 WRITE_MASK = 0b1111'1111'1000'0000'1111'0000'0011'1111;
|
static constexpr u32 WRITE_MASK = 0b1111'1111'1000'0000'1111'0000'0011'1111;
|
||||||
|
|
||||||
|
static constexpr u32 ANY_BREAKPOINTS_ENABLED_BITS = (1u << 24) | (1u << 26) | (1u << 27) | (1u << 28);
|
||||||
|
static constexpr u32 MASTER_ENABLE_BITS = (1u << 23) | (1u << 31);
|
||||||
|
|
||||||
|
constexpr bool ExecutionBreakpointsEnabled() const
|
||||||
|
{
|
||||||
|
const u32 mask = (1u << 23) | (1u << 24) | (1u << 31);
|
||||||
|
return ((bits & mask) == mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool DataReadBreakpointsEnabled() const
|
||||||
|
{
|
||||||
|
const u32 mask = (1u << 23) | (1u << 25) | (1u << 26) | (1u << 31);
|
||||||
|
return ((bits & mask) == mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool DataWriteBreakpointsEnabled() const
|
||||||
|
{
|
||||||
|
const u32 mask = (1u << 23) | (1u << 25) | (1u << 27) | (1u << 31);
|
||||||
|
return ((bits & mask) == mask);
|
||||||
|
}
|
||||||
} dcic;
|
} dcic;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue