1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Separates 6502 State object to make it optional.

Also makes a few minor const improvements while I'm poking around.
This commit is contained in:
Thomas Harte 2020-04-02 19:11:27 -04:00
parent 7ed8e33622
commit dfc1c7d358
7 changed files with 244 additions and 195 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

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

@ -680,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);
}
@ -706,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

@ -254,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.
@ -284,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,83 @@
//
// 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];
}

View File

@ -0,0 +1,134 @@
//
// 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> {
/// Instantiates a new State based on the processor @c src.
State(const ProcessorBase &src);
/// Applies this state to @c target.
void apply(ProcessorBase &target);
/*!
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);
}
}
};
// Boilerplate follows here, to establish 'reflection' for the state struct defined above.
inline State::Registers::Registers() {
if(needs_declare()) {
DeclareField(program_counter);
DeclareField(stack_pointer);
DeclareField(flags);
DeclareField(a);
DeclareField(x);
DeclareField(y);
}
}
inline 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 State::Inputs::Inputs() {
if(needs_declare()) {
DeclareField(ready);
DeclareField(irq);
DeclareField(nmi);
DeclareField(reset);
}
}
}
}
#endif /* State_h */