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

Attempt to build fixed operations into type.

This simplifies callees and should make all helper functions automatically able to optimise themselves for fixed operations.
This commit is contained in:
Thomas Harte 2023-12-21 23:08:18 -05:00
parent 213dfe037d
commit 85f814c632
13 changed files with 207 additions and 195 deletions

View File

@ -80,13 +80,10 @@ class ConcreteMachine:
}
// MARK: - MC68000::BusHandler.
using Microcycle = CPU::MC68000::Microcycle;
template <Microcycle::OperationT op> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const auto operation = (op != CPU::MC68000::Operation::DecodeDynamically) ? op : cycle.operation;
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
HalfCycles total_length;
if(operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) {
if(cycle.operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) {
total_length = chipset_.run_until_after_cpu_slot().duration;
assert(total_length >= cycle.length);
} else {
@ -96,19 +93,19 @@ class ConcreteMachine:
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
// Check for assertion of reset.
if(operation & CPU::MC68000::Operation::Reset) {
if(cycle.operation & CPU::MC68000::Operation::Reset) {
memory_.reset();
LOG("Reset; PC is around " << PADHEX(8) << mc68000_.get_state().registers.program_counter);
}
// Autovector interrupts.
if(operation & CPU::MC68000::Operation::InterruptAcknowledge) {
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
mc68000_.set_is_peripheral_address(true);
return total_length - cycle.length;
}
// Do nothing if no address is exposed.
if(!(operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length;
if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length;
// Grab the target address to pick a memory source.
const uint32_t address = cycle.host_endian_byte_address();
@ -117,7 +114,7 @@ class ConcreteMachine:
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
if(!memory_.regions[address >> 18].read_write_mask) {
if((operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) {
if((cycle.operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) {
// Check for various potential chip accesses.
// Per the manual:
@ -135,7 +132,7 @@ class ConcreteMachine:
const bool select_a = !(address & 0x1000);
const bool select_b = !(address & 0x2000);
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
uint16_t result = 0xffff;
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
@ -157,13 +154,13 @@ class ConcreteMachine:
memory_.perform(cycle);
} else {
// This'll do for open bus, for now.
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(0xffff);
}
// Don't log for the region that is definitely just ROM this machine doesn't have.
if(address < 0xf0'0000) {
LOG("Unmapped " << (operation & CPU::MC68000::Operation::Read ? "read from " : "write to ") << PADHEX(6) << ((*cycle.address)&0xffffff) << " of " << cycle.value16());
LOG("Unmapped " << (cycle.operation & CPU::MC68000::Operation::Read ? "read from " : "write to ") << PADHEX(6) << ((*cycle.address)&0xffffff) << " of " << cycle.value16());
}
}
}

View File

@ -841,17 +841,6 @@ void Chipset::update_interrupts() {
}
}
void Chipset::perform(const CPU::MC68000::Microcycle &cycle) {
using Microcycle = CPU::MC68000::Microcycle;
const uint32_t register_address = *cycle.address & ChipsetAddressMask;
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(read(register_address));
} else {
write(register_address, cycle.value16());
}
}
void Chipset::write(uint32_t address, uint16_t value, bool allow_conversion) {
#define ApplySetClear(target, mask) { \
if(value & 0x8000) { \

View File

@ -58,7 +58,15 @@ class Chipset: private ClockingHint::Observer {
Changes run_until_after_cpu_slot();
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
void perform(const CPU::MC68000::Microcycle &);
template <typename Microcycle>
void perform(const Microcycle &cycle) {
const uint32_t register_address = *cycle.address & ChipsetAddressMask;
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(read(register_address));
} else {
write(register_address, cycle.value16());
}
}
/// Sets the current state of the CIA interrupt lines.
void set_cia_interrupts(bool cia_a, bool cia_b);

View File

@ -19,8 +19,8 @@ namespace Amiga {
class MemoryMap {
private:
static constexpr auto PermitRead = CPU::MC68000::Microcycle::PermitRead;
static constexpr auto PermitWrite = CPU::MC68000::Microcycle::PermitWrite;
static constexpr auto PermitRead = CPU::MC68000::Operation::PermitRead;
static constexpr auto PermitWrite = CPU::MC68000::Operation::PermitWrite;
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
public:
@ -109,7 +109,8 @@ class MemoryMap {
/// Performs the provided microcycle, which the caller guarantees to be a memory access,
/// and in the Zorro register range.
bool perform(const CPU::MC68000::Microcycle &cycle) {
template <typename Microcycle>
bool perform(const Microcycle &cycle) {
if(!fast_autoconf_visible_) return false;
const uint32_t register_address = *cycle.address & 0xfe;

View File

@ -191,15 +191,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
mc68000_.run_for(cycles);
}
using Microcycle = CPU::MC68000::Microcycle;
template <Microcycle::OperationT op> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const auto operation = (op != CPU::MC68000::Operation::DecodeDynamically) ? op : cycle.operation;
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
// Advance time.
advance_time(cycle.length);
// A null cycle leaves nothing else to do.
if(!(operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0);
if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0);
// Grab the address.
auto address = cycle.host_endian_byte_address();
@ -220,8 +217,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// for interrupt acknowledge cycles always has all bits set except the
// lowest explicit address lines.
if(
!CPU::MC68000::Operation::data_select_active(operation) ||
(operation & CPU::MC68000::Operation::InterruptAcknowledge)
!cycle.data_select_active() ||
(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge)
) return HalfCycles(0);
// Grab the word-precision address being accessed.
@ -231,18 +228,18 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
default: assert(false);
case BusDevice::Unassigned:
fill_unmapped<op>(cycle);
fill_unmapped(cycle);
return delay;
case BusDevice::VIA: {
if(*cycle.address & 1) {
fill_unmapped<op>(cycle);
fill_unmapped(cycle);
} else {
const int register_address = address >> 9;
// VIA accesses are via address 0xefe1fe + register*512,
// which at word precision is 0x77f0ff + register*256.
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_high(via_.read(register_address));
} else {
via_.write(register_address, cycle.value8_high());
@ -251,7 +248,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
} return delay;
case BusDevice::PhaseRead: {
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_low(phase_ & 7);
}
} return delay;
@ -261,13 +258,13 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
const int register_address = address >> 9;
// The IWM; this is a purely polled device, so can be run on demand.
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_low(iwm_->read(register_address));
} else {
iwm_->write(register_address, cycle.value8_low());
}
} else {
fill_unmapped<op>(cycle);
fill_unmapped(cycle);
}
} return delay;
@ -278,14 +275,14 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// Even accesses = read; odd = write.
if(*cycle.address & 1) {
// Odd access => this is a write. Data will be in the upper byte.
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
scsi_.write(register_address, 0xff, dma_acknowledge);
} else {
scsi_.write(register_address, cycle.value8_high());
}
} else {
// Even access => this is a read.
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_high(scsi_.read(register_address, dma_acknowledge));
}
}
@ -293,19 +290,19 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
case BusDevice::SCCReadResetPhase: {
// Any word access here adjusts phase.
if(operation & CPU::MC68000::Operation::SelectWord) {
if(cycle.operation & CPU::MC68000::Operation::SelectWord) {
adjust_phase();
} else {
// A0 = 1 => reset; A0 = 0 => read.
if(*cycle.address & 1) {
scc_.reset();
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(0xffff);
}
} else {
const auto read = scc_.read(int(address >> 1));
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_high(read);
}
}
@ -314,20 +311,20 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
case BusDevice::SCCWrite: {
// Any word access here adjusts phase.
if(operation & CPU::MC68000::Operation::SelectWord) {
if(cycle.operation & CPU::MC68000::Operation::SelectWord) {
adjust_phase();
} else {
// This is definitely a byte access; either it's to an odd address, in which
// case it will reach the SCC, or it isn't, in which case it won't.
if(*cycle.address & 1) {
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
scc_.write(int(address >> 1), 0xff);
cycle.value->b = 0xff;
} else {
scc_.write(int(address >> 1), cycle.value->b);
}
} else {
fill_unmapped<op>(cycle);
fill_unmapped(cycle);
}
}
} return delay;
@ -355,7 +352,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
} break;
case BusDevice::ROM: {
if(!(operation & CPU::MC68000::Operation::Read)) return delay;
if(!(cycle.operation & CPU::MC68000::Operation::Read)) return delay;
memory_base = rom_;
address &= rom_mask_;
} break;
@ -547,10 +544,9 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
++phase_;
}
template <Microcycle::OperationT op>
template <typename Microcycle>
forceinline void fill_unmapped(const Microcycle &cycle) {
const auto operation = (op != CPU::MC68000::Operation::DecodeDynamically) ? op : cycle.operation;
if(!(operation & CPU::MC68000::Operation::Read)) return;
if(!(cycle.operation & CPU::MC68000::Operation::Read)) return;
cycle.set_value16(0xffff);
}

View File

@ -176,10 +176,7 @@ class ConcreteMachine:
}
// MARK: MC68000::BusHandler
using Microcycle = CPU::MC68000::Microcycle;
template <Microcycle::OperationT op> HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
const auto operation = (op != CPU::MC68000::Operation::DecodeDynamically) ? op : cycle.operation;
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
// Just in case the last cycle was an interrupt acknowledge or bus error. TODO: find a better solution?
mc68000_.set_is_peripheral_address(false);
mc68000_.set_bus_error(false);
@ -188,15 +185,15 @@ class ConcreteMachine:
advance_time(cycle.length);
// Check for assertion of reset.
if(operation & CPU::MC68000::Operation::Reset) {
if(cycle.operation & CPU::MC68000::Operation::Reset) {
LOG("Unhandled Reset");
}
// A null cycle leaves nothing else to do.
if(!(operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0);
if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0);
// An interrupt acknowledge, perhaps?
if(operation & CPU::MC68000::Operation::InterruptAcknowledge) {
if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) {
// Current implementation: everything other than 6 (i.e. the MFP) is autovectored.
const int interrupt_level = cycle.word_address()&7;
if(interrupt_level != 6) {
@ -205,7 +202,7 @@ class ConcreteMachine:
mc68000_.set_is_peripheral_address(true);
return HalfCycles(0);
} else {
if(operation & CPU::MC68000::Operation::SelectByte) {
if(cycle.operation & CPU::MC68000::Operation::SelectByte) {
const int interrupt = mfp_->acknowledge_interrupt();
if(interrupt != Motorola::MFP68901::MFP68901::NoAcknowledgement) {
cycle.value->b = uint8_t(interrupt);
@ -222,7 +219,7 @@ class ConcreteMachine:
// If this is a new strobing of the address signal, test for bus error and pre-DTack delay.
HalfCycles delay(0);
if(operation & CPU::MC68000::Operation::NewAddress) {
if(cycle.operation & CPU::MC68000::Operation::NewAddress) {
// Bus error test.
if(
// Anything unassigned should generate a bus error.
@ -257,7 +254,7 @@ class ConcreteMachine:
case BusDevice::ROM:
memory = rom_.data();
if(!(operation & CPU::MC68000::Operation::Read)) {
if(!(cycle.operation & CPU::MC68000::Operation::Read)) {
return delay;
}
address -= rom_start_;
@ -271,7 +268,7 @@ class ConcreteMachine:
TOS 1.0 appears to attempt to read from the catridge before it has setup
the bus error vector. Therefore I assume no bus error flows.
*/
switch(operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
default: break;
case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read:
cycle.value->w = 0xffff;
@ -313,9 +310,9 @@ class ConcreteMachine:
case 0x8250: case 0x8252: case 0x8254: case 0x8256:
case 0x8258: case 0x825a: case 0x825c: case 0x825e:
case 0x8260: case 0x8262:
if(!CPU::MC68000::Operation::data_select_active(operation)) return delay;
if(!cycle.data_select_active()) return delay;
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(video_->read(int(address >> 1)));
} else {
video_->write(int(address >> 1), cycle.value16());
@ -324,9 +321,9 @@ class ConcreteMachine:
// DMA.
case 0x8604: case 0x8606: case 0x8608: case 0x860a: case 0x860c:
if(!CPU::MC68000::Operation::data_select_active(operation)) return delay;
if(!cycle.data_select_active()) return delay;
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value16(dma_->read(int(address >> 1)));
} else {
dma_->write(int(address >> 1), cycle.value16());
@ -356,12 +353,12 @@ class ConcreteMachine:
case 0x88d0: case 0x88d2: case 0x88d4: case 0x88d6: case 0x88d8: case 0x88da: case 0x88dc: case 0x88de:
case 0x88e0: case 0x88e2: case 0x88e4: case 0x88e6: case 0x88e8: case 0x88ea: case 0x88ec: case 0x88ee:
case 0x88f0: case 0x88f2: case 0x88f4: case 0x88f6: case 0x88f8: case 0x88fa: case 0x88fc: case 0x88fe:
if(!CPU::MC68000::Operation::data_select_active(operation)) return delay;
if(!cycle.data_select_active()) return delay;
advance_time(HalfCycles(2));
update_audio();
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_high(GI::AY38910::Utility::read(ay_));
} else {
// Net effect here: addresses with bit 1 set write to a register,
@ -379,9 +376,9 @@ class ConcreteMachine:
case 0xfa28: case 0xfa2a: case 0xfa2c: case 0xfa2e:
case 0xfa30: case 0xfa32: case 0xfa34: case 0xfa36:
case 0xfa38: case 0xfa3a: case 0xfa3c: case 0xfa3e:
if(!CPU::MC68000::Operation::data_select_active(operation)) return delay;
if(!cycle.data_select_active()) return delay;
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_low(mfp_->read(int(address >> 1)));
} else {
mfp_->write(int(address >> 1), cycle.value8_low());
@ -391,11 +388,11 @@ class ConcreteMachine:
// ACIAs.
case 0xfc00: case 0xfc02: case 0xfc04: case 0xfc06: {
// Set VPA.
mc68000_.set_is_peripheral_address(!CPU::MC68000::Operation::data_select_active(operation));
if(!CPU::MC68000::Operation::data_select_active(operation)) return delay;
mc68000_.set_is_peripheral_address(!cycle.data_select_active());
if(!cycle.data_select_active()) return delay;
const auto acia_ = (address & 4) ? &midi_acia_ : &keyboard_acia_;
if(operation & CPU::MC68000::Operation::Read) {
if(cycle.operation & CPU::MC68000::Operation::Read) {
cycle.set_value8_high((*acia_)->read(int(address >> 1)));
} else {
(*acia_)->write(int(address >> 1), cycle.value8_high());
@ -409,7 +406,7 @@ class ConcreteMachine:
//
// In both write cases, immediately reinstall the first eight bytes of RAM from ROM, so that any write to
// that area is in effect a no-op. This is cheaper than the conditionality of actually checking.
switch(operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) {
default:
break;

View File

@ -96,8 +96,8 @@ struct TestProcessor: public CPU::MC68000::BusHandler {
if(!instructions_remaining_) comparitor();
}
using Microcycle = CPU::MC68000::Microcycle;
template <Microcycle::OperationT op> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
if(cycle.data_select_active()) {
cycle.apply(&ram[cycle.host_endian_byte_address()]);
}

View File

@ -36,8 +36,7 @@ class EmuTOS: public ComparativeBusHandler {
return m68000_.get_state();
}
using Microcycle = CPU::MC68000::Microcycle;
template <Microcycle::OperationT op> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
template <typename Microcycle> perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;

View File

@ -39,8 +39,7 @@ class QL: public ComparativeBusHandler {
return m68000_.get_state();
}
using Microcycle = CPU::MC68000::Microcycle;
template <Microcycle::OperationT op> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;
@ -60,8 +59,7 @@ class QL: public ComparativeBusHandler {
if(cycle.data_select_active()) {
uint16_t peripheral_result = 0xffff;
const auto operation = (op != Microcycle::DecodeDynamically) ? op : cycle.operation;
switch(operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
default: break;
case Microcycle::SelectWord | Microcycle::Read:

View File

@ -78,8 +78,7 @@ class RAM68000: public CPU::MC68000::BusHandler {
return &ram_[(address >> 1) % ram_.size()];
}
using Microcycle = CPU::MC68000::Microcycle;
template <Microcycle::OperationT op> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
const uint32_t word_address = cycle.word_address();
duration_ += cycle.length;

View File

@ -13,6 +13,8 @@
#include "../../Numeric/RegisterSizes.hpp"
#include "../../InstructionSets/M68k/RegisterSet.hpp"
#include <cassert>
namespace CPU::MC68000 {
using OperationT = unsigned int;
@ -68,13 +70,23 @@ static constexpr OperationT BusGrant = 1 << 12;
/// .operation field.
static constexpr OperationT DecodeDynamically = NewAddress | SameAddress;
/*! @returns true if any data select line is active; @c false otherwise. */
constexpr bool data_select_active(OperationT operation) {
return bool(operation & (SelectWord | SelectByte | InterruptAcknowledge));
}
// PermitRead and PermitWrite are used as part of the read/write mask
// supplied to @c Microcycle::apply; they are picked to be small enough values that
// a byte can be used for storage.
static constexpr OperationT PermitRead = 1 << 3;
static constexpr OperationT PermitWrite = 1 << 4;
};
template <OperationT op>
struct MicrocycleOperationStorage {
static constexpr auto operation = op;
};
template <>
struct MicrocycleOperationStorage<Operation::DecodeDynamically> {
OperationT operation = 0;
};
/*!
A microcycle is an atomic unit of 68000 bus activity it is a single item large enough
fully to specify a sequence of bus events that occur without any possible interruption.
@ -98,13 +110,10 @@ constexpr bool data_select_active(OperationT operation) {
of an address-strobing microcycle, it can just supply those periods for accounting and
avoid the runtime cost of actual DTack emulation. But such as the bus allows.)
*/
struct Microcycle {
template <OperationT op = Operation::DecodeDynamically>
struct Microcycle: public MicrocycleOperationStorage<op> {
using OperationT = OperationT;
/// Contains a valid combination of the various static constexpr int flags, describing the operation
/// performed by this Microcycle.
OperationT operation = 0;
/// Describes the duration of this Microcycle.
HalfCycles length = HalfCycles(4);
@ -135,21 +144,46 @@ struct Microcycle {
*/
SlicedInt16 *value = nullptr;
Microcycle(OperationT operation) : operation(operation) {}
Microcycle(OperationT operation, HalfCycles length) : operation(operation), length(length) {}
Microcycle() {}
constexpr Microcycle() noexcept {}
constexpr Microcycle(OperationT dynamic_operation) noexcept {
if constexpr (op == Operation::DecodeDynamically) {
MicrocycleOperationStorage<op>::operation = dynamic_operation;
} else {
assert(MicrocycleOperationStorage<op>::operation == dynamic_operation);
}
}
constexpr Microcycle(OperationT dynamic_operation, HalfCycles length) noexcept :
Microcycle(dynamic_operation), length(length) {}
constexpr Microcycle(HalfCycles length) noexcept {
static_assert(op != Operation::DecodeDynamically);
this->length = length;
}
template <typename MicrocycleRHS>
Microcycle &operator =(const MicrocycleRHS &rhs) {
static_assert(op == Operation::DecodeDynamically);
/* TODO */
this->operation = rhs.operation;
return *this;
}
/// @returns @c true if two Microcycles are equal; @c false otherwise.
bool operator ==(const Microcycle &rhs) const {
if(value != rhs.value) return false;
if(address != rhs.address) return false;
if(length != rhs.length) return false;
if(operation != rhs.operation) return false;
if(this->operation != rhs.operation) return false;
return true;
}
// Various inspectors.
/*! @returns true if any data select line is active; @c false otherwise. */
bool data_select_active() const {
return bool(this->operation & (Operation::SelectWord | Operation::SelectByte | Operation::InterruptAcknowledge));
}
/*!
@returns 0 if this byte access wants the low part of a 16-bit word; 8 if it wants the high part,
i.e. take a word quantity and shift it right by this amount to get the quantity being
@ -190,14 +224,14 @@ struct Microcycle {
@returns non-zero if the 68000 LDS is asserted; zero otherwise.
*/
forceinline int lower_data_select() const {
return (operation & Operation::SelectByte & *address) | (operation & Operation::SelectWord);
return (this->operation & Operation::SelectByte & *address) | (this->operation & Operation::SelectWord);
}
/*!
@returns non-zero if the 68000 UDS is asserted; zero otherwise.
*/
forceinline int upper_data_select() const {
return (operation & Operation::SelectByte & ~*address) | (operation & Operation::SelectWord);
return (this->operation & Operation::SelectByte & ~*address) | (this->operation & Operation::SelectWord);
}
/*!
@ -232,9 +266,9 @@ struct Microcycle {
*/
forceinline uint32_t host_endian_byte_address() const {
#if TARGET_RT_BIG_ENDIAN
return *address & 0xffffff;
return *address & 0xff'ffff;
#else
return (*address ^ (operation & Operation::SelectByte)) & 0xffffff;
return (*address ^ (this->operation & Operation::SelectByte)) & 0xff'ffff;
#endif
}
@ -245,7 +279,7 @@ struct Microcycle {
*/
forceinline uint16_t value16() const {
const uint16_t values[] = { value->w, uint16_t((value->b << 8) | value->b) };
return values[operation & Operation::SelectByte];
return values[this->operation & Operation::SelectByte];
}
/*!
@ -253,7 +287,7 @@ struct Microcycle {
*/
forceinline uint8_t value8_high() const {
const uint8_t values[] = { uint8_t(value->w >> 8), value->b};
return values[operation & Operation::SelectByte];
return values[this->operation & Operation::SelectByte];
}
/*!
@ -268,8 +302,8 @@ struct Microcycle {
currently being read. Assumes this is a read cycle.
*/
forceinline void set_value16(uint16_t v) const {
assert(operation & Operation::Read);
if(operation & Operation::SelectWord) {
assert(this->operation & Operation::Read);
if(this->operation & Operation::SelectWord) {
value->w = v;
} else {
value->b = uint8_t(v >> byte_shift());
@ -280,8 +314,8 @@ struct Microcycle {
Equivalent to set_value16((v << 8) | 0x00ff).
*/
forceinline void set_value8_high(uint8_t v) const {
assert(operation & Operation::Read);
if(operation & Operation::SelectWord) {
assert(this->operation & Operation::Read);
if(this->operation & Operation::SelectWord) {
value->w = uint16_t(0x00ff | (v << 8));
} else {
value->b = uint8_t(v | byte_mask());
@ -292,20 +326,14 @@ struct Microcycle {
Equivalent to set_value16(v | 0xff00).
*/
forceinline void set_value8_low(uint8_t v) const {
assert(operation & Operation::Read);
if(operation & Operation::SelectWord) {
assert(this->operation & Operation::Read);
if(this->operation & Operation::SelectWord) {
value->w = 0xff00 | v;
} else {
value->b = uint8_t(v | untouched_byte_mask());
}
}
// PermitRead and PermitWrite are used as part of the read/write mask
// supplied to @c apply; they are picked to be small enough values that
// a byte can be used for storage.
static constexpr OperationT PermitRead = 1 << 3;
static constexpr OperationT PermitWrite = 1 << 4;
/*!
Assuming this to be a cycle with a data select active, applies it to @c target
subject to the read_write_mask, where 'applies' means:
@ -314,27 +342,27 @@ struct Microcycle {
* if this is a word read, reads a word (in the host platform's endianness) from @c target; and
* if this is a write, does the converse of a read.
*/
forceinline void apply(uint8_t *target, OperationT read_write_mask = PermitRead | PermitWrite) const {
assert( (operation & (Operation::SelectWord | Operation::SelectByte)) != (Operation::SelectWord | Operation::SelectByte));
forceinline void apply(uint8_t *target, OperationT read_write_mask = Operation::PermitRead | Operation::PermitWrite) const {
assert( (this->operation & (Operation::SelectWord | Operation::SelectByte)) != (Operation::SelectWord | Operation::SelectByte));
switch((operation | read_write_mask) & (Operation::SelectWord | Operation::SelectByte | Operation::Read | PermitRead | PermitWrite)) {
switch((this->operation | read_write_mask) & (Operation::SelectWord | Operation::SelectByte | Operation::Read | Operation::PermitRead | Operation::PermitWrite)) {
default:
break;
case Operation::SelectWord | Operation::Read | PermitRead:
case Operation::SelectWord | Operation::Read | PermitRead | PermitWrite:
case Operation::SelectWord | Operation::Read | Operation::PermitRead:
case Operation::SelectWord | Operation::Read | Operation::PermitRead | Operation::PermitWrite:
value->w = *reinterpret_cast<uint16_t *>(target);
break;
case Operation::SelectByte | Operation::Read | PermitRead:
case Operation::SelectByte | Operation::Read | PermitRead | PermitWrite:
case Operation::SelectByte | Operation::Read | Operation::PermitRead:
case Operation::SelectByte | Operation::Read | Operation::PermitRead | Operation::PermitWrite:
value->b = *target;
break;
case Operation::SelectWord | PermitWrite:
case Operation::SelectWord | PermitWrite | PermitRead:
case Operation::SelectWord | Operation::PermitWrite:
case Operation::SelectWord | Operation::PermitWrite | Operation::PermitRead:
*reinterpret_cast<uint16_t *>(target) = value->w;
break;
case Operation::SelectByte | PermitWrite:
case Operation::SelectByte | PermitWrite | PermitRead:
case Operation::SelectByte | Operation::PermitWrite:
case Operation::SelectByte | Operation::PermitWrite | Operation::PermitRead:
*target = value->b;
break;
}
@ -357,8 +385,8 @@ class BusHandler {
can be used to select an appropriate execution path at compile time. Otherwise
cycle.operation must be inspected at runtime.
*/
template <Microcycle::OperationT operation>
HalfCycles perform_bus_operation([[maybe_unused]] const Microcycle &cycle, [[maybe_unused]] int is_supervisor) {
template <typename Microcycle>
HalfCycles perform_bus_operation(const Microcycle &, [[maybe_unused]] int is_supervisor) {
return HalfCycles(0);
}

View File

@ -195,8 +195,8 @@ enum ExecutionState: int {
#undef AddressingDispatch
/// @returns The proper select lines for @c instruction's operand size, assuming it is either byte or word.
template <typename InstructionT> Microcycle::OperationT data_select(const InstructionT &instruction) {
return Microcycle::OperationT(1 << int(instruction.operand_size()));
template <typename InstructionT> OperationT data_select(const InstructionT &instruction) {
return OperationT(1 << int(instruction.operand_size()));
}
// MARK: - The state machine.
@ -282,8 +282,8 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Performs the bus operation and then applies a `Spend` of its length
// plus any additional length returned by the bus handler.
#define PerformBusOperation(x, op) \
delay = bus_handler_.template perform_bus_operation<op>(x, is_supervisor_); \
#define PerformBusOperation(x) \
delay = bus_handler_.perform_bus_operation(x, is_supervisor_); \
Spend(x.length + delay)
// TODO: the templated operation type to perform_bus_operation is intended to allow a much
@ -292,7 +292,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Performs no bus activity for the specified number of microcycles.
#define IdleBus(n) \
idle.length = HalfCycles((n) << 2); \
PerformBusOperation(idle, 0)
PerformBusOperation(idle)
// Spin until DTACK, VPA or BERR is asserted (unless DTACK is implicit),
// holding the bus cycle provided.
@ -315,7 +315,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
//
// (1) wait until end of current 10-cycle window;
// (2) run for the next 10-cycle window.
#define CompleteAccess(x, op) \
#define CompleteAccess(x) \
if(berr_) { \
RaiseBusOrAddressError(AccessFault, x); \
} \
@ -324,11 +324,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
} else { \
x.length = HalfCycles(4); \
} \
PerformBusOperation(x, op)
PerformBusOperation(x)
// Performs the memory access implied by the announce, perform pair,
// honouring DTACK, BERR and VPA as necessary.
#define AccessPair(val, announce, announce_op, perform, perform_op) \
#define AccessPair(val, announce, perform) \
perform.value = &val; \
if constexpr (!dtack_is_implicit) { \
announce.length = HalfCycles(4); \
@ -336,9 +336,9 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
if(*perform.address & (perform.operation >> 1) & 1) { \
RaiseBusOrAddressError(AddressError, perform); \
} \
PerformBusOperation(announce, announce_op); \
PerformBusOperation(announce); \
WaitForDTACK(announce); \
CompleteAccess(perform, perform_op);
CompleteAccess(perform);
// Sets up the next data access size and read flags.
#define SetupDataAccess(read_flag, select_flag) \
@ -351,15 +351,15 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Performs the access established by SetupDataAccess into val.
#define Access(val) \
AccessPair(val, access_announce, Operation::DecodeDynamically, access, Operation::DecodeDynamically)
AccessPair(val, access_announce, access)
// Performs the access established by SetupDataAccess into val.
#define AccessOp(val, read_flag, select_flag) \
AccessPair(val, access_announce, Operation::NewAddress | Operation::IsData | (read_flag), access, Operation::SameAddress | Operation::IsData | (read_flag) | (select_flag))
#define AccessOp(val) \
AccessPair(val, access_announce, access)
// Reads the program (i.e. non-data) word from addr into val.
#define ReadProgramWord(val) \
AccessPair(val, read_program_announce, ReadProgramAnnounceOperation, read_program, ReadProgramOperation); \
AccessPair(val, read_program_announce, read_program); \
program_counter_.l += 2;
// Reads one futher word from the program counter and inserts it into
@ -384,7 +384,7 @@ 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.
BeginState(WaitForDTACK):
PerformBusOperation(awaiting_dtack, 0);
PerformBusOperation(awaiting_dtack);
if(dtack_ || berr_ || vpa_) {
MoveToStateDynamic(post_dtack_state_);
@ -428,16 +428,16 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
SetDataAddress(temporary_address_.l);
temporary_address_.l = 0;
AccessOp(registers_[15].high, Operation::Read, Operation::SelectWord); // nF
AccessOp(registers_[15].high); // nF
temporary_address_.l += 2;
AccessOp(registers_[15].low, Operation::Read, Operation::SelectWord); // nf
AccessOp(registers_[15].low); // nf
temporary_address_.l += 2;
AccessOp(program_counter_.high, Operation::Read, Operation::SelectWord); // nV
AccessOp(program_counter_.high); // nV
temporary_address_.l += 2;
AccessOp(program_counter_.low, Operation::Read, Operation::SelectWord); // nv
AccessOp(program_counter_.low); // nv
Prefetch(); // np
IdleBus(1); // n
@ -457,13 +457,13 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Push status and current program counter.
// Write order is wacky here, but I think it's correct.
registers_[15].l -= 2;
AccessOp(instruction_address_.low, 0, Operation::SelectWord); // ns
AccessOp(instruction_address_.low); // ns
registers_[15].l -= 4;
AccessOp(captured_status_, 0, Operation::SelectWord); // ns
AccessOp(captured_status_); // ns
registers_[15].l += 2;
AccessOp(instruction_address_.high, 0, Operation::SelectWord); // nS
AccessOp(instruction_address_.high); // nS
registers_[15].l -= 2;
// Grab new program counter.
@ -471,10 +471,10 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
SetDataAddress(temporary_address_.l);
temporary_address_.l = uint32_t(exception_vector_ << 2);
AccessOp(program_counter_.high, Operation::Read, Operation::SelectWord); // nV
AccessOp(program_counter_.high); // nV
temporary_address_.l += 2;
AccessOp(program_counter_.low, Operation::Read, Operation::SelectWord); // nv
AccessOp(program_counter_.low); // nv
// Populate the prefetch queue.
Prefetch(); // np
@ -530,17 +530,17 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// COMPLETE GUESS.
temporary_address_.l = program_counter_.l - 4;
registers_[15].l -= 2;
AccessOp(temporary_address_.low, 0, Operation::SelectWord); // ns [pc.l]
AccessOp(temporary_address_.low); // ns [pc.l]
registers_[15].l -= 4;
AccessOp(captured_status_, 0, Operation::SelectWord); // ns [sr]
AccessOp(captured_status_); // ns [sr]
registers_[15].l += 2;
AccessOp(temporary_address_.high, 0, Operation::SelectWord); // nS [pc.h]
AccessOp(temporary_address_.high); // nS [pc.h]
registers_[15].l -= 4;
temporary_value_.w = opcode_;
AccessOp(temporary_value_.low, 0, Operation::SelectWord); // ns [instruction register]
AccessOp(temporary_value_.low); // ns [instruction register]
// Construct the function code; which is:
//
@ -561,13 +561,13 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
temporary_address_.l = *bus_error_.address;
registers_[15].l -= 2;
AccessOp(temporary_address_.low, 0, Operation::SelectWord); // ns [error address.l]
AccessOp(temporary_address_.low); // ns [error address.l]
registers_[15].l -= 4;
AccessOp(temporary_value_.low, 0, Operation::SelectWord); // ns [function code]
AccessOp(temporary_value_.low); // ns [function code]
registers_[15].l += 2;
AccessOp(temporary_address_.high, 0, Operation::SelectWord); // nS [error address.h]
AccessOp(temporary_address_.high); // nS [error address.h]
registers_[15].l -= 2;
// Grab new program counter.
@ -575,10 +575,10 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
SetDataAddress(temporary_address_.l);
temporary_address_.l = uint32_t(exception_vector_ << 2);
AccessOp(program_counter_.high, Operation::Read, Operation::SelectWord); // nV
AccessOp(program_counter_.high); // nV
temporary_address_.l += 2;
AccessOp(program_counter_.low, Operation::Read, Operation::SelectWord); // nv
AccessOp(program_counter_.low); // nv
// Populate the prefetch queue.
Prefetch(); // np
@ -602,14 +602,14 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Push low part of program counter.
registers_[15].l -= 2;
AccessOp(instruction_address_.low, 0, Operation::SelectWord); // ns
AccessOp(instruction_address_.low); // ns
// Do the interrupt cycle, to obtain a vector.
temporary_address_.l = 0xffff'fff1 | uint32_t(captured_interrupt_level_ << 1);
interrupt_cycles[0].address = interrupt_cycles[1].address = &temporary_address_.l;
interrupt_cycles[0].value = interrupt_cycles[1].value = &temporary_value_.low;
PerformBusOperation(interrupt_cycles[0], InterruptCycleOperations[0]);
CompleteAccess(interrupt_cycles[1], InterruptCycleOperations[1]); // ni
PerformBusOperation(interrupt_cycles[0]);
CompleteAccess(interrupt_cycles[1]); // ni
// If VPA is set, autovector.
if(vpa_) {
@ -628,10 +628,10 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
SetDataAddress(registers_[15].l);
registers_[15].l -= 4;
AccessOp(captured_status_, 0, Operation::SelectWord); // ns
AccessOp(captured_status_); // ns
registers_[15].l += 2;
AccessOp(instruction_address_.high, 0, Operation::SelectWord); // nS
AccessOp(instruction_address_.high); // nS
registers_[15].l -= 2;
// Grab new program counter.
@ -639,10 +639,10 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
SetDataAddress(temporary_address_.l);
temporary_address_.l = uint32_t(temporary_value_.b << 2);
AccessOp(program_counter_.high, Operation::Read, Operation::SelectWord); // nV
AccessOp(program_counter_.high); // nV
temporary_address_.l += 2;
AccessOp(program_counter_.low, Operation::Read, Operation::SelectWord); // nv
AccessOp(program_counter_.low); // nv
// Populate the prefetch queue.
Prefetch(); // np
@ -2634,11 +2634,11 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
tas_cycles[3].value = tas_cycles[4].value = &operand_[0].low;
// First two parts: the read.
PerformBusOperation(tas_cycles[0], TASOperations[0]);
CompleteAccess(tas_cycles[1], TASOperations[1]);
PerformBusOperation(tas_cycles[0]);
CompleteAccess(tas_cycles[1]);
// Third part: processing time.
PerformBusOperation(tas_cycles[2], TASOperations[2]);
PerformBusOperation(tas_cycles[2]);
// Do the actual TAS operation.
status_.overflow_flag = status_.carry_flag = 0;
@ -2647,8 +2647,8 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
// Final parts: write back.
operand_[0].b |= 0x80;
PerformBusOperation(tas_cycles[3], TASOperations[3]);
CompleteAccess(tas_cycles[4], TASOperations[4]);
PerformBusOperation(tas_cycles[3]);
CompleteAccess(tas_cycles[4]);
Prefetch();
MoveToStateSpecific(Decode);
@ -2762,7 +2762,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
//
BeginState(RESET):
IdleBus(2);
PerformBusOperation(reset_cycle, ResetOperation);
PerformBusOperation(reset_cycle);
Prefetch();
MoveToStateSpecific(Decode);
@ -3088,13 +3088,13 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
captured_interrupt_level_ = bus_interrupt_level_;
read_program.value = &prefetch_.high;
bus_handler_.template perform_bus_operation<ReadProgramAnnounceOperation>(read_program_announce, is_supervisor_);
bus_handler_.template perform_bus_operation<ReadProgramOperation>(read_program, is_supervisor_);
bus_handler_.perform_bus_operation(read_program_announce, is_supervisor_);
bus_handler_.perform_bus_operation(read_program, is_supervisor_);
program_counter_.l += 2;
read_program.value = &prefetch_.low;
bus_handler_.template perform_bus_operation<ReadProgramAnnounceOperation>(read_program_announce, is_supervisor_);
bus_handler_.template perform_bus_operation<ReadProgramOperation>(read_program, is_supervisor_);
bus_handler_.perform_bus_operation(read_program_announce, is_supervisor_);
bus_handler_.perform_bus_operation(read_program, is_supervisor_);
program_counter_.l += 2;
}

View File

@ -138,10 +138,10 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
/// Used by some dedicated read-modify-write perform patterns to
/// determine the size of the bus operation.
Microcycle::OperationT select_flag_ = 0;
OperationT select_flag_ = 0;
// Captured bus/address-error state.
Microcycle bus_error_;
Microcycle<Operation::DecodeDynamically> bus_error_;
// Flow controller methods implemented.
using Preinstruction = InstructionSet::M68k::Preinstruction;
@ -181,26 +181,26 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
// Some microcycles that will be modified as required and used in the main loop;
// the semantics of a switch statement make in-place declarations awkward and
// some of these may persist across multiple calls to run_for.
Microcycle idle{0};
Microcycle<OperationT(0)> idle;
// Read a program word. All accesses via the program counter are word sized.
static constexpr Microcycle::OperationT
static constexpr OperationT
ReadProgramAnnounceOperation = Operation::Read | Operation::NewAddress | Operation::IsProgram;
static constexpr Microcycle::OperationT
static constexpr OperationT
ReadProgramOperation = Operation::Read | Operation::SameAddress | Operation::SelectWord | Operation::IsProgram;
Microcycle read_program_announce { ReadProgramAnnounceOperation };
Microcycle read_program { ReadProgramOperation };
Microcycle<ReadProgramAnnounceOperation> read_program_announce{};
Microcycle<ReadProgramOperation> read_program{};
// Read a data word or byte.
Microcycle access_announce {
Microcycle<Operation::DecodeDynamically> access_announce {
Operation::Read | Operation::NewAddress | Operation::IsData
};
Microcycle access {
Microcycle<Operation::DecodeDynamically> access {
Operation::Read | Operation::SameAddress | Operation::SelectWord | Operation::IsData
};
// TAS.
static constexpr Microcycle::OperationT
static constexpr OperationT
TASOperations[5] = {
Operation::Read | Operation::NewAddress | Operation::IsData,
Operation::Read | Operation::SameAddress | Operation::IsData | Operation::SelectByte,
@ -208,7 +208,7 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
Operation::SameAddress | Operation::IsData,
Operation::SameAddress | Operation::IsData | Operation::SelectByte,
};
Microcycle tas_cycles[5] = {
Microcycle<Operation::DecodeDynamically> tas_cycles[5] = {
{ TASOperations[0] },
{ TASOperations[1] },
{ TASOperations[2] },
@ -217,22 +217,22 @@ struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
};
// Reset.
static constexpr Microcycle::OperationT ResetOperation = CPU::MC68000::Operation::Reset;
Microcycle reset_cycle { ResetOperation, HalfCycles(248) };
static constexpr OperationT ResetOperation = CPU::MC68000::Operation::Reset;
Microcycle<ResetOperation> reset_cycle { HalfCycles(248) };
// Interrupt acknowledge.
static constexpr Microcycle::OperationT
static constexpr OperationT
InterruptCycleOperations[2] = {
Operation::InterruptAcknowledge | Operation::Read | Operation::NewAddress,
Operation::InterruptAcknowledge | Operation::Read | Operation::SameAddress | Operation::SelectByte
};
Microcycle interrupt_cycles[2] = {
Microcycle<Operation::DecodeDynamically> interrupt_cycles[2] = {
{ InterruptCycleOperations[0] },
{ InterruptCycleOperations[1] },
};
// Holding spot when awaiting DTACK/etc.
Microcycle awaiting_dtack;
Microcycle<Operation::DecodeDynamically> awaiting_dtack;
};
}