mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Move ExecutionState
into Implementation.hpp; use goto
to avoid some double switches.
Re: the latter, yuck. Yuck yuck yuck. But it does mean I can stop going back and forth on how to structure conditionality on effective address generation segueing into fetches without doubling up on tests.
This commit is contained in:
parent
aa9e7eb7a2
commit
bef12f3d65
@ -15,6 +15,28 @@
|
|||||||
namespace CPU {
|
namespace CPU {
|
||||||
namespace MC68000Mk2 {
|
namespace MC68000Mk2 {
|
||||||
|
|
||||||
|
/// States for the state machine which are named by
|
||||||
|
/// me for their purpose rather than automatically by file position.
|
||||||
|
/// These are negative to avoid ambiguity with the other group.
|
||||||
|
enum ExecutionState: int {
|
||||||
|
Reset = std::numeric_limits<int>::min(),
|
||||||
|
Decode,
|
||||||
|
WaitForDTACK,
|
||||||
|
FetchOperand,
|
||||||
|
StoreOperand,
|
||||||
|
|
||||||
|
// Various forms of perform; each of these will
|
||||||
|
// perform the current instruction, then do the
|
||||||
|
// indicated bus cycle.
|
||||||
|
|
||||||
|
Perform_np,
|
||||||
|
Perform_np_n,
|
||||||
|
|
||||||
|
// MOVE has unique bus usage, so has a specialised state.
|
||||||
|
|
||||||
|
MOVEWrite,
|
||||||
|
};
|
||||||
|
|
||||||
// MARK: - The state machine.
|
// MARK: - The state machine.
|
||||||
|
|
||||||
template <class BusHandler, bool dtack_is_implicit, bool permit_overrun, bool signal_will_perform>
|
template <class BusHandler, bool dtack_is_implicit, bool permit_overrun, bool signal_will_perform>
|
||||||
@ -34,21 +56,23 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
#define CheckOverrun() if constexpr (permit_overrun) ConsiderExit()
|
#define CheckOverrun() if constexpr (permit_overrun) ConsiderExit()
|
||||||
|
|
||||||
// Sets `x` as the next state, and exits now if all remaining time has been extended and permit_overrun is true.
|
// Sets `x` as the next state, and exits now if all remaining time has been extended and permit_overrun is true.
|
||||||
#define MoveToState(x) state_ = (x); if (permit_overrun && time_remaining_ <= HalfCycles(0)) return
|
// Jumps directly to the state otherwise.
|
||||||
|
#define MoveToState(x) { state_ = ExecutionState::x; if (permit_overrun && time_remaining_ <= HalfCycles(0)) return; goto x; }
|
||||||
|
|
||||||
|
// Sets the start position for state x.
|
||||||
|
#define BeginState(x) case ExecutionState::x: x
|
||||||
|
|
||||||
//
|
//
|
||||||
// So basic structure is, in general:
|
// So basic structure is, in general:
|
||||||
//
|
//
|
||||||
// case Action:
|
// BeginState(Action):
|
||||||
// do_something();
|
// do_something();
|
||||||
// Spend(20);
|
// Spend(20);
|
||||||
// do_something_else();
|
// do_something_else();
|
||||||
// Spend(10);
|
// Spend(10);
|
||||||
// do_a_third_thing();
|
// do_a_third_thing();
|
||||||
// Spend(30);
|
// Spend(30);
|
||||||
//
|
// MoveToState(next_action);
|
||||||
// MoveToState(next_action);
|
|
||||||
// break;
|
|
||||||
//
|
//
|
||||||
// Additional notes:
|
// Additional notes:
|
||||||
//
|
//
|
||||||
@ -83,7 +107,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
awaiting_dtack = x; \
|
awaiting_dtack = x; \
|
||||||
awaiting_dtack.length = HalfCycles(2); \
|
awaiting_dtack.length = HalfCycles(2); \
|
||||||
post_dtack_state_ = __COUNTER__+1; \
|
post_dtack_state_ = __COUNTER__+1; \
|
||||||
state_ = State::WaitForDTACK; \
|
state_ = ExecutionState::WaitForDTACK; \
|
||||||
break; \
|
break; \
|
||||||
} \
|
} \
|
||||||
[[fallthrough]]; case __COUNTER__:
|
[[fallthrough]]; case __COUNTER__:
|
||||||
@ -133,17 +157,18 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
|
|
||||||
// Spin in place, one cycle at a time, until one of DTACK,
|
// Spin in place, one cycle at a time, until one of DTACK,
|
||||||
// BERR or VPA is asserted.
|
// BERR or VPA is asserted.
|
||||||
case State::WaitForDTACK:
|
BeginState(WaitForDTACK):
|
||||||
PerformBusOperation(awaiting_dtack);
|
PerformBusOperation(awaiting_dtack);
|
||||||
|
|
||||||
if(dtack_ || berr_ || vpa_) {
|
if(dtack_ || berr_ || vpa_) {
|
||||||
state_ = post_dtack_state_;
|
state_ = post_dtack_state_;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
MoveToState(WaitForDTACK);
|
||||||
|
|
||||||
// Perform the RESET exception, which seeds the stack pointer and program
|
// Perform the RESET exception, which seeds the stack pointer and program
|
||||||
// counter, populates the prefetch queue, and then moves to instruction dispatch.
|
// counter, populates the prefetch queue, and then moves to instruction dispatch.
|
||||||
case State::Reset:
|
BeginState(Reset):
|
||||||
IdleBus(7); // (n-)*5 nn
|
IdleBus(7); // (n-)*5 nn
|
||||||
|
|
||||||
// Establish general reset state.
|
// Establish general reset state.
|
||||||
@ -168,12 +193,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
IdleBus(1); // n
|
IdleBus(1); // n
|
||||||
Prefetch(); // np
|
Prefetch(); // np
|
||||||
|
|
||||||
MoveToState(State::Decode);
|
MoveToState(Decode);
|
||||||
break;
|
|
||||||
|
|
||||||
// Inspect the prefetch queue in order to decode the next instruction,
|
// Inspect the prefetch queue in order to decode the next instruction,
|
||||||
// and segue into the fetching of operands.
|
// and segue into the fetching of operands.
|
||||||
case State::Decode:
|
BeginState(Decode):
|
||||||
opcode_ = prefetch_.high.w;
|
opcode_ = prefetch_.high.w;
|
||||||
instruction_ = decoder_.decode(opcode_);
|
instruction_ = decoder_.decode(opcode_);
|
||||||
instruction_address_ = program_counter_.l - 4;
|
instruction_address_ = program_counter_.l - 4;
|
||||||
@ -201,7 +225,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
// for CLR or MOVE SR, <ea>.
|
// for CLR or MOVE SR, <ea>.
|
||||||
//
|
//
|
||||||
// TODO: add MOVE special case, somewhere.
|
// TODO: add MOVE special case, somewhere.
|
||||||
case State::FetchOperand:
|
BeginState(FetchOperand):
|
||||||
// Check that this operand is meant to be fetched.
|
// Check that this operand is meant to be fetched.
|
||||||
if(!(operand_flags_ & (1 << next_operand_))) {
|
if(!(operand_flags_ & (1 << next_operand_))) {
|
||||||
state_ = perform_state_;
|
state_ = perform_state_;
|
||||||
@ -214,8 +238,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
case Mode::DataRegisterDirect:
|
case Mode::DataRegisterDirect:
|
||||||
operand_[next_operand_] = registers_[instruction_.lreg(next_operand_)];
|
operand_[next_operand_] = registers_[instruction_.lreg(next_operand_)];
|
||||||
++next_operand_;
|
++next_operand_;
|
||||||
state_ = next_operand_ == 2 ? perform_state_ : State::FetchOperand;
|
if(next_operand_ == 2) {
|
||||||
continue;
|
state_ = perform_state_;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MoveToState(FetchOperand);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
@ -225,11 +252,10 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
// Store operand is a lot simpler: only one operand is ever stored, and its address
|
// Store operand is a lot simpler: only one operand is ever stored, and its address
|
||||||
// is already known. So this can either skip straight back to ::Decode if the target
|
// is already known. So this can either skip straight back to ::Decode if the target
|
||||||
// is a register, otherwise a single write operation can occur.
|
// is a register, otherwise a single write operation can occur.
|
||||||
case State::StoreOperand:
|
BeginState(StoreOperand):
|
||||||
if(instruction_.mode(next_operand_) <= Mode::AddressRegisterDirect) {
|
if(instruction_.mode(next_operand_) <= Mode::AddressRegisterDirect) {
|
||||||
registers_[instruction_.lreg(next_operand_)] = operand_[next_operand_];
|
registers_[instruction_.lreg(next_operand_)] = operand_[next_operand_];
|
||||||
state_ = State::Decode;
|
MoveToState(Decode);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make a decision on how I'm going to deal with byte/word/longword.
|
// TODO: make a decision on how I'm going to deal with byte/word/longword.
|
||||||
@ -241,27 +267,26 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
//
|
//
|
||||||
#define MoveToWritePhase() \
|
#define MoveToWritePhase() \
|
||||||
next_operand_ = operand_flags_ >> 3; \
|
next_operand_ = operand_flags_ >> 3; \
|
||||||
MoveToState(operand_flags_ & 0x0c ? State::StoreOperand : State::Decode)
|
if(operand_flags_ & 0x0c) MoveToState(StoreOperand) else MoveToState(Decode)
|
||||||
|
|
||||||
case State::Perform_np:
|
BeginState(Perform_np):
|
||||||
InstructionSet::M68k::perform<InstructionSet::M68k::Model::M68000>(
|
InstructionSet::M68k::perform<InstructionSet::M68k::Model::M68000>(
|
||||||
instruction_, operand_[0], operand_[1], status_, *static_cast<ProcessorBase *>(this));
|
instruction_, operand_[0], operand_[1], status_, *static_cast<ProcessorBase *>(this));
|
||||||
Prefetch(); // np
|
Prefetch(); // np
|
||||||
|
MoveToWritePhase();
|
||||||
|
|
||||||
MoveToWritePhase();
|
BeginState(Perform_np_n):
|
||||||
break;
|
|
||||||
|
|
||||||
case State::Perform_np_n:
|
|
||||||
InstructionSet::M68k::perform<InstructionSet::M68k::Model::M68000>(
|
InstructionSet::M68k::perform<InstructionSet::M68k::Model::M68000>(
|
||||||
instruction_, operand_[0], operand_[1], status_, *static_cast<ProcessorBase *>(this));
|
instruction_, operand_[0], operand_[1], status_, *static_cast<ProcessorBase *>(this));
|
||||||
Prefetch(); // np
|
Prefetch(); // np
|
||||||
IdleBus(1); // n
|
IdleBus(1); // n
|
||||||
|
MoveToWritePhase();
|
||||||
MoveToWritePhase();
|
|
||||||
break;
|
|
||||||
|
|
||||||
#undef MoveToWritePhase
|
#undef MoveToWritePhase
|
||||||
|
|
||||||
|
|
||||||
|
// Various states TODO.
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printf("Unhandled state: %d\n", state_);
|
printf("Unhandled state: %d\n", state_);
|
||||||
assert(false);
|
assert(false);
|
||||||
@ -296,11 +321,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
|||||||
using Mode = InstructionSet::M68k::AddressingMode;
|
using Mode = InstructionSet::M68k::AddressingMode;
|
||||||
|
|
||||||
switch(instruction_.operation) {
|
switch(instruction_.operation) {
|
||||||
BIND(NBCD, instruction_.mode(0) == Mode::DataRegisterDirect ? State::Perform_np_n : State::Perform_np);
|
BIND(NBCD, instruction_.mode(0) == Mode::DataRegisterDirect ? ExecutionState::Perform_np_n : ExecutionState::Perform_np);
|
||||||
|
|
||||||
// MOVEs are a special case for having an operand they write but did not read. So they segue into a
|
// MOVEs are a special case for having an operand they write but did not read. So they segue into a
|
||||||
// specialised state for writing the result.
|
// specialised state for writing the result.
|
||||||
BIND(MOVEw, State::MOVEWrite);
|
BIND(MOVEw, ExecutionState::MOVEWrite);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
|
@ -13,36 +13,13 @@
|
|||||||
#include "../../../InstructionSets/M68k/Perform.hpp"
|
#include "../../../InstructionSets/M68k/Perform.hpp"
|
||||||
#include "../../../InstructionSets/M68k/Status.hpp"
|
#include "../../../InstructionSets/M68k/Status.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace CPU {
|
namespace CPU {
|
||||||
namespace MC68000Mk2 {
|
namespace MC68000Mk2 {
|
||||||
|
|
||||||
struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
|
struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
|
||||||
/// States for the state machine which are named by
|
int state_ = std::numeric_limits<int>::min();
|
||||||
/// me for their purpose rather than automatically by file position.
|
|
||||||
/// These are negative to avoid ambiguity with the other group.
|
|
||||||
enum State: int {
|
|
||||||
Reset = -1,
|
|
||||||
Decode = -2,
|
|
||||||
WaitForDTACK = -3,
|
|
||||||
FetchOperand = -4,
|
|
||||||
StoreOperand = -5,
|
|
||||||
|
|
||||||
// Various different effective address calculations.
|
|
||||||
|
|
||||||
CalculateAnDn = -5,
|
|
||||||
|
|
||||||
// Various forms of perform; each of these will
|
|
||||||
// perform the current instruction, then do the
|
|
||||||
// indicated bus cycle.
|
|
||||||
|
|
||||||
Perform_np = -6,
|
|
||||||
Perform_np_n = -7,
|
|
||||||
|
|
||||||
// MOVE has unique bus usage, so has a specialised state.
|
|
||||||
|
|
||||||
MOVEWrite = -8,
|
|
||||||
};
|
|
||||||
int state_ = State::Reset;
|
|
||||||
|
|
||||||
/// Counts time left on the clock before the current batch of processing
|
/// Counts time left on the clock before the current batch of processing
|
||||||
/// is complete; may be less than zero.
|
/// is complete; may be less than zero.
|
||||||
@ -137,14 +114,6 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
|
|||||||
// some of these may persist across multiple calls to run_for.
|
// some of these may persist across multiple calls to run_for.
|
||||||
Microcycle idle{0};
|
Microcycle idle{0};
|
||||||
|
|
||||||
// Read a data word.
|
|
||||||
Microcycle read_word_data_announce {
|
|
||||||
Microcycle::Read | Microcycle::NewAddress | Microcycle::IsData
|
|
||||||
};
|
|
||||||
Microcycle read_word_data {
|
|
||||||
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsData
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read a program word. All accesses via the program counter are word sized.
|
// Read a program word. All accesses via the program counter are word sized.
|
||||||
Microcycle read_program_announce {
|
Microcycle read_program_announce {
|
||||||
Microcycle::Read | Microcycle::NewAddress | Microcycle::IsProgram
|
Microcycle::Read | Microcycle::NewAddress | Microcycle::IsProgram
|
||||||
@ -153,6 +122,28 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
|
|||||||
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsProgram
|
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsProgram
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Read a data word or byte.
|
||||||
|
Microcycle read_word_data_announce {
|
||||||
|
Microcycle::Read | Microcycle::NewAddress | Microcycle::IsData
|
||||||
|
};
|
||||||
|
Microcycle read_word_data {
|
||||||
|
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsData
|
||||||
|
};
|
||||||
|
Microcycle read_byte_data {
|
||||||
|
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectByte | Microcycle::IsData
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write a data word or byte.
|
||||||
|
Microcycle write_word_data_announce {
|
||||||
|
Microcycle::NewAddress | Microcycle::IsData
|
||||||
|
};
|
||||||
|
Microcycle write_word_data {
|
||||||
|
Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsData
|
||||||
|
};
|
||||||
|
Microcycle write_byte_data {
|
||||||
|
Microcycle::SameAddress | Microcycle::SelectByte | Microcycle::IsData
|
||||||
|
};
|
||||||
|
|
||||||
// Holding spot when awaiting DTACK/etc.
|
// Holding spot when awaiting DTACK/etc.
|
||||||
Microcycle awaiting_dtack;
|
Microcycle awaiting_dtack;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user