2 Commits

Author SHA1 Message Date
5ec5e6dddc thumb: add disassembler
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2023-09-27 17:31:00 +05:30
208527b7f8 thumb: initialise instruction formats
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2023-09-27 01:36:05 +05:30
14 changed files with 1082 additions and 12 deletions

View File

@@ -1,8 +1,7 @@
#include "instruction.hh"
#include "util/bits.hh"
namespace matar {
namespace arm {
namespace matar::arm {
std::string
Instruction::disassemble() {
auto condition = stringify(this->condition);
@@ -232,4 +231,3 @@ Instruction::disassemble() {
data);
}
}
}

View File

@@ -2,8 +2,7 @@
#include "util/bits.hh"
#include <iterator>
namespace matar {
namespace arm {
namespace matar::arm {
Instruction::Instruction(uint32_t insn)
: condition(static_cast<Condition>(bit_range(insn, 28, 31))) {
// Branch and exhcange
@@ -275,4 +274,3 @@ Instruction::Instruction(uint32_t insn)
}
}
}
}

View File

@@ -5,8 +5,7 @@
#include <fmt/ostream.h>
#include <variant>
namespace matar {
namespace arm {
namespace matar::arm {
// https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts>
@@ -223,4 +222,3 @@ struct Instruction {
#endif
};
}
}

View File

@@ -3,6 +3,7 @@
#include "util/log.hh"
#include <algorithm>
#include <cstdio>
#include <type_traits>
namespace matar {
CpuImpl::CpuImpl(const Bus& bus) noexcept

View File

@@ -15,6 +15,7 @@ class CpuImpl {
void chg_mode(const Mode to);
void exec(const arm::Instruction instruction);
// TODO: get rid of this
#ifndef MATAR_CPU_TESTS
private:
#endif

View File

@@ -5,4 +5,5 @@ lib_sources += files(
'alu.cc'
)
subdir('arm')
subdir('arm')
subdir('thumb')

View File

@@ -0,0 +1,150 @@
#include "instruction.hh"
#include "util/bits.hh"
namespace matar::thumb {
std::string
Instruction::disassemble() {
return std::visit(
overloaded{
[](MoveShiftedRegister& data) {
return fmt::format("{} R{:d},R{:d},#{:d}",
stringify(data.opcode),
data.rd,
data.rs,
data.offset);
},
[](AddSubtract& data) {
return fmt::format("{} R{:d},R{:d},{}{:d}",
stringify(data.opcode),
data.rd,
data.rs,
(data.imm ? '#' : 'R'),
data.offset);
},
[](MovCmpAddSubImmediate& data) {
return fmt::format(
"{} R{:d},#{:d}", stringify(data.opcode), data.rd, data.offset);
},
[](AluOperations& data) {
return fmt::format(
"{} R{:d},R{:d}", stringify(data.opcode), data.rd, data.rs);
},
[](HiRegisterOperations& data) {
if (data.opcode == HiRegisterOperations::OpCode::BX) {
return fmt::format("{} R{:d}", stringify(data.opcode), data.rs);
}
return fmt::format(
"{} R{:d},R{:d}", stringify(data.opcode), data.rd, data.rs);
},
[](PcRelativeLoad& data) {
return fmt::format("LDR R{:d},[PC,#{:d}]", data.rd, data.word);
},
[](LoadStoreRegisterOffset& data) {
return fmt::format("{}{} R{:d},[R{:d},R{:d}]",
(data.load ? "LDR" : "STR"),
(data.byte ? "B" : ""),
data.rd,
data.rb,
data.ro);
},
[](LoadStoreSignExtendedHalfword& data) {
if (!data.s && !data.h) {
return fmt::format(
"STRH R{:d},[R{:d},R{:d}]", data.rd, data.rb, data.ro);
}
return fmt::format("{}{} R{:d},[R{:d},R{:d}]",
(data.s ? "LDS" : "LDR"),
(data.h ? 'H' : 'B'),
data.rd,
data.rb,
data.ro);
},
[](LoadStoreImmediateOffset& data) {
return fmt::format("{}{} R{:d},[R{:d},#{:d}]",
(data.load ? "LDR" : "STR"),
(data.byte ? "B" : ""),
data.rd,
data.rb,
data.offset);
},
[](LoadStoreHalfword& data) {
return fmt::format("{} R{:d},[R{:d},#{:d}]",
(data.load ? "LDRH" : "STRH"),
data.rd,
data.rb,
data.offset);
},
[](SpRelativeLoad& data) {
return fmt::format("{} R{:d},[SP,#{:d}]",
(data.load ? "LDR" : "STR"),
data.rd,
data.word);
},
[](LoadAddress& data) {
return fmt::format("ADD R{:d},{},#{:d}",
data.rd,
(data.sp ? "SP" : "PC"),
data.word);
},
[](AddOffsetStackPointer& data) {
return fmt::format(
"ADD SP,#{}{:d}", (data.sign ? '-' : '+'), data.word);
},
[](PushPopRegister& data) {
std::string regs;
for (uint8_t i = 0; i < 16; i++) {
if (get_bit(data.regs, i))
fmt::format_to(std::back_inserter(regs), "R{:d},", i);
};
if (data.load) {
if (data.pclr)
regs += "PC";
else
regs.pop_back();
return fmt::format("POP {{{}}}", regs);
} else {
if (data.pclr)
regs += "LR";
else
regs.pop_back();
return fmt::format("PUSH {{{}}}", regs);
}
},
[](MultipleLoad& data) {
std::string regs;
for (uint8_t i = 0; i < 16; i++) {
if (get_bit(data.regs, i))
fmt::format_to(std::back_inserter(regs), "R{:d},", i);
};
regs.pop_back();
return fmt::format(
"{} R{}!,{{{}}}", (data.load ? "LDMIA" : "STMIA"), data.rb, regs);
},
[](SoftwareInterrupt) { return std::string("SWI"); },
[](ConditionalBranch& data) {
return fmt::format("B{} {:d}",
stringify(data.condition),
data.offset);
},
[](UnconditionalBranch& data) {
return fmt::format("B {:d}", data.offset);
},
[](LongBranchWithLink& data) {
// duh this manual be empty for H = 0
return fmt::format(
"BL{} {:d}", (data.high ? "H" : ""), data.offset);
},
[](auto) { return std::string("unknown instruction"); } },
data);
}
}

View File

@@ -0,0 +1,191 @@
#include "instruction.hh"
#include "util/bits.hh"
namespace matar::thumb {
Instruction::Instruction(uint16_t insn) {
// Format 2: Add/Subtract
if ((insn & 0xF800) == 0x1800) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rs = bit_range(insn, 3, 5);
uint8_t offset = bit_range(insn, 6, 8);
AddSubtract::OpCode opcode =
static_cast<AddSubtract::OpCode>(get_bit(insn, 9));
bool imm = get_bit(insn, 10);
data = AddSubtract{
.rd = rd, .rs = rs, .offset = offset, .opcode = opcode, .imm = imm
};
// Format 1: Move Shifted Register
} else if ((insn & 0xE000) == 0x0000) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rs = bit_range(insn, 3, 5);
uint8_t offset = bit_range(insn, 6, 10);
ShiftType opcode = static_cast<ShiftType>(bit_range(insn, 11, 12));
data = MoveShiftedRegister{
.rd = rd, .rs = rs, .offset = offset, .opcode = opcode
};
// Format 3: Move/compare/add/subtract immediate
} else if ((insn & 0xE000) == 0x2000) {
uint8_t offset = bit_range(insn, 0, 7);
uint8_t rd = bit_range(insn, 8, 10);
MovCmpAddSubImmediate::OpCode opcode =
static_cast<MovCmpAddSubImmediate::OpCode>(bit_range(insn, 11, 12));
data =
MovCmpAddSubImmediate{ .offset = offset, .rd = rd, .opcode = opcode };
// Format 4: ALU operations
} else if ((insn & 0xFC00) == 0x4000) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rs = bit_range(insn, 3, 5);
AluOperations::OpCode opcode =
static_cast<AluOperations::OpCode>(bit_range(insn, 6, 9));
data = AluOperations{ .rd = rd, .rs = rs, .opcode = opcode };
// Format 5: Hi register operations/branch exchange
} else if ((insn & 0xFC00) == 0x4400) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rs = bit_range(insn, 3, 5);
bool hi_2 = get_bit(insn, 6);
bool hi_1 = get_bit(insn, 7);
HiRegisterOperations::OpCode opcode =
static_cast<HiRegisterOperations::OpCode>(bit_range(insn, 8, 9));
rd += (hi_1 ? LO_GPR_COUNT : 0);
rs += (hi_2 ? LO_GPR_COUNT : 0);
data = HiRegisterOperations{ .rd = rd, .rs = rs, .opcode = opcode };
// Format 6: PC-relative load
} else if ((insn & 0xF800) == 0x4800) {
uint8_t word = bit_range(insn, 0, 7);
uint8_t rd = bit_range(insn, 8, 10);
data = PcRelativeLoad{ .word = word, .rd = rd };
// Format 7: Load/store with register offset
} else if ((insn & 0xF200) == 0x5000) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rb = bit_range(insn, 3, 5);
uint8_t ro = bit_range(insn, 6, 8);
bool byte = get_bit(insn, 10);
bool load = get_bit(insn, 11);
data = LoadStoreRegisterOffset{
.rd = rd, .rb = rb, .ro = ro, .byte = byte, .load = load
};
// Format 8: Load/store sign-extended byte/halfword
} else if ((insn & 0xF200) == 0x5200) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rb = bit_range(insn, 3, 5);
uint8_t ro = bit_range(insn, 6, 8);
bool s = get_bit(insn, 10);
bool h = get_bit(insn, 11);
data = LoadStoreSignExtendedHalfword{
.rd = rd, .rb = rb, .ro = ro, .s = s, .h = h
};
// Format 9: Load/store with immediate offset
} else if ((insn & 0xF000) == 0x6000) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rb = bit_range(insn, 3, 5);
uint8_t offset = bit_range(insn, 6, 10);
bool load = get_bit(insn, 11);
bool byte = get_bit(insn, 12);
data = LoadStoreImmediateOffset{
.rd = rd, .rb = rb, .offset = offset, .load = load, .byte = byte
};
// Format 10: Load/store halfword
} else if ((insn & 0xF000) == 0x8000) {
uint8_t rd = bit_range(insn, 0, 2);
uint8_t rb = bit_range(insn, 3, 5);
uint8_t offset = bit_range(insn, 6, 10);
bool load = get_bit(insn, 11);
data = LoadStoreHalfword{
.rd = rd, .rb = rb, .offset = offset, .load = load
};
// Format 11: SP-relative load/store
} else if ((insn & 0xF000) == 0x9000) {
uint8_t word = bit_range(insn, 0, 7);
uint8_t rd = bit_range(insn, 8, 10);
bool load = get_bit(insn, 11);
data = SpRelativeLoad{ .word = word, .rd = rd, .load = load };
// Format 12: Load address
} else if ((insn & 0xF000) == 0xA000) {
uint8_t word = bit_range(insn, 0, 7);
uint8_t rd = bit_range(insn, 8, 10);
bool sp = get_bit(insn, 11);
data = LoadAddress{ .word = word, .rd = rd, .sp = sp };
// Format 12: Load address
} else if ((insn & 0xF000) == 0xA000) {
uint8_t word = bit_range(insn, 0, 7);
uint8_t rd = bit_range(insn, 8, 10);
bool sp = get_bit(insn, 11);
data = LoadAddress{ .word = word, .rd = rd, .sp = sp };
// Format 13: Add offset to stack pointer
} else if ((insn & 0xFF00) == 0xB000) {
uint8_t word = bit_range(insn, 0, 6);
bool sign = get_bit(insn, 7);
data = AddOffsetStackPointer{ .word = word, .sign = sign };
// Format 14: Push/pop registers
} else if ((insn & 0xF600) == 0xB400) {
uint8_t regs = bit_range(insn, 0, 7);
bool pclr = get_bit(insn, 8);
bool load = get_bit(insn, 11);
data = PushPopRegister{ .regs = regs, .pclr = pclr, .load = load };
// Format 15: Multiple load/store
} else if ((insn & 0xF000) == 0xC000) {
uint8_t regs = bit_range(insn, 0, 7);
uint8_t rb = bit_range(insn, 8, 10);
bool load = get_bit(insn, 11);
data = MultipleLoad{ .regs = regs, .rb = rb, .load = load };
// Format 17: Software interrupt
} else if ((insn & 0xFF00) == 0xDF00) {
data = SoftwareInterrupt{};
// Format 16: Conditional branch
} else if ((insn & 0xF000) == 0xD000) {
uint16_t offset = bit_range(insn, 0, 7);
Condition condition = static_cast<Condition>(bit_range(insn, 8, 11));
data = ConditionalBranch{ .offset = static_cast<uint16_t>(offset << 1),
.condition = condition };
// Format 18: Unconditional branch
} else if ((insn & 0xF800) == 0xE000) {
uint16_t offset = bit_range(insn, 0, 10);
data =
UnconditionalBranch{ .offset = static_cast<uint16_t>(offset << 1) };
// Format 19: Long branch with link
} else if ((insn & 0xF000) == 0xF000) {
uint16_t offset = bit_range(insn, 0, 10);
bool high = get_bit(insn, 11);
data = LongBranchWithLink{ .offset = static_cast<uint16_t>(offset << 1),
.high = high };
}
}
}

View File

@@ -0,0 +1,282 @@
#pragma once
#include "cpu/alu.hh"
#include "cpu/psr.hh"
#include <cstdint>
#include <fmt/ostream.h>
#include <variant>
namespace matar::thumb {
// https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
static constexpr size_t INSTRUCTION_SIZE = 2;
static constexpr uint8_t LO_GPR_COUNT = 8;
struct MoveShiftedRegister {
uint8_t rd;
uint8_t rs;
uint8_t offset;
ShiftType opcode;
};
struct AddSubtract {
enum class OpCode {
ADD = 0,
SUB = 1
};
uint8_t rd;
uint8_t rs;
uint8_t offset;
OpCode opcode;
bool imm;
};
constexpr auto
stringify(AddSubtract::OpCode opcode) {
#define CASE(opcode) \
case AddSubtract::OpCode::opcode: \
return #opcode;
switch (opcode) {
CASE(ADD)
CASE(SUB)
}
#undef CASE
return "";
}
struct MovCmpAddSubImmediate {
enum class OpCode {
MOV = 0b00,
CMP = 0b01,
ADD = 0b10,
SUB = 0b11
};
uint8_t offset;
uint8_t rd;
OpCode opcode;
};
constexpr auto
stringify(MovCmpAddSubImmediate::OpCode opcode) {
#define CASE(opcode) \
case MovCmpAddSubImmediate::OpCode::opcode: \
return #opcode;
switch (opcode) {
CASE(MOV)
CASE(CMP)
CASE(ADD)
CASE(SUB)
}
#undef CASE
return "";
}
struct AluOperations {
enum class OpCode {
AND = 0b0000,
EOR = 0b0001,
LSL = 0b0010,
LSR = 0b0011,
ASR = 0b0100,
ADC = 0b0101,
SBC = 0b0110,
ROR = 0b0111,
TST = 0b1000,
NEG = 0b1001,
CMP = 0b1010,
CMN = 0b1011,
ORR = 0b1100,
MUL = 0b1101,
BIC = 0b1110,
MVN = 0b1111
};
uint8_t rd;
uint8_t rs;
OpCode opcode;
};
constexpr auto
stringify(AluOperations::OpCode opcode) {
#define CASE(opcode) \
case AluOperations::OpCode::opcode: \
return #opcode;
switch (opcode) {
CASE(AND)
CASE(EOR)
CASE(LSL)
CASE(LSR)
CASE(ASR)
CASE(ADC)
CASE(SBC)
CASE(ROR)
CASE(TST)
CASE(NEG)
CASE(CMP)
CASE(CMN)
CASE(ORR)
CASE(MUL)
CASE(BIC)
CASE(MVN)
}
#undef CASE
return "";
}
struct HiRegisterOperations {
enum class OpCode {
ADD = 0b00,
CMP = 0b01,
MOV = 0b10,
BX = 0b11
};
uint8_t rd;
uint8_t rs;
OpCode opcode;
};
constexpr auto
stringify(HiRegisterOperations::OpCode opcode) {
#define CASE(opcode) \
case HiRegisterOperations::OpCode::opcode: \
return #opcode;
switch (opcode) {
CASE(ADD)
CASE(CMP)
CASE(MOV)
CASE(BX)
}
#undef CASE
return "";
}
struct PcRelativeLoad {
uint8_t word;
uint8_t rd;
};
struct LoadStoreRegisterOffset {
uint8_t rd;
uint8_t rb;
uint8_t ro;
bool byte;
bool load;
};
struct LoadStoreSignExtendedHalfword {
uint8_t rd;
uint8_t rb;
uint8_t ro;
bool s;
bool h;
};
struct LoadStoreImmediateOffset {
uint8_t rd;
uint8_t rb;
uint8_t offset;
bool load;
bool byte;
};
struct LoadStoreHalfword {
uint8_t rd;
uint8_t rb;
uint8_t offset;
bool load;
};
struct SpRelativeLoad {
uint8_t word;
uint8_t rd;
bool load;
};
struct LoadAddress {
uint8_t word;
uint8_t rd;
bool sp;
};
struct AddOffsetStackPointer {
uint8_t word;
bool sign;
};
struct PushPopRegister {
uint8_t regs;
bool pclr;
bool load;
};
struct MultipleLoad {
uint8_t regs;
uint8_t rb;
bool load;
};
struct ConditionalBranch {
uint16_t offset;
Condition condition;
};
struct SoftwareInterrupt {};
struct UnconditionalBranch {
uint16_t offset;
};
struct LongBranchWithLink {
uint16_t offset;
bool high;
};
using InstructionData = std::variant<MoveShiftedRegister,
AddSubtract,
MovCmpAddSubImmediate,
AluOperations,
HiRegisterOperations,
PcRelativeLoad,
LoadStoreRegisterOffset,
LoadStoreSignExtendedHalfword,
LoadStoreImmediateOffset,
LoadStoreHalfword,
SpRelativeLoad,
LoadAddress,
AddOffsetStackPointer,
PushPopRegister,
MultipleLoad,
ConditionalBranch,
SoftwareInterrupt,
UnconditionalBranch,
LongBranchWithLink>;
struct Instruction {
InstructionData data;
Instruction(uint16_t insn);
#ifdef DISASSEMBLER
std::string disassemble();
#endif
};
}

View File

@@ -0,0 +1,7 @@
lib_sources += files(
'instruction.cc'
)
if get_option('disassembler')
lib_sources += files('disassembler.cc')
endif

View File

@@ -6,7 +6,7 @@ lib_sources = files(
subdir('util')
subdir('cpu')
lib_cpp_args = [ ]
lib_cpp_args = []
fmt = dependency('fmt', version : '>=10.1.0', static: true)
if not fmt.found()

View File

@@ -1 +1,2 @@
subdir('arm')
subdir('arm')
subdir('thumb')

View File

@@ -0,0 +1,439 @@
#include "cpu/thumb/instruction.hh"
#include <catch2/catch_test_macros.hpp>
static constexpr auto TAG = "[thumb][disassembly]";
using namespace matar;
using namespace thumb;
TEST_CASE("Move Shifted Register", TAG) {
uint16_t raw = 0b0001001101100011;
Instruction instruction(raw);
MoveShiftedRegister* lsl = nullptr;
REQUIRE((lsl = std::get_if<MoveShiftedRegister>(&instruction.data)));
CHECK(lsl->rd == 3);
CHECK(lsl->rs == 4);
CHECK(lsl->offset == 13);
CHECK(lsl->opcode == ShiftType::ASR);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "ASR R3,R4,#13");
lsl->opcode = ShiftType::LSR;
CHECK(instruction.disassemble() == "LSR R3,R4,#13");
lsl->opcode = ShiftType::LSL;
CHECK(instruction.disassemble() == "LSL R3,R4,#13");
#endif
}
TEST_CASE("Add/Subtract", TAG) {
uint16_t raw = 0b0001111101001111;
Instruction instruction(raw);
AddSubtract* add = nullptr;
REQUIRE((add = std::get_if<AddSubtract>(&instruction.data)));
CHECK(add->rd == 7);
CHECK(add->rs == 1);
CHECK(add->offset == 5);
CHECK(add->opcode == AddSubtract::OpCode::SUB);
CHECK(add->imm == true);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "SUB R7,R1,#5");
add->imm = false;
CHECK(instruction.disassemble() == "SUB R7,R1,R5");
add->opcode = AddSubtract::OpCode::ADD;
CHECK(instruction.disassemble() == "ADD R7,R1,R5");
#endif
}
TEST_CASE("Move/Compare/Add/Subtract Immediate", TAG) {
uint16_t raw = 0b0010111001011011;
Instruction instruction(raw);
MovCmpAddSubImmediate* mov = nullptr;
REQUIRE((mov = std::get_if<MovCmpAddSubImmediate>(&instruction.data)));
CHECK(mov->offset == 91);
CHECK(mov->rd == 6);
CHECK(mov->opcode == MovCmpAddSubImmediate::OpCode::CMP);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "CMP R6,#91");
mov->opcode = MovCmpAddSubImmediate::OpCode::ADD;
CHECK(instruction.disassemble() == "ADD R6,#91");
mov->opcode = MovCmpAddSubImmediate::OpCode::SUB;
CHECK(instruction.disassemble() == "SUB R6,#91");
mov->opcode = MovCmpAddSubImmediate::OpCode::MOV;
CHECK(instruction.disassemble() == "MOV R6,#91");
#endif
}
TEST_CASE("ALU Operations", TAG) {
uint16_t raw = 0b0100000110011111;
Instruction instruction(raw);
AluOperations* alu = nullptr;
REQUIRE((alu = std::get_if<AluOperations>(&instruction.data)));
CHECK(alu->rd == 7);
CHECK(alu->rs == 3);
CHECK(alu->opcode == AluOperations::OpCode::SBC);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "SBC R7,R3");
#define OPCODE(op) \
alu->opcode = AluOperations::OpCode::op; \
CHECK(instruction.disassemble() == #op " R7,R3");
OPCODE(AND)
OPCODE(EOR)
OPCODE(LSL)
OPCODE(LSR)
OPCODE(ASR)
OPCODE(ADC)
OPCODE(SBC)
OPCODE(ROR)
OPCODE(TST)
OPCODE(NEG)
OPCODE(CMP)
OPCODE(CMN)
OPCODE(ORR)
OPCODE(MUL)
OPCODE(BIC)
OPCODE(MVN)
#undef OPCODE
#endif
}
TEST_CASE("Hi Register Operations/Branch Exchange", TAG) {
HiRegisterOperations* hi = nullptr;
uint16_t raw = 0b0100011000011010;
SECTION("both lo") {
Instruction instruction(raw);
REQUIRE((hi = std::get_if<HiRegisterOperations>(&instruction.data)));
CHECK(hi->rd == 2);
CHECK(hi->rs == 3);
}
SECTION("hi rd") {
raw |= 1 << 7;
Instruction instruction(raw);
REQUIRE((hi = std::get_if<HiRegisterOperations>(&instruction.data)));
CHECK(hi->rd == 10);
CHECK(hi->rs == 3);
}
SECTION("hi rs") {
raw |= 1 << 6;
Instruction instruction(raw);
REQUIRE((hi = std::get_if<HiRegisterOperations>(&instruction.data)));
CHECK(hi->rd == 2);
CHECK(hi->rs == 11);
}
if (hi)
CHECK(hi->opcode == HiRegisterOperations::OpCode::MOV);
SECTION("both hi") {
raw |= 1 << 6;
raw |= 1 << 7;
Instruction instruction(raw);
REQUIRE((hi = std::get_if<HiRegisterOperations>(&instruction.data)));
CHECK(hi->rd == 10);
CHECK(hi->rs == 11);
CHECK(hi->opcode == HiRegisterOperations::OpCode::MOV);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "MOV R10,R11");
hi->opcode = HiRegisterOperations::OpCode::ADD;
CHECK(instruction.disassemble() == "ADD R10,R11");
hi->opcode = HiRegisterOperations::OpCode::CMP;
CHECK(instruction.disassemble() == "CMP R10,R11");
hi->opcode = HiRegisterOperations::OpCode::BX;
CHECK(instruction.disassemble() == "BX R11");
#endif
}
}
TEST_CASE("PC Relative Load", TAG) {
uint16_t raw = 0b0100101011100110;
Instruction instruction(raw);
PcRelativeLoad* ldr = nullptr;
REQUIRE((ldr = std::get_if<PcRelativeLoad>(&instruction.data)));
CHECK(ldr->word == 230);
CHECK(ldr->rd == 2);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "LDR R2,[PC,#230]");
#endif
}
TEST_CASE("Load/Store with Register Offset", TAG) {
uint16_t raw = 0b0101000110011101;
Instruction instruction(raw);
LoadStoreRegisterOffset* ldr = nullptr;
REQUIRE((ldr = std::get_if<LoadStoreRegisterOffset>(&instruction.data)));
CHECK(ldr->rd == 5);
CHECK(ldr->rb == 3);
CHECK(ldr->ro == 6);
CHECK(ldr->byte == false);
CHECK(ldr->load == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "STR R5,[R3,R6]");
ldr->byte = true;
CHECK(instruction.disassemble() == "STRB R5,[R3,R6]");
ldr->load = true;
CHECK(instruction.disassemble() == "LDRB R5,[R3,R6]");
ldr->byte = false;
CHECK(instruction.disassemble() == "LDR R5,[R3,R6]");
#endif
}
TEST_CASE("Load/Store Sign-Extended Byte/Halfword", TAG) {
uint16_t raw = 0b0101001110011101;
Instruction instruction(raw);
LoadStoreSignExtendedHalfword* ldr = nullptr;
REQUIRE(
(ldr = std::get_if<LoadStoreSignExtendedHalfword>(&instruction.data)));
CHECK(ldr->rd == 5);
CHECK(ldr->rb == 3);
CHECK(ldr->ro == 6);
CHECK(ldr->s == false);
CHECK(ldr->h == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "STRH R5,[R3,R6]");
ldr->h = true;
CHECK(instruction.disassemble() == "LDRH R5,[R3,R6]");
ldr->s = true;
CHECK(instruction.disassemble() == "LDSH R5,[R3,R6]");
ldr->h = false;
CHECK(instruction.disassemble() == "LDSB R5,[R3,R6]");
#endif
}
TEST_CASE("Load/Store with Immediate Offset", TAG) {
uint16_t raw = 0b0110010110011101;
Instruction instruction(raw);
LoadStoreImmediateOffset* ldr = nullptr;
REQUIRE((ldr = std::get_if<LoadStoreImmediateOffset>(&instruction.data)));
CHECK(ldr->rd == 5);
CHECK(ldr->rb == 3);
CHECK(ldr->offset == 22);
CHECK(ldr->byte == false);
CHECK(ldr->load == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "STR R5,[R3,#22]");
ldr->byte = true;
CHECK(instruction.disassemble() == "STRB R5,[R3,#22]");
ldr->load = true;
CHECK(instruction.disassemble() == "LDRB R5,[R3,#22]");
ldr->byte = false;
CHECK(instruction.disassemble() == "LDR R5,[R3,#22]");
#endif
}
TEST_CASE("Load/Store Halfword", TAG) {
uint16_t raw = 0b1000011010011101;
Instruction instruction(raw);
LoadStoreHalfword* ldr = nullptr;
REQUIRE((ldr = std::get_if<LoadStoreHalfword>(&instruction.data)));
CHECK(ldr->rd == 5);
CHECK(ldr->rb == 3);
CHECK(ldr->offset == 26);
CHECK(ldr->load == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "STRH R5,[R3,#26]");
ldr->load = true;
CHECK(instruction.disassemble() == "LDRH R5,[R3,#26]");
#endif
}
TEST_CASE("SP-Relative Load/Store", TAG) {
uint16_t raw = 0b1001010010011101;
Instruction instruction(raw);
SpRelativeLoad* ldr = nullptr;
REQUIRE((ldr = std::get_if<SpRelativeLoad>(&instruction.data)));
CHECK(ldr->rd == 4);
CHECK(ldr->word == 157);
CHECK(ldr->load == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "STR R4,[SP,#157]");
ldr->load = true;
CHECK(instruction.disassemble() == "LDR R4,[SP,#157]");
#endif
}
TEST_CASE("Load Adress", TAG) {
uint16_t raw = 0b1010000110001111;
Instruction instruction(raw);
LoadAddress* add = nullptr;
REQUIRE((add = std::get_if<LoadAddress>(&instruction.data)));
CHECK(add->word == 143);
CHECK(add->rd == 1);
CHECK(add->sp == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "ADD R1,PC,#143");
add->sp = true;
CHECK(instruction.disassemble() == "ADD R1,SP,#143");
#endif
}
TEST_CASE("Add Offset to Stack Pointer", TAG) {
uint16_t raw = 0b1011000000100101;
Instruction instruction(raw);
AddOffsetStackPointer* add = nullptr;
REQUIRE((add = std::get_if<AddOffsetStackPointer>(&instruction.data)));
CHECK(add->word == 37);
CHECK(add->sign == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "ADD SP,#+37");
add->sign = true;
CHECK(instruction.disassemble() == "ADD SP,#-37");
#endif
}
TEST_CASE("Push/Pop Registers", TAG) {
uint16_t raw = 0b1011010000110101;
Instruction instruction(raw);
PushPopRegister* push = nullptr;
REQUIRE((push = std::get_if<PushPopRegister>(&instruction.data)));
CHECK(push->regs == 53);
CHECK(push->pclr == false);
CHECK(push->load == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "PUSH {R0,R2,R4,R5}");
push->pclr = true;
CHECK(instruction.disassemble() == "PUSH {R0,R2,R4,R5,LR}");
push->load = true;
CHECK(instruction.disassemble() == "POP {R0,R2,R4,R5,PC}");
push->pclr = false;
CHECK(instruction.disassemble() == "POP {R0,R2,R4,R5}");
#endif
}
TEST_CASE("Multiple Load/Store", TAG) {
uint16_t raw = 0b1100011001100101;
Instruction instruction(raw);
MultipleLoad* ldm = nullptr;
REQUIRE((ldm = std::get_if<MultipleLoad>(&instruction.data)));
CHECK(ldm->regs == 101);
CHECK(ldm->rb == 6);
CHECK(ldm->load == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "STMIA R6!,{R0,R2,R5,R6}");
ldm->load = true;
CHECK(instruction.disassemble() == "LDMIA R6!,{R0,R2,R5,R6}");
#endif
}
TEST_CASE("Conditional Branch", TAG) {
uint16_t raw = 0b1101100101110100;
Instruction instruction(raw);
ConditionalBranch* b = nullptr;
REQUIRE((b = std::get_if<ConditionalBranch>(&instruction.data)));
// 116 << 2
CHECK(b->offset == 232);
CHECK(b->condition == Condition::LS);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "BLS 232");
#endif
}
TEST_CASE("SoftwareInterrupt") {
uint16_t raw = 0b1101111100110011;
Instruction instruction(raw);
SoftwareInterrupt* swi = nullptr;
REQUIRE((swi = std::get_if<SoftwareInterrupt>(&instruction.data)));
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "SWI");
#endif
}
TEST_CASE("Unconditional Branch") {
uint16_t raw = 0b1110011100110011;
Instruction instruction(raw);
UnconditionalBranch* b = nullptr;
REQUIRE((b = std::get_if<UnconditionalBranch>(&instruction.data)));
// 1843 << 2
REQUIRE(b->offset == 3686);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "B 3686");
#endif
}
TEST_CASE("Long Branch with link") {
uint16_t raw = 0b1111010011101100;
Instruction instruction(raw);
LongBranchWithLink* bl = nullptr;
REQUIRE((bl = std::get_if<LongBranchWithLink>(&instruction.data)));
// 1260 << 1
CHECK(bl->offset == 2520);
CHECK(bl->high == false);
#ifdef DISASSEMBLER
CHECK(instruction.disassemble() == "BL 2520");
bl->high = true;
CHECK(instruction.disassemble() == "BLH 2520");
#endif
}

View File

@@ -0,0 +1,3 @@
tests_sources += files(
'instruction.cc'
)