thumb: add execution of instructions
also arm: fix some instructions Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -212,18 +212,19 @@ using InstructionData = std::variant<BranchAndExchange,
|
||||
SoftwareInterrupt>;
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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 <algorithm>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 <cstdint>
|
||||
|
||||
@@ -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<uint32_t, GPR_COUNT - GPR_FIQ_FIRST - 1> fiq;
|
||||
std::array<uint32_t, GPR_COUNT - GPR_SVC_FIRST - 1> svc;
|
||||
@@ -57,5 +64,7 @@ class CpuImpl {
|
||||
Psr irq;
|
||||
Psr und;
|
||||
} spsr_banked; // banked saved program status registers
|
||||
|
||||
bool is_flushed;
|
||||
};
|
||||
}
|
||||
|
@@ -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<int32_t>(data.offset + pc + 2 * INSTRUCTION_SIZE));
|
||||
},
|
||||
[pc](UnconditionalBranch& data) {
|
||||
return fmt::format(
|
||||
"B #{:d}",
|
||||
static_cast<int32_t>(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);
|
||||
|
384
src/cpu/thumb/exec.cc
Normal file
384
src/cpu/thumb/exec.cc
Normal file
@@ -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<uint32_t>(static_cast<int8_t>(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<State>(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<int32_t>(cpu.bus->read_byte(address))
|
||||
<< 24) >>
|
||||
24;
|
||||
break;
|
||||
case 0b11:
|
||||
// sign extend the halfword
|
||||
cpu.gpr[data.rd] =
|
||||
(static_cast<int32_t>(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);
|
||||
}
|
||||
}
|
@@ -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<HiRegisterOperations::OpCode>(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<uint16_t>(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<int16_t>(bit_range(insn, 0, 6));
|
||||
bool sign = get_bit(insn, 7);
|
||||
|
||||
data = AddOffsetStackPointer{ .word = word, .sign = sign };
|
||||
word <<= 2;
|
||||
word = static_cast<int16_t>(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<Condition>(bit_range(insn, 8, 11));
|
||||
|
||||
data = ConditionalBranch{ .offset = static_cast<uint16_t>(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<uint16_t>(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<uint16_t>(offset << 1),
|
||||
.high = high };
|
||||
offset <<= 1;
|
||||
|
||||
data = LongBranchWithLink{ .offset = offset, .high = high };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,10 @@
|
||||
#include <fmt/ostream.h>
|
||||
#include <variant>
|
||||
|
||||
namespace matar::thumb {
|
||||
namespace matar {
|
||||
class CpuImpl;
|
||||
|
||||
namespace thumb {
|
||||
|
||||
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||
template<class... Ts>
|
||||
@@ -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<MoveShiftedRegister,
|
||||
LongBranchWithLink>;
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
lib_sources += files(
|
||||
'instruction.cc'
|
||||
'instruction.cc',
|
||||
'exec.cc'
|
||||
)
|
||||
|
||||
if get_option('disassembler')
|
||||
|
Reference in New Issue
Block a user