mame/src/devices/cpu/arm7/arm7ops.cpp
Peter Ferrie 0c261fca16 arm7ops.cpp: repair broken ARM [Peter Ferrie]
sorry, I couldn't resist.
2017-03-09 14:32:27 -08:00

1966 lines
46 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Steve Ellenoff,R. Belmont,Ryan Holtz
#include "emu.h"
#include "arm7.h"
#include "arm7core.h"
#include "arm7help.h"
int64_t arm7_cpu_device::saturate_qbit_overflow(int64_t res)
{
if (res > 2147483647) // INT32_MAX
{ // overflow high? saturate and set Q
res = 2147483647;
set_cpsr(GET_CPSR | Q_MASK);
}
else if (res < (-2147483647-1)) // INT32_MIN
{ // overflow low? saturate and set Q
res = (-2147483647-1);
set_cpsr(GET_CPSR | Q_MASK);
}
return res;
}
void arm7_cpu_device::SwitchMode(uint32_t cpsr_mode_val)
{
uint32_t cspr = m_r[eCPSR] & ~MODE_FLAG;
set_cpsr(cspr | cpsr_mode_val);
}
/* Decodes an Op2-style shifted-register form. If @carry@ is non-zero the
* shifter carry output will manifest itself as @*carry == 0@ for carry clear
* and @*carry != 0@ for carry set.
SJE: Rules:
IF RC = 256, Result = no shift.
LSL 0 = Result = RM, Carry = Old Contents of CPSR C Bit
LSL(0,31) = Result shifted, least significant bit is in carry out
LSL 32 = Result of 0, Carry = Bit 0 of RM
LSL >32 = Result of 0, Carry out 0
LSR 0 = LSR 32 (see below)
LSR 32 = Result of 0, Carry = Bit 31 of RM
LSR >32 = Result of 0, Carry out 0
ASR >=32 = ENTIRE Result = bit 31 of RM
ROR 32 = Result = RM, Carry = Bit 31 of RM
ROR >32 = Same result as ROR n-32 until amount in range of 1-32 then follow rules
*/
uint32_t arm7_cpu_device::decodeShift(uint32_t insn, uint32_t *pCarry)
{
uint32_t k = (insn & INSN_OP2_SHIFT) >> INSN_OP2_SHIFT_SHIFT; // Bits 11-7
uint32_t rm = GetRegister(insn & INSN_OP2_RM);
uint32_t t = (insn & INSN_OP2_SHIFT_TYPE) >> INSN_OP2_SHIFT_TYPE_SHIFT;
if ((insn & INSN_OP2_RM) == 0xf) {
// "If a register is used to specify the shift amount the PC will be 12 bytes ahead." (instead of 8)
rm += t & 1 ? 12 : 8;
}
/* All shift types ending in 1 are Rk, not #k */
if (t & 1)
{
// LOG(("%08x: RegShift %02x %02x\n", R15, k >> 1, GetRegister(k >> 1)));
#if ARM7_DEBUG_CORE
if ((insn & 0x80) == 0x80)
LOG(("%08x: RegShift ERROR (p36)\n", R15));
#endif
// see p35 for check on this
//k = GetRegister(k >> 1) & 0x1f;
// Keep only the bottom 8 bits for a Register Shift
k = GetRegister(k >> 1) & 0xff;
if (k == 0) /* Register shift by 0 is a no-op */
{
// LOG(("%08x: NO-OP Regshift\n", R15));
/* TODO this is wrong for at least ROR by reg with lower
* 5 bits 0 but lower 8 bits non zero */
if (pCarry)
*pCarry = GET_CPSR & C_MASK;
return rm;
}
}
/* Decode the shift type and perform the shift */
switch (t >> 1)
{
case 0: /* LSL */
// LSL 32 = Result of 0, Carry = Bit 0 of RM
// LSL >32 = Result of 0, Carry out 0
if (k >= 32)
{
if (pCarry)
*pCarry = (k == 32) ? rm & 1 : 0;
return 0;
}
else
{
if (pCarry)
{
// LSL 0 = Result = RM, Carry = Old Contents of CPSR C Bit
// LSL (0,31) = Result shifted, least significant bit is in carry out
*pCarry = k ? (rm & (1 << (32 - k))) : (GET_CPSR & C_MASK);
}
return k ? LSL(rm, k) : rm;
}
case 1: /* LSR */
if (k == 0 || k == 32)
{
if (pCarry)
*pCarry = rm & SIGN_BIT;
return 0;
}
else if (k > 32)
{
if (pCarry)
*pCarry = 0;
return 0;
}
else
{
if (pCarry)
*pCarry = (rm & (1 << (k - 1)));
return LSR(rm, k);
}
case 2: /* ASR */
if (k == 0 || k > 32)
k = 32;
if (pCarry)
*pCarry = (rm & (1 << (k - 1)));
if (k >= 32)
return rm & SIGN_BIT ? 0xffffffffu : 0;
else
{
if (rm & SIGN_BIT)
return LSR(rm, k) | (0xffffffffu << (32 - k));
else
return LSR(rm, k);
}
case 3: /* ROR and RRX */
if (k)
{
while (k > 32)
k -= 32;
if (pCarry)
*pCarry = rm & (1 << (k - 1));
return ROR(rm, k);
}
else
{
/* RRX */
if (pCarry)
*pCarry = (rm & 1);
return LSR(rm, 1) | ((GET_CPSR & C_MASK) << 2);
}
}
LOG(("%08x: Decodeshift error\n", R15));
return 0;
} /* decodeShift */
int arm7_cpu_device::loadInc(uint32_t pat, uint32_t rbv, uint32_t s, int mode)
{
int i, result;
uint32_t data;
result = 0;
rbv &= ~3;
for (i = 0; i < 16; i++)
{
if ((pat >> i) & 1)
{
if (!m_pendingAbtD) // "Overwriting of registers stops when the abort happens."
{
data = READ32(rbv += 4);
if (i == 15)
{
if (s) /* Pull full contents from stack */
SetModeRegister(mode, 15, data);
else if (MODE32) /* Pull only address, preserve mode & status flags */
SetModeRegister(mode, 15, data);
else
{
SetModeRegister(mode, 15, (GetModeRegister(mode, 15) & ~0x03FFFFFC) | (data & 0x03FFFFFC));
}
}
else
{
SetModeRegister(mode, i, data);
}
}
result++;
}
}
return result;
}
int arm7_cpu_device::loadDec(uint32_t pat, uint32_t rbv, uint32_t s, int mode)
{
int i, result;
uint32_t data;
result = 0;
rbv &= ~3;
for (i = 15; i >= 0; i--)
{
if ((pat >> i) & 1)
{
if (!m_pendingAbtD) // "Overwriting of registers stops when the abort happens."
{
data = READ32(rbv -= 4);
if (i == 15)
{
if (s) /* Pull full contents from stack */
SetModeRegister(mode, 15, data);
else if (MODE32) /* Pull only address, preserve mode & status flags */
SetModeRegister(mode, 15, data);
else
{
SetModeRegister(mode, 15, (GetModeRegister(mode, 15) & ~0x03FFFFFC) | (data & 0x03FFFFFC));
}
}
else
{
SetModeRegister(mode, i, data);
}
}
result++;
}
}
return result;
}
int arm7_cpu_device::storeInc(uint32_t pat, uint32_t rbv, int mode)
{
int i, result;
result = 0;
for (i = 0; i < 16; i++)
{
if ((pat >> i) & 1)
{
#if ARM7_DEBUG_CORE
if (i == 15) /* R15 is plus 12 from address of STM */
LOG(("%08x: StoreInc on R15\n", R15));
#endif
WRITE32(rbv += 4, GetModeRegister(mode, i));
result++;
}
}
return result;
} /* storeInc */
int arm7_cpu_device::storeDec(uint32_t pat, uint32_t rbv, int mode)
{
int i, result = 0, cnt;
// pre-count the # of registers being stored
for (i = 15; i >= 0; i--)
{
if ((pat >> i) & 1)
{
result++;
// starting address
rbv -= 4;
}
}
cnt = 0;
for (i = 0; i <= 15; i++)
{
if ((pat >> i) & 1)
{
#if ARM7_DEBUG_CORE
if (i == 15) /* R15 is plus 12 from address of STM */
LOG(("%08x: StoreDec on R15\n", R15));
#endif
WRITE32(rbv + (cnt * 4), GetModeRegister(mode, i));
cnt++;
}
}
return result;
} /* storeDec */
/***************************************************************************
* OPCODE HANDLING
***************************************************************************/
// Co-Processor Data Operation
void arm7_cpu_device::HandleCoProcDO(uint32_t insn)
{
// This instruction simply instructs the co-processor to do something, no data is returned to ARM7 core
arm7_do_callback(*m_program, insn, 0, 0); // simply pass entire opcode to callback - since data format is actually dependent on co-proc implementation
}
// Co-Processor Register Transfer - To/From Arm to Co-Proc
void arm7_cpu_device::HandleCoProcRT(uint32_t insn)
{
/* xxxx 1110 oooL nnnn dddd cccc ppp1 mmmm */
// Load (MRC) data from Co-Proc to ARM7 register
if (insn & 0x00100000) // Bit 20 = Load or Store
{
uint32_t res = arm7_rt_r_callback(*m_program, insn, 0); // RT Read handler must parse opcode & return appropriate result
if (!m_pendingUnd)
{
SetRegister((insn >> 12) & 0xf, res);
}
}
// Store (MCR) data from ARM7 to Co-Proc register
else
{
arm7_rt_w_callback(*m_program, insn, GetRegister((insn >> 12) & 0xf), 0);
}
}
/* Data Transfer - To/From Arm to Co-Proc
Loading or Storing, the co-proc function is responsible to read/write from the base register supplied + offset
8 bit immediate value Base Offset address is << 2 to get the actual #
issues - #1 - the co-proc function, needs direct access to memory reads or writes (ie, so we must send a pointer to a func)
- #2 - the co-proc may adjust the base address (especially if it reads more than 1 word), so a pointer to the register must be used
but the old value of the register must be restored if write back is not set..
- #3 - when post incrementing is used, it's up to the co-proc func. to add the offset, since the transfer
address supplied in that case, is simply the base. I suppose this is irrelevant if write back not set
but if co-proc reads multiple address, it must handle the offset adjustment itself.
*/
// todo: test with valid instructions
void arm7_cpu_device::HandleCoProcDT(uint32_t insn)
{
uint32_t rn = (insn >> 16) & 0xf;
uint32_t rnv = GetRegister(rn); // Get Address Value stored from Rn
uint32_t ornv = rnv; // Keep value of Rn
uint32_t off = (insn & 0xff) << 2; // Offset is << 2 according to manual
uint32_t *prn = &ARM7REG(rn); // Pointer to our register, so it can be changed in the callback
#if ARM7_DEBUG_CORE
if (((insn >> 16) & 0xf) == 15 && (insn & 0x200000))
LOG(("%08x: Illegal use of R15 as base for write back value!\n", R15));
#endif
// Pre-Increment base address (IF POST INCREMENT - CALL BACK FUNCTION MUST DO IT)
if ((insn & 0x1000000) && off)
{
// Up - Down bit
if (insn & 0x800000)
rnv += off;
else
rnv -= off;
}
// Load (LDC) data from ARM7 memory to Co-Proc memory
if (insn & 0x00100000)
{
arm7_dt_r_callback(insn, prn);
}
// Store (STC) data from Co-Proc to ARM7 memory
else
{
arm7_dt_w_callback(insn, prn);
}
if (m_pendingUnd != 0) return;
// If writeback not used - ensure the original value of RN is restored in case co-proc callback changed value
if ((insn & 0x200000) == 0)
SetRegister(rn, ornv);
}
void arm7_cpu_device::HandleBranch(uint32_t insn, bool h_bit)
{
uint32_t off = (insn & INSN_BRANCH) << 2;
if (h_bit)
{
// H goes to bit1
off |= (insn & 0x01000000) >> 23;
}
/* Save PC into LR if this is a branch with link */
if (insn & INSN_BL)
{
SetRegister(14, R15 + 4);
}
/* Sign-extend the 24-bit offset in our calculations */
if (off & 0x2000000u)
{
if (MODE32)
R15 -= ((~(off | 0xfc000000u)) + 1) - 8;
else
R15 = ((R15 - (((~(off | 0xfc000000u)) + 1) - 8)) & 0x03FFFFFC) | (R15 & ~0x03FFFFFC);
}
else
{
if (MODE32)
R15 += off + 8;
else
R15 = ((R15 + (off + 8)) & 0x03FFFFFC) | (R15 & ~0x03FFFFFC);
}
}
void arm7_cpu_device::HandleMemSingle(uint32_t insn)
{
uint32_t rn, rnv, off, rd, rnv_old = 0;
/* Fetch the offset */
if (insn & INSN_I)
{
/* Register Shift */
off = decodeShift(insn, nullptr);
}
else
{
/* Immediate Value */
off = insn & INSN_SDT_IMM;
}
/* Calculate Rn, accounting for PC */
rn = (insn & INSN_RN) >> INSN_RN_SHIFT;
if (insn & INSN_SDT_P)
{
/* Pre-indexed addressing */
if (insn & INSN_SDT_U)
{
if ((MODE32) || (rn != eR15))
rnv = (GetRegister(rn) + off);
else
rnv = (GET_PC + off);
}
else
{
if ((MODE32) || (rn != eR15))
rnv = (GetRegister(rn) - off);
else
rnv = (GET_PC - off);
}
if (insn & INSN_SDT_W)
{
rnv_old = GetRegister(rn);
SetRegister(rn, rnv);
// check writeback???
}
else if (rn == eR15)
{
rnv = rnv + 8;
}
}
else
{
/* Post-indexed addressing */
if (rn == eR15)
{
if (MODE32)
rnv = R15 + 8;
else
rnv = GET_PC + 8;
}
else
{
rnv = GetRegister(rn);
}
}
/* Do the transfer */
rd = (insn & INSN_RD) >> INSN_RD_SHIFT;
if (insn & INSN_SDT_L)
{
/* Load */
if (insn & INSN_SDT_B)
{
uint32_t data = READ8(rnv);
if (!m_pendingAbtD)
{
SetRegister(rd, data);
}
}
else
{
uint32_t data = READ32(rnv);
if (!m_pendingAbtD)
{
if (rd == eR15)
{
if (MODE32)
R15 = data - 4;
else
R15 = (R15 & ~0x03FFFFFC) /* N Z C V I F M1 M0 */ | ((data - 4) & 0x03FFFFFC);
// LDR, PC takes 2S + 2N + 1I (5 total cycles)
ARM7_ICOUNT -= 2;
}
else
{
SetRegister(rd, data);
}
}
}
}
else
{
/* Store */
if (insn & INSN_SDT_B)
{
#if ARM7_DEBUG_CORE
if (rd == eR15)
LOG(("Wrote R15 in byte mode\n"));
#endif
WRITE8(rnv, (uint8_t) GetRegister(rd) & 0xffu);
}
else
{
#if ARM7_DEBUG_CORE
if (rd == eR15)
LOG(("Wrote R15 in 32bit mode\n"));
#endif
//WRITE32(rnv, rd == eR15 ? R15 + 8 : GetRegister(rd));
WRITE32(rnv, rd == eR15 ? R15 + 8 + 4 : GetRegister(rd)); // manual says STR rd = PC, +12
}
// Store takes only 2 N Cycles, so add + 1
ARM7_ICOUNT += 1;
}
if (m_pendingAbtD)
{
if ((insn & INSN_SDT_P) && (insn & INSN_SDT_W))
{
SetRegister(rn, rnv_old);
}
}
else
{
/* Do post-indexing writeback */
if (!(insn & INSN_SDT_P)/* && (insn & INSN_SDT_W)*/)
{
if (insn & INSN_SDT_U)
{
/* Writeback is applied in pipeline, before value is read from mem,
so writeback is effectively ignored */
if (rd == rn) {
SetRegister(rn, GetRegister(rd));
// todo: check for offs... ?
}
else {
if ((insn & INSN_SDT_W) != 0)
LOG(("%08x: RegisterWritebackIncrement %d %d %d\n", R15, (insn & INSN_SDT_P) != 0, (insn & INSN_SDT_W) != 0, (insn & INSN_SDT_U) != 0));
SetRegister(rn, (rnv + off));
}
}
else
{
/* Writeback is applied in pipeline, before value is read from mem,
so writeback is effectively ignored */
if (rd == rn) {
SetRegister(rn, GetRegister(rd));
}
else {
SetRegister(rn, (rnv - off));
if ((insn & INSN_SDT_W) != 0)
LOG(("%08x: RegisterWritebackDecrement %d %d %d\n", R15, (insn & INSN_SDT_P) != 0, (insn & INSN_SDT_W) != 0, (insn & INSN_SDT_U) != 0));
}
}
}
}
// arm7_check_irq_state();
} /* HandleMemSingle */
void arm7_cpu_device::HandleHalfWordDT(uint32_t insn)
{
uint32_t rn, rnv, off, rd, rnv_old = 0;
// Immediate or Register Offset?
if (insn & 0x400000) { // Bit 22 - 1 = immediate, 0 = register
// imm. value in high nibble (bits 8-11) and lo nibble (bit 0-3)
off = (((insn >> 8) & 0x0f) << 4) | (insn & 0x0f);
}
else {
// register
off = GetRegister(insn & 0x0f);
}
/* Calculate Rn, accounting for PC */
rn = (insn & INSN_RN) >> INSN_RN_SHIFT;
if (insn & INSN_SDT_P)
{
/* Pre-indexed addressing */
if (insn & INSN_SDT_U)
{
rnv = (GetRegister(rn) + off);
}
else
{
rnv = (GetRegister(rn) - off);
}
if (insn & INSN_SDT_W)
{
rnv_old = GetRegister(rn);
SetRegister(rn, rnv);
// check writeback???
}
else if (rn == eR15)
{
rnv = (rnv) + 8;
}
}
else
{
/* Post-indexed addressing */
if (rn == eR15)
{
rnv = R15 + 8;
}
else
{
rnv = GetRegister(rn);
}
}
/* Do the transfer */
rd = (insn & INSN_RD) >> INSN_RD_SHIFT;
/* Load */
if (insn & INSN_SDT_L)
{
// Signed?
if (insn & 0x40)
{
uint32_t newval;
// Signed Half Word?
if (insn & 0x20) {
uint16_t signbyte, databyte;
databyte = READ16(rnv) & 0xFFFF;
signbyte = (databyte & 0x8000) ? 0xffff : 0;
newval = (uint32_t)(signbyte << 16)|databyte;
}
// Signed Byte
else {
uint8_t databyte;
uint32_t signbyte;
databyte = READ8(rnv) & 0xff;
signbyte = (databyte & 0x80) ? 0xffffff : 0;
newval = (uint32_t)(signbyte << 8)|databyte;
}
if (!m_pendingAbtD)
{
// PC?
if (rd == eR15)
{
R15 = newval + 8;
// LDR(H,SH,SB) PC takes 2S + 2N + 1I (5 total cycles)
ARM7_ICOUNT -= 2;
}
else
{
SetRegister(rd, newval);
R15 += 4;
}
}
else
{
R15 += 4;
}
}
// Unsigned Half Word
else
{
uint32_t newval = READ16(rnv);
if (!m_pendingAbtD)
{
if (rd == eR15)
{
R15 = newval + 8;
// extra cycles for LDR(H,SH,SB) PC (5 total cycles)
ARM7_ICOUNT -= 2;
}
else
{
SetRegister(rd, newval);
R15 += 4;
}
}
else
{
R15 += 4;
}
}
}
/* Store or ARMv5+ dword insns */
else
{
if ((insn & 0x60) == 0x40) // LDRD
{
SetRegister(rd, READ32(rnv));
SetRegister(rd+1, READ32(rnv+4));
R15 += 4;
}
else if ((insn & 0x60) == 0x60) // STRD
{
WRITE32(rnv, GetRegister(rd));
WRITE32(rnv+4, GetRegister(rd+1));
R15 += 4;
}
else
{
// WRITE16(rnv, rd == eR15 ? R15 + 8 : GetRegister(rd));
WRITE16(rnv, rd == eR15 ? R15 + 8 + 4 : GetRegister(rd)); // manual says STR RD=PC, +12 of address
// if R15 is not increased then e.g. "STRH R10, [R15,#$10]" will be executed over and over again
#if 0
if (rn != eR15)
#endif
R15 += 4;
// STRH takes 2 cycles, so we add + 1
ARM7_ICOUNT += 1;
}
}
if (m_pendingAbtD)
{
if ((insn & INSN_SDT_P) && (insn & INSN_SDT_W))
{
SetRegister(rn, rnv_old);
}
}
else
{
// SJE: No idea if this writeback code works or makes sense here..
/* Do post-indexing writeback */
if (!(insn & INSN_SDT_P)/* && (insn & INSN_SDT_W)*/)
{
if (insn & INSN_SDT_U)
{
/* Writeback is applied in pipeline, before value is read from mem,
so writeback is effectively ignored */
if (rd == rn) {
SetRegister(rn, GetRegister(rd));
// todo: check for offs... ?
}
else {
if ((insn & INSN_SDT_W) != 0)
LOG(("%08x: RegisterWritebackIncrement %d %d %d\n", R15, (insn & INSN_SDT_P) != 0, (insn & INSN_SDT_W) != 0, (insn & INSN_SDT_U) != 0));
SetRegister(rn, (rnv + off));
}
}
else
{
/* Writeback is applied in pipeline, before value is read from mem,
so writeback is effectively ignored */
if (rd == rn) {
SetRegister(rn, GetRegister(rd));
}
else {
SetRegister(rn, (rnv - off));
if ((insn & INSN_SDT_W) != 0)
LOG(("%08x: RegisterWritebackDecrement %d %d %d\n", R15, (insn & INSN_SDT_P) != 0, (insn & INSN_SDT_W) != 0, (insn & INSN_SDT_U) != 0));
}
}
}
}
}
void arm7_cpu_device::HandleSwap(uint32_t insn)
{
uint32_t rn, rm, rd, tmp;
rn = GetRegister((insn >> 16) & 0xf); // reg. w/read address
rm = GetRegister(insn & 0xf); // reg. w/write address
rd = (insn >> 12) & 0xf; // dest reg
#if ARM7_DEBUG_CORE
if (rn == 15 || rm == 15 || rd == 15)
LOG(("%08x: Illegal use of R15 in Swap Instruction\n", R15));
#endif
// can be byte or word
if (insn & 0x400000)
{
tmp = READ8(rn);
WRITE8(rn, rm);
SetRegister(rd, tmp);
}
else
{
tmp = READ32(rn);
WRITE32(rn, rm);
SetRegister(rd, tmp);
}
R15 += 4;
// Instruction takes 1S+2N+1I cycles - so we subtract one more..
ARM7_ICOUNT -= 1;
}
void arm7_cpu_device::HandlePSRTransfer(uint32_t insn)
{
int reg = (insn & 0x400000) ? SPSR : eCPSR; // Either CPSR or SPSR
uint32_t newval, val;
int oldmode = GET_CPSR & MODE_FLAG;
// get old value of CPSR/SPSR
newval = GetRegister(reg);
// MSR (bit 21 set) - Copy value to CPSR/SPSR
if ((insn & 0x00200000))
{
// Immediate Value?
if (insn & INSN_I) {
// Value can be specified for a Right Rotate, 2x the value specified.
int by = (insn & INSN_OP2_ROTATE) >> INSN_OP2_ROTATE_SHIFT;
if (by)
val = ROR(insn & INSN_OP2_IMM, by << 1);
else
val = insn & INSN_OP2_IMM;
}
// Value from Register
else
{
val = GetRegister(insn & 0x0f);
}
// apply field code bits
if (reg == eCPSR)
{
if (oldmode != eARM7_MODE_USER)
{
if (insn & 0x00010000)
{
newval = (newval & 0xffffff00) | (val & 0x000000ff);
}
if (insn & 0x00020000)
{
newval = (newval & 0xffff00ff) | (val & 0x0000ff00);
}
if (insn & 0x00040000)
{
newval = (newval & 0xff00ffff) | (val & 0x00ff0000);
}
}
// status flags can be modified regardless of mode
if (insn & 0x00080000)
{
// TODO for non ARMv5E mask should be 0xf0000000 (ie mask Q bit)
newval = (newval & 0x00ffffff) | (val & 0xf8000000);
}
}
else // SPSR has stricter requirements
{
if (((GET_CPSR & 0x1f) > 0x10) && ((GET_CPSR & 0x1f) < 0x1f))
{
if (insn & 0x00010000)
{
newval = (newval & 0xffffff00) | (val & 0xff);
}
if (insn & 0x00020000)
{
newval = (newval & 0xffff00ff) | (val & 0xff00);
}
if (insn & 0x00040000)
{
newval = (newval & 0xff00ffff) | (val & 0xff0000);
}
if (insn & 0x00080000)
{
// TODO for non ARMv5E mask should be 0xf0000000 (ie mask Q bit)
newval = (newval & 0x00ffffff) | (val & 0xf8000000);
}
}
}
#if 0
// force valid mode
newval |= 0x10;
#endif
// Update the Register
if (reg == eCPSR)
{
set_cpsr(newval);
}
else
SetRegister(reg, newval);
// Switch to new mode if changed
if ((newval & MODE_FLAG) != oldmode)
SwitchMode(GET_MODE);
}
// MRS (bit 21 clear) - Copy CPSR or SPSR to specified Register
else
{
SetRegister((insn >> 12)& 0x0f, GetRegister(reg));
}
}
void arm7_cpu_device::HandleALU(uint32_t insn)
{
uint32_t op2, sc = 0, rd, rn, opcode;
uint32_t by, rdn;
// Normal Data Processing : 1S
// Data Processing with register specified shift : 1S + 1I
// Data Processing with PC written : 2S + 1N
// Data Processing with register specified shift and PC written : 2S + 1N + 1I
opcode = (insn & INSN_OPCODE) >> INSN_OPCODE_SHIFT;
rd = 0;
rn = 0;
/* --------------*/
/* Construct Op2 */
/* --------------*/
/* Immediate constant */
if (insn & INSN_I)
{
by = (insn & INSN_OP2_ROTATE) >> INSN_OP2_ROTATE_SHIFT;
if (by)
{
op2 = ROR(insn & INSN_OP2_IMM, by << 1);
sc = op2 & SIGN_BIT;
}
else
{
op2 = insn & INSN_OP2; // SJE: Shouldn't this be INSN_OP2_IMM?
sc = GET_CPSR & C_MASK;
}
}
/* Op2 = Register Value */
else
{
op2 = decodeShift(insn, (insn & INSN_S) ? &sc : nullptr);
// LD TODO sc will always be 0 if this applies
if (!(insn & INSN_S))
sc = 0;
// extra cycle (register specified shift)
ARM7_ICOUNT -= 1;
}
// LD TODO this comment is wrong
/* Calculate Rn to account for pipelining */
if ((opcode & 0xd) != 0xd) /* No Rn in MOV */
{
if ((rn = (insn & INSN_RN) >> INSN_RN_SHIFT) == eR15)
{
#if ARM7_DEBUG_CORE
LOG(("%08x: Pipelined R15 (Shift %d)\n", R15, (insn & INSN_I ? 8 : insn & 0x10u ? 12 : 12)));
#endif
if (MODE32)
rn = R15 + 8;
else
rn = GET_PC + 8;
}
else
{
rn = GetRegister(rn);
}
}
/* Perform the operation */
switch (opcode)
{
/* Arithmetic operations */
case OPCODE_SBC:
rd = (rn - op2 - (GET_CPSR & C_MASK ? 0 : 1));
HandleALUSubFlags(rd, rn, op2);
break;
case OPCODE_CMP:
case OPCODE_SUB:
rd = (rn - op2);
HandleALUSubFlags(rd, rn, op2);
break;
case OPCODE_RSC:
rd = (op2 - rn - (GET_CPSR & C_MASK ? 0 : 1));
HandleALUSubFlags(rd, op2, rn);
break;
case OPCODE_RSB:
rd = (op2 - rn);
HandleALUSubFlags(rd, op2, rn);
break;
case OPCODE_ADC:
rd = (rn + op2 + ((GET_CPSR & C_MASK) >> C_BIT));
HandleALUAddFlags(rd, rn, op2);
break;
case OPCODE_CMN:
case OPCODE_ADD:
rd = (rn + op2);
HandleALUAddFlags(rd, rn, op2);
break;
/* Logical operations */
case OPCODE_AND:
case OPCODE_TST:
rd = rn & op2;
HandleALULogicalFlags(rd, sc);
break;
case OPCODE_BIC:
rd = rn & ~op2;
HandleALULogicalFlags(rd, sc);
break;
case OPCODE_TEQ:
case OPCODE_EOR:
rd = rn ^ op2;
HandleALULogicalFlags(rd, sc);
break;
case OPCODE_ORR:
rd = rn | op2;
HandleALULogicalFlags(rd, sc);
break;
case OPCODE_MOV:
rd = op2;
HandleALULogicalFlags(rd, sc);
break;
case OPCODE_MVN:
rd = (~op2);
HandleALULogicalFlags(rd, sc);
break;
}
/* Put the result in its register if not one of the test only opcodes (TST,TEQ,CMP,CMN) */
rdn = (insn & INSN_RD) >> INSN_RD_SHIFT;
if ((opcode & 0xc) != 0x8)
{
// If Rd = R15, but S Flag not set, Result is placed in R15, but CPSR is not affected (page 44)
if (rdn == eR15 && !(insn & INSN_S))
{
if (MODE32)
{
R15 = rd;
}
else
{
R15 = (R15 & ~0x03FFFFFC) | (rd & 0x03FFFFFC);
}
// extra cycles (PC written)
ARM7_ICOUNT -= 2;
}
else
{
// Rd = 15 and S Flag IS set, Result is placed in R15, and current mode SPSR moved to CPSR
if (rdn == eR15) {
if (MODE32)
{
// When Rd is R15 and the S flag is set the result of the operation is placed in R15 and the SPSR corresponding to
// the current mode is moved to the CPSR. This allows state changes which automatically restore both PC and
// CPSR. --> This form of instruction should not be used in User mode. <--
if (GET_MODE != eARM7_MODE_USER)
{
// Update CPSR from SPSR
set_cpsr(GetRegister(SPSR));
SwitchMode(GET_MODE);
}
R15 = rd;
}
else
{
uint32_t temp;
R15 = rd; //(R15 & 0x03FFFFFC) | (rd & 0xFC000003);
temp = (GET_CPSR & 0x0FFFFF20) | (rd & 0xF0000000) /* N Z C V */ | ((rd & 0x0C000000) >> (26 - 6)) /* I F */ | (rd & 0x00000003) /* M1 M0 */;
set_cpsr( temp);
SwitchMode( temp & 3);
}
// extra cycles (PC written)
ARM7_ICOUNT -= 2;
/* IRQ masks may have changed in this instruction */
// arm7_check_irq_state();
}
else
/* S Flag is set - Write results to register & update CPSR (which was already handled using HandleALU flag macros) */
SetRegister(rdn, rd);
}
}
// SJE: Don't think this applies any more.. (see page 44 at bottom)
/* TST & TEQ can affect R15 (the condition code register) with the S bit set */
else if (rdn == eR15)
{
if (insn & INSN_S) {
#if ARM7_DEBUG_CORE
LOG(("%08x: TST class on R15 s bit set\n", R15));
#endif
if (MODE32)
R15 = rd;
else
{
uint32_t temp;
R15 = (R15 & 0x03FFFFFC) | (rd & ~0x03FFFFFC);
temp = (GET_CPSR & 0x0FFFFF20) | (rd & 0xF0000000) /* N Z C V */ | ((rd & 0x0C000000) >> (26 - 6)) /* I F */ | (rd & 0x00000003) /* M1 M0 */;
set_cpsr( temp);
SwitchMode( temp & 3);
}
/* IRQ masks may have changed in this instruction */
// arm7_check_irq_state();
}
else
{
#if ARM7_DEBUG_CORE
LOG(("%08x: TST class on R15 no s bit set\n", R15));
#endif
}
// extra cycles (PC written)
ARM7_ICOUNT -= 2;
}
// compensate for the -3 at the end
ARM7_ICOUNT += 2;
}
void arm7_cpu_device::HandleMul(uint32_t insn)
{
uint32_t r, rm, rs;
// MUL takes 1S + mI and MLA 1S + (m+1)I cycles to execute, where S and I are as
// defined in 6.2 Cycle Types on page 6-2.
// m is the number of 8 bit multiplier array cycles required to complete the
// multiply, which is controlled by the value of the multiplier operand
// specified by Rs.
rm = GetRegister(insn & INSN_MUL_RM);
rs = GetRegister((insn & INSN_MUL_RS) >> INSN_MUL_RS_SHIFT);
/* Do the basic multiply of Rm and Rs */
r = rm * rs;
#if ARM7_DEBUG_CORE
if ((insn & INSN_MUL_RM) == 0xf ||
((insn & INSN_MUL_RS) >> INSN_MUL_RS_SHIFT) == 0xf ||
((insn & INSN_MUL_RN) >> INSN_MUL_RN_SHIFT) == 0xf)
LOG(("%08x: R15 used in mult\n", R15));
#endif
/* Add on Rn if this is a MLA */
if (insn & INSN_MUL_A)
{
r += GetRegister((insn & INSN_MUL_RN) >> INSN_MUL_RN_SHIFT);
// extra cycle for MLA
ARM7_ICOUNT -= 1;
}
/* Write the result */
SetRegister((insn & INSN_MUL_RD) >> INSN_MUL_RD_SHIFT, r);
/* Set N and Z if asked */
if (insn & INSN_S)
{
set_cpsr((GET_CPSR & ~(N_MASK | Z_MASK)) | HandleALUNZFlags(r));
}
if (rs & SIGN_BIT) rs = -rs;
if (rs < 0x00000100) ARM7_ICOUNT -= 1 + 1;
else if (rs < 0x00010000) ARM7_ICOUNT -= 1 + 2;
else if (rs < 0x01000000) ARM7_ICOUNT -= 1 + 3;
else ARM7_ICOUNT -= 1 + 4;
ARM7_ICOUNT += 3;
}
// todo: add proper cycle counts
void arm7_cpu_device::HandleSMulLong(uint32_t insn)
{
int32_t rm, rs;
uint32_t rhi, rlo;
int64_t res;
// MULL takes 1S + (m+1)I and MLAL 1S + (m+2)I cycles to execute, where m is the
// number of 8 bit multiplier array cycles required to complete the multiply, which is
// controlled by the value of the multiplier operand specified by Rs.
rm = (int32_t)GetRegister(insn & 0xf);
rs = (int32_t)GetRegister(((insn >> 8) & 0xf));
rhi = (insn >> 16) & 0xf;
rlo = (insn >> 12) & 0xf;
#if ARM7_DEBUG_CORE
if ((insn & 0xf) == 15 || ((insn >> 8) & 0xf) == 15 || ((insn >> 16) & 0xf) == 15 || ((insn >> 12) & 0xf) == 15)
LOG(("%08x: Illegal use of PC as a register in SMULL opcode\n", R15));
#endif
/* Perform the multiplication */
res = (int64_t)rm * rs;
/* Add on Rn if this is a MLA */
if (insn & INSN_MUL_A)
{
int64_t acum = (int64_t)((((int64_t)(GetRegister(rhi))) << 32) | GetRegister(rlo));
res += acum;
// extra cycle for MLA
ARM7_ICOUNT -= 1;
}
/* Write the result (upper dword goes to RHi, lower to RLo) */
SetRegister(rhi, res >> 32);
SetRegister(rlo, res & 0xFFFFFFFF);
/* Set N and Z if asked */
if (insn & INSN_S)
{
set_cpsr((GET_CPSR & ~(N_MASK | Z_MASK)) | HandleLongALUNZFlags(res));
}
if (rs < 0) rs = -rs;
if (rs < 0x00000100) ARM7_ICOUNT -= 1 + 1 + 1;
else if (rs < 0x00010000) ARM7_ICOUNT -= 1 + 2 + 1;
else if (rs < 0x01000000) ARM7_ICOUNT -= 1 + 3 + 1;
else ARM7_ICOUNT -= 1 + 4 + 1;
ARM7_ICOUNT += 3;
}
// todo: add proper cycle counts
void arm7_cpu_device::HandleUMulLong(uint32_t insn)
{
uint32_t rm, rs;
uint32_t rhi, rlo;
uint64_t res;
// MULL takes 1S + (m+1)I and MLAL 1S + (m+2)I cycles to execute, where m is the
// number of 8 bit multiplier array cycles required to complete the multiply, which is
// controlled by the value of the multiplier operand specified by Rs.
rm = (int32_t)GetRegister(insn & 0xf);
rs = (int32_t)GetRegister(((insn >> 8) & 0xf));
rhi = (insn >> 16) & 0xf;
rlo = (insn >> 12) & 0xf;
#if ARM7_DEBUG_CORE
if (((insn & 0xf) == 15) || (((insn >> 8) & 0xf) == 15) || (((insn >> 16) & 0xf) == 15) || (((insn >> 12) & 0xf) == 15))
LOG(("%08x: Illegal use of PC as a register in SMULL opcode\n", R15));
#endif
/* Perform the multiplication */
res = (uint64_t)rm * rs;
/* Add on Rn if this is a MLA */
if (insn & INSN_MUL_A)
{
uint64_t acum = (uint64_t)((((uint64_t)(GetRegister(rhi))) << 32) | GetRegister(rlo));
res += acum;
// extra cycle for MLA
ARM7_ICOUNT -= 1;
}
/* Write the result (upper dword goes to RHi, lower to RLo) */
SetRegister(rhi, res >> 32);
SetRegister(rlo, res & 0xFFFFFFFF);
/* Set N and Z if asked */
if (insn & INSN_S)
{
set_cpsr((GET_CPSR & ~(N_MASK | Z_MASK)) | HandleLongALUNZFlags(res));
}
if (rs < 0x00000100) ARM7_ICOUNT -= 1 + 1 + 1;
else if (rs < 0x00010000) ARM7_ICOUNT -= 1 + 2 + 1;
else if (rs < 0x01000000) ARM7_ICOUNT -= 1 + 3 + 1;
else ARM7_ICOUNT -= 1 + 4 + 1;
ARM7_ICOUNT += 3;
}
void arm7_cpu_device::HandleMemBlock(uint32_t insn)
{
uint32_t rb = (insn & INSN_RN) >> INSN_RN_SHIFT;
uint32_t rbp = GetRegister(rb);
int result;
#if ARM7_DEBUG_CORE
if (rbp & 3)
LOG(("%08x: Unaligned Mem Transfer @ %08x\n", R15, rbp));
#endif
// Normal LDM instructions take nS + 1N + 1I and LDM PC takes (n+1)S + 2N + 1I
// incremental cycles, where S,N and I are as defined in 6.2 Cycle Types on page 6-2.
// STM instructions take (n-1)S + 2N incremental cycles to execute, where n is the
// number of words transferred.
if (insn & INSN_BDT_L)
{
/* Loading */
if (insn & INSN_BDT_U)
{
/* Incrementing */
if (!(insn & INSN_BDT_P))
{
rbp = rbp + (- 4);
}
// S Flag Set, but R15 not in list = User Bank Transfer
if (insn & INSN_BDT_S && (insn & 0x8000) == 0)
{
// !! actually switching to user mode triggers a section permission fault in Happy Fish 302-in-1 (BP C0030DF4, press F5 ~16 times) !!
// set to user mode - then do the transfer, and set back
//int curmode = GET_MODE;
//SwitchMode(eARM7_MODE_USER);
LOG(("%08x: User Bank Transfer not fully tested - please check if working properly!\n", R15));
result = loadInc(insn & 0xffff, rbp, insn & INSN_BDT_S, eARM7_MODE_USER);
// todo - not sure if Writeback occurs on User registers also..
//SwitchMode(curmode);
}
else
result = loadInc(insn & 0xffff, rbp, insn & INSN_BDT_S, GET_MODE);
if ((insn & INSN_BDT_W) && !m_pendingAbtD)
{
#if ARM7_DEBUG_CORE
if (rb == 15)
LOG(("%08x: Illegal LDRM writeback to r15\n", R15));
#endif
// "A LDM will always overwrite the updated base if the base is in the list." (also for a user bank transfer?)
// GBA "V-Rally 3" expects R0 not to be overwritten with the updated base value [BP 8077B0C]
if (((insn >> rb) & 1) == 0)
{
SetRegister(rb, GetRegister(rb) + result * 4);
}
}
// R15 included? (NOTE: CPSR restore must occur LAST otherwise wrong registers restored!)
if ((insn & 0x8000) && !m_pendingAbtD)
{
R15 -= 4; // SJE: I forget why i did this?
// S - Flag Set? Signals transfer of current mode SPSR->CPSR
if (insn & INSN_BDT_S)
{
if (MODE32)
{
set_cpsr(GetRegister(SPSR));
SwitchMode(GET_MODE);
}
else
{
uint32_t temp;
// LOG(("LDM + S | R15 %08X CPSR %08X\n", R15, GET_CPSR));
temp = (GET_CPSR & 0x0FFFFF20) | (R15 & 0xF0000000) /* N Z C V */ | ((R15 & 0x0C000000) >> (26 - 6)) /* I F */ | (R15 & 0x00000003) /* M1 M0 */;
set_cpsr( temp);
SwitchMode(temp & 3);
}
}
// LDM PC - takes 2 extra cycles
ARM7_ICOUNT -= 2;
}
}
else
{
/* Decrementing */
if (!(insn & INSN_BDT_P))
{
rbp = rbp - (- 4);
}
// S Flag Set, but R15 not in list = User Bank Transfer
if (insn & INSN_BDT_S && ((insn & 0x8000) == 0))
{
// set to user mode - then do the transfer, and set back
//int curmode = GET_MODE;
//SwitchMode(eARM7_MODE_USER);
LOG(("%08x: User Bank Transfer not fully tested - please check if working properly!\n", R15));
result = loadDec(insn & 0xffff, rbp, insn & INSN_BDT_S, eARM7_MODE_USER);
// todo - not sure if Writeback occurs on User registers also..
//SwitchMode(curmode);
}
else
result = loadDec(insn & 0xffff, rbp, insn & INSN_BDT_S, GET_MODE);
if ((insn & INSN_BDT_W) && !m_pendingAbtD)
{
if (rb == 0xf)
LOG(("%08x: Illegal LDRM writeback to r15\n", R15));
// "A LDM will always overwrite the updated base if the base is in the list." (also for a user bank transfer?)
if (((insn >> rb) & 1) == 0)
{
SetRegister(rb, GetRegister(rb) - result * 4);
}
}
// R15 included? (NOTE: CPSR restore must occur LAST otherwise wrong registers restored!)
if ((insn & 0x8000) && !m_pendingAbtD) {
R15 -= 4; // SJE: I forget why i did this?
// S - Flag Set? Signals transfer of current mode SPSR->CPSR
if (insn & INSN_BDT_S)
{
if (MODE32)
{
set_cpsr(GetRegister(SPSR));
SwitchMode(GET_MODE);
}
else
{
uint32_t temp;
// LOG(("LDM + S | R15 %08X CPSR %08X\n", R15, GET_CPSR));
temp = (GET_CPSR & 0x0FFFFF20) /* N Z C V I F M4 M3 M2 M1 M0 */ | (R15 & 0xF0000000) /* N Z C V */ | ((R15 & 0x0C000000) >> (26 - 6)) /* I F */ | (R15 & 0x00000003) /* M1 M0 */;
set_cpsr(temp);
SwitchMode(temp & 3);
}
}
// LDM PC - takes 2 extra cycles
ARM7_ICOUNT -= 2;
}
}
// LDM (NO PC) takes (n)S + 1N + 1I cycles (n = # of register transfers)
ARM7_ICOUNT -= result + 1 + 1;
} /* Loading */
else
{
/* Storing - STM */
if (insn & (1 << eR15))
{
#if ARM7_DEBUG_CORE
LOG(("%08x: Writing R15 in strm\n", R15));
#endif
/* special case handling if writing to PC */
R15 += 12;
}
if (insn & INSN_BDT_U)
{
/* Incrementing */
if (!(insn & INSN_BDT_P))
{
rbp = rbp + (- 4);
}
// S Flag Set = User Bank Transfer
if (insn & INSN_BDT_S)
{
// todo: needs to be tested..
// set to user mode - then do the transfer, and set back
//int curmode = GET_MODE;
//SwitchMode(eARM7_MODE_USER);
LOG(("%08x: User Bank Transfer not fully tested - please check if working properly!\n", R15));
result = storeInc(insn & 0xffff, rbp, eARM7_MODE_USER);
// todo - not sure if Writeback occurs on User registers also..
//SwitchMode(curmode);
}
else
result = storeInc(insn & 0xffff, rbp, GET_MODE);
if ((insn & INSN_BDT_W) && !m_pendingAbtD)
{
SetRegister(rb, GetRegister(rb) + result * 4);
}
}
else
{
/* Decrementing - but real CPU writes in incrementing order */
if (!(insn & INSN_BDT_P))
{
rbp = rbp - (-4);
}
// S Flag Set = User Bank Transfer
if (insn & INSN_BDT_S)
{
// set to user mode - then do the transfer, and set back
//int curmode = GET_MODE;
//SwitchMode(eARM7_MODE_USER);
LOG(("%08x: User Bank Transfer not fully tested - please check if working properly!\n", R15));
result = storeDec(insn & 0xffff, rbp, eARM7_MODE_USER);
// todo - not sure if Writeback occurs on User registers also..
//SwitchMode(curmode);
}
else
result = storeDec(insn & 0xffff, rbp, GET_MODE);
if ((insn & INSN_BDT_W) && !m_pendingAbtD)
{
SetRegister(rb, GetRegister(rb) - result * 4);
}
}
if (insn & (1 << eR15))
R15 -= 12;
// STM takes (n-1)S + 2N cycles (n = # of register transfers)
ARM7_ICOUNT -= (result - 1) + 2;
}
// We will specify the cycle count for each case, so remove the -3 that occurs at the end
ARM7_ICOUNT += 3;
} /* HandleMemBlock */
const arm7_cpu_device::arm7ops_ophandler arm7_cpu_device::ops_handler[0x20] =
{
&arm7_cpu_device::arm7ops_0123, &arm7_cpu_device::arm7ops_0123, &arm7_cpu_device::arm7ops_0123, &arm7_cpu_device::arm7ops_0123,
&arm7_cpu_device::arm7ops_4567, &arm7_cpu_device::arm7ops_4567, &arm7_cpu_device::arm7ops_4567, &arm7_cpu_device::arm7ops_4567,
&arm7_cpu_device::arm7ops_89, &arm7_cpu_device::arm7ops_89, &arm7_cpu_device::arm7ops_ab, &arm7_cpu_device::arm7ops_ab,
&arm7_cpu_device::arm7ops_cd, &arm7_cpu_device::arm7ops_cd, &arm7_cpu_device::arm7ops_e, &arm7_cpu_device::arm7ops_f,
&arm7_cpu_device::arm9ops_undef,&arm7_cpu_device::arm9ops_1, &arm7_cpu_device::arm9ops_undef,&arm7_cpu_device::arm9ops_undef,
&arm7_cpu_device::arm9ops_undef,&arm7_cpu_device::arm9ops_57, &arm7_cpu_device::arm9ops_undef,&arm7_cpu_device::arm9ops_57,
&arm7_cpu_device::arm9ops_89, &arm7_cpu_device::arm9ops_89, &arm7_cpu_device::arm9ops_ab, &arm7_cpu_device::arm9ops_ab,
&arm7_cpu_device::arm9ops_c, &arm7_cpu_device::arm9ops_undef,&arm7_cpu_device::arm9ops_e, &arm7_cpu_device::arm9ops_undef,
};
void arm7_cpu_device::arm9ops_undef(uint32_t insn)
{
// unsupported instruction
LOG(("ARM7: Instruction %08X unsupported\n", insn));
}
void arm7_cpu_device::arm9ops_1(uint32_t insn)
{
/* Change processor state (CPS) */
if ((insn & 0x00f10020) == 0x00000000)
{
// unsupported (armv6 onwards only)
arm9ops_undef(insn);
R15 += 4;
}
else if ((insn & 0x00ff00f0) == 0x00010000) /* set endianness (SETEND) */
{
// unsupported (armv6 onwards only)
arm9ops_undef(insn);
R15 += 4;
}
else
{
arm9ops_undef(insn);
R15 += 4;
}
}
void arm7_cpu_device::arm9ops_57(uint32_t insn)
{
/* Cache Preload (PLD) */
if ((insn & 0x0070f000) == 0x0050f000)
{
// unsupported (armv6 onwards only)
arm9ops_undef(insn);
R15 += 4;
}
else
{
arm9ops_undef(insn);
R15 += 4;
}
}
void arm7_cpu_device::arm9ops_89(uint32_t insn)
{
/* Save Return State (SRS) */
if ((insn & 0x005f0f00) == 0x004d0500)
{
// unsupported (armv6 onwards only)
arm9ops_undef(insn);
R15 += 4;
}
else if ((insn & 0x00500f00) == 0x00100a00) /* Return From Exception (RFE) */
{
// unsupported (armv6 onwards only)
arm9ops_undef(insn);
R15 += 4;
}
else
{
arm9ops_undef(insn);
R15 += 4;
}
}
void arm7_cpu_device::arm9ops_ab(uint32_t insn)
{
// BLX
HandleBranch(insn, true);
set_cpsr(GET_CPSR|T_MASK);
}
void arm7_cpu_device::arm9ops_c(uint32_t insn)
{
/* Additional coprocessor double register transfer */
if ((insn & 0x00e00000) == 0x00400000)
{
// unsupported
arm9ops_undef(insn);
R15 += 4;
}
else
{
arm9ops_undef(insn);
R15 += 4;
}
}
void arm7_cpu_device::arm9ops_e(uint32_t insn)
{
/* Additional coprocessor register transfer */
// unsupported
arm9ops_undef(insn);
R15 += 4;
}
void arm7_cpu_device::arm7ops_0123(uint32_t insn)
{
//case 0:
//case 1:
//case 2:
//case 3:
/* Branch and Exchange (BX) */
if ((insn & 0x0ffffff0) == 0x012fff10) // bits 27-4 == 000100101111111111110001
{
R15 = GetRegister(insn & 0x0f);
// If new PC address has A0 set, switch to Thumb mode
if (R15 & 1) {
set_cpsr(GET_CPSR|T_MASK);
R15--;
}
}
else if ((insn & 0x0ff000f0) == 0x01600010) // CLZ - v5
{
uint32_t rm = insn&0xf;
uint32_t rd = (insn>>12)&0xf;
SetRegister(rd, count_leading_zeros(GetRegister(rm)));
R15 += 4;
}
else if ((insn & 0x0ff000f0) == 0x01000050) // QADD - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>16)&0xf);
int64_t res;
res = saturate_qbit_overflow((int64_t)src1 + (int64_t)src2);
SetRegister((insn>>12)&0xf, (int32_t)res);
R15 += 4;
}
else if ((insn & 0x0ff000f0) == 0x01400050) // QDADD - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>16)&0xf);
int64_t res;
// check if doubling operation will overflow
res = (int64_t)src2 * 2;
saturate_qbit_overflow(res);
src2 *= 2;
res = saturate_qbit_overflow((int64_t)src1 + (int64_t)src2);
SetRegister((insn>>12)&0xf, (int32_t)res);
R15 += 4;
}
else if ((insn & 0x0ff000f0) == 0x01200050) // QSUB - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>16)&0xf);
int64_t res;
res = saturate_qbit_overflow((int64_t)src1 - (int64_t)src2);
SetRegister((insn>>12)&0xf, (int32_t)res);
R15 += 4;
}
else if ((insn & 0x0ff000f0) == 0x01600050) // QDSUB - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>16)&0xf);
int64_t res;
// check if doubling operation will overflow
res = (int64_t)src2 * 2;
saturate_qbit_overflow(res);
src2 *= 2;
res = saturate_qbit_overflow((int64_t)src1 - (int64_t)src2);
SetRegister((insn>>12)&0xf, (int32_t)res);
R15 += 4;
}
else if ((insn & 0x0ff00090) == 0x01000080) // SMLAxy - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>8)&0xf);
int32_t res1;
// select top and bottom halves of src1/src2 and sign extend if necessary
if (insn & 0x20)
{
src1 >>= 16;
}
src1 &= 0xffff;
if (src1 & 0x8000)
{
src1 |= 0xffff0000;
}
if (insn & 0x40)
{
src2 >>= 16;
}
src2 &= 0xffff;
if (src2 & 0x8000)
{
src2 |= 0xffff0000;
}
// do the signed multiply
res1 = src1 * src2;
// and the accumulate. NOTE: only the accumulate can cause an overflow, which is why we do it this way.
saturate_qbit_overflow((int64_t)res1 + (int64_t)GetRegister((insn>>12)&0xf));
SetRegister((insn>>16)&0xf, res1 + GetRegister((insn>>12)&0xf));
R15 += 4;
}
else if ((insn & 0x0ff00090) == 0x01400080) // SMLALxy - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>8)&0xf);
int64_t dst;
dst = (int64_t)GetRegister((insn>>12)&0xf);
dst |= (int64_t)GetRegister((insn>>16)&0xf)<<32;
// do the multiply and accumulate
dst += (int64_t)src1 * (int64_t)src2;
// write back the result
SetRegister((insn>>12)&0xf, (uint32_t)dst);
SetRegister((insn>>16)&0xf, (uint32_t)(dst >> 32));
R15 += 4;
}
else if ((insn & 0x0ff00090) == 0x01600080) // SMULxy - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>8)&0xf);
int32_t res;
// select top and bottom halves of src1/src2 and sign extend if necessary
if (insn & 0x20)
{
src1 >>= 16;
}
src1 &= 0xffff;
if (src1 & 0x8000)
{
src1 |= 0xffff0000;
}
if (insn & 0x40)
{
src2 >>= 16;
}
src2 &= 0xffff;
if (src2 & 0x8000)
{
src2 |= 0xffff0000;
}
res = src1 * src2;
SetRegister((insn>>16)&0xf, res);
R15 += 4;
}
else if ((insn & 0x0ff000b0) == 0x012000a0) // SMULWy - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>8)&0xf);
int64_t res;
if (insn & 0x40)
{
src2 >>= 16;
}
src2 &= 0xffff;
if (src2 & 0x8000)
{
src2 |= 0xffff0000;
}
res = (int64_t)src1 * (int64_t)src2;
res >>= 16;
SetRegister((insn>>16)&0xf, (uint32_t)res);
R15 += 4;
}
else if ((insn & 0x0ff000b0) == 0x01200080) // SMLAWy - v5
{
int32_t src1 = GetRegister(insn&0xf);
int32_t src2 = GetRegister((insn>>8)&0xf);
int32_t src3 = GetRegister((insn>>12)&0xf);
int64_t res;
if (insn & 0x40)
{
src2 >>= 16;
}
src2 &= 0xffff;
if (src2 & 0x8000)
{
src2 |= 0xffff0000;
}
res = (int64_t)src1 * (int64_t)src2;
res >>= 16;
// check for overflow and set the Q bit
saturate_qbit_overflow((int64_t)src3 + res);
// do the real accumulate
src3 += (int32_t)res;
// write the result back
SetRegister((insn>>16)&0xf, (uint32_t)res);
R15 += 4;
}
else
/* Multiply OR Swap OR Half Word Data Transfer */
if ((insn & 0x0e000000) == 0 && (insn & 0x80) && (insn & 0x10)) // bits 27-25=000 bit 7=1 bit 4=1
{
/* Half Word Data Transfer */
if (insn & 0x60) // bits = 6-5 != 00
{
HandleHalfWordDT(insn);
}
else
/* Swap */
if (insn & 0x01000000) // bit 24 = 1
{
HandleSwap(insn);
}
/* Multiply Or Multiply Long */
else
{
/* multiply long */
if (insn & 0x800000) // Bit 23 = 1 for Multiply Long
{
/* Signed? */
if (insn & 0x00400000)
HandleSMulLong(insn);
else
HandleUMulLong(insn);
}
/* multiply */
else
{
HandleMul(insn);
}
R15 += 4;
}
}
/* Data Processing OR PSR Transfer */
else if ((insn & 0x0c000000) == 0) // bits 27-26 == 00 - This check can only exist properly after Multiplication check above
{
/* PSR Transfer (MRS & MSR) */
if (((insn & 0x00100000) == 0) && ((insn & 0x01800000) == 0x01000000)) // S bit must be clear, and bit 24,23 = 10
{
HandlePSRTransfer(insn);
ARM7_ICOUNT += 2; // PSR only takes 1 - S Cycle, so we add + 2, since at end, we -3..
R15 += 4;
}
/* Data Processing */
else
{
HandleALU(insn);
}
}
// break;
}
void arm7_cpu_device::arm7ops_4567(uint32_t insn) /* Data Transfer - Single Data Access */
{
//case 4:
//case 5:
//case 6:
//case 7:
HandleMemSingle(insn);
R15 += 4;
// break;
}
void arm7_cpu_device::arm7ops_89(uint32_t insn) /* Block Data Transfer/Access */
{
//case 8:
//case 9:
HandleMemBlock(insn);
R15 += 4;
// break;
}
void arm7_cpu_device::arm7ops_ab(uint32_t insn) /* Branch or Branch & Link */
{
//case 0xa:
//case 0xb:
HandleBranch(insn, false);
// break;
}
void arm7_cpu_device::arm7ops_cd(uint32_t insn) /* Co-Processor Data Transfer */
{
//case 0xc:
//case 0xd:
HandleCoProcDT(insn);
R15 += 4;
// break;
}
void arm7_cpu_device::arm7ops_e(uint32_t insn) /* Co-Processor Data Operation or Register Transfer */
{
//case 0xe:
if (insn & 0x10)
HandleCoProcRT(insn);
else
HandleCoProcDO(insn);
R15 += 4;
// break;
}
void arm7_cpu_device::arm7ops_f(uint32_t insn) /* Software Interrupt */
{
m_pendingSwi = true;
update_irq_state();
arm7_check_irq_state();
//couldn't find any cycle counts for SWI
// break;
}