2022-05-16 15:44:16 +00:00
|
|
|
//
|
|
|
|
// 68000Mk2Implementation.hpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 16/05/2022.
|
|
|
|
// Copyright © 2022 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#ifndef _8000Mk2Implementation_h
|
|
|
|
#define _8000Mk2Implementation_h
|
|
|
|
|
2022-05-16 20:57:40 +00:00
|
|
|
#include <cassert>
|
|
|
|
|
2022-05-16 15:44:16 +00:00
|
|
|
namespace CPU {
|
|
|
|
namespace MC68000Mk2 {
|
|
|
|
|
2022-05-16 15:59:03 +00:00
|
|
|
template <class BusHandler, bool dtack_is_implicit, bool permit_overrun, bool signal_will_perform>
|
|
|
|
void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perform>::run_for(HalfCycles duration) {
|
|
|
|
// Accumulate the newly paid-in cycles. If this instance remains in deficit, exit.
|
2022-05-16 20:57:40 +00:00
|
|
|
time_remaining_ += duration;
|
|
|
|
if(time_remaining_ <= HalfCycles(0)) return;
|
2022-05-16 15:59:03 +00:00
|
|
|
|
|
|
|
// Check whether all remaining time has been expended; if so then exit, having set this line up as
|
|
|
|
// the next resumption point.
|
2022-05-17 00:38:17 +00:00
|
|
|
#define ConsiderExit() if(time_remaining_ <= HalfCycles(0)) { state_ = __COUNTER__+1; return; } [[fallthrough]]; case __COUNTER__:
|
2022-05-16 15:59:03 +00:00
|
|
|
|
2022-05-16 20:57:40 +00:00
|
|
|
// Subtracts `n` half-cycles from `time_remaining_`; if permit_overrun is false, also ConsiderExit()
|
|
|
|
#define Spend(n) time_remaining_ -= (n); if constexpr (!permit_overrun) ConsiderExit()
|
2022-05-16 15:59:03 +00:00
|
|
|
|
2022-05-17 01:00:25 +00:00
|
|
|
// Performs ConsiderExit() only if permit_overrun is true.
|
2022-05-16 15:59:03 +00:00
|
|
|
#define CheckOverrun() if constexpr (permit_overrun) ConsiderExit()
|
|
|
|
|
2022-05-17 01:00:25 +00:00
|
|
|
// 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
|
|
|
|
|
2022-05-16 15:59:03 +00:00
|
|
|
//
|
2022-05-16 20:57:40 +00:00
|
|
|
// So basic structure is, in general:
|
2022-05-16 15:59:03 +00:00
|
|
|
//
|
|
|
|
// case Action:
|
|
|
|
// do_something();
|
|
|
|
// Spend(20);
|
|
|
|
// do_something_else();
|
|
|
|
// Spend(10);
|
|
|
|
// do_a_third_thing();
|
|
|
|
// Spend(30);
|
|
|
|
//
|
2022-05-17 01:00:25 +00:00
|
|
|
// MoveToState(next_action);
|
2022-05-16 15:59:03 +00:00
|
|
|
// break;
|
|
|
|
//
|
|
|
|
// Additional notes:
|
|
|
|
//
|
|
|
|
// Action and all equivalents should be negative values, since the
|
2022-05-17 00:38:17 +00:00
|
|
|
// switch-for-computed-goto-for-a-coroutine structure uses __COUNTER__* for
|
2022-05-16 15:59:03 +00:00
|
|
|
// its invented entry- and exit-points, meaning that negative numbers are
|
|
|
|
// the easiest group that is safely definitely never going to collide.
|
2022-05-17 00:38:17 +00:00
|
|
|
//
|
|
|
|
// (* an extension supported by at least GCC, Clang and MSVC)
|
2022-05-16 15:59:03 +00:00
|
|
|
|
2022-05-16 20:57:40 +00:00
|
|
|
|
|
|
|
// Some microcycles that will be modified as required and used in the loop below;
|
|
|
|
// the semantics of a switch statement make in-place declarations awkward.
|
|
|
|
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
|
|
|
|
};
|
|
|
|
Microcycle read_program {
|
|
|
|
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsProgram
|
|
|
|
};
|
|
|
|
|
|
|
|
// Holding spot when awaiting DTACK/etc.
|
|
|
|
Microcycle awaiting_dtack;
|
|
|
|
|
|
|
|
// Spare containers:
|
2022-05-17 00:38:17 +00:00
|
|
|
uint32_t address = 0; // For an address, where it isn't provideable by reference.
|
|
|
|
HalfCycles delay; // To receive any additional time added on by calls to perform_bus_operation.
|
2022-05-16 20:57:40 +00:00
|
|
|
|
|
|
|
// Helper macros for common bus transactions:
|
|
|
|
|
|
|
|
// Performs the bus operation and then applies a `Spend` of its length
|
|
|
|
// plus any additional length returned by the bus handler.
|
|
|
|
#define PerformBusOperation(x) \
|
|
|
|
delay = bus_handler_.perform_bus_operation(x, is_supervisor_); \
|
|
|
|
Spend(x.length + delay)
|
|
|
|
|
|
|
|
// Performs no bus activity for the specified number of microcycles.
|
|
|
|
#define IdleBus(n) \
|
|
|
|
idle.length = HalfCycles(n * 4); \
|
|
|
|
PerformBusOperation(idle)
|
|
|
|
|
|
|
|
// Spin until DTACK, VPA or BERR is asserted (unless DTACK is implicit),
|
|
|
|
// holding the bus cycle provided.
|
|
|
|
#define WaitForDTACK(x) \
|
|
|
|
if constexpr (!dtack_is_implicit && !dtack_ && !vpa_ && !berr_) { \
|
|
|
|
awaiting_dtack = x; \
|
|
|
|
awaiting_dtack.length = HalfCycles(2); \
|
2022-05-17 00:38:17 +00:00
|
|
|
post_dtack_state_ = __COUNTER__+1; \
|
2022-05-16 20:57:40 +00:00
|
|
|
state_ = State::WaitForDTACK; \
|
|
|
|
break; \
|
|
|
|
} \
|
2022-05-17 00:38:17 +00:00
|
|
|
[[fallthrough]]; case __COUNTER__:
|
2022-05-16 20:57:40 +00:00
|
|
|
|
|
|
|
// Performs the bus operation provided, which will be one with a
|
|
|
|
// SelectWord or SelectByte operation, stretching it to match the E
|
|
|
|
// bus if VPA is currently asserted.
|
|
|
|
//
|
|
|
|
// TODO: If BERR is asserted, stop here and perform a bus error exception.
|
|
|
|
//
|
|
|
|
// TODO: If VPA is asserted, stretch this cycle.
|
|
|
|
#define CompleteAccess(x) \
|
|
|
|
PerformBusOperation(x)
|
|
|
|
|
|
|
|
// Performs the memory access implied by the announce, perform pair,
|
|
|
|
// honouring DTACK, BERR and VPA as necessary.
|
|
|
|
#define AccessPair(addr, val, announce, perform) \
|
|
|
|
perform.address = announce.address = &addr; \
|
|
|
|
perform.value = &val; \
|
|
|
|
if constexpr (!dtack_is_implicit) { \
|
|
|
|
announce.length = HalfCycles(4); \
|
|
|
|
} \
|
|
|
|
PerformBusOperation(announce); \
|
|
|
|
WaitForDTACK(announce); \
|
|
|
|
CompleteAccess(perform);
|
|
|
|
|
|
|
|
// Reads the data (i.e. non-program) word from addr into val.
|
|
|
|
#define ReadDataWord(addr, val) AccessPair(addr, val, read_word_data_announce, read_word_data)
|
|
|
|
|
|
|
|
// Reads the program (i.e. non-data) word from addr into val.
|
|
|
|
#define ReadProgramWord(val) \
|
|
|
|
AccessPair(program_counter_.l, val, read_program_announce, read_program); \
|
|
|
|
program_counter_.l += 2;
|
|
|
|
|
|
|
|
// Reads one futher word from the program counter and inserts it into
|
|
|
|
// the prefetch queue.
|
|
|
|
#define Prefetch() \
|
2022-05-17 12:26:35 +00:00
|
|
|
prefetch_.high = prefetch_.low; \
|
|
|
|
ReadProgramWord(prefetch_.low)
|
2022-05-16 20:57:40 +00:00
|
|
|
|
2022-05-16 15:59:03 +00:00
|
|
|
// Otherwise continue for all time, until back in debt.
|
|
|
|
// Formatting is slightly obtuse here to make this look more like a coroutine.
|
2022-05-16 20:57:40 +00:00
|
|
|
while(true) { switch(state_) {
|
|
|
|
|
|
|
|
// Spin in place, one cycle at a time, until one of DTACK,
|
|
|
|
// BERR or VPA is asserted.
|
|
|
|
case State::WaitForDTACK:
|
|
|
|
PerformBusOperation(awaiting_dtack);
|
|
|
|
|
|
|
|
if(dtack_ || berr_ || vpa_) {
|
|
|
|
state_ = post_dtack_state_;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// 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:
|
|
|
|
IdleBus(7); // (n-)*5 nn
|
|
|
|
|
2022-05-17 00:04:13 +00:00
|
|
|
address = 0; ReadDataWord(address, registers_[15].high); // nF
|
|
|
|
address += 2; ReadDataWord(address, registers_[15].low); // nf
|
2022-05-16 20:57:40 +00:00
|
|
|
address += 2; ReadDataWord(address, program_counter_.high); // nV
|
|
|
|
address += 2; ReadDataWord(address, program_counter_.low); // nv
|
|
|
|
|
|
|
|
Prefetch(); // np
|
|
|
|
IdleBus(1); // n
|
|
|
|
Prefetch(); // np
|
2022-05-16 15:59:03 +00:00
|
|
|
|
2022-05-17 12:26:35 +00:00
|
|
|
MoveToState(State::Decode);
|
2022-05-16 20:57:40 +00:00
|
|
|
break;
|
2022-05-16 15:59:03 +00:00
|
|
|
|
2022-05-17 12:26:35 +00:00
|
|
|
// Inspect the prefetch queue in order to decode the next instruction,
|
|
|
|
// and segue into the fetching of operands.
|
|
|
|
case State::Decode:
|
|
|
|
opcode_ = prefetch_.high.w;
|
|
|
|
instruction_ = decoder_.decode(opcode_);
|
|
|
|
|
|
|
|
// TODO: it might be better to switch on instruction_.operation
|
|
|
|
// and establish both the instruction pattern and the operand
|
|
|
|
// flags here?
|
|
|
|
operand_flags_ = InstructionSet::M68k::operand_flags<InstructionSet::M68k::Model::M68000>(instruction_.operation);
|
|
|
|
|
|
|
|
operand_ = 0;
|
|
|
|
[[fallthrough]];
|
|
|
|
|
|
|
|
// Check the operand flags to determine whether the operand at index
|
|
|
|
// operand_ needs to be fetched, and do so.
|
|
|
|
case State::FetchOperand:
|
|
|
|
|
|
|
|
[[fallthrough]];
|
2022-05-17 01:00:25 +00:00
|
|
|
default:
|
|
|
|
assert(false);
|
2022-05-16 15:59:03 +00:00
|
|
|
}}
|
|
|
|
|
2022-05-17 01:02:25 +00:00
|
|
|
#undef Prefetch
|
|
|
|
#undef ReadProgramWord
|
|
|
|
#undef ReadDataWord
|
|
|
|
#undef AccessPair
|
|
|
|
#undef CompleteAccess
|
|
|
|
#undef WaitForDTACK
|
|
|
|
#undef IdleBus
|
|
|
|
#undef PerformBusOperation
|
|
|
|
#undef MoveToState
|
2022-05-16 15:59:03 +00:00
|
|
|
#undef CheckOverrun
|
|
|
|
#undef Spend
|
|
|
|
#undef ConsiderExit
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-05-17 00:04:13 +00:00
|
|
|
template <class BusHandler, bool dtack_is_implicit, bool permit_overrun, bool signal_will_perform>
|
|
|
|
CPU::MC68000Mk2::State Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perform>::get_state() {
|
|
|
|
return CPU::MC68000Mk2::State();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class BusHandler, bool dtack_is_implicit, bool permit_overrun, bool signal_will_perform>
|
|
|
|
void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perform>::set_state(const CPU::MC68000Mk2::State &) {
|
|
|
|
}
|
|
|
|
|
2022-05-16 15:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* _8000Mk2Implementation_h */
|