mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +00:00
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.
This commit is contained in:
parent
0f85cffc78
commit
b306776ba9
@ -537,6 +537,8 @@
|
|||||||
4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = "<group>"; };
|
4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = "<group>"; };
|
||||||
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = "<group>"; };
|
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = "<group>"; };
|
||||||
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = "<group>"; };
|
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = "<group>"; };
|
||||||
|
4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Implementation.hpp; sourceTree = "<group>"; };
|
||||||
|
4B322DF41F5A2714004EB04C /* 6502Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Storage.hpp; sourceTree = "<group>"; };
|
||||||
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = "<group>"; };
|
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = "<group>"; };
|
||||||
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
|
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
|
||||||
4B38F3421F2EB3E900D9235D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/AmstradCPC/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
4B38F3421F2EB3E900D9235D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/AmstradCPC/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||||
@ -1518,6 +1520,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */,
|
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */,
|
||||||
|
4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */,
|
||||||
|
4B322DF41F5A2714004EB04C /* 6502Storage.hpp */,
|
||||||
);
|
);
|
||||||
path = Implementation;
|
path = Implementation;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -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 {
|
enum Flag: uint8_t {
|
||||||
Sign = 0x80,
|
Sign = 0x80,
|
||||||
@ -102,10 +103,12 @@ class BusHandler {
|
|||||||
void flush() {}
|
void flush() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#include "Implementation/6502Storage.hpp"
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
A base class from which the 6502 descends; separated for implementation reasons only.
|
A base class from which the 6502 descends; separated for implementation reasons only.
|
||||||
*/
|
*/
|
||||||
class ProcessorBase {
|
class ProcessorBase: public ProcessorStorage {
|
||||||
public:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Gets the value of a register.
|
Gets the value of a register.
|
||||||
@ -175,136 +178,6 @@ class ProcessorBase {
|
|||||||
@returns @c true if the 6502 is jammed; @c false otherwise.
|
@returns @c true if the 6502 is jammed; @c false otherwise.
|
||||||
*/
|
*/
|
||||||
bool is_jammed();
|
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 <typename T, bool uses_ready_line> class Processor: public ProcessorBas
|
|||||||
T &bus_handler_;
|
T &bus_handler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#include "Implementation/6502Implementation.hpp"
|
||||||
/*
|
|
||||||
Implementation. Users: no need to read beyond here.
|
|
||||||
*/
|
|
||||||
template <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::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 <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ const uint8_t CPU::MOS6502::JamOpcode = 0xf2;
|
|||||||
|
|
||||||
#define JAM {CycleFetchOperand, CycleScheduleJam}
|
#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),
|
/* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetI, CycleReadVectorLow, CycleReadVectorHigh),
|
||||||
/* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA),
|
/* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA),
|
||||||
/* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO),
|
/* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO),
|
||||||
@ -277,7 +277,7 @@ bool ProcessorBase::is_jammed() {
|
|||||||
return is_jammed_;
|
return is_jammed_;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessorBase::ProcessorBase() :
|
ProcessorStorage::ProcessorStorage() :
|
||||||
is_jammed_(false),
|
is_jammed_(false),
|
||||||
ready_line_is_enabled_(false),
|
ready_line_is_enabled_(false),
|
||||||
ready_is_active_(false),
|
ready_is_active_(false),
|
||||||
|
637
Processors/6502/Implementation/6502Implementation.hpp
Normal file
637
Processors/6502/Implementation/6502Implementation.hpp
Normal file
@ -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 <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::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 <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::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;
|
||||||
|
}
|
148
Processors/6502/Implementation/6502Storage.hpp
Normal file
148
Processors/6502/Implementation/6502Storage.hpp
Normal file
@ -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 */
|
Loading…
x
Reference in New Issue
Block a user