From b306776ba9922decebf200d0a75a6d95d9db0865 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 1 Sep 2017 19:46:29 -0400 Subject: [PATCH] Removes from 6502.hpp all remaining implementation details, making it purely an interface document. Though those details remain visible to files including 6502.hpp through necessity. --- .../Clock Signal.xcodeproj/project.pbxproj | 4 + Processors/6502/6502.hpp | 765 +----------------- Processors/6502/Implementation/6502Base.cpp | 4 +- .../Implementation/6502Implementation.hpp | 637 +++++++++++++++ .../6502/Implementation/6502Storage.hpp | 148 ++++ 5 files changed, 797 insertions(+), 761 deletions(-) create mode 100644 Processors/6502/Implementation/6502Implementation.hpp create mode 100644 Processors/6502/Implementation/6502Storage.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 19ec1c533..b03b62e9e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -537,6 +537,8 @@ 4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = ""; }; 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = ""; }; 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = ""; }; + 4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Implementation.hpp; sourceTree = ""; }; + 4B322DF41F5A2714004EB04C /* 6502Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Storage.hpp; sourceTree = ""; }; 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = ""; }; 4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = ""; }; 4B38F3421F2EB3E900D9235D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/AmstradCPC/StaticAnalyser.cpp; sourceTree = ""; }; @@ -1518,6 +1520,8 @@ isa = PBXGroup; children = ( 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */, + 4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */, + 4B322DF41F5A2714004EB04C /* 6502Storage.hpp */, ); path = Implementation; sourceTree = ""; diff --git a/Processors/6502/6502.hpp b/Processors/6502/6502.hpp index 24e53758e..7d8e0ea01 100644 --- a/Processors/6502/6502.hpp +++ b/Processors/6502/6502.hpp @@ -34,7 +34,8 @@ enum Register { }; /* - Flags as defined on the 6502; can be used to decode the result of @c get_flags or to form a value for @c set_flags. + Flags as defined on the 6502; can be used to decode the result of @c get_value_of_register(Flags) or to form a value for + the corresponding set. */ enum Flag: uint8_t { Sign = 0x80, @@ -102,10 +103,12 @@ class BusHandler { void flush() {} }; +#include "Implementation/6502Storage.hpp" + /*! A base class from which the 6502 descends; separated for implementation reasons only. */ -class ProcessorBase { +class ProcessorBase: public ProcessorStorage { public: /*! Gets the value of a register. @@ -175,136 +178,6 @@ class ProcessorBase { @returns @c true if the 6502 is jammed; @c false otherwise. */ bool is_jammed(); - - protected: - ProcessorBase(); - - /* - This emulation functions by decomposing instructions into micro programs, consisting of the micro operations - as per the enum below. Each micro op takes at most one cycle. By convention, those called CycleX take a cycle - to perform whereas those called OperationX occur for free (so, in effect, their cost is loaded onto the next cycle). - */ - enum MicroOp { - CycleFetchOperation, CycleFetchOperand, OperationDecodeOperation, CycleIncPCPushPCH, - CyclePushPCH, CyclePushPCL, CyclePushA, CyclePushOperand, - OperationSetI, - - OperationBRKPickVector, OperationNMIPickVector, OperationRSTPickVector, - CycleReadVectorLow, CycleReadVectorHigh, - - CycleReadFromS, CycleReadFromPC, - CyclePullOperand, CyclePullPCL, CyclePullPCH, CyclePullA, - CycleNoWritePush, - CycleReadAndIncrementPC, CycleIncrementPCAndReadStack, CycleIncrementPCReadPCHLoadPCL, CycleReadPCHLoadPCL, - CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddress, CycleLoadAddressAbsolute, - OperationLoadAddressZeroPage, CycleLoadAddessZeroX, CycleLoadAddessZeroY, CycleAddXToAddressLow, - CycleAddYToAddressLow, CycleAddXToAddressLowRead, OperationCorrectAddressHigh, CycleAddYToAddressLowRead, - OperationMoveToNextProgram, OperationIncrementPC, - CycleFetchOperandFromAddress, CycleWriteOperandToAddress, OperationCopyOperandFromA, OperationCopyOperandToA, - CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh, OperationDecrementOperand, - OperationIncrementOperand, OperationORA, OperationAND, OperationEOR, - OperationINS, OperationADC, OperationSBC, OperationLDA, - OperationLDX, OperationLDY, OperationLAX, OperationSTA, - OperationSTX, OperationSTY, OperationSAX, OperationSHA, - OperationSHX, OperationSHY, OperationSHS, OperationCMP, - OperationCPX, OperationCPY, OperationBIT, OperationASL, - OperationASO, OperationROL, OperationRLA, OperationLSR, - OperationLSE, OperationASR, OperationROR, OperationRRA, - OperationCLC, OperationCLI, OperationCLV, OperationCLD, - OperationSEC, OperationSEI, OperationSED, OperationINC, - OperationDEC, OperationINX, OperationDEX, OperationINY, - OperationDEY, OperationBPL, OperationBMI, OperationBVC, - OperationBVS, OperationBCC, OperationBCS, OperationBNE, - OperationBEQ, OperationTXA, OperationTYA, OperationTXS, - OperationTAY, OperationTAX, OperationTSX, OperationARR, - OperationSBX, OperationLXA, OperationANE, OperationANC, - OperationLAS, CycleAddSignedOperandToPC, OperationSetFlagsFromOperand, OperationSetOperandFromFlagsWithBRKSet, - OperationSetOperandFromFlags, - OperationSetFlagsFromA, - CycleScheduleJam - }; - - static const MicroOp operations[256][10]; - - const MicroOp *scheduled_program_counter_; - - /* - Storage for the 6502 registers; F is stored as individual flags. - */ - RegisterPair pc_, last_operation_pc_; - uint8_t a_, x_, y_, s_; - uint8_t carry_flag_, negative_result_, zero_result_, decimal_flag_, overflow_flag_, inverse_interrupt_flag_; - - /* - Temporary state for the micro programs. - */ - uint8_t operation_, operand_; - RegisterPair address_, next_address_; - - /* - Temporary storage allowing a common dispatch point for calling perform_bus_operation; - possibly deferring is no longer of value. - */ - BusOperation next_bus_operation_; - uint16_t bus_address_; - uint8_t *bus_value_; - - /*! - Gets the flags register. - - @see set_flags - - @returns The current value of the flags register. - */ - inline uint8_t get_flags(); - - /*! - Sets the flags register. - - @see set_flags - - @param flags The new value of the flags register. - */ - inline void set_flags(uint8_t flags); - - bool is_jammed_; - Cycles cycles_left_to_run_; - - enum InterruptRequestFlags: uint8_t { - Reset = 0x80, - IRQ = Flag::Interrupt, - NMI = 0x20, - - PowerOn = 0x10, - }; - uint8_t interrupt_requests_; - - bool ready_is_active_; - bool ready_line_is_enabled_; - - uint8_t irq_line_, irq_request_history_; - bool nmi_line_is_enabled_, set_overflow_line_is_enabled_; - - /*! - Gets the program representing an RST response. - - @returns The program representing an RST response. - */ - inline const MicroOp *get_reset_program(); - - /*! - Gets the program representing an IRQ response. - - @returns The program representing an IRQ response. - */ - inline const MicroOp *get_irq_program(); - - /*! - Gets the program representing an NMI response. - - @returns The program representing an NMI response. - */ - inline const MicroOp *get_nmi_program(); }; /*! @@ -340,633 +213,7 @@ template class Processor: public ProcessorBas T &bus_handler_; }; - -/* - Implementation. Users: no need to read beyond here. -*/ -template void Processor::run_for(const Cycles cycles) { - static const MicroOp doBranch[] = { - CycleReadFromPC, - CycleAddSignedOperandToPC, - OperationMoveToNextProgram - }; - static uint8_t throwaway_target; - static const MicroOp fetch_decode_execute[] = { - CycleFetchOperation, - CycleFetchOperand, - OperationDecodeOperation - }; - - // These plus program below act to give the compiler permission to update these values - // without touching the class storage (i.e. it explicitly says they need be completely up - // to date in this stack frame only); which saves some complicated addressing - RegisterPair nextAddress = next_address_; - BusOperation nextBusOperation = next_bus_operation_; - uint16_t busAddress = bus_address_; - uint8_t *busValue = bus_value_; - -#define checkSchedule(op) \ -if(!scheduled_program_counter_) {\ -if(interrupt_requests_) {\ - if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\ - interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\ - scheduled_program_counter_ = get_reset_program();\ - } else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\ - interrupt_requests_ &= ~InterruptRequestFlags::NMI;\ - scheduled_program_counter_ = get_nmi_program();\ - } else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\ - scheduled_program_counter_ = get_irq_program();\ - } \ -} else {\ - scheduled_program_counter_ = fetch_decode_execute;\ -}\ -op;\ -} - -#define bus_access() \ -interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_; \ -irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \ -number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue); \ -nextBusOperation = BusOperation::None; \ -if(number_of_cycles <= Cycles(0)) break; - - checkSchedule(); - Cycles number_of_cycles = cycles + cycles_left_to_run_; - - while(number_of_cycles > Cycles(0)) { - - while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) { - number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); - } - - if(!uses_ready_line || !ready_is_active_) { - if(nextBusOperation != BusOperation::None) { - bus_access(); - } - - while(1) { - - const MicroOp cycle = *scheduled_program_counter_; - scheduled_program_counter_++; - -#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val; val = 0xff -#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val; val = 0xff -#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target; throwaway_target = 0xff -#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val - - switch(cycle) { - -#pragma mark - Fetch/Decode - - case CycleFetchOperation: { - last_operation_pc_ = pc_; - pc_.full++; - read_op(operation_, last_operation_pc_.full); - } break; - - case CycleFetchOperand: - read_mem(operand_, pc_.full); - break; - - case OperationDecodeOperation: - scheduled_program_counter_ = operations[operation_]; - continue; - - case OperationMoveToNextProgram: - scheduled_program_counter_ = nullptr; - checkSchedule(); - continue; - -#define push(v) {\ - uint16_t targetAddress = s_ | 0x100; s_--;\ - write_mem(v, targetAddress);\ -} - - case CycleIncPCPushPCH: pc_.full++; // deliberate fallthrough - case CyclePushPCH: push(pc_.bytes.high); break; - case CyclePushPCL: push(pc_.bytes.low); break; - case CyclePushOperand: push(operand_); break; - case CyclePushA: push(a_); break; - case CycleNoWritePush: { - uint16_t targetAddress = s_ | 0x100; s_--; - read_mem(operand_, targetAddress); - } - break; - -#undef push - - case CycleReadFromS: throwaway_read(s_ | 0x100); break; - case CycleReadFromPC: throwaway_read(pc_.full); break; - - case OperationBRKPickVector: - // NMI can usurp BRK-vector operations - nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; - interrupt_requests_ &= ~InterruptRequestFlags::NMI; // TODO: this probably doesn't happen now? - continue; - case OperationNMIPickVector: nextAddress.full = 0xfffa; continue; - case OperationRSTPickVector: nextAddress.full = 0xfffc; continue; - case CycleReadVectorLow: read_mem(pc_.bytes.low, nextAddress.full); break; - case CycleReadVectorHigh: read_mem(pc_.bytes.high, nextAddress.full+1); break; - case OperationSetI: inverse_interrupt_flag_ = 0; continue; - - case CyclePullPCL: s_++; read_mem(pc_.bytes.low, s_ | 0x100); break; - case CyclePullPCH: s_++; read_mem(pc_.bytes.high, s_ | 0x100); break; - case CyclePullA: s_++; read_mem(a_, s_ | 0x100); break; - case CyclePullOperand: s_++; read_mem(operand_, s_ | 0x100); break; - case OperationSetFlagsFromOperand: set_flags(operand_); continue; - case OperationSetOperandFromFlagsWithBRKSet: operand_ = get_flags() | Flag::Break; continue; - case OperationSetOperandFromFlags: operand_ = get_flags(); continue; - case OperationSetFlagsFromA: zero_result_ = negative_result_ = a_; continue; - - case CycleIncrementPCAndReadStack: pc_.full++; throwaway_read(s_ | 0x100); break; - case CycleReadPCLFromAddress: read_mem(pc_.bytes.low, address_.full); break; - case CycleReadPCHFromAddress: address_.bytes.low++; read_mem(pc_.bytes.high, address_.full); break; - - case CycleReadAndIncrementPC: { - uint16_t oldPC = pc_.full; - pc_.full++; - throwaway_read(oldPC); - } break; - -#pragma mark - JAM - - case CycleScheduleJam: { - is_jammed_ = true; - scheduled_program_counter_ = operations[CPU::MOS6502::JamOpcode]; - } continue; - -#pragma mark - Bitwise - - case OperationORA: a_ |= operand_; negative_result_ = zero_result_ = a_; continue; - case OperationAND: a_ &= operand_; negative_result_ = zero_result_ = a_; continue; - case OperationEOR: a_ ^= operand_; negative_result_ = zero_result_ = a_; continue; - -#pragma mark - Load and Store - - case OperationLDA: a_ = negative_result_ = zero_result_ = operand_; continue; - case OperationLDX: x_ = negative_result_ = zero_result_ = operand_; continue; - case OperationLDY: y_ = negative_result_ = zero_result_ = operand_; continue; - case OperationLAX: a_ = x_ = negative_result_ = zero_result_ = operand_; continue; - - case OperationSTA: operand_ = a_; continue; - case OperationSTX: operand_ = x_; continue; - case OperationSTY: operand_ = y_; continue; - case OperationSAX: operand_ = a_ & x_; continue; - case OperationSHA: operand_ = a_ & x_ & (address_.bytes.high+1); continue; - case OperationSHX: operand_ = x_ & (address_.bytes.high+1); continue; - case OperationSHY: operand_ = y_ & (address_.bytes.high+1); continue; - case OperationSHS: s_ = a_ & x_; operand_ = s_ & (address_.bytes.high+1); continue; - - case OperationLXA: - a_ = x_ = (a_ | 0xee) & operand_; - negative_result_ = zero_result_ = a_; - continue; - -#pragma mark - Compare - - case OperationCMP: { - const uint16_t temp16 = a_ - operand_; - negative_result_ = zero_result_ = (uint8_t)temp16; - carry_flag_ = ((~temp16) >> 8)&1; - } continue; - case OperationCPX: { - const uint16_t temp16 = x_ - operand_; - negative_result_ = zero_result_ = (uint8_t)temp16; - carry_flag_ = ((~temp16) >> 8)&1; - } continue; - case OperationCPY: { - const uint16_t temp16 = y_ - operand_; - negative_result_ = zero_result_ = (uint8_t)temp16; - carry_flag_ = ((~temp16) >> 8)&1; - } continue; - -#pragma mark - BIT - - case OperationBIT: - zero_result_ = operand_ & a_; - negative_result_ = operand_; - overflow_flag_ = operand_&Flag::Overflow; - continue; - -#pragma mark ADC/SBC (and INS) - - case OperationINS: - operand_++; // deliberate fallthrough - case OperationSBC: - if(decimal_flag_) { - const uint16_t notCarry = carry_flag_ ^ 0x1; - const uint16_t decimalResult = (uint16_t)a_ - (uint16_t)operand_ - notCarry; - uint16_t temp16; - - temp16 = (a_&0xf) - (operand_&0xf) - notCarry; - if(temp16 > 0xf) temp16 -= 0x6; - temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00); - temp16 += (a_&0xf0) - (operand_&0xf0); - - overflow_flag_ = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1; - negative_result_ = (uint8_t)temp16; - zero_result_ = (uint8_t)decimalResult; - - if(temp16 > 0xff) temp16 -= 0x60; - - carry_flag_ = (temp16 > 0xff) ? 0 : Flag::Carry; - a_ = (uint8_t)temp16; - continue; - } else { - operand_ = ~operand_; - } - - // deliberate fallthrough - case OperationADC: - if(decimal_flag_) { - const uint16_t decimalResult = (uint16_t)a_ + (uint16_t)operand_ + (uint16_t)carry_flag_; - - uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + carry_flag_; - if(low_nibble >= 0xa) low_nibble = ((low_nibble + 0x6) & 0xf) + 0x10; - uint16_t result = (uint16_t)(a_ & 0xf0) + (uint16_t)(operand_ & 0xf0) + (uint16_t)low_nibble; - negative_result_ = (uint8_t)result; - overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; - if(result >= 0xa0) result += 0x60; - - carry_flag_ = (result >> 8) ? 1 : 0; - a_ = (uint8_t)result; - zero_result_ = (uint8_t)decimalResult; - } else { - const uint16_t result = (uint16_t)a_ + (uint16_t)operand_ + (uint16_t)carry_flag_; - overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; - negative_result_ = zero_result_ = a_ = (uint8_t)result; - carry_flag_ = (result >> 8)&1; - } - - // fix up in case this was INS - if(cycle == OperationINS) operand_ = ~operand_; - continue; - -#pragma mark - Shifts and Rolls - - case OperationASL: - carry_flag_ = operand_ >> 7; - operand_ <<= 1; - negative_result_ = zero_result_ = operand_; - continue; - - case OperationASO: - carry_flag_ = operand_ >> 7; - operand_ <<= 1; - a_ |= operand_; - negative_result_ = zero_result_ = a_; - continue; - - case OperationROL: { - const uint8_t temp8 = (uint8_t)((operand_ << 1) | carry_flag_); - carry_flag_ = operand_ >> 7; - operand_ = negative_result_ = zero_result_ = temp8; - } continue; - - case OperationRLA: { - const uint8_t temp8 = (uint8_t)((operand_ << 1) | carry_flag_); - carry_flag_ = operand_ >> 7; - operand_ = temp8; - a_ &= operand_; - negative_result_ = zero_result_ = a_; - } continue; - - case OperationLSR: - carry_flag_ = operand_ & 1; - operand_ >>= 1; - negative_result_ = zero_result_ = operand_; - continue; - - case OperationLSE: - carry_flag_ = operand_ & 1; - operand_ >>= 1; - a_ ^= operand_; - negative_result_ = zero_result_ = a_; - continue; - - case OperationASR: - a_ &= operand_; - carry_flag_ = a_ & 1; - a_ >>= 1; - negative_result_ = zero_result_ = a_; - continue; - - case OperationROR: { - const uint8_t temp8 = (uint8_t)((operand_ >> 1) | (carry_flag_ << 7)); - carry_flag_ = operand_ & 1; - operand_ = negative_result_ = zero_result_ = temp8; - } continue; - - case OperationRRA: { - const uint8_t temp8 = (uint8_t)((operand_ >> 1) | (carry_flag_ << 7)); - carry_flag_ = operand_ & 1; - operand_ = temp8; - } continue; - - case OperationDecrementOperand: operand_--; continue; - case OperationIncrementOperand: operand_++; continue; - - case OperationCLC: carry_flag_ = 0; continue; - case OperationCLI: inverse_interrupt_flag_ = Flag::Interrupt; continue; - case OperationCLV: overflow_flag_ = 0; continue; - case OperationCLD: decimal_flag_ = 0; continue; - - case OperationSEC: carry_flag_ = Flag::Carry; continue; - case OperationSEI: inverse_interrupt_flag_ = 0; continue; - case OperationSED: decimal_flag_ = Flag::Decimal; continue; - - case OperationINC: operand_++; negative_result_ = zero_result_ = operand_; continue; - case OperationDEC: operand_--; negative_result_ = zero_result_ = operand_; continue; - case OperationINX: x_++; negative_result_ = zero_result_ = x_; continue; - case OperationDEX: x_--; negative_result_ = zero_result_ = x_; continue; - case OperationINY: y_++; negative_result_ = zero_result_ = y_; continue; - case OperationDEY: y_--; negative_result_ = zero_result_ = y_; continue; - - case OperationANE: - a_ = (a_ | 0xee) & operand_ & x_; - negative_result_ = zero_result_ = a_; - continue; - - case OperationANC: - a_ &= operand_; - negative_result_ = zero_result_ = a_; - carry_flag_ = a_ >> 7; - continue; - - case OperationLAS: - a_ = x_ = s_ = s_ & operand_; - negative_result_ = zero_result_ = a_; - continue; - -#pragma mark - Addressing Mode Work - - case CycleAddXToAddressLow: - nextAddress.full = address_.full + x_; - address_.bytes.low = nextAddress.bytes.low; - if(address_.bytes.high != nextAddress.bytes.high) { - throwaway_read(address_.full); - break; - } - continue; - case CycleAddXToAddressLowRead: - nextAddress.full = address_.full + x_; - address_.bytes.low = nextAddress.bytes.low; - throwaway_read(address_.full); - break; - case CycleAddYToAddressLow: - nextAddress.full = address_.full + y_; - address_.bytes.low = nextAddress.bytes.low; - if(address_.bytes.high != nextAddress.bytes.high) { - throwaway_read(address_.full); - break; - } - continue; - case CycleAddYToAddressLowRead: - nextAddress.full = address_.full + y_; - address_.bytes.low = nextAddress.bytes.low; - throwaway_read(address_.full); - break; - case OperationCorrectAddressHigh: - address_.full = nextAddress.full; - continue; - case CycleIncrementPCFetchAddressLowFromOperand: - pc_.full++; - read_mem(address_.bytes.low, operand_); - break; - case CycleAddXToOperandFetchAddressLow: - operand_ += x_; - read_mem(address_.bytes.low, operand_); - break; - case CycleIncrementOperandFetchAddressHigh: - operand_++; - read_mem(address_.bytes.high, operand_); - break; - case CycleIncrementPCReadPCHLoadPCL: // deliberate fallthrough - pc_.full++; - case CycleReadPCHLoadPCL: { - uint16_t oldPC = pc_.full; - pc_.bytes.low = operand_; - read_mem(pc_.bytes.high, oldPC); - } break; - - case CycleReadAddressHLoadAddressL: - address_.bytes.low = operand_; pc_.full++; - read_mem(address_.bytes.high, pc_.full); - break; - - case CycleLoadAddressAbsolute: { - uint16_t nextPC = pc_.full+1; - pc_.full += 2; - address_.bytes.low = operand_; - read_mem(address_.bytes.high, nextPC); - } break; - - case OperationLoadAddressZeroPage: - pc_.full++; - address_.full = operand_; - continue; - - case CycleLoadAddessZeroX: - pc_.full++; - address_.full = (operand_ + x_)&0xff; - throwaway_read(operand_); - break; - - case CycleLoadAddessZeroY: - pc_.full++; - address_.full = (operand_ + y_)&0xff; - throwaway_read(operand_); - break; - - case OperationIncrementPC: pc_.full++; continue; - case CycleFetchOperandFromAddress: read_mem(operand_, address_.full); break; - case CycleWriteOperandToAddress: write_mem(operand_, address_.full); break; - case OperationCopyOperandFromA: operand_ = a_; continue; - case OperationCopyOperandToA: a_ = operand_; continue; - -#pragma mark - Branching - -#define BRA(condition) pc_.full++; if(condition) scheduled_program_counter_ = doBranch - - case OperationBPL: BRA(!(negative_result_&0x80)); continue; - case OperationBMI: BRA(negative_result_&0x80); continue; - case OperationBVC: BRA(!overflow_flag_); continue; - case OperationBVS: BRA(overflow_flag_); continue; - case OperationBCC: BRA(!carry_flag_); continue; - case OperationBCS: BRA(carry_flag_); continue; - case OperationBNE: BRA(zero_result_); continue; - case OperationBEQ: BRA(!zero_result_); continue; - - case CycleAddSignedOperandToPC: - nextAddress.full = (uint16_t)(pc_.full + (int8_t)operand_); - pc_.bytes.low = nextAddress.bytes.low; - if(nextAddress.bytes.high != pc_.bytes.high) { - uint16_t halfUpdatedPc = pc_.full; - pc_.full = nextAddress.full; - throwaway_read(halfUpdatedPc); - break; - } - continue; - -#undef BRA - -#pragma mark - Transfers - - case OperationTXA: zero_result_ = negative_result_ = a_ = x_; continue; - case OperationTYA: zero_result_ = negative_result_ = a_ = y_; continue; - case OperationTXS: s_ = x_; continue; - case OperationTAY: zero_result_ = negative_result_ = y_ = a_; continue; - case OperationTAX: zero_result_ = negative_result_ = x_ = a_; continue; - case OperationTSX: zero_result_ = negative_result_ = x_ = s_; continue; - - case OperationARR: - if(decimal_flag_) { - a_ &= operand_; - uint8_t unshiftedA = a_; - a_ = (uint8_t)((a_ >> 1) | (carry_flag_ << 7)); - zero_result_ = negative_result_ = a_; - overflow_flag_ = (a_^(a_ << 1))&Flag::Overflow; - - if((unshiftedA&0xf) + (unshiftedA&0x1) > 5) a_ = ((a_ + 6)&0xf) | (a_ & 0xf0); - - carry_flag_ = ((unshiftedA&0xf0) + (unshiftedA&0x10) > 0x50) ? 1 : 0; - if(carry_flag_) a_ += 0x60; - } else { - a_ &= operand_; - a_ = (uint8_t)((a_ >> 1) | (carry_flag_ << 7)); - negative_result_ = zero_result_ = a_; - carry_flag_ = (a_ >> 6)&1; - overflow_flag_ = (a_^(a_ << 1))&Flag::Overflow; - } - continue; - - case OperationSBX: - x_ &= a_; - uint16_t difference = x_ - operand_; - x_ = (uint8_t)difference; - negative_result_ = zero_result_ = x_; - carry_flag_ = ((difference >> 8)&1)^1; - continue; - } - - if(uses_ready_line && ready_line_is_enabled_ && isReadOperation(nextBusOperation)) { - ready_is_active_ = true; - break; - } - bus_access(); - } - } - } - - cycles_left_to_run_ = number_of_cycles; - next_address_ = nextAddress; - next_bus_operation_ = nextBusOperation; - bus_address_ = busAddress; - bus_value_ = busValue; - - bus_handler_.flush(); -} - -template void Processor::set_ready_line(bool active) { - assert(uses_ready_line); - if(active) { - ready_line_is_enabled_ = true; - } else { - ready_line_is_enabled_ = false; - ready_is_active_ = false; - } -} - -void ProcessorBase::set_reset_line(bool active) { - interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::Reset) | (active ? InterruptRequestFlags::Reset : 0); -} - -bool ProcessorBase::get_is_resetting() { - return !!(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)); -} - -void ProcessorBase::set_power_on(bool active) { - interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::PowerOn) | (active ? InterruptRequestFlags::PowerOn : 0); -} - -void ProcessorBase::set_irq_line(bool active) { - irq_line_ = active ? Flag::Interrupt : 0; -} - -void ProcessorBase::set_overflow_line(bool active) { - // a leading edge will set the overflow flag - if(active && !set_overflow_line_is_enabled_) - overflow_flag_ = Flag::Overflow; - set_overflow_line_is_enabled_ = active; -} - -void ProcessorBase::set_nmi_line(bool active) { - // NMI is edge triggered, not level - if(active && !nmi_line_is_enabled_) - interrupt_requests_ |= InterruptRequestFlags::NMI; - nmi_line_is_enabled_ = active; -} - -inline const ProcessorBase::MicroOp *ProcessorBase::get_reset_program() { - static const MicroOp reset[] = { - CycleFetchOperand, - CycleFetchOperand, - CycleNoWritePush, - CycleNoWritePush, - OperationRSTPickVector, - CycleNoWritePush, - CycleReadVectorLow, - CycleReadVectorHigh, - OperationMoveToNextProgram - }; - return reset; -} - -inline const ProcessorBase::MicroOp *ProcessorBase::get_irq_program() { - static const MicroOp reset[] = { - CycleFetchOperand, - CycleFetchOperand, - CyclePushPCH, - CyclePushPCL, - OperationBRKPickVector, - OperationSetOperandFromFlags, - CyclePushOperand, - OperationSetI, - CycleReadVectorLow, - CycleReadVectorHigh, - OperationMoveToNextProgram - }; - return reset; -} - -inline const ProcessorBase::MicroOp *ProcessorBase::get_nmi_program() { - static const MicroOp reset[] = { - CycleFetchOperand, - CycleFetchOperand, - CyclePushPCH, - CyclePushPCL, - OperationNMIPickVector, - OperationSetOperandFromFlags, - CyclePushOperand, - CycleReadVectorLow, - CycleReadVectorHigh, - OperationMoveToNextProgram - }; - return reset; -} - -uint8_t ProcessorBase::get_flags() { - return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_; -} - -void ProcessorBase::set_flags(uint8_t flags) { - carry_flag_ = flags & Flag::Carry; - negative_result_ = flags & Flag::Sign; - zero_result_ = (~flags) & Flag::Zero; - overflow_flag_ = flags & Flag::Overflow; - inverse_interrupt_flag_ = (~flags) & Flag::Interrupt; - decimal_flag_ = flags & Flag::Decimal; -} +#include "Implementation/6502Implementation.hpp" } } diff --git a/Processors/6502/Implementation/6502Base.cpp b/Processors/6502/Implementation/6502Base.cpp index 9f14b6189..b44c23422 100644 --- a/Processors/6502/Implementation/6502Base.cpp +++ b/Processors/6502/Implementation/6502Base.cpp @@ -69,7 +69,7 @@ const uint8_t CPU::MOS6502::JamOpcode = 0xf2; #define JAM {CycleFetchOperand, CycleScheduleJam} -const ProcessorBase::MicroOp ProcessorBase::operations[256][10] = { +const ProcessorStorage::MicroOp ProcessorStorage::operations[256][10] = { /* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetI, CycleReadVectorLow, CycleReadVectorHigh), /* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA), /* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO), @@ -277,7 +277,7 @@ bool ProcessorBase::is_jammed() { return is_jammed_; } -ProcessorBase::ProcessorBase() : +ProcessorStorage::ProcessorStorage() : is_jammed_(false), ready_line_is_enabled_(false), ready_is_active_(false), diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp new file mode 100644 index 000000000..b17c5a517 --- /dev/null +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -0,0 +1,637 @@ +// +// 6502Implementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +/* + Here lies the implementations of those methods declared in the CPU::MOS6502::Processor template, or declared + as inline within CPU::MOS6502::ProcessorBase. So it's stuff that has to be in a header file, visible from + 6502.hpp, but it's implementation stuff. +*/ + +template void Processor::run_for(const Cycles cycles) { + static const MicroOp doBranch[] = { + CycleReadFromPC, + CycleAddSignedOperandToPC, + OperationMoveToNextProgram + }; + static uint8_t throwaway_target; + static const MicroOp fetch_decode_execute[] = { + CycleFetchOperation, + CycleFetchOperand, + OperationDecodeOperation + }; + + // These plus program below act to give the compiler permission to update these values + // without touching the class storage (i.e. it explicitly says they need be completely up + // to date in this stack frame only); which saves some complicated addressing + RegisterPair nextAddress = next_address_; + BusOperation nextBusOperation = next_bus_operation_; + uint16_t busAddress = bus_address_; + uint8_t *busValue = bus_value_; + +#define checkSchedule(op) \ +if(!scheduled_program_counter_) {\ +if(interrupt_requests_) {\ + if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\ + interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\ + scheduled_program_counter_ = get_reset_program();\ + } else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\ + interrupt_requests_ &= ~InterruptRequestFlags::NMI;\ + scheduled_program_counter_ = get_nmi_program();\ + } else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\ + scheduled_program_counter_ = get_irq_program();\ + } \ +} else {\ + scheduled_program_counter_ = fetch_decode_execute;\ +}\ +op;\ +} + +#define bus_access() \ +interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_; \ +irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \ +number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue); \ +nextBusOperation = BusOperation::None; \ +if(number_of_cycles <= Cycles(0)) break; + + checkSchedule(); + Cycles number_of_cycles = cycles + cycles_left_to_run_; + + while(number_of_cycles > Cycles(0)) { + + while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) { + number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); + } + + if(!uses_ready_line || !ready_is_active_) { + if(nextBusOperation != BusOperation::None) { + bus_access(); + } + + while(1) { + + const MicroOp cycle = *scheduled_program_counter_; + scheduled_program_counter_++; + +#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val; val = 0xff +#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val; val = 0xff +#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target; throwaway_target = 0xff +#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val + + switch(cycle) { + +#pragma mark - Fetch/Decode + + case CycleFetchOperation: { + last_operation_pc_ = pc_; + pc_.full++; + read_op(operation_, last_operation_pc_.full); + } break; + + case CycleFetchOperand: + read_mem(operand_, pc_.full); + break; + + case OperationDecodeOperation: + scheduled_program_counter_ = operations[operation_]; + continue; + + case OperationMoveToNextProgram: + scheduled_program_counter_ = nullptr; + checkSchedule(); + continue; + +#define push(v) {\ + uint16_t targetAddress = s_ | 0x100; s_--;\ + write_mem(v, targetAddress);\ +} + + case CycleIncPCPushPCH: pc_.full++; // deliberate fallthrough + case CyclePushPCH: push(pc_.bytes.high); break; + case CyclePushPCL: push(pc_.bytes.low); break; + case CyclePushOperand: push(operand_); break; + case CyclePushA: push(a_); break; + case CycleNoWritePush: { + uint16_t targetAddress = s_ | 0x100; s_--; + read_mem(operand_, targetAddress); + } + break; + +#undef push + + case CycleReadFromS: throwaway_read(s_ | 0x100); break; + case CycleReadFromPC: throwaway_read(pc_.full); break; + + case OperationBRKPickVector: + // NMI can usurp BRK-vector operations + nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; + interrupt_requests_ &= ~InterruptRequestFlags::NMI; // TODO: this probably doesn't happen now? + continue; + case OperationNMIPickVector: nextAddress.full = 0xfffa; continue; + case OperationRSTPickVector: nextAddress.full = 0xfffc; continue; + case CycleReadVectorLow: read_mem(pc_.bytes.low, nextAddress.full); break; + case CycleReadVectorHigh: read_mem(pc_.bytes.high, nextAddress.full+1); break; + case OperationSetI: inverse_interrupt_flag_ = 0; continue; + + case CyclePullPCL: s_++; read_mem(pc_.bytes.low, s_ | 0x100); break; + case CyclePullPCH: s_++; read_mem(pc_.bytes.high, s_ | 0x100); break; + case CyclePullA: s_++; read_mem(a_, s_ | 0x100); break; + case CyclePullOperand: s_++; read_mem(operand_, s_ | 0x100); break; + case OperationSetFlagsFromOperand: set_flags(operand_); continue; + case OperationSetOperandFromFlagsWithBRKSet: operand_ = get_flags() | Flag::Break; continue; + case OperationSetOperandFromFlags: operand_ = get_flags(); continue; + case OperationSetFlagsFromA: zero_result_ = negative_result_ = a_; continue; + + case CycleIncrementPCAndReadStack: pc_.full++; throwaway_read(s_ | 0x100); break; + case CycleReadPCLFromAddress: read_mem(pc_.bytes.low, address_.full); break; + case CycleReadPCHFromAddress: address_.bytes.low++; read_mem(pc_.bytes.high, address_.full); break; + + case CycleReadAndIncrementPC: { + uint16_t oldPC = pc_.full; + pc_.full++; + throwaway_read(oldPC); + } break; + +#pragma mark - JAM + + case CycleScheduleJam: { + is_jammed_ = true; + scheduled_program_counter_ = operations[CPU::MOS6502::JamOpcode]; + } continue; + +#pragma mark - Bitwise + + case OperationORA: a_ |= operand_; negative_result_ = zero_result_ = a_; continue; + case OperationAND: a_ &= operand_; negative_result_ = zero_result_ = a_; continue; + case OperationEOR: a_ ^= operand_; negative_result_ = zero_result_ = a_; continue; + +#pragma mark - Load and Store + + case OperationLDA: a_ = negative_result_ = zero_result_ = operand_; continue; + case OperationLDX: x_ = negative_result_ = zero_result_ = operand_; continue; + case OperationLDY: y_ = negative_result_ = zero_result_ = operand_; continue; + case OperationLAX: a_ = x_ = negative_result_ = zero_result_ = operand_; continue; + + case OperationSTA: operand_ = a_; continue; + case OperationSTX: operand_ = x_; continue; + case OperationSTY: operand_ = y_; continue; + case OperationSAX: operand_ = a_ & x_; continue; + case OperationSHA: operand_ = a_ & x_ & (address_.bytes.high+1); continue; + case OperationSHX: operand_ = x_ & (address_.bytes.high+1); continue; + case OperationSHY: operand_ = y_ & (address_.bytes.high+1); continue; + case OperationSHS: s_ = a_ & x_; operand_ = s_ & (address_.bytes.high+1); continue; + + case OperationLXA: + a_ = x_ = (a_ | 0xee) & operand_; + negative_result_ = zero_result_ = a_; + continue; + +#pragma mark - Compare + + case OperationCMP: { + const uint16_t temp16 = a_ - operand_; + negative_result_ = zero_result_ = (uint8_t)temp16; + carry_flag_ = ((~temp16) >> 8)&1; + } continue; + case OperationCPX: { + const uint16_t temp16 = x_ - operand_; + negative_result_ = zero_result_ = (uint8_t)temp16; + carry_flag_ = ((~temp16) >> 8)&1; + } continue; + case OperationCPY: { + const uint16_t temp16 = y_ - operand_; + negative_result_ = zero_result_ = (uint8_t)temp16; + carry_flag_ = ((~temp16) >> 8)&1; + } continue; + +#pragma mark - BIT + + case OperationBIT: + zero_result_ = operand_ & a_; + negative_result_ = operand_; + overflow_flag_ = operand_&Flag::Overflow; + continue; + +#pragma mark ADC/SBC (and INS) + + case OperationINS: + operand_++; // deliberate fallthrough + case OperationSBC: + if(decimal_flag_) { + const uint16_t notCarry = carry_flag_ ^ 0x1; + const uint16_t decimalResult = (uint16_t)a_ - (uint16_t)operand_ - notCarry; + uint16_t temp16; + + temp16 = (a_&0xf) - (operand_&0xf) - notCarry; + if(temp16 > 0xf) temp16 -= 0x6; + temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00); + temp16 += (a_&0xf0) - (operand_&0xf0); + + overflow_flag_ = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1; + negative_result_ = (uint8_t)temp16; + zero_result_ = (uint8_t)decimalResult; + + if(temp16 > 0xff) temp16 -= 0x60; + + carry_flag_ = (temp16 > 0xff) ? 0 : Flag::Carry; + a_ = (uint8_t)temp16; + continue; + } else { + operand_ = ~operand_; + } + + // deliberate fallthrough + case OperationADC: + if(decimal_flag_) { + const uint16_t decimalResult = (uint16_t)a_ + (uint16_t)operand_ + (uint16_t)carry_flag_; + + uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + carry_flag_; + if(low_nibble >= 0xa) low_nibble = ((low_nibble + 0x6) & 0xf) + 0x10; + uint16_t result = (uint16_t)(a_ & 0xf0) + (uint16_t)(operand_ & 0xf0) + (uint16_t)low_nibble; + negative_result_ = (uint8_t)result; + overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; + if(result >= 0xa0) result += 0x60; + + carry_flag_ = (result >> 8) ? 1 : 0; + a_ = (uint8_t)result; + zero_result_ = (uint8_t)decimalResult; + } else { + const uint16_t result = (uint16_t)a_ + (uint16_t)operand_ + (uint16_t)carry_flag_; + overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; + negative_result_ = zero_result_ = a_ = (uint8_t)result; + carry_flag_ = (result >> 8)&1; + } + + // fix up in case this was INS + if(cycle == OperationINS) operand_ = ~operand_; + continue; + +#pragma mark - Shifts and Rolls + + case OperationASL: + carry_flag_ = operand_ >> 7; + operand_ <<= 1; + negative_result_ = zero_result_ = operand_; + continue; + + case OperationASO: + carry_flag_ = operand_ >> 7; + operand_ <<= 1; + a_ |= operand_; + negative_result_ = zero_result_ = a_; + continue; + + case OperationROL: { + const uint8_t temp8 = (uint8_t)((operand_ << 1) | carry_flag_); + carry_flag_ = operand_ >> 7; + operand_ = negative_result_ = zero_result_ = temp8; + } continue; + + case OperationRLA: { + const uint8_t temp8 = (uint8_t)((operand_ << 1) | carry_flag_); + carry_flag_ = operand_ >> 7; + operand_ = temp8; + a_ &= operand_; + negative_result_ = zero_result_ = a_; + } continue; + + case OperationLSR: + carry_flag_ = operand_ & 1; + operand_ >>= 1; + negative_result_ = zero_result_ = operand_; + continue; + + case OperationLSE: + carry_flag_ = operand_ & 1; + operand_ >>= 1; + a_ ^= operand_; + negative_result_ = zero_result_ = a_; + continue; + + case OperationASR: + a_ &= operand_; + carry_flag_ = a_ & 1; + a_ >>= 1; + negative_result_ = zero_result_ = a_; + continue; + + case OperationROR: { + const uint8_t temp8 = (uint8_t)((operand_ >> 1) | (carry_flag_ << 7)); + carry_flag_ = operand_ & 1; + operand_ = negative_result_ = zero_result_ = temp8; + } continue; + + case OperationRRA: { + const uint8_t temp8 = (uint8_t)((operand_ >> 1) | (carry_flag_ << 7)); + carry_flag_ = operand_ & 1; + operand_ = temp8; + } continue; + + case OperationDecrementOperand: operand_--; continue; + case OperationIncrementOperand: operand_++; continue; + + case OperationCLC: carry_flag_ = 0; continue; + case OperationCLI: inverse_interrupt_flag_ = Flag::Interrupt; continue; + case OperationCLV: overflow_flag_ = 0; continue; + case OperationCLD: decimal_flag_ = 0; continue; + + case OperationSEC: carry_flag_ = Flag::Carry; continue; + case OperationSEI: inverse_interrupt_flag_ = 0; continue; + case OperationSED: decimal_flag_ = Flag::Decimal; continue; + + case OperationINC: operand_++; negative_result_ = zero_result_ = operand_; continue; + case OperationDEC: operand_--; negative_result_ = zero_result_ = operand_; continue; + case OperationINX: x_++; negative_result_ = zero_result_ = x_; continue; + case OperationDEX: x_--; negative_result_ = zero_result_ = x_; continue; + case OperationINY: y_++; negative_result_ = zero_result_ = y_; continue; + case OperationDEY: y_--; negative_result_ = zero_result_ = y_; continue; + + case OperationANE: + a_ = (a_ | 0xee) & operand_ & x_; + negative_result_ = zero_result_ = a_; + continue; + + case OperationANC: + a_ &= operand_; + negative_result_ = zero_result_ = a_; + carry_flag_ = a_ >> 7; + continue; + + case OperationLAS: + a_ = x_ = s_ = s_ & operand_; + negative_result_ = zero_result_ = a_; + continue; + +#pragma mark - Addressing Mode Work + + case CycleAddXToAddressLow: + nextAddress.full = address_.full + x_; + address_.bytes.low = nextAddress.bytes.low; + if(address_.bytes.high != nextAddress.bytes.high) { + throwaway_read(address_.full); + break; + } + continue; + case CycleAddXToAddressLowRead: + nextAddress.full = address_.full + x_; + address_.bytes.low = nextAddress.bytes.low; + throwaway_read(address_.full); + break; + case CycleAddYToAddressLow: + nextAddress.full = address_.full + y_; + address_.bytes.low = nextAddress.bytes.low; + if(address_.bytes.high != nextAddress.bytes.high) { + throwaway_read(address_.full); + break; + } + continue; + case CycleAddYToAddressLowRead: + nextAddress.full = address_.full + y_; + address_.bytes.low = nextAddress.bytes.low; + throwaway_read(address_.full); + break; + case OperationCorrectAddressHigh: + address_.full = nextAddress.full; + continue; + case CycleIncrementPCFetchAddressLowFromOperand: + pc_.full++; + read_mem(address_.bytes.low, operand_); + break; + case CycleAddXToOperandFetchAddressLow: + operand_ += x_; + read_mem(address_.bytes.low, operand_); + break; + case CycleIncrementOperandFetchAddressHigh: + operand_++; + read_mem(address_.bytes.high, operand_); + break; + case CycleIncrementPCReadPCHLoadPCL: // deliberate fallthrough + pc_.full++; + case CycleReadPCHLoadPCL: { + uint16_t oldPC = pc_.full; + pc_.bytes.low = operand_; + read_mem(pc_.bytes.high, oldPC); + } break; + + case CycleReadAddressHLoadAddressL: + address_.bytes.low = operand_; pc_.full++; + read_mem(address_.bytes.high, pc_.full); + break; + + case CycleLoadAddressAbsolute: { + uint16_t nextPC = pc_.full+1; + pc_.full += 2; + address_.bytes.low = operand_; + read_mem(address_.bytes.high, nextPC); + } break; + + case OperationLoadAddressZeroPage: + pc_.full++; + address_.full = operand_; + continue; + + case CycleLoadAddessZeroX: + pc_.full++; + address_.full = (operand_ + x_)&0xff; + throwaway_read(operand_); + break; + + case CycleLoadAddessZeroY: + pc_.full++; + address_.full = (operand_ + y_)&0xff; + throwaway_read(operand_); + break; + + case OperationIncrementPC: pc_.full++; continue; + case CycleFetchOperandFromAddress: read_mem(operand_, address_.full); break; + case CycleWriteOperandToAddress: write_mem(operand_, address_.full); break; + case OperationCopyOperandFromA: operand_ = a_; continue; + case OperationCopyOperandToA: a_ = operand_; continue; + +#pragma mark - Branching + +#define BRA(condition) pc_.full++; if(condition) scheduled_program_counter_ = doBranch + + case OperationBPL: BRA(!(negative_result_&0x80)); continue; + case OperationBMI: BRA(negative_result_&0x80); continue; + case OperationBVC: BRA(!overflow_flag_); continue; + case OperationBVS: BRA(overflow_flag_); continue; + case OperationBCC: BRA(!carry_flag_); continue; + case OperationBCS: BRA(carry_flag_); continue; + case OperationBNE: BRA(zero_result_); continue; + case OperationBEQ: BRA(!zero_result_); continue; + + case CycleAddSignedOperandToPC: + nextAddress.full = (uint16_t)(pc_.full + (int8_t)operand_); + pc_.bytes.low = nextAddress.bytes.low; + if(nextAddress.bytes.high != pc_.bytes.high) { + uint16_t halfUpdatedPc = pc_.full; + pc_.full = nextAddress.full; + throwaway_read(halfUpdatedPc); + break; + } + continue; + +#undef BRA + +#pragma mark - Transfers + + case OperationTXA: zero_result_ = negative_result_ = a_ = x_; continue; + case OperationTYA: zero_result_ = negative_result_ = a_ = y_; continue; + case OperationTXS: s_ = x_; continue; + case OperationTAY: zero_result_ = negative_result_ = y_ = a_; continue; + case OperationTAX: zero_result_ = negative_result_ = x_ = a_; continue; + case OperationTSX: zero_result_ = negative_result_ = x_ = s_; continue; + + case OperationARR: + if(decimal_flag_) { + a_ &= operand_; + uint8_t unshiftedA = a_; + a_ = (uint8_t)((a_ >> 1) | (carry_flag_ << 7)); + zero_result_ = negative_result_ = a_; + overflow_flag_ = (a_^(a_ << 1))&Flag::Overflow; + + if((unshiftedA&0xf) + (unshiftedA&0x1) > 5) a_ = ((a_ + 6)&0xf) | (a_ & 0xf0); + + carry_flag_ = ((unshiftedA&0xf0) + (unshiftedA&0x10) > 0x50) ? 1 : 0; + if(carry_flag_) a_ += 0x60; + } else { + a_ &= operand_; + a_ = (uint8_t)((a_ >> 1) | (carry_flag_ << 7)); + negative_result_ = zero_result_ = a_; + carry_flag_ = (a_ >> 6)&1; + overflow_flag_ = (a_^(a_ << 1))&Flag::Overflow; + } + continue; + + case OperationSBX: + x_ &= a_; + uint16_t difference = x_ - operand_; + x_ = (uint8_t)difference; + negative_result_ = zero_result_ = x_; + carry_flag_ = ((difference >> 8)&1)^1; + continue; + } + + if(uses_ready_line && ready_line_is_enabled_ && isReadOperation(nextBusOperation)) { + ready_is_active_ = true; + break; + } + bus_access(); + } + } + } + + cycles_left_to_run_ = number_of_cycles; + next_address_ = nextAddress; + next_bus_operation_ = nextBusOperation; + bus_address_ = busAddress; + bus_value_ = busValue; + + bus_handler_.flush(); +} + +template void Processor::set_ready_line(bool active) { + assert(uses_ready_line); + if(active) { + ready_line_is_enabled_ = true; + } else { + ready_line_is_enabled_ = false; + ready_is_active_ = false; + } +} + +void ProcessorBase::set_reset_line(bool active) { + interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::Reset) | (active ? InterruptRequestFlags::Reset : 0); +} + +bool ProcessorBase::get_is_resetting() { + return !!(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)); +} + +void ProcessorBase::set_power_on(bool active) { + interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::PowerOn) | (active ? InterruptRequestFlags::PowerOn : 0); +} + +void ProcessorBase::set_irq_line(bool active) { + irq_line_ = active ? Flag::Interrupt : 0; +} + +void ProcessorBase::set_overflow_line(bool active) { + // a leading edge will set the overflow flag + if(active && !set_overflow_line_is_enabled_) + overflow_flag_ = Flag::Overflow; + set_overflow_line_is_enabled_ = active; +} + +void ProcessorBase::set_nmi_line(bool active) { + // NMI is edge triggered, not level + if(active && !nmi_line_is_enabled_) + interrupt_requests_ |= InterruptRequestFlags::NMI; + nmi_line_is_enabled_ = active; +} + +inline const ProcessorStorage::MicroOp *ProcessorStorage::get_reset_program() { + static const MicroOp reset[] = { + CycleFetchOperand, + CycleFetchOperand, + CycleNoWritePush, + CycleNoWritePush, + OperationRSTPickVector, + CycleNoWritePush, + CycleReadVectorLow, + CycleReadVectorHigh, + OperationMoveToNextProgram + }; + return reset; +} + +inline const ProcessorStorage::MicroOp *ProcessorStorage::get_irq_program() { + static const MicroOp reset[] = { + CycleFetchOperand, + CycleFetchOperand, + CyclePushPCH, + CyclePushPCL, + OperationBRKPickVector, + OperationSetOperandFromFlags, + CyclePushOperand, + OperationSetI, + CycleReadVectorLow, + CycleReadVectorHigh, + OperationMoveToNextProgram + }; + return reset; +} + +inline const ProcessorStorage::MicroOp *ProcessorStorage::get_nmi_program() { + static const MicroOp reset[] = { + CycleFetchOperand, + CycleFetchOperand, + CyclePushPCH, + CyclePushPCL, + OperationNMIPickVector, + OperationSetOperandFromFlags, + CyclePushOperand, + CycleReadVectorLow, + CycleReadVectorHigh, + OperationMoveToNextProgram + }; + return reset; +} + +uint8_t ProcessorStorage::get_flags() { + return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_; +} + +void ProcessorStorage::set_flags(uint8_t flags) { + carry_flag_ = flags & Flag::Carry; + negative_result_ = flags & Flag::Sign; + zero_result_ = (~flags) & Flag::Zero; + overflow_flag_ = flags & Flag::Overflow; + inverse_interrupt_flag_ = (~flags) & Flag::Interrupt; + decimal_flag_ = flags & Flag::Decimal; +} diff --git a/Processors/6502/Implementation/6502Storage.hpp b/Processors/6502/Implementation/6502Storage.hpp new file mode 100644 index 000000000..27a19fe3c --- /dev/null +++ b/Processors/6502/Implementation/6502Storage.hpp @@ -0,0 +1,148 @@ +// +// 6502Storage.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef MOS6502Storage_h +#define MOS6502Storage_h + +/*! + A repository for all the internal state of a CPU::MOS6502::Processor; extracted into a separate base + class in order to remove it from visibility within the main 6502.hpp. +*/ +class ProcessorStorage { + protected: + ProcessorStorage(); + + /* + This emulation functions by decomposing instructions into micro programs, consisting of the micro operations + as per the enum below. Each micro op takes at most one cycle. By convention, those called CycleX take a cycle + to perform whereas those called OperationX occur for free (so, in effect, their cost is loaded onto the next cycle). + */ + enum MicroOp { + CycleFetchOperation, CycleFetchOperand, OperationDecodeOperation, CycleIncPCPushPCH, + CyclePushPCH, CyclePushPCL, CyclePushA, CyclePushOperand, + OperationSetI, + + OperationBRKPickVector, OperationNMIPickVector, OperationRSTPickVector, + CycleReadVectorLow, CycleReadVectorHigh, + + CycleReadFromS, CycleReadFromPC, + CyclePullOperand, CyclePullPCL, CyclePullPCH, CyclePullA, + CycleNoWritePush, + CycleReadAndIncrementPC, CycleIncrementPCAndReadStack, CycleIncrementPCReadPCHLoadPCL, CycleReadPCHLoadPCL, + CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddress, CycleLoadAddressAbsolute, + OperationLoadAddressZeroPage, CycleLoadAddessZeroX, CycleLoadAddessZeroY, CycleAddXToAddressLow, + CycleAddYToAddressLow, CycleAddXToAddressLowRead, OperationCorrectAddressHigh, CycleAddYToAddressLowRead, + OperationMoveToNextProgram, OperationIncrementPC, + CycleFetchOperandFromAddress, CycleWriteOperandToAddress, OperationCopyOperandFromA, OperationCopyOperandToA, + CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh, OperationDecrementOperand, + OperationIncrementOperand, OperationORA, OperationAND, OperationEOR, + OperationINS, OperationADC, OperationSBC, OperationLDA, + OperationLDX, OperationLDY, OperationLAX, OperationSTA, + OperationSTX, OperationSTY, OperationSAX, OperationSHA, + OperationSHX, OperationSHY, OperationSHS, OperationCMP, + OperationCPX, OperationCPY, OperationBIT, OperationASL, + OperationASO, OperationROL, OperationRLA, OperationLSR, + OperationLSE, OperationASR, OperationROR, OperationRRA, + OperationCLC, OperationCLI, OperationCLV, OperationCLD, + OperationSEC, OperationSEI, OperationSED, OperationINC, + OperationDEC, OperationINX, OperationDEX, OperationINY, + OperationDEY, OperationBPL, OperationBMI, OperationBVC, + OperationBVS, OperationBCC, OperationBCS, OperationBNE, + OperationBEQ, OperationTXA, OperationTYA, OperationTXS, + OperationTAY, OperationTAX, OperationTSX, OperationARR, + OperationSBX, OperationLXA, OperationANE, OperationANC, + OperationLAS, CycleAddSignedOperandToPC, OperationSetFlagsFromOperand, OperationSetOperandFromFlagsWithBRKSet, + OperationSetOperandFromFlags, + OperationSetFlagsFromA, + CycleScheduleJam + }; + + static const MicroOp operations[256][10]; + + const MicroOp *scheduled_program_counter_; + + /* + Storage for the 6502 registers; F is stored as individual flags. + */ + RegisterPair pc_, last_operation_pc_; + uint8_t a_, x_, y_, s_; + uint8_t carry_flag_, negative_result_, zero_result_, decimal_flag_, overflow_flag_, inverse_interrupt_flag_; + + /* + Temporary state for the micro programs. + */ + uint8_t operation_, operand_; + RegisterPair address_, next_address_; + + /* + Temporary storage allowing a common dispatch point for calling perform_bus_operation; + possibly deferring is no longer of value. + */ + BusOperation next_bus_operation_; + uint16_t bus_address_; + uint8_t *bus_value_; + + /*! + Gets the flags register. + + @see set_flags + + @returns The current value of the flags register. + */ + inline uint8_t get_flags(); + + /*! + Sets the flags register. + + @see set_flags + + @param flags The new value of the flags register. + */ + inline void set_flags(uint8_t flags); + + bool is_jammed_; + Cycles cycles_left_to_run_; + + enum InterruptRequestFlags: uint8_t { + Reset = 0x80, + IRQ = Flag::Interrupt, + NMI = 0x20, + + PowerOn = 0x10, + }; + uint8_t interrupt_requests_; + + bool ready_is_active_; + bool ready_line_is_enabled_; + + uint8_t irq_line_, irq_request_history_; + bool nmi_line_is_enabled_, set_overflow_line_is_enabled_; + + /*! + Gets the program representing an RST response. + + @returns The program representing an RST response. + */ + inline const MicroOp *get_reset_program(); + + /*! + Gets the program representing an IRQ response. + + @returns The program representing an IRQ response. + */ + inline const MicroOp *get_irq_program(); + + /*! + Gets the program representing an NMI response. + + @returns The program representing an NMI response. + */ + inline const MicroOp *get_nmi_program(); +}; + +#endif /* _502Storage_h */