mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-05 21:32:55 +00:00
Merge pull request #1000 from TomHarte/CopperTests
Amiga: regularises timing; improves Copper sleep/wait costs
This commit is contained in:
commit
c4055fde97
@ -26,6 +26,8 @@
|
||||
#include "Keyboard.hpp"
|
||||
#include "MemoryMap.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
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() {
|
||||
|
@ -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<false>(length);
|
||||
}
|
||||
|
||||
Chipset::Changes Chipset::run_until_cpu_slot() {
|
||||
Chipset::Changes Chipset::run_until_after_cpu_slot() {
|
||||
return run<true>();
|
||||
}
|
||||
|
||||
@ -317,7 +317,18 @@ template <int cycle> 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 <int cycle> 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 <int cycle, bool stop_if_cpu> 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 <int cycle, bool stop_if_cpu> 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 <int cycle, bool stop_if_cpu> 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<decltype(sprites_)>::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 <bool stop_on_cpu> 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<x, stop_on_cpu>()) {\
|
||||
return x - first_slot;\
|
||||
}\
|
||||
} else {\
|
||||
perform_cycle<x, stop_on_cpu>(); \
|
||||
} \
|
||||
output<x>(); \
|
||||
if((x + 1) == last_slot) break; \
|
||||
#define C(x) \
|
||||
case x: \
|
||||
output<x>(); \
|
||||
\
|
||||
if constexpr (stop_on_cpu) { \
|
||||
if(perform_cycle<x, stop_on_cpu>()) { \
|
||||
return 1 + x - first_slot; \
|
||||
} \
|
||||
} else { \
|
||||
perform_cycle<x, stop_on_cpu>(); \
|
||||
} \
|
||||
\
|
||||
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 <bool stop_on_cpu> 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() {
|
||||
|
@ -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 &);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
Loading…
x
Reference in New Issue
Block a user