mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-24 10:29:16 +00:00
Merge pull request #1350 from TomHarte/ArchimedesROM
Add RISC OS catalogue entry; do some basic ARM debugging.
This commit is contained in:
commit
7a5ed6c427
InstructionSets/ARM
Machines/Utility
OSBindings/Mac/Clock SignalTests
ROMImages/Archimedes
@ -18,7 +18,7 @@ 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>
|
||||
template <Model model, typename MemoryT>
|
||||
struct Executor {
|
||||
bool should_schedule(Condition condition) {
|
||||
return registers_.test(condition);
|
||||
@ -49,7 +49,7 @@ struct Executor {
|
||||
// PC will be 8 bytes ahead when used as Rs."
|
||||
shift_amount =
|
||||
fields.shift_register() == 15 ?
|
||||
registers_.pc(8) :
|
||||
registers_.pc(4) :
|
||||
registers_.active[fields.shift_register()];
|
||||
|
||||
// A register shift amount of 0 has a different meaning than an in-instruction
|
||||
@ -84,7 +84,7 @@ struct Executor {
|
||||
// when used as Rn or Rm."
|
||||
const uint32_t operand1 =
|
||||
(fields.operand1() == 15) ?
|
||||
registers_.pc(shift_by_register ? 12 : 8) :
|
||||
registers_.pc(shift_by_register ? 8 : 4) :
|
||||
registers_.active[fields.operand1()];
|
||||
|
||||
uint32_t operand2;
|
||||
@ -100,7 +100,7 @@ struct Executor {
|
||||
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);
|
||||
operand2 = decode_shift<true, shift_sets_carry>(fields, rotate_carry, shift_by_register ? 8 : 4);
|
||||
}
|
||||
|
||||
// Perform the data processing operation.
|
||||
@ -216,11 +216,11 @@ struct Executor {
|
||||
// * 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 multiplicand = fields.multiplicand() == 15 ? registers_.pc(4) : registers_.active[fields.multiplicand()];
|
||||
const uint32_t multiplier = fields.multiplier() == 15 ? registers_.pc_status(4) : 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()]);
|
||||
(fields.multiplicand() == 15 ? registers_.pc_status(8) : registers_.active[fields.accumulator()]);
|
||||
|
||||
const uint32_t result = multiplicand * multiplier + accumulator;
|
||||
|
||||
@ -238,9 +238,9 @@ struct Executor {
|
||||
constexpr BranchFlags flags(f);
|
||||
|
||||
if constexpr (flags.operation() == BranchFlags::Operation::BL) {
|
||||
registers_.active[14] = registers_.pc(4);
|
||||
registers_.active[14] = registers_.pc(0);
|
||||
}
|
||||
registers_.set_pc(registers_.pc(8) + branch.offset());
|
||||
registers_.set_pc(registers_.pc(4) + branch.offset());
|
||||
}
|
||||
|
||||
template <Flags f> void perform(SingleDataTransfer transfer) {
|
||||
@ -255,13 +255,13 @@ struct Executor {
|
||||
// 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);
|
||||
offset = decode_shift<false, false>(transfer, carry, 4);
|
||||
}
|
||||
|
||||
// Obtain base address.
|
||||
uint32_t address =
|
||||
transfer.base() == 15 ?
|
||||
registers_.pc(8) :
|
||||
registers_.pc(4) :
|
||||
registers_.active[transfer.base()];
|
||||
|
||||
// Determine what the address will be after offsetting.
|
||||
@ -287,15 +287,15 @@ struct Executor {
|
||||
if constexpr (flags.operation() == SingleDataTransferFlags::Operation::STR) {
|
||||
const uint32_t source =
|
||||
transfer.source() == 15 ?
|
||||
registers_.pc_status(12) :
|
||||
registers_.pc_status(8) :
|
||||
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);
|
||||
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);
|
||||
did_write = bus.template write<uint32_t>(address, source, registers_.mode(), trans);
|
||||
}
|
||||
|
||||
if(!did_write) {
|
||||
@ -308,10 +308,10 @@ struct Executor {
|
||||
|
||||
if constexpr (flags.transfer_byte()) {
|
||||
uint8_t target;
|
||||
did_read = bus_.template read<uint8_t>(address, target, registers_.mode(), trans);
|
||||
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);
|
||||
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."
|
||||
@ -354,7 +354,7 @@ struct Executor {
|
||||
// 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_.pc_status(4) :
|
||||
registers_.active[transfer.base()];
|
||||
const uint32_t initial_address = address;
|
||||
|
||||
@ -422,7 +422,7 @@ struct Executor {
|
||||
// "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);
|
||||
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
|
||||
@ -433,7 +433,7 @@ struct Executor {
|
||||
// * 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);
|
||||
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
|
||||
@ -458,7 +458,7 @@ struct Executor {
|
||||
} 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);
|
||||
bus.template read<uint32_t>(address, throwaway, registers_.mode(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,7 +501,7 @@ struct Executor {
|
||||
if(list & (1 << 15)) {
|
||||
uint32_t value;
|
||||
if constexpr (flags.operation() == BlockDataTransferFlags::Operation::STM) {
|
||||
value = registers_.pc_status(12);
|
||||
value = registers_.pc_status(8);
|
||||
access(value);
|
||||
} else {
|
||||
access(value);
|
||||
@ -544,22 +544,36 @@ struct Executor {
|
||||
registers_.exception<Registers::Exception::UndefinedInstruction>();
|
||||
}
|
||||
|
||||
MemoryT bus_;
|
||||
MemoryT bus;
|
||||
|
||||
/// Sets the expected address of the instruction after whichever is about to be executed.
|
||||
/// So it's PC+4 compared to most other systems.
|
||||
void set_pc(uint32_t pc) {
|
||||
registers_.set_pc(pc);
|
||||
}
|
||||
|
||||
/// @returns The address of the instruction that should be fetched next. So as execution of each instruction
|
||||
/// begins, this will be +4 from the instruction being executed; at the end of the instruction it'll either still be +4
|
||||
/// or else be some other address if a branch or exception has occurred.
|
||||
uint32_t pc() const {
|
||||
return registers_.pc(0);
|
||||
}
|
||||
|
||||
/// @returns The current processor mode.
|
||||
Mode mode() const {
|
||||
return registers_.mode();
|
||||
}
|
||||
|
||||
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);
|
||||
/// executor's program counter appropriately.
|
||||
template <Model model, typename MemoryT>
|
||||
void execute(uint32_t instruction, Executor<model, MemoryT> &executor) {
|
||||
executor.set_pc(executor.pc() + 4);
|
||||
dispatch<model>(instruction, executor);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
namespace InstructionSet::ARM {
|
||||
|
||||
enum class Model {
|
||||
ARM2,
|
||||
ARMv2,
|
||||
};
|
||||
|
||||
enum class Condition {
|
||||
@ -142,7 +142,7 @@ struct DataProcessingFlags {
|
||||
|
||||
/// @returns The operation to apply.
|
||||
constexpr DataProcessingOperation operation() const {
|
||||
return DataProcessingOperation((flags_ >> 21) & 0xf);
|
||||
return DataProcessingOperation((flags_ >> (21 - FlagsStartBit)) & 0xf);
|
||||
}
|
||||
|
||||
/// @returns @c true if operand 2 is defined by the @c rotate() and @c immediate() fields;
|
||||
@ -232,7 +232,7 @@ struct SingleDataTransferFlags {
|
||||
return flag_bit<20>(flags_) ? Operation::LDR : Operation::STR;
|
||||
}
|
||||
|
||||
constexpr bool offset_is_immediate() const { return flag_bit<25>(flags_); }
|
||||
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_); }
|
||||
@ -389,6 +389,7 @@ private:
|
||||
};
|
||||
|
||||
/// Operation mapper; use the free function @c dispatch as defined below.
|
||||
template <Model>
|
||||
struct OperationMapper {
|
||||
static Condition condition(uint32_t instruction) {
|
||||
return Condition(instruction >> 28);
|
||||
@ -506,8 +507,8 @@ struct SampleScheduler {
|
||||
/// Decodes @c instruction, making an appropriate call into @c scheduler.
|
||||
///
|
||||
/// 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;
|
||||
template <Model model, typename SchedulerT> void dispatch(uint32_t instruction, SchedulerT &scheduler) {
|
||||
OperationMapper<model> mapper;
|
||||
|
||||
// Test condition.
|
||||
const auto condition = mapper.condition(instruction);
|
||||
|
@ -39,8 +39,10 @@ enum class Mode {
|
||||
/// 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.
|
||||
/// The PC contained here is always taken to be **the address of the current instruction + 4**,
|
||||
/// i.e. whatever should be executed next, 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:
|
||||
@ -93,7 +95,7 @@ struct Registers {
|
||||
}
|
||||
}
|
||||
|
||||
Mode mode() {
|
||||
Mode mode() const {
|
||||
return mode_;
|
||||
}
|
||||
|
||||
|
@ -540,6 +540,10 @@ Description::Description(Name name) {
|
||||
*this = Description(name, "Electron", "the Electron MOS ROM v1.00", "os.rom", 16*1024, 0xbf63fb1fu);
|
||||
break;
|
||||
|
||||
case Name::AcornRISCOS319:
|
||||
*this = Description(name, "Archimedes", "RISC OS v3.19", "ROM319", 2*1024*1024, 0x00c7a3d3u);
|
||||
break;
|
||||
|
||||
case Name::MasterSystemJapaneseBIOS: *this = Description(name, "MasterSystem", "the Japanese Master System BIOS", "japanese-bios.sms", 8*1024, 0x48d44a13u); break;
|
||||
case Name::MasterSystemWesternBIOS: *this = Description(name, "MasterSystem", "the European/US Master System BIOS", "bios.sms", 8*1024, 0x0072ed54u); break;
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace ROM {
|
||||
enum Name {
|
||||
None,
|
||||
|
||||
// Acorn.
|
||||
// Acorn Electron.
|
||||
AcornBASICII,
|
||||
AcornElectronMOS100,
|
||||
PRESADFSSlot1,
|
||||
@ -31,6 +31,9 @@ enum Name {
|
||||
PRESAdvancedPlus6,
|
||||
Acorn1770DFS,
|
||||
|
||||
// Acorn Archimedes.
|
||||
AcornRISCOS319,
|
||||
|
||||
// Amiga.
|
||||
AmigaKickstart10,
|
||||
AmigaKickstart11,
|
||||
|
@ -9,29 +9,44 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "../../../InstructionSets/ARM/Executor.hpp"
|
||||
#include "CSROMFetcher.hpp"
|
||||
|
||||
using namespace InstructionSet::ARM;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Memory {
|
||||
std::vector<uint8_t> rom;
|
||||
|
||||
template <typename IntT>
|
||||
bool write(uint32_t address, IntT source, Mode mode, bool trans) {
|
||||
(void)address;
|
||||
(void)source;
|
||||
(void)mode;
|
||||
(void)trans;
|
||||
printf("W of %08x to %08x [%lu]\n", source, address, sizeof(IntT));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
bool read(uint32_t address, IntT &source, Mode mode, bool trans) {
|
||||
(void)address;
|
||||
(void)source;
|
||||
if(address > 0x3800000) {
|
||||
has_moved_rom_ = true;
|
||||
source = *reinterpret_cast<const IntT *>(&rom[address - 0x3800000]);
|
||||
} else if(!has_moved_rom_) {
|
||||
// TODO: this is true only very transiently.
|
||||
source = *reinterpret_cast<const IntT *>(&rom[address]);
|
||||
} else {
|
||||
printf("Unknown read from %08x [%lu]\n", address, sizeof(IntT));
|
||||
}
|
||||
|
||||
(void)mode;
|
||||
(void)trans;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool has_moved_rom_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
@ -41,15 +56,6 @@ struct Memory {
|
||||
|
||||
@implementation ARMDecoderTests
|
||||
|
||||
//- (void)testXYX {
|
||||
// Executor<Memory> scheduler;
|
||||
//
|
||||
// for(int c = 0; c < 65536; c++) {
|
||||
// InstructionSet::ARM::dispatch(c << 16, scheduler);
|
||||
// }
|
||||
// InstructionSet::ARM::dispatch(0xEAE06900, scheduler);
|
||||
//}
|
||||
|
||||
- (void)testBarrelShifterLogicalLeft {
|
||||
uint32_t value;
|
||||
uint32_t carry;
|
||||
@ -195,4 +201,22 @@ struct Memory {
|
||||
XCTAssertEqual(carry, 0);
|
||||
}
|
||||
|
||||
// TODO: turn the below into a trace-driven test case.
|
||||
/*- (void)testROM319 {
|
||||
constexpr ROM::Name rom_name = ROM::Name::AcornRISCOS319;
|
||||
ROM::Request request(rom_name);
|
||||
const auto roms = CSROMFetcher()(request);
|
||||
|
||||
Executor<Model::ARMv2, Memory> executor;
|
||||
executor.bus.rom = roms.find(rom_name)->second;
|
||||
|
||||
for(int c = 0; c < 1000; c++) {
|
||||
uint32_t instruction;
|
||||
executor.bus.read(executor.pc(), instruction, executor.mode(), false);
|
||||
|
||||
printf("%08x: %08x\n", executor.pc(), instruction);
|
||||
execute<Model::ARMv2>(instruction, executor);
|
||||
}
|
||||
}*/
|
||||
|
||||
@end
|
||||
|
5
ROMImages/Archimedes/readme.txt
Normal file
5
ROMImages/Archimedes/readme.txt
Normal file
@ -0,0 +1,5 @@
|
||||
ROM files would ordinarily go here; RISC OS remains a commercial product so these are not included with the emulator.
|
||||
|
||||
Expected files:
|
||||
|
||||
ROM319 — the RISC OS 3.19 ROM.
|
Loading…
x
Reference in New Issue
Block a user