diff --git a/Machines/Amiga/Amiga.cpp b/Machines/Amiga/Amiga.cpp index 775e89695..d6e314f80 100644 --- a/Machines/Amiga/Amiga.cpp +++ b/Machines/Amiga/Amiga.cpp @@ -80,13 +80,10 @@ class ConcreteMachine: } // MARK: - MC68000::BusHandler. - using Microcycle = CPU::MC68000::Microcycle; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - const auto operation = (op != Microcycle::DecodeDynamically) ? op : cycle.operation; - + template 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 & Microcycle::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 & Microcycle::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 & Microcycle::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 & (Microcycle::NewAddress | Microcycle::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 & (Microcycle::SelectByte | Microcycle::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 & Microcycle::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 & Microcycle::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 & Microcycle::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()); } } } diff --git a/Machines/Amiga/Chipset.cpp b/Machines/Amiga/Chipset.cpp index 534486e9d..80c5dc683 100644 --- a/Machines/Amiga/Chipset.cpp +++ b/Machines/Amiga/Chipset.cpp @@ -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 & Microcycle::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) { \ diff --git a/Machines/Amiga/Chipset.hpp b/Machines/Amiga/Chipset.hpp index 5b3ac8b10..5f1a2c3dd 100644 --- a/Machines/Amiga/Chipset.hpp +++ b/Machines/Amiga/Chipset.hpp @@ -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 + 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); diff --git a/Machines/Amiga/Disk.cpp b/Machines/Amiga/Disk.cpp index f2f667899..dcaa51571 100644 --- a/Machines/Amiga/Disk.cpp +++ b/Machines/Amiga/Disk.cpp @@ -175,8 +175,8 @@ void Chipset::DiskController::set_mtr_sel_side_dir_step(uint8_t value) { const bool is_selected = !(value & select_mask); // Both the motor state and the ID shifter are affected upon - // changes in drive selection only. - if(difference & select_mask) { + // the trailing edge of changes in drive selection only. + if(difference & select_mask & ~value) { // If transitioning to inactive, shift the drive ID value; // if transitioning to active, possibly reset the drive // ID and definitely latch the new motor state. diff --git a/Machines/Amiga/MemoryMap.hpp b/Machines/Amiga/MemoryMap.hpp index 9e6c8fdf1..91f2de5ff 100644 --- a/Machines/Amiga/MemoryMap.hpp +++ b/Machines/Amiga/MemoryMap.hpp @@ -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,13 +109,13 @@ 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 + bool perform(const Microcycle &cycle) { if(!fast_autoconf_visible_) return false; const uint32_t register_address = *cycle.address & 0xfe; - using Microcycle = CPU::MC68000::Microcycle; - if(cycle.operation & Microcycle::Read) { + if(cycle.operation & CPU::MC68000::Operation::Read) { // Re: Autoconf: // // "All read registers physically return only the top 4 bits of data, on D31-D28"; diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 24d6e9623..ccbb589d5 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -191,15 +191,12 @@ template class ConcreteMachin mc68000_.run_for(cycles); } - using Microcycle = CPU::MC68000::Microcycle; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - const auto operation = (op != Microcycle::DecodeDynamically) ? op : cycle.operation; - + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { // Advance time. advance_time(cycle.length); // A null cycle leaves nothing else to do. - if(!(operation & (Microcycle::NewAddress | Microcycle::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(); @@ -219,7 +216,10 @@ template class ConcreteMachin // having set VPA above deals with those given that the generated address // for interrupt acknowledge cycles always has all bits set except the // lowest explicit address lines. - if(!cycle.data_select_active() || (operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0); + if( + !cycle.data_select_active() || + (cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) + ) return HalfCycles(0); // Grab the word-precision address being accessed. uint8_t *memory_base = nullptr; @@ -228,18 +228,18 @@ template class ConcreteMachin default: assert(false); case BusDevice::Unassigned: - fill_unmapped(cycle); + fill_unmapped(cycle); return delay; case BusDevice::VIA: { if(*cycle.address & 1) { - fill_unmapped(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 & Microcycle::Read) { + if(cycle.operation & CPU::MC68000::Operation::Read) { cycle.set_value8_high(via_.read(register_address)); } else { via_.write(register_address, cycle.value8_high()); @@ -248,7 +248,7 @@ template class ConcreteMachin } return delay; case BusDevice::PhaseRead: { - if(operation & Microcycle::Read) { + if(cycle.operation & CPU::MC68000::Operation::Read) { cycle.set_value8_low(phase_ & 7); } } return delay; @@ -258,13 +258,13 @@ template class ConcreteMachin const int register_address = address >> 9; // The IWM; this is a purely polled device, so can be run on demand. - if(operation & Microcycle::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(cycle); + fill_unmapped(cycle); } } return delay; @@ -275,14 +275,14 @@ template 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 & Microcycle::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 & Microcycle::Read) { + if(cycle.operation & CPU::MC68000::Operation::Read) { cycle.set_value8_high(scsi_.read(register_address, dma_acknowledge)); } } @@ -290,19 +290,19 @@ template class ConcreteMachin case BusDevice::SCCReadResetPhase: { // Any word access here adjusts phase. - if(operation & Microcycle::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 & Microcycle::Read) { + if(cycle.operation & CPU::MC68000::Operation::Read) { cycle.set_value16(0xffff); } } else { const auto read = scc_.read(int(address >> 1)); - if(operation & Microcycle::Read) { + if(cycle.operation & CPU::MC68000::Operation::Read) { cycle.set_value8_high(read); } } @@ -311,20 +311,20 @@ template class ConcreteMachin case BusDevice::SCCWrite: { // Any word access here adjusts phase. - if(operation & Microcycle::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 & Microcycle::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(cycle); + fill_unmapped(cycle); } } } return delay; @@ -352,7 +352,7 @@ template class ConcreteMachin } break; case BusDevice::ROM: { - if(!(operation & Microcycle::Read)) return delay; + if(!(cycle.operation & CPU::MC68000::Operation::Read)) return delay; memory_base = rom_; address &= rom_mask_; } break; @@ -544,10 +544,9 @@ template class ConcreteMachin ++phase_; } - template + template forceinline void fill_unmapped(const Microcycle &cycle) { - const auto operation = (op != Microcycle::DecodeDynamically) ? op : cycle.operation; - if(!(operation & Microcycle::Read)) return; + if(!(cycle.operation & CPU::MC68000::Operation::Read)) return; cycle.set_value16(0xffff); } diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index 935d26b63..84c74aa2c 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -176,10 +176,7 @@ class ConcreteMachine: } // MARK: MC68000::BusHandler - using Microcycle = CPU::MC68000::Microcycle; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { - const auto operation = (op != Microcycle::DecodeDynamically) ? op : cycle.operation; - + template 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 & Microcycle::Reset) { + if(cycle.operation & CPU::MC68000::Operation::Reset) { LOG("Unhandled Reset"); } // A null cycle leaves nothing else to do. - if(!(operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); + if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); // An interrupt acknowledge, perhaps? - if(operation & Microcycle::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 & Microcycle::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 & Microcycle::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 & Microcycle::Read)) { + if(!(cycle.operation & CPU::MC68000::Operation::Read)) { return delay; } address -= rom_start_; @@ -271,12 +268,12 @@ 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 & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { default: break; - case Microcycle::SelectWord | Microcycle::Read: + case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: cycle.value->w = 0xffff; break; - case Microcycle::SelectByte | Microcycle::Read: + case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: cycle.value->b = 0xff; break; } @@ -315,7 +312,7 @@ class ConcreteMachine: case 0x8260: case 0x8262: if(!cycle.data_select_active()) return delay; - if(operation & Microcycle::Read) { + if(cycle.operation & CPU::MC68000::Operation::Read) { cycle.set_value16(video_->read(int(address >> 1))); } else { video_->write(int(address >> 1), cycle.value16()); @@ -326,7 +323,7 @@ class ConcreteMachine: case 0x8604: case 0x8606: case 0x8608: case 0x860a: case 0x860c: if(!cycle.data_select_active()) return delay; - if(operation & Microcycle::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,13 +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(!cycle.data_select_active()) return delay; advance_time(HalfCycles(2)); update_audio(); - if(operation & Microcycle::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, @@ -382,7 +378,7 @@ class ConcreteMachine: case 0xfa38: case 0xfa3a: case 0xfa3c: case 0xfa3e: if(!cycle.data_select_active()) return delay; - if(operation & Microcycle::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()); @@ -396,7 +392,7 @@ class ConcreteMachine: if(!cycle.data_select_active()) return delay; const auto acia_ = (address & 4) ? &midi_acia_ : &keyboard_acia_; - if(operation & Microcycle::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()); @@ -410,23 +406,23 @@ 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 & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { default: break; - case Microcycle::SelectWord | Microcycle::Read: + case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: cycle.value->w = *reinterpret_cast(&memory[address]); break; - case Microcycle::SelectByte | Microcycle::Read: + case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: cycle.value->b = memory[address]; break; - case Microcycle::SelectWord: + case CPU::MC68000::Operation::SelectWord: if(address >= video_range_.low_address && address < video_range_.high_address) video_.flush(); *reinterpret_cast(&memory[address]) = cycle.value->w; reinstall_rom_vector(); break; - case Microcycle::SelectByte: + case CPU::MC68000::Operation::SelectByte: if(address >= video_range_.low_address && address < video_range_.high_address) video_.flush(); memory[address] = cycle.value->b; diff --git a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm index 34ca473c4..c58f58b44 100644 --- a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm +++ b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm @@ -96,8 +96,8 @@ struct TestProcessor: public CPU::MC68000::BusHandler { if(!instructions_remaining_) comparitor(); } - using Microcycle = CPU::MC68000::Microcycle; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { if(cycle.data_select_active()) { cycle.apply(&ram[cycle.host_endian_byte_address()]); } diff --git a/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm index 343b61399..698ef7913 100644 --- a/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm +++ b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm @@ -36,8 +36,7 @@ class EmuTOS: public ComparativeBusHandler { return m68000_.get_state(); } - using Microcycle = CPU::MC68000::Microcycle; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + template perform_bus_operation(const Microcycle &cycle, int) { const uint32_t address = cycle.word_address(); uint32_t word_address = address; diff --git a/OSBindings/Mac/Clock SignalTests/QLTests.mm b/OSBindings/Mac/Clock SignalTests/QLTests.mm index 5ac39ff1d..9c656480d 100644 --- a/OSBindings/Mac/Clock SignalTests/QLTests.mm +++ b/OSBindings/Mac/Clock SignalTests/QLTests.mm @@ -39,8 +39,7 @@ class QL: public ComparativeBusHandler { return m68000_.get_state(); } - using Microcycle = CPU::MC68000::Microcycle; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + template 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: diff --git a/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp b/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp index 973251c49..d6fa8e94f 100644 --- a/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp +++ b/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp @@ -78,8 +78,7 @@ class RAM68000: public CPU::MC68000::BusHandler { return &ram_[(address >> 1) % ram_.size()]; } - using Microcycle = CPU::MC68000::Microcycle; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { const uint32_t word_address = cycle.word_address(); duration_ += cycle.length; diff --git a/Processors/68000/68000.hpp b/Processors/68000/68000.hpp index ba6732710..c6d1b1b54 100644 --- a/Processors/68000/68000.hpp +++ b/Processors/68000/68000.hpp @@ -13,8 +13,79 @@ #include "../../Numeric/RegisterSizes.hpp" #include "../../InstructionSets/M68k/RegisterSet.hpp" +#include + namespace CPU::MC68000 { +using OperationT = unsigned int; + +namespace Operation { + +/// Indicates that the address strobe and exactly one of the data strobes are active; you can determine +/// which by inspecting the low bit of the provided address. The RW line indicates a read. +static constexpr OperationT SelectByte = 1 << 0; +// Maintenance note: this is bit 0 to reduce the cost of getting a host-endian +// bytewise address. The assumption that it is bit 0 is also used for branchless +// selection in a few places. See implementation of host_endian_byte_address(), +// value8_high(), value8_low() and value16(). + +/// Indicates that the address and both data select strobes are active. +static constexpr OperationT SelectWord = 1 << 1; + +/// If set, indicates a read. Otherwise, a write. +static constexpr OperationT Read = 1 << 2; + +// Two-bit gap deliberately left here for PermitRead/Write below; these are not +// real 68000 signals, they're to do with internal manipulation only. + +/// A NewAddress cycle is one in which the address strobe is initially low but becomes high; +/// this correlates to states 0 to 5 of a standard read/write cycle. +static constexpr OperationT NewAddress = 1 << 5; + +/// A SameAddress cycle is one in which the address strobe is continuously asserted, but neither +/// of the data strobes are. +static constexpr OperationT SameAddress = 1 << 6; + +/// A Reset cycle is one in which the RESET output is asserted. +static constexpr OperationT Reset = 1 << 7; + +/// Contains the value of line FC0 if it is not implicit via InterruptAcknowledge. +static constexpr OperationT IsData = 1 << 8; + +/// Contains the value of line FC1 if it is not implicit via InterruptAcknowledge. +static constexpr OperationT IsProgram = 1 << 9; + +/// The interrupt acknowledge cycle is that during which the 68000 seeks to obtain the vector for +/// an interrupt it plans to observe. Noted on a real 68000 by all FCs being set to 1. +static constexpr OperationT InterruptAcknowledge = 1 << 10; + +/// Represents the state of the 68000's valid memory address line — indicating whether this microcycle +/// is synchronised with the E clock to satisfy a valid peripheral address request. +static constexpr OperationT IsPeripheral = 1 << 11; + +/// Provides the 68000's bus grant line — indicating whether a bus request has been acknowledged. +static constexpr OperationT BusGrant = 1 << 12; + +/// An otherwise invalid combination; used as an implementation detail elsewhere. Shouldn't be exposed. +static constexpr OperationT DecodeDynamically = NewAddress | SameAddress; + +// 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 +struct MicrocycleOperationStorage { + static constexpr auto operation = op; +}; +template <> +struct MicrocycleOperationStorage { + 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. @@ -38,61 +109,12 @@ namespace CPU::MC68000 { 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 { - using OperationT = unsigned int; - - /// Indicates that the address strobe and exactly one of the data strobes are active; you can determine - /// which by inspecting the low bit of the provided address. The RW line indicates a read. - static constexpr OperationT SelectByte = 1 << 0; - // Maintenance note: this is bit 0 to reduce the cost of getting a host-endian - // bytewise address. The assumption that it is bit 0 is also used for branchless - // selection in a few places. See implementation of host_endian_byte_address(), - // value8_high(), value8_low() and value16(). - - /// Indicates that the address and both data select strobes are active. - static constexpr OperationT SelectWord = 1 << 1; - - /// If set, indicates a read. Otherwise, a write. - static constexpr OperationT Read = 1 << 2; - - // Two-bit gap deliberately left here for PermitRead/Write below. - - /// A NewAddress cycle is one in which the address strobe is initially low but becomes high; - /// this correlates to states 0 to 5 of a standard read/write cycle. - static constexpr OperationT NewAddress = 1 << 5; - - /// A SameAddress cycle is one in which the address strobe is continuously asserted, but neither - /// of the data strobes are. - static constexpr OperationT SameAddress = 1 << 6; - - /// A Reset cycle is one in which the RESET output is asserted. - static constexpr OperationT Reset = 1 << 7; - - /// Contains the value of line FC0 if it is not implicit via InterruptAcknowledge. - static constexpr OperationT IsData = 1 << 8; - - /// Contains the value of line FC1 if it is not implicit via InterruptAcknowledge. - static constexpr OperationT IsProgram = 1 << 9; - - /// The interrupt acknowledge cycle is that during which the 68000 seeks to obtain the vector for - /// an interrupt it plans to observe. Noted on a real 68000 by all FCs being set to 1. - static constexpr OperationT InterruptAcknowledge = 1 << 10; - - /// Represents the state of the 68000's valid memory address line — indicating whether this microcycle - /// is synchronised with the E clock to satisfy a valid peripheral address request. - static constexpr OperationT IsPeripheral = 1 << 11; - - /// Provides the 68000's bus grant line — indicating whether a bus request has been acknowledged. - static constexpr OperationT BusGrant = 1 << 12; - - /// An otherwise invalid combination; used as the operaiton template parameter to @c perform_bus_operation if - /// the operation wasn't knowable in advance and the receiver should decode dynamically using the microcycle's - /// .operation field. - static constexpr OperationT DecodeDynamically = NewAddress | SameAddress; - - /// Contains a valid combination of the various static constexpr int flags, describing the operation - /// performed by this Microcycle. - OperationT operation = 0; +template +struct Microcycle: public MicrocycleOperationStorage { + // One of the following is also exposed here via inheritance: + // + // static constexpr OperationT operation; or + // OperationT operation; /// Describes the duration of this Microcycle. HalfCycles length = HalfCycles(4); @@ -124,24 +146,48 @@ 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::operation = dynamic_operation; + } else { + assert(MicrocycleOperationStorage::operation == dynamic_operation); + } + } + constexpr Microcycle(OperationT dynamic_operation, HalfCycles length) noexcept : Microcycle(dynamic_operation) { + this->length = length; + } + constexpr Microcycle(HalfCycles length) noexcept { + static_assert(op != Operation::DecodeDynamically); + this->length = length; + } + + template + Microcycle &operator =(const MicrocycleRHS &rhs) { + static_assert(op == Operation::DecodeDynamically); + this->operation = rhs.operation; + this->value = rhs.value; + this->address = rhs.address; + this->length = rhs.length; + + return *this; + } /// @returns @c true if two Microcycles are equal; @c false otherwise. - bool operator ==(const Microcycle &rhs) const { + template + bool operator ==(const MicrocycleRHS &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. */ - forceinline bool data_select_active() const { - return bool(operation & (SelectWord | SelectByte | InterruptAcknowledge)); + bool data_select_active() const { + return bool(this->operation & (Operation::SelectWord | Operation::SelectByte | Operation::InterruptAcknowledge)); } /*! @@ -184,14 +230,14 @@ struct Microcycle { @returns non-zero if the 68000 LDS is asserted; zero otherwise. */ forceinline int lower_data_select() const { - return (operation & SelectByte & *address) | (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 & SelectByte & ~*address) | (operation & SelectWord); + return (this->operation & Operation::SelectByte & ~*address) | (this->operation & Operation::SelectWord); } /*! @@ -226,9 +272,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 & SelectByte)) & 0xffffff; + return (*address ^ (this->operation & Operation::SelectByte)) & 0xff'ffff; #endif } @@ -239,7 +285,7 @@ struct Microcycle { */ forceinline uint16_t value16() const { const uint16_t values[] = { value->w, uint16_t((value->b << 8) | value->b) }; - return values[operation & SelectByte]; + return values[this->operation & Operation::SelectByte]; } /*! @@ -247,7 +293,7 @@ struct Microcycle { */ forceinline uint8_t value8_high() const { const uint8_t values[] = { uint8_t(value->w >> 8), value->b}; - return values[operation & SelectByte]; + return values[this->operation & Operation::SelectByte]; } /*! @@ -262,8 +308,8 @@ struct Microcycle { currently being read. Assumes this is a read cycle. */ forceinline void set_value16(uint16_t v) const { - assert(operation & Microcycle::Read); - if(operation & Microcycle::SelectWord) { + assert(this->operation & Operation::Read); + if(this->operation & Operation::SelectWord) { value->w = v; } else { value->b = uint8_t(v >> byte_shift()); @@ -274,8 +320,8 @@ struct Microcycle { Equivalent to set_value16((v << 8) | 0x00ff). */ forceinline void set_value8_high(uint8_t v) const { - assert(operation & Microcycle::Read); - if(operation & Microcycle::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()); @@ -286,49 +332,46 @@ struct Microcycle { Equivalent to set_value16(v | 0xff00). */ forceinline void set_value8_low(uint8_t v) const { - assert(operation & Microcycle::Read); - if(operation & Microcycle::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: + subject to the @c read_write_mask, where 'applies' means: * if this is a byte read, reads a single byte from @c target; * 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 & (SelectWord | SelectByte)) != (SelectWord | 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) & (SelectWord | SelectByte | Read | PermitRead | PermitWrite)) { + switch( + (this->operation | read_write_mask) & + (Operation::SelectWord | Operation::SelectByte | Operation::Read | Operation::PermitRead | Operation::PermitWrite) + ) { default: break; - case SelectWord | Read | PermitRead: - case SelectWord | Read | PermitRead | PermitWrite: + case Operation::SelectWord | Operation::Read | Operation::PermitRead: + case Operation::SelectWord | Operation::Read | Operation::PermitRead | Operation::PermitWrite: value->w = *reinterpret_cast(target); break; - case SelectByte | Read | PermitRead: - case SelectByte | Read | PermitRead | PermitWrite: + case Operation::SelectByte | Operation::Read | Operation::PermitRead: + case Operation::SelectByte | Operation::Read | Operation::PermitRead | Operation::PermitWrite: value->b = *target; break; - case SelectWord | PermitWrite: - case SelectWord | PermitWrite | PermitRead: + case Operation::SelectWord | Operation::PermitWrite: + case Operation::SelectWord | Operation::PermitWrite | Operation::PermitRead: *reinterpret_cast(target) = value->w; break; - case SelectByte | PermitWrite: - case SelectByte | PermitWrite | PermitRead: + case Operation::SelectByte | Operation::PermitWrite: + case Operation::SelectByte | Operation::PermitWrite | Operation::PermitRead: *target = value->b; break; } @@ -345,14 +388,13 @@ class BusHandler { Provides the bus handler with a single Microcycle to 'perform'. FC0 and FC1 are provided inside the microcycle as the IsData and IsProgram - flags; FC2 is provided here as is_supervisor — it'll be either 0 or 1. + flags; FC2 is provided here as @c is_supervisor — it'll be either 0 or 1. - If @c operation is any value other than Microcycle::DecodeDynamically then it - can be used to select an appropriate execution path at compile time. Otherwise - cycle.operation must be inspected at runtime. + The @c Microcycle might be any instantiation of @c Microcycle above; + whether with a static constexpr operation or with a runtime-selected one. */ - template - HalfCycles perform_bus_operation([[maybe_unused]] const Microcycle &cycle, [[maybe_unused]] int is_supervisor) { + template + HalfCycles perform_bus_operation(const Microcycle &, [[maybe_unused]] int is_supervisor) { return HalfCycles(0); } diff --git a/Processors/68000/Implementation/68000Implementation.hpp b/Processors/68000/Implementation/68000Implementation.hpp index 6c217d533..6ae77f3b4 100644 --- a/Processors/68000/Implementation/68000Implementation.hpp +++ b/Processors/68000/Implementation/68000Implementation.hpp @@ -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 Microcycle::OperationT data_select(const InstructionT &instruction) { - return Microcycle::OperationT(1 << int(instruction.operand_size())); +template OperationT data_select(const InstructionT &instruction) { + return OperationT(1 << int(instruction.operand_size())); } // MARK: - The state machine. @@ -282,8 +282,8 @@ void Processor(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> 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) \ - access_announce.operation = Microcycle::NewAddress | Microcycle::IsData | (read_flag); \ - access.operation = Microcycle::SameAddress | Microcycle::IsData | (read_flag) | (select_flag); + access_announce.operation = Operation::NewAddress | Operation::IsData | (read_flag); \ + access.operation = Operation::SameAddress | Operation::IsData | (read_flag) | (select_flag); // Sets the address source for the next data access. #define SetDataAddress(addr) \ @@ -351,15 +351,15 @@ void Processor() == InstructionSet::M68k::DataSize::LongWord) { \ - SetupDataAccess(Microcycle::Read, Microcycle::SelectWord); \ + SetupDataAccess(Operation::Read, Operation::SelectWord); \ MoveToStateSpecific(FetchOperand_l); \ } else { \ if constexpr (InstructionSet::M68k::operand_size() == InstructionSet::M68k::DataSize::Byte) { \ - SetupDataAccess(Microcycle::Read, Microcycle::SelectByte); \ + SetupDataAccess(Operation::Read, Operation::SelectByte); \ } else { \ - SetupDataAccess(Microcycle::Read, Microcycle::SelectWord); \ + SetupDataAccess(Operation::Read, Operation::SelectWord); \ } \ MoveToStateSpecific(FetchOperand_bw); \ } @@ -823,10 +823,10 @@ void Processor(read_program_announce, is_supervisor_); - bus_handler_.template perform_bus_operation(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(read_program_announce, is_supervisor_); - bus_handler_.template perform_bus_operation(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; } diff --git a/Processors/68000/Implementation/68000Storage.hpp b/Processors/68000/Implementation/68000Storage.hpp index 47626d90e..948b7d49f 100644 --- a/Processors/68000/Implementation/68000Storage.hpp +++ b/Processors/68000/Implementation/68000Storage.hpp @@ -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 bus_error_; // Flow controller methods implemented. using Preinstruction = InstructionSet::M68k::Preinstruction; @@ -181,58 +181,54 @@ 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 idle; // Read a program word. All accesses via the program counter are word sized. - static constexpr Microcycle::OperationT - ReadProgramAnnounceOperation = Microcycle::Read | Microcycle::NewAddress | Microcycle::IsProgram; - static constexpr Microcycle::OperationT - ReadProgramOperation = Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsProgram; - Microcycle read_program_announce { ReadProgramAnnounceOperation }; - Microcycle read_program { ReadProgramOperation }; + static constexpr OperationT + ReadProgramAnnounceOperation = Operation::Read | Operation::NewAddress | Operation::IsProgram; + static constexpr OperationT + ReadProgramOperation = Operation::Read | Operation::SameAddress | Operation::SelectWord | Operation::IsProgram; + Microcycle read_program_announce{}; + Microcycle read_program{}; // Read a data word or byte. - Microcycle access_announce { - Microcycle::Read | Microcycle::NewAddress | Microcycle::IsData + Microcycle access_announce { + Operation::Read | Operation::NewAddress | Operation::IsData }; - Microcycle access { - Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsData + Microcycle access { + Operation::Read | Operation::SameAddress | Operation::SelectWord | Operation::IsData }; // TAS. - static constexpr Microcycle::OperationT + static constexpr OperationT TASOperations[5] = { - Microcycle::Read | Microcycle::NewAddress | Microcycle::IsData, - Microcycle::Read | Microcycle::SameAddress | Microcycle::IsData | Microcycle::SelectByte, - Microcycle::SameAddress, - Microcycle::SameAddress | Microcycle::IsData, - Microcycle::SameAddress | Microcycle::IsData | Microcycle::SelectByte, + Operation::Read | Operation::NewAddress | Operation::IsData, + Operation::Read | Operation::SameAddress | Operation::IsData | Operation::SelectByte, + Operation::SameAddress, + Operation::SameAddress | Operation::IsData, + Operation::SameAddress | Operation::IsData | Operation::SelectByte, }; - Microcycle tas_cycles[5] = { - { TASOperations[0] }, - { TASOperations[1] }, - { TASOperations[2] }, - { TASOperations[3] }, - { TASOperations[4] }, - }; + Microcycle tas_cycle0; + Microcycle tas_cycle1; + Microcycle tas_cycle2; + Microcycle tas_cycle3; + Microcycle tas_cycle4; // Reset. - static constexpr Microcycle::OperationT ResetOperation = Microcycle::Reset; - Microcycle reset_cycle { ResetOperation, HalfCycles(248) }; + static constexpr OperationT ResetOperation = CPU::MC68000::Operation::Reset; + Microcycle reset_cycle { HalfCycles(248) }; // Interrupt acknowledge. - static constexpr Microcycle::OperationT + static constexpr OperationT InterruptCycleOperations[2] = { - Microcycle::InterruptAcknowledge | Microcycle::Read | Microcycle::NewAddress, - Microcycle::InterruptAcknowledge | Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectByte + Operation::InterruptAcknowledge | Operation::Read | Operation::NewAddress, + Operation::InterruptAcknowledge | Operation::Read | Operation::SameAddress | Operation::SelectByte }; - Microcycle interrupt_cycles[2] = { - { InterruptCycleOperations[0] }, - { InterruptCycleOperations[1] }, - }; + Microcycle interrupt_cycle0; + Microcycle interrupt_cycle1; // Holding spot when awaiting DTACK/etc. - Microcycle awaiting_dtack; + Microcycle awaiting_dtack; }; }