Snippets

Displaying: CNoeR3000A.cpp | View full page

//===============================================================================
// Noesis R3000A CPU core
// (c) whenever Rich Whitehouse
// an easily-debuggable and pliable R3000A core.
// has been live-tested inside a PSX emulator with SotN, but not too extensively.
//===============================================================================

#include "CNoeR3000A.h"

#define NOER3000A_DISASM_VALUES() 0 //useful for debugging - shows register values as ints when disassembling from a live context

struct SR3000ADefaultOpHandler
{
	typedef void (*TDefaultOpHandlerFunction)(CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandler);
	typedef void (*TDefaultOpStringFormatFunction)(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const uint32_t opAddr, const SR3000ADefaultOpHandler *pHandler);

	explicit SR3000ADefaultOpHandler(const char *pOpName, TDefaultOpHandlerFunction pOpHandler, TDefaultOpStringFormatFunction pOpStringFormat)
		: mpOpName(pOpName)
		, mpOpHandler(pOpHandler)
		, mpOpStringFormat(pOpStringFormat)
	{
		NoeAssert(strlen(pOpName) <= CNoeR3000A::skMaximumOpNameLength);
	}

	const char *mpOpName;
	TDefaultOpHandlerFunction mpOpHandler;
	TDefaultOpStringFormatFunction mpOpStringFormat;
};

namespace
{
	const uint64_t skDefaultCyclesPerOp = 1;

	const uint32_t skDefaultDelayBranchCycles = 0;
	const uint32_t skDefaultDelayLoadCycles = 0;

	const char *skGPRNameForIndex[CNoeR3000A::kGPR_Count] =
	{
		"r0", //kGPR_Zero,
		"at", //kGPR_AT,
		"v0", //kGPR_V0,
		"v1", //kGPR_V1,
		"a0", //kGPR_A0,
		"a1", //kGPR_A1,
		"a2", //kGPR_A2,
		"a3", //kGPR_A3,
		"t0", //kGPR_T0,
		"t1", //kGPR_T1,
		"t2", //kGPR_T2,
		"t3", //kGPR_T3,
		"t4", //kGPR_T4,
		"t5", //kGPR_T5,
		"t6", //kGPR_T6,
		"t7", //kGPR_T7,
		"s0", //kGPR_S0,
		"s1", //kGPR_S1,
		"s2", //kGPR_S2,
		"s3", //kGPR_S3,
		"s4", //kGPR_S4,
		"s5", //kGPR_S5,
		"s6", //kGPR_S6,
		"s7", //kGPR_S7,
		"t8", //kGPR_T8,
		"t9", //kGPR_T9,
		"k0", //kGPR_K0,
		"k1", //kGPR_K1,
		"gp", //kGPR_GP,
		"sp", //kGPR_SP,
		"fp", //kGPR_FP,
		"ra" //kGPR_RA,  
	};

	const char *skIRNameForIndex[CNoeR3000A::kIR_Count] =
	{
		"lo", //kIR_Lo,
		"hi", //kIR_Hi,
		"pc", //kIR_PC,
		"op", //kIR_Op,
	};


	template<typename DataType>
	DataType default_read_memory(CNoeR3000A *pCpu, const uint32_t addr, const CNoeR3000A::EAccessType accessType)
	{
		return 0;
	}

	template<typename DataType>
	void default_write_memory(CNoeR3000A *pCpu, const uint32_t addr, const DataType memVal, const CNoeR3000A::EAccessType accessType)
	{
	}

	void default_write_cop0_reg(CNoeR3000A *pCpu, const CNoeR3000A::EC0R cop0Reg, const uint32_t regVal)
	{
		pCpu->C0R()[cop0Reg] = regVal;
	}

#if NOER3000A_DEBUGGER()
	CNoeDebugger::EDebuggerReadWriteResult debugger_read_data(CNoeDebugger *pDebugger, void *pDest, const uint64_t readAddr, const uint32_t readSize)
	{
		CNoeR3000A *pCpu = static_cast<CNoeR3000A *>(pDebugger->GetUserData());
		switch (readSize)
		{
		case 1:
			*static_cast<uint8_t *>(pDest) = pCpu->ReadMemory8(static_cast<uint32_t>(readAddr), CNoeR3000A::kAT_External);
			break;
		case 2:
			*static_cast<uint16_t *>(pDest) = pCpu->ReadMemory16(static_cast<uint32_t>(readAddr), CNoeR3000A::kAT_External);
			break;
		case 4:
			*static_cast<uint32_t *>(pDest) = pCpu->ReadMemory32(static_cast<uint32_t>(readAddr), CNoeR3000A::kAT_External);
			break;
		default:
			for (uint32_t readAmount = 0; readAmount < readSize; ++readAmount)
			{
				static_cast<uint8_t *>(pDest)[readAmount] = pCpu->ReadMemory8(static_cast<uint32_t>(readAddr + readAmount), CNoeR3000A::kAT_External);
			}
			break;
		}
		return CNoeDebugger::kDRWR_Success;
	}

	CNoeDebugger::EDebuggerReadWriteResult debugger_write_data(CNoeDebugger *pDebugger, const void *pSrc, const uint64_t writeAddr, const uint32_t writeSize)
	{
		CNoeR3000A *pCpu = static_cast<CNoeR3000A *>(pDebugger->GetUserData());
		switch (writeSize)
		{
		case 1:
			pCpu->WriteMemory8(static_cast<uint32_t>(writeAddr), *static_cast<const uint8_t *>(pSrc), CNoeR3000A::kAT_External);
			break;
		case 2:
			pCpu->WriteMemory16(static_cast<uint32_t>(writeAddr), *static_cast<const uint16_t *>(pSrc), CNoeR3000A::kAT_External);
			break;
		case 4:
			pCpu->WriteMemory32(static_cast<uint32_t>(writeAddr), *static_cast<const uint32_t *>(pSrc), CNoeR3000A::kAT_External);
			break;
		default:
			for (uint32_t writeAmount = 0; writeAmount < writeSize; ++writeAmount)
			{
				pCpu->WriteMemory8(static_cast<uint32_t>(writeAddr + writeAmount), static_cast<const uint8_t *>(pSrc)[writeAmount], CNoeR3000A::kAT_External);
			}
			break;
		}
		return CNoeDebugger::kDRWR_Success;
	}

	uint32_t *debugger_reg_pointer(CNoeDebugger *pDebugger, const uint32_t regIndex)
	{
		CNoeR3000A *pCpu = static_cast<CNoeR3000A *>(pDebugger->GetUserData());
		if (regIndex < CNoeR3000A::kGPR_Count)
		{
			return &pCpu->GPR()[regIndex];
		}
		else if (regIndex < (CNoeR3000A::kGPR_Count + CNoeR3000A::kIR_Count))
		{
			return &pCpu->IR()[regIndex - CNoeR3000A::kGPR_Count];
		}
		else if (regIndex < (CNoeR3000A::kGPR_Count + CNoeR3000A::kIR_Count + CNoeR3000A::kC0R_Count))
		{
			return &pCpu->C0R()[regIndex - (CNoeR3000A::kGPR_Count + CNoeR3000A::kIR_Count)];
		}
		return NULL;
	}

	CNoeDebugger::EDebuggerReadWriteResult debugger_read_reg(CNoeDebugger *pDebugger, void *pDest, const uint32_t regIndex)
	{
		if (uint32_t *pReg = debugger_reg_pointer(pDebugger, regIndex))
		{
			memcpy(pDest, pReg, sizeof(uint32_t));
			return CNoeDebugger::kDRWR_Success;
		}
		return CNoeDebugger::kDRWR_Failure;
	}

	CNoeDebugger::EDebuggerReadWriteResult debugger_write_reg(CNoeDebugger *pDebugger, const void *pSrc, const uint32_t regIndex)
	{
		if (uint32_t *pReg = debugger_reg_pointer(pDebugger, regIndex))
		{
			memcpy(pReg, pSrc, sizeof(uint32_t));
			return CNoeDebugger::kDRWR_Success;
		}
		return CNoeDebugger::kDRWR_Failure;
	}

	void debugger_disassemble(CNoeDebugger *pDebugger, CNoeMemWriteBuffer *pDisasmOut, const uint64_t addr, const uint64_t refAddr, const uint32_t instructionCount)
	{
		CNoeR3000A *pCpu = static_cast<CNoeR3000A *>(pDebugger->GetUserData());
		char disasmString[CNoeR3000A::skMaximumOpStringLength];
		for (uint32_t instructionIndex = 0; instructionIndex < instructionCount; ++instructionIndex)
		{
			const uint32_t opAddr = static_cast<uint32_t>(addr) + instructionIndex * CNoeR3000A::skInstructionSize;
			const uint32_t opVal = pCpu->ReadInstruction(opAddr, CNoeR3000A::kAT_External);
			pCpu->FormatOpString(disasmString, opVal, opAddr);
			NoeAssert(CNoeR3000A::skInstructionSize == sizeof(opVal));
			pDisasmOut->Write(NNoeDebugProtocol::SDisasmEntry(opAddr, CNoeR3000A::skInstructionSize));
			pDisasmOut->Write(opVal);
			pDisasmOut->WriteString("%s", disasmString);
		}
	}
#endif

	#define OP_OB(opName) SR3000ADefaultOpHandler(#opName, CNoeR3000AOps::opName, CNoeR3000AOps::opName##_format)
	#define OP_IMPLEMENT(opName) static void opName(CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	#define OP_IMPLEMENT_FMT(opName) static void opName##_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const uint32_t opAddr, const SR3000ADefaultOpHandler *pHandlerObject)

	uint32_t &reg_sa(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		const uint32_t regIndex = CNoeR3000A::GetInstructionSA(opVal);
		return pCpu->GPR()[regIndex];
	}
	uint32_t &reg_rd(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		const uint32_t regIndex = CNoeR3000A::GetInstructionRD(opVal);
		return pCpu->GPR()[regIndex];
	}
	uint32_t &reg_rt(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		const uint32_t regIndex = CNoeR3000A::GetInstructionRT(opVal);
		return pCpu->GPR()[regIndex];
	}
	uint32_t &reg_rs(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		const uint32_t regIndex = CNoeR3000A::GetInstructionRS(opVal);
		return pCpu->GPR()[regIndex];
	}

	bool sa_is_r0(const uint32_t opVal)
	{
		return CNoeR3000A::GetInstructionSA(opVal) == CNoeR3000A::kGPR_Zero;
	}
	bool rd_is_r0(const uint32_t opVal)
	{
		return CNoeR3000A::GetInstructionRD(opVal) == CNoeR3000A::kGPR_Zero;
	}
	bool rt_is_r0(const uint32_t opVal)
	{
		return CNoeR3000A::GetInstructionRT(opVal) == CNoeR3000A::kGPR_Zero;
	}
	bool rs_is_r0(const uint32_t opVal)
	{
		return CNoeR3000A::GetInstructionRS(opVal) == CNoeR3000A::kGPR_Zero;
	}

	uint32_t op_imm_16(const uint32_t opVal)
	{
		return (opVal & 0xFFFF);
	}

	int16_t op_imm_s16(const uint32_t opVal)
	{
		return int16_t(opVal & 0xFFFF);
	}

	const int skCircularTempStringBufferSize = CNoeR3000A::skMaximumOpStringLength * 8;
	char sCircularTempStringBuffer[skCircularTempStringBufferSize];
	int sCircularTempStringBufferOffset = 0;
	char *get_temp_string_buffer(const int bufferSize)
	{
		NoeAssert(bufferSize < skCircularTempStringBufferSize);
		if ((sCircularTempStringBufferOffset + bufferSize) > skCircularTempStringBufferSize)
		{
			//wrap around
			sCircularTempStringBufferOffset = 0;
		}

		char *pBuffer = &sCircularTempStringBuffer[sCircularTempStringBufferOffset];
		sCircularTempStringBufferOffset += bufferSize;

		return pBuffer;
	}

	const char *string_for_op(const SR3000ADefaultOpHandler *pHandlerObject)
	{
		const int opNameLength = strlen(pHandlerObject->mpOpName);
		const int paddedNameLength = CNoeR3000A::skMaximumOpNameLength + 4;
		char *pOpString = get_temp_string_buffer(paddedNameLength + 1);

		memcpy(pOpString, pHandlerObject->mpOpName, opNameLength);
		memset(pOpString + opNameLength, ' ', paddedNameLength - opNameLength);
		pOpString[paddedNameLength] = 0;

		return pOpString;
	}

#if NOER3000A_DISASM_VALUES()
	const char *reg_string_internal(CNoeR3000A *pCpu, const uint32_t regIndex)
	{
		char *pRegString = get_temp_string_buffer(32);
		NoeSPrintF(pRegString, 32, "%s(%i)", CNoeR3000A::GetGPRName(CNoeR3000A::EGPR(regIndex)), pCpu->GPR()[regIndex]);
		return pRegString;
	}
	const char *reg_string_sa_internal(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		return reg_string_internal(pCpu, CNoeR3000A::GetInstructionSA(opVal));
	}
	const char *reg_string_rd_internal(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		return reg_string_internal(pCpu, CNoeR3000A::GetInstructionRD(opVal));
	}
	const char *reg_string_rt_internal(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		return reg_string_internal(pCpu, CNoeR3000A::GetInstructionRT(opVal));
	}
	const char *reg_string_rs_internal(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		return reg_string_internal(pCpu, CNoeR3000A::GetInstructionRS(opVal));
	}
	#define reg_string_sa(opVal) reg_string_sa_internal(pCpu, opVal)
	#define reg_string_rd(opVal) reg_string_rd_internal(pCpu, opVal)
	#define reg_string_rt(opVal) reg_string_rt_internal(pCpu, opVal)
	#define reg_string_rs(opVal) reg_string_rs_internal(pCpu, opVal)
#else
	const char *reg_string_sa(const uint32_t opVal)
	{
		return CNoeR3000A::GetGPRName(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionSA(opVal)));
	}
	const char *reg_string_rd(const uint32_t opVal)
	{
		return CNoeR3000A::GetGPRName(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRD(opVal)));
	}
	const char *reg_string_rt(const uint32_t opVal)
	{
		return CNoeR3000A::GetGPRName(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)));
	}
	const char *reg_string_rs(const uint32_t opVal)
	{
		return CNoeR3000A::GetGPRName(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRS(opVal)));
	}
#endif
	const char *cop0_reg_string(const uint32_t regIndex)
	{
		NoeAssert(regIndex < CNoeR3000A::kC0R_Count);
		char *pRegString = get_temp_string_buffer(4);
		NoeSPrintF(pRegString, 4, "c%i", regIndex);
		return pRegString;
	}

	void jump_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const uint32_t opAddr, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		const uint32_t jumpAddr = CNoeR3000A::GetInstructionJumpAddress(opVal) +
		                          (opAddr & CNoeR3000A::skInstructionJumpAddressPCMask);
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s$%0x",
			string_for_op(pHandlerObject), jumpAddr);
	}

	void conditional_branch_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const uint32_t opAddr, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, $%0x",
			string_for_op(pHandlerObject), reg_string_rs(opVal), opAddr + (op_imm_16(opVal) << 2));
	}

	void conditional_branch_reg_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const uint32_t opAddr, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s, $%0x",
			string_for_op(pHandlerObject), reg_string_rs(opVal), reg_string_rt(opVal), opAddr + (op_imm_16(opVal) << 2));
	}

	void load_store_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %i(%s)",
			string_for_op(pHandlerObject), reg_string_rt(opVal), op_imm_s16(opVal), reg_string_rs(opVal));
	}

	void op_reg_st_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s",
			string_for_op(pHandlerObject), reg_string_rs(opVal), reg_string_rt(opVal));
	}

	void op_reg_ts_is16_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s, %i",
			string_for_op(pHandlerObject), reg_string_rt(opVal), reg_string_rs(opVal), int32_t(op_imm_s16(opVal)));
	}

	void op_reg_ts_iu16_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s, %i",
			string_for_op(pHandlerObject), reg_string_rt(opVal), reg_string_rs(opVal), op_imm_16(opVal));
	}

	void op_reg_dt_sa_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s, %i",
			string_for_op(pHandlerObject), reg_string_rd(opVal), reg_string_rt(opVal), CNoeR3000A::GetInstructionSA(opVal));
	}

	void op_reg_d_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s",
			string_for_op(pHandlerObject), reg_string_rd(opVal));
	}

	void op_reg_s_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s",
			string_for_op(pHandlerObject), reg_string_rs(opVal));
	}

	void op_reg_dts_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s, %s",
			string_for_op(pHandlerObject), reg_string_rd(opVal), reg_string_rt(opVal), reg_string_rs(opVal));
	}

	void op_reg_dst_string_format(char *pStringOut, CNoeR3000A *pCpu, const uint32_t opVal, const SR3000ADefaultOpHandler *pHandlerObject)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s, %s",
			string_for_op(pHandlerObject), reg_string_rd(opVal), reg_string_rs(opVal), reg_string_rt(opVal));
	}
}

class CNoeR3000AOps
{
public:
	//invalid op (currently, just ignore)
	OP_IMPLEMENT(invalid)
	{
	}
	OP_IMPLEMENT_FMT(invalid)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "<invalid op>");
	}

	//nop
	OP_IMPLEMENT(nop)
	{
	}
	OP_IMPLEMENT_FMT(nop)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "nop");
	}

	//redirect to table2
	OP_IMPLEMENT(table2)
	{
		const SR3000ADefaultOpHandler &opHandler = CNoeR3000A::mspTable2OpHandlers[CNoeR3000A::GetInstructionOp2(opVal)];
		opHandler.mpOpHandler(pCpu, opVal, &opHandler);
	}
	OP_IMPLEMENT_FMT(table2)
	{
		const SR3000ADefaultOpHandler &opHandler = CNoeR3000A::mspTable2OpHandlers[CNoeR3000A::GetInstructionOp2(opVal)];
		opHandler.mpOpStringFormat(pStringOut, pCpu, opVal, opAddr, &opHandler);
	}

	//redirect to conditional branch table
	OP_IMPLEMENT(cbranch)
	{
		const SR3000ADefaultOpHandler &opHandler = CNoeR3000A::mspCBranchOpHandlers[CNoeR3000A::GetInstructionRT(opVal)];
		opHandler.mpOpHandler(pCpu, opVal, &opHandler);
	}
	OP_IMPLEMENT_FMT(cbranch)
	{
		const SR3000ADefaultOpHandler &opHandler = CNoeR3000A::mspCBranchOpHandlers[CNoeR3000A::GetInstructionRT(opVal)];
		opHandler.mpOpStringFormat(pStringOut, pCpu, opVal, opAddr, &opHandler);
	}

	//jump
	OP_IMPLEMENT(j)
	{
		const uint32_t jumpAddr = CNoeR3000A::GetInstructionJumpAddress(opVal) +
		                          (pCpu->mpIR[CNoeR3000A::kIR_PC] & CNoeR3000A::skInstructionJumpAddressPCMask);
		pCpu->TakeBranch(jumpAddr);
	}
	OP_IMPLEMENT_FMT(j)
	{
		jump_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//jump register
	OP_IMPLEMENT(jr)
	{
		pCpu->TakeBranch(reg_rs(pCpu, opVal));
	}
	OP_IMPLEMENT_FMT(jr)
	{
		op_reg_s_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//jump and link register
	OP_IMPLEMENT(jalr)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = pCpu->mpIR[CNoeR3000A::kIR_PC] + CNoeR3000A::skInstructionSize;
		}
		pCpu->TakeBranch(reg_rs(pCpu, opVal));
	}
	OP_IMPLEMENT_FMT(jalr)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s",
			string_for_op(pHandlerObject), reg_string_rd(opVal), reg_string_rs(opVal));
	}

	//jump and link
	OP_IMPLEMENT(jal)
	{
		const uint32_t jumpAddr = CNoeR3000A::GetInstructionJumpAddress(opVal) +
		                          (pCpu->mpIR[CNoeR3000A::kIR_PC] & CNoeR3000A::skInstructionJumpAddressPCMask);
		SetReturnAddress(pCpu);
		pCpu->TakeBranch(jumpAddr);
	}
	OP_IMPLEMENT_FMT(jal)
	{
		jump_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//add immediate
	OP_IMPLEMENT(addi)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = reg_rs(pCpu, opVal) + op_imm_s16(opVal);
		}
	}
	OP_IMPLEMENT_FMT(addi)
	{
		op_reg_ts_is16_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//add immediate unsigned
	OP_IMPLEMENT(addiu)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = reg_rs(pCpu, opVal) + op_imm_s16(opVal);
		}
	}
	OP_IMPLEMENT_FMT(addiu)
	{
		op_reg_ts_is16_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//set on less than immediate
	OP_IMPLEMENT(slti)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = (int32_t(reg_rs(pCpu, opVal)) < op_imm_s16(opVal)) ? 1 : 0;
		}
	}
	OP_IMPLEMENT_FMT(slti)
	{
		op_reg_ts_is16_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//set on less than immediate unsigned
	OP_IMPLEMENT(sltiu)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = (reg_rs(pCpu, opVal) < op_imm_16(opVal)) ? 1 : 0;
		}
	}
	OP_IMPLEMENT_FMT(sltiu)
	{
		op_reg_ts_iu16_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//and immediate
	OP_IMPLEMENT(andi)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = reg_rs(pCpu, opVal) & op_imm_16(opVal);
		}
	}
	OP_IMPLEMENT_FMT(andi)
	{
		op_reg_ts_iu16_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//or immediate
	OP_IMPLEMENT(ori)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = reg_rs(pCpu, opVal) | op_imm_16(opVal);
		}
	}
	OP_IMPLEMENT_FMT(ori)
	{
		op_reg_ts_iu16_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//xor immediate
	OP_IMPLEMENT(xori)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = reg_rs(pCpu, opVal) ^ op_imm_16(opVal);
		}
	}
	OP_IMPLEMENT_FMT(xori)
	{
		op_reg_ts_iu16_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//load upper immediate
	OP_IMPLEMENT(lui)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = (op_imm_16(opVal) << 16);
		}
	}
	OP_IMPLEMENT_FMT(lui)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %i",
			string_for_op(pHandlerObject), reg_string_rt(opVal), op_imm_16(opVal));
	}

	//coprocessor 0 op
	OP_IMPLEMENT(cop0)
	{
		const SR3000ADefaultOpHandler &opHandler = CNoeR3000A::mspCOP0OpHandlers[CNoeR3000A::GetInstructionRS(opVal)];
		opHandler.mpOpHandler(pCpu, opVal, &opHandler);
	}
	OP_IMPLEMENT_FMT(cop0)
	{
		const SR3000ADefaultOpHandler &opHandler = CNoeR3000A::mspCOP0OpHandlers[CNoeR3000A::GetInstructionRS(opVal)];
		opHandler.mpOpStringFormat(pStringOut, pCpu, opVal, opAddr, &opHandler);
	}

	//load byte
	OP_IMPLEMENT(lb)
	{
		const int8_t readVal = pCpu->ReadMemory8(reg_rs(pCpu, opVal) + op_imm_s16(opVal));
		pCpu->LoadGPR(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)), opVal, int32_t(readVal));
	}
	OP_IMPLEMENT_FMT(lb)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//load half-word
	OP_IMPLEMENT(lh)
	{
		const int16_t readVal = pCpu->ReadMemory16(reg_rs(pCpu, opVal) + op_imm_s16(opVal));
		pCpu->LoadGPR(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)), opVal, int32_t(readVal));
	}
	OP_IMPLEMENT_FMT(lh)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//load unaligned word left
	OP_IMPLEMENT(lwl)
	{
		const uint32_t readAddr = reg_rs(pCpu, opVal) + op_imm_s16(opVal);
		const uint32_t readVal = pCpu->ReadMemory32((readAddr & ~3));
		const uint32_t unalignedShift = ((3 - (readAddr & 3)) << 3);
		const uint32_t rtMask = (1 << unalignedShift) - 1;

		uint32_t &rt = reg_rt(pCpu, opVal);
		const uint32_t newRegVal = (rt & rtMask) | (readVal << unalignedShift);
		pCpu->LoadGPR(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)), opVal, newRegVal);
	}
	OP_IMPLEMENT_FMT(lwl)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//load word
	OP_IMPLEMENT(lw)
	{
		const uint32_t readVal = pCpu->ReadMemory32(reg_rs(pCpu, opVal) + op_imm_s16(opVal));
		pCpu->LoadGPR(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)), opVal, readVal);
	}
	OP_IMPLEMENT_FMT(lw)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//load byte unsigned
	OP_IMPLEMENT(lbu)
	{
		const uint8_t readVal = pCpu->ReadMemory8(reg_rs(pCpu, opVal) + op_imm_s16(opVal));
		pCpu->LoadGPR(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)), opVal, readVal);
	}
	OP_IMPLEMENT_FMT(lbu)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//load half-word unsigned
	OP_IMPLEMENT(lhu)
	{
		const uint16_t readVal = pCpu->ReadMemory16(reg_rs(pCpu, opVal) + op_imm_s16(opVal));
		pCpu->LoadGPR(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)), opVal, readVal);
	}
	OP_IMPLEMENT_FMT(lhu)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//load unaligned word right
	OP_IMPLEMENT(lwr)
	{
		const uint32_t readAddr = reg_rs(pCpu, opVal) + op_imm_s16(opVal);
		const uint32_t readVal = pCpu->ReadMemory32((readAddr & ~3));
		const uint32_t unalignedShift = ((readAddr & 3) << 3);
		const uint32_t rtMask = (((1 << unalignedShift) - 1) << (32 - unalignedShift));

		uint32_t &rt = reg_rt(pCpu, opVal);
		const uint32_t newRegVal = (rt & rtMask) | (readVal >> unalignedShift);
		pCpu->LoadGPR(CNoeR3000A::EGPR(CNoeR3000A::GetInstructionRT(opVal)), opVal, newRegVal);
	}
	OP_IMPLEMENT_FMT(lwr)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//store byte
	OP_IMPLEMENT(sb)
	{
		pCpu->WriteMemory8(reg_rs(pCpu, opVal) + op_imm_s16(opVal), reinterpret_cast<uint8_t &>(reg_rt(pCpu, opVal)));
	}
	OP_IMPLEMENT_FMT(sb)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//store half-word
	OP_IMPLEMENT(sh)
	{
		pCpu->WriteMemory16(reg_rs(pCpu, opVal) + op_imm_s16(opVal), reinterpret_cast<uint16_t &>(reg_rt(pCpu, opVal)));
	}
	OP_IMPLEMENT_FMT(sh)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//store unaligned word left
	OP_IMPLEMENT(swl)
	{
		const uint32_t addr = reg_rs(pCpu, opVal) + op_imm_s16(opVal);
		const uint32_t unalignedShift = ((3 - (addr & 3)) << 3);
		const uint32_t memMask = (((1 << unalignedShift) - 1) << (32 - unalignedShift));
		const uint32_t memVal = pCpu->ReadMemory32((addr & ~3));

		pCpu->WriteMemory32((addr & ~3), (reg_rt(pCpu, opVal) >> unalignedShift) | (memVal & memMask));
	}
	OP_IMPLEMENT_FMT(swl)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//store word
	OP_IMPLEMENT(sw)
	{
		pCpu->WriteMemory32(reg_rs(pCpu, opVal) + op_imm_s16(opVal), reg_rt(pCpu, opVal));
	}
	OP_IMPLEMENT_FMT(sw)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//store unaligned word right
	OP_IMPLEMENT(swr)
	{
		const uint32_t addr = reg_rs(pCpu, opVal) + op_imm_s16(opVal);
		const uint32_t unalignedShift = ((addr & 3) << 3);
		const uint32_t memMask = (1 << unalignedShift) - 1;
		const uint32_t memVal = pCpu->ReadMemory32((addr & ~3));

		pCpu->WriteMemory32((addr & ~3), (reg_rt(pCpu, opVal) << unalignedShift) | (memVal & memMask));
	}
	OP_IMPLEMENT_FMT(swr)
	{
		load_store_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//shift left logical
	OP_IMPLEMENT(sll)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = (reg_rt(pCpu, opVal) << CNoeR3000A::GetInstructionSA(opVal));
		}
	}
	OP_IMPLEMENT_FMT(sll)
	{
		op_reg_dt_sa_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//shift right logical
	OP_IMPLEMENT(srl)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = (reg_rt(pCpu, opVal) >> CNoeR3000A::GetInstructionSA(opVal));
		}
	}
	OP_IMPLEMENT_FMT(srl)
	{
		op_reg_dt_sa_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//shift right arithmetic
	OP_IMPLEMENT(sra)
	{
		if (!rd_is_r0(opVal))
		{
			reinterpret_cast<int32_t &>(reg_rd(pCpu, opVal)) = (int32_t(reg_rt(pCpu, opVal)) >> CNoeR3000A::GetInstructionSA(opVal));
		}
	}
	OP_IMPLEMENT_FMT(sra)
	{
		op_reg_dt_sa_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//shift left logical variable
	OP_IMPLEMENT(sllv)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = (reg_rt(pCpu, opVal) << reg_rs(pCpu, opVal));
		}
	}
	OP_IMPLEMENT_FMT(sllv)
	{
		op_reg_dts_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//shift right logical variable
	OP_IMPLEMENT(srlv)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = (reg_rt(pCpu, opVal) >> reg_rs(pCpu, opVal));
		}
	}
	OP_IMPLEMENT_FMT(srlv)
	{
		op_reg_dts_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//shift right arithmetic variable
	OP_IMPLEMENT(srav)
	{
		if (!rd_is_r0(opVal))
		{
			reinterpret_cast<int32_t &>(reg_rd(pCpu, opVal)) = (int32_t(reg_rt(pCpu, opVal)) >> reg_rs(pCpu, opVal));
		}
	}
	OP_IMPLEMENT_FMT(srav)
	{
		op_reg_dts_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//move from hi
	OP_IMPLEMENT(mfhi)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = pCpu->mpIR[CNoeR3000A::kIR_Hi];
		}
	}
	OP_IMPLEMENT_FMT(mfhi)
	{
		op_reg_d_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//move to hi
	OP_IMPLEMENT(mthi)
	{
		pCpu->mpIR[CNoeR3000A::kIR_Hi] = reg_rd(pCpu, opVal);
	}
	OP_IMPLEMENT_FMT(mthi)
	{
		op_reg_d_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//move from lo
	OP_IMPLEMENT(mflo)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = pCpu->mpIR[CNoeR3000A::kIR_Lo];
		}
	}
	OP_IMPLEMENT_FMT(mflo)
	{
		op_reg_d_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//move to lo
	OP_IMPLEMENT(mtlo)
	{
		pCpu->mpIR[CNoeR3000A::kIR_Lo] = reg_rd(pCpu, opVal);
	}
	OP_IMPLEMENT_FMT(mtlo)
	{
		op_reg_d_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//multiply
	OP_IMPLEMENT(mult)
	{
		const uint64_t result = uint64_t(int64_t(reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal))) *
		                                 int64_t(reinterpret_cast<int32_t &>(reg_rt(pCpu, opVal))));
		pCpu->mpIR[CNoeR3000A::kIR_Lo] = uint32_t(result);
		pCpu->mpIR[CNoeR3000A::kIR_Hi] = uint32_t(result >> 32);
	}
	OP_IMPLEMENT_FMT(mult)
	{
		op_reg_st_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//multiply unsigned
	OP_IMPLEMENT(multu)
	{
		const uint64_t result = uint64_t(reg_rs(pCpu, opVal)) * uint64_t(reg_rt(pCpu, opVal));
		pCpu->mpIR[CNoeR3000A::kIR_Lo] = uint32_t(result);
		pCpu->mpIR[CNoeR3000A::kIR_Hi] = uint32_t(result >> 32);
	}
	OP_IMPLEMENT_FMT(multu)
	{
		op_reg_st_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//divide
	OP_IMPLEMENT(div)
	{
		if (reg_rt(pCpu, opVal) != 0)
		{
			pCpu->mpIR[CNoeR3000A::kIR_Lo] = uint32_t(reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) / reinterpret_cast<int32_t &>(reg_rt(pCpu, opVal)));
			pCpu->mpIR[CNoeR3000A::kIR_Hi] = uint32_t(reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) % reinterpret_cast<int32_t &>(reg_rt(pCpu, opVal)));
		}
		else
		{
			SetDivideByZeroResult(pCpu, reg_rs(pCpu, opVal));
		}
	}
	OP_IMPLEMENT_FMT(div)
	{
		op_reg_st_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//divide unsigned
	OP_IMPLEMENT(divu)
	{
		if (reg_rt(pCpu, opVal) != 0)
		{
			pCpu->mpIR[CNoeR3000A::kIR_Lo] = reg_rs(pCpu, opVal) / reg_rt(pCpu, opVal);
			pCpu->mpIR[CNoeR3000A::kIR_Hi] = reg_rs(pCpu, opVal) % reg_rt(pCpu, opVal);
		}
		else
		{
			SetDivideByZeroResult(pCpu, reg_rs(pCpu, opVal));
		}
	}
	OP_IMPLEMENT_FMT(divu)
	{
		op_reg_st_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//add
	OP_IMPLEMENT(add)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = reg_rs(pCpu, opVal) + reg_rt(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(add)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//add unsigned
	OP_IMPLEMENT(addu)
	{
		add(pCpu, opVal, pHandlerObject);
	}
	OP_IMPLEMENT_FMT(addu)
	{
		add_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//subtract
	OP_IMPLEMENT(sub)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = reg_rs(pCpu, opVal) - reg_rt(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(sub)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//subtract unsigned
	OP_IMPLEMENT(subu)
	{
		sub(pCpu, opVal, pHandlerObject);
	}
	OP_IMPLEMENT_FMT(subu)
	{
		sub_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//and
	OP_IMPLEMENT(and)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = reg_rs(pCpu, opVal) & reg_rt(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(and)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//or
	OP_IMPLEMENT(or)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = reg_rs(pCpu, opVal) | reg_rt(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(or)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//xor
	OP_IMPLEMENT(xor)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = reg_rs(pCpu, opVal) ^ reg_rt(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(xor)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//nor
	OP_IMPLEMENT(nor)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = ~(reg_rs(pCpu, opVal) | reg_rt(pCpu, opVal));
		}
	}
	OP_IMPLEMENT_FMT(nor)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//set on less than
	OP_IMPLEMENT(slt)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = (int32_t(reg_rs(pCpu, opVal)) < int32_t(reg_rt(pCpu, opVal))) ? 1 : 0;
		}
	}
	OP_IMPLEMENT_FMT(slt)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//set on less than unsigned
	OP_IMPLEMENT(sltu)
	{
		if (!rd_is_r0(opVal))
		{
			reg_rd(pCpu, opVal) = (reg_rs(pCpu, opVal) < reg_rt(pCpu, opVal)) ? 1 : 0;
		}
	}
	OP_IMPLEMENT_FMT(sltu)
	{
		op_reg_dst_string_format(pStringOut, pCpu, opVal, pHandlerObject);
	}

	//branch if == rt
	OP_IMPLEMENT(beq)
	{
		if (reg_rs(pCpu, opVal) == reg_rt(pCpu, opVal))
		{
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(beq)
	{
		conditional_branch_reg_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//branch if != rt
	OP_IMPLEMENT(bne)
	{
		if (reg_rs(pCpu, opVal) != reg_rt(pCpu, opVal))
		{
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(bne)
	{
		conditional_branch_reg_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//branch if <= 0
	OP_IMPLEMENT(blez)
	{
		if (reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) <= 0)
		{
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(blez)
	{
		conditional_branch_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//branch if > 0
	OP_IMPLEMENT(bgtz)
	{
		if (reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) > 0)
		{
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(bgtz)
	{
		conditional_branch_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//branch if < 0
	OP_IMPLEMENT(bltz)
	{
		if (reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) < 0)
		{
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(bltz)
	{
		conditional_branch_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//branch if >= 0
	OP_IMPLEMENT(bgez)
	{
		if (reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) >= 0)
		{
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(bgez)
	{
		conditional_branch_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//branch if < 0 and link
	OP_IMPLEMENT(bltzal)
	{
		if (reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) < 0)
		{
			SetReturnAddress(pCpu);
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(bltzal)
	{
		conditional_branch_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//branch if >= 0 and link
	OP_IMPLEMENT(bgezal)
	{
		if (reinterpret_cast<int32_t &>(reg_rs(pCpu, opVal)) >= 0)
		{
			SetReturnAddress(pCpu);
			TakeConditionalBranch(pCpu, opVal);
		}
	}
	OP_IMPLEMENT_FMT(bgezal)
	{
		conditional_branch_string_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//move from cop0 register
	OP_IMPLEMENT(mfc0)
	{
		if (!rt_is_r0(opVal))
		{
			reg_rt(pCpu, opVal) = pCpu->C0R()[CNoeR3000A::GetInstructionRD(opVal)];
		}
	}
	OP_IMPLEMENT_FMT(mfc0)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s",
			string_for_op(pHandlerObject), reg_string_rt(opVal), cop0_reg_string(CNoeR3000A::GetInstructionRD(opVal)));
	}
	//move from cop0 register
	OP_IMPLEMENT(cfc0)
	{
		mfc0(pCpu, opVal, pHandlerObject);
	}
	OP_IMPLEMENT_FMT(cfc0)
	{
		mfc0_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//move to cop0 register
	OP_IMPLEMENT(mtc0)
	{
		pCpu->mpWriteCOP0Reg(pCpu, CNoeR3000A::EC0R(CNoeR3000A::GetInstructionRD(opVal)), reg_rt(pCpu, opVal));
	}
	OP_IMPLEMENT_FMT(mtc0)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "%s%s, %s",
			string_for_op(pHandlerObject), reg_string_rt(opVal), cop0_reg_string(CNoeR3000A::GetInstructionRD(opVal)));
	}
	//move to cop0 register
	OP_IMPLEMENT(ctc0)
	{
		mtc0(pCpu, opVal, pHandlerObject);
	}
	OP_IMPLEMENT_FMT(ctc0)
	{
		mtc0_format(pStringOut, pCpu, opVal, opAddr, pHandlerObject);
	}

	//undo shift done at exception entry time
	OP_IMPLEMENT(rfe)
	{
		const uint32_t kStatusMask = (1 << 6) - 1;
		const uint32_t oldStatus = pCpu->C0R()[CNoeR3000A::kC0R_Status];
		const uint32_t newStatus = (oldStatus & ~kStatusMask) | ((oldStatus & kStatusMask) >> 2);
		pCpu->mpWriteCOP0Reg(pCpu, CNoeR3000A::kC0R_Status, newStatus);
	}
	OP_IMPLEMENT_FMT(rfe)
	{
		NoeSPrintF(pStringOut, CNoeR3000A::skMaximumOpStringLength, "rfe");
	}

private:
	static void TakeConditionalBranch(CNoeR3000A *pCpu, const uint32_t opVal)
	{
		pCpu->TakeBranch(pCpu->mpIR[CNoeR3000A::kIR_PC] + (op_imm_s16(opVal) << 2));
	}

	static void SetReturnAddress(CNoeR3000A *pCpu)
	{
		pCpu->mpGPR[CNoeR3000A::kGPR_RA] = pCpu->mpIR[CNoeR3000A::kIR_PC] + CNoeR3000A::skInstructionSize;
	}

	static void SetDivideByZeroResult(CNoeR3000A *pCpu, const uint32_t eVal)
	{
		//should check on hardware
		pCpu->mpIR[CNoeR3000A::kIR_Lo] = UINT_MAX;
		pCpu->mpIR[CNoeR3000A::kIR_Hi] = eVal;
	}

	CNoeR3000AOps(); //should never be instantiated
};

SR3000ADefaultOpHandler CNoeR3000A::mspDefaultOpHandlers[CNoeR3000A::skInstructionOpMaxCount] =
{
	OP_OB(table2), OP_OB(cbranch), OP_OB(j), OP_OB(jal), OP_OB(beq), OP_OB(bne), OP_OB(blez), OP_OB(bgtz),
	OP_OB(addi), OP_OB(addiu), OP_OB(slti), OP_OB(sltiu), OP_OB(andi), OP_OB(ori), OP_OB(xori), OP_OB(lui),
	OP_OB(cop0), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(lb), OP_OB(lh), OP_OB(lwl), OP_OB(lw), OP_OB(lbu), OP_OB(lhu), OP_OB(lwr), OP_OB(invalid),
	OP_OB(sb), OP_OB(sh), OP_OB(swl), OP_OB(sw), OP_OB(invalid), OP_OB(invalid), OP_OB(swr), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid)
};

SR3000ADefaultOpHandler CNoeR3000A::mspTable2OpHandlers[CNoeR3000A::skInstructionOpMaxCount] =
{
	OP_OB(sll), OP_OB(invalid), OP_OB(srl), OP_OB(sra), OP_OB(sllv), OP_OB(invalid), OP_OB(srlv), OP_OB(srav),
	OP_OB(jr), OP_OB(jalr), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid)/*syscall*/, OP_OB(invalid)/*break*/, OP_OB(invalid), OP_OB(invalid),
	OP_OB(mfhi), OP_OB(mthi), OP_OB(mflo), OP_OB(mtlo), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(mult), OP_OB(multu), OP_OB(div), OP_OB(divu), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(add), OP_OB(addu), OP_OB(sub), OP_OB(subu), OP_OB(and), OP_OB(or), OP_OB(xor), OP_OB(nor),
	OP_OB(invalid), OP_OB(invalid), OP_OB(slt), OP_OB(sltu), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid)
};

SR3000ADefaultOpHandler CNoeR3000A::mspCBranchOpHandlers[CNoeR3000A::skInstructionRegMaxCount] =
{
	OP_OB(bltz), OP_OB(bgez), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(bltzal), OP_OB(bgezal), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid)
};

SR3000ADefaultOpHandler CNoeR3000A::mspCOP0OpHandlers[CNoeR3000A::skInstructionRegMaxCount] =
{
	OP_OB(mfc0), OP_OB(invalid), OP_OB(cfc0), OP_OB(invalid), OP_OB(mtc0), OP_OB(invalid), OP_OB(ctc0), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(rfe), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid),
	OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid), OP_OB(invalid)
};

CNoeR3000A::CNoeR3000A()
	: mpUserData(NULL)
#if NOER3000A_DEBUGGER()
	, mDebugger()
#endif
{
	SetDefaults();
	Reset();

#if NOER3000A_DEBUGGER()
	mDebugger.SetUserData(this);

	mDebugger.SetDebuggerReadData(debugger_read_data);
	mDebugger.SetDebuggerWriteData(debugger_write_data);

	mDebugger.SetDebuggerReadReg(debugger_read_reg);
	mDebugger.SetDebuggerWriteReg(debugger_write_reg);

	mDebugger.SetDebuggerDisassemble(debugger_disassemble);

	const NNoeDebugProtocol::SContext debugContext("MIPS_R3000A", skInstructionSize, 0, NNoeDebugProtocol::skRegCountAll);
	mDebugger.SetContexts(&debugContext, 1);

	//no need to keep track of register indices, as we implicitly associate based on added order/range
	NoeAssert(mDebugger.GetRegisterCount() == 0);
	for (uint32_t gprIndex = 0; gprIndex < CNoeR3000A::kGPR_Count; ++gprIndex)
	{
		mDebugger.AddRegister(skGPRNameForIndex[gprIndex], sizeof(uint32_t));
	}
	for (uint32_t irIndex = 0; irIndex < CNoeR3000A::kIR_Count; ++irIndex)
	{
		mDebugger.AddRegister(skIRNameForIndex[irIndex], sizeof(uint32_t),
			(irIndex == CNoeR3000A::kIR_PC) ? NNoeDebugProtocol::kRF_PC : NNoeDebugProtocol::kRF_None);
	}
	for (uint32_t c0rIndex = 0; c0rIndex < CNoeR3000A::kC0R_Count; ++c0rIndex)
	{
		mDebugger.AddRegister(cop0_reg_string(c0rIndex), sizeof(uint32_t));
	}
#endif
}

void CNoeR3000A::ExecuteCycles(const uint64_t cycleCount)
{
	const uint64_t targetCycleCount = mCyclesExecuted + cycleCount;
	NoeAssert(targetCycleCount > mCyclesExecuted);
	while (mCyclesExecuted < targetCycleCount)
	{
		ReadAndExecuteOp();
	}
}

void CNoeR3000A::ConsumeCycles(const int64_t cycleCount)
{
	mCyclesExecuted += cycleCount;
	if (mpOnCyclesConsumed)
	{
		mpOnCyclesConsumed(this, cycleCount);
	}
}

void CNoeR3000A::CheckDelayedOperations()
{
	//check for n-cycle delayed branches
	if (mDelayBranch.mTargetCycle && mDelayBranch.mTargetCycle <= mCyclesExecuted)
	{
		mpIR[kIR_PC] = mDelayBranch.mAddr;
		mDelayBranch.mTargetCycle = 0;

		if (mpOnBranchTaken)
		{
			mpOnBranchTaken(this, mDelayBranch.mAddr);
		}
	}

	//check for n-cycle delayed loads
	if (mDelayGPRLoadMask)
	{
		uint32_t bitStart, bitEndPlusOne;
		NoeGetFirstLastBitSet64(&bitStart, &bitEndPlusOne, mDelayGPRLoadMask);
		for (uint32_t currentBitIndex = bitStart; currentBitIndex < bitEndPlusOne; ++currentBitIndex)
		{
			const uint64_t currentBit = (1ULL << uint64_t(currentBitIndex));
			if (mDelayGPRLoadMask & currentBit)
			{
				const SDelayGPRLoad &delayLoad = mDelayGPRLoad[currentBitIndex];
				if (delayLoad.mTargetCycle <= mCyclesExecuted)
				{
					mpGPR[currentBitIndex] = delayLoad.mRegVal;
					mDelayGPRLoadMask &= ~currentBit;
				}
			}
		}
	}
}

void CNoeR3000A::AddOpHandler(const SOpHandler &opHandler)
{
	const uint32_t op = GetInstructionOp(opHandler.mOpVal);
	mOpHandlers[op].push_back(opHandler);
}

void CNoeR3000A::SetDefaults()
{
	for (int32_t opIndex = 0; opIndex < skInstructionOpMaxCount; ++opIndex)
	{
		mOpHandlers[opIndex].clear();
	}

	SetReadMemory8(NULL);
	SetReadMemory16(NULL);
	SetReadMemory32(NULL);
	SetReadInstruction(NULL);
	SetWriteMemory8(NULL);
	SetWriteMemory16(NULL);
	SetWriteMemory32(NULL);

	SetWriteCOP0Reg(NULL);
	
	SetDelayLoadForOp(NULL);

	SetOnCyclesConsumed(NULL);
	SetOnOpExecuted(NULL);
	SetOnBranchBegin(NULL);
	SetOnBranchTaken(NULL);

	SetGPRMemory(NULL);
	SetIRMemory(NULL);
	SetC0RMemory(NULL);

	SetCyclesPerOp(skDefaultCyclesPerOp);

	SetDelayBranchCycles(skDefaultDelayBranchCycles);
	SetDelayLoadCycles(skDefaultDelayLoadCycles);
}

void CNoeR3000A::Reset()
{
	mCyclesExecuted = 0;
	mDelayBranch = SDelayBranch();
	mDelayGPRLoadMask = 0;

#if NOER3000A_DEBUGGER()
	mDebugger.OnReset();
#endif
}

void CNoeR3000A::SetReadMemory8(TReadMemory8 pReadMemory8)
{
	mpReadMemory8 = (pReadMemory8) ? pReadMemory8 : default_read_memory<uint8_t>;
}

void CNoeR3000A::SetReadMemory16(TReadMemory16 pReadMemory16)
{
	mpReadMemory16 = (pReadMemory16) ? pReadMemory16 : default_read_memory<uint16_t>;
}

void CNoeR3000A::SetReadMemory32(TReadMemory32 pReadMemory32)
{
	mpReadMemory32 = (pReadMemory32) ? pReadMemory32 : default_read_memory<uint32_t>;
}

void CNoeR3000A::SetReadInstruction(TReadInstruction pReadInstruction)
{
	mpReadInstruction = (pReadInstruction) ? pReadInstruction : default_read_memory<uint32_t>;
}

void CNoeR3000A::SetWriteMemory8(TWriteMemory8 pWriteMemory8)
{
	mpWriteMemory8 = (pWriteMemory8) ? pWriteMemory8 : default_write_memory<uint8_t>;
}

void CNoeR3000A::SetWriteMemory16(TWriteMemory16 pWriteMemory16)
{
	mpWriteMemory16 = (pWriteMemory16) ? pWriteMemory16 : default_write_memory<uint16_t>;
}

void CNoeR3000A::SetWriteMemory32(TWriteMemory32 pWriteMemory32)
{
	mpWriteMemory32 = (pWriteMemory32) ? pWriteMemory32 : default_write_memory<uint32_t>;
}

void CNoeR3000A::SetWriteCOP0Reg(TWriteCOP0Reg pWriteCOP0Reg)
{
	mpWriteCOP0Reg = (pWriteCOP0Reg) ? pWriteCOP0Reg : default_write_cop0_reg;
}

void CNoeR3000A::SetDelayLoadForOp(TDelayLoadForOp pDelayLoadForOp)
{
	mpDelayLoadForOp = pDelayLoadForOp;
}

void CNoeR3000A::SetOnCyclesConsumed(TOnCyclesConsumed pOnCyclesConsumed)
{
	mpOnCyclesConsumed = pOnCyclesConsumed;
}

void CNoeR3000A::SetOnOpExecuted(TOnOpExecuted pOnOpExecuted)
{
	mpOnOpExecuted = pOnOpExecuted;
}

void CNoeR3000A::SetOnBranchBegin(TOnBranch pOnBranchBegin)
{
	mpOnBranchBegin = pOnBranchBegin;
}

void CNoeR3000A::SetOnBranchTaken(TOnBranch pOnBranchTaken)
{
	mpOnBranchTaken = pOnBranchTaken;
}

void CNoeR3000A::SetGPRMemory(uint32_t *pGPR)
{
	mpGPR = (pGPR) ? pGPR : mDefaultGPR;
}

void CNoeR3000A::SetIRMemory(uint32_t *pIR)
{
	mpIR = (pIR) ? pIR : mDefaultIR;
}

void CNoeR3000A::SetC0RMemory(uint32_t *pC0R)
{
	mpC0R = (pC0R) ? pC0R : mDefaultC0R;
}

const char *CNoeR3000A::GetGPRName(const EGPR gprIndex)
{
	return skGPRNameForIndex[gprIndex];
}

void CNoeR3000A::ReadAndExecuteOp()
{
	const uint32_t opAddr = mpIR[kIR_PC];
	mpIR[kIR_Op] = ReadInstruction(opAddr);

#if NOER3000A_DEBUGGER()
	mDebugger.CheckInstructionBP(opAddr);

	//spin in place while suspended, waiting to receive a command to resume.
	//currently we don't explicitly support stepping in/over/out, so they'll just be
	//treated as single steps with this behavior.
	do
	{
		mDebugger.Update();
	} while (mDebugger.GetStatus() == NNoeDebugProtocol::kStatus_Halted);
#endif

	mpIR[kIR_PC] += skInstructionSize;
	//consume default number of cycles. usually it will be appropriate to make this 0,
	//and burn the appropriate number of cycles for actually reading the instruction,
	//particularly contingent upon whether the instruction was in a cache or not.
	ConsumeCycles(mCyclesPerOp);

	ExecuteOp(mpIR[kIR_Op], opAddr);

	NoeAssert(mpGPR[kGPR_Zero] == 0);
}

void CNoeR3000A::ExecuteOp(const uint32_t opVal, const uint32_t opAddr)
{
	//don't bother doing anything if this is a pure nop
	if (opVal != 0)
	{
		const uint32_t op = GetInstructionOp(opVal);
	
		//first see if we have any custom handlers for this op
		bool ranCustomHandler = false;
		const TOpHandlerList &handlers = mOpHandlers[op];
		for (TOpHandlerList::const_iterator it = handlers.begin(); it != handlers.end(); ++it)
		{
			const SOpHandler &handler = *it;
			if (handler.mOpVal == (opVal & handler.mOpMask) &&
				handler.mpOpHandler(this, opVal))
			{
				ranCustomHandler = true;
				break;
			}
		}

		//if nothing handled it, fall back to our table
		if (!ranCustomHandler)
		{
			const SR3000ADefaultOpHandler &opHandler = mspDefaultOpHandlers[op];
			opHandler.mpOpHandler(this, opVal, &opHandler);
		}
	}

	if (mpOnOpExecuted)
	{
		mpOnOpExecuted(this, opVal);
	}

	CheckDelayedOperations();
}

void CNoeR3000A::TakeBranch(const uint32_t addr)
{
	if (mpOnBranchBegin)
	{
		mpOnBranchBegin(this, addr);
	}

	if (mDelayBranchCycles)
	{
		mDelayBranch.mAddr = addr;
		mDelayBranch.mTargetCycle = mCyclesExecuted + mDelayBranchCycles;
	}
	else
	{
		//execute the delay slot in-place - even if we don't have a cycle-based delay set, this is a mandatory part of the architecture
		ReadAndExecuteOp();

		mpIR[kIR_PC] = addr;

		if (mpOnBranchTaken)
		{
			mpOnBranchTaken(this, addr);
		}
	}
}

void CNoeR3000A::LoadGPR(const EGPR gprIndex, const uint32_t opVal, const uint32_t regVal)
{
	if (gprIndex != kGPR_Zero)
	{
		const uint32_t delayLoadCycles = (mpDelayLoadForOp) ? mpDelayLoadForOp(this, opVal) : mDelayLoadCycles;
		if (!delayLoadCycles)
		{
			mpGPR[gprIndex] = regVal;
		}
		else
		{
			mDelayGPRLoad[gprIndex].mRegVal = regVal;
			mDelayGPRLoad[gprIndex].mTargetCycle = mCyclesExecuted + delayLoadCycles;
			mDelayGPRLoadMask |= (1ULL << gprIndex);
		}
	}
}

#if NOER3000A_DEBUGGER()
	#define DEBUGGER_CHECK_READ(addr, size, accessType) \
		if (accessType == kAT_Default) \
		{ \
			mDebugger.CheckDataReadBP(addr, size); \
		}
	#define DEBUGGER_CHECK_WRITE(addr, size, accessType) \
		if (accessType == kAT_Default) \
		{ \
			mDebugger.CheckDataWriteBP(addr, size); \
		}
#else
	#define DEBUGGER_CHECK_READ(addr, size, accessType)
	#define DEBUGGER_CHECK_WRITE(addr, size, accessType)
#endif

uint8_t CNoeR3000A::ReadMemory8(const uint32_t addr, const EAccessType accessType)
{
	DEBUGGER_CHECK_READ(addr, sizeof(uint8_t), accessType);
	return mpReadMemory8(this, addr, accessType);
}

uint16_t CNoeR3000A::ReadMemory16(const uint32_t addr, const EAccessType accessType)
{
	DEBUGGER_CHECK_READ(addr, sizeof(uint16_t), accessType);
	return mpReadMemory16(this, addr, accessType);
}

uint32_t CNoeR3000A::ReadMemory32(const uint32_t addr, const EAccessType accessType)
{
	DEBUGGER_CHECK_READ(addr, sizeof(uint32_t), accessType);
	return mpReadMemory32(this, addr, accessType);
}

uint32_t CNoeR3000A::ReadInstruction(const uint32_t addr, const EAccessType accessType)
{
	return mpReadInstruction(this, addr, accessType);
}

void CNoeR3000A::WriteMemory8(const uint32_t addr, const uint8_t memVal, const EAccessType accessType)
{
	DEBUGGER_CHECK_WRITE(addr, sizeof(uint8_t), accessType);
	mpWriteMemory8(this, addr, memVal, accessType);
}

void CNoeR3000A::WriteMemory16(const uint32_t addr, const uint16_t memVal, const EAccessType accessType)
{
	DEBUGGER_CHECK_WRITE(addr, sizeof(uint16_t), accessType);
	mpWriteMemory16(this, addr, memVal, accessType);
}

void CNoeR3000A::WriteMemory32(const uint32_t addr, const uint32_t memVal, const EAccessType accessType)
{
	DEBUGGER_CHECK_WRITE(addr, sizeof(uint32_t), accessType);
	mpWriteMemory32(this, addr, memVal, accessType);
}

void CNoeR3000A::FormatOpString(char *pStringOut, const uint32_t opVal, const uint32_t opAddr)
{
	if (opVal != 0)
	{
		const uint32_t op = GetInstructionOp(opVal);
	
		const TOpHandlerList &handlers = mOpHandlers[op];
		for (TOpHandlerList::const_iterator it = handlers.begin(); it != handlers.end(); ++it)
		{
			const SOpHandler &handler = *it;
			if (handler.mOpVal == (opVal & handler.mOpMask) &&
				handler.mpOpStringFormat &&
				handler.mpOpStringFormat(pStringOut, this, opVal))
			{
				return;
			}
		}

		const SR3000ADefaultOpHandler &opHandler = mspDefaultOpHandlers[op];
		opHandler.mpOpStringFormat(pStringOut, this, opVal, opAddr, &opHandler);
	}
	else
	{
		CNoeR3000AOps::nop_format(pStringOut, this, opVal, opAddr, NULL);
	}
}


































































8337195 page hits since February 11, 2009.

Site design and contents (c) 2009 Rich Whitehouse. Except those contents which happen to be images or screenshots containing shit that is (c) someone/something else entirely. That shit isn't really mine. Fair use though! FAIR USE!
All works on this web site are the result of my own personal efforts, and are not in any way supported by any given company. You alone are responsible for any damages which you may incur as a result of this web site or files related to this web site.