1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-09 20:25:19 +00:00
Files
CLK/Processors/6502/Implementation/6502Storage.hpp
2024-12-01 09:04:32 -05:00

301 lines
16 KiB
C++

//
// 6502Storage.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/09/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
/*!
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(Personality);
/*!
This emulation functions by decomposing instructions into micro programs, consisting of the micro operations
defined by MicroOp. 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).
This micro-instruction set was put together in a fairly ad hoc fashion, I'm afraid, so is unlikely to be optimal.
*/
enum MicroOp: uint8_t {
CycleFetchOperation, // fetches (PC) to operation_, storing PC to last_operation_pc_ before incrementing it
CycleFetchOperand, // 6502: fetches from (PC) to operand_; 65C02: as 6502 unless operation_ indicates a one-cycle NOP, in which case this is a no0op
OperationDecodeOperation, // schedules the microprogram associated with operation_
OperationMoveToNextProgram, // either schedules the next fetch-decode-execute or an interrupt response if a request has been pending for at least one cycle
CycleIncPCPushPCH, // increments the PC and pushes PC.h to the stack
CyclePushPCL, // pushes PC.l to the stack
CyclePushPCH, // pushes PC.h to the stack
CyclePushA, // pushes A to the stack
CyclePushX, // pushes X to the stack
CyclePushY, // pushes Y to the stack
CyclePushOperand, // pushes operand_ to the stack
OperationSetIRQFlags, // 6502: sets I; 65C02: sets I and resets D
OperationSetNMIRSTFlags, // 6502: no-op. 65C02: resets D
OperationBRKPickVector, // 65C02: sets next_address_ to the BRK vector location; 6502: as 65C02 if no NMI is pending; otherwise sets next_address_ to the NMI address and resets the internal NMI-pending flag
OperationNMIPickVector, // sets next_address_ to the NMI vector
OperationRSTPickVector, // sets next_address_ to the RST vector
CycleReadVectorLow, // reads PC.l from next_address_
CycleReadVectorHigh, // reads PC.h from (next_address_+1)
CycleReadFromS, // performs a read from the stack pointer, throwing the result away
CycleReadFromPC, // performs a read from the program counter, throwing the result away
CyclePullPCL, // pulls PC.l from the stack
CyclePullPCH, // pulls PC.h from the stack
CyclePullA, // pulls A from the stack
CyclePullX, // pulls X from the stack
CyclePullY, // pulls Y from the stack
CyclePullOperand, // pulls operand_ from the stack
CycleNoWritePush, // decrements S as though it were a push, but reads from the new stack address instead of writing
CycleReadAndIncrementPC, // reads from the PC, throwing away the result, and increments the PC
CycleIncrementPCAndReadStack, // increments the PC and reads from the stack pointer, throwing away the result
CycleIncrementPCReadPCHLoadPCL, // increments the PC, schedules a read of PC.h from the post-incremented PC, then copies operand_ to PC.l
CycleReadPCHLoadPCL, // schedules a read of PC.h from the post-incremented PC, then copies operand_ to PC.l
CycleReadAddressHLoadAddressL, // increments the PC; copies operand_ to address_.l; reads address_.h from the new PC
CycleReadPCLFromAddress, // reads PC.l from address_
CycleReadPCHFromAddressLowInc, // increments address_.l and reads PC.h from address_
CycleReadPCHFromAddressFixed, // if address_.l is 0, increments address_.h; and reads PC.h from address_
CycleReadPCHFromAddressInc, // increments address_ and reads PC.h from it
CycleLoadAddressAbsolute, // copies operand_ to address_.l, increments the PC, reads address_.h from PC, increments the PC again
OperationLoadAddressZeroPage, // copies operand_ to address_ and increments the PC
CycleLoadAddessZeroX, // copies (operand_+x)&0xff to address_, increments the PC, and reads from operand_, throwing away the result
CycleLoadAddessZeroY, // copies (operand_+y)&0xff to address_, increments the PC, and reads from operand_, throwing away the result
CycleAddXToAddressLow, // calculates address_ + x and stores it to next_address_; copies next_address_.l back to address_.l; 6502: if address_ now does not equal next_address_, schedules a throwaway read from address_; 65C02: schedules a throwaway read from PC-1
CycleAddYToAddressLow, // calculates address_ + y and stores it to next_address_; copies next_address_.l back to address_.l; 6502: if address_ now does not equal next_address_, schedules a throwaway read from address_; 65C02: schedules a throwaway read from PC-1
OperationCorrectAddressHigh, // copies next_address_ to address_
// Calculates address_ + x and stores it to next_address; copies next_address.l back to address_.l.
//
// 6502: schedules a throwaway read from address_.
// 65C02: schedules a throwaway read from PC-1 if a page boundary was crossed;
// otherwise does as per the 6502.
//
CycleAddXToAddressLowRead,
CycleAddYToAddressLowRead, // As per CycleAddXToAddressLowRead, but uses Y rather than X.
OperationIncrementPC, // increments the PC
CycleFetchOperandFromAddress, // fetches operand_ from address_
CycleWriteOperandToAddress, // writes operand_ to address_
CycleIncrementPCFetchAddressLowFromOperand, // increments the PC and loads address_.l from (operand_)
CycleAddXToOperandFetchAddressLow, // adds x [in]to operand_, producing an 8-bit result, and reads address_.l from (operand_)
CycleIncrementOperandFetchAddressHigh, // increments operand_, producing an 8-bit result, and reads address_.h from (operand_)
OperationDecrementOperand, // decrements operand_
OperationIncrementOperand, // increments operand_
CycleFetchAddressLowFromOperand, // reads address_.l from (operand_)
OperationORA, // ORs operand_ into a, setting the negative and zero flags
OperationAND, // ANDs operand_ into a, setting the negative and zero flags
OperationEOR, // EORs operand_ into a, setting the negative and zero flags
OperationINS, // increments operand_, then performs an SBC of operand_ from a
OperationADC, // performs an ADC of operand_ into a_; if this is a 65C02 and decimal mode is set, performs an extra read to operand_ from address_
OperationSBC, // performs an SBC of operand_ from a_; if this is a 65C02 and decimal mode is set, performs an extra read to operand_ from address_
OperationCMP, // CMPs a and operand_, setting negative, zero and carry flags
OperationCPX, // CMPs x and operand_, setting negative, zero and carry flags
OperationCPY, // CMPs y and operand_, setting negative, zero and carry flags
OperationBIT, // sets the zero, negative and overflow flags as per a BIT of operand_ against a
OperationBITNoNV, // sets the zero flag as per a BIT of operand_ against a
OperationLDA, // loads a with operand_, setting the negative and zero flags
OperationLDX, // loads x with operand_, setting the negative and zero flags
OperationLDY, // loads y with operand_, setting the negative and zero flags
OperationLAX, // loads a and x with operand_, setting the negative and zero flags
OperationCopyOperandToA, // sets a_ = operand_, not setting any flags
OperationSTA, // loads operand_ with a
OperationSTX, // loads operand_ with x
OperationSTY, // loads operand_ with y
OperationSTZ, // loads operand_ with 0
OperationSAX, // loads operand_ with a & x
OperationSHA, // loads operand_ with a & x & (address.h+1)
OperationSHX, // loads operand_ with x & (address.h+1)
OperationSHY, // loads operand_ with y & (address.h+1)
OperationSHS, // loads s with a & x, then loads operand_ with s & (address.h+1)
OperationASL, // shifts operand_ left, moving the top bit into carry and setting the negative and zero flags
OperationASO, // performs an ASL of operand and ORs it into a
OperationROL, // performs a ROL of operand_
OperationRLA, // performs a ROL of operand_ and ANDs it into a
OperationLSR, // shifts operand_ right, setting carry, negative and zero flags
OperationLSE, // performs an LSR and EORs the result into a
OperationASR, // ANDs operand_ into a, then performs an LSR
OperationROR, // performs a ROR of operand_, setting carry, negative and zero flags
OperationRRA, // performs a ROR of operand_ but sets only the carry flag
OperationCLC, // resets the carry flag
OperationCLI, // resets I
OperationCLV, // resets the overflow flag
OperationCLD, // resets the decimal flag
OperationSEC, // sets the carry flag
OperationSEI, // sets I
OperationSED, // sets the decimal flag
OperationRMB, // resets the bit in operand_ implied by operatiopn_
OperationSMB, // sets the bit in operand_ implied by operatiopn_
OperationTRB, // sets zero according to operand_ & a, then resets any bits in operand_ that are set in a
OperationTSB, // sets zero according to operand_ & a, then sets any bits in operand_ that are set in a
OperationINC, // increments operand_, setting the negative and zero flags
OperationDEC, // decrements operand_, setting the negative and zero flags
OperationINX, // increments x, setting the negative and zero flags
OperationDEX, // decrements x, setting the negative and zero flags
OperationINY, // increments y, setting the negative and zero flags
OperationDEY, // decrements y, setting the negative and zero flags
OperationINA, // increments a, setting the negative and zero flags
OperationDEA, // decrements a, setting the negative and zero flags
OperationBPL, // schedules the branch program if the negative flag is clear
OperationBMI, // schedules the branch program if the negative flag is set
OperationBVC, // schedules the branch program if the overflow flag is clear
OperationBVS, // schedules the branch program if the overflow flag is set
OperationBCC, // schedules the branch program if the carry flag is clear
OperationBCS, // schedules the branch program if the carry flag is set
OperationBNE, // schedules the branch program if the zero flag is clear
OperationBEQ, // schedules the branch program if the zero flag is set; 65C02: otherwise jumps straight into a fetch-decode-execute without considering whether to take an interrupt
OperationBRA, // schedules the branch program
OperationBBRBBS, // inspecting the operation_, if the appropriate bit of operand_ is set or clear schedules a program to read and act upon the second operand; otherwise schedule a program to read and discard it
OperationTXA, // copies x to a, setting the zero and negative flags
OperationTYA, // copies y to a, setting the zero and negative flags
OperationTXS, // copies x to s
OperationTAY, // copies a to y, setting the zero and negative flags
OperationTAX, // copies a to x, setting the zero and negative flags
OperationTSX, // copies s to x, setting the zero and negative flags
/* The following are amongst the 6502's undocumented (/unintended) operations */
OperationARR, // performs a mixture of ANDing operand_ into a, and shifting the result right
OperationSBX, // performs a mixture of an SBC of x&a and operand_, mutating x
OperationLXA, // loads a and x with (a | 0xee) & operand, setting the negative and zero flags
OperationANE, // loads a_ with (a | 0xee) & operand & x, setting the negative and zero flags
OperationANC, // ANDs operand_ into a, setting the negative and zero flags, and loading carry as if the result were shifted right
OperationLAS, // loads a, x and s with s & operand, setting the negative and zero flags
CycleFetchFromHalfUpdatedPC, // performs a throwaway read from (PC + (signed)operand).l combined with PC.h
CycleAddSignedOperandToPC, // sets next_address to PC + (signed)operand. If the high byte of next_address differs from the PC, schedules a throwaway read from the half-updated PC. 65C02 specific: if the top two bytes are the same, proceeds directly to fetch-decode-execute, ignoring any pending interrupts.
OperationAddSignedOperandToPC16, // adds (signed)operand into the PC, leaving old PC in next_address_ and skipping a program step if there was no carry from low to high byte
CycleFetchFromNextAddress, // performs a throwaway fetch from next_address_
OperationSetFlagsFromOperand, // sets all flags based on operand_
OperationSetOperandFromFlagsWithBRKSet, // sets operand_ to the value of all flags, with the break flag set
OperationSetOperandFromFlags, // sets operand_ to the value of all flags
OperationSetFlagsFromA, // sets the zero and negative flags based on the value of a
OperationSetFlagsFromX, // sets the zero and negative flags based on the value of x
OperationSetFlagsFromY, // sets the zero and negative flags based on the value of y
OperationScheduleWait, // puts the processor into WAI mode (i.e. it'll do nothing until an interrupt is received)
OperationScheduleStop, // puts the processor into STP mode (i.e. it'll do nothing until a reset is received)
CycleFetchFFFE, // perform a throwaway read from $FFFE
CycleFetchFFFF, // perform a throwaway read from $FFFF
OperationSetJAMmed, // decrements the micro-operation program counter back to the operation before this one, marking the CPU as jammed
};
using InstructionList = MicroOp[12];
/// Defines the locations in operations_ of various named microprograms; the first 256 entries
/// in operations_ are mapped directly from instruction codes and therefore not named.
enum class OperationsSlot {
/// Fetches the next operation, and its operand, then schedules the corresponding set of operations_.
/// [Caveat: the 65C02 adds single-cycle NOPs; this microprogram won't fetch an operand for those].
FetchDecodeExecute = 256,
/// Performs the 6502's reset sequence.
Reset,
/// Performs the 6502's IRQ sequence.
IRQ,
/// Performs the 6502's NMI sequence.
NMI,
/// Performs a branch, e.g. the entry for BCC will evaluate whether carry is clear and, if so, will jump
/// to this instruction list.
DoBRA,
/// On a 65c02,
DoBBRBBS,
DoNotBBRBBS,
Max
};
InstructionList operations_[size_t(OperationsSlot::Max)];
const MicroOp *scheduled_program_counter_ = nullptr;
/*
Storage for the 6502 registers; F is stored as individual flags.
*/
RegisterPair16 pc_, last_operation_pc_;
uint8_t a_, x_, y_, s_ = 0;
MOS6502Esque::LazyFlags flags_;
/*
Temporary state for the micro programs.
*/
uint8_t operation_, operand_;
RegisterPair16 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_ = BusOperation::None;
uint16_t bus_address_;
uint8_t *bus_value_;
static inline uint8_t bus_throwaway_;
/*!
Gets the flags register.
@see set_flags
@returns The current value of the flags register.
*/
inline uint8_t get_flags() const;
/*!
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_ = false;
Cycles cycles_left_to_run_;
enum InterruptRequestFlags: uint8_t {
Reset = 0x80,
IRQ = MOS6502Esque::Flag::Interrupt,
NMI = 0x20,
PowerOn = 0x10,
};
uint8_t interrupt_requests_ = InterruptRequestFlags::PowerOn;
bool ready_is_active_ = false;
bool ready_line_is_enabled_ = false;
bool stop_is_active_ = false;
bool wait_is_active_ = false;
uint8_t irq_line_ = 0, irq_request_history_ = 0;
bool nmi_line_is_enabled_ = false, set_overflow_line_is_enabled_ = false;
// Allow state objects to capture and apply state.
friend struct State;
};