mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-22 12:33:29 +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 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.
|
||||
|
||||
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()
|
||||
|
||||
// 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:
|
||||
//
|
||||
// case Action:
|
||||
// BeginState(Action):
|
||||
// do_something();
|
||||
// Spend(20);
|
||||
// do_something_else();
|
||||
// Spend(10);
|
||||
// do_a_third_thing();
|
||||
// Spend(30);
|
||||
//
|
||||
// MoveToState(next_action);
|
||||
// break;
|
||||
// MoveToState(next_action);
|
||||
//
|
||||
// Additional notes:
|
||||
//
|
||||
@ -83,7 +107,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
||||
awaiting_dtack = x; \
|
||||
awaiting_dtack.length = HalfCycles(2); \
|
||||
post_dtack_state_ = __COUNTER__+1; \
|
||||
state_ = State::WaitForDTACK; \
|
||||
state_ = ExecutionState::WaitForDTACK; \
|
||||
break; \
|
||||
} \
|
||||
[[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,
|
||||
// BERR or VPA is asserted.
|
||||
case State::WaitForDTACK:
|
||||
BeginState(WaitForDTACK):
|
||||
PerformBusOperation(awaiting_dtack);
|
||||
|
||||
if(dtack_ || berr_ || vpa_) {
|
||||
state_ = post_dtack_state_;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
MoveToState(WaitForDTACK);
|
||||
|
||||
// Perform the RESET exception, which seeds the stack pointer and program
|
||||
// counter, populates the prefetch queue, and then moves to instruction dispatch.
|
||||
case State::Reset:
|
||||
BeginState(Reset):
|
||||
IdleBus(7); // (n-)*5 nn
|
||||
|
||||
// Establish general reset state.
|
||||
@ -168,12 +193,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
||||
IdleBus(1); // n
|
||||
Prefetch(); // np
|
||||
|
||||
MoveToState(State::Decode);
|
||||
break;
|
||||
MoveToState(Decode);
|
||||
|
||||
// Inspect the prefetch queue in order to decode the next instruction,
|
||||
// and segue into the fetching of operands.
|
||||
case State::Decode:
|
||||
BeginState(Decode):
|
||||
opcode_ = prefetch_.high.w;
|
||||
instruction_ = decoder_.decode(opcode_);
|
||||
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>.
|
||||
//
|
||||
// TODO: add MOVE special case, somewhere.
|
||||
case State::FetchOperand:
|
||||
BeginState(FetchOperand):
|
||||
// Check that this operand is meant to be fetched.
|
||||
if(!(operand_flags_ & (1 << next_operand_))) {
|
||||
state_ = perform_state_;
|
||||
@ -214,8 +238,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
||||
case Mode::DataRegisterDirect:
|
||||
operand_[next_operand_] = registers_[instruction_.lreg(next_operand_)];
|
||||
++next_operand_;
|
||||
state_ = next_operand_ == 2 ? perform_state_ : State::FetchOperand;
|
||||
continue;
|
||||
if(next_operand_ == 2) {
|
||||
state_ = perform_state_;
|
||||
continue;
|
||||
}
|
||||
MoveToState(FetchOperand);
|
||||
|
||||
default:
|
||||
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
|
||||
// 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.
|
||||
case State::StoreOperand:
|
||||
BeginState(StoreOperand):
|
||||
if(instruction_.mode(next_operand_) <= Mode::AddressRegisterDirect) {
|
||||
registers_[instruction_.lreg(next_operand_)] = operand_[next_operand_];
|
||||
state_ = State::Decode;
|
||||
continue;
|
||||
MoveToState(Decode);
|
||||
}
|
||||
|
||||
// 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() \
|
||||
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>(
|
||||
instruction_, operand_[0], operand_[1], status_, *static_cast<ProcessorBase *>(this));
|
||||
Prefetch(); // np
|
||||
MoveToWritePhase();
|
||||
|
||||
MoveToWritePhase();
|
||||
break;
|
||||
|
||||
case State::Perform_np_n:
|
||||
BeginState(Perform_np_n):
|
||||
InstructionSet::M68k::perform<InstructionSet::M68k::Model::M68000>(
|
||||
instruction_, operand_[0], operand_[1], status_, *static_cast<ProcessorBase *>(this));
|
||||
Prefetch(); // np
|
||||
IdleBus(1); // n
|
||||
|
||||
MoveToWritePhase();
|
||||
break;
|
||||
MoveToWritePhase();
|
||||
|
||||
#undef MoveToWritePhase
|
||||
|
||||
|
||||
// Various states TODO.
|
||||
|
||||
default:
|
||||
printf("Unhandled state: %d\n", state_);
|
||||
assert(false);
|
||||
@ -296,11 +321,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
|
||||
using Mode = InstructionSet::M68k::AddressingMode;
|
||||
|
||||
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
|
||||
// specialised state for writing the result.
|
||||
BIND(MOVEw, State::MOVEWrite);
|
||||
BIND(MOVEw, ExecutionState::MOVEWrite);
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
|
@ -13,36 +13,13 @@
|
||||
#include "../../../InstructionSets/M68k/Perform.hpp"
|
||||
#include "../../../InstructionSets/M68k/Status.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace CPU {
|
||||
namespace MC68000Mk2 {
|
||||
|
||||
struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
|
||||
/// 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 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;
|
||||
int state_ = std::numeric_limits<int>::min();
|
||||
|
||||
/// Counts time left on the clock before the current batch of processing
|
||||
/// 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.
|
||||
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.
|
||||
Microcycle read_program_announce {
|
||||
Microcycle::Read | Microcycle::NewAddress | Microcycle::IsProgram
|
||||
@ -153,6 +122,28 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
|
||||
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.
|
||||
Microcycle awaiting_dtack;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user