1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-07 23:29:06 +00:00
CLK/Machines/Amiga/Chipset.cpp

1087 lines
35 KiB
C++
Raw Normal View History

2021-07-23 01:16:23 +00:00
//
// Chipset.cpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Chipset.hpp"
//#ifndef NDEBUG
2021-07-23 01:16:23 +00:00
//#define NDEBUG
//#endif
2021-07-23 01:16:23 +00:00
#define LOG_PREFIX "[Amiga chipset] "
#include "../../Outputs/Log.hpp"
2021-08-11 22:47:35 +00:00
#include <algorithm>
2021-07-23 01:16:23 +00:00
#include <cassert>
using namespace Amiga;
namespace {
template <typename EnumT, EnumT... T> struct Mask {
static constexpr uint16_t value = 0;
};
template <typename EnumT, EnumT F, EnumT... T> struct Mask<EnumT, F, T...> {
static constexpr uint16_t value = uint16_t(F) | Mask<EnumT, T...>::value;
};
template <InterruptFlag... Flags> struct InterruptMask: Mask<InterruptFlag, Flags...> {};
template <DMAFlag... Flags> struct DMAMask: Mask<DMAFlag, Flags...> {};
}
#define DMA_CONSTRUCT *this, reinterpret_cast<uint16_t *>(map.chip_ram.data()), map.chip_ram.size() >> 1
2021-10-04 15:12:13 +00:00
Chipset::Chipset(MemoryMap &map, int input_clock_rate) :
blitter_(DMA_CONSTRUCT),
sprites_{
Sprite{DMA_CONSTRUCT}, Sprite{DMA_CONSTRUCT}, Sprite{DMA_CONSTRUCT}, Sprite{DMA_CONSTRUCT},
Sprite{DMA_CONSTRUCT}, Sprite{DMA_CONSTRUCT}, Sprite{DMA_CONSTRUCT}, Sprite{DMA_CONSTRUCT}
},
bitplanes_(DMA_CONSTRUCT),
copper_(DMA_CONSTRUCT),
audio_(DMA_CONSTRUCT, input_clock_rate),
2021-10-04 23:45:05 +00:00
crt_(908, 4, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
2021-10-24 03:17:13 +00:00
cia_a_handler_(map, disk_controller_, mouse_),
2021-10-04 23:45:05 +00:00
cia_b_handler_(disk_controller_),
cia_a(cia_a_handler_),
2021-10-09 11:08:59 +00:00
cia_b(cia_b_handler_),
disk_(DMA_CONSTRUCT),
disk_controller_(Cycles(input_clock_rate), *this, disk_, cia_b),
keyboard_(cia_a.serial_input) {
2021-10-05 22:38:56 +00:00
disk_controller_.set_clocking_hint_observer(this);
2021-11-17 20:33:46 +00:00
joysticks_.emplace_back(new Joystick());
cia_a_handler_.set_joystick(&joystick(0));
2021-11-21 13:13:55 +00:00
// Very conservatively crop, to roughly the centre 88% of a frame.
// This rectange was specifically calibrated around the default Workbench display.
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.055f, 0.88f, 0.88f));
2021-07-23 01:16:23 +00:00
}
#undef DMA_CONSTRUCT
Chipset::Changes Chipset::run_for(HalfCycles length) {
return run<false>(length);
}
Chipset::Changes Chipset::run_until_cpu_slot() {
return run<true>();
}
void Chipset::set_cia_interrupts(bool cia_a_interrupt, bool cia_b_interrupt) {
2021-07-28 23:36:30 +00:00
// TODO: are these really latched, or are they active live?
2021-10-29 18:29:22 +00:00
// If latched, is it only on a leading edge?
// interrupt_requests_ &= ~InterruptMask<InterruptFlag::IOPortsAndTimers, InterruptFlag::External>::value;
interrupt_requests_ |=
(cia_a_interrupt ? InterruptMask<InterruptFlag::IOPortsAndTimers>::value : 0) |
(cia_b_interrupt ? InterruptMask<InterruptFlag::External>::value : 0);
update_interrupts();
}
void Chipset::posit_interrupt(InterruptFlag flag) {
interrupt_requests_ |= uint16_t(flag);
update_interrupts();
2021-07-28 23:36:30 +00:00
}
void DMADeviceBase::posit_interrupt(InterruptFlag flag) {
chipset_.posit_interrupt(flag);
}
template <int cycle> void Chipset::output() {
// Notes to self on guesses below:
//
// Hardware stop is at 0x18;
// 12/64 * 227 = 42.5625
//
// "However, horizontal blanking actually limits the displayable
// video to 368 low resolution pixel"
//
// => 184 windows out of 227 are visible, which concurs.
//
// Advance audio.
audio_.output();
// Trigger any sprite loads encountered.
constexpr auto dcycle = cycle << 1;
static_assert(std::tuple_size<decltype(sprites_)>::value % 2 == 0);
for(size_t c = 0; c < sprites_.size(); c += 2) {
if( sprites_[c].visible &&
dcycle <= sprites_[c].h_start &&
dcycle+2 > sprites_[c].h_start) {
sprite_shifters_[c >> 1].load<0>(
sprites_[c].data[1],
sprites_[c].data[0],
sprites_[c].h_start & 1);
}
if( sprites_[c+1].visible &&
dcycle <= sprites_[c + 1].h_start &&
dcycle+2 > sprites_[c + 1].h_start) {
sprite_shifters_[c >> 1].load<1>(
sprites_[c + 1].data[1],
sprites_[c + 1].data[0],
sprites_[c + 1].h_start & 1);
}
}
//
// Horizontal sync: HC18HC35;
// Horizontal blank: HC15HC53.
//
// Beyond that: guesswork.
//
// So, from cycle 0:
//
// 15 cycles border/pixels;
// 3 cycles blank;
// 17 cycles sync;
// 3 cycles blank;
// 9 cycles colour burst;
// 6 cycles blank;
// then more border/pixels to end of line.
//
// (???)
constexpr int end_of_pixels = 15;
constexpr int blank1 = 3 + end_of_pixels;
constexpr int sync = 17 + blank1;
constexpr int blank2 = 3 + sync;
constexpr int burst = 9 + blank2;
constexpr int blank3 = 6 + burst;
static_assert(blank3 == 53);
#define LINK(location, action, length) \
if(cycle == (location)) { \
crt_.action((length) * 4); \
}
if(y_ < vertical_blank_height_) {
if(!cycle) {
flush_output();
}
// Put three lines of sync at the centre of the vertical blank period.
// Offset by half a line if interlaced and on an odd frame.
const int midline = vertical_blank_height_ >> 1;
2021-11-02 21:34:03 +00:00
if(is_long_field_) {
if(y_ < midline - 1 || y_ > midline + 2) {
LINK(blank1, output_blank, blank1);
LINK(sync, output_sync, sync - blank1);
LINK(line_length_ - 1, output_blank, line_length_ - 1 - sync);
} else if(y_ == midline - 1) {
LINK(113, output_blank, 113);
LINK(line_length_ - 1, output_sync, line_length_ - 1 - 113);
} else if(y_ == midline + 2) {
LINK(113, output_sync, 113);
LINK(line_length_ - 1, output_blank, line_length_ - 1 - 113);
} else {
LINK(blank1, output_sync, blank1);
LINK(sync, output_blank, sync - blank1);
LINK(line_length_ - 1, output_sync, line_length_ - 1 - sync);
}
} else {
if(y_ < midline - 1 || y_ > midline + 1) {
LINK(blank1, output_blank, blank1);
LINK(sync, output_sync, sync - blank1);
LINK(line_length_ - 1, output_blank, line_length_ - 1 - sync);
} else {
LINK(blank1, output_sync, blank1);
LINK(sync, output_blank, sync - blank1);
LINK(line_length_ - 1, output_sync, line_length_ - 1 - sync);
}
}
} else {
// TODO: incorporate the lowest display window bits elsewhere.
display_horizontal_ |= cycle == (display_window_start_[0] >> 1);
display_horizontal_ &= cycle != (display_window_stop_[0] >> 1);
if(cycle == end_of_pixels) {
flush_output();
}
// Output the correct sequence of blanks, syncs and burst atomically.
LINK(blank1, output_blank, blank1 - end_of_pixels);
LINK(sync, output_sync, sync - blank1);
LINK(blank2, output_blank, blank2 - sync);
LINK(burst, output_default_colour_burst, burst - blank2); // TODO: only if colour enabled.
LINK(blank3, output_blank, blank3 - burst);
if constexpr (cycle < end_of_pixels || cycle > blank3) {
const bool is_pixel_display = display_horizontal_ && fetch_vertical_;
if(
(is_pixel_display == is_border_) ||
(is_border_ && border_colour_ != palette_[0])) {
flush_output();
is_border_ = !is_pixel_display;
border_colour_ = palette_[0];
}
if(is_pixel_display) {
if(!pixels_) {
uint16_t *const new_pixels = reinterpret_cast<uint16_t *>(crt_.begin_data(4 * size_t(line_length_ + end_of_pixels - cycle)));
if(new_pixels) {
flush_output();
}
pixels_ = new_pixels;
}
if(pixels_) {
// TODO:
//
// (1) dump bit 5 of a six-bitplane playfield if this Chipset doesn't support extra half-bright.
// (2) priorities, in general;
// (3) collisions;
// (4) a much less dense implementation. In particular: map from [16]-bit input to present or
// absent flags, use those to update collisions and then to index a mask table for compositing.
// Which would mean getting through the whole ordeal without branching... as soon as I come up
// with a branchless method for priorities. Hmmmm.
const uint32_t source = bitplane_pixels_.get(is_high_res_);
// TODO:
if(dual_playfields_) {
// Dense: just write both.
if(even_over_odd_) {
2021-11-26 23:08:10 +00:00
pixels_[0] = palette_[8 + ((source >> 27) & 7)];
pixels_[1] = palette_[8 + ((source >> 19) & 7)];
pixels_[2] = palette_[8 + ((source >> 11) & 7)];
pixels_[3] = palette_[8 + ((source >> 3) & 7)];
2021-11-18 10:36:38 +00:00
if((source >> 24) & 7) pixels_[0] = palette_[(source >> 24) & 7];
if((source >> 16) & 7) pixels_[1] = palette_[(source >> 16) & 7];
if((source >> 8) & 7) pixels_[2] = palette_[(source >> 8) & 7];
if(source & 7) pixels_[3] = palette_[source & 7];
} else {
pixels_[0] = palette_[(source >> 24) & 7];
pixels_[1] = palette_[(source >> 16) & 7];
pixels_[2] = palette_[(source >> 8) & 7];
pixels_[3] = palette_[source & 7];
2021-11-26 23:08:10 +00:00
if((source >> 27) & 7) pixels_[0] = palette_[8 + ((source >> 27) & 7)];
if((source >> 19) & 7) pixels_[1] = palette_[8 + ((source >> 19) & 7)];
if((source >> 11) & 7) pixels_[2] = palette_[8 + ((source >> 11) & 7)];
if((source >> 3) & 7) pixels_[3] = palette_[8 + ((source >> 3) & 7)];
}
} else {
pixels_[0] = swizzled_palette_[source >> 24];
pixels_[1] = swizzled_palette_[(source >> 16) & 0xff];
pixels_[2] = swizzled_palette_[(source >> 8) & 0xff];
pixels_[3] = swizzled_palette_[source & 0xff];
}
size_t index = sprite_shifters_.size();
for(auto shifter = sprite_shifters_.rbegin(); shifter != sprite_shifters_.rend(); ++shifter) {
--index;
const auto data = shifter->get();
if(!data) continue;
const auto base = (index << 2) + 16;
if(sprites_[(index << 1) + 1].attached) {
2021-11-19 19:19:47 +00:00
// Left pixel.
if(data >> 4) {
pixels_[0] = pixels_[1] = palette_[16 + (data >> 4)];
}
// Right pixel.
if(data & 15) {
pixels_[2] = pixels_[3] = palette_[16 + (data & 15)];
}
} else {
// Left pixel.
if((data >> 4) & 3) {
pixels_[0] = pixels_[1] = palette_[base + ((data >> 4)&3)];
}
2021-11-26 16:58:50 +00:00
if(data >> 6) {
pixels_[0] = pixels_[1] = palette_[base + (data >> 6)];
}
2021-11-19 19:19:47 +00:00
// Right pixel.
if(data & 3) {
pixels_[2] = pixels_[3] = palette_[base + (data & 3)];
}
2021-11-26 16:58:50 +00:00
if((data >> 2) & 3) {
pixels_[2] = pixels_[3] = palette_[base + ((data >> 2)&3)];
}
}
}
pixels_ += 4;
}
}
++zone_duration_;
}
}
#undef LINK
// Update all active pixel shifters.
bitplane_pixels_.shift(is_high_res_);
sprite_shifters_[0].shift();
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() {
if(!zone_duration_) return;
if(is_border_) {
uint16_t *const pixels = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(pixels) {
*pixels = border_colour_;
}
crt_.output_data(zone_duration_ * 4, 1);
} else {
crt_.output_data(zone_duration_ * 4);
}
zone_duration_ = 0;
pixels_ = nullptr;
}
/// @returns @c true if this was a CPU slot; @c false otherwise.
template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
2021-11-09 12:11:23 +00:00
constexpr uint16_t AudioFlags[] = {
DMAMask<DMAFlag::AudioChannel0, DMAFlag::AllBelow>::value,
DMAMask<DMAFlag::AudioChannel1, DMAFlag::AllBelow>::value,
DMAMask<DMAFlag::AudioChannel2, DMAFlag::AllBelow>::value,
DMAMask<DMAFlag::AudioChannel3, DMAFlag::AllBelow>::value,
};
2021-08-10 13:19:15 +00:00
constexpr auto BlitterFlag = DMAMask<DMAFlag::Blitter, DMAFlag::AllBelow>::value;
2021-08-10 23:01:41 +00:00
constexpr auto BitplaneFlag = DMAMask<DMAFlag::Bitplane, DMAFlag::AllBelow>::value;
constexpr auto CopperFlag = DMAMask<DMAFlag::Copper, DMAFlag::AllBelow>::value;
constexpr auto DiskFlag = DMAMask<DMAFlag::Disk, DMAFlag::AllBelow>::value;
2021-10-22 20:07:20 +00:00
constexpr auto SpritesFlag = DMAMask<DMAFlag::Sprites, DMAFlag::AllBelow>::value;
2021-08-10 23:01:41 +00:00
// Update state as to whether bitplane fetching should happen now.
//
// TODO: figure out how the hard stops factor into this.
2021-09-24 02:06:13 +00:00
//
2021-08-10 23:01:41 +00:00
// Top priority: bitplane collection.
2021-10-19 22:18:39 +00:00
// TODO: mask off fetch_window_'s lower bits. (Dependant on high/low-res?)
2021-10-31 16:01:38 +00:00
// Also: fetch_stop_ and that + 12/8 is the best I can discern from the Hardware Reference,
// but very obviously isn't how the actual hardware works. Explore on that.
2021-10-19 22:18:39 +00:00
fetch_horizontal_ |= cycle == fetch_window_[0];
2021-10-31 16:01:38 +00:00
if(cycle == fetch_window_[1]) fetch_stop_ = cycle + (is_high_res_ ? 12 : 8);
fetch_horizontal_ &= cycle != fetch_stop_;
if((dma_control_ & BitplaneFlag) == BitplaneFlag) {
2021-08-10 23:01:41 +00:00
// 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;
}
2021-08-10 23:01:41 +00:00
}
if constexpr (cycle & 1) {
2021-08-10 23:01:41 +00:00
// Odd slot use/priority:
//
2021-08-10 23:01:41 +00:00
// 1. Bitplane fetches [dealt with above].
// 2. Refresh, disk, audio, or sprites. Depending on region.
//
2021-08-10 23:01:41 +00:00
// Blitter and CPU priority is dealt with below.
2021-10-22 20:07:20 +00:00
if constexpr (cycle >= 0x07 && cycle < 0x0d) {
if((dma_control_ & DiskFlag) == DiskFlag) {
if(disk_.advance_dma()) {
return false;
}
}
}
2021-10-22 20:07:20 +00:00
2021-11-09 12:11:23 +00:00
if constexpr (cycle >= 0xd && cycle < 0x14) {
constexpr auto channel = (cycle - 0xd) >> 1;
2021-11-26 20:23:54 +00:00
static_assert(channel >= 0 && channel < 4);
2021-11-09 12:11:23 +00:00
if((dma_control_ & AudioFlags[channel]) == AudioFlags[channel]) {
if(audio_.advance_dma(channel)) {
2021-11-09 12:11:23 +00:00
return false;
}
}
}
if constexpr (cycle >= 0x15 && cycle < 0x35) {
if((dma_control_ & SpritesFlag) == SpritesFlag && y_ >= vertical_blank_height_) {
2021-10-22 20:07:20 +00:00
constexpr auto sprite_id = (cycle - 0x15) >> 2;
static_assert(sprite_id >= 0 && sprite_id < std::tuple_size<decltype(sprites_)>::value);
2021-11-26 20:23:54 +00:00
if(sprites_[sprite_id].advance_dma(cycle&2)) {
2021-10-22 20:07:20 +00:00
return false;
}
}
}
2021-08-10 23:01:41 +00:00
} else {
// Bitplanes being dealt with, specific odd-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())) {
2021-08-10 23:01:41 +00:00
return false;
}
} else {
copper_.stop();
}
}
// Down here: give first refusal to the Blitter, otherwise
// pass on to the CPU.
return (dma_control_ & BlitterFlag) != BlitterFlag || !blitter_.advance_dma();
}
/// Performs all slots starting with @c first_slot and ending just before @c last_slot.
/// If @c stop_on_cpu is true, stops upon discovery of a CPU slot.
///
/// @returns the number of slots completed if @c stop_on_cpu was true and a CPU slot was found.
/// @c -1 otherwise.
template <bool stop_on_cpu> int Chipset::advance_slots(int first_slot, int last_slot) {
if(first_slot == last_slot) {
return -1;
}
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; \
[[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);
switch(first_slot) {
C10(0); C10(10); C10(20); C10(30); C10(40);
C10(50); C10(60); C10(70); C10(80); C10(90);
C10(100); C10(110); C10(120); C10(130); C10(140);
C10(150); C10(160); C10(170); C10(180); C10(190);
C10(200); C10(210);
C(220); C(221); C(222); C(223); C(224);
C(225); C(226); C(227); C(228);
default: assert(false);
}
#undef C
return -1;
}
template <bool stop_on_cpu> Chipset::Changes Chipset::run(HalfCycles length) {
Changes changes;
// This code uses 'pixels' as a measure, which is equivalent to one pixel clock time,
// or half a cycle.
auto pixels_remaining = length.as<int>();
int hsyncs = 0, vsyncs = 0;
// Update raster position, spooling out graphics.
while(pixels_remaining) {
// Determine number of pixels left on this line.
const int line_pixels = std::min(pixels_remaining, (line_length_ * 4) - line_cycle_);
const int start_slot = line_cycle_ >> 2;
const int end_slot = (line_cycle_ + line_pixels) >> 2;
const int actual_slots = advance_slots<stop_on_cpu>(start_slot, end_slot);
if(stop_on_cpu && actual_slots >= 0) {
// Run until the end of the named slot.
if(actual_slots) {
const int actual_line_pixels =
(4 - (line_cycle_ & 3)) + ((actual_slots - 1) << 2);
line_cycle_ += actual_line_pixels;
changes.duration += HalfCycles(actual_line_pixels);
}
// Just ensure an exit.
pixels_remaining = 0;
} else {
line_cycle_ += line_pixels;
changes.duration += HalfCycles(line_pixels);
pixels_remaining -= line_pixels;
}
2021-10-19 22:18:39 +00:00
// Advance intraline counter and possibly ripple upwards into
// lines and fields.
if(line_cycle_ == (line_length_ * 4)) {
++hsyncs;
line_cycle_ = 0;
++y_;
2021-08-10 23:01:41 +00:00
fetch_vertical_ |= y_ == display_window_start_[1];
fetch_vertical_ &= y_ != display_window_stop_[1];
if(did_fetch_) {
bitplanes_.do_end_of_line();
previous_bitplanes_.clear();
}
2021-10-19 22:18:39 +00:00
did_fetch_ = false;
2021-10-31 16:01:38 +00:00
fetch_horizontal_ = false;
fetch_stop_ = 0xffff;
2021-11-02 21:34:03 +00:00
if(y_ == short_field_height_ + is_long_field_) {
++vsyncs;
interrupt_requests_ |= InterruptMask<InterruptFlag::VerticalBlank>::value;
update_interrupts();
y_ = 0;
// TODO: the manual is vague on when this happens. Try to find out.
2021-08-11 22:47:35 +00:00
copper_.reload<0>();
2021-11-02 21:34:03 +00:00
// Toggle next field length if interlaced.
is_long_field_ ^= interlace_;
}
for(auto &sprite: sprites_) {
sprite.advance_line(y_, y_ == vertical_blank_height_);
}
}
assert(line_cycle_ < line_length_ * 4);
}
2021-11-07 13:19:16 +00:00
// Advance the keyboard's serial output, at
// close enough to 1,000,000 ticks/second.
keyboard_divider_ += changes.duration;
keyboard_.run_for(keyboard_divider_.divide(HalfCycles(14)));
// The CIAs are on the E clock.
cia_divider_ += changes.duration;
const HalfCycles e_clocks = cia_divider_.divide(HalfCycles(20));
if(e_clocks > HalfCycles(0)) {
cia_a.run_for(e_clocks);
cia_b.run_for(e_clocks);
}
2021-10-05 22:38:56 +00:00
// Propagate TOD updates to the CIAs, and feed their new interrupt
// outputs back to here.
cia_a.advance_tod(vsyncs);
cia_b.advance_tod(hsyncs);
set_cia_interrupts(cia_a.get_interrupt_line(), cia_b.get_interrupt_line());
2021-10-05 22:38:56 +00:00
// Update the disk controller, if any drives are active.
if(!disk_controller_is_sleeping_) {
disk_controller_.run_for(changes.duration.cycles());
}
// Record the interrupt level.
// TODO: is this useful?
changes.interrupt_level = interrupt_level_;
return changes;
}
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;
next_bitplanes_ = data;
}
void Chipset::update_interrupts() {
interrupt_level_ = 0;
const uint16_t enabled_requests = interrupt_enable_ & interrupt_requests_ & 0x3fff;
if(enabled_requests && (interrupt_enable_ & 0x4000)) {
if(enabled_requests & InterruptMask<InterruptFlag::External>::value) {
interrupt_level_ = 6;
} else if(enabled_requests & InterruptMask<InterruptFlag::SerialPortReceive, InterruptFlag::DiskSyncMatch>::value) {
interrupt_level_ = 5;
} else if(enabled_requests & InterruptMask<InterruptFlag::AudioChannel0, InterruptFlag::AudioChannel1, InterruptFlag::AudioChannel2, InterruptFlag::AudioChannel3>::value) {
interrupt_level_ = 4;
} else if(enabled_requests & InterruptMask<InterruptFlag::Copper, InterruptFlag::VerticalBlank, InterruptFlag::Blitter>::value) {
interrupt_level_ = 3;
} else if(enabled_requests & InterruptMask<InterruptFlag::IOPortsAndTimers>::value) {
interrupt_level_ = 2;
} else if(enabled_requests & InterruptMask<InterruptFlag::SerialPortTransmit, InterruptFlag::DiskBlock, InterruptFlag::Software>::value) {
interrupt_level_ = 1;
}
}
}
2021-07-23 01:16:23 +00:00
void Chipset::perform(const CPU::MC68000::Microcycle &cycle) {
using Microcycle = CPU::MC68000::Microcycle;
#define RW(address) address | ((cycle.operation & Microcycle::Read) << 12)
#define Read(address) address | (Microcycle::Read << 12)
2021-07-23 01:16:23 +00:00
#define Write(address) address
#define ApplySetClear(target, mask) { \
2021-07-23 01:16:23 +00:00
const uint16_t value = cycle.value16(); \
if(value & 0x8000) { \
target |= (value & mask); \
2021-07-23 01:16:23 +00:00
} else { \
target &= ~(value & mask); \
2021-07-23 01:16:23 +00:00
} \
}
const uint32_t register_address = *cycle.address & 0x1fe;
switch(RW(register_address)) {
2021-07-23 01:16:23 +00:00
default:
LOG("Unimplemented chipset " << (cycle.operation & Microcycle::Read ? "read" : "write") << " " << PADHEX(6) << *cycle.address);
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(0xffff);
}
2021-07-23 01:16:23 +00:00
break;
2021-07-26 20:21:51 +00:00
// Raster position.
2021-10-29 21:05:11 +00:00
case Read(0x004): { // VPOSR; b15 = LOF, b0 = b8 of y position.
const uint16_t position = uint16_t(y_ >> 8);
2021-11-02 10:47:39 +00:00
cycle.set_value16(
position |
(is_long_field_ ? 0x8000 : 0x0000)
);
// b8b14 should be:
// 00 for PAL Agnus or fat Agnus
// 10 for NTSC Agnus or fat Agnus
// 20 for PAL high-res
// 30 for NTSC high-res
} break;
case Read(0x006): { // VHPOSR; b0b7 = horizontal; b8b15 = low bits of vertical position.
const uint16_t position = uint16_t(((line_cycle_ >> 1) & 0x00ff) | (y_ << 8));
cycle.set_value16(position);
} break;
2021-11-02 21:34:03 +00:00
case Write(0x02a): // VPOSW
LOG("TODO: write vertical position high " << PADHEX(4) << cycle.value16());
break;
2021-11-02 21:34:03 +00:00
case Write(0x02c): { // VHPOSW
LOG("TODO: write vertical position low " << PADHEX(4) << cycle.value16());
2021-11-02 21:34:03 +00:00
const uint16_t value = cycle.value16();
is_long_field_ = value & 0x8000;
} break;
2021-07-26 20:21:51 +00:00
// Joystick/mouse input.
2021-10-29 21:05:11 +00:00
case Read(0x00a): // JOY0DAT
cycle.set_value16(mouse_.get_position());
2021-10-24 03:17:13 +00:00
break;
2021-10-29 21:05:11 +00:00
case Read(0x00c): // JOY1DAT
2021-11-17 20:33:46 +00:00
cycle.set_value16(joystick(0).get_position());
2021-07-26 20:21:51 +00:00
break;
2021-10-29 21:05:11 +00:00
case Write(0x034): // POTGO
// LOG("TODO: pot port start");
break;
2021-10-29 21:05:11 +00:00
case Read(0x016): // POTGOR / POTINP
// LOG("TODO: pot port read");
cycle.set_value16(0xff00);
break;
2021-10-07 12:11:32 +00:00
// Disk DMA and control.
2021-10-29 21:05:11 +00:00
case Write(0x020): disk_.set_pointer<0, 16>(cycle.value16()); break; // DSKPTH
case Write(0x022): disk_.set_pointer<0, 0>(cycle.value16()); break; // DSKPTL
case Write(0x024): disk_.set_length(cycle.value16()); break; // DSKLEN
2021-10-29 21:05:11 +00:00
case Write(0x026): // DSKDAT
2021-07-23 01:16:23 +00:00
LOG("TODO: disk DMA; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
2021-10-29 21:05:11 +00:00
case Write(0x09e): // ADKCON
LOG("Write disk control");
ApplySetClear(paula_disk_control_, 0x7fff);
2021-10-07 12:11:32 +00:00
disk_controller_.set_control(paula_disk_control_);
2021-11-03 01:18:59 +00:00
disk_.set_control(paula_disk_control_);
audio_.set_modulation_flags(paula_disk_control_);
2021-10-07 12:11:32 +00:00
break;
2021-10-29 21:05:11 +00:00
case Read(0x010): // ADKCONR
LOG("Read disk control");
2021-10-07 12:11:32 +00:00
cycle.set_value16(paula_disk_control_);
break;
2021-10-29 21:05:11 +00:00
case Write(0x07e): // DSKSYNC
2021-10-07 12:11:32 +00:00
disk_controller_.set_sync_word(cycle.value16());
break;
2021-10-29 21:05:11 +00:00
case Read(0x01a): // DSKBYTR
2021-10-07 12:11:32 +00:00
LOG("TODO: disk status");
assert(false); // Not yet implemented.
2021-10-07 12:11:32 +00:00
break;
2021-07-23 01:16:23 +00:00
// Refresh.
2021-10-29 21:05:11 +00:00
case Write(0x028): // REFPTR
2021-07-23 01:16:23 +00:00
LOG("TODO (maybe): refresh; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
// Serial port.
2021-10-29 21:05:11 +00:00
case Read(0x018): // SERDATR
2021-09-15 01:53:07 +00:00
LOG("TODO: serial data and status");
cycle.set_value16(0x3000); // i.e. transmit buffer empty.
break;
2021-10-29 21:05:11 +00:00
case Write(0x030): // SERDAT
2021-07-23 01:16:23 +00:00
LOG("TODO: serial data: " << PADHEX(4) << cycle.value16());
break;
2021-10-29 21:05:11 +00:00
case Write(0x032): // SERPER
2021-07-23 01:16:23 +00:00
LOG("TODO: serial control: " << PADHEX(4) << cycle.value16());
2021-07-30 22:24:27 +00:00
serial_.set_control(cycle.value16());
2021-07-23 01:16:23 +00:00
break;
// DMA management.
2021-10-29 21:05:11 +00:00
case Read(0x002): // DMACONR
2021-07-23 01:16:23 +00:00
cycle.set_value16(dma_control_ | blitter_.get_status());
break;
2021-10-29 21:05:11 +00:00
case Write(0x096): // DMACON
ApplySetClear(dma_control_, 0x1fff);
audio_.set_channel_enables(dma_control_);
2021-07-23 01:16:23 +00:00
break;
// Interrupts.
2021-10-29 21:05:11 +00:00
case Write(0x09a): // INTENA
ApplySetClear(interrupt_enable_, 0x7fff);
2021-07-23 01:16:23 +00:00
update_interrupts();
break;
2021-10-29 21:05:11 +00:00
case Read(0x01c): // INTENAR
cycle.set_value16(interrupt_enable_);
break;
2021-10-29 21:05:11 +00:00
case Write(0x09c): // INTREQ
ApplySetClear(interrupt_requests_, 0x7fff);
2021-07-23 01:16:23 +00:00
update_interrupts();
audio_.set_interrupt_requests(interrupt_requests_);
2021-07-23 01:16:23 +00:00
break;
2021-10-29 21:05:11 +00:00
case Read(0x01e): // INTREQR
cycle.set_value16(interrupt_requests_);
break;
2021-07-23 01:16:23 +00:00
// Display management.
2021-10-29 21:05:11 +00:00
case Write(0x08e): { // DIWSTRT
const uint16_t value = cycle.value16();
display_window_start_[0] = value & 0xff;
display_window_start_[1] = value >> 8;
} break;
2021-10-29 21:05:11 +00:00
case Write(0x090): { // DIWSTOP
const uint16_t value = cycle.value16();
display_window_stop_[0] = 0x100 | (value & 0xff);
display_window_stop_[1] = value >> 8;
display_window_stop_[1] |= ((value >> 7) & 0x100) ^ 0x100;
} break;
2021-10-29 21:05:11 +00:00
case Write(0x092): // DDFSTRT
if(fetch_window_[0] != cycle.value16()) {
LOG("Fetch window start set to " << std::dec << cycle.value16());
}
2021-10-19 22:18:39 +00:00
fetch_window_[0] = cycle.value16();
break;
2021-10-29 21:05:11 +00:00
case Write(0x094): // DDFSTOP
2021-10-23 04:10:01 +00:00
// TODO: something in my interpretation of ddfstart and ddfstop
// means a + 8 is needed below for high-res displays. Investigate.
2021-10-31 16:01:38 +00:00
if(fetch_window_[1] != cycle.value16()) {
LOG("Fetch window stop set to " << std::dec << fetch_window_[1]);
}
2021-10-31 16:01:38 +00:00
fetch_window_[1] = cycle.value16();
break;
2021-07-23 01:16:23 +00:00
// Bitplanes.
2021-10-29 21:05:11 +00:00
case Write(0x0e0): bitplanes_.set_pointer<0, 16>(cycle.value16()); break; // BPL1PTH
case Write(0x0e2): bitplanes_.set_pointer<0, 0>(cycle.value16()); break; // BPL1PTL
case Write(0x0e4): bitplanes_.set_pointer<1, 16>(cycle.value16()); break; // BPL2PTH
case Write(0x0e6): bitplanes_.set_pointer<1, 0>(cycle.value16()); break; // BPL2PTL
case Write(0x0e8): bitplanes_.set_pointer<2, 16>(cycle.value16()); break; // BPL3PTH
case Write(0x0ea): bitplanes_.set_pointer<2, 0>(cycle.value16()); break; // BPL3PTL
case Write(0x0ec): bitplanes_.set_pointer<3, 16>(cycle.value16()); break; // BPL4PTH
case Write(0x0ee): bitplanes_.set_pointer<3, 0>(cycle.value16()); break; // BPL4PTL
case Write(0x0f0): bitplanes_.set_pointer<4, 16>(cycle.value16()); break; // BPL5PTH
case Write(0x0f2): bitplanes_.set_pointer<4, 0>(cycle.value16()); break; // BPL5PTL
case Write(0x0f4): bitplanes_.set_pointer<5, 16>(cycle.value16()); break; // BPL6PTH
case Write(0x0f6): bitplanes_.set_pointer<5, 0>(cycle.value16()); break; // BPL6PTL
case Write(0x100): { // BPLCON0
const auto value = cycle.value16();
bitplanes_.set_control(value);
is_high_res_ = value & 0x8000;
hold_and_modify_ = value & 0x0800;
dual_playfields_ = value & 0x0400;
interlace_ = value & 0x0004;
// LOG("New video control at " << std::dec << y_ << "; high res: " << is_high_res_ << " HAM: " << hold_and_modify_ << " dual: " << dual_playfields_ << " interlace: " << interlace_);
} break;
2021-10-29 21:05:11 +00:00
case Write(0x102): { // BPLCON1
2021-08-11 22:47:35 +00:00
const uint8_t delay = cycle.value8_low();
odd_delay_ = delay & 0x0f;
even_delay_ = delay >> 4;
} break;
case Write(0x104): { // BPLCON2
const auto value = cycle.value16();
odd_priority_ = value & 7;
even_priority_ = (value >> 3) & 7;
even_over_odd_ = value & 0x40;
} break;
2021-10-29 21:05:11 +00:00
case Write(0x106): // BPLCON3 (ECS)
2021-07-23 01:16:23 +00:00
LOG("TODO: Bitplane control; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
2021-10-29 21:05:11 +00:00
case Write(0x108): bitplanes_.set_modulo<0>(cycle.value16()); break; // BPL1MOD
case Write(0x10a): bitplanes_.set_modulo<1>(cycle.value16()); break; // BPL2MOD
2021-07-23 01:16:23 +00:00
case Write(0x110):
case Write(0x112):
case Write(0x114):
case Write(0x116):
case Write(0x118):
case Write(0x11a):
LOG("TODO: Bitplane data; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
case Read(0x110): case Read(0x112): case Read(0x114): case Read(0x116):
case Read(0x118): case Read(0x11a):
cycle.set_value16(0xffff);
2021-07-24 03:10:00 +00:00
LOG("Invalid read at " << PADHEX(6) << *cycle.address);
2021-07-23 01:16:23 +00:00
break;
// Blitter.
2021-10-16 20:30:45 +00:00
case Read(0x040): blitter_.set_control(0, 0xffff); break; // UGH. Have fallen into quite a hole here with my
case Read(0x042): blitter_.set_control(1, 0xffff); break; // Read/Write macros. TODO: some sort of canonical decode?
// Templatey to hit the usual Read/Write cases first?
2021-07-23 01:16:23 +00:00
case Write(0x040): blitter_.set_control(0, cycle.value16()); break;
case Write(0x042): blitter_.set_control(1, cycle.value16()); break;
case Write(0x044): blitter_.set_first_word_mask(cycle.value16()); break;
case Write(0x046): blitter_.set_last_word_mask(cycle.value16()); break;
case Write(0x048): blitter_.set_pointer<2, 16>(cycle.value16()); break;
case Write(0x04a): blitter_.set_pointer<2, 0>(cycle.value16()); break;
case Write(0x04c): blitter_.set_pointer<1, 16>(cycle.value16()); break;
case Write(0x04e): blitter_.set_pointer<1, 0>(cycle.value16()); break;
case Write(0x050): blitter_.set_pointer<0, 16>(cycle.value16()); break;
case Write(0x052): blitter_.set_pointer<0, 0>(cycle.value16()); break;
case Write(0x054): blitter_.set_pointer<3, 16>(cycle.value16()); break;
case Write(0x056): blitter_.set_pointer<3, 0>(cycle.value16()); break;
2021-07-23 01:16:23 +00:00
case Write(0x058): blitter_.set_size(cycle.value16()); break;
case Write(0x05a): blitter_.set_minterms(cycle.value16()); break;
// case Write(0x05c): blitter_.set_vertical_size(cycle.value16()); break;
// case Write(0x05e): blitter_.set_horizontal_size(cycle.value16()); break;
2021-07-23 01:16:23 +00:00
2021-10-29 18:29:22 +00:00
case Write(0x060): blitter_.set_modulo<2>(cycle.value16()); break;
case Write(0x062): blitter_.set_modulo<1>(cycle.value16()); break;
case Write(0x064): blitter_.set_modulo<0>(cycle.value16()); break;
case Write(0x066): blitter_.set_modulo<3>(cycle.value16()); break;
2021-07-23 01:16:23 +00:00
case Write(0x070): blitter_.set_data(2, cycle.value16()); break;
case Write(0x072): blitter_.set_data(1, cycle.value16()); break;
case Write(0x074): blitter_.set_data(0, cycle.value16()); break;
2021-11-09 12:11:23 +00:00
// Audio.
#define Audio(index, pointer) \
case Write(pointer + 0): audio_.set_pointer<index, 16>(cycle.value16()); break; \
case Write(pointer + 2): audio_.set_pointer<index, 0>(cycle.value16()); break; \
case Write(pointer + 4): audio_.set_length(index, cycle.value16()); break; \
case Write(pointer + 6): audio_.set_period(index, cycle.value16()); break; \
case Write(pointer + 8): audio_.set_volume(index, cycle.value16()); break; \
case Write(pointer + 10): audio_.set_data(index, cycle.value16()); break; \
2021-11-09 12:11:23 +00:00
Audio(0, 0x0a0);
Audio(1, 0x0b0);
Audio(2, 0x0c0);
Audio(3, 0x0d0);
#undef Audio
2021-07-26 02:16:31 +00:00
2021-07-23 01:16:23 +00:00
// Copper.
2021-10-29 21:05:11 +00:00
case Write(0x02e): copper_.set_control(cycle.value16()); break; // COPCON
case Write(0x080): copper_.set_pointer<0, 16>(cycle.value16()); break; // COP1LCH
case Write(0x082): copper_.set_pointer<0, 0>(cycle.value16()); break; // COP1LCL
case Write(0x084): copper_.set_pointer<1, 16>(cycle.value16()); break; // COP2LCH
case Write(0x086): copper_.set_pointer<1, 0>(cycle.value16()); break; // COP2LCL
2021-07-23 01:16:23 +00:00
case Write(0x088): case Read(0x088):
2021-08-11 22:47:35 +00:00
copper_.reload<0>();
2021-07-23 01:16:23 +00:00
break;
case Write(0x08a): case Read(0x08a):
2021-08-11 22:47:35 +00:00
copper_.reload<1>();
2021-07-23 01:16:23 +00:00
break;
case Write(0x08c):
LOG("TODO: coprocessor instruction fetch identity " << PADHEX(4) << cycle.value16());
break;
// Sprites.
#define Sprite(index, pointer, position) \
case Write(pointer + 0): sprites_[index].set_pointer<0, 16>(cycle.value16()); break; \
case Write(pointer + 2): sprites_[index].set_pointer<0, 0>(cycle.value16()); break; \
2021-07-23 01:16:23 +00:00
case Write(position + 0): sprites_[index].set_start_position(cycle.value16()); break; \
case Write(position + 2): sprites_[index].set_stop_and_control(cycle.value16()); break; \
case Write(position + 4): sprites_[index].set_image_data(0, cycle.value16()); break; \
case Write(position + 6): sprites_[index].set_image_data(1, cycle.value16()); break;
Sprite(0, 0x120, 0x140);
Sprite(1, 0x124, 0x148);
Sprite(2, 0x128, 0x150);
Sprite(3, 0x12c, 0x158);
Sprite(4, 0x130, 0x160);
Sprite(5, 0x134, 0x168);
Sprite(6, 0x138, 0x170);
Sprite(7, 0x13c, 0x178);
#undef Sprite
// Colour palette.
case Write(0x180): case Write(0x182): case Write(0x184): case Write(0x186):
case Write(0x188): case Write(0x18a): case Write(0x18c): case Write(0x18e):
case Write(0x190): case Write(0x192): case Write(0x194): case Write(0x196):
case Write(0x198): case Write(0x19a): case Write(0x19c): case Write(0x19e):
case Write(0x1a0): case Write(0x1a2): case Write(0x1a4): case Write(0x1a6):
case Write(0x1a8): case Write(0x1aa): case Write(0x1ac): case Write(0x1ae):
case Write(0x1b0): case Write(0x1b2): case Write(0x1b4): case Write(0x1b6):
case Write(0x1b8): case Write(0x1ba): case Write(0x1bc): case Write(0x1be): {
2021-08-11 22:47:35 +00:00
// Store once in regular, linear order.
const auto entry_address = (register_address - 0x180) >> 1;
uint8_t *const entry = reinterpret_cast<uint8_t *>(&palette_[entry_address]);
entry[0] = cycle.value8_high();
entry[1] = cycle.value8_low();
2021-08-11 22:47:35 +00:00
// Also store in bit-swizzled order. In this array,
// instead of being indexed as [b4 b3 b2 b1 b0], index
// as [b3 b1 b4 b2 b0], and include a second set of the
// 32 colours, stored as half-bright.
2021-08-11 22:47:35 +00:00
const auto swizzled_address =
(entry_address&0x01) |
((entry_address&0x02) << 2) |
((entry_address&0x04) >> 1) |
((entry_address&0x08) << 1) |
((entry_address&0x10) >> 2);
uint8_t *const swizzled_entry = reinterpret_cast<uint8_t *>(&swizzled_palette_[swizzled_address]);
swizzled_entry[0] = cycle.value8_high();
swizzled_entry[1] = cycle.value8_low();
swizzled_entry[64] = (swizzled_entry[0] >> 1) & 0x77;
swizzled_entry[65] = (swizzled_entry[1] >> 1) & 0x77;
} break;
2021-07-23 01:16:23 +00:00
}
#undef ApplySetClear
#undef Write
#undef Read
#undef RW
}
2021-08-11 22:47:35 +00:00
// MARK: - CRT connection.
void Chipset::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Chipset::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status();
}
void Chipset::set_display_type(Outputs::Display::DisplayType type) {
crt_.set_display_type(type);
}
Outputs::Display::DisplayType Chipset::get_display_type() const {
return crt_.get_display_type();
}
2021-10-04 23:45:05 +00:00
// MARK: - CIA A.
2021-10-24 03:17:13 +00:00
Chipset::CIAAHandler::CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse) :
map_(map), controller_(controller), mouse_(mouse) {}
void Chipset::CIAAHandler::set_port_output(MOS::MOS6526::Port port, uint8_t value) {
if(port) {
2021-10-05 10:38:11 +00:00
// CIA A, Port B: Parallel port output.
2021-10-04 23:45:05 +00:00
LOG("TODO: parallel output " << PADHEX(2) << +value);
} else {
2021-10-05 10:38:11 +00:00
// CIA A, Port A:
//
// b7: /FIR1
// b6: /FIR0
// b5: /RDY
// b4: /TRK0
// b3: /WPRO
// b2: /CHNG
// b1: /LED [output]
// b0: OVL [output]
if(observer_) {
observer_->set_led_status(led_name, !(value & 2));
}
map_.set_overlay(value & 1);
}
}
uint8_t Chipset::CIAAHandler::get_port_input(MOS::MOS6526::Port port) {
if(port) {
LOG("TODO: parallel input?");
} else {
2021-11-17 20:33:46 +00:00
// Use the mouse as FIR0, the joystick as FIR1.
2021-10-24 03:17:13 +00:00
return
controller_.get_rdy_trk0_wpro_chng() &
2021-11-17 20:33:46 +00:00
mouse_.get_cia_button() &
(1 | (joystick_->get_cia_button() << 1));
}
return 0xff;
}
void Chipset::CIAAHandler::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led(led_name, Activity::Observer::LEDPresentation::Persistent);
}
}
2021-10-04 23:45:05 +00:00
// MARK: - CIA B.
Chipset::CIABHandler::CIABHandler(DiskController &controller) : controller_(controller) {}
void Chipset::CIABHandler::set_port_output(MOS::MOS6526::Port port, uint8_t value) {
if(port) {
2021-10-05 10:38:11 +00:00
// CIA B, Port B:
//
// Disk motor control, drive and head selection,
// and stepper control:
controller_.set_mtr_sel_side_dir_step(value);
2021-10-05 10:38:11 +00:00
} else {
// CIA B, Port A: Serial port control.
//
// b7: /DTR
// b6: /RTS
// b5: /CD
// b4: /CTS
// b3: /DSR
// b2: SEL
// b1: POUT
// b0: BUSY
LOG("TODO: DTR/RTS/etc: " << PADHEX(2) << +value);
}
}
uint8_t Chipset::CIABHandler::get_port_input(MOS::MOS6526::Port) {
LOG("Unexpected: input for CIA B");
2021-10-05 10:38:11 +00:00
return 0xff;
2021-10-04 23:45:05 +00:00
}
2021-10-05 22:38:56 +00:00
// MARK: - ClockingHintObserver.
void Chipset::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference preference) {
disk_controller_is_sleeping_ = preference == ClockingHint::Preference::None;
}
2021-11-17 19:26:51 +00:00
// MARK: - Synchronisation.
void Chipset::flush() {
}