mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
904 lines
25 KiB
C++
904 lines
25 KiB
C++
#include "pch.h"
|
|
#include "SNES/Coprocessors/ST018/ArmV3Cpu.h"
|
|
#include "SNES/Coprocessors/ST018/St018.h"
|
|
#include "GBA/GbaCpuMultiply.h"
|
|
#include "Shared/ArmEnums.h"
|
|
#include "Shared/Emulator.h"
|
|
#include "Shared/EmuSettings.h"
|
|
#include "Utilities/Serializer.h"
|
|
|
|
ArmOpCategory ArmV3Cpu::_armCategory[0x1000];
|
|
ArmV3Cpu::Func ArmV3Cpu::_armTable[0x1000];
|
|
|
|
void ArmV3Cpu::Init(Emulator* emu, St018* st018)
|
|
{
|
|
_emu = emu;
|
|
_st018 = st018;
|
|
}
|
|
|
|
ArmV3Cpu::~ArmV3Cpu()
|
|
{
|
|
}
|
|
|
|
void ArmV3Cpu::StaticInit()
|
|
{
|
|
InitArmOpTable();
|
|
}
|
|
|
|
void ArmV3Cpu::SwitchMode(ArmV3CpuMode mode)
|
|
{
|
|
//High bit of mode is always set according to psr test
|
|
mode = (ArmV3CpuMode)((int)mode | 0x10);
|
|
|
|
if(_state.CPSR.Mode == mode) {
|
|
return;
|
|
}
|
|
|
|
ArmV3CpuMode orgMode = _state.CPSR.Mode;
|
|
switch(orgMode) {
|
|
default:
|
|
case ArmV3CpuMode::System:
|
|
case ArmV3CpuMode::User:
|
|
memcpy(_state.UserRegs, &_state.R[8], 7 * sizeof(uint32_t));
|
|
break;
|
|
|
|
case ArmV3CpuMode::Fiq:
|
|
memcpy(_state.FiqRegs, &_state.R[8], 7 * sizeof(uint32_t));
|
|
break;
|
|
|
|
case ArmV3CpuMode::Irq:
|
|
memcpy(_state.UserRegs, &_state.R[8], 5 * sizeof(uint32_t));
|
|
memcpy(_state.IrqRegs, &_state.R[13], 2 * sizeof(uint32_t));
|
|
break;
|
|
|
|
case ArmV3CpuMode::Supervisor:
|
|
memcpy(_state.UserRegs, &_state.R[8], 5 * sizeof(uint32_t));
|
|
memcpy(_state.SupervisorRegs, &_state.R[13], 2 * sizeof(uint32_t));
|
|
break;
|
|
|
|
case ArmV3CpuMode::Abort:
|
|
memcpy(_state.UserRegs, &_state.R[8], 5 * sizeof(uint32_t));
|
|
memcpy(_state.AbortRegs, &_state.R[13], 2 * sizeof(uint32_t));
|
|
break;
|
|
|
|
case ArmV3CpuMode::Undefined:
|
|
memcpy(_state.UserRegs, &_state.R[8], 5 * sizeof(uint32_t));
|
|
memcpy(_state.UndefinedRegs, &_state.R[13], 2 * sizeof(uint32_t));
|
|
break;
|
|
}
|
|
|
|
_state.CPSR.Mode = mode;
|
|
|
|
if(mode != ArmV3CpuMode::Fiq) {
|
|
memcpy(&_state.R[8], _state.UserRegs, 7 * sizeof(uint32_t));
|
|
switch(mode) {
|
|
case ArmV3CpuMode::Irq: memcpy(&_state.R[13], _state.IrqRegs, 2 * sizeof(uint32_t)); break;
|
|
case ArmV3CpuMode::Supervisor: memcpy(&_state.R[13], _state.SupervisorRegs, 2 * sizeof(uint32_t)); break;
|
|
case ArmV3CpuMode::Abort: memcpy(&_state.R[13], _state.AbortRegs, 2 * sizeof(uint32_t)); break;
|
|
case ArmV3CpuMode::Undefined: memcpy(&_state.R[13], _state.UndefinedRegs, 2 * sizeof(uint32_t)); break;
|
|
}
|
|
} else {
|
|
memcpy(&_state.R[8], _state.FiqRegs, 7 * sizeof(uint32_t));
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::ReloadPipeline()
|
|
{
|
|
ArmV3CpuPipeline& pipe = _state.Pipeline;
|
|
pipe.Mode = ArmV3AccessMode::Prefetch | ArmV3AccessMode::Word;
|
|
|
|
pipe.ReloadRequested = false;
|
|
pipe.Fetch.Address = _state.R[15] = _state.R[15] & ~0x03;
|
|
|
|
pipe.Fetch.OpCode = ReadCode(pipe.Mode, pipe.Fetch.Address);
|
|
pipe.Execute = pipe.Decode;
|
|
pipe.Decode = pipe.Fetch;
|
|
|
|
pipe.Fetch.Address = _state.R[15] = (_state.R[15] + 4);
|
|
pipe.Fetch.OpCode = ReadCode(pipe.Mode, pipe.Fetch.Address);
|
|
}
|
|
|
|
void ArmV3Cpu::ProcessException(ArmV3CpuMode mode, ArmV3CpuVector vector)
|
|
{
|
|
#ifndef DUMMYCPU
|
|
ArmV3CpuFlags cpsr = _state.CPSR;
|
|
SwitchMode(mode);
|
|
GetSpsr() = cpsr;
|
|
_state.CPSR.IrqDisable = true;
|
|
_state.R[14] = _state.Pipeline.Decode.Address;
|
|
_state.R[15] = (uint32_t)vector;
|
|
_state.Pipeline.ReloadRequested = true;
|
|
#endif
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::ReadCode(ArmV3AccessModeVal mode, uint32_t addr)
|
|
{
|
|
#ifndef DUMMYCPU
|
|
//Next access should be sequential
|
|
//This is done before the call to Read() because e.g if DMA pauses the CPU and
|
|
//runs, the next access will not be sequential (force-nseq-access test)
|
|
_state.Pipeline.Mode |= ArmV3AccessMode::Sequential;
|
|
return _st018->ReadCpu(mode, addr);
|
|
#else
|
|
uint32_t value = _st018->DebugCpuRead(mode, addr);
|
|
LogMemoryOperation(addr, value, mode, MemoryOperationType::ExecOpCode);
|
|
return value;
|
|
#endif
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::Read(ArmV3AccessModeVal mode, uint32_t addr)
|
|
{
|
|
#ifndef DUMMYCPU
|
|
_state.Pipeline.Mode &= ~ArmV3AccessMode::Sequential;
|
|
return _st018->ReadCpu(mode, addr);
|
|
#else
|
|
uint32_t value = _st018->DebugCpuRead(mode, addr);
|
|
LogMemoryOperation(addr, value, mode, MemoryOperationType::Read);
|
|
return value;
|
|
#endif
|
|
}
|
|
|
|
void ArmV3Cpu::Write(ArmV3AccessModeVal mode, uint32_t addr, uint32_t value)
|
|
{
|
|
#ifndef DUMMYCPU
|
|
_state.Pipeline.Mode &= ~ArmV3AccessMode::Sequential;
|
|
_st018->WriteCpu(mode, addr, value);
|
|
#else
|
|
LogMemoryOperation(addr, value, mode, MemoryOperationType::Write);
|
|
#endif
|
|
}
|
|
|
|
void ArmV3Cpu::Idle()
|
|
{
|
|
#ifndef DUMMYCPU
|
|
_state.Pipeline.Mode &= ~ArmV3AccessMode::Sequential;
|
|
_st018->ProcessIdleCycle();
|
|
#endif
|
|
}
|
|
|
|
void ArmV3Cpu::Idle(uint8_t cycleCount)
|
|
{
|
|
switch(cycleCount) {
|
|
case 4: Idle(); [[fallthrough]];
|
|
case 3: Idle(); [[fallthrough]];
|
|
case 2: Idle(); [[fallthrough]];
|
|
case 1: Idle(); break;
|
|
}
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::R(uint8_t reg)
|
|
{
|
|
return _state.R[reg];
|
|
}
|
|
|
|
ArmV3CpuFlags& ArmV3Cpu::GetSpsr()
|
|
{
|
|
switch(_state.CPSR.Mode) {
|
|
default:
|
|
case ArmV3CpuMode::User: return _state.CPSR;
|
|
case ArmV3CpuMode::Fiq: return _state.FiqSpsr;
|
|
case ArmV3CpuMode::Irq: return _state.IrqSpsr;
|
|
case ArmV3CpuMode::Supervisor: return _state.SupervisorSpsr;
|
|
case ArmV3CpuMode::Abort: return _state.AbortSpsr;
|
|
case ArmV3CpuMode::Undefined: return _state.UndefinedSpsr;
|
|
case ArmV3CpuMode::System: return _state.CPSR;
|
|
}
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::Add(uint32_t op1, uint32_t op2, bool carry, bool updateFlags)
|
|
{
|
|
uint32_t result = op1 + op2 + (uint32_t)carry;
|
|
if(updateFlags) {
|
|
uint32_t overflow = ~(op1 ^ op2) & (op1 ^ result) & (1 << 31);
|
|
_state.CPSR.Negative = result & (1 << 31);
|
|
_state.CPSR.Zero = (uint32_t)result == 0;
|
|
_state.CPSR.Overflow = overflow;
|
|
_state.CPSR.Carry = (op1 ^ op2 ^ overflow ^ result) & (1 << 31);
|
|
}
|
|
return (uint32_t)result;
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::Sub(uint32_t op1, uint32_t op2, bool carry, bool updateFlags)
|
|
{
|
|
return Add(op1, ~op2, carry, updateFlags);
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::LogicalOp(uint32_t result, bool carry, bool updateFlags)
|
|
{
|
|
//"If the S bit is set(and Rd is not R15, see below) the V flag in the CPSR will be unaffected, the C
|
|
//flag will be set to the carry out from the barrel shifter (or preserved when the shift
|
|
//operation is LSL #0), the Z flag will be set if and only if the result is all zeros, and the N
|
|
//flag will be set to the logical value of bit 31 of the result."
|
|
if(updateFlags) {
|
|
_state.CPSR.Carry = carry;
|
|
_state.CPSR.Zero = result == 0;
|
|
_state.CPSR.Negative = result & (1 << 31);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::RotateRight(uint32_t value, uint32_t shift)
|
|
{
|
|
return (value >> shift) | (value << (32 - shift));
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::RotateRight(uint32_t value, uint32_t shift, bool& carry)
|
|
{
|
|
carry = (value >> (shift - 1)) & 1;
|
|
return (value >> shift) | (value << (32 - shift));
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::ShiftLsl(uint32_t value, uint8_t shift, bool& carry)
|
|
{
|
|
if(shift) {
|
|
carry = shift < 33 ? (value & (1 << (32 - shift))) : 0;
|
|
value = shift < 32 ? (value << shift) : 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::ShiftLsr(uint32_t value, uint8_t shift, bool& carry)
|
|
{
|
|
if(shift) {
|
|
carry = shift < 33 ? (value & (1 << (shift - 1))) : 0;
|
|
value = shift < 32 ? (value >> shift) : 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::ShiftAsr(uint32_t value, uint8_t shift, bool& carry)
|
|
{
|
|
if(shift) {
|
|
carry = shift < 33 ? (value & (1 << (shift - 1))) : (value & (1 << 31));
|
|
value = shift < 32 ? ((int32_t)value >> shift) : ((int32_t)value >> 31);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::ShiftRor(uint32_t value, uint8_t shift, bool& carry)
|
|
{
|
|
if(shift) {
|
|
shift &= 0x1F;
|
|
if(shift) {
|
|
value = (value << (32 - shift)) | (value >> shift);
|
|
}
|
|
carry = value & (1 << 31);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint32_t ArmV3Cpu::ShiftRrx(uint32_t value, bool& carry)
|
|
{
|
|
bool orgCarry = carry;
|
|
carry = value & 0x01;
|
|
return (value >> 1) | (orgCarry << 31);
|
|
}
|
|
|
|
void ArmV3Cpu::PowerOn(bool forReset)
|
|
{
|
|
uint64_t cycleCount = _state.CycleCount;
|
|
_state = {};
|
|
_state.Pipeline.ReloadRequested = true;
|
|
|
|
_state.CPSR.Mode = ArmV3CpuMode::Supervisor;
|
|
_state.CPSR.IrqDisable = true;
|
|
_state.CPSR.FiqDisable = true;
|
|
|
|
ProcessPipeline();
|
|
|
|
if(forReset) {
|
|
_state.CycleCount = cycleCount;
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::SetProgramCounter(uint32_t addr)
|
|
{
|
|
//Used by debugger - set new PC and reload pipeline (using debugger reads)
|
|
_state.R[15] = addr;
|
|
|
|
ArmV3CpuPipeline& pipe = _state.Pipeline;
|
|
pipe.Mode = ArmV3AccessMode::Prefetch | ArmV3AccessMode::Word | ArmV3AccessMode::Sequential;
|
|
pipe.ReloadRequested = false;
|
|
pipe.Execute.Address = _state.R[15] = _state.R[15] & ~0x03;
|
|
pipe.Execute.OpCode = _st018->DebugCpuRead(pipe.Mode, pipe.Execute.Address);
|
|
pipe.Decode.Address = _state.R[15] = _state.R[15] + 4;
|
|
pipe.Decode.OpCode = _st018->DebugCpuRead(pipe.Mode, pipe.Decode.Address);
|
|
pipe.Fetch.Address = _state.R[15] = _state.R[15] + 4;
|
|
pipe.Fetch.OpCode = _st018->DebugCpuRead(pipe.Mode, pipe.Fetch.Address);
|
|
}
|
|
|
|
ArmV3CpuState& ArmV3Cpu::GetState()
|
|
{
|
|
return _state;
|
|
}
|
|
|
|
ArmOpCategory ArmV3Cpu::GetArmOpCategory(uint32_t opCode)
|
|
{
|
|
uint16_t opType = ((opCode & 0x0FF00000) >> 16) | ((opCode & 0xF0) >> 4);
|
|
return _armCategory[opType];
|
|
}
|
|
|
|
void ArmV3Cpu::ArmBranch()
|
|
{
|
|
bool withLink = (_opCode & (1 << 24)) != 0;
|
|
int32_t offset = (((int32_t)_opCode << 8) >> 6); //sign extend + shift right by 2
|
|
if(withLink) {
|
|
_state.R[14] = _state.R[15] - 4;
|
|
}
|
|
|
|
_state.R[15] += offset;
|
|
_state.Pipeline.ReloadRequested = true;
|
|
}
|
|
|
|
void ArmV3Cpu::ArmMsr()
|
|
{
|
|
//----_00i1_0d10_mmmm_1111_oooo_oooo_oooo
|
|
//i: immediate
|
|
//d: destination psr
|
|
//o: operand
|
|
//m: mask
|
|
bool immediate = _opCode & (1 << 25);
|
|
bool writeToSpsr = _opCode & (1 << 22);
|
|
uint8_t mask = (_opCode >> 16) & 0x0F;
|
|
|
|
if(writeToSpsr && (_state.CPSR.Mode == ArmV3CpuMode::User || _state.CPSR.Mode == ArmV3CpuMode::System)) {
|
|
return;
|
|
}
|
|
|
|
uint32_t value;
|
|
if(immediate) {
|
|
value = _opCode & 0xFF;
|
|
uint8_t shift = (_opCode >> 8) & 0x0F;
|
|
if(shift) {
|
|
value = RotateRight(value, shift * 2);
|
|
}
|
|
} else {
|
|
value = R(_opCode & 0x0F);
|
|
}
|
|
|
|
ArmV3CpuFlags& flags = writeToSpsr ? GetSpsr() : _state.CPSR;
|
|
if(mask & 0x08) {
|
|
flags.Negative = value & (1 << 31);
|
|
flags.Zero = value & (1 << 30);
|
|
flags.Carry = value & (1 << 29);
|
|
flags.Overflow = value & (1 << 28);
|
|
}
|
|
|
|
if(mask & 0x01) {
|
|
if(writeToSpsr || _state.CPSR.Mode != ArmV3CpuMode::User) {
|
|
if(!writeToSpsr) {
|
|
SwitchMode((ArmV3CpuMode)(value & 0x1F));
|
|
} else {
|
|
flags.Mode = (ArmV3CpuMode)(value & 0x1F);
|
|
}
|
|
flags.FiqDisable = value & (1 << 6);
|
|
flags.IrqDisable = value & (1 << 7);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::ArmMrs()
|
|
{
|
|
//----_0001_0s00_1111_dddd_0000_0000_0000
|
|
//s: source psr
|
|
//d: destination reg
|
|
bool useSpsr = _opCode & (1 << 22);
|
|
uint8_t rd = (_opCode >> 12) & 0x0F;
|
|
SetR(rd, useSpsr ? GetSpsr().ToInt32() : _state.CPSR.ToInt32());
|
|
}
|
|
|
|
void ArmV3Cpu::ArmDataProcessing()
|
|
{
|
|
//Data Processing
|
|
//----_00io_ooos_nnnn_dddd_pppp_pppp_pppp
|
|
//i: immediate
|
|
//o: opcode
|
|
//s: set flags
|
|
//n: 1st operand reg
|
|
//d: destination reg
|
|
//p: 2nd operand
|
|
|
|
bool immediate = (_opCode & (1 << 25)) != 0;
|
|
uint8_t rn = (_opCode >> 16) & 0x0F;
|
|
uint32_t op1 = R(rn);
|
|
uint8_t dstReg = (_opCode >> 12) & 0x0F;
|
|
bool updateFlags = (_opCode & (1 << 20)) != 0;
|
|
|
|
uint32_t op2;
|
|
bool carry = _state.CPSR.Carry;
|
|
if(immediate) {
|
|
uint8_t shift = (_opCode >> 8) & 0x0F;
|
|
op2 = (_opCode & 0xFF);
|
|
if(shift) {
|
|
op2 = RotateRight(op2, shift * 2, carry);
|
|
}
|
|
} else {
|
|
uint8_t shiftType = (_opCode >> 5) & 0x03;
|
|
uint8_t shift;
|
|
uint8_t rm = _opCode & 0x0F;
|
|
op2 = R(rm);
|
|
|
|
bool useRegValue = _opCode & (1 << 4);
|
|
if(useRegValue) {
|
|
//Shift amount in register
|
|
Idle();
|
|
uint8_t rs = (_opCode >> 8) & 0x0F;
|
|
shift = R(rs) + (rs == 15 ? 4 : 0);
|
|
if(rm == 15) {
|
|
op2 += 4;
|
|
}
|
|
if(rn == 15) {
|
|
op1 += 4;
|
|
}
|
|
} else {
|
|
shift = (_opCode >> 7) & 0x1F;
|
|
}
|
|
|
|
switch(shiftType) {
|
|
case 0: op2 = ShiftLsl(op2, shift, carry); break;
|
|
case 1: op2 = ShiftLsr(op2, (useRegValue || shift) ? shift : 32, carry); break;
|
|
case 2: op2 = ShiftAsr(op2, (useRegValue || shift) ? shift : 32, carry); break;
|
|
case 3: op2 = (!useRegValue && shift == 0) ? ShiftRrx(op2, carry) : ShiftRor(op2, shift, carry); break;
|
|
}
|
|
}
|
|
|
|
switch((ArmAluOperation)((_opCode >> 21) & 0x0F)) {
|
|
case ArmAluOperation::And: SetR(dstReg, LogicalOp(op1 & op2, carry, updateFlags)); break;
|
|
case ArmAluOperation::Eor: SetR(dstReg, LogicalOp(op1 ^ op2, carry, updateFlags)); break;
|
|
case ArmAluOperation::Sub: SetR(dstReg, Sub(op1, op2, true, updateFlags)); break;
|
|
case ArmAluOperation::Rsb: SetR(dstReg, Sub(op2, op1, true, updateFlags)); break;
|
|
case ArmAluOperation::Add: SetR(dstReg, Add(op1, op2, false, updateFlags)); break;
|
|
case ArmAluOperation::Adc: SetR(dstReg, Add(op1, op2, _state.CPSR.Carry, updateFlags)); break;
|
|
case ArmAluOperation::Sbc: SetR(dstReg, Sub(op1, op2, _state.CPSR.Carry, updateFlags)); break;
|
|
case ArmAluOperation::Rsc: SetR(dstReg, Sub(op2, op1, _state.CPSR.Carry, updateFlags)); break;
|
|
case ArmAluOperation::Tst: LogicalOp(op1 & op2, carry, true); break;
|
|
case ArmAluOperation::Teq: LogicalOp(op1 ^ op2, carry, true); break;
|
|
case ArmAluOperation::Cmp: Sub(op1, op2, true, true); break;
|
|
case ArmAluOperation::Cmn: Add(op1, op2, false, true); break;
|
|
case ArmAluOperation::Orr: SetR(dstReg, LogicalOp(op1 | op2, carry, updateFlags)); break;
|
|
case ArmAluOperation::Mov: SetR(dstReg, LogicalOp(op2, carry, updateFlags)); break;
|
|
case ArmAluOperation::Bic: SetR(dstReg, LogicalOp(op1 & ~op2, carry, updateFlags)); break;
|
|
case ArmAluOperation::Mvn: SetR(dstReg, LogicalOp(~op2, carry, updateFlags)); break;
|
|
}
|
|
|
|
if(dstReg == 15 && updateFlags) {
|
|
ArmV3CpuFlags spsr = GetSpsr();
|
|
SwitchMode(spsr.Mode);
|
|
_state.CPSR = spsr;
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::ArmMultiply()
|
|
{
|
|
//Multiply and Multiply-Accumulate (MUL, MLA)
|
|
//----_0000_00as_dddd_nnnn_ssss_1001_mmmm
|
|
uint8_t rd = (_opCode >> 16) & 0x0F;
|
|
uint8_t rn = (_opCode >> 12) & 0x0F;
|
|
uint8_t rs = (_opCode >> 8) & 0x0F;
|
|
uint8_t rm = _opCode & 0x0F;
|
|
bool updateFlags = (_opCode & (1 << 20)) != 0;
|
|
bool multAndAcc = (_opCode & (1 << 21)) != 0;
|
|
|
|
MultiplicationOutput output;
|
|
if(multAndAcc) {
|
|
output = GbaCpuMultiply::mla(R(rm), R(rs), R(rn));
|
|
} else {
|
|
output = GbaCpuMultiply::mul(R(rm), R(rs));
|
|
}
|
|
|
|
Idle(output.CycleCount);
|
|
if(multAndAcc) {
|
|
Idle();
|
|
}
|
|
|
|
uint32_t result = output.Output;
|
|
SetR(rd, result);
|
|
|
|
if(updateFlags) {
|
|
_state.CPSR.Carry = output.Carry;
|
|
_state.CPSR.Zero = result == 0;
|
|
_state.CPSR.Negative = (result & (1 << 31));
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::ArmMultiplyLong()
|
|
{
|
|
//Multiply Long and Multiply-Accumulate Long (MULL,MLAL)
|
|
//----_0000_1uas_hhhh_llll_ssss_1001_mmmm
|
|
uint8_t rh = (_opCode >> 16) & 0x0F;
|
|
uint8_t rl = (_opCode >> 12) & 0x0F;
|
|
uint8_t rs = (_opCode >> 8) & 0x0F;
|
|
uint8_t rm = _opCode & 0x0F;
|
|
|
|
bool updateFlags = (_opCode & (1 << 20)) != 0;
|
|
bool multAndAcc = (_opCode & (1 << 21)) != 0;
|
|
bool sign = (_opCode & (1 << 22)) != 0;
|
|
|
|
Idle();
|
|
|
|
MultiplicationOutput output;
|
|
if(sign) {
|
|
if(multAndAcc) {
|
|
output = GbaCpuMultiply::smlal(R(rl), R(rh), R(rm), R(rs));
|
|
} else {
|
|
output = GbaCpuMultiply::smull(R(rm), R(rs));
|
|
}
|
|
} else {
|
|
if(multAndAcc) {
|
|
output = GbaCpuMultiply::umlal(R(rl), R(rh), R(rm), R(rs));
|
|
} else {
|
|
output = GbaCpuMultiply::umull(R(rm), R(rs));
|
|
}
|
|
}
|
|
|
|
Idle(output.CycleCount);
|
|
if(multAndAcc) {
|
|
Idle();
|
|
}
|
|
|
|
uint64_t result = output.Output;
|
|
|
|
SetR(rl, (uint32_t)result);
|
|
SetR(rh, (uint32_t)(result >> 32));
|
|
|
|
if(updateFlags) {
|
|
_state.CPSR.Carry = output.Carry;
|
|
_state.CPSR.Zero = result == 0;
|
|
_state.CPSR.Negative = (result & ((uint64_t)1 << 63));
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::ArmSingleDataTransfer()
|
|
{
|
|
//Single Data Transfer (LDR, STR)
|
|
//----_01ip_ubwl_nnnn_dddd_oooo_oooo_oooo
|
|
//i: immediate (when cleared)
|
|
//p: post/pre: add offset after/before transfer
|
|
//u: down/up: subtract/add offset from/to base
|
|
//b: byte transfer
|
|
//w: write-back: write address into base
|
|
//l: store/load
|
|
//n: base register
|
|
//d: src/dst register
|
|
//o: offset (immediate or register)
|
|
bool immediate = (_opCode & (1 << 25)) == 0;
|
|
bool pre = (_opCode & (1 << 24)) != 0;
|
|
bool up = (_opCode & (1 << 23)) != 0;
|
|
bool byte = (_opCode & (1 << 22)) != 0;
|
|
bool writeBack = (_opCode & (1 << 21)) != 0;
|
|
bool load = (_opCode & (1 << 20)) != 0;
|
|
uint8_t rn = (_opCode >> 16) & 0x0F;
|
|
uint8_t rd = (_opCode >> 12) & 0x0F;
|
|
|
|
uint32_t addr = R(rn);
|
|
|
|
int32_t offset;
|
|
if(immediate) {
|
|
offset = _opCode & 0xFFF;
|
|
} else {
|
|
uint8_t shiftType = (_opCode >> 5) & 0x03;
|
|
uint8_t shift;
|
|
uint8_t rm = _opCode & 0x0F;
|
|
shift = (_opCode >> 7) & 0x1F;
|
|
|
|
offset = R(rm);
|
|
bool carry = _state.CPSR.Carry;
|
|
switch(shiftType) {
|
|
case 0: offset = ShiftLsl(offset, shift, carry); break;
|
|
case 1: offset = ShiftLsr(offset, shift ? shift : 32, carry); break;
|
|
case 2: offset = ShiftAsr(offset, shift ? shift : 32, carry); break;
|
|
case 3: offset = shift == 0 ? ShiftRrx(offset, carry) : ShiftRor(offset, shift, carry); break;
|
|
}
|
|
}
|
|
|
|
if(pre) {
|
|
addr += up ? offset : -offset;
|
|
}
|
|
|
|
ArmV3AccessModeVal mode = byte ? ArmV3AccessMode::Byte : ArmV3AccessMode::Word;
|
|
if(load) {
|
|
SetR(rd, Read(mode, addr));
|
|
Idle();
|
|
} else {
|
|
Write(mode, addr, R(rd) + (rd == 15 ? 4 : 0));
|
|
}
|
|
|
|
if(!pre) {
|
|
addr += up ? offset : -offset;
|
|
}
|
|
|
|
if((rd != rn || !load) && (writeBack || !pre)) {
|
|
SetR(rn, addr);
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::ArmBlockDataTransfer()
|
|
{
|
|
//Block Data Transfer (LDM, STM)
|
|
//----_100p_uswl_nnnn_rrrr_rrrr_rrrr_rrrr
|
|
//p: post/pre: add offset after/before transfer
|
|
//u: down/up: subtract/add offset from/to base
|
|
//s: psr & force user bit
|
|
//w: write-back: write address into base
|
|
//l: store/load
|
|
//n: base register
|
|
//r: register list
|
|
bool pre = (_opCode & (1 << 24)) != 0;
|
|
bool up = (_opCode & (1 << 23)) != 0;
|
|
bool psrForceUser = (_opCode & (1 << 22)) != 0;
|
|
bool writeBack = (_opCode & (1 << 21)) != 0;
|
|
bool load = (_opCode & (1 << 20)) != 0;
|
|
uint8_t rn = (_opCode >> 16) & 0x0F;
|
|
uint16_t regMask = (uint16_t)_opCode;
|
|
|
|
uint32_t base = R(rn) + (rn == 15 ? 4 : 0);
|
|
uint32_t addr = base;
|
|
|
|
uint8_t regCount = 0;
|
|
for(int i = 0; i < 16; i++) {
|
|
regCount += (regMask & (1 << i)) ? 1 : 0;
|
|
}
|
|
|
|
if(!regMask) {
|
|
//Glitch when mask is empty - only R15 is stored/loaded, but address changes as if all 16 were written/loaded
|
|
regCount = 16;
|
|
regMask = 0x8000;
|
|
}
|
|
|
|
if(!up) {
|
|
addr -= (regCount - (pre ? 0 : 1)) * 4;
|
|
} else if(pre) {
|
|
addr += 4;
|
|
}
|
|
|
|
if(writeBack && load) {
|
|
SetR(rn, base + (regCount * (up ? 4 : -4)));
|
|
}
|
|
|
|
ArmV3CpuMode orgMode = _state.CPSR.Mode;
|
|
if(psrForceUser && (!load || (load && !(regMask & 0x8000)))) {
|
|
SwitchMode(ArmV3CpuMode::User);
|
|
}
|
|
|
|
bool firstReg = true;
|
|
ArmV3AccessModeVal mode = ArmV3AccessMode::Word;
|
|
for(int i = 0; i < 16; i++) {
|
|
if(regMask & (1 << i)) {
|
|
if(!load) {
|
|
Write(mode, addr, R(i) + (i == 15 ? 4 : 0));
|
|
}
|
|
|
|
if(firstReg && writeBack) {
|
|
//Write-back happens at this point, based on the gba-tests/arm test 522
|
|
SetR(rn, base + (regCount * (up ? 4 : -4)));
|
|
firstReg = false;
|
|
}
|
|
|
|
if(load) {
|
|
//LDM doesn't appear to be affected by the rotation that is usually applied to unaligned reads? based on gba-tests/arm test 508
|
|
SetR(i, Read(mode | ArmV3AccessMode::NoRotate, addr));
|
|
}
|
|
|
|
mode |= ArmV3AccessMode::Sequential;
|
|
addr += 4;
|
|
}
|
|
}
|
|
|
|
if(load) {
|
|
Idle();
|
|
}
|
|
|
|
SwitchMode(orgMode);
|
|
|
|
if(psrForceUser && load && (regMask & 0x8000)) {
|
|
ArmV3CpuFlags spsr = GetSpsr();
|
|
SwitchMode(spsr.Mode);
|
|
_state.CPSR = spsr;
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::ArmSingleDataSwap()
|
|
{
|
|
//Single Data Swap (SWP)
|
|
//----_0001_0b00_nnnn_dddd_0000_1001_mmmm
|
|
bool byte = _opCode & (1 << 22);
|
|
uint8_t rn = (_opCode >> 16) & 0x0F;
|
|
uint8_t rd = (_opCode >> 12) & 0x0F;
|
|
uint8_t rm = _opCode & 0x0F;
|
|
|
|
uint32_t mode = byte ? ArmV3AccessMode::Byte : ArmV3AccessMode::Word;
|
|
uint32_t val = Read(mode, R(rn));
|
|
Idle();
|
|
Write(mode, R(rn), R(rm));
|
|
SetR(rd, val);
|
|
}
|
|
|
|
void ArmV3Cpu::ArmSoftwareInterrupt()
|
|
{
|
|
ProcessException(ArmV3CpuMode::Supervisor, ArmV3CpuVector::SoftwareIrq);
|
|
}
|
|
|
|
void ArmV3Cpu::ArmInvalidOp()
|
|
{
|
|
#ifndef DUMMYCPU
|
|
ProcessException(ArmV3CpuMode::Undefined, ArmV3CpuVector::Undefined);
|
|
#endif
|
|
}
|
|
|
|
bool ArmV3Cpu::CheckConditions(uint32_t condCode)
|
|
{
|
|
/*Code Suffix Flags Meaning
|
|
0000 EQ Z set equal
|
|
0001 NE Z clear not equal
|
|
0010 CS C set unsigned higher or same
|
|
0011 CC C clear unsigned lower
|
|
0100 MI N set negative
|
|
0101 PL N clear positive or zero
|
|
0110 VS V set overflow
|
|
0111 VC V clear no overflow
|
|
1000 HI C set and Z clear unsigned higher
|
|
1001 LS C clear or Z set unsigned lower or same
|
|
1010 GE N equals V greater or equal
|
|
1011 LT N not equal to V less than
|
|
1100 GT Z clear AND(N equals V) greater than
|
|
1101 LE Z set OR(N not equal to V) less than or equal
|
|
1110 AL(ignored) always
|
|
*/
|
|
switch(condCode) {
|
|
case 0: return _state.CPSR.Zero;
|
|
case 1: return !_state.CPSR.Zero;
|
|
case 2: return _state.CPSR.Carry;
|
|
case 3: return !_state.CPSR.Carry;
|
|
case 4: return _state.CPSR.Negative;
|
|
case 5: return !_state.CPSR.Negative;
|
|
case 6: return _state.CPSR.Overflow;
|
|
case 7: return !_state.CPSR.Overflow;
|
|
case 8: return _state.CPSR.Carry && !_state.CPSR.Zero;
|
|
case 9: return !_state.CPSR.Carry || _state.CPSR.Zero;
|
|
case 10: return _state.CPSR.Negative == _state.CPSR.Overflow;
|
|
case 11: return _state.CPSR.Negative != _state.CPSR.Overflow;
|
|
case 12: return !_state.CPSR.Zero && (_state.CPSR.Negative == _state.CPSR.Overflow);
|
|
case 13: return _state.CPSR.Zero || (_state.CPSR.Negative != _state.CPSR.Overflow);
|
|
case 14: return true;
|
|
case 15: return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ArmV3Cpu::InitArmOpTable()
|
|
{
|
|
auto addEntry = [=](int i, Func func, ArmOpCategory category) {
|
|
_armTable[i] = func;
|
|
_armCategory[i] = category;
|
|
};
|
|
|
|
for(int i = 0; i < 0x1000; i++) {
|
|
addEntry(i, &ArmV3Cpu::ArmInvalidOp, ArmOpCategory::InvalidOp);
|
|
}
|
|
|
|
//Data Processing / PSR Transfer (MRS, MSR)
|
|
//----_00??_????_----_----_----_????_----
|
|
for(int i = 0; i <= 0x3FF; i++) {
|
|
ArmAluOperation operation = (ArmAluOperation)((i >> 5) & 0x0F);
|
|
bool setConditionCodes = (i & 0x10) != 0;
|
|
if(!setConditionCodes && operation >= ArmAluOperation::Tst && operation <= ArmAluOperation::Cmn) {
|
|
if(i & 0x020) {
|
|
addEntry(0x000 | i, &ArmV3Cpu::ArmMsr, ArmOpCategory::Msr);
|
|
} else {
|
|
addEntry(0x000 | i, &ArmV3Cpu::ArmMrs, ArmOpCategory::Mrs);
|
|
}
|
|
} else {
|
|
addEntry(0x000 | i, &ArmV3Cpu::ArmDataProcessing, ArmOpCategory::DataProcessing);
|
|
}
|
|
}
|
|
|
|
//Branch and Branch with Link (B, BL)
|
|
//----_101?_????_----_----_----_????_----
|
|
for(int i = 0; i <= 0x1FF; i++) {
|
|
addEntry(0xA00 | i, &ArmV3Cpu::ArmBranch, ArmOpCategory::Branch);
|
|
}
|
|
|
|
//Single Data Transfer (LDR, STR)
|
|
//----_01??_????_----_----_----_????_----
|
|
for(int i = 0; i <= 0x3FF; i++) {
|
|
addEntry(0x400 | i, &ArmV3Cpu::ArmSingleDataTransfer, ArmOpCategory::SingleDataTransfer);
|
|
}
|
|
|
|
//Block Data Transfer (LDM, STM)
|
|
//----_100?_????_----_----_----_????_----
|
|
for(int i = 0; i <= 0x1FF; i++) {
|
|
addEntry(0x800 | i, &ArmV3Cpu::ArmBlockDataTransfer, ArmOpCategory::BlockDataTransfer);
|
|
}
|
|
|
|
//Multiply and Multiply-Accumulate (MUL, MLA)
|
|
//----_0000_00??_----_----_----_1001_----
|
|
for(int i = 0; i <= 0x03; i++) {
|
|
addEntry(0x009 | (i << 4), &ArmV3Cpu::ArmMultiply, ArmOpCategory::Multiply);
|
|
}
|
|
|
|
//Multiply Long and Multiply-Accumulate Long (MULL,MLAL)
|
|
//----_0000_1???_----_----_----_1001_----
|
|
for(int i = 0; i <= 0x07; i++) {
|
|
addEntry(0x089 | (i << 4), &ArmV3Cpu::ArmMultiplyLong, ArmOpCategory::MultiplyLong);
|
|
}
|
|
|
|
//Single Data Swap (SWP)
|
|
//----_0001_0000_----_----_----_1001_----
|
|
addEntry(0x109, &ArmV3Cpu::ArmSingleDataSwap, ArmOpCategory::SingleDataSwap); //word
|
|
addEntry(0x149, &ArmV3Cpu::ArmSingleDataSwap, ArmOpCategory::SingleDataSwap); //byte
|
|
|
|
for(int i = 0; i <= 0xFF; i++) {
|
|
addEntry(0xF00 + i, &ArmV3Cpu::ArmSoftwareInterrupt, ArmOpCategory::SoftwareInterrupt);
|
|
}
|
|
}
|
|
|
|
void ArmV3Cpu::Serialize(Serializer& s)
|
|
{
|
|
SV(_state.CycleCount);
|
|
|
|
SV(_state.Pipeline.Fetch.Address);
|
|
SV(_state.Pipeline.Fetch.OpCode);
|
|
SV(_state.Pipeline.Decode.Address);
|
|
SV(_state.Pipeline.Decode.OpCode);
|
|
SV(_state.Pipeline.Execute.Address);
|
|
SV(_state.Pipeline.Execute.OpCode);
|
|
SV(_state.Pipeline.ReloadRequested);
|
|
SV(_state.Pipeline.Mode);
|
|
|
|
SV(_state.CPSR.Mode);
|
|
SV(_state.CPSR.FiqDisable);
|
|
SV(_state.CPSR.IrqDisable);
|
|
SV(_state.CPSR.Overflow);
|
|
SV(_state.CPSR.Carry);
|
|
SV(_state.CPSR.Zero);
|
|
SV(_state.CPSR.Negative);
|
|
|
|
SVArray(_state.R, 16);
|
|
SVArray(_state.UserRegs, 7);
|
|
SVArray(_state.FiqRegs, 7);
|
|
SVArray(_state.IrqRegs, 2);
|
|
SVArray(_state.SupervisorRegs, 2);
|
|
SVArray(_state.AbortRegs, 2);
|
|
SVArray(_state.UndefinedRegs, 2);
|
|
|
|
SV(_state.FiqSpsr.Mode);
|
|
SV(_state.FiqSpsr.FiqDisable);
|
|
SV(_state.FiqSpsr.IrqDisable);
|
|
SV(_state.FiqSpsr.Overflow);
|
|
SV(_state.FiqSpsr.Carry);
|
|
SV(_state.FiqSpsr.Zero);
|
|
SV(_state.FiqSpsr.Negative);
|
|
|
|
SV(_state.IrqSpsr.Mode);
|
|
SV(_state.IrqSpsr.FiqDisable);
|
|
SV(_state.IrqSpsr.IrqDisable);
|
|
SV(_state.IrqSpsr.Overflow);
|
|
SV(_state.IrqSpsr.Carry);
|
|
SV(_state.IrqSpsr.Zero);
|
|
SV(_state.IrqSpsr.Negative);
|
|
|
|
SV(_state.SupervisorSpsr.Mode);
|
|
SV(_state.SupervisorSpsr.FiqDisable);
|
|
SV(_state.SupervisorSpsr.IrqDisable);
|
|
SV(_state.SupervisorSpsr.Overflow);
|
|
SV(_state.SupervisorSpsr.Carry);
|
|
SV(_state.SupervisorSpsr.Zero);
|
|
SV(_state.SupervisorSpsr.Negative);
|
|
|
|
SV(_state.AbortSpsr.Mode);
|
|
SV(_state.AbortSpsr.FiqDisable);
|
|
SV(_state.AbortSpsr.IrqDisable);
|
|
SV(_state.AbortSpsr.Overflow);
|
|
SV(_state.AbortSpsr.Carry);
|
|
SV(_state.AbortSpsr.Zero);
|
|
SV(_state.AbortSpsr.Negative);
|
|
|
|
SV(_state.UndefinedSpsr.Mode);
|
|
SV(_state.UndefinedSpsr.FiqDisable);
|
|
SV(_state.UndefinedSpsr.IrqDisable);
|
|
SV(_state.UndefinedSpsr.Overflow);
|
|
SV(_state.UndefinedSpsr.Carry);
|
|
SV(_state.UndefinedSpsr.Zero);
|
|
SV(_state.UndefinedSpsr.Negative);
|
|
}
|