diff --git a/.clang-tidy b/.clang-tidy index e380b8c..4271e9b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -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 ' \ No newline at end of file diff --git a/apps/target/main.cc b/apps/target/main.cc index f86a81d..7116a5b 100644 --- a/apps/target/main.cc +++ b/apps/target/main.cc @@ -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; } diff --git a/include/bus.hh b/include/bus.hh index badadb7..caa11aa 100644 --- a/include/bus.hh +++ b/include/bus.hh @@ -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); diff --git a/include/cpu/cpu.hh b/include/cpu/cpu.hh index 041a078..fe3dbf9 100644 --- a/include/cpu/cpu.hh +++ b/include/cpu/cpu.hh @@ -1,58 +1,19 @@ -#pragma once - #include "bus.hh" -#include "instruction.hh" -#include "psr.hh" -#include - -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; - std::array 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 fiq; - std::array svc; - std::array abt; - std::array irq; - std::array und; - - // visible registers before the mode switch - std::array 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 impl; }; diff --git a/include/cpu/meson.build b/include/cpu/meson.build index fd15eab..4f9f321 100644 --- a/include/cpu/meson.build +++ b/include/cpu/meson.build @@ -1,6 +1,3 @@ headers += files( 'cpu.hh', - 'instruction.hh', - 'psr.hh', - 'utility.hh' ) \ No newline at end of file diff --git a/include/header.hh b/include/header.hh index 82e803c..472fd4c 100644 --- a/include/header.hh +++ b/include/header.hh @@ -4,6 +4,8 @@ #include struct Header { + static constexpr uint8_t HEADER_SIZE = 192; + enum class UniqueCode { Old, // old games New, // new games diff --git a/include/memory.hh b/include/memory.hh index 9262a68..5bb1ac2 100644 --- a/include/memory.hh +++ b/include/memory.hh @@ -4,14 +4,14 @@ #include #include #include +#include #include class Memory { public: static constexpr size_t BIOS_SIZE = 1024 * 16; - Memory(std::array&& bios, - std::vector&& rom) noexcept; + Memory(std::array&& bios, std::vector&& 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 invalid_mem; std::vector rom; Header header; void parse_header(); diff --git a/include/meson.build b/include/meson.build index b5d86db..a12baca 100644 --- a/include/meson.build +++ b/include/meson.build @@ -4,6 +4,8 @@ headers = files( 'header.hh', ) +inc = include_directories('.') + subdir('cpu') install_headers(headers, subdir: meson.project_name(), preserve_path: true) \ No newline at end of file diff --git a/meson.build b/meson.build index 585ea74..7cd45c5 100644 --- a/meson.build +++ b/meson.build @@ -30,8 +30,6 @@ else endif ''' -inc = include_directories('include') - subdir('include') subdir('src') subdir('apps') diff --git a/result-dev b/result-dev new file mode 120000 index 0000000..35d25b5 --- /dev/null +++ b/result-dev @@ -0,0 +1 @@ +/nix/store/6p64j7f5xl7rzf1i1avdn18nq9j0yisw-matar-dev \ No newline at end of file diff --git a/src/bus.cc b/src/bus.cc index 8b4d939..b55287d 100644 --- a/src/bus.cc +++ b/src/bus.cc @@ -1,7 +1,7 @@ #include "bus.hh" #include -Bus::Bus(Memory& memory) +Bus::Bus(const Memory& memory) : memory(std::make_shared(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); } diff --git a/src/cpu/arm/exec.cc b/src/cpu/arm/exec.cc new file mode 100644 index 0000000..88b67e7 --- /dev/null +++ b/src/cpu/arm/exec.cc @@ -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(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(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(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(static_cast(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(&data.offset)) { + offset = *immediate; + } else if (const Shift* shift = std::get_if(&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(gpr[data.rd]) << 16) >> 16; + + // byte + } else { + gpr[data.rd] = bus->read_byte(address); + + // sign extend the byte + gpr[data.rd] = + (static_cast(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(&data.operand)) { + op_2 = *immediate; + } else if (const Shift* shift = std::get_if(&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); +} diff --git a/src/cpu/instruction.cc b/src/cpu/arm/instruction.cc similarity index 99% rename from src/cpu/instruction.cc rename to src/cpu/arm/instruction.cc index b5e503c..8a2f4b9 100644 --- a/src/cpu/instruction.cc +++ b/src/cpu/arm/instruction.cc @@ -1,4 +1,4 @@ -#include "cpu/instruction.hh" +#include "instruction.hh" #include "cpu/utility.hh" #include "util/bits.hh" #include @@ -21,7 +21,7 @@ Instruction::Instruction(uint32_t insn) // lsh 2 and sign extend the 26 bit offset to 32 bits offset = (static_cast(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 operand; - if (!imm) { + if (imm) { uint32_t immediate = bit_range(insn, 0, 7); uint8_t rotate = bit_range(insn, 8, 11); diff --git a/include/cpu/instruction.hh b/src/cpu/arm/instruction.hh similarity index 94% rename from include/cpu/instruction.hh rename to src/cpu/arm/instruction.hh index 150eaca..a47a15a 100644 --- a/include/cpu/instruction.hh +++ b/src/cpu/arm/instruction.hh @@ -10,6 +10,8 @@ template overloaded(Ts...) -> overloaded; 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(); }; } diff --git a/src/cpu/arm/meson.build b/src/cpu/arm/meson.build new file mode 100644 index 0000000..f5ef827 --- /dev/null +++ b/src/cpu/arm/meson.build @@ -0,0 +1,4 @@ +lib_sources += files( + 'instruction.cc', + 'exec.cc' +) \ No newline at end of file diff --git a/src/cpu/cpu-impl.cc b/src/cpu/cpu-impl.cc new file mode 100644 index 0000000..b29e657 --- /dev/null +++ b/src/cpu/cpu-impl.cc @@ -0,0 +1,144 @@ +#include "cpu-impl.hh" +#include "util/bits.hh" +#include "util/log.hh" +#include "utility.hh" +#include +#include + +using namespace logger; + +CpuImpl::CpuImpl(const Bus& bus) noexcept + : bus(std::make_shared(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; + } + } +} diff --git a/src/cpu/cpu-impl.hh b/src/cpu/cpu-impl.hh new file mode 100644 index 0000000..b4073c0 --- /dev/null +++ b/src/cpu/cpu-impl.hh @@ -0,0 +1,57 @@ +#pragma once + +#include "bus.hh" +#include "cpu/arm/instruction.hh" +#include "cpu/psr.hh" + +#include + +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; + std::array 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 fiq; + std::array svc; + std::array abt; + std::array irq; + std::array und; + + // visible registers before the mode switch + std::array 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 +}; diff --git a/src/cpu/cpu.cc b/src/cpu/cpu.cc index bc03dd7..fe5923f 100644 --- a/src/cpu/cpu.cc +++ b/src/cpu/cpu.cc @@ -1,714 +1,12 @@ #include "cpu/cpu.hh" -#include "cpu/utility.hh" -#include "util/bits.hh" -#include "util/log.hh" -#include -#include +#include "cpu-impl.hh" -using namespace logger; +Cpu::Cpu(const Bus& bus) noexcept + : impl(std::make_unique(bus)){}; -Cpu::Cpu(Bus& bus) - : bus(std::make_shared(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(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(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(gpr[data.rm]) * - static_cast(gpr[data.rs]) + - (data.acc ? static_cast(gpr[data.rdhi]) << 32 | - static_cast(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(gpr[data.rm]) * - static_cast(gpr[data.rs]) + - (data.acc ? static_cast(gpr[data.rdhi]) << 32 | - static_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(&data.offset)) { - offset = *immediate; - } else if (const Shift* shift = std::get_if(&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(gpr[data.rd]) << 16) >> 16; - - // byte - } else { - gpr[data.rd] = bus->read_byte(address); - - // sign extend the byte - gpr[data.rd] = - (static_cast(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(&data.operand)) { - op_2 = *immediate; - } else if (const Shift* shift = std::get_if(&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(); +}; diff --git a/src/cpu/meson.build b/src/cpu/meson.build index 11d93d8..139497d 100644 --- a/src/cpu/meson.build +++ b/src/cpu/meson.build @@ -1,6 +1,8 @@ lib_sources += files( + 'cpu-impl.cc', 'cpu.cc', - 'instruction.cc', 'psr.cc', 'utility.cc' -) \ No newline at end of file +) + +subdir('arm') \ No newline at end of file diff --git a/src/cpu/psr.cc b/src/cpu/psr.cc index c3070b1..1770342 100644 --- a/src/cpu/psr.cc +++ b/src/cpu/psr.cc @@ -1,4 +1,4 @@ -#include "cpu/psr.hh" +#include "psr.hh" #include "util/bits.hh" #include "util/log.hh" diff --git a/include/cpu/psr.hh b/src/cpu/psr.hh similarity index 100% rename from include/cpu/psr.hh rename to src/cpu/psr.hh diff --git a/src/cpu/utility.cc b/src/cpu/utility.cc index e4dc55b..4c0fe26 100644 --- a/src/cpu/utility.cc +++ b/src/cpu/utility.cc @@ -1,4 +1,4 @@ -#include "cpu/utility.hh" +#include "utility.hh" #include "util/bits.hh" #include @@ -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; } diff --git a/include/cpu/utility.hh b/src/cpu/utility.hh similarity index 97% rename from include/cpu/utility.hh rename to src/cpu/utility.hh index 918bfb2..aa319d5 100644 --- a/include/cpu/utility.hh +++ b/src/cpu/utility.hh @@ -3,7 +3,6 @@ #include #include -static constexpr size_t ARM_INSTRUCTION_SIZE = 4; static constexpr size_t THUMB_INSTRUCTION_SIZE = 2; enum class Mode { diff --git a/src/memory.cc b/src/memory.cc index 459921a..7d86511 100644 --- a/src/memory.cc +++ b/src/memory.cc @@ -4,11 +4,12 @@ #include "util/log.hh" #include "util/utils.hh" #include +#include using namespace logger; Memory::Memory(std::array&& bios, - std::vector&& rom) noexcept + std::vector&& 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; diff --git a/src/util/utils.hh b/src/util/utils.hh index 60819e9..f0c4a11 100644 --- a/src/util/utils.hh +++ b/src/util/utils.hh @@ -3,8 +3,6 @@ #include #include #include -#include -#include #include // Why I wrote this myself? I do not know diff --git a/tests/cpu/arm/exec.cc b/tests/cpu/arm/exec.cc new file mode 100644 index 0000000..424445b --- /dev/null +++ b/tests/cpu/arm/exec.cc @@ -0,0 +1,736 @@ +#include "cpu/cpu-impl.hh" +#include "cpu/utility.hh" +#include "util/bits.hh" +#include +#include + +class CpuFixture { + public: + CpuFixture() + : cpu(Bus(Memory(std::array(), + std::vector(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(&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(&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(&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(bit_range(result, 0, 31))); + CHECK(cpu.gpr[5] == static_cast(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(bit_range(result, 0, 31))); + CHECK(cpu.gpr[5] == static_cast(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(bit_range(result, 0, 31))); + CHECK(cpu.gpr[5] == static_cast(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(&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(-259039045)); + } + + SECTION("byte") { + swap->byte = true; + exec(data); + + CHECK(cpu.gpr[4] == (3241011111 & 0xFF)); + CHECK(cpu.bus->read_byte(cpu.gpr[9]) == + static_cast(-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(&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(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(&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(-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(-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(&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(&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 diff --git a/tests/cpu/arm/instruction.cc b/tests/cpu/arm/instruction.cc new file mode 100644 index 0000000..15b5a4c --- /dev/null +++ b/tests/cpu/arm/instruction.cc @@ -0,0 +1,469 @@ +#include "cpu/arm/instruction.hh" +#include "cpu/utility.hh" +#include + +#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(&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(&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(&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(&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(&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(&instruction.data))); + CHECK(instruction.condition == Condition::AL); + + REQUIRE((shift = std::get_if(&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(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(&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(&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(&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(&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(&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(&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(&instruction.data))); + CHECK(instruction.condition == Condition::AL); + + // operand 2 is a shifted register + REQUIRE((shift = std::get_if(&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(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(&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(&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(&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 diff --git a/tests/cpu/arm/meson.build b/tests/cpu/arm/meson.build new file mode 100644 index 0000000..840fc8f --- /dev/null +++ b/tests/cpu/arm/meson.build @@ -0,0 +1,4 @@ +tests_sources += files( + 'instruction.cc', + 'exec.cc' +) \ No newline at end of file diff --git a/tests/cpu/instruction.cc b/tests/cpu/instruction.cc deleted file mode 100644 index dbb3f54..0000000 --- a/tests/cpu/instruction.cc +++ /dev/null @@ -1,468 +0,0 @@ -#include "cpu/instruction.hh" -#include "cpu/utility.hh" -#include -#include - -[[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(&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(&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(&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(&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(&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(&instruction.data))); - REQUIRE(instruction.condition == Condition::AL); - - REQUIRE((shift = std::get_if(&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(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(&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(&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(&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(&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(&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(&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(&instruction.data))); - REQUIRE(instruction.condition == Condition::AL); - - // operand 2 is a shifted register - REQUIRE((shift = std::get_if(&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(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(&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(&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(&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"); -} diff --git a/tests/cpu/meson.build b/tests/cpu/meson.build index 020125c..e108b62 100644 --- a/tests/cpu/meson.build +++ b/tests/cpu/meson.build @@ -1,3 +1 @@ -tests_sources += files( - 'instruction.cc' -) \ No newline at end of file +subdir('arm') \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build index cb27544..ee7be6a 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -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 )