1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +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:
Thomas Harte 2017-09-01 19:46:29 -04:00
parent 0f85cffc78
commit b306776ba9
5 changed files with 797 additions and 761 deletions

View File

@ -537,6 +537,8 @@
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>"; };
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>"; };
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>"; };
@ -1518,6 +1520,8 @@
isa = PBXGroup;
children = (
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */,
4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */,
4B322DF41F5A2714004EB04C /* 6502Storage.hpp */,
);
path = Implementation;
sourceTree = "<group>";

View File

@ -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 <typename T, bool uses_ready_line> class Processor: public ProcessorBas
T &bus_handler_;
};
/*
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;
}
#include "Implementation/6502Implementation.hpp"
}
}

View File

@ -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),

View 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;
}

View 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 */