diff --git a/Machines/Amiga/Amiga.cpp b/Machines/Amiga/Amiga.cpp index a0aed3471..e41c4a11d 100644 --- a/Machines/Amiga/Amiga.cpp +++ b/Machines/Amiga/Amiga.cpp @@ -26,6 +26,8 @@ #include "Keyboard.hpp" #include "MemoryMap.hpp" +#include + namespace { // NTSC clock rate: 2*3.579545 = 7.15909Mhz. @@ -81,15 +83,14 @@ class ConcreteMachine: HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) { // Do a quick advance check for Chip RAM access; add a suitable delay if required. - HalfCycles access_delay; + HalfCycles total_length; if(cycle.operation & Microcycle::NewAddress && *cycle.address < 0x20'0000) { - access_delay = chipset_.run_until_cpu_slot().duration; + total_length = chipset_.run_until_after_cpu_slot().duration; + assert(total_length >= cycle.length); + } else { + total_length = cycle.length; + chipset_.run_for(total_length); } - - // Compute total length. - const HalfCycles total_length = cycle.length + access_delay; - - chipset_.run_for(total_length); mc68000_.set_interrupt_level(chipset_.get_interrupt_level()); // Check for assertion of reset. @@ -101,11 +102,11 @@ class ConcreteMachine: // Autovector interrupts. if(cycle.operation & Microcycle::InterruptAcknowledge) { mc68000_.set_is_peripheral_address(true); - return access_delay; + return total_length - cycle.length; } // Do nothing if no address is exposed. - if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return access_delay; + if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return total_length - cycle.length; // Grab the target address to pick a memory source. const uint32_t address = cycle.host_endian_byte_address(); @@ -165,7 +166,7 @@ class ConcreteMachine: ); } - return access_delay; + return total_length - cycle.length; } void flush() { diff --git a/Machines/Amiga/Chipset.cpp b/Machines/Amiga/Chipset.cpp index 1ee225673..885df8b3b 100644 --- a/Machines/Amiga/Chipset.cpp +++ b/Machines/Amiga/Chipset.cpp @@ -8,9 +8,9 @@ #include "Chipset.hpp" -//#ifndef NDEBUG -//#define NDEBUG -//#endif +#ifndef NDEBUG +#define NDEBUG +#endif #define LOG_PREFIX "[Amiga chipset] " #include "../../Outputs/Log.hpp" @@ -71,7 +71,7 @@ Chipset::Changes Chipset::run_for(HalfCycles length) { return run(length); } -Chipset::Changes Chipset::run_until_cpu_slot() { +Chipset::Changes Chipset::run_until_after_cpu_slot() { return run(); } @@ -317,7 +317,18 @@ template void Chipset::output() { // video to 368 low resolution pixel" // // => 184 windows out of 227 are visible, which concurs. - // + + // TODO: Reload bitplanes if anything is pending. +// if(has_next_bitplanes_) { +// has_next_bitplanes_ = false; +// bitplane_pixels_.set( +// previous_bitplanes_, +// next_bitplanes_, +// odd_delay_, +// even_delay_ +// ); +// previous_bitplanes_ = next_bitplanes_; +// } // Advance audio. audio_.output(); @@ -457,18 +468,6 @@ template void Chipset::output() { sprite_shifters_[1].shift(); sprite_shifters_[2].shift(); sprite_shifters_[3].shift(); - - // Reload if anything is pending. - if(has_next_bitplanes_) { - has_next_bitplanes_ = false; - bitplane_pixels_.set( - previous_bitplanes_, - next_bitplanes_, - odd_delay_, - even_delay_ - ); - previous_bitplanes_ = next_bitplanes_; - } } void Chipset::flush_output() { @@ -515,22 +514,66 @@ template bool Chipset::perform_cycle() { if(cycle == fetch_window_[1]) fetch_stop_ = cycle + (is_high_res_ ? 12 : 8); fetch_horizontal_ &= cycle != fetch_stop_; if((dma_control_ & BitplaneFlag) == BitplaneFlag) { - // TODO: offer a cycle for bitplane collection. - // Probably need to indicate odd or even? if(fetch_vertical_ && fetch_horizontal_ && bitplanes_.advance_dma(cycle)) { did_fetch_ = true; return false; } } + // Contradictory snippets from the Hardware Reference manual: + // + // 1) + // The Copper is a two-cycle processor that requests the bus only during + // odd-numbered memory cycles. This prevents collision with audio, disk, + // refresh, sprites, and most low resolution display DMA access, all of which + // use only the even-numbered memory cycles. + // + // 2) + // |<- - - - - - - - average 68000 cycle - - - - - - - - ->| + // | | + // |<- - - - internal - - - ->|<- - - - - memory - - - ->| + // | operation | access | + // | portion | portion | + // | | | + // | odd cycle, | even cycle, | + // | assigned to | available to | + // | other devices | the 68000 | + // + // Figure 6-10: Normal 68000 Cycle + + // There's also Figure 6-9, which in theory nails down slot usage, but + // numbers the boundaries between slots rather than the slots themselves... + // and has nine slots depicted between positions $20 and $21. So + // whether the boundary numbers assign to the slots on their left or on + // their right is entirely opaque. + + // I therefore take the word of Toni Wilen via https://eab.abime.net/showpost.php?p=938307&postcount=2 + // as definitive: "CPU ... generally ... uses even [cycles] only". + // + // So probably the Copper requests the bus only on _even_ cycles? + + + // General rule: + // + // Chipset work on odd cycles, 68000 access on even. + // + // Exceptions: + // + // Bitplanes, the Blitter if a flag is set. + if constexpr (cycle & 1) { // Odd slot use/priority: // // 1. Bitplane fetches [dealt with above]. - // 2. Refresh, disk, audio, or sprites. Depending on region. + // 2. Refresh, disk, audio, sprites or Copper. Depending on region. // // Blitter and CPU priority is dealt with below. - if constexpr (cycle >= 0x07 && cycle < 0x0d) { + if constexpr (cycle >= 0x00 && cycle < 0x08) { + // Memory refresh, four slots per line. + return true; + } + + if constexpr (cycle >= 0x08 && cycle < 0x0e) { if((dma_control_ & DiskFlag) == DiskFlag) { if(disk_.advance_dma()) { return false; @@ -538,9 +581,10 @@ template bool Chipset::perform_cycle() { } } - if constexpr (cycle >= 0xd && cycle < 0x14) { - constexpr auto channel = (cycle - 0xd) >> 1; + if constexpr (cycle >= 0xe && cycle < 0x16) { + constexpr auto channel = (cycle - 0xe) >> 1; static_assert(channel >= 0 && channel < 4); + static_assert(cycle != 0x15 || channel == 3); if((dma_control_ & AudioFlags[channel]) == AudioFlags[channel]) { if(audio_.advance_dma(channel)) { @@ -549,23 +593,23 @@ template bool Chipset::perform_cycle() { } } - if constexpr (cycle >= 0x15 && cycle < 0x35) { + if constexpr (cycle >= 0x16 && cycle < 0x36) { if((dma_control_ & SpritesFlag) == SpritesFlag && y_ >= vertical_blank_height_) { - constexpr auto sprite_id = (cycle - 0x15) >> 2; + constexpr auto sprite_id = (cycle - 0x16) >> 2; static_assert(sprite_id >= 0 && sprite_id < std::tuple_size::value); - if(sprites_[sprite_id].advance_dma(cycle&2)) { + if(sprites_[sprite_id].advance_dma(!(cycle&2))) { return false; } } } } else { - // Bitplanes being dealt with, specific odd-cycle responsibility + // Bitplanes having been dealt with, specific even-cycle responsibility // is just possibly to pass to the Copper. // // The Blitter and CPU are dealt with outside of the odd/even test. if((dma_control_ & CopperFlag) == CopperFlag) { - if(copper_.advance_dma(uint16_t(((y_ & 0xff) << 8) | (cycle & 0xfe)), blitter_.get_status())) { + if(copper_.advance_dma(uint16_t(((y_ & 0xff) << 8) | cycle), blitter_.get_status())) { return false; } } else { @@ -589,17 +633,19 @@ template int Chipset::advance_slots(int first_slot, int last_ } assert(last_slot > first_slot); -#define C(x) \ - case x: \ - if constexpr(stop_on_cpu) {\ - if(perform_cycle()) {\ - return x - first_slot;\ - }\ - } else {\ - perform_cycle(); \ - } \ - output(); \ - if((x + 1) == last_slot) break; \ +#define C(x) \ + case x: \ + output(); \ + \ + if constexpr (stop_on_cpu) { \ + if(perform_cycle()) { \ + return 1 + x - first_slot; \ + } \ + } else { \ + perform_cycle(); \ + } \ + \ + if((x + 1) == last_slot) break; \ [[fallthrough]] #define C10(x) C(x); C(x+1); C(x+2); C(x+3); C(x+4); C(x+5); C(x+6); C(x+7); C(x+8); C(x+9); @@ -724,11 +770,17 @@ template Chipset::Changes Chipset::run(HalfCycles length) { } void Chipset::post_bitplanes(const BitplaneData &data) { - // Posted bitplanes should be received at the end of the - // current memory slot. So put them aside for now, and - // deal with them momentarily. - has_next_bitplanes_ = true; + // For now this retains the storage that'll be used when I switch to + // deferred loading, but continues to act as if the Amiga were barrel + // shifting bitplane data. next_bitplanes_ = data; + bitplane_pixels_.set( + previous_bitplanes_, + next_bitplanes_, + odd_delay_, + even_delay_ + ); + previous_bitplanes_ = next_bitplanes_; } void Chipset::update_interrupts() { diff --git a/Machines/Amiga/Chipset.hpp b/Machines/Amiga/Chipset.hpp index 555855902..057eb484a 100644 --- a/Machines/Amiga/Chipset.hpp +++ b/Machines/Amiga/Chipset.hpp @@ -54,8 +54,8 @@ class Chipset: private ClockingHint::Observer { /// Advances the stated amount of time. Changes run_for(HalfCycles); - /// Advances to the next available CPU slot. - Changes run_until_cpu_slot(); + /// Advances to the end of the next available CPU slot. + 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 &); diff --git a/Machines/Amiga/Copper.cpp b/Machines/Amiga/Copper.cpp index ced0a4b43..a668953eb 100644 --- a/Machines/Amiga/Copper.cpp +++ b/Machines/Amiga/Copper.cpp @@ -23,8 +23,8 @@ namespace { bool satisfies_raster(uint16_t position, uint16_t blitter_status, uint16_t *instruction) { const uint16_t mask = 0x8000 | (instruction[1] & 0x7ffe); return - (position & mask) >= (instruction[0] & mask) - && (!(blitter_status & 0x4000) || (instruction[1] & 0x8000)); + (position & mask) >= (instruction[0] & mask) && + (!(blitter_status & 0x4000) || (instruction[1] & 0x8000)); } } @@ -81,7 +81,7 @@ bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) { case State::Waiting: if(satisfies_raster(position, blitter_status, instruction_)) { - LOG("Unblocked waiting for " << PADHEX(4) << instruction_[0] << " at " << position); + LOG("Unblocked waiting for " << PADHEX(4) << instruction_[0] << " at " << PADHEX(4) << position << " with mask " << PADHEX(4) << (instruction_[1] & 0x7ffe)); state_ = State::FetchFirstWord; } return false; @@ -90,6 +90,7 @@ bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) { instruction_[0] = ram_[address_ & ram_mask_]; ++address_; state_ = State::FetchSecondWord; + LOG("First word fetch at " << PADHEX(4) << position); break; case State::FetchSecondWord: { @@ -100,6 +101,7 @@ bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) { // Read in the second instruction word. instruction_[1] = ram_[address_ & ram_mask_]; ++address_; + LOG("Second word fetch at " << PADHEX(4) << position); // Check for a MOVE. if(!(instruction_[0] & 1)) { @@ -129,10 +131,13 @@ bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) { // Got to here => this is a WAIT or a SKIP. + const bool raster_is_satisfied = satisfies_raster(position, blitter_status, instruction_); + if(!(instruction_[1] & 1)) { - // A WAIT. Just note that this is now waiting; the proper test - // will be applied from the next potential `advance_dma` onwards. - state_ = State::Waiting; + // A WAIT. Empirically, I don't think this waits at all if the test is + // already satisfied. + state_ = raster_is_satisfied ? State::FetchFirstWord : State::Waiting; + if(raster_is_satisfied) LOG("Will wait from " << PADHEX(4) << position); break; } diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib index 3711d25bd..a46638a5a 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - +