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 <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);
|
||||||
|
11
flake.nix
11
flake.nix
@@ -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
|
||||||
]);
|
]);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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();
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
26
meson.build
26
meson.build
@@ -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
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
|
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);
|
||||||
|
@@ -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
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