Emulator/ArmCore.Thumb.cs
namespace sGBA;
public partial class ArmCore
{
private void ExecuteThumb()
{
uint opcode = _prefetch0 & 0xFFFF;
_prefetch0 = _prefetch1;
_prefetch1 = Memory.Load16( Gprs[15] );
OpenBusPrefetch = _prefetch1 | (_prefetch1 << 16);
Cycles += 1 + Memory.WaitstatesSeq16[(Gprs[15] >> 24) & 0xF];
uint top = opcode >> 8;
switch ( opcode >> 12 )
{
case 0:
ThumbShiftImm( opcode );
break;
case 1:
if ( (opcode & 0x1800) == 0x1800 )
ThumbAddSub( opcode );
else
ThumbShiftImm( opcode );
break;
case 2:
ThumbImmOp( opcode );
break;
case 3:
ThumbImmOp( opcode );
break;
case 4:
if ( (opcode & 0x0800) == 0 )
{
if ( (opcode & 0x0400) == 0 )
ThumbAluOp( opcode );
else
ThumbHiRegBx( opcode );
}
else
ThumbPcRelLoad( opcode );
break;
case 5:
if ( (opcode & 0xF200) == 0x5000 )
ThumbLoadStoreReg( opcode );
else if ( (opcode & 0xF200) == 0x5200 )
ThumbLoadStoreSignedHalf( opcode );
else
ThumbLoadStoreImmWord( opcode );
break;
case 6:
case 7:
ThumbLoadStoreImmWord( opcode );
break;
case 8:
ThumbLoadStoreHalf( opcode );
break;
case 9:
ThumbSpRelLoadStore( opcode );
break;
case 10:
ThumbLoadAddress( opcode );
break;
case 11:
if ( (opcode & 0x0600) == 0 )
ThumbSpOffset( opcode );
else if ( (opcode & 0x0600) == 0x0400 )
ThumbPushPop( opcode );
else if ( (opcode & 0xFF00) == 0xBE00 )
{ /* BKPT - ignore */ }
break;
case 12:
ThumbBlockTransfer( opcode );
break;
case 13:
if ( (opcode & 0x0F00) == 0x0F00 )
ThumbSwi( opcode );
else
ThumbCondBranch( opcode );
break;
case 14:
ThumbBranch( opcode );
break;
case 15:
ThumbBranchLink( opcode );
break;
}
if ( !_prefetchFlushed )
Gprs[15] += 2;
}
private void ThumbShiftImm( uint opcode )
{
uint rd = opcode & 7;
uint rm = (opcode >> 3) & 7;
uint amount = (opcode >> 6) & 0x1F;
uint op = (opcode >> 11) & 3;
uint val = Gprs[rm];
switch ( op )
{
case 0:
if ( amount == 0 ) { Gprs[rd] = val; }
else { FlagC = ((val >> (int)(32 - amount)) & 1) != 0; Gprs[rd] = val << (int)amount; }
break;
case 1:
if ( amount == 0 ) { FlagC = (val & 0x80000000) != 0; Gprs[rd] = 0; }
else { FlagC = ((val >> (int)(amount - 1)) & 1) != 0; Gprs[rd] = val >> (int)amount; }
break;
case 2:
if ( amount == 0 ) { FlagC = (val & 0x80000000) != 0; Gprs[rd] = FlagC ? 0xFFFFFFFF : 0; }
else { FlagC = ((val >> (int)(amount - 1)) & 1) != 0; Gprs[rd] = (uint)((int)val >> (int)amount); }
break;
}
FlagN = (Gprs[rd] & 0x80000000) != 0;
FlagZ = Gprs[rd] == 0;
}
private void ThumbAddSub( uint opcode )
{
uint rd = opcode & 7;
uint rn = (opcode >> 3) & 7;
bool isImm = ((opcode >> 10) & 1) != 0;
bool isSub = ((opcode >> 9) & 1) != 0;
uint operand = isImm ? (opcode >> 6) & 7 : Gprs[(opcode >> 6) & 7];
uint a = Gprs[rn];
if ( isSub )
{
Gprs[rd] = a - operand;
SetSubFlags( a, operand, Gprs[rd] );
}
else
{
Gprs[rd] = a + operand;
SetAddFlags( a, operand, Gprs[rd] );
}
}
private void ThumbImmOp( uint opcode )
{
uint rd = (opcode >> 8) & 7;
uint imm = opcode & 0xFF;
uint op = (opcode >> 11) & 3;
switch ( op )
{
case 0:
Gprs[rd] = imm;
FlagN = false;
FlagZ = imm == 0;
break;
case 1:
uint cmpResult = Gprs[rd] - imm;
SetSubFlags( Gprs[rd], imm, cmpResult );
break;
case 2:
{
uint old = Gprs[rd];
Gprs[rd] += imm;
SetAddFlags( old, imm, Gprs[rd] );
}
break;
case 3:
{
uint old = Gprs[rd];
Gprs[rd] -= imm;
SetSubFlags( old, imm, Gprs[rd] );
}
break;
}
}
private void ThumbAluOp( uint opcode )
{
uint rd = opcode & 7;
uint rm = (opcode >> 3) & 7;
uint op = (opcode >> 6) & 0xF;
uint a = Gprs[rd], b = Gprs[rm];
uint result;
switch ( op )
{
case 0x0: result = a & b; SetLogicFlags( result, FlagC ); Gprs[rd] = result; break;
case 0x1: result = a ^ b; SetLogicFlags( result, FlagC ); Gprs[rd] = result; break;
case 0x2:
{
uint shift = b & 0xFF;
if ( shift == 0 ) { result = a; }
else if ( shift < 32 ) { FlagC = ((a >> (int)(32 - shift)) & 1) != 0; result = a << (int)shift; }
else if ( shift == 32 ) { FlagC = (a & 1) != 0; result = 0; }
else { FlagC = false; result = 0; }
FlagN = (result & 0x80000000) != 0; FlagZ = result == 0;
Gprs[rd] = result;
int lslCr = (int)((Gprs[15] >> 24) & 0xF); Cycles += Memory.WaitstatesNonseq16[lslCr] - Memory.WaitstatesSeq16[lslCr];
}
break;
case 0x3:
{
uint shift = b & 0xFF;
if ( shift == 0 ) { result = a; }
else if ( shift < 32 ) { FlagC = ((a >> (int)(shift - 1)) & 1) != 0; result = a >> (int)shift; }
else if ( shift == 32 ) { FlagC = (a & 0x80000000) != 0; result = 0; }
else { FlagC = false; result = 0; }
FlagN = (result & 0x80000000) != 0; FlagZ = result == 0;
Gprs[rd] = result;
int lsrCr = (int)((Gprs[15] >> 24) & 0xF); Cycles += Memory.WaitstatesNonseq16[lsrCr] - Memory.WaitstatesSeq16[lsrCr];
}
break;
case 0x4:
{
uint shift = b & 0xFF;
if ( shift == 0 ) { result = a; }
else if ( shift < 32 ) { FlagC = ((a >> (int)(shift - 1)) & 1) != 0; result = (uint)((int)a >> (int)shift); }
else { FlagC = (a & 0x80000000) != 0; result = FlagC ? 0xFFFFFFFF : 0u; }
FlagN = (result & 0x80000000) != 0; FlagZ = result == 0;
Gprs[rd] = result;
int asrCr = (int)((Gprs[15] >> 24) & 0xF); Cycles += Memory.WaitstatesNonseq16[asrCr] - Memory.WaitstatesSeq16[asrCr];
}
break;
case 0x5: result = a + b + (FlagC ? 1u : 0); SetAdcFlags( a, b, FlagC ); Gprs[rd] = result; break;
case 0x6: result = a - b - (FlagC ? 0u : 1u); SetSbcFlags( a, b, FlagC ); Gprs[rd] = result; break;
case 0x7:
{
uint shift = b & 0xFF;
if ( shift == 0 ) { result = a; }
else { shift &= 31; if ( shift == 0 ) { FlagC = (a & 0x80000000) != 0; result = a; } else { result = Ror( a, (int)shift ); FlagC = (result & 0x80000000) != 0; } }
FlagN = (result & 0x80000000) != 0; FlagZ = result == 0;
Gprs[rd] = result;
int rorCr = (int)((Gprs[15] >> 24) & 0xF); Cycles += Memory.WaitstatesNonseq16[rorCr] - Memory.WaitstatesSeq16[rorCr];
}
break;
case 0x8: result = a & b; SetLogicFlags( result, FlagC ); break;
case 0x9: result = 0 - b; SetSubFlags( 0, b, result ); Gprs[rd] = result; break;
case 0xA: result = a - b; SetSubFlags( a, b, result ); break;
case 0xB: result = a + b; SetAddFlags( a, b, result ); break;
case 0xC: result = a | b; SetLogicFlags( result, FlagC ); Gprs[rd] = result; break;
case 0xD:
result = a * b;
FlagN = (result & 0x80000000) != 0;
FlagZ = result == 0;
Gprs[rd] = result;
Cycles += Memory.MemoryStall( Gprs[15], MultiplyExtraCycles( a ) );
{ int thumbMulCr = (int)((Gprs[15] >> 24) & 0xF); Cycles += Memory.WaitstatesNonseq16[thumbMulCr] - Memory.WaitstatesSeq16[thumbMulCr]; }
break;
case 0xE: result = a & ~b; SetLogicFlags( result, FlagC ); Gprs[rd] = result; break;
case 0xF: result = ~b; SetLogicFlags( result, FlagC ); Gprs[rd] = result; break;
}
}
private void ThumbHiRegBx( uint opcode )
{
uint op = (opcode >> 8) & 3;
uint rd = (opcode & 7) | ((opcode >> 4) & 8);
uint rm = (opcode >> 3) & 0xF;
switch ( op )
{
case 0:
Gprs[rd] += Gprs[rm];
if ( rd == 15 ) _prefetchFlushed = true;
break;
case 1:
{
uint result = Gprs[rd] - Gprs[rm];
SetSubFlags( Gprs[rd], Gprs[rm], result );
}
break;
case 2:
Gprs[rd] = Gprs[rm];
if ( rd == 15 ) _prefetchFlushed = true;
break;
case 3:
ThumbMode = (Gprs[rm] & 1) != 0;
Gprs[15] = Gprs[rm] & ~1u;
_prefetchFlushed = true;
break;
}
}
private void ThumbPcRelLoad( uint opcode )
{
uint rd = (opcode >> 8) & 7;
uint offset = (opcode & 0xFF) << 2;
uint addr = (Gprs[15] & ~3u) + offset;
Gprs[rd] = Memory.Load32( addr );
int dr = (int)((addr >> 24) & 0xF);
int wait = Memory.WaitstatesNonseq32[dr] + 2;
if ( dr < 8 )
wait = Memory.MemoryStall( Gprs[15], wait );
Cycles += wait;
int cr = (int)((Gprs[15] >> 24) & 0xF);
Cycles += Memory.WaitstatesNonseq16[cr] - Memory.WaitstatesSeq16[cr];
}
private void ThumbLoadStoreReg( uint opcode )
{
uint rd = opcode & 7;
uint rn = (opcode >> 3) & 7;
uint rm = (opcode >> 6) & 7;
uint addr = Gprs[rn] + Gprs[rm];
bool isLoad = ((opcode >> 11) & 1) != 0;
bool isByte = ((opcode >> 10) & 1) != 0;
if ( isLoad )
{
if ( isByte )
Gprs[rd] = Memory.Load8( addr );
else
Gprs[rd] = ReadWordRotated( addr );
}
else
{
if ( isByte )
Memory.Store8( addr, (byte)Gprs[rd] );
else
Memory.Store32( addr, Gprs[rd] );
}
int dr = (int)((addr >> 24) & 0xF);
{
int wait = isByte ? Memory.WaitstatesNonseq16[dr] : Memory.WaitstatesNonseq32[dr];
wait += isLoad ? 2 : 1;
if ( dr < 8 )
wait = Memory.MemoryStall( Gprs[15], wait );
Cycles += wait;
}
int cr = (int)((Gprs[15] >> 24) & 0xF);
Cycles += Memory.WaitstatesNonseq16[cr] - Memory.WaitstatesSeq16[cr];
}
private void ThumbLoadStoreSignedHalf( uint opcode )
{
uint rd = opcode & 7;
uint rn = (opcode >> 3) & 7;
uint rm = (opcode >> 6) & 7;
uint addr = Gprs[rn] + Gprs[rm];
uint op = (opcode >> 10) & 3;
switch ( op )
{
case 0: Memory.Store16( addr, (ushort)Gprs[rd] ); break;
case 1: Gprs[rd] = (uint)(sbyte)Memory.Load8( addr ); break;
case 2: Gprs[rd] = Memory.Load16( addr ); break;
case 3:
if ( (addr & 1) != 0 )
Gprs[rd] = (uint)(sbyte)Memory.Load8( addr );
else
Gprs[rd] = (uint)(short)Memory.Load16( addr );
break;
}
int dr = (int)((addr >> 24) & 0xF); bool isStore = op == 0;
int wait = Memory.WaitstatesNonseq16[dr] + (isStore ? 1 : 2);
if ( dr < 8 )
wait = Memory.MemoryStall( Gprs[15], wait );
Cycles += wait;
int cr = (int)((Gprs[15] >> 24) & 0xF);
Cycles += Memory.WaitstatesNonseq16[cr] - Memory.WaitstatesSeq16[cr];
}
private void ThumbLoadStoreImmWord( uint opcode )
{
uint top3 = (opcode >> 13) & 7;
uint rd = opcode & 7;
uint rn = (opcode >> 3) & 7;
uint offset5 = (opcode >> 6) & 0x1F;
bool isLoad = ((opcode >> 11) & 1) != 0;
uint addr = 0;
bool isByte = false;
if ( top3 == 3 )
{
isByte = ((opcode >> 12) & 1) != 0;
if ( isByte )
{
addr = Gprs[rn] + offset5;
if ( isLoad ) Gprs[rd] = Memory.Load8( addr );
else Memory.Store8( addr, (byte)Gprs[rd] );
}
else
{
addr = Gprs[rn] + offset5 * 4;
if ( isLoad ) Gprs[rd] = ReadWordRotated( addr );
else Memory.Store32( addr, Gprs[rd] );
}
}
else if ( top3 == 2 )
{
uint rm = (opcode >> 6) & 7;
addr = Gprs[rn] + Gprs[rm];
isByte = ((opcode >> 10) & 1) != 0;
if ( isByte )
{
if ( isLoad ) Gprs[rd] = Memory.Load8( addr );
else Memory.Store8( addr, (byte)Gprs[rd] );
}
else
{
if ( isLoad ) Gprs[rd] = ReadWordRotated( addr );
else Memory.Store32( addr, Gprs[rd] );
}
}
int dr = (int)((addr >> 24) & 0xF);
int wait = isByte ? Memory.WaitstatesNonseq16[dr] : Memory.WaitstatesNonseq32[dr];
wait += isLoad ? 2 : 1;
if ( dr < 8 )
wait = Memory.MemoryStall( Gprs[15], wait );
Cycles += wait;
int cr = (int)((Gprs[15] >> 24) & 0xF);
Cycles += Memory.WaitstatesNonseq16[cr] - Memory.WaitstatesSeq16[cr];
}
private void ThumbLoadStoreHalf( uint opcode )
{
uint rd = opcode & 7;
uint rn = (opcode >> 3) & 7;
uint offset = ((opcode >> 6) & 0x1F) << 1;
uint addr = Gprs[rn] + offset;
bool isLoad = ((opcode >> 11) & 1) != 0;
if ( isLoad )
Gprs[rd] = Memory.Load16( addr );
else
Memory.Store16( addr, (ushort)Gprs[rd] );
int dr = (int)((addr >> 24) & 0xF);
int wait = Memory.WaitstatesNonseq16[dr] + (isLoad ? 2 : 1);
if ( dr < 8 )
wait = Memory.MemoryStall( Gprs[15], wait );
Cycles += wait;
int cr = (int)((Gprs[15] >> 24) & 0xF);
Cycles += Memory.WaitstatesNonseq16[cr] - Memory.WaitstatesSeq16[cr];
}
private void ThumbSpRelLoadStore( uint opcode )
{
uint rd = (opcode >> 8) & 7;
uint offset = (opcode & 0xFF) << 2;
uint addr = Gprs[13] + offset;
bool isLoad = ((opcode >> 11) & 1) != 0;
if ( isLoad )
Gprs[rd] = ReadWordRotated( addr );
else
Memory.Store32( addr, Gprs[rd] );
int dr = (int)((addr >> 24) & 0xF);
int wait = Memory.WaitstatesNonseq32[dr] + (isLoad ? 2 : 1);
if ( dr < 8 )
wait = Memory.MemoryStall( Gprs[15], wait );
Cycles += wait;
int cr = (int)((Gprs[15] >> 24) & 0xF);
Cycles += Memory.WaitstatesNonseq16[cr] - Memory.WaitstatesSeq16[cr];
}
private void ThumbLoadAddress( uint opcode )
{
uint rd = (opcode >> 8) & 7;
uint offset = (opcode & 0xFF) << 2;
bool useSP = ((opcode >> 11) & 1) != 0;
if ( useSP )
Gprs[rd] = Gprs[13] + offset;
else
Gprs[rd] = (Gprs[15] & ~3u) + offset;
}
private void ThumbSpOffset( uint opcode )
{
uint offset = (opcode & 0x7F) << 2;
if ( (opcode & 0x80) != 0 )
Gprs[13] -= offset;
else
Gprs[13] += offset;
}
private void ThumbPushPop( uint opcode )
{
bool isLoad = ((opcode >> 11) & 1) != 0;
bool extraReg = ((opcode >> 8) & 1) != 0;
byte regList = (byte)(opcode & 0xFF);
int count = BitCount( regList ) + (extraReg ? 1 : 0);
int instructionRegion = (int)((Gprs[15] >> 24) & 0xF);
if ( isLoad )
{
uint addr = Gprs[13];
for ( int i = 0; i < 8; i++ )
{
if ( (regList & (1 << i)) != 0 )
{
Gprs[i] = Memory.Load32( addr );
addr += 4;
}
}
if ( extraReg )
{
Gprs[15] = Memory.Load32( addr );
addr += 4;
_prefetchFlushed = true;
}
Gprs[13] = addr;
}
else
{
uint addr = Gprs[13] - (uint)(count * 4);
Gprs[13] = addr;
for ( int i = 0; i < 8; i++ )
{
if ( (regList & (1 << i)) != 0 )
{
Memory.Store32( addr, Gprs[i] );
addr += 4;
}
}
if ( extraReg )
{
Memory.Store32( addr, Gprs[14] );
}
}
int dr = (int)((Gprs[13] >> 24) & 0xF);
if ( count > 0 )
{
int firstWait = Memory.WaitstatesNonseq32[dr];
int seqWait = Memory.WaitstatesSeq32[dr];
int blockWait = seqWait - firstWait + count * (seqWait + 1) + (isLoad ? 1 : 0);
if ( dr < 8 )
blockWait = Memory.MemoryStall( Gprs[15], blockWait );
Cycles += blockWait;
}
Cycles += Memory.WaitstatesNonseq16[instructionRegion] - Memory.WaitstatesSeq16[instructionRegion];
}
private void ThumbBlockTransfer( uint opcode )
{
uint rn = (opcode >> 8) & 7;
bool isLoad = ((opcode >> 11) & 1) != 0;
byte regList = (byte)(opcode & 0xFF);
uint addr = Gprs[rn];
int instructionRegion = (int)((Gprs[15] >> 24) & 0xF);
if ( regList == 0 )
{
if ( isLoad )
{
Gprs[15] = Memory.Load32( addr );
_prefetchFlushed = true;
}
else
{
Memory.Store32( addr, Gprs[15] + 2 );
}
Gprs[rn] += 0x40;
int dr0 = (int)((addr >> 24) & 0xF);
int seqWait = Memory.WaitstatesSeq32[dr0];
int emptyWait = 2 * seqWait - Memory.WaitstatesNonseq32[dr0] + 16 + (isLoad ? 1 : 0);
if ( dr0 < 8 )
emptyWait = Memory.MemoryStall( Gprs[15], emptyWait );
Cycles += emptyWait;
Cycles += Memory.WaitstatesNonseq16[instructionRegion] - Memory.WaitstatesSeq16[instructionRegion];
return;
}
int count = BitCount( regList );
uint startAddr = addr;
for ( int i = 0; i < 8; i++ )
{
if ( (regList & (1 << i)) == 0 ) continue;
if ( isLoad )
Gprs[i] = Memory.Load32( addr );
else
Memory.Store32( addr, Gprs[i] );
addr += 4;
}
if ( !isLoad || (regList & (1 << (int)rn)) == 0 )
Gprs[rn] = addr;
{
int blockRegion = (int)((startAddr >> 24) & 0xF);
int firstWait = Memory.WaitstatesNonseq32[blockRegion];
int seqWait = Memory.WaitstatesSeq32[blockRegion];
int blockWait = seqWait - firstWait + count * (seqWait + 1) + (isLoad ? 1 : 0);
uint endAddr = startAddr + (uint)(count * 4);
int endRegion = (int)((endAddr >> 24) & 0xF);
if ( endRegion < 8 )
blockWait = Memory.MemoryStall( Gprs[15], blockWait );
Cycles += blockWait;
}
Cycles += Memory.WaitstatesNonseq16[instructionRegion] - Memory.WaitstatesSeq16[instructionRegion];
}
private void ThumbCondBranch( uint opcode )
{
uint cond = (opcode >> 8) & 0xF;
if ( !CheckCondition( cond ) ) return;
int offset = (sbyte)(opcode & 0xFF);
offset <<= 1;
Gprs[15] = (uint)(Gprs[15] + offset);
_prefetchFlushed = true;
}
private void ThumbBranch( uint opcode )
{
int offset = (int)(opcode & 0x7FF);
if ( (offset & 0x400) != 0 ) offset |= unchecked((int)0xFFFFF800);
offset <<= 1;
Gprs[15] = (uint)(Gprs[15] + offset);
_prefetchFlushed = true;
}
private void ThumbBranchLink( uint opcode )
{
bool isSecond = ((opcode >> 11) & 1) != 0;
if ( !isSecond )
{
int offset = (int)(opcode & 0x7FF);
if ( (offset & 0x400) != 0 ) offset |= unchecked((int)0xFFFFF800);
Gprs[14] = (uint)(Gprs[15] + (offset << 12));
}
else
{
uint temp = Gprs[14] + ((opcode & 0x7FF) << 1);
Gprs[14] = (Gprs[15] - 2) | 1;
Gprs[15] = temp;
_prefetchFlushed = true;
}
}
private void ThumbSwi( uint opcode )
{
uint comment = opcode & 0xFF;
if ( Gba.Bios.HandleSwi( comment ) )
return;
uint savedCpsr = GetCpsrRaw();
SetPrivilegeMode( PrivilegeMode.Supervisor );
SetSpsr( savedCpsr );
Gprs[14] = Gprs[15] - 2;
IrqDisable = true;
ThumbMode = false;
Gprs[15] = GbaConstants.BaseSwi;
_prefetchFlushed = true;
}
}