tests: [WIP] add unit tests for some of the instructions
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
@@ -80,6 +81,9 @@ main(int argc, const char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::flush(std::cout);
|
||||
std::flush(std::cerr);
|
||||
|
||||
{
|
||||
Memory memory(std::move(bios), std::move(rom));
|
||||
Bus bus(memory);
|
||||
|
11
flake.nix
11
flake.nix
@@ -8,17 +8,23 @@
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
# "i686-linux"
|
||||
];
|
||||
|
||||
eachSystem = with nixpkgs.lib; f: foldAttrs mergeAttrs { }
|
||||
(map (s: mapAttrs (_: v: { ${s} = v; }) (f s)) systems);
|
||||
in
|
||||
eachSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
|
||||
# aliases
|
||||
llvm = pkgs.llvmPackages_16;
|
||||
stdenv = llvm.libcxxStdenv;
|
||||
|
||||
# packages
|
||||
catch2_v3 = pkgs.callPackage ./nix/catch2.nix { inherit stdenv; };
|
||||
|
||||
#dependencies
|
||||
nativeBuildInputs = with pkgs; [
|
||||
meson
|
||||
ninja
|
||||
@@ -26,6 +32,7 @@
|
||||
# libraries
|
||||
pkg-config
|
||||
fmt.dev
|
||||
catch2_v3.dev
|
||||
];
|
||||
in
|
||||
rec {
|
||||
@@ -51,6 +58,8 @@
|
||||
matar = pkgs.mkShell.override { inherit stdenv; } {
|
||||
name = "matar";
|
||||
packages = nativeBuildInputs ++ (with pkgs; [
|
||||
llvm.libcxx
|
||||
|
||||
# dev tools
|
||||
clang-tools_16
|
||||
]);
|
||||
|
@@ -35,7 +35,7 @@ class Cpu {
|
||||
bool is_flushed;
|
||||
|
||||
void chg_mode(const Mode to);
|
||||
void exec_arm(const ArmInstruction instruction);
|
||||
void exec_arm(const arm::ArmInstruction instruction);
|
||||
|
||||
struct {
|
||||
std::array<uint32_t, GPR_COUNT - GPR_FIQ_FIRST - 1> fiq;
|
||||
|
@@ -9,159 +9,154 @@ struct overloaded : Ts... {
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
class ArmInstruction {
|
||||
public:
|
||||
ArmInstruction() = delete;
|
||||
ArmInstruction(uint32_t insn);
|
||||
namespace arm {
|
||||
struct BranchAndExchange {
|
||||
uint8_t rn;
|
||||
};
|
||||
|
||||
auto get_condition() const { return condition; }
|
||||
auto get_data() const { return data; }
|
||||
struct Branch {
|
||||
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 {
|
||||
uint8_t rn;
|
||||
struct MultiplyLong {
|
||||
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 {
|
||||
bool link;
|
||||
uint32_t offset;
|
||||
};
|
||||
uint32_t operand;
|
||||
bool spsr;
|
||||
Type type;
|
||||
// ignored outside MSR_flg
|
||||
bool imm;
|
||||
};
|
||||
|
||||
struct Multiply {
|
||||
uint8_t rm;
|
||||
uint8_t rs;
|
||||
uint8_t rn;
|
||||
uint8_t rd;
|
||||
bool set;
|
||||
bool acc;
|
||||
};
|
||||
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 MultiplyLong {
|
||||
uint8_t rm;
|
||||
uint8_t rs;
|
||||
uint8_t rdlo;
|
||||
uint8_t rdhi;
|
||||
bool set;
|
||||
bool acc;
|
||||
bool uns;
|
||||
};
|
||||
struct CoprocessorDataOperation {
|
||||
uint8_t crm;
|
||||
uint8_t cp;
|
||||
uint8_t cpn;
|
||||
uint8_t crd;
|
||||
uint8_t crn;
|
||||
uint8_t cp_opc;
|
||||
};
|
||||
|
||||
struct SingleDataSwap {
|
||||
uint8_t rm;
|
||||
uint8_t rd;
|
||||
uint8_t rn;
|
||||
bool byte;
|
||||
};
|
||||
struct CoprocessorRegisterTransfer {
|
||||
uint8_t crm;
|
||||
uint8_t cp;
|
||||
uint8_t cpn;
|
||||
uint8_t rd;
|
||||
uint8_t crn;
|
||||
bool load;
|
||||
uint8_t cp_opc;
|
||||
};
|
||||
|
||||
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 Undefined {};
|
||||
struct SoftwareInterrupt {};
|
||||
|
||||
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;
|
||||
};
|
||||
using InstructionData = std::variant<BranchAndExchange,
|
||||
Branch,
|
||||
Multiply,
|
||||
MultiplyLong,
|
||||
SingleDataSwap,
|
||||
SingleDataTransfer,
|
||||
HalfwordTransfer,
|
||||
BlockDataTransfer,
|
||||
DataProcessing,
|
||||
PsrTransfer,
|
||||
CoprocessorDataTransfer,
|
||||
CoprocessorDataOperation,
|
||||
CoprocessorRegisterTransfer,
|
||||
Undefined,
|
||||
SoftwareInterrupt>;
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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:
|
||||
struct ArmInstruction {
|
||||
Condition condition;
|
||||
InstructionData data;
|
||||
|
||||
ArmInstruction(uint32_t insn);
|
||||
std::string disassemble();
|
||||
};
|
||||
}
|
||||
|
26
meson.build
26
meson.build
@@ -3,10 +3,32 @@ project('matar', 'cpp',
|
||||
license : 'GPLv3',
|
||||
default_options : ['warning_level=3',
|
||||
'werror=true',
|
||||
'optimization=3',
|
||||
'cpp_std=c++20'])
|
||||
'optimization=3'])
|
||||
|
||||
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')
|
||||
|
||||
subdir('include')
|
||||
subdir('src')
|
||||
subdir('apps')
|
||||
|
||||
subdir('tests')
|
||||
|
17
nix/catch2.nix
Normal file
17
nix/catch2.nix
Normal 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" ];
|
||||
}
|
@@ -116,27 +116,29 @@ Cpu::chg_mode(const Mode to) {
|
||||
}
|
||||
|
||||
void
|
||||
Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
auto cond = instruction.get_condition();
|
||||
auto data = instruction.get_data();
|
||||
Cpu::exec_arm(const arm::ArmInstruction instruction) {
|
||||
auto cond = instruction.condition;
|
||||
auto data = instruction.data;
|
||||
|
||||
if (!cpsr.condition(cond)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pc_error = [](uint8_t r) {
|
||||
if (r == 15)
|
||||
if (r == PC_INDEX)
|
||||
log_error("Using PC (R15) as operand register");
|
||||
};
|
||||
|
||||
auto pc_warn = [](uint8_t r) {
|
||||
if (r == 15)
|
||||
if (r == PC_INDEX)
|
||||
log_warn("Using PC (R15) as operand register");
|
||||
};
|
||||
|
||||
using namespace arm;
|
||||
|
||||
std::visit(
|
||||
overloaded{
|
||||
[this, pc_warn](ArmInstruction::BranchAndExchange& data) {
|
||||
[this, pc_warn](BranchAndExchange& data) {
|
||||
State state = static_cast<State>(data.rn & 1);
|
||||
|
||||
pc_warn(data.rn);
|
||||
@@ -156,7 +158,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
// pc is affected so flush the pipeline
|
||||
is_flushed = true;
|
||||
},
|
||||
[this](ArmInstruction::Branch& data) {
|
||||
[this](Branch& data) {
|
||||
if (data.link)
|
||||
gpr[14] = pc - ARM_INSTRUCTION_SIZE;
|
||||
|
||||
@@ -168,7 +170,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
// pc is affected so flush the pipeline
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_error](ArmInstruction::Multiply& data) {
|
||||
[this, pc_error](Multiply& data) {
|
||||
if (data.rd == data.rm)
|
||||
log_error("rd and rm are not distinct in {}",
|
||||
typeid(data).name());
|
||||
@@ -186,7 +188,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
cpsr.set_c(0);
|
||||
}
|
||||
},
|
||||
[this, pc_error](ArmInstruction::MultiplyLong& data) {
|
||||
[this, pc_error](MultiplyLong& data) {
|
||||
if (data.rdhi == data.rdlo || data.rdhi == data.rm ||
|
||||
data.rdlo == data.rm)
|
||||
log_error("rdhi, rdlo and rm are not distinct in {}",
|
||||
@@ -226,8 +228,8 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
cpsr.set_v(0);
|
||||
}
|
||||
},
|
||||
[](ArmInstruction::Undefined) { log_warn("Undefined instruction"); },
|
||||
[this, pc_error](ArmInstruction::SingleDataSwap& data) {
|
||||
[](Undefined) { log_warn("Undefined instruction"); },
|
||||
[this, pc_error](SingleDataSwap& data) {
|
||||
pc_error(data.rm);
|
||||
pc_error(data.rn);
|
||||
pc_error(data.rd);
|
||||
@@ -240,7 +242,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
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 address = gpr[data.rn];
|
||||
|
||||
@@ -316,7 +318,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
if (data.rd == PC_INDEX && data.load)
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_warn, pc_error](ArmInstruction::HalfwordTransfer& data) {
|
||||
[this, pc_warn, pc_error](HalfwordTransfer& data) {
|
||||
uint32_t address = gpr[data.rn];
|
||||
|
||||
if (!data.pre && data.write)
|
||||
@@ -345,15 +347,16 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
gpr[data.rd] = bus->read_halfword(address);
|
||||
|
||||
// sign extend the halfword
|
||||
if (get_bit(gpr[data.rd], PC_INDEX))
|
||||
gpr[data.rd] |= 0xFFFF0000;
|
||||
gpr[data.rd] =
|
||||
(static_cast<int32_t>(gpr[data.rd]) << 16) >> 16;
|
||||
|
||||
// byte
|
||||
} else {
|
||||
gpr[data.rd] = bus->read_byte(address);
|
||||
|
||||
// sign extend the byte
|
||||
if (get_bit(gpr[data.rd], 7))
|
||||
gpr[data.rd] |= 0xFFFFFF00;
|
||||
gpr[data.rd] =
|
||||
(static_cast<int32_t>(gpr[data.rd]) << 24) >> 24;
|
||||
}
|
||||
// unsigned halfword
|
||||
} else if (data.half) {
|
||||
@@ -379,7 +382,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
if (data.rd == PC_INDEX && data.load)
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_error](ArmInstruction::BlockDataTransfer& data) {
|
||||
[this, pc_error](BlockDataTransfer& data) {
|
||||
uint32_t address = gpr[data.rn];
|
||||
Mode mode = cpsr.mode();
|
||||
uint8_t alignment = 4; // word
|
||||
@@ -449,7 +452,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
// load back the original mode registers
|
||||
chg_mode(mode);
|
||||
},
|
||||
[this, pc_error](ArmInstruction::PsrTransfer& data) {
|
||||
[this, pc_error](PsrTransfer& data) {
|
||||
if (data.spsr && cpsr.mode() == Mode::User) {
|
||||
log_error("Accessing SPSR in User mode in {}",
|
||||
typeid(data).name());
|
||||
@@ -458,18 +461,18 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
Psr& psr = data.spsr ? spsr : cpsr;
|
||||
|
||||
switch (data.type) {
|
||||
case ArmInstruction::PsrTransfer::Type::Mrs:
|
||||
case PsrTransfer::Type::Mrs:
|
||||
pc_error(data.operand);
|
||||
gpr[data.operand] = psr.raw();
|
||||
break;
|
||||
case ArmInstruction::PsrTransfer::Type::Msr:
|
||||
case PsrTransfer::Type::Msr:
|
||||
pc_error(data.operand);
|
||||
|
||||
if (cpsr.mode() != Mode::User) {
|
||||
psr.set_all(gpr[data.operand]);
|
||||
break;
|
||||
}
|
||||
case ArmInstruction::PsrTransfer::Type::Msr_flg:
|
||||
case PsrTransfer::Type::Msr_flg:
|
||||
psr.set_n(get_bit(data.operand, 31));
|
||||
psr.set_z(get_bit(data.operand, 30));
|
||||
psr.set_c(get_bit(data.operand, 29));
|
||||
@@ -477,7 +480,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
break;
|
||||
}
|
||||
},
|
||||
[this, pc_error](ArmInstruction::DataProcessing& data) {
|
||||
[this, pc_error](DataProcessing& data) {
|
||||
uint32_t op_1 = gpr[data.rn];
|
||||
uint32_t op_2 = 0;
|
||||
|
||||
@@ -671,7 +674,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
is_flushed = true;
|
||||
}
|
||||
},
|
||||
[this](ArmInstruction::SoftwareInterrupt) {
|
||||
[this](SoftwareInterrupt) {
|
||||
chg_mode(Mode::Supervisor);
|
||||
pc = 0x08;
|
||||
spsr = cpsr;
|
||||
@@ -690,7 +693,7 @@ Cpu::step() {
|
||||
if (cpsr.state() == State::Arm) {
|
||||
debug(cur_pc);
|
||||
uint32_t x = bus->read_word(cur_pc);
|
||||
ArmInstruction instruction(x);
|
||||
arm::ArmInstruction instruction(x);
|
||||
log_info("{:#034b}", x);
|
||||
|
||||
exec_arm(instruction);
|
||||
|
@@ -336,7 +336,8 @@ ArmInstruction::disassemble() {
|
||||
if (*offset == 0) {
|
||||
expression = "";
|
||||
} else {
|
||||
expression = fmt::format(",#{:d}", *offset);
|
||||
expression =
|
||||
fmt::format(",{}#{:d}", (data.up ? '+' : '-'), *offset);
|
||||
}
|
||||
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
|
||||
// Shifts are always immediate in single data transfer
|
||||
@@ -365,7 +366,8 @@ ArmInstruction::disassemble() {
|
||||
if (data.offset == 0) {
|
||||
expression = "";
|
||||
} else {
|
||||
expression = fmt::format(",#{:d}", data.offset);
|
||||
expression = fmt::format(
|
||||
",{}#{:d}", (data.up ? '+' : '-'), data.offset);
|
||||
}
|
||||
} else {
|
||||
expression =
|
||||
|
198
tests/cpu/instruction.cc
Normal file
198
tests/cpu/instruction.cc
Normal 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
3
tests/cpu/meson.build
Normal file
@@ -0,0 +1,3 @@
|
||||
tests_sources += files(
|
||||
'instruction.cc'
|
||||
)
|
19
tests/meson.build
Normal file
19
tests/meson.build
Normal 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)
|
Reference in New Issue
Block a user