1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-01 22:41:32 +00:00

Merge pull request #1343 from TomHarte/ARM2Ops

Attempt an implementation of the ARM2 instruction set.
This commit is contained in:
Thomas Harte 2024-03-01 15:20:28 -05:00 committed by GitHub
commit 7b28b3d634
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1177 additions and 299 deletions

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

View 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);
}
}

View File

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

View 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_;
};
}

View File

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

View File

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

View File

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