tests: add execution tests

all but data processing

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-09-18 18:23:52 +05:30
parent dd9dd5f116
commit fa96a4d09f
31 changed files with 2076 additions and 1265 deletions

View File

@@ -5,4 +5,5 @@ Checks: '
, -cppcoreguidelines-pro-bounds-constant-array-index
, -cppcoreguidelines-macro-usage
, -cppcoreguidelines-avoid-const-or-ref-data-members
, -cppcoreguidelines-non-private-member-variables-in-classes
'

View File

@@ -82,9 +82,9 @@ main(int argc, const char* argv[]) {
}
std::flush(std::cout);
std::flush(std::cerr);
std::flush(std::cout);
{
try {
Memory memory(std::move(bios), std::move(rom));
Bus bus(memory);
Cpu cpu(bus);
@@ -92,7 +92,11 @@ main(int argc, const char* argv[]) {
cpu.step();
sleep(1);
}
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}
return 0;
}

View File

@@ -5,7 +5,7 @@
class Bus {
public:
Bus(Memory& memory);
Bus(const Memory& memory);
uint8_t read_byte(size_t address);
void write_byte(size_t address, uint8_t byte);

View File

@@ -1,58 +1,19 @@
#pragma once
#include "bus.hh"
#include "instruction.hh"
#include "psr.hh"
#include <cstdint>
using std::size_t;
class CpuImpl;
class Cpu {
public:
Cpu(Bus& bus);
Cpu(const Bus& bus) noexcept;
Cpu(const Cpu&) = delete;
Cpu(Cpu&&) = delete;
Cpu& operator=(const Cpu&) = delete;
Cpu& operator=(Cpu&&) = delete;
~Cpu();
void step();
private:
static constexpr uint8_t GPR_COUNT = 16;
static constexpr uint8_t GPR_FIQ_FIRST = 8;
static constexpr uint8_t GPR_SVC_FIRST = 13;
static constexpr uint8_t GPR_ABT_FIRST = 13;
static constexpr uint8_t GPR_IRQ_FIRST = 13;
static constexpr uint8_t GPR_UND_FIRST = 13;
static constexpr uint8_t GPR_SYS_USR_FIRST = 8;
std::shared_ptr<Bus> bus;
std::array<uint32_t, GPR_COUNT> gpr; // general purpose registers
Psr cpsr; // current program status register
Psr spsr; // status program status register
static constexpr uint8_t PC_INDEX = 15;
uint32_t& pc = gpr[PC_INDEX];
bool is_flushed;
void chg_mode(const Mode to);
void exec_arm(const arm::Instruction instruction);
struct {
std::array<uint32_t, GPR_COUNT - GPR_FIQ_FIRST - 1> fiq;
std::array<uint32_t, GPR_COUNT - GPR_SVC_FIRST - 1> svc;
std::array<uint32_t, GPR_COUNT - GPR_ABT_FIRST - 1> abt;
std::array<uint32_t, GPR_COUNT - GPR_IRQ_FIRST - 1> irq;
std::array<uint32_t, GPR_COUNT - GPR_UND_FIRST - 1> und;
// visible registers before the mode switch
std::array<uint32_t, GPR_COUNT - GPR_SYS_USR_FIRST> old;
} gpr_banked; // banked general purpose registers
struct {
Psr fiq;
Psr svc;
Psr abt;
Psr irq;
Psr und;
} spsr_banked; // banked saved program status registers
std::unique_ptr<CpuImpl> impl;
};

View File

@@ -1,6 +1,3 @@
headers += files(
'cpu.hh',
'instruction.hh',
'psr.hh',
'utility.hh'
)

View File

@@ -4,6 +4,8 @@
#include <string>
struct Header {
static constexpr uint8_t HEADER_SIZE = 192;
enum class UniqueCode {
Old, // old games
New, // new games

View File

@@ -4,14 +4,14 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <unordered_map>
#include <vector>
class Memory {
public:
static constexpr size_t BIOS_SIZE = 1024 * 16;
Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
std::vector<uint8_t>&& rom) noexcept;
Memory(std::array<uint8_t, BIOS_SIZE>&& bios, std::vector<uint8_t>&& rom);
uint8_t read(size_t address) const;
void write(size_t address, uint8_t byte);
@@ -58,6 +58,7 @@ class Memory {
#undef MEMORY_REGION
std::unordered_map<size_t, uint8_t> invalid_mem;
std::vector<uint8_t> rom;
Header header;
void parse_header();

View File

@@ -4,6 +4,8 @@ headers = files(
'header.hh',
)
inc = include_directories('.')
subdir('cpu')
install_headers(headers, subdir: meson.project_name(), preserve_path: true)

View File

@@ -30,8 +30,6 @@ else
endif
'''
inc = include_directories('include')
subdir('include')
subdir('src')
subdir('apps')

1
result-dev Symbolic link
View File

@@ -0,0 +1 @@
/nix/store/6p64j7f5xl7rzf1i1avdn18nq9j0yisw-matar-dev

View File

@@ -1,7 +1,7 @@
#include "bus.hh"
#include <memory>
Bus::Bus(Memory& memory)
Bus::Bus(const Memory& memory)
: memory(std::make_shared<Memory>(memory)) {}
uint8_t
@@ -31,5 +31,5 @@ Bus::read_word(size_t address) {
void
Bus::write_word(size_t address, uint32_t word) {
memory->write_halfword(address, word);
memory->write_word(address, word);
}

590
src/cpu/arm/exec.cc Normal file
View File

@@ -0,0 +1,590 @@
#include "cpu/cpu-impl.hh"
#include "util/bits.hh"
#include "util/log.hh"
using namespace logger;
void
CpuImpl::exec_arm(const arm::Instruction instruction) {
auto cond = instruction.condition;
auto data = instruction.data;
if (!cpsr.condition(cond)) {
return;
}
auto pc_error = [](uint8_t r) {
if (r == PC_INDEX)
log_error("Using PC (R15) as operand register");
};
auto pc_warn = [](uint8_t r) {
if (r == PC_INDEX)
log_warn("Using PC (R15) as operand register");
};
using namespace arm;
std::visit(
overloaded{
[this, pc_warn](BranchAndExchange& data) {
State state = static_cast<State>(data.rn & 1);
pc_warn(data.rn);
// set state
cpsr.set_state(state);
// copy to PC
pc = gpr[data.rn];
// ignore [1:0] bits for arm and 0 bit for thumb
rst_bit(pc, 0);
if (state == State::Arm)
rst_bit(pc, 1);
// pc is affected so flush the pipeline
is_flushed = true;
},
[this](Branch& data) {
if (data.link)
gpr[14] = pc - INSTRUCTION_SIZE;
// data.offset accounts for two instructions ahead when
// disassembling, so need to adjust
pc = static_cast<int32_t>(pc) - 2 * INSTRUCTION_SIZE + data.offset;
// pc is affected so flush the pipeline
is_flushed = true;
},
[this, pc_error](Multiply& data) {
if (data.rd == data.rm)
log_error("rd and rm are not distinct in {}",
typeid(data).name());
pc_error(data.rd);
pc_error(data.rd);
pc_error(data.rd);
gpr[data.rd] =
gpr[data.rm] * gpr[data.rs] + (data.acc ? gpr[data.rn] : 0);
if (data.set) {
cpsr.set_z(gpr[data.rd] == 0);
cpsr.set_n(get_bit(gpr[data.rd], 31));
cpsr.set_c(0);
}
},
[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 {}",
typeid(data).name());
pc_error(data.rdhi);
pc_error(data.rdlo);
pc_error(data.rm);
pc_error(data.rs);
if (data.uns) {
auto cast = [](uint32_t x) -> uint64_t {
return static_cast<uint64_t>(x);
};
uint64_t eval = cast(gpr[data.rm]) * cast(gpr[data.rs]) +
(data.acc ? (cast(gpr[data.rdhi]) << 32) |
cast(gpr[data.rdlo])
: 0);
gpr[data.rdlo] = bit_range(eval, 0, 31);
gpr[data.rdhi] = bit_range(eval, 32, 63);
} else {
auto cast = [](uint32_t x) -> int64_t {
return static_cast<int64_t>(static_cast<int32_t>(x));
};
int64_t eval = cast(gpr[data.rm]) * cast(gpr[data.rs]) +
(data.acc ? (cast(gpr[data.rdhi]) << 32) |
cast(gpr[data.rdlo])
: 0);
gpr[data.rdlo] = bit_range(eval, 0, 31);
gpr[data.rdhi] = bit_range(eval, 32, 63);
}
if (data.set) {
cpsr.set_z(gpr[data.rdhi] == 0 && gpr[data.rdlo] == 0);
cpsr.set_n(get_bit(gpr[data.rdhi], 31));
cpsr.set_c(0);
cpsr.set_v(0);
}
},
[](Undefined) { log_warn("Undefined instruction"); },
[this, pc_error](SingleDataSwap& data) {
pc_error(data.rm);
pc_error(data.rn);
pc_error(data.rd);
if (data.byte) {
gpr[data.rd] = bus->read_byte(gpr[data.rn]);
bus->write_byte(gpr[data.rn], gpr[data.rm] & 0xFF);
} else {
gpr[data.rd] = bus->read_word(gpr[data.rn]);
bus->write_word(gpr[data.rn], gpr[data.rm]);
}
},
[this, pc_warn, pc_error](SingleDataTransfer& data) {
uint32_t offset = 0;
uint32_t address = gpr[data.rn];
if (!data.pre && data.write)
log_warn("Write-back enabled with post-indexing in {}",
typeid(data).name());
if (data.rn == PC_INDEX && data.write)
log_warn("Write-back enabled with base register as PC {}",
typeid(data).name());
if (data.write)
pc_warn(data.rn);
// evaluate the offset
if (const uint16_t* immediate =
std::get_if<uint16_t>(&data.offset)) {
offset = *immediate;
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
uint8_t amount =
(shift->data.immediate ? shift->data.operand
: gpr[shift->data.operand] & 0xFF);
bool carry = cpsr.c();
if (!shift->data.immediate)
pc_error(shift->data.operand);
pc_error(shift->rm);
offset =
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
cpsr.set_c(carry);
}
// PC is always two instructions ahead
if (data.rn == PC_INDEX)
address -= 2 * INSTRUCTION_SIZE;
if (data.pre)
address += (data.up ? offset : -offset);
// load
if (data.load) {
// byte
if (data.byte)
gpr[data.rd] = bus->read_byte(address);
// word
else
gpr[data.rd] = bus->read_word(address);
// store
} else {
// take PC into consideration
if (data.rd == PC_INDEX)
address += INSTRUCTION_SIZE;
// byte
if (data.byte)
bus->write_byte(address, gpr[data.rd] & 0xFF);
// word
else
bus->write_word(address, gpr[data.rd]);
}
if (!data.pre)
address += (data.up ? offset : -offset);
if (!data.pre || data.write)
gpr[data.rn] = address;
if (data.rd == PC_INDEX && data.load)
is_flushed = true;
},
[this, pc_warn, pc_error](HalfwordTransfer& data) {
uint32_t address = gpr[data.rn];
uint32_t offset = 0;
if (!data.pre && data.write)
log_error("Write-back enabled with post-indexing in {}",
typeid(data).name());
if (data.sign && !data.load)
log_error("Signed data found in {}", typeid(data).name());
if (data.write)
pc_warn(data.rn);
// offset is register number (4 bits) when not an immediate
if (!data.imm) {
pc_error(data.offset);
offset = gpr[data.offset];
} else {
offset = data.offset;
}
// PC is always two instructions ahead
if (data.rn == PC_INDEX)
address -= 2 * INSTRUCTION_SIZE;
if (data.pre)
address += (data.up ? offset : -offset);
// load
if (data.load) {
// signed
if (data.sign) {
// halfword
if (data.half) {
gpr[data.rd] = bus->read_halfword(address);
// sign extend the halfword
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
gpr[data.rd] =
(static_cast<int32_t>(gpr[data.rd]) << 24) >> 24;
}
// unsigned halfword
} else if (data.half) {
gpr[data.rd] = bus->read_halfword(address);
}
// store
} else {
// take PC into consideration
if (data.rd == PC_INDEX)
address += INSTRUCTION_SIZE;
// halfword
if (data.half)
bus->write_halfword(address, gpr[data.rd]);
}
if (!data.pre)
address += (data.up ? offset : -offset);
if (!data.pre || data.write)
gpr[data.rn] = address;
if (data.rd == PC_INDEX && data.load)
is_flushed = true;
},
[this, pc_error](BlockDataTransfer& data) {
uint32_t address = gpr[data.rn];
Mode mode = cpsr.mode();
uint8_t alignment = 4; // word
uint8_t i = 0;
uint8_t n_regs = std::popcount(data.regs);
pc_error(data.rn);
if (cpsr.mode() == Mode::User && data.s) {
log_error("Bit S is set outside priviliged modes in {}",
typeid(data).name());
}
// we just change modes to load user registers
if ((!get_bit(data.regs, PC_INDEX) && data.s) ||
(!data.load && data.s)) {
chg_mode(Mode::User);
if (data.write) {
log_error("Write-back enable for user bank registers in {}",
typeid(data).name());
}
}
// account for decrement
if (!data.up)
address -= (n_regs - 1) * alignment;
if (data.pre)
address += (data.up ? alignment : -alignment);
if (data.load) {
if (get_bit(data.regs, PC_INDEX) && data.s && data.load) {
// current mode's spsr is already loaded when it was
// switched
spsr = cpsr;
}
for (i = 0; i < GPR_COUNT; i++) {
if (get_bit(data.regs, i)) {
gpr[i] = bus->read_word(address);
address += alignment;
}
}
} else {
for (i = 0; i < GPR_COUNT; i++) {
if (get_bit(data.regs, i)) {
bus->write_word(address, gpr[i]);
address += alignment;
}
}
}
if (!data.pre)
address += (data.up ? alignment : -alignment);
// reset back to original address + offset if incremented earlier
if (data.up)
address -= n_regs * alignment;
else
address -= alignment;
if (!data.pre || data.write)
gpr[data.rn] = address;
if (data.load && get_bit(data.regs, PC_INDEX))
is_flushed = true;
// load back the original mode registers
chg_mode(mode);
},
[this, pc_error](PsrTransfer& data) {
if (data.spsr && cpsr.mode() == Mode::User) {
log_error("Accessing SPSR in User mode in {}",
typeid(data).name());
}
Psr& psr = data.spsr ? spsr : cpsr;
switch (data.type) {
case PsrTransfer::Type::Mrs:
pc_error(data.operand);
gpr[data.operand] = psr.raw();
break;
case PsrTransfer::Type::Msr:
pc_error(data.operand);
if (cpsr.mode() != Mode::User) {
psr.set_all(gpr[data.operand]);
}
break;
case PsrTransfer::Type::Msr_flg:
uint32_t operand =
(data.imm ? data.operand : gpr[data.operand]);
psr.set_n(get_bit(operand, 31));
psr.set_z(get_bit(operand, 30));
psr.set_c(get_bit(operand, 29));
psr.set_v(get_bit(operand, 28));
break;
}
},
[this, pc_error](DataProcessing& data) {
uint32_t op_1 = gpr[data.rn];
uint32_t op_2 = 0;
uint32_t result = 0;
bool overflow = cpsr.v();
bool carry = cpsr.c();
bool negative = cpsr.n();
bool zero = cpsr.z();
if (const uint32_t* immediate =
std::get_if<uint32_t>(&data.operand)) {
op_2 = *immediate;
} else if (const Shift* shift = std::get_if<Shift>(&data.operand)) {
uint8_t amount =
(shift->data.immediate ? shift->data.operand
: gpr[shift->data.operand] & 0xFF);
bool carry = cpsr.c();
if (!shift->data.immediate)
pc_error(shift->data.operand);
pc_error(shift->rm);
op_2 =
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
cpsr.set_c(carry);
// PC is 12 bytes ahead when shifting
if (data.rn == PC_INDEX)
op_1 += INSTRUCTION_SIZE;
}
switch (data.opcode) {
case OpCode::AND: {
result = op_1 & op_2;
negative = get_bit(result, 31);
} break;
case OpCode::EOR: {
result = op_1 ^ op_2;
negative = get_bit(result, 31);
} break;
case OpCode::SUB: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
result = op_1 - op_2;
negative = get_bit(result, 31);
carry = op_1 < op_2;
overflow = s1 != s2 && s2 == negative;
} break;
case OpCode::RSB: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
result = op_2 - op_1;
negative = get_bit(result, 31);
carry = op_2 < op_1;
overflow = s1 != s2 && s1 == negative;
} break;
case OpCode::ADD: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
// result_ is 33 bits
uint64_t result_ = op_2 + op_1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 == s2 && s1 != negative;
} break;
case OpCode::ADC: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_2 + op_1 + carry;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 == s2 && s1 != negative;
} break;
case OpCode::SBC: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_1 - op_2 + carry - 1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 != s2 && s2 == negative;
} break;
case OpCode::RSC: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_1 - op_2 + carry - 1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 != s2 && s1 == negative;
} break;
case OpCode::TST: {
result = op_1 & op_2;
negative = get_bit(result, 31);
} break;
case OpCode::TEQ: {
result = op_1 ^ op_2;
negative = get_bit(result, 31);
} break;
case OpCode::CMP: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
result = op_1 - op_2;
negative = get_bit(result, 31);
carry = op_1 < op_2;
overflow = s1 != s2 && s2 == negative;
} break;
case OpCode::CMN: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_2 + op_1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 == s2 && s1 != negative;
} break;
case OpCode::ORR: {
result = op_1 | op_2;
negative = get_bit(result, 31);
} break;
case OpCode::MOV: {
result = op_2;
negative = get_bit(result, 31);
} break;
case OpCode::BIC: {
result = op_1 & ~op_2;
negative = get_bit(result, 31);
} break;
case OpCode::MVN: {
result = ~op_2;
negative = get_bit(result, 31);
} break;
}
zero = result == 0;
debug(carry);
debug(overflow);
debug(zero);
debug(negative);
auto set_conditions = [this, carry, overflow, negative, zero]() {
cpsr.set_c(carry);
cpsr.set_v(overflow);
cpsr.set_n(negative);
cpsr.set_z(zero);
};
if (data.set) {
if (data.rd == PC_INDEX) {
if (cpsr.mode() == Mode::User)
log_error("Running {} in User mode",
typeid(data).name());
} else {
set_conditions();
}
}
if (data.opcode == OpCode::TST || data.opcode == OpCode::TEQ ||
data.opcode == OpCode::CMP || data.opcode == OpCode::CMN) {
set_conditions();
} else {
gpr[data.rd] = result;
if (data.rd == PC_INDEX || data.opcode == OpCode::MVN)
is_flushed = true;
}
},
[this](SoftwareInterrupt) {
chg_mode(Mode::Supervisor);
pc = 0x08;
spsr = cpsr;
},
[](auto& data) {
log_error("Unimplemented {} instruction", typeid(data).name());
} },
data);
}

View File

@@ -1,4 +1,4 @@
#include "cpu/instruction.hh"
#include "instruction.hh"
#include "cpu/utility.hh"
#include "util/bits.hh"
#include <iterator>
@@ -21,7 +21,7 @@ Instruction::Instruction(uint32_t insn)
// lsh 2 and sign extend the 26 bit offset to 32 bits
offset = (static_cast<int32_t>(offset) << 8) >> 6;
offset += 2 * ARM_INSTRUCTION_SIZE;
offset += 2 * INSTRUCTION_SIZE;
data = Branch{ .link = link, .offset = offset };
@@ -46,7 +46,7 @@ Instruction::Instruction(uint32_t insn)
uint8_t rdhi = bit_range(insn, 16, 19);
bool set = get_bit(insn, 20);
bool acc = get_bit(insn, 21);
bool uns = get_bit(insn, 22);
bool uns = !get_bit(insn, 22);
data = MultiplyLong{ .rm = rm,
.rs = rs,
@@ -166,13 +166,13 @@ Instruction::Instruction(uint32_t insn)
} else if ((opcode == OpCode::TEQ || opcode == OpCode::CMN) && !set) {
uint32_t operand = 0;
if (!imm) {
operand = bit_range(insn, 0, 3);
} else {
if (imm) {
uint32_t immediate = bit_range(insn, 0, 7);
uint8_t rotate = bit_range(insn, 8, 11);
operand = std::rotr(immediate, rotate * 2);
} else {
operand = bit_range(insn, 0, 3);
}
data = PsrTransfer{ .operand = operand,
@@ -184,7 +184,7 @@ Instruction::Instruction(uint32_t insn)
} else {
std::variant<Shift, uint32_t> operand;
if (!imm) {
if (imm) {
uint32_t immediate = bit_range(insn, 0, 7);
uint8_t rotate = bit_range(insn, 8, 11);

View File

@@ -10,6 +10,8 @@ template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
namespace arm {
static constexpr size_t INSTRUCTION_SIZE = 4;
struct BranchAndExchange {
uint8_t rn;
};
@@ -156,6 +158,10 @@ struct Instruction {
InstructionData data;
Instruction(uint32_t insn);
Instruction(Condition condition, InstructionData data) noexcept
: condition(condition)
, data(data){};
std::string disassemble();
};
}

4
src/cpu/arm/meson.build Normal file
View File

@@ -0,0 +1,4 @@
lib_sources += files(
'instruction.cc',
'exec.cc'
)

144
src/cpu/cpu-impl.cc Normal file
View File

@@ -0,0 +1,144 @@
#include "cpu-impl.hh"
#include "util/bits.hh"
#include "util/log.hh"
#include "utility.hh"
#include <algorithm>
#include <cstdio>
using namespace logger;
CpuImpl::CpuImpl(const Bus& bus) noexcept
: bus(std::make_shared<Bus>(bus))
, gpr({ 0 })
, cpsr(0)
, spsr(0)
, is_flushed(false)
, gpr_banked({ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } })
, spsr_banked({ 0, 0, 0, 0, 0 }) {
cpsr.set_mode(Mode::Supervisor);
cpsr.set_irq_disabled(true);
cpsr.set_fiq_disabled(true);
cpsr.set_state(State::Arm);
log_info("CPU successfully initialised");
// PC always points to two instructions ahead
// PC - 2 is the instruction being executed
pc += 2 * arm::INSTRUCTION_SIZE;
}
/* change modes */
void
CpuImpl::chg_mode(const Mode to) {
Mode from = cpsr.mode();
if (from == to)
return;
/* TODO: replace visible registers with view once I understand how to
* concatenate views */
#define STORE_BANKED(mode, MODE) \
std::copy(gpr.begin() + GPR_##MODE##_FIRST, \
gpr.begin() + gpr.size() - 1, \
gpr_banked.mode.begin())
switch (from) {
case Mode::Fiq:
STORE_BANKED(fiq, FIQ);
spsr_banked.fiq = spsr;
break;
case Mode::Supervisor:
STORE_BANKED(svc, SVC);
spsr_banked.svc = spsr;
break;
case Mode::Abort:
STORE_BANKED(abt, ABT);
spsr_banked.abt = spsr;
break;
case Mode::Irq:
STORE_BANKED(irq, IRQ);
spsr_banked.irq = spsr;
break;
case Mode::Undefined:
STORE_BANKED(und, UND);
spsr_banked.und = spsr;
break;
case Mode::User:
case Mode::System:
STORE_BANKED(old, SYS_USR);
break;
}
#define RESTORE_BANKED(mode, MODE) \
std::copy(gpr_banked.mode.begin(), \
gpr_banked.mode.end(), \
gpr.begin() + GPR_##MODE##_FIRST)
switch (to) {
case Mode::Fiq:
RESTORE_BANKED(fiq, FIQ);
spsr = spsr_banked.fiq;
break;
case Mode::Supervisor:
RESTORE_BANKED(svc, SVC);
spsr = spsr_banked.svc;
break;
case Mode::Abort:
RESTORE_BANKED(abt, ABT);
spsr = spsr_banked.abt;
break;
case Mode::Irq:
RESTORE_BANKED(irq, IRQ);
spsr = spsr_banked.irq;
break;
case Mode::Undefined:
RESTORE_BANKED(und, UND);
spsr = spsr_banked.und;
break;
case Mode::User:
case Mode::System:
STORE_BANKED(old, SYS_USR);
break;
}
#undef RESTORE_BANKED
cpsr.set_mode(to);
}
void
CpuImpl::step() {
// Current instruction is two instructions behind PC
uint32_t cur_pc = pc - 2 * arm::INSTRUCTION_SIZE;
if (cpsr.state() == State::Arm) {
debug(cur_pc);
uint32_t x = bus->read_word(cur_pc);
arm::Instruction instruction(x);
log_info("{:#034b}", x);
exec_arm(instruction);
log_info("0x{:08X} : {}", cur_pc, instruction.disassemble());
if (is_flushed) {
// if flushed, do not increment the PC, instead set it to two
// instructions ahead to account for flushed "fetch" and "decode"
// instructions
pc += 2 * arm::INSTRUCTION_SIZE;
is_flushed = false;
} else {
// if not flushed continue like normal
pc += arm::INSTRUCTION_SIZE;
}
}
}

57
src/cpu/cpu-impl.hh Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include "bus.hh"
#include "cpu/arm/instruction.hh"
#include "cpu/psr.hh"
#include <cstdint>
class CpuImpl {
public:
CpuImpl(const Bus& bus) noexcept;
void step();
void chg_mode(const Mode to);
void exec_arm(const arm::Instruction instruction);
static constexpr uint8_t GPR_COUNT = 16;
static constexpr uint8_t GPR_FIQ_FIRST = 8;
static constexpr uint8_t GPR_SVC_FIRST = 13;
static constexpr uint8_t GPR_ABT_FIRST = 13;
static constexpr uint8_t GPR_IRQ_FIRST = 13;
static constexpr uint8_t GPR_UND_FIRST = 13;
static constexpr uint8_t GPR_SYS_USR_FIRST = 8;
std::shared_ptr<Bus> bus;
std::array<uint32_t, GPR_COUNT> gpr; // general purpose registers
Psr cpsr; // current program status register
Psr spsr; // status program status register
static constexpr uint8_t PC_INDEX = 15;
static_assert(PC_INDEX < GPR_COUNT);
uint32_t& pc = gpr[PC_INDEX];
bool is_flushed;
struct {
std::array<uint32_t, GPR_COUNT - GPR_FIQ_FIRST - 1> fiq;
std::array<uint32_t, GPR_COUNT - GPR_SVC_FIRST - 1> svc;
std::array<uint32_t, GPR_COUNT - GPR_ABT_FIRST - 1> abt;
std::array<uint32_t, GPR_COUNT - GPR_IRQ_FIRST - 1> irq;
std::array<uint32_t, GPR_COUNT - GPR_UND_FIRST - 1> und;
// visible registers before the mode switch
std::array<uint32_t, GPR_COUNT - GPR_SYS_USR_FIRST> old;
} gpr_banked; // banked general purpose registers
struct {
Psr fiq;
Psr svc;
Psr abt;
Psr irq;
Psr und;
} spsr_banked; // banked saved program status registers
};

View File

@@ -1,714 +1,12 @@
#include "cpu/cpu.hh"
#include "cpu/utility.hh"
#include "util/bits.hh"
#include "util/log.hh"
#include <algorithm>
#include <cstdio>
#include "cpu-impl.hh"
using namespace logger;
Cpu::Cpu(const Bus& bus) noexcept
: impl(std::make_unique<CpuImpl>(bus)){};
Cpu::Cpu(Bus& bus)
: bus(std::make_shared<Bus>(bus))
, gpr({ 0 })
, cpsr(0)
, spsr(0)
, is_flushed(false)
, gpr_banked({ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } })
, spsr_banked({ 0, 0, 0, 0, 0 }) {
cpsr.set_mode(Mode::Supervisor);
cpsr.set_irq_disabled(true);
cpsr.set_fiq_disabled(true);
cpsr.set_state(State::Arm);
log_info("CPU successfully initialised");
// PC always points to two instructions ahead
// PC - 2 is the instruction being executed
pc += 2 * ARM_INSTRUCTION_SIZE;
}
/* change modes */
void
Cpu::chg_mode(const Mode to) {
Mode from = cpsr.mode();
if (from == to)
return;
/* TODO: replace visible registers with view once I understand how to
* concatenate views */
#define STORE_BANKED(mode, MODE) \
std::copy(gpr.begin() + GPR_##MODE##_FIRST, \
gpr.begin() + gpr.size() - 1, \
gpr_banked.mode.begin())
switch (from) {
case Mode::Fiq:
STORE_BANKED(fiq, FIQ);
spsr_banked.fiq = spsr;
break;
case Mode::Supervisor:
STORE_BANKED(svc, SVC);
spsr_banked.svc = spsr;
break;
case Mode::Abort:
STORE_BANKED(abt, ABT);
spsr_banked.abt = spsr;
break;
case Mode::Irq:
STORE_BANKED(irq, IRQ);
spsr_banked.irq = spsr;
break;
case Mode::Undefined:
STORE_BANKED(und, UND);
spsr_banked.und = spsr;
break;
case Mode::User:
case Mode::System:
STORE_BANKED(old, SYS_USR);
break;
}
#define RESTORE_BANKED(mode, MODE) \
std::copy(gpr_banked.mode.begin(), \
gpr_banked.mode.end(), \
gpr.begin() + GPR_##MODE##_FIRST)
switch (to) {
case Mode::Fiq:
RESTORE_BANKED(fiq, FIQ);
spsr = spsr_banked.fiq;
break;
case Mode::Supervisor:
RESTORE_BANKED(svc, SVC);
spsr = spsr_banked.svc;
break;
case Mode::Abort:
RESTORE_BANKED(abt, ABT);
spsr = spsr_banked.abt;
break;
case Mode::Irq:
RESTORE_BANKED(irq, IRQ);
spsr = spsr_banked.irq;
break;
case Mode::Undefined:
RESTORE_BANKED(und, UND);
spsr = spsr_banked.und;
break;
case Mode::User:
case Mode::System:
STORE_BANKED(old, SYS_USR);
break;
}
#undef RESTORE_BANKED
cpsr.set_mode(to);
}
void
Cpu::exec_arm(const arm::Instruction instruction) {
auto cond = instruction.condition;
auto data = instruction.data;
if (!cpsr.condition(cond)) {
return;
}
auto pc_error = [](uint8_t r) {
if (r == PC_INDEX)
log_error("Using PC (R15) as operand register");
};
auto pc_warn = [](uint8_t r) {
if (r == PC_INDEX)
log_warn("Using PC (R15) as operand register");
};
using namespace arm;
std::visit(
overloaded{
[this, pc_warn](BranchAndExchange& data) {
State state = static_cast<State>(data.rn & 1);
pc_warn(data.rn);
// set state
cpsr.set_state(state);
// copy to PC
pc = gpr[data.rn];
// ignore [1:0] bits for arm and 0 bit for thumb
rst_bit(pc, 0);
if (state == State::Arm)
rst_bit(pc, 1);
// pc is affected so flush the pipeline
is_flushed = true;
},
[this](Branch& data) {
if (data.link)
gpr[14] = pc - ARM_INSTRUCTION_SIZE;
// data.offset accounts for two instructions ahead when
// disassembling, so need to adjust
pc =
static_cast<int32_t>(pc) - 2 * ARM_INSTRUCTION_SIZE + data.offset;
// pc is affected so flush the pipeline
is_flushed = true;
},
[this, pc_error](Multiply& data) {
if (data.rd == data.rm)
log_error("rd and rm are not distinct in {}",
typeid(data).name());
pc_error(data.rd);
pc_error(data.rd);
pc_error(data.rd);
gpr[data.rd] =
gpr[data.rm] * gpr[data.rs] + (data.acc ? gpr[data.rn] : 0);
if (data.set) {
cpsr.set_z(gpr[data.rd] == 0);
cpsr.set_n(get_bit(gpr[data.rd], 31));
cpsr.set_c(0);
}
},
[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 {}",
typeid(data).name());
pc_error(data.rdhi);
pc_error(data.rdlo);
pc_error(data.rm);
pc_error(data.rs);
if (data.uns) {
uint64_t eval =
static_cast<uint64_t>(gpr[data.rm]) *
static_cast<uint64_t>(gpr[data.rs]) +
(data.acc ? static_cast<uint64_t>(gpr[data.rdhi]) << 32 |
static_cast<uint64_t>(gpr[data.rdlo])
: 0);
gpr[data.rdlo] = bit_range(eval, 0, 31);
gpr[data.rdhi] = bit_range(eval, 32, 63);
} else {
int64_t eval =
static_cast<int64_t>(gpr[data.rm]) *
static_cast<int64_t>(gpr[data.rs]) +
(data.acc ? static_cast<int64_t>(gpr[data.rdhi]) << 32 |
static_cast<int64_t>(gpr[data.rdlo])
: 0);
gpr[data.rdlo] = bit_range(eval, 0, 31);
gpr[data.rdhi] = bit_range(eval, 32, 63);
}
if (data.set) {
cpsr.set_z(gpr[data.rdhi] == 0 && gpr[data.rdlo] == 0);
cpsr.set_n(get_bit(gpr[data.rdhi], 31));
cpsr.set_c(0);
cpsr.set_v(0);
}
},
[](Undefined) { log_warn("Undefined instruction"); },
[this, pc_error](SingleDataSwap& data) {
pc_error(data.rm);
pc_error(data.rn);
pc_error(data.rd);
if (data.byte) {
gpr[data.rd] = bus->read_byte(gpr[data.rn]);
bus->write_byte(gpr[data.rn], gpr[data.rm] & 0xFF);
} else {
gpr[data.rd] = bus->read_word(gpr[data.rn]);
bus->write_word(gpr[data.rn], gpr[data.rm]);
}
},
[this, pc_warn, pc_error](SingleDataTransfer& data) {
uint32_t offset = 0;
uint32_t address = gpr[data.rn];
if (!data.pre && data.write)
log_warn("Write-back enabled with post-indexing in {}",
typeid(data).name());
if (data.rn == PC_INDEX && data.write)
log_warn("Write-back enabled with base register as PC {}",
typeid(data).name());
if (data.write)
pc_warn(data.rn);
// evaluate the offset
if (const uint16_t* immediate =
std::get_if<uint16_t>(&data.offset)) {
offset = *immediate;
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
uint8_t amount =
(shift->data.immediate ? shift->data.operand
: gpr[shift->data.operand] & 0xFF);
bool carry = cpsr.c();
if (!shift->data.immediate)
pc_error(shift->data.operand);
pc_error(shift->rm);
offset =
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
cpsr.set_c(carry);
}
// PC is always two instructions ahead
if (data.rn == PC_INDEX)
address -= 2 * ARM_INSTRUCTION_SIZE;
if (data.pre)
address += (data.up ? offset : -offset);
debug(address);
// load
if (data.load) {
// byte
if (data.byte)
gpr[data.rd] = bus->read_byte(address);
// word
else
gpr[data.rd] = bus->read_word(address);
// store
} else {
// take PC into consideration
if (data.rd == PC_INDEX)
address += ARM_INSTRUCTION_SIZE;
// byte
if (data.byte)
bus->write_byte(address, gpr[data.rd] & 0xFF);
// word
else
bus->write_word(address, gpr[data.rd]);
}
if (!data.pre)
address += (data.up ? offset : -offset);
if (!data.pre || data.write)
gpr[data.rn] = address;
if (data.rd == PC_INDEX && data.load)
is_flushed = true;
},
[this, pc_warn, pc_error](HalfwordTransfer& data) {
uint32_t address = gpr[data.rn];
if (!data.pre && data.write)
log_error("Write-back enabled with post-indexing in {}",
typeid(data).name());
if (data.sign && !data.load)
log_error("Signed data found in {}", typeid(data).name());
if (data.write)
pc_warn(data.rn);
// offset is register number (4 bits) when not an immediate
if (!data.imm)
pc_error(data.offset);
if (data.pre)
address += (data.up ? data.offset : -data.offset);
// load
if (data.load) {
// signed
if (data.sign) {
// halfword
if (data.half) {
gpr[data.rd] = bus->read_halfword(address);
// sign extend the halfword
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
gpr[data.rd] =
(static_cast<int32_t>(gpr[data.rd]) << 24) >> 24;
}
// unsigned halfword
} else if (data.half) {
gpr[data.rd] = bus->read_halfword(address);
}
// store
} else {
// take PC into consideration
if (data.rd == PC_INDEX)
address += ARM_INSTRUCTION_SIZE;
// halfword
if (data.half)
bus->write_halfword(address, gpr[data.rd]);
}
if (!data.pre)
address += (data.up ? data.offset : -data.offset);
if (!data.pre || data.write)
gpr[data.rn] = address;
if (data.rd == PC_INDEX && data.load)
is_flushed = true;
},
[this, pc_error](BlockDataTransfer& data) {
uint32_t address = gpr[data.rn];
Mode mode = cpsr.mode();
uint8_t alignment = 4; // word
uint8_t i = 0;
uint8_t n_regs = std::popcount(data.regs);
pc_error(data.rn);
if (cpsr.mode() == Mode::User && data.s) {
log_error("Bit S is set outside priviliged modes in {}",
typeid(data).name());
}
// we just change modes to load user registers
if ((!get_bit(data.regs, PC_INDEX) && data.s) ||
(!data.load && data.s)) {
chg_mode(Mode::User);
if (data.write) {
log_error("Write-back enable for user bank registers in {}",
typeid(data).name());
}
}
// account for decrement
if (!data.up)
address -= (n_regs - 1) * alignment;
if (data.pre)
address += (data.up ? alignment : -alignment);
if (data.load) {
if (get_bit(data.regs, PC_INDEX) && data.s && data.load) {
// current mode's spsr is already loaded when it was
// switched
spsr = cpsr;
}
for (i = 0; i < GPR_COUNT; i++) {
if (get_bit(data.regs, i)) {
gpr[i] = bus->read_word(address);
address += alignment;
}
}
} else {
for (i = 0; i < GPR_COUNT; i++) {
if (get_bit(data.regs, i)) {
bus->write_word(address, gpr[i]);
address += alignment;
}
}
}
if (!data.pre)
address += (data.up ? alignment : -alignment);
// reset back to original address + offset if incremented earlier
if (data.up)
address -= n_regs * alignment;
if (!data.pre || data.write)
gpr[data.rn] = address;
if (data.load && get_bit(data.regs, PC_INDEX))
is_flushed = true;
// load back the original mode registers
chg_mode(mode);
},
[this, pc_error](PsrTransfer& data) {
if (data.spsr && cpsr.mode() == Mode::User) {
log_error("Accessing SPSR in User mode in {}",
typeid(data).name());
}
Psr& psr = data.spsr ? spsr : cpsr;
switch (data.type) {
case PsrTransfer::Type::Mrs:
pc_error(data.operand);
gpr[data.operand] = psr.raw();
break;
case PsrTransfer::Type::Msr:
pc_error(data.operand);
if (cpsr.mode() != Mode::User) {
psr.set_all(gpr[data.operand]);
}
break;
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));
psr.set_v(get_bit(data.operand, 28));
break;
}
},
[this, pc_error](DataProcessing& data) {
uint32_t op_1 = gpr[data.rn];
uint32_t op_2 = 0;
uint32_t result = 0;
bool overflow = cpsr.v();
bool carry = cpsr.c();
bool negative = cpsr.n();
bool zero = cpsr.z();
if (const uint32_t* immediate =
std::get_if<uint32_t>(&data.operand)) {
op_2 = *immediate;
} else if (const Shift* shift = std::get_if<Shift>(&data.operand)) {
uint8_t amount =
(shift->data.immediate ? shift->data.operand
: gpr[shift->data.operand] & 0xFF);
bool carry = cpsr.c();
if (!shift->data.immediate)
pc_error(shift->data.operand);
pc_error(shift->rm);
op_2 =
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
cpsr.set_c(carry);
// PC is 12 bytes ahead when shifting
if (data.rn == PC_INDEX)
op_1 += ARM_INSTRUCTION_SIZE;
}
switch (data.opcode) {
case OpCode::AND: {
result = op_1 & op_2;
negative = get_bit(result, 31);
} break;
case OpCode::EOR: {
result = op_1 ^ op_2;
negative = get_bit(result, 31);
} break;
case OpCode::SUB: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
result = op_1 - op_2;
negative = get_bit(result, 31);
carry = op_1 < op_2;
overflow = s1 != s2 && s2 == negative;
} break;
case OpCode::RSB: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
result = op_2 - op_1;
negative = get_bit(result, 31);
carry = op_2 < op_1;
overflow = s1 != s2 && s1 == negative;
} break;
case OpCode::ADD: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
// result_ is 33 bits
uint64_t result_ = op_2 + op_1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 == s2 && s1 != negative;
} break;
case OpCode::ADC: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_2 + op_1 + carry;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 == s2 && s1 != negative;
} break;
case OpCode::SBC: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_1 - op_2 + carry - 1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 != s2 && s2 == negative;
} break;
case OpCode::RSC: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_1 - op_2 + carry - 1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 != s2 && s1 == negative;
} break;
case OpCode::TST: {
result = op_1 & op_2;
negative = get_bit(result, 31);
} break;
case OpCode::TEQ: {
result = op_1 ^ op_2;
negative = get_bit(result, 31);
} break;
case OpCode::CMP: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
result = op_1 - op_2;
negative = get_bit(result, 31);
carry = op_1 < op_2;
overflow = s1 != s2 && s2 == negative;
} break;
case OpCode::CMN: {
bool s1 = get_bit(op_1, 31);
bool s2 = get_bit(op_2, 31);
uint64_t result_ = op_2 + op_1;
result = result_ & 0xFFFFFFFF;
negative = get_bit(result, 31);
carry = get_bit(result_, 32);
overflow = s1 == s2 && s1 != negative;
} break;
case OpCode::ORR: {
result = op_1 | op_2;
negative = get_bit(result, 31);
} break;
case OpCode::MOV: {
result = op_2;
negative = get_bit(result, 31);
} break;
case OpCode::BIC: {
result = op_1 & ~op_2;
negative = get_bit(result, 31);
} break;
case OpCode::MVN: {
result = ~op_2;
negative = get_bit(result, 31);
} break;
}
zero = result == 0;
debug(carry);
debug(overflow);
debug(zero);
debug(negative);
auto set_conditions = [this, carry, overflow, negative, zero]() {
cpsr.set_c(carry);
cpsr.set_v(overflow);
cpsr.set_n(negative);
cpsr.set_z(zero);
};
if (data.set) {
if (data.rd == 15) {
if (cpsr.mode() == Mode::User)
log_error("Running {} in User mode",
typeid(data).name());
} else {
set_conditions();
}
}
if (data.opcode == OpCode::TST || data.opcode == OpCode::TEQ ||
data.opcode == OpCode::CMP || data.opcode == OpCode::CMN) {
set_conditions();
} else {
gpr[data.rd] = result;
if (data.rd == 15 || data.opcode == OpCode::MVN)
is_flushed = true;
}
},
[this](SoftwareInterrupt) {
chg_mode(Mode::Supervisor);
pc = 0x08;
spsr = cpsr;
},
[](auto& data) {
log_error("Unimplemented {} instruction", typeid(data).name());
} },
data);
}
Cpu::~Cpu() = default;
void
Cpu::step() {
// Current instruction is two instructions behind PC
uint32_t cur_pc = pc - 2 * ARM_INSTRUCTION_SIZE;
if (cpsr.state() == State::Arm) {
debug(cur_pc);
uint32_t x = bus->read_word(cur_pc);
arm::Instruction instruction(x);
log_info("{:#034b}", x);
exec_arm(instruction);
log_info("0x{:08X} : {}", cur_pc, instruction.disassemble());
if (is_flushed) {
// if flushed, do not increment the PC, instead set it to two
// instructions ahead to account for flushed "fetch" and "decode"
// instructions
pc += 2 * ARM_INSTRUCTION_SIZE;
is_flushed = false;
} else {
// if not flushed continue like normal
pc += ARM_INSTRUCTION_SIZE;
}
}
}
impl->step();
};

View File

@@ -1,6 +1,8 @@
lib_sources += files(
'cpu-impl.cc',
'cpu.cc',
'instruction.cc',
'psr.cc',
'utility.cc'
)
)
subdir('arm')

View File

@@ -1,4 +1,4 @@
#include "cpu/psr.hh"
#include "psr.hh"
#include "util/bits.hh"
#include "util/log.hh"

View File

@@ -1,4 +1,4 @@
#include "cpu/utility.hh"
#include "utility.hh"
#include "util/bits.hh"
#include <bit>
@@ -102,13 +102,11 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
break;
case ShiftType::ROR:
if (amount == 0) {
bool old_carry = carry;
eval = (value >> 1) | (carry << 31);
carry = get_bit(value, 0);
eval = (value >> 1) | (old_carry << 31);
} else {
carry = get_bit(value, (amount % 32 + 31) % 32);
eval = std::rotr(value, amount);
carry = get_bit(value, (amount % 32 + 31) % 32);
}
break;
}

View File

@@ -3,7 +3,6 @@
#include <fmt/ostream.h>
#include <ostream>
static constexpr size_t ARM_INSTRUCTION_SIZE = 4;
static constexpr size_t THUMB_INSTRUCTION_SIZE = 2;
enum class Mode {

View File

@@ -4,11 +4,12 @@
#include "util/log.hh"
#include "util/utils.hh"
#include <bitset>
#include <stdexcept>
using namespace logger;
Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
std::vector<uint8_t>&& rom) noexcept
std::vector<uint8_t>&& rom)
: bios(std::move(bios))
, board_wram({ 0 })
, chip_wram({ 0 })
@@ -116,18 +117,24 @@ Memory::read_word(size_t address) const {
}
void
Memory::write_word(size_t address, uint32_t halfword) {
Memory::write_word(size_t address, uint32_t word) {
if (address & 0b11)
log_warn("Writing to a non aligned word address");
write(address, halfword & 0xFF);
write(address + 1, halfword >> 8 & 0xFF);
write(address + 2, halfword >> 16 & 0xFF);
write(address + 3, halfword >> 24 & 0xFF);
write(address, word & 0xFF);
write(address + 1, word >> 8 & 0xFF);
write(address + 2, word >> 16 & 0xFF);
write(address + 3, word >> 24 & 0xFF);
}
void
Memory::parse_header() {
if (rom.size() < header.HEADER_SIZE) {
throw std::out_of_range(
"ROM is not large enough to even have a header");
}
// entrypoint
header.entrypoint =
rom[0x00] | rom[0x01] << 8 | rom[0x02] << 16 | rom[0x03] << 24;

View File

@@ -3,8 +3,6 @@
#include <array>
#include <bit>
#include <fmt/core.h>
#include <iomanip>
#include <sstream>
#include <string>
// Why I wrote this myself? I do not know

736
tests/cpu/arm/exec.cc Normal file
View File

@@ -0,0 +1,736 @@
#include "cpu/cpu-impl.hh"
#include "cpu/utility.hh"
#include "util/bits.hh"
#include <catch2/catch_test_macros.hpp>
#include <variant>
class CpuFixture {
public:
CpuFixture()
: cpu(Bus(Memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
std::vector<uint8_t>(Header::HEADER_SIZE)))) {}
protected:
void exec(arm::InstructionData data, Condition condition = Condition::AL) {
arm::Instruction instruction(condition, data);
cpu.exec_arm(instruction);
}
void reset(uint32_t value = 0) {
cpu.pc = value + arm::INSTRUCTION_SIZE * 2;
}
CpuImpl cpu;
};
#define TAG "arm execution"
using namespace arm;
TEST_CASE_METHOD(CpuFixture, "Branch and Exchange", TAG) {
InstructionData data = BranchAndExchange{ .rn = 3 };
cpu.gpr[3] = 342890;
exec(data);
CHECK(cpu.pc == 342890);
}
TEST_CASE_METHOD(CpuFixture, "Branch", TAG) {
InstructionData data = Branch{ .link = false, .offset = 3489748 };
Branch* branch = std::get_if<Branch>(&data);
exec(data);
CHECK(cpu.pc == 3489748);
CHECK(cpu.gpr[14] == 0);
// with link
reset();
branch->link = true;
exec(data);
CHECK(cpu.pc == 3489748);
CHECK(cpu.gpr[14] == 0 + INSTRUCTION_SIZE);
}
TEST_CASE_METHOD(CpuFixture, "Multiply", TAG) {
InstructionData data = Multiply{
.rm = 10, .rs = 11, .rn = 3, .rd = 5, .set = false, .acc = false
};
Multiply* multiply = std::get_if<Multiply>(&data);
cpu.gpr[10] = 234912349;
cpu.gpr[11] = 124897;
cpu.gpr[3] = 99999;
{
uint32_t result = 234912349ull * 124897ull & 0xFFFFFFFF;
exec(data);
CHECK(cpu.gpr[5] == result);
}
// with accumulate
{
uint32_t result = 234912349ull * 124897ull + 99999ull & 0xFFFFFFFF;
multiply->acc = true;
exec(data);
CHECK(cpu.gpr[5] == result);
}
// with set
{
uint32_t result = 234912349ull * 124897ull + 99999ull & 0xFFFFFFFF;
multiply->set = true;
exec(data);
CHECK(cpu.gpr[5] == result);
CHECK(cpu.cpsr.n() == get_bit(result, 31));
}
// with set and zero
{
cpu.gpr[10] = 0;
cpu.gpr[3] = 0;
exec(data);
CHECK(cpu.gpr[5] == 0);
CHECK(cpu.cpsr.n() == false);
CHECK(cpu.cpsr.z() == true);
}
}
TEST_CASE_METHOD(CpuFixture, "Multiply Long", TAG) {
InstructionData data = MultiplyLong{ .rm = 10,
.rs = 11,
.rdlo = 3,
.rdhi = 5,
.set = false,
.acc = false,
.uns = true };
MultiplyLong* multiply_long = std::get_if<MultiplyLong>(&data);
cpu.gpr[10] = 234912349;
cpu.gpr[11] = 124897;
// unsigned
{
uint64_t result = 234912349ull * 124897ull;
exec(data);
CHECK(cpu.gpr[3] == bit_range(result, 0, 31));
CHECK(cpu.gpr[5] == bit_range(result, 32, 63));
}
// signed
{
int64_t result = 234912349ll * -124897ll;
cpu.gpr[11] *= -1;
multiply_long->uns = false;
exec(data);
CHECK(cpu.gpr[3] == static_cast<uint32_t>(bit_range(result, 0, 31)));
CHECK(cpu.gpr[5] == static_cast<uint32_t>(bit_range(result, 32, 63)));
}
// accumulate
{
cpu.gpr[3] = 99999;
cpu.gpr[5] = -444333391;
int64_t result =
234912349ll * -124897ll + (99999ll | -444333391ll << 32);
multiply_long->acc = true;
exec(data);
CHECK(cpu.gpr[3] == static_cast<uint32_t>(bit_range(result, 0, 31)));
CHECK(cpu.gpr[5] == static_cast<uint32_t>(bit_range(result, 32, 63)));
}
// set
{
cpu.gpr[3] = 99999;
cpu.gpr[5] = -444333391;
int64_t result =
234912349ll * -124897ll + (99999ll | -444333391ll << 32);
multiply_long->set = true;
exec(data);
CHECK(cpu.gpr[3] == static_cast<uint32_t>(bit_range(result, 0, 31)));
CHECK(cpu.gpr[5] == static_cast<uint32_t>(bit_range(result, 32, 63)));
CHECK(cpu.cpsr.n() == true);
CHECK(cpu.cpsr.z() == false);
}
// zero
{
cpu.gpr[10] = 0;
cpu.gpr[5] = 0;
cpu.gpr[3] = 0;
exec(data);
CHECK(cpu.gpr[3] == 0);
CHECK(cpu.gpr[5] == 0);
CHECK(cpu.cpsr.n() == false);
CHECK(cpu.cpsr.z() == true);
}
}
TEST_CASE_METHOD(CpuFixture, "Single Data Swap", TAG) {
InstructionData data =
SingleDataSwap{ .rm = 3, .rd = 4, .rn = 9, .byte = false };
SingleDataSwap* swap = std::get_if<SingleDataSwap>(&data);
cpu.gpr[9] = 0x3FED;
cpu.gpr[3] = 94235087;
cpu.gpr[3] = -259039045;
cpu.bus->write_word(cpu.gpr[9], 3241011111);
SECTION("word") {
exec(data);
CHECK(cpu.gpr[4] == 3241011111);
CHECK(cpu.bus->read_word(cpu.gpr[9]) ==
static_cast<uint32_t>(-259039045));
}
SECTION("byte") {
swap->byte = true;
exec(data);
CHECK(cpu.gpr[4] == (3241011111 & 0xFF));
CHECK(cpu.bus->read_byte(cpu.gpr[9]) ==
static_cast<uint8_t>(-259039045 & 0xFF));
}
}
TEST_CASE_METHOD(CpuFixture, "Single Data Transfer", TAG) {
InstructionData data =
SingleDataTransfer{ .offset = Shift{ .rm = 3,
.data =
ShiftData{
.type = ShiftType::ROR,
.immediate = true,
.operand = 29,
} },
.rd = 5,
.rn = 7,
.load = true,
.write = false,
.byte = false,
.up = true,
.pre = true };
SingleDataTransfer* data_transfer = std::get_if<SingleDataTransfer>(&data);
cpu.gpr[3] = 1596;
cpu.gpr[12] = 3;
cpu.gpr[7] = 6;
cpu.gpr[5] = -911111;
// shifted register (immediate)
{
cpu.bus->write_word(12774, 95995);
exec(data);
CHECK(cpu.gpr[5] == 95995);
}
// shifted register (register)
{
data_transfer->offset = Shift{ .rm = 3,
.data = ShiftData{
.type = ShiftType::LSL,
.immediate = false,
.operand = 12,
} };
cpu.bus->write_word(12774, 3948123487);
exec(data);
CHECK(cpu.gpr[5] == 3948123487);
}
// immediate
{
data_transfer->offset = static_cast<uint16_t>(3489);
// 6 + 3489
cpu.bus->write_word(3495, 68795467);
exec(data);
CHECK(cpu.gpr[5] == 68795467);
}
// down
{
cpu.gpr[7] = 18044;
data_transfer->up = false;
// 18044 - 3489
cpu.bus->write_word(14555, 5949595);
exec(data);
CHECK(cpu.gpr[5] == 5949595);
// no write back
CHECK(cpu.gpr[7] == 18044);
}
// write
{
data_transfer->write = true;
cpu.bus->write_word(14555, 967844);
exec(data);
CHECK(cpu.gpr[5] == 967844);
// 18044 - 3489
CHECK(cpu.gpr[7] == 14555);
}
// post
{
data_transfer->write = false;
data_transfer->pre = false;
cpu.bus->write_word(14555, 61119);
exec(data);
CHECK(cpu.gpr[5] == 61119);
// 14555 - 3489
CHECK(cpu.gpr[7] == 11066);
}
// store
{
data_transfer->load = false;
exec(data);
CHECK(cpu.bus->read_word(11066) == 61119);
// 11066 - 3489
CHECK(cpu.gpr[7] == 7577);
}
// r15 as rn
{
data_transfer->rn = 15;
cpu.gpr[15] = 7577;
exec(data);
CHECK(cpu.bus->read_word(7577 - 2 * INSTRUCTION_SIZE) == 61119);
// 7577 - 3489
CHECK(cpu.gpr[15] == 4088 - 2 * INSTRUCTION_SIZE);
// cleanup
data_transfer->rn = 7;
}
// r15 as rd
{
// 4088
data_transfer->rd = 15;
cpu.gpr[15] = 444444;
exec(data);
CHECK(cpu.bus->read_word(7577 + INSTRUCTION_SIZE) == 444444);
// 7577 - 3489
CHECK(cpu.gpr[7] == 4088 + INSTRUCTION_SIZE);
// cleanup
data_transfer->rd = 5;
cpu.gpr[7] -= INSTRUCTION_SIZE;
}
// byte
{
data_transfer->byte = true;
cpu.gpr[5] = 458267584;
exec(data);
CHECK(cpu.bus->read_word(4088) == (458267584 & 0xFF));
// 4088 - 3489
CHECK(cpu.gpr[7] == 599);
}
}
TEST_CASE_METHOD(CpuFixture, "Halfword Transfer", TAG) {
InstructionData data = HalfwordTransfer{ .offset = 12,
.half = true,
.sign = false,
.rd = 11,
.rn = 10,
.load = true,
.write = false,
.imm = false,
.up = true,
.pre = true };
HalfwordTransfer* hw_transfer = std::get_if<HalfwordTransfer>(&data);
cpu.gpr[12] = 8404;
cpu.gpr[11] = 459058287;
cpu.gpr[10] = 900;
// register offset
{
// 900 + 8404
cpu.bus->write_word(9304, 3948123487);
exec(data);
CHECK(cpu.gpr[11] == (3948123487 & 0xFFFF));
}
// immediate offset
{
hw_transfer->imm = true;
hw_transfer->offset = 167;
// 900 + 167
cpu.bus->write_word(1067, 594633302);
exec(data);
CHECK(cpu.gpr[11] == (594633302 & 0xFFFF));
}
// down
{
hw_transfer->up = false;
// 900 - 167
cpu.bus->write_word(733, 222221);
exec(data);
CHECK(cpu.gpr[11] == (222221 & 0xFFFF));
// no write back
CHECK(cpu.gpr[10] == 900);
}
// write
{
hw_transfer->write = true;
// 900 - 167
cpu.bus->write_word(733, 100000005);
exec(data);
CHECK(cpu.gpr[11] == (100000005 & 0xFFFF));
// 900 - 167
CHECK(cpu.gpr[10] == 733);
}
// post
{
hw_transfer->pre = false;
hw_transfer->write = false;
cpu.bus->write_word(733, 6111909);
exec(data);
CHECK(cpu.gpr[11] == (6111909 & 0xFFFF));
// 733 - 167
CHECK(cpu.gpr[10] == 566);
}
// store
{
hw_transfer->load = false;
exec(data);
CHECK(cpu.bus->read_halfword(566) == (6111909 & 0xFFFF));
// 566 - 167
CHECK(cpu.gpr[10] == 399);
}
// r15 as rn
{
hw_transfer->rn = 15;
cpu.gpr[15] = 399;
exec(data);
CHECK(cpu.bus->read_halfword(399 - 2 * INSTRUCTION_SIZE) ==
(6111909 & 0xFFFF));
// 399 - 167
CHECK(cpu.gpr[15] == 232 - 2 * INSTRUCTION_SIZE);
// cleanup
hw_transfer->rn = 10;
}
// r15 as rd
{
hw_transfer->rd = 15;
cpu.gpr[15] = 224;
exec(data);
CHECK(cpu.bus->read_halfword(399 + INSTRUCTION_SIZE) == 224);
// 399 - 167
CHECK(cpu.gpr[10] == 232 + INSTRUCTION_SIZE);
// cleanup
hw_transfer->rd = 11;
cpu.gpr[10] = 399;
}
// signed halfword
{
hw_transfer->load = true;
hw_transfer->sign = true;
cpu.bus->write_halfword(399, -12345);
exec(data);
CHECK(cpu.gpr[11] == static_cast<uint32_t>(-12345));
// 399 - 167
CHECK(cpu.gpr[10] == 232);
}
// signed byte
{
hw_transfer->half = false;
cpu.bus->write_byte(232, -56);
exec(data);
CHECK(cpu.gpr[11] == static_cast<uint32_t>(-56));
// 232 - 167
CHECK(cpu.gpr[10] == 65);
}
}
TEST_CASE_METHOD(CpuFixture, "Block Data Transfer", TAG) {
InstructionData data = BlockDataTransfer{ .regs = 0b1010100111000001,
.rn = 10,
.load = true,
.write = false,
.s = false,
.up = true,
.pre = true };
BlockDataTransfer* block_transfer = std::get_if<BlockDataTransfer>(&data);
// load
SECTION("load") {
// populate memory
cpu.bus->write_word(3448, 38947234);
cpu.bus->write_word(3452, 237164);
cpu.bus->write_word(3456, 679785111);
cpu.bus->write_word(3460, 905895898);
cpu.bus->write_word(3464, 131313333);
cpu.bus->write_word(3468, 131);
cpu.bus->write_word(3472, 989231);
cpu.bus->write_word(3476, 6);
auto checker = [](decltype(cpu.gpr)& gpr, uint32_t rnval = 0) {
CHECK(gpr[0] == 237164);
CHECK(gpr[1] == 0);
CHECK(gpr[2] == 0);
CHECK(gpr[3] == 0);
CHECK(gpr[4] == 0);
CHECK(gpr[5] == 0);
CHECK(gpr[6] == 679785111);
CHECK(gpr[7] == 905895898);
CHECK(gpr[8] == 131313333);
CHECK(gpr[9] == 0);
CHECK(gpr[10] == rnval);
CHECK(gpr[11] == 131);
CHECK(gpr[12] == 0);
CHECK(gpr[13] == 989231);
CHECK(gpr[14] == 0);
CHECK(gpr[15] == 6);
for (uint8_t i = 0; i < 16; i++) {
gpr[i] = 0;
}
};
cpu.gpr[10] = 3448;
exec(data);
checker(cpu.gpr, 3448);
// with write
cpu.gpr[10] = 3448;
block_transfer->write = true;
exec(data);
checker(cpu.gpr, 3448 + INSTRUCTION_SIZE);
// decrement
block_transfer->write = false;
block_transfer->up = false;
// adjust rn
cpu.gpr[10] = 3480;
exec(data);
checker(cpu.gpr, 3480);
// with write
cpu.gpr[10] = 3480;
block_transfer->write = true;
exec(data);
checker(cpu.gpr, 3480 - INSTRUCTION_SIZE);
// post increment
block_transfer->write = false;
block_transfer->up = true;
block_transfer->pre = false;
// adjust rn
cpu.gpr[10] = 3452;
exec(data);
checker(cpu.gpr, 3452 + INSTRUCTION_SIZE);
// post decrement
block_transfer->up = false;
// adjust rn
cpu.gpr[10] = 3476;
exec(data);
checker(cpu.gpr, 3476 - INSTRUCTION_SIZE);
// with s bit
cpu.chg_mode(Mode::Fiq);
block_transfer->s = true;
CHECK(cpu.cpsr.raw() != cpu.spsr.raw());
exec(data);
CHECK(cpu.cpsr.raw() == cpu.spsr.raw());
}
// store
SECTION("store") {
block_transfer->load = false;
// populate registers
cpu.gpr[0] = 237164;
cpu.gpr[6] = 679785111;
cpu.gpr[7] = 905895898;
cpu.gpr[8] = 131313333;
cpu.gpr[11] = 131;
cpu.gpr[13] = 989231;
cpu.gpr[15] = 6;
auto checker = [this]() {
CHECK(cpu.bus->read_word(5548) == 237164);
CHECK(cpu.bus->read_word(5552) == 679785111);
CHECK(cpu.bus->read_word(5556) == 905895898);
CHECK(cpu.bus->read_word(5560) == 131313333);
CHECK(cpu.bus->read_word(5564) == 131);
CHECK(cpu.bus->read_word(5568) == 989231);
CHECK(cpu.bus->read_word(5572) == 6);
for (uint8_t i = 0; i < 8; i++)
cpu.bus->write_word(5548 + i * 4, 0);
};
cpu.gpr[10] = 5544; // base
exec(data);
checker();
// decrement
block_transfer->write = false;
block_transfer->up = false;
// adjust rn
cpu.gpr[10] = 5576;
exec(data);
checker();
// post increment
block_transfer->up = true;
block_transfer->pre = false;
// adjust rn
cpu.gpr[10] = 5548;
exec(data);
checker();
// post decrement
block_transfer->up = false;
// adjust rn
cpu.gpr[10] = 5572;
exec(data);
checker();
// with s bit
cpu.chg_mode(Mode::Fiq);
block_transfer->s = true;
cpu.chg_mode(Mode::Supervisor);
// User's R13 is different (unset at this point)
CHECK(cpu.bus->read_word(5568) == 0);
exec(data);
}
}
TEST_CASE_METHOD(CpuFixture, "PSR Transfer", TAG) {
InstructionData data = PsrTransfer{
.operand = 12,
.spsr = false,
.type = PsrTransfer::Type::Mrs,
.imm = false,
};
PsrTransfer* psr_transfer = std::get_if<PsrTransfer>(&data);
SECTION("MRS") {
cpu.gpr[12] = 12389398;
CHECK(cpu.cpsr.raw() != cpu.gpr[12]);
exec(data);
CHECK(cpu.cpsr.raw() == cpu.gpr[12]);
psr_transfer->spsr = true;
// with SPSR
CHECK(cpu.spsr.raw() != cpu.gpr[12]);
exec(data);
CHECK(cpu.spsr.raw() == cpu.gpr[12]);
}
// MSR
SECTION("MSR") {
psr_transfer->type = PsrTransfer::Type::Msr;
cpu.gpr[12] = 0;
// go to the reserved bits
cpu.gpr[12] |= 16556 << 8;
CHECK(cpu.cpsr.raw() != cpu.gpr[12]);
exec(data);
CHECK(cpu.cpsr.raw() == cpu.gpr[12]);
psr_transfer->spsr = true;
// with SPSR
CHECK(cpu.spsr.raw() != cpu.gpr[12]);
exec(data);
CHECK(cpu.spsr.raw() == cpu.gpr[12]);
}
// MSR_flg
SECTION("MSR_flg") {
psr_transfer->type = PsrTransfer::Type::Msr_flg;
cpu.gpr[12] = 1490352945;
// go to the reserved bits
exec(data);
CHECK(cpu.cpsr.n() == get_bit(1490352945, 31));
CHECK(cpu.cpsr.z() == get_bit(1490352945, 30));
CHECK(cpu.cpsr.c() == get_bit(1490352945, 29));
CHECK(cpu.cpsr.v() == get_bit(1490352945, 28));
// with SPSR and immediate operand
psr_transfer->operand = 99333394;
psr_transfer->imm = true;
psr_transfer->spsr = true;
exec(data);
CHECK(cpu.spsr.n() == get_bit(9933394, 31));
CHECK(cpu.spsr.z() == get_bit(9933394, 30));
CHECK(cpu.spsr.c() == get_bit(9933394, 29));
CHECK(cpu.spsr.v() == get_bit(9933394, 28));
}
}
#undef TAG

View File

@@ -0,0 +1,469 @@
#include "cpu/arm/instruction.hh"
#include "cpu/utility.hh"
#include <catch2/catch_test_macros.hpp>
#define TAG "disassembler"
using namespace arm;
TEST_CASE("Branch and Exchange", TAG) {
uint32_t raw = 0b11000001001011111111111100011010;
Instruction instruction(raw);
BranchAndExchange* bx = nullptr;
REQUIRE((bx = std::get_if<BranchAndExchange>(&instruction.data)));
CHECK(instruction.condition == Condition::GT);
CHECK(bx->rn == 10);
CHECK(instruction.disassemble() == "BXGT R10");
}
TEST_CASE("Branch", TAG) {
uint32_t raw = 0b11101011100001010111111111000011;
Instruction instruction(raw);
Branch* b = nullptr;
REQUIRE((b = std::get_if<Branch>(&instruction.data)));
CHECK(instruction.condition == Condition::AL);
// last 24 bits = 8748995
// (8748995 << 8) >> 6 sign extended = 0xFE15FF0C
// Also +8 since PC is two instructions ahead
CHECK(b->offset == 0xFE15FF14);
CHECK(b->link == true);
CHECK(instruction.disassemble() == "BL 0xFE15FF14");
b->link = false;
CHECK(instruction.disassemble() == "B 0xFE15FF14");
}
TEST_CASE("Multiply", TAG) {
uint32_t raw = 0b00000000001110101110111110010000;
Instruction instruction(raw);
Multiply* mul = nullptr;
REQUIRE((mul = std::get_if<Multiply>(&instruction.data)));
CHECK(instruction.condition == Condition::EQ);
CHECK(mul->rm == 0);
CHECK(mul->rs == 15);
CHECK(mul->rn == 14);
CHECK(mul->rd == 10);
CHECK(mul->acc == true);
CHECK(mul->set == true);
CHECK(instruction.disassemble() == "MLAEQS R10,R0,R15,R14");
mul->acc = false;
mul->set = false;
CHECK(instruction.disassemble() == "MULEQ R10,R0,R15");
}
TEST_CASE("Multiply Long", TAG) {
uint32_t raw = 0b00010000100111100111011010010010;
Instruction instruction(raw);
MultiplyLong* mull = nullptr;
REQUIRE((mull = std::get_if<MultiplyLong>(&instruction.data)));
CHECK(instruction.condition == Condition::NE);
CHECK(mull->rm == 2);
CHECK(mull->rs == 6);
CHECK(mull->rdlo == 7);
CHECK(mull->rdhi == 14);
CHECK(mull->acc == false);
CHECK(mull->set == true);
CHECK(mull->uns == true);
CHECK(instruction.disassemble() == "UMULLNES R7,R14,R2,R6");
mull->acc = true;
CHECK(instruction.disassemble() == "UMLALNES R7,R14,R2,R6");
mull->uns = false;
mull->set = false;
CHECK(instruction.disassemble() == "SMLALNE 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);
CHECK(instruction.condition == Condition::AL);
CHECK(instruction.disassemble() == "UND");
}
TEST_CASE("Single Data Swap", TAG) {
uint32_t raw = 0b10100001000010010101000010010110;
Instruction instruction(raw);
SingleDataSwap* swp = nullptr;
REQUIRE((swp = std::get_if<SingleDataSwap>(&instruction.data)));
CHECK(instruction.condition == Condition::GE);
CHECK(swp->rm == 6);
CHECK(swp->rd == 5);
CHECK(swp->rn == 9);
CHECK(swp->byte == false);
CHECK(instruction.disassemble() == "SWPGE R5,R6,[R9]");
swp->byte = true;
CHECK(instruction.disassemble() == "SWPGEB R5,R6,[R9]");
}
TEST_CASE("Single Data Transfer", TAG) {
uint32_t raw = 0b11100111101000101010111100000110;
Instruction instruction(raw);
SingleDataTransfer* ldr = nullptr;
Shift* shift = nullptr;
REQUIRE((ldr = std::get_if<SingleDataTransfer>(&instruction.data)));
CHECK(instruction.condition == Condition::AL);
REQUIRE((shift = std::get_if<Shift>(&ldr->offset)));
CHECK(shift->rm == 6);
CHECK(shift->data.immediate == true);
CHECK(shift->data.type == ShiftType::LSL);
CHECK(shift->data.operand == 30);
CHECK(ldr->rd == 10);
CHECK(ldr->rn == 2);
CHECK(ldr->load == false);
CHECK(ldr->write == true);
CHECK(ldr->byte == false);
CHECK(ldr->up == true);
CHECK(ldr->pre == true);
ldr->load = true;
ldr->byte = true;
ldr->write = false;
shift->data.type = ShiftType::ROR;
CHECK(instruction.disassemble() == "LDRB R10,[R2,+R6,ROR #30]");
ldr->up = false;
ldr->pre = false;
CHECK(instruction.disassemble() == "LDRB R10,[R2],-R6,ROR #30");
ldr->offset = static_cast<uint16_t>(9023);
CHECK(instruction.disassemble() == "LDRB R10,[R2],-#9023");
ldr->pre = true;
CHECK(instruction.disassemble() == "LDRB R10,[R2,-#9023]");
}
TEST_CASE("Halfword Transfer", TAG) {
uint32_t raw = 0b00110001101011110010000010110110;
Instruction instruction(raw);
HalfwordTransfer* ldr = nullptr;
REQUIRE((ldr = std::get_if<HalfwordTransfer>(&instruction.data)));
CHECK(instruction.condition == Condition::CC);
// offset is not immediate
CHECK(ldr->imm == 0);
// hence this offset is a register number (rm)
CHECK(ldr->offset == 6);
CHECK(ldr->half == true);
CHECK(ldr->sign == false);
CHECK(ldr->rd == 2);
CHECK(ldr->rn == 15);
CHECK(ldr->load == false);
CHECK(ldr->write == true);
CHECK(ldr->up == true);
CHECK(ldr->pre == true);
CHECK(instruction.disassemble() == "STRCCH R2,[R15,+R6]!");
ldr->pre = false;
ldr->load = true;
ldr->sign = true;
ldr->up = false;
CHECK(instruction.disassemble() == "LDRCCSH R2,[R15],-R6");
ldr->half = false;
CHECK(instruction.disassemble() == "LDRCCSB R2,[R15],-R6");
ldr->load = false;
// not a register anymore
ldr->imm = 1;
ldr->offset = 90;
CHECK(instruction.disassemble() == "STRCCSB R2,[R15],-#90");
}
TEST_CASE("Block Data Transfer", TAG) {
uint32_t raw = 0b10011001010101110100000101101101;
Instruction instruction(raw);
BlockDataTransfer* ldm = nullptr;
REQUIRE((ldm = std::get_if<BlockDataTransfer>(&instruction.data)));
CHECK(instruction.condition == Condition::LS);
{
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;
CHECK(ldm->regs == regs);
}
CHECK(ldm->rn == 7);
CHECK(ldm->load == true);
CHECK(ldm->write == false);
CHECK(ldm->s == true);
CHECK(ldm->up == false);
CHECK(ldm->pre == true);
CHECK(instruction.disassemble() == "LDMLSDB R7,{R0,R2,R3,R5,R6,R8,R14}^");
ldm->write = true;
ldm->s = false;
ldm->up = true;
CHECK(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;
CHECK(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)));
CHECK(instruction.condition == Condition::MI);
CHECK(mrs->type == PsrTransfer::Type::Mrs);
// Operand is a register in the case of MRS (PSR -> Register)
CHECK(mrs->operand == 10);
CHECK(mrs->spsr == true);
CHECK(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)));
CHECK(instruction.condition == Condition::AL);
CHECK(msr->type == PsrTransfer::Type::Msr);
// Operand is a register in the case of MSR (Register -> PSR)
CHECK(msr->operand == 8);
CHECK(msr->spsr == false);
CHECK(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)));
CHECK(instruction.condition == Condition::VS);
CHECK(msr->type == PsrTransfer::Type::Msr_flg);
CHECK(msr->imm == 0);
CHECK(msr->operand == 8);
CHECK(msr->spsr == false);
CHECK(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)));
CHECK(instruction.condition == Condition::AL);
CHECK(msr->type == PsrTransfer::Type::Msr_flg);
CHECK(msr->imm == 1);
// 104 (32 bits) rotated by 2 * 7
CHECK(msr->operand == 27262976);
CHECK(msr->spsr == true);
CHECK(instruction.disassemble() == "MSR SPSR_flg,#27262976");
}
}
TEST_CASE("Data Processing", TAG) {
uint32_t raw = 0b11100000000111100111101101100001;
Instruction instruction(raw);
DataProcessing* alu = nullptr;
Shift* shift = nullptr;
REQUIRE((alu = std::get_if<DataProcessing>(&instruction.data)));
CHECK(instruction.condition == Condition::AL);
// operand 2 is a shifted register
REQUIRE((shift = std::get_if<Shift>(&alu->operand)));
CHECK(shift->rm == 1);
CHECK(shift->data.immediate == true);
CHECK(shift->data.type == ShiftType::ROR);
CHECK(shift->data.operand == 22);
CHECK(alu->rd == 7);
CHECK(alu->rn == 14);
CHECK(alu->set == true);
CHECK(alu->opcode == OpCode::AND);
CHECK(instruction.disassemble() == "ANDS R7,R14,R1,ROR #22");
shift->data.immediate = false;
shift->data.operand = 2;
alu->set = false;
CHECK(instruction.disassemble() == "AND R7,R14,R1,ROR R2");
alu->operand = static_cast<uint32_t>(3300012);
CHECK(instruction.disassemble() == "AND R7,R14,#3300012");
SECTION("set-only operations") {
alu->set = true;
alu->opcode = OpCode::TST;
CHECK(instruction.disassemble() == "TST R14,#3300012");
alu->opcode = OpCode::TEQ;
CHECK(instruction.disassemble() == "TEQ R14,#3300012");
alu->opcode = OpCode::CMP;
CHECK(instruction.disassemble() == "CMP R14,#3300012");
alu->opcode = OpCode::CMN;
CHECK(instruction.disassemble() == "CMN R14,#3300012");
}
SECTION("destination operations") {
alu->opcode = OpCode::EOR;
CHECK(instruction.disassemble() == "EOR R7,R14,#3300012");
alu->opcode = OpCode::SUB;
CHECK(instruction.disassemble() == "SUB R7,R14,#3300012");
alu->opcode = OpCode::RSB;
CHECK(instruction.disassemble() == "RSB R7,R14,#3300012");
alu->opcode = OpCode::SUB;
CHECK(instruction.disassemble() == "SUB R7,R14,#3300012");
alu->opcode = OpCode::ADC;
CHECK(instruction.disassemble() == "ADC R7,R14,#3300012");
alu->opcode = OpCode::SBC;
CHECK(instruction.disassemble() == "SBC R7,R14,#3300012");
alu->opcode = OpCode::RSC;
CHECK(instruction.disassemble() == "RSC R7,R14,#3300012");
alu->opcode = OpCode::ORR;
CHECK(instruction.disassemble() == "ORR R7,R14,#3300012");
alu->opcode = OpCode::MOV;
CHECK(instruction.disassemble() == "MOV R7,#3300012");
alu->opcode = OpCode::BIC;
CHECK(instruction.disassemble() == "BIC R7,R14,#3300012");
alu->opcode = OpCode::MVN;
CHECK(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)));
CHECK(instruction.condition == Condition::GE);
CHECK(ldc->offset == 70);
CHECK(ldc->cpn == 1);
CHECK(ldc->crd == 15);
CHECK(ldc->rn == 5);
CHECK(ldc->load == false);
CHECK(ldc->write == true);
CHECK(ldc->len == false);
CHECK(ldc->up == true);
CHECK(ldc->pre == true);
CHECK(instruction.disassemble() == "STCGE p1,c15,[R5,#70]!");
ldc->load = true;
ldc->pre = false;
ldc->write = false;
ldc->len = true;
CHECK(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)));
CHECK(instruction.condition == Condition::AL);
CHECK(cdp->crm == 6);
CHECK(cdp->cp == 2);
CHECK(cdp->cpn == 1);
CHECK(cdp->crd == 15);
CHECK(cdp->crn == 5);
CHECK(cdp->cp_opc == 10);
CHECK(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)));
CHECK(instruction.condition == Condition::AL);
CHECK(mrc->crm == 6);
CHECK(mrc->cp == 2);
CHECK(mrc->cpn == 1);
CHECK(mrc->rd == 15);
CHECK(mrc->crn == 5);
CHECK(mrc->load == false);
CHECK(mrc->cp_opc == 5);
CHECK(instruction.disassemble() == "MCR p1,5,R15,c5,c6,2");
}
TEST_CASE("Software Interrupt", TAG) {
uint32_t raw = 0b00001111101010101010101010101010;
Instruction instruction(raw);
CHECK(instruction.condition == Condition::EQ);
CHECK(instruction.disassemble() == "SWIEQ");
}
#undef TAG

View File

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

View File

@@ -1,468 +0,0 @@
#include "cpu/instruction.hh"
#include "cpu/utility.hh"
#include <catch2/catch_test_macros.hpp>
#include <cstdint>
[[maybe_unused]] static constexpr auto TAG = "disassembler";
using namespace arm;
TEST_CASE("Branch and Exchange", TAG) {
uint32_t raw = 0b11000001001011111111111100011010;
Instruction 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;
Instruction 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;
Instruction 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;
Instruction 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("Undefined", TAG) {
// notice how this is the same as single data transfer except the shift
// is now a register based shift
uint32_t raw = 0b11100111101000101010111100010110;
Instruction instruction(raw);
REQUIRE(instruction.condition == Condition::AL);
REQUIRE(instruction.disassemble() == "UND");
}
TEST_CASE("Single Data Swap", TAG) {
uint32_t raw = 0b10100001000010010101000010010110;
Instruction 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;
Instruction 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;
Instruction 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");
ldr->load = false;
// not a register anymore
ldr->imm = 1;
ldr->offset = 90;
REQUIRE(instruction.disassemble() == "STRCCSB R2,[R15],-#90");
}
TEST_CASE("Block Data Transfer", TAG) {
uint32_t raw = 0b10011001010101110100000101101101;
Instruction instruction(raw);
BlockDataTransfer* ldm = nullptr;
REQUIRE((ldm = std::get_if<BlockDataTransfer>(&instruction.data)));
REQUIRE(instruction.condition == Condition::LS);
{
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");
}

View File

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

View File

@@ -2,6 +2,8 @@ tests_deps = [
lib
]
src = include_directories('../src')
tests_sources = files()
subdir('cpu')
@@ -12,7 +14,7 @@ catch2_tests = executable(
tests_sources,
dependencies: catch2,
link_with: tests_deps,
include_directories: inc,
include_directories: [inc, src],
build_by_default: false
)