thumb: add disassembler

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-09-27 17:31:00 +05:30
parent 208527b7f8
commit 5ec5e6dddc
13 changed files with 716 additions and 71 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

@@ -1,24 +1,10 @@
#include "instruction.hh" #include "instruction.hh"
#include "util/bits.hh" #include "util/bits.hh"
#include <iterator>
namespace matar {
namespace thumb {
namespace matar::thumb {
Instruction::Instruction(uint16_t insn) { Instruction::Instruction(uint16_t insn) {
// Format 1: Move Shifted Register // Format 2: Add/Subtract
if ((insn & 0xE000) == 0x0000) { 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, 10);
ShiftType opcode = static_cast<ShiftType>(bit_range(insn, 11, 12));
data = MoveShiftedRegister{
.rd = rd, .rs = rs, .offset = offset, .opcode = opcode
};
// Format 2: Add/Subtract
} else if ((insn & 0xF800) == 0x1800) {
uint8_t rd = bit_range(insn, 0, 2); uint8_t rd = bit_range(insn, 0, 2);
uint8_t rs = bit_range(insn, 3, 5); uint8_t rs = bit_range(insn, 3, 5);
uint8_t offset = bit_range(insn, 6, 8); uint8_t offset = bit_range(insn, 6, 8);
@@ -30,6 +16,17 @@ Instruction::Instruction(uint16_t insn) {
.rd = rd, .rs = rs, .offset = offset, .opcode = opcode, .imm = imm .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 // Format 3: Move/compare/add/subtract immediate
} else if ((insn & 0xE000) == 0x2000) { } else if ((insn & 0xE000) == 0x2000) {
uint8_t offset = bit_range(insn, 0, 7); uint8_t offset = bit_range(insn, 0, 7);
@@ -58,9 +55,10 @@ Instruction::Instruction(uint16_t insn) {
HiRegisterOperations::OpCode opcode = HiRegisterOperations::OpCode opcode =
static_cast<HiRegisterOperations::OpCode>(bit_range(insn, 8, 9)); static_cast<HiRegisterOperations::OpCode>(bit_range(insn, 8, 9));
data = HiRegisterOperations{ rd += (hi_1 ? LO_GPR_COUNT : 0);
.rd = rd, .rs = rs, .hi_2 = hi_2, .hi_1 = hi_1, .opcode = opcode rs += (hi_2 ? LO_GPR_COUNT : 0);
};
data = HiRegisterOperations{ .rd = rd, .rs = rs, .opcode = opcode };
// Format 6: PC-relative load // Format 6: PC-relative load
} else if ((insn & 0xF800) == 0x4800) { } else if ((insn & 0xF800) == 0x4800) {
uint8_t word = bit_range(insn, 0, 7); uint8_t word = bit_range(insn, 0, 7);
@@ -168,24 +166,26 @@ Instruction::Instruction(uint16_t insn) {
// Format 16: Conditional branch // Format 16: Conditional branch
} else if ((insn & 0xF000) == 0xD000) { } else if ((insn & 0xF000) == 0xD000) {
uint8_t offset = bit_range(insn, 0, 7); uint16_t offset = bit_range(insn, 0, 7);
Condition condition = static_cast<Condition>(bit_range(insn, 8, 11)); Condition condition = static_cast<Condition>(bit_range(insn, 8, 11));
data = ConditionalBranch{ .offset = offset, .condition = condition }; data = ConditionalBranch{ .offset = static_cast<uint16_t>(offset << 1),
.condition = condition };
// Format 18: Unconditional branch // Format 18: Unconditional branch
} else if ((insn & 0xF800) == 0xE000) { } else if ((insn & 0xF800) == 0xE000) {
uint16_t offset = bit_range(insn, 0, 10); uint16_t offset = bit_range(insn, 0, 10);
data = UnconditionalBranch{ .offset = offset }; data =
UnconditionalBranch{ .offset = static_cast<uint16_t>(offset << 1) };
// Format 19: Long branch with link // Format 19: Long branch with link
} else if ((insn & 0xF000) == 0xF000) { } else if ((insn & 0xF000) == 0xF000) {
uint16_t offset = bit_range(insn, 0, 10); uint16_t offset = bit_range(insn, 0, 10);
bool high = get_bit(insn, 11); bool high = get_bit(insn, 11);
data = LongBranchWithLink{ .offset = offset, .high = high }; data = LongBranchWithLink{ .offset = static_cast<uint16_t>(offset << 1),
.high = high };
} }
} }
} }
}

View File

@@ -1,13 +1,14 @@
#pragma once #pragma once
#include "cpu/alu.hh" #include "cpu/alu.hh"
#include "cpu/psr.hh" #include "cpu/psr.hh"
#include <cstdint> #include <cstdint>
#include <fmt/ostream.h> #include <fmt/ostream.h>
#include <variant> #include <variant>
namespace matar { namespace matar::thumb {
namespace thumb {
// https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts> template<class... Ts>
struct overloaded : Ts... { struct overloaded : Ts... {
using Ts::operator()...; using Ts::operator()...;
@@ -16,6 +17,7 @@ template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>; overloaded(Ts...) -> overloaded<Ts...>;
static constexpr size_t INSTRUCTION_SIZE = 2; static constexpr size_t INSTRUCTION_SIZE = 2;
static constexpr uint8_t LO_GPR_COUNT = 8;
struct MoveShiftedRegister { struct MoveShiftedRegister {
uint8_t rd; uint8_t rd;
@@ -37,6 +39,21 @@ struct AddSubtract {
bool imm; 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 { struct MovCmpAddSubImmediate {
enum class OpCode { enum class OpCode {
MOV = 0b00, MOV = 0b00,
@@ -50,6 +67,23 @@ struct MovCmpAddSubImmediate {
OpCode opcode; 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 { struct AluOperations {
enum class OpCode { enum class OpCode {
AND = 0b0000, AND = 0b0000,
@@ -75,6 +109,36 @@ struct AluOperations {
OpCode opcode; 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 { struct HiRegisterOperations {
enum class OpCode { enum class OpCode {
ADD = 0b00, ADD = 0b00,
@@ -85,11 +149,26 @@ struct HiRegisterOperations {
uint8_t rd; uint8_t rd;
uint8_t rs; uint8_t rs;
bool hi_2;
bool hi_1;
OpCode opcode; 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 { struct PcRelativeLoad {
uint8_t word; uint8_t word;
uint8_t rd; uint8_t rd;
@@ -156,7 +235,7 @@ struct MultipleLoad {
}; };
struct ConditionalBranch { struct ConditionalBranch {
uint8_t offset; uint16_t offset;
Condition condition; Condition condition;
}; };
@@ -196,35 +275,8 @@ struct Instruction {
Instruction(uint16_t insn); Instruction(uint16_t insn);
#ifdef DISASSEMBLER
std::string disassemble(); std::string disassemble();
#endif
}; };
std::ostream&
operator<<(std::ostream& os, const AddSubtract::OpCode cond);
std::ostream&
operator<<(std::ostream& os, const MovCmpAddSubImmediate::OpCode cond);
std::ostream&
operator<<(std::ostream& os, const AluOperations::OpCode cond);
std::ostream&
operator<<(std::ostream& os, const HiRegisterOperations::OpCode cond);
}
}
namespace fmt {
template<>
struct formatter<matar::thumb::AddSubtract::OpCode> : ostream_formatter {};
template<>
struct formatter<matar::thumb::MovCmpAddSubImmediate::OpCode>
: ostream_formatter {};
template<>
struct formatter<matar::thumb::AluOperations::OpCode> : ostream_formatter {};
template<>
struct formatter<matar::thumb::HiRegisterOperations::OpCode>
: ostream_formatter {};
} }

View File

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

View File

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