mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-17 10:06:21 +00:00
Merge pull request #1343 from TomHarte/ARM2Ops
Attempt an implementation of the ARM2 instruction set.
This commit is contained in:
commit
7b28b3d634
122
InstructionSets/ARM/BarrelShifter.hpp
Normal file
122
InstructionSets/ARM/BarrelShifter.hpp
Normal file
@ -0,0 +1,122 @@
|
||||
//
|
||||
// BarrelShifter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/02/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace InstructionSet::ARM {
|
||||
|
||||
enum class ShiftType {
|
||||
LogicalLeft = 0b00,
|
||||
LogicalRight = 0b01,
|
||||
ArithmeticRight = 0b10,
|
||||
RotateRight = 0b11,
|
||||
};
|
||||
|
||||
template <bool writeable> struct Carry;
|
||||
template <> struct Carry<true> {
|
||||
using type = uint32_t &;
|
||||
};
|
||||
template <> struct Carry<false> {
|
||||
using type = const uint32_t;
|
||||
};
|
||||
|
||||
/// Apply a rotation of @c type to @c source of @c amount; @c carry should be either @c 1 or @c 0
|
||||
/// at call to represent the current value of the carry flag. If @c set_carry is @c true then @c carry will
|
||||
/// receive the new value of the carry flag following the rotation — @c 0 for no carry, @c non-0 for carry.
|
||||
template <ShiftType type, bool set_carry>
|
||||
void shift(uint32_t &source, uint32_t amount, typename Carry<set_carry>::type carry) {
|
||||
switch(type) {
|
||||
case ShiftType::LogicalLeft:
|
||||
if(amount > 32) {
|
||||
if constexpr (set_carry) carry = 0;
|
||||
source = 0;
|
||||
} else if(amount == 32) {
|
||||
if constexpr (set_carry) carry = source & 1;
|
||||
source = 0;
|
||||
} else if(amount > 0) {
|
||||
if constexpr (set_carry) carry = source & (0x8000'0000 >> (amount - 1));
|
||||
source <<= amount;
|
||||
}
|
||||
break;
|
||||
|
||||
case ShiftType::LogicalRight:
|
||||
if(amount > 32) {
|
||||
if constexpr (set_carry) carry = 0;
|
||||
source = 0;
|
||||
} else if(amount == 32) {
|
||||
if constexpr (set_carry) carry = source & 0x8000'0000;
|
||||
source = 0;
|
||||
} else if(amount > 0) {
|
||||
if constexpr (set_carry) carry = source & (1 << (amount - 1));
|
||||
source >>= amount;
|
||||
} else {
|
||||
// A logical shift right by '0' is treated as a shift by 32;
|
||||
// assemblers are supposed to map LSR #0 to LSL #0.
|
||||
if constexpr (set_carry) carry = source & 0x8000'0000;
|
||||
source = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case ShiftType::ArithmeticRight: {
|
||||
const uint32_t sign = (source & 0x8000'0000) ? 0xffff'ffff : 0x0000'0000;
|
||||
|
||||
if(amount >= 32) {
|
||||
if constexpr (set_carry) carry = sign;
|
||||
source = sign;
|
||||
} else if(amount > 0) {
|
||||
if constexpr (set_carry) carry = source & (1 << (amount - 1));
|
||||
source = (source >> amount) | (sign << (32 - amount));
|
||||
} else {
|
||||
// As per logical right, an arithmetic shift of '0' is
|
||||
// treated as a shift by 32.
|
||||
if constexpr (set_carry) carry = source & 0x8000'0000;
|
||||
source = sign;
|
||||
}
|
||||
} break;
|
||||
|
||||
case ShiftType::RotateRight: {
|
||||
if(amount == 32) {
|
||||
if constexpr (set_carry) carry = source & 0x8000'0000;
|
||||
} else if(amount == 0) {
|
||||
// Rotate right by 0 is treated as a rotate right by 1 through carry.
|
||||
const uint32_t high = carry << 31;
|
||||
if constexpr (set_carry) carry = source & 1;
|
||||
source = (source >> 1) | high;
|
||||
} else {
|
||||
amount &= 31;
|
||||
if constexpr (set_carry) carry = source & (1 << (amount - 1));
|
||||
source = (source >> amount) | (source << (32 - amount));
|
||||
}
|
||||
} break;
|
||||
|
||||
// TODO: upon adoption of C++20, use std::rotr.
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Acts as per @c shift above, but applies runtime shift-type selection.
|
||||
template <bool set_carry>
|
||||
void shift(ShiftType type, uint32_t &source, uint32_t amount, typename Carry<set_carry>::type carry) {
|
||||
switch(type) {
|
||||
case ShiftType::LogicalLeft:
|
||||
shift<ShiftType::LogicalLeft, set_carry>(source, amount, carry);
|
||||
break;
|
||||
case ShiftType::LogicalRight:
|
||||
shift<ShiftType::LogicalRight, set_carry>(source, amount, carry);
|
||||
break;
|
||||
case ShiftType::ArithmeticRight:
|
||||
shift<ShiftType::ArithmeticRight, set_carry>(source, amount, carry);
|
||||
break;
|
||||
case ShiftType::RotateRight:
|
||||
shift<ShiftType::RotateRight, set_carry>(source, amount, carry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
559
InstructionSets/ARM/Executor.hpp
Normal file
559
InstructionSets/ARM/Executor.hpp
Normal file
@ -0,0 +1,559 @@
|
||||
//
|
||||
// Executor.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/03/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BarrelShifter.hpp"
|
||||
#include "OperationMapper.hpp"
|
||||
#include "Registers.hpp"
|
||||
#include "../../Numeric/Carry.hpp"
|
||||
|
||||
namespace InstructionSet::ARM {
|
||||
|
||||
/// A class compatible with the @c OperationMapper definition of a scheduler which applies all actions
|
||||
/// immediately, updating either a set of @c Registers or using the templated @c MemoryT to access
|
||||
/// memory. No hooks are currently provided for applying realistic timing.
|
||||
template <typename MemoryT>
|
||||
struct Executor {
|
||||
bool should_schedule(Condition condition) {
|
||||
return registers_.test(condition);
|
||||
}
|
||||
|
||||
template <bool allow_register, bool set_carry, typename FieldsT>
|
||||
uint32_t decode_shift(FieldsT fields, uint32_t &rotate_carry, uint32_t pc_offset) {
|
||||
uint32_t shift_amount;
|
||||
if constexpr (allow_register) {
|
||||
if(fields.shift_count_is_register()) {
|
||||
// "When R15 appears in either of the Rn or Rs positions it will give the value
|
||||
// of the PC alone, with the PSR bits replaced by zeroes. ...
|
||||
//
|
||||
// If a register is used to specify the shift amount, the
|
||||
// PC will be 8 bytes ahead when used as Rs."
|
||||
shift_amount =
|
||||
fields.shift_register() == 15 ?
|
||||
registers_.pc(8) :
|
||||
registers_.active[fields.shift_register()];
|
||||
} else {
|
||||
shift_amount = fields.shift_amount();
|
||||
}
|
||||
} else {
|
||||
shift_amount = fields.shift_amount();
|
||||
}
|
||||
|
||||
// "When R15 appears in the Rm position it will give the value of the PC together
|
||||
// with the PSR flags to the barrel shifter. ...
|
||||
//
|
||||
// If the shift amount is specified in the instruction, the PC will be 8 bytes ahead.
|
||||
// If a register is used to specify the shift amount, the PC will be ... 12 bytes ahead
|
||||
// when used as Rn or Rm."
|
||||
uint32_t operand2;
|
||||
if(fields.operand2() == 15) {
|
||||
operand2 = registers_.pc_status(pc_offset);
|
||||
} else {
|
||||
operand2 = registers_.active[fields.operand2()];
|
||||
}
|
||||
shift<set_carry>(fields.shift_type(), operand2, shift_amount, rotate_carry);
|
||||
|
||||
return operand2;
|
||||
}
|
||||
|
||||
template <Flags f> void perform(DataProcessing fields) {
|
||||
constexpr DataProcessingFlags flags(f);
|
||||
const bool shift_by_register = !flags.operand2_is_immediate() && fields.shift_count_is_register();
|
||||
|
||||
// Write a raw result into the PC proxy if the target is R15; it'll be stored properly later.
|
||||
uint32_t pc_proxy = 0;
|
||||
auto &destination = fields.destination() == 15 ? pc_proxy : registers_.active[fields.destination()];
|
||||
|
||||
// "When R15 appears in either of the Rn or Rs positions it will give the value
|
||||
// of the PC alone, with the PSR bits replaced by zeroes. ...
|
||||
//
|
||||
// If the shift amount is specified in the instruction, the PC will be 8 bytes ahead.
|
||||
// If a register is used to specify the shift amount, the PC will be ... 12 bytes ahead
|
||||
// when used as Rn or Rm."
|
||||
const uint32_t operand1 =
|
||||
(fields.operand1() == 15) ?
|
||||
registers_.pc(shift_by_register ? 12 : 8) :
|
||||
registers_.active[fields.operand1()];
|
||||
|
||||
uint32_t operand2;
|
||||
uint32_t rotate_carry = registers_.c();
|
||||
|
||||
// Populate carry from the shift only if it'll be used.
|
||||
constexpr bool shift_sets_carry = is_logical(flags.operation()) && flags.set_condition_codes();
|
||||
|
||||
// Get operand 2.
|
||||
if constexpr (flags.operand2_is_immediate()) {
|
||||
operand2 = fields.immediate();
|
||||
if(fields.rotate()) {
|
||||
shift<ShiftType::RotateRight, shift_sets_carry>(operand2, fields.rotate(), rotate_carry);
|
||||
}
|
||||
} else {
|
||||
operand2 = decode_shift<true, shift_sets_carry>(fields, rotate_carry, shift_by_register ? 12 : 8);
|
||||
}
|
||||
|
||||
// Perform the data processing operation.
|
||||
uint32_t conditions = 0;
|
||||
switch(flags.operation()) {
|
||||
// Logical operations.
|
||||
case DataProcessingOperation::AND: conditions = destination = operand1 & operand2; break;
|
||||
case DataProcessingOperation::EOR: conditions = destination = operand1 ^ operand2; break;
|
||||
case DataProcessingOperation::ORR: conditions = destination = operand1 | operand2; break;
|
||||
case DataProcessingOperation::BIC: conditions = destination = operand1 & ~operand2; break;
|
||||
|
||||
case DataProcessingOperation::MOV: conditions = destination = operand2; break;
|
||||
case DataProcessingOperation::MVN: conditions = destination = ~operand2; break;
|
||||
|
||||
case DataProcessingOperation::TST: conditions = operand1 & operand2; break;
|
||||
case DataProcessingOperation::TEQ: conditions = operand1 ^ operand2; break;
|
||||
|
||||
case DataProcessingOperation::ADD:
|
||||
case DataProcessingOperation::ADC:
|
||||
case DataProcessingOperation::CMN:
|
||||
conditions = operand1 + operand2;
|
||||
|
||||
if constexpr (flags.operation() == DataProcessingOperation::ADC) {
|
||||
conditions += registers_.c();
|
||||
}
|
||||
|
||||
if constexpr (flags.set_condition_codes()) {
|
||||
registers_.set_c(Numeric::carried_out<true, 31>(operand1, operand2, conditions));
|
||||
registers_.set_v(Numeric::overflow<true>(operand1, operand2, conditions));
|
||||
}
|
||||
|
||||
if constexpr (!is_comparison(flags.operation())) {
|
||||
destination = conditions;
|
||||
}
|
||||
break;
|
||||
|
||||
case DataProcessingOperation::SUB:
|
||||
case DataProcessingOperation::SBC:
|
||||
case DataProcessingOperation::CMP:
|
||||
conditions = operand1 - operand2;
|
||||
|
||||
if constexpr (flags.operation() == DataProcessingOperation::SBC) {
|
||||
conditions -= registers_.c();
|
||||
}
|
||||
|
||||
if constexpr (flags.set_condition_codes()) {
|
||||
registers_.set_c(Numeric::carried_out<false, 31>(operand1, operand2, conditions));
|
||||
registers_.set_v(Numeric::overflow<false>(operand1, operand2, conditions));
|
||||
}
|
||||
|
||||
if constexpr (!is_comparison(flags.operation())) {
|
||||
destination = conditions;
|
||||
}
|
||||
break;
|
||||
|
||||
case DataProcessingOperation::RSB:
|
||||
case DataProcessingOperation::RSC:
|
||||
conditions = operand2 - operand1;
|
||||
|
||||
if constexpr (flags.operation() == DataProcessingOperation::RSC) {
|
||||
conditions -= registers_.c();
|
||||
}
|
||||
|
||||
if constexpr (flags.set_condition_codes()) {
|
||||
registers_.set_c(Numeric::carried_out<false, 31>(operand2, operand1, conditions));
|
||||
registers_.set_v(Numeric::overflow<false>(operand2, operand1, conditions));
|
||||
}
|
||||
|
||||
destination = conditions;
|
||||
break;
|
||||
}
|
||||
|
||||
if constexpr (flags.set_condition_codes()) {
|
||||
// "When Rd is a register other than R15, the condition code flags in the PSR may be
|
||||
// updated from the ALU flags as described above. When Rd is R15 and the S flag in
|
||||
// the instruction is set, the PSR is overwritten by the corresponding ALU result.
|
||||
//
|
||||
// ... if the instruction is of a type which does not normally produce a result
|
||||
// (CMP, CMN, TST, TEQ) but Rd is R15 and the S bit is set, the result will be used in
|
||||
// this case to update those PSR flags which are not protected by virtue of the
|
||||
// processor mode."
|
||||
|
||||
if(fields.destination() == 15) {
|
||||
if constexpr (is_comparison(flags.operation())) {
|
||||
registers_.set_status(pc_proxy);
|
||||
} else {
|
||||
registers_.set_status(pc_proxy);
|
||||
registers_.set_pc(pc_proxy);
|
||||
}
|
||||
} else {
|
||||
// Set N and Z in a unified way.
|
||||
registers_.set_nz(conditions);
|
||||
|
||||
// Set C from the barrel shifter if applicable.
|
||||
if constexpr (shift_sets_carry) {
|
||||
registers_.set_c(rotate_carry);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// "If the S flag is clear when Rd is R15, only the 24 PC bits of R15 will be written."
|
||||
if(fields.destination() == 15) {
|
||||
registers_.set_pc(pc_proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <Flags f> void perform(Multiply fields) {
|
||||
constexpr MultiplyFlags flags(f);
|
||||
|
||||
// R15 rules:
|
||||
//
|
||||
// * Rs: no PSR, 8 bytes ahead;
|
||||
// * Rn: with PSR, 8 bytes ahead;
|
||||
// * Rm: with PSR, 12 bytes ahead.
|
||||
|
||||
const uint32_t multiplicand = fields.multiplicand() == 15 ? registers_.pc(8) : registers_.active[fields.multiplicand()];
|
||||
const uint32_t multiplier = fields.multiplier() == 15 ? registers_.pc_status(8) : registers_.active[fields.multiplier()];
|
||||
const uint32_t accumulator =
|
||||
flags.operation() == MultiplyFlags::Operation::MUL ? 0 :
|
||||
(fields.multiplicand() == 15 ? registers_.pc_status(12) : registers_.active[fields.accumulator()]);
|
||||
|
||||
const uint32_t result = multiplicand * multiplier + accumulator;
|
||||
|
||||
if constexpr (flags.set_condition_codes()) {
|
||||
registers_.set_nz(result);
|
||||
// V is unaffected; C is undefined.
|
||||
}
|
||||
|
||||
if(fields.destination() != 15) {
|
||||
registers_.active[fields.destination()] = result;
|
||||
}
|
||||
}
|
||||
|
||||
template <Flags f> void perform(Branch branch) {
|
||||
constexpr BranchFlags flags(f);
|
||||
|
||||
if constexpr (flags.operation() == BranchFlags::Operation::BL) {
|
||||
registers_.active[14] = registers_.pc(4);
|
||||
}
|
||||
registers_.set_pc(registers_.pc(8) + branch.offset());
|
||||
}
|
||||
|
||||
template <Flags f> void perform(SingleDataTransfer transfer) {
|
||||
constexpr SingleDataTransferFlags flags(f);
|
||||
|
||||
// Calculate offset.
|
||||
uint32_t offset;
|
||||
if constexpr (flags.offset_is_immediate()) {
|
||||
offset = transfer.immediate();
|
||||
} else {
|
||||
// The 8 shift control bits are described in 6.2.3, but
|
||||
// the register specified shift amounts are not available
|
||||
// in this instruction class.
|
||||
uint32_t carry = registers_.c();
|
||||
offset = decode_shift<false, false>(transfer, carry, 8);
|
||||
}
|
||||
|
||||
// Obtain base address.
|
||||
uint32_t address =
|
||||
transfer.base() == 15 ?
|
||||
registers_.pc(8) :
|
||||
registers_.active[transfer.base()];
|
||||
|
||||
// Determine what the address will be after offsetting.
|
||||
uint32_t offsetted_address = address;
|
||||
if constexpr (flags.add_offset()) {
|
||||
offsetted_address += offset;
|
||||
} else {
|
||||
offsetted_address -= offset;
|
||||
}
|
||||
|
||||
// If preindexing, apply now.
|
||||
if constexpr (flags.pre_index()) {
|
||||
address = offsetted_address;
|
||||
}
|
||||
|
||||
// Check for an address exception.
|
||||
if(address >= (1 << 26)) {
|
||||
registers_.exception<Registers::Exception::Address>();
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr bool trans = !flags.pre_index() && flags.write_back_address();
|
||||
if constexpr (flags.operation() == SingleDataTransferFlags::Operation::STR) {
|
||||
const uint32_t source =
|
||||
transfer.source() == 15 ?
|
||||
registers_.pc_status(12) :
|
||||
registers_.active[transfer.source()];
|
||||
|
||||
bool did_write;
|
||||
if constexpr (flags.transfer_byte()) {
|
||||
did_write = bus_.template write<uint8_t>(address, uint8_t(source), registers_.mode(), trans);
|
||||
} else {
|
||||
// "The data presented to the data bus are not affected if the address is not word aligned".
|
||||
did_write = bus_.template write<uint32_t>(address, source, registers_.mode(), trans);
|
||||
}
|
||||
|
||||
if(!did_write) {
|
||||
registers_.exception<Registers::Exception::DataAbort>();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
bool did_read;
|
||||
uint32_t value;
|
||||
|
||||
if constexpr (flags.transfer_byte()) {
|
||||
uint8_t target;
|
||||
did_read = bus_.template read<uint8_t>(address, target, registers_.mode(), trans);
|
||||
value = target;
|
||||
} else {
|
||||
did_read = bus_.template read<uint32_t>(address, value, registers_.mode(), trans);
|
||||
|
||||
// "An address offset from a word boundary will cause the data to be rotated into the
|
||||
// register so that the addressed byte occuplies bits 0 to 7."
|
||||
switch(address & 3) {
|
||||
case 0: break;
|
||||
case 1: value = (value >> 8) | (value << 24); break;
|
||||
case 2: value = (value >> 16) | (value << 16); break;
|
||||
case 3: value = (value >> 24) | (value << 8); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!did_read) {
|
||||
registers_.exception<Registers::Exception::DataAbort>();
|
||||
return;
|
||||
}
|
||||
|
||||
if(transfer.destination() == 15) {
|
||||
registers_.set_pc(value);
|
||||
} else {
|
||||
registers_.active[transfer.destination()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// If either postindexing or else with writeback, update base.
|
||||
if constexpr (!flags.pre_index() || flags.write_back_address()) {
|
||||
if(transfer.base() == 15) {
|
||||
registers_.set_pc(offsetted_address);
|
||||
} else {
|
||||
registers_.active[transfer.base()] = offsetted_address;
|
||||
}
|
||||
}
|
||||
}
|
||||
template <Flags f> void perform(BlockDataTransfer transfer) {
|
||||
constexpr BlockDataTransferFlags flags(f);
|
||||
|
||||
// Grab a copy of the list of registers to transfer.
|
||||
const uint16_t list = transfer.register_list();
|
||||
|
||||
// Read the base address and take a copy in case a data abort means that
|
||||
// it has to be restored later, and to write that value rather than
|
||||
// the final address if the base register is first in the write-out list.
|
||||
uint32_t address = transfer.base() == 15 ?
|
||||
registers_.pc_status(8) :
|
||||
registers_.active[transfer.base()];
|
||||
const uint32_t initial_address = address;
|
||||
|
||||
// Figure out what the final address will be, since that's what'll be
|
||||
// in the output if the base register is second or beyond in the
|
||||
// write-out list.
|
||||
//
|
||||
// Writes are always ordered from lowest address to highest; adjust the
|
||||
// start address if this write is supposed to fill memory downward from
|
||||
// the base.
|
||||
|
||||
// TODO: use std::popcount when adopting C++20.
|
||||
uint32_t total = ((list & 0xa) >> 1) + (list & 0x5);
|
||||
total = ((list & 0xc) >> 2) + (list & 0x3);
|
||||
|
||||
uint32_t final_address;
|
||||
if constexpr (!flags.add_offset()) {
|
||||
final_address = address + total * 4;
|
||||
address = final_address;
|
||||
} else {
|
||||
final_address = address + total * 4;
|
||||
}
|
||||
|
||||
// For loads, keep a record of the value replaced by the last load and
|
||||
// where it came from. A data abort cancels both the current load and
|
||||
// the one before it, so this is used by this implementation to undo
|
||||
// the previous load in that case.
|
||||
struct {
|
||||
uint32_t *target = nullptr;
|
||||
uint32_t value;
|
||||
} last_replacement;
|
||||
|
||||
// Check whether access is forced ot the user bank; if so then switch
|
||||
// to it now. Also keep track of the original mode to switch back at
|
||||
// the end.
|
||||
const Mode original_mode = registers_.mode();
|
||||
const bool adopt_user_mode =
|
||||
(
|
||||
flags.operation() == BlockDataTransferFlags::Operation::STM &&
|
||||
flags.load_psr()
|
||||
) ||
|
||||
(
|
||||
flags.operation() == BlockDataTransferFlags::Operation::LDM &&
|
||||
!(list & (1 << 15))
|
||||
);
|
||||
if(adopt_user_mode) {
|
||||
registers_.set_mode(Mode::User);
|
||||
}
|
||||
|
||||
bool address_error = false;
|
||||
|
||||
// Keep track of whether all accesses succeeded in order potentially to
|
||||
// throw a data abort later.
|
||||
bool accesses_succeeded = true;
|
||||
const auto access = [&](uint32_t &value) {
|
||||
// Update address in advance for:
|
||||
// * pre-indexed upward stores; and
|
||||
// * post-indxed downward stores.
|
||||
if constexpr (flags.pre_index() == flags.add_offset()) {
|
||||
address += 4;
|
||||
}
|
||||
|
||||
if constexpr (flags.operation() == BlockDataTransferFlags::Operation::STM) {
|
||||
if(!address_error) {
|
||||
// "If the abort occurs during a store multiple instruction, ARM takes little action until
|
||||
// the instruction completes, whereupon it enters the data abort trap. The memory manager is
|
||||
// responsible for preventing erroneous writes to the memory."
|
||||
accesses_succeeded &= bus_.template write<uint32_t>(address, value, registers_.mode(), false);
|
||||
}
|
||||
} else {
|
||||
// When ARM detects a data abort during a load multiple instruction, it modifies the operation of
|
||||
// the instruction to ensure that recovery is possible.
|
||||
//
|
||||
// * Overwriting of registers stops when the abort happens. The aborting load will not
|
||||
// take place, nor will the preceding one ...
|
||||
// * The base register is restored, to its modified value if write-back was requested.
|
||||
if(accesses_succeeded) {
|
||||
const uint32_t replaced = value;
|
||||
accesses_succeeded &= bus_.template read<uint32_t>(address, value, registers_.mode(), false);
|
||||
|
||||
// Update the last-modified slot if the access succeeded; otherwise
|
||||
// undo the last modification if there was one, and undo the base
|
||||
// address change.
|
||||
if(accesses_succeeded) {
|
||||
last_replacement.value = replaced;
|
||||
last_replacement.target = &value;
|
||||
} else {
|
||||
if(last_replacement.target) {
|
||||
*last_replacement.target = last_replacement.value;
|
||||
}
|
||||
|
||||
// Also restore the base register.
|
||||
if(transfer.base() != 15) {
|
||||
if constexpr (flags.write_back_address()) {
|
||||
registers_.active[transfer.base()] = final_address;
|
||||
} else {
|
||||
registers_.active[transfer.base()] = initial_address;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Implicitly: do the access anyway, but don't store the value. I think.
|
||||
uint32_t throwaway;
|
||||
bus_.template read<uint32_t>(address, throwaway, registers_.mode(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Update address after the fact for:
|
||||
// * post-indexed upward stores; and
|
||||
// * pre-indxed downward stores.
|
||||
if constexpr (flags.pre_index() != flags.add_offset()) {
|
||||
address += 4;
|
||||
}
|
||||
};
|
||||
|
||||
// Check for an address exception.
|
||||
address_error = address >= (1 << 26);
|
||||
|
||||
// Write out registers 1 to 14.
|
||||
for(int c = 0; c < 15; c++) {
|
||||
if(list & (1 << c)) {
|
||||
access(registers_.active[c]);
|
||||
|
||||
// Modify base register after each write if writeback is enabled.
|
||||
// This'll ensure the unmodified value goes out if it was the
|
||||
// first-selected register only.
|
||||
if constexpr (flags.write_back_address()) {
|
||||
if(transfer.base() != 15) {
|
||||
registers_.active[transfer.base()] = final_address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Definitively write back, even if the earlier register list
|
||||
// was empty.
|
||||
if constexpr (flags.write_back_address()) {
|
||||
if(transfer.base() != 15) {
|
||||
registers_.active[transfer.base()] = final_address;
|
||||
}
|
||||
}
|
||||
|
||||
// Read or write the program counter as a special case if it was in the list.
|
||||
if(list & (1 << 15)) {
|
||||
uint32_t value;
|
||||
if constexpr (flags.operation() == BlockDataTransferFlags::Operation::STM) {
|
||||
value = registers_.pc_status(12);
|
||||
access(value);
|
||||
} else {
|
||||
access(value);
|
||||
registers_.set_pc(value);
|
||||
if constexpr (flags.load_psr()) {
|
||||
registers_.set_status(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If user mode was unnaturally forced, switch back to the actual
|
||||
// current operating mode.
|
||||
if(adopt_user_mode) {
|
||||
registers_.set_mode(original_mode);
|
||||
}
|
||||
|
||||
// Finally throw an exception if necessary.
|
||||
if(address_error) {
|
||||
registers_.exception<Registers::Exception::Address>();
|
||||
} else if(!accesses_succeeded) {
|
||||
registers_.exception<Registers::Exception::DataAbort>();
|
||||
}
|
||||
}
|
||||
|
||||
void software_interrupt() {
|
||||
registers_.exception<Registers::Exception::SoftwareInterrupt>();
|
||||
}
|
||||
void unknown() {
|
||||
registers_.exception<Registers::Exception::UndefinedInstruction>();
|
||||
}
|
||||
|
||||
// Act as if no coprocessors present.
|
||||
template <Flags> void perform(CoprocessorRegisterTransfer) {
|
||||
registers_.exception<Registers::Exception::UndefinedInstruction>();
|
||||
}
|
||||
template <Flags> void perform(CoprocessorDataOperation) {
|
||||
registers_.exception<Registers::Exception::UndefinedInstruction>();
|
||||
}
|
||||
template <Flags> void perform(CoprocessorDataTransfer) {
|
||||
registers_.exception<Registers::Exception::UndefinedInstruction>();
|
||||
}
|
||||
|
||||
MemoryT bus_;
|
||||
|
||||
void set_pc(uint32_t pc) {
|
||||
registers_.set_pc(pc);
|
||||
}
|
||||
|
||||
private:
|
||||
Registers registers_;
|
||||
};
|
||||
|
||||
/// Provides an analogue of the @c OperationMapper -affiliated @c dispatch that also updates the
|
||||
/// program counter in an executor's register bank appropriately.
|
||||
template <typename MemoryT>
|
||||
void dispatch(uint32_t pc, uint32_t instruction, Executor<MemoryT> &executor) {
|
||||
executor.set_pc(pc);
|
||||
dispatch(instruction, executor);
|
||||
}
|
||||
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../Reflection/Dispatcher.hpp"
|
||||
#include "BarrelShifter.hpp"
|
||||
|
||||
namespace InstructionSet::ARM {
|
||||
|
||||
@ -16,44 +17,6 @@ enum class Model {
|
||||
ARM2,
|
||||
};
|
||||
|
||||
enum class Operation {
|
||||
AND, /// Rd = Op1 AND Op2.
|
||||
EOR, /// Rd = Op1 EOR Op2.
|
||||
SUB, /// Rd = Op1 - Op2.
|
||||
RSB, /// Rd = Op2 - Op1.
|
||||
ADD, /// Rd = Op1 + Op2.
|
||||
ADC, /// Rd = Op1 + Ord2 + C.
|
||||
SBC, /// Rd = Op1 - Op2 + C.
|
||||
RSC, /// Rd = Op2 - Op1 + C.
|
||||
TST, /// Set condition codes on Op1 AND Op2.
|
||||
TEQ, /// Set condition codes on Op1 EOR Op2.
|
||||
CMP, /// Set condition codes on Op1 - Op2.
|
||||
CMN, /// Set condition codes on Op1 + Op2.
|
||||
ORR, /// Rd = Op1 OR Op2.
|
||||
MOV, /// Rd = Op2
|
||||
BIC, /// Rd = Op1 AND NOT Op2.
|
||||
MVN, /// Rd = NOT Op2.
|
||||
|
||||
MUL, /// Rd = Rm * Rs
|
||||
MLA, /// Rd = Rm * Rs + Rn
|
||||
B, /// Add offset to PC; programmer allows for PC being two words ahead.
|
||||
BL, /// Copy PC and PSR to R14, then branch. Copied PC points to next instruction.
|
||||
|
||||
LDR, /// Read single byte or word from [base + offset], possibly mutating the base.
|
||||
STR, /// Write a single byte or word to [base + offset], possibly mutating the base.
|
||||
LDM, /// Read 1–16 words from [base], possibly mutating it.
|
||||
STM, /// Write 1-16 words to [base], possibly mutating it.
|
||||
SWI, /// Perform a software interrupt.
|
||||
|
||||
CDP, /// Coprocessor data operation.
|
||||
MRC, /// Move from coprocessor register to ARM register.
|
||||
MCR, /// Move from ARM register to coprocessor register.
|
||||
LDC, /// Coprocessor data transfer load.
|
||||
STC, /// Coprocessor data transfer store.
|
||||
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class Condition {
|
||||
EQ, NE, CS, CC,
|
||||
MI, PL, VS, VC,
|
||||
@ -61,13 +24,6 @@ enum class Condition {
|
||||
GT, LE, AL, NV,
|
||||
};
|
||||
|
||||
enum class ShiftType {
|
||||
LogicalLeft = 0b00,
|
||||
LogicalRight = 0b01,
|
||||
ArithmeticRight = 0b10,
|
||||
RotateRight = 0b11,
|
||||
};
|
||||
|
||||
//
|
||||
// Implementation details.
|
||||
//
|
||||
@ -87,7 +43,7 @@ struct WithShiftControlBits {
|
||||
constexpr WithShiftControlBits(uint32_t opcode) noexcept : opcode_(opcode) {}
|
||||
|
||||
/// The operand 2 register index if @c operand2_is_immediate() is @c false; meaningless otherwise.
|
||||
int operand2() const { return opcode_ & 0xf; }
|
||||
int operand2() const { return opcode_ & 0xf; }
|
||||
/// The type of shift to apply to operand 2 if @c operand2_is_immediate() is @c false; meaningless otherwise.
|
||||
ShiftType shift_type() const { return ShiftType((opcode_ >> 5) & 3); }
|
||||
/// @returns @c true if the amount to shift by should be taken from a register; @c false if it is an immediate value.
|
||||
@ -104,6 +60,23 @@ protected:
|
||||
//
|
||||
// Branch (i.e. B and BL).
|
||||
//
|
||||
struct BranchFlags {
|
||||
constexpr BranchFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
enum class Operation {
|
||||
B, /// Add offset to PC; programmer allows for PC being two words ahead.
|
||||
BL, /// Copy PC and PSR to R14, then branch. Copied PC points to next instruction.
|
||||
};
|
||||
|
||||
/// @returns The operation to apply.
|
||||
constexpr Operation operation() const {
|
||||
return flag_bit<24>(flags_) ? Operation::BL : Operation::B;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
};
|
||||
|
||||
struct Branch {
|
||||
constexpr Branch(uint32_t opcode) noexcept : opcode_(opcode) {}
|
||||
|
||||
@ -117,15 +90,67 @@ private:
|
||||
//
|
||||
// Data processing (i.e. AND to MVN).
|
||||
//
|
||||
enum class DataProcessingOperation {
|
||||
AND, /// Rd = Op1 AND Op2.
|
||||
EOR, /// Rd = Op1 EOR Op2.
|
||||
SUB, /// Rd = Op1 - Op2.
|
||||
RSB, /// Rd = Op2 - Op1.
|
||||
ADD, /// Rd = Op1 + Op2.
|
||||
ADC, /// Rd = Op1 + Ord2 + C.
|
||||
SBC, /// Rd = Op1 - Op2 + C.
|
||||
RSC, /// Rd = Op2 - Op1 + C.
|
||||
TST, /// Set condition codes on Op1 AND Op2.
|
||||
TEQ, /// Set condition codes on Op1 EOR Op2.
|
||||
CMP, /// Set condition codes on Op1 - Op2.
|
||||
CMN, /// Set condition codes on Op1 + Op2.
|
||||
ORR, /// Rd = Op1 OR Op2.
|
||||
MOV, /// Rd = Op2
|
||||
BIC, /// Rd = Op1 AND NOT Op2.
|
||||
MVN, /// Rd = NOT Op2.
|
||||
};
|
||||
|
||||
constexpr bool is_logical(DataProcessingOperation operation) {
|
||||
switch(operation) {
|
||||
case DataProcessingOperation::AND:
|
||||
case DataProcessingOperation::EOR:
|
||||
case DataProcessingOperation::TST:
|
||||
case DataProcessingOperation::TEQ:
|
||||
case DataProcessingOperation::ORR:
|
||||
case DataProcessingOperation::MOV:
|
||||
case DataProcessingOperation::BIC:
|
||||
case DataProcessingOperation::MVN:
|
||||
return true;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool is_comparison(DataProcessingOperation operation) {
|
||||
switch(operation) {
|
||||
case DataProcessingOperation::TST:
|
||||
case DataProcessingOperation::TEQ:
|
||||
case DataProcessingOperation::CMP:
|
||||
case DataProcessingOperation::CMN:
|
||||
return true;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct DataProcessingFlags {
|
||||
constexpr DataProcessingFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
/// @returns The operation to apply.
|
||||
constexpr DataProcessingOperation operation() const {
|
||||
return DataProcessingOperation((flags_ >> 21) & 0xf);
|
||||
}
|
||||
|
||||
/// @returns @c true if operand 2 is defined by the @c rotate() and @c immediate() fields;
|
||||
/// @c false if it is defined by the @c shift_*() and @c operand2() fields.
|
||||
constexpr bool operand2_is_immediate() { return flag_bit<25>(flags_); }
|
||||
constexpr bool operand2_is_immediate() const { return flag_bit<25>(flags_); }
|
||||
|
||||
/// @c true if the status register should be updated; @c false otherwise.
|
||||
constexpr bool set_condition_codes() { return flag_bit<20>(flags_); }
|
||||
constexpr bool set_condition_codes() const { return flag_bit<20>(flags_); }
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
@ -157,7 +182,17 @@ struct MultiplyFlags {
|
||||
constexpr MultiplyFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
/// @c true if the status register should be updated; @c false otherwise.
|
||||
constexpr bool set_condition_codes() { return flag_bit<20>(flags_); }
|
||||
constexpr bool set_condition_codes() const { return flag_bit<20>(flags_); }
|
||||
|
||||
enum class Operation {
|
||||
MUL, /// Rd = Rm * Rs
|
||||
MLA, /// Rd = Rm * Rs + Rn
|
||||
};
|
||||
|
||||
/// @returns The operation to apply.
|
||||
constexpr Operation operation() const {
|
||||
return flag_bit<21>(flags_) ? Operation::MLA : Operation::MUL;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
@ -188,11 +223,20 @@ private:
|
||||
struct SingleDataTransferFlags {
|
||||
constexpr SingleDataTransferFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
constexpr bool offset_is_immediate() { return flag_bit<25>(flags_); }
|
||||
constexpr bool pre_index() { return flag_bit<24>(flags_); }
|
||||
constexpr bool add_offset() { return flag_bit<23>(flags_); }
|
||||
constexpr bool transfer_byte() { return flag_bit<22>(flags_); }
|
||||
constexpr bool write_back_address() { return flag_bit<21>(flags_); }
|
||||
enum class Operation {
|
||||
LDR, /// Read single byte or word from [base + offset], possibly mutating the base.
|
||||
STR, /// Write a single byte or word to [base + offset], possibly mutating the base.
|
||||
};
|
||||
|
||||
constexpr Operation operation() const {
|
||||
return flag_bit<20>(flags_) ? Operation::LDR : Operation::STR;
|
||||
}
|
||||
|
||||
constexpr bool offset_is_immediate() const { return flag_bit<25>(flags_); }
|
||||
constexpr bool pre_index() const { return flag_bit<24>(flags_); }
|
||||
constexpr bool add_offset() const { return flag_bit<23>(flags_); }
|
||||
constexpr bool transfer_byte() const { return flag_bit<22>(flags_); }
|
||||
constexpr bool write_back_address() const { return flag_bit<21>(flags_); }
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
@ -220,10 +264,19 @@ struct SingleDataTransfer: public WithShiftControlBits {
|
||||
struct BlockDataTransferFlags {
|
||||
constexpr BlockDataTransferFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
constexpr bool pre_index() { return flag_bit<24>(flags_); }
|
||||
constexpr bool add_offset() { return flag_bit<23>(flags_); }
|
||||
constexpr bool load_psr() { return flag_bit<22>(flags_); }
|
||||
constexpr bool write_back_address() { return flag_bit<21>(flags_); }
|
||||
enum class Operation {
|
||||
LDM, /// Read 1–16 words from [base], possibly mutating it.
|
||||
STM, /// Write 1-16 words to [base], possibly mutating it.
|
||||
};
|
||||
|
||||
constexpr Operation operation() const {
|
||||
return flag_bit<20>(flags_) ? Operation::LDM : Operation::STM;
|
||||
}
|
||||
|
||||
constexpr bool pre_index() const { return flag_bit<24>(flags_); }
|
||||
constexpr bool add_offset() const { return flag_bit<23>(flags_); }
|
||||
constexpr bool load_psr() const { return flag_bit<22>(flags_); }
|
||||
constexpr bool write_back_address() const { return flag_bit<21>(flags_); }
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
@ -245,7 +298,7 @@ struct BlockDataTransfer: public WithShiftControlBits {
|
||||
struct CoprocessorDataOperationFlags {
|
||||
constexpr CoprocessorDataOperationFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
constexpr int operation() const { return (flags_ >> (FlagsStartBit - 20)) & 0xf; }
|
||||
constexpr int coprocessor_operation() const { return (flags_ >> (FlagsStartBit - 20)) & 0xf; }
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
@ -254,11 +307,11 @@ private:
|
||||
struct CoprocessorDataOperation {
|
||||
constexpr CoprocessorDataOperation(uint32_t opcode) noexcept : opcode_(opcode) {}
|
||||
|
||||
int operand1() { return (opcode_ >> 16) & 0xf; }
|
||||
int operand2() { return opcode_ & 0xf; }
|
||||
int destination() { return (opcode_ >> 12) & 0xf; }
|
||||
int coprocessor() { return (opcode_ >> 8) & 0xf; }
|
||||
int information() { return (opcode_ >> 5) & 0x7; }
|
||||
int operand1() const { return (opcode_ >> 16) & 0xf; }
|
||||
int operand2() const { return opcode_ & 0xf; }
|
||||
int destination() const { return (opcode_ >> 12) & 0xf; }
|
||||
int coprocessor() const { return (opcode_ >> 8) & 0xf; }
|
||||
int information() const { return (opcode_ >> 5) & 0x7; }
|
||||
|
||||
private:
|
||||
uint32_t opcode_;
|
||||
@ -270,7 +323,15 @@ private:
|
||||
struct CoprocessorRegisterTransferFlags {
|
||||
constexpr CoprocessorRegisterTransferFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
constexpr int operation() const { return (flags_ >> (FlagsStartBit - 20)) & 0x7; }
|
||||
enum class Operation {
|
||||
MRC, /// Move from coprocessor register to ARM register.
|
||||
MCR, /// Move from ARM register to coprocessor register.
|
||||
};
|
||||
|
||||
constexpr Operation operation() const {
|
||||
return flag_bit<20>(flags_) ? Operation::MRC : Operation::MCR;
|
||||
}
|
||||
constexpr int coprocessor_operation() const { return (flags_ >> (FlagsStartBit - 20)) & 0x7; }
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
@ -279,11 +340,11 @@ private:
|
||||
struct CoprocessorRegisterTransfer {
|
||||
constexpr CoprocessorRegisterTransfer(uint32_t opcode) noexcept : opcode_(opcode) {}
|
||||
|
||||
int operand1() { return (opcode_ >> 16) & 0xf; }
|
||||
int operand2() { return opcode_ & 0xf; }
|
||||
int destination() { return (opcode_ >> 12) & 0xf; }
|
||||
int coprocessor() { return (opcode_ >> 8) & 0xf; }
|
||||
int information() { return (opcode_ >> 5) & 0x7; }
|
||||
int operand1() const { return (opcode_ >> 16) & 0xf; }
|
||||
int operand2() const { return opcode_ & 0xf; }
|
||||
int destination() const { return (opcode_ >> 12) & 0xf; }
|
||||
int coprocessor() const { return (opcode_ >> 8) & 0xf; }
|
||||
int information() const { return (opcode_ >> 5) & 0x7; }
|
||||
|
||||
private:
|
||||
uint32_t opcode_;
|
||||
@ -295,10 +356,18 @@ private:
|
||||
struct CoprocessorDataTransferFlags {
|
||||
constexpr CoprocessorDataTransferFlags(uint8_t flags) noexcept : flags_(flags) {}
|
||||
|
||||
constexpr bool pre_index() { return flag_bit<24>(flags_); }
|
||||
constexpr bool add_offset() { return flag_bit<23>(flags_); }
|
||||
constexpr bool transfer_length() { return flag_bit<22>(flags_); }
|
||||
constexpr bool write_back_address() { return flag_bit<21>(flags_); }
|
||||
enum class Operation {
|
||||
LDC, /// Coprocessor data transfer load.
|
||||
STC, /// Coprocessor data transfer store.
|
||||
};
|
||||
|
||||
constexpr Operation operation() const {
|
||||
return flag_bit<20>(flags_) ? Operation::LDC : Operation::STC;
|
||||
}
|
||||
constexpr bool pre_index() const { return flag_bit<24>(flags_); }
|
||||
constexpr bool add_offset() const { return flag_bit<23>(flags_); }
|
||||
constexpr bool transfer_length() const { return flag_bit<22>(flags_); }
|
||||
constexpr bool write_back_address() const { return flag_bit<21>(flags_); }
|
||||
|
||||
private:
|
||||
uint8_t flags_;
|
||||
@ -307,13 +376,13 @@ private:
|
||||
struct CoprocessorDataTransfer {
|
||||
constexpr CoprocessorDataTransfer(uint32_t opcode) noexcept : opcode_(opcode) {}
|
||||
|
||||
int base() { return (opcode_ >> 16) & 0xf; }
|
||||
int base() const { return (opcode_ >> 16) & 0xf; }
|
||||
|
||||
int source() { return (opcode_ >> 12) & 0xf; }
|
||||
int destination() { return (opcode_ >> 12) & 0xf; }
|
||||
int source() const { return (opcode_ >> 12) & 0xf; }
|
||||
int destination() const { return (opcode_ >> 12) & 0xf; }
|
||||
|
||||
int coprocessor() { return (opcode_ >> 8) & 0xf; }
|
||||
int offset() { return opcode_ & 0xff; }
|
||||
int coprocessor() const { return (opcode_ >> 8) & 0xf; }
|
||||
int offset() const { return opcode_ & 0xff; }
|
||||
|
||||
private:
|
||||
uint32_t opcode_;
|
||||
@ -321,9 +390,17 @@ private:
|
||||
|
||||
/// Operation mapper; use the free function @c dispatch as defined below.
|
||||
struct OperationMapper {
|
||||
template <int i, typename SchedulerT> void dispatch(uint32_t instruction, SchedulerT &scheduler) {
|
||||
static Condition condition(uint32_t instruction) {
|
||||
return Condition(instruction >> 28);
|
||||
}
|
||||
|
||||
template <int i, typename SchedulerT>
|
||||
static void dispatch(uint32_t instruction, SchedulerT &scheduler) {
|
||||
// Put the 8-bit segment of instruction back into its proper place;
|
||||
// this allows all the tests below to be written so as to coordinate
|
||||
// properly with the data sheet, and since it's all compile-time work
|
||||
// it doesn't cost anything.
|
||||
constexpr auto partial = uint32_t(i << 20);
|
||||
const auto condition = Condition(instruction >> 28);
|
||||
|
||||
// Cf. the ARM2 datasheet, p.45. Tests below match its ordering
|
||||
// other than that 'undefined' is the fallthrough case. More specific
|
||||
@ -332,11 +409,7 @@ struct OperationMapper {
|
||||
|
||||
// Data processing; cf. p.17.
|
||||
if constexpr (((partial >> 26) & 0b11) == 0b00) {
|
||||
constexpr auto operation = Operation(int(Operation::AND) + ((partial >> 21) & 0xf));
|
||||
scheduler.template perform<operation, i>(
|
||||
condition,
|
||||
DataProcessing(instruction)
|
||||
);
|
||||
scheduler.template perform<i>(DataProcessing(instruction));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -345,48 +418,32 @@ struct OperationMapper {
|
||||
// This implementation provides only eight bits baked into the template parameters so
|
||||
// an additional dynamic test is required to check whether this is really, really MUL or MLA.
|
||||
if(((instruction >> 4) & 0b1111) == 0b1001) {
|
||||
constexpr bool is_mla = partial & (1 << 21);
|
||||
scheduler.template perform<is_mla ? Operation::MLA : Operation::MUL, i>(
|
||||
condition,
|
||||
Multiply(instruction)
|
||||
);
|
||||
scheduler.template perform<i>(Multiply(instruction));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Single data transfer (LDR, STR); cf. p.25.
|
||||
if constexpr (((partial >> 26) & 0b11) == 0b01) {
|
||||
constexpr bool is_ldr = partial & (1 << 20);
|
||||
scheduler.template perform<is_ldr ? Operation::LDR : Operation::STR, i>(
|
||||
condition,
|
||||
SingleDataTransfer(instruction)
|
||||
);
|
||||
scheduler.template perform<i>(SingleDataTransfer(instruction));
|
||||
return;
|
||||
}
|
||||
|
||||
// Block data transfer (LDM, STM); cf. p.29.
|
||||
if constexpr (((partial >> 25) & 0b111) == 0b100) {
|
||||
constexpr bool is_ldm = partial & (1 << 20);
|
||||
scheduler.template perform<is_ldm ? Operation::LDM : Operation::STM, i>(
|
||||
condition,
|
||||
BlockDataTransfer(instruction)
|
||||
);
|
||||
scheduler.template perform<i>(BlockDataTransfer(instruction));
|
||||
return;
|
||||
}
|
||||
|
||||
// Branch and branch with link (B, BL); cf. p.15.
|
||||
if constexpr (((partial >> 25) & 0b111) == 0b101) {
|
||||
constexpr bool is_bl = partial & (1 << 24);
|
||||
scheduler.template perform<is_bl ? Operation::BL : Operation::B>(
|
||||
condition,
|
||||
Branch(instruction)
|
||||
);
|
||||
scheduler.template perform<i>(Branch(instruction));
|
||||
return;
|
||||
}
|
||||
|
||||
// Software interreupt; cf. p.35.
|
||||
if constexpr (((partial >> 24) & 0b1111) == 0b1111) {
|
||||
scheduler.software_interrupt(condition);
|
||||
scheduler.software_interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -396,73 +453,69 @@ struct OperationMapper {
|
||||
if constexpr (((partial >> 24) & 0b1111) == 0b1110) {
|
||||
if(instruction & (1 << 4)) {
|
||||
// Register transfer.
|
||||
const auto parameters = CoprocessorRegisterTransfer(instruction);
|
||||
constexpr bool is_mrc = partial & (1 << 20);
|
||||
scheduler.template perform<is_mrc ? Operation::MRC : Operation::MCR, i>(
|
||||
condition,
|
||||
parameters
|
||||
);
|
||||
scheduler.template perform<i>(CoprocessorRegisterTransfer(instruction));
|
||||
} else {
|
||||
// Data operation.
|
||||
const auto parameters = CoprocessorDataOperation(instruction);
|
||||
scheduler.template perform<i>(
|
||||
condition,
|
||||
parameters
|
||||
);
|
||||
scheduler.template perform<i>(CoprocessorDataOperation(instruction));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Coprocessor data transfers; cf. p.39.
|
||||
if constexpr (((partial >> 25) & 0b111) == 0b110) {
|
||||
constexpr bool is_ldc = partial & (1 << 20);
|
||||
scheduler.template perform<is_ldc ? Operation::LDC : Operation::STC, i>(
|
||||
condition,
|
||||
CoprocessorDataTransfer(instruction)
|
||||
);
|
||||
scheduler.template perform<i>(CoprocessorDataTransfer(instruction));
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback position.
|
||||
scheduler.unknown(instruction);
|
||||
scheduler.unknown();
|
||||
}
|
||||
};
|
||||
|
||||
/// A brief documentation of the interface expected by @c dispatch below; will be a concept if/when this project adopts C++20.
|
||||
struct SampleScheduler {
|
||||
// General template arguments:
|
||||
/// @returns @c true if the rest of the instruction should be decoded and supplied
|
||||
/// to the scheduler as defined below; @c false otherwise.
|
||||
bool should_schedule(Condition condition);
|
||||
|
||||
// Template argument:
|
||||
//
|
||||
// (1) Operation, telling the function which operation to perform. Will always be from the subset
|
||||
// implied by the operation category; and
|
||||
// (2) Flags, an opaque type which can be converted into a DataProcessingFlags, MultiplyFlags, etc,
|
||||
// by simply construction, to provide all flags that can be baked into the template parameters.
|
||||
// Flags, an opaque type which can be converted into a DataProcessingFlags, MultiplyFlags, etc,
|
||||
// by simple construction, to provide all flags that can be baked into the template parameters.
|
||||
//
|
||||
// Arguments are ommitted if not relevant.
|
||||
// Function argument:
|
||||
//
|
||||
// Function arguments:
|
||||
//
|
||||
// (1) Condition, indicating the condition code associated with this operation; and
|
||||
// (2) An operation-specific encapsulation of the operation code for decoding of fields that didn't
|
||||
// An operation-specific encapsulation of the operation code for decoding of fields that didn't
|
||||
// fit into the template parameters.
|
||||
template <Operation, Flags> void perform(Condition, DataProcessing);
|
||||
template <Operation, Flags> void perform(Condition, Multiply);
|
||||
template <Operation, Flags> void perform(Condition, SingleDataTransfer);
|
||||
template <Operation, Flags> void perform(Condition, BlockDataTransfer);
|
||||
template <Operation> void perform(Condition, Branch);
|
||||
template <Operation, Flags> void perform(Condition, CoprocessorRegisterTransfer);
|
||||
template <Flags> void perform(Condition, CoprocessorDataOperation);
|
||||
template<Operation, Flags> void perform(Condition, CoprocessorDataTransfer);
|
||||
//
|
||||
// Either or both may be omitted if unnecessary.
|
||||
template <Flags> void perform(DataProcessing);
|
||||
template <Flags> void perform(Multiply);
|
||||
template <Flags> void perform(SingleDataTransfer);
|
||||
template <Flags> void perform(BlockDataTransfer);
|
||||
template <Flags> void perform(Branch);
|
||||
template <Flags> void perform(CoprocessorRegisterTransfer);
|
||||
template <Flags> void perform(CoprocessorDataOperation);
|
||||
template <Flags> void perform(CoprocessorDataTransfer);
|
||||
|
||||
// Irregular operations.
|
||||
void software_interrupt(Condition);
|
||||
void unknown(uint32_t opcode);
|
||||
void software_interrupt();
|
||||
void unknown();
|
||||
};
|
||||
|
||||
/// Decodes @c instruction, making an appropriate call into @c scheduler.
|
||||
///
|
||||
/// In lieue of C++20, see the sample definition of SampleScheduler above for the expected interface.
|
||||
/// In lieu of C++20, see the sample definition of SampleScheduler above for the expected interface.
|
||||
template <typename SchedulerT> void dispatch(uint32_t instruction, SchedulerT &scheduler) {
|
||||
OperationMapper mapper;
|
||||
|
||||
// Test condition.
|
||||
const auto condition = mapper.condition(instruction);
|
||||
if(!scheduler.should_schedule(condition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch body.
|
||||
Reflection::dispatch(mapper, (instruction >> FlagsStartBit) & 0xff, instruction, scheduler);
|
||||
}
|
||||
|
||||
|
271
InstructionSets/ARM/Registers.hpp
Normal file
271
InstructionSets/ARM/Registers.hpp
Normal file
@ -0,0 +1,271 @@
|
||||
//
|
||||
// Status.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/02/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OperationMapper.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace InstructionSet::ARM {
|
||||
|
||||
namespace ConditionCode {
|
||||
|
||||
static constexpr uint32_t Negative = 1 << 31;
|
||||
static constexpr uint32_t Zero = 1 << 30;
|
||||
static constexpr uint32_t Carry = 1 << 29;
|
||||
static constexpr uint32_t Overflow = 1 << 28;
|
||||
static constexpr uint32_t IRQDisable = 1 << 27;
|
||||
static constexpr uint32_t FIQDisable = 1 << 26;
|
||||
static constexpr uint32_t Mode = (1 << 1) | (1 << 0);
|
||||
|
||||
static constexpr uint32_t Address = FIQDisable - Mode - 1;
|
||||
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
User = 0b00,
|
||||
FIQ = 0b01,
|
||||
IRQ = 0b10,
|
||||
Supervisor = 0b11,
|
||||
};
|
||||
|
||||
/// Combines the ARM registers and status flags into a single whole, given that the architecture
|
||||
/// doesn't have the same degree of separation as others.
|
||||
///
|
||||
/// The PC contained here is always taken to be **the address of the current instruction**,
|
||||
/// i.e. disregarding pipeline differences. Appropriate prefetch offsets are left to other code to handle.
|
||||
/// This is to try to keep this structure independent of a specific ARM implementation.
|
||||
struct Registers {
|
||||
public:
|
||||
/// Sets the N and Z flags according to the value of @c result.
|
||||
void set_nz(uint32_t value) {
|
||||
zero_result_ = negative_flag_ = value;
|
||||
}
|
||||
|
||||
/// Sets C if @c value is non-zero; resets it otherwise.
|
||||
void set_c(uint32_t value) {
|
||||
carry_flag_ = value;
|
||||
}
|
||||
|
||||
/// @returns @c 1 if carry is set; @c 0 otherwise.
|
||||
uint32_t c() const {
|
||||
return carry_flag_ ? 1 : 0;
|
||||
}
|
||||
|
||||
/// Sets V if the highest bit of @c value is set; resets it otherwise.
|
||||
void set_v(uint32_t value) {
|
||||
overflow_flag_ = value;
|
||||
}
|
||||
|
||||
/// @returns The full PC + status bits.
|
||||
uint32_t pc_status(uint32_t offset) const {
|
||||
return
|
||||
uint32_t(mode_) |
|
||||
((active[15] + offset) & ConditionCode::Address) |
|
||||
(negative_flag_ & ConditionCode::Negative) |
|
||||
(zero_result_ ? 0 : ConditionCode::Zero) |
|
||||
(carry_flag_ ? ConditionCode::Carry : 0) |
|
||||
((overflow_flag_ >> 3) & ConditionCode::Overflow) |
|
||||
interrupt_flags_;
|
||||
}
|
||||
|
||||
/// Sets status bits only, subject to mode.
|
||||
void set_status(uint32_t status) {
|
||||
// ... in user mode the other flags (I, F, M1, M0) are protected from direct change
|
||||
// but in non-user modes these will also be affected, accepting copies of bits 27, 26,
|
||||
// 1 and 0 of the result respectively.
|
||||
|
||||
negative_flag_ = status;
|
||||
overflow_flag_ = status << 3;
|
||||
carry_flag_ = status & ConditionCode::Carry;
|
||||
zero_result_ = ~status & ConditionCode::Zero;
|
||||
|
||||
if(mode_ != Mode::User) {
|
||||
set_mode(Mode(status & 3));
|
||||
interrupt_flags_ = status & (ConditionCode::IRQDisable | ConditionCode::FIQDisable);
|
||||
}
|
||||
}
|
||||
|
||||
Mode mode() {
|
||||
return mode_;
|
||||
}
|
||||
|
||||
/// Sets a new PC.
|
||||
/// TODO: communicate this onward.
|
||||
void set_pc(uint32_t value) {
|
||||
active[15] = value & ConditionCode::Address;
|
||||
}
|
||||
|
||||
uint32_t pc(uint32_t offset) const {
|
||||
return (active[15] + offset) & ConditionCode::Address;
|
||||
}
|
||||
|
||||
// MARK: - Exceptions.
|
||||
|
||||
enum class Exception {
|
||||
/// Reset line went from high to low.
|
||||
Reset = 0x00,
|
||||
/// Either an undefined instruction or a coprocessor instruction for which no coprocessor was found.
|
||||
UndefinedInstruction = 0x04,
|
||||
/// Code executed a software interrupt.
|
||||
SoftwareInterrupt = 0x08,
|
||||
/// The memory subsystem indicated an abort during prefetch and that instruction has now come to the head of the queue.
|
||||
PrefetchAbort = 0x0c,
|
||||
/// The memory subsystem indicated an abort during an instruction; if it is an LDR or STR then this should be signalled
|
||||
/// before any instruction execution. If it was an LDM then loading stops upon a data abort but both an LDM and STM
|
||||
/// otherwise complete, including pointer writeback.
|
||||
DataAbort = 0x10,
|
||||
/// The first data transfer attempted within an instruction was above address 0x3ff'ffff.
|
||||
Address = 0x14,
|
||||
/// The IRQ line was low at the end of an instruction and ConditionCode::IRQDisable was not set.
|
||||
IRQ = 0x18,
|
||||
/// The FIQ went low at least one cycle ago and ConditionCode::FIQDisable was not set.
|
||||
FIQ = 0x1c,
|
||||
};
|
||||
|
||||
template <Exception exception>
|
||||
void exception() {
|
||||
interrupt_flags_ |= ConditionCode::IRQDisable;
|
||||
if constexpr (exception == Exception::Reset || exception == Exception::FIQ) {
|
||||
interrupt_flags_ |= ConditionCode::FIQDisable;
|
||||
}
|
||||
|
||||
switch(exception) {
|
||||
case Exception::IRQ:
|
||||
set_mode(Mode::IRQ);
|
||||
active[14] = pc(8);
|
||||
break;
|
||||
case Exception::FIQ:
|
||||
set_mode(Mode::FIQ);
|
||||
active[14] = pc(8);
|
||||
break;
|
||||
default:
|
||||
set_mode(Mode::Supervisor);
|
||||
active[14] = pc(4);
|
||||
break;
|
||||
}
|
||||
|
||||
set_pc(uint32_t(exception));
|
||||
}
|
||||
|
||||
// MARK: - Condition tests.
|
||||
|
||||
bool test(Condition condition) {
|
||||
const auto ne = [&]() -> bool {
|
||||
return zero_result_;
|
||||
};
|
||||
const auto cs = [&]() -> bool {
|
||||
return carry_flag_;
|
||||
};
|
||||
const auto mi = [&]() -> bool {
|
||||
return negative_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto vs = [&]() -> bool {
|
||||
return overflow_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto hi = [&]() -> bool {
|
||||
return carry_flag_ && zero_result_;
|
||||
};
|
||||
const auto lt = [&]() -> bool {
|
||||
return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative;
|
||||
};
|
||||
const auto le = [&]() -> bool {
|
||||
return !zero_result_ || lt();
|
||||
};
|
||||
|
||||
switch(condition) {
|
||||
case Condition::EQ: return !ne();
|
||||
case Condition::NE: return ne();
|
||||
case Condition::CS: return cs();
|
||||
case Condition::CC: return !cs();
|
||||
case Condition::MI: return mi();
|
||||
case Condition::PL: return !mi();
|
||||
case Condition::VS: return vs();
|
||||
case Condition::VC: return !vs();
|
||||
|
||||
case Condition::HI: return hi();
|
||||
case Condition::LS: return !hi();
|
||||
case Condition::GE: return !lt();
|
||||
case Condition::LT: return lt();
|
||||
case Condition::GT: return !le();
|
||||
case Condition::LE: return le();
|
||||
|
||||
case Condition::AL: return true;
|
||||
case Condition::NV: return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<uint32_t, 16> active;
|
||||
|
||||
void set_mode(Mode target_mode) {
|
||||
if(mode_ == target_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For outgoing modes other than FIQ, only save the final two registers for now;
|
||||
// if the incoming mode is FIQ then the other five will be saved in the next switch.
|
||||
switch(mode_) {
|
||||
case Mode::FIQ:
|
||||
std::copy(active.begin() + 8, active.begin() + 15, fiq_registers_.begin());
|
||||
break;
|
||||
case Mode::User:
|
||||
std::copy(active.begin() + 13, active.begin() + 15, user_registers_.begin() + 5);
|
||||
break;
|
||||
case Mode::Supervisor:
|
||||
std::copy(active.begin() + 13, active.begin() + 15, supervisor_registers_.begin());
|
||||
break;
|
||||
case Mode::IRQ:
|
||||
std::copy(active.begin() + 13, active.begin() + 15, irq_registers_.begin());
|
||||
break;
|
||||
}
|
||||
|
||||
// For all modes except FIQ: restore the final two registers to their appropriate values.
|
||||
// For FIQ: save an additional five, then overwrite seven.
|
||||
switch(target_mode) {
|
||||
case Mode::FIQ:
|
||||
std::copy(active.begin() + 8, active.begin() + 13, user_registers_.begin());
|
||||
std::copy(fiq_registers_.begin(), fiq_registers_.end(), active.begin() + 8);
|
||||
break;
|
||||
case Mode::User:
|
||||
std::copy(user_registers_.begin() + 5, user_registers_.end(), active.begin() + 13);
|
||||
break;
|
||||
case Mode::Supervisor:
|
||||
std::copy(supervisor_registers_.begin(), supervisor_registers_.end(), active.begin() + 13);
|
||||
break;
|
||||
case Mode::IRQ:
|
||||
std::copy(irq_registers_.begin(), irq_registers_.end(), active.begin() + 13);
|
||||
break;
|
||||
}
|
||||
|
||||
// If FIQ is outgoing then there's another five registers to restore.
|
||||
if(mode_ == Mode::FIQ) {
|
||||
std::copy(user_registers_.begin(), user_registers_.begin() + 5, active.begin() + 8);
|
||||
}
|
||||
|
||||
mode_ = target_mode;
|
||||
}
|
||||
|
||||
private:
|
||||
Mode mode_ = Mode::Supervisor;
|
||||
|
||||
uint32_t zero_result_ = 0;
|
||||
uint32_t negative_flag_ = 0;
|
||||
uint32_t interrupt_flags_ = 0;
|
||||
uint32_t carry_flag_ = 0;
|
||||
uint32_t overflow_flag_ = 0;
|
||||
|
||||
// Various shadow registers.
|
||||
std::array<uint32_t, 7> user_registers_;
|
||||
std::array<uint32_t, 7> fiq_registers_;
|
||||
std::array<uint32_t, 2> irq_registers_;
|
||||
std::array<uint32_t, 2> supervisor_registers_;
|
||||
};
|
||||
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
//
|
||||
// Status.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/02/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OperationMapper.hpp"
|
||||
|
||||
namespace InstructionSet::ARM {
|
||||
|
||||
namespace ConditionCode {
|
||||
|
||||
static constexpr uint32_t Negative = 1 << 31;
|
||||
static constexpr uint32_t Zero = 1 << 30;
|
||||
static constexpr uint32_t Carry = 1 << 29;
|
||||
static constexpr uint32_t Overflow = 1 << 28;
|
||||
static constexpr uint32_t IRQDisable = 1 << 27;
|
||||
static constexpr uint32_t FIQDisable = 1 << 26;
|
||||
static constexpr uint32_t Mode = (1 << 1) | (1 << 0);
|
||||
|
||||
static constexpr uint32_t Address = FIQDisable - Mode - 1;
|
||||
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
User = 0b00,
|
||||
FIQ = 0b01,
|
||||
IRQ = 0b10,
|
||||
Supervisor = 0b11,
|
||||
};
|
||||
|
||||
struct Status {
|
||||
public:
|
||||
/// Sets the N and Z flags according to the value of @c result.
|
||||
void set_nz(uint32_t value) {
|
||||
zero_result_ = negative_flag_ = value;
|
||||
}
|
||||
|
||||
/// Sets C if @c value is non-zero; resets it otherwise.
|
||||
void set_c(uint32_t value) {
|
||||
carry_flag_ = value;
|
||||
}
|
||||
|
||||
/// Sets V if the highest bit of @c value is set; resets it otherwise.
|
||||
void set_v(uint32_t value) {
|
||||
overflow_flag_ = value;
|
||||
}
|
||||
|
||||
/// @returns The program counter address only, optionally with a post-increment.
|
||||
template <bool increment>
|
||||
uint32_t pc() {
|
||||
const uint32_t result = pc_;
|
||||
if constexpr (increment) {
|
||||
pc_ = (pc_ + 4) & ConditionCode::Address;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void begin_irq() { interrupt_flags_ |= ConditionCode::IRQDisable; }
|
||||
void begin_fiq() { interrupt_flags_ |= ConditionCode::FIQDisable; }
|
||||
|
||||
/// @returns The full PC + status bits.
|
||||
uint32_t get() const {
|
||||
return
|
||||
uint32_t(mode_) |
|
||||
pc_ |
|
||||
(negative_flag_ & ConditionCode::Negative) |
|
||||
(zero_result_ ? 0 : ConditionCode::Zero) |
|
||||
(carry_flag_ ? ConditionCode::Carry : 0) |
|
||||
((overflow_flag_ >> 3) & ConditionCode::Overflow) |
|
||||
interrupt_flags_;
|
||||
}
|
||||
|
||||
bool test(Condition condition) {
|
||||
const auto ne = [&]() -> bool {
|
||||
return zero_result_;
|
||||
};
|
||||
const auto cs = [&]() -> bool {
|
||||
return carry_flag_;
|
||||
};
|
||||
const auto mi = [&]() -> bool {
|
||||
return negative_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto vs = [&]() -> bool {
|
||||
return overflow_flag_ & ConditionCode::Negative;
|
||||
};
|
||||
const auto hi = [&]() -> bool {
|
||||
return carry_flag_ && zero_result_;
|
||||
};
|
||||
const auto lt = [&]() -> bool {
|
||||
return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative;
|
||||
};
|
||||
const auto le = [&]() -> bool {
|
||||
return !zero_result_ || lt();
|
||||
};
|
||||
|
||||
switch(condition) {
|
||||
case Condition::EQ: return !ne();
|
||||
case Condition::NE: return ne();
|
||||
case Condition::CS: return cs();
|
||||
case Condition::CC: return !cs();
|
||||
case Condition::MI: return mi();
|
||||
case Condition::PL: return !mi();
|
||||
case Condition::VS: return vs();
|
||||
case Condition::VC: return !vs();
|
||||
|
||||
case Condition::HI: return hi();
|
||||
case Condition::LS: return !hi();
|
||||
case Condition::GE: return !lt();
|
||||
case Condition::LT: return lt();
|
||||
case Condition::GT: return !le();
|
||||
case Condition::LE: return le();
|
||||
|
||||
case Condition::AL: return true;
|
||||
case Condition::NV: return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t pc_ = 0;
|
||||
Mode mode_ = Mode::Supervisor;
|
||||
|
||||
uint32_t zero_result_ = 0;
|
||||
uint32_t negative_flag_ = 0;
|
||||
uint32_t interrupt_flags_ = 0;
|
||||
uint32_t carry_flag_ = 0;
|
||||
uint32_t overflow_flag_ = 0;
|
||||
};
|
||||
|
||||
}
|
@ -1335,7 +1335,9 @@
|
||||
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
|
||||
4B2005402B804AA300420C5C /* OperationMapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = OperationMapper.hpp; sourceTree = "<group>"; };
|
||||
4B2005422B804D6400420C5C /* ARMDecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ARMDecoderTests.mm; sourceTree = "<group>"; };
|
||||
4B2005462B8BD7A500420C5C /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = "<group>"; };
|
||||
4B2005462B8BD7A500420C5C /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = "<group>"; };
|
||||
4B2005472B8FB13D00420C5C /* BarrelShifter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BarrelShifter.hpp; sourceTree = "<group>"; };
|
||||
4B2005482B92697500420C5C /* Executor.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Executor.hpp; sourceTree = "<group>"; };
|
||||
4B2130E0273A7A0A008A77B4 /* Audio.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cpp; sourceTree = "<group>"; };
|
||||
4B2130E1273A7A0A008A77B4 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
|
||||
4B228CD424D773B30077EF25 /* CSScanTarget.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSScanTarget.mm; sourceTree = "<group>"; };
|
||||
@ -2759,8 +2761,10 @@
|
||||
4B20053D2B804A4F00420C5C /* ARM */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2005472B8FB13D00420C5C /* BarrelShifter.hpp */,
|
||||
4B2005482B92697500420C5C /* Executor.hpp */,
|
||||
4B2005402B804AA300420C5C /* OperationMapper.hpp */,
|
||||
4B2005462B8BD7A500420C5C /* Status.hpp */,
|
||||
4B2005462B8BD7A500420C5C /* Registers.hpp */,
|
||||
);
|
||||
path = ARM;
|
||||
sourceTree = "<group>";
|
||||
|
@ -8,28 +8,30 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "../../../InstructionSets/ARM/OperationMapper.hpp"
|
||||
#include "../../../InstructionSets/ARM/Status.hpp"
|
||||
#include "../../../InstructionSets/ARM/Executor.hpp"
|
||||
|
||||
using namespace InstructionSet::ARM;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Scheduler {
|
||||
template <Operation, Flags> void perform(Condition, DataProcessing) {}
|
||||
template <Operation, Flags> void perform(Condition, Multiply) {}
|
||||
template <Operation, Flags> void perform(Condition, SingleDataTransfer) {}
|
||||
template <Operation, Flags> void perform(Condition, BlockDataTransfer) {}
|
||||
|
||||
template <Operation op> void perform(Condition condition, Branch branch) {
|
||||
printf("Branch %sif %d; add %08x\n", op == Operation::BL ? "with link " : "", int(condition), branch.offset());
|
||||
struct Memory {
|
||||
template <typename IntT>
|
||||
bool write(uint32_t address, IntT source, Mode mode, bool trans) {
|
||||
(void)address;
|
||||
(void)source;
|
||||
(void)mode;
|
||||
(void)trans;
|
||||
return true;
|
||||
}
|
||||
template <Operation, Flags> void perform(Condition, CoprocessorRegisterTransfer) {}
|
||||
template <Flags> void perform(Condition, CoprocessorDataOperation) {}
|
||||
template<Operation, Flags> void perform(Condition, CoprocessorDataTransfer) {}
|
||||
|
||||
void software_interrupt(Condition) {}
|
||||
void unknown(uint32_t) {}
|
||||
template <typename IntT>
|
||||
bool read(uint32_t address, IntT &source, Mode mode, bool trans) {
|
||||
(void)address;
|
||||
(void)source;
|
||||
(void)mode;
|
||||
(void)trans;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@ -40,11 +42,12 @@ struct Scheduler {
|
||||
@implementation ARMDecoderTests
|
||||
|
||||
- (void)testXYX {
|
||||
Scheduler scheduler;
|
||||
Executor<Memory> scheduler;
|
||||
|
||||
for(int c = 0; c < 65536; c++) {
|
||||
InstructionSet::ARM::dispatch(c << 16, scheduler);
|
||||
}
|
||||
InstructionSet::ARM::dispatch(0xEAE06900, scheduler);
|
||||
// const auto intr = Instruction<Model::ARM2>(1);
|
||||
// NSLog(@"%d", intr.operation());
|
||||
}
|
||||
|
||||
@end
|
||||
|
Loading…
Reference in New Issue
Block a user