diff --git a/src/cpu/arm/disassembler.cc b/src/cpu/arm/disassembler.cc index 9354336..20884c0 100644 --- a/src/cpu/arm/disassembler.cc +++ b/src/cpu/arm/disassembler.cc @@ -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); } } -} diff --git a/src/cpu/arm/instruction.cc b/src/cpu/arm/instruction.cc index b53682a..80d51da 100644 --- a/src/cpu/arm/instruction.cc +++ b/src/cpu/arm/instruction.cc @@ -2,8 +2,7 @@ #include "util/bits.hh" #include -namespace matar { -namespace arm { +namespace matar::arm { Instruction::Instruction(uint32_t insn) : condition(static_cast(bit_range(insn, 28, 31))) { // Branch and exhcange @@ -275,4 +274,3 @@ Instruction::Instruction(uint32_t insn) } } } -} diff --git a/src/cpu/arm/instruction.hh b/src/cpu/arm/instruction.hh index 024cf9c..03d6d44 100644 --- a/src/cpu/arm/instruction.hh +++ b/src/cpu/arm/instruction.hh @@ -5,8 +5,7 @@ #include #include -namespace matar { -namespace arm { +namespace matar::arm { // https://en.cppreference.com/w/cpp/utility/variant/visit template @@ -223,4 +222,3 @@ struct Instruction { #endif }; } -} diff --git a/src/cpu/cpu-impl.cc b/src/cpu/cpu-impl.cc index e00d561..5da109e 100644 --- a/src/cpu/cpu-impl.cc +++ b/src/cpu/cpu-impl.cc @@ -3,6 +3,7 @@ #include "util/log.hh" #include #include +#include namespace matar { CpuImpl::CpuImpl(const Bus& bus) noexcept diff --git a/src/cpu/cpu-impl.hh b/src/cpu/cpu-impl.hh index f128ed3..a622871 100644 --- a/src/cpu/cpu-impl.hh +++ b/src/cpu/cpu-impl.hh @@ -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 diff --git a/src/cpu/thumb/disassembler.cc b/src/cpu/thumb/disassembler.cc new file mode 100644 index 0000000..08eafa1 --- /dev/null +++ b/src/cpu/thumb/disassembler.cc @@ -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); +} +} diff --git a/src/cpu/thumb/instruction.cc b/src/cpu/thumb/instruction.cc index d9b769b..0061eb1 100644 --- a/src/cpu/thumb/instruction.cc +++ b/src/cpu/thumb/instruction.cc @@ -1,24 +1,10 @@ #include "instruction.hh" #include "util/bits.hh" -#include - -namespace matar { -namespace thumb { +namespace matar::thumb { Instruction::Instruction(uint16_t insn) { - // Format 1: Move Shifted Register - 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(bit_range(insn, 11, 12)); - - data = MoveShiftedRegister{ - .rd = rd, .rs = rs, .offset = offset, .opcode = opcode - }; - - // Format 2: Add/Subtract - } else if ((insn & 0xF800) == 0x1800) { + // 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); @@ -30,6 +16,17 @@ Instruction::Instruction(uint16_t insn) { .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(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); @@ -58,9 +55,10 @@ Instruction::Instruction(uint16_t insn) { HiRegisterOperations::OpCode opcode = static_cast(bit_range(insn, 8, 9)); - data = HiRegisterOperations{ - .rd = rd, .rs = rs, .hi_2 = hi_2, .hi_1 = hi_1, .opcode = opcode - }; + 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); @@ -168,24 +166,26 @@ Instruction::Instruction(uint16_t insn) { // Format 16: Conditional branch } 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(bit_range(insn, 8, 11)); - data = ConditionalBranch{ .offset = offset, .condition = condition }; + data = ConditionalBranch{ .offset = static_cast(offset << 1), + .condition = condition }; // Format 18: Unconditional branch } else if ((insn & 0xF800) == 0xE000) { uint16_t offset = bit_range(insn, 0, 10); - data = UnconditionalBranch{ .offset = offset }; + data = + UnconditionalBranch{ .offset = static_cast(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 = offset, .high = high }; + data = LongBranchWithLink{ .offset = static_cast(offset << 1), + .high = high }; } } } -} diff --git a/src/cpu/thumb/instruction.hh b/src/cpu/thumb/instruction.hh index 1f9cba7..d9432ad 100644 --- a/src/cpu/thumb/instruction.hh +++ b/src/cpu/thumb/instruction.hh @@ -1,13 +1,14 @@ #pragma once + #include "cpu/alu.hh" #include "cpu/psr.hh" #include #include #include -namespace matar { -namespace thumb { +namespace matar::thumb { +// https://en.cppreference.com/w/cpp/utility/variant/visit template struct overloaded : Ts... { using Ts::operator()...; @@ -16,6 +17,7 @@ template overloaded(Ts...) -> overloaded; static constexpr size_t INSTRUCTION_SIZE = 2; +static constexpr uint8_t LO_GPR_COUNT = 8; struct MoveShiftedRegister { uint8_t rd; @@ -37,6 +39,21 @@ struct AddSubtract { 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, @@ -50,6 +67,23 @@ struct MovCmpAddSubImmediate { 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, @@ -75,6 +109,36 @@ struct AluOperations { 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, @@ -85,11 +149,26 @@ struct HiRegisterOperations { uint8_t rd; uint8_t rs; - bool hi_2; - bool hi_1; 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; @@ -156,7 +235,7 @@ struct MultipleLoad { }; struct ConditionalBranch { - uint8_t offset; + uint16_t offset; Condition condition; }; @@ -196,35 +275,8 @@ struct Instruction { Instruction(uint16_t insn); +#ifdef DISASSEMBLER 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 : ostream_formatter {}; - -template<> -struct formatter - : ostream_formatter {}; - -template<> -struct formatter : ostream_formatter {}; - -template<> -struct formatter - : ostream_formatter {}; } diff --git a/src/cpu/thumb/meson.build b/src/cpu/thumb/meson.build index f030302..861c3d7 100644 --- a/src/cpu/thumb/meson.build +++ b/src/cpu/thumb/meson.build @@ -1,3 +1,7 @@ lib_sources += files( 'instruction.cc' -) \ No newline at end of file +) + +if get_option('disassembler') + lib_sources += files('disassembler.cc') +endif \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 566f98c..72b8722 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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() diff --git a/tests/cpu/meson.build b/tests/cpu/meson.build index e108b62..1345315 100644 --- a/tests/cpu/meson.build +++ b/tests/cpu/meson.build @@ -1 +1,2 @@ -subdir('arm') \ No newline at end of file +subdir('arm') +subdir('thumb') \ No newline at end of file diff --git a/tests/cpu/thumb/instruction.cc b/tests/cpu/thumb/instruction.cc new file mode 100644 index 0000000..c19465f --- /dev/null +++ b/tests/cpu/thumb/instruction.cc @@ -0,0 +1,439 @@ +#include "cpu/thumb/instruction.hh" +#include + +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(&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(&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(&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(&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(&instruction.data))); + + CHECK(hi->rd == 2); + CHECK(hi->rs == 3); + } + + SECTION("hi rd") { + raw |= 1 << 7; + Instruction instruction(raw); + REQUIRE((hi = std::get_if(&instruction.data))); + + CHECK(hi->rd == 10); + CHECK(hi->rs == 3); + } + + SECTION("hi rs") { + raw |= 1 << 6; + Instruction instruction(raw); + REQUIRE((hi = std::get_if(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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(&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 +} diff --git a/tests/cpu/thumb/meson.build b/tests/cpu/thumb/meson.build new file mode 100644 index 0000000..020125c --- /dev/null +++ b/tests/cpu/thumb/meson.build @@ -0,0 +1,3 @@ +tests_sources += files( + 'instruction.cc' +) \ No newline at end of file