From dd9dd5f1160f6630c05dde28f64835c88dcf5b9f Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Sun, 17 Sep 2023 09:50:32 +0530 Subject: [PATCH] tests: complete disassembler tests Signed-off-by: Amneesh Singh --- .github/workflows/main.yml | 11 +- README | 1 - README.md | 22 +++ flake.lock | 8 +- flake.nix | 37 +++-- include/cpu/cpu.hh | 2 +- include/cpu/instruction.hh | 5 +- meson.build | 19 ++- meson_options.txt | 1 + nix/catch2.nix | 17 --- src/cpu/cpu.cc | 8 +- src/cpu/instruction.cc | 21 ++- src/cpu/psr.cc | 2 + src/cpu/utility.cc | 16 +- src/memory.cc | 2 +- src/meson.build | 3 +- tests/cpu/instruction.cc | 304 ++++++++++++++++++++++++++++++++++--- 17 files changed, 389 insertions(+), 90 deletions(-) delete mode 100644 README create mode 100644 README.md create mode 100644 meson_options.txt delete mode 100644 nix/catch2.nix diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a759924..b0bafa1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,14 +15,17 @@ jobs: auto-optimise-store = true experimental-features = nix-command flakes - - name: meson build + - name: setup run: nix develop -c meson setup $BUILDDIR - - name: clang-format check + - name: fmt run: nix develop -c ninja clang-format-check -C $BUILDDIR - - name: clang-tidy check + - name: lint run: nix develop -c ninja clang-tidy -C $BUILDDIR - - name: ninja compile + - name: tests + run: nix develop -c ninja test -C $BUILDDIR + + - name: build run: nix develop -c ninja -C $BUILDDIR diff --git a/README b/README deleted file mode 100644 index 8de16dc..0000000 --- a/README +++ /dev/null @@ -1 +0,0 @@ -nothing to be seen here yet. LEAVE \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b6a91f --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +nothing to be seen here yet. LEAVE + +But if you are curious (probably not), read ahead + +# Dependencies +## Tested toolchains + +- LLVM 16.0.6 +- GCC 12.3.0 + +In theory, any toolchain supporting at least the C++20 standard should work. +I am using LLVM's clang and libcxx as the primary toolchain. + +## Static libraries + +| Name | Version | Required? | +|:------:|:----------|:---------:| +| fmt | >= 10.1.1 | yes | +| catch2 | >= 3.4 | for tests | + +This goes without saying but using a different toolchain to compile these libraries before linking probably won't work. +I will add meson wrap support once LLVM 17 is out, since I want to get rid of fmt. diff --git a/flake.lock b/flake.lock index 4fc8bba..31ab479 100644 --- a/flake.lock +++ b/flake.lock @@ -2,16 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1692007866, - "narHash": "sha256-X8w0vPZjZxMm68VCwh/BHDoKRGp+BgzQ6w7Nkif6IVM=", + "lastModified": 1694911158, + "narHash": "sha256-5WENkcO8O5SuA5pozpVppLGByWfHVv/1wOWgB2+TfV4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "de2b8ddf94d6cc6161b7659649594c79bd66c13b", + "rev": "46423a1a750594236673c1d741def4e93cf5a8f7", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixpkgs-unstable", + "ref": "master", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 8f5f015..9aca1f9 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,10 @@ { description = "matar"; + inputs = { - nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable; + nixpkgs.url = github:nixos/nixpkgs/master; }; + outputs = { self, nixpkgs }: let systems = [ @@ -21,22 +23,30 @@ llvm = pkgs.llvmPackages_16; stdenv = llvm.libcxxStdenv; - # packages - catch2_v3 = pkgs.callPackage ./nix/catch2.nix { inherit stdenv; }; + # TODO: this is ugly #dependencies - nativeBuildInputs = with pkgs; [ - meson - ninja + nativeBuildInputs = with pkgs; + [ + meson + ninja - # libraries - pkg-config - fmt.dev - catch2_v3.dev - ]; + # libraries + pkg-config + cmake + + ((pkgs.fmt.override { + inherit stdenv; + enableShared = false; + }).overrideAttrs (oa: { + cmakeFlags = oa.cmakeFlags ++ [ "-DFMT_TEST=off" ]; + })).dev + (catch2_3.override { inherit stdenv; }).out + ]; in rec { packages = rec { + inherit (llvm) libcxxabi; matar = stdenv.mkDerivation rec { name = "matar"; version = "0.1"; @@ -44,6 +54,7 @@ ".hh" ".cc" ".build" + "meson_options.txt" ]; outputs = [ "out" "dev" ]; @@ -58,9 +69,7 @@ matar = pkgs.mkShell.override { inherit stdenv; } { name = "matar"; packages = nativeBuildInputs ++ (with pkgs; [ - llvm.libcxx - - # dev tools + # lsp clang-tools_16 ]); }; diff --git a/include/cpu/cpu.hh b/include/cpu/cpu.hh index b5e3bb9..041a078 100644 --- a/include/cpu/cpu.hh +++ b/include/cpu/cpu.hh @@ -35,7 +35,7 @@ class Cpu { bool is_flushed; void chg_mode(const Mode to); - void exec_arm(const arm::ArmInstruction instruction); + void exec_arm(const arm::Instruction instruction); struct { std::array fiq; diff --git a/include/cpu/instruction.hh b/include/cpu/instruction.hh index 8889abe..150eaca 100644 --- a/include/cpu/instruction.hh +++ b/include/cpu/instruction.hh @@ -64,7 +64,6 @@ struct HalfwordTransfer { uint8_t rn; bool load; bool write; - bool byte; bool imm; bool up; bool pre; @@ -152,11 +151,11 @@ using InstructionData = std::variant; -struct ArmInstruction { +struct Instruction { Condition condition; InstructionData data; - ArmInstruction(uint32_t insn); + Instruction(uint32_t insn); std::string disassemble(); }; } diff --git a/meson.build b/meson.build index e544e9c..585ea74 100644 --- a/meson.build +++ b/meson.build @@ -3,21 +3,26 @@ project('matar', 'cpp', license : 'GPLv3', default_options : ['warning_level=3', 'werror=true', - 'optimization=3']) + 'optimization=3', + 'cpp_std=c++20']) compiler = meson.get_compiler('cpp') -if compiler.has_argument('-std=c++23') +''' +TODO: use and instead of libfmt once LLVM 17 is out + +if compiler.has_argument('-std=c++2c') + add_global_arguments('-std=c++2c', language: 'cpp') +elif compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') +elif compiler.has_argument('-std=c++20') + add_global_arguments('-std=c++20', language: 'cpp') else error(compiler.get_id() + ' ' + compiler.version() + 'does not meet the compiler requirements') endif -''' -TODO: use and instead of libfmt once LLVM 17 is out - if compiler.has_argument('-fexperimental-library') add_global_arguments('-fexperimental-library', language: 'cpp') else @@ -31,4 +36,6 @@ subdir('include') subdir('src') subdir('apps') -subdir('tests') +if get_option('tests') + subdir('tests') +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..113ea48 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('tests', type : 'boolean', value : true, description: 'enable tests') diff --git a/nix/catch2.nix b/nix/catch2.nix deleted file mode 100644 index e9e0255..0000000 --- a/nix/catch2.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ stdenv, fetchFromGitHub, meson, ninja }: - -stdenv.mkDerivation rec { - name = "catch2"; - version = "3.4.0"; - - src = fetchFromGitHub { - owner = "catchorg"; - repo = "Catch2"; - rev = "v${version}"; - sha256 = "sha256-DqGGfNjKPW9HFJrX9arFHyNYjB61uoL6NabZatTWrr0="; - }; - - nativeBuildInputs = [ meson ninja ]; - - outputs = [ "out" "dev" ]; -} diff --git a/src/cpu/cpu.cc b/src/cpu/cpu.cc index 5366ffb..bc03dd7 100644 --- a/src/cpu/cpu.cc +++ b/src/cpu/cpu.cc @@ -116,7 +116,7 @@ Cpu::chg_mode(const Mode to) { } void -Cpu::exec_arm(const arm::ArmInstruction instruction) { +Cpu::exec_arm(const arm::Instruction instruction) { auto cond = instruction.condition; auto data = instruction.data; @@ -470,8 +470,8 @@ Cpu::exec_arm(const arm::ArmInstruction instruction) { if (cpsr.mode() != Mode::User) { psr.set_all(gpr[data.operand]); - break; } + break; case PsrTransfer::Type::Msr_flg: psr.set_n(get_bit(data.operand, 31)); psr.set_z(get_bit(data.operand, 30)); @@ -648,7 +648,7 @@ Cpu::exec_arm(const arm::ArmInstruction instruction) { debug(zero); debug(negative); - auto set_conditions = [=]() { + auto set_conditions = [this, carry, overflow, negative, zero]() { cpsr.set_c(carry); cpsr.set_v(overflow); cpsr.set_n(negative); @@ -693,7 +693,7 @@ Cpu::step() { if (cpsr.state() == State::Arm) { debug(cur_pc); uint32_t x = bus->read_word(cur_pc); - arm::ArmInstruction instruction(x); + arm::Instruction instruction(x); log_info("{:#034b}", x); exec_arm(instruction); diff --git a/src/cpu/instruction.cc b/src/cpu/instruction.cc index af7cfff..b5e503c 100644 --- a/src/cpu/instruction.cc +++ b/src/cpu/instruction.cc @@ -5,7 +5,7 @@ using namespace arm; -ArmInstruction::ArmInstruction(uint32_t insn) +Instruction::Instruction(uint32_t insn) : condition(static_cast(bit_range(insn, 28, 31))) { // Branch and exhcange if ((insn & 0x0FFFFFF0) == 0x012FFF10) { @@ -164,10 +164,9 @@ ArmInstruction::ArmInstruction(uint32_t insn) .type = PsrTransfer::Type::Mrs, .imm = 0 }; } else if ((opcode == OpCode::TEQ || opcode == OpCode::CMN) && !set) { - bool imm = get_bit(insn, 25); uint32_t operand = 0; - if (imm) { + if (!imm) { operand = bit_range(insn, 0, 3); } else { uint32_t immediate = bit_range(insn, 0, 7); @@ -185,12 +184,11 @@ ArmInstruction::ArmInstruction(uint32_t insn) } else { std::variant operand; - if (imm) { + if (!imm) { uint32_t immediate = bit_range(insn, 0, 7); uint8_t rotate = bit_range(insn, 8, 11); operand = std::rotr(immediate, rotate * 2); - } else { uint8_t rm = bit_range(insn, 0, 3); bool reg = get_bit(insn, 4); @@ -240,7 +238,7 @@ ArmInstruction::ArmInstruction(uint32_t insn) // Coprocessor data operation } else if ((insn & 0x0F000010) == 0x0E000000) { - uint8_t crm = bit_range(insn, 0, 4); + uint8_t crm = bit_range(insn, 0, 3); uint8_t cp = bit_range(insn, 5, 7); uint8_t cpn = bit_range(insn, 8, 11); uint8_t crd = bit_range(insn, 12, 15); @@ -256,7 +254,7 @@ ArmInstruction::ArmInstruction(uint32_t insn) // Coprocessor register transfer } else if ((insn & 0x0F000010) == 0x0E000010) { - uint8_t crm = bit_range(insn, 0, 4); + uint8_t crm = bit_range(insn, 0, 3); uint8_t cp = bit_range(insn, 5, 7); uint8_t cpn = bit_range(insn, 8, 11); uint8_t rd = bit_range(insn, 12, 15); @@ -277,8 +275,7 @@ ArmInstruction::ArmInstruction(uint32_t insn) } std::string -ArmInstruction::disassemble() { - static const std::string undefined = "UNDEFINED"; +Instruction::disassemble() { // goddamn this is gore // TODO: make this less ugly return std::visit( @@ -319,7 +316,7 @@ ArmInstruction::disassemble() { data.rm, data.rs); }, - [](Undefined) { return undefined; }, + [](Undefined) { return std::string("UND"); }, [this](SingleDataSwap& data) { return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]", condition, @@ -485,7 +482,7 @@ ArmInstruction::disassemble() { data.cp); }, [this](CoprocessorRegisterTransfer& data) { - return fmt::format("{}{} p{},{},c{},c{},c{},{}", + return fmt::format("{}{} p{},{},R{},c{},c{},{}", (data.load ? "MRC" : "MCR"), condition, data.cpn, @@ -495,6 +492,6 @@ ArmInstruction::disassemble() { data.crm, data.cp); }, - [](auto) { return undefined; } }, + [](auto) { return std::string("unknown instruction"); } }, data); } diff --git a/src/cpu/psr.cc b/src/cpu/psr.cc index bbdf635..c3070b1 100644 --- a/src/cpu/psr.cc +++ b/src/cpu/psr.cc @@ -92,4 +92,6 @@ Psr::condition(Condition cond) const { case Condition::AL: return true; } + + return false; } diff --git a/src/cpu/utility.cc b/src/cpu/utility.cc index cec5def..e4dc55b 100644 --- a/src/cpu/utility.cc +++ b/src/cpu/utility.cc @@ -69,6 +69,8 @@ operator<<(std::ostream& os, const OpCode opcode) { uint32_t eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { + uint32_t eval = 0; + switch (shift_type) { case ShiftType::LSL: @@ -77,7 +79,8 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { else if (amount > 32) carry = 0; - return value << amount; + eval = value << amount; + break; case ShiftType::LSR: if (amount > 0 && amount <= 32) @@ -87,7 +90,8 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { else carry = get_bit(value, 31); - return value >> amount; + eval = value >> amount; + break; case ShiftType::ASR: if (amount > 0 && amount <= 32) carry = get_bit(value, amount - 1); @@ -95,17 +99,21 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { carry = get_bit(value, 31); return static_cast(value) >> amount; + break; case ShiftType::ROR: if (amount == 0) { bool old_carry = carry; carry = get_bit(value, 0); - return (value >> 1) | (old_carry << 31); + eval = (value >> 1) | (old_carry << 31); } else { carry = get_bit(value, (amount % 32 + 31) % 32); - return std::rotr(value, amount); + eval = std::rotr(value, amount); } + break; } + + return eval; } std::ostream& diff --git a/src/memory.cc b/src/memory.cc index e1e8a5f..459921a 100644 --- a/src/memory.cc +++ b/src/memory.cc @@ -58,7 +58,7 @@ Memory::read(size_t address) const { return rom[address - ROM_2_START]; } else { log_error("Invalid memory region accessed"); - return 0; + return 0xFF; } } diff --git a/src/meson.build b/src/meson.build index 29253c8..d8865d8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,8 +11,7 @@ lib = library( lib_sources, dependencies: [fmt], include_directories: inc, - install: true, - cpp_args: '-DFMT_HEADER_ONLY' + install: true ) import('pkgconfig').generate(lib) diff --git a/tests/cpu/instruction.cc b/tests/cpu/instruction.cc index a5cbe85..dbb3f54 100644 --- a/tests/cpu/instruction.cc +++ b/tests/cpu/instruction.cc @@ -1,15 +1,15 @@ #include "cpu/instruction.hh" #include "cpu/utility.hh" #include -#include +#include -static constexpr auto TAG = "disassembler"; +[[maybe_unused]] static constexpr auto TAG = "disassembler"; using namespace arm; TEST_CASE("Branch and Exchange", TAG) { uint32_t raw = 0b11000001001011111111111100011010; - ArmInstruction instruction(raw); + Instruction instruction(raw); BranchAndExchange* bx = nullptr; REQUIRE((bx = std::get_if(&instruction.data))); @@ -22,7 +22,7 @@ TEST_CASE("Branch and Exchange", TAG) { TEST_CASE("Branch", TAG) { uint32_t raw = 0b11101011100001010111111111000011; - ArmInstruction instruction(raw); + Instruction instruction(raw); Branch* b = nullptr; REQUIRE((b = std::get_if(&instruction.data))); @@ -42,7 +42,7 @@ TEST_CASE("Branch", TAG) { TEST_CASE("Multiply", TAG) { uint32_t raw = 0b00000000001110101110111110010000; - ArmInstruction instruction(raw); + Instruction instruction(raw); Multiply* mul = nullptr; REQUIRE((mul = std::get_if(&instruction.data))); @@ -64,7 +64,7 @@ TEST_CASE("Multiply", TAG) { TEST_CASE("Multiply Long", TAG) { uint32_t raw = 0b00010000100111100111011010010010; - ArmInstruction instruction(raw); + Instruction instruction(raw); MultiplyLong* mull = nullptr; REQUIRE((mull = std::get_if(&instruction.data))); @@ -88,9 +88,19 @@ TEST_CASE("Multiply Long", TAG) { REQUIRE(instruction.disassemble() == "UMLALNE R7,R14,R2,R6"); } +TEST_CASE("Undefined", TAG) { + // notice how this is the same as single data transfer except the shift + // is now a register based shift + uint32_t raw = 0b11100111101000101010111100010110; + Instruction instruction(raw); + + REQUIRE(instruction.condition == Condition::AL); + REQUIRE(instruction.disassemble() == "UND"); +} + TEST_CASE("Single Data Swap", TAG) { uint32_t raw = 0b10100001000010010101000010010110; - ArmInstruction instruction(raw); + Instruction instruction(raw); SingleDataSwap* swp = nullptr; REQUIRE((swp = std::get_if(&instruction.data))); @@ -109,7 +119,7 @@ TEST_CASE("Single Data Swap", TAG) { TEST_CASE("Single Data Transfer", TAG) { uint32_t raw = 0b11100111101000101010111100000110; - ArmInstruction instruction(raw); + Instruction instruction(raw); SingleDataTransfer* ldr = nullptr; Shift* shift = nullptr; @@ -148,7 +158,7 @@ TEST_CASE("Single Data Transfer", TAG) { TEST_CASE("Halfword Transfer", TAG) { uint32_t raw = 0b00110001101011110010000010110110; - ArmInstruction instruction(raw); + Instruction instruction(raw); HalfwordTransfer* ldr = nullptr; REQUIRE((ldr = std::get_if(&instruction.data))); @@ -179,20 +189,280 @@ TEST_CASE("Halfword Transfer", TAG) { ldr->half = false; REQUIRE(instruction.disassemble() == "LDRCCSB R2,[R15],-R6"); + ldr->load = false; // not a register anymore - ldr->load = false; ldr->imm = 1; ldr->offset = 90; REQUIRE(instruction.disassemble() == "STRCCSB R2,[R15],-#90"); } -TEST_CASE("Undefined", TAG) { - // notice how this is the same as single data transfer except the shift is - // now a register based shift - uint32_t raw = 0b11100111101000101010111100010110; +TEST_CASE("Block Data Transfer", TAG) { + uint32_t raw = 0b10011001010101110100000101101101; + Instruction instruction(raw); + BlockDataTransfer* ldm = nullptr; - REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED"); + REQUIRE((ldm = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::LS); - raw = 0b11100110000000000000000000010000; - REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED"); + { + uint16_t regs = 0; + regs |= 1 << 0; + regs |= 1 << 2; + regs |= 1 << 3; + regs |= 1 << 5; + regs |= 1 << 6; + regs |= 1 << 8; + regs |= 1 << 14; + + REQUIRE(ldm->regs == regs); + } + + REQUIRE(ldm->rn == 7); + REQUIRE(ldm->load == true); + REQUIRE(ldm->write == false); + REQUIRE(ldm->s == true); + REQUIRE(ldm->up == false); + REQUIRE(ldm->pre == true); + + REQUIRE(instruction.disassemble() == "LDMLSDB R7,{R0,R2,R3,R5,R6,R8,R14}^"); + + ldm->write = true; + ldm->s = false; + ldm->up = true; + + REQUIRE(instruction.disassemble() == "LDMLSIB R7!,{R0,R2,R3,R5,R6,R8,R14}"); + + ldm->regs &= ~(1 << 6); + ldm->regs &= ~(1 << 3); + ldm->regs &= ~(1 << 8); + ldm->load = false; + ldm->pre = false; + + REQUIRE(instruction.disassemble() == "STMLSIA R7!,{R0,R2,R5,R14}"); +} + +TEST_CASE("PSR Transfer", TAG) { + PsrTransfer* msr = nullptr; + + SECTION("MRS") { + uint32_t raw = 0b01000001010011111010000000000000; + Instruction instruction(raw); + PsrTransfer* mrs = nullptr; + + REQUIRE((mrs = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::MI); + + REQUIRE(mrs->type == PsrTransfer::Type::Mrs); + // Operand is a register in the case of MRS (PSR -> Register) + REQUIRE(mrs->operand == 10); + REQUIRE(mrs->spsr == true); + + REQUIRE(instruction.disassemble() == "MRSMI R10,SPSR_all"); + } + + SECTION("MSR") { + uint32_t raw = 0b11100001001010011111000000001000; + Instruction instruction(raw); + PsrTransfer* msr = nullptr; + + REQUIRE((msr = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::AL); + + REQUIRE(msr->type == PsrTransfer::Type::Msr); + // Operand is a register in the case of MSR (Register -> PSR) + REQUIRE(msr->operand == 8); + REQUIRE(msr->spsr == false); + + REQUIRE(instruction.disassemble() == "MSR CPSR_all,R8"); + } + + SECTION("MSR_flg with register operand") { + uint32_t raw = 0b01100001001010001111000000001000; + Instruction instruction(raw); + + REQUIRE((msr = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::VS); + + REQUIRE(msr->type == PsrTransfer::Type::Msr_flg); + REQUIRE(msr->imm == 0); + REQUIRE(msr->operand == 8); + REQUIRE(msr->spsr == false); + + REQUIRE(instruction.disassemble() == "MSRVS CPSR_flg,R8"); + } + + SECTION("MSR_flg with immediate operand") { + uint32_t raw = 0b11100011011010001111011101101000; + Instruction instruction(raw); + + REQUIRE((msr = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::AL); + + REQUIRE(msr->type == PsrTransfer::Type::Msr_flg); + REQUIRE(msr->imm == 1); + + // 104 (32 bits) rotated by 2 * 7 + REQUIRE(msr->operand == 27262976); + REQUIRE(msr->spsr == true); + + REQUIRE(instruction.disassemble() == "MSR SPSR_flg,#27262976"); + } +} + +TEST_CASE("Data Processing", TAG) { + uint32_t raw = 0b11100010000111100111101101100001; + Instruction instruction(raw); + DataProcessing* alu = nullptr; + Shift* shift = nullptr; + + REQUIRE((alu = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::AL); + + // operand 2 is a shifted register + REQUIRE((shift = std::get_if(&alu->operand))); + REQUIRE(shift->rm == 1); + REQUIRE(shift->data.immediate == true); + REQUIRE(shift->data.type == ShiftType::ROR); + REQUIRE(shift->data.operand == 22); + + REQUIRE(alu->rd == 7); + REQUIRE(alu->rn == 14); + REQUIRE(alu->set == true); + REQUIRE(alu->opcode == OpCode::AND); + + REQUIRE(instruction.disassemble() == "ANDS R7,R14,R1,ROR #22"); + + shift->data.immediate = false; + shift->data.operand = 2; + alu->set = false; + + REQUIRE(instruction.disassemble() == "AND R7,R14,R1,ROR R2"); + + alu->operand = static_cast(3300012); + REQUIRE(instruction.disassemble() == "AND R7,R14,#3300012"); + + SECTION("set-only operations") { + alu->set = true; + + alu->opcode = OpCode::TST; + REQUIRE(instruction.disassemble() == "TST R14,#3300012"); + + alu->opcode = OpCode::TEQ; + REQUIRE(instruction.disassemble() == "TEQ R14,#3300012"); + + alu->opcode = OpCode::CMP; + REQUIRE(instruction.disassemble() == "CMP R14,#3300012"); + + alu->opcode = OpCode::CMN; + REQUIRE(instruction.disassemble() == "CMN R14,#3300012"); + } + + SECTION("destination operations") { + alu->opcode = OpCode::EOR; + REQUIRE(instruction.disassemble() == "EOR R7,R14,#3300012"); + + alu->opcode = OpCode::SUB; + REQUIRE(instruction.disassemble() == "SUB R7,R14,#3300012"); + + alu->opcode = OpCode::RSB; + REQUIRE(instruction.disassemble() == "RSB R7,R14,#3300012"); + + alu->opcode = OpCode::SUB; + REQUIRE(instruction.disassemble() == "SUB R7,R14,#3300012"); + + alu->opcode = OpCode::ADC; + REQUIRE(instruction.disassemble() == "ADC R7,R14,#3300012"); + + alu->opcode = OpCode::SBC; + REQUIRE(instruction.disassemble() == "SBC R7,R14,#3300012"); + + alu->opcode = OpCode::RSC; + REQUIRE(instruction.disassemble() == "RSC R7,R14,#3300012"); + + alu->opcode = OpCode::ORR; + REQUIRE(instruction.disassemble() == "ORR R7,R14,#3300012"); + + alu->opcode = OpCode::MOV; + REQUIRE(instruction.disassemble() == "MOV R7,#3300012"); + + alu->opcode = OpCode::BIC; + REQUIRE(instruction.disassemble() == "BIC R7,R14,#3300012"); + + alu->opcode = OpCode::MVN; + REQUIRE(instruction.disassemble() == "MVN R7,#3300012"); + } +} + +TEST_CASE("Coprocessor Data Transfer", TAG) { + uint32_t raw = 0b10101101101001011111000101000110; + Instruction instruction(raw); + CoprocessorDataTransfer* ldc = nullptr; + + REQUIRE((ldc = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::GE); + + REQUIRE(ldc->offset == 70); + REQUIRE(ldc->cpn == 1); + REQUIRE(ldc->crd == 15); + REQUIRE(ldc->rn == 5); + REQUIRE(ldc->load == false); + REQUIRE(ldc->write == true); + REQUIRE(ldc->len == false); + REQUIRE(ldc->up == true); + REQUIRE(ldc->pre == true); + + REQUIRE(instruction.disassemble() == "STCGE p1,c15,[R5,#70]!"); + + ldc->load = true; + ldc->pre = false; + ldc->write = false; + ldc->len = true; + + REQUIRE(instruction.disassemble() == "LDCGEL p1,c15,[R5],#70"); +} + +TEST_CASE("Coprocessor Operand Operation", TAG) { + uint32_t raw = 0b11101110101001011111000101000110; + Instruction instruction(raw); + CoprocessorDataOperation* cdp = nullptr; + + REQUIRE((cdp = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::AL); + + REQUIRE(cdp->crm == 6); + REQUIRE(cdp->cp == 2); + REQUIRE(cdp->cpn == 1); + REQUIRE(cdp->crd == 15); + REQUIRE(cdp->crn == 5); + REQUIRE(cdp->cp_opc == 10); + + REQUIRE(instruction.disassemble() == "CDP p1,10,c15,c5,c6,2"); +} + +TEST_CASE("Coprocessor Register Transfer", TAG) { + uint32_t raw = 0b11101110101001011111000101010110; + Instruction instruction(raw); + CoprocessorRegisterTransfer* mrc = nullptr; + + REQUIRE( + (mrc = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::AL); + + REQUIRE(mrc->crm == 6); + REQUIRE(mrc->cp == 2); + REQUIRE(mrc->cpn == 1); + REQUIRE(mrc->rd == 15); + REQUIRE(mrc->crn == 5); + REQUIRE(mrc->load == false); + REQUIRE(mrc->cp_opc == 5); + + REQUIRE(instruction.disassemble() == "MCR p1,5,R15,c5,c6,2"); +} + +TEST_CASE("Software Interrupt", TAG) { + uint32_t raw = 0b00001111101010101010101010101010; + Instruction instruction(raw); + + REQUIRE(instruction.condition == Condition::EQ); + REQUIRE(instruction.disassemble() == "SWIEQ"); }