mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
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**.
This commit is contained in:
parent
272383cac7
commit
4fbe983527
@ -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<State> {
|
||||
/*!
|
||||
Provides the current state of the well-known, published internal registers.
|
||||
*/
|
||||
struct Registers: public Reflection::StructImpl<Registers> {
|
||||
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<Inputs> {
|
||||
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<Registers> {
|
||||
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.
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -33,28 +33,29 @@ template <Personality personality, typename T, bool uses_ready_line> 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 <Personality personality, typename T, bool uses_ready_line> 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 <Personality personality, typename T, bool uses_ready_line> 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() {
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user