From 6b073c60672acb060950cea1189a918123f3bb5d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2022 15:10:54 -0400 Subject: [PATCH] Attempt to round out addressing modes, shift to a header, as per templating on BusHandler. --- InstructionSets/M68k/Executor.cpp | 179 ----------- InstructionSets/M68k/Executor.hpp | 11 + .../Implementation/ExecutorImplementation.hpp | 282 ++++++++++++++++++ InstructionSets/M68k/Instruction.hpp | 12 +- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 5 files changed, 301 insertions(+), 185 deletions(-) create mode 100644 InstructionSets/M68k/Implementation/ExecutorImplementation.hpp diff --git a/InstructionSets/M68k/Executor.cpp b/InstructionSets/M68k/Executor.cpp index f0f9521d4..6c15fd977 100644 --- a/InstructionSets/M68k/Executor.cpp +++ b/InstructionSets/M68k/Executor.cpp @@ -8,182 +8,3 @@ #include "Executor.hpp" -#include "Perform.hpp" - -#include - -using namespace InstructionSet::M68k; - -template -Executor::Executor(BusHandler &handler) : bus_handler_(handler) { - reset(); -} - -template -void Executor::reset() { - // Establish: supervisor state, all interrupts blocked. - status_.set_status(0b0010'0011'1000'0000); - - // Seed stack pointer and program counter. - data_[7] = bus_handler_.template read(0); - program_counter_.l = bus_handler_.template read(4); -} - -template -void Executor::read(DataSize size, uint32_t address, CPU::SlicedInt32 &value) { - switch(size) { - case DataSize::Byte: - value.b = bus_handler_.template read(address); - break; - case DataSize::Word: - value.w = bus_handler_.template read(address); - break; - case DataSize::LongWord: - value.l = bus_handler_.template read(address); - break; - } -} - -template -void Executor::write(DataSize size, uint32_t address, CPU::SlicedInt32 value) { - switch(size) { - case DataSize::Byte: - bus_handler_.template write(address, value.b); - break; - case DataSize::Word: - bus_handler_.template write(address, value.w); - break; - case DataSize::LongWord: - bus_handler_.template write(address, value.l); - break; - } -} - - -template -typename Executor::EffectiveAddress Executor::calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index) { - EffectiveAddress ea; - switch(instruction.mode(index)) { - case AddressingMode::None: - // Permit an uninitialised effective address to be returned; - // this value shouldn't be used. - break; - - // - // Operands that don't have effective addresses, which are returned as values. - // - case AddressingMode::DataRegisterDirect: - ea.value.l = data_[instruction.reg(index)]; - ea.is_address = false; - break; - case AddressingMode::AddressRegisterDirect: - ea.value.l = address_[instruction.reg(index)]; - ea.is_address = false; - break; - case AddressingMode::Quick: - ea.value.l = quick(instruction.operation, opcode); - ea.is_address = false; - break; - case AddressingMode::ImmediateData: - read(instruction.size(), program_counter_.l, ea.value.l); - program_counter_.l += (instruction.size() == DataSize::LongWord) ? 4 : 2; - ea.is_address = false; - break; - - // - // Operands that are effective addresses. - // - - default: - // TODO. - assert(false); - break; - } - - return ea; -} - - -template -void Executor::run_for_instructions(int count) { - while(count--) { - // TODO: check interrupt level, trace flag. - - // Read the next instruction. - const auto instruction_address = program_counter_.l; - const uint16_t opcode = bus_handler_.template read(program_counter_.l); - const Preinstruction instruction = decoder_.decode(opcode); - program_counter_.l += 2; - - // TODO: check privilege level. - - // Temporary storage. - CPU::SlicedInt32 operand_[2]; - EffectiveAddress effective_address_[2]; - - // Calculate effective addresses; copy 'addresses' into the - // operands by default both: (i) because they might be values, - // rather than addresses; and (ii) then they'll be there for use - // by LEA and PEA. - // - // TODO: this work should be performed by a full Decoder, so that it can be cached. - effective_address_[0] = calculate_effective_address(instruction, opcode, 0); - effective_address_[1] = calculate_effective_address(instruction, opcode, 1); - operand_[0] = effective_address_[0].value; - operand_[1] = effective_address_[1].value; - - // Obtain the appropriate sequence. - // - // TODO: make a decision about whether this goes into a fully-decoded Instruction. - Sequence sequence(instruction.operation); - - // Perform it. - while(!sequence.empty()) { - const auto step = sequence.pop_front(); - - switch(step) { - default: assert(false); // i.e. TODO - - case Step::FetchOp1: - case Step::FetchOp2: { - const auto index = int(step) & 1; - - // If the operand wasn't indirect, it's already fetched. - if(!effective_address_[index].is_address) continue; - - // TODO: potential bus alignment exception. - read(instruction.size(), effective_address_[index].value, operand_[index]); - } break; - - case Step::Perform: - perform(instruction, operand_[0], operand_[1], status_, this); - break; - - case Step::StoreOp1: - case Step::StoreOp2: { - const auto index = int(step) & 1; - - // If the operand wasn't indirect, it's already fetched. - if(!effective_address_[index].is_address) { - // This must be either address or data register indirect. - assert( - instruction.mode(index) == AddressingMode::DataRegisterDirect || - instruction.mode(index) == AddressingMode::AddressRegisterDirect); - - // TODO: is it worth holding registers as a single block to avoid this conditional? - if(instruction.mode(index) == AddressingMode::DataRegisterDirect) { - data_[instruction.reg(index)] = operand_[index]; - } else { - address_[instruction.reg(index)] = operand_[index]; - } - - break; - } - - // TODO: potential bus alignment exception. - write(instruction.size(), effective_address_[index].value, operand_[index]); - } break; - } - } - } -} diff --git a/InstructionSets/M68k/Executor.hpp b/InstructionSets/M68k/Executor.hpp index 6bf5a4bca..9655c801e 100644 --- a/InstructionSets/M68k/Executor.hpp +++ b/InstructionSets/M68k/Executor.hpp @@ -50,15 +50,26 @@ template class Executor { void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value); void write(DataSize size, uint32_t address, CPU::SlicedInt32 value); + template IntT read_pc(); + uint32_t index_8bitdisplacement(); // Processor state. Status status_; CPU::SlicedInt32 program_counter_; CPU::SlicedInt32 data_[8], address_[8]; CPU::SlicedInt32 stack_pointers_[2]; + uint32_t instruction_address_; + + // A lookup table to ensure that A7 is adjusted by 2 rather than 1 in + // postincrement and predecrement mode. + static constexpr uint32_t byte_increments[] = { + 1, 1, 1, 1, 1, 1, 1, 2 + }; }; } } +#include "Implementation/ExecutorImplementation.hpp" + #endif /* InstructionSets_M68k_Executor_hpp */ diff --git a/InstructionSets/M68k/Implementation/ExecutorImplementation.hpp b/InstructionSets/M68k/Implementation/ExecutorImplementation.hpp new file mode 100644 index 000000000..a8b0f317a --- /dev/null +++ b/InstructionSets/M68k/Implementation/ExecutorImplementation.hpp @@ -0,0 +1,282 @@ +// +// ExecutorImplementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/05/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_M68k_ExecutorImplementation_hpp +#define InstructionSets_M68k_ExecutorImplementation_hpp + +#include "Perform.hpp" +#include + +namespace InstructionSet { +namespace M68k { + +template +Executor::Executor(BusHandler &handler) : bus_handler_(handler) { + reset(); +} + +template +void Executor::reset() { + // Establish: supervisor state, all interrupts blocked. + status_.set_status(0b0010'0011'1000'0000); + + // Seed stack pointer and program counter. + data_[7] = bus_handler_.template read(0); + program_counter_.l = bus_handler_.template read(4); +} + +template +void Executor::read(DataSize size, uint32_t address, CPU::SlicedInt32 &value) { + switch(size) { + case DataSize::Byte: + value.b = bus_handler_.template read(address); + break; + case DataSize::Word: + value.w = bus_handler_.template read(address); + break; + case DataSize::LongWord: + value.l = bus_handler_.template read(address); + break; + } +} + +template +void Executor::write(DataSize size, uint32_t address, CPU::SlicedInt32 value) { + switch(size) { + case DataSize::Byte: + bus_handler_.template write(address, value.b); + break; + case DataSize::Word: + bus_handler_.template write(address, value.w); + break; + case DataSize::LongWord: + bus_handler_.template write(address, value.l); + break; + } +} + +template +template IntT Executor::read_pc() { + const IntT result = bus_handler_.template read(program_counter_.l); + + if constexpr (sizeof(IntT) == 4) { + program_counter_.l += 4; + } else { + program_counter_.l += 2; + } + + return result; +} + +template +uint32_t Executor::index_8bitdisplacement() { + // TODO: if not a 68000, check bit 8 for whether this should be a full extension word; + // also include the scale field even if not. + const auto extension = read_pc(); + const auto offset = int8_t(extension); + const int register_index = (extension >> 11) & 7; + const uint32_t displacement = (extension & 0x8000) ? address_[register_index].l : data_[register_index].l; + return offset + (extension & 0x800) ? displacement : uint16_t(displacement); +} + +template +typename Executor::EffectiveAddress Executor::calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index) { + EffectiveAddress ea; + + switch(instruction.mode(index)) { + case AddressingMode::None: + // Permit an uninitialised effective address to be returned; + // this value shouldn't be used. + break; + + // + // Operands that don't have effective addresses, which are returned as values. + // + case AddressingMode::DataRegisterDirect: + ea.value.l = data_[instruction.reg(index)]; + ea.is_address = false; + break; + case AddressingMode::AddressRegisterDirect: + ea.value.l = address_[instruction.reg(index)]; + ea.is_address = false; + break; + case AddressingMode::Quick: + ea.value.l = quick(instruction.operation, opcode); + ea.is_address = false; + break; + case AddressingMode::ImmediateData: + read(instruction.size(), program_counter_.l, ea.value.l); + program_counter_.l += (instruction.size() == DataSize::LongWord) ? 4 : 2; + ea.is_address = false; + break; + + // + // Absolute addresses. + // + case AddressingMode::AbsoluteShort: + ea.value.l = int16_t(read_pc()); + ea.is_address = true; + break; + case AddressingMode::AbsoluteLong: + ea.value.l = read_pc(); + ea.is_address = true; + break; + + // + // Address register indirects. + // + case AddressingMode::AddressRegisterIndirect: + ea.value.l = address_[instruction.reg(index)]; + ea.is_address = true; + break; + case AddressingMode::AddressRegisterIndirectWithPostincrement: { + const auto reg = instruction.reg(index); + + ea.value.l = address_[reg]; + ea.is_address = true; + + switch(instruction.size()) { + case DataSize::Byte: address_[reg] += byte_increments[reg]; break; + case DataSize::Word: address_[reg] += 2; break; + case DataSize::LongWord: address_[reg] += 4; break; + } + } break; + case AddressingMode::AddressRegisterIndirectWithPredecrement: { + const auto reg = instruction.reg(index); + + switch(instruction.size()) { + case DataSize::Byte: address_[reg] -= byte_increments[reg]; break; + case DataSize::Word: address_[reg] -= 2; break; + case DataSize::LongWord: address_[reg] -= 4; break; + } + + ea.value.l = address_[reg]; + ea.is_address = true; + } break; + case AddressingMode::AddressRegisterIndirectWithDisplacement: + ea.value.l = address_[instruction.reg(index)] + int16_t(read_pc()); + ea.is_address = true; + break; + case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement: + ea.value.l = address_[instruction.reg(index)] + index_8bitdisplacement(); + ea.is_address = true; + break; + + // + // PC-relative addresses. + // + // TODO: rephrase these in terms of instruction_address_. Just for security + // against whatever mutations the PC has been through already to get to here. + // + case AddressingMode::ProgramCounterIndirectWithDisplacement: + ea.value.l = program_counter_.l + int16_t(read_pc()); + ea.is_address = true; + break; + case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement: + ea.value.l = program_counter_.l + index_8bitdisplacement(); + ea.is_address = true; + break; + + default: + // TODO. + assert(false); + break; + } + + return ea; +} + + +template +void Executor::run_for_instructions(int count) { + while(count--) { + // TODO: check interrupt level, trace flag. + + // Read the next instruction. + instruction_address_ = program_counter_.l; + const auto opcode = read_pc(); + const Preinstruction instruction = decoder_.decode(opcode); + program_counter_.l += 2; + + // TODO: check privilege level. + + // Temporary storage. + CPU::SlicedInt32 operand_[2]; + EffectiveAddress effective_address_[2]; + + // Calculate effective addresses; copy 'addresses' into the + // operands by default both: (i) because they might be values, + // rather than addresses; and (ii) then they'll be there for use + // by LEA and PEA. + // + // TODO: this work should be performed by a full Decoder, so that it can be cached. + effective_address_[0] = calculate_effective_address(instruction, opcode, 0); + effective_address_[1] = calculate_effective_address(instruction, opcode, 1); + operand_[0] = effective_address_[0].value; + operand_[1] = effective_address_[1].value; + + // Obtain the appropriate sequence. + // + // TODO: make a decision about whether this goes into a fully-decoded Instruction. + Sequence sequence(instruction.operation); + + // Perform it. + while(!sequence.empty()) { + const auto step = sequence.pop_front(); + + switch(step) { + default: assert(false); // i.e. TODO + + case Step::FetchOp1: + case Step::FetchOp2: { + const auto index = int(step) & 1; + + // If the operand wasn't indirect, it's already fetched. + if(!effective_address_[index].is_address) continue; + + // TODO: potential bus alignment exception. + read(instruction.size(), effective_address_[index].value, operand_[index]); + } break; + + case Step::Perform: + perform(instruction, operand_[0], operand_[1], status_, this); + break; + + case Step::StoreOp1: + case Step::StoreOp2: { + const auto index = int(step) & 1; + + // If the operand wasn't indirect, it's already fetched. + if(!effective_address_[index].is_address) { + // This must be either address or data register indirect. + assert( + instruction.mode(index) == AddressingMode::DataRegisterDirect || + instruction.mode(index) == AddressingMode::AddressRegisterDirect); + + // TODO: is it worth holding registers as a single block to avoid this conditional? + if(instruction.mode(index) == AddressingMode::DataRegisterDirect) { + data_[instruction.reg(index)] = operand_[index]; + } else { + address_[instruction.reg(index)] = operand_[index]; + } + + break; + } + + // TODO: potential bus alignment exception. + write(instruction.size(), effective_address_[index].value, operand_[index]); + } break; + } + } + } +} + +} +} + +#endif /* InstructionSets_M68k_ExecutorImplementation_hpp */ diff --git a/InstructionSets/M68k/Instruction.hpp b/InstructionSets/M68k/Instruction.hpp index 4e9236eda..95cf9dff6 100644 --- a/InstructionSets/M68k/Instruction.hpp +++ b/InstructionSets/M68k/Instruction.hpp @@ -303,23 +303,23 @@ enum class AddressingMode: uint8_t { AddressRegisterIndirectWithDisplacement = 0b00'101, /// (d8, An, Xn) AddressRegisterIndirectWithIndex8bitDisplacement = 0b00'110, - /// (bd, An, Xn) + /// (bd, An, Xn) [68020+] AddressRegisterIndirectWithIndexBaseDisplacement = 0b10'000, - /// ([bd, An, Xn], od) + /// ([bd, An, Xn], od) [68020+] MemoryIndirectPostindexed = 0b10'001, - /// ([bd, An], Xn, od) + /// ([bd, An], Xn, od) [68020+] MemoryIndirectPreindexed = 0b10'010, /// (d16, PC) ProgramCounterIndirectWithDisplacement = 0b01'010, /// (d8, PC, Xn) ProgramCounterIndirectWithIndex8bitDisplacement = 0b01'011, - /// (bd, PC, Xn) + /// (bd, PC, Xn) [68020+] ProgramCounterIndirectWithIndexBaseDisplacement = 0b10'011, - /// ([bd, PC, Xn], od) + /// ([bd, PC, Xn], od) [68020+] ProgramCounterMemoryIndirectPostindexed = 0b10'100, - /// ([bc, PC], Xn, od) + /// ([bc, PC], Xn, od) [68020+] ProgramCounterMemoryIndirectPreindexed = 0b10'101, /// (xxx).W diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index af932c3f9..9ae760f2b 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1943,6 +1943,7 @@ 4BB5B99A281B244400522DA9 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; sourceTree = ""; }; 4BB5B99B281C805300522DA9 /* Executor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Executor.cpp; sourceTree = ""; }; 4BB5B99C281C805300522DA9 /* Executor.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Executor.hpp; sourceTree = ""; }; + 4BB5B99F281F121200522DA9 /* ExecutorImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ExecutorImplementation.hpp; sourceTree = ""; }; 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = ""; }; 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = ""; }; 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreGCR.cpp; path = Encodings/CommodoreGCR.cpp; sourceTree = ""; }; @@ -4092,6 +4093,7 @@ isa = PBXGroup; children = ( 4BB5B99A281B244400522DA9 /* PerformImplementation.hpp */, + 4BB5B99F281F121200522DA9 /* ExecutorImplementation.hpp */, ); path = Implementation; sourceTree = "";