From 4fbe9835270d820feef88bd6f53b9d2b640a0f32 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Mar 2020 00:33:27 -0400 Subject: [PATCH] Provisionally adds `State` and `get_state` to the 6502. `set_state` may be a little more complicated, requiring a way to advance in single-cycle steps **without applying bus accesses**. --- Processors/6502/6502.hpp | 111 ++++++++++++++++++ Processors/6502/Implementation/6502Base.cpp | 56 +++++++++ .../Implementation/6502Implementation.hpp | 44 +++---- .../6502/Implementation/6502Storage.hpp | 1 + 4 files changed, 192 insertions(+), 20 deletions(-) diff --git a/Processors/6502/6502.hpp b/Processors/6502/6502.hpp index 2dac65439..6e511b9f8 100644 --- a/Processors/6502/6502.hpp +++ b/Processors/6502/6502.hpp @@ -15,6 +15,8 @@ #include "../RegisterSizes.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../Reflection/Enum.hpp" +#include "../../Reflection/Struct.hpp" namespace CPU { namespace MOS6502 { @@ -128,6 +130,82 @@ class ProcessorBase: public ProcessorStorage { public: ProcessorBase(Personality personality) : ProcessorStorage(personality) {} + struct State: public Reflection::StructImpl { + /*! + Provides the current state of the well-known, published internal registers. + */ + struct Registers: public Reflection::StructImpl { + uint16_t program_counter; + uint8_t stack_pointer; + uint8_t flags; + uint8_t a, x, y; + + Registers(); + } registers; + + /*! + Provides the current state of the processor's various input lines that aren't + related to an access cycle. + */ + struct Inputs: public Reflection::StructImpl { + bool ready; + bool irq; + bool nmi; + bool reset; + + Inputs(); + } inputs; + + /*! + Contains internal state used by this particular implementation of a 6502. Most of it + does not necessarily correlate with anything in a real 6502, and some of it very + obviously doesn't. + */ + struct ExecutionState: public Reflection::StructImpl { + ReflectableEnum(Phase, + Reset, IRQ, NMI, Instruction, Stopped, Waiting, Jammed + ); + + /// Current executon phase, e.g. standard instruction flow or responding to an IRQ. + Phase phase; + /// A count of the number of cycles since this instance of this phase last began. + /// E.g. if the phase is currently execution an instruction, this might be 0 to 7. + int cycles_into_phase; + + // The following are very internal things. At the minute I + // consider these 'reliable' for inter-launch state + // preservation only on the grounds that this implementation + // of a 6502 is now empirically stable. + // + // If cycles_into_phase is 0, the values below need not be + // retained, they're entirely ephemeral. If providing a state + // for persistance, machines that can should advance until + // cycles_into_phase is 0. + uint8_t operation, operand; + uint16_t address, next_address; + + ExecutionState(); + } execution_state; + + State() { + if(needs_declare()) { + DeclareField(registers); + DeclareField(execution_state); + DeclareField(inputs); + } + } + }; + + /*! + Gets current processor state. + */ + State get_state(); + + /*! + Sets current processor state. + */ + void set_state(const State &); + /*! Gets the value of a register. @@ -198,6 +276,39 @@ class ProcessorBase: public ProcessorStorage { bool is_jammed(); }; +// Boilerplate follows here, to establish 'reflection' for the state struct defined above. +inline ProcessorBase::State::Registers::Registers() { + if(needs_declare()) { + DeclareField(program_counter); + DeclareField(stack_pointer); + DeclareField(flags); + DeclareField(a); + DeclareField(x); + DeclareField(y); + } +} + +inline ProcessorBase::State::ExecutionState::ExecutionState() { + if(needs_declare()) { + AnnounceEnum(Phase); + DeclareField(phase); + DeclareField(cycles_into_phase); + DeclareField(operation); + DeclareField(operand); + DeclareField(address); + DeclareField(next_address); + } +} + +inline ProcessorBase::State::Inputs::Inputs() { + if(needs_declare()) { + DeclareField(ready); + DeclareField(irq); + DeclareField(nmi); + DeclareField(reset); + } +} + /*! @abstact Template providing emulation of a 6502 processor. diff --git a/Processors/6502/Implementation/6502Base.cpp b/Processors/6502/Implementation/6502Base.cpp index 7de1bc331..8b4b16afc 100644 --- a/Processors/6502/Implementation/6502Base.cpp +++ b/Processors/6502/Implementation/6502Base.cpp @@ -42,3 +42,59 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) { bool ProcessorBase::is_jammed() { return is_jammed_; } + +ProcessorBase::State ProcessorBase::get_state() { + ProcessorBase::State state; + + // Fill in registers. + state.registers.program_counter = pc_.full; + state.registers.stack_pointer = s_; + state.registers.flags = get_flags(); + state.registers.a = a_; + state.registers.x = x_; + state.registers.y = y_; + + // Fill in other inputs. + state.inputs.ready = ready_line_is_enabled_; + state.inputs.irq = irq_line_; + state.inputs.nmi = nmi_line_is_enabled_; + state.inputs.reset = interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn); + + // Fill in execution state. + state.execution_state.operation = operation_; + state.execution_state.operand = operand_; + state.execution_state.cycles_into_phase = cycles_in_phase_; + state.execution_state.address = address_.full; + state.execution_state.next_address = next_address_.full; + if(is_jammed_) { + state.execution_state.phase = State::ExecutionState::Phase::Jammed; + } else if(wait_is_active_) { + state.execution_state.phase = State::ExecutionState::Phase::Waiting; + } else if(stop_is_active_) { + state.execution_state.phase = State::ExecutionState::Phase::Stopped; + } else { + // Test for the micro-op pointer being inside the reset, IRQ or NMI programs. + // If not then the only thing left is instruction. + auto is_in_program = [this](const MicroOp *const op) -> bool { + if(scheduled_program_counter_ < op) return false; + + const MicroOp *final_op = op; + while(*final_op != OperationMoveToNextProgram) { + ++final_op; + } + return scheduled_program_counter_ < final_op; + }; + + if(is_in_program(get_reset_program())) { + state.execution_state.phase = State::ExecutionState::Phase::Reset; + } else if(is_in_program(get_irq_program())) { + state.execution_state.phase = State::ExecutionState::Phase::IRQ; + } else if(is_in_program(get_nmi_program())) { + state.execution_state.phase = State::ExecutionState::Phase::NMI; + } else { + state.execution_state.phase = State::ExecutionState::Phase::Instruction; + } + } + + return state; +} diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index 9e8c04984..c23eb6f9a 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -33,28 +33,29 @@ template void Proces uint16_t busAddress = bus_address_; uint8_t *busValue = bus_value_; -#define checkSchedule(op) \ +#define checkSchedule() \ 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;\ + 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;\ + }\ + cycles_in_phase_ = 0; \ } #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); \ + ++cycles_in_phase_; \ nextBusOperation = BusOperation::None; \ if(number_of_cycles <= Cycles(0)) break; @@ -66,11 +67,13 @@ template void Proces // Deal with a potential RDY state, if this 6502 has anything connected to ready. while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) { number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); + ++cycles_in_phase_; } // Deal with a potential STP state, if this 6502 implements STP. while(has_stpwai(personality) && stop_is_active_ && number_of_cycles > Cycles(0)) { number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); + ++cycles_in_phase_; if(interrupt_requests_ & InterruptRequestFlags::Reset) { stop_is_active_ = false; checkSchedule(); @@ -81,6 +84,7 @@ template void Proces // Deal with a potential WAI state, if this 6502 implements WAI. while(has_stpwai(personality) && wait_is_active_ && number_of_cycles > Cycles(0)) { number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); + ++cycles_in_phase_; interrupt_requests_ |= (irq_line_ & inverse_interrupt_flag_); if(interrupt_requests_ & InterruptRequestFlags::NMI || irq_line_) { wait_is_active_ = false; @@ -731,7 +735,7 @@ void ProcessorBase::set_nmi_line(bool active) { } inline const ProcessorStorage::MicroOp *ProcessorStorage::get_reset_program() { - static const MicroOp reset[] = { + static constexpr MicroOp reset[] = { CycleFetchOperand, CycleFetchOperand, CycleNoWritePush, @@ -747,7 +751,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_reset_program() { } inline const ProcessorStorage::MicroOp *ProcessorStorage::get_irq_program() { - static const MicroOp reset[] = { + static constexpr MicroOp irq[] = { CycleFetchOperand, CycleFetchOperand, CyclePushPCH, @@ -760,11 +764,11 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_irq_program() { CycleReadVectorHigh, OperationMoveToNextProgram }; - return reset; + return irq; } inline const ProcessorStorage::MicroOp *ProcessorStorage::get_nmi_program() { - static const MicroOp reset[] = { + static constexpr MicroOp nmi[] = { CycleFetchOperand, CycleFetchOperand, CyclePushPCH, @@ -777,7 +781,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_nmi_program() { CycleReadVectorHigh, OperationMoveToNextProgram }; - return reset; + return nmi; } uint8_t ProcessorStorage::get_flags() { diff --git a/Processors/6502/Implementation/6502Storage.hpp b/Processors/6502/Implementation/6502Storage.hpp index bc06c0e2c..f41965aa7 100644 --- a/Processors/6502/Implementation/6502Storage.hpp +++ b/Processors/6502/Implementation/6502Storage.hpp @@ -201,6 +201,7 @@ class ProcessorStorage { InstructionList operations_[256]; const MicroOp *scheduled_program_counter_ = nullptr; + int cycles_in_phase_ = 0; /* Storage for the 6502 registers; F is stored as individual flags.