tests: complete disassembler tests

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-09-17 09:50:32 +05:30
parent be7deb349a
commit dd9dd5f116
17 changed files with 389 additions and 90 deletions

View File

@@ -15,14 +15,17 @@ jobs:
auto-optimise-store = true auto-optimise-store = true
experimental-features = nix-command flakes experimental-features = nix-command flakes
- name: meson build - name: setup
run: nix develop -c meson setup $BUILDDIR run: nix develop -c meson setup $BUILDDIR
- name: clang-format check - name: fmt
run: nix develop -c ninja clang-format-check -C $BUILDDIR 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 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 run: nix develop -c ninja -C $BUILDDIR

1
README
View File

@@ -1 +0,0 @@
nothing to be seen here yet. LEAVE

22
README.md Normal file
View File

@@ -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.

8
flake.lock generated
View File

@@ -2,16 +2,16 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1692007866, "lastModified": 1694911158,
"narHash": "sha256-X8w0vPZjZxMm68VCwh/BHDoKRGp+BgzQ6w7Nkif6IVM=", "narHash": "sha256-5WENkcO8O5SuA5pozpVppLGByWfHVv/1wOWgB2+TfV4=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "de2b8ddf94d6cc6161b7659649594c79bd66c13b", "rev": "46423a1a750594236673c1d741def4e93cf5a8f7",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "nixpkgs-unstable", "ref": "master",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@@ -1,8 +1,10 @@
{ {
description = "matar"; description = "matar";
inputs = { inputs = {
nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable; nixpkgs.url = github:nixos/nixpkgs/master;
}; };
outputs = { self, nixpkgs }: outputs = { self, nixpkgs }:
let let
systems = [ systems = [
@@ -21,22 +23,30 @@
llvm = pkgs.llvmPackages_16; llvm = pkgs.llvmPackages_16;
stdenv = llvm.libcxxStdenv; stdenv = llvm.libcxxStdenv;
# packages
catch2_v3 = pkgs.callPackage ./nix/catch2.nix { inherit stdenv; };
# TODO: this is ugly
#dependencies #dependencies
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs;
meson [
ninja meson
ninja
# libraries # libraries
pkg-config pkg-config
fmt.dev cmake
catch2_v3.dev
]; ((pkgs.fmt.override {
inherit stdenv;
enableShared = false;
}).overrideAttrs (oa: {
cmakeFlags = oa.cmakeFlags ++ [ "-DFMT_TEST=off" ];
})).dev
(catch2_3.override { inherit stdenv; }).out
];
in in
rec { rec {
packages = rec { packages = rec {
inherit (llvm) libcxxabi;
matar = stdenv.mkDerivation rec { matar = stdenv.mkDerivation rec {
name = "matar"; name = "matar";
version = "0.1"; version = "0.1";
@@ -44,6 +54,7 @@
".hh" ".hh"
".cc" ".cc"
".build" ".build"
"meson_options.txt"
]; ];
outputs = [ "out" "dev" ]; outputs = [ "out" "dev" ];
@@ -58,9 +69,7 @@
matar = pkgs.mkShell.override { inherit stdenv; } { matar = pkgs.mkShell.override { inherit stdenv; } {
name = "matar"; name = "matar";
packages = nativeBuildInputs ++ (with pkgs; [ packages = nativeBuildInputs ++ (with pkgs; [
llvm.libcxx # lsp
# dev tools
clang-tools_16 clang-tools_16
]); ]);
}; };

View File

@@ -35,7 +35,7 @@ class Cpu {
bool is_flushed; bool is_flushed;
void chg_mode(const Mode to); void chg_mode(const Mode to);
void exec_arm(const arm::ArmInstruction instruction); void exec_arm(const arm::Instruction instruction);
struct { struct {
std::array<uint32_t, GPR_COUNT - GPR_FIQ_FIRST - 1> fiq; std::array<uint32_t, GPR_COUNT - GPR_FIQ_FIRST - 1> fiq;

View File

@@ -64,7 +64,6 @@ struct HalfwordTransfer {
uint8_t rn; uint8_t rn;
bool load; bool load;
bool write; bool write;
bool byte;
bool imm; bool imm;
bool up; bool up;
bool pre; bool pre;
@@ -152,11 +151,11 @@ using InstructionData = std::variant<BranchAndExchange,
Undefined, Undefined,
SoftwareInterrupt>; SoftwareInterrupt>;
struct ArmInstruction { struct Instruction {
Condition condition; Condition condition;
InstructionData data; InstructionData data;
ArmInstruction(uint32_t insn); Instruction(uint32_t insn);
std::string disassemble(); std::string disassemble();
}; };
} }

View File

@@ -3,21 +3,26 @@ project('matar', 'cpp',
license : 'GPLv3', license : 'GPLv3',
default_options : ['warning_level=3', default_options : ['warning_level=3',
'werror=true', 'werror=true',
'optimization=3']) 'optimization=3',
'cpp_std=c++20'])
compiler = meson.get_compiler('cpp') compiler = meson.get_compiler('cpp')
if compiler.has_argument('-std=c++23') '''
TODO: use <print> and <format> 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') add_global_arguments('-std=c++23', language: 'cpp')
elif compiler.has_argument('-std=c++2b') elif compiler.has_argument('-std=c++2b')
add_global_arguments('-std=c++2b', language: 'cpp') add_global_arguments('-std=c++2b', language: 'cpp')
elif compiler.has_argument('-std=c++20')
add_global_arguments('-std=c++20', language: 'cpp')
else else
error(compiler.get_id() + ' ' + compiler.version() + 'does not meet the compiler requirements') error(compiler.get_id() + ' ' + compiler.version() + 'does not meet the compiler requirements')
endif endif
'''
TODO: use <print> and <format> instead of libfmt once LLVM 17 is out
if compiler.has_argument('-fexperimental-library') if compiler.has_argument('-fexperimental-library')
add_global_arguments('-fexperimental-library', language: 'cpp') add_global_arguments('-fexperimental-library', language: 'cpp')
else else
@@ -31,4 +36,6 @@ subdir('include')
subdir('src') subdir('src')
subdir('apps') subdir('apps')
subdir('tests') if get_option('tests')
subdir('tests')
endif

1
meson_options.txt Normal file
View File

@@ -0,0 +1 @@
option('tests', type : 'boolean', value : true, description: 'enable tests')

View File

@@ -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" ];
}

View File

@@ -116,7 +116,7 @@ Cpu::chg_mode(const Mode to) {
} }
void void
Cpu::exec_arm(const arm::ArmInstruction instruction) { Cpu::exec_arm(const arm::Instruction instruction) {
auto cond = instruction.condition; auto cond = instruction.condition;
auto data = instruction.data; auto data = instruction.data;
@@ -470,8 +470,8 @@ Cpu::exec_arm(const arm::ArmInstruction instruction) {
if (cpsr.mode() != Mode::User) { if (cpsr.mode() != Mode::User) {
psr.set_all(gpr[data.operand]); psr.set_all(gpr[data.operand]);
break;
} }
break;
case PsrTransfer::Type::Msr_flg: case PsrTransfer::Type::Msr_flg:
psr.set_n(get_bit(data.operand, 31)); psr.set_n(get_bit(data.operand, 31));
psr.set_z(get_bit(data.operand, 30)); psr.set_z(get_bit(data.operand, 30));
@@ -648,7 +648,7 @@ Cpu::exec_arm(const arm::ArmInstruction instruction) {
debug(zero); debug(zero);
debug(negative); debug(negative);
auto set_conditions = [=]() { auto set_conditions = [this, carry, overflow, negative, zero]() {
cpsr.set_c(carry); cpsr.set_c(carry);
cpsr.set_v(overflow); cpsr.set_v(overflow);
cpsr.set_n(negative); cpsr.set_n(negative);
@@ -693,7 +693,7 @@ Cpu::step() {
if (cpsr.state() == State::Arm) { if (cpsr.state() == State::Arm) {
debug(cur_pc); debug(cur_pc);
uint32_t x = bus->read_word(cur_pc); uint32_t x = bus->read_word(cur_pc);
arm::ArmInstruction instruction(x); arm::Instruction instruction(x);
log_info("{:#034b}", x); log_info("{:#034b}", x);
exec_arm(instruction); exec_arm(instruction);

View File

@@ -5,7 +5,7 @@
using namespace arm; using namespace arm;
ArmInstruction::ArmInstruction(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
if ((insn & 0x0FFFFFF0) == 0x012FFF10) { if ((insn & 0x0FFFFFF0) == 0x012FFF10) {
@@ -164,10 +164,9 @@ ArmInstruction::ArmInstruction(uint32_t insn)
.type = PsrTransfer::Type::Mrs, .type = PsrTransfer::Type::Mrs,
.imm = 0 }; .imm = 0 };
} else if ((opcode == OpCode::TEQ || opcode == OpCode::CMN) && !set) { } else if ((opcode == OpCode::TEQ || opcode == OpCode::CMN) && !set) {
bool imm = get_bit(insn, 25);
uint32_t operand = 0; uint32_t operand = 0;
if (imm) { if (!imm) {
operand = bit_range(insn, 0, 3); operand = bit_range(insn, 0, 3);
} else { } else {
uint32_t immediate = bit_range(insn, 0, 7); uint32_t immediate = bit_range(insn, 0, 7);
@@ -185,12 +184,11 @@ ArmInstruction::ArmInstruction(uint32_t insn)
} else { } else {
std::variant<Shift, uint32_t> operand; std::variant<Shift, uint32_t> operand;
if (imm) { if (!imm) {
uint32_t immediate = bit_range(insn, 0, 7); uint32_t immediate = bit_range(insn, 0, 7);
uint8_t rotate = bit_range(insn, 8, 11); uint8_t rotate = bit_range(insn, 8, 11);
operand = std::rotr(immediate, rotate * 2); operand = std::rotr(immediate, rotate * 2);
} else { } else {
uint8_t rm = bit_range(insn, 0, 3); uint8_t rm = bit_range(insn, 0, 3);
bool reg = get_bit(insn, 4); bool reg = get_bit(insn, 4);
@@ -240,7 +238,7 @@ ArmInstruction::ArmInstruction(uint32_t insn)
// Coprocessor data operation // Coprocessor data operation
} else if ((insn & 0x0F000010) == 0x0E000000) { } 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 cp = bit_range(insn, 5, 7);
uint8_t cpn = bit_range(insn, 8, 11); uint8_t cpn = bit_range(insn, 8, 11);
uint8_t crd = bit_range(insn, 12, 15); uint8_t crd = bit_range(insn, 12, 15);
@@ -256,7 +254,7 @@ ArmInstruction::ArmInstruction(uint32_t insn)
// Coprocessor register transfer // Coprocessor register transfer
} else if ((insn & 0x0F000010) == 0x0E000010) { } 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 cp = bit_range(insn, 5, 7);
uint8_t cpn = bit_range(insn, 8, 11); uint8_t cpn = bit_range(insn, 8, 11);
uint8_t rd = bit_range(insn, 12, 15); uint8_t rd = bit_range(insn, 12, 15);
@@ -277,8 +275,7 @@ ArmInstruction::ArmInstruction(uint32_t insn)
} }
std::string std::string
ArmInstruction::disassemble() { Instruction::disassemble() {
static const std::string undefined = "UNDEFINED";
// goddamn this is gore // goddamn this is gore
// TODO: make this less ugly // TODO: make this less ugly
return std::visit( return std::visit(
@@ -319,7 +316,7 @@ ArmInstruction::disassemble() {
data.rm, data.rm,
data.rs); data.rs);
}, },
[](Undefined) { return undefined; }, [](Undefined) { return std::string("UND"); },
[this](SingleDataSwap& data) { [this](SingleDataSwap& data) {
return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]", return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]",
condition, condition,
@@ -485,7 +482,7 @@ ArmInstruction::disassemble() {
data.cp); data.cp);
}, },
[this](CoprocessorRegisterTransfer& data) { [this](CoprocessorRegisterTransfer& data) {
return fmt::format("{}{} p{},{},c{},c{},c{},{}", return fmt::format("{}{} p{},{},R{},c{},c{},{}",
(data.load ? "MRC" : "MCR"), (data.load ? "MRC" : "MCR"),
condition, condition,
data.cpn, data.cpn,
@@ -495,6 +492,6 @@ ArmInstruction::disassemble() {
data.crm, data.crm,
data.cp); data.cp);
}, },
[](auto) { return undefined; } }, [](auto) { return std::string("unknown instruction"); } },
data); data);
} }

View File

@@ -92,4 +92,6 @@ Psr::condition(Condition cond) const {
case Condition::AL: case Condition::AL:
return true; return true;
} }
return false;
} }

View File

@@ -69,6 +69,8 @@ operator<<(std::ostream& os, const OpCode opcode) {
uint32_t uint32_t
eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
uint32_t eval = 0;
switch (shift_type) { switch (shift_type) {
case ShiftType::LSL: case ShiftType::LSL:
@@ -77,7 +79,8 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
else if (amount > 32) else if (amount > 32)
carry = 0; carry = 0;
return value << amount; eval = value << amount;
break;
case ShiftType::LSR: case ShiftType::LSR:
if (amount > 0 && amount <= 32) if (amount > 0 && amount <= 32)
@@ -87,7 +90,8 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
else else
carry = get_bit(value, 31); carry = get_bit(value, 31);
return value >> amount; eval = value >> amount;
break;
case ShiftType::ASR: case ShiftType::ASR:
if (amount > 0 && amount <= 32) if (amount > 0 && amount <= 32)
carry = get_bit(value, amount - 1); 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); carry = get_bit(value, 31);
return static_cast<int32_t>(value) >> amount; return static_cast<int32_t>(value) >> amount;
break;
case ShiftType::ROR: case ShiftType::ROR:
if (amount == 0) { if (amount == 0) {
bool old_carry = carry; bool old_carry = carry;
carry = get_bit(value, 0); carry = get_bit(value, 0);
return (value >> 1) | (old_carry << 31); eval = (value >> 1) | (old_carry << 31);
} else { } else {
carry = get_bit(value, (amount % 32 + 31) % 32); carry = get_bit(value, (amount % 32 + 31) % 32);
return std::rotr(value, amount); eval = std::rotr(value, amount);
} }
break;
} }
return eval;
} }
std::ostream& std::ostream&

View File

@@ -58,7 +58,7 @@ Memory::read(size_t address) const {
return rom[address - ROM_2_START]; return rom[address - ROM_2_START];
} else { } else {
log_error("Invalid memory region accessed"); log_error("Invalid memory region accessed");
return 0; return 0xFF;
} }
} }

View File

@@ -11,8 +11,7 @@ lib = library(
lib_sources, lib_sources,
dependencies: [fmt], dependencies: [fmt],
include_directories: inc, include_directories: inc,
install: true, install: true
cpp_args: '-DFMT_HEADER_ONLY'
) )
import('pkgconfig').generate(lib) import('pkgconfig').generate(lib)

View File

@@ -1,15 +1,15 @@
#include "cpu/instruction.hh" #include "cpu/instruction.hh"
#include "cpu/utility.hh" #include "cpu/utility.hh"
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <iostream> #include <cstdint>
static constexpr auto TAG = "disassembler"; [[maybe_unused]] static constexpr auto TAG = "disassembler";
using namespace arm; using namespace arm;
TEST_CASE("Branch and Exchange", TAG) { TEST_CASE("Branch and Exchange", TAG) {
uint32_t raw = 0b11000001001011111111111100011010; uint32_t raw = 0b11000001001011111111111100011010;
ArmInstruction instruction(raw); Instruction instruction(raw);
BranchAndExchange* bx = nullptr; BranchAndExchange* bx = nullptr;
REQUIRE((bx = std::get_if<BranchAndExchange>(&instruction.data))); REQUIRE((bx = std::get_if<BranchAndExchange>(&instruction.data)));
@@ -22,7 +22,7 @@ TEST_CASE("Branch and Exchange", TAG) {
TEST_CASE("Branch", TAG) { TEST_CASE("Branch", TAG) {
uint32_t raw = 0b11101011100001010111111111000011; uint32_t raw = 0b11101011100001010111111111000011;
ArmInstruction instruction(raw); Instruction instruction(raw);
Branch* b = nullptr; Branch* b = nullptr;
REQUIRE((b = std::get_if<Branch>(&instruction.data))); REQUIRE((b = std::get_if<Branch>(&instruction.data)));
@@ -42,7 +42,7 @@ TEST_CASE("Branch", TAG) {
TEST_CASE("Multiply", TAG) { TEST_CASE("Multiply", TAG) {
uint32_t raw = 0b00000000001110101110111110010000; uint32_t raw = 0b00000000001110101110111110010000;
ArmInstruction instruction(raw); Instruction instruction(raw);
Multiply* mul = nullptr; Multiply* mul = nullptr;
REQUIRE((mul = std::get_if<Multiply>(&instruction.data))); REQUIRE((mul = std::get_if<Multiply>(&instruction.data)));
@@ -64,7 +64,7 @@ TEST_CASE("Multiply", TAG) {
TEST_CASE("Multiply Long", TAG) { TEST_CASE("Multiply Long", TAG) {
uint32_t raw = 0b00010000100111100111011010010010; uint32_t raw = 0b00010000100111100111011010010010;
ArmInstruction instruction(raw); Instruction instruction(raw);
MultiplyLong* mull = nullptr; MultiplyLong* mull = nullptr;
REQUIRE((mull = std::get_if<MultiplyLong>(&instruction.data))); REQUIRE((mull = std::get_if<MultiplyLong>(&instruction.data)));
@@ -88,9 +88,19 @@ TEST_CASE("Multiply Long", TAG) {
REQUIRE(instruction.disassemble() == "UMLALNE R7,R14,R2,R6"); 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) { TEST_CASE("Single Data Swap", TAG) {
uint32_t raw = 0b10100001000010010101000010010110; uint32_t raw = 0b10100001000010010101000010010110;
ArmInstruction instruction(raw); Instruction instruction(raw);
SingleDataSwap* swp = nullptr; SingleDataSwap* swp = nullptr;
REQUIRE((swp = std::get_if<SingleDataSwap>(&instruction.data))); REQUIRE((swp = std::get_if<SingleDataSwap>(&instruction.data)));
@@ -109,7 +119,7 @@ TEST_CASE("Single Data Swap", TAG) {
TEST_CASE("Single Data Transfer", TAG) { TEST_CASE("Single Data Transfer", TAG) {
uint32_t raw = 0b11100111101000101010111100000110; uint32_t raw = 0b11100111101000101010111100000110;
ArmInstruction instruction(raw); Instruction instruction(raw);
SingleDataTransfer* ldr = nullptr; SingleDataTransfer* ldr = nullptr;
Shift* shift = nullptr; Shift* shift = nullptr;
@@ -148,7 +158,7 @@ TEST_CASE("Single Data Transfer", TAG) {
TEST_CASE("Halfword Transfer", TAG) { TEST_CASE("Halfword Transfer", TAG) {
uint32_t raw = 0b00110001101011110010000010110110; uint32_t raw = 0b00110001101011110010000010110110;
ArmInstruction instruction(raw); Instruction instruction(raw);
HalfwordTransfer* ldr = nullptr; HalfwordTransfer* ldr = nullptr;
REQUIRE((ldr = std::get_if<HalfwordTransfer>(&instruction.data))); REQUIRE((ldr = std::get_if<HalfwordTransfer>(&instruction.data)));
@@ -179,20 +189,280 @@ TEST_CASE("Halfword Transfer", TAG) {
ldr->half = false; ldr->half = false;
REQUIRE(instruction.disassemble() == "LDRCCSB R2,[R15],-R6"); REQUIRE(instruction.disassemble() == "LDRCCSB R2,[R15],-R6");
ldr->load = false;
// not a register anymore // not a register anymore
ldr->load = false;
ldr->imm = 1; ldr->imm = 1;
ldr->offset = 90; ldr->offset = 90;
REQUIRE(instruction.disassemble() == "STRCCSB R2,[R15],-#90"); REQUIRE(instruction.disassemble() == "STRCCSB R2,[R15],-#90");
} }
TEST_CASE("Undefined", TAG) { TEST_CASE("Block Data Transfer", TAG) {
// notice how this is the same as single data transfer except the shift is uint32_t raw = 0b10011001010101110100000101101101;
// now a register based shift Instruction instruction(raw);
uint32_t raw = 0b11100111101000101010111100010110; BlockDataTransfer* ldm = nullptr;
REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED"); REQUIRE((ldm = std::get_if<BlockDataTransfer>(&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<PsrTransfer>(&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<PsrTransfer>(&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<PsrTransfer>(&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<PsrTransfer>(&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<DataProcessing>(&instruction.data)));
REQUIRE(instruction.condition == Condition::AL);
// operand 2 is a shifted register
REQUIRE((shift = std::get_if<Shift>(&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<uint32_t>(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<CoprocessorDataTransfer>(&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<CoprocessorDataOperation>(&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<CoprocessorRegisterTransfer>(&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");
} }