tests: [WIP] add unit tests for some of the instructions

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-09-15 14:07:23 +05:30
parent aa96237c37
commit be7deb349a
11 changed files with 445 additions and 173 deletions

View File

@@ -6,6 +6,7 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <ostream>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
@@ -80,6 +81,9 @@ main(int argc, const char* argv[]) {
return 1; return 1;
} }
std::flush(std::cout);
std::flush(std::cerr);
{ {
Memory memory(std::move(bios), std::move(rom)); Memory memory(std::move(bios), std::move(rom));
Bus bus(memory); Bus bus(memory);

View File

@@ -8,17 +8,23 @@
systems = [ systems = [
"x86_64-linux" "x86_64-linux"
"aarch64-linux" "aarch64-linux"
# "i686-linux"
]; ];
eachSystem = with nixpkgs.lib; f: foldAttrs mergeAttrs { } eachSystem = with nixpkgs.lib; f: foldAttrs mergeAttrs { }
(map (s: mapAttrs (_: v: { ${s} = v; }) (f s)) systems); (map (s: mapAttrs (_: v: { ${s} = v; }) (f s)) systems);
in in
eachSystem (system: eachSystem (system:
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
# aliases
llvm = pkgs.llvmPackages_16; llvm = pkgs.llvmPackages_16;
stdenv = llvm.libcxxStdenv; stdenv = llvm.libcxxStdenv;
# packages
catch2_v3 = pkgs.callPackage ./nix/catch2.nix { inherit stdenv; };
#dependencies
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
meson meson
ninja ninja
@@ -26,6 +32,7 @@
# libraries # libraries
pkg-config pkg-config
fmt.dev fmt.dev
catch2_v3.dev
]; ];
in in
rec { rec {
@@ -51,6 +58,8 @@
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
# dev tools # 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 ArmInstruction instruction); void exec_arm(const arm::ArmInstruction 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

@@ -9,159 +9,154 @@ struct overloaded : Ts... {
template<class... Ts> template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>; overloaded(Ts...) -> overloaded<Ts...>;
class ArmInstruction { namespace arm {
public: struct BranchAndExchange {
ArmInstruction() = delete; uint8_t rn;
ArmInstruction(uint32_t insn); };
auto get_condition() const { return condition; } struct Branch {
auto get_data() const { return data; } bool link;
uint32_t offset;
};
std::string disassemble(); struct Multiply {
uint8_t rm;
uint8_t rs;
uint8_t rn;
uint8_t rd;
bool set;
bool acc;
};
struct BranchAndExchange { struct MultiplyLong {
uint8_t rn; uint8_t rm;
uint8_t rs;
uint8_t rdlo;
uint8_t rdhi;
bool set;
bool acc;
bool uns;
};
struct SingleDataSwap {
uint8_t rm;
uint8_t rd;
uint8_t rn;
bool byte;
};
struct SingleDataTransfer {
std::variant<uint16_t, Shift> offset;
uint8_t rd;
uint8_t rn;
bool load;
bool write;
bool byte;
bool up;
bool pre;
};
struct HalfwordTransfer {
uint8_t offset;
bool half;
bool sign;
uint8_t rd;
uint8_t rn;
bool load;
bool write;
bool byte;
bool imm;
bool up;
bool pre;
};
struct BlockDataTransfer {
uint16_t regs;
uint8_t rn;
bool load;
bool write;
bool s;
bool up;
bool pre;
};
struct DataProcessing {
std::variant<Shift, uint32_t> operand;
uint8_t rd;
uint8_t rn;
bool set;
OpCode opcode;
};
struct PsrTransfer {
enum class Type {
Mrs,
Msr,
Msr_flg
}; };
struct Branch { uint32_t operand;
bool link; bool spsr;
uint32_t offset; Type type;
}; // ignored outside MSR_flg
bool imm;
};
struct Multiply { struct CoprocessorDataTransfer {
uint8_t rm; uint8_t offset;
uint8_t rs; uint8_t cpn;
uint8_t rn; uint8_t crd;
uint8_t rd; uint8_t rn;
bool set; bool load;
bool acc; bool write;
}; bool len;
bool up;
bool pre;
};
struct MultiplyLong { struct CoprocessorDataOperation {
uint8_t rm; uint8_t crm;
uint8_t rs; uint8_t cp;
uint8_t rdlo; uint8_t cpn;
uint8_t rdhi; uint8_t crd;
bool set; uint8_t crn;
bool acc; uint8_t cp_opc;
bool uns; };
};
struct SingleDataSwap { struct CoprocessorRegisterTransfer {
uint8_t rm; uint8_t crm;
uint8_t rd; uint8_t cp;
uint8_t rn; uint8_t cpn;
bool byte; uint8_t rd;
}; uint8_t crn;
bool load;
uint8_t cp_opc;
};
struct SingleDataTransfer { struct Undefined {};
std::variant<uint16_t, Shift> offset; struct SoftwareInterrupt {};
uint8_t rd;
uint8_t rn;
bool load;
bool write;
bool byte;
bool up;
bool pre;
};
struct HalfwordTransfer { using InstructionData = std::variant<BranchAndExchange,
uint8_t offset; Branch,
bool half; Multiply,
bool sign; MultiplyLong,
uint8_t rd; SingleDataSwap,
uint8_t rn; SingleDataTransfer,
bool load; HalfwordTransfer,
bool write; BlockDataTransfer,
bool byte; DataProcessing,
bool imm; PsrTransfer,
bool up; CoprocessorDataTransfer,
bool pre; CoprocessorDataOperation,
}; CoprocessorRegisterTransfer,
Undefined,
SoftwareInterrupt>;
struct BlockDataTransfer { struct ArmInstruction {
uint16_t regs;
uint8_t rn;
bool load;
bool write;
bool s;
bool up;
bool pre;
};
struct DataProcessing {
std::variant<Shift, uint32_t> operand;
uint8_t rd;
uint8_t rn;
bool set;
OpCode opcode;
};
struct PsrTransfer {
enum class Type {
Mrs,
Msr,
Msr_flg
};
uint32_t operand;
bool spsr;
Type type;
// ignored outside MSR_flg
bool imm;
};
struct CoprocessorDataTransfer {
uint8_t offset;
uint8_t cpn;
uint8_t crd;
uint8_t rn;
bool load;
bool write;
bool len;
bool up;
bool pre;
};
struct CoprocessorDataOperation {
uint8_t crm;
uint8_t cp;
uint8_t cpn;
uint8_t crd;
uint8_t crn;
uint8_t cp_opc;
};
struct CoprocessorRegisterTransfer {
uint8_t crm;
uint8_t cp;
uint8_t cpn;
uint8_t rd;
uint8_t crn;
bool load;
uint8_t cp_opc;
};
struct Undefined {};
struct SoftwareInterrupt {};
using InstructionData = std::variant<BranchAndExchange,
Branch,
Multiply,
MultiplyLong,
SingleDataSwap,
SingleDataTransfer,
HalfwordTransfer,
BlockDataTransfer,
DataProcessing,
PsrTransfer,
CoprocessorDataTransfer,
CoprocessorDataOperation,
CoprocessorRegisterTransfer,
Undefined,
SoftwareInterrupt>;
private:
Condition condition; Condition condition;
InstructionData data; InstructionData data;
ArmInstruction(uint32_t insn);
std::string disassemble();
}; };
}

View File

@@ -3,10 +3,32 @@ 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')
if 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')
else
error(compiler.get_id() + ' ' + compiler.version() + 'does not meet the compiler requirements')
endif
'''
TODO: use <print> and <format> instead of libfmt once LLVM 17 is out
if compiler.has_argument('-fexperimental-library')
add_global_arguments('-fexperimental-library', language: 'cpp')
else
error(compiler.get_id() + ' ' + compiler.version() + 'does not support -fexperimental-library')
endif
'''
inc = include_directories('include') inc = include_directories('include')
subdir('include') subdir('include')
subdir('src') subdir('src')
subdir('apps') subdir('apps')
subdir('tests')

17
nix/catch2.nix Normal file
View File

@@ -0,0 +1,17 @@
{ 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,27 +116,29 @@ Cpu::chg_mode(const Mode to) {
} }
void void
Cpu::exec_arm(const ArmInstruction instruction) { Cpu::exec_arm(const arm::ArmInstruction instruction) {
auto cond = instruction.get_condition(); auto cond = instruction.condition;
auto data = instruction.get_data(); auto data = instruction.data;
if (!cpsr.condition(cond)) { if (!cpsr.condition(cond)) {
return; return;
} }
auto pc_error = [](uint8_t r) { auto pc_error = [](uint8_t r) {
if (r == 15) if (r == PC_INDEX)
log_error("Using PC (R15) as operand register"); log_error("Using PC (R15) as operand register");
}; };
auto pc_warn = [](uint8_t r) { auto pc_warn = [](uint8_t r) {
if (r == 15) if (r == PC_INDEX)
log_warn("Using PC (R15) as operand register"); log_warn("Using PC (R15) as operand register");
}; };
using namespace arm;
std::visit( std::visit(
overloaded{ overloaded{
[this, pc_warn](ArmInstruction::BranchAndExchange& data) { [this, pc_warn](BranchAndExchange& data) {
State state = static_cast<State>(data.rn & 1); State state = static_cast<State>(data.rn & 1);
pc_warn(data.rn); pc_warn(data.rn);
@@ -156,7 +158,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
// pc is affected so flush the pipeline // pc is affected so flush the pipeline
is_flushed = true; is_flushed = true;
}, },
[this](ArmInstruction::Branch& data) { [this](Branch& data) {
if (data.link) if (data.link)
gpr[14] = pc - ARM_INSTRUCTION_SIZE; gpr[14] = pc - ARM_INSTRUCTION_SIZE;
@@ -168,7 +170,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
// pc is affected so flush the pipeline // pc is affected so flush the pipeline
is_flushed = true; is_flushed = true;
}, },
[this, pc_error](ArmInstruction::Multiply& data) { [this, pc_error](Multiply& data) {
if (data.rd == data.rm) if (data.rd == data.rm)
log_error("rd and rm are not distinct in {}", log_error("rd and rm are not distinct in {}",
typeid(data).name()); typeid(data).name());
@@ -186,7 +188,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
cpsr.set_c(0); cpsr.set_c(0);
} }
}, },
[this, pc_error](ArmInstruction::MultiplyLong& data) { [this, pc_error](MultiplyLong& data) {
if (data.rdhi == data.rdlo || data.rdhi == data.rm || if (data.rdhi == data.rdlo || data.rdhi == data.rm ||
data.rdlo == data.rm) data.rdlo == data.rm)
log_error("rdhi, rdlo and rm are not distinct in {}", log_error("rdhi, rdlo and rm are not distinct in {}",
@@ -226,8 +228,8 @@ Cpu::exec_arm(const ArmInstruction instruction) {
cpsr.set_v(0); cpsr.set_v(0);
} }
}, },
[](ArmInstruction::Undefined) { log_warn("Undefined instruction"); }, [](Undefined) { log_warn("Undefined instruction"); },
[this, pc_error](ArmInstruction::SingleDataSwap& data) { [this, pc_error](SingleDataSwap& data) {
pc_error(data.rm); pc_error(data.rm);
pc_error(data.rn); pc_error(data.rn);
pc_error(data.rd); pc_error(data.rd);
@@ -240,7 +242,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
bus->write_word(gpr[data.rn], gpr[data.rm]); bus->write_word(gpr[data.rn], gpr[data.rm]);
} }
}, },
[this, pc_warn, pc_error](ArmInstruction::SingleDataTransfer& data) { [this, pc_warn, pc_error](SingleDataTransfer& data) {
uint32_t offset = 0; uint32_t offset = 0;
uint32_t address = gpr[data.rn]; uint32_t address = gpr[data.rn];
@@ -316,7 +318,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
if (data.rd == PC_INDEX && data.load) if (data.rd == PC_INDEX && data.load)
is_flushed = true; is_flushed = true;
}, },
[this, pc_warn, pc_error](ArmInstruction::HalfwordTransfer& data) { [this, pc_warn, pc_error](HalfwordTransfer& data) {
uint32_t address = gpr[data.rn]; uint32_t address = gpr[data.rn];
if (!data.pre && data.write) if (!data.pre && data.write)
@@ -345,15 +347,16 @@ Cpu::exec_arm(const ArmInstruction instruction) {
gpr[data.rd] = bus->read_halfword(address); gpr[data.rd] = bus->read_halfword(address);
// sign extend the halfword // sign extend the halfword
if (get_bit(gpr[data.rd], PC_INDEX)) gpr[data.rd] =
gpr[data.rd] |= 0xFFFF0000; (static_cast<int32_t>(gpr[data.rd]) << 16) >> 16;
// byte // byte
} else { } else {
gpr[data.rd] = bus->read_byte(address); gpr[data.rd] = bus->read_byte(address);
// sign extend the byte // sign extend the byte
if (get_bit(gpr[data.rd], 7)) gpr[data.rd] =
gpr[data.rd] |= 0xFFFFFF00; (static_cast<int32_t>(gpr[data.rd]) << 24) >> 24;
} }
// unsigned halfword // unsigned halfword
} else if (data.half) { } else if (data.half) {
@@ -379,7 +382,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
if (data.rd == PC_INDEX && data.load) if (data.rd == PC_INDEX && data.load)
is_flushed = true; is_flushed = true;
}, },
[this, pc_error](ArmInstruction::BlockDataTransfer& data) { [this, pc_error](BlockDataTransfer& data) {
uint32_t address = gpr[data.rn]; uint32_t address = gpr[data.rn];
Mode mode = cpsr.mode(); Mode mode = cpsr.mode();
uint8_t alignment = 4; // word uint8_t alignment = 4; // word
@@ -449,7 +452,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
// load back the original mode registers // load back the original mode registers
chg_mode(mode); chg_mode(mode);
}, },
[this, pc_error](ArmInstruction::PsrTransfer& data) { [this, pc_error](PsrTransfer& data) {
if (data.spsr && cpsr.mode() == Mode::User) { if (data.spsr && cpsr.mode() == Mode::User) {
log_error("Accessing SPSR in User mode in {}", log_error("Accessing SPSR in User mode in {}",
typeid(data).name()); typeid(data).name());
@@ -458,18 +461,18 @@ Cpu::exec_arm(const ArmInstruction instruction) {
Psr& psr = data.spsr ? spsr : cpsr; Psr& psr = data.spsr ? spsr : cpsr;
switch (data.type) { switch (data.type) {
case ArmInstruction::PsrTransfer::Type::Mrs: case PsrTransfer::Type::Mrs:
pc_error(data.operand); pc_error(data.operand);
gpr[data.operand] = psr.raw(); gpr[data.operand] = psr.raw();
break; break;
case ArmInstruction::PsrTransfer::Type::Msr: case PsrTransfer::Type::Msr:
pc_error(data.operand); pc_error(data.operand);
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 ArmInstruction::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));
psr.set_c(get_bit(data.operand, 29)); psr.set_c(get_bit(data.operand, 29));
@@ -477,7 +480,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
break; break;
} }
}, },
[this, pc_error](ArmInstruction::DataProcessing& data) { [this, pc_error](DataProcessing& data) {
uint32_t op_1 = gpr[data.rn]; uint32_t op_1 = gpr[data.rn];
uint32_t op_2 = 0; uint32_t op_2 = 0;
@@ -671,7 +674,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
is_flushed = true; is_flushed = true;
} }
}, },
[this](ArmInstruction::SoftwareInterrupt) { [this](SoftwareInterrupt) {
chg_mode(Mode::Supervisor); chg_mode(Mode::Supervisor);
pc = 0x08; pc = 0x08;
spsr = cpsr; spsr = cpsr;
@@ -690,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);
ArmInstruction instruction(x); arm::ArmInstruction instruction(x);
log_info("{:#034b}", x); log_info("{:#034b}", x);
exec_arm(instruction); exec_arm(instruction);

View File

@@ -336,7 +336,8 @@ ArmInstruction::disassemble() {
if (*offset == 0) { if (*offset == 0) {
expression = ""; expression = "";
} else { } else {
expression = fmt::format(",#{:d}", *offset); expression =
fmt::format(",{}#{:d}", (data.up ? '+' : '-'), *offset);
} }
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) { } else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
// Shifts are always immediate in single data transfer // Shifts are always immediate in single data transfer
@@ -365,7 +366,8 @@ ArmInstruction::disassemble() {
if (data.offset == 0) { if (data.offset == 0) {
expression = ""; expression = "";
} else { } else {
expression = fmt::format(",#{:d}", data.offset); expression = fmt::format(
",{}#{:d}", (data.up ? '+' : '-'), data.offset);
} }
} else { } else {
expression = expression =

198
tests/cpu/instruction.cc Normal file
View File

@@ -0,0 +1,198 @@
#include "cpu/instruction.hh"
#include "cpu/utility.hh"
#include <catch2/catch_test_macros.hpp>
#include <iostream>
static constexpr auto TAG = "disassembler";
using namespace arm;
TEST_CASE("Branch and Exchange", TAG) {
uint32_t raw = 0b11000001001011111111111100011010;
ArmInstruction instruction(raw);
BranchAndExchange* bx = nullptr;
REQUIRE((bx = std::get_if<BranchAndExchange>(&instruction.data)));
REQUIRE(instruction.condition == Condition::GT);
REQUIRE(bx->rn == 10);
REQUIRE(instruction.disassemble() == "BXGT R10");
}
TEST_CASE("Branch", TAG) {
uint32_t raw = 0b11101011100001010111111111000011;
ArmInstruction instruction(raw);
Branch* b = nullptr;
REQUIRE((b = std::get_if<Branch>(&instruction.data)));
REQUIRE(instruction.condition == Condition::AL);
// last 24 bits = 8748995
// (8748995 << 8) >> 6 sign extended = 0xFE15FF0C
// Also +8 since PC is two instructions ahead
REQUIRE(b->offset == 0xFE15FF14);
REQUIRE(b->link == true);
REQUIRE(instruction.disassemble() == "BL 0xFE15FF14");
b->link = false;
REQUIRE(instruction.disassemble() == "B 0xFE15FF14");
}
TEST_CASE("Multiply", TAG) {
uint32_t raw = 0b00000000001110101110111110010000;
ArmInstruction instruction(raw);
Multiply* mul = nullptr;
REQUIRE((mul = std::get_if<Multiply>(&instruction.data)));
REQUIRE(instruction.condition == Condition::EQ);
REQUIRE(mul->rm == 0);
REQUIRE(mul->rs == 15);
REQUIRE(mul->rn == 14);
REQUIRE(mul->rd == 10);
REQUIRE(mul->acc == true);
REQUIRE(mul->set == true);
REQUIRE(instruction.disassemble() == "MLAEQS R10,R0,R15,R14");
mul->acc = false;
mul->set = false;
REQUIRE(instruction.disassemble() == "MULEQ R10,R0,R15");
}
TEST_CASE("Multiply Long", TAG) {
uint32_t raw = 0b00010000100111100111011010010010;
ArmInstruction instruction(raw);
MultiplyLong* mull = nullptr;
REQUIRE((mull = std::get_if<MultiplyLong>(&instruction.data)));
REQUIRE(instruction.condition == Condition::NE);
REQUIRE(mull->rm == 2);
REQUIRE(mull->rs == 6);
REQUIRE(mull->rdlo == 7);
REQUIRE(mull->rdhi == 14);
REQUIRE(mull->acc == false);
REQUIRE(mull->set == true);
REQUIRE(mull->uns == false);
REQUIRE(instruction.disassemble() == "SMULLNES R7,R14,R2,R6");
mull->acc = true;
REQUIRE(instruction.disassemble() == "SMLALNES R7,R14,R2,R6");
mull->uns = true;
mull->set = false;
REQUIRE(instruction.disassemble() == "UMLALNE R7,R14,R2,R6");
}
TEST_CASE("Single Data Swap", TAG) {
uint32_t raw = 0b10100001000010010101000010010110;
ArmInstruction instruction(raw);
SingleDataSwap* swp = nullptr;
REQUIRE((swp = std::get_if<SingleDataSwap>(&instruction.data)));
REQUIRE(instruction.condition == Condition::GE);
REQUIRE(swp->rm == 6);
REQUIRE(swp->rd == 5);
REQUIRE(swp->rn == 9);
REQUIRE(swp->byte == false);
REQUIRE(instruction.disassemble() == "SWPGE R5,R6,[R9]");
swp->byte = true;
REQUIRE(instruction.disassemble() == "SWPGEB R5,R6,[R9]");
}
TEST_CASE("Single Data Transfer", TAG) {
uint32_t raw = 0b11100111101000101010111100000110;
ArmInstruction instruction(raw);
SingleDataTransfer* ldr = nullptr;
Shift* shift = nullptr;
REQUIRE((ldr = std::get_if<SingleDataTransfer>(&instruction.data)));
REQUIRE(instruction.condition == Condition::AL);
REQUIRE((shift = std::get_if<Shift>(&ldr->offset)));
REQUIRE(shift->rm == 6);
REQUIRE(shift->data.immediate == true);
REQUIRE(shift->data.type == ShiftType::LSL);
REQUIRE(shift->data.operand == 30);
REQUIRE(ldr->rd == 10);
REQUIRE(ldr->rn == 2);
REQUIRE(ldr->load == false);
REQUIRE(ldr->write == true);
REQUIRE(ldr->byte == false);
REQUIRE(ldr->up == true);
REQUIRE(ldr->pre == true);
ldr->load = true;
ldr->byte = true;
ldr->write = false;
shift->data.type = ShiftType::ROR;
REQUIRE(instruction.disassemble() == "LDRB R10,[R2,+R6,ROR #30]");
ldr->up = false;
ldr->pre = false;
REQUIRE(instruction.disassemble() == "LDRB R10,[R2],-R6,ROR #30");
ldr->offset = static_cast<uint16_t>(9023);
REQUIRE(instruction.disassemble() == "LDRB R10,[R2],-#9023");
ldr->pre = true;
REQUIRE(instruction.disassemble() == "LDRB R10,[R2,-#9023]");
}
TEST_CASE("Halfword Transfer", TAG) {
uint32_t raw = 0b00110001101011110010000010110110;
ArmInstruction instruction(raw);
HalfwordTransfer* ldr = nullptr;
REQUIRE((ldr = std::get_if<HalfwordTransfer>(&instruction.data)));
REQUIRE(instruction.condition == Condition::CC);
// offset is not immediate
REQUIRE(ldr->imm == 0);
// hence this offset is a register number (rm)
REQUIRE(ldr->offset == 6);
REQUIRE(ldr->half == true);
REQUIRE(ldr->sign == false);
REQUIRE(ldr->rd == 2);
REQUIRE(ldr->rn == 15);
REQUIRE(ldr->load == false);
REQUIRE(ldr->write == true);
REQUIRE(ldr->up == true);
REQUIRE(ldr->pre == true);
REQUIRE(instruction.disassemble() == "STRCCH R2,[R15,+R6]!");
ldr->pre = false;
ldr->load = true;
ldr->sign = true;
ldr->up = false;
REQUIRE(instruction.disassemble() == "LDRCCSH R2,[R15],-R6");
ldr->half = false;
REQUIRE(instruction.disassemble() == "LDRCCSB R2,[R15],-R6");
// 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;
REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED");
raw = 0b11100110000000000000000000010000;
REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED");
}

3
tests/cpu/meson.build Normal file
View File

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

19
tests/meson.build Normal file
View File

@@ -0,0 +1,19 @@
tests_deps = [
lib
]
tests_sources = files()
subdir('cpu')
catch2 = dependency('catch2-with-main', version: '>=3.4.0', static: true)
catch2_tests = executable(
meson.project_name() + '_tests',
tests_sources,
dependencies: catch2,
link_with: tests_deps,
include_directories: inc,
build_by_default: false
)
test('catch2 tests', catch2_tests)