From 36d71a4ee20117b0a4e285025f36a5981b6dcb67 Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Sat, 30 Sep 2023 01:31:09 +0530 Subject: [PATCH] thumb: add execution of instructions also arm: fix some instructions Signed-off-by: Amneesh Singh --- src/cpu/alu.cc | 41 +- src/cpu/alu.hh | 11 +- src/cpu/arm/exec.cc | 51 +- src/cpu/arm/instruction.hh | 9 +- src/cpu/cpu-impl.cc | 31 +- src/cpu/cpu-impl.hh | 17 +- src/cpu/thumb/disassembler.cc | 25 +- src/cpu/thumb/exec.cc | 384 +++++++ src/cpu/thumb/instruction.cc | 78 +- src/cpu/thumb/instruction.hh | 33 +- src/cpu/thumb/meson.build | 3 +- tests/cpu/arm/exec.cc | 11 +- tests/cpu/arm/meson.build | 1 - tests/cpu/{arm/fixture.cc => cpu-fixture.cc} | 4 +- tests/cpu/{arm/fixture.hh => cpu-fixture.hh} | 5 + tests/cpu/meson.build | 4 + tests/cpu/thumb/exec.cc | 990 +++++++++++++++++++ tests/cpu/thumb/instruction.cc | 88 +- tests/cpu/thumb/meson.build | 3 +- 19 files changed, 1632 insertions(+), 157 deletions(-) create mode 100644 src/cpu/thumb/exec.cc rename tests/cpu/{arm/fixture.cc => cpu-fixture.cc} (98%) rename tests/cpu/{arm/fixture.hh => cpu-fixture.hh} (87%) create mode 100644 tests/cpu/thumb/exec.cc diff --git a/src/cpu/alu.cc b/src/cpu/alu.cc index 6569624..7a74e49 100644 --- a/src/cpu/alu.cc +++ b/src/cpu/alu.cc @@ -3,7 +3,7 @@ namespace matar { uint32_t -eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { +eval_shift(ShiftType shift_type, uint32_t value, uint32_t amount, bool& carry) { uint32_t eval = 0; switch (shift_type) { @@ -48,4 +48,43 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { return eval; } + +uint32_t +sub(uint32_t a, uint32_t b, bool& carry, bool& overflow) { + bool s1 = get_bit(a, 31); + bool s2 = get_bit(b, 31); + + uint32_t result = a - b; + + carry = b <= a; + overflow = s1 != s2 && s2 == get_bit(result, 31); + + return result; +} + +uint32_t +add(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c) { + bool s1 = get_bit(a, 31); + bool s2 = get_bit(b, 31); + + uint64_t result = a + b + c; + + carry = get_bit(result, 32); + overflow = s1 == s2 && s2 != get_bit(result, 31); + + return result & 0xFFFFFFFF; +} + +uint32_t +sbc(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c) { + bool s1 = get_bit(a, 31); + bool s2 = get_bit(b, 31); + + uint64_t result = a - b - !c; + + carry = get_bit(result, 32); + overflow = s1 != s2 && s2 == get_bit(result, 31); + + return result & 0xFFFFFFFF; +} } diff --git a/src/cpu/alu.hh b/src/cpu/alu.hh index c11d062..24d01df 100644 --- a/src/cpu/alu.hh +++ b/src/cpu/alu.hh @@ -40,5 +40,14 @@ struct Shift { }; uint32_t -eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry); +eval_shift(ShiftType shift_type, uint32_t value, uint32_t amount, bool& carry); + +uint32_t +sub(uint32_t a, uint32_t b, bool& carry, bool& overflow); + +uint32_t +add(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c = 0); + +uint32_t +sbc(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c); } diff --git a/src/cpu/arm/exec.cc b/src/cpu/arm/exec.cc index 86a12c6..3b8f255 100644 --- a/src/cpu/arm/exec.cc +++ b/src/cpu/arm/exec.cc @@ -307,6 +307,7 @@ Instruction::exec(CpuImpl& cpu) { } } + // TODO: clean this shit // account for decrement if (!data.up) address -= (n_regs - 1) * alignment; @@ -419,44 +420,6 @@ Instruction::exec(CpuImpl& cpu) { bool overflow = cpu.cpsr.v(); bool carry = cpu.cpsr.c(); - auto sub = [&carry, &overflow](uint32_t a, uint32_t b) -> uint32_t { - bool s1 = get_bit(a, 31); - bool s2 = get_bit(b, 31); - - uint32_t result = a - b; - - carry = b <= a; - overflow = s1 != s2 && s2 == get_bit(result, 31); - return result; - }; - - auto add = [&carry, &overflow]( - uint32_t a, uint32_t b, bool c = 0) -> uint32_t { - bool s1 = get_bit(a, 31); - bool s2 = get_bit(b, 31); - - // 33 bits - uint64_t result_ = a + b + c; - uint32_t result = result_ & 0xFFFFFFFF; - - carry = get_bit(result_, 32); - overflow = s1 == s2 && s2 != get_bit(result, 31); - return result; - }; - - auto sbc = [&carry, - &overflow](uint32_t a, uint32_t b, bool c) -> uint32_t { - bool s1 = get_bit(a, 31); - bool s2 = get_bit(b, 31); - - uint64_t result_ = a - b + c - 1; - uint32_t result = result_ & 0xFFFFFFFF; - - carry = get_bit(result_, 32); - overflow = s1 != s2 && s2 == get_bit(result, 31); - return result; - }; - switch (data.opcode) { case OpCode::AND: case OpCode::TST: @@ -469,23 +432,23 @@ Instruction::exec(CpuImpl& cpu) { break; case OpCode::SUB: case OpCode::CMP: - result = sub(op_1, op_2); + result = sub(op_1, op_2, carry, overflow); break; case OpCode::RSB: - result = sub(op_2, op_1); + result = sub(op_2, op_1, carry, overflow); break; case OpCode::ADD: case OpCode::CMN: - result = add(op_1, op_2); + result = add(op_1, op_2, carry, overflow); break; case OpCode::ADC: - result = add(op_1, op_2, carry); + result = add(op_1, op_2, carry, overflow, carry); break; case OpCode::SBC: - result = sbc(op_1, op_2, carry); + result = sbc(op_1, op_2, carry, overflow, carry); break; case OpCode::RSC: - result = sbc(op_2, op_1, carry); + result = sbc(op_2, op_1, carry, overflow, carry); break; case OpCode::ORR: result = op_1 | op_2; diff --git a/src/cpu/arm/instruction.hh b/src/cpu/arm/instruction.hh index ceffa39..25e09d0 100644 --- a/src/cpu/arm/instruction.hh +++ b/src/cpu/arm/instruction.hh @@ -212,18 +212,19 @@ using InstructionData = std::variant; struct Instruction { - Condition condition; - InstructionData data; - Instruction(uint32_t insn); - Instruction(Condition condition, InstructionData data) noexcept + Instruction(Condition condition, InstructionData data) : condition(condition) , data(data){}; + void exec(CpuImpl& cpu); #ifdef DISASSEMBLER std::string disassemble(); #endif + + Condition condition; + InstructionData data; }; } } diff --git a/src/cpu/cpu-impl.cc b/src/cpu/cpu-impl.cc index 0e3ba48..0255714 100644 --- a/src/cpu/cpu-impl.cc +++ b/src/cpu/cpu-impl.cc @@ -1,4 +1,6 @@ #include "cpu-impl.hh" +#include "cpu/arm/instruction.hh" +#include "cpu/thumb/instruction.hh" #include "util/bits.hh" #include "util/log.hh" #include @@ -11,9 +13,9 @@ CpuImpl::CpuImpl(const Bus& bus) noexcept , gpr({ 0 }) , cpsr(0) , spsr(0) - , is_flushed(false) , gpr_banked({ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } }) - , spsr_banked({ 0, 0, 0, 0, 0 }) { + , spsr_banked({ 0, 0, 0, 0, 0 }) + , is_flushed(false) { cpsr.set_mode(Mode::Supervisor); cpsr.set_irq_disabled(true); cpsr.set_fiq_disabled(true); @@ -120,25 +122,38 @@ CpuImpl::step() { uint32_t cur_pc = pc - 2 * arm::INSTRUCTION_SIZE; if (cpsr.state() == State::Arm) { - uint32_t x = bus->read_word(cur_pc); - arm::Instruction instruction(x); + arm::Instruction instruction(bus->read_word(cur_pc)); + +#ifdef DISASSEMBLER + glogger.info("0x{:08X} : {}", cur_pc, instruction.disassemble()); +#endif instruction.exec(*this); + } else { + thumb::Instruction instruction(bus->read_halfword(cur_pc)); + #ifdef DISASSEMBLER - glogger.info("{:#034b}", x); - glogger.info("0x{:08X} : {}", cur_pc, instruction.disassemble()); + glogger.info("0x{:08X} : {}", cur_pc, instruction.disassemble(cur_pc)); #endif + instruction.exec(*this); + } + + // advance PC + { + size_t size = cpsr.state() == State::Arm ? arm::INSTRUCTION_SIZE + : thumb::INSTRUCTION_SIZE; + 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; + pc += 2 * size; is_flushed = false; } else { // if not flushed continue like normal - pc += arm::INSTRUCTION_SIZE; + pc += size; } } } diff --git a/src/cpu/cpu-impl.hh b/src/cpu/cpu-impl.hh index af46bb5..fa7b2ce 100644 --- a/src/cpu/cpu-impl.hh +++ b/src/cpu/cpu-impl.hh @@ -1,8 +1,9 @@ #pragma once +#include "arm/instruction.hh" #include "bus.hh" -#include "cpu/arm/instruction.hh" #include "cpu/psr.hh" +#include "thumb/instruction.hh" #include @@ -16,6 +17,7 @@ class CpuImpl { private: friend void arm::Instruction::exec(CpuImpl& cpu); + friend void thumb::Instruction::exec(CpuImpl& cpu); static constexpr uint8_t GPR_COUNT = 16; @@ -32,13 +34,18 @@ class CpuImpl { Psr cpsr; // current program status register Psr spsr; // status program status register + static constexpr uint8_t SP_INDEX = 13; + static_assert(SP_INDEX < GPR_COUNT); + uint32_t& sp = gpr[SP_INDEX]; + + static constexpr uint8_t LR_INDEX = 14; + static_assert(LR_INDEX < GPR_COUNT); + uint32_t& lr = gpr[LR_INDEX]; + 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; @@ -57,5 +64,7 @@ class CpuImpl { Psr irq; Psr und; } spsr_banked; // banked saved program status registers + + bool is_flushed; }; } diff --git a/src/cpu/thumb/disassembler.cc b/src/cpu/thumb/disassembler.cc index 436da4b..0fde846 100644 --- a/src/cpu/thumb/disassembler.cc +++ b/src/cpu/thumb/disassembler.cc @@ -3,7 +3,7 @@ namespace matar::thumb { std::string -Instruction::disassemble() { +Instruction::disassemble(uint32_t pc) { return std::visit( overloaded{ [](MoveShiftedRegister& data) { @@ -90,8 +90,7 @@ Instruction::disassemble() { data.word); }, [](AddOffsetStackPointer& data) { - return fmt::format( - "ADD SP,#{}{:d}", (data.sign ? '-' : '+'), data.word); + return fmt::format("ADD SP,#{:d}", data.word); }, [](PushPopRegister& data) { std::string regs; @@ -130,18 +129,24 @@ Instruction::disassemble() { return fmt::format( "{} R{}!,{{{}}}", (data.load ? "LDMIA" : "STMIA"), data.rb, regs); }, - [](SoftwareInterrupt) { return std::string("SWI"); }, - [](ConditionalBranch& data) { - return fmt::format( - "B{} {:d}", stringify(data.condition), data.offset); + [](SoftwareInterrupt& data) { + return fmt::format("SWI {:d}", data.vector); }, - [](UnconditionalBranch& data) { - return fmt::format("B {:d}", data.offset); + [pc](ConditionalBranch& data) { + return fmt::format( + "B{} #{:d}", + stringify(data.condition), + static_cast(data.offset + pc + 2 * INSTRUCTION_SIZE)); + }, + [pc](UnconditionalBranch& data) { + return fmt::format( + "B #{:d}", + static_cast(data.offset + pc + 2 * INSTRUCTION_SIZE)); }, [](LongBranchWithLink& data) { // duh this manual be empty for H = 0 return fmt::format( - "BL{} {:d}", (data.high ? "H" : ""), data.offset); + "BL{} #{:d}", (data.high ? "H" : ""), data.offset); }, [](auto) { return std::string("unknown instruction"); } }, data); diff --git a/src/cpu/thumb/exec.cc b/src/cpu/thumb/exec.cc new file mode 100644 index 0000000..d925669 --- /dev/null +++ b/src/cpu/thumb/exec.cc @@ -0,0 +1,384 @@ +#include "cpu/cpu-impl.hh" +#include "instruction.hh" +#include "util/bits.hh" +#include "util/log.hh" + +namespace matar::thumb { +void +Instruction::exec(CpuImpl& cpu) { + auto set_cc = [&cpu](bool c, bool v, bool n, bool z) { + cpu.cpsr.set_c(c); + cpu.cpsr.set_v(v); + cpu.cpsr.set_n(n); + cpu.cpsr.set_z(z); + }; + + std::visit( + overloaded{ + [&cpu, set_cc](MoveShiftedRegister& data) { + if (data.opcode == ShiftType::ROR) + glogger.error("Invalid opcode in {}", typeid(data).name()); + + bool carry = cpu.cpsr.c(); + + uint32_t shifted = + eval_shift(data.opcode, cpu.gpr[data.rs], data.offset, carry); + + cpu.gpr[data.rd] = shifted; + + set_cc(carry, cpu.cpsr.v(), get_bit(shifted, 31), shifted == 0); + }, + [&cpu, set_cc](AddSubtract& data) { + uint32_t offset = + data.imm ? static_cast(static_cast(data.offset)) + : cpu.gpr[data.offset]; + uint32_t result = 0; + bool carry = cpu.cpsr.c(); + bool overflow = cpu.cpsr.v(); + + switch (data.opcode) { + case AddSubtract::OpCode::ADD: + result = add(cpu.gpr[data.rs], offset, carry, overflow); + break; + case AddSubtract::OpCode::SUB: + result = sub(cpu.gpr[data.rs], offset, carry, overflow); + break; + } + + cpu.gpr[data.rd] = result; + set_cc(carry, overflow, get_bit(result, 31), result == 0); + }, + [&cpu, set_cc](MovCmpAddSubImmediate& data) { + uint32_t result = 0; + bool carry = cpu.cpsr.c(); + bool overflow = cpu.cpsr.v(); + + switch (data.opcode) { + case MovCmpAddSubImmediate::OpCode::MOV: + result = data.offset; + carry = 0; + break; + case MovCmpAddSubImmediate::OpCode::ADD: + result = + add(cpu.gpr[data.rd], data.offset, carry, overflow); + break; + case MovCmpAddSubImmediate::OpCode::SUB: + case MovCmpAddSubImmediate::OpCode::CMP: + result = + sub(cpu.gpr[data.rd], data.offset, carry, overflow); + break; + } + + set_cc(carry, overflow, get_bit(result, 31), result == 0); + if (data.opcode != MovCmpAddSubImmediate::OpCode::CMP) + cpu.gpr[data.rd] = result; + }, + [&cpu, set_cc](AluOperations& data) { + uint32_t op_1 = cpu.gpr[data.rd]; + uint32_t op_2 = cpu.gpr[data.rs]; + uint32_t result = 0; + + bool carry = cpu.cpsr.c(); + bool overflow = cpu.cpsr.v(); + + switch (data.opcode) { + case AluOperations::OpCode::AND: + case AluOperations::OpCode::TST: + result = op_1 & op_2; + break; + case AluOperations::OpCode::EOR: + result = op_1 ^ op_2; + break; + case AluOperations::OpCode::LSL: + result = eval_shift(ShiftType::LSL, op_1, op_2, carry); + break; + case AluOperations::OpCode::LSR: + result = eval_shift(ShiftType::LSR, op_1, op_2, carry); + break; + case AluOperations::OpCode::ASR: + result = eval_shift(ShiftType::ASR, op_1, op_2, carry); + break; + case AluOperations::OpCode::ADC: + result = add(op_1, op_2, carry, overflow, carry); + break; + case AluOperations::OpCode::SBC: + result = sbc(op_1, op_2, carry, overflow, carry); + break; + case AluOperations::OpCode::ROR: + result = eval_shift(ShiftType::ROR, op_1, op_2, carry); + break; + case AluOperations::OpCode::NEG: + result = -op_2; + break; + case AluOperations::OpCode::CMP: + result = sub(op_1, op_2, carry, overflow); + break; + case AluOperations::OpCode::CMN: + result = add(op_1, op_2, carry, overflow); + break; + case AluOperations::OpCode::ORR: + result = op_1 | op_2; + break; + case AluOperations::OpCode::MUL: + result = op_1 * op_2; + break; + case AluOperations::OpCode::BIC: + result = op_1 & ~op_2; + break; + case AluOperations::OpCode::MVN: + result = ~op_2; + break; + } + + if (data.opcode != AluOperations::OpCode::TST && + data.opcode != AluOperations::OpCode::CMP && + data.opcode != AluOperations::OpCode::CMN) + cpu.gpr[data.rd] = result; + + set_cc(carry, overflow, get_bit(result, 31), result == 0); + }, + [&cpu, set_cc](HiRegisterOperations& data) { + uint32_t op_1 = cpu.gpr[data.rd]; + uint32_t op_2 = cpu.gpr[data.rs]; + + bool carry = cpu.cpsr.c(); + bool overflow = cpu.cpsr.v(); + + // PC is already current + 4, so dont need to do that + if (data.rd == cpu.PC_INDEX) + rst_bit(op_1, 0); + + if (data.rs == cpu.PC_INDEX) + rst_bit(op_2, 0); + + switch (data.opcode) { + case HiRegisterOperations::OpCode::ADD: { + cpu.gpr[data.rd] = add(op_1, op_2, carry, overflow); + + if (data.rd == cpu.PC_INDEX) + cpu.is_flushed = true; + } break; + case HiRegisterOperations::OpCode::CMP: { + uint32_t result = sub(op_1, op_2, carry, overflow); + set_cc(carry, overflow, get_bit(result, 31), result == 0); + } break; + case HiRegisterOperations::OpCode::MOV: { + cpu.gpr[data.rd] = op_2; + + if (data.rd == cpu.PC_INDEX) + cpu.is_flushed = true; + } break; + case HiRegisterOperations::OpCode::BX: { + State state = static_cast(op_2 & 1); + + // set state + cpu.cpsr.set_state(state); + + // copy to PC + cpu.pc = op_2; + + // ignore [1:0] bits for arm and 0 bit for thumb + rst_bit(cpu.pc, 0); + + if (state == State::Arm) + rst_bit(cpu.pc, 1); + + // pc is affected so flush the pipeline + cpu.is_flushed = true; + } break; + } + }, + [&cpu](PcRelativeLoad& data) { + uint32_t pc = cpu.pc; + rst_bit(pc, 1); + + cpu.gpr[data.rd] = cpu.bus->read_word(pc + data.word); + }, + [&cpu](LoadStoreRegisterOffset& data) { + uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro]; + + if (data.load) { + if (data.byte) { + cpu.gpr[data.rd] = cpu.bus->read_byte(address); + } else { + cpu.gpr[data.rd] = cpu.bus->read_word(address); + } + } else { + if (data.byte) { + cpu.bus->write_byte(address, cpu.gpr[data.rd] & 0xFF); + } else { + cpu.bus->write_word(address, cpu.gpr[data.rd]); + } + } + }, + [&cpu](LoadStoreSignExtendedHalfword& data) { + uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro]; + + switch (data.s << 1 | data.h) { + case 0b00: + cpu.bus->write_halfword(address, cpu.gpr[data.rd] & 0xFFFF); + break; + case 0b01: + cpu.gpr[data.rd] = cpu.bus->read_halfword(address); + break; + case 0b10: + // sign extend and load the byte + cpu.gpr[data.rd] = + (static_cast(cpu.bus->read_byte(address)) + << 24) >> + 24; + break; + case 0b11: + // sign extend the halfword + cpu.gpr[data.rd] = + (static_cast(cpu.bus->read_halfword(address)) + << 16) >> + 16; + break; + + // unreachable + default: { + } + } + }, + [&cpu](LoadStoreImmediateOffset& data) { + uint32_t address = cpu.gpr[data.rb] + data.offset; + + if (data.load) { + if (data.byte) { + cpu.gpr[data.rd] = cpu.bus->read_byte(address); + } else { + cpu.gpr[data.rd] = cpu.bus->read_word(address); + } + } else { + if (data.byte) { + cpu.bus->write_byte(address, cpu.gpr[data.rd] & 0xFF); + } else { + cpu.bus->write_word(address, cpu.gpr[data.rd]); + } + } + }, + [&cpu](LoadStoreHalfword& data) { + uint32_t address = cpu.gpr[data.rb] + data.offset; + + if (data.load) { + cpu.gpr[data.rd] = cpu.bus->read_halfword(address); + } else { + cpu.bus->write_halfword(address, cpu.gpr[data.rd] & 0xFFFF); + } + }, + [&cpu](SpRelativeLoad& data) { + uint32_t address = cpu.sp + data.word; + + if (data.load) { + cpu.gpr[data.rd] = cpu.bus->read_word(address); + } else { + cpu.bus->write_word(address, cpu.gpr[data.rd]); + } + }, + [&cpu](LoadAddress& data) { + if (data.sp) { + cpu.gpr[data.rd] = cpu.sp + data.word; + } else { + // PC is already current + 4, so dont need to do that + // force bit 1 to 0 + cpu.gpr[data.rd] = (cpu.pc & ~(1 << 1)) + data.word; + } + }, + [&cpu](AddOffsetStackPointer& data) { cpu.sp += data.word; }, + [&cpu](PushPopRegister& data) { + if (data.load) { + for (uint8_t i = 0; i < 8; i++) { + if (get_bit(data.regs, i)) { + cpu.gpr[i] = cpu.bus->read_word(cpu.sp); + cpu.sp += 4; + } + } + + if (data.pclr) { + cpu.pc = cpu.bus->read_word(cpu.sp); + cpu.sp += 4; + cpu.is_flushed = true; + } + } else { + if (data.pclr) { + cpu.sp -= 4; + cpu.bus->write_word(cpu.sp, cpu.lr); + } + + for (int8_t i = 7; i >= 0; i--) { + if (get_bit(data.regs, i)) { + cpu.sp -= 4; + cpu.bus->write_word(cpu.sp, cpu.gpr[i]); + } + } + } + }, + [&cpu](MultipleLoad& data) { + uint32_t rb = cpu.gpr[data.rb]; + + if (data.load) { + for (uint8_t i = 0; i < 8; i++) { + if (get_bit(data.regs, i)) { + cpu.gpr[i] = cpu.bus->read_word(rb); + rb += 4; + } + } + } else { + for (int8_t i = 7; i >= 0; i--) { + if (get_bit(data.regs, i)) { + rb -= 4; + cpu.bus->write_word(rb, cpu.gpr[i]); + } + } + } + + cpu.gpr[data.rb] = rb; + }, + [&cpu](ConditionalBranch& data) { + if (data.condition == Condition::AL) + glogger.warn("Condition 1110 (AL) is undefined"); + + if (!cpu.cpsr.condition(data.condition)) + return; + + cpu.pc += data.offset; + cpu.is_flushed = true; + }, + [&cpu](SoftwareInterrupt& data) { + // next instruction is one instruction behind PC + cpu.lr = cpu.pc - INSTRUCTION_SIZE; + cpu.spsr = cpu.cpsr; + cpu.pc = data.vector; + cpu.cpsr.set_state(State::Arm); + cpu.chg_mode(Mode::Supervisor); + cpu.is_flushed = true; + }, + [&cpu](UnconditionalBranch& data) { + cpu.pc += data.offset; + cpu.is_flushed = true; + }, + [&cpu](LongBranchWithLink& data) { + // 12 bit integer + int32_t offset = data.offset; + + if (data.high) { + uint32_t old_pc = cpu.pc; + + cpu.pc = cpu.lr + offset; + cpu.lr = (old_pc - INSTRUCTION_SIZE) | 1; + cpu.is_flushed = true; + } else { + // 12 + 11 = 23 bit + offset <<= 11; + // sign extend + offset = (offset << 9) >> 9; + cpu.lr = cpu.pc + offset; + } + }, + [](auto& data) { + glogger.error("Unknown thumb format : {}", typeid(data).name()); + } }, + data); +} +} diff --git a/src/cpu/thumb/instruction.cc b/src/cpu/thumb/instruction.cc index 0061eb1..673a4f4 100644 --- a/src/cpu/thumb/instruction.cc +++ b/src/cpu/thumb/instruction.cc @@ -1,5 +1,6 @@ #include "instruction.hh" #include "util/bits.hh" +#include "util/log.hh" namespace matar::thumb { Instruction::Instruction(uint16_t insn) { @@ -55,16 +56,20 @@ Instruction::Instruction(uint16_t insn) { HiRegisterOperations::OpCode opcode = static_cast(bit_range(insn, 8, 9)); + if (opcode == HiRegisterOperations::OpCode::BX && hi_1) + glogger.warn("H1 set with BX"); + rd += (hi_1 ? LO_GPR_COUNT : 0); rs += (hi_2 ? LO_GPR_COUNT : 0); data = HiRegisterOperations{ .rd = rd, .rs = rs, .opcode = opcode }; // Format 6: PC-relative load } else if ((insn & 0xF800) == 0x4800) { - uint8_t word = bit_range(insn, 0, 7); - uint8_t rd = bit_range(insn, 8, 10); + uint16_t word = bit_range(insn, 0, 7); + uint8_t rd = bit_range(insn, 8, 10); - data = PcRelativeLoad{ .word = word, .rd = rd }; + data = + PcRelativeLoad{ .word = static_cast(word << 2), .rd = rd }; // Format 7: Load/store with register offset } else if ((insn & 0xF200) == 0x5000) { @@ -91,13 +96,16 @@ Instruction::Instruction(uint16_t insn) { }; // Format 9: Load/store with immediate offset - } else if ((insn & 0xF000) == 0x6000) { + } else if ((insn & 0xE000) == 0x6000) { uint8_t rd = bit_range(insn, 0, 2); uint8_t rb = bit_range(insn, 3, 5); uint8_t offset = bit_range(insn, 6, 10); bool load = get_bit(insn, 11); bool byte = get_bit(insn, 12); + if (!byte) + offset <<= 2; + data = LoadStoreImmediateOffset{ .rd = rd, .rb = rb, .offset = offset, .load = load, .byte = byte }; @@ -109,40 +117,43 @@ Instruction::Instruction(uint16_t insn) { uint8_t offset = bit_range(insn, 6, 10); bool load = get_bit(insn, 11); + offset <<= 1; + data = LoadStoreHalfword{ .rd = rd, .rb = rb, .offset = offset, .load = load }; // Format 11: SP-relative load/store } else if ((insn & 0xF000) == 0x9000) { - uint8_t word = bit_range(insn, 0, 7); - uint8_t rd = bit_range(insn, 8, 10); - bool load = get_bit(insn, 11); + uint16_t word = bit_range(insn, 0, 7); + uint8_t rd = bit_range(insn, 8, 10); + bool load = get_bit(insn, 11); + + word <<= 2; data = SpRelativeLoad{ .word = word, .rd = rd, .load = load }; // Format 12: Load address } else if ((insn & 0xF000) == 0xA000) { - uint8_t word = bit_range(insn, 0, 7); - uint8_t rd = bit_range(insn, 8, 10); - bool sp = get_bit(insn, 11); + uint16_t word = bit_range(insn, 0, 7); + uint8_t rd = bit_range(insn, 8, 10); + bool sp = get_bit(insn, 11); - data = LoadAddress{ .word = word, .rd = rd, .sp = sp }; - - // Format 12: Load address - } else if ((insn & 0xF000) == 0xA000) { - uint8_t word = bit_range(insn, 0, 7); - uint8_t rd = bit_range(insn, 8, 10); - bool sp = get_bit(insn, 11); + word <<= 2; data = LoadAddress{ .word = word, .rd = rd, .sp = sp }; // Format 13: Add offset to stack pointer } else if ((insn & 0xFF00) == 0xB000) { - uint8_t word = bit_range(insn, 0, 6); + int16_t word = static_cast(bit_range(insn, 0, 6)); bool sign = get_bit(insn, 7); - data = AddOffsetStackPointer{ .word = word, .sign = sign }; + word <<= 2; + word = static_cast(word * (sign ? -1 : 1)); + + data = AddOffsetStackPointer{ + .word = word, + }; // Format 14: Push/pop registers } else if ((insn & 0xF600) == 0xB400) { @@ -162,30 +173,41 @@ Instruction::Instruction(uint16_t insn) { // Format 17: Software interrupt } else if ((insn & 0xFF00) == 0xDF00) { - data = SoftwareInterrupt{}; + uint8_t vector = bit_range(insn, 0, 7); + + data = SoftwareInterrupt{ .vector = vector }; // Format 16: Conditional branch } else if ((insn & 0xF000) == 0xD000) { - uint16_t offset = bit_range(insn, 0, 7); + int32_t offset = bit_range(insn, 0, 7); Condition condition = static_cast(bit_range(insn, 8, 11)); - data = ConditionalBranch{ .offset = static_cast(offset << 1), - .condition = condition }; + offset <<= 1; + + // sign extend the 9 bit integer + offset = (offset << 23) >> 23; + + data = ConditionalBranch{ .offset = offset, .condition = condition }; // Format 18: Unconditional branch } else if ((insn & 0xF800) == 0xE000) { - uint16_t offset = bit_range(insn, 0, 10); + int32_t offset = bit_range(insn, 0, 10); - data = - UnconditionalBranch{ .offset = static_cast(offset << 1) }; + offset <<= 1; + + // sign extend the 12 bit integer + offset = (offset << 20) >> 20; + + data = UnconditionalBranch{ .offset = offset }; // Format 19: Long branch with link } else if ((insn & 0xF000) == 0xF000) { uint16_t offset = bit_range(insn, 0, 10); bool high = get_bit(insn, 11); - data = LongBranchWithLink{ .offset = static_cast(offset << 1), - .high = high }; + offset <<= 1; + + data = LongBranchWithLink{ .offset = offset, .high = high }; } } } diff --git a/src/cpu/thumb/instruction.hh b/src/cpu/thumb/instruction.hh index d9432ad..6a165ee 100644 --- a/src/cpu/thumb/instruction.hh +++ b/src/cpu/thumb/instruction.hh @@ -6,7 +6,10 @@ #include #include -namespace matar::thumb { +namespace matar { +class CpuImpl; + +namespace thumb { // https://en.cppreference.com/w/cpp/utility/variant/visit template @@ -170,7 +173,7 @@ stringify(HiRegisterOperations::OpCode opcode) { } struct PcRelativeLoad { - uint8_t word; + uint16_t word; uint8_t rd; }; @@ -206,20 +209,19 @@ struct LoadStoreHalfword { }; struct SpRelativeLoad { - uint8_t word; + uint16_t word; uint8_t rd; bool load; }; struct LoadAddress { - uint8_t word; + uint16_t word; uint8_t rd; bool sp; }; struct AddOffsetStackPointer { - uint8_t word; - bool sign; + int16_t word; }; struct PushPopRegister { @@ -235,14 +237,16 @@ struct MultipleLoad { }; struct ConditionalBranch { - uint16_t offset; + int32_t offset; Condition condition; }; -struct SoftwareInterrupt {}; +struct SoftwareInterrupt { + uint8_t vector; +}; struct UnconditionalBranch { - uint16_t offset; + int32_t offset; }; struct LongBranchWithLink { @@ -271,12 +275,17 @@ using InstructionData = std::variant; struct Instruction { - InstructionData data; - Instruction(uint16_t insn); + Instruction(InstructionData data) + : data(data) {} + + void exec(CpuImpl& cpu); #ifdef DISASSEMBLER - std::string disassemble(); + std::string disassemble(uint32_t pc = 0); #endif + + InstructionData data; }; } +} diff --git a/src/cpu/thumb/meson.build b/src/cpu/thumb/meson.build index 861c3d7..788697e 100644 --- a/src/cpu/thumb/meson.build +++ b/src/cpu/thumb/meson.build @@ -1,5 +1,6 @@ lib_sources += files( - 'instruction.cc' + 'instruction.cc', + 'exec.cc' ) if get_option('disassembler') diff --git a/tests/cpu/arm/exec.cc b/tests/cpu/arm/exec.cc index 533a806..fcb3791 100644 --- a/tests/cpu/arm/exec.cc +++ b/tests/cpu/arm/exec.cc @@ -1,9 +1,7 @@ +#include "cpu/cpu-fixture.hh" #include "cpu/cpu-impl.hh" -#include "fixture.hh" #include "util/bits.hh" #include -#include -#include using namespace matar; @@ -14,11 +12,11 @@ using namespace arm; TEST_CASE_METHOD(CpuFixture, "Branch and Exchange", TAG) { InstructionData data = BranchAndExchange{ .rn = 3 }; - setr(3, 342890); + setr(3, 342800); exec(data); - CHECK(getr(15) == 342890); + CHECK(getr(15) == 342800); } TEST_CASE_METHOD(CpuFixture, "Branch", TAG) { @@ -806,14 +804,11 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) { processing->opcode = OpCode::AND; - cpsr.set_z(false); - set_psr(cpsr); exec(data, Condition::EQ); // condition is false CHECK(getr(5) == 0); - cpsr = psr(); cpsr.set_z(true); set_psr(cpsr); exec(data, Condition::EQ); diff --git a/tests/cpu/arm/meson.build b/tests/cpu/arm/meson.build index 0aa1cfd..840fc8f 100644 --- a/tests/cpu/arm/meson.build +++ b/tests/cpu/arm/meson.build @@ -1,5 +1,4 @@ tests_sources += files( - 'fixture.cc', 'instruction.cc', 'exec.cc' ) \ No newline at end of file diff --git a/tests/cpu/arm/fixture.cc b/tests/cpu/cpu-fixture.cc similarity index 98% rename from tests/cpu/arm/fixture.cc rename to tests/cpu/cpu-fixture.cc index b7a2272..dcbec86 100644 --- a/tests/cpu/arm/fixture.cc +++ b/tests/cpu/cpu-fixture.cc @@ -1,4 +1,4 @@ -#include "fixture.hh" +#include "cpu-fixture.hh" Psr CpuFixture::psr(bool spsr) { @@ -37,7 +37,7 @@ CpuFixture::set_psr(Psr psr, bool spsr) { } // We need these workarounds to just use the public API and not private -// fields. Assuming that these work correctly is necessary. Besides, all it +// fields. Assuming that these work correctly is necessary. Besides, all that // matters is that the public API is correct. uint32_t CpuFixture::getr_(uint8_t r, CpuImpl& cpu) { diff --git a/tests/cpu/arm/fixture.hh b/tests/cpu/cpu-fixture.hh similarity index 87% rename from tests/cpu/arm/fixture.hh rename to tests/cpu/cpu-fixture.hh index 46d6287..8d57471 100644 --- a/tests/cpu/arm/fixture.hh +++ b/tests/cpu/cpu-fixture.hh @@ -15,6 +15,11 @@ class CpuFixture { instruction.exec(cpu); } + void exec(thumb::InstructionData data) { + thumb::Instruction instruction(data); + instruction.exec(cpu); + } + void reset(uint32_t value = 0) { setr(15, value + 8); } uint32_t getr(uint8_t r) { return getr_(r, cpu); } diff --git a/tests/cpu/meson.build b/tests/cpu/meson.build index 1345315..d82d9cb 100644 --- a/tests/cpu/meson.build +++ b/tests/cpu/meson.build @@ -1,2 +1,6 @@ +tests_sources += files( + 'cpu-fixture.cc' +) + subdir('arm') subdir('thumb') \ No newline at end of file diff --git a/tests/cpu/thumb/exec.cc b/tests/cpu/thumb/exec.cc new file mode 100644 index 0000000..3098cde --- /dev/null +++ b/tests/cpu/thumb/exec.cc @@ -0,0 +1,990 @@ +#include "cpu/cpu-fixture.hh" +#include "cpu/cpu-impl.hh" +#include "cpu/thumb/instruction.hh" +#include "util/bits.hh" +#include + +using namespace matar; + +#define TAG "[thumb][execution]" + +using namespace thumb; + +TEST_CASE_METHOD(CpuFixture, "Move Shifted Register", TAG) { + InstructionData data = MoveShiftedRegister{ + .rd = 3, .rs = 5, .offset = 15, .opcode = ShiftType::LSL + }; + MoveShiftedRegister* move = std::get_if(&data); + + SECTION("LSL") { + setr(3, 0); + setr(5, 6687); + // LSL + exec(data); + CHECK(getr(3) == 219119616); + + setr(5, 0); + // zero + exec(data); + CHECK(getr(3) == 0); + CHECK(psr().z()); + } + + SECTION("LSR") { + move->opcode = ShiftType::LSR; + setr(5, -1827489745); + // LSR + exec(data); + CHECK(getr(3) == 75301); + CHECK(!psr().n()); + + setr(5, 4444); + // zero flag + exec(data); + CHECK(getr(3) == 0); + CHECK(psr().z()); + } + + SECTION("ASR") { + setr(5, -1827489745); + move->opcode = ShiftType::ASR; + // ASR + exec(data); + CHECK(psr().n()); + CHECK(getr(3) == 4294911525); + + setr(5, 500); + // zero flag + exec(data); + CHECK(getr(3) == 0); + CHECK(psr().z()); + } +} + +TEST_CASE_METHOD(CpuFixture, "Add/Subtract", TAG) { + InstructionData data = AddSubtract{ .rd = 5, + .rs = 2, + .offset = 7, + .opcode = AddSubtract::OpCode::ADD, + .imm = false }; + AddSubtract* add = std::get_if(&data); + setr(2, 378427891); + setr(7, -666666); + + SECTION("ADD") { + // register + exec(data); + CHECK(getr(5) == 377761225); + + add->imm = true; + setr(2, (1u << 31) - 1); + // immediate and overflow + exec(data); + CHECK(getr(5) == 2147483654); + CHECK(psr().v()); + + setr(2, -7); + // zero + exec(data); + CHECK(getr(5) == 0); + CHECK(psr().z()); + } + + add->imm = true; + + SECTION("SUB") { + add->opcode = AddSubtract::OpCode::SUB; + setr(2, -((1u << 31) - 1)); + add->offset = 4; + exec(data); + CHECK(getr(5) == 2147483645); + CHECK(psr().v()); + + setr(2, ~0u); + add->offset = -4; + // carry + exec(data); + CHECK(getr(5) == 3); + CHECK(psr().c()); + + setr(2, 0); + add->offset = 0; + // zero + exec(data); + + CHECK(getr(5) == 0); + CHECK(psr().z()); + } +} + +TEST_CASE_METHOD(CpuFixture, "Move/Compare/Add/Subtract Immediate", TAG) { + InstructionData data = MovCmpAddSubImmediate{ + .offset = 251, .rd = 5, .opcode = MovCmpAddSubImmediate::OpCode::MOV + }; + MovCmpAddSubImmediate* move = std::get_if(&data); + + SECTION("MOV") { + exec(data); + CHECK(getr(5) == 251); + + move->offset = 0; + // zero + exec(data); + CHECK(getr(5) == 0); + CHECK(psr().z()); + } + + SECTION("CMP") { + setr(5, 251); + move->opcode = MovCmpAddSubImmediate::OpCode::CMP; + CHECK(!psr().z()); + exec(data); + CHECK(getr(5) == 251); + CHECK(psr().z()); + + // overflow + setr(5, -((1u << 31) - 1)); + CHECK(!psr().v()); + exec(data); + CHECK(getr(5) == 2147483649); + CHECK(psr().v()); + } + + SECTION("ADD") { + move->opcode = MovCmpAddSubImmediate::OpCode::ADD; + setr(5, (1u << 31) - 1); + // immediate and overflow + exec(data); + CHECK(getr(5) == 2147483898); + CHECK(psr().v()); + + setr(5, -251); + // zero + exec(data); + CHECK(getr(5) == 0); + CHECK(psr().z()); + } + + SECTION("SUB") { + // same as CMP but loaded + setr(5, 251); + move->opcode = MovCmpAddSubImmediate::OpCode::SUB; + CHECK(!psr().z()); + exec(data); + CHECK(getr(5) == 0); + CHECK(psr().z()); + + // overflow + setr(5, -((1u << 31) - 1)); + CHECK(!psr().v()); + exec(data); + CHECK(getr(5) == 2147483398); + CHECK(psr().v()); + } +} + +TEST_CASE_METHOD(CpuFixture, "ALU Operations", TAG) { + InstructionData data = + AluOperations{ .rd = 1, .rs = 3, .opcode = AluOperations::OpCode::AND }; + AluOperations* alu = std::get_if(&data); + + setr(1, 328940001); + setr(3, -991); + + SECTION("AND") { + // 328940001 & -991 + exec(data); + CHECK(getr(1) == 328939553); + CHECK(!psr().n()); + + setr(3, 0); + CHECK(!psr().z()); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("EOR") { + alu->opcode = AluOperations::OpCode::EOR; + // 328940001 ^ -991 + exec(data); + CHECK(getr(1) == 3966027200); + CHECK(psr().n()); + + setr(3, 3966027200); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + CHECK(!psr().n()); + } + + SECTION("LSL") { + setr(3, 3); + alu->opcode = AluOperations::OpCode::LSL; + // 328940001 << 3 + exec(data); + CHECK(getr(1) == 2631520008); + CHECK(psr().n()); + + setr(1, 0); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("LSR") { + alu->opcode = AluOperations::OpCode::LSR; + setr(3, 991); + // 328940001 >> 991 + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + + setr(1, -83885328); + setr(3, 5); + // -83885328 >> 5 + exec(data); + CHECK(getr(1) == 131596311); + CHECK(!psr().z()); + CHECK(!psr().n()); + } + + SECTION("ASR") { + alu->opcode = AluOperations::OpCode::ASR; + setr(3, 991); + // 328940001 >> 991 + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + + setr(1, -83885328); + setr(3, 5); + // -83885328 >> 5 + exec(data); + CHECK(getr(1) == 4292345879); + CHECK(!psr().z()); + CHECK(psr().n()); + } + + SECTION("ADC") { + alu->opcode = AluOperations::OpCode::ADC; + setr(3, (1u << 31) - 1); + Psr cpsr = psr(); + cpsr.set_c(true); + set_psr(cpsr); + // 2147483647 + 328940001 + 1 + exec(data); + CHECK(getr(1) == 2476423649); + CHECK(psr().v()); + CHECK(psr().n()); + CHECK(!psr().c()); + + setr(3, -328940001); + setr(1, 328940001); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("SBC") { + alu->opcode = AluOperations::OpCode::SBC; + setr(3, -((1u << 31) - 1)); + + Psr cpsr = psr(); + cpsr.set_c(false); + set_psr(cpsr); + + // 328940001 - -2147483647 - 1 + exec(data); + CHECK(getr(1) == 2476423647); + CHECK(psr().v()); + CHECK(psr().n()); + CHECK(!psr().c()); + + setr(1, -34892); + setr(3, -34893); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("ROR") { + setr(3, 993); + alu->opcode = AluOperations::OpCode::ROR; + // 328940001 ROR 993 + exec(data); + CHECK(getr(1) == 2311953648); + CHECK(psr().n()); + CHECK(psr().c()); + + setr(1, 0); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("TST") { + alu->opcode = AluOperations::OpCode::TST; + // 328940001 & -991 + exec(data); + // no change + CHECK(getr(1) == 328940001); + + setr(3, 0); + CHECK(!psr().z()); + // zero + exec(data); + CHECK(getr(1) == 328940001); + CHECK(psr().z()); + } + + SECTION("NEG") { + alu->opcode = AluOperations::OpCode::NEG; + // -(-991) + exec(data); + CHECK(getr(1) == 991); + + setr(3, 0); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("CMP") { + alu->opcode = AluOperations::OpCode::CMP; + setr(3, -((1u << 31) - 1)); + // 328940001 - -2147483647 + exec(data); + // no change + CHECK(getr(1) == 328940001); + CHECK(psr().v()); + CHECK(psr().n()); + CHECK(!psr().c()); + + setr(1, -34892); + setr(3, -34892); + // zero + exec(data); + // no change (-34892) + CHECK(getr(1) == 4294932404); + CHECK(psr().z()); + } + + SECTION("CMN") { + alu->opcode = AluOperations::OpCode::CMN; + setr(3, (1u << 31) - 1); + // 2147483647 + 328940001 + exec(data); + CHECK(getr(1) == 328940001); + CHECK(psr().v()); + CHECK(psr().n()); + CHECK(!psr().c()); + + setr(3, -328940001); + setr(1, 328940001); + // zero + exec(data); + CHECK(getr(1) == 328940001); + CHECK(psr().z()); + } + + SECTION("ORR") { + alu->opcode = AluOperations::OpCode::ORR; + // 328940001 | -991 + exec(data); + CHECK(getr(1) == 4294966753); + CHECK(psr().n()); + + setr(1, 0); + setr(3, 0); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("MUL") { + alu->opcode = AluOperations::OpCode::MUL; + // 328940001 * -991 (lower 32 bits) (-325979540991 & 0xFFFFFFFF) + exec(data); + CHECK(getr(1) == 437973505); + + setr(3, 0); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("BIC") { + alu->opcode = AluOperations::OpCode::BIC; + // 328940001 & ~ -991 + exec(data); + CHECK(getr(1) == 448); + CHECK(!psr().n()); + + setr(3, ~0u); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } + + SECTION("MVN") { + alu->opcode = AluOperations::OpCode::MVN; + //~ -991 + exec(data); + CHECK(getr(1) == 990); + CHECK(!psr().n()); + + setr(3, 24358); + // negative + exec(data); + CHECK(getr(1) == 4294942937); + CHECK(psr().n()); + + setr(3, ~0u); + // zero + exec(data); + CHECK(getr(1) == 0); + CHECK(psr().z()); + } +} + +TEST_CASE_METHOD(CpuFixture, "Hi Register Operations/Branch Exchange", TAG) { + InstructionData data = HiRegisterOperations{ + .rd = 5, .rs = 15, .opcode = HiRegisterOperations::OpCode::ADD + }; + HiRegisterOperations* hi = std::get_if(&data); + + setr(15, 3452948950); + setr(5, 958656720); + + SECTION("ADD") { + exec(data); + CHECK(getr(5) == 116638374); + + // hi + hi + hi->rd = 14; + hi->rs = 15; + setr(14, 42589); + exec(data); + CHECK(getr(14) == 3452991539); + } + + SECTION("CMP") { + hi->opcode = HiRegisterOperations::OpCode::CMP; + exec(data); + + // no change + CHECK(getr(5) == 958656720); + CHECK(!psr().n()); + CHECK(!psr().c()); + CHECK(!psr().v()); + CHECK(!psr().z()); + + setr(15, 958656720); + // zero + exec(data); + // no change + CHECK(getr(5) == 958656720); + CHECK(psr().z()); + } + + SECTION("MOV") { + hi->opcode = HiRegisterOperations::OpCode::MOV; + exec(data); + + CHECK(getr(5) == 3452948950); + } + + SECTION("BX") { + hi->opcode = HiRegisterOperations::OpCode::BX; + hi->rs = 10; + + SECTION("Arm") { + setr(10, 2189988); + exec(data); + CHECK(getr(15) == 2189988); + // switched to arm + CHECK(psr().state() == State::Arm); + } + + SECTION("Thumb") { + setr(10, 2189989); + exec(data); + CHECK(getr(15) == 2189988); + + // switched to thumb + CHECK(psr().state() == State::Thumb); + } + } +} + +TEST_CASE_METHOD(CpuFixture, "PC Relative Load", TAG) { + InstructionData data = PcRelativeLoad{ .word = 380, .rd = 0 }; + + setr(15, 13804); + // 13804 + 380 + bus.write_word(14184, 489753492); + + CHECK(getr(0) == 0); + exec(data); + CHECK(getr(0) == 489753492); +} + +TEST_CASE_METHOD(CpuFixture, "Load/Store with Register Offset", TAG) { + InstructionData data = LoadStoreRegisterOffset{ + .rd = 3, .rb = 0, .ro = 7, .byte = false, .load = false + }; + LoadStoreRegisterOffset* load = std::get_if(&data); + + setr(7, 9910); + setr(0, 1034); + setr(3, 389524259); + + SECTION("store") { + // 9910 + 1034 + CHECK(bus.read_word(10944) == 0); + exec(data); + CHECK(bus.read_word(10944) == 389524259); + + // byte + load->byte = true; + bus.write_word(10944, 0); + exec(data); + CHECK(bus.read_word(10944) == 35); + } + + SECTION("load") { + load->load = true; + bus.write_word(10944, 11123489); + exec(data); + CHECK(getr(3) == 11123489); + + // byte + load->byte = true; + exec(data); + CHECK(getr(3) == 33); + } +} + +TEST_CASE_METHOD(CpuFixture, "Load/Store Sign Extended Byte/Halfword", TAG) { + InstructionData data = LoadStoreSignExtendedHalfword{ + .rd = 3, .rb = 0, .ro = 7, .s = false, .h = false + }; + LoadStoreSignExtendedHalfword* load = + std::get_if(&data); + + setr(7, 9910); + setr(0, 1034); + setr(3, 389524259); + + SECTION("SH = 00") { + // 9910 + 1034 + CHECK(bus.read_word(10944) == 0); + exec(data); + CHECK(bus.read_word(10944) == 43811); + } + + SECTION("SH = 01") { + load->h = true; + bus.write_word(10944, 11123489); + exec(data); + CHECK(getr(3) == 47905); + } + + SECTION("SH = 10") { + load->s = true; + bus.write_word(10944, 34521594); + exec(data); + // sign extended 250 byte (0xFA) + CHECK(getr(3) == 4294967290); + } + + SECTION("SH = 11") { + load->s = true; + load->h = true; + bus.write_word(10944, 11123489); + // sign extended 47905 halfword (0xBB21) + exec(data); + CHECK(getr(3) == 4294949665); + } +} + +TEST_CASE_METHOD(CpuFixture, "Load/Store with Immediate Offset", TAG) { + InstructionData data = LoadStoreImmediateOffset{ + .rd = 3, .rb = 0, .offset = 110, .load = false, .byte = false + }; + LoadStoreImmediateOffset* load = + std::get_if(&data); + + setr(0, 1034); + setr(3, 389524259); + + SECTION("store") { + // 110 + 1034 + CHECK(bus.read_word(1144) == 0); + exec(data); + CHECK(bus.read_word(1144) == 389524259); + + // byte + load->byte = true; + bus.write_word(1144, 0); + exec(data); + CHECK(bus.read_word(1144) == 35); + } + + SECTION("load") { + load->load = true; + bus.write_word(1144, 11123489); + exec(data); + CHECK(getr(3) == 11123489); + + // byte + load->byte = true; + exec(data); + CHECK(getr(3) == 33); + } +} + +TEST_CASE_METHOD(CpuFixture, "Load/Store Halfword", TAG) { + InstructionData data = + LoadStoreHalfword{ .rd = 3, .rb = 0, .offset = 110, .load = false }; + LoadStoreHalfword* load = std::get_if(&data); + + setr(0, 1034); + setr(3, 389524259); + + SECTION("store") { + // 110 + 1034 + CHECK(bus.read_word(1144) == 0); + exec(data); + CHECK(bus.read_word(1144) == 43811); + } + + SECTION("load") { + load->load = true; + bus.write_word(1144, 11123489); + exec(data); + CHECK(getr(3) == 47905); + } +} + +TEST_CASE_METHOD(CpuFixture, "SP Relative Load", TAG) { + InstructionData data = + SpRelativeLoad{ .word = 808, .rd = 1, .load = false }; + SpRelativeLoad* load = std::get_if(&data); + + setr(1, 2349505744); + // sp + setr(13, 336); + + SECTION("store") { + // 110 + 1034 + CHECK(bus.read_word(1144) == 0); + exec(data); + CHECK(bus.read_word(1144) == 2349505744); + } + + SECTION("load") { + load->load = true; + bus.write_word(1144, 11123489); + exec(data); + CHECK(getr(1) == 11123489); + } +} + +TEST_CASE_METHOD(CpuFixture, "Load Address", TAG) { + InstructionData data = LoadAddress{ .word = 808, .rd = 1, .sp = false }; + LoadAddress* load = std::get_if(&data); + + // pc + setr(15, 336485); + // sp + setr(13, 69879977); + + SECTION("PC") { + exec(data); + CHECK(getr(1) == 337293); + } + + SECTION("SP") { + load->sp = true; + exec(data); + CHECK(getr(1) == 69880785); + } +} + +TEST_CASE_METHOD(CpuFixture, "Add Offset to Stack Pointer", TAG) { + InstructionData data = AddOffsetStackPointer{ .word = 473 }; + AddOffsetStackPointer* add = std::get_if(&data); + + // sp + setr(13, 69879977); + + SECTION("positive") { + exec(data); + CHECK(getr(13) == 69880450); + } + + SECTION("negative") { + add->word = -473; + exec(data); + CHECK(getr(13) == 69879504); + } +} + +TEST_CASE_METHOD(CpuFixture, "Push/Pop Registers", TAG) { + InstructionData data = + PushPopRegister{ .regs = 0b11010011, .pclr = false, .load = false }; + PushPopRegister* push = std::get_if(&data); + // registers = 0, 1, 4, 6, 7 + + SECTION("push (store)") { + + // populate registers + setr(0, 237164); + setr(1, 679785111); + setr(4, 905895898); + setr(6, 131313333); + setr(7, 131); + + auto checker = [this]() { + // address + CHECK(bus.read_word(5548) == 237164); + CHECK(bus.read_word(5552) == 679785111); + CHECK(bus.read_word(5556) == 905895898); + CHECK(bus.read_word(5560) == 131313333); + CHECK(bus.read_word(5564) == 131); + }; + + // set stack pointer to top of stack + setr(13, 5568); + + SECTION("without LR") { + exec(data); + checker(); + CHECK(getr(13) == 5548); + } + + SECTION("with LR") { + push->pclr = true; + // populate lr + setr(14, 999304); + // add another word on stack (5568 + 4) + setr(13, 5572); + exec(data); + + CHECK(bus.read_word(5568) == 999304); + checker(); + CHECK(getr(13) == 5548); + } + } + + SECTION("pop (load)") { + push->load = true; + + // populate memory + bus.write_word(5548, 237164); + bus.write_word(5552, 679785111); + bus.write_word(5556, 905895898); + bus.write_word(5560, 131313333); + bus.write_word(5564, 131); + + auto checker = [this]() { + CHECK(getr(0) == 237164); + CHECK(getr(1) == 679785111); + CHECK(getr(2) == 0); + CHECK(getr(3) == 0); + CHECK(getr(4) == 905895898); + CHECK(getr(5) == 0); + CHECK(getr(6) == 131313333); + CHECK(getr(7) == 131); + + for (uint8_t i = 0; i < 8; i++) { + setr(i, 0); + } + }; + + // set stack pointer to bottom of stack + setr(13, 5548); + + SECTION("without SP") { + exec(data); + checker(); + CHECK(getr(13) == 5568); + } + + SECTION("with SP") { + push->pclr = true; + // populate next address + bus.write_word(5568, 93333912); + exec(data); + + CHECK(getr(15) == 93333912); + checker(); + CHECK(getr(13) == 5572); + } + } +} + +TEST_CASE_METHOD(CpuFixture, "Multiple Load/Store", TAG) { + InstructionData data = + MultipleLoad{ .regs = 0b11010101, .rb = 2, .load = false }; + MultipleLoad* push = std::get_if(&data); + // registers = 0, 1, 4, 6, 7 + + SECTION("push (store)") { + + // populate registers + setr(0, 237164); + setr(4, 905895898); + setr(6, 131313333); + setr(7, 131); + + // set R2 (base) to top of stack + setr(2, 5568); + + exec(data); + + CHECK(bus.read_word(5548) == 237164); + CHECK(bus.read_word(5552) == 5568); + CHECK(bus.read_word(5556) == 905895898); + CHECK(bus.read_word(5560) == 131313333); + CHECK(bus.read_word(5564) == 131); + // write back + CHECK(getr(2) == 5548); + } + + SECTION("pop (load)") { + push->load = true; + + // populate memory + bus.write_word(5548, 237164); + bus.write_word(5552, 679785111); + bus.write_word(5556, 905895898); + bus.write_word(5560, 131313333); + bus.write_word(5564, 131); + + // base + setr(2, 5548); + + exec(data); + CHECK(getr(0) == 237164); + CHECK(getr(1) == 0); + CHECK(getr(2) == 5568); // write back + CHECK(getr(3) == 0); + CHECK(getr(4) == 905895898); + CHECK(getr(5) == 0); + CHECK(getr(6) == 131313333); + CHECK(getr(7) == 131); + } +} + +TEST_CASE_METHOD(CpuFixture, "Conditional Branch", TAG) { + InstructionData data = + ConditionalBranch{ .offset = -192, .condition = Condition::EQ }; + ConditionalBranch* branch = std::get_if(&data); + + setr(15, 4589344); + + SECTION("z") { + Psr cpsr = psr(); + // condition is false + exec(data); + CHECK(getr(15) == 4589344); + + cpsr.set_z(true); + set_psr(cpsr); + // condition is true + exec(data); + CHECK(getr(15) == 4589152); + } + + SECTION("c") { + branch->condition = Condition::CS; + Psr cpsr = psr(); + // condition is false + exec(data); + CHECK(getr(15) == 4589344); + + cpsr.set_c(true); + set_psr(cpsr); + // condition is true + exec(data); + CHECK(getr(15) == 4589152); + } + + SECTION("n") { + branch->condition = Condition::MI; + Psr cpsr = psr(); + // condition is false + exec(data); + CHECK(getr(15) == 4589344); + + cpsr.set_n(true); + set_psr(cpsr); + // condition is true + exec(data); + CHECK(getr(15) == 4589152); + } + + SECTION("v") { + branch->condition = Condition::VS; + Psr cpsr = psr(); + // condition is false + exec(data); + CHECK(getr(15) == 4589344); + + cpsr.set_v(true); + set_psr(cpsr); + // condition is true + exec(data); + CHECK(getr(15) == 4589152); + } +} + +TEST_CASE_METHOD(CpuFixture, "Software Interrupt", TAG) { + InstructionData data = SoftwareInterrupt{ .vector = 33 }; + + setr(15, 4492); + exec(data); + CHECK(psr().raw() == psr(true).raw()); + CHECK(getr(14) == 4490); + CHECK(getr(15) == 33); + CHECK(psr().state() == State::Arm); + CHECK(psr().mode() == Mode::Supervisor); +} + +TEST_CASE_METHOD(CpuFixture, "Unconditional Branch", TAG) { + InstructionData data = UnconditionalBranch{ .offset = -920 }; + + setr(15, 4589344); + exec(data); + CHECK(getr(15) == 4588424); +} + +TEST_CASE_METHOD(CpuFixture, "Long Branch With Link", TAG) { + InstructionData data = LongBranchWithLink{ .offset = 3262, .high = false }; + LongBranchWithLink* branch = std::get_if(&data); + + // high + setr(15, 4589344); + + exec(data); + CHECK(getr(14) == 2881312); + + // low + branch->high = true; + exec(data); + CHECK(getr(14) == 4589343); + CHECK(getr(15) == 2884574); +} diff --git a/tests/cpu/thumb/instruction.cc b/tests/cpu/thumb/instruction.cc index 6e510a3..49f0e11 100644 --- a/tests/cpu/thumb/instruction.cc +++ b/tests/cpu/thumb/instruction.cc @@ -178,11 +178,12 @@ TEST_CASE("PC Relative Load", TAG) { PcRelativeLoad* ldr = nullptr; REQUIRE((ldr = std::get_if(&instruction.data))); - CHECK(ldr->word == 230); + // 230 << 2 + CHECK(ldr->word == 920); CHECK(ldr->rd == 2); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "LDR R2,[PC,#230]"); + CHECK(instruction.disassemble() == "LDR R2,[PC,#920]"); #endif } @@ -247,21 +248,32 @@ TEST_CASE("Load/Store with Immediate Offset", TAG) { REQUIRE((ldr = std::get_if(&instruction.data))); CHECK(ldr->rd == 5); CHECK(ldr->rb == 3); - CHECK(ldr->offset == 22); + // 22 << 4 when byte == false + CHECK(ldr->offset == 88); CHECK(ldr->byte == false); CHECK(ldr->load == false); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "STR R5,[R3,#22]"); + CHECK(instruction.disassemble() == "STR R5,[R3,#88]"); - ldr->byte = true; + ldr->load = true; + CHECK(instruction.disassemble() == "LDR R5,[R3,#88]"); +#endif + + // byte + raw = 0b0111010110011101; + instruction = Instruction(raw); + + INFO(instruction.data.index()); + REQUIRE((ldr = std::get_if(&instruction.data))); + CHECK(ldr->byte == true); + CHECK(ldr->offset == 22); + +#ifdef DISASSEMBLER CHECK(instruction.disassemble() == "STRB R5,[R3,#22]"); ldr->load = true; CHECK(instruction.disassemble() == "LDRB R5,[R3,#22]"); - - ldr->byte = false; - CHECK(instruction.disassemble() == "LDR R5,[R3,#22]"); #endif } @@ -273,14 +285,15 @@ TEST_CASE("Load/Store Halfword", TAG) { REQUIRE((ldr = std::get_if(&instruction.data))); CHECK(ldr->rd == 5); CHECK(ldr->rb == 3); - CHECK(ldr->offset == 26); + // 26 << 1 + CHECK(ldr->offset == 52); CHECK(ldr->load == false); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "STRH R5,[R3,#26]"); + CHECK(instruction.disassemble() == "STRH R5,[R3,#52]"); ldr->load = true; - CHECK(instruction.disassemble() == "LDRH R5,[R3,#26]"); + CHECK(instruction.disassemble() == "LDRH R5,[R3,#52]"); #endif } @@ -291,14 +304,15 @@ TEST_CASE("SP-Relative Load/Store", TAG) { REQUIRE((ldr = std::get_if(&instruction.data))); CHECK(ldr->rd == 4); - CHECK(ldr->word == 157); + // 157 << 2 + CHECK(ldr->word == 628); CHECK(ldr->load == false); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "STR R4,[SP,#157]"); + CHECK(instruction.disassemble() == "STR R4,[SP,#628]"); ldr->load = true; - CHECK(instruction.disassemble() == "LDR R4,[SP,#157]"); + CHECK(instruction.disassemble() == "LDR R4,[SP,#628]"); #endif } @@ -308,15 +322,16 @@ TEST_CASE("Load Adress", TAG) { LoadAddress* add = nullptr; REQUIRE((add = std::get_if(&instruction.data))); - CHECK(add->word == 143); + // 143 << 2 + CHECK(add->word == 572); CHECK(add->rd == 1); CHECK(add->sp == false); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "ADD R1,PC,#143"); + CHECK(instruction.disassemble() == "ADD R1,PC,#572"); add->sp = true; - CHECK(instruction.disassemble() == "ADD R1,SP,#143"); + CHECK(instruction.disassemble() == "ADD R1,SP,#572"); #endif } @@ -326,14 +341,21 @@ TEST_CASE("Add Offset to Stack Pointer", TAG) { AddOffsetStackPointer* add = nullptr; REQUIRE((add = std::get_if(&instruction.data))); - CHECK(add->word == 37); - CHECK(add->sign == false); + // 37 << 2 + CHECK(add->word == 148); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "ADD SP,#+37"); + CHECK(instruction.disassemble() == "ADD SP,#148"); +#endif - add->sign = true; - CHECK(instruction.disassemble() == "ADD SP,#-37"); + raw = 0b1011000010100101; + instruction = Instruction(raw); + + REQUIRE((add = std::get_if(&instruction.data))); + CHECK(add->word == -148); + +#ifdef DISASSEMBLER + CHECK(instruction.disassemble() == "ADD SP,#-148"); #endif } @@ -380,17 +402,18 @@ TEST_CASE("Multiple Load/Store", TAG) { } TEST_CASE("Conditional Branch", TAG) { - uint16_t raw = 0b1101100101110100; + uint16_t raw = 0b1101100110110100; Instruction instruction(raw); ConditionalBranch* b = nullptr; REQUIRE((b = std::get_if(&instruction.data))); - // 116 << 2 - CHECK(b->offset == 232); + // (-76 << 1) + CHECK(b->offset == -152); CHECK(b->condition == Condition::LS); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "BLS 232"); + // (-76 << 1) + PC (0) + 4 + CHECK(instruction.disassemble() == "BLS #-148"); #endif } @@ -402,7 +425,7 @@ TEST_CASE("SoftwareInterrupt") { REQUIRE((swi = std::get_if(&instruction.data))); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "SWI"); + CHECK(instruction.disassemble() == "SWI 51"); #endif } @@ -412,11 +435,12 @@ TEST_CASE("Unconditional Branch") { UnconditionalBranch* b = nullptr; REQUIRE((b = std::get_if(&instruction.data))); - // 1843 << 2 - REQUIRE(b->offset == 3686); + // (2147483443 << 1) + REQUIRE(b->offset == -410); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "B 3686"); + // (2147483443 << 1) + PC(0) + 4 + CHECK(instruction.disassemble() == "B #-406"); #endif } @@ -431,10 +455,10 @@ TEST_CASE("Long Branch with link") { CHECK(bl->high == false); #ifdef DISASSEMBLER - CHECK(instruction.disassemble() == "BL 2520"); + CHECK(instruction.disassemble() == "BL #2520"); bl->high = true; - CHECK(instruction.disassemble() == "BLH 2520"); + CHECK(instruction.disassemble() == "BLH #2520"); #endif } diff --git a/tests/cpu/thumb/meson.build b/tests/cpu/thumb/meson.build index 020125c..840fc8f 100644 --- a/tests/cpu/thumb/meson.build +++ b/tests/cpu/thumb/meson.build @@ -1,3 +1,4 @@ tests_sources += files( - 'instruction.cc' + 'instruction.cc', + 'exec.cc' ) \ No newline at end of file