From 0b674c7c64b33929dce8f6b815b3009cf453ece5 Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 14 Sep 2023 10:12:30 +0530 Subject: [PATCH] [UNTESTED] refactor how instructions are parsed Signed-off-by: Amneesh Singh --- apps/target/main.cc | 6 +- include/cpu/instruction.hh | 1 + include/cpu/meson.build | 1 + include/cpu/utility.hh | 80 +---- src/cpu/cpu.cc | 265 ++++++++++++++++- src/cpu/cpu.hh | 3 +- src/cpu/instruction.cc | 582 ++++++++++++++++++------------------- src/cpu/instruction.hh | 132 +++++++++ src/cpu/utility.cc | 43 +++ src/cpu/utility.hh | 14 + src/header.hh | 4 +- 11 files changed, 742 insertions(+), 389 deletions(-) create mode 120000 include/cpu/instruction.hh mode change 100644 => 120000 include/cpu/utility.hh create mode 100644 src/cpu/instruction.hh diff --git a/apps/target/main.cc b/apps/target/main.cc index 3a5d692..53bd8f8 100644 --- a/apps/target/main.cc +++ b/apps/target/main.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include // NOLINTBEGIN @@ -83,7 +84,10 @@ main(int argc, const char* argv[]) { Memory memory(std::move(bios), std::move(rom)); Bus bus(memory); Cpu cpu(bus); - cpu.step(); + while (true) { + cpu.step(); + sleep(1); + } } return 0; } diff --git a/include/cpu/instruction.hh b/include/cpu/instruction.hh new file mode 120000 index 0000000..cadc12e --- /dev/null +++ b/include/cpu/instruction.hh @@ -0,0 +1 @@ +../../src/cpu/instruction.hh \ No newline at end of file diff --git a/include/cpu/meson.build b/include/cpu/meson.build index 3026ff2..fd15eab 100644 --- a/include/cpu/meson.build +++ b/include/cpu/meson.build @@ -1,5 +1,6 @@ headers += files( 'cpu.hh', + 'instruction.hh', 'psr.hh', 'utility.hh' ) \ No newline at end of file diff --git a/include/cpu/utility.hh b/include/cpu/utility.hh deleted file mode 100644 index dcee945..0000000 --- a/include/cpu/utility.hh +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include -#include - -static constexpr size_t ARM_INSTRUCTION_SIZE = 4; -static constexpr size_t THUMB_INSTRUCTION_SIZE = 2; - -enum class Mode { - /* M[4:0] in PSR */ - User = 0b10000, - Fiq = 0b10001, - Irq = 0b10010, - Supervisor = 0b10011, - Abort = 0b10111, - Undefined = 0b11011, - System = 0b11111, -}; - -enum class State { - Arm = 0, - Thumb = 1 -}; - -enum class Condition { - EQ = 0b0000, - NE = 0b0001, - CS = 0b0010, - CC = 0b0011, - MI = 0b0100, - PL = 0b0101, - VS = 0b0110, - VC = 0b0111, - HI = 0b1000, - LS = 0b1001, - GE = 0b1010, - LT = 0b1011, - GT = 0b1100, - LE = 0b1101, - AL = 0b1110 -}; - -// https://fmt.dev/dev/api.html#std-ostream-support -std::ostream& -operator<<(std::ostream& os, const Condition cond); -template<> -struct fmt::formatter : ostream_formatter {}; - -enum class OpCode { - AND = 0b0000, - EOR = 0b0001, - SUB = 0b0010, - RSB = 0b0011, - ADD = 0b0100, - ADC = 0b0101, - SBC = 0b0110, - RSC = 0b0111, - TST = 0b1000, - TEQ = 0b1001, - CMP = 0b1010, - CMN = 0b1011, - ORR = 0b1100, - MOV = 0b1101, - BIC = 0b1110, - MVN = 0b1111 -}; - -enum class ShiftType { - LSL = 0b00, - LSR = 0b01, - ASR = 0b10, - ROR = 0b11 -}; - -// https://fmt.dev/dev/api.html#std-ostream-support -std::ostream& -operator<<(std::ostream& os, const ShiftType cond); -template<> -struct fmt::formatter : ostream_formatter {}; diff --git a/include/cpu/utility.hh b/include/cpu/utility.hh new file mode 120000 index 0000000..6b3e57e --- /dev/null +++ b/include/cpu/utility.hh @@ -0,0 +1 @@ +../../src/cpu/utility.hh \ No newline at end of file diff --git a/src/cpu/cpu.cc b/src/cpu/cpu.cc index d2097da..e5afd94 100644 --- a/src/cpu/cpu.cc +++ b/src/cpu/cpu.cc @@ -1,4 +1,5 @@ #include "cpu.hh" +#include "util/bits.hh" #include "util/log.hh" #include "utility.hh" #include @@ -112,16 +113,276 @@ Cpu::chg_mode(const Mode to) { cpsr.set_mode(to); } +void +Cpu::exec_arm(const ArmInstruction instruction) { + auto cond = instruction.get_condition(); + auto data = instruction.get_data(); + + if (!cpsr.condition(cond)) { + return; + } + + auto pc_error = [](uint8_t r) { + if (r == 15) + log_error("Using PC (R15) as operand register"); + }; + + auto pc_warn = [](uint8_t r) { + if (r == 15) + log_warn("Using PC (R15) as operand register"); + }; + + std::visit( + overloaded{ + [this, pc_warn](ArmInstruction::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_nth_bit(pc, 0); + + if (state == State::Arm) + rst_nth_bit(pc, 1); + }, + [this](ArmInstruction::Branch& data) { + auto offset = data.offset; + // lsh 2 and sign extend the 26 bit offset to 32 bits + offset <<= 2; + + if (get_nth_bit(offset, 25)) + offset |= 0xFC000000; + + if (data.link) + gpr[14] = pc - ARM_INSTRUCTION_SIZE; + + pc += offset - ARM_INSTRUCTION_SIZE; + }, + [this, pc_error](ArmInstruction::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(!static_cast(gpr[data.rd])); + cpsr.set_n(get_nth_bit(gpr[data.rd], 31)); + cpsr.set_c(0); + } + }, + [this, pc_error](ArmInstruction::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] = get_bit_range(eval, 0, 31); + gpr[data.rdhi] = get_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] = get_bit_range(eval, 0, 31); + gpr[data.rdhi] = get_bit_range(eval, 32, 63); + } + + if (data.set) { + cpsr.set_z(!(static_cast(gpr[data.rdhi]) || + static_cast(gpr[data.rdlo]))); + cpsr.set_n(get_nth_bit(gpr[data.rdhi], 31)); + cpsr.set_c(0); + cpsr.set_v(0); + } + }, + [](ArmInstruction::Undefined) { log_warn("Undefined instruction"); }, + [this, pc_warn](ArmInstruction::SingleDataSwap& data) { + pc_warn(data.rm); + pc_warn(data.rn); + pc_warn(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](ArmInstruction::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.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); + + eval_shift(shift->data.type, gpr[shift->rm], amount, carry); + + cpsr.set_c(carry); + } + + // PC is always two instructions ahead + if (data.rn == 15) + address -= 2 * ARM_INSTRUCTION_SIZE; + + if (data.pre) + address += (data.up ? offset : -offset); + + // load + if (data.load) { + // byte + if (data.byte) + gpr[data.rd] = bus->read_byte(address); + // word + else + gpr[data.rd] = bus->read_word(address); + // store + } else { + // take PC into consideration + if (data.rd == 15) + 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; + }, + [this, pc_warn, pc_error](ArmInstruction::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 + if (get_nth_bit(gpr[data.rd], 15)) + gpr[data.rd] |= 0xFFFF0000; + // byte + } else { + gpr[data.rd] = bus->read_byte(address); + + // sign extend the byte + if (get_nth_bit(gpr[data.rd], 7)) + gpr[data.rd] |= 0xFFFFFF00; + } + // unsigned halfword + } else if (data.half) { + gpr[data.rd] = bus->read_halfword(address); + } + // store + } else { + // take PC into consideration + if (data.rd == 15) + 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; + }, + [this](ArmInstruction::SoftwareInterrupt) { + chg_mode(Mode::Supervisor); + pc = 0x08; + spsr = cpsr; + }, + [](auto& data) { log_error("{} instruction", typeid(data).name()); } }, + data); +} + void Cpu::step() { uint32_t cur_pc = pc - 2 * ARM_INSTRUCTION_SIZE; if (cpsr.state() == State::Arm) { + ArmInstruction instruction(bus->read_word(cur_pc)); log_info("{:#034b}", bus->read_word(cur_pc)); - std::string disassembled = exec_arm(bus->read_word(cur_pc)); + exec_arm(instruction); - log_info("{:#010X} : {}", cur_pc, disassembled); + log_info("{:#010X} : {}", cur_pc, instruction.disassemble()); pc += ARM_INSTRUCTION_SIZE; } diff --git a/src/cpu/cpu.hh b/src/cpu/cpu.hh index 83af397..fa120fd 100644 --- a/src/cpu/cpu.hh +++ b/src/cpu/cpu.hh @@ -1,6 +1,7 @@ #pragma once #include "bus.hh" +#include "instruction.hh" #include "psr.hh" #include @@ -50,5 +51,5 @@ class Cpu { } spsr_banked; // banked saved program status registers void chg_mode(const Mode to); - std::string exec_arm(const uint32_t insn); + void exec_arm(const ArmInstruction instruction); }; diff --git a/src/cpu/instruction.cc b/src/cpu/instruction.cc index b93d331..0ab8cbd 100644 --- a/src/cpu/instruction.cc +++ b/src/cpu/instruction.cc @@ -1,342 +1,181 @@ -#include "cpu.hh" -#include "cpu/utility.hh" +#include "instruction.hh" #include "util/bits.hh" -#include "util/log.hh" -#include - -using namespace logger; -using std::array; -using stringv = std::string_view; - -std::string -Cpu::exec_arm(const uint32_t insn) { - Condition cond = static_cast(get_bit_range(insn, 28, 31)); - std::string disassembled; - - auto pc_error = [](uint8_t r, stringv syn) { - if (r == 15) - log_error("Using PC (R15) as operand in {}", syn); - }; - - auto pc_undefined = [](uint8_t r, stringv syn) { - if (r == 15) - log_warn("Using PC (R15) as operand in {}", syn); - }; +ArmInstruction::ArmInstruction(uint32_t insn) + : condition(static_cast(get_bit_range(insn, 28, 31))) { // Branch and exhcange if ((insn & 0x0FFFFFF0) == 0x012FFF10) { - static constexpr stringv syn = "BX"; - uint8_t rn = insn & 0b1111; - pc_undefined(rn, syn); + data = BranchAndExchange{ rn }; - disassembled = fmt::format("{}{} R{:d}", syn, cond, rn); - - if (cpsr.condition(cond)) { - State state = static_cast(rn & 1); - - // set state - cpsr.set_state(state); - - // copy to PC - pc = gpr[rn]; - - // ignore [1:0] bits for arm and 0 bit for thumb - rst_nth_bit(pc, 0); - if (state == State::Arm) - rst_nth_bit(pc, 1); - } // Branch } else if ((insn & 0x0E000000) == 0x0A000000) { - static constexpr stringv syn = "B"; - bool link = get_nth_bit(insn, 24); uint32_t offset = get_bit_range(insn, 0, 23); - disassembled = - fmt::format("{}{}{} {:#08X}", syn, (link ? "L" : ""), cond, offset); - - if (cpsr.condition(cond)) { - // lsh 2 and sign extend the 26 bit offset to 32 - // bits - offset <<= 2; - if (get_nth_bit(offset, 25)) - offset |= 0xFC000000; - - if (link) - gpr[14] = pc - ARM_INSTRUCTION_SIZE; - - pc += offset - ARM_INSTRUCTION_SIZE; - } + data = Branch{ .link = link, .offset = offset }; // Multiply } else if ((insn & 0x0FC000F0) == 0x00000090) { - static constexpr array syn = { "MUL", "MLA" }; - uint8_t rm = get_bit_range(insn, 0, 3); uint8_t rs = get_bit_range(insn, 8, 11); uint8_t rn = get_bit_range(insn, 12, 15); uint8_t rd = get_bit_range(insn, 16, 19); - bool s = get_nth_bit(insn, 20); - bool a = get_nth_bit(insn, 21); + bool set = get_nth_bit(insn, 20); + bool acc = get_nth_bit(insn, 21); - if (rd == rm) - log_error("rd and rm are not distinct in {} : {:d}", syn[a], rd); - - pc_error(rd, syn[a]); - pc_error(rm, syn[a]); - pc_error(rs, syn[a]); - - if (a) { - pc_error(rn, syn[a]); - disassembled = fmt::format("{}{}{} R{:d},R{:d},R{:d},R{:d}", - syn[a], - cond, - (s ? "S" : ""), - rd, - rm, - rs, - rn); - } else { - disassembled = fmt::format("{}{}{} R{:d},R{:d},R{:d}", - syn[a], - cond, - (s ? "S" : ""), - rd, - rm, - rs); - } - - if (cpsr.condition(cond)) { - gpr[rd] = gpr[rm] * gpr[rs] + (a ? gpr[rn] : 0); - - if (s) { - cpsr.set_z(!static_cast(gpr[rd])); - cpsr.set_n(get_nth_bit(gpr[rd], 31)); - cpsr.set_c(0); - } - } - // Multiply long - } else if ((insn & 0x0F8000F0) == 0x00800090) { - static constexpr array, 2> syn = { - { { "SMULL", "SMLAL" }, { "UMULL", "UMLAL" } } + data = Multiply{ + .rm = rm, .rs = rs, .rn = rn, .rd = rd, .set = set, .acc = acc }; + // Multiply long + } else if ((insn & 0x0F8000F0) == 0x00800090) { uint8_t rm = get_bit_range(insn, 0, 3); uint8_t rs = get_bit_range(insn, 8, 11); uint8_t rdlo = get_bit_range(insn, 12, 15); uint8_t rdhi = get_bit_range(insn, 16, 19); - bool s = get_nth_bit(insn, 20); - bool a = get_nth_bit(insn, 21); - bool u = get_nth_bit(insn, 22); + bool set = get_nth_bit(insn, 20); + bool acc = get_nth_bit(insn, 21); + bool uns = get_nth_bit(insn, 22); - if (rdhi == rdlo || rdhi == rm || rdlo == rm) - log_error("rdhi, rdlo and rm are not distinct in {}", syn[u][a]); + data = MultiplyLong{ .rm = rm, + .rs = rs, + .rdlo = rdlo, + .rdhi = rdhi, + .set = set, + .acc = acc, + .uns = uns }; - pc_error(rdhi, syn[u][a]); - pc_error(rdlo, syn[u][a]); - pc_error(rm, syn[u][a]); - pc_error(rs, syn[u][a]); - - disassembled = fmt::format("{}{}{} R{:d},R{:d},R{:d},R{:d}", - syn[u][a], - cond, - (s ? "S" : ""), - rdlo, - rdhi, - rm, - rs); - - if (cpsr.condition(cond)) { - if (u) { - uint64_t eval = static_cast(gpr[rm]) * - static_cast(gpr[rs]) + - (a ? static_cast(gpr[rdhi]) << 32 | - static_cast(gpr[rdlo]) - : 0); - - gpr[rdlo] = get_bit_range(eval, 0, 31); - gpr[rdhi] = get_bit_range(eval, 32, 63); - - } else { - int64_t eval = static_cast(gpr[rm]) * - static_cast(gpr[rs]) + - (a ? static_cast(gpr[rdhi]) << 32 | - static_cast(gpr[rdlo]) - : 0); - - gpr[rdlo] = get_bit_range(eval, 0, 31); - gpr[rdhi] = get_bit_range(eval, 32, 63); - } - - if (s) { - cpsr.set_z(!(static_cast(gpr[rdhi]) || - static_cast(gpr[rdlo]))); - cpsr.set_n(get_nth_bit(gpr[rdhi], 31)); - cpsr.set_c(0); - cpsr.set_v(0); - } - } + // Undefined + } else if ((insn & 0x0E000010) == 0x06000010) { + data = Undefined{}; // Single data swap } else if ((insn & 0x0FB00FF0) == 0x01000090) { - static constexpr stringv syn = "SWP"; - uint8_t rm = get_bit_range(insn, 0, 3); uint8_t rd = get_bit_range(insn, 12, 15); uint8_t rn = get_bit_range(insn, 16, 19); - bool b = get_nth_bit(insn, 22); + bool byte = get_nth_bit(insn, 22); - pc_undefined(rm, syn); - pc_undefined(rn, syn); - pc_undefined(rd, syn); + data = SingleDataSwap{ .rm = rm, .rd = rd, .rn = rn, .byte = byte }; - disassembled = fmt::format( - "{}{}{} R{:d},R{:d},[R{:d}]", syn, cond, (b ? "B" : ""), rd, rm, rn); + // Single data transfer + } else if ((insn & 0x0C000000) == 0x04000000) { - if (cpsr.condition(cond)) { - if (b) { - gpr[rd] = bus->read_byte(gpr[rn]); - bus->write_byte(gpr[rn], gpr[rm] & 0xFF); - } else { - gpr[rd] = bus->read_word(gpr[rn]); - bus->write_word(gpr[rn], gpr[rm]); - } + std::variant offset; + uint8_t rd = get_bit_range(insn, 12, 15); + uint8_t rn = get_bit_range(insn, 16, 19); + bool load = get_nth_bit(insn, 20); + bool write = get_nth_bit(insn, 21); + bool byte = get_nth_bit(insn, 22); + bool up = get_nth_bit(insn, 23); + bool pre = get_nth_bit(insn, 24); + bool imm = get_nth_bit(insn, 25); + + if (imm) { + uint8_t rm = get_bit_range(insn, 0, 3); + bool reg = get_nth_bit(insn, 4); + ShiftType shift_type = + static_cast(get_bit_range(insn, 5, 6)); + uint8_t operand = get_bit_range(insn, (reg ? 8 : 7), 11); + + offset = Shift{ .rm = rm, + .data = ShiftData{ .type = shift_type, + .immediate = !reg, + .operand = operand } }; + } else { + offset = static_cast(get_bit_range(insn, 0, 11)); } + data = SingleDataTransfer{ .offset = offset, + .rd = rd, + .rn = rn, + .load = load, + .write = write, + .byte = byte, + .up = up, + .pre = pre }; + // Halfword transfer - // TODO: create abstraction to reuse for block data and single data - // transfer } else if ((insn & 0x0E000090) == 0x00000090) { - static constexpr array syn_ = { "STR", "LDR" }; + uint8_t offset = get_bit_range(insn, 0, 3); + bool half = get_nth_bit(insn, 5); + bool sign = get_nth_bit(insn, 6); + uint8_t rd = get_bit_range(insn, 12, 15); + uint8_t rn = get_bit_range(insn, 16, 19); + bool load = get_nth_bit(insn, 20); + bool write = get_nth_bit(insn, 21); + bool imm = get_nth_bit(insn, 22); + bool up = get_nth_bit(insn, 23); + bool pre = get_nth_bit(insn, 24); - uint8_t rm = get_bit_range(insn, 0, 3); - uint8_t h = get_nth_bit(insn, 5); - uint8_t s = get_nth_bit(insn, 6); - uint8_t rd = get_bit_range(insn, 12, 15); - uint8_t rn = get_bit_range(insn, 16, 19); - bool l = get_nth_bit(insn, 20); - bool w = get_nth_bit(insn, 21); - bool imm = get_nth_bit(insn, 22); - bool u = get_nth_bit(insn, 23); - bool p = get_nth_bit(insn, 24); - uint32_t offset = 0; + offset |= (imm ? get_bit_range(insn, 8, 11) << 2 : 0); - std::string syn = - fmt::format("{}{}{}", syn_[l], (s ? "S" : ""), (h ? 'H' : 'B')); + data = HalfwordTransfer{ .offset = offset, + .half = half, + .sign = sign, + .rd = rd, + .rn = rn, + .load = load, + .write = write, + .imm = imm, + .up = up, + .pre = pre }; - if (!p && w) - log_error("Write-back enabled with post-indexing in {}", syn); + // Block data transfer + } else if ((insn & 0x0E000000) == 0x08000000) { + /*static constexpr array syn = { "STM", "LDM" }; - if (s && !l) - log_error("Signed data found in {}", syn); - - if (w) - pc_error(rn, syn); - pc_error(rm, syn); + uint16_t regs = get_bit_range(insn, 0, 15); + uint8_t rn = get_bit_range(insn, 16, 19); + bool load = get_nth_bit(insn, 20); + bool write = get_nth_bit(insn, 21); + bool s = get_nth_bit(insn, 22); + bool up = get_nth_bit(insn, 23); + bool pre = get_nth_bit(insn, 24); + // disassembly { - offset = (imm ? get_bit_range(insn, 8, 11) << 4 | rm : gpr[rm]); - std::string offset_str = fmt::format("{}{}{:d}", - (u ? "" : "-"), - (imm ? '#' : 'R'), - (imm ? offset : rm)); + uint8_t lpu = load << 2 | pre << 1 | up; + std::string addr_mode; - disassembled = fmt::format( - "{}{}{}{} R{:d},{}", - syn_[l], - cond, - (s ? "S" : ""), - (h ? 'H' : 'B'), - rd, - (!offset ? fmt::format("[R{:d}]", rn) - : p - ? fmt::format(",[R{:d},{}]{}", rn, offset_str, (w ? "!" : "")) - : fmt::format(",[R{:d}],{}", rn, offset_str))); - } - - if (cpsr.condition(cond)) { - uint32_t address = gpr[rn]; - - if (p) - address += (u ? offset : -offset); - - // load - if (l) { - // signed - if (s) { - // halfword - if (h) { - gpr[rd] = bus->read_halfword(address); - // sign extend the halfword - if (get_nth_bit(gpr[rd], 15)) - gpr[rd] |= 0xFFFF0000; - // byte - } else { - // sign extend the byte - gpr[rd] = bus->read_byte(address); - if (get_nth_bit(gpr[rd], 7)) - gpr[rd] |= 0xFFFFFF00; - } - // unsigned halfword - } else if (h) { - gpr[rd] = bus->read_halfword(address); - } - // store - } else { - // halfword - if (h) { - // take PC into consideration - if (rd == 15) - address += 12; - bus->write_halfword(address, gpr[rd]); - } + switch(lpu) { } + }*/ - if (!p) - address += (u ? offset : -offset); - - if (!p || w) - gpr[rn] = address; - } + data = Undefined{}; // Software Interrupt // What to do here? } else if ((insn & 0x0F000000) == 0x0F000000) { - static constexpr stringv syn = "SWI"; - if (cpsr.condition(cond)) { - chg_mode(Mode::Supervisor); - pc = 0x08; - spsr = cpsr; - } - - disassembled = fmt::format("{}{} 0", syn, cond); + data = SoftwareInterrupt{}; // Coprocessor data transfer } else if ((insn & 0x0E000000) == 0x0C000000) { - static constexpr array syn = { "STC", "LDC" }; + uint8_t offset = get_bit_range(insn, 0, 7); + uint8_t cpn = get_bit_range(insn, 8, 11); + uint8_t crd = get_bit_range(insn, 12, 15); + uint8_t rn = get_bit_range(insn, 16, 19); + bool load = get_nth_bit(insn, 20); + bool write = get_nth_bit(insn, 21); + bool len = get_nth_bit(insn, 22); + bool up = get_nth_bit(insn, 23); + bool pre = get_nth_bit(insn, 24); - [[maybe_unused]] uint8_t offset = get_bit_range(insn, 0, 7); - uint8_t cpn = get_bit_range(insn, 8, 11); - uint8_t crd = get_bit_range(insn, 12, 15); - [[maybe_unused]] uint8_t rn = get_bit_range(insn, 16, 19); - bool l = get_nth_bit(insn, 20); - [[maybe_unused]] bool w = get_nth_bit(insn, 21); - bool n = get_nth_bit(insn, 22); - [[maybe_unused]] bool u = get_nth_bit(insn, 23); - [[maybe_unused]] bool p = get_nth_bit(insn, 24); - - disassembled = fmt::format( - "{}{}{} p{},c{},{}", syn[l], cond, (n ? "L" : ""), cpn, crd, "a."); - - log_error("Unimplemented instruction: {}", syn[l]); + data = CoprocessorDataTransfer{ .offset = offset, + .cpn = cpn, + .crd = crd, + .rn = rn, + .load = load, + .write = write, + .len = len, + .up = up, + .pre = pre }; // Coprocessor data operation } else if ((insn & 0x0F000010) == 0x0E000000) { - static constexpr stringv syn = "CDP"; - uint8_t crm = get_bit_range(insn, 0, 4); uint8_t cp = get_bit_range(insn, 5, 7); uint8_t cpn = get_bit_range(insn, 8, 11); @@ -344,42 +183,177 @@ Cpu::exec_arm(const uint32_t insn) { uint8_t crn = get_bit_range(insn, 16, 19); uint8_t cp_opc = get_bit_range(insn, 20, 23); - disassembled = fmt::format("{}{} p{},{},c{},c{},c{},{}", - syn, - cond, - cpn, - cp_opc, - crd, - crn, - crm, - cp); - - log_error("Unimplemented instruction: {}", syn); + data = CoprocessorDataOperation{ .crm = crm, + .cp = cp, + .cpn = cpn, + .crd = crd, + .crn = crn, + .cp_opc = cp_opc }; // Coprocessor register transfer } else if ((insn & 0x0F000010) == 0x0E000010) { - static constexpr array syn = { "MCR", "MRC" }; - uint8_t crm = get_bit_range(insn, 0, 4); uint8_t cp = get_bit_range(insn, 5, 7); uint8_t cpn = get_bit_range(insn, 8, 11); uint8_t rd = get_bit_range(insn, 12, 15); uint8_t crn = get_bit_range(insn, 16, 19); - bool l = get_nth_bit(insn, 20); + bool load = get_nth_bit(insn, 20); uint8_t cp_opc = get_bit_range(insn, 21, 23); - disassembled = fmt::format("{}{} p{},{},c{},c{},c{},{}", - syn[l], - cond, - cpn, - cp_opc, - rd, - crn, - crm, - cp); - - log_error("Unimplemented instruction: {}", syn[l]); + data = CoprocessorRegisterTransfer{ .crm = crm, + .cp = cp, + .cpn = cpn, + .rd = rd, + .crn = crn, + .load = load, + .cp_opc = cp_opc }; + } else { + data = Undefined{}; } - - return disassembled; +} + +std::string +ArmInstruction::disassemble() { + static const std::string undefined = "UNDEFINED"; + // goddamn this is gore + // TODO: make this less ugly + return std::visit( + overloaded{ + [this](BranchAndExchange& data) { + return fmt::format("BX{} R{:d}", condition, data.rn); + }, + [this](Branch& data) { + return fmt::format( + "B{}{} {:#08X}", (data.link ? "L" : ""), condition, data.offset); + }, + [this](Multiply& data) { + if (data.acc) { + return fmt::format("MLA{}{} R{:d},R{:d},R{:d},R{:d}", + condition, + (data.set ? "S" : ""), + data.rd, + data.rm, + data.rs, + data.rn); + } else { + return fmt::format("MUL{}{} R{:d},R{:d},R{:d}", + condition, + (data.set ? "S" : ""), + data.rd, + data.rm, + data.rs); + } + }, + [this](MultiplyLong& data) { + return fmt::format("{}{}{}{} R{:d},R{:d},R{:d},R{:d}", + (data.uns ? 'U' : 'S'), + (data.acc ? "MLAL" : "MULL"), + condition, + (data.set ? "S" : ""), + data.rdlo, + data.rdhi, + data.rm, + data.rs); + }, + [](Undefined) { return undefined; }, + [this](SingleDataSwap& data) { + return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]", + condition, + (data.byte ? "B" : ""), + data.rd, + data.rm, + data.rn); + }, + [this](SingleDataTransfer& data) { + std::string expression; + std::string address; + + if (const uint16_t* offset = std::get_if(&data.offset)) { + if (*offset == 0) { + expression = ""; + } else { + expression = fmt::format(",#{:d}", *offset); + } + } else if (const Shift* shift = std::get_if(&data.offset)) { + expression = fmt::format(",{}R{:d},{} {}{:d}", + (data.up ? '+' : '-'), + shift->rm, + shift->data.type, + (shift->data.immediate ? '#' : 'R'), + shift->data.operand); + } + + return fmt::format( + "{}{}{}{} R{:d},[R{:d}{}]{}", + (data.load ? "LDR" : "STR"), + condition, + (data.byte ? "B" : ""), + (!data.pre && data.write ? "T" : ""), + data.rd, + data.rn, + (data.pre ? expression : ""), + (data.pre ? (data.write ? "!" : "") : expression)); + }, + [this](HalfwordTransfer& data) { + std::string expression; + + if (data.imm) { + if (data.offset == 0) { + expression = ""; + } else { + expression = fmt::format(",#{:d}", data.offset); + } + } else { + expression = + fmt::format(",{}R{:d}", (data.up ? '+' : '-'), data.offset); + } + + return fmt::format( + "{}{}{}{} R{:d},[R{:d}{}]{}", + (data.load ? "LDR" : "STR"), + condition, + (data.sign ? "S" : ""), + (data.half ? 'H' : 'B'), + data.rd, + data.rn, + (data.pre ? expression : ""), + (data.pre ? (data.write ? "!" : "") : expression)); + }, + [this](SoftwareInterrupt) { return fmt::format("SWI{}", condition); }, + [this](CoprocessorDataTransfer& data) { + std::string expression = fmt::format(",#{:d}", data.offset); + return fmt::format( + "{}{}{} p{:d},c{:d},[R{:d}{}]{}", + (data.load ? "LDC" : "STC"), + condition, + (data.len ? "L" : ""), + data.cpn, + data.crd, + data.rn, + (data.pre ? expression : ""), + (data.pre ? (data.write ? "!" : "") : expression)); + }, + [this](CoprocessorDataOperation& data) { + return fmt::format("CDP{} p{},{},c{},c{},c{},{}", + condition, + data.cpn, + data.cp_opc, + data.crd, + data.crn, + data.crm, + data.cp); + }, + [this](CoprocessorRegisterTransfer& data) { + return fmt::format("{}{} p{},{},c{},c{},c{},{}", + (data.load ? "MRC" : "MCR"), + condition, + data.cpn, + data.cp_opc, + data.rd, + data.crn, + data.crm, + data.cp); + }, + [](auto) { return undefined; } }, + data); } diff --git a/src/cpu/instruction.hh b/src/cpu/instruction.hh new file mode 100644 index 0000000..48c6838 --- /dev/null +++ b/src/cpu/instruction.hh @@ -0,0 +1,132 @@ +#include "cpu/utility.hh" +#include +#include + +template +struct overloaded : Ts... { + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; + +class ArmInstruction { + public: + ArmInstruction() = delete; + ArmInstruction(uint32_t insn); + + auto get_condition() const { return condition; } + auto get_data() const { return data; } + + std::string disassemble(); + + struct BranchAndExchange { + uint8_t rn; + }; + + struct Branch { + bool link; + uint32_t offset; + }; + + struct Multiply { + uint8_t rm; + uint8_t rs; + uint8_t rn; + uint8_t rd; + bool set; + bool acc; + }; + + struct MultiplyLong { + uint8_t rm; + uint8_t rs; + uint8_t rdlo; + uint8_t rdhi; + bool set; + bool acc; + bool uns; + }; + + struct SingleDataSwap { + uint8_t rm; + uint8_t rd; + uint8_t rn; + bool byte; + }; + + struct SingleDataTransfer { + std::variant offset; + uint8_t rd; + uint8_t rn; + bool load; + bool write; + bool byte; + bool up; + bool pre; + }; + + struct HalfwordTransfer { + uint8_t offset; + bool half; + bool sign; + uint8_t rd; + uint8_t rn; + bool load; + bool write; + bool byte; + bool imm; + bool up; + bool pre; + }; + + struct CoprocessorDataTransfer { + uint8_t offset; + uint8_t cpn; + uint8_t crd; + uint8_t rn; + bool load; + bool write; + bool len; + bool up; + bool pre; + }; + + struct CoprocessorDataOperation { + uint8_t crm; + uint8_t cp; + uint8_t cpn; + uint8_t crd; + uint8_t crn; + uint8_t cp_opc; + }; + + struct CoprocessorRegisterTransfer { + uint8_t crm; + uint8_t cp; + uint8_t cpn; + uint8_t rd; + uint8_t crn; + bool load; + uint8_t cp_opc; + }; + + struct Undefined {}; + struct SoftwareInterrupt {}; + + using InstructionData = std::variant; + + private: + Condition condition; + InstructionData data; +}; diff --git a/src/cpu/utility.cc b/src/cpu/utility.cc index 9c17af9..33e3f8d 100644 --- a/src/cpu/utility.cc +++ b/src/cpu/utility.cc @@ -1,4 +1,6 @@ #include "utility.hh" +#include "util/bits.hh" +#include std::ostream& operator<<(std::ostream& os, const Condition cond) { @@ -33,6 +35,47 @@ operator<<(std::ostream& os, const Condition cond) { return os; } +uint32_t +eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) { + switch (shift_type) { + case ShiftType::LSL: + + if (amount > 0 && amount <= 32) + carry = get_nth_bit(value, 32 - amount); + else if (amount > 32) + carry = 0; + + return value << amount; + case ShiftType::LSR: + + if (amount > 0 && amount <= 32) + carry = get_nth_bit(value, amount - 1); + else if (amount > 32) + carry = 0; + else + carry = get_nth_bit(value, 31); + + return value >> amount; + case ShiftType::ASR: + if (amount > 0 && amount <= 32) + carry = get_nth_bit(value, amount - 1); + else + carry = get_nth_bit(value, 31); + + return static_cast(value) >> amount; + case ShiftType::ROR: + if (amount == 0) { + bool old_carry = carry; + + carry = get_nth_bit(value, 0); + return (value >> 1) | (old_carry << 31); + } else { + carry = get_nth_bit(value, (amount % 32 + 31) % 32); + return std::rotr(value, amount); + } + } +} + std::ostream& operator<<(std::ostream& os, const ShiftType shift_type) { diff --git a/src/cpu/utility.hh b/src/cpu/utility.hh index dcee945..6df4874 100644 --- a/src/cpu/utility.hh +++ b/src/cpu/utility.hh @@ -72,6 +72,20 @@ enum class ShiftType { ROR = 0b11 }; +struct ShiftData { + ShiftType type; + bool immediate; + uint8_t operand; +}; + +struct Shift { + uint8_t rm; + ShiftData data; +}; + +uint32_t +eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry); + // https://fmt.dev/dev/api.html#std-ostream-support std::ostream& operator<<(std::ostream& os, const ShiftType cond); diff --git a/src/header.hh b/src/header.hh index 4e454d3..81ef421 100644 --- a/src/header.hh +++ b/src/header.hh @@ -4,7 +4,7 @@ #include #include -typedef struct { +struct Header { enum class UniqueCode { Old, // old games New, // new games @@ -42,4 +42,4 @@ typedef struct { BootMode multiboot; uint32_t multiboot_entrypoint; uint8_t slave_id; -} Header; +};