BizHawk/libmeteor/source/interpreter_thumb.cpp

1223 lines
25 KiB
C++

// Meteor - A Nintendo Gameboy Advance emulator
// Copyright (C) 2009-2011 Philippe Daouadi
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef __INTERPRETER_THUMB_H__
#define __INTERPRETER_THUMB_H__
/*
- From GBATEK -
THUMB Binary Opcode Format
Fo|_15|_14|_13|_12|_11|_10|_9_|_8_|_7_|_6_|_5_|_4_|_3_|_2_|_1_|_0_|
_1|_0___0___0_|__Op___|_______Offset______|____Rs_____|____Rd_____|Shifted
_2|_0___0___0___1___1_|_I,_Op_|___Rn/nn___|____Rs_____|____Rd_____|ADD/SUB
_3|_0___0___1_|__Op___|____Rd_____|_____________Offset____________|Immedi.
_4|_0___1___0___0___0___0_|______Op_______|____Rs_____|____Rd_____|AluOp
_5|_0___1___0___0___0___1_|__Op___|Hd_|Hs_|____Rs_____|____Rd_____|HiReg/BX
_6|_0___1___0___0___1_|____Rd_____|_____________Word______________|LDR PC
_7|_0___1___0___1_|__Op___|_0_|___Ro______|____Rb_____|____Rd_____|LDR/STR
_8|_0___1___0___1_|__Op___|_1_|___Ro______|____Rb_____|____Rd_____|""H/SB/SH
_9|_0___1___1_|__Op___|_______Offset______|____Rb_____|____Rd_____|""{B}
10|_1___0___0___0_|Op_|_______Offset______|____Rb_____|____Rd_____|""H
11|_1___0___0___1_|Op_|____Rd_____|_____________Word______________|"" SP
12|_1___0___1___0_|Op_|____Rd_____|_____________Word______________|ADD PC/SP
13|_1___0___1___1___0___0___0___0_|_S_|___________Word____________|ADD SP,nn
14|_1___0___1___1_|Op_|_1___0_|_R_|____________Rlist______________|PUSH/POP
17|_1___0___1___1___1___1___1___0_|___________User_Data___________|BKPT ARM9
15|_1___1___0___0_|Op_|____Rb_____|____________Rlist______________|STM/LDM
16|_1___1___0___1_|_____Cond______|_________Signed_Offset_________|B{cond}
U_|_1___1___0___1___1___1___1___0_|_____________var_______________|UNDEF ARM9
17|_1___1___0___1___1___1___1___1_|___________User_Data___________|SWI
18|_1___1___1___0___0_|________________Offset_____________________|B
19|_1___1___1___0___1_|_________________________var___________|_0_|BLXsuf ARM9
U_|_1___1___1___0___1_|_________________________var___________|_1_|UNDEF ARM9
19|_1___1___1___1_|_H_|______________Offset_Low/High______________|BL (BLX ARM9)
*/
#include "ameteor/interpreter.hpp"
#include "globals.hpp"
#include "cpu_globals.hpp"
#include "ameteor/memory.hpp"
#include "ameteor.hpp"
#include "debug.hpp"
#define Rb ((code >> 8) & 0x7)
#define Ro ((code >> 6) & 0x7)
#define Rs ((code >> 3) & 0x7)
#define Rd ((code ) & 0x7)
#define Imm (code & 0xFF)
#define Off ((code >> 6) & 0x1F)
//#define HiRs (((code & (0x1 << 6)) >> 3) | Rs)
#define HiRs ((code >> 3) & 0xF)
#define HiRd (((code & (0x1 << 7)) >> 4) | Rd)
#ifdef METDEBUG
# define NOT_PC(reg) \
if (reg == 15) \
met_abort("Register is PC")
#else
# define NOT_PC(reg) {}
#endif
#define THUMB(name) \
inline void Interpreter::t##name ()
#define NITHUMB(name) \
void Interpreter::t##name ()
namespace AMeteor
{
// move shifted register
THUMB(_Shift)
{
if ((code >> 13) & 0x7)
{
met_abort("Bits 13-15 must be 000 for shift instructions");
}
uint8_t off = Off;
switch ((code >> 11) & 0x3)
{
case 0x0: // LSL
if (off)
{
SETF(C, R(Rs) & (0x1 << (32-off)));
R(Rd) = R(Rs) << off;
FZ(R(Rd));
FN(R(Rd));
}
else
{
R(Rd) = R(Rs);
FZ(R(Rd));
FN(R(Rd));
}
break;
case 0x1: // LSR
if (off)
{
SETF(C, R(Rs) & (0x1 << (off-1)));
R(Rd) = R(Rs) >> off;
FZ(R(Rd));
FN(R(Rd));
}
else
{
SETF(C, R(Rs) & (0x1 << 31));
R(Rd) = 0;
FLAG_Z = 1;
FLAG_N = 0;
}
break;
case 0x2: // ASR
if (off)
{
SETF(C, (((int32_t)R(Rs)) >> (off - 1)) & 0x1);
R(Rd) = ((int32_t)R(Rs)) >> off;
FZ(R(Rd));
FN(R(Rd));
}
else
{
if (R(Rd) & (0x1 << 31))
{
R(Rd) = 0xFFFFFFFF;
FLAG_Z = 0;
FLAG_N = 1;
FLAG_C = 1;
}
else
{
R(Rd) = 0;
FLAG_Z = 1;
FLAG_N = 0;
FLAG_C = 0;
}
}
break;
case 0x3: // reserved
met_abort("Bits 11-12 must not be 11 for shift instructions");
break;
}
CYCLES16Seq(R(15), 1);
}
// add/substract
THUMB(ADDSUB)
{
if (((code >> 11) & 0x1F) != 0x3)
{
met_abort("Bits 11-15 should be 00011 for add/substract");
}
uint32_t op2;
if ((code >> 10) & 0x1) // imm
op2 = Ro;
else // reg
op2 = R(Ro);
if ((code >> 9) & 0x1) // SUB
{
#ifdef X86_ASM
asm("subl %6, %5\n"
"setzb %1\n"
"setsb %2\n"
"setncb %3\n"
"setob %4\n"
:"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"0"(R(Rs)), "r"(op2));
#else
uint32_t op1 = R(Rs), res = R(Rd) = op1 - op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = SUBCARRY(op1, op2, res);
FLAG_V = SUBOVERFLOW(op1, op2, res);
#endif
}
else // ADD
{
#ifdef X86_ASM
asm("addl %6, %5\n"
"setzb %1\n"
"setsb %2\n"
"setcb %3\n"
"setob %4\n"
:"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"0"(R(Rs)), "r"(op2));
#else
uint32_t op1 = R(Rs), res = R(Rd) = op1 + op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = ADDCARRY(op1, op2, res);
FLAG_V = ADDOVERFLOW(op1, op2, res);
#endif
}
CYCLES16Seq(R(15), 1);
}
// move/compare/add/substact immediate
THUMB(_Imm)
{
if (((code >> 13) & 0x7) != 0x1)
{
met_abort("Bits 13-15 must be 001 for immediate instructions");
}
switch ((code >> 11) & 0x3)
{
case 0x0: // MOV
R(Rb) = Imm;
FLAG_Z = !Imm;
FLAG_N = 0;
break;
case 0x1: // CMP
#ifdef X86_ASM
asm("cmpl %5, %4\n"
"setzb %0\n"
"setsb %1\n"
"setncb %2\n"
"setob %3\n"
:"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"r"(R(Rb)), "r"(Imm));
#else
{
uint32_t op1 = R(Rb), op2 = Imm, res = op1 - op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = SUBCARRY(op1, op2, res);
FLAG_V = SUBOVERFLOW(op1, op2, res);
}
#endif
break;
case 0x2: // ADD
#ifdef X86_ASM
asm("addl %6, %5\n"
"setzb %1\n"
"setsb %2\n"
"setcb %3\n"
"setob %4\n"
:"=r"(R(Rb)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"0"(R(Rb)), "r"(Imm));
#else
{
uint32_t op1 = R(Rb), op2 = Imm, res = R(Rb) = op1 + op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = ADDCARRY(op1, op2, res);
FLAG_V = ADDOVERFLOW(op1, op2, res);
}
#endif
break;
case 0x3: // SUB
#ifdef X86_ASM
asm("subl %6, %5\n"
"setzb %1\n"
"setsb %2\n"
"setncb %3\n"
"setob %4\n"
:"=r"(R(Rb)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"0"(R(Rb)), "r"(Imm));
#else
{
uint32_t op1 = R(Rb), op2 = Imm, res = R(Rb) = op1 - op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = SUBCARRY(op1, op2, res);
FLAG_V = SUBOVERFLOW(op1, op2, res);
}
#endif
break;
}
CYCLES16Seq(R(15), 1);
}
// ALU operations
THUMB(_ALU)
{
if (((code >> 10) & 0x3F) != 0x10)
{
met_abort("Bits 10-15 must be 010000 for ALU instructions");
}
uint8_t opcode = (code >> 6) & 0xF;
// TODO put a ifndef X86_ASM here around this declaration
uint32_t res = 0; // to avoid a warning
uint8_t shift;
switch (opcode)
{
case 0x0: // AND
#ifdef X86_ASM
asm("andl %4, %3\n"
"setzb %1\n"
"setsb %2\n"
:"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N)
:"0"(R(Rd)), "r"(R(Rs)));
#else
res = R(Rd) &= R(Rs);
FLAG_Z = !res;
FLAG_N = res >> 31;
#endif
break;
case 0x1: // EOR
#ifdef X86_ASM
asm("xorl %4, %3\n"
"setzb %1\n"
"setsb %2\n"
:"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N)
:"0"(R(Rd)), "r"(R(Rs)));
#else
res = R(Rd) ^= R(Rs);
FLAG_Z = !res;
FLAG_N = res >> 31;
#endif
break;
case 0x2: // LSL
ICYCLES(1);
shift = R(Rs) & 0xFF;
if (shift)
{
if (shift == 32)
{
SETFB(C, R(Rd) & 0x1);
R(Rd) = 0;
FLAG_Z = FLAG_N = 0;
}
else if (shift < 32)
{
SETFB(C, (R(Rd) >> (32-shift)) & 0x1);
res = R(Rd) <<= shift;
FLAG_Z = !res;
FLAG_N = res >> 31;
}
else
{
R(Rd) = 0;
FLAG_C = FLAG_Z = FLAG_N = 0;
}
}
else
{
res = R(Rd);
FLAG_Z = !res;
FLAG_N = res >> 31;
}
break;
case 0x3: // LSR
ICYCLES(1);
shift = R(Rs) & 0xFF;
if (shift)
{
if (shift == 32)
{
SETFB(C, R(Rd) >> 31);
R(Rd) = 0;
FLAG_Z = FLAG_N = 0;
}
else if (shift < 32)
{
SETFB(C, (R(Rd) >> (shift-1)) & 0x1);
res = R(Rd) >>= shift;
FLAG_Z = !res;
FLAG_N = res >> 31;
}
else
{
R(Rd) = 0;
FLAG_C = FLAG_Z = FLAG_N = 0;
}
}
else
{
res = R(Rd);
FLAG_Z = !res;
FLAG_N = res >> 31;
}
break;
case 0x4: // ASR
ICYCLES(1);
shift = R(Rs) & 0xFF;
if (shift)
{
if (shift >= 32)
{
R(Rd) = ((int32_t)R(Rs)) >> 31;
FLAG_C = FLAG_Z = FLAG_N = R(Rd) & 0x1;
}
else
{
SETFB(C, (((int32_t)R(Rd)) >> (shift-1)) & 0x1);
res = R(Rd) = ((int32_t)R(Rd)) >> shift;
FLAG_Z = !res;
FLAG_N = res >> 31;
}
}
else
{
res = R(Rd);
FLAG_Z = !res;
FLAG_N = res >> 31;
}
break;
case 0x5: // ADC
res = R(Rd) + R(Rs) + FLAG_C;
FLAG_C = ADDCARRY(R(Rd), R(Rs), res);
FLAG_V = ADDOVERFLOW(R(Rd), R(Rs), res);
R(Rd) = res;
break;
case 0x6: // SBC
res = R(Rd) - R(Rs) + FLAG_C - 1;
FLAG_C = SUBCARRY(R(Rd), R(Rs), res);
FLAG_V = SUBOVERFLOW(R(Rd), R(Rs), res);
R(Rd) = res;
break;
case 0x7: // ROR
ICYCLES(1);
shift = R(Rs) & 0xFF;
if (shift)
{
shift %= 32;
res = R(Rd);
SETFB(C, (res >> (shift - 1)) & 0x1);
res = R(Rd) = ROR(res, shift);
FLAG_Z = !res;
FLAG_N = res >> 31;
}
else
{
res = R(Rd);
FLAG_Z = !res;
FLAG_N = res >> 31;
}
break;
case 0x8: // TST
#ifdef X86_ASM
asm("testl %3, %2\n"
"setzb %0\n"
"setsb %1\n"
:"=m"(FLAG_Z), "=m"(FLAG_N)
:"r"(R(Rd)), "r"(R(Rs)));
#else
res = R(Rd) & R(Rs);
FLAG_Z = !res;
FLAG_N = res >> 31;
#endif
break;
case 0x9: // NEG
#ifdef X86_ASM
asm("negl %5\n"
"setzb %1\n"
"setsb %2\n"
"setncb %3\n"
"setob %4\n"
:"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"0"(R(Rs)));
#else
{
uint32_t op2;
op2 = R(Rs);
res = R(Rd) = -op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
// we overflow only if op2 == INT_MIN
FLAG_V = SUBOVERFLOW(0, op2, res);
// we have a carry only if op2 == 0
FLAG_C = SUBCARRY(0, op2, res);
}
#endif
break;
case 0xA: // CMP
#ifdef X86_ASM
asm("cmpl %5, %4\n"
"setzb %0\n"
"setsb %1\n"
"setncb %2\n"
"setob %3\n"
:"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"r"(R(Rd)), "r"(R(Rs)));
#else
{
uint32_t op1, op2;
op1 = R(Rd);
op2 = R(Rs);
res = op1 - op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = SUBCARRY(op1, op2, res);
FLAG_V = SUBOVERFLOW(op1, op2, res);
}
#endif
break;
case 0xB: // CMN
#ifdef X86_ASM
asm("addl %5, %4\n"
"setzb %0\n"
"setsb %1\n"
"setcb %2\n"
"setob %3\n"
:"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"r"(R(Rd)), "r"(R(Rs))
:"4");
#else
{
uint32_t op1, op2;
op1 = R(Rd);
op2 = R(Rs);
res = op1 + op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = ADDCARRY(op1, op2, res);
FLAG_V = ADDOVERFLOW(op1, op2, res);
}
#endif
break;
case 0xC: // ORR
#ifdef X86_ASM
asm("orl %4, %3\n"
"setzb %1\n"
"setsb %2\n"
:"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N)
:"0"(R(Rd)), "r"(R(Rs)));
#else
res = R(Rd) |= R(Rs);
FLAG_Z = !res;
FLAG_N = res >> 31;
#endif
break;
case 0xD: // MUL
MULICYCLES(Rs);
res = R(Rd) *= R(Rs);
FLAG_Z = !res;
FLAG_N = res >> 31;
break;
case 0xE: // BIC
res = R(Rd) &= ~R(Rs);
FLAG_Z = !res;
FLAG_N = res >> 31;
break;
case 0xF: // MVN
res = R(Rd) = ~R(Rs);
FLAG_Z = !res;
FLAG_N = res >> 31;
break;
}
CYCLES16Seq(R(15), 1);
}
// Hi register operations/branch exchange
THUMB(_HiRegOp)
{
if (((code >> 10) & 0x3F) != 0x11)
{
met_abort("Bits 10-15 must be 010001 for HiReg instructions");
}
uint8_t rd = HiRd, rs = HiRs;
switch ((code >> 8) & 0x3)
{
case 0x0: // ADD
R(rd) += R(rs);
if (rd == 15)
{
R(15) &= 0xFFFFFFFE;
CYCLES16NSeq(R(15), 3);
R(15) += 2;
}
else
CYCLES16Seq(R(15), 1);
break;
case 0x1: // CMP
#ifdef X86_ASM
asm("cmpl %5, %4\n"
"setzb %0\n"
"setsb %1\n"
"setncb %2\n"
"setob %3\n"
:"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V)
:"r"(R(rd)), "r"(R(rs)));
#else
{
uint32_t op1 = R(rd), op2 = R(rs), res = op1 - op2;
FLAG_Z = !res;
FLAG_N = res >> 31;
FLAG_C = SUBCARRY(op1, op2, res);
FLAG_V = SUBOVERFLOW(op1, op2, res);
}
#endif
CYCLES16Seq(R(15), 1);
break;
case 0x2:
if (rd != 8 || rs != 8) // MOV
{
R(rd) = R(rs);
if (rd == 15)
{
R(15) &= 0xFFFFFFFE;
CYCLES16NSeq(R(15), 3);
R(15) += 2;
}
else
CYCLES16Seq(R(15), 1);
}
else // NOP
CYCLES16Seq(R(15), 1);
break;
case 0x3:
if (Rd)
met_abort("Rd must be 0 for BX/BLX instructions");
if (code & (0x1 << 7)) // BLX
{
NOT_PC(rs);
met_abort("BLX not implemented");
}
else // BX
{
if (R(rs) & 0x1)
{
R(15) = (R(rs) & 0xFFFFFFFE) + 2;
CYCLES16NSeq(R(15), 3);
}
else
{
// switch to arm
FLAG_T = 0;
R(15) = (R(rs) & 0xFFFFFFFC) + 4;
CYCLES32NSeq(R(15), 3);
}
}
break;
}
}
// load PC-relative
THUMB(LDRimm)
{
if (((code >> 11) & 0x1F) != 0x9)
{
met_abort("Bits 11-15 must be 01001 for LDR with imm instructions");
}
uint32_t add = (R(15) & 0xFFFFFFFC) + (Imm << 2);
R(Rb) = MEM.Read32(add);
CYCLES32NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
}
// load/store with register offset
// and load/store sign-extended byte/halfword
THUMB(STRLDRreg)
{
if (((code >> 12) & 0xF) != 0x5)
{
met_abort("Bits 12-15 must be 0101 for LDR/STR with reg instructions");
}
uint32_t add = R(Ro) + R(Rs);
switch ((code >> 9) & 0x7)
{
// load/store with register offset
case 0x0: // STR
MEM.Write32(add, R(Rd));
CYCLES32NSeq(add, 1);
CYCLES16NSeq(R(15), 1);
break;
case 0x2: // STRB
MEM.Write8(add, R(Rd));
CYCLES16NSeq(add, 1);
CYCLES16NSeq(R(15), 1);
break;
case 0x4: // LDR
R(Rd) = MEM.Read32(add);
CYCLES32NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
break;
case 0x6: // LDRB
R(Rd) = MEM.Read8(add);
CYCLES16NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
break;
// load/store sign-extended byte/halfword
case 0x1: // STRH
MEM.Write16(add, R(Rd));
CYCLES16NSeq(add, 1);
CYCLES16NSeq(R(15), 1);
break;
case 0x3: // LDSB
R(Rd) = (int8_t)MEM.Read8(add);
CYCLES16NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
break;
case 0x5: // LDRH
R(Rd) = MEM.Read16(add);
CYCLES16NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
break;
case 0x7: // LDSH
R(Rd) = (int16_t)MEM.Read16(add);
CYCLES16NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
break;
}
}
// load/store with immediate offset
THUMB(STRLDRoff)
{
if (((code >> 13) & 0x7) != 0x3)
{
met_abort("Bits 13-15 must be 011 for LDR/STR with off instructions");
}
uint32_t add;
switch ((code >> 11) & 0x3)
{
case 0x0: // STR
add = R(Rs) + (Off << 2);
MEM.Write32(add, R(Rd));
CYCLES32NSeq(add, 1);
CYCLES16NSeq(R(15), 1);
break;
case 0x1: // LDR
add = R(Rs) + (Off << 2);
R(Rd) = MEM.Read32(add);
CYCLES32NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
break;
case 0x2: // STRB
add = R(Rs) + Off;
MEM.Write8(add, R(Rd));
CYCLES16NSeq(add, 1);
CYCLES16NSeq(R(15), 1);
break;
case 0x3: // LDRB
add = R(Rs) + Off;
R(Rd) = MEM.Read8(add);
CYCLES16NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
break;
}
}
// load/store halfword
THUMB(LDRHSTRHoff)
{
if (((code >> 12) & 0xF) != 0x8)
{
met_abort("Bits 12-15 must be 1000 for LDRH/STRH with off instructions");
}
uint32_t add = R(Rs) + (Off * 2);
if (code & (0x1 << 11)) // LDRH
{
R(Rd) = MEM.Read16(add);
CYCLES16NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
}
else // STRH
{
MEM.Write16(add, R(Rd));
CYCLES16NSeq(add, 1);
CYCLES16NSeq(R(15), 1);
}
}
// load/store SP-relative
THUMB(STRLDRsp)
{
if (((code >> 12) & 0xF) != 0x9)
{
met_abort("Bits 12-15 must be 1001 for LDR/STR SP-relative instructions");
}
uint32_t add = R(13) + (Imm << 2);
if (code & (0x1 << 11)) // LDR
{
R(Rb) = MEM.Read32(add);
CYCLES32NSeq(add, 1);
ICYCLES(1);
CYCLES16Seq(R(15), 1);
}
else // STR
{
MEM.Write32(add, R(Rb));
CYCLES32NSeq(add, 1);
CYCLES16NSeq(R(15), 1);
}
}
// get relative address
THUMB(ADDpcsp)
{
if (((code >> 12) & 0xF) != 0xA)
{
met_abort("Bits 12-15 must be 1010 for ADD relative instructions");
}
if (code & (0x1 << 11)) // with SP
{
R(Rb) = R(13) + (Imm << 2);
}
else // with PC
{
R(Rb) = (R(15) & 0xFFFFFFFC) + (Imm << 2);
}
CYCLES16Seq(R(15), 1);
}
// add offset to stack pointer
THUMB(ADDsp)
{
if (((code >> 8) & 0xFF) != 0xB0)
{
met_abort("Bits 8-15 must be 10110000 for ADD to SP instructions");
}
if (code & (0x1 << 7)) // substract
{
R(13) -= ((code & 0x7F) << 2);
}
else // add
{
R(13) += ((code & 0x7F) << 2);
}
CYCLES16Seq(R(15), 1);
}
// push/pop registers
THUMB(PUSHPOP)
{
if (((code >> 12) & 0xF) != 0xB)
{
met_abort("Bits 12-15 must be 1011 for PUSH/POP instructions");
}
if (((code >> 9) & 0x3) != 0x2)
{
met_abort("Bits 9-10 must be 10 for PUSH/POP instructions");
}
static const uint8_t NumBits[] =
{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
uint8_t numregs =
NumBits[(code >> 4) & 0xF] +
NumBits[ code & 0xF];
if (code & (0x1 << 8))
++numregs;
uint32_t add, newsp;
if (code & (0x1 << 11)) // POP
{
newsp = R(13) + 4 * numregs;
add = R(13) & 0xFFFFFFFC;
CYCLES32NSeq(add, numregs);
ICYCLES(1);
for (register uint8_t n = 0; n < 8; ++n)
if (code & (0x1 << n))
{
R(n) = MEM.Read32 (add);
add += 4;
}
if (code & (0x1 << 8)) // POP PC
{
R(15) = (MEM.Read32(add) + 2) & 0xFFFFFFFE;
CYCLES16NSeq(R(15), 3);
}
else
CYCLES16Seq(R(15), 1);
}
else // PUSH
{
newsp = R(13) - 4 * numregs;
add = newsp & 0xFFFFFFFC;
CYCLES32NSeq(add, numregs);
CYCLES16NSeq(R(15), 1);
for (register uint8_t n = 0; n < 8; ++n)
if (code & (0x1 << n))
{
MEM.Write32 (add, R(n));
add += 4;
}
if (code & (0x1 << 8)) // PUSH LR
{
MEM.Write32 (add, R(14));
}
}
R(13) = newsp;
}
// multiple load/store
THUMB(STMLDM)
{
if (((code >> 12) & 0xF) != 0xC)
{
met_abort("Bits 12-15 must be 1100 for STM/LDM instructions");
}
static const uint8_t NumBits[] =
{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
uint8_t numregs =
NumBits[(code >> 4) & 0xF] +
NumBits[ code & 0xF];
uint32_t add = R(Rb) & 0xFFFFFFFC;
if (code & (0x1 << 11)) // LDMIA
{
CYCLES32NSeq(add, numregs);
ICYCLES(1);
for (register uint8_t n = 0; n < 8; ++n)
if (code & (0x1 << n))
{
R(n) = MEM.Read32 (add);
add += 4;
}
CYCLES16Seq(R(15), 1);
}
else // STMIA
{
CYCLES32NSeq(add, numregs);
CYCLES16NSeq(R(15), 1);
for (register uint8_t n = 0; n < 8; ++n)
if (code & (0x1 << n))
{
MEM.Write32 (add, R(n));
add += 4;
}
}
if (!(code & (0x1 << Rb)))
R(Rb) = R(Rb) + 4 * numregs;
}
// conditional branch
THUMB(_CondBranch)
{
if (((code >> 12) & 0xF) != 0xD)
{
met_abort("Bits 12-15 must be 1101 for branch on condition instructions");
}
bool branch = false;
switch ((code >> 8) & 0xF)
{
case 0x0: // BEQ
if (FLAG_Z)
branch = true;
break;
case 0x1: // BNE
if (!FLAG_Z)
branch = true;
break;
case 0x2: // BCS
if (FLAG_C)
branch = true;
break;
case 0x3: // BCC
if (!FLAG_C)
branch = true;
break;
case 0x4: // BMI
if (FLAG_N)
branch = true;
break;
case 0x5: // BPL
if (!FLAG_N)
branch = true;
break;
case 0x6: // BVS
if (FLAG_V)
branch = true;
break;
case 0x7: // BVC
if (!FLAG_V)
branch = true;
break;
case 0x8: // BHI
if (FLAG_C && !FLAG_Z)
branch = true;
break;
case 0x9: // BLS
if (!FLAG_C || FLAG_Z)
branch = true;
break;
case 0xA: // BGE
if (FLAG_N == FLAG_V)
branch = true;
break;
case 0xB: // BLT
if (FLAG_N != FLAG_V)
branch = true;
break;
case 0xC: // BGT
if (!FLAG_Z && FLAG_N == FLAG_V)
branch = true;
break;
case 0xD: // BLE
if (FLAG_Z || FLAG_N != FLAG_V)
branch = true;
break;
case 0xE: // undefined
met_abort("Undefined branch on condition");
break;
case 0xF: // reserved for SWI instruction
met_abort("Bits 8-11 must not be 1111 for branch instruction");
break;
}
if (branch)
{
R(15) += (((int32_t)(int8_t)Imm) << 1) + 2;
CYCLES16NSeq(R(15), 3);
}
else
CYCLES16Seq(R(15), 1);
}
// software interrupt and breakpoint
THUMB(SWI)
{
CPU.SoftwareInterrupt(code & 0xFF);
// FIXME seems wrong !
CYCLES32NSeq(0, 3);
}
// unconditional branch
THUMB(B)
{
if (((code >> 11) & 0x1F) != 0x1C)
{
met_abort("Bits 11-15 must be 11100 for branch instructions");
}
int32_t off = (code & 0x7FF) << 1;
if (off & 0x800)
off |= 0xFFFFF000; // extends sign bit
R(15) += off + 2;
CYCLES16NSeq(R(15), 3);
}
// long branch with link
THUMB(_BL1)
{
if (((code >> 11) & 0x1F) != 0x1E)
{
met_abort("Bits 11-15 must be 11110 for long branch instructions");
}
if (code & (0x1 << 10)) // negative offset
R(14) = R(15) + (((int32_t)((code & 0x7FF) << 12)) | 0xFF800000);
else
R(14) = R(15) + ((code & 0x7FF) << 12);
CYCLES16Seq(R(15), 1);
}
THUMB(_BL2)
{
// XXX we dont do check here to be sure this instruction is called after
// BL1
uint32_t add = R(14) + ((code & 0x7FF) << 1);
if (code & (0x1 << 11)) // BL
{
R(14) = (R(15)-2) | 0x1;
R(15) = (add & 0xFFFFFFFE) + 2;
}
else // BLX
{
met_abort("BLX not implemented");
if (code & 0x1)
met_abort("BLX with odd address");
FLAG_T = 0;
}
CYCLES16NSeq(R(15), 3);
}
NITHUMB(_Code)
{
switch (code >> 13)
{
case 0: // 000
if ((code & 0x1800) == 0x1800) // 00011
tADDSUB();
else // 000
t_Shift();
break;
case 1: // 001
t_Imm();
break;
case 2: // 010
switch ((code >> 10) & 0x7)
{
case 0: // 010000
t_ALU();
break;
case 1: // 010001
t_HiRegOp();
break;
case 2:
case 3: // 01001x
tLDRimm();
break;
default:
tSTRLDRreg();
break;
}
break;
case 3: // 011
tSTRLDRoff();
break;
case 4: // 100
if (code & (0x1 << 12)) // 1001
tSTRLDRsp();
else // 100
tLDRHSTRHoff();
break;
case 5: // 101
if (code & (0x1 << 12)) // 1011
{
switch (code & 0x0600)
{
case 0x0000: // 1011x00
tADDsp();
break;
case 0x0400: // 1011x10
tPUSHPOP();
break;
default:
met_abort("not implemented or unknown");
break;
}
}
else // 101
tADDpcsp();
break;
case 6: // 110
if (code & (0x1 << 12)) // 1101
{
if ((code & 0x0F00) == 0x0F00) // 11011111
tSWI();
else // 1101xxxx
t_CondBranch();
}
else // 1100
tSTMLDM();
break;
case 7: // 111
switch ((code >> 11) & 0x3)
{
case 0: // 11100
tB();
break;
case 2: // 11110
t_BL1();
break;
case 3: // 11111
t_BL2();
break;
default:
met_abort("not implemented or unknown");
break;
}
break;
}
}
}
#undef Rb
#undef Ro
#undef Rs
#undef Rd
#undef Imm
#undef THUMB
#endif