1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00

Merge pull request #693 from TomHarte/STComposite

Adds colour composite output to the ST
This commit is contained in:
Thomas Harte 2019-12-21 00:04:54 -05:00 committed by GitHub
commit b4fd506361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 106 additions and 13 deletions

View File

@ -8,6 +8,7 @@
#include "MFP68901.hpp" #include "MFP68901.hpp"
#include <algorithm>
#include <cstring> #include <cstring>
#define LOG_PREFIX "[MFP] " #define LOG_PREFIX "[MFP] "
@ -179,12 +180,26 @@ void MFP68901::run_for(HalfCycles time) {
cycles_left_ += time; cycles_left_ += time;
const int cycles = int(cycles_left_.flush<Cycles>().as_integral()); const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
if(!cycles) return;
for(int c = 0; c < 4; ++c) { for(int c = 0; c < 4; ++c) {
if(timers_[c].mode >= TimerMode::Delay) { if(timers_[c].mode >= TimerMode::Delay) {
const int dividend = (cycles + timers_[c].prescale - timers_[c].divisor); // This code applies the timer prescaling only. prescale_count is used to count
const int decrements = dividend / timers_[c].prescale; // upwards rather than downwards for simplicity, but on the real hardware it's
timers_[c].divisor = timers_[c].prescale - (dividend % timers_[c].prescale); // pretty safe to assume it actually counted downwards. So the clamp to 0 is
if(decrements) decrement_timer(c, decrements); // because gymnastics may need to occur when the prescale value is altered, e.g.
// if a prescale of 256 is set and the prescale_count is currently 2 then the
// counter should roll over in 254 cycles. If the user at that point changes the
// prescale_count to 1 then the counter will need to be altered to -253 and
// allowed to keep counting up until it crosses both 0 and 1.
const int dividend = timers_[c].prescale_count + cycles;
const int decrements = std::max(dividend / timers_[c].prescale, 0);
if(decrements) {
decrement_timer(c, decrements);
timers_[c].prescale_count = dividend % timers_[c].prescale;
} else {
timers_[c].prescale_count += cycles;
}
} }
} }
} }
@ -198,11 +213,17 @@ HalfCycles MFP68901::get_next_sequence_point() {
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) { void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale); LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale);
timers_[timer].mode = mode; timers_[timer].mode = mode;
timers_[timer].prescale = prescale;
if(reset_timer) { if(reset_timer) {
timers_[timer].divisor = prescale; timers_[timer].prescale_count = 0;
timers_[timer].value = timers_[timer].reload_value; timers_[timer].value = timers_[timer].reload_value;
} else {
// This hoop is because the prescale_count here goes upward but I'm assuming it goes downward in
// real hardware. Therefore this deals with the "switched to a lower prescaling" case whereby the
// old cycle should be allowed naturally to expire.
timers_[timer].prescale_count = prescale - (timers_[timer].prescale - timers_[timer].prescale_count);
} }
timers_[timer].prescale = prescale;
} }
void MFP68901::set_timer_data(int timer, uint8_t value) { void MFP68901::set_timer_data(int timer, uint8_t value) {

View File

@ -93,7 +93,7 @@ class MFP68901: public ClockingHint::Source {
uint8_t value = 0; uint8_t value = 0;
uint8_t reload_value = 0; uint8_t reload_value = 0;
int prescale = 1; int prescale = 1;
int divisor = 1; int prescale_count = 1;
bool event_input = false; bool event_input = false;
} timers_[4]; } timers_[4];
uint8_t timer_ab_control_[2] = { 0, 0 }; uint8_t timer_ab_control_[2] = { 0, 0 };

View File

@ -28,6 +28,7 @@
#include "../../../ClockReceiver/JustInTime.hpp" #include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../ClockReceiver/ForceInline.hpp" #include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Configurable/StandardOptions.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
@ -40,6 +41,12 @@
namespace Atari { namespace Atari {
namespace ST { namespace ST {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape)
);
}
const int CLOCK_RATE = 8021247; const int CLOCK_RATE = 8021247;
using Target = Analyser::Static::Target; using Target = Analyser::Static::Target;
@ -57,6 +64,7 @@ class ConcreteMachine:
public Activity::Source, public Activity::Source,
public MediaTarget::Machine, public MediaTarget::Machine,
public GI::AY38910::PortHandler, public GI::AY38910::PortHandler,
public Configurable::Device,
public Video::RangeObserver { public Video::RangeObserver {
public: public:
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
@ -130,6 +138,10 @@ class ConcreteMachine:
video_->set_scan_target(scan_target); video_->set_scan_target(scan_target);
} }
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() final { Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_; return &speaker_;
} }
@ -613,6 +625,30 @@ class ConcreteMachine:
void video_did_change_access_range(Video *video) final { void video_did_change_access_range(Video *video) final {
video_range_ = video->get_memory_access_range(); video_range_ = video->get_memory_access_range();
} }
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Atari::ST::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
}; };
} }

View File

@ -16,6 +16,9 @@
namespace Atari { namespace Atari {
namespace ST { namespace ST {
/// @returns The options available for an Atari ST.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine { class Machine {
public: public:
virtual ~Machine(); virtual ~Machine();

View File

@ -107,11 +107,11 @@ void DMAController::write(int address, uint16_t value) {
// of the byte it is adjusting. // of the byte it is adjusting.
case 4: address_ = int((address_ & 0x00ffff) | ((value & 0xff) << 16)); break; case 4: address_ = int((address_ & 0x00ffff) | ((value & 0xff) << 16)); break;
case 5: case 5:
if((value << 8) ^ address_ & ~(value << 8) & 0x8000) address_ += 0x10000; if(((value << 8) ^ address_) & ~(value << 8) & 0x8000) address_ += 0x10000;
address_ = int((address_ & 0xff00ff) | ((value & 0xff) << 8)); address_ = int((address_ & 0xff00ff) | ((value & 0xff) << 8));
break; break;
case 6: case 6:
if(value ^ address_ & ~value & 0x80) address_ += 0x100; if((value ^ address_) & ~value & 0x80) address_ += 0x100;
address_ = int((address_ & 0xffff00) | ((value & 0xfe) << 0)); address_ = int((address_ & 0xffff00) | ((value & 0xfe) << 0));
break; // Lowest bit: discarded. break; // Lowest bit: discarded.
} }

View File

@ -122,6 +122,10 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target); crt_.set_scan_target(scan_target);
} }
void Video::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
void Video::run_for(HalfCycles duration) { void Video::run_for(HalfCycles duration) {
deferrer_.run_for(duration); deferrer_.run_for(duration);
} }
@ -489,6 +493,7 @@ void Video::Shifter::flush_output(OutputMode next_mode) {
switch(output_mode_) { switch(output_mode_) {
case OutputMode::Sync: crt_.output_sync(duration_); break; case OutputMode::Sync: crt_.output_sync(duration_); break;
case OutputMode::Blank: crt_.output_blank(duration_); break; case OutputMode::Blank: crt_.output_blank(duration_); break;
case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_); break;
case OutputMode::Border: { case OutputMode::Border: {
// if(!border_colour_) { // if(!border_colour_) {
// crt_.output_blank(duration_); // crt_.output_blank(duration_);
@ -508,9 +513,29 @@ void Video::Shifter::flush_output(OutputMode next_mode) {
output_mode_ = next_mode; output_mode_ = next_mode;
} }
void Video::Shifter::output_colour_burst(int duration) {
// More hackery afoot here; if and when duration_ crosses a threshold of 40,
// output 40 cycles of colour burst and then redirect to blank.
if(output_mode_ != OutputMode::ColourBurst) {
flush_output(OutputMode::ColourBurst);
}
duration_ += duration;
if(duration_ >= 40) {
const int blank_duration = duration_ - 40;
duration_ = 40;
flush_output(OutputMode::Blank);
output_blank(blank_duration);
}
}
void Video::Shifter::output_blank(int duration) { void Video::Shifter::output_blank(int duration) {
if(output_mode_ != OutputMode::Blank) { if(output_mode_ != OutputMode::Blank) {
// Bit of a hack: if this is a transition from sync or we're really in
// colour burst, divert into that.
if(output_mode_ == OutputMode::Sync || output_mode_ == OutputMode::ColourBurst) {
output_colour_burst(duration);
return;
}
flush_output(OutputMode::Blank); flush_output(OutputMode::Blank);
} }
duration_ += duration; duration_ += duration;

View File

@ -37,6 +37,11 @@ class Video {
*/ */
void set_scan_target(Outputs::Display::ScanTarget *scan_target); void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/*!
Sets the type of output.
*/
void set_display_type(Outputs::Display::DisplayType);
/*! /*!
Produces the next @c duration period of pixels. Produces the next @c duration period of pixels.
*/ */
@ -168,13 +173,14 @@ class Video {
void output_sync(int duration); void output_sync(int duration);
void output_border(int duration, OutputBpp bpp); void output_border(int duration, OutputBpp bpp);
void output_pixels(int duration, OutputBpp bpp); void output_pixels(int duration, OutputBpp bpp);
void output_colour_burst(int duration);
void load(uint64_t value); void load(uint64_t value);
private: private:
int duration_ = 0; int duration_ = 0;
enum class OutputMode { enum class OutputMode {
Sync, Blank, Border, Pixels Sync, Blank, Border, Pixels, ColourBurst
} output_mode_ = OutputMode::Sync; } output_mode_ = OutputMode::Sync;
uint16_t border_colour_ = 0; uint16_t border_colour_ = 0;
OutputBpp bpp_ = OutputBpp::Four; OutputBpp bpp_ = OutputBpp::Four;

View File

@ -143,6 +143,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AtariST), Atari::ST::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Macintosh), Apple::Macintosh::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Macintosh), Apple::Macintosh::get_options()));

View File

@ -4916,7 +4916,7 @@
"$(USER_LIBRARY_DIR)/Frameworks", "$(USER_LIBRARY_DIR)/Frameworks",
); );
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_OPTIMIZATION_LEVEL = 3; GCC_OPTIMIZATION_LEVEL = fast;
MACOSX_DEPLOYMENT_TARGET = 10.12; MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };

View File

@ -228,6 +228,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
case Analyser::Machine::AmstradCPC: return @"CompositeOptions"; case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
case Analyser::Machine::AppleII: return @"AppleIIOptions"; case Analyser::Machine::AppleII: return @"AppleIIOptions";
case Analyser::Machine::Atari2600: return @"Atari2600Options"; case Analyser::Machine::Atari2600: return @"Atari2600Options";
case Analyser::Machine::AtariST: return @"CompositeOptions";
case Analyser::Machine::ColecoVision: return @"CompositeOptions"; case Analyser::Machine::ColecoVision: return @"CompositeOptions";
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions"; case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::Macintosh: return @"MacintoshOptions"; case Analyser::Machine::Macintosh: return @"MacintoshOptions";