[UNTESTED] complete initial disassembler structure for ARM

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-09-15 05:23:07 +05:30
parent 169723275e
commit 7fc6876264
9 changed files with 701 additions and 149 deletions

View File

@@ -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;
}
}
}