kolibrios/contrib/other/uarm/CPU.c
maxcodehack a03882245a Upload uARM emulator (linux4kolibri)
git-svn-id: svn://kolibrios.org@8327 a494cfbc-eb01-0410-851d-a64ba20cac60
2020-12-05 14:28:30 +00:00

2737 lines
71 KiB
C
Executable File

/*
TODO:
-verify DSP adds, subtracts, multiplies
-MCR/MRC to R15 is special - handle it
TOTOv6:
-Endianness support (incl for pagetable lookups)
*/
#include "CPU.h"
#include "math64.h"
#define ARM_MODE_2_REG 0x0F
#define ARM_MODE_2_WORD 0x10
#define ARM_MODE_2_LOAD 0x20
#define ARM_MODE_2_T 0x40
#define ARM_MODE_2_INV 0x80
#define ARM_MODE_3_REG 0x0F //flag for actual reg number used
#define ARM_MODE_3_TYPE 0x30 //flag for the below 4 types
#define ARM_MODE_3_H 0x00
#define ARM_MODE_3_SH 0x10
#define ARM_MODE_3_SB 0x20
#define ARM_MODE_3_D 0x30
#define ARM_MODE_3_LOAD 0x40
#define ARM_MODE_3_INV 0x80
#define ARM_MODE_4_REG 0x0F
#define ARM_MODE_4_INC 0x10 //incr or decr
#define ARM_MODE_4_BFR 0x20 //before or after
#define ARM_MODE_4_WBK 0x40 //writeback?
#define ARM_MODE_4_S 0x80 //S bit set?
#define ARM_MODE_5_REG 0x0F
#define ARM_MODE_5_ADD_BEFORE 0x10 //add value before?
#define ARM_MODE_5_ADD_AFTER 0x20 //add value after?
#define ARM_MODE_5_IS_OPTION 0x40 //is value option (as opposed to offset)
#define ARM_MODE_5_RR 0x80 //MCRR or MRCC instrs
#ifdef ARM_V6
#define ARM_CPSR_UND_AND (~(ARM_SR_M | ARM_SR_E))
#define ARM_CPSR_UND_ORR (ARM_SR_I | ARM_SR_MODE_UND | (cpu->EEE ? ARM_SR_E : 0))
#define ARM_CPSR_SWI_AND (~(ARM_SR_M | ARM_SR_E))
#define ARM_CPSR_SWI_ORR (ARM_SR_I | ARM_SR_MODE_SVC | (cpu->EEE ? ARM_SR_E : 0))
#define ARM_CPSR_PAB_AND (~(ARM_SR_M | ARM_SR_E))
#define ARM_CPSR_PAB_ORR (ARM_SR_I | ARM_SR_A | ARM_SR_MODE_ABT | (cpu->EEE ? ARM_SR_E : 0))
#define ARM_CPSR_DAB_AND (~(ARM_SR_M | ARM_SR_E))
#define ARM_CPSR_DAB_ORR (ARM_SR_I | ARM_SR_A | ARM_SR_MODE_ABT | (cpu->EEE ? ARM_SR_E : 0))
#define ARM_CPSR_IRQ_AND (~(ARM_SR_M | ARM_SR_E))
#define ARM_CPSR_IRQ_ORR (ARM_SR_I | ARM_SR_A | ARM_SR_MODE_IRQ | (cpu->EEE ? ARM_SR_E : 0))
#define ARM_CPSR_FIQ_AND (~(ARM_SR_M | ARM_SR_E))
#define ARM_CPSR_FIQ_ORR (ARM_SR_I | ARM_SR_A | ARM_SR_F | ARM_SR_MODE_IRQ | (cpu->EEE ? ARM_SR_E : 0))
#else
#define ARM_CPSR_UND_AND (~ARM_SR_M)
#define ARM_CPSR_UND_ORR (ARM_SR_I | ARM_SR_MODE_UND)
#define ARM_CPSR_SWI_AND (~ARM_SR_M)
#define ARM_CPSR_SWI_ORR (ARM_SR_I | ARM_SR_MODE_SVC)
#define ARM_CPSR_PAB_AND (~ARM_SR_M)
#define ARM_CPSR_PAB_ORR (ARM_SR_I | ARM_SR_MODE_ABT)
#define ARM_CPSR_DAB_AND (~ARM_SR_M)
#define ARM_CPSR_DAB_ORR (ARM_SR_I | ARM_SR_MODE_ABT)
#define ARM_CPSR_IRQ_AND (~ARM_SR_M)
#define ARM_CPSR_IRQ_ORR (ARM_SR_I | ARM_SR_MODE_IRQ)
#define ARM_CPSR_FIQ_AND (~ARM_SR_M)
#define ARM_CPSR_FIQ_ORR (ARM_SR_I | ARM_SR_F | ARM_SR_MODE_FIQ)
#endif
static _INLINE_ UInt32 cpuPrvROR(UInt32 val, UInt8 ror){
if(ror) val = (val >> (UInt32)ror) | (val << (UInt32)(32 - ror));
return val;
}
static _INLINE_ void cpuPrvSetPC(ArmCpu* cpu, UInt32 pc){
cpu->regs[15] = pc &~ 1UL;
cpu->CPSR &=~ ARM_SR_T;
if(pc & 1) cpu->CPSR |= ARM_SR_T;
else if(pc & 2) cpu->emulErrF(cpu, "Attempt to branch to non-word-aligned ARM address");
}
static _INLINE_ UInt32 cpuPrvGetReg(ArmCpu* cpu, UInt8 reg, Boolean wasT, Boolean specialPC){
UInt32 ret;
ret = cpu->regs[reg];
if(reg == 15) ret += (wasT) ? 2 : 4;
if(wasT && specialPC) ret &=~ 3UL;
return ret;
}
static _INLINE_ void cpuPrvSetReg(ArmCpu* cpu, UInt8 reg, UInt32 val){
if(reg == 15){
cpuPrvSetPC(cpu, val);
}
else cpu->regs[reg] = val;
}
UInt32 cpuGetRegExternal(ArmCpu* cpu, UInt8 reg){
if(reg < 16){ // real reg
return (reg == 15) ? (cpu->regs[15] + ((cpu->CPSR & ARM_SR_T) ? 1 : 0)) : cpu->regs[reg];
}
else if(reg == ARM_REG_NUM_CPSR){
return cpu->CPSR;
}
else if(reg == ARM_REG_NUM_SPSR){
return cpu->SPSR;
}
return 0;
}
void cpuSetReg(ArmCpu* cpu, UInt8 reg, UInt32 val){
cpuPrvSetReg(cpu, reg, val);
}
#define cpuSetReg _DO_NOT_USE_cpuSetReg_IN_CPU_C_
static ArmBankedRegs* cpuPrvModeToBankedRegsPtr(ArmCpu* cpu, UInt8 mode){
switch(mode){
case ARM_SR_MODE_USR:
case ARM_SR_MODE_SYS:
return &cpu->bank_usr;
case ARM_SR_MODE_FIQ:
return &cpu->bank_fiq;
case ARM_SR_MODE_IRQ:
return &cpu->bank_irq;
case ARM_SR_MODE_SVC:
return &cpu->bank_svc;
case ARM_SR_MODE_ABT:
return &cpu->bank_abt;
case ARM_SR_MODE_UND:
return &cpu->bank_und;
default:
cpu->emulErrF(cpu, "Invalid mode passed to cpuPrvModeToBankedRegsPtr()");
return NULL;
}
}
static void cpuPrvSwitchToMode(ArmCpu* cpu, UInt8 newMode){
ArmBankedRegs *saveTo, *getFrom;
UInt32 tmp;
UInt8 i, curMode;
curMode = cpu->CPSR & ARM_SR_M;
if(curMode == newMode) return;
if(curMode == ARM_SR_MODE_FIQ || newMode == ARM_SR_MODE_FIQ){ //bank/unbank the fiq regs
for(i = 0; i < 5; i++){
tmp = cpu->extra_regs[i];
cpu->extra_regs[i] = cpu->regs[i + 8];
cpu->regs[i + 8] = tmp;
}
}
saveTo = cpuPrvModeToBankedRegsPtr(cpu, curMode);
getFrom = cpuPrvModeToBankedRegsPtr(cpu, newMode);
if(saveTo == getFrom) return; //we're done if no regs to switch [this happens if we switch user<->system]
saveTo->R13 = cpu->regs[13];
saveTo->R14 = cpu->regs[14];
saveTo->SPSR = cpu->SPSR;
cpu->regs[13] = getFrom->R13;
cpu->regs[14] = getFrom->R14;
cpu->SPSR = getFrom->SPSR;
cpu->CPSR = (cpu->CPSR &~ ARM_SR_M) | newMode;
}
static void cpuPrvException(ArmCpu* cpu, UInt32 vector_pc, UInt32 lr, UInt32 newCPSR){
UInt32 cpsr = cpu->CPSR;
cpuPrvSwitchToMode(cpu, newCPSR & ARM_SR_M);
cpu->CPSR = newCPSR;
cpu->SPSR = cpsr;
cpu->regs[14] = lr;
cpu->regs[15] = vector_pc;
}
static void cpuPrvHandleMemErr(ArmCpu* cpu, UInt32 addr, _UNUSED_ UInt8 sz, _UNUSED_ Boolean write, Boolean instrFetch, UInt8 fsr){
if(cpu->setFaultAdrF) cpu->setFaultAdrF(cpu, addr, fsr);
if(instrFetch){
//handle prefetch abort
cpuPrvException(cpu, cpu->vectorBase + ARM_VECTOR_OFFT_P_ABT, cpu->regs[15] + 4, ARM_CPSR_PAB_ORR | (cpu->CPSR & ARM_CPSR_PAB_AND));
}
else{
//handle data abort
cpuPrvException(cpu, cpu->vectorBase + ARM_VECTOR_OFFT_D_ABT, cpu->regs[15] + ((cpu->CPSR & ARM_SR_T) ? 6 : 4) /* instr + 8*/, ARM_CPSR_DAB_ORR | (cpu->CPSR & ARM_CPSR_DAB_AND));
}
}
static _INLINE_ UInt32 cpuPrvArmAdrMode_1(ArmCpu* cpu, UInt32 instr, Boolean* carryOutP, Boolean wasT, Boolean specialPC){
UInt32 ret;
UInt8 v, a;
Boolean co = ((cpu->CPSR & ARM_SR_C) != 0); //be default carry out = C flag
if(instr & 0x02000000UL){ //immed
v = (instr >> 7) & 0x1E;
ret = cpuPrvROR(instr & 0xFF, v);
if (v) co = ((ret & 0x80000000UL) != 0);
}
else{
v = (instr >> 5) & 3; //get shift type
ret = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC); //get Rm
if(instr & 0x00000010UL){ //reg with reg shift
a = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC); //get the relevant part of Rs, we only care for lower 8 bits (note we use uint8 for this)
if(a != 0){ //else all is already good
switch(v){ //perform shifts
case 0: //LSL
if(a < 32){
co = (ret >> (32 - a)) & 1;
ret = ret << a;
}
else if(a == 32){
co = ret & 1;
ret = 0;
}
else{ // >32
co = 0;
ret = 0;
}
break;
case 1: //LSR
if(a < 32){
co = (ret >> (a - 1)) & 1;
ret = ret >> a;
}
else if(a == 32){
co = ret >> 31;
ret = 0;
}
else{ // >32
co = 0;
ret = 0;
}
break;
case 2: //ASR
if(a < 32){
co = (ret >> (a - 1)) & 1;
ret = ((Int32)ret >> a);
}
else{ // >=32
if(ret & 0x80000000UL){
co = 1;
ret = 0xFFFFFFFFUL;
}
else{
co = 0;
ret = 0;
}
}
break;
case 3: //ROR
if(a == 0){
//nothing...
}
else{
a &= 0x1F;
if(a == 0){
co = ret >> 31;
}
else{
co = (ret >> (a - 1)) & 1;
ret = cpuPrvROR(ret, a);
}
}
break;
}
}
}
else{ //reg with immed shift
a = (instr >> 7) & 0x1F; //get imm
switch(v){
case 0: //LSL
if(a == 0){
//nothing
}
else{
co = (ret >> (32 - a)) & 1;
ret = ret << a;
}
break;
case 1: //LSR
if(a == 0){
co = ret >> 31;
ret = 0;
}
else{
co = (ret >> (a - 1)) & 1;
ret = ret >> a;
}
break;
case 2: //ASR
if(a == 0){
if(ret & 0x80000000UL){
co = 1;
ret = 0xFFFFFFFFUL;
}
else{
co = 0;
ret = 0;
}
}
else{
co = (ret >> (a - 1)) & 1;
if(ret & 0x80000000UL){
ret = (ret >> a) | (0xFFFFFFFFUL << (32 - a));
}
else{
ret = ret >> a;
}
}
break;
case 3: //ROR or RRX
if(a == 0){ //RRX
a = co;
co = ret & 1;
ret = ret >> 1;
if(a) ret |= 0x80000000UL;
}
else{
co = (ret >> (a - 1)) & 1;
ret = cpuPrvROR(ret, a);
}
break;
}
}
}
*carryOutP = co;
return ret;
}
/*
idea:
addbefore is what to add to add to base reg before addressing, addafter is what to add after. we ALWAYS do writeback, but if not requested by instr, it will be zero
for [Rx, 5] baseReg = x addbefore = 5 addafter = -5
for [Rx, 5]! baseReg = x addBefore = 0 addafter = 0
for [Rx], 5 baseReg = x addBefore = 0 addAfter = 5
t = T bit (LDR vs LDRT)
baseReg is returned in return val along with flags:
ARM_MODE_2_REG is mask for reg
ARM_MODE_2_WORD is flag for word access
ARM_MODE_2_LOAD is flag for load
ARM_MODE_2_INV is flag for invalid instructions
ARM_MODE_2_T is flag for T
*/
static _INLINE_ UInt8 cpuPrvArmAdrMode_2(ArmCpu* cpu, UInt32 instr, UInt32* addBeforeP, UInt32* addWritebackP, Boolean wasT, Boolean specialPC){
UInt32 val;
UInt8 reg, shift;
reg = (instr >> 16) & 0x0F;
if(!(instr & 0x02000000UL)){ //immediate
val = instr & 0xFFFUL;
}
else{ //[scaled] register
if(instr & 0x00000010UL) reg |= ARM_MODE_2_INV; //invalid instrucitons need to be reported
val = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
shift = (instr >> 7) & 0x1F;
switch((instr >> 5) & 3){
case 0: //LSL
val <<= shift;
break;
case 1: //LSR
val = shift ? (val >> shift) : 0;
break;
case 2: //ASR
val = shift ? (UInt32)((((Int32)val) >> shift)) : ((val & 0x80000000UL) ? 0xFFFFFFFFUL : 0x00000000UL);
break;
case 3: //ROR/RRX
if(shift){
val = cpuPrvROR(val, shift);
}
else{ //RRX
val = val >> 1;
if(cpu->CPSR & ARM_SR_C) val |= 0x80000000UL;
}
}
}
if(!(instr & 0x00400000UL)) reg |= ARM_MODE_2_WORD;
if(instr & 0x00100000UL) reg |= ARM_MODE_2_LOAD;
if(!(instr & 0x00800000UL)) val = -val;
if(!(instr & 0x01000000UL)){
*addBeforeP = 0;
*addWritebackP = val;
if(instr & 0x00200000UL) reg |= ARM_MODE_2_T;
}
else if(instr & 0x00200000UL){
*addBeforeP = val;
*addWritebackP = val;
}
else{
*addBeforeP = val;
*addWritebackP = 0;
}
return reg;
}
/*
same comments as for addr mode 2 apply
#define ARM_MODE_3_REG 0x0F //flag for actual reg number used
#define ARM_MODE_3_TYPE 0x30 //flag for the below 4 types
#define ARM_MODE_3_H 0x00
#define ARM_MODE_3_SH 0x10
#define ARM_MODE_3_SB 0x20
#define ARM_MODE_3_D 0x30
#define ARM_MODE_3_LOAD 0x40
#define ARM_MODE_3_INV 0x80
*/
static _INLINE_ UInt8 cpuPrvArmAdrMode_3(ArmCpu* cpu, UInt32 instr, UInt32* addBeforeP, UInt32* addWritebackP, Boolean wasT, Boolean specialPC){
UInt32 val;
UInt8 reg;
Boolean S, H, L;
reg = (instr >> 16) & 0x0F;
if(instr & 0x00400000UL){ //immediate
val = ((instr >> 4) & 0xF0) | (instr & 0x0F);
}
else{
if(instr & 0x00000F00UL) reg |= ARM_MODE_3_INV; //bits 8-11 must be 1 always
val = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
}
L = (instr & 0x00100000UL) != 0;
H = (instr & 0x00000020UL) != 0;
S = (instr & 0x00000040UL) != 0;
if(S && H){
reg |= ARM_MODE_3_SH;
}
else if(S){
reg |= ARM_MODE_3_SB;
}
else if(H){
reg |= ARM_MODE_3_H;
}
else{
reg |= ARM_MODE_3_INV; //S == 0 && H == 0 is invalid mode 3 operation
}
if((instr & 0x00000090UL) != 0x00000090UL) reg |= ARM_MODE_3_INV; //bits 4 and 7 must be 1 always
if(S && !L){ //LDRD/STRD is encoded thusly
reg = (reg &~ ARM_MODE_3_TYPE) | ARM_MODE_3_D;
L = !H;
}
if(L) reg |= ARM_MODE_3_LOAD;
if(!(instr & 0x00800000UL)) val = -val;
if(!(instr & 0x01000000UL)){
*addBeforeP = 0;
*addWritebackP = val;
if(instr & 0x00200000UL) reg |= ARM_MODE_3_INV; //W must be 0 in this case, else unpredictable (in this case - invalid instr)
}
else if(instr & 0x00200000UL){
*addBeforeP = val;
*addWritebackP = val;
}
else{
*addBeforeP = val;
*addWritebackP = 0;
}
return reg;
}
/*
#define ARM_MODE_4_REG 0x0F
#define ARM_MODE_4_INC 0x10 //incr or decr
#define ARM_MODE_4_BFR 0x20 //after or before
#define ARM_MODE_4_WBK 0x40 //writeback?
#define ARM_MODE_4_S 0x80 //S bit set?
*/
static UInt8 cpuPrvArmAdrMode_4(_UNUSED_ ArmCpu* cpu, UInt32 instr, UInt16* regs){
UInt8 reg;
*regs = instr;
reg = (instr >> 16) & 0x0F;
if(instr & 0x00400000UL) reg |= ARM_MODE_4_S;
if(instr & 0x00200000UL) reg |= ARM_MODE_4_WBK;
if(instr & 0x00800000UL) reg |= ARM_MODE_4_INC;
if(instr & 0x01000000UL) reg |= ARM_MODE_4_BFR;
return reg;
}
/*
#define ARM_MODE_5_REG 0x0F
#define ARM_MODE_5_ADD_BEFORE 0x10 //add value before?
#define ARM_MODE_5_ADD_AFTER 0x20 //add value after?
#define ARM_MODE_5_IS_OPTION 0x40 //is value option (as opposed to offset)
#define ARM_MODE_5_RR 0x80
*/
static _INLINE_ UInt8 cpuPrvArmAdrMode_5(_UNUSED_ ArmCpu* cpu, UInt32 instr, UInt32* valP){
UInt32 val;
UInt8 reg;
val = instr & 0xFF;
reg = (instr >> 16) & 0x0F;
if(!(instr & 0x01000000UL)){ //unindexed or postindexed
if(instr & 0x00200000UL){ //postindexed
reg |= ARM_MODE_5_ADD_AFTER;
}
else{ //unindexed
if(!(instr & 0x00800000UL)) reg |= ARM_MODE_5_RR; //U must be 1 for unindexed, else it is MCRR/MRCC
reg |= ARM_MODE_5_IS_OPTION;
}
}
else{ //offset or preindexed
reg |= ARM_MODE_5_ADD_BEFORE;
if(instr & 0x00200000UL){ //preindexed
reg |= ARM_MODE_5_ADD_AFTER;
}
}
if(!(reg & ARM_MODE_5_IS_OPTION)){
val = val << 2;
if(!(instr & 0x00800000UL)) val = -val;
}
*valP = val;
return reg;
}
static _INLINE_ void cpuPrvSetPSR(ArmCpu* cpu, UInt8 mask, Boolean privileged, Boolean R, UInt32 val){
if(R){ //setting SPSR in sys or usr mode is no harm since they arent used, so just do it without any checks
cpu->SPSR = val;
}
else{
UInt32 newCPSR = cpu->CPSR;
if(privileged){
if(mask & 1){
cpuPrvSwitchToMode(cpu, val & ARM_SR_M);
newCPSR = (newCPSR & 0xFFFFFF00UL) | (val & 0x000000FFUL);
}
if(mask & 2) newCPSR = (newCPSR & 0xFFFF00FFUL) | (val & 0x0000FF00UL);
if(mask & 4) newCPSR = (newCPSR & 0xFF00FFFFUL) | (val & 0x00FF0000UL);
}
if(mask & 8) newCPSR = (newCPSR & 0x00FFFFFFUL) | (val & 0xFF000000UL);
cpu->CPSR = newCPSR;
}
}
static _INLINE_ Boolean cpuPrvSignedAdditionOverflows(UInt32 a, UInt32 b, UInt32 sum){
return ((a ^ b ^ 0x80000000UL) & (a ^ sum)) >> 31;
}
static _INLINE_ Boolean cpuPrvSignedSubtractionOverflows(UInt32 a, UInt32 b, UInt32 diff){ //diff = a - b
return ((a ^ b) & (a ^ diff)) >> 31;
}
static _INLINE_ UInt32 cpuPrvMedia_signedSaturate32(UInt32 sign){
return (sign & 0x80000000UL) ? 0xFFFFFFFFUL : 0;
}
#ifdef ARM_V6
static _INLINE_ UInt32 cpuPrvMedia_sxtb(UInt8 v){
UInt32 r = v;
if(v & 0x80) r |= 0xFFFFFF00UL;
return r;
}
static _INLINE_ UInt32 cpuPrvMedia_uxtb(UInt8 v){
UInt32 r = v;
return r;
}
static _INLINE_ UInt32 cpuPrvMedia_sxth(UInt16 v){
UInt32 r = v;
if(v & 0x8000UL) r |= 0xFFFF0000UL;
return r;
}
static _INLINE_ UInt32 cpuPrvMedia_uxth(UInt16 v){
UInt32 r = v;
return r;
}
static _INLINE_ UInt32 cpuPrvMedia_sxt16(UInt32 v){
if(v & 0x00800000UL){
v |= 0xFF000000UL;
}
else{
v &=~ 0xFF000000UL;
}
if(v & 0x00000080UL){
v |= 0x0000FF00UL;
}
else{
v &=~ 0x0000FF00UL;
}
return v;
}
static _INLINE_ UInt32 cpuPrvMedia_uxt16(UInt32 v){
v &=~ 0xFF00FF00UL;
return v;
}
static _INLINE_ UInt32 cpuPrvMedia_dualAdd(UInt32 a, UInt32 b){ //3 ands, 2 ads, 1 sub
UInt32 sum = a + b;
sum -= (((a & 0xFFFFUL) + (b & 0xFFFFUL)) & 0xFFFF0000UL); //think about it...it should work...
return sum;
}
static _INLINE_ UInt32 cpuPrvMedia_SignedSat(UInt32 val, UInt8 from_bits, UInt8 to_bits){ //returns value. [orred with 0x80000000UL to indicate sat was done]
UInt32 check_mask, top_in;
if(from_bits <= to_bits) return val; //no saturation needed
top_in = (1UL << (from_bits - 1));
check_mask = (1UL << to_bits) - (1UL << from_bits);
if(val & top_in){ //input was negative
if((val & check_mask) != check_mask){ //saturate
val = top_in | 0x80000000UL;
}
}
else{
if(val & check_mask){ //saturate
val = (top_in - 1) | 0x80000000UL;
}
}
return val;
}
static _INLINE_ Boolean cpuPrvMediaInstrs(ArmCpu* cpu, UInt32 instr, Boolean wasT, Boolean specialPC){
UInt64 v64;
UInt32 v32a, v32b, v32c, v32d;
Boolean vB = false;
UInt8 v8a, v8b;
UInt16 v16;
switch((instr >> 20) & 0x1F){
case 0: //parrallel addition/subtraction signed
case 1:
case 2:
case 3:
//TODO
break;
case 4: //parrallel addition/subtraction unsigned
case 5:
case 6:
case 7:
//TODO
break;
case 8: //packing/unpacking/saturation/reversal
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
v8a = (instr >> 16) & 0x0F;
v8b = (instr >> 5) & 7;
switch((instr >> 20) & 7){
case 0: switch(v8b){
case 0: //PKH
case 2:
case 4:
case 6:
v32a = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC); //Rn
v32b = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC); //Rm
v8a = (instr >> 7) & 0x1F;
if(instr & 0x00000040UL){ //ASR
if(!v8a) v8a = 32;
v32b = (((Int32)v32b) >> v8a);
v32c = (v32b & 0xFFFFUL) | (v32a & 0xFFFF0000UL);
}
else{ //LSL
v32b <<= v8a;
v32c = (v32a & 0xFFFFUL) | (v32b & 0xFFFF0000UL);
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32c);
break;
case 3: //SXTAB16 SXTB16
vB = true; //sign extend
goto xt16;
case 5: //SEL
v32a = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC); //Rn
v32b = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC); //Rm
v32c = 0;
if(cpu->CPSR & ARM_SR_GE_0) v32c |= 0x000000FFUL;
if(cpu->CPSR & ARM_SR_GE_1) v32c |= 0x0000FF00UL;
if(cpu->CPSR & ARM_SR_GE_2) v32c |= 0x00FF0000UL;
if(cpu->CPSR & ARM_SR_GE_2) v32c |= 0xFF000000UL;
v32c = (v32a & v32c) | (v32b & ~v32c);
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32c);
break;
default:
return false;
}
break;
case 2: switch(v8b){
case 1: //SSAT16
//TODO;
break;
case 3: //SXTAB SXTB
vB = true; //sign extend
goto xtb;
case 0:
case 2:
case 4:
case 6:
goto ssat;
break;
default:
return false;
}
break;
case 3: switch(v8b){
case 1: //REV
//TODO;
break;
case 3: //SXTAH SXTH
vB = true; //sign extend
goto xth;
case 5: //REV16
//TOOD;
break;
case 0: //SSAT
case 2:
case 4:
case 6:
ssat:
//TODO
break;
default:
return false;
}
break;
case 4: //UXTAB16 UXTB16
if(v8b != 3) return false;
vB = false; //not sign extend
xt16:
v32a = (v8a == 15) ? 0 : cpuPrvGetReg(cpu, v8a, wasT, specialPC); //Rn
v32b = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC); //Rm
v32b = cpuPrvROR(v32b, (instr >> 7) & 0x18);
v32b = vB ? cpuPrvMedia_sxt16(v32b) : cpuPrvMedia_uxt16(v32b);
v32c = cpuPrvMedia_dualAdd(v32a, v32b);
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32a);
break;
case 6: switch(v8b){
case 1: //USAT16
//TODO;
break;
case 3: //UXTAB UXTB
vB = false; //not sign extend
xtb:
v32a = (v8a == 15) ? 0 : cpuPrvGetReg(cpu, v8a, wasT, specialPC); //Rn
v32b = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC) //Rm
v32b = cpuPrvROR(v32b, (instr >> 7) & 0x18);
v32b = vB ? cpuPrvMedia_sxtb(v32b) : cpuPrvMedia_uxtb(v32b);
v32a += v32b;
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32a);
break;
case 0:
case 2:
case 4:
case 6:
goto usat;
default:
return false;
}
break;
case 7: switch(v8b){
case 1: //RBIT
v32a = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
v32b = 0;
v32c = 0x80000000UL;
v32d = 0x00000001UL;
//faster ways exist, but this is smaller code
for(v8a = 0; v8a < 32; v8a++, v32c >>= 1, v32d <<= 1) if(v32a & v32c) v32b |= v32d;
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32b);
break;
case 3: //UXTAH UXTH
vB = false; //not sign extend
xth:
v32a = (v8a == 15) ? 0 : cpuPrvGetReg(cpu, v8a, wasT, specialPC); //Rn
v32b = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC) //Rm
v32b = cpuPrvROR(v32b, (instr >> 7) & 0x18);
v32b = vB ? cpuPrvMedia_sxth(v32b) : cpuPrvMedia_uxth(v32b);
v32a += v32b;
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32a);
break;
case 5: //REVSH
//TODO;
break;
case 0: //USAT
case 2:
case 4:
case 6:
usat:
//TODO;
break;
default:
return false;
}
break;
default:
return false;
}
break;
case 16: //signed multiplies
case 17:
case 18:
case 19:
case 20:
case 21:
case 22:
case 23:
v8a = (instr >> 12) & 0x0F;
if((instr & 0x00700000UL) == 0x00000000){ //SMLAD, SMUAD, SMLSD, SMUSD
v32a = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC); //Rm
v32b = cpuPrvGetReg(cpu, (instr >> 0) & 0x0F, wasT, specialPC); //Rn
if(instr & 0x00000020UL) v32a = cpuPrvROR(v32a, 16);
v32c = (Int32)((Int16)(v32a & 0xFFFFUL)) * (Int32)((Int16)(v32b & 0xFFFFUL));
v32a = (Int32)((Int16)(v32a >> 16)) * (Int32)((Int16)(v32b >> 16));
v32b = (v8a == 15) ? 0 : cpuPrvGetReg(cpu, v8a, wasT, specialPC);
if((instr & 0x000000C0UL) == 0x00000000){ //SMLAD, SMUAD
//now add them and mark Q flag is we overflow
v32d = v32a + v32c;
vB = cpuPrvSignedAdditionOverflows(v32a, v32c, v32d);
}
else if((instr & 0x000000C0UL) == 0x00000040UL){ //SMLSD, SMUSD
v32d = v32a - v32c;
vB = cpuPrvSignedSubtractionOverflows(v32a, v32c, v32d);
}
else return false;
v32a = v32d + v32b;
vB = vB || cpuPrvSignedAdditionOverflows(v32d, v32b, v32a);
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32a);
}
else if((instr & 0x00700000UL) == 0x00400000UL){ //SMLALD, SMLSLD
v64 = u64_from_halves(cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC), cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC));
v32a = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC); //Rm
v32b = cpuPrvGetReg(cpu, (instr >> 0) & 0x0F, wasT, specialPC); //Rn
if(instr & 0x00000020UL) v32a = cpuPrvROR(v32a, 16);
v32c = (Int32)((Int16)(v32a & 0xFFFFUL)) * (Int32)((Int16)(v32b & 0xFFFFUL));
v32a = (Int32)((Int16)(v32a >> 16)) * (Int32)((Int16)(v32b >> 16));
if((instr & 0x000000C0UL) == 0x00000000){ //SMLALD
v64 = u64_add(v64, u64_add(u64_xtnd32(u64_32_to_64(v32a)), u64_xtnd32(u64_32_to_64(v32c))));
}
else if((instr & 0x000000C0UL) == 0x00000040){ //SMLSLD
v64 = u64_add(v64, u64_sub(u64_xtnd32(u64_32_to_64(v32a)), u64_xtnd32(u64_32_to_64(v32c))));
}
else return false;
cpuPrvSetReg(cpu, (instr >> 16) & 0x0F, u64_get_hi(v64));
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, u64_64_to_32(v64));
}
else if((instr & 0x00700000UL) == 0x00500000UL){ //SMMLA, SMMUL, SMMLS
v32a = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC); //Rm
v32b = cpuPrvGetReg(cpu, (instr >> 0) & 0x0F, wasT, specialPC); //Rn
v32c = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC); //Ra
v64 = u64_smul3232(v32a, v32b);
if((instr & 0x000000C0UL) == 0x00000000){ //SMMLA, SMMUL
if(v8a == 15){ //SMMUL
//nothing to do here
}
else{ //SMMLA
v64 = u64_add(v64, u64_shl(u64_32_to_64(v32c), 32));
}
}
else if((instr & 0x000000C0UL) == 0x000000C0){ //SMMLS
v64 = u64_sub(v64, u64_shl(u64_32_to_64(v32c), 32));
}
else return false;
if(instr & 0x00000010UL) v64 = u64_add(v64, u64_32_to_64(0x80000000UL)); //round if requested
cpuPrvSetReg(cpu, (instr >> 16) & 0x0F, u64_get_hi(v64));
}
else return false;
if(vB){ //overflow
cpu->CPSR |= ARM_SR_Q;
}
break;
case 24: //USAD8 USADA8
if(instr & 0x000000E0UL) return false;
v32a = ((instr & 0x0000F000UL) == 0x0000F000UL) ? 0 : cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC); //Ra
v32b = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC); //Rn
v32c = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC); //Rm
for(v8a = 0; v8a < 4; v8a++){
v16 = (v32b & 0xFF) - (v32c & 0xFF);
if(v16 & 0x8000U) v16 = -v16;
v32a += v16;
v32b >>= 8;
v32c >>= 8;
}
cpuPrvSetReg(cpu, (instr >> 16) & 0x0F, v32a);
break;
case 26:
case 27:
vB = true; //sign extend
goto bitfield_extract;
case 30:
case 31:
vB = false; //do not sign extend
bitfield_extract:
if((instr & 0x00000060UL) == 0x00000040){ //unsigned bitfield extract
v32a = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
v8a = ((instr >> 16) & 0x1F) + 1;
v8b = (instr >> 7) & 0x1F;
v32a >>= v8b;
v32c = 0xFFFFFFFFUL << v8a;
v32a &=~ v32c;
if(vB){ //sign extend
if(v32a & (1UL << (v8a - 1))) v32a |= v32c;
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32a);
}
else return false;
break;
case 28:
case 29:
if(instr & 0x00000060UL){
return false;
}
else{ //BFC BFI
v8a = 31 - ((instr >> 16) & 0x1F);
v8b = (instr >> 7) & 0x1F;
v32a = 0xFFFFFFFFUL;
v32a >>= v8b;
v32a <<= v8b + v8a;
v32a >>= v8a;
v32b = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC);
v32b &= ~v32a;
if((instr & 0x0000000FUL) == 0x0000000F){ //BFC
//bits already clear
}
else{ //BFI
v32c = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
v32c >>= v8b;
v32c <<= v8b + v8a;
v32c >>= v8a;
v32b |= v32c;
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32b);
}
break;
default:
return false;
}
return true;
}
#endif
static Err cpuPrvExecInstr(ArmCpu* cpu, UInt32 instr, UInt32 instrPC/* lower bit always clear */, Boolean wasT , Boolean privileged, Boolean specialPC/* for thumb*/){
Boolean specialInstr = false, usesUsrRegs, execute = false, L, ok;
UInt8 fsr;
usesUsrRegs = ((cpu->CPSR & ARM_SR_M) == ARM_SR_MODE_USR) || ((cpu->CPSR & ARM_SR_M) == ARM_SR_MODE_SYS);
//check condition code
{
switch(instr >> 29UL){
case 0: //EQ / NE
execute = (cpu->CPSR & ARM_SR_Z) != 0;
break;
case 1: //CS / CC
execute = (cpu->CPSR & ARM_SR_C) != 0;
break;
case 2: //MI/PL
execute = (cpu->CPSR & ARM_SR_N) != 0;
break;
case 3: //VS/VC
execute = (cpu->CPSR & ARM_SR_V) != 0;
break;
case 4: //HI/LS
execute = (cpu->CPSR & ARM_SR_C) && !(cpu->CPSR & ARM_SR_Z);
break;
case 5: //GE/LT
execute = !(cpu->CPSR & ARM_SR_N) == !(cpu->CPSR & ARM_SR_V); //check for N == V
break;
case 6: //GT/LE
execute = !(cpu->CPSR & ARM_SR_N) == !(cpu->CPSR & ARM_SR_V); //check for N == V
execute = execute && !(cpu->CPSR & ARM_SR_Z); //enforce Z==0
break;
case 7:
specialInstr = (instr & 0x10000000UL) != 0;
execute = true;
break;
}
if((instr & 0x10000000UL) && !specialInstr) execute = !execute; //invert for inverted conditions
}
//execute, if needed
if(execute){
UInt64 v64;
register UInt32 adr, tmp, v32;
UInt32 m32, x32; //non-register 32-bit val
UInt16 v16;
UInt8 va8, vb8 = 0, vc8;
switch((instr >> 24) & 0x0F){
case 0:
case 1: //data processing immediate shift, register shift and misc instrs and mults
if(specialInstr) goto invalid_instr;
if((instr & 0x00000090UL) == 0x00000090){ //multiplies, extra load/stores (table 3.2)
if((instr & 0x00000060UL) == 0x00000000){ //swp[b], mult(acc), mult(acc) long
if(instr & 0x01000000UL){ //SWB/SWPB
switch((instr >> 20) & 0x0F){
case 0: //SWP
adr = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC);
ok = cpu->memF(cpu, &m32, adr, 4, false, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, 4, false, false, fsr);
goto instr_done;
}
tmp = m32;
m32 = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
ok = cpu->memF(cpu, &m32, adr, 4, true, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, 4, true, false, fsr);
goto instr_done;
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, tmp);
break;
case 4: //SWPB
adr = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC);
ok = cpu->memF(cpu, &vc8, adr, 1, false, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, 1, false, false, fsr);
goto instr_done;
}
va8 = vc8;
vc8 = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
ok = cpu->memF(cpu, &vc8, adr, 1, true, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, 1, true, false, fsr);
goto instr_done;
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, va8);
break;
#ifdef ARM_V6
/* about exclusives: these are for SMP. we do not have SMP, so for now they always succeed */
case 8: //STREX
if((instr & 0x00000F00UL) != 0x00000F00UL) goto invalid_instr;
adr = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC);
tmp = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
ok = cpu->memF(cpu, &tmp, adr, 4, true, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, 4, true, false, fsr);
goto instr_done;
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, 0); //0 -> success
break;
case 9: //LDREX
if((instr & 0x00000F0FUL) != 0x00000F0FUL) goto invalid_instr;
adr = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC);
ok = cpu->memF(cpu, &tmp, adr, 4, false, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, 4, false, false, fsr);
goto instr_done;
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, tmp);
break;
/* these are a v7/v6K thing - we do not bother
case 10: //STREXD
//TODO
case 11: //LDREXD
//TODO
case 12: //STREXB
//TODO
case 13: //LDREXB
//TODO
case 14: //STREXH
//TODO
case 15: //LDREXH
//TODO
*/
#endif
default:
goto invalid_instr;
}
}
else switch((instr >> 20) & 0x0F){ //multiplies
case 0: //MUL
case 1:
tmp = 0;
if(instr & 0x0000F000UL) goto invalid_instr;
goto mul32;
case 2: //MLA
case 3:
tmp = cpuPrvGetReg(cpu, (instr >> 12 ) & 0x0F, wasT, specialPC);
mul32:
tmp += cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC) * cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
cpuPrvSetReg(cpu, (instr >> 16) & 0x0F, tmp);
if(instr & 0x00100000UL){ //S
adr = cpu->CPSR &~ (ARM_SR_Z | ARM_SR_N);
if(!tmp) adr |= ARM_SR_Z;
if(tmp & 0x80000000UL) adr |= ARM_SR_N;
cpu->CPSR = adr;
}
goto instr_done;
#ifdef ARM_V6
case 4: //UMAAL
v64 = u64_32_to_64((instr >> 12) & 0x0F);
v64 = u64_add32(v64, (instr >> 16) & 0x0F);
goto mul64;
case 6: //MLS
tmp = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC);
tmp *= cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
tmp = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC) - tmp;
cpuPrvSetReg(cpu, (instr >> 16) & 0x0F, tmp);
break;
#endif
case 8: //UMULL
case 9:
case 12: //SMULL
case 13:
v64 = u64_zero();
goto mul64;
case 10: //UMLAL
case 11:
case 14: //SMLAL
case 15:
v64 = u64_from_halves(cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC), cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC));
mul64:
adr = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC);
tmp = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC);
v64 = u64_add(v64, (instr & 0x00400000UL) ? u64_smul3232(adr, tmp) : u64_umul3232(adr, tmp));
v32 = u64_get_hi(v64);
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, u64_64_to_32(v64));
cpuPrvSetReg(cpu, (instr >> 16) & 0x0F, v32);
if(instr & 0x00100000UL){ //S
adr = cpu->CPSR &~ (ARM_SR_Z | ARM_SR_N);
if(u64_isZero(v64)) adr |= ARM_SR_Z;
if(v32 & 0x80000000UL) adr |= ARM_SR_N;
cpu->CPSR = adr;
}
break;
default:
goto invalid_instr;
}
}
else{ //load/store signed/unsigned byte/halfword/two_words
UInt32 store[2] = {0,0};
UInt8* store8 = (UInt8*)store;
UInt16* store16 = (UInt16*)store;
va8 = cpuPrvArmAdrMode_3(cpu, instr, &m32, &x32, wasT, specialPC);
tmp = m32;
v32 = x32;
if(va8 & ARM_MODE_3_INV ) goto invalid_instr;
adr = cpuPrvGetReg(cpu, va8 & ARM_MODE_3_REG, wasT, specialPC);
switch(va8 & ARM_MODE_3_TYPE){
case ARM_MODE_3_H:
case ARM_MODE_3_SH:
vb8 = 2;
break;
case ARM_MODE_3_SB:
vb8 = 1;
break;
case ARM_MODE_3_D:
vb8 = 8;
break;
}
if(va8 & ARM_MODE_3_LOAD){
ok = cpu->memF(cpu, store, adr + tmp, vb8, false, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr + tmp, vb8, false, false, fsr);
goto instr_done;
}
if(vb8 == 1){
tmp = *store8;
if(tmp & 0x80) tmp |= 0xFFFFFF00UL; //sign-extend
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, tmp);
}
else if(vb8 == 2){
tmp = *store16;
if(((va8 & ARM_MODE_3_TYPE) == ARM_MODE_3_SH) && (tmp & 0x8000UL)) tmp |= 0xFFFF0000UL;
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, tmp);
}
else{
cpuPrvSetReg(cpu, ((instr >> 12) & 0x0F) + 0, store[0]);
cpuPrvSetReg(cpu, ((instr >> 12) & 0x0F) + 1, store[1]);
}
if(v32) cpuPrvSetReg(cpu, va8 & ARM_MODE_3_REG, v32 + adr);
}
else{
if(v32){
v32 += adr;
va8 |= ARM_MODE_3_INV; //re-use flag to mean writeback
}
if(vb8 == 1){
*store8 = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC);
}
else if(vb8 == 2){
*store16 = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC);
}
else{
store[0] = cpuPrvGetReg(cpu, ((instr >> 12) & 0x0F) + 0, wasT, specialPC);
store[1] = cpuPrvGetReg(cpu, ((instr >> 12) & 0x0F) + 1, wasT, specialPC);
}
adr += tmp;
ok = cpu->memF(cpu, store, adr, vb8, true, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, vb8, true, false, fsr);
goto instr_done;
}
if(va8 & ARM_MODE_3_INV) cpuPrvSetReg(cpu, va8 & ARM_MODE_3_REG, v32);
}
}
goto instr_done;
}
else if((instr & 0x01900000UL) == 0x01000000UL){ //misc instrs (table 3.3)
tmp = (instr >> 4) & 0x0F;
switch(tmp){
case 0: //move reg to PSR or move PSR to reg
if((instr & 0x00BF0FFFUL) == 0x000F0000UL){ //move PSR to reg
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, (instr & 0x00400000UL) ? cpu->SPSR : cpu->CPSR); //access in user and sys mode is undefined. for us that means returning garbage that is currently in "cpu->SPSR"
}
else if((instr & 0x00B0FFF0UL) == 0x0020F000UL){ //move reg to PSR
cpuPrvSetPSR(cpu, (instr >> 16) & 0x0F, privileged, (instr & 0x00400000UL) !=0, cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC));
}
else goto invalid_instr;
goto instr_done;
case 1: //BLX/BX/BXJ or CLZ
case 3:
if(instr & 0x00400000UL){ //CLZ
if((instr & 0x0FFF0FF0UL) != 0x016F0F10UL) goto invalid_instr;
tmp = cpuPrvGetReg(cpu, instr &0xF, wasT, specialPC);
adr = 0x80000000UL;
for(va8 = 0; va8 < 32; va8++, adr >>= 1) if(tmp & adr) break;
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, va8);
}
else{ //BL / BLX / BXJ
if((instr & 0x0FFFFF00UL) != 0x012FFF00UL) goto invalid_instr;
if((instr & 0x00000030UL) == 0x00000030UL) cpuPrvSetReg(cpu, 14, instrPC + (wasT ? 3 : 4)); //save return value for BLX
cpuPrvSetPC(cpu, cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC));
}
goto instr_done;
case 5: //enhanced DSP adds/subtracts
if(instr & 0x00000F00UL) goto invalid_instr;
tmp = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC); //Rm
adr = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC); //Rn
vb8 = 0; //used as boolead for if saturation happened
switch((instr >> 21) & 3){ //what op?
case 0: //QADD
v32 = tmp + adr;
vb8 = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
if(vb8) v32 = cpuPrvMedia_signedSaturate32(adr);
break;
case 1: //QSUB
v32 = tmp - adr;
vb8 = cpuPrvSignedAdditionOverflows(tmp, adr, v32);
if(vb8) v32 = cpuPrvMedia_signedSaturate32(tmp);
break;
case 2: //QDADD
v32 = adr << 1;
vb8 = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
if(vb8) v32 = cpuPrvMedia_signedSaturate32(adr);
adr = v32;
v32 = tmp + adr;
if(cpuPrvSignedAdditionOverflows(adr, tmp, v32)){
vb8 = 1;
v32 = cpuPrvMedia_signedSaturate32(adr);
}
break;
case 3: //QDSUB
v32 = adr << 1;
vb8 = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
if(vb8) v32 = cpuPrvMedia_signedSaturate32(adr);
adr = v32;
v32 = tmp + adr;
if(cpuPrvSignedAdditionOverflows(adr, tmp, v32)){
vb8 = 1;
v32 = cpuPrvMedia_signedSaturate32(adr);
}
break;
default:
v32 = 0; //make compiler happy;
break;
}
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, v32); //save result
if(vb8) cpu->CPSR |= ARM_SR_Q;
goto instr_done;
case 7: //soft breakpoint
cpuPrvException(cpu, cpu->vectorBase + ARM_VECTOR_OFFT_P_ABT, instrPC + 4, ARM_CPSR_PAB_ORR | (cpu->CPSR & ARM_CPSR_PAB_ORR));
goto instr_done;
case 8: //enhanced DSP multiplies
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
if((instr & 0x00000090UL) != 0x00000080UL) goto invalid_instr;
tmp = cpuPrvGetReg(cpu, instr & 0x0F, wasT, specialPC); //Rm
adr = cpuPrvGetReg(cpu, (instr >> 8) & 0x0F, wasT, specialPC); //Rs
vb8 = 0; //used as boolead for if saturation happened
va8 = 0; //used as temporary boolean flag
switch((instr >> 21) & 3){ //what op?
case 0: //SMLAxy
if(instr & 0x20) tmp >>= 16;
else tmp &= 0xFFFFUL;
if(tmp & 0x8000UL) tmp |= 0xFFFF0000UL;
if(instr & 0x40) adr >>= 16;
else adr &= 0xFFFFUL;
if(adr & 0x8000UL) adr |= 0xFFFF0000UL;
tmp *= adr;
adr = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC); //Rn
v32 = tmp + adr;
vb8 = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
break;
case 1: //SMLAWy/SMULWy
if(instr & 0x40) adr >>= 16;
else adr &= 0xFFFFUL;
if(adr & 0x8000UL) adr |= 0xFFFF0000UL;
adr = u64_64_to_32(u64_shr(u64_smul3232(tmp, adr), 16)); //do the multiplication, WAS: adr = (((UInt64)tmp) * ((UInt64)adr)) >> 16;
if(instr & 0x20){ //SMULWy
v32 = adr;
if(instr & 0x0000F000UL) goto invalid_instr;
}
else{ //SMLAWy
tmp = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC); //Rn
v32 = adr + tmp;
vb8 = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
}
break;
case 2: //SMLALxy
if(instr & 0x20) tmp >>= 16;
else tmp &= 0xFFFFUL;
if(tmp & 0x8000UL) tmp |= 0xFFFF0000UL;
if(instr & 0x40) adr >>= 16;
else adr &= 0xFFFFUL;
if(adr & 0x8000UL) adr |= 0xFFFF0000UL;
adr *= tmp;
if(adr & 0x80000000UL) va8 |= 1; //neg
tmp = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC); //RdLo
if((tmp + adr) < tmp) va8 |= 2; //carry
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, tmp);
v32 = cpuPrvGetReg(cpu, (instr >> 16) & 0x0F, wasT, specialPC); //RdHi
if(va8 & 2) v32++;
if(va8 & 1) v32--;
break;
case 3: //SMULxy
if(instr & 0x0000F000UL) goto invalid_instr;
if(instr & 0x20) tmp >>= 16;
else tmp &= 0xFFFFUL;
if(tmp & 0x8000UL) tmp |= 0xFFFF0000UL;
if(instr & 0x40) adr >>= 16;
else adr &= 0xFFFFUL;
if(adr & 0x8000UL) adr |= 0xFFFF0000UL;
v32 = tmp * adr;
break;
default: //make compiler happy
v32 = 0;
break;
}
cpuPrvSetReg(cpu, (instr >> 16) & 0x0F, v32);
if(vb8) cpu->CPSR |= ARM_SR_Q;
goto instr_done;
default:
goto invalid_instr;
}
}
goto data_processing;
break;
case 2:
case 3: //data process immediate val, move imm to SR
if(specialInstr) goto invalid_instr;
if((instr & 0x01900000UL) == 0x01000000UL){ //all NON-data-processing instrs in this space are here
if(instr & 0x00200000UL){ //MSR imm and hints
if((instr & 0x00400000UL) || (instr & 0x000F0000UL)){ //move imm to PSR
cpuPrvSetPSR(cpu, (instr >> 16) & 0x0F, privileged, (instr & 0x00400000UL) != 0, cpuPrvROR(instr & 0xFF, ((instr >> 8) & 0x0F) * 2));
}
else{
#ifdef ARM_V6
if((instr & 0x000000F0UL) == 0x000000F0){ //debug hint
err_str("DEBUG hint 0x");
err_hex(instr & 0x0F);
err_str(" at 0x");
err_hex(instrPC);
err_str("\r\n");
}
else switch(instr){
case 0: //NOP
break;
case 1: //YIELD;
break;
case 2: //WFE
break;
case 3: //WFI
break;
case 4: //SEV
break;
}
goto instr_done; //all hints are treated as valid and do nothing...
#endif
goto invalid_instr;
}
}
else if(instr & 0x00400000UL){ //MOVT (high halfword 16-bit immediate load)
adr = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC) & 0xFFFFUL;
adr |= ((instr & 0xFFFUL) << 16) | ((instr & 0xF0000UL) << 12);
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, adr);
}
else{ //MOVW (16-bit immediate load)
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, (instr & 0xFFFUL) | ((instr >> 4) & 0xF000UL));
}
goto instr_done;
}
data_processing: //data processing
{
Boolean carryOut, carryIn, V, S, store = true;
S = (instr & 0x00100000UL) != 0;
V = (cpu->CPSR & ARM_SR_V) != 0;
carryIn = (cpu->CPSR & ARM_SR_C) != 0;
tmp = cpuPrvArmAdrMode_1(cpu, instr, &carryOut, wasT, specialPC);
va8 = (instr >> 16) & 0x0F;
switch((instr >> 21) & 0x0F){
case 0: //AND
tmp = cpuPrvGetReg(cpu, va8, wasT, specialPC) & tmp;
break;
case 1: //EOR
tmp = cpuPrvGetReg(cpu, va8, wasT, specialPC) ^ tmp;
break;
case 2: //SUB
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
v32 = adr - tmp;
if(S) V = cpuPrvSignedSubtractionOverflows(adr, tmp, v32);
if(S) carryOut = adr >= tmp;
tmp = v32;
break;
case 3: //RSB
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
v32 = tmp - adr;
if(S) V = cpuPrvSignedSubtractionOverflows(tmp, adr, v32);
if(S) carryOut = tmp >= adr;
tmp = v32;
break;
case 4: //ADD
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
v32 = adr + tmp;
if(S) V = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
if(S) carryOut = v32 < adr;
tmp = v32;
break;
case 5: //ADC
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
if(carryIn){
v32 = adr + tmp + 1;
if(S) carryOut = v32 <= adr;
}
else{
v32 = adr + tmp;
if(S) carryOut = v32 < adr;
}
if(S) V = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
tmp = v32;
break;
case 6: //SBC
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
if(carryIn){
v32 = adr - tmp;
if(S) carryOut = adr >= tmp;
}
else{
v32 = adr - tmp - 1;
if(S) carryOut = adr > tmp;
}
if(S) V = cpuPrvSignedSubtractionOverflows(adr, tmp, v32);
tmp = v32;
break;
case 7: //RSC
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
if(carryIn){
v32 = tmp - adr;
if(S) carryOut = tmp >= adr;
}
else{
v32 = tmp - adr - 1;
if(S) carryOut = tmp > adr;
}
if(S) V = cpuPrvSignedSubtractionOverflows(tmp, adr, v32);
tmp = v32;
break;
case 8: //TST
if(!S) goto invalid_instr;
store = false;
tmp = cpuPrvGetReg(cpu, va8, wasT, specialPC) & tmp;
break;
case 9: //TEQ
if(!S) goto invalid_instr;
store = false;
tmp = cpuPrvGetReg(cpu, va8, wasT, specialPC) ^ tmp;
break;
case 10: //CMP
if(!S) goto invalid_instr;
store = false;
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
V = cpuPrvSignedSubtractionOverflows(adr, tmp, adr - tmp); //((adr ^ tmp) & (adr ^ (adr - tmp))) >> 31;
carryOut = adr >= tmp;
tmp = adr - tmp;
break;
case 11: //CMN
if(!S) goto invalid_instr;
store = false;
adr = cpuPrvGetReg(cpu, va8, wasT, specialPC);
v32 = adr + tmp;
V = cpuPrvSignedAdditionOverflows(adr, tmp, v32);
carryOut = v32 < adr;
tmp = v32;
break;
case 12: //ORR
tmp = cpuPrvGetReg(cpu, va8, wasT, specialPC) | tmp;
break;
case 13: //MOV
//tmp already equals tmp
break;
case 14: //BIC
tmp = cpuPrvGetReg(cpu, va8, wasT, specialPC) & ~tmp;
break;
case 15: //MVN
tmp = ~tmp;
break;
}
vb8 = (instr >> 12) & 0x0F;
if(S){ //update flags or restore CPSR
if(!usesUsrRegs && vb8 == 15 && store){
UInt32 sr;
sr = cpu->SPSR;
cpuPrvSwitchToMode(cpu, sr & ARM_SR_M);
cpu->CPSR = sr;
cpu->regs[15] = tmp; //do it right here - if we let it use cpuPrvSetReg, it will check lower bit...
store = false;
}
else{
adr = cpu->CPSR &~ (ARM_SR_Z | ARM_SR_N | ARM_SR_C | ARM_SR_V);
if(!tmp) adr |= ARM_SR_Z;
if(tmp & 0x80000000UL) adr |= ARM_SR_N;
if(carryOut) adr |= ARM_SR_C;
if(V) adr |= ARM_SR_V;
cpu->CPSR = adr;
}
}
if(store){
if(vb8 == 15){
cpuPrvSetReg(cpu, vb8, tmp &~ 1UL);
cpu->CPSR &=~ ARM_SR_T;
if(tmp & 1) cpu->CPSR |= ARM_SR_T;
}
else{
cpu->regs[vb8] = tmp; //not pc - no need for func call cpuPrvSetReg(cpu, vb8, tmp);
}
}
goto instr_done;
}
break;
case 4:
case 5: //load/stor imm offset
goto load_store_mode_2;
break;
case 6:
case 7: //load/store reg offset
if(instr & 0x00000010UL){ //media and undefined instrs
#ifdef ARM_V6
if(cpuPrvMediaInstrs(cpu, instr, wasT, specialPC)) goto instr_done;
#endif
goto invalid_instr;
}
load_store_mode_2:
if(specialInstr){ //handle PLD
if((instr & 0x0D70F000UL) == 0x0550F000UL) goto instr_done; //PLD
goto invalid_instr;
}
va8 = cpuPrvArmAdrMode_2(cpu, instr, &m32, &x32, wasT, specialPC);
tmp = m32;
v32 = x32;
if(va8 & ARM_MODE_2_INV) goto invalid_instr;
if(va8 & ARM_MODE_2_T) privileged = false;
vb8 = (va8 & ARM_MODE_2_WORD) ? 4 : 1; //get operation size
adr = cpuPrvGetReg(cpu, va8 & ARM_MODE_2_REG, wasT, specialPC);
if(va8 & ARM_MODE_2_LOAD){
ok = cpu->memF(cpu, &m32, adr + tmp, vb8, false, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr + tmp, vb8, false, false, fsr);
goto instr_done;
}
if(vb8 == 1) m32 = *(UInt8*)&m32; //endian-free way to make it a valid 8-bit value, if need be
tmp = m32;
cpuPrvSetReg(cpu, (instr >> 12) & 0x0F, tmp);
if(v32) cpuPrvSetReg(cpu, va8 & ARM_MODE_2_REG, v32 + adr);
}
else{
if(v32){
v32 += adr;
va8 |= ARM_MODE_2_INV; //re-use flag to mean writeack
}
adr += tmp;
if(vb8 == 1){
m32 = 0;
*(UInt8*)&m32 = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC);
}
else{
m32 = cpuPrvGetReg(cpu, (instr >> 12) & 0x0F, wasT, specialPC);
}
ok = cpu->memF(cpu, &m32, adr, vb8, true, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, vb8, true, false, fsr);
goto instr_done;
}
if(va8 & ARM_MODE_2_INV) cpuPrvSetReg(cpu, va8 & ARM_MODE_2_REG, v32);
}
goto instr_done;
case 8:
case 9: //load/store multiple
if(specialInstr) goto invalid_instr;
va8 = cpuPrvArmAdrMode_4(cpu, instr, &v16);
if((va8 & ARM_MODE_4_S) && usesUsrRegs) goto invalid_instr; //no S mode please in modes with no baked regs //or SPSR
L = (instr & 0x00100000UL) != 0;
tmp = adr = cpuPrvGetReg(cpu, va8 & ARM_MODE_4_REG, wasT, specialPC);
specialInstr = L && (va8 & ARM_MODE_4_S) && (v16 & 0x8000UL) && !usesUsrRegs; //specialInstr = "copyCPSR"
for(vc8 = 0; vc8 < 16; vc8++){
vb8 = (va8 & ARM_MODE_4_INC) ? vc8 : 15 - vc8;
if(v16 & (1UL << vb8)){
UInt32* reg = cpu->regs + vb8;
if(L){
if(va8 & ARM_MODE_4_S){ //handle LDM(2) and LDM(3)
if(v16 & 0x8000UL){ //handle LDM(3)
/* nothing to do here, we did all we need above in line beginning with "specialInstr=" */
}
else if(!usesUsrRegs){ //handle LDM(2)
if(vb8 >= 8 && vb8 <= 12 && (cpu->CPSR & ARM_SR_M) == ARM_SR_MODE_FIQ){ //handle fiq/usr banked regs
reg = cpu->extra_regs + vb8 - 8;
}
else if(vb8 == 13){
reg = &cpu->bank_usr.R13;
}
else if(vb8 == 14){
reg = &cpu->bank_usr.R14;
}
}
}
else if(vb8 == 15){ //handle LDM(1)'s use of PC
/* nothing to do here - all is handled below */
}
}
else if(va8 & ARM_MODE_4_S){ //handle STM(2)'s access to user regs
if(!usesUsrRegs){
if(vb8 >= 8 && vb8 <= 12 && (cpu->CPSR & ARM_SR_M) == ARM_SR_MODE_FIQ){ //handle fiq/usr banked regs
reg = cpu->extra_regs + vb8 - 8;
}
else if(vb8 == 13){
reg = &cpu->bank_usr.R13;
}
else if(vb8 == 14){
reg = &cpu->bank_usr.R14;
}
}
}
if(va8 & ARM_MODE_4_BFR) adr += (va8 & ARM_MODE_4_INC) ? 4L : -4L;
ok = cpu->memF(cpu, reg, adr, 4, !L, privileged, &fsr);
if(!ok){
cpuPrvHandleMemErr(cpu, adr, 4, !L, false, fsr);
if(v16 & (1UL << (va8 & ARM_MODE_4_REG))) cpuPrvSetReg(cpu, va8 & ARM_MODE_4_REG, tmp);
goto instr_done;
}
if(!(va8 & ARM_MODE_4_BFR)) adr += (va8 & ARM_MODE_4_INC) ? 4L : -4L;
}
}
if(va8 & ARM_MODE_4_WBK){
cpuPrvSetReg(cpu, va8 & ARM_MODE_4_REG, adr);
}
if(specialInstr){ //process LDM(3) SPSR->CPSR copy
v32 = cpu->SPSR;
cpuPrvSwitchToMode(cpu, v32 & ARM_SR_M);
cpu->CPSR = v32;
}
else if((v16 & 0x8000U) && !(va8 & ARM_MODE_4_S)){ //we just loaded PC
if(cpu->regs[15] & 1){
cpu->regs[15] &=~ 1UL;
cpu->CPSR |= ARM_SR_T;
}
else{
cpu->CPSR &=~ ARM_SR_T;
}
}
goto instr_done;
case 10:
case 11: //B/BL/BLX(if cond=0b1111)
tmp = instr & 0x00FFFFFFUL; //get offset
if(tmp & 0x00800000UL) tmp |= 0xFF000000UL; //sign extend
tmp = tmp << (wasT ? 1 : 2); //shift left 2(ARM) or 1(thumb)
tmp += instrPC + (wasT ? 4 : 8); //add where PC would point in an ARM
if(specialInstr){ //handle BLX
if(instr & 0x01000000UL) tmp += 2;
cpu->regs[14] = instrPC + (wasT ? 2 : 4);
if(!(cpu->CPSR & ARM_SR_T)) tmp |= 1UL; //set T flag if needed
}
else{ //not BLX -> differentiate between BL and B
if(instr & 0x01000000UL) cpu->regs[14] = instrPC + (wasT ? 2 : 4);
if(cpu->CPSR & ARM_SR_T) tmp |= 1UL; //keep T flag as needed
}
cpuPrvSetPC(cpu, tmp);
goto instr_done;
case 12:
case 13: //coprocessor load/store and double register transfers
va8 = cpuPrvArmAdrMode_5(cpu, instr, &m32);
v32 = m32;
vb8 = (instr >> 8) & 0x0F;
if(vb8 >= 14){ //cp14 and cp15 are for priviledged users only
if(!privileged) goto invalid_instr;
}
else if(!(cpu->CPAR & (1UL << vb8))) goto invalid_instr; //others are access-controlled by CPAR
if(va8 & ARM_MODE_5_RR){ //handle MCRR, MRCC
if(!cpu->coproc[vb8].twoRegF) goto invalid_instr;
if(!cpu->coproc[vb8].twoRegF(cpu, cpu->coproc[vb8].userData, (instr & 0x00100000UL) != 0, (instr >> 4) & 0x0F,(instr >> 12) & 0x0F, (instr >> 16) & 0x0F, instr & 0x0F)) goto invalid_instr;
}
else{
vc8 = v32;
tmp = adr = cpuPrvGetReg(cpu, va8 & ARM_MODE_5_REG, wasT, specialPC);
tmp += v32;
if(!cpu->coproc[vb8].memAccess) goto invalid_instr;
if(!cpu->coproc[vb8].memAccess(cpu, cpu->coproc[vb8].userData, specialInstr, (instr & 0x00400000UL) !=0, !(instr & 0x00100000UL), (instr >> 12) & 0x0F, (va8 & ARM_MODE_5_ADD_BEFORE) ? tmp : adr, (va8 & ARM_MODE_5_IS_OPTION) ? &vc8 : NULL)) goto invalid_instr;
if(va8 & ARM_MODE_5_ADD_AFTER) cpuPrvSetReg(cpu, va8 & ARM_MODE_5_REG, tmp);
}
goto instr_done;
case 14: //coprocessor data processing and register transfers
vb8 = (instr >> 8) & 0x0F;
if(vb8 >= 14){ //cp14 and cp15 are for priviledged users only
if(!privileged) goto invalid_instr;
}
else if(!(cpu->CPAR & (1UL << vb8))) goto invalid_instr; //others are access-controlled by CPAR
if(instr & 0x00000010UL){ //MCR/MRC
if(!cpu->coproc[vb8].regXfer) goto invalid_instr;
if(!cpu->coproc[vb8].regXfer(cpu, cpu->coproc[vb8].userData, specialInstr, (instr & 0x00100000UL) != 0, (instr >> 21) & 0x07, (instr >> 12) & 0x0F, (instr >> 16) & 0x0F, instr & 0x0F, (instr >> 5) & 0x07)) goto invalid_instr;
}
else{ //CDP
if(!cpu->coproc[vb8].dataProcessing) goto invalid_instr;
if(!cpu->coproc[vb8].dataProcessing(cpu, cpu->coproc[vb8].userData, specialInstr, (instr >> 20) & 0x0F, (instr >> 12) & 0x0F, (instr >> 16) & 0x0F, instr & 0x0F, (instr >> 5) & 0x07)) goto invalid_instr;
}
goto instr_done;
case 15: //SWI
if(specialInstr) goto invalid_instr;
cpuPrvException(cpu, cpu->vectorBase + ARM_VECTOR_OFFT_SWI, instrPC + (wasT ? 2 : 4), ARM_CPSR_SWI_ORR | (cpu->CPSR & ARM_CPSR_SWI_AND));
goto instr_done;
}
invalid_instr:
if(instr == HYPERCALL_ARM && privileged){
if(cpu->hypercallF && cpu->hypercallF(cpu)) goto instr_done;
}
err_str("Invalid instr 0x");
err_hex(instr);
err_str(" seen at 0x");
err_hex(instrPC);
err_str(". CPSR=0x");
err_hex(cpu->CPSR);
err_str("\r\n");
cpuPrvException(cpu, cpu->vectorBase + ARM_VECTOR_OFFT_UND, instrPC + (wasT ? 2 : 4), ARM_CPSR_UND_ORR | (cpu->CPSR & ARM_CPSR_UND_AND));
instr_done:;
}
return errNone;
}
static Err cpuPrvCycleArm(ArmCpu* cpu){
Boolean privileged, ok;
UInt32 instr, pc;
UInt8 fsr;
privileged = (cpu->CPSR & ARM_SR_M) != ARM_SR_MODE_USR;
//fetch instruction
{
ok = icacheFetch(&cpu->ic, pc = cpu->regs[15], 4, privileged, &fsr, &instr);
if(!ok){
cpuPrvHandleMemErr(cpu, cpu->regs[15], 4, false, true, fsr);
return errNone; //exit here so that debugger can see us execute first instr of execption handler
}
cpu->regs[15] += 4;
}
return cpuPrvExecInstr(cpu, instr, pc, false, privileged, false);
}
static Err cpuPrvCycleThumb(ArmCpu* cpu){
Boolean privileged, vB, specialPC = false, ok;
UInt32 instr = 0xE0000000UL /*most likely thing*/, pc;
UInt16 instrT, v16;
UInt8 v8, fsr;
privileged = (cpu->CPSR & ARM_SR_M) != ARM_SR_MODE_USR;
pc = cpu->regs[15];
ok = icacheFetch(&cpu->ic, pc, 2, privileged, &fsr, &instrT);
if(!ok){
cpuPrvHandleMemErr(cpu, pc, 2, false, true, fsr);
return errNone; //exit here so that debugger can see us execute first instr of execption handler
}
cpu->regs[15] += 2;
switch(instrT >> 12){
case 0: // LSL(1) LSR(1) ASR(1) ADD(1) SUB(1) ADD(3) SUB(3)
case 1:
if((instrT & 0x1800) != 0x1800){ // LSL(1) LSR(1) ASR(1)
instr |= 0x01B00000UL | ((instrT & 0x7) << 12) | ((instrT >> 3) & 7) | ((instrT >> 6) & 0x60) | ((instrT << 1) & 0xF80);
}
else{
vB = (instrT & 0x0200) != 0; // SUB or ADD ?
instr |= ((vB ? 5UL : 9UL) << 20) | (((UInt32)(instrT & 0x38)) << 13) | ((instrT & 0x07) << 12) | ((instrT >> 6) & 0x07);
if(instrT & 0x0400){ // ADD(1) SUB(1)
instr |= 0x02000000UL;
}
else{ // ADD(3) SUB(3)
// nothing to do here
}
}
break;
case 2: // MOV(1) CMP(1) ADD(2) SUB(2)
case 3:
instr |= instrT & 0x00FF;
switch((instrT >> 11) & 3){
case 0: // MOV(1)
instr |= 0x03B00000UL | ((instrT & 0x0700) << 4);
break;
case 1: // CMP(1)
instr |= 0x03500000UL | (((UInt32)(instrT & 0x0700)) << 8);
break;
case 2: // ADD(2)
instr |= 0x02900000UL | ((instrT & 0x0700) << 4) | (((UInt32)(instrT & 0x0700)) << 8);
break;
case 3: // SUB(2)
instr |= 0x02500000UL | ((instrT & 0x0700) << 4) | (((UInt32)(instrT & 0x0700)) << 8);
break;
}
break;
case 4: // LDR(3) ADD(4) CMP(3) MOV(3) BX MVN CMP(2) CMN TST ADC SBC NEG MUL LSL(2) LSR(2) ASR(2) ROR AND EOR ORR BIC
if(instrT & 0x0800){ // LDR(3)
instr |= 0x059F0000UL | ((instrT & 0xFF) << 2) | ((instrT & 0x700) << 4);
specialPC = true;
}
else if(instrT & 0x0400){ // ADD(4) CMP(3) MOV(3) BX
UInt8 vD;
vD = (instrT & 7) | ((instrT >> 4) & 0x08);
v8 = (instrT >> 3) & 0xF;
switch((instrT >> 8) & 3){
case 0: // ADD(4)
instr |= 0x00800000UL | (((UInt32)vD) << 16) | (((UInt32)vD) << 12) | v8;
break;
case 1: // CMP(3)
instr |= 0x01500000UL | (((UInt32)vD) << 16) | v8;
break;
case 2: // MOV(3)
instr |= 0x01A00000UL | (((UInt32)vD) << 12) | v8;
break;
case 3: // BX
if(instrT == 0x4778){ //special handing for thumb's "BX PC" as aparently docs are wrong on it
cpuPrvSetPC(cpu, (cpu->regs[15] + 2) &~ 3UL);
goto instr_done;
}
instr |= 0x012FFF10UL | ((instrT >> 3) & 0x0F);
break;
}
}
else{ // AND EOR LSL(2) LSR(2) ASR(2) ADC SBC ROR TST NEG CMP(2) CMN ORR MUL BIC MVN (in val_tabl order)
static const UInt32 val_tabl[16] = {0x00100000UL, 0x00300000UL, 0x01B00010UL, 0x01B00030UL, 0x01B00050UL, 0x00B00000UL, 0x00D00000UL, 0x01B00070UL, 0x01100000UL, 0x02700000UL, 0x01500000UL, 0x01700000UL, 0x01900000UL, 0x00100090UL, 0x01D00000UL, 0x01F00000UL};
//00 = none
//10 = bit0 val
//11 = bit3 val
//MVN BIC MUL ORR CMN CMP(2) NEG TST ROR SBC ADC ASR(2) LSR(2) LSL(2) EOR AND
const UInt32 use16 = 0x2AAE280AUL; //0010 1010 1010 1110 0010 1000 0000 1010
const UInt32 use12 = 0xA208AAAAUL; //1010 0010 0000 1000 1010 1010 1010 1010
const UInt32 use8 = 0x0800C3F0UL; //0000 1000 0000 0000 1100 0011 1111 0000
const UInt32 use0 = 0xFFF3BEAFUL; //1111 1111 1111 0011 1011 1110 1010 1111
UInt8 vals[4] = {0};
vals[2] = (instrT & 7);
vals[3] = (instrT >> 3) & 7;
v8 = (instrT >> 6) & 15;
instr |= val_tabl[v8];
v8 <<= 1;
instr |= ((UInt32)(vals[(use16 >> v8) & 3UL])) << 16;
instr |= ((UInt32)(vals[(use12 >> v8) & 3UL])) << 12;
instr |= ((UInt32)(vals[(use8 >> v8) & 3UL])) << 8;
instr |= ((UInt32)(vals[(use0 >> v8) & 3UL])) << 0;
}
break;
case 5: // STR(2) STRH(2) STRB(2) LDRSB LDR(2) LDRH(2) LDRB(2) LDRSH (in val_tbl orver)
{
static const UInt32 val_tabl[8] = {0x07800000UL, 0x018000B0UL, 0x07C00000UL, 0x019000D0UL, 0x07900000UL, 0x019000B0UL, 0x07D00000UL, 0x019000F0UL};
instr |= ((instrT >> 6) & 7) | ((instrT & 7) << 12) | (((UInt32)(instrT & 0x38)) << 13) | val_tabl[(instrT >> 9) & 7];
}
break;
case 6: // LDR(1) STR(1) (bit11 set = ldr)
instr |= ((instrT & 7) << 12) | (((UInt32)(instrT & 0x38)) << 13) | ((instrT >> 4) & 0x7C) | 0x05800000UL;
if(instrT & 0x0800) instr |= 0x00100000UL;
break;
case 7: // LDRB(1) STRB(1) (bit11 set = ldrb)
instr |= ((instrT & 7) << 12) | (((UInt32)(instrT & 0x38)) << 13) | ((instrT >> 6) & 0x1F) | 0x05C00000UL;
if(instrT & 0x0800) instr |= 0x00100000UL;
break;
case 8: // LDRH(1) STRH(1) (bit11 set = ldrh)
instr |= ((instrT & 7) << 12) | (((UInt32)(instrT & 0x38)) << 13) | ((instrT >> 5) & 0x0E) | ((instrT >> 1) & 0x300) | 0x01C000B0UL;
if(instrT & 0x0800) instr |= 0x00100000UL;
break;
case 9: // LDR(4) STR(3) (bit11 set = ldr)
instr |= ((instrT & 0x700) << 4) | ((instrT & 0xFF) << 2) | 0x058D0000UL;
if(instrT & 0x0800) instr |= 0x00100000UL;
break;
case 10: // ADD(5) ADD(6) (bit11 set = add(6))
instr |= ((instrT & 0x700) << 4) | (instrT &0xFF) | 0x028D0F00UL; //encode add to SP, line below sets the bit needed to reference PC instead when needed)
if(!(instrT & 0x0800)) instr |= 0x00020000UL;
else specialPC = true;
break;
case 11: // ADD(7) SUB(4) PUSH POP BKPT
if((instrT & 0x0600) == 0x0400){ //PUSH POP
instr |= (instrT & 0xFF) | 0x000D0000UL;
if(instrT & 0x0800){ //POP
if(instrT & 0x0100) instr |= 0x00008000UL;
instr |= 0x08B00000UL;
}
else{ //PUSH
if(instrT & 0x0100) instr |= 0x00004000UL;
instr |= 0x09200000UL;
}
}
else if(instrT & 0x0100){
goto undefined;
}
else switch((instrT >> 9) & 7){
case 0: // ADD(7) SUB(4)
instr |= 0x020DDF00UL | (instrT & 0x7F) | ((instrT & 0x0080) ? 0x00400000UL : 0x00800000UL);
break;
#ifdef ARM_V6
case 1: //SXTH SXTB UXTH UXTB
instr |= 0x060F0070UL | ((instrT >> 3) & 7) | ((instrT & 7) << 12);
switch((instrT >> 6) & 3){
case 0: //SXTH
instr |= 0x00B00000UL;
break;
case 1: //SXTB
instr |= 0x00A00000UL;
break;
case 2: //UXTH
instr |= 0x00F00000UL;
break;
case 3: //UXTB
instr |= 0x00E00000UL;
break;
}
break;
case 3: //SETEND, CPY
if((instrT & 0x00FE) == 0x0050){ //SETEND
instr |= 0x01010000UL;
if(instrT & 0x0008) instr |= 0x00000200UL;
}
else if((instrT & 0x00E8) == 0x0060){ //CPS
instr |= 0x01080000UL | ((instrT & 7) << 6);
if(instrT & 0x0010) instr |= 0x00040000UL;
}
else goto undefined;
break;
case 5: //REV REV16, REVSH
instr |= 0x060F0F00UL | ((instrT >> 3) & 7) | ((instrT & 7) << 12);
switch((instrT >> 6) & 3){
case 0: //REV
instr |= 0x00B00030UL;
break;
case 1: //REV16
instr |= 0x00B000B0UL;
break;
case 2: // ???
goto undefined;
case 3:
instr |= 0x00F000B0UL;
break;
}
break;
#endif
case 7: //BKPT
instr |= 0x01200070UL | (instrT & 0x0F) | ((instrT & 0xF0) << 4);
break;
default:
goto undefined;
}
break;
case 12: // LDMIA STMIA (bit11 set = ldmia)
instr |= 0x08800000UL | (((UInt32)(instrT & 0x700)) << 8) | (instrT & 0xFF);
if(instrT & 0x0800) instr |= 0x00100000UL;
if(!((1UL << ((instrT >> 8) & 0x07)) & instrT)) instr |= 0x00200000UL; //set W bit if needed
break;
case 13: // B(1), SWI, undefined instr space
v8 = ((instrT >> 8) & 0x0F);
if(v8 == 14){ // undefined instr
goto undefined;
}
else if(v8 == 15){ // SWI
instr |= 0x0F000000UL | (instrT & 0xFF);
}
else{ // B(1)
instr = (((UInt32)v8) << 28) | 0x0A000000UL | (instrT & 0xFF);
if(instrT & 0x80) instr |= 0x00FFFF00UL;
}
break;
case 14: // B(2) BL BLX(1) undefined instr space
case 15:
v16 = (instrT & 0x7FF);
switch((instrT >> 11) & 3){
case 0: //B(2)
instr |= 0x0A000000UL | v16;
if(instrT & 0x0400) instr |= 0x00FFF800UL;
break;
case 1: //BLX(1)_suffix
instr = cpu->regs[15];
cpu->regs[15] = (cpu->regs[14] + 2 + (((UInt32)v16) << 1)) &~ 3UL;
cpu->regs[14] = instr | 1UL;
cpu->CPSR &=~ ARM_SR_T;
goto instr_done;
case 2: //BLX(1)_prefix BL_prefix
instr = v16;
if(instrT & 0x0400) instr |= 0x000FF800UL;
cpu->regs[14] = cpu->regs[15] + (instr << 12);
goto instr_done;
case 3: //BL_suffix
instr = cpu->regs[15];
cpu->regs[15] = cpu->regs[14] + 2 + (((UInt32)v16) << 1);
cpu->regs[14] = instr | 1UL;
goto instr_done;
}
if(instrT & 0x0800) goto undefined; //avoid BLX_suffix and undefined instr space in there
instr |= 0x0A000000UL | (instrT & 0x7FF);
if(instrT & 0x0400) instr |= 0x00FFF800UL;
break;
}
instr_execute:
return cpuPrvExecInstr(cpu, instr, pc, true, privileged, specialPC);
instr_done:
return errNone;
undefined:
if(instrT == HYPERCALL_THUMB){
instr = HYPERCALL_ARM;
goto instr_execute;
}
instr = 0xE7F000F0UL | (instrT & 0x0F) | ((instrT & 0xFFF0) << 4); //guranteed undefined instr, inside it we store the original thumb instr :)=-)
goto instr_execute;
}
Err cpuInit(ArmCpu* cpu, UInt32 pc, ArmCpuMemF memF, ArmCpuEmulErr emulErrF, ArmCpuHypercall hypercallF, ArmSetFaultAdrF setFaultAdrF){
if(!TYPE_CHECK){
emulErrF(cpu, "Type size error! CPU init aborted");
return errInternal;
}
__mem_zero(cpu, sizeof(ArmCpu));
cpu->CPSR = ARM_SR_I | ARM_SR_F | ARM_SR_MODE_SVC; //start w/o interrupts in supervisor mode
cpuPrvSetPC(cpu, pc);
cpu->memF = memF;
cpu->emulErrF = emulErrF;
cpu->hypercallF = hypercallF;
cpu->setFaultAdrF = setFaultAdrF;
icacheInit(&cpu->ic, cpu, memF);
return errNone;
}
Err cpuDeinit(_UNUSED_ ArmCpu* cpu){
return errNone;
}
void cpuCycle(ArmCpu* cpu){
UInt32 vector, newCPSR;
if(cpu->waitingFiqs && !(cpu->CPSR & ARM_SR_F)){
newCPSR = ARM_CPSR_FIQ_ORR | (cpu->CPSR & ARM_CPSR_FIQ_AND);
vector = cpu->vectorBase + ARM_VECTOR_OFFT_FIQ;
}
else if(cpu->waitingIrqs && !(cpu->CPSR & ARM_SR_I)){
newCPSR = ARM_CPSR_IRQ_ORR | (cpu->CPSR & ARM_CPSR_IRQ_AND);
vector = cpu->vectorBase + ARM_VECTOR_OFFT_IRQ;
}
#ifdef ARM_V6
else if(cpu->impreciseAbtWaiting && !(cpu->CPSR & ARM_SR_A)){
newCPSR = ARM_CPSR_DAB_ORR | (cpu->CPSR & ARM_CPSR_DAB_AND);
vector = cpu->vectorBase + ARM_VECTOR_OFFT_D_ABT;
}
#endif
else{
goto normal;
}
cpuPrvException(cpu, vector, cpu->regs[15] + 4, newCPSR);
normal:
if(cpu->CPSR & ARM_SR_T){
cpuPrvCycleThumb(cpu);
}
else{
cpuPrvCycleArm(cpu);
}
}
void cpuIrq(ArmCpu* cpu, Boolean fiq, Boolean raise){ //unraise when acknowledged
if(fiq){
if(raise){
cpu->waitingFiqs++;
}
else if(cpu->waitingFiqs){
cpu->waitingFiqs--;
}
else{
cpu->emulErrF(cpu,"Cannot unraise FIQ when none raised");
}
}
else{
if(raise){
cpu->waitingIrqs++;
}
else if(cpu->waitingIrqs){
cpu->waitingIrqs--;
}
else{
cpu->emulErrF(cpu,"Cannot unraise IRQ when none raised");
}
}
}
void cpuIcacheInval(ArmCpu* cpu){
icacheInval(&cpu->ic);
}
void cpuIcacheInvalAddr(ArmCpu* cpu, UInt32 addr){
icacheInvalAddr(&cpu->ic, addr);
}
void cpuCoprocessorRegister(ArmCpu* cpu, UInt8 cpNum, ArmCoprocessor* coproc){
cpu->coproc[cpNum] = *coproc;
}
void cpuCoprocessorUnregister(ArmCpu* cpu, UInt8 cpNum){
ArmCoprocessor cp;
__mem_zero(&cp, sizeof(ArmCoprocessor));
cpu->coproc[cpNum] = cp;
}
void cpuSetVectorAddr(ArmCpu* cpu, UInt32 adr){
cpu->vectorBase = adr;
}
UInt16 cpuGetCPAR(ArmCpu* cpu){
return cpu->CPAR;
}
void cpuSetCPAR(ArmCpu* cpu, UInt16 cpar){
cpu->CPAR = cpar;
}
#ifdef ARM_V6
void cpuSignalImpreciseAbt(ArmCpu* cpu, Boolean raise){
cpu->impreciseAbtWaiting = raise;
}
#endif