Created
June 17, 2014 00:49
-
-
Save Subv/aa164b886c7e32cf977d to your computer and use it in GitHub Desktop.
ARM Interpreter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <stdio.h> | |
#include <nds.h> | |
#include "ARM.h" | |
#include "Thumb.h" | |
#include "GBA.h" | |
#include "Opcodes.h" | |
#include "Utils.h" | |
CPSR _CPSR; | |
Registers _Registers; | |
FILE* ROMFile; | |
uint8_t GBA_BIOS[16384]; // BIOS 00000000-00003FFF | |
// 00004000-01FFFFFF not used | |
uint8_t GBA_WRAM1[262144]; // On-Board Work RAM 02000000-0203FFFF | |
// 02040000-02FFFFFF not used | |
uint8_t GBA_WRAM2[32768]; // On-Chip Work RAM 03000000-03007FFF | |
// 03008000 - 03FFFFFF not used | |
uint8_t GBA_IO[0x3FF]; // IO Registers 04000000-040003FE | |
// 04000400-04FFFFFF not used | |
uint8_t GBA_PRAM[1024]; // Palette RAM 05000000-050003FF | |
// 05000400-05FFFFFF not used | |
uint8_t GBA_VRAM[98304]; // Video RAM 06000000-06017FFF | |
// 06018000-06FFFFFF not used | |
uint8_t GBA_OAM[1024]; // OAM 07000000-070003FF | |
// 07000400-07FFFFFF not used | |
void InitializeCPU() | |
{ | |
_CPSR.Full = 0; | |
for (uint8_t i = 0; i < 16; ++i) | |
_Registers.R[i] = 0; | |
} | |
void RunARMInstruction(Instruction instruction); | |
void RunThumbInstruction(ThumbInstruction instruction); | |
void LoadROM(FILE* file, GBAHeader& header) | |
{ | |
std::cout << "Memory: " << mallinfo().arena << " " << mallinfo().uordblks << " " << mallinfo().fordblks << std::endl; | |
std::cout << "Loading rom " << header.title << std::endl; | |
std::cout << "Executing " << std::hex << header.entryPoint << std::endl; | |
ROMFile = file; | |
RunARMInstruction(Instruction(header.entryPoint)); | |
// The PC is now updated and we can continue execution | |
while (true) | |
{ | |
if (_CPSR.bits.T == 0) | |
{ | |
// We're in 32 bits mode | |
// Read 4 bytes | |
uint32_t data = ReadBytesFromROM<uint32_t>(PC); | |
Instruction instr(data); | |
RunARMInstruction(instr); | |
} | |
else | |
{ | |
// We're in Thumb mode | |
// Read 2 bytes | |
uint16_t data = ReadBytesFromROM<uint16_t>(PC); | |
ThumbInstruction instr(data); | |
RunThumbInstruction(instr); | |
} | |
std::cout << "Going to next instruction, PC = " << PC << std::endl; | |
Utils::WaitForKey(KEY_A); | |
consoleClear(); | |
swiWaitForVBlank(); | |
} | |
} | |
bool CheckCondition(uint8_t condition) | |
{ | |
switch (condition) | |
{ | |
case 0x0: // Equals, Z = 1 | |
return _CPSR.bits.Z == 1; | |
case 0x1: // Not Equals, Z = 0 | |
return _CPSR.bits.Z == 0; | |
case 0x2: // Carry Set, C = 1 | |
return _CPSR.bits.C == 1; | |
case 0x3: // Carry Not Set, C = 0 | |
return _CPSR.bits.C == 0; | |
case 0x4: // Negative, N = 1 | |
return _CPSR.bits.N == 1; | |
case 0x5: // Positive or Zero, N = 0 | |
return _CPSR.bits.N == 0; | |
case 0x6: // Overflow, V = 1 | |
return _CPSR.bits.V == 1; | |
case 0x7: // No Overflow, V = 0 | |
return _CPSR.bits.V == 0; | |
case 0x8: // Unsigned Higher, C = 1 and Z = 0 | |
return _CPSR.bits.C == 1 && _CPSR.bits.Z == 0; | |
case 0x9: // Unsigned Lower or Same, C = 0 or Z = 1 | |
return _CPSR.bits.C == 0 || _CPSR.bits.Z == 1; | |
case 0xA: // Greater or Equal, N = V | |
return _CPSR.bits.N == _CPSR.bits.V; | |
case 0xB: // Less Than, N != V | |
return _CPSR.bits.N != _CPSR.bits.V; | |
case 0xC: // Greater Than, Z = 0 and N = V | |
return _CPSR.bits.Z == 0 && _CPSR.bits.N == _CPSR.bits.V; | |
case 0xD: // Less Or Equal, Z = 1 or N != V | |
return _CPSR.bits.Z == 1 || _CPSR.bits.N == _CPSR.bits.V; | |
case 0xE: // Always | |
return true; | |
default: | |
{ | |
Utils::Assert(false, "Not supported condition"); | |
return false; | |
} | |
} | |
} | |
inline void HandleImmediateDataProcessing(Instruction instruction) | |
{ | |
DataProcessingInmediateInstruction instr(instruction.Full); | |
std::cout << "Handling a dataProc instruction: Full: " << std::hex << instr.Full << " Opcode: " << (unsigned)instr.Members.Opcode << " Immediate " << std::endl; | |
// Immediate operation, this means that we have a number and not a register as the second operand | |
std::cout << std::hex << " Is: " << (unsigned)instr.Members.Is << " and nn: " << (unsigned)instr.Members.nn << std::endl; | |
std::cout << std::hex << " Affected Register: " << (unsigned)instr.Members.Rd << std::endl; | |
// Handle the opcode | |
switch (instr.Members.Opcode) | |
{ | |
case ARM_MOV: | |
_Registers.R[instr.Members.Rd] = Utils::RotateRight(instr.Members.nn, instr.Members.Is * 2); | |
break; | |
case ARM_ADD: | |
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rn] + Utils::RotateRight(instr.Members.nn, instr.Members.Is * 2); | |
if (instr.Members.Rn == 15) // If we're using PC, add another 8 bytes | |
_Registers.R[instr.Members.Rd] += 8; | |
break; | |
} | |
std::cout << "Register 0:" << (unsigned)_Registers.R[0] << std::endl; | |
} | |
inline void HandleRegisterDataProcessing(Instruction instruction) | |
{ | |
} | |
inline void HandleImmediatePSRTransfer(Instruction instruction) | |
{ | |
PSRTransferInstruction instr(instruction.Full); | |
std::cout << "PSR Transfer Type: " << std::hex << (unsigned)instr.Members.Opcode << " PSR: " << (unsigned)instr.Members.Psr << " Rd: " << (unsigned)instr.Members.Rd; | |
} | |
inline void HandleRegisterPSRTransfer(Instruction instruction) | |
{ | |
PSRTransferInstruction instr(instruction.Full); | |
std::cout << "PSR Transfer Type: " << std::hex << (unsigned)instr.Members.Opcode << " PSR: " << (unsigned)instr.Members.Psr << " Rd: " << (unsigned)instr.Members.Rd; | |
switch (instr.Members.Opcode) | |
{ | |
case ARM_MSR: // Write to PSR | |
{ | |
std::cout << "Write C: " << (unsigned)instr.Members.C << " Write X " << (unsigned)instr.Members.X << " Write S " << (unsigned)instr.Members.S << " Write F " << (unsigned)instr.Members.F << std::endl; | |
std::cout << "Source register: " << (unsigned)(instr.Members.LastBits.Rm >> 4) << std::endl; | |
uint32_t byteMask = 0; | |
if (instr.Members.C) | |
byteMask |= 0x000000FF; | |
if (instr.Members.X) | |
byteMask |= 0x0000FF00; | |
if (instr.Members.S) | |
byteMask |= 0x00FF0000; | |
if (instr.Members.F) | |
byteMask |= 0x00FF0000; | |
if (instr.Members.Psr == 0) // We only support CPSR for now | |
_CPSR.Full = (_CPSR.Full & ~byteMask) | (_Registers.R[instr.Members.LastBits.Rm >> 4] & byteMask); | |
break; | |
} | |
case ARM_MRS: // Load PSR | |
break; | |
default: | |
Utils::Assert(false, "Not supported PSR operation"); | |
break; | |
} | |
} | |
void HandleImmediateSingleDataTransfer(Instruction instruction) | |
{ | |
SingleDataTransferInstruction instr(instruction.Full); | |
std::cout << "Immediate SDT Load/Store: " << (unsigned)instr.Members.L << std::endl; | |
switch (instr.Members.L) | |
{ | |
case ARM_STR: // Store | |
{ | |
uint32_t address = 0; | |
uint32_t offset = instr.Members.GetImmediateOffset(); | |
if (instr.Members.U == 0) | |
offset = -offset; | |
if (instr.Members.P) | |
address = _Registers.R[instr.Members.Rn] + offset; | |
else | |
{ | |
address = _Registers.R[instr.Members.Rn]; | |
_Registers.R[instr.Members.Rn] += offset; | |
} | |
if (address >= IO_REGISTERS_START && address <= IO_REGISTERS_END) | |
GBA_IO[address - IO_REGISTERS_START] = _Registers.R[instr.Members.Rd]; | |
std::cout << std::hex << "Register: " << (unsigned)instr.Members.Rn << " Register Val: " << _Registers.R[instr.Members.Rn] << " Writing to address " << address << std::endl; | |
break; | |
} | |
case ARM_LDR: // Load | |
uint32_t address = 0; | |
uint32_t offset = instr.Members.GetImmediateOffset(); | |
if (instr.Members.U == 0) | |
offset = -offset; | |
if (instr.Members.P) | |
address = _Registers.R[instr.Members.Rn] + offset; | |
else | |
{ | |
address = _Registers.R[instr.Members.Rn]; | |
_Registers.R[instr.Members.Rn] += offset; | |
} | |
if (instr.Members.Rn == 15) // If we're using PC as the source register | |
{ | |
if (_CPSR.bits.T) | |
address += 4; // Add 4 bytes in Thumb mode | |
else | |
address += 8; // Add 8 bytes in ARM mode | |
// Since we're using PC-relative addressing, we have to grab the data from the ROM | |
_Registers.R[instr.Members.Rd] = ReadBytesFromROM<uint32_t>(address); | |
} | |
if (address >= GBA_WRAM2_START && address <= GBA_WRAM2_END) | |
_Registers.R[instr.Members.Rd] = GBA_WRAM2[address - GBA_WRAM2_START]; | |
std::cout << "Rn: " << (unsigned)instr.Members.Rn << std::endl; | |
std::cout << "Value of Rn: " << (unsigned)_Registers.R[instr.Members.Rn] << std::endl; | |
std::cout << "Offset: " << (unsigned)offset << std::endl; | |
std::cout << "Output to register: " << std::hex << _Registers.R[instr.Members.Rd] << std::endl; | |
break; | |
} | |
} | |
void HandleRegisterSingleDataTransfer(Instruction instruction) | |
{ | |
SingleDataTransferInstruction instr(instruction.Full); | |
std::cout << "Register SDT Load/Store: " << (unsigned)instr.Members.L << std::endl; | |
switch (instr.Members.L) | |
{ | |
case ARM_STR: // Store | |
break; | |
case ARM_LDR: // Load | |
break; | |
} | |
} | |
void HandleBranchLinkInstruction(Instruction instruction) | |
{ | |
BranchLinkInstruction instr(instruction.Full); | |
PC += 8 + instr.Members.nn * 4; | |
if (instr.Members.Opcode == 0x1) | |
LR = PC + 4; // Update LR register | |
std::cout << "We have a branch! Jumping to " << std::hex << PC << std::endl; | |
} | |
void HandleBranchExchangeInstruction(Instruction instruction) | |
{ | |
std::cout << "WE HAVE A BRANCH EXCHANGE" << std::endl; | |
BranchExchangeInstruction instr(instruction.Full); | |
switch (instr.Members.Opcode) | |
{ | |
case ARM_BX: | |
_CPSR.bits.T = (_Registers.R[instr.Members.Rn] & 0x1) != 0; | |
PC = _Registers.R[instr.Members.Rn]; | |
if (_CPSR.bits.T) | |
{ | |
std::cout << "Switching to Thumb." << std::endl; | |
PC -= 1; | |
} | |
break; | |
case ARM_BLX: | |
_CPSR.bits.T = (_Registers.R[instr.Members.Rn] & 0x1) != 0; | |
LR = PC + 4; | |
PC = _Registers.R[instr.Members.Rn]; | |
if (_CPSR.bits.T) | |
PC -= 1; | |
break; | |
} | |
} | |
void RunARMInstruction(Instruction instruction) | |
{ | |
uint8_t condition = instruction.Members.Condition; // The condition is the first 4 bits of the instruction | |
std::cout << "Condition: " << std::hex << (unsigned)instruction.Members.Condition << std::endl; | |
if (!CheckCondition(condition)) | |
{ | |
std::cout << "Didn't pass the condition" << std::endl; | |
return; | |
} | |
// Now identify the instruction we're dealing with, different types of instructions have different structures. | |
if (instruction.CheckPass(3, 25, 0x5)) // This is a branch instruction, 1 0 1 | |
HandleBranchLinkInstruction(instruction); | |
else if (instruction.CheckPass(20, 8, 0x12FFF)) // This is a branch exchange instruction | |
HandleBranchExchangeInstruction(instruction); | |
else if (instruction.CheckPass(2, 26, 0) && instruction.CheckPass(2, 23, 0x2) && instruction.CheckPass(1, 20, 0)) | |
{ | |
std::cout << "Found PSR Instruction" << std::endl; | |
// It is a PSR instruction | |
if (instruction.Members.I == 1) | |
HandleImmediatePSRTransfer(instruction); | |
else | |
HandleRegisterPSRTransfer(instruction); | |
PC += 4; | |
} | |
else if (instruction.CheckPass(2, 26, 0)) // Data Processing Instruction | |
{ | |
if (instruction.Members.I == 1) | |
HandleImmediateDataProcessing(instruction); | |
else | |
HandleRegisterDataProcessing(instruction); | |
PC += 4; | |
} | |
else if (instruction.CheckPass(2, 26, 0x1)) | |
{ | |
// We have a single data transfer instruction (LDR, STR, PLD) | |
if (instruction.Members.I == 0) | |
HandleImmediateSingleDataTransfer(instruction); | |
else | |
HandleRegisterSingleDataTransfer(instruction); | |
PC += 4; | |
} | |
std::cout << "Instruction ran" << std::endl; | |
} | |
void HandleThumbMoveShiftedRegister(ThumbInstruction instruction) | |
{ | |
ThumbMoveShiftedRegisterInstruction instr(instruction.Full); | |
switch (instr.Members.Opcode) | |
{ | |
case THUMB_MSR_LSL: | |
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rs] << instr.Members.Offset; | |
break; | |
case THUMB_MSR_LSR: | |
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rs] >> instr.Members.Offset; | |
break; | |
case THUMB_MSR_ASR: | |
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rs] >> instr.Members.Offset; // TODO: Extend the last bit | |
break; | |
default: | |
Utils::Assert(false, "Thumb: Not implemented Move Shift Register opcode"); | |
break; | |
} | |
} | |
void HandleThumbLoadPCRelative(ThumbInstruction instruction) | |
{ | |
ThumbLoadPCRelativeInstruction instr(instruction.Full); | |
_Registers.R[instr.Members.Rd] = ReadBytesFromROM<uint32_t>((PC & 0xFFFFFFFC) + instr.Members.nn * 4 + 4); | |
std::cout << "THUMB: Register " << std::hex << (unsigned)instr.Members.Rd << " now contains: " << (unsigned)_Registers.R[instr.Members.Rd] << std::endl; | |
} | |
void HandleThumbALUInstruction(ThumbInstruction instruction) | |
{ | |
ThumbALUInstruction instr(instruction.Full); | |
std::cout << "Thumb ALU Operation, Opcode: " << std::hex << (unsigned)instr.Members.Opcode << std::endl; | |
switch (instr.Members.Opcode) | |
{ | |
case THUMB_AND: | |
_Registers.R[instr.Members.Rd] &= _Registers.R[instr.Members.Rs]; | |
break; | |
case THUMB_EOR: | |
_Registers.R[instr.Members.Rd] ^= _Registers.R[instr.Members.Rs]; | |
break; | |
case THUMB_LSL: | |
_Registers.R[instr.Members.Rd] <<= _Registers.R[instr.Members.Rs] & 0xFF; | |
break; | |
case THUMB_LSR: | |
_Registers.R[instr.Members.Rd] >>= _Registers.R[instr.Members.Rs] & 0xFF; | |
break; | |
default: | |
Utils::Assert(false, "Thumb: Not implemented ALU opcode"); | |
break; | |
} | |
} | |
bool HandleThumbConditionalBranchInstruction(ThumbInstruction instruction) | |
{ | |
ThumbConditionalBranchInstruction instr(instruction.Full); | |
if (!CheckCondition(instr.Members.Opcode)) | |
return false; | |
std::cout << "Thumb: Taking Branch: " << (unsigned)instr.Members.Offset << std::endl; | |
PC += (instr.Members.Offset << 1) + 4; | |
// Why isn't this working? IDA says it should jump from 0000010C to 00000126, the instruction is 0B D2 (0xD20B), | |
// but it's jumping to 00000116, where does the other 0x10 come from? | |
return true; | |
} | |
bool HandleHiRegisterOperationInstruction(ThumbInstruction instruction) | |
{ | |
ThumbHiRegisterOperationInstruction instr(instruction.Full); | |
switch (instr.Members.Opcode) | |
{ | |
case THUMB_ADD: | |
_Registers.R[instr.Members.GetRd()] += _Registers.R[instr.Members.GetRs()]; | |
break; | |
case THUMB_MOV: | |
_Registers.R[instr.Members.GetRd()] = _Registers.R[instr.Members.GetRs()]; | |
break; | |
case THUMB_CMP: | |
break; | |
case THUMB_BX: | |
_CPSR.bits.T = Utils::CheckBit(_Registers.R[instr.Members.GetRs()], 0); | |
if (instr.Members.MSBd == 0) | |
{ | |
// BX | |
PC = _Registers.R[instr.Members.GetRs()]; | |
if (!_CPSR.bits.T) // If we're out of Thumb mode, increase the address by 2, the code will add another 2 when this function exits. | |
PC += 2; | |
} | |
else | |
{ | |
// BLX | |
LR = (PC + 2) | 1; | |
PC = _Registers.R[instr.Members.GetRs()] & 0xFFFFFFFE; | |
} | |
return true; | |
break; | |
default: | |
Utils::Assert(false, "Thumb: Not supported Hi Register Operation opcode"); | |
break; | |
} | |
return false; | |
} | |
void RunThumbInstruction(ThumbInstruction instruction) | |
{ | |
if (instruction.CheckPass(3, 13, 0)) | |
HandleThumbMoveShiftedRegister(instruction); | |
else if (instruction.CheckPass(5, 11, 0x9)) | |
HandleThumbLoadPCRelative(instruction); | |
else if (instruction.CheckPass(6, 10, 0x10)) | |
HandleThumbALUInstruction(instruction); | |
else if (instruction.CheckPass(4, 12, 0xD)) | |
{ | |
if (HandleThumbConditionalBranchInstruction(instruction)) | |
return; // Prevent incrementing the PC if the branch was taken | |
} | |
else if (instruction.CheckPass(6, 10, 0x11)) | |
{ | |
if (HandleHiRegisterOperationInstruction(instruction)) | |
return; | |
} | |
PC += 2; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef OPCODES_H | |
#define OPCODES_H | |
/* ARM OPCODES */ | |
// Branch Exchange opcodes | |
#define ARM_BX 0x1 | |
#define ARM_BLX 0x3 | |
// Data Processing opcodes | |
#define ARM_MOV 0xD | |
#define ARM_ADD 0x4 | |
// PSR Transfer opcodes | |
#define ARM_MRS 0x0 | |
#define ARM_MSR 0x1 | |
// Single Data Transfer opcodes | |
#define ARM_STR 0x0 | |
#define ARM_LDR 0x1 | |
/* THUMB OPCODES */ | |
// Move Shifted Register opcodes | |
#define THUMB_MSR_LSL 0x0 | |
#define THUMB_MSR_LSR 0x1 | |
#define THUMB_MSR_ASR 0x2 | |
// ALU Operation opcodes | |
#define THUMB_AND 0x0 | |
#define THUMB_EOR 0x1 | |
#define THUMB_LSL 0x2 | |
#define THUMB_LSR 0x3 | |
// Conditional Branch opcodes | |
#define THUMB_BEQ 0x0 | |
#define THUMB_BNE 0x1 | |
#define THUMB_BCS 0x2 | |
#define THUMB_BCC 0x3 | |
#define THUMB_BMI 0x4 | |
#define THUMB_BPL 0x5 | |
#define THUMB_BVS 0x6 | |
#define THUMB_BVC 0x7 | |
#define THUMB_BHI 0x8 | |
#define THUMB_BLS 0x9 | |
#define THUMB_BGE 0xA | |
#define THUMB_BLT 0xB | |
#define THUMB_BGT 0xC | |
#define THUMB_BLE 0xD | |
// Hi Register Operation opcodes | |
#define THUMB_ADD 0x0 | |
#define THUMB_CMP 0x1 | |
#define THUMB_MOV 0x2 | |
#define THUMB_BX 0x3 // Can be BLX | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef THUMB_H | |
#define THUMB_H | |
#include <stdint.h> | |
union ThumbHiRegisterOperationInstruction | |
{ | |
struct | |
{ | |
uint8_t Rd : 3; // Destination Register | |
uint8_t Rs : 3; // Source Register | |
uint8_t MSBs : 1; // Source Register most significant bit | |
uint8_t MSBd : 1; // Destination Register most significant bit (or BL/BLX flag) | |
uint8_t Opcode : 2; | |
uint8_t Unused : 6; // = 010001b | |
uint8_t GetRs() | |
{ | |
return (MSBs << 3) | Rs; | |
} | |
uint8_t GetRd() | |
{ | |
return (MSBd << 3) | Rd; | |
} | |
} __attribute__((packed)) Members; | |
uint16_t Full; | |
ThumbHiRegisterOperationInstruction(uint16_t val) { Full = val; } | |
}; | |
union ThumbConditionalBranchInstruction | |
{ | |
struct | |
{ | |
int8_t Offset : 3; // Signed offset | |
uint8_t Opcode : 4; | |
uint8_t Unused : 4; // = 1101b | |
} __attribute__((packed)) Members; | |
uint16_t Full; | |
ThumbConditionalBranchInstruction(uint16_t val) { Full = val; } | |
}; | |
union ThumbALUInstruction | |
{ | |
struct | |
{ | |
uint8_t Rd : 3; // Destination register | |
uint8_t Rs : 3; // Source register | |
uint8_t Opcode : 4; | |
uint8_t Unused : 6; // = 010000b | |
} __attribute__((packed)) Members; | |
uint16_t Full; | |
ThumbALUInstruction(uint16_t val) { Full = val; } | |
}; | |
union ThumbLoadPCRelativeInstruction | |
{ | |
struct | |
{ | |
uint8_t nn; | |
uint8_t Rd : 3; // Destination register | |
uint8_t Unused : 5; | |
} __attribute__((packed)) Members; | |
uint16_t Full; | |
ThumbLoadPCRelativeInstruction(uint16_t val) { Full = val; } | |
}; | |
union ThumbMoveShiftedRegisterInstruction | |
{ | |
struct | |
{ | |
uint8_t Unused : 3; // = 000 | |
uint8_t Opcode : 2; | |
uint8_t Offset : 5; | |
uint8_t Rs : 3; // Source register | |
uint8_t Rd : 3; // Destination register | |
} __attribute__((packed)) Members; | |
uint16_t Full; | |
ThumbMoveShiftedRegisterInstruction(uint16_t val) { Full = val; } | |
}; | |
union ThumbInstruction | |
{ | |
uint16_t Full; | |
bool CheckPass(uint8_t bits, uint8_t start, uint16_t check) | |
{ | |
uint16_t extract = 0; | |
extract = (Full >> start) & ((1 << bits) - 1); | |
return extract == check; | |
} | |
ThumbInstruction(uint16_t val) { Full = val; } | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment