1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-29 00:29:34 +00:00

Merge pull request #793 from TomHarte/Z80State

Adds reflective state for the Z80.
This commit is contained in:
Thomas Harte 2020-05-14 00:18:58 -04:00 committed by GitHub
commit 4f30118b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 367 additions and 17 deletions

View File

@ -141,6 +141,8 @@
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */; };
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */; };
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */; };
4B1B58F6246CC4E8009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58F4246CC4E8009C171E /* State.cpp */; };
4B1B58F7246CC4E8009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58F4246CC4E8009C171E /* State.cpp */; };
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */; };
4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */; };
4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */; };
@ -984,6 +986,8 @@
4B1667FB1FFF215F00A16032 /* KonamiWithSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = KonamiWithSCC.hpp; path = MSX/Cartridges/KonamiWithSCC.hpp; sourceTree = "<group>"; };
4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StringSerialiser.cpp; sourceTree = "<group>"; };
4B17B58A20A8A9D9007CCA8F /* StringSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StringSerialiser.hpp; sourceTree = "<group>"; };
4B1B58F4246CC4E8009C171E /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
4B1B58F5246CC4E8009C171E /* State.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiKeyboardMachine.cpp; sourceTree = "<group>"; };
4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiKeyboardMachine.hpp; sourceTree = "<group>"; };
4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiConfigurable.cpp; sourceTree = "<group>"; };
@ -2008,6 +2012,16 @@
name = Cartridges;
sourceTree = "<group>";
};
4B1B58F3246CC4E8009C171E /* State */ = {
isa = PBXGroup;
children = (
4B1B58F4246CC4E8009C171E /* State.cpp */,
4B1B58F5246CC4E8009C171E /* State.hpp */,
);
name = State;
path = Z80/State;
sourceTree = "<group>";
};
4B1E85791D174DEC001EF87D /* 6532 */ = {
isa = PBXGroup;
children = (
@ -2630,6 +2644,7 @@
4B77069C1EC904570053B588 /* Z80.hpp */,
4B322DFC1F5A2981004EB04C /* AllRAM */,
4B322DFF1F5A2981004EB04C /* Implementation */,
4B1B58F3246CC4E8009C171E /* State */,
);
name = Z80;
sourceTree = "<group>";
@ -4441,6 +4456,7 @@
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */,
4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */,
4B1B58F7246CC4E8009C171E /* State.cpp in Sources */,
4B0ACC03237756F6008902D0 /* Line.cpp in Sources */,
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */,
@ -4555,6 +4571,7 @@
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */,
4B894538201967B4007DE474 /* Tape.cpp in Sources */,
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */,
4B1B58F6246CC4E8009C171E /* State.cpp in Sources */,
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */,
4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */,
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */,

View File

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:Clock Signal.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

View File

@ -86,6 +86,7 @@ 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')
SOURCES += glob.glob('../../Processors/Z80/State/*.cpp')
SOURCES += glob.glob('../../Reflection/*.cpp')

View File

@ -285,7 +285,7 @@ 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.
// Allow state objects to capture and apply state.
friend class State;
};

View File

@ -11,7 +11,7 @@
using namespace CPU::MOS6502;
State::State(const ProcessorBase &src): State() {
// Fill in registers.
// Registers.
registers.program_counter = src.pc_.full;
registers.stack_pointer = src.s_;
registers.flags = src.get_flags();
@ -19,13 +19,13 @@ State::State(const ProcessorBase &src): State() {
registers.x = src.x_;
registers.y = src.y_;
// Fill in other inputs.
// 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.
execution_state.operation = src.operation_;
execution_state.operand = src.operand_;
execution_state.address = src.address_.full;
@ -51,7 +51,7 @@ State::State(const ProcessorBase &src): State() {
}
void State::apply(ProcessorBase &target) {
// Grab registers.
// Registers.
target.pc_.full = registers.program_counter;
target.s_ = registers.stack_pointer;
target.set_flags(registers.flags);
@ -59,13 +59,13 @@ void State::apply(ProcessorBase &target) {
target.x_ = registers.x;
target.y_ = registers.y;
// Grab other inputs.
// 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.
// 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;

View File

@ -1,13 +1,13 @@
//
// State.h
// State.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/04/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef State_h
#define State_h
#ifndef State_hpp
#define State_hpp
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
@ -93,4 +93,4 @@ struct State: public Reflection::StructImpl<State> {
}
}
#endif /* State_h */
#endif /* State_hpp */

View File

@ -127,8 +127,6 @@ class ProcessorStorage {
InstructionPage() : r_step(1), is_indexed(false) {}
};
typedef MicroOp InstructionTable[256][30];
ProcessorStorage();
void install_default_instruction_set();
@ -195,8 +193,8 @@ class ProcessorStorage {
@returns The current value of the flags register.
*/
uint8_t get_flags() {
uint8_t result =
uint8_t get_flags() const {
return
(sign_result_ & Flag::Sign) |
(zero_result_ ? 0 : Flag::Zero) |
(bit53_result_ & (Flag::Bit5 | Flag::Bit3)) |
@ -204,7 +202,6 @@ class ProcessorStorage {
(parity_overflow_result_ & Flag::Parity) |
subtract_flag_ |
(carry_result_ & Flag::Carry);
return result;
}
/*!
@ -224,6 +221,7 @@ class ProcessorStorage {
carry_result_ = flags;
}
typedef MicroOp InstructionTable[256][30];
virtual void assemble_page(InstructionPage &target, InstructionTable &table, bool add_offsets) = 0;
virtual void copy_program(const MicroOp *source, std::vector<MicroOp> &destination) = 0;
@ -232,4 +230,6 @@ class ProcessorStorage {
void assemble_cb_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets);
void assemble_base_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets, InstructionPage &cb_page);
// Allos state objects to capture and apply state.
friend class State;
};

View File

@ -0,0 +1,222 @@
//
// State.cpp
// Clock Signal
//
// Created by Thomas Harte on 13/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "State.hpp"
#include <cassert>
using namespace CPU::Z80;
State::State(const ProcessorBase &src): State() {
// Registers.
registers.a = src.a_;
registers.flags = src.get_flags();
registers.bc = src.bc_.full;
registers.de = src.de_.full;
registers.hl = src.hl_.full;
registers.bcDash = src.bcDash_.full;
registers.deDash = src.deDash_.full;
registers.hlDash = src.hlDash_.full;
registers.ix = src.ix_.full;
registers.iy = src.iy_.full;
registers.ir = src.ir_.full;
registers.program_counter = src.pc_.full;
registers.stack_pointer = src.sp_.full;
registers.memptr = src.memptr_.full;
registers.interrupt_mode = src.interrupt_mode_;
registers.iff1 = src.iff1_;
registers.iff2 = src.iff2_;
// Inputs.
inputs.irq = src.irq_line_;
inputs.nmi = src.nmi_line_;
inputs.wait = src.wait_line_;
inputs.bus_request = src.bus_request_line_;
// Execution State.
execution_state.is_halted = src.halt_mask_ == 0x00;
execution_state.requests = src.request_status_;
execution_state.last_requests = src.last_request_status_;
execution_state.temp8 = src.temp8_;
execution_state.temp16 = src.temp16_.full;
execution_state.operation = src.operation_;
execution_state.flag_adjustment_history = src.flag_adjustment_history_;
execution_state.pc_increment = src.pc_increment_;
execution_state.refresh_address = src.refresh_addr_.full;
execution_state.half_cycles_into_step = src.number_of_cycles_.as<int>();
// Search for the current holder of the scheduled_program_counter_.
#define ContainedBy(x) (src.scheduled_program_counter_ >= &src.x[0]) && (src.scheduled_program_counter_ < &src.x[src.x.size()])
#define Populate(x, y) \
execution_state.phase = ExecutionState::Phase::x; \
execution_state.steps_into_phase = int(src.scheduled_program_counter_ - &src.y[0]);
if(ContainedBy(conditional_call_untaken_program_)) {
Populate(UntakenConditionalCall, conditional_call_untaken_program_);
} else if(ContainedBy(reset_program_)) {
Populate(Reset, reset_program_);
} else if(ContainedBy(irq_program_[0])) {
Populate(IRQMode0, irq_program_[0]);
} else if(ContainedBy(irq_program_[1])) {
Populate(IRQMode1, irq_program_[1]);
} else if(ContainedBy(irq_program_[2])) {
Populate(IRQMode2, irq_program_[2]);
} else if(ContainedBy(nmi_program_)) {
Populate(NMI, nmi_program_);
} else {
if(src.current_instruction_page_ == &src.base_page_) {
execution_state.instruction_page = 0;
} else if(src.current_instruction_page_ == &src.ed_page_) {
execution_state.instruction_page = 0xed;
} else if(src.current_instruction_page_ == &src.fd_page_) {
execution_state.instruction_page = 0xfd;
} else if(src.current_instruction_page_ == &src.dd_page_) {
execution_state.instruction_page = 0xdd;
} else if(src.current_instruction_page_ == &src.cb_page_) {
execution_state.instruction_page = 0xcb;
} else if(src.current_instruction_page_ == &src.fdcb_page_) {
execution_state.instruction_page = 0xfdcb;
} else if(src.current_instruction_page_ == &src.ddcb_page_) {
execution_state.instruction_page = 0xddcb;
}
if(ContainedBy(current_instruction_page_->fetch_decode_execute)) {
Populate(FetchDecode, current_instruction_page_->fetch_decode_execute);
} else {
// There's no need to determine which opcode because that knowledge is already
// contained in the dedicated opcode field.
Populate(Operation, current_instruction_page_->instructions[src.operation_ & src.halt_mask_]);
}
}
assert(execution_state.steps_into_phase >= 0);
#undef Populate
#undef ContainedBy
}
void State::apply(ProcessorBase &target) {
// Registers.
target.a_ = registers.a;
target.set_flags(registers.flags);
target.bc_.full = registers.bc;
target.de_.full = registers.de;
target.hl_.full = registers.hl;
target.bcDash_.full = registers.bcDash;
target.deDash_.full = registers.deDash;
target.hlDash_.full = registers.hlDash;
target.ix_.full = registers.ix;
target.iy_.full = registers.iy;
target.ir_.full = registers.ir;
target.pc_.full = registers.program_counter;
target.sp_.full = registers.stack_pointer;
target.memptr_.full = registers.memptr;
target.interrupt_mode_ = registers.interrupt_mode;
target.iff1_ = registers.iff1;
target.iff2_ = registers.iff2;
// Inputs.
target.irq_line_ = inputs.irq;
target.nmi_line_ = inputs.nmi;
target.wait_line_ = inputs.wait;
target.bus_request_line_ = inputs.bus_request;
// Execution State.
target.halt_mask_ = execution_state.is_halted ? 0x00 : 0xff;
target.request_status_ = execution_state.requests;
target.last_request_status_ = execution_state.last_requests;
target.temp8_ = execution_state.temp8;
target.temp16_.full = execution_state.temp16;
target.operation_ = execution_state.operation;
target.flag_adjustment_history_ = execution_state.flag_adjustment_history;
target.pc_increment_ = execution_state.pc_increment;
target.refresh_addr_.full = execution_state.refresh_address;
target.number_of_cycles_ = HalfCycles(execution_state.half_cycles_into_step);
switch(execution_state.instruction_page) {
default: target.current_instruction_page_ = &target.base_page_; break;
case 0xed: target.current_instruction_page_ = &target.ed_page_; break;
case 0xdd: target.current_instruction_page_ = &target.dd_page_; break;
case 0xcb: target.current_instruction_page_ = &target.cb_page_; break;
case 0xfd: target.current_instruction_page_ = &target.fd_page_; break;
case 0xfdcb: target.current_instruction_page_ = &target.fdcb_page_; break;
case 0xddcb: target.current_instruction_page_ = &target.ddcb_page_; break;
}
switch(execution_state.phase) {
case ExecutionState::Phase::UntakenConditionalCall: target.scheduled_program_counter_ = &target.conditional_call_untaken_program_[0]; break;
case ExecutionState::Phase::Reset: target.scheduled_program_counter_ = &target.reset_program_[0]; break;
case ExecutionState::Phase::IRQMode0: target.scheduled_program_counter_ = &target.irq_program_[0][0]; break;
case ExecutionState::Phase::IRQMode1: target.scheduled_program_counter_ = &target.irq_program_[1][0]; break;
case ExecutionState::Phase::IRQMode2: target.scheduled_program_counter_ = &target.irq_program_[2][0]; break;
case ExecutionState::Phase::NMI: target.scheduled_program_counter_ = &target.nmi_program_[0]; break;
case ExecutionState::Phase::FetchDecode: target.scheduled_program_counter_ = &target.current_instruction_page_->fetch_decode_execute[0]; break;
case ExecutionState::Phase::Operation: target.scheduled_program_counter_ = target.current_instruction_page_->instructions[target.operation_]; break;
}
target.scheduled_program_counter_ += execution_state.steps_into_phase;
}
// 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(a);
DeclareField(flags);
DeclareField(bc);
DeclareField(de);
DeclareField(hl);
DeclareField(bcDash);
DeclareField(deDash);
DeclareField(hlDash);
DeclareField(ix);
DeclareField(iy);
DeclareField(ir);
DeclareField(program_counter);
DeclareField(stack_pointer);
DeclareField(interrupt_mode);
DeclareField(iff1);
DeclareField(iff2);
DeclareField(memptr);
}
}
State::ExecutionState::ExecutionState() {
if(needs_declare()) {
DeclareField(is_halted);
DeclareField(requests);
DeclareField(last_requests);
DeclareField(temp8);
DeclareField(operation);
DeclareField(temp16);
DeclareField(flag_adjustment_history);
DeclareField(pc_increment);
DeclareField(refresh_address);
AnnounceEnum(Phase);
DeclareField(phase);
DeclareField(half_cycles_into_step);
DeclareField(steps_into_phase);
DeclareField(instruction_page);
}
}
State::Inputs::Inputs() {
if(needs_declare()) {
DeclareField(irq);
DeclareField(nmi);
DeclareField(bus_request);
DeclareField(wait);
}
}

View File

@ -0,0 +1,100 @@
//
// State.hpp
// Clock Signal
//
// Created by Thomas Harte on 13/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef State_hpp
#define State_hpp
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../Z80.hpp"
namespace CPU {
namespace Z80 {
/*!
Provides a means for capturing or restoring complete Z80 state.
This is an optional adjunct to the Z80 class. If you want to take the rest of the Z80
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> {
uint8_t a;
uint8_t flags;
uint16_t bc, de, hl;
uint16_t bcDash, deDash, hlDash;
uint16_t ix, iy, ir;
uint16_t program_counter, stack_pointer;
uint16_t memptr;
int interrupt_mode;
bool iff1, iff2;
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 irq = false;
bool nmi = false;
bool bus_request = false;
bool wait = false;
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> {
bool is_halted;
uint8_t requests;
uint8_t last_requests;
uint8_t temp8;
uint8_t operation;
uint16_t temp16;
unsigned int flag_adjustment_history;
uint16_t pc_increment;
uint16_t refresh_address;
ReflectableEnum(Phase,
UntakenConditionalCall, Reset, IRQMode0, IRQMode1, IRQMode2,
NMI, FetchDecode, Operation
);
Phase phase;
int half_cycles_into_step;
int steps_into_phase;
uint16_t instruction_page = 0;
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_hpp */

View File

@ -11,6 +11,7 @@
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <type_traits>
// MARK: - Setters
@ -113,11 +114,20 @@ template <typename Type> bool Reflection::get(const Struct &target, const std::s
const auto target_type = target.type_of(name);
if(!target_type) return false;
// If type is a direct match, copy.
if(*target_type == typeid(Type)) {
memcpy(&value, target.get(name), sizeof(Type));
return true;
}
// If the type is a registered enum and the value type is int, copy.
if constexpr (std::is_integral<Type>::value && sizeof(Type) == sizeof(int)) {
if(!Enum::name(*target_type).empty()) {
memcpy(&value, target.get(name), sizeof(int));
return true;
}
}
return false;
}