[UNTESTED] complete initial disassembler structure for ARM
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
397
src/cpu/cpu.cc
397
src/cpu/cpu.cc
@@ -1,4 +1,5 @@
|
||||
#include "cpu/cpu.hh"
|
||||
#include "cpu/utility.hh"
|
||||
#include "util/bits.hh"
|
||||
#include "util/log.hh"
|
||||
#include <algorithm>
|
||||
@@ -11,15 +12,17 @@ Cpu::Cpu(Bus& bus)
|
||||
, gpr({ 0 })
|
||||
, cpsr(0)
|
||||
, spsr(0)
|
||||
, is_flushed(false)
|
||||
, gpr_banked({ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } })
|
||||
, spsr_banked({ 0, 0, 0, 0, 0 }) {
|
||||
cpsr.set_mode(Mode::System);
|
||||
cpsr.set_mode(Mode::Supervisor);
|
||||
cpsr.set_irq_disabled(true);
|
||||
cpsr.set_fiq_disabled(true);
|
||||
cpsr.set_state(State::Arm);
|
||||
log_info("CPU successfully initialised");
|
||||
|
||||
// PC is always two instructions ahead in the pipeline
|
||||
// PC always points to two instructions ahead
|
||||
// PC - 2 is the instruction being executed
|
||||
pc += 2 * ARM_INSTRUCTION_SIZE;
|
||||
}
|
||||
|
||||
@@ -35,7 +38,7 @@ Cpu::chg_mode(const Mode to) {
|
||||
* concatenate views */
|
||||
#define STORE_BANKED(mode, MODE) \
|
||||
std::copy(gpr.begin() + GPR_##MODE##_FIRST, \
|
||||
gpr.begin() + GPR_COUNT - 1, \
|
||||
gpr.begin() + gpr.size() - 1, \
|
||||
gpr_banked.mode.begin())
|
||||
|
||||
switch (from) {
|
||||
@@ -145,23 +148,25 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
pc = gpr[data.rn];
|
||||
|
||||
// ignore [1:0] bits for arm and 0 bit for thumb
|
||||
rst_nth_bit(pc, 0);
|
||||
rst_bit(pc, 0);
|
||||
|
||||
if (state == State::Arm)
|
||||
rst_nth_bit(pc, 1);
|
||||
rst_bit(pc, 1);
|
||||
|
||||
// pc is affected so flush the pipeline
|
||||
is_flushed = true;
|
||||
},
|
||||
[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;
|
||||
// data.offset accounts for two instructions ahead when
|
||||
// disassembling, so need to adjust
|
||||
pc =
|
||||
static_cast<int32_t>(pc) - 2 * ARM_INSTRUCTION_SIZE + data.offset;
|
||||
|
||||
// pc is affected so flush the pipeline
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_error](ArmInstruction::Multiply& data) {
|
||||
if (data.rd == data.rm)
|
||||
@@ -176,8 +181,8 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
gpr[data.rm] * gpr[data.rs] + (data.acc ? gpr[data.rn] : 0);
|
||||
|
||||
if (data.set) {
|
||||
cpsr.set_z(!static_cast<bool>(gpr[data.rd]));
|
||||
cpsr.set_n(get_nth_bit(gpr[data.rd], 31));
|
||||
cpsr.set_z(gpr[data.rd] == 0);
|
||||
cpsr.set_n(get_bit(gpr[data.rd], 31));
|
||||
cpsr.set_c(0);
|
||||
}
|
||||
},
|
||||
@@ -199,8 +204,8 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
static_cast<uint64_t>(gpr[data.rdlo])
|
||||
: 0);
|
||||
|
||||
gpr[data.rdlo] = get_bit_range(eval, 0, 31);
|
||||
gpr[data.rdhi] = get_bit_range(eval, 32, 63);
|
||||
gpr[data.rdlo] = bit_range(eval, 0, 31);
|
||||
gpr[data.rdhi] = bit_range(eval, 32, 63);
|
||||
|
||||
} else {
|
||||
int64_t eval =
|
||||
@@ -210,23 +215,22 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
static_cast<int64_t>(gpr[data.rdlo])
|
||||
: 0);
|
||||
|
||||
gpr[data.rdlo] = get_bit_range(eval, 0, 31);
|
||||
gpr[data.rdhi] = get_bit_range(eval, 32, 63);
|
||||
gpr[data.rdlo] = bit_range(eval, 0, 31);
|
||||
gpr[data.rdhi] = bit_range(eval, 32, 63);
|
||||
}
|
||||
|
||||
if (data.set) {
|
||||
cpsr.set_z(!(static_cast<bool>(gpr[data.rdhi]) ||
|
||||
static_cast<bool>(gpr[data.rdlo])));
|
||||
cpsr.set_n(get_nth_bit(gpr[data.rdhi], 31));
|
||||
cpsr.set_z(gpr[data.rdhi] == 0 && gpr[data.rdlo] == 0);
|
||||
cpsr.set_n(get_bit(gpr[data.rdhi], 31));
|
||||
cpsr.set_c(0);
|
||||
cpsr.set_v(0);
|
||||
}
|
||||
},
|
||||
[](ArmInstruction::Undefined) { log_warn("Undefined instruction"); },
|
||||
[this, pc_warn](ArmInstruction::SingleDataSwap& data) {
|
||||
pc_warn(data.rm);
|
||||
pc_warn(data.rn);
|
||||
pc_warn(data.rd);
|
||||
[this, pc_error](ArmInstruction::SingleDataSwap& data) {
|
||||
pc_error(data.rm);
|
||||
pc_error(data.rn);
|
||||
pc_error(data.rd);
|
||||
|
||||
if (data.byte) {
|
||||
gpr[data.rd] = bus->read_byte(gpr[data.rn]);
|
||||
@@ -244,6 +248,10 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
log_warn("Write-back enabled with post-indexing in {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.rn == PC_INDEX && data.write)
|
||||
log_warn("Write-back enabled with base register as PC {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.write)
|
||||
pc_warn(data.rn);
|
||||
|
||||
@@ -262,18 +270,21 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
pc_error(shift->data.operand);
|
||||
pc_error(shift->rm);
|
||||
|
||||
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
|
||||
offset =
|
||||
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
|
||||
|
||||
cpsr.set_c(carry);
|
||||
}
|
||||
|
||||
// PC is always two instructions ahead
|
||||
if (data.rn == 15)
|
||||
if (data.rn == PC_INDEX)
|
||||
address -= 2 * ARM_INSTRUCTION_SIZE;
|
||||
|
||||
if (data.pre)
|
||||
address += (data.up ? offset : -offset);
|
||||
|
||||
debug(address);
|
||||
|
||||
// load
|
||||
if (data.load) {
|
||||
// byte
|
||||
@@ -285,7 +296,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
// store
|
||||
} else {
|
||||
// take PC into consideration
|
||||
if (data.rd == 15)
|
||||
if (data.rd == PC_INDEX)
|
||||
address += ARM_INSTRUCTION_SIZE;
|
||||
|
||||
// byte
|
||||
@@ -301,6 +312,9 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
|
||||
if (!data.pre || data.write)
|
||||
gpr[data.rn] = address;
|
||||
|
||||
if (data.rd == PC_INDEX && data.load)
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_warn, pc_error](ArmInstruction::HalfwordTransfer& data) {
|
||||
uint32_t address = gpr[data.rn];
|
||||
@@ -331,14 +345,14 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
gpr[data.rd] = bus->read_halfword(address);
|
||||
|
||||
// sign extend the halfword
|
||||
if (get_nth_bit(gpr[data.rd], 15))
|
||||
if (get_bit(gpr[data.rd], PC_INDEX))
|
||||
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))
|
||||
if (get_bit(gpr[data.rd], 7))
|
||||
gpr[data.rd] |= 0xFFFFFF00;
|
||||
}
|
||||
// unsigned halfword
|
||||
@@ -348,7 +362,7 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
// store
|
||||
} else {
|
||||
// take PC into consideration
|
||||
if (data.rd == 15)
|
||||
if (data.rd == PC_INDEX)
|
||||
address += ARM_INSTRUCTION_SIZE;
|
||||
|
||||
// halfword
|
||||
@@ -361,28 +375,337 @@ Cpu::exec_arm(const ArmInstruction instruction) {
|
||||
|
||||
if (!data.pre || data.write)
|
||||
gpr[data.rn] = address;
|
||||
|
||||
if (data.rd == PC_INDEX && data.load)
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_error](ArmInstruction::BlockDataTransfer& data) {
|
||||
uint32_t address = gpr[data.rn];
|
||||
Mode mode = cpsr.mode();
|
||||
uint8_t alignment = 4; // word
|
||||
uint8_t i = 0;
|
||||
uint8_t n_regs = std::popcount(data.regs);
|
||||
|
||||
pc_error(data.rn);
|
||||
|
||||
if (cpsr.mode() == Mode::User && data.s) {
|
||||
log_error("Bit S is set outside priviliged modes in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
|
||||
// we just change modes to load user registers
|
||||
if ((!get_bit(data.regs, PC_INDEX) && data.s) ||
|
||||
(!data.load && data.s)) {
|
||||
chg_mode(Mode::User);
|
||||
|
||||
if (data.write) {
|
||||
log_error("Write-back enable for user bank registers in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
}
|
||||
|
||||
// account for decrement
|
||||
if (!data.up)
|
||||
address -= (n_regs - 1) * alignment;
|
||||
|
||||
if (data.pre)
|
||||
address += (data.up ? alignment : -alignment);
|
||||
|
||||
if (data.load) {
|
||||
if (get_bit(data.regs, PC_INDEX) && data.s && data.load) {
|
||||
// current mode's spsr is already loaded when it was
|
||||
// switched
|
||||
spsr = cpsr;
|
||||
}
|
||||
|
||||
for (i = 0; i < GPR_COUNT; i++) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
gpr[i] = bus->read_word(address);
|
||||
address += alignment;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < GPR_COUNT; i++) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
bus->write_word(address, gpr[i]);
|
||||
address += alignment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.pre)
|
||||
address += (data.up ? alignment : -alignment);
|
||||
|
||||
// reset back to original address + offset if incremented earlier
|
||||
if (data.up)
|
||||
address -= n_regs * alignment;
|
||||
|
||||
if (!data.pre || data.write)
|
||||
gpr[data.rn] = address;
|
||||
|
||||
if (data.load && get_bit(data.regs, PC_INDEX))
|
||||
is_flushed = true;
|
||||
|
||||
// load back the original mode registers
|
||||
chg_mode(mode);
|
||||
},
|
||||
[this, pc_error](ArmInstruction::PsrTransfer& data) {
|
||||
if (data.spsr && cpsr.mode() == Mode::User) {
|
||||
log_error("Accessing SPSR in User mode in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
|
||||
Psr& psr = data.spsr ? spsr : cpsr;
|
||||
|
||||
switch (data.type) {
|
||||
case ArmInstruction::PsrTransfer::Type::Mrs:
|
||||
pc_error(data.operand);
|
||||
gpr[data.operand] = psr.raw();
|
||||
break;
|
||||
case ArmInstruction::PsrTransfer::Type::Msr:
|
||||
pc_error(data.operand);
|
||||
|
||||
if (cpsr.mode() != Mode::User) {
|
||||
psr.set_all(gpr[data.operand]);
|
||||
break;
|
||||
}
|
||||
case ArmInstruction::PsrTransfer::Type::Msr_flg:
|
||||
psr.set_n(get_bit(data.operand, 31));
|
||||
psr.set_z(get_bit(data.operand, 30));
|
||||
psr.set_c(get_bit(data.operand, 29));
|
||||
psr.set_v(get_bit(data.operand, 28));
|
||||
break;
|
||||
}
|
||||
},
|
||||
[this, pc_error](ArmInstruction::DataProcessing& data) {
|
||||
uint32_t op_1 = gpr[data.rn];
|
||||
uint32_t op_2 = 0;
|
||||
|
||||
uint32_t result = 0;
|
||||
|
||||
bool overflow = cpsr.v();
|
||||
bool carry = cpsr.c();
|
||||
bool negative = cpsr.n();
|
||||
bool zero = cpsr.z();
|
||||
|
||||
if (const uint32_t* immediate =
|
||||
std::get_if<uint32_t>(&data.operand)) {
|
||||
op_2 = *immediate;
|
||||
} else if (const Shift* shift = std::get_if<Shift>(&data.operand)) {
|
||||
uint8_t amount =
|
||||
(shift->data.immediate ? shift->data.operand
|
||||
: gpr[shift->data.operand] & 0xFF);
|
||||
|
||||
bool carry = cpsr.c();
|
||||
|
||||
if (!shift->data.immediate)
|
||||
pc_error(shift->data.operand);
|
||||
pc_error(shift->rm);
|
||||
|
||||
op_2 =
|
||||
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
|
||||
|
||||
cpsr.set_c(carry);
|
||||
|
||||
// PC is 12 bytes ahead when shifting
|
||||
if (data.rn == PC_INDEX)
|
||||
op_1 += ARM_INSTRUCTION_SIZE;
|
||||
}
|
||||
|
||||
switch (data.opcode) {
|
||||
case OpCode::AND: {
|
||||
result = op_1 & op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::EOR: {
|
||||
result = op_1 ^ op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::SUB: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
result = op_1 - op_2;
|
||||
negative = get_bit(result, 31);
|
||||
carry = op_1 < op_2;
|
||||
overflow = s1 != s2 && s2 == negative;
|
||||
} break;
|
||||
case OpCode::RSB: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
result = op_2 - op_1;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = op_2 < op_1;
|
||||
overflow = s1 != s2 && s1 == negative;
|
||||
} break;
|
||||
case OpCode::ADD: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
// result_ is 33 bits
|
||||
uint64_t result_ = op_2 + op_1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 == s2 && s1 != negative;
|
||||
} break;
|
||||
case OpCode::ADC: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_2 + op_1 + carry;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 == s2 && s1 != negative;
|
||||
} break;
|
||||
case OpCode::SBC: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_1 - op_2 + carry - 1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 != s2 && s2 == negative;
|
||||
} break;
|
||||
case OpCode::RSC: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_1 - op_2 + carry - 1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 != s2 && s1 == negative;
|
||||
} break;
|
||||
case OpCode::TST: {
|
||||
result = op_1 & op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::TEQ: {
|
||||
result = op_1 ^ op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::CMP: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
result = op_1 - op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = op_1 < op_2;
|
||||
overflow = s1 != s2 && s2 == negative;
|
||||
} break;
|
||||
case OpCode::CMN: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_2 + op_1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 == s2 && s1 != negative;
|
||||
} break;
|
||||
case OpCode::ORR: {
|
||||
result = op_1 | op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::MOV: {
|
||||
result = op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::BIC: {
|
||||
result = op_1 & ~op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::MVN: {
|
||||
result = ~op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
}
|
||||
|
||||
zero = result == 0;
|
||||
|
||||
debug(carry);
|
||||
debug(overflow);
|
||||
debug(zero);
|
||||
debug(negative);
|
||||
|
||||
auto set_conditions = [=]() {
|
||||
cpsr.set_c(carry);
|
||||
cpsr.set_v(overflow);
|
||||
cpsr.set_n(negative);
|
||||
cpsr.set_z(zero);
|
||||
};
|
||||
|
||||
if (data.set) {
|
||||
if (data.rd == 15) {
|
||||
if (cpsr.mode() == Mode::User)
|
||||
log_error("Running {} in User mode",
|
||||
typeid(data).name());
|
||||
} else {
|
||||
set_conditions();
|
||||
}
|
||||
}
|
||||
|
||||
if (data.opcode == OpCode::TST || data.opcode == OpCode::TEQ ||
|
||||
data.opcode == OpCode::CMP || data.opcode == OpCode::CMN) {
|
||||
set_conditions();
|
||||
} else {
|
||||
gpr[data.rd] = result;
|
||||
if (data.rd == 15 || data.opcode == OpCode::MVN)
|
||||
is_flushed = true;
|
||||
}
|
||||
},
|
||||
[this](ArmInstruction::SoftwareInterrupt) {
|
||||
chg_mode(Mode::Supervisor);
|
||||
pc = 0x08;
|
||||
spsr = cpsr;
|
||||
},
|
||||
[](auto& data) { log_error("{} instruction", typeid(data).name()); } },
|
||||
[](auto& data) {
|
||||
log_error("Unimplemented {} instruction", typeid(data).name());
|
||||
} },
|
||||
data);
|
||||
}
|
||||
|
||||
void
|
||||
Cpu::step() {
|
||||
// Current instruction is two instructions behind PC
|
||||
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));
|
||||
debug(cur_pc);
|
||||
uint32_t x = bus->read_word(cur_pc);
|
||||
ArmInstruction instruction(x);
|
||||
log_info("{:#034b}", x);
|
||||
|
||||
exec_arm(instruction);
|
||||
|
||||
log_info("{:#010X} : {}", cur_pc, instruction.disassemble());
|
||||
log_info("0x{:08X} : {}", cur_pc, instruction.disassemble());
|
||||
|
||||
pc += ARM_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;
|
||||
is_flushed = false;
|
||||
} else {
|
||||
// if not flushed continue like normal
|
||||
pc += ARM_INSTRUCTION_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user