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/instruction.hh b/include/cpu/arm/instruction.hh similarity index 96% rename from include/cpu/instruction.hh rename to include/cpu/arm/instruction.hh index 150eaca..1dcdcaf 100644 --- a/include/cpu/instruction.hh +++ b/include/cpu/arm/instruction.hh @@ -156,6 +156,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/include/cpu/arm/meson.build b/include/cpu/arm/meson.build new file mode 100644 index 0000000..5e6d296 --- /dev/null +++ b/include/cpu/arm/meson.build @@ -0,0 +1,3 @@ +headers += files( + 'instruction.hh', +) \ No newline at end of file diff --git a/include/cpu/cpu.hh b/include/cpu/cpu.hh index 041a078..cf4ed71 100644 --- a/include/cpu/cpu.hh +++ b/include/cpu/cpu.hh @@ -1,7 +1,7 @@ #pragma once +#include "arm/instruction.hh" #include "bus.hh" -#include "instruction.hh" #include "psr.hh" #include @@ -10,7 +10,7 @@ using std::size_t; class Cpu { public: - Cpu(Bus& bus); + Cpu(const Bus& bus); void step(); private: @@ -30,7 +30,9 @@ class Cpu { Psr spsr; // status program status register static constexpr uint8_t PC_INDEX = 15; - uint32_t& pc = gpr[PC_INDEX]; + static_assert(PC_INDEX < GPR_COUNT); + + uint32_t& pc = gpr[PC_INDEX]; bool is_flushed; diff --git a/include/cpu/meson.build b/include/cpu/meson.build index fd15eab..cc00b7d 100644 --- a/include/cpu/meson.build +++ b/include/cpu/meson.build @@ -1,6 +1,7 @@ headers += files( 'cpu.hh', - 'instruction.hh', 'psr.hh', 'utility.hh' -) \ No newline at end of file +) + +subdir('arm') \ No newline at end of file diff --git a/include/memory.hh b/include/memory.hh index 9262a68..303321d 100644 --- a/include/memory.hh +++ b/include/memory.hh @@ -10,8 +10,7 @@ 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); 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..5988128 --- /dev/null +++ b/src/cpu/arm/exec.cc @@ -0,0 +1,576 @@ +#include "cpu/cpu.hh" +#include "util/bits.hh" +#include "util/log.hh" + +using namespace logger; + +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); +} 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..2289968 100644 --- a/src/cpu/instruction.cc +++ b/src/cpu/arm/instruction.cc @@ -1,4 +1,4 @@ -#include "cpu/instruction.hh" +#include "cpu/arm/instruction.hh" #include "cpu/utility.hh" #include "util/bits.hh" #include @@ -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/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.cc b/src/cpu/cpu.cc index bc03dd7..ab79d93 100644 --- a/src/cpu/cpu.cc +++ b/src/cpu/cpu.cc @@ -7,7 +7,7 @@ using namespace logger; -Cpu::Cpu(Bus& bus) +Cpu::Cpu(const Bus& bus) : bus(std::make_shared(bus)) , gpr({ 0 }) , cpsr(0) @@ -115,576 +115,6 @@ Cpu::chg_mode(const Mode to) { 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); -} - void Cpu::step() { // Current instruction is two instructions behind PC diff --git a/src/cpu/meson.build b/src/cpu/meson.build index 11d93d8..6664d73 100644 --- a/src/cpu/meson.build +++ b/src/cpu/meson.build @@ -1,6 +1,7 @@ lib_sources += files( '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/memory.cc b/src/memory.cc index 459921a..649232c 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() < 192) { + 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..2f68758 --- /dev/null +++ b/tests/cpu/arm/exec.cc @@ -0,0 +1,227 @@ +#include "cpu/cpu.hh" +#include "cpu/utility.hh" +#include +#include +#include +#include +#include + +// I could have written some public API but that wouldn't be the best practice, +// so instead I will try to do my best to test these functions using memory +// manipulation. We also use a fake PC to match the current instruction's +// address. +// +// We are going to use some addresses for specific tasks +// - (4 * 400) + 4 => Storing, then reading registers +// +// We are also going to keep some registers reserved for testing +// - R0 is always zero +// - R1 for reading PSR + +class CpuFixture { + public: + uint32_t fake_pc = 2 * ARM_INSTRUCTION_SIZE; + + CpuFixture() + // BIOS is all zeroes so let's do what we can + : memory(std::array(), + std::vector(192)) + , bus(memory) + , cpu(bus) {} + + void write_register(uint8_t rd, uint8_t value, uint8_t rotate = 0) { + // MOV + uint32_t raw = 0b11100011101000000000000000000000; + raw |= rd << 12; + raw |= rotate << 8; + raw |= value; + execute(raw); + } + + uint32_t read_register(uint8_t rd) { + // use R0 + static constexpr uint16_t offset = MAX_FAKE_PC + ARM_INSTRUCTION_SIZE; + + uint32_t raw = 0b11100101100000000000000000000000; + raw |= rd << 12; + raw |= offset; + execute(raw); + + return bus.read_word(offset + (rd == 15 ? ARM_INSTRUCTION_SIZE : 0)); + } + + Psr read_cpsr() { + // use R1 + uint32_t raw = 0b11100001000011110001000000000000; + execute(raw); + + return Psr(read_register(1)); + } + + void execute(uint32_t raw) { + bus.write_word(fake_pc - 2 * ARM_INSTRUCTION_SIZE, raw); + step(); + } + + private: + static constexpr uint32_t MAX_FAKE_PC = 400 * ARM_INSTRUCTION_SIZE; + Memory memory; + + void step() { + cpu.step(); + fake_pc += ARM_INSTRUCTION_SIZE; + if (fake_pc == MAX_FAKE_PC) + fake_pc = 0; + } + + protected: + Bus bus; + Cpu cpu; +}; + +#define TAG "arm execution" + +using namespace arm; + +TEST_CASE_METHOD(CpuFixture, "Test fixture", TAG) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution value_d; + std::uniform_int_distribution shift_d(0, (1 << 4) - 1); + + // R0 is reserved to be 0 so that it can be used as as offset + write_register(0, 0); + REQUIRE(read_register(0) == 0); + + for (uint8_t i = 1; i < 15; i++) { + uint8_t value = value_d(gen); + uint8_t shift = shift_d(gen); + uint32_t amount = std::rotr(static_cast(value), 2 * shift); + + write_register(i, value, shift); + REQUIRE(read_register(i) == amount); + } + + REQUIRE(read_cpsr().mode() == Mode::Supervisor); + + INFO("Fixture is OK"); +} + +TEST_CASE_METHOD(CpuFixture, "Branch and Exchange", TAG) { + uint32_t raw = 0b11100001001011111111111100011010; + + write_register(10, 240); + + execute(raw); + fake_pc = 240 + 2 * ARM_INSTRUCTION_SIZE; + + REQUIRE(read_register(15) == 240 + 2 * ARM_INSTRUCTION_SIZE); +} + +// TODO write BX for when switching to thumb + +TEST_CASE_METHOD(CpuFixture, "Branch", TAG) { + uint32_t raw = 0b11101011000000000000000000111100; + + uint32_t old_pc = fake_pc; + execute(raw); + fake_pc = old_pc + 240; + + // pipeline is flushed + fake_pc += 2 * ARM_INSTRUCTION_SIZE; + + REQUIRE(read_register(15) == old_pc + 240 + 2 * ARM_INSTRUCTION_SIZE); + REQUIRE(read_register(14) == old_pc - ARM_INSTRUCTION_SIZE); +} + +TEST_CASE_METHOD(CpuFixture, "Multiply", TAG) { + uint32_t raw = 0b11100000001111011100101110011010; + uint32_t result = 0; + + write_register(10, 230); + write_register(11, 192); + write_register(12, 37); + + execute(raw); + result = 230 * 192 + 37; + + REQUIRE(read_register(13) == result); + REQUIRE(read_cpsr().n() == (result >> 31 & 1)); + + // when product is zero + write_register(10, 230); + write_register(11, 0); + write_register(12, 0); + + execute(raw); + + REQUIRE(read_register(13) == 0); + REQUIRE(read_cpsr().z() == true); +} + +TEST_CASE_METHOD(CpuFixture, "Multiply Long", TAG) { + uint32_t raw = 0b11100000101111011100101110011010; + uint64_t result = 0; + + write_register(10, 230, 3); // 2550136835 + write_register(11, 192, 12); // 49152 + write_register(12, 255, 9); // 4177920 + write_register(13, 11, 4); // 184549376 + + result = 2550136835ull * 49152ull + (184549376ull << 32 | 4177920ull); + + execute(raw); + + REQUIRE(read_register(12) == (result & 0xFFFFFFFF)); + REQUIRE(read_register(13) == (result >> 32 & 0xFFFFFFFF)); + REQUIRE(read_cpsr().z() == false); + REQUIRE(read_cpsr().n() == (result >> 63 & 1)); + + // signed + raw = 0b11100000111111011100101110011010; + + write_register(12, 255, 9); // 4177920 + write_register(13, 11, 4); // 184549376 + + execute(raw); + + REQUIRE(read_register(12) == (result & 0xFFFFFFFF)); + REQUIRE(read_register(13) == (result >> 32 & 0xFFFFFFFF)); + REQUIRE(read_cpsr().z() == false); + REQUIRE(read_cpsr().n() == (result >> 63 & 1)); + + // 0 and no accumulation + raw = 0b11100000110111011100101110011010; + + write_register(10, 0); + execute(raw); + + REQUIRE(read_register(12) == 0); + REQUIRE(read_register(13) == 0); + REQUIRE(read_cpsr().z() == true); +} + +TEST_CASE_METHOD(CpuFixture, "Single Data Swap", TAG) { + write_register(6, 230, 3); // 2550136835 + write_register(9, 160, 0); // 160 + bus.write_word(read_register(9), 49152); + + SECTION("word") { + uint32_t raw = 0b11100001000010010101000010010110; + execute(raw); + + REQUIRE(read_register(5) == 49152); + REQUIRE(bus.read_word(read_register(9)) == 2550136835); + } + + SECTION("byte") { + uint32_t raw = 0b11100001010010010101000010010110; + + execute(raw); + + REQUIRE(read_register(5) == (49152 & 0xFF)); + REQUIRE(bus.read_byte(read_register(9)) == (2550136835 & 0xFF)); + } +} + +#undef TAG diff --git a/tests/cpu/arm/instruction.cc b/tests/cpu/arm/instruction.cc new file mode 100644 index 0000000..537cf56 --- /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))); + 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 == true); + + REQUIRE(instruction.disassemble() == "UMULLNES R7,R14,R2,R6"); + + mull->acc = true; + REQUIRE(instruction.disassemble() == "UMLALNES R7,R14,R2,R6"); + + mull->uns = false; + mull->set = false; + REQUIRE(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); + + 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 = 0b11100000000111100111101101100001; + 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"); +} + +#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/cpu.cc b/tests/cpu/cpu.cc new file mode 100644 index 0000000..e69de29 diff --git a/tests/cpu/instruction.cc b/tests/cpu/instruction.cc index dbb3f54..5e4d3ae 100644 --- a/tests/cpu/instruction.cc +++ b/tests/cpu/instruction.cc @@ -1,7 +1,6 @@ -#include "cpu/instruction.hh" +#include "cpu/arm/instruction.hh" #include "cpu/utility.hh" #include -#include [[maybe_unused]] static constexpr auto TAG = "disassembler"; 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