1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-25 08:25:09 +00:00

Add a ticking-but-diconnected CRTC.

This commit is contained in:
Thomas Harte
2025-09-15 23:16:42 -04:00
parent 69748a1f1b
commit 71d7b1dfad

View File

@@ -11,12 +11,14 @@
#include "Machines/MachineTypes.hpp"
#include "Components/6522/6522.hpp"
#include "Components/6845/CRTC6845.hpp"
#include "Components/SN76489/SN76489.hpp"
#include "Processors/6502/6502.hpp"
#include "Analyser/Static/Acorn/Target.hpp"
#include "Outputs/Log.hpp"
#include "Outputs/CRT/CRT.hpp"
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
@@ -30,6 +32,9 @@ namespace BBCMicro {
namespace {
using Logger = Log::Logger<Log::Source::BBCMicro>;
/*!
Combines an SN76489 with an appropriate asynchronous queue and filtering speaker.
*/
struct Audio {
Audio() :
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_),
@@ -68,9 +73,15 @@ private:
HalfCycles time_since_update_;
};
/*!
Models the user-port VIA.
*/
struct UserVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
};
/*!
Models the system VIA, which connects to the SN76489 and the keyboard.
*/
struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
SystemVIAPortHandler(Audio &audio) : audio_(audio) {}
@@ -124,6 +135,213 @@ private:
Audio &audio_;
};
/*!
Handles CRTC bus activity.
*/
class CRTCBusHandler {
public:
CRTCBusHandler(const uint8_t *const ram) :
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1),
ram_(ram) {}
void set_palette(const uint8_t value) {
const auto index = value >> 4;
Logger::info().append("Palette entry %d set to %x", index, value & 0xf);
}
void set_control(const uint8_t value) {
Logger::info().append("Video control set to %x", value);
}
/*!
The CRTC entry function for the main part of each clock cycle; takes the current
bus state and determines what output to produce based on the current palette and mode.
*/
void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
// // The gate array waits 2us to react to the CRTC's vsync signal, and then
// // caps output at 4us. Since the clock rate is 1Mhz, that's 2 and 4 cycles,
// // respectively.
// if(state.hsync) {
// cycles_into_hsync_++;
// } else {
// cycles_into_hsync_ = 0;
// }
//
// const bool is_hsync = (cycles_into_hsync_ >= 2 && cycles_into_hsync_ < 6);
// const bool is_colour_burst = (cycles_into_hsync_ >= 7 && cycles_into_hsync_ < 11);
//
// // Sync is taken to override pixels, and is combined as a simple OR.
// const bool is_sync = is_hsync || state.vsync;
// const bool is_blank = !is_sync && state.hsync;
//
// OutputMode output_mode;
// if(is_sync) {
// output_mode = OutputMode::Sync;
// } else if(is_colour_burst) {
// output_mode = OutputMode::ColourBurst;
// } else if(is_blank) {
// output_mode = OutputMode::Blank;
// } else if(state.display_enable) {
// output_mode = OutputMode::Pixels;
// } else {
// output_mode = OutputMode::Border;
// }
//
// // If a transition between sync/border/pixels just occurred, flush whatever was
// // in progress to the CRT and reset counting.
// if(output_mode != previous_output_mode_) {
// if(cycles_) {
// switch(previous_output_mode_) {
// default:
// case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break;
// case OutputMode::Sync: crt_.output_sync(cycles_ * 16); break;
// case OutputMode::Border: output_border(cycles_); break;
// case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_ * 16); break;
// case OutputMode::Pixels:
// crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
// pixel_pointer_ = pixel_data_ = nullptr;
// break;
// }
// }
//
// cycles_ = 0;
// previous_output_mode_ = output_mode;
// }
//
// // Increment cycles since state changed.
// cycles_++;
//
// // Collect some more pixels if output is ongoing.
// if(previous_output_mode_ == OutputMode::Pixels) {
// if(!pixel_data_) {
// pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8);
// }
// if(pixel_pointer_) {
// // the CPC shuffles output lines as:
// // MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
// // ... so form the real access address.
// const uint16_t address =
// uint16_t(
// ((state.refresh_address & 0x3ff) << 1) |
// ((state.row_address & 0x7) << 11) |
// ((state.refresh_address & 0x3000) << 2)
// );
//
// // Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at
// // hsync, so there's no risk of pixel_pointer_ overrunning 320 output pixels without
// // exactly reaching 320 output pixels.
// switch(mode_) {
// case 0:
// reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
// reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
// pixel_pointer_ += 2 * sizeof(uint16_t);
// break;
//
// case 1:
// reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
// reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
// pixel_pointer_ += 2 * sizeof(uint32_t);
// break;
//
// case 2:
// reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
// reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
// pixel_pointer_ += 2 * sizeof(uint64_t);
// break;
//
// case 3:
// reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
// reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
// pixel_pointer_ += 2 * sizeof(uint16_t);
// break;
//
// }
//
// // Flush the current buffer pixel if full; the CRTC allows many different display
// // widths so it's not necessarily possible to predict the correct number in advance
// // and using the upper bound could lead to inefficient behaviour.
// if(pixel_pointer_ == pixel_data_ + 320) {
// crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
// pixel_pointer_ = pixel_data_ = nullptr;
// cycles_ = 0;
// }
// }
// }
//
// // Latch mode four cycles after HSYNC was signalled, if still active.
// if(cycles_into_hsync_ == 4 && mode_ != next_mode_) {
// mode_ = next_mode_;
// switch(mode_) {
// default:
// case 0: pixel_divider_ = 4; break;
// case 1: pixel_divider_ = 2; break;
// case 2: pixel_divider_ = 1; break;
// }
// build_mode_table();
// }
//
// // For the interrupt timer: notify the leading edge of vertical sync and the
// // trailing edge of horizontal sync.
// if(was_vsync_ != state.vsync) {
// interrupt_timer_.set_vsync(state.vsync);
// }
// if(was_hsync_ && !state.hsync) {
// interrupt_timer_.signal_hsync();
// }
//
// // Update current state for edge detection next time around.
// was_vsync_ = state.vsync;
// was_hsync_ = state.hsync;
}
/// Sets the destination for output.
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
crt_.set_scan_target(scan_target);
}
/// @returns The current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return crt_.get_scaled_scan_status();
}
/// Sets the type of display.
void set_display_type(const Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
/// Gets the type of display.
Outputs::Display::DisplayType get_display_type() const {
return crt_.get_display_type();
}
private:
void output_border(const int length) {
assert(length >= 0);
crt_.output_blank(length * 16);
}
enum class OutputMode {
Sync,
Blank,
ColourBurst,
Border,
Pixels
} previous_output_mode_ = OutputMode::Sync;
int cycles_ = 0;
bool was_hsync_ = false, was_vsync_ = false;
int cycles_into_hsync_ = 0;
Outputs::CRT::CRT crt_;
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
const uint8_t *const ram_ = nullptr;
};
using CRTC = Motorola::CRTC::CRTC6845<
CRTCBusHandler,
Motorola::CRTC::Personality::HD6845S,
Motorola::CRTC::CursorType::None>;
}
class ConcreteMachine:
@@ -141,7 +359,9 @@ public:
m6502_(*this),
system_via_port_handler_(audio_),
user_via_(user_via_port_handler_),
system_via_(system_via_port_handler_)
system_via_(system_via_port_handler_),
crtc_bus_handler_(ram_.data()),
crtc_(crtc_bus_handler_)
{
set_clock_rate(2'000'000);
@@ -205,7 +425,7 @@ public:
//
// Dependent device updates.
// 1Mhz devices.
//
const auto half_cycles = HalfCycles(duration.as_integral());
audio_ += half_cycles;
@@ -213,6 +433,12 @@ public:
user_via_.run_for(half_cycles);
//
// 2Mhz devices.
//
// TODO: if CRTC clock is 1Mhz, adapt.
crtc_.run_for(duration);
//
// Check for an IO access; if found then perform that and exit.
//
@@ -235,7 +461,37 @@ public:
} else {
page_sideways(*value & 0xf);
}
} else {
} else if(address >= 0xfe00 && address < 0xfe08) {
if(is_read(operation)) {
if(address & 1) {
*value = crtc_.get_register();
} else {
*value = crtc_.get_status();
}
} else {
if(address & 1) {
crtc_.set_register(*value);
} else {
crtc_.select_register(*value);
}
}
} else if(address >= 0xfe20 && address < 0xfe30) {
if(is_read(operation)) {
*value = 0xfe;
} else {
switch(address) {
case 0xfe20:
crtc_bus_handler_.set_control(*value);
crtc_2mhz_ = *value & 0x08;
break;
case 0xfe21:
crtc_bus_handler_.set_palette(*value);
break;
}
}
}
else {
Logger::error()
.append("Unhandled IO %s at %04x", is_read(operation) ? "read" : "write", address)
.append_if(!is_read(operation), ": %02x", *value);
@@ -269,9 +525,12 @@ private:
}
// MARK: - ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *) override {}
Outputs::Display::ScanStatus get_scan_status() const override {
return Outputs::Display::ScanStatus{};
void set_scan_target(Outputs::Display::ScanTarget *const target) override {
crtc_bus_handler_.set_scan_target(target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
return crtc_bus_handler_.get_scaled_scan_status();
}
// MARK: - TimedMachine.
@@ -339,6 +598,10 @@ private:
}
Audio audio_;
CRTCBusHandler crtc_bus_handler_;
CRTC crtc_;
bool crtc_2mhz_ = true;
};
}