mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge pull request #779 from TomHarte/6502State
Provisionally adds `State` and `get/set_state` to the 6502.
This commit is contained in:
commit
c4b114133a
2
.github/workflows/ccpp.yml
vendored
2
.github/workflows/ccpp.yml
vendored
@ -17,6 +17,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons
|
||||
run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get --fix-missing install libsdl2-dev scons
|
||||
- name: Make
|
||||
run: cd OSBindings/SDL; scons
|
||||
|
@ -30,7 +30,7 @@ class MultiStruct: public Reflection::Struct {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() final {
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
@ -39,7 +39,7 @@ class MultiStruct: public Reflection::Struct {
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) final {
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
@ -48,7 +48,7 @@ class MultiStruct: public Reflection::Struct {
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) final {
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
@ -56,7 +56,7 @@ class MultiStruct: public Reflection::Struct {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) final {
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
#include "../RegisterSizes.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Reflection/Enum.hpp"
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
namespace CPU {
|
||||
namespace MOS6502 {
|
||||
@ -29,8 +31,7 @@ enum Register {
|
||||
Flags,
|
||||
A,
|
||||
X,
|
||||
Y,
|
||||
S
|
||||
Y
|
||||
};
|
||||
|
||||
/*
|
||||
@ -128,6 +129,81 @@ 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.
|
||||
|
||||
@ -198,6 +274,40 @@ 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(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.
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include "../6502.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace CPU::MOS6502;
|
||||
|
||||
const uint8_t CPU::MOS6502::JamOpcode = 0xf2;
|
||||
@ -21,7 +23,6 @@ uint16_t ProcessorBase::get_value_of_register(Register r) {
|
||||
case Register::A: return a_;
|
||||
case Register::X: return x_;
|
||||
case Register::Y: return y_;
|
||||
case Register::S: return s_;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@ -34,7 +35,6 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
|
||||
case Register::A: a_ = static_cast<uint8_t>(value); break;
|
||||
case Register::X: x_ = static_cast<uint8_t>(value); break;
|
||||
case Register::Y: y_ = static_cast<uint8_t>(value); break;
|
||||
case Register::S: s_ = static_cast<uint8_t>(value); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@ -42,3 +42,79 @@ 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.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];
|
||||
}
|
||||
|
@ -13,17 +13,7 @@
|
||||
*/
|
||||
|
||||
template <Personality personality, typename T, bool uses_ready_line> void Processor<personality, T, uses_ready_line>::run_for(const Cycles cycles) {
|
||||
static const MicroOp do_branch[] = {
|
||||
CycleReadFromPC,
|
||||
CycleAddSignedOperandToPC,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
static uint8_t throwaway_target;
|
||||
static const MicroOp fetch_decode_execute[] = {
|
||||
CycleFetchOperation,
|
||||
CycleFetchOperand,
|
||||
OperationDecodeOperation
|
||||
};
|
||||
|
||||
// These plus program below act to give the compiler permission to update these values
|
||||
// without touching the class storage (i.e. it explicitly says they need be completely up
|
||||
@ -33,28 +23,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_ = operations_[size_t(OperationsSlot::Reset)];\
|
||||
} else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\
|
||||
interrupt_requests_ &= ~InterruptRequestFlags::NMI;\
|
||||
scheduled_program_counter_ = operations_[size_t(OperationsSlot::NMI)];\
|
||||
} else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\
|
||||
scheduled_program_counter_ = operations_[size_t(OperationsSlot::IRQ)];\
|
||||
} \
|
||||
} 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;
|
||||
|
||||
@ -66,11 +57,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 +74,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;
|
||||
@ -566,7 +560,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
#define BRA(condition) \
|
||||
pc_.full++; \
|
||||
if(condition) { \
|
||||
scheduled_program_counter_ = do_branch; \
|
||||
scheduled_program_counter_ = operations_[size_t(OperationsSlot::DoBRA)]; \
|
||||
}
|
||||
|
||||
case OperationBPL: BRA(!(negative_result_&0x80)); continue;
|
||||
@ -593,7 +587,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
// 65C02 modification to all branches: a branch that is taken but requires only a single cycle
|
||||
// to target its destination skips any pending interrupts.
|
||||
// Cf. http://forum.6502.org/viewtopic.php?f=4&t=1634
|
||||
scheduled_program_counter_ = fetch_decode_execute;
|
||||
scheduled_program_counter_ = operations_[size_t(OperationsSlot::FetchDecodeExecute)];
|
||||
}
|
||||
continue;
|
||||
|
||||
@ -611,22 +605,9 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
// and (iii) read from the corresponding zero page.
|
||||
const uint8_t mask = uint8_t(1 << ((operation_ >> 4)&7));
|
||||
if((operand_ & mask) == ((operation_ & 0x80) ? mask : 0)) {
|
||||
static const MicroOp do_branch[] = {
|
||||
CycleFetchOperand, // Fetch offset.
|
||||
OperationIncrementPC,
|
||||
CycleFetchFromHalfUpdatedPC,
|
||||
OperationAddSignedOperandToPC16,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
scheduled_program_counter_ = do_branch;
|
||||
scheduled_program_counter_ = operations_[size_t(OperationsSlot::DoBBRBBS)];
|
||||
} else {
|
||||
static const MicroOp do_not_branch[] = {
|
||||
CycleFetchOperand,
|
||||
OperationIncrementPC,
|
||||
CycleFetchFromHalfUpdatedPC,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
scheduled_program_counter_ = do_not_branch;
|
||||
scheduled_program_counter_ = operations_[size_t(OperationsSlot::DoNotBBRBBS)];
|
||||
}
|
||||
} break;
|
||||
|
||||
@ -730,56 +711,6 @@ void ProcessorBase::set_nmi_line(bool active) {
|
||||
nmi_line_is_enabled_ = active;
|
||||
}
|
||||
|
||||
inline const ProcessorStorage::MicroOp *ProcessorStorage::get_reset_program() {
|
||||
static const MicroOp reset[] = {
|
||||
CycleFetchOperand,
|
||||
CycleFetchOperand,
|
||||
CycleNoWritePush,
|
||||
CycleNoWritePush,
|
||||
OperationRSTPickVector,
|
||||
CycleNoWritePush,
|
||||
OperationSetNMIRSTFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
return reset;
|
||||
}
|
||||
|
||||
inline const ProcessorStorage::MicroOp *ProcessorStorage::get_irq_program() {
|
||||
static const MicroOp reset[] = {
|
||||
CycleFetchOperand,
|
||||
CycleFetchOperand,
|
||||
CyclePushPCH,
|
||||
CyclePushPCL,
|
||||
OperationBRKPickVector,
|
||||
OperationSetOperandFromFlags,
|
||||
CyclePushOperand,
|
||||
OperationSetIRQFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
return reset;
|
||||
}
|
||||
|
||||
inline const ProcessorStorage::MicroOp *ProcessorStorage::get_nmi_program() {
|
||||
static const MicroOp reset[] = {
|
||||
CycleFetchOperand,
|
||||
CycleFetchOperand,
|
||||
CyclePushPCH,
|
||||
CyclePushPCL,
|
||||
OperationNMIPickVector,
|
||||
OperationSetOperandFromFlags,
|
||||
CyclePushOperand,
|
||||
OperationSetNMIRSTFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh,
|
||||
OperationMoveToNextProgram
|
||||
};
|
||||
return reset;
|
||||
}
|
||||
|
||||
uint8_t ProcessorStorage::get_flags() {
|
||||
return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ ProcessorStorage::ProcessorStorage(Personality personality) {
|
||||
decimal_flag_ &= Flag::Decimal;
|
||||
overflow_flag_ &= Flag::Overflow;
|
||||
|
||||
const InstructionList operations_6502[256] = {
|
||||
const InstructionList operations_6502[] = {
|
||||
/* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetIRQFlags, CycleReadVectorLow, CycleReadVectorHigh),
|
||||
/* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA),
|
||||
/* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO),
|
||||
@ -218,8 +218,79 @@ ProcessorStorage::ProcessorStorage(Personality personality) {
|
||||
/* 0xfa NOP # */ ImpliedNop(), /* 0xfb INS abs, y */ AbsoluteYReadModifyWrite(OperationINS),
|
||||
/* 0xfc NOP abs, x */ AbsoluteXNop(), /* 0xfd SBC abs, x */ AbsoluteXRead(OperationSBC),
|
||||
/* 0xfe INC abs, x */ AbsoluteXReadModifyWrite(OperationINC), /* 0xff INS abs, x */ AbsoluteXReadModifyWrite(OperationINS),
|
||||
|
||||
/* 0x100: Fetch, decode, execute. */
|
||||
{
|
||||
CycleFetchOperation,
|
||||
CycleFetchOperand,
|
||||
OperationDecodeOperation
|
||||
},
|
||||
|
||||
/* 0x101: Reset. */
|
||||
Program(
|
||||
CycleFetchOperand,
|
||||
CycleFetchOperand,
|
||||
CycleNoWritePush,
|
||||
CycleNoWritePush,
|
||||
OperationRSTPickVector,
|
||||
CycleNoWritePush,
|
||||
OperationSetNMIRSTFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh
|
||||
),
|
||||
|
||||
/* 0x102: IRQ. */
|
||||
Program(
|
||||
CycleFetchOperand,
|
||||
CycleFetchOperand,
|
||||
CyclePushPCH,
|
||||
CyclePushPCL,
|
||||
OperationBRKPickVector,
|
||||
OperationSetOperandFromFlags,
|
||||
CyclePushOperand,
|
||||
OperationSetIRQFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh
|
||||
),
|
||||
|
||||
/* 0x103: NMI. */
|
||||
Program(
|
||||
CycleFetchOperand,
|
||||
CycleFetchOperand,
|
||||
CyclePushPCH,
|
||||
CyclePushPCL,
|
||||
OperationNMIPickVector,
|
||||
OperationSetOperandFromFlags,
|
||||
CyclePushOperand,
|
||||
OperationSetNMIRSTFlags,
|
||||
CycleReadVectorLow,
|
||||
CycleReadVectorHigh
|
||||
),
|
||||
|
||||
/* 0x104: Do BRA. */
|
||||
Program(
|
||||
CycleReadFromPC,
|
||||
CycleAddSignedOperandToPC
|
||||
),
|
||||
|
||||
/* 0x105: Do BBR or BBS. */
|
||||
Program(
|
||||
CycleFetchOperand, // Fetch offset.
|
||||
OperationIncrementPC,
|
||||
CycleFetchFromHalfUpdatedPC,
|
||||
OperationAddSignedOperandToPC16
|
||||
),
|
||||
|
||||
/* 0x106: Complete BBR or BBS without branching. */
|
||||
Program(
|
||||
CycleFetchOperand,
|
||||
OperationIncrementPC,
|
||||
CycleFetchFromHalfUpdatedPC
|
||||
)
|
||||
};
|
||||
|
||||
static_assert(sizeof(operations_6502) == sizeof(operations_));
|
||||
|
||||
// Install the basic 6502 table.
|
||||
memcpy(operations_, operations_6502, sizeof(operations_));
|
||||
|
||||
|
@ -24,7 +24,7 @@ class ProcessorStorage {
|
||||
|
||||
This micro-instruction set was put together in a fairly ad hoc fashion, I'm afraid, so is unlikely to be optimal.
|
||||
*/
|
||||
enum MicroOp {
|
||||
enum MicroOp: uint8_t {
|
||||
CycleFetchOperation, // fetches (PC) to operation_, storing PC to last_operation_pc_ before incrementing it
|
||||
CycleFetchOperand, // 6502: fetches from (PC) to operand_; 65C02: as 6502 unless operation_ indicates a one-cycle NOP, in which case this is a no0op
|
||||
OperationDecodeOperation, // schedules the microprogram associated with operation_
|
||||
@ -197,10 +197,35 @@ class ProcessorStorage {
|
||||
OperationScheduleStop, // puts the processor into STP mode (i.e. it'll do nothing until a reset is received)
|
||||
};
|
||||
|
||||
using InstructionList = MicroOp[10];
|
||||
InstructionList operations_[256];
|
||||
using InstructionList = MicroOp[12];
|
||||
/// Defines the locations in operations_ of various named microprograms; the first 256 entries
|
||||
/// in operations_ are mapped directly from instruction codes and therefore not named.
|
||||
enum class OperationsSlot {
|
||||
/// Fetches the next operation, and its operand, then schedules the corresponding set of operations_.
|
||||
/// [Caveat: the 65C02 adds single-cycle NOPs; this microprogram won't fetch an operand for those].
|
||||
FetchDecodeExecute = 256,
|
||||
|
||||
/// Performs the 6502's reset sequence.
|
||||
Reset,
|
||||
/// Performs the 6502's IRQ sequence.
|
||||
IRQ,
|
||||
/// Performs the 6502's NMI sequence.
|
||||
NMI,
|
||||
|
||||
/// Performs a branch, e.g. the entry for BCC will evaluate whether carry is clear and, if so, will jump
|
||||
/// to this instruction list.
|
||||
DoBRA,
|
||||
|
||||
/// On a 65c02,
|
||||
DoBBRBBS,
|
||||
DoNotBBRBBS,
|
||||
|
||||
Max
|
||||
};
|
||||
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.
|
||||
@ -260,27 +285,6 @@ class ProcessorStorage {
|
||||
|
||||
uint8_t irq_line_ = 0, irq_request_history_ = 0;
|
||||
bool nmi_line_is_enabled_ = false, set_overflow_line_is_enabled_ = false;
|
||||
|
||||
/*!
|
||||
Gets the program representing an RST response.
|
||||
|
||||
@returns The program representing an RST response.
|
||||
*/
|
||||
inline const MicroOp *get_reset_program();
|
||||
|
||||
/*!
|
||||
Gets the program representing an IRQ response.
|
||||
|
||||
@returns The program representing an IRQ response.
|
||||
*/
|
||||
inline const MicroOp *get_irq_program();
|
||||
|
||||
/*!
|
||||
Gets the program representing an NMI response.
|
||||
|
||||
@returns The program representing an NMI response.
|
||||
*/
|
||||
inline const MicroOp *get_nmi_program();
|
||||
};
|
||||
|
||||
#endif /* _502Storage_h */
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include "Struct.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
// MARK: - Setters
|
||||
|
||||
@ -107,18 +109,84 @@ bool Reflection::fuzzy_set(Struct &target, const std::string &name, const std::s
|
||||
|
||||
// MARK: - Getters
|
||||
|
||||
template <typename Type> bool Reflection::get(Struct &target, const std::string &name, Type &value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <> bool Reflection::get(Struct &target, const std::string &name, bool &value) {
|
||||
template <typename Type> bool Reflection::get(const Struct &target, const std::string &name, Type &value) {
|
||||
const auto target_type = target.type_of(name);
|
||||
if(!target_type) return false;
|
||||
|
||||
if(*target_type == typeid(bool)) {
|
||||
value = *reinterpret_cast<const bool *>(target.get(name));
|
||||
if(*target_type == typeid(Type)) {
|
||||
memcpy(&value, target.get(name), sizeof(Type));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Type> Type Reflection::get(const Struct &target, const std::string &name) {
|
||||
Type value;
|
||||
get(target, name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// MARK: - Description
|
||||
|
||||
std::string Reflection::Struct::description() const {
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "{";
|
||||
|
||||
bool is_first = true;
|
||||
for(const auto &key: all_keys()) {
|
||||
if(!is_first) stream << ", ";
|
||||
is_first = false;
|
||||
stream << key << ": ";
|
||||
|
||||
const auto type = type_of(key);
|
||||
|
||||
// Output Bools as yes/no.
|
||||
if(*type == typeid(bool)) {
|
||||
bool value;
|
||||
::Reflection::get(*this, key, value);
|
||||
stream << (value ? "true" : "false");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Output Ints of all sizes as hex.
|
||||
#define OutputIntC(int_type, cast_type) if(*type == typeid(int_type)) { stream << std::setfill('0') << std::setw(sizeof(int_type)*2) << std::hex << cast_type(::Reflection::get<int_type>(*this, key)); continue; }
|
||||
#define OutputInt(int_type) OutputIntC(int_type, int_type)
|
||||
OutputIntC(int8_t, int16_t);
|
||||
OutputIntC(uint8_t, uint16_t);
|
||||
OutputInt(int16_t);
|
||||
OutputInt(uint16_t);
|
||||
OutputInt(int32_t);
|
||||
OutputInt(uint32_t);
|
||||
OutputInt(int64_t);
|
||||
OutputInt(uint64_t);
|
||||
#undef OutputInt
|
||||
|
||||
// Output floats and strings natively.
|
||||
#define OutputNative(val_type) if(*type == typeid(val_type)) { stream << ::Reflection::get<val_type>(*this, key); continue; }
|
||||
OutputNative(float);
|
||||
OutputNative(double);
|
||||
OutputNative(char *);
|
||||
OutputNative(std::string);
|
||||
#undef OutputNAtive
|
||||
|
||||
// Output the current value of any enums.
|
||||
if(!Enum::name(*type).empty()) {
|
||||
const int value = ::Reflection::get<int>(*this, key);
|
||||
stream << Enum::to_string(*type, value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recurse to deal with embedded objects.
|
||||
if(*type == typeid(Reflection::Struct)) {
|
||||
const Reflection::Struct *const child = reinterpret_cast<const Reflection::Struct *>(get(key));
|
||||
stream << child->description();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
stream << "}";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#ifndef Struct_hpp
|
||||
#define Struct_hpp
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
@ -24,12 +25,18 @@ namespace Reflection {
|
||||
#define DeclareField(Name) declare(&Name, #Name)
|
||||
|
||||
struct Struct {
|
||||
virtual std::vector<std::string> all_keys() = 0;
|
||||
virtual const std::type_info *type_of(const std::string &name) = 0;
|
||||
virtual std::vector<std::string> all_keys() const = 0;
|
||||
virtual const std::type_info *type_of(const std::string &name) const = 0;
|
||||
virtual void set(const std::string &name, const void *value) = 0;
|
||||
virtual const void *get(const std::string &name) = 0;
|
||||
virtual std::vector<std::string> values_for(const std::string &name) = 0;
|
||||
virtual const void *get(const std::string &name) const = 0;
|
||||
virtual std::vector<std::string> values_for(const std::string &name) const = 0;
|
||||
virtual ~Struct() {}
|
||||
|
||||
/*!
|
||||
@returns A string describing this struct. This string has no guaranteed layout, may not be
|
||||
sufficiently formed for a formal language parser, etc.
|
||||
*/
|
||||
std::string description() const;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -86,9 +93,14 @@ bool fuzzy_set(Struct &target, const std::string &name, const std::string &value
|
||||
|
||||
@returns @c true if the property was successfully read; @c false otherwise.
|
||||
*/
|
||||
template <typename Type> bool get(Struct &target, const std::string &name, Type &value);
|
||||
template <typename Type> bool get(const Struct &target, const std::string &name, Type &value);
|
||||
|
||||
template <> bool get(Struct &target, const std::string &name, bool &value);
|
||||
/*!
|
||||
Attempts to get the property @c name to @c value ; will perform limited type conversions.
|
||||
|
||||
@returns @c true if the property was successfully read; a default-constructed instance of Type otherwise.
|
||||
*/
|
||||
template <typename Type> Type get(const Struct &target, const std::string &name);
|
||||
|
||||
|
||||
// TODO: move this elsewhere. It's just a sketch anyway.
|
||||
@ -106,10 +118,10 @@ template <typename Owner> class StructImpl: public Struct {
|
||||
@returns the value of type @c Type that is loaded from the offset registered for the field @c name.
|
||||
It is the caller's responsibility to provide an appropriate type of data.
|
||||
*/
|
||||
const void *get(const std::string &name) final {
|
||||
const void *get(const std::string &name) const final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return nullptr;
|
||||
return reinterpret_cast<uint8_t *>(this) + iterator->second.offset;
|
||||
return reinterpret_cast<const uint8_t *>(this) + iterator->second.offset;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -126,7 +138,7 @@ template <typename Owner> class StructImpl: public Struct {
|
||||
/*!
|
||||
@returns @c type_info for the field @c name.
|
||||
*/
|
||||
const std::type_info *type_of(const std::string &name) final {
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
const auto iterator = contents_.find(name);
|
||||
if(iterator == contents_.end()) return nullptr;
|
||||
return iterator->second.type;
|
||||
@ -136,7 +148,7 @@ template <typename Owner> class StructImpl: public Struct {
|
||||
@returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct;
|
||||
the empty list otherwise.
|
||||
*/
|
||||
std::vector<std::string> values_for(const std::string &name) final {
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::vector<std::string> result;
|
||||
|
||||
// Return an empty vector if this field isn't declared.
|
||||
@ -168,7 +180,7 @@ template <typename Owner> class StructImpl: public Struct {
|
||||
/*!
|
||||
@returns A vector of all declared fields for this struct.
|
||||
*/
|
||||
std::vector<std::string> all_keys() final {
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::vector<std::string> keys;
|
||||
for(const auto &pair: contents_) {
|
||||
keys.push_back(pair.first);
|
||||
@ -190,14 +202,14 @@ template <typename Owner> class StructImpl: public Struct {
|
||||
*/
|
||||
|
||||
/*!
|
||||
Exposes the field pointed to by @c t for reflection as @c name.
|
||||
Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct,
|
||||
it'll be the struct that's exposed.
|
||||
*/
|
||||
template <typename Type> void declare(Type *t, const std::string &name) {
|
||||
contents_.emplace(
|
||||
std::make_pair(
|
||||
name,
|
||||
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type))
|
||||
));
|
||||
if constexpr (std::is_class<Type>()) {
|
||||
if(declare_reflectable(t, name)) return;
|
||||
}
|
||||
declare_emplace(t, name);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -233,7 +245,7 @@ template <typename Owner> class StructImpl: public Struct {
|
||||
@returns @c true if this subclass of @c Struct has not yet declared any fields.
|
||||
*/
|
||||
bool needs_declare() {
|
||||
return !contents_.size();
|
||||
return contents_.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -256,6 +268,24 @@ template <typename Owner> class StructImpl: public Struct {
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Type> bool declare_reflectable(Type *t, const std::string &name) {
|
||||
Reflection::Struct *const str = static_cast<Reflection::Struct *>(t);
|
||||
if(str) {
|
||||
declare_emplace(str, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Type> void declare_emplace(Type *t, const std::string &name) {
|
||||
contents_.emplace(
|
||||
std::make_pair(
|
||||
name,
|
||||
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type))
|
||||
));
|
||||
}
|
||||
|
||||
struct Field {
|
||||
const std::type_info *type;
|
||||
ssize_t offset;
|
||||
|
@ -45,7 +45,7 @@ void Drive::set_rotation_speed(float revolutions_per_minute) {
|
||||
// From there derive the appropriate rotational multiplier and possibly update the
|
||||
// count of cycles since the index hole proportionally.
|
||||
const float new_rotational_multiplier = float(cycles_per_revolution_) / float(get_input_clock_rate());
|
||||
cycles_since_index_hole_ *= new_rotational_multiplier / rotational_multiplier_;
|
||||
cycles_since_index_hole_ = Cycles::IntType(float(cycles_since_index_hole_) * new_rotational_multiplier / rotational_multiplier_);
|
||||
rotational_multiplier_ = new_rotational_multiplier;
|
||||
cycles_since_index_hole_ %= cycles_per_revolution_;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user