1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Merge pull request #782 from TomHarte/6502Tidy

Makes `State`, and therefore the 'Reflection' dependency, an optional adjunct to the 6502.
This commit is contained in:
Thomas Harte 2020-04-02 20:49:18 -04:00 committed by GitHub
commit ab81d1093d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 249 additions and 201 deletions

View File

@ -783,6 +783,8 @@
4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */; };
4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; };
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; };
4BC57CD92436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; };
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
@ -1655,6 +1657,8 @@
4BC57CD2243427C700FBC404 /* AudioProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AudioProducer.hpp; sourceTree = "<group>"; };
4BC57CD32434282000FBC404 /* TimedMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimedMachine.hpp; sourceTree = "<group>"; };
4BC57CD424342E0600FBC404 /* MachineTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineTypes.hpp; sourceTree = "<group>"; };
4BC57CD72436A61300FBC404 /* State.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4BC57CD82436A62900FBC404 /* State.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000MoveTests.mm; sourceTree = "<group>"; };
4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; };
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
@ -1939,6 +1943,7 @@
4B6A4C8E1F58F09E00E3F787 /* 6502.hpp */,
4B6A4C901F58F09E00E3F787 /* AllRAM */,
4B6A4C931F58F09E00E3F787 /* Implementation */,
4BC57CD62436A61300FBC404 /* State */,
);
path = 6502;
sourceTree = "<group>";
@ -3504,6 +3509,15 @@
path = AtariST;
sourceTree = "<group>";
};
4BC57CD62436A61300FBC404 /* State */ = {
isa = PBXGroup;
children = (
4BC57CD72436A61300FBC404 /* State.hpp */,
4BC57CD82436A62900FBC404 /* State.cpp */,
);
path = State;
sourceTree = "<group>";
};
4BC9DF4A1D04691600F44158 /* Components */ = {
isa = PBXGroup;
children = (
@ -4289,6 +4303,7 @@
4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */,
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */,
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
@ -4552,6 +4567,7 @@
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
4BC57CD92436A62900FBC404 /* State.cpp in Sources */,
4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */,
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */,
4B0ACC3223775819008902D0 /* Atari2600.cpp in Sources */,

View File

@ -82,6 +82,7 @@ SOURCES += glob.glob('../../Outputs/OpenGL/*.cpp')
SOURCES += glob.glob('../../Outputs/OpenGL/Primitives/*.cpp')
SOURCES += glob.glob('../../Processors/6502/Implementation/*.cpp')
SOURCES += glob.glob('../../Processors/6502/State/*.cpp')
SOURCES += glob.glob('../../Processors/68000/Implementation/*.cpp')
SOURCES += glob.glob('../../Processors/Z80/Implementation/*.cpp')

View File

@ -15,8 +15,6 @@
#include "../RegisterSizes.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Reflection/Enum.hpp"
#include "../../Reflection/Struct.hpp"
namespace CPU {
namespace MOS6502 {
@ -129,81 +127,6 @@ 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<ExecutionState> {
ReflectableEnum(Phase,
Instruction, Stopped, Waiting, Jammed, Ready
);
/// Current executon phase, e.g. standard instruction flow or responding to an IRQ.
Phase phase;
int micro_program;
int micro_program_offset;
// 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.
@ -212,7 +135,7 @@ class ProcessorBase: public ProcessorStorage {
@param r The register to set.
@returns The value of the register. 8-bit registers will be returned as unsigned.
*/
uint16_t get_value_of_register(Register r);
uint16_t get_value_of_register(Register r) const;
/*!
Sets the value of a register.
@ -236,7 +159,7 @@ class ProcessorBase: public ProcessorStorage {
@returns @c true if the line is logically active; @c false otherwise.
*/
inline bool get_is_resetting();
inline bool get_is_resetting() const;
/*!
This emulation automatically sets itself up in power-on state at creation, which has the effect of triggering a
@ -271,43 +194,9 @@ class ProcessorBase: public ProcessorStorage {
@returns @c true if the 6502 is jammed; @c false otherwise.
*/
bool is_jammed();
bool is_jammed() const;
};
// 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(micro_program);
DeclareField(micro_program_offset);
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.

View File

@ -14,7 +14,7 @@ using namespace CPU::MOS6502;
const uint8_t CPU::MOS6502::JamOpcode = 0xf2;
uint16_t ProcessorBase::get_value_of_register(Register r) {
uint16_t ProcessorBase::get_value_of_register(Register r) const {
switch (r) {
case Register::ProgramCounter: return pc_.full;
case Register::LastOperationAddress: return last_operation_pc_.full;
@ -39,82 +39,6 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
}
}
bool ProcessorBase::is_jammed() {
bool ProcessorBase::is_jammed() const {
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.address = address_.full;
state.execution_state.next_address = next_address_.full;
if(ready_is_active_) {
state.execution_state.phase = State::ExecutionState::Phase::Ready;
} else 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 {
state.execution_state.phase = State::ExecutionState::Phase::Instruction;
}
const auto micro_offset = size_t(scheduled_program_counter_ - &operations_[0][0]);
const auto list_length = sizeof(InstructionList) / sizeof(MicroOp);
state.execution_state.micro_program = int(micro_offset / list_length);
state.execution_state.micro_program_offset = int(micro_offset % list_length);
assert(&operations_[state.execution_state.micro_program][state.execution_state.micro_program_offset] == scheduled_program_counter_);
return state;
}
void ProcessorBase::set_state(const State &state) {
// Grab registers.
pc_.full = state.registers.program_counter;
s_ = state.registers.stack_pointer;
set_flags(state.registers.flags);
a_ = state.registers.a;
x_ = state.registers.x;
y_ = state.registers.y;
// Grab other inputs.
ready_line_is_enabled_ = state.inputs.ready;
set_irq_line(state.inputs.irq);
set_nmi_line(state.inputs.nmi);
set_reset_line(state.inputs.reset);
// Set execution state.
ready_is_active_ = is_jammed_ = wait_is_active_ = stop_is_active_ = false;
switch(state.execution_state.phase) {
case State::ExecutionState::Phase::Ready: ready_is_active_ = true; break;
case State::ExecutionState::Phase::Jammed: is_jammed_ = true; break;
case State::ExecutionState::Phase::Stopped: stop_is_active_ = true; break;
case State::ExecutionState::Phase::Waiting: wait_is_active_ = true; break;
case State::ExecutionState::Phase::Instruction: break;
}
operation_ = state.execution_state.operation;
operand_ = state.execution_state.operand;
address_.full = state.execution_state.address;
next_address_.full = state.execution_state.next_address;
scheduled_program_counter_ = &operations_[state.execution_state.micro_program][state.execution_state.micro_program_offset];
}

View File

@ -38,14 +38,12 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
} else {\
scheduled_program_counter_ = operations_[size_t(OperationsSlot::FetchDecodeExecute)];\
}\
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;
@ -57,13 +55,11 @@ 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();
@ -74,7 +70,6 @@ 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;
@ -685,7 +680,7 @@ void ProcessorBase::set_reset_line(bool active) {
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::Reset) | (active ? InterruptRequestFlags::Reset : 0);
}
bool ProcessorBase::get_is_resetting() {
bool ProcessorBase::get_is_resetting() const {
return interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn);
}
@ -711,7 +706,7 @@ void ProcessorBase::set_nmi_line(bool active) {
nmi_line_is_enabled_ = active;
}
uint8_t ProcessorStorage::get_flags() {
uint8_t ProcessorStorage::get_flags() const {
return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_;
}

View File

@ -225,7 +225,6 @@ class ProcessorStorage {
InstructionList operations_[size_t(OperationsSlot::Max)];
const MicroOp *scheduled_program_counter_ = nullptr;
int cycles_in_phase_ = 0;
/*
Storage for the 6502 registers; F is stored as individual flags.
@ -255,7 +254,7 @@ class ProcessorStorage {
@returns The current value of the flags register.
*/
inline uint8_t get_flags();
inline uint8_t get_flags() const;
/*!
Sets the flags register.
@ -285,6 +284,9 @@ class ProcessorStorage {
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 install state.
friend class State;
};
#endif /* _502Storage_h */

View File

@ -0,0 +1,125 @@
//
// State.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/04/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "State.hpp"
using namespace CPU::MOS6502;
State::State(const ProcessorBase &src): State() {
// Fill in registers.
registers.program_counter = src.pc_.full;
registers.stack_pointer = src.s_;
registers.flags = src.get_flags();
registers.a = src.a_;
registers.x = src.x_;
registers.y = src.y_;
// Fill in other inputs.
inputs.ready = src.ready_line_is_enabled_;
inputs.irq = src.irq_line_;
inputs.nmi = src.nmi_line_is_enabled_;
inputs.reset = src.interrupt_requests_ & (ProcessorStorage::InterruptRequestFlags::Reset | ProcessorStorage::InterruptRequestFlags::PowerOn);
// Fill in execution state.
execution_state.operation = src.operation_;
execution_state.operand = src.operand_;
execution_state.address = src.address_.full;
execution_state.next_address = src.next_address_.full;
if(src.ready_is_active_) {
execution_state.phase = State::ExecutionState::Phase::Ready;
} else if(src.is_jammed_) {
execution_state.phase = State::ExecutionState::Phase::Jammed;
} else if(src.wait_is_active_) {
execution_state.phase = State::ExecutionState::Phase::Waiting;
} else if(src.stop_is_active_) {
execution_state.phase = State::ExecutionState::Phase::Stopped;
} else {
execution_state.phase = State::ExecutionState::Phase::Instruction;
}
const auto micro_offset = size_t(src.scheduled_program_counter_ - &src.operations_[0][0]);
const auto list_length = sizeof(ProcessorStorage::InstructionList) / sizeof(ProcessorStorage::MicroOp);
execution_state.micro_program = int(micro_offset / list_length);
execution_state.micro_program_offset = int(micro_offset % list_length);
assert(&src.operations_[execution_state.micro_program][execution_state.micro_program_offset] == src.scheduled_program_counter_);
}
void State::apply(ProcessorBase &target) {
// Grab registers.
target.pc_.full = registers.program_counter;
target.s_ = registers.stack_pointer;
target.set_flags(registers.flags);
target.a_ = registers.a;
target.x_ = registers.x;
target.y_ = registers.y;
// Grab other inputs.
target.ready_line_is_enabled_ = inputs.ready;
target.set_irq_line(inputs.irq);
target.set_nmi_line(inputs.nmi);
target.set_reset_line(inputs.reset);
// Set execution state.
target.ready_is_active_ = target.is_jammed_ = target.wait_is_active_ = target.stop_is_active_ = false;
switch(execution_state.phase) {
case State::ExecutionState::Phase::Ready: target.ready_is_active_ = true; break;
case State::ExecutionState::Phase::Jammed: target.is_jammed_ = true; break;
case State::ExecutionState::Phase::Stopped: target.stop_is_active_ = true; break;
case State::ExecutionState::Phase::Waiting: target.wait_is_active_ = true; break;
case State::ExecutionState::Phase::Instruction: break;
}
target.operation_ = execution_state.operation;
target.operand_ = execution_state.operand;
target.address_.full = execution_state.address;
target.next_address_.full = execution_state.next_address;
target.scheduled_program_counter_ = &target.operations_[execution_state.micro_program][execution_state.micro_program_offset];
}
// Boilerplate follows here, to establish 'reflection'.
State::State() {
if(needs_declare()) {
DeclareField(registers);
DeclareField(execution_state);
DeclareField(inputs);
}
}
State::Registers::Registers() {
if(needs_declare()) {
DeclareField(program_counter);
DeclareField(stack_pointer);
DeclareField(flags);
DeclareField(a);
DeclareField(x);
DeclareField(y);
}
}
State::ExecutionState::ExecutionState() {
if(needs_declare()) {
AnnounceEnum(Phase);
DeclareField(phase);
DeclareField(micro_program);
DeclareField(micro_program_offset);
DeclareField(operation);
DeclareField(operand);
DeclareField(address);
DeclareField(next_address);
}
}
State::Inputs::Inputs() {
if(needs_declare()) {
DeclareField(ready);
DeclareField(irq);
DeclareField(nmi);
DeclareField(reset);
}
}

View File

@ -0,0 +1,96 @@
//
// State.h
// Clock Signal
//
// Created by Thomas Harte on 02/04/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef State_h
#define State_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../6502.hpp"
namespace CPU {
namespace MOS6502 {
/*!
Provides a means for capturing or restoring complete 6502 state.
This is an optional adjunct to the 6502 class. If you want to take the rest of the 6502
implementation but don't want any of the overhead of my sort-of half-reflection as
encapsulated in Reflection/[Enum/Struct].hpp just don't use this class.
*/
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<ExecutionState> {
ReflectableEnum(Phase,
Instruction, Stopped, Waiting, Jammed, Ready
);
/// Current executon phase, e.g. standard instruction flow or responding to an IRQ.
Phase phase;
int micro_program;
int micro_program_offset;
// 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;
/// Default constructor; makes no guarantees as to field values beyond those given above.
State();
/// Instantiates a new State based on the processor @c src.
State(const ProcessorBase &src);
/// Applies this state to @c target.
void apply(ProcessorBase &target);
};
}
}
#endif /* State_h */