diff --git a/InstructionSets/M68k/Decoder.cpp b/InstructionSets/M68k/Decoder.cpp index 49b190846..ce2359c19 100644 --- a/InstructionSets/M68k/Decoder.cpp +++ b/InstructionSets/M68k/Decoder.cpp @@ -76,8 +76,6 @@ constexpr Operation Predecoder::operation(OpT op) { } switch(op) { - case MOVEMtoRl: case MOVEMtoMl: return Operation::MOVEMl; - case MOVEMtoRw: case MOVEMtoMw: return Operation::MOVEMw; case MOVEPtoRl: case MOVEPtoMl: return Operation::MOVEPl; case MOVEPtoRw: case MOVEPtoMw: return Operation::MOVEPw; @@ -258,7 +256,14 @@ template uint32_t Predecoder::invalid_operands() { case SUBQw: case SUBQl: return ~TwoOperandMask< Quick, - AlterableAddressingModes + AlterableAddressingModesNoAn + >::value; + + case ADDQAw: case ADDQAl: + case SUBQAw: case SUBQAl: + return ~TwoOperandMask< + Quick, + An >::value; case OpT(Operation::MOVEb): @@ -449,16 +454,16 @@ template uint32_t Predecoder::invalid_operands() { An >::value; - case MOVEMtoMw: case MOVEMtoMl: + case OpT(Operation::MOVEMtoMw): case OpT(Operation::MOVEMtoMl): return ~TwoOperandMask< Imm, Ind | PreDec | d16An | d8AnXn | XXXw | XXXl >::value; - case MOVEMtoRw: case MOVEMtoRl: + case OpT(Operation::MOVEMtoRw): case OpT(Operation::MOVEMtoRl): return ~TwoOperandMask< - Ind | PostInc | d16An | d8AnXn | XXXw | XXXl | d16PC | d8PCXn, - Imm + Imm, + Ind | PostInc | d16An | d8AnXn | XXXw | XXXl | d16PC | d8PCXn >::value; case MOVEPtoRl: case MOVEPtoRw: @@ -490,7 +495,7 @@ template Preinstruction Predecoder::validated op1_mode, op1_reg, op2_mode, op2_reg, requires_supervisor(operation), - size(operation), + operand_size(operation), condition); } @@ -503,7 +508,7 @@ template Preinstruction Predecoder::validated op1_mode, op1_reg, op2_mode, op2_reg, requires_supervisor(operation), - size(operation), + operand_size(operation), condition); } @@ -789,26 +794,27 @@ template Preinstruction Predecoder::decode(ui // b0–b2 and b3–b5: effective address. // [already decoded: b10: direction] // - case MOVEMtoMl: case MOVEMtoMw: + case OpT(Operation::MOVEMtoMl): case OpT(Operation::MOVEMtoMw): + case OpT(Operation::MOVEMtoRl): case OpT(Operation::MOVEMtoRw): return validated( AddressingMode::ImmediateData, 0, combined_mode(ea_mode, ea_register), ea_register); - case MOVEMtoRl: case MOVEMtoRw: - return validated( - combined_mode(ea_mode, ea_register), ea_register, - AddressingMode::ImmediateData, 0); - // // MARK: TRAP, BCCb, BSRb // // No further operands decoded, but note that one is somewhere in the opcode. // case OpT(Operation::TRAP): - case OpT(Operation::Bccb): case OpT(Operation::BSRb): return validated(AddressingMode::Quick); + case OpT(Operation::Bccb): + return validated( + AddressingMode::Quick, 0, + AddressingMode::None, 0, + Condition((instruction >> 8) & 0xf)); + // // MARK: LINKw // @@ -827,7 +833,9 @@ template Preinstruction Predecoder::decode(ui // b9–b11: an immediate value, embedded in the opcode. // case ADDQb: case ADDQw: case ADDQl: + case ADDQAw: case ADDQAl: case SUBQb: case SUBQw: case SUBQl: + case SUBQAw: case SUBQAl: return validated( AddressingMode::Quick, 0, combined_mode(ea_mode, ea_register), ea_register); @@ -1073,10 +1081,10 @@ Preinstruction Predecoder::decode4(uint16_t instruction) { case 0x840: Decode(Op::PEA); // 4-128 (p232) - case 0x880: Decode(MOVEMtoMw); - case 0x8c0: Decode(MOVEMtoMl); - case 0xc80: Decode(MOVEMtoRw); - case 0xcc0: Decode(MOVEMtoRl); + case 0x880: Decode(Op::MOVEMtoMw); + case 0x8c0: Decode(Op::MOVEMtoMl); + case 0xc80: Decode(Op::MOVEMtoRw); + case 0xcc0: Decode(Op::MOVEMtoRl); // 4-192 (p296) case 0xa00: Decode(Op::TSTb); @@ -1108,16 +1116,50 @@ template Preinstruction Predecoder::decode5(uint16_t instruction) { using Op = Operation; - switch(instruction & 0x1c0) { + switch(instruction & 0x1f8) { // 4-11 (p115) - case 0x000: Decode(ADDQb); - case 0x040: Decode(ADDQw); - case 0x080: Decode(ADDQl); + case 0x000: + case 0x010: case 0x018: + case 0x020: case 0x028: + case 0x030: case 0x038: + Decode(ADDQb); + + case 0x040: + case 0x050: case 0x058: + case 0x060: case 0x068: + case 0x070: case 0x078: + Decode(ADDQw); + + case 0x080: + case 0x090: case 0x098: + case 0x0a0: case 0x0a8: + case 0x0b0: case 0x0b8: + Decode(ADDQl); + + case 0x048: Decode(ADDQAw); + case 0x088: Decode(ADDQAl); // 4-181 (p285) - case 0x100: Decode(SUBQb); - case 0x140: Decode(SUBQw); - case 0x180: Decode(SUBQl); + case 0x100: + case 0x110: case 0x118: + case 0x120: case 0x128: + case 0x130: case 0x138: + Decode(SUBQb); + + case 0x140: + case 0x150: case 0x158: + case 0x160: case 0x168: + case 0x170: case 0x178: + Decode(SUBQw); + + case 0x180: + case 0x190: case 0x198: + case 0x1a0: case 0x1a8: + case 0x1b0: case 0x1b8: + Decode(SUBQl); + + case 0x148: Decode(SUBQAw); + case 0x188: Decode(SUBQAl); default: break; } diff --git a/InstructionSets/M68k/Decoder.hpp b/InstructionSets/M68k/Decoder.hpp index 59bae5cce..30052304a 100644 --- a/InstructionSets/M68k/Decoder.hpp +++ b/InstructionSets/M68k/Decoder.hpp @@ -66,10 +66,7 @@ template class Predecoder { // time that's knowable from the Operation alone, hence the rather awkward // extension of @c Operation. enum ExtendedOperation: OpT { - MOVEMtoRl = uint8_t(Operation::Max) + 1, MOVEMtoRw, - MOVEMtoMl, MOVEMtoMw, - - MOVEPtoRl, MOVEPtoRw, + MOVEPtoRl = uint8_t(Operation::Max) + 1, MOVEPtoRw, MOVEPtoMl, MOVEPtoMw, MOVEQ, diff --git a/InstructionSets/M68k/ExceptionVectors.hpp b/InstructionSets/M68k/ExceptionVectors.hpp new file mode 100644 index 000000000..83fbf2552 --- /dev/null +++ b/InstructionSets/M68k/ExceptionVectors.hpp @@ -0,0 +1,50 @@ +// +// ExceptionVectors.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/05/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_M68k_ExceptionVectors_hpp +#define InstructionSets_M68k_ExceptionVectors_hpp + +namespace InstructionSet { +namespace M68k { + +enum Exception { + InitialStackPointer = 0, + InitialProgramCounter = 1, + AccessFault = 2, + AddressError = 3, + IllegalInstruction = 4, + IntegerDivideByZero = 5, + CHK = 6, + TRAPV = 7, + PrivilegeViolation = 8, + Trace = 9, + Line1010 = 10, + Line1111 = 11, + CoprocessorProtocolViolation = 13, + FormatError = 14, + UninitialisedInterrupt = 15, + SpuriousInterrupt = 24, + InterruptAutovectorBase = 25, + TrapBase = 32, + FPBranchOrSetOnUnorderedCondition = 48, + FPInexactResult = 49, + FPDivideByZero = 50, + FPUnderflow = 51, + FPOperandError = 52, + FPOverflow = 53, + FPSignallingNAN = 54, + FPUnimplementedDataType = 55, + MMUConfigurationError = 56, + MMUIllegalOperationError = 57, + MMUAccessLevelViolationError = 58, +}; + +} +} + +#endif /* InstructionSets_M68k_ExceptionVectors_hpp */ diff --git a/InstructionSets/M68k/Executor.hpp b/InstructionSets/M68k/Executor.hpp new file mode 100644 index 000000000..35009461f --- /dev/null +++ b/InstructionSets/M68k/Executor.hpp @@ -0,0 +1,178 @@ +// +// Executor.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/04/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_M68k_Executor_hpp +#define InstructionSets_M68k_Executor_hpp + +#include "Decoder.hpp" +#include "Instruction.hpp" +#include "Model.hpp" +#include "Perform.hpp" +#include "Status.hpp" + +namespace InstructionSet { +namespace M68k { + +/// Maps the 68k function codes such that bits 0, 1 and 2 represent +/// FC0, FC1 and FC2 respectively. +enum class FunctionCode { + UserData = 0b001, + UserProgram = 0b010, + SupervisorData = 0b101, + SupervisorProgram = 0b110, + InterruptAcknowledge = 0b111, +}; + +/// The Executor is templated on a class that implements bus handling as defined below; +/// the bus handler is responsible for all reads and writes, and will also receive resets and +/// interrupt acknowledgements. +/// +/// The executor will provide 32-bit addresses and act as if it had a 32-bit data bus, even +/// if interpretting the original 68000 instruction set. +struct BusHandler { + /// Write @c value of type/size @c IntT to @c address with the processor signalling + /// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t + /// or @c uint32_t. + template void write(uint32_t address, IntT value, FunctionCode function); + + /// Read and return a value of type/size @c IntT from @c address with the processor signalling + /// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t + /// or @c uint32_t. + template IntT read(uint32_t address, FunctionCode function); + + /// React to the processor programmatically strobing its RESET output. + void reset(); + + /// Respond to an interrupt acknowledgement at @c interrupt_level from the processor. + /// Should return @c -1 in order to trigger autovectoring, or the appropriate exception vector + /// number otherwise. + /// + /// It is undefined behaviour to return a number greater than 255. + int acknowlege_interrupt(int interrupt_level); +}; + +/// Ties together the decoder, sequencer and performer to provide an executor for 680x0 instruction streams. +/// As is standard for these executors, no bus- or cache-level fidelity to any real 680x0 is attempted. This is +/// simply an executor of 680x0 code. +template class Executor { + public: + Executor(BusHandler &); + + /// Reset the processor, back to a state as if just externally reset. + void reset(); + + /// Executes the number of instructions specified; + /// other events — such as initial reset or branching + /// to exceptions — may be zero costed, and interrupts + /// will not necessarily take effect immediately when signalled. + void run_for_instructions(int); + + /// Call this at any time to interrupt processing with a bus error; + /// the function code and address must be provided. Internally + /// this will raise a C++ exception, and therefore doesn't return. + [[noreturn]] void signal_bus_error(FunctionCode, uint32_t address); + + /// Sets the current input interrupt level. + void set_interrupt_level(int); + + // TODO: this will likely be shared in some capacity with the bus-accurate versions of the 680x0; + // therefore it will almost certainly be factored out in future. + struct Registers { + uint32_t data[8], address[7]; + uint32_t user_stack_pointer; + uint32_t supervisor_stack_pointer; + uint16_t status; + uint32_t program_counter; + }; + Registers get_state(); + void set_state(const Registers &); + + private: + class State: public NullFlowController { + public: + State(BusHandler &handler) : bus_handler_(handler) {} + + void run(int &); + bool stopped = false; + + void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value); + void write(DataSize size, uint32_t address, CPU::SlicedInt32 value); + template IntT read(uint32_t address, bool is_from_pc = false); + template void write(uint32_t address, IntT value); + + template IntT read_pc(); + + // Processor state. + Status status; + CPU::SlicedInt32 program_counter; + CPU::SlicedInt32 registers[16]; // D0–D7 followed by A0–A7. + CPU::SlicedInt32 stack_pointers[2]; + uint32_t instruction_address; + uint16_t instruction_opcode; + + // Things that are ephemerally duplicative of Status. + int active_stack_pointer = 0; + Status::FlagT should_trace = 0; + + // Bus state. + int interrupt_input = 0; + + // 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 + }; + + // Flow control; Cf. Perform.hpp. + template void raise_exception(int); + + void did_update_status(); + + template void complete_bcc(bool matched_condition, IntT offset); + void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset); + void bsr(uint32_t offset); + void jmp(uint32_t); + void jsr(uint32_t offset); + void rtr(); + void rts(); + void rte(); + void stop(); + void reset(); + + void link(Preinstruction instruction, uint32_t offset); + void unlink(uint32_t &address); + void pea(uint32_t address); + + void move_to_usp(uint32_t address); + void move_from_usp(uint32_t &address); + + template void movep(Preinstruction instruction, uint32_t source, uint32_t dest); + template void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest); + template void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest); + + void tas(Preinstruction instruction, uint32_t address); + + private: + BusHandler &bus_handler_; + Predecoder decoder_; + + struct EffectiveAddress { + CPU::SlicedInt32 value; + bool requires_fetch; + }; + EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index); + uint32_t index_8bitdisplacement(); + } state_; +}; + +} +} + +#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..682c4507b --- /dev/null +++ b/InstructionSets/M68k/Implementation/ExecutorImplementation.hpp @@ -0,0 +1,695 @@ +// +// 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 "../ExceptionVectors.hpp" + +#include + +namespace InstructionSet { +namespace M68k { + +#define An(x) state_.registers[8 + x] +#define Dn(x) state_.registers[x] +#define sp An(7) + +#define AccessException(code, address, vector) \ + uint64_t(((vector) << 8) | uint64_t(code) | ((address) << 16)) + +// MARK: - Executor itself. + +template +Executor::Executor(BusHandler &handler) : state_(handler) { + reset(); +} + +template +void Executor::reset() { + // Establish: supervisor state, all interrupts blocked. + state_.status.set_status(0b0010'0011'1000'0000); + state_.did_update_status(); + + // Clear the STOPped state, if currently active. + state_.stopped = false; + + // Seed stack pointer and program counter. + sp.l = state_.template read(0) & 0xffff'fffe; + state_.program_counter.l = state_.template read(4); +} + +template +void Executor::signal_bus_error(FunctionCode code, uint32_t address) { + throw AccessException(code, address, Exception::AccessFault); +} + +template +void Executor::set_interrupt_level(int level) { + state_.interrupt_input_ = level; + state_.stopped &= state_.interrupt_input_ <= state_.status.interrupt_level; +} + +template +void Executor::run_for_instructions(int count) { + if(state_.stopped) return; + + while(count > 0) { + try { + state_.run(count); + } catch (uint64_t exception) { + // Potiental source of an exception #1: STOP. Check for that first. + if(state_.stopped) return; + + // Unpack the exception; this is the converse of the AccessException macro. + const int vector_address = (exception >> 6) & 0xfc; + const uint16_t code = uint16_t(exception & 0xff); + const uint32_t faulting_address = uint32_t(exception >> 16); + + // Grab the status to store, then switch into supervisor mode. + const uint16_t status = state_.status.status(); + state_.status.is_supervisor = true; + state_.status.trace_flag = 0; + state_.did_update_status(); + + // Ensure no tracing occurs into the exception. + state_.should_trace = 0; + + // Push status and the program counter at instruction start. + state_.template write(sp.l - 14, code); + state_.template write(sp.l - 12, faulting_address); + state_.template write(sp.l - 8, state_.instruction_opcode); + state_.template write(sp.l - 6, status); + state_.template write(sp.l - 4, state_.instruction_address); + sp.l -= 14; + + // Fetch the new program counter; reset on a double fault. + try { + state_.program_counter.l = state_.template read(vector_address); + } catch (uint64_t) { + // TODO: I think this is incorrect, but need to verify consistency + // across different 680x0s. + reset(); + } + } + } +} + +template +typename Executor::Registers Executor::get_state() { + Registers result; + + for(int c = 0; c < 8; c++) { + result.data[c] = Dn(c).l; + } + for(int c = 0; c < 7; c++) { + result.address[c] = An(c).l; + } + result.status = state_.status.status(); + result.program_counter = state_.program_counter.l; + + state_.stack_pointers[state_.active_stack_pointer] = sp; + result.user_stack_pointer = state_.stack_pointers[0].l; + result.supervisor_stack_pointer = state_.stack_pointers[1].l; + + return result; +} + +template +void Executor::set_state(const Registers &state) { + for(int c = 0; c < 8; c++) { + Dn(c).l = state.data[c]; + } + for(int c = 0; c < 7; c++) { + An(c).l = state.address[c]; + } + state_.status.set_status(state.status); + state_.did_update_status(); + state_.program_counter.l = state.program_counter; + + state_.stack_pointers[0].l = state.user_stack_pointer; + state_.stack_pointers[1].l = state.supervisor_stack_pointer; + sp = state_.stack_pointers[state_.active_stack_pointer]; +} + +#undef Dn +#undef An + +// MARK: - State. + +#define An(x) registers[8 + x] +#define Dn(x) registers[x] + +template +template +IntT Executor::State::read(uint32_t address, bool is_from_pc) { + const auto code = FunctionCode((active_stack_pointer << 2) | 1 << int(is_from_pc)); + if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) { + throw AccessException(code, address, Exception::AddressError | (int(is_from_pc) << 3) | (1 << 4)); + } + + return bus_handler_.template read(address, code); +} + +template +template +void Executor::State::write(uint32_t address, IntT value) { + const auto code = FunctionCode((active_stack_pointer << 2) | 1); + if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) { + throw AccessException(code, address, Exception::AddressError); + } + + bus_handler_.template write(address, value, code); +} + +template +void Executor::State::read(DataSize size, uint32_t address, CPU::SlicedInt32 &value) { + switch(size) { + case DataSize::Byte: value.b = read(address); break; + case DataSize::Word: value.w = read(address); break; + case DataSize::LongWord: value.l = read(address); break; + } +} + +template +void Executor::State::write(DataSize size, uint32_t address, CPU::SlicedInt32 value) { + switch(size) { + case DataSize::Byte: write(address, value.b); break; + case DataSize::Word: write(address, value.w); break; + case DataSize::LongWord: write(address, value.l); break; + } +} + +template +template IntT Executor::State::read_pc() { + const IntT result = read(program_counter.l, true); + + if constexpr (sizeof(IntT) == 4) { + program_counter.l += 4; + } else { + program_counter.l += 2; + } + + return result; +} + +template +uint32_t Executor::State::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 >> 12) & 7; + const uint32_t displacement = registers[register_index + ((extension >> 12) & 0x08)].l; + const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement); + return offset + sized_displacement; +} + +template +typename Executor::State::EffectiveAddress +Executor::State::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: + case AddressingMode::AddressRegisterDirect: + ea.value = registers[instruction.lreg(index)]; + ea.requires_fetch = false; + break; + case AddressingMode::Quick: + ea.value.l = quick(opcode, instruction.operation); + ea.requires_fetch = false; + break; + case AddressingMode::ImmediateData: + switch(instruction.operand_size()) { + case DataSize::Byte: + ea.value.l = read_pc() & 0xff; + break; + case DataSize::Word: + ea.value.l = read_pc(); + break; + case DataSize::LongWord: + ea.value.l = read_pc(); + break; + } + ea.requires_fetch = false; + break; + + // + // Absolute addresses. + // + case AddressingMode::AbsoluteShort: + ea.value.l = int16_t(read_pc()); + ea.requires_fetch = true; + break; + case AddressingMode::AbsoluteLong: + ea.value.l = read_pc(); + ea.requires_fetch = true; + break; + + // + // Address register indirects. + // + case AddressingMode::AddressRegisterIndirect: + ea.value = An(instruction.reg(index)); + ea.requires_fetch = true; + break; + case AddressingMode::AddressRegisterIndirectWithPostincrement: { + const auto reg = instruction.reg(index); + + ea.value = An(reg); + ea.requires_fetch = true; + + switch(instruction.operand_size()) { + case DataSize::Byte: An(reg).l += byte_increments[reg]; break; + case DataSize::Word: An(reg).l += 2; break; + case DataSize::LongWord: An(reg).l += 4; break; + } + } break; + case AddressingMode::AddressRegisterIndirectWithPredecrement: { + const auto reg = instruction.reg(index); + + switch(instruction.operand_size()) { + case DataSize::Byte: An(reg).l -= byte_increments[reg]; break; + case DataSize::Word: An(reg).l -= 2; break; + case DataSize::LongWord: An(reg).l -= 4; break; + } + + ea.value = An(reg); + ea.requires_fetch = true; + } break; + case AddressingMode::AddressRegisterIndirectWithDisplacement: + ea.value.l = An(instruction.reg(index)).l + int16_t(read_pc()); + ea.requires_fetch = true; + break; + case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement: + ea.value.l = An(instruction.reg(index)).l + index_8bitdisplacement(); + ea.requires_fetch = true; + break; + + // + // PC-relative addresses. + // + case AddressingMode::ProgramCounterIndirectWithDisplacement: + ea.value.l = program_counter.l + int16_t(read_pc()); + ea.requires_fetch = true; + break; + case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement: + ea.value.l = program_counter.l + index_8bitdisplacement(); + ea.requires_fetch = true; + break; + + default: + assert(false); + } + + return ea; +} + +template +void Executor::State::run(int &count) { + while(count--) { + // Check for a new interrupt. + if(interrupt_input > status.interrupt_level) { + const int vector = bus_handler_.acknowlege_interrupt(interrupt_input); + if(vector >= 0) { + raise_exception(vector); + } else { + raise_exception(Exception::InterruptAutovectorBase - 1 + interrupt_input); + } + status.interrupt_level = interrupt_input; + } + + // Capture the trace bit, indicating whether to trace + // after this instruction. + // + // If an exception occurs, this value will be cleared, but + // it'll persist across mere status register changes for + // one instruction's duration. + should_trace = status.trace_flag; + + // Read the next instruction. + instruction_address = program_counter.l; + instruction_opcode = read_pc(); + const Preinstruction instruction = decoder_.decode(instruction_opcode); + + if(instruction.requires_supervisor() && !status.is_supervisor) { + raise_exception(Exception::PrivilegeViolation); + continue; + } + if(instruction.operation == Operation::Undefined) { + switch(instruction_opcode & 0xf000) { + default: + raise_exception(Exception::IllegalInstruction); + continue; + case 0xa000: + raise_exception(Exception::Line1010); + continue; + case 0xf000: + raise_exception(Exception::Line1111); + continue; + } + } + + // 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. + effective_address_[0] = calculate_effective_address(instruction, instruction_opcode, 0); + effective_address_[1] = calculate_effective_address(instruction, instruction_opcode, 1); + operand_[0] = effective_address_[0].value; + operand_[1] = effective_address_[1].value; + + // Obtain the appropriate sequence. + const auto flags = operand_flags(instruction.operation); + +#define fetch_operand(n) \ + if(effective_address_[n].requires_fetch) { \ + read(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \ + } + + if(flags & FetchOp1) { fetch_operand(0); } + if(flags & FetchOp2) { fetch_operand(1); } + +#undef fetch_operand + + perform(instruction, operand_[0], operand_[1], status, *this); + +#define store_operand(n) \ + if(!effective_address_[n].requires_fetch) { \ + registers[instruction.lreg(n)] = operand_[n]; \ + } else { \ + write(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \ + } + + if(flags & StoreOp1) { store_operand(0); } + if(flags & StoreOp2) { store_operand(1); } + +#undef store_operand + + // If the trace bit was set, trigger the trace exception. + if(should_trace) { + raise_exception(Exception::Trace); + } + } +} + +// MARK: - Flow Control. + +template +template +void Executor::State::raise_exception(int index) { + const uint32_t address = index << 2; + + // Grab the status to store, then switch into supervisor mode + // and disable tracing. + const uint16_t previous_status = status.status(); + status.is_supervisor = true; + status.trace_flag = 0; + did_update_status(); + + // Push status and the program counter at instruction start. + write(sp.l - 4, use_current_instruction_pc ? instruction_address : program_counter.l); + write(sp.l - 6, previous_status); + sp.l -= 6; + + // Ensure no tracing occurs into the exception. + should_trace = 0; + + // Fetch the new program counter. + program_counter.l = read(address); +} + +template +void Executor::State::did_update_status() { + // Shuffle the stack pointers. + stack_pointers[active_stack_pointer] = sp; + sp = stack_pointers[int(status.is_supervisor)]; + active_stack_pointer = int(status.is_supervisor); +} + +template +void Executor::State::stop() { + stopped = true; + + // Raise an exception to exit the run loop; it doesn't matter + // what value is used as long as it is a uint64_t, so 0 will do. + throw uint64_t(); +} + +template +void Executor::State::reset() { + bus_handler_.reset(); +} + +template +void Executor::State::jmp(uint32_t address) { + program_counter.l = address; +} + +template +template void Executor::State::complete_bcc(bool branch, IntT offset) { + if(branch) { + program_counter.l = instruction_address + offset + 2; + } +} + +template +void Executor::State::complete_dbcc(bool matched_condition, bool overflowed, int16_t offset) { + if(!matched_condition && !overflowed) { + program_counter.l = instruction_address + offset + 2; + } +} + +template +void Executor::State::bsr(uint32_t offset) { + sp.l -= 4; + write(sp.l, program_counter.l); + program_counter.l = instruction_address + offset; +} + +template +void Executor::State::jsr(uint32_t address) { + sp.l -= 4; + write(sp.l, program_counter.l); + program_counter.l = address; +} + +template +void Executor::State::link(Preinstruction instruction, uint32_t offset) { + const auto reg = 8 + instruction.reg<0>(); + + sp.l -= 4; + write(sp.l, Dn(reg).l); + Dn(reg) = sp; + sp.l += offset; +} + +template +void Executor::State::unlink(uint32_t &address) { + sp.l = address; + address = read(sp.l); + sp.l += 4; +} + +template +void Executor::State::pea(uint32_t address) { + sp.l -= 4; + write(sp.l, address); +} + +template +void Executor::State::rtr() { + status.set_ccr(read(sp.l)); + sp.l += 2; + rts(); +} + +template +void Executor::State::rte() { + status.set_status(read(sp.l)); + sp.l += 2; + rts(); +} + +template +void Executor::State::rts() { + program_counter.l = read(sp.l); + sp.l += 4; +} + +template +void Executor::State::tas(Preinstruction instruction, uint32_t address) { + uint8_t value; + if(instruction.mode<0>() != AddressingMode::DataRegisterDirect) { + value = read(address); + write(address, value | 0x80); + } else { + value = uint8_t(address); + Dn(instruction.reg<0>()).b = uint8_t(address | 0x80); + } + + status.overflow_flag = status.carry_flag = 0; + status.zero_result = value; + status.negative_flag = value & 0x80; +} + +template +void Executor::State::move_to_usp(uint32_t address) { + stack_pointers[0].l = address; +} + +template +void Executor::State::move_from_usp(uint32_t &address) { + address = stack_pointers[0].l; +} + +template +template +void Executor::State::movep(Preinstruction instruction, uint32_t source, uint32_t dest) { + if(instruction.mode<0>() == AddressingMode::DataRegisterDirect) { + // Move register to memory. + const uint32_t reg = source; + uint32_t address = dest; + + if constexpr (sizeof(IntT) == 4) { + write(address, uint8_t(reg >> 24)); + address += 2; + + write(address, uint8_t(reg >> 16)); + address += 2; + } + + write(address, uint8_t(reg >> 8)); + address += 2; + + write(address, uint8_t(reg)); + } else { + // Move memory to register. + uint32_t ® = Dn(instruction.reg<1>()).l; + uint32_t address = source; + + if constexpr (sizeof(IntT) == 4) { + reg = read(address) << 24; + address += 2; + + reg |= read(address) << 16; + address += 2; + } else { + reg &= 0xffff0000; + } + + reg |= read(address) << 8; + address += 2; + + reg |= read(address); + } +} + +template +template +void Executor::State::movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest) { + // Move registers to memory. This is the only permitted use of the predecrement mode, + // which reverses output order. + + if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPredecrement) { + // The structure of the code in the mainline part of the executor is such + // that the address register will already have been predecremented before + // reaching here, and it'll have been by two bytes per the operand size + // rather than according to the instruction size. That's not wanted, so undo it. + // + // TODO: with the caveat that the 68020+ have different behaviour: + // + // "For the MC68020, MC68030, MC68040, and CPU32, if the addressing register is also + // moved to memory, the value written is the initial register value decremented by the + // size of the operation. The MC68000 and MC68010 write the initial register value + // (not decremented)." + An(instruction.reg<1>()).l += 2; + + uint32_t address = An(instruction.reg<1>()).l; + int index = 15; + + while(source) { + if(source & 1) { + address -= sizeof(IntT); + write(address, IntT(registers[index].l)); + } + --index; + source >>= 1; + } + + An(instruction.reg<1>()).l = address; + return; + } + + int index = 0; + while(source) { + if(source & 1) { + write(dest, IntT(registers[index].l)); + dest += sizeof(IntT); + } + ++index; + source >>= 1; + } +} + +template +template +void Executor::State::movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest) { + // Move memory to registers. + // + // A 68000 convention has been broken here; the instruction form is: + // MOVEM , # + // ... but the instruction is encoded as [MOVEM] [#] [ea]. + // + // This project's decoder decodes as #, . + int index = 0; + while(source) { + if(source & 1) { + if constexpr (sizeof(IntT) == 2) { + registers[index].l = int16_t(read(dest)); + } else { + registers[index].l = read(dest); + } + dest += sizeof(IntT); + } + ++index; + source >>= 1; + } + + if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPostincrement) { + // "If the effective address is specified by the postincrement mode ... + // [i]f the addressing register is also loaded from memory, the memory value is + // ignored and the register is written with the postincremented effective address." + + An(instruction.reg<1>()).l = dest; + } +} + +#undef sp +#undef Dn +#undef An +#undef AccessException + +} +} + +#endif /* InstructionSets_M68k_ExecutorImplementation_hpp */ diff --git a/InstructionSets/M68k/Implementation/InstructionOperandFlags.hpp b/InstructionSets/M68k/Implementation/InstructionOperandFlags.hpp new file mode 100644 index 000000000..2d42cafd0 --- /dev/null +++ b/InstructionSets/M68k/Implementation/InstructionOperandFlags.hpp @@ -0,0 +1,145 @@ +// +// InstructionOperandFlags.hpp +// Clock Signal +// +// Created by Thomas Harte on 09/05/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_68k_InstructionOperandFlags_hpp +#define InstructionSets_68k_InstructionOperandFlags_hpp + +namespace InstructionSet { +namespace M68k { + +template uint8_t operand_flags(Operation r_operation) { + switch((t_operation != Operation::Undefined) ? t_operation : r_operation) { + default: + assert(false); + + // + // No operands are fetched or stored. + // (which means that source and destination will appear as their effective addresses) + // + case Operation::PEA: + case Operation::JMP: case Operation::JSR: + case Operation::MOVEPw: case Operation::MOVEPl: + case Operation::MOVEMtoMw: case Operation::MOVEMtoMl: + case Operation::MOVEMtoRw: case Operation::MOVEMtoRl: + case Operation::TAS: + case Operation::RTR: case Operation::RTS: case Operation::RTE: + return 0; + + // + // Single-operand read. + // + case Operation::MOVEtoSR: case Operation::MOVEtoCCR: case Operation::MOVEtoUSP: + case Operation::ORItoSR: case Operation::ORItoCCR: + case Operation::ANDItoSR: case Operation::ANDItoCCR: + case Operation::EORItoSR: case Operation::EORItoCCR: + case Operation::Bccb: case Operation::Bccw: case Operation::Bccl: + case Operation::BSRb: case Operation::BSRw: case Operation::BSRl: + case Operation::TSTb: case Operation::TSTw: case Operation::TSTl: + return FetchOp1; + + // + // Single-operand write. + // + case Operation::MOVEfromSR: case Operation::MOVEfromUSP: + case Operation::Scc: + return StoreOp1; + + // + // Single-operand read-modify-write. + // + case Operation::NBCD: + case Operation::NOTb: case Operation::NOTw: case Operation::NOTl: + case Operation::NEGb: case Operation::NEGw: case Operation::NEGl: + case Operation::NEGXb: case Operation::NEGXw: case Operation::NEGXl: + case Operation::EXTbtow: case Operation::EXTwtol: + case Operation::SWAP: + case Operation::UNLINK: + case Operation::ASLm: case Operation::ASRm: + case Operation::LSLm: case Operation::LSRm: + case Operation::ROLm: case Operation::RORm: + case Operation::ROXLm: case Operation::ROXRm: + return FetchOp1 | StoreOp1; + + // + // CLR, which is model-dependent. + // + case Operation::CLRb: case Operation::CLRw: case Operation::CLRl: + if constexpr (model == Model::M68000) { + return FetchOp1 | StoreOp1; + } else { + return StoreOp1; + } + + // + // Two-operand; read both. + // + case Operation::CMPb: case Operation::CMPw: case Operation::CMPl: + case Operation::CMPAw: case Operation::CMPAl: + case Operation::CHK: + case Operation::BTST: + case Operation::LINKw: + return FetchOp1 | FetchOp2; + + // + // Two-operand; read source, write dest. + // + case Operation::MOVEb: case Operation::MOVEw: case Operation::MOVEl: + case Operation::MOVEAw: case Operation::MOVEAl: + return FetchOp1 | StoreOp2; + + // + // Two-operand; read both, write dest. + // + case Operation::ABCD: case Operation::SBCD: + case Operation::ADDb: case Operation::ADDw: case Operation::ADDl: + case Operation::ADDAw: case Operation::ADDAl: + case Operation::ADDXb: case Operation::ADDXw: case Operation::ADDXl: + case Operation::SUBb: case Operation::SUBw: case Operation::SUBl: + case Operation::SUBAw: case Operation::SUBAl: + case Operation::SUBXb: case Operation::SUBXw: case Operation::SUBXl: + case Operation::ORb: case Operation::ORw: case Operation::ORl: + case Operation::ANDb: case Operation::ANDw: case Operation::ANDl: + case Operation::EORb: case Operation::EORw: case Operation::EORl: + case Operation::DIVU: case Operation::DIVS: + case Operation::MULU: case Operation::MULS: + case Operation::ASLb: case Operation::ASLw: case Operation::ASLl: + case Operation::ASRb: case Operation::ASRw: case Operation::ASRl: + case Operation::LSLb: case Operation::LSLw: case Operation::LSLl: + case Operation::LSRb: case Operation::LSRw: case Operation::LSRl: + case Operation::ROLb: case Operation::ROLw: case Operation::ROLl: + case Operation::RORb: case Operation::RORw: case Operation::RORl: + case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl: + case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl: + case Operation::BCHG: + case Operation::BCLR: case Operation::BSET: + return FetchOp1 | FetchOp2 | StoreOp2; + + // + // Two-operand; read both, write source. + // + case Operation::DBcc: + return FetchOp1 | FetchOp2 | StoreOp1; + + // + // Two-operand; read both, write both. + // + case Operation::EXG: + return FetchOp1 | FetchOp2 | StoreOp1 | StoreOp2; + + // + // Two-operand; just write destination. + // + case Operation::LEA: + return StoreOp2; + } +} + +} +} + +#endif /* InstructionSets_68k_InstructionOperandFlags_hpp */ diff --git a/InstructionSets/M68k/Implementation/InstructionOperandSize.hpp b/InstructionSets/M68k/Implementation/InstructionOperandSize.hpp new file mode 100644 index 000000000..7c00c26de --- /dev/null +++ b/InstructionSets/M68k/Implementation/InstructionOperandSize.hpp @@ -0,0 +1,120 @@ +// +// InstructionOperandSize.hpp +// Clock Signal +// +// Created by Thomas Harte on 09/05/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_68k_InstructionOperandSize_hpp +#define InstructionSets_68k_InstructionOperandSize_hpp + +namespace InstructionSet { +namespace M68k { + +constexpr DataSize operand_size(Operation operation) { + switch(operation) { + // These are given a value arbitrarily, to + // complete the switch statement. + case Operation::Undefined: + case Operation::NOP: + case Operation::STOP: + case Operation::RESET: + case Operation::RTE: case Operation::RTR: + case Operation::TRAP: + case Operation::TRAPV: + + case Operation::ABCD: case Operation::SBCD: + case Operation::NBCD: + case Operation::ADDb: case Operation::ADDXb: + case Operation::SUBb: case Operation::SUBXb: + case Operation::MOVEb: + case Operation::ORItoCCR: + case Operation::ANDItoCCR: + case Operation::EORItoCCR: + case Operation::BTST: case Operation::BCLR: + case Operation::BCHG: case Operation::BSET: + case Operation::CMPb: case Operation::TSTb: + case Operation::Bccb: case Operation::BSRb: + case Operation::CLRb: + case Operation::Scc: + case Operation::NEGXb: case Operation::NEGb: + case Operation::ASLb: case Operation::ASRb: + case Operation::LSLb: case Operation::LSRb: + case Operation::ROLb: case Operation::RORb: + case Operation::ROXLb: case Operation::ROXRb: + case Operation::ANDb: case Operation::EORb: + case Operation::NOTb: case Operation::ORb: + case Operation::TAS: + return DataSize::Byte; + + case Operation::ADDw: case Operation::ADDAw: + case Operation::ADDXw: case Operation::SUBw: + case Operation::SUBAw: case Operation::SUBXw: + case Operation::MOVEw: case Operation::MOVEAw: + case Operation::ORItoSR: + case Operation::ANDItoSR: + case Operation::EORItoSR: + case Operation::MOVEtoSR: + case Operation::MOVEfromSR: + case Operation::MOVEtoCCR: + case Operation::CMPw: case Operation::CMPAw: + case Operation::TSTw: + case Operation::DBcc: + case Operation::Bccw: case Operation::BSRw: + case Operation::CLRw: + case Operation::NEGXw: case Operation::NEGw: + case Operation::ASLw: case Operation::ASLm: + case Operation::ASRw: case Operation::ASRm: + case Operation::LSLw: case Operation::LSLm: + case Operation::LSRw: case Operation::LSRm: + case Operation::ROLw: case Operation::ROLm: + case Operation::RORw: case Operation::RORm: + case Operation::ROXLw: case Operation::ROXLm: + case Operation::ROXRw: case Operation::ROXRm: + case Operation::MOVEMtoRw: + case Operation::MOVEMtoRl: + case Operation::MOVEMtoMw: + case Operation::MOVEMtoMl: + case Operation::MOVEPw: + case Operation::ANDw: case Operation::EORw: + case Operation::NOTw: case Operation::ORw: + case Operation::DIVU: case Operation::DIVS: + case Operation::MULU: case Operation::MULS: + case Operation::EXTbtow: + case Operation::LINKw: + case Operation::CHK: + return DataSize::Word; + + case Operation::ADDl: case Operation::ADDAl: + case Operation::ADDXl: case Operation::SUBl: + case Operation::SUBAl: case Operation::SUBXl: + case Operation::MOVEl: case Operation::MOVEAl: + case Operation::LEA: case Operation::PEA: + case Operation::EXG: case Operation::SWAP: + case Operation::MOVEtoUSP: + case Operation::MOVEfromUSP: + case Operation::CMPl: case Operation::CMPAl: + case Operation::TSTl: + case Operation::JMP: case Operation::JSR: + case Operation::RTS: + case Operation::Bccl: case Operation::BSRl: + case Operation::CLRl: + case Operation::NEGXl: case Operation::NEGl: + case Operation::ASLl: case Operation::ASRl: + case Operation::LSLl: case Operation::LSRl: + case Operation::ROLl: case Operation::RORl: + case Operation::ROXLl: case Operation::ROXRl: + case Operation::MOVEPl: + case Operation::ANDl: case Operation::EORl: + case Operation::NOTl: case Operation::ORl: + case Operation::EXTwtol: + case Operation::UNLINK: + return DataSize::LongWord; + } +} + +} +} + +#endif /* InstructionSets_68k_InstructionOperandSize_hpp */ diff --git a/InstructionSets/M68k/Implementation/PerformImplementation.hpp b/InstructionSets/M68k/Implementation/PerformImplementation.hpp new file mode 100644 index 000000000..66e9512e5 --- /dev/null +++ b/InstructionSets/M68k/Implementation/PerformImplementation.hpp @@ -0,0 +1,1209 @@ +// +// PerformImplementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 28/04/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_M68k_PerformImplementation_h +#define InstructionSets_M68k_PerformImplementation_h + +#include "../ExceptionVectors.hpp" + +#include +#include + +namespace InstructionSet { +namespace M68k { + +#define u_extend16(x) uint32_t(int16_t(x)) +#define s_extend16(x) int32_t(int16_t(x)) + +template < + Model model, + typename FlowController, + Operation operation = Operation::Undefined +> void perform(Preinstruction instruction, CPU::SlicedInt32 &src, CPU::SlicedInt32 &dest, Status &status, FlowController &flow_controller) { + +#define sub_overflow() ((result ^ destination) & (destination ^ source)) +#define add_overflow() ((result ^ destination) & ~(destination ^ source)) + switch((operation != Operation::Undefined) ? operation : instruction.operation) { + /* + ABCD adds the lowest bytes from the source and destination using BCD arithmetic, + obeying the extend flag. + */ + case Operation::ABCD: { + // Pull out the two halves, for simplicity. + const uint8_t source = src.b; + const uint8_t destination = dest.b; + const int extend = (status.extend_flag ? 1 : 0); + + // Perform the BCD add by evaluating the two nibbles separately. + const int unadjusted_result = destination + source + extend; + int result = (destination & 0xf) + (source & 0xf) + extend; + result += + (destination & 0xf0) + + (source & 0xf0) + + (((9 - result) >> 4) & 0x06); // i.e. ((result > 0x09) ? 0x06 : 0x00) + result += ((0x9f - result) >> 4) & 0x60; // i.e. ((result > 0x9f) ? 0x60 : 0x00) + + // Set all flags essentially as if this were normal addition. + status.zero_result |= result & 0xff; + status.extend_flag = status.carry_flag = uint_fast32_t(result & ~0xff); + status.negative_flag = result & 0x80; + status.overflow_flag = ~unadjusted_result & result & 0x80; + + // Store the result. + dest.b = uint8_t(result); + } break; + +#define addop(a, b, x) a + b + (x ? 1 : 0) +#define subop(a, b, x) a - b - (x ? 1 : 0) +#define z_set(a, b) a = b +#define z_or(a, b) a |= b + +#define addsubb(a, b, op, overflow, x, zero_op) \ + const int source = a; \ + const int destination = b; \ + const auto result = op(destination, source, x); \ + \ + b = uint8_t(result); \ + zero_op(status.zero_result, b); \ + status.extend_flag = status.carry_flag = uint_fast32_t(result & ~0xff); \ + status.negative_flag = result & 0x80; \ + status.overflow_flag = overflow() & 0x80; + +#define addsubw(a, b, op, overflow, x, zero_op) \ + const int source = a; \ + const int destination = b; \ + const auto result = op(destination, source, x); \ + \ + b = uint16_t(result); \ + zero_op(status.zero_result, b); \ + status.extend_flag = status.carry_flag = uint_fast32_t(result & ~0xffff); \ + status.negative_flag = result & 0x8000; \ + status.overflow_flag = overflow() & 0x8000; + +#define addsubl(a, b, op, overflow, x, zero_op) \ + const uint64_t source = a; \ + const uint64_t destination = b; \ + const auto result = op(destination, source, x); \ + \ + b = uint32_t(result); \ + zero_op(status.zero_result, b); \ + status.extend_flag = status.carry_flag = uint_fast32_t(result >> 32); \ + status.negative_flag = result & 0x80000000; \ + status.overflow_flag = overflow() & 0x80000000; + +#define addb(a, b, x, z) addsubb(a, b, addop, add_overflow, x, z) +#define subb(a, b, x, z) addsubb(a, b, subop, sub_overflow, x, z) +#define addw(a, b, x, z) addsubw(a, b, addop, add_overflow, x, z) +#define subw(a, b, x, z) addsubw(a, b, subop, sub_overflow, x, z) +#define addl(a, b, x, z) addsubl(a, b, addop, add_overflow, x, z) +#define subl(a, b, x, z) addsubl(a, b, subop, sub_overflow, x, z) + +#define no_extend(op, a, b) op(a, b, 0, z_set) +#define extend(op, a, b) op(a, b, status.extend_flag, z_or) + + // ADD and ADDA add two quantities, the latter sign extending and without setting any flags; + // ADDQ and SUBQ act as ADD and SUB, but taking the second argument from the instruction code. + case Operation::ADDb: { + no_extend( addb, + src.b, + dest.b); + } break; + + case Operation::ADDXb: { + extend( addb, + src.b, + dest.b); + } break; + + case Operation::ADDw: { + no_extend( addw, + src.w, + dest.w); + } break; + + case Operation::ADDXw: { + extend( addw, + src.w, + dest.w); + } break; + + case Operation::ADDl: { + no_extend( addl, + src.l, + dest.l); + } break; + + case Operation::ADDXl: { + extend( addl, + src.l, + dest.l); + } break; + + case Operation::SUBb: { + no_extend( subb, + src.b, + dest.b); + } break; + + case Operation::SUBXb: { + extend( subb, + src.b, + dest.b); + } break; + + case Operation::SUBw: { + no_extend( subw, + src.w, + dest.w); + } break; + + case Operation::SUBXw: { + extend( subw, + src.w, + dest.w); + } break; + + case Operation::SUBl: { + no_extend( subl, + src.l, + dest.l); + } break; + + case Operation::SUBXl: { + extend( subl, + src.l, + dest.l); + } break; + +#undef addl +#undef addw +#undef addb +#undef subl +#undef subw +#undef subb +#undef addsubl +#undef addsubw +#undef addsubb +#undef z_set +#undef z_or +#undef no_extend +#undef extend +#undef addop +#undef subop + + case Operation::ADDAw: + dest.l += u_extend16(src.w); + break; + + case Operation::ADDAl: + dest.l += src.l; + break; + + case Operation::SUBAw: + dest.l -= u_extend16(src.w); + break; + + case Operation::SUBAl: + dest.l -= src.l; + break; + +#define get_mask() \ + const uint32_t mask_size = (instruction.mode<1>() == AddressingMode::DataRegisterDirect) ? 31 : 7; \ + const uint32_t bit_position = src.l & mask_size; \ + const uint32_t bit_mask = 1 << bit_position + + // BTST/BCLR/etc: modulo for the mask depends on whether memory or a data register is the target. + case Operation::BTST: { + get_mask(); + status.zero_result = dest.l & bit_mask; + } break; + + case Operation::BCLR: { + get_mask(); + + status.zero_result = dest.l & bit_mask; + dest.l &= ~bit_mask; + flow_controller.did_bit_op(bit_position); + } break; + + case Operation::BCHG: { + get_mask(); + + status.zero_result = dest.l & bit_mask; + dest.l ^= bit_mask; + flow_controller.did_bit_op(bit_position); + } break; + + case Operation::BSET: { + get_mask(); + + status.zero_result = dest.l & bit_mask; + dest.l |= bit_mask; + flow_controller.did_bit_op(bit_position); + } break; + +#undef get_mask + + case Operation::Bccb: + flow_controller.template complete_bcc( + status.evaluate_condition(instruction.condition()), + src.b); + break; + + case Operation::Bccw: + flow_controller.template complete_bcc( + status.evaluate_condition(instruction.condition()), + src.w); + break; + + case Operation::Bccl: + flow_controller.template complete_bcc( + status.evaluate_condition(instruction.condition()), + src.l); + break; + + case Operation::BSRb: + flow_controller.bsr(int8_t(src.b) + 2); + break; + case Operation::BSRw: + flow_controller.bsr(int16_t(src.w) + 2); + break; + case Operation::BSRl: + flow_controller.bsr(src.l + 2); + break; + + case Operation::DBcc: { + const bool matched_condition = status.evaluate_condition(instruction.condition()); + bool overflowed = false; + + // Classify the dbcc. + if(!matched_condition) { + -- src.w; + overflowed = src.w == 0xffff; + } + + // Take the branch. + flow_controller.complete_dbcc( + matched_condition, + overflowed, + int16_t(dest.w)); + } break; + + case Operation::Scc: + src.b = status.evaluate_condition(instruction.condition()) ? 0xff : 0x00; + break; + + /* + CLRs: store 0 to the destination, set the zero flag, and clear + negative, overflow and carry. + */ + case Operation::CLRb: + src.b = 0; + status.negative_flag = status.overflow_flag = status.carry_flag = status.zero_result = 0; + break; + + case Operation::CLRw: + src.w = 0; + status.negative_flag = status.overflow_flag = status.carry_flag = status.zero_result = 0; + break; + + case Operation::CLRl: + src.l = 0; + status.negative_flag = status.overflow_flag = status.carry_flag = status.zero_result = 0; + break; + + /* + CMP.b, CMP.l and CMP.w: sets the condition flags (other than extend) based on a subtraction + of the source from the destination; the result of the subtraction is not stored. + */ + case Operation::CMPb: { + const uint8_t source = src.b; + const uint8_t destination = dest.b; + const int result = destination - source; + + status.zero_result = result & 0xff; + status.carry_flag = Status::FlagT(result & ~0xff); + status.negative_flag = result & 0x80; + status.overflow_flag = sub_overflow() & 0x80; + } break; + + case Operation::CMPw: { + const uint16_t source = src.w; + const uint16_t destination = dest.w; + const int result = destination - source; + + status.zero_result = result & 0xffff; + status.carry_flag = Status::FlagT(result & ~0xffff); + status.negative_flag = result & 0x8000; + status.overflow_flag = sub_overflow() & 0x8000; + } break; + + case Operation::CMPAw: { + const auto source = uint64_t(u_extend16(src.w)); + const uint64_t destination = dest.l; + const auto result = destination - source; + + status.zero_result = uint32_t(result); + status.carry_flag = result >> 32; + status.negative_flag = result & 0x80000000; + status.overflow_flag = sub_overflow() & 0x80000000; + } break; + + // TODO: is there any benefit to keeping both of these? + case Operation::CMPAl: + case Operation::CMPl: { + const auto source = uint64_t(src.l); + const auto destination = uint64_t(dest.l); + const auto result = destination - source; + + status.zero_result = uint32_t(result); + status.carry_flag = result >> 32; + status.negative_flag = result & 0x80000000; + status.overflow_flag = sub_overflow() & 0x80000000; + } break; + + // JMP: copies EA(0) to the program counter. + case Operation::JMP: + flow_controller.jmp(src.l); + break; + + // JSR: jump to EA(0), pushing the current PC to the stack. + case Operation::JSR: + flow_controller.jsr(src.l); + break; + + /* + MOVE.b, MOVE.l and MOVE.w: move the least significant byte or word, or the entire long word, + and set negative, zero, overflow and carry as appropriate. + */ + case Operation::MOVEb: + status.zero_result = dest.b = src.b; + status.negative_flag = status.zero_result & 0x80; + status.overflow_flag = status.carry_flag = 0; + break; + + case Operation::MOVEw: + status.zero_result = dest.w = src.w; + status.negative_flag = status.zero_result & 0x8000; + status.overflow_flag = status.carry_flag = 0; + break; + + case Operation::MOVEl: + status.zero_result = dest.l = src.l; + status.negative_flag = status.zero_result & 0x80000000; + status.overflow_flag = status.carry_flag = 0; + break; + + /* + MOVEA.l: move the entire long word; + MOVEA.w: move the least significant word and sign extend it. + Neither sets any flags. + */ + case Operation::MOVEAw: + dest.l = u_extend16(src.w); + break; + + case Operation::MOVEAl: + dest.l = src.l; + break; + + case Operation::LEA: + dest.l = src.l; + break; + + case Operation::PEA: + flow_controller.pea(src.l); + break; + + /* + Status word moves and manipulations. + */ + + case Operation::MOVEtoSR: + status.set_status(src.w); + flow_controller.did_update_status(); + break; + + case Operation::MOVEfromSR: + src.w = status.status(); + break; + + case Operation::MOVEtoCCR: + status.set_ccr(src.w); + break; + + case Operation::MOVEtoUSP: + flow_controller.move_to_usp(src.l); + break; + + case Operation::MOVEfromUSP: + flow_controller.move_from_usp(src.l); + break; + + case Operation::EXTbtow: + src.w = uint16_t(int8_t(src.b)); + status.overflow_flag = status.carry_flag = 0; + status.zero_result = src.w; + status.negative_flag = status.zero_result & 0x8000; + break; + + case Operation::EXTwtol: + src.l = u_extend16(src.w); + status.overflow_flag = status.carry_flag = 0; + status.zero_result = src.l; + status.negative_flag = status.zero_result & 0x80000000; + break; + +#define and_op(a, b) a &= b +#define or_op(a, b) a |= b +#define eor_op(a, b) a ^= b + +#define apply(op, func) { \ + auto sr = status.status(); \ + op(sr, src.w); \ + func(sr); \ +} + +#define set_status(x) status.set_status(x); flow_controller.did_update_status() +#define set_ccr(x) status.set_ccr(x) + +#define apply_op_sr(op) apply(op, set_status) +#define apply_op_ccr(op) apply(op, set_ccr) + + case Operation::ANDItoSR: apply_op_sr(and_op); break; + case Operation::EORItoSR: apply_op_sr(eor_op); break; + case Operation::ORItoSR: apply_op_sr(or_op); break; + + case Operation::ANDItoCCR: apply_op_ccr(and_op); break; + case Operation::EORItoCCR: apply_op_ccr(eor_op); break; + case Operation::ORItoCCR: apply_op_ccr(or_op); break; + +#undef apply_op_ccr +#undef apply_op_sr +#undef set_ccr +#undef set_status +#undef apply +#undef eor_op +#undef or_op +#undef and_op + + /* + Multiplications. + */ + + case Operation::MULU: + dest.l = dest.w * src.w; + status.carry_flag = status.overflow_flag = 0; + status.zero_result = dest.l; + status.negative_flag = status.zero_result & 0x80000000; + flow_controller.did_mulu(src.w); + break; + + case Operation::MULS: + dest.l = + u_extend16(dest.w) * u_extend16(src.w); + status.carry_flag = status.overflow_flag = 0; + status.zero_result = dest.l; + status.negative_flag = status.zero_result & 0x80000000; + flow_controller.did_muls(src.w); + break; + + /* + Divisions. + */ + +#define announce_divide_by_zero() \ + status.negative_flag = status.overflow_flag = 0; \ + status.zero_result = 1; \ + flow_controller.raise_exception(Exception::IntegerDivideByZero) + + case Operation::DIVU: { + status.carry_flag = 0; + + // An attempt to divide by zero schedules an exception. + if(!src.w) { + // Schedule a divide-by-zero exception. + announce_divide_by_zero(); + return; + } + + uint32_t dividend = dest.l; + uint32_t divisor = src.w; + const auto quotient = dividend / divisor; + + // If overflow would occur, appropriate flags are set and the result is not written back. + if(quotient > 65535) { + status.overflow_flag = status.zero_result = status.negative_flag = 1; + flow_controller.template did_divu(dividend, divisor); + return; + } + + const uint16_t remainder = uint16_t(dividend % divisor); + dest.l = uint32_t((remainder << 16) | uint16_t(quotient)); + + status.overflow_flag = 0; + status.zero_result = quotient; + status.negative_flag = status.zero_result & 0x8000; + flow_controller.template did_divu(dividend, divisor); + } break; + + case Operation::DIVS: { + status.carry_flag = 0; + + // An attempt to divide by zero schedules an exception. + if(!src.w) { + // Schedule a divide-by-zero exception. + announce_divide_by_zero(); + break; + } + + const int32_t signed_dividend = int32_t(dest.l); + const int32_t signed_divisor = s_extend16(src.w); + const auto result_sign = + ( (0 <= signed_dividend) - (signed_dividend < 0) ) * + ( (0 <= signed_divisor) - (signed_divisor < 0) ); + + const uint32_t dividend = uint32_t(std::abs(signed_dividend)); + const uint32_t divisor = uint32_t(std::abs(signed_divisor)); + + int cycles_expended = 12; // Covers the nn nnn n to get beyond the sign test. + if(signed_dividend < 0) { + cycles_expended += 2; // An additional microycle applies if the dividend is negative. + } + + // Check for overflow. If it exists, work here is already done. + const auto quotient = dividend / divisor; + if(quotient > 32767) { + status.overflow_flag = 1; + flow_controller.template did_divs(signed_dividend, signed_divisor); + break; + } + + const uint16_t remainder = uint16_t(signed_dividend % signed_divisor); + const int signed_quotient = result_sign*int(quotient); + dest.l = uint32_t((remainder << 16) | uint16_t(signed_quotient)); + + status.zero_result = Status::FlagT(signed_quotient); + status.negative_flag = status.zero_result & 0x8000; + status.overflow_flag = 0; + flow_controller.template did_divs(signed_dividend, signed_divisor); + } break; + +#undef announce_divide_by_zero + + // TRAP, which is a nicer form of ILLEGAL. + case Operation::TRAP: + flow_controller.template raise_exception(src.l + Exception::TrapBase); + break; + + case Operation::TRAPV: { + if(status.overflow_flag) { + flow_controller.template raise_exception(Exception::TRAPV); + } + } break; + + case Operation::CHK: { + const bool is_under = s_extend16(dest.w) < 0; + const bool is_over = s_extend16(dest.w) > s_extend16(src.w); + + status.overflow_flag = status.carry_flag = 0; + status.zero_result = dest.w; + + // Test applied for N: + // + // if Dn < 0, set negative flag; + // otherwise, if Dn > , reset negative flag. + if(is_over) status.negative_flag = 0; + if(is_under) status.negative_flag = 1; + + // No exception is the default course of action; deviate only if an + // exception is necessary. + flow_controller.did_chk(is_under, is_over); + if(is_under || is_over) { + flow_controller.template raise_exception(Exception::CHK); + } + } break; + + /* + NEGs: negatives the destination, setting the zero, + negative, overflow and carry flags appropriate, and extend. + + NB: since the same logic as SUB is used to calculate overflow, + and SUB calculates `destination - source`, the NEGs deliberately + label 'source' and 'destination' differently from Motorola. + */ + case Operation::NEGb: { + const int destination = 0; + const int source = src.b; + const auto result = destination - source; + src.b = uint8_t(result); + + status.zero_result = result & 0xff; + status.extend_flag = status.carry_flag = Status::FlagT(result & ~0xff); + status.negative_flag = result & 0x80; + status.overflow_flag = sub_overflow() & 0x80; + } break; + + case Operation::NEGw: { + const int destination = 0; + const int source = src.w; + const auto result = destination - source; + src.w = uint16_t(result); + + status.zero_result = result & 0xffff; + status.extend_flag = status.carry_flag = Status::FlagT(result & ~0xffff); + status.negative_flag = result & 0x8000; + status.overflow_flag = sub_overflow() & 0x8000; + } break; + + case Operation::NEGl: { + const uint64_t destination = 0; + const uint64_t source = src.l; + const auto result = destination - source; + src.l = uint32_t(result); + + status.zero_result = uint_fast32_t(result); + status.extend_flag = status.carry_flag = result >> 32; + status.negative_flag = result & 0x80000000; + status.overflow_flag = sub_overflow() & 0x80000000; + } break; + + /* + NEGXs: NEG, with extend. + */ + case Operation::NEGXb: { + const int source = src.b; + const int destination = 0; + const auto result = destination - source - (status.extend_flag ? 1 : 0); + src.b = uint8_t(result); + + status.zero_result |= result & 0xff; + status.extend_flag = status.carry_flag = Status::FlagT(result & ~0xff); + status.negative_flag = result & 0x80; + status.overflow_flag = sub_overflow() & 0x80; + } break; + + case Operation::NEGXw: { + const int source = src.w; + const int destination = 0; + const auto result = destination - source - (status.extend_flag ? 1 : 0); + src.w = uint16_t(result); + + status.zero_result |= result & 0xffff; + status.extend_flag = status.carry_flag = Status::FlagT(result & ~0xffff); + status.negative_flag = result & 0x8000; + status.overflow_flag = sub_overflow() & 0x8000; + } break; + + case Operation::NEGXl: { + const uint64_t source = src.l; + const uint64_t destination = 0; + const auto result = destination - source - (status.extend_flag ? 1 : 0); + src.l = uint32_t(result); + + status.zero_result |= uint_fast32_t(result); + status.extend_flag = status.carry_flag = result >> 32; + status.negative_flag = result & 0x80000000; + status.overflow_flag = sub_overflow() & 0x80000000; + } break; + + /* + The no-op. + */ + case Operation::NOP: break; + + /* + LINK and UNLINK help with stack frames, allowing a certain + amount of stack space to be allocated or deallocated. + */ + + case Operation::LINKw: + flow_controller.link(instruction, int16_t(dest.w)); + break; + + case Operation::UNLINK: + flow_controller.unlink(src.l); + break; + + /* + TAS: sets zero and negative depending on the current value of the destination, + and sets the high bit, using a specialised atomic bus cycle. + */ + + case Operation::TAS: + flow_controller.tas(instruction, src.l); + break; + + /* + Bitwise operators: AND, OR and EOR. All three clear the overflow and carry flags, + and set zero and negative appropriately. + */ +#define op_and(x, y) x &= y +#define op_or(x, y) x |= y +#define op_eor(x, y) x ^= y + +#define bitwise(source, dest, sign_mask, operator) \ + operator(dest, source); \ + status.overflow_flag = status.carry_flag = 0; \ + status.zero_result = dest; \ + status.negative_flag = dest & sign_mask; + +#define andx(source, dest, sign_mask) bitwise(source, dest, sign_mask, op_and) +#define eorx(source, dest, sign_mask) bitwise(source, dest, sign_mask, op_eor) +#define orx(source, dest, sign_mask) bitwise(source, dest, sign_mask, op_or) + +#define op_bwl(name, op) \ + case Operation::name##b: op(src.b, dest.b, 0x80); break; \ + case Operation::name##w: op(src.w, dest.w, 0x8000); break; \ + case Operation::name##l: op(src.l, dest.l, 0x80000000); break; + + op_bwl(AND, andx); + op_bwl(EOR, eorx); + op_bwl(OR, orx); + +#undef op_bwl +#undef orx +#undef eorx +#undef andx +#undef bitwise +#undef op_eor +#undef op_or +#undef op_and + + // NOTs: take the logical inverse, affecting the negative and zero flags. + case Operation::NOTb: + src.b ^= 0xff; + status.zero_result = src.b; + status.negative_flag = status.zero_result & 0x80; + status.overflow_flag = status.carry_flag = 0; + break; + + case Operation::NOTw: + src.w ^= 0xffff; + status.zero_result = src.w; + status.negative_flag = status.zero_result & 0x8000; + status.overflow_flag = status.carry_flag = 0; + break; + + case Operation::NOTl: + src.l ^= 0xffffffff; + status.zero_result = src.l; + status.negative_flag = status.zero_result & 0x80000000; + status.overflow_flag = status.carry_flag = 0; + break; + +#define sbcd(d) \ + const int extend = (status.extend_flag ? 1 : 0); \ + const int unadjusted_result = destination - source - extend; \ + \ + const int top = (destination & 0xf0) - (source & 0xf0) - (0x60 & (unadjusted_result >> 4)); \ + \ + int result = (destination & 0xf) - (source & 0xf) - extend; \ + const int low_adjustment = 0x06 & (result >> 4); \ + status.extend_flag = status.carry_flag = Status::FlagT( \ + (unadjusted_result - low_adjustment) & 0x300 \ + ); \ + result = result + top - low_adjustment; \ + \ + /* Store the result. */ \ + d = uint8_t(result); \ + \ + /* Set all remaining flags essentially as if this were normal subtraction. */ \ + status.zero_result |= d; \ + status.negative_flag = result & 0x80; \ + status.overflow_flag = unadjusted_result & ~result & 0x80; \ + + /* + SBCD subtracts the lowest byte of the source from that of the destination using + BCD arithmetic, obeying the extend flag. + */ + case Operation::SBCD: { + const uint8_t source = src.b; + const uint8_t destination = dest.b; + sbcd(dest.b); + } break; + + /* + NBCD is like SBCD except that the result is 0 - source rather than + destination - source. + */ + case Operation::NBCD: { + const uint8_t source = src.b; + const uint8_t destination = 0; + sbcd(src.b); + } break; + +#undef sbcd + + // EXG and SWAP exchange/swap words or long words. + + case Operation::EXG: { + const auto temporary = src.l; + src.l = dest.l; + dest.l = temporary; + } break; + + case Operation::SWAP: { + uint16_t *const words = reinterpret_cast(&src.l); + const auto temporary = words[0]; + words[0] = words[1]; + words[1] = temporary; + + status.zero_result = src.l; + status.negative_flag = temporary & 0x8000; + status.overflow_flag = status.carry_flag = 0; + } break; + + /* + Shifts and rotates. + */ +#define set_neg_zero(v, m) \ + status.zero_result = Status::FlagT(v); \ + status.negative_flag = status.zero_result & Status::FlagT(m); + +#define set_neg_zero_overflow(v, m) \ + set_neg_zero(v, m); \ + status.overflow_flag = (Status::FlagT(value) ^ status.zero_result) & Status::FlagT(m); + +#define decode_shift_count() \ + int shift_count = src.l & 63; \ + flow_controller.did_shift(shift_count); + +#define set_flags_w(t) set_flags(src.w, 0x8000, t) + +#define asl(destination, size) {\ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + status.carry_flag = status.overflow_flag = 0; \ + } else { \ + destination = (shift_count < size) ? decltype(destination)(value << shift_count) : 0; \ + status.extend_flag = status.carry_flag = Status::FlagT(value) & Status::FlagT( (1u << (size - 1)) >> (shift_count - 1) ); \ + \ + if(shift_count >= size) status.overflow_flag = value && (value != decltype(value)(-1)); \ + else { \ + const auto mask = decltype(destination)(0xffffffff << (size - shift_count)); \ + status.overflow_flag = mask & value && ((mask & value) != mask); \ + } \ + } \ + \ + set_neg_zero(destination, 1 << (size - 1)); \ +} + + case Operation::ASLm: { + const auto value = src.w; + src.w = uint16_t(value << 1); + status.extend_flag = status.carry_flag = value & 0x8000; + set_neg_zero_overflow(src.w, 0x8000); + } break; + case Operation::ASLb: asl(dest.b, 8); break; + case Operation::ASLw: asl(dest.w, 16); break; + case Operation::ASLl: asl(dest.l, 32); break; + +#define asr(destination, size) {\ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + status.carry_flag = 0; \ + } else { \ + destination = (shift_count < size) ? \ + decltype(destination)(\ + (value >> shift_count) | \ + ((value & decltype(value)(1 << (size - 1)) ? 0xffffffff : 0x000000000) << (size - shift_count)) \ + ) : \ + decltype(destination)( \ + (value & decltype(value)(1 << (size - 1))) ? 0xffffffff : 0x000000000 \ + ); \ + status.extend_flag = status.carry_flag = Status::FlagT(value) & Status::FlagT(1 << (shift_count - 1)); \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ASRm: { + const auto value = src.w; + src.w = (value&0x8000) | (value >> 1); + status.extend_flag = status.carry_flag = value & 1; + set_neg_zero_overflow(src.w, 0x8000); + } break; + case Operation::ASRb: asr(dest.b, 8); break; + case Operation::ASRw: asr(dest.w, 16); break; + case Operation::ASRl: asr(dest.l, 32); break; + + +#undef set_neg_zero_overflow +#define set_neg_zero_overflow(v, m) \ + set_neg_zero(v, m); \ + status.overflow_flag = 0; + +#undef set_flags +#define set_flags(v, m, t) \ + status.zero_result = v; \ + status.negative_flag = status.zero_result & (m); \ + status.overflow_flag = 0; \ + status.carry_flag = value & (t); + +#define lsl(destination, size) {\ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + status.carry_flag = 0; \ + } else { \ + destination = (shift_count < size) ? decltype(destination)(value << shift_count) : 0; \ + status.extend_flag = status.carry_flag = Status::FlagT(value) & Status::FlagT( (1u << (size - 1)) >> (shift_count - 1) ); \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::LSLm: { + const auto value = src.w; + src.w = uint16_t(value << 1); + status.extend_flag = status.carry_flag = value & 0x8000; + set_neg_zero_overflow(src.w, 0x8000); + } break; + case Operation::LSLb: lsl(dest.b, 8); break; + case Operation::LSLw: lsl(dest.w, 16); break; + case Operation::LSLl: lsl(dest.l, 32); break; + +#define lsr(destination, size) {\ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + status.carry_flag = 0; \ + } else { \ + destination = (shift_count < size) ? (value >> shift_count) : 0; \ + status.extend_flag = status.carry_flag = value & Status::FlagT(1 << (shift_count - 1)); \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::LSRm: { + const auto value = src.w; + src.w = value >> 1; + status.extend_flag = status.carry_flag = value & 1; + set_neg_zero_overflow(src.w, 0x8000); + } break; + case Operation::LSRb: lsr(dest.b, 8); break; + case Operation::LSRw: lsr(dest.w, 16); break; + case Operation::LSRl: lsr(dest.l, 32); break; + +#define rol(destination, size) { \ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + status.carry_flag = 0; \ + } else { \ + shift_count &= (size - 1); \ + destination = decltype(destination)( \ + (value << shift_count) | \ + (value >> (size - shift_count)) \ + ); \ + status.carry_flag = Status::FlagT(destination & 1); \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ROLm: { + const auto value = src.w; + src.w = uint16_t((value << 1) | (value >> 15)); + status.carry_flag = src.w & 1; + set_neg_zero_overflow(src.w, 0x8000); + } break; + case Operation::ROLb: rol(dest.b, 8); break; + case Operation::ROLw: rol(dest.w, 16); break; + case Operation::ROLl: rol(dest.l, 32); break; + +#define ror(destination, size) { \ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + status.carry_flag = 0; \ + } else { \ + shift_count &= (size - 1); \ + destination = decltype(destination)(\ + (value >> shift_count) | \ + (value << (size - shift_count)) \ + );\ + status.carry_flag = destination & Status::FlagT(1 << (size - 1)); \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::RORm: { + const auto value = src.w; + src.w = uint16_t((value >> 1) | (value << 15)); + status.carry_flag = src.w & 0x8000; + set_neg_zero_overflow(src.w, 0x8000); + } break; + case Operation::RORb: ror(dest.b, 8); break; + case Operation::RORw: ror(dest.w, 16); break; + case Operation::RORl: ror(dest.l, 32); break; + +#define roxl(destination, size) { \ + decode_shift_count(); \ + \ + shift_count %= (size + 1); \ + uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \ + compound = \ + (compound << shift_count) | \ + (compound >> (size + 1 - shift_count)); \ + status.carry_flag = status.extend_flag = Status::FlagT((compound >> size) & 1); \ + destination = decltype(destination)(compound); \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ROXLm: { + const auto value = src.w; + src.w = uint16_t((value << 1) | (status.extend_flag ? 0x0001 : 0x0000)); + status.extend_flag = value & 0x8000; + set_flags_w(0x8000); + } break; + case Operation::ROXLb: roxl(dest.b, 8); break; + case Operation::ROXLw: roxl(dest.w, 16); break; + case Operation::ROXLl: roxl(dest.l, 32); break; + +#define roxr(destination, size) { \ + decode_shift_count(); \ + \ + shift_count %= (size + 1); \ + uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \ + compound = \ + (compound >> shift_count) | \ + (compound << (size + 1 - shift_count)); \ + status.carry_flag = status.extend_flag = Status::FlagT((compound >> size) & 1); \ + destination = decltype(destination)(compound); \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ROXRm: { + const auto value = src.w; + src.w = (value >> 1) | (status.extend_flag ? 0x8000 : 0x0000); + status.extend_flag = value & 0x0001; + set_flags_w(0x0001); + } break; + case Operation::ROXRb: roxr(dest.b, 8); break; + case Operation::ROXRw: roxr(dest.w, 16); break; + case Operation::ROXRl: roxr(dest.l, 32); break; + +#undef roxr +#undef roxl +#undef ror +#undef rol +#undef asr +#undef lsr +#undef lsl +#undef asl + +#undef set_flags +#undef decode_shift_count +#undef set_flags_w +#undef set_neg_zero_overflow +#undef set_neg_zero + + case Operation::MOVEPl: + flow_controller.template movep(instruction, src.l, dest.l); + break; + + case Operation::MOVEPw: + flow_controller.template movep(instruction, src.l, dest.l); + break; + + case Operation::MOVEMtoRl: + flow_controller.template movem_toR(instruction, src.l, dest.l); + break; + + case Operation::MOVEMtoMl: + flow_controller.template movem_toM(instruction, src.l, dest.l); + break; + + case Operation::MOVEMtoRw: + flow_controller.template movem_toR(instruction, src.l, dest.l); + break; + + case Operation::MOVEMtoMw: + flow_controller.template movem_toM(instruction, src.l, dest.l); + break; + + /* + RTE and RTR share an implementation. + */ + case Operation::RTR: + flow_controller.rtr(); + break; + + case Operation::RTE: + flow_controller.rte(); + break; + + case Operation::RTS: + flow_controller.rts(); + break; + + /* + TSTs: compare to zero. + */ + + case Operation::TSTb: + status.carry_flag = status.overflow_flag = 0; + status.zero_result = src.b; + status.negative_flag = status.zero_result & 0x80; + break; + + case Operation::TSTw: + status.carry_flag = status.overflow_flag = 0; + status.zero_result = src.w; + status.negative_flag = status.zero_result & 0x8000; + break; + + case Operation::TSTl: + status.carry_flag = status.overflow_flag = 0; + status.zero_result = src.l; + status.negative_flag = status.zero_result & 0x80000000; + break; + + case Operation::STOP: + status.set_status(src.w); + flow_controller.did_update_status(); + flow_controller.stop(); + break; + + case Operation::RESET: + flow_controller.reset(); + break; + + /* + Development period debugging. + */ + default: + assert(false); + break; + } +#undef sub_overflow +#undef add_overflow + +#undef u_extend16 +#undef s_extend16 + +} + +} +} + +#endif /* InstructionSets_M68k_PerformImplementation_h */ diff --git a/InstructionSets/M68k/Instruction.cpp b/InstructionSets/M68k/Instruction.cpp new file mode 100644 index 000000000..b4184c868 --- /dev/null +++ b/InstructionSets/M68k/Instruction.cpp @@ -0,0 +1,295 @@ +// +// Instruction.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/05/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#include "Instruction.hpp" + +#include + +using namespace InstructionSet::M68k; + +std::string Preinstruction::operand_description(int index, int opcode) const { + switch(mode(index)) { + default: assert(false); + + case AddressingMode::None: + return ""; + + case AddressingMode::DataRegisterDirect: + return std::string("D") + std::to_string(reg(index)); + + case AddressingMode::AddressRegisterDirect: + return std::string("A") + std::to_string(reg(index)); + case AddressingMode::AddressRegisterIndirect: + return std::string("(A") + std::to_string(reg(index)) + ")"; + case AddressingMode::AddressRegisterIndirectWithPostincrement: + return std::string("(A") + std::to_string(reg(index)) + ")+"; + case AddressingMode::AddressRegisterIndirectWithPredecrement: + return std::string("-(A") + std::to_string(reg(index)) + ")"; + case AddressingMode::AddressRegisterIndirectWithDisplacement: + return std::string("(d16, A") + std::to_string(reg(index)) + ")"; + case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement: + return std::string("(d8, A") + std::to_string(reg(index)) + ", Xn)"; + + case AddressingMode::ProgramCounterIndirectWithDisplacement: + return "(d16, PC)"; + case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement: + return "(d8, PC, Xn)"; + + case AddressingMode::AbsoluteShort: + return "(xxx).w"; + case AddressingMode::AbsoluteLong: + return "(xxx).l"; + + case AddressingMode::ImmediateData: + return "#"; + + case AddressingMode::Quick: + if(opcode == -1) { + return "Q"; + } + return std::to_string(int(quick(uint16_t(opcode), operation))); + } +} + +std::string Preinstruction::to_string(int opcode) const { + bool flip_operands = false; + const char *instruction; + + switch(operation) { + case Operation::Undefined: instruction = "None"; break; + case Operation::NOP: instruction = "NOP"; break; + case Operation::ABCD: instruction = "ABCD"; break; + case Operation::SBCD: instruction = "SBCD"; break; + case Operation::NBCD: instruction = "NBCD"; break; + + case Operation::ADDb: instruction = "ADD.b"; break; + case Operation::ADDw: instruction = "ADD.w"; break; + case Operation::ADDl: instruction = "ADD.l"; break; + + case Operation::ADDAw: + if(mode<0>() == AddressingMode::Quick) { + instruction = "ADD.w"; + } else { + instruction = "ADDA.w"; + } + break; + case Operation::ADDAl: + if(mode<0>() == AddressingMode::Quick) { + instruction = "ADD.l"; + } else { + instruction = "ADDA.l"; + } + break; + + case Operation::ADDXb: instruction = "ADDX.b"; break; + case Operation::ADDXw: instruction = "ADDX.w"; break; + case Operation::ADDXl: instruction = "ADDX.l"; break; + + case Operation::SUBb: instruction = "SUB.b"; break; + case Operation::SUBw: instruction = "SUB.w"; break; + case Operation::SUBl: instruction = "SUB.l"; break; + + case Operation::SUBAw: + if(mode<0>() == AddressingMode::Quick) { + instruction = "SUB.w"; + } else { + instruction = "SUBA.w"; + } + break; + case Operation::SUBAl: + if(mode<0>() == AddressingMode::Quick) { + instruction = "SUB.l"; + } else { + instruction = "SUBA.l"; + } + break; + + case Operation::SUBXb: instruction = "SUBX.b"; break; + case Operation::SUBXw: instruction = "SUBX.w"; break; + case Operation::SUBXl: instruction = "SUBX.l"; break; + + case Operation::MOVEb: instruction = "MOVE.b"; break; + case Operation::MOVEw: instruction = "MOVE.w"; break; + case Operation::MOVEl: + if(mode<0>() == AddressingMode::Quick) { + instruction = "MOVE.q"; + } else { + instruction = "MOVE.l"; + } + break; + + case Operation::MOVEAw: instruction = "MOVEA.w"; break; + case Operation::MOVEAl: instruction = "MOVEA.l"; break; + + case Operation::LEA: instruction = "LEA"; break; + case Operation::PEA: instruction = "PEA"; break; + + case Operation::MOVEtoSR: instruction = "MOVEtoSR"; break; + case Operation::MOVEfromSR: instruction = "MOVEfromSR"; break; + case Operation::MOVEtoCCR: instruction = "MOVEtoCCR"; break; + case Operation::MOVEtoUSP: instruction = "MOVEtoUSP"; break; + case Operation::MOVEfromUSP: instruction = "MOVEfromUSP"; break; + + case Operation::ORItoSR: instruction = "ORItoSR"; break; + case Operation::ORItoCCR: instruction = "ORItoCCR"; break; + case Operation::ANDItoSR: instruction = "ANDItoSR"; break; + case Operation::ANDItoCCR: instruction = "ANDItoCCR"; break; + case Operation::EORItoSR: instruction = "EORItoSR"; break; + case Operation::EORItoCCR: instruction = "EORItoCCR"; break; + + case Operation::BTST: instruction = "BTST"; break; + case Operation::BCLR: instruction = "BCLR"; break; + case Operation::BCHG: instruction = "BCHG"; break; + case Operation::BSET: instruction = "BSET"; break; + + case Operation::CMPb: instruction = "CMP.b"; break; + case Operation::CMPw: instruction = "CMP.w"; break; + case Operation::CMPl: instruction = "CMP.l"; break; + + case Operation::CMPAw: instruction = "CMPA.w"; break; + case Operation::CMPAl: instruction = "CMPA.l"; break; + + case Operation::TSTb: instruction = "TST.b"; break; + case Operation::TSTw: instruction = "TST.w"; break; + case Operation::TSTl: instruction = "TST.l"; break; + + case Operation::JMP: instruction = "JMP"; break; + case Operation::JSR: instruction = "JSR"; break; + case Operation::RTS: instruction = "RTS"; break; + case Operation::DBcc: instruction = "DBcc"; break; + case Operation::Scc: instruction = "Scc"; break; + + case Operation::Bccb: + case Operation::Bccl: + case Operation::Bccw: instruction = "Bcc"; break; + + case Operation::BSRb: + case Operation::BSRl: + case Operation::BSRw: instruction = "BSR"; break; + + case Operation::CLRb: instruction = "CLR.b"; break; + case Operation::CLRw: instruction = "CLR.w"; break; + case Operation::CLRl: instruction = "CLR.l"; break; + + case Operation::NEGXb: instruction = "NEGX.b"; break; + case Operation::NEGXw: instruction = "NEGX.w"; break; + case Operation::NEGXl: instruction = "NEGX.l"; break; + + case Operation::NEGb: instruction = "NEG.b"; break; + case Operation::NEGw: instruction = "NEG.w"; break; + case Operation::NEGl: instruction = "NEG.l"; break; + + case Operation::ASLb: instruction = "ASL.b"; break; + case Operation::ASLw: instruction = "ASL.w"; break; + case Operation::ASLl: instruction = "ASL.l"; break; + case Operation::ASLm: instruction = "ASL.w"; break; + + case Operation::ASRb: instruction = "ASR.b"; break; + case Operation::ASRw: instruction = "ASR.w"; break; + case Operation::ASRl: instruction = "ASR.l"; break; + case Operation::ASRm: instruction = "ASR.w"; break; + + case Operation::LSLb: instruction = "LSL.b"; break; + case Operation::LSLw: instruction = "LSL.w"; break; + case Operation::LSLl: instruction = "LSL.l"; break; + case Operation::LSLm: instruction = "LSL.w"; break; + + case Operation::LSRb: instruction = "LSR.b"; break; + case Operation::LSRw: instruction = "LSR.w"; break; + case Operation::LSRl: instruction = "LSR.l"; break; + case Operation::LSRm: instruction = "LSR.w"; break; + + case Operation::ROLb: instruction = "ROL.b"; break; + case Operation::ROLw: instruction = "ROL.w"; break; + case Operation::ROLl: instruction = "ROL.l"; break; + case Operation::ROLm: instruction = "ROL.w"; break; + + case Operation::RORb: instruction = "ROR.b"; break; + case Operation::RORw: instruction = "ROR.w"; break; + case Operation::RORl: instruction = "ROR.l"; break; + case Operation::RORm: instruction = "ROR.w"; break; + + case Operation::ROXLb: instruction = "ROXL.b"; break; + case Operation::ROXLw: instruction = "ROXL.w"; break; + case Operation::ROXLl: instruction = "ROXL.l"; break; + case Operation::ROXLm: instruction = "ROXL.w"; break; + + case Operation::ROXRb: instruction = "ROXR.b"; break; + case Operation::ROXRw: instruction = "ROXR.w"; break; + case Operation::ROXRl: instruction = "ROXR.l"; break; + case Operation::ROXRm: instruction = "ROXR.w"; break; + + case Operation::MOVEMtoMl: instruction = "MOVEM.l"; break; + case Operation::MOVEMtoMw: instruction = "MOVEM.w"; break; + case Operation::MOVEMtoRl: + instruction = "MOVEM.l"; + flip_operands = true; + break; + case Operation::MOVEMtoRw: + instruction = "MOVEM.w"; + flip_operands = true; + break; + + case Operation::MOVEPl: instruction = "MOVEP.l"; break; + case Operation::MOVEPw: instruction = "MOVEP.w"; break; + + case Operation::ANDb: instruction = "AND.b"; break; + case Operation::ANDw: instruction = "AND.w"; break; + case Operation::ANDl: instruction = "AND.l"; break; + + case Operation::EORb: instruction = "EOR.b"; break; + case Operation::EORw: instruction = "EOR.w"; break; + case Operation::EORl: instruction = "EOR.l"; break; + + case Operation::NOTb: instruction = "NOT.b"; break; + case Operation::NOTw: instruction = "NOT.w"; break; + case Operation::NOTl: instruction = "NOT.l"; break; + + case Operation::ORb: instruction = "OR.b"; break; + case Operation::ORw: instruction = "OR.w"; break; + case Operation::ORl: instruction = "OR.l"; break; + + case Operation::MULU: instruction = "MULU"; break; + case Operation::MULS: instruction = "MULS"; break; + case Operation::DIVU: instruction = "DIVU"; break; + case Operation::DIVS: instruction = "DIVS"; break; + + case Operation::RTE: instruction = "RTE"; break; + case Operation::RTR: instruction = "RTR"; break; + + case Operation::TRAP: instruction = "TRAP"; break; + case Operation::TRAPV: instruction = "TRAPV"; break; + case Operation::CHK: instruction = "CHK"; break; + + case Operation::EXG: instruction = "EXG"; break; + case Operation::SWAP: instruction = "SWAP"; break; + + case Operation::TAS: instruction = "TAS"; break; + + case Operation::EXTbtow: instruction = "EXT.w"; break; + case Operation::EXTwtol: instruction = "EXT.l"; break; + + case Operation::LINKw: instruction = "LINK"; break; + case Operation::UNLINK: instruction = "UNLINK"; break; + + case Operation::STOP: instruction = "STOP"; break; + case Operation::RESET: instruction = "RESET"; break; + + default: + assert(false); + } + + const std::string operand1 = operand_description(0 ^ int(flip_operands), opcode); + const std::string operand2 = operand_description(1 ^ int(flip_operands), opcode); + + std::string result = instruction; + if(!operand1.empty()) result += std::string(" ") + operand1; + if(!operand2.empty()) result += std::string(", ") + operand2; + + return result; +} diff --git a/InstructionSets/M68k/Instruction.hpp b/InstructionSets/M68k/Instruction.hpp index 252fb5e77..a016fac9c 100644 --- a/InstructionSets/M68k/Instruction.hpp +++ b/InstructionSets/M68k/Instruction.hpp @@ -9,9 +9,12 @@ #ifndef InstructionSets_68k_Instruction_hpp #define InstructionSets_68k_Instruction_hpp -#include #include "Model.hpp" +#include +#include +#include + namespace InstructionSet { namespace M68k { @@ -70,7 +73,9 @@ enum class Operation: uint8_t { ROXLb, ROXLw, ROXLl, ROXLm, ROXRb, ROXRw, ROXRl, ROXRm, - MOVEMl, MOVEMw, + MOVEMtoRl, MOVEMtoRw, + MOVEMtoMl, MOVEMtoMw, + MOVEPl, MOVEPw, ANDb, ANDw, ANDl, @@ -111,6 +116,7 @@ constexpr bool requires_supervisor(Operation op) { case Operation::EORItoSR: case Operation::RTE: case Operation::RESET: case Operation::STOP: case Operation::MOVEtoUSP: case Operation::MOVEfromUSP: + case Operation::MOVEtoSR: return true; default: @@ -125,108 +131,15 @@ enum class DataSize { }; /// Classifies operations by the size of their memory accesses, if any. -constexpr DataSize size(Operation operation) { - switch(operation) { - // These are given a value arbitrarily, to - // complete the switch statement. - case Operation::Undefined: - case Operation::NOP: - case Operation::STOP: - case Operation::RESET: - case Operation::RTE: case Operation::RTR: - case Operation::TRAP: - case Operation::TRAPV: +/// +/// For any operations that don't fit the neat model of reading one or two operands, +/// then writing zero or one, the size determines the data size of the operands only, +/// not any other accesses. +constexpr DataSize operand_size(Operation operation); - case Operation::ABCD: case Operation::SBCD: - case Operation::NBCD: - case Operation::ADDb: case Operation::ADDXb: - case Operation::SUBb: case Operation::SUBXb: - case Operation::MOVEb: - case Operation::ORItoCCR: - case Operation::ANDItoCCR: - case Operation::EORItoCCR: - case Operation::BTST: case Operation::BCLR: - case Operation::BCHG: case Operation::BSET: - case Operation::CMPb: case Operation::TSTb: - case Operation::Bccb: case Operation::BSRb: - case Operation::CLRb: - case Operation::NEGXb: case Operation::NEGb: - case Operation::ASLb: case Operation::ASRb: - case Operation::LSLb: case Operation::LSRb: - case Operation::ROLb: case Operation::RORb: - case Operation::ROXLb: case Operation::ROXRb: - case Operation::ANDb: case Operation::EORb: - case Operation::NOTb: case Operation::ORb: - case Operation::CHK: - case Operation::TAS: - return DataSize::Byte; - - case Operation::ADDw: case Operation::ADDAw: - case Operation::ADDXw: case Operation::SUBw: - case Operation::SUBAw: case Operation::SUBXw: - case Operation::MOVEw: case Operation::MOVEAw: - case Operation::ORItoSR: - case Operation::ANDItoSR: - case Operation::EORItoSR: - case Operation::MOVEtoSR: - case Operation::MOVEfromSR: - case Operation::MOVEtoCCR: - case Operation::CMPw: case Operation::CMPAw: - case Operation::TSTw: - case Operation::DBcc: case Operation::Scc: - case Operation::Bccw: case Operation::BSRw: - case Operation::CLRw: - case Operation::NEGXw: case Operation::NEGw: - case Operation::ASLw: case Operation::ASLm: - case Operation::ASRw: case Operation::ASRm: - case Operation::LSLw: case Operation::LSLm: - case Operation::LSRw: case Operation::LSRm: - case Operation::ROLw: case Operation::ROLm: - case Operation::RORw: case Operation::RORm: - case Operation::ROXLw: case Operation::ROXLm: - case Operation::ROXRw: case Operation::ROXRm: - case Operation::MOVEMw: - case Operation::MOVEPw: - case Operation::ANDw: case Operation::EORw: - case Operation::NOTw: case Operation::ORw: - case Operation::DIVU: case Operation::DIVS: - case Operation::MULU: case Operation::MULS: - case Operation::EXTbtow: - case Operation::LINKw: - return DataSize::Word; - - case Operation::ADDl: case Operation::ADDAl: - case Operation::ADDXl: case Operation::SUBl: - case Operation::SUBAl: case Operation::SUBXl: - case Operation::MOVEl: case Operation::MOVEAl: - case Operation::LEA: case Operation::PEA: - case Operation::EXG: case Operation::SWAP: - case Operation::MOVEtoUSP: - case Operation::MOVEfromUSP: - case Operation::CMPl: case Operation::CMPAl: - case Operation::TSTl: - case Operation::JMP: case Operation::JSR: - case Operation::RTS: - case Operation::Bccl: case Operation::BSRl: - case Operation::CLRl: - case Operation::NEGXl: case Operation::NEGl: - case Operation::ASLl: case Operation::ASRl: - case Operation::LSLl: case Operation::LSRl: - case Operation::ROLl: case Operation::RORl: - case Operation::ROXLl: case Operation::ROXRl: - case Operation::MOVEMl: - case Operation::MOVEPl: - case Operation::ANDl: case Operation::EORl: - case Operation::NOTl: case Operation::ORl: - case Operation::EXTwtol: - case Operation::UNLINK: - return DataSize::LongWord; - } -} - -template -constexpr uint32_t quick(uint16_t instruction) { - switch(op) { +template +constexpr uint32_t quick(uint16_t instruction, Operation r_op = Operation::Undefined) { + switch((t_op != Operation::Undefined) ? t_op : r_op) { case Operation::Bccb: case Operation::BSRb: case Operation::MOVEl: return uint32_t(int8_t(instruction)); @@ -239,19 +152,22 @@ constexpr uint32_t quick(uint16_t instruction) { } } -constexpr uint32_t quick(Operation op, uint16_t instruction) { - switch(op) { - case Operation::MOVEl: return quick(instruction); - case Operation::Bccb: return quick(instruction); - case Operation::BSRb: return quick(instruction); - case Operation::TRAP: return quick(instruction); +static constexpr uint8_t FetchOp1 = (1 << 0); +static constexpr uint8_t FetchOp2 = (1 << 1); +static constexpr uint8_t StoreOp1 = (1 << 2); +static constexpr uint8_t StoreOp2 = (1 << 3); - default: - // ADDw is arbitrary; anything other than those listed above will do. - return quick(instruction); - } -} +/*! + Provides a bitfield with a value in the range 0–15 indicating which of the provided operation's + operands are accessed via standard fetch and store cycles; the bitfield is composted of + [Fetch/Store]Op[1/2] as defined above. + Unusual bus sequences, such as TAS or MOVEM, are not described here. +*/ +template +uint8_t operand_flags(Operation r_operation = Operation::Undefined); + +/// Lists the various condition codes used by the 680x0. enum class Condition { True = 0x00, False = 0x01, High = 0x02, LowOrSame = 0x03, @@ -303,23 +219,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 @@ -356,27 +272,44 @@ class Preinstruction { // For one-operand instructions, only argument 0 will // be provided, and will be a source and/or destination as // per the semantics of the operation. + // + // The versions templated on index do a range check; + // if using the runtime versions then results for indices + // other than 0 and 1 are undefined. + AddressingMode mode(int index) const { + return AddressingMode(operands_[index] >> 3); + } template AddressingMode mode() const { if constexpr (index > 1) { return AddressingMode::None; } - return AddressingMode(operands_[index] & 0x1f); + return mode(index); + } + int reg(int index) const { + return operands_[index] & 7; } template int reg() const { if constexpr (index > 1) { return 0; } - return operands_[index] >> 5; + return reg(index); } - bool requires_supervisor() { + /// @returns 0–7 to indicate data registers 0 to 7, or 8–15 to indicate address registers 0 to 7 respectively. + /// Provides undefined results if the addressing mode is not either @c DataRegisterDirect or + /// @c AddressRegisterDirect. + int lreg(int index) const { + return operands_[index] & 0xf; + } + + bool requires_supervisor() const { return flags_ & 0x80; } - DataSize size() { + DataSize operand_size() const { return DataSize(flags_ & 0x03); } - Condition condition() { + Condition condition() const { return Condition((flags_ >> 2) & 0x0f); } @@ -384,6 +317,8 @@ class Preinstruction { uint8_t operands_[2] = { uint8_t(AddressingMode::None), uint8_t(AddressingMode::None)}; uint8_t flags_ = 0; + std::string operand_description(int index, int opcode) const; + public: Preinstruction( Operation operation, @@ -393,8 +328,8 @@ class Preinstruction { DataSize size, Condition condition) : operation(operation) { - operands_[0] = uint8_t(op1_mode) | uint8_t(op1_reg << 5); - operands_[1] = uint8_t(op2_mode) | uint8_t(op2_reg << 5); + operands_[0] = uint8_t((uint8_t(op1_mode) << 3) | op1_reg); + operands_[1] = uint8_t((uint8_t(op2_mode) << 3) | op2_reg); flags_ = uint8_t( (is_supervisor ? 0x80 : 0x00) | (int(condition) << 2) | @@ -403,9 +338,17 @@ class Preinstruction { } Preinstruction() {} + + /// Produces a string description of this instruction; if @c opcode + /// is supplied then any quick fields in this instruction will be decoded; + /// otherwise they'll be printed as just 'Q'. + std::string to_string(int opcode = -1) const; }; } } +#include "Implementation/InstructionOperandSize.hpp" +#include "Implementation/InstructionOperandFlags.hpp" + #endif /* InstructionSets_68k_Instruction_hpp */ diff --git a/InstructionSets/M68k/Perform.hpp b/InstructionSets/M68k/Perform.hpp new file mode 100644 index 000000000..6351b1448 --- /dev/null +++ b/InstructionSets/M68k/Perform.hpp @@ -0,0 +1,168 @@ +// +// Perform.hpp +// Clock Signal +// +// Created by Thomas Harte on 28/04/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_M68k_Perform_h +#define InstructionSets_M68k_Perform_h + +#include "Model.hpp" +#include "Instruction.hpp" +#include "Status.hpp" +#include "../../Numeric/RegisterSizes.hpp" + +namespace InstructionSet { +namespace M68k { + +struct NullFlowController { + // + // Various operation-specific did-perform notfications; these all relate to operations + // with variable timing on a 68000, providing the fields that contribute to that timing. + // + + /// Indicates that a @c MULU was performed, providing the @c source operand. + template void did_mulu(IntT) {} + + /// Indicates that a @c MULS was performed, providing the @c source operand. + template void did_muls(IntT) {} + + /// Indicates that a @c CHK was performed, along with whether the result @c was_under zero or @c was_over the source operand. + void did_chk([[maybe_unused]] bool was_under, [[maybe_unused]] bool was_over) {} + + /// Indicates an in-register shift or roll occurred, providing the number of bits shifted by. + void did_shift([[maybe_unused]] int bit_count) {} + + /// Indicates that a @c DIVU was performed, providing the @c dividend and @c divisor. + /// If @c did_overflow is @c true then the divide ended in overflow. + template void did_divu([[maybe_unused]] uint32_t dividend, [[maybe_unused]] uint32_t divisor) {} + + /// Indicates that a @c DIVS was performed, providing the @c dividend and @c divisor. + /// If @c did_overflow is @c true then the divide ended in overflow. + template void did_divs([[maybe_unused]] int32_t dividend, [[maybe_unused]] int32_t divisor) {} + + /// Indicates that a bit-manipulation operation (i.e. BTST, BCHG or BSET) was performed, affecting the bit at posiition @c bit_position. + void did_bit_op([[maybe_unused]] int bit_position) {} + + /// Provides a notification that the upper byte of the status register has been affected by the current instruction; + /// this gives an opportunity to track the supervisor flag. + void did_update_status() {} + + // + // Operations that don't fit the reductive load-modify-store pattern; these are requests from perform + // that the flow controller do something (and, correspondingly, do not have empty implementations). + // + // All offsets are the native values as encoded in the corresponding operations. + // + + /// If @c matched_condition is @c true, apply the @c offset to the PC. + template void complete_bcc(bool matched_condition, IntT offset); + + /// If both @c matched_condition and @c overflowed are @c false, apply @c offset to the PC. + void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset); + + /// Push the program counter of the next instruction to the stack, and add @c offset to the PC. + void bsr(uint32_t offset); + + /// Push the program counter of the next instruction to the stack, and load @c offset to the PC. + void jsr(uint32_t address); + + /// Set the program counter to @c address. + void jmp(uint32_t address); + + /// Pop a word from the stack and use that to set the status condition codes. Then pop a new value for the PC. + void rtr(); + + /// Pop a word from the stack and use that to set the entire status register. Then pop a new value for the PC. + void rte(); + + /// Pop a new value for the PC from the stack. + void rts(); + + /// Put the processor into the stopped state, waiting for interrupts. + void stop(); + + /// Assert the reset output. + void reset(); + + /// Perform LINK using the address register identified by @c instruction and the specified @c offset. + void link(Preinstruction instruction, uint32_t offset); + + /// Perform unlink, with @c address being the target address register. + void unlink(uint32_t &address); + + /// Push @c address to the stack. + void pea(uint32_t address); + + /// Replace the current user stack pointer with @c address. + /// The processor is guranteed to be in supervisor mode. + void move_to_usp(uint32_t address); + + /// Put the value of the user stack pointer into @c address. + /// The processor is guranteed to be in supervisor mode. + void move_from_usp(uint32_t &address); + + /// Perform an atomic TAS cycle; if @c instruction indicates that this is a TAS Dn then + /// perform the TAS directly upon that register; otherwise perform it on the memory at + /// @c address. If this is a TAS Dn then @c address will contain the initial value of + /// the register. + void tas(Preinstruction instruction, uint32_t address); + + /// Use @c instruction to determine the direction of this MOVEP and perform it; + /// @c source is the first operand provided to the MOVEP — either an address or register + /// contents — and @c dest is the second. + /// + /// @c IntT may be either uint16_t or uint32_t. + template void movep(Preinstruction instruction, uint32_t source, uint32_t dest); + + /// Perform a MOVEM to memory, from registers. @c instruction will indicate the mask as the first operand, + /// and the target address and addressing mode as the second; the mask and address are also supplied + /// as @c mask and @c address. If the addressing mode is -(An) then the address register will have + /// been decremented already. + /// + /// The receiver is responsible for updating the address register if applicable. + /// + /// @c IntT may be either uint16_t or uint32_t. + template void movem_toM(Preinstruction instruction, uint32_t mask, uint32_t address); + + /// Perform a MOVEM to registers, from memory. @c instruction will indicate the mask as the first operand, + /// and the target address and addressing mode as the second; the mask and address are also supplied + /// as @c mask and @c address. If the addressing mode is (An)+ then the address register will have been + /// incremented, but @c address will be its value before that occurred. + /// + /// The receiver is responsible for updating the address register if applicable. + /// + /// @c IntT may be either uint16_t or uint32_t. + template void movem_toR(Preinstruction instruction, uint32_t mask, uint32_t address); + + /// Raises a short-form exception using @c vector. If @c use_current_instruction_pc is @c true, + /// the program counter for the current instruction is included in the resulting stack frame. Otherwise the program + /// counter for the next instruction is used. + template + void raise_exception([[maybe_unused]] int vector); +}; + +/// Performs @c instruction using @c source and @c dest (one or both of which may be ignored as per +/// the semantics of the operation). +/// +/// Any change in processor status will be applied to @c status. If this operation does not fit the reductive model +/// of being a read and possibly a modify and possibly a write of up to two operands then the @c flow_controller +/// will be asked to fill in the gaps. +/// +/// If the template parameter @c operation is not @c Operation::Undefined then that operation will be performed, ignoring +/// whatever is specifed in @c instruction. This allows selection either at compile time or at run time; per Godbolt all modern +/// compilers seem to be smart enough fully to optimise the compile-time case. +template < + Model model, + typename FlowController, + Operation operation = Operation::Undefined +> void perform(Preinstruction instruction, CPU::RegisterPair32 &source, CPU::RegisterPair32 &dest, Status &status, FlowController &flow_controller); + +} +} + +#include "Implementation/PerformImplementation.hpp" + +#endif /* InstructionSets_M68k_Perform_h */ diff --git a/InstructionSets/M68k/Status.hpp b/InstructionSets/M68k/Status.hpp new file mode 100644 index 000000000..c0af4d9f8 --- /dev/null +++ b/InstructionSets/M68k/Status.hpp @@ -0,0 +1,127 @@ +// +// Status.hpp +// Clock Signal +// +// Created by Thomas Harte on 28/04/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_M68k_Status_h +#define InstructionSets_M68k_Status_h + +#include "Instruction.hpp" + +namespace InstructionSet { +namespace M68k { + +namespace ConditionCode { + +static constexpr uint16_t Carry = 1 << 0; +static constexpr uint16_t Overflow = 1 << 1; +static constexpr uint16_t Zero = 1 << 2; +static constexpr uint16_t Negative = 1 << 3; +static constexpr uint16_t Extend = 1 << 4; + +static constexpr uint16_t Supervisor = 1 << 13; +static constexpr uint16_t Trace = 1 << 15; + +static constexpr uint16_t InterruptPriorityMask = 0b111 << 8; + +} + + +struct Status { + /// Generally holds an unevaluated flag for potential later lazy evaluation; it'll be zero for one outcome, + /// non-zero for the other, but no guarantees are made about the potential range of non-zero values. + using FlagT = uint_fast32_t; + + /* b15 */ + FlagT trace_flag = 0; // The trace flag is set if and only if this value is non-zero. + + /* b13 */ + bool is_supervisor = false; // true => processor is in supervisor mode; false => it isn't. + + /* b7–b9 */ + int interrupt_level = 0; // The direct integer value of the current interrupt level. + // Values of 8 or greater have undefined meaning. + + /* b0–b4 */ + FlagT zero_result = 0; // The zero flag is set if and only if this value is zero. + FlagT carry_flag = 0; // The carry flag is set if and only if this value is non-zero. + FlagT extend_flag = 0; // The extend flag is set if and only if this value is non-zero. + FlagT overflow_flag = 0; // The overflow flag is set if and only if this value is non-zero. + FlagT negative_flag = 0; // The negative flag is set if and only this value is non-zero. + + /// Gets the current condition codes. + constexpr uint16_t ccr() const { + return + (carry_flag ? ConditionCode::Carry : 0) | + (overflow_flag ? ConditionCode::Overflow : 0) | + (!zero_result ? ConditionCode::Zero : 0) | + (negative_flag ? ConditionCode::Negative : 0) | + (extend_flag ? ConditionCode::Extend : 0); + } + + /// Sets the current condition codes. + constexpr void set_ccr(uint16_t ccr) { + carry_flag = ccr & ConditionCode::Carry; + overflow_flag = ccr & ConditionCode::Overflow; + zero_result = ~ccr & ConditionCode::Zero; + negative_flag = ccr & ConditionCode::Negative; + extend_flag = ccr & ConditionCode::Extend; + } + + /// Gets the current value of the status register. + constexpr uint16_t status() const { + return uint16_t( + ccr() | + (interrupt_level << 8) | + (trace_flag ? ConditionCode::Trace : 0) | + (is_supervisor ? ConditionCode::Supervisor : 0) + ); + } + + /// Sets the current value of the status register; + /// @returns @c true if the processor finishes in supervisor mode; @c false otherwise. + constexpr bool set_status(uint16_t status) { + set_ccr(status); + + interrupt_level = (status >> 8) & 7; + trace_flag = status & ConditionCode::Trace; + is_supervisor = status & ConditionCode::Supervisor; + + return is_supervisor; + } + + /// Evaluates @c condition. + bool evaluate_condition(Condition condition) { + switch(condition) { + default: + case Condition::True: return true; + case Condition::False: return false; + case Condition::High: return zero_result && !carry_flag; + case Condition::LowOrSame: return !zero_result || carry_flag; + case Condition::CarryClear: return !carry_flag; + case Condition::CarrySet: return carry_flag; + case Condition::NotEqual: return zero_result; + case Condition::Equal: return !zero_result; + case Condition::OverflowClear: return !overflow_flag; + case Condition::OverflowSet: return overflow_flag; + case Condition::Positive: return !negative_flag; + case Condition::Negative: return negative_flag; + case Condition::GreaterThanOrEqual: + return (negative_flag && overflow_flag) || (!negative_flag && !overflow_flag); + case Condition::LessThan: + return (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag); + case Condition::GreaterThan: + return zero_result && ((negative_flag && overflow_flag) || (!negative_flag && !overflow_flag)); + case Condition::LessThanOrEqual: + return !zero_result || (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag); + } + } +}; + +} +} + +#endif /* InstructionSets_M68k_Status_h */ diff --git a/Numeric/RegisterSizes.hpp b/Numeric/RegisterSizes.hpp new file mode 100644 index 000000000..39321ffc0 --- /dev/null +++ b/Numeric/RegisterSizes.hpp @@ -0,0 +1,84 @@ +// +// RegisterSizes.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/05/2017. +// Copyright 2017 Thomas Harte. All rights reserved. +// + +#ifndef RegisterSizes_hpp +#define RegisterSizes_hpp + +#include + +#pragma pack(push, 1) + +// Unions below assume a modern consumer architecture, +// and that this compiler offers C-style anonymous structs. + +namespace CPU { + +/// Provides access to all intermediate parts of a larger int. +template union RegisterPair { + RegisterPair(Full v) : full(v) {} + RegisterPair() {} + + Full full; +#if TARGET_RT_BIG_ENDIAN + struct { + Half high, low; + } halves; +#else + struct { + Half low, high; + } halves; +#endif +}; + +using RegisterPair16 = RegisterPair; +using RegisterPair32 = RegisterPair; + +/// Provides access to lower portions of a larger int. +template union SlicedInt {}; + +template <> union SlicedInt { + struct { + uint16_t w; + }; + + struct { +#if TARGET_RT_BIG_ENDIAN + uint8_t __padding[1]; +#endif + uint8_t b; + }; +}; + +template <> union SlicedInt { + struct { + uint32_t l; + }; + + struct { +#if TARGET_RT_BIG_ENDIAN + uint16_t __padding[1]; +#endif + uint16_t w; + }; + + struct { +#if TARGET_RT_BIG_ENDIAN + uint8_t __padding[3]; +#endif + uint8_t b; + }; +}; + +using SlicedInt16 = SlicedInt; +using SlicedInt32 = SlicedInt; + +} + +#pragma pack(pop) + +#endif /* RegisterSizes_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 4cbddec4e..93b7c032d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -131,6 +131,9 @@ 4B0ACC3223775819008902D0 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC2223775819008902D0 /* Atari2600.cpp */; }; 4B0ACC3323775819008902D0 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC2223775819008902D0 /* Atari2600.cpp */; }; 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; + 4B0DA67B282DCDF100C12F17 /* Instruction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0DA67A282DCC4200C12F17 /* Instruction.cpp */; }; + 4B0DA67C282DCDF300C12F17 /* Instruction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0DA67A282DCC4200C12F17 /* Instruction.cpp */; }; + 4B0DA67D282DCDF300C12F17 /* Instruction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0DA67A282DCC4200C12F17 /* Instruction.cpp */; }; 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; 4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; 4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; }; @@ -311,6 +314,36 @@ 4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; }; 4B75F979280D7C5100121055 /* 68000DecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B75F978280D7C5100121055 /* 68000DecoderTests.mm */; }; 4B75F97B280D7C7700121055 /* 68000 Decoding in Resources */ = {isa = PBXBuildFile; fileRef = 4B75F97A280D7C7700121055 /* 68000 Decoding */; }; + 4B7752A628217DF80073E2C5 /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; }; + 4B7752A728217E060073E2C5 /* Blitter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E126AA27BA0060A31F /* Blitter.cpp */; }; + 4B7752A828217E110073E2C5 /* Nick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAA26783E2000CA44E8 /* Nick.cpp */; }; + 4B7752A928217E200073E2C5 /* 65816Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */; }; + 4B7752AA28217E370073E2C5 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; }; + 4B7752AB28217E560073E2C5 /* SZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B946326377C0200E7097C /* SZX.cpp */; }; + 4B7752AC28217E6E0073E2C5 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; }; + 4B7752AD28217E770073E2C5 /* AmigaADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080C826A238CC00D03FD8 /* AmigaADF.cpp */; }; + 4B7752AE28217E830073E2C5 /* 2MG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD74256CA15E00176FCC /* 2MG.cpp */; }; + 4B7752AF28217E890073E2C5 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; }; + 4B7752B028217E9A0073E2C5 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE211FE253FC80900435408 /* StaticAnalyser.cpp */; }; + 4B7752B128217EA30073E2C5 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080CF26A257A200D03FD8 /* StaticAnalyser.cpp */; }; + 4B7752B228217EAE0073E2C5 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */; }; + 4B7752B328217EB90073E2C5 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58F4246CC4E8009C171E /* State.cpp */; }; + 4B7752B428217ECB0073E2C5 /* ZXSpectrumTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1C212605996900B85C66 /* ZXSpectrumTAP.cpp */; }; + 4B7752B528217ED30073E2C5 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; }; + 4B7752B628217EE70073E2C5 /* DSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7CC263E33B10092AEE1 /* DSK.cpp */; }; + 4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E426AA4A660060A31F /* Chipset.cpp */; }; + 4B7752B828217F110073E2C5 /* Amiga.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080D826A25ADA00D03FD8 /* Amiga.cpp */; }; + 4B7752B928217F140073E2C5 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2130E0273A7A0A008A77B4 /* Audio.cpp */; }; + 4B7752BA28217F160073E2C5 /* Bitplanes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681C2751A104001671EC /* Bitplanes.cpp */; }; + 4B7752BB28217F1A0073E2C5 /* Copper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC6236C26F4235400F83DFE /* Copper.cpp */; }; + 4B7752BC28217F1D0073E2C5 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1A1B1C27320FBB00119335 /* Disk.cpp */; }; + 4B7752BD28217F200073E2C5 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E826B384080060A31F /* Keyboard.cpp */; }; + 4B7752BE28217F220073E2C5 /* MouseJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C6818275196E8001671EC /* MouseJoystick.cpp */; }; + 4B7752BF28217F250073E2C5 /* Sprites.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681427517A59001671EC /* Sprites.cpp */; }; + 4B7752C028217F3D0073E2C5 /* Line.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC00237756EC008902D0 /* Line.cpp */; }; + 4B7752C128217F490073E2C5 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; }; + 4B7752C228217F5C0073E2C5 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; }; + 4B7752C328217F720073E2C5 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; }; 4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; }; 4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; }; 4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; }; @@ -444,6 +477,8 @@ 4B7C681B275196E8001671EC /* MouseJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C6818275196E8001671EC /* MouseJoystick.cpp */; }; 4B7C681E2751A104001671EC /* Bitplanes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681C2751A104001671EC /* Bitplanes.cpp */; }; 4B7C681F2751A104001671EC /* Bitplanes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681C2751A104001671EC /* Bitplanes.cpp */; }; + 4B7C7A00282C3BCA002D6C0B /* 68000flamewingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C79FF282C3BCA002D6C0B /* 68000flamewingTests.mm */; }; + 4B7C7A07282C3DED002D6C0B /* flamewing 68000 BCD tests in Resources */ = {isa = PBXBuildFile; fileRef = 4B7C7A06282C3DED002D6C0B /* flamewing 68000 BCD tests */; }; 4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; }; 4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; }; 4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; }; @@ -1134,6 +1169,7 @@ 4B0ACC2523775819008902D0 /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = ""; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; + 4B0DA67A282DCC4200C12F17 /* Instruction.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Instruction.cpp; sourceTree = ""; }; 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = ""; }; 4B0E04E91FC9E5DA00F43484 /* CAS.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CAS.hpp; sourceTree = ""; }; 4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 9918.hpp; sourceTree = ""; }; @@ -1227,7 +1263,6 @@ 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; 4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = ""; }; - 4B2C455C1EC9442600FC74DD /* RegisterSizes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RegisterSizes.hpp; sourceTree = ""; }; 4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Electron.cpp; sourceTree = ""; }; 4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Electron.hpp; sourceTree = ""; }; 4B2E86B525D7490E0024F1E9 /* ReactiveDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ReactiveDevice.cpp; sourceTree = ""; }; @@ -1451,6 +1486,9 @@ 4B7C6819275196E8001671EC /* MouseJoystick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseJoystick.hpp; sourceTree = ""; }; 4B7C681C2751A104001671EC /* Bitplanes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Bitplanes.cpp; sourceTree = ""; }; 4B7C681D2751A104001671EC /* Bitplanes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Bitplanes.hpp; sourceTree = ""; }; + 4B7C79FE282AFA9B002D6C0B /* ExceptionVectors.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ExceptionVectors.hpp; sourceTree = ""; }; + 4B7C79FF282C3BCA002D6C0B /* 68000flamewingTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000flamewingTests.mm; sourceTree = ""; }; + 4B7C7A06282C3DED002D6C0B /* flamewing 68000 BCD tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "flamewing 68000 BCD tests"; sourceTree = ""; }; 4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = ""; }; 4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = ""; }; 4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; @@ -1932,6 +1970,12 @@ 4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MacintoshIMG.hpp; sourceTree = ""; }; 4BB4BFB722A4372E0069048D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSizes.hpp; sourceTree = ""; }; + 4BB5B996281B1E3F00522DA9 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; + 4BB5B997281B1F7B00522DA9 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; + 4BB5B99A281B244400522DA9 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; 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 = ""; }; @@ -2009,6 +2053,8 @@ 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DirectAccessDevice.cpp; sourceTree = ""; }; 4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DirectAccessDevice.hpp; sourceTree = ""; }; + 4BC8C01028294C3A0018A501 /* InstructionOperandSize.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InstructionOperandSize.hpp; sourceTree = ""; }; + 4BC8C01228294DEB0018A501 /* InstructionOperandFlags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InstructionOperandFlags.hpp; sourceTree = ""; }; 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = ""; }; 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = ""; }; 4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = ""; }; @@ -2403,6 +2449,7 @@ 4B1414631B588A1100E04248 /* Test Binaries */ = { isa = PBXGroup; children = ( + 4B7C7A06282C3DED002D6C0B /* flamewing 68000 BCD tests */, 4B680CE323A555CA00451D43 /* 68000 Comparative Tests */, 4B75F97A280D7C7700121055 /* 68000 Decoding */, 4B683B002727BE6F0043E541 /* Amiga Blitter Tests */, @@ -3157,10 +3204,16 @@ 4B79629B2819681F008130F9 /* M68k */ = { isa = PBXGroup; children = ( + 4B79629F2819681F008130F9 /* Decoder.cpp */, + 4B0DA67A282DCC4200C12F17 /* Instruction.cpp */, + 4B79629E2819681F008130F9 /* Decoder.hpp */, + 4B7C79FE282AFA9B002D6C0B /* ExceptionVectors.hpp */, + 4BB5B99C281C805300522DA9 /* Executor.hpp */, 4B79629C2819681F008130F9 /* Instruction.hpp */, 4B79629D2819681F008130F9 /* Model.hpp */, - 4B79629E2819681F008130F9 /* Decoder.hpp */, - 4B79629F2819681F008130F9 /* Decoder.cpp */, + 4BB5B996281B1E3F00522DA9 /* Perform.hpp */, + 4BB5B997281B1F7B00522DA9 /* Status.hpp */, + 4BB5B999281B244400522DA9 /* Implementation */, ); path = M68k; sourceTree = ""; @@ -3204,6 +3257,7 @@ 4BD155312716362A00410C6E /* BitSpread.hpp */, 4B7BA03E23D55E7900B98D9E /* CRC.hpp */, 4B7BA03F23D55E7900B98D9E /* LFSR.hpp */, + 4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */, 4BFEA2F12682A90200EBF94C /* Sizes.hpp */, ); name = Numeric; @@ -4069,6 +4123,17 @@ path = Macintosh; sourceTree = ""; }; + 4BB5B999281B244400522DA9 /* Implementation */ = { + isa = PBXGroup; + children = ( + 4BB5B99F281F121200522DA9 /* ExecutorImplementation.hpp */, + 4BC8C01228294DEB0018A501 /* InstructionOperandFlags.hpp */, + 4BC8C01028294C3A0018A501 /* InstructionOperandSize.hpp */, + 4BB5B99A281B244400522DA9 /* PerformImplementation.hpp */, + ); + path = Implementation; + sourceTree = ""; + }; 4BB697CF1D4BA44900248BDF /* Encodings */ = { isa = PBXGroup; children = ( @@ -4157,6 +4222,7 @@ 4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */, 4B9D0C4C22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm */, 4B75F978280D7C5100121055 /* 68000DecoderTests.mm */, + 4B7C79FF282C3BCA002D6C0B /* 68000flamewingTests.mm */, 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */, 4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */, 4BD388872239E198002D14B5 /* 68000Tests.mm */, @@ -4258,7 +4324,6 @@ children = ( 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */, 4BFCA1221ECBDCAF00AC40C1 /* AllRAMProcessor.hpp */, - 4B2C455C1EC9442600FC74DD /* RegisterSizes.hpp */, 4B1414561B58879D00E04248 /* 6502 */, 4B4DEC15252BFA9C004583AC /* 6502Esque */, 4BF8D4CC251C0C9C00BBE21B /* 65816 */, @@ -5212,6 +5277,7 @@ 4BB2997E1B587D8400A49093 /* ldxb in Resources */, 4BB298F51B587D8400A49093 /* adcb in Resources */, 4BB299981B587D8400A49093 /* nopax in Resources */, + 4B7C7A07282C3DED002D6C0B /* flamewing 68000 BCD tests in Resources */, 4BB299931B587D8400A49093 /* lxab in Resources */, 4BB299F01B587D8400A49093 /* trap4 in Resources */, 4B8DF6722550D91600F3433C /* CPUBIT.sfc in Resources */, @@ -5562,6 +5628,7 @@ 4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */, 4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */, 4B894527201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4B0DA67C282DCDF300C12F17 /* Instruction.cpp in Sources */, 4BB244D622AABAF600BE20E5 /* z8530.cpp in Sources */, 4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */, 4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */, @@ -5768,6 +5835,7 @@ 4BC080D926A25ADA00D03FD8 /* Amiga.cpp in Sources */, 4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */, 4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */, + 4B0DA67B282DCDF100C12F17 /* Instruction.cpp in Sources */, 4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */, 4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, @@ -5866,15 +5934,18 @@ 4B778F1523A5EC980000D260 /* PartialMachineCycle.cpp in Sources */, 4BEDA43125B3C700000C2DBD /* Executor.cpp in Sources */, 4B778F6123A5F3560000D260 /* Disk.cpp in Sources */, + 4B7752B628217EE70073E2C5 /* DSK.cpp in Sources */, 4B778F2523A5EDF40000D260 /* Encoder.cpp in Sources */, 4B778F4223A5F1A70000D260 /* MemoryFuzzer.cpp in Sources */, 4B778F0123A5EBA00000D260 /* MacintoshIMG.cpp in Sources */, + 4B7752AD28217E770073E2C5 /* AmigaADF.cpp in Sources */, 4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */, 4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */, 4B778F0A23A5EC150000D260 /* TapePRG.cpp in Sources */, 4B778F0823A5EC150000D260 /* CSW.cpp in Sources */, 4B778F5323A5F23F0000D260 /* SerialBus.cpp in Sources */, 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, + 4B7752BA28217F160073E2C5 /* Bitplanes.cpp in Sources */, 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */, 4B778F4E23A5F2160000D260 /* StaticAnalyser.cpp in Sources */, 4B778F5523A5F2A70000D260 /* Keyboard.cpp in Sources */, @@ -5885,6 +5956,7 @@ 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */, 4B778F3123A5F0CB0000D260 /* Keyboard.cpp in Sources */, 4B778F4A23A5F1FB0000D260 /* StaticAnalyser.cpp in Sources */, + 4B7752AB28217E560073E2C5 /* SZX.cpp in Sources */, 4BD91D772401C2B8007BDC91 /* PatrikRakTests.swift in Sources */, 4B680CE223A5553100451D43 /* 68000ComparativeTests.mm in Sources */, 4B778F3723A5F11C0000D260 /* Parser.cpp in Sources */, @@ -5892,6 +5964,7 @@ 4B90467422C6FADD000E2074 /* 68000BitwiseTests.mm in Sources */, 4B7962A12819681F008130F9 /* Decoder.cpp in Sources */, 4B778F2A23A5EF0F0000D260 /* BitReverse.cpp in Sources */, + 4B7752BB28217F1A0073E2C5 /* Copper.cpp in Sources */, 4B778F1D23A5ED470000D260 /* DiskController.cpp in Sources */, 4B778F0023A5EB990000D260 /* G64.cpp in Sources */, 4B778F4B23A5F2030000D260 /* StaticAnalyser.cpp in Sources */, @@ -5905,6 +5978,7 @@ 4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */, 4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */, 4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */, + 4B7752C228217F5C0073E2C5 /* Spectrum.cpp in Sources */, 4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */, 4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */, 4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */, @@ -5912,9 +5986,11 @@ 4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */, 4B778F4D23A5F20F0000D260 /* StaticAnalyser.cpp in Sources */, 4B778F0423A5EBB00000D260 /* OricMFMDSK.cpp in Sources */, + 4B7752BE28217F220073E2C5 /* MouseJoystick.cpp in Sources */, 4B8DF4D825465B7500F3433C /* IIgsMemoryMapTests.mm in Sources */, 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, 4B4F477C253530B7004245B8 /* Jeek816Tests.swift in Sources */, + 4B7752B928217F140073E2C5 /* Audio.cpp in Sources */, 4B778F0F23A5EC560000D260 /* PCMTrack.cpp in Sources */, 4B778F1123A5EC650000D260 /* FileHolder.cpp in Sources */, 4B778EFC23A5EB8B0000D260 /* AcornADF.cpp in Sources */, @@ -5930,15 +6006,20 @@ 4B778F4823A5F1E70000D260 /* StaticAnalyser.cpp in Sources */, 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */, 4B778F2823A5EEF80000D260 /* Cartridge.cpp in Sources */, + 4B7752B528217ED30073E2C5 /* SNA.cpp in Sources */, 4BEDA3C025B25563000C2DBD /* Decoder.cpp in Sources */, 4B778F4C23A5F2090000D260 /* StaticAnalyser.cpp in Sources */, 4B778F2623A5EE350000D260 /* Acorn.cpp in Sources */, + 4B7752C128217F490073E2C5 /* FAT.cpp in Sources */, 4B778F5B23A5F2DE0000D260 /* Tape.cpp in Sources */, 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */, 4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */, + 4B7752B228217EAE0073E2C5 /* StaticAnalyser.cpp in Sources */, + 4B7752BC28217F1D0073E2C5 /* Disk.cpp in Sources */, 4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */, 4B778EFA23A5EB790000D260 /* DMK.cpp in Sources */, 4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */, + 4B7752AF28217E890073E2C5 /* DAT.cpp in Sources */, 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */, 4B778EFF23A5EB940000D260 /* D64.cpp in Sources */, 4BEDA3BB25B25563000C2DBD /* Decoder.cpp in Sources */, @@ -5957,12 +6038,15 @@ 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4B778F6023A5F3460000D260 /* Disk.cpp in Sources */, 4B778F5C23A5F3070000D260 /* MSX.cpp in Sources */, + 4B7752AA28217E370073E2C5 /* ROMCatalogue.cpp in Sources */, 4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */, 4B778F4023A5F1910000D260 /* z8530.cpp in Sources */, 4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */, 4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */, + 4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */, 4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */, 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */, + 4B0DA67D282DCDF300C12F17 /* Instruction.cpp in Sources */, 4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */, 4B778F2223A5EDDD0000D260 /* PulseQueuedTape.cpp in Sources */, 4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */, @@ -5980,19 +6064,26 @@ 4B778F4423A5F1BE0000D260 /* CommodoreGCR.cpp in Sources */, 4B778EF923A5EB740000D260 /* MSA.cpp in Sources */, 4B4DEC07252BFA56004583AC /* 65816Base.cpp in Sources */, + 4B7752A628217DF80073E2C5 /* Dave.cpp in Sources */, 4B778F2323A5EDE40000D260 /* Tape.cpp in Sources */, + 4B7752A728217E060073E2C5 /* Blitter.cpp in Sources */, 4B778F4F23A5F21C0000D260 /* StaticAnalyser.cpp in Sources */, 4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */, 4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */, + 4B7752A928217E200073E2C5 /* 65816Storage.cpp in Sources */, + 4B7752AC28217E6E0073E2C5 /* StaticAnalyser.cpp in Sources */, 4B778F1223A5EC720000D260 /* CRT.cpp in Sources */, 4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */, 4B778F3C23A5F16F0000D260 /* FIRFilter.cpp in Sources */, 4B778F5423A5F2600000D260 /* UnformattedTrack.cpp in Sources */, + 4B7752B028217E9A0073E2C5 /* StaticAnalyser.cpp in Sources */, 4B778EF823A5EB6E0000D260 /* NIB.cpp in Sources */, 4B9D0C4B22C7D70A00DE1AD3 /* 68000BCDTests.mm in Sources */, 4B778F5E23A5F3230000D260 /* Oric.cpp in Sources */, + 4B7752B828217F110073E2C5 /* Amiga.cpp in Sources */, 4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */, 4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */, + 4B7752C328217F720073E2C5 /* Z80.cpp in Sources */, 4B778F1A23A5ED320000D260 /* Video.cpp in Sources */, 4B778F3B23A5F1650000D260 /* KeyboardMachine.cpp in Sources */, 4B778F2E23A5F09E0000D260 /* IRQDelegatePortHandler.cpp in Sources */, @@ -6001,17 +6092,20 @@ 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */, 4B778EFE23A5EB910000D260 /* CPCDSK.cpp in Sources */, 4B778F5823A5F2C60000D260 /* Tape.cpp in Sources */, + 4B7752B328217EB90073E2C5 /* State.cpp in Sources */, 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */, 4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */, 4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */, 4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */, 4BC6237226F94BCB00F83DFE /* MintermTests.mm in Sources */, + 4B7752BF28217F250073E2C5 /* Sprites.cpp in Sources */, 4B778F3923A5F11C0000D260 /* Shifter.cpp in Sources */, 4BEE4BD425A26E2B00011BD2 /* x86DecoderTests.mm in Sources */, 4B778F3623A5F1040000D260 /* Target.cpp in Sources */, 4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */, 4B778F3D23A5F1750000D260 /* ncr5380.cpp in Sources */, 4BF701A026FFD32300996424 /* AmigaBlitterTests.mm in Sources */, + 4B7752B428217ECB0073E2C5 /* ZXSpectrumTAP.cpp in Sources */, 4B778F6323A5F3630000D260 /* Tape.cpp in Sources */, 4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */, 4BEE1EC022B5E236000A26A6 /* MacGCRTests.mm in Sources */, @@ -6032,11 +6126,17 @@ 4B778F1B23A5ED380000D260 /* Video.cpp in Sources */, 4B778F4723A5F1DD0000D260 /* StaticAnalyser.cpp in Sources */, 4B778F1923A5ED1B0000D260 /* 6502Storage.cpp in Sources */, + 4B7752A828217E110073E2C5 /* Nick.cpp in Sources */, + 4B7752AE28217E830073E2C5 /* 2MG.cpp in Sources */, 4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */, 4B778F1E23A5EDC00000D260 /* DriveSpeedAccumulator.cpp in Sources */, 4B778F4323A5F1B00000D260 /* ImplicitSectors.cpp in Sources */, + 4B7752B128217EA30073E2C5 /* StaticAnalyser.cpp in Sources */, 4B778F5123A5F2290000D260 /* StaticAnalyser.cpp in Sources */, + 4B7752C028217F3D0073E2C5 /* Line.cpp in Sources */, + 4B7C7A00282C3BCA002D6C0B /* 68000flamewingTests.mm in Sources */, 4B778F0223A5EBA40000D260 /* MFMSectorDump.cpp in Sources */, + 4B7752BD28217F200073E2C5 /* Keyboard.cpp in Sources */, 4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */, 4B778F3E23A5F17C0000D260 /* IWM.cpp in Sources */, 4BD91D732401960C007BDC91 /* STX.cpp in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm index cec3017f1..44bdb7879 100644 --- a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm +++ b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm @@ -9,11 +9,81 @@ #import #include "../../../Processors/68000/68000.hpp" +#include "../../../InstructionSets/M68k/Executor.hpp" +#include "../../../InstructionSets/M68k/Decoder.hpp" #include #include #include +//#define USE_EXISTING_IMPLEMENTATION + +namespace { + +/// Binds a 68000 executor to 16mb of RAM. +struct Test68000 { + std::array ram; + InstructionSet::M68k::Executor processor; + + Test68000() : processor(*this) { + } + + void run_for_instructions(int instructions) { + processor.run_for_instructions(instructions); + } + + // Initial test-case implementation: + // do a very sedate read and write. + + template IntT read(uint32_t address, InstructionSet::M68k::FunctionCode) { + if constexpr (sizeof(IntT) == 1) { + return IntT(ram[address & 0xffffff]); + } + + if constexpr (sizeof(IntT) == 2) { + return IntT( + (ram[address & 0xffffff] << 8) | + ram[(address+1) & 0xffffff] + ); + } + + if constexpr (sizeof(IntT) == 4) { + return IntT( + (ram[address & 0xffffff] << 24) | + (ram[(address+1) & 0xffffff] << 16) | + (ram[(address+2) & 0xffffff] << 8) | + ram[(address+3) & 0xffffff] + ); + } + return 0; + } + + template void write(uint32_t address, IntT value, InstructionSet::M68k::FunctionCode) { + if constexpr (sizeof(IntT) == 1) { + ram[address & 0xffffff] = uint8_t(value); + } + + if constexpr (sizeof(IntT) == 2) { + ram[address & 0xffffff] = uint8_t(value >> 8); + ram[(address+1) & 0xffffff] = uint8_t(value); + } + + if constexpr (sizeof(IntT) == 4) { + ram[address & 0xffffff] = uint8_t(value >> 24); + ram[(address+1) & 0xffffff] = uint8_t(value >> 16); + ram[(address+2) & 0xffffff] = uint8_t(value >> 8); + ram[(address+3) & 0xffffff] = uint8_t(value); + } + } + + void reset() {} + int acknowlege_interrupt(int) { + return -1; + } +}; + +} + @interface M68000ComparativeTests : XCTestCase @end @@ -23,19 +93,21 @@ NSMutableSet *_failures; NSMutableArray *_failingOpcodes; + + Test68000 _test68000; } - (void)setUp { + // These will accumulate a list of failing tests and associated opcodes. + _failures = [[NSMutableSet alloc] init]; + _failingOpcodes = [[NSMutableArray alloc] init]; + // To limit tests run to a subset of files and/or of tests, uncomment and fill in below. // _fileSet = [NSSet setWithArray:@[@"jmp_jsr.json"]]; // _testSet = [NSSet setWithArray:@[@"CHK 41a8"]]; } - (void)testAll { - // These will accumulate a list of failing tests and associated opcodes. - _failures = [[NSMutableSet alloc] init]; - _failingOpcodes = [[NSMutableArray alloc] init]; - // Get the full list of available test files. NSBundle *const bundle = [NSBundle bundleForClass:[self class]]; NSArray *const tests = [bundle URLsForResourcesWithExtension:@"json" subdirectory:@"68000 Comparative Tests"]; @@ -44,16 +116,24 @@ for(NSURL *url in tests) { // Compare against a file set if one has been supplied. if(_fileSet && ![_fileSet containsObject:[url lastPathComponent]]) continue; - NSLog(@"Testing %@", url); +// NSLog(@"Testing %@", url); [self testJSONAtURL:url]; } - // Output a summary of failures. - NSLog(@"Total: %@", @(_failures.count)); - NSLog(@"Failures: %@", _failures); - NSLog(@"Failing opcodes:"); - for(NSNumber *number in _failingOpcodes) { - NSLog(@"%04x", number.intValue); + XCTAssert(_failures.count == 0); + + // Output a summary of failures, if any. + if(_failures.count) { + NSLog(@"Total failures: %@", @(_failures.count)); + NSLog(@"Failures: %@", _failures); + NSLog(@"Failing opcodes:"); + + InstructionSet::M68k::Predecoder decoder; + for(NSNumber *number in _failingOpcodes) { + const auto decoded = decoder.decode(number.intValue); + const std::string description = decoded.to_string(number.intValue); + NSLog(@"%04x %s", number.intValue, description.c_str()); + } } } @@ -70,17 +150,23 @@ // Perform each dictionary in the array as a test. for(NSDictionary *test in jsonContents) { if(![test isKindOfClass:[NSDictionary class]]) continue; - [self testOperation:test]; + + // Only entries with a name are valid. + NSString *const name = test[@"name"]; + if(!name) continue; + + // Compare against a test set if one has been supplied. + if(_testSet && ![_testSet containsObject:name]) continue; + +#ifdef USE_EXISTING_IMPLEMENTATION + [self testOperationClassic:test name:name]; +#else + [self testOperationExecutor:test name:name]; +#endif } } -- (void)testOperation:(NSDictionary *)test { - // Only entries with a name are valid. - NSString *const name = test[@"name"]; - if(!name) return; - - // Compare against a test set if one has been supplied. - if(_testSet && ![_testSet containsObject:name]) return; +- (void)testOperationClassic:(NSDictionary *)test name:(NSString *)name { // This is the test class for 68000 execution. struct Test68000: public CPU::MC68000::BusHandler { @@ -195,4 +281,88 @@ test68000->run_for_instructions(1, comparitor); } +- (void)setInitialState:(NSDictionary *)test { + // Definitively erase any prior memory contents; + // 0xce is arbitrary but hopefully easier to spot + // in potential errors than e.g. 0x00 or 0xff. + memset(_test68000.ram.data(), 0xce, _test68000.ram.size()); + + // Apply initial memory state. + NSArray *const initialMemory = test[@"initial memory"]; + NSEnumerator *enumerator = [initialMemory objectEnumerator]; + while(true) { + NSNumber *const address = [enumerator nextObject]; + NSNumber *const value = [enumerator nextObject]; + + if(!address || !value) break; + _test68000.ram[address.integerValue] = value.integerValue; + } + + // Apply initial processor state. + NSDictionary *const initialState = test[@"initial state"]; + auto state = _test68000.processor.get_state(); + for(int c = 0; c < 8; ++c) { + const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c]; + const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c]; + + state.data[c] = uint32_t([initialState[dX] integerValue]); + if(c < 7) + state.address[c] = uint32_t([initialState[aX] integerValue]); + } + state.supervisor_stack_pointer = uint32_t([initialState[@"a7"] integerValue]); + state.user_stack_pointer = uint32_t([initialState[@"usp"] integerValue]); + state.status = [initialState[@"sr"] integerValue]; + state.program_counter = uint32_t([initialState[@"pc"] integerValue]); + _test68000.processor.set_state(state); +} + +- (void)testOperationExecutor:(NSDictionary *)test name:(NSString *)name { + [self setInitialState:test]; + + // Run the thing. + _test68000.run_for_instructions(1); + + // Test the end state. + NSDictionary *const finalState = test[@"final state"]; + const auto state = _test68000.processor.get_state(); + for(int c = 0; c < 8; ++c) { + const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c]; + const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c]; + + if(state.data[c] != [finalState[dX] integerValue]) [_failures addObject:name]; + if(c < 7 && state.address[c] != [finalState[aX] integerValue]) [_failures addObject:name]; + +// XCTAssertEqual(state.data[c], [finalState[dX] integerValue], @"%@: D%d inconsistent", name, c); +// if(c < 7) { +// XCTAssertEqual(state.address[c], [finalState[aX] integerValue], @"%@: A%d inconsistent", name, c); +// } + } + if(state.supervisor_stack_pointer != [finalState[@"a7"] integerValue]) [_failures addObject:name]; + if(state.user_stack_pointer != [finalState[@"usp"] integerValue]) [_failures addObject:name]; + if(state.status != [finalState[@"sr"] integerValue]) [_failures addObject:name]; + +// XCTAssertEqual(state.supervisor_stack_pointer, [finalState[@"a7"] integerValue], @"%@: A7 inconsistent", name); +// XCTAssertEqual(state.user_stack_pointer, [finalState[@"usp"] integerValue], @"%@: USP inconsistent", name); +// XCTAssertEqual(state.status, [finalState[@"sr"] integerValue], @"%@: Status inconsistent", name); +// XCTAssertEqual(state.program_counter, [finalState[@"pc"] integerValue], @"%@: Program counter inconsistent", name); + + // Test final memory state. + NSArray *const finalMemory = test[@"final memory"]; + NSEnumerator *enumerator = [finalMemory objectEnumerator]; + while(true) { + NSNumber *const address = [enumerator nextObject]; + NSNumber *const value = [enumerator nextObject]; + + if(!address || !value) break; +// XCTAssertEqual(_test68000.ram[address.integerValue], value.integerValue, @"%@: Memory at location %@ inconsistent", name, address); + if(_test68000.ram[address.integerValue] != value.integerValue) [_failures addObject:name]; + } + + // If this test is now in the failures set, add the corresponding opcode for + // later logging. + if([_failures containsObject:name]) { + [_failingOpcodes addObject:@(_test68000.read(0x100, InstructionSet::M68k::FunctionCode()))]; + } +} + @end diff --git a/OSBindings/Mac/Clock SignalTests/68000DecoderTests.mm b/OSBindings/Mac/Clock SignalTests/68000DecoderTests.mm index cb4019603..32844d16f 100644 --- a/OSBindings/Mac/Clock SignalTests/68000DecoderTests.mm +++ b/OSBindings/Mac/Clock SignalTests/68000DecoderTests.mm @@ -15,51 +15,6 @@ using namespace InstructionSet::M68k; @interface M68000DecoderTests : XCTestCase @end -namespace { - -template NSString *operand(Preinstruction instruction, uint16_t opcode) { - switch(instruction.mode()) { - default: return [NSString stringWithFormat:@"[Mode %d?]", int(instruction.mode())]; - - case AddressingMode::None: - return @""; - - case AddressingMode::DataRegisterDirect: - return [NSString stringWithFormat:@"D%d", instruction.reg()]; - - case AddressingMode::AddressRegisterDirect: - return [NSString stringWithFormat:@"A%d", instruction.reg()]; - case AddressingMode::AddressRegisterIndirect: - return [NSString stringWithFormat:@"(A%d)", instruction.reg()]; - case AddressingMode::AddressRegisterIndirectWithPostincrement: - return [NSString stringWithFormat:@"(A%d)+", instruction.reg()]; - case AddressingMode::AddressRegisterIndirectWithPredecrement: - return [NSString stringWithFormat:@"-(A%d)", instruction.reg()]; - case AddressingMode::AddressRegisterIndirectWithDisplacement: - return [NSString stringWithFormat:@"(d16, A%d)", instruction.reg()]; - case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement: - return [NSString stringWithFormat:@"(d8, A%d, Xn)", instruction.reg()]; - - case AddressingMode::ProgramCounterIndirectWithDisplacement: - return @"(d16, PC)"; - case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement: - return @"(d8, PC, Xn)"; - - case AddressingMode::AbsoluteShort: - return @"(xxx).w"; - case AddressingMode::AbsoluteLong: - return @"(xxx).l"; - - case AddressingMode::ImmediateData: - return @"#"; - - case AddressingMode::Quick: - return [NSString stringWithFormat:@"%d", quick(instruction.operation, opcode)]; - } -} - -} - @implementation M68000DecoderTests - (void)testInstructionSpecs { @@ -81,208 +36,7 @@ template NSString *operand(Preinstruction instruction, uint16_t opco const auto found = decoder.decode(uint16_t(instr)); - NSString *instruction; - switch(found.operation) { - case Operation::Undefined: instruction = @"None"; break; - case Operation::NOP: instruction = @"NOP"; break; - case Operation::ABCD: instruction = @"ABCD"; break; - case Operation::SBCD: instruction = @"SBCD"; break; - case Operation::NBCD: instruction = @"NBCD"; break; - - case Operation::ADDb: instruction = @"ADD.b"; break; - case Operation::ADDw: instruction = @"ADD.w"; break; - case Operation::ADDl: instruction = @"ADD.l"; break; - - case Operation::ADDAw: instruction = @"ADDA.w"; break; - case Operation::ADDAl: instruction = @"ADDA.l"; break; - - case Operation::ADDXb: instruction = @"ADDX.b"; break; - case Operation::ADDXw: instruction = @"ADDX.w"; break; - case Operation::ADDXl: instruction = @"ADDX.l"; break; - - case Operation::SUBb: instruction = @"SUB.b"; break; - case Operation::SUBw: instruction = @"SUB.w"; break; - case Operation::SUBl: instruction = @"SUB.l"; break; - - case Operation::SUBAw: instruction = @"SUBA.w"; break; - case Operation::SUBAl: instruction = @"SUBA.l"; break; - - case Operation::SUBXb: instruction = @"SUBX.b"; break; - case Operation::SUBXw: instruction = @"SUBX.w"; break; - case Operation::SUBXl: instruction = @"SUBX.l"; break; - - case Operation::MOVEb: instruction = @"MOVE.b"; break; - case Operation::MOVEw: instruction = @"MOVE.w"; break; - case Operation::MOVEl: - if(found.mode<0>() == AddressingMode::Quick) { - instruction = @"MOVE.q"; - } else { - instruction = @"MOVE.l"; - } - break; - - case Operation::MOVEAw: instruction = @"MOVEA.w"; break; - case Operation::MOVEAl: instruction = @"MOVEA.l"; break; - - case Operation::LEA: instruction = @"LEA"; break; - case Operation::PEA: instruction = @"PEA"; break; - - case Operation::MOVEtoSR: instruction = @"MOVEtoSR"; break; - case Operation::MOVEfromSR: instruction = @"MOVEfromSR"; break; - case Operation::MOVEtoCCR: instruction = @"MOVEtoCCR"; break; - case Operation::MOVEtoUSP: instruction = @"MOVEtoUSP"; break; - case Operation::MOVEfromUSP: instruction = @"MOVEfromUSP"; break; - - case Operation::ORItoSR: instruction = @"ORItoSR"; break; - case Operation::ORItoCCR: instruction = @"ORItoCCR"; break; - case Operation::ANDItoSR: instruction = @"ANDItoSR"; break; - case Operation::ANDItoCCR: instruction = @"ANDItoCCR"; break; - case Operation::EORItoSR: instruction = @"EORItoSR"; break; - case Operation::EORItoCCR: instruction = @"EORItoCCR"; break; - - case Operation::BTST: instruction = @"BTST"; break; - case Operation::BCLR: instruction = @"BCLR"; break; - case Operation::BCHG: instruction = @"BCHG"; break; - case Operation::BSET: instruction = @"BSET"; break; - - case Operation::CMPb: instruction = @"CMP.b"; break; - case Operation::CMPw: instruction = @"CMP.w"; break; - case Operation::CMPl: instruction = @"CMP.l"; break; - - case Operation::CMPAw: instruction = @"CMPA.w"; break; - case Operation::CMPAl: instruction = @"CMPA.l"; break; - - case Operation::TSTb: instruction = @"TST.b"; break; - case Operation::TSTw: instruction = @"TST.w"; break; - case Operation::TSTl: instruction = @"TST.l"; break; - - case Operation::JMP: instruction = @"JMP"; break; - case Operation::JSR: instruction = @"JSR"; break; - case Operation::RTS: instruction = @"RTS"; break; - case Operation::DBcc: instruction = @"DBcc"; break; - case Operation::Scc: instruction = @"Scc"; break; - - case Operation::Bccb: - case Operation::Bccl: - case Operation::Bccw: instruction = @"Bcc"; break; - - case Operation::BSRb: - case Operation::BSRl: - case Operation::BSRw: instruction = @"BSR"; break; - - case Operation::CLRb: instruction = @"CLR.b"; break; - case Operation::CLRw: instruction = @"CLR.w"; break; - case Operation::CLRl: instruction = @"CLR.l"; break; - - case Operation::NEGXb: instruction = @"NEGX.b"; break; - case Operation::NEGXw: instruction = @"NEGX.w"; break; - case Operation::NEGXl: instruction = @"NEGX.l"; break; - - case Operation::NEGb: instruction = @"NEG.b"; break; - case Operation::NEGw: instruction = @"NEG.w"; break; - case Operation::NEGl: instruction = @"NEG.l"; break; - - case Operation::ASLb: instruction = @"ASL.b"; break; - case Operation::ASLw: instruction = @"ASL.w"; break; - case Operation::ASLl: instruction = @"ASL.l"; break; - case Operation::ASLm: instruction = @"ASL.w"; break; - - case Operation::ASRb: instruction = @"ASR.b"; break; - case Operation::ASRw: instruction = @"ASR.w"; break; - case Operation::ASRl: instruction = @"ASR.l"; break; - case Operation::ASRm: instruction = @"ASR.w"; break; - - case Operation::LSLb: instruction = @"LSL.b"; break; - case Operation::LSLw: instruction = @"LSL.w"; break; - case Operation::LSLl: instruction = @"LSL.l"; break; - case Operation::LSLm: instruction = @"LSL.w"; break; - - case Operation::LSRb: instruction = @"LSR.b"; break; - case Operation::LSRw: instruction = @"LSR.w"; break; - case Operation::LSRl: instruction = @"LSR.l"; break; - case Operation::LSRm: instruction = @"LSR.w"; break; - - case Operation::ROLb: instruction = @"ROL.b"; break; - case Operation::ROLw: instruction = @"ROL.w"; break; - case Operation::ROLl: instruction = @"ROL.l"; break; - case Operation::ROLm: instruction = @"ROL.w"; break; - - case Operation::RORb: instruction = @"ROR.b"; break; - case Operation::RORw: instruction = @"ROR.w"; break; - case Operation::RORl: instruction = @"ROR.l"; break; - case Operation::RORm: instruction = @"ROR.w"; break; - - case Operation::ROXLb: instruction = @"ROXL.b"; break; - case Operation::ROXLw: instruction = @"ROXL.w"; break; - case Operation::ROXLl: instruction = @"ROXL.l"; break; - case Operation::ROXLm: instruction = @"ROXL.w"; break; - - case Operation::ROXRb: instruction = @"ROXR.b"; break; - case Operation::ROXRw: instruction = @"ROXR.w"; break; - case Operation::ROXRl: instruction = @"ROXR.l"; break; - case Operation::ROXRm: instruction = @"ROXR.w"; break; - - case Operation::MOVEMl: instruction = @"MOVEM.l"; break; - case Operation::MOVEMw: instruction = @"MOVEM.w"; break; - - case Operation::MOVEPl: instruction = @"MOVEP.l"; break; - case Operation::MOVEPw: instruction = @"MOVEP.w"; break; - - case Operation::ANDb: instruction = @"AND.b"; break; - case Operation::ANDw: instruction = @"AND.w"; break; - case Operation::ANDl: instruction = @"AND.l"; break; - - case Operation::EORb: instruction = @"EOR.b"; break; - case Operation::EORw: instruction = @"EOR.w"; break; - case Operation::EORl: instruction = @"EOR.l"; break; - - case Operation::NOTb: instruction = @"NOT.b"; break; - case Operation::NOTw: instruction = @"NOT.w"; break; - case Operation::NOTl: instruction = @"NOT.l"; break; - - case Operation::ORb: instruction = @"OR.b"; break; - case Operation::ORw: instruction = @"OR.w"; break; - case Operation::ORl: instruction = @"OR.l"; break; - - case Operation::MULU: instruction = @"MULU"; break; - case Operation::MULS: instruction = @"MULS"; break; - case Operation::DIVU: instruction = @"DIVU"; break; - case Operation::DIVS: instruction = @"DIVS"; break; - - case Operation::RTE: instruction = @"RTE"; break; - case Operation::RTR: instruction = @"RTR"; break; - - case Operation::TRAP: instruction = @"TRAP"; break; - case Operation::TRAPV: instruction = @"TRAPV"; break; - case Operation::CHK: instruction = @"CHK"; break; - - case Operation::EXG: instruction = @"EXG"; break; - case Operation::SWAP: instruction = @"SWAP"; break; - - case Operation::TAS: instruction = @"TAS"; break; - - case Operation::EXTbtow: instruction = @"EXT.w"; break; - case Operation::EXTwtol: instruction = @"EXT.l"; break; - - case Operation::LINKw: instruction = @"LINK"; break; - case Operation::UNLINK: instruction = @"UNLINK"; break; - - case Operation::STOP: instruction = @"STOP"; break; - case Operation::RESET: instruction = @"RESET"; break; - - // For now, skip any unmapped operations. - default: - XCTAssert(false, @"Operation %d unhandled by test case", int(found.operation)); - continue; - } - - NSString *const operand1 = operand<0>(found, uint16_t(instr)); - NSString *const operand2 = operand<1>(found, uint16_t(instr)); - - if(operand1.length) instruction = [instruction stringByAppendingFormat:@" %@", operand1]; - if(operand2.length) instruction = [instruction stringByAppendingFormat:@", %@", operand2]; - - XCTAssertFalse(found.mode<0>() == AddressingMode::None && found.mode<1>() != AddressingMode::None, @"Decoding of %@ provided a second operand but not a first", instrName); + NSString *const instruction = [NSString stringWithUTF8String:found.to_string(instr).c_str()]; XCTAssertEqualObjects(instruction, expected, "%@ should decode as %@; got %@", instrName, expected, instruction); } } diff --git a/OSBindings/Mac/Clock SignalTests/68000flamewingTests.mm b/OSBindings/Mac/Clock SignalTests/68000flamewingTests.mm new file mode 100644 index 000000000..a39177870 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/68000flamewingTests.mm @@ -0,0 +1,103 @@ +// +// 68000ComparativeTests.cpp +// Clock SignalTests +// +// Created by Thomas Harte on 14/12/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#import + +#include "../../../InstructionSets/M68k/Perform.hpp" + +using namespace InstructionSet::M68k; + +@interface M68000flamewingTests : XCTestCase +@end + +@implementation M68000flamewingTests + +- (Status)statusWithflamewingFlags:(int)flags { + Status status; + status.carry_flag = status.extend_flag = flags & 2; + status.zero_result = ~flags & 1; + status.negative_flag = 0; + status.overflow_flag = 0; + return status; +} + +- (void)validate:(const uint8_t *)test source:(int)source dest:(int)dest flags:(int)flags result:(uint32_t)result status:(Status)status operation:(NSString *)operation { + const uint8_t result_flags = test[0]; + const uint8_t result_value = test[1]; + + NSString *const testName = + [NSString stringWithFormat:@"%@ %02x, %02x [%c%c]", operation, source, dest, (flags & 2) ? 'X' : '-', (flags & 1) ? 'Z' : '-']; + XCTAssertEqual(result, uint32_t(result_value), @"Wrong value received for %@", testName); + XCTAssertEqual(status.ccr(), uint16_t(result_flags), @"Wrong status received for %@", testName); +} + +- (void)testAll { + // Get the full list of available test files. + NSBundle *const bundle = [NSBundle bundleForClass:[self class]]; + NSURL *const testURL = [bundle URLForResource:@"bcd-table" withExtension:@"bin" subdirectory:@"flamewing 68000 BCD tests"]; + NSData *const testData = [NSData dataWithContentsOfURL:testURL]; + const uint8_t *bytes = reinterpret_cast(testData.bytes); + + NullFlowController flow_controller; + + // Test ABCD. + for(int source = 0; source < 256; source++) { + for(int dest = 0; dest < 256; dest++) { + for(int flags = 0; flags < 4; flags++) { + Status status = [self statusWithflamewingFlags:flags]; + + CPU::SlicedInt32 s, d; + s.l = source; + d.l = dest; + + perform( + Preinstruction(), s, d, status, flow_controller); + + [self validate:bytes source:source dest:dest flags:flags result:d.l status:status operation:@"ABCD"]; + bytes += 2; + } + } + } + + // Test SBCD. + for(int source = 0; source < 256; source++) { + for(int dest = 0; dest < 256; dest++) { + for(int flags = 0; flags < 4; flags++) { + Status status = [self statusWithflamewingFlags:flags]; + + CPU::SlicedInt32 s, d; + s.l = source; + d.l = dest; + + perform( + Preinstruction(), s, d, status, flow_controller); + + [self validate:bytes source:source dest:dest flags:flags result:d.l status:status operation:@"SBCD"]; + bytes += 2; + } + } + } + + // Test NBCD. + for(int source = 0; source < 256; source++) { + for(int flags = 0; flags < 4; flags++) { + Status status = [self statusWithflamewingFlags:flags]; + + CPU::SlicedInt32 s, d; + s.l = source; + + perform( + Preinstruction(), s, d, status, flow_controller); + + [self validate:bytes source:source dest:0 flags:flags result:s.l status:status operation:@"NBCD"]; + bytes += 2; + } + } +} + +@end diff --git a/OSBindings/Mac/Clock SignalTests/flamewing 68000 BCD tests/bcd-table.bin b/OSBindings/Mac/Clock SignalTests/flamewing 68000 BCD tests/bcd-table.bin new file mode 100644 index 000000000..98377ac52 Binary files /dev/null and b/OSBindings/Mac/Clock SignalTests/flamewing 68000 BCD tests/bcd-table.bin differ diff --git a/OSBindings/Mac/Clock SignalTests/flamewing 68000 BCD tests/readme.md b/OSBindings/Mac/Clock SignalTests/flamewing 68000 BCD tests/readme.md new file mode 100644 index 000000000..3cd1b23db --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/flamewing 68000 BCD tests/readme.md @@ -0,0 +1,5 @@ +# flamewing's 68000 BCD tests + +These test results were generated by code obtained from https://github.com/flamewing/68k-bcd-verifier + +The code itself is licensed under the GPL 3.0; the file included here is not part of the original repository but is generated as part of the build process so it is probably safest to assume it is covered by the GPL despite the rule that program output isn't covered — it is arguably part of the program, not program output. \ No newline at end of file diff --git a/Processors/6502/6502.hpp b/Processors/6502/6502.hpp index 671cddfb4..e177c495b 100644 --- a/Processors/6502/6502.hpp +++ b/Processors/6502/6502.hpp @@ -15,7 +15,7 @@ #include "../6502Esque/6502Esque.hpp" #include "../6502Esque/Implementation/LazyFlags.hpp" -#include "../RegisterSizes.hpp" +#include "../../Numeric/RegisterSizes.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" namespace CPU { diff --git a/Processors/65816/65816.hpp b/Processors/65816/65816.hpp index 7ba4a988f..e65b7cd45 100644 --- a/Processors/65816/65816.hpp +++ b/Processors/65816/65816.hpp @@ -14,7 +14,7 @@ #include #include -#include "../RegisterSizes.hpp" +#include "../../Numeric/RegisterSizes.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" #include "../6502Esque/6502Esque.hpp" #include "../6502Esque/Implementation/LazyFlags.hpp" diff --git a/Processors/68000/68000.hpp b/Processors/68000/68000.hpp index e846c2a1d..068cee4e3 100644 --- a/Processors/68000/68000.hpp +++ b/Processors/68000/68000.hpp @@ -20,7 +20,7 @@ #include "../../ClockReceiver/ForceInline.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" -#include "../RegisterSizes.hpp" +#include "../../Numeric/RegisterSizes.hpp" namespace CPU { namespace MC68000 { diff --git a/Processors/68000/Implementation/68000Implementation.hpp b/Processors/68000/Implementation/68000Implementation.hpp index aee4c7de2..812d2a0ab 100644 --- a/Processors/68000/Implementation/68000Implementation.hpp +++ b/Processors/68000/Implementation/68000Implementation.hpp @@ -1595,6 +1595,8 @@ template void Proces overflow_flag_ = carry_flag_ = 0; } break; +#undef sbcd + /* Shifts and rotates. */ @@ -1764,7 +1766,7 @@ template void Proces } break; case Operation::ROLb: rol(destination()->halves.low.halves.low, 8); break; case Operation::ROLw: rol(destination()->halves.low.full, 16); break; - case Operation::ROLl: rol(destination()->full, 32); break; + case Operation::ROLl: rol(destination()->full, 32); break; #define ror(destination, size) { \ @@ -1793,7 +1795,7 @@ template void Proces } break; case Operation::RORb: ror(destination()->halves.low.halves.low, 8); break; case Operation::RORw: ror(destination()->halves.low.full, 16); break; - case Operation::RORl: ror(destination()->full, 32); break; + case Operation::RORl: ror(destination()->full, 32); break; #define roxl(destination, size) { \ decode_shift_count(); \ @@ -1853,13 +1855,13 @@ template void Proces #undef lsl #undef asl -#undef set_flags +#undef set_flagsx #undef decode_shift_count #undef set_flags_b #undef set_flags_w #undef set_flags_l #undef set_neg_zero_overflow -#undef set_net_zero +#undef set_neg_zero /* RTE and RTR share an implementation. diff --git a/Processors/68000/State/State.hpp b/Processors/68000/State/State.hpp index f15337e86..08160d24a 100644 --- a/Processors/68000/State/State.hpp +++ b/Processors/68000/State/State.hpp @@ -28,11 +28,14 @@ struct State: public Reflection::StructImpl { Provides the current state of the well-known, published internal registers. */ struct Registers: public Reflection::StructImpl { + // Official registers. uint32_t data[8], address[7]; uint32_t user_stack_pointer; uint32_t supervisor_stack_pointer; uint16_t status; uint32_t program_counter; + + // The 68000's prefetch queue. uint32_t prefetch; uint16_t instruction; @@ -56,8 +59,8 @@ struct State: public Reflection::StructImpl { } inputs; /*! - Contains internal state used by this particular implementation of a 6502. Most of it - does not necessarily correlate with anything in a real 6502, and some of it very + Contains internal state used by this particular implementation of a 68000. Most of it + does not necessarily correlate with anything in a real 68000, and some of it very obviously doesn't. */ struct ExecutionState: public Reflection::StructImpl { diff --git a/Processors/RegisterSizes.hpp b/Processors/RegisterSizes.hpp deleted file mode 100644 index 2cb9af172..000000000 --- a/Processors/RegisterSizes.hpp +++ /dev/null @@ -1,39 +0,0 @@ -// -// RegisterSizes.hpp -// Clock Signal -// -// Created by Thomas Harte on 14/05/2017. -// Copyright 2017 Thomas Harte. All rights reserved. -// - -#ifndef RegisterSizes_hpp -#define RegisterSizes_hpp - -#include - -namespace CPU { - -template union RegisterPair { - RegisterPair(Full v) : full(v) {} - RegisterPair() {} - - Full full; -#pragma pack(push, 1) -#if TARGET_RT_BIG_ENDIAN - struct { - Half high, low; - } halves; -#else - struct { - Half low, high; - } halves; -#endif -#pragma pack(pop) -}; - -typedef RegisterPair RegisterPair16; -typedef RegisterPair RegisterPair32; - -} - -#endif /* RegisterSizes_hpp */ diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index caa0ae2f8..d5e2e91da 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -13,7 +13,7 @@ #include #include -#include "../RegisterSizes.hpp" +#include "../../Numeric/RegisterSizes.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ForceInline.hpp"