diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp index 715140843..628f1566a 100644 --- a/Components/9918/Implementation/9918.cpp +++ b/Components/9918/Implementation/9918.cpp @@ -218,9 +218,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { // Convert 456 clocked half cycles per line to 342 internal cycles per line; // the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised // for this part. So multiply by three quarters. - int int_cycles = int(cycles.as_integral() * 3) + this->cycles_error_; - this->cycles_error_ = int_cycles & 3; - int_cycles >>= 2; + int int_cycles = this->clock_converter_.to_internal(cycles.as<int>()); if(!int_cycles) return; // There are two intertwined processes here, 'writing' (which means writing to the @@ -236,7 +234,10 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { if(write_cycles_pool) { // Determine how much writing to do. - const int write_cycles = std::min(342 - this->write_pointer_.column, write_cycles_pool); + const int write_cycles = std::min( + this->clock_converter_.CyclesPerLine - this->write_pointer_.column, + write_cycles_pool + ); const int end_column = this->write_pointer_.column + write_cycles; LineBuffer &line_buffer = this->line_buffers_[this->write_pointer_.row]; @@ -274,16 +275,16 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { // Perform memory accesses. // ------------------------ #define fetch(function) \ - if(final_window != 171) { \ + if(final_window != this->clock_converter_.AccessWindowCyclesPerLine) { \ function<true>(first_window, final_window);\ } else {\ function<false>(first_window, final_window);\ } - // column_ and end_column are in 342-per-line cycles; - // adjust them to a count of windows. - const int first_window = this->write_pointer_.column >> 1; - const int final_window = end_column >> 1; + // Adjust column_ and end_column to the access-window clock before calling + // the mode-applicable fetch function. + const int first_window = this->clock_converter_.to_access_clock(this->write_pointer_.column); + const int final_window = this->clock_converter_.to_access_clock(end_column); if(first_window != final_window) { switch(line_buffer.line_mode) { case LineMode::Text: fetch(this->template fetch_tms_text); break; @@ -336,7 +337,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { this->write_pointer_.column = end_column; write_cycles_pool -= write_cycles; - if(this->write_pointer_.column == 342) { + if(this->write_pointer_.column == this->clock_converter_.CyclesPerLine) { this->write_pointer_.column = 0; this->write_pointer_.row = (this->write_pointer_.row + 1) % this->mode_timing_.total_lines; LineBuffer &next_line_buffer = this->line_buffers_[this->write_pointer_.row]; @@ -345,7 +346,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { this->set_current_screen_mode(); // Based on the output mode, pick a line mode. - next_line_buffer.first_pixel_output_column = 86; + next_line_buffer.first_pixel_output_column = 86; // TODO: these should be a function of ClockConverter::CyclesPerLine. next_line_buffer.next_border_column = 342; this->mode_timing_.maximum_visible_sprites = 4; switch(this->screen_mode_) { @@ -379,25 +380,31 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { if(read_cycles_pool) { // Determine how much time has passed in the remainder of this line, and proceed. - const int target_read_cycles = std::min(342 - this->read_pointer_.column, read_cycles_pool); + const int target_read_cycles = std::min( + this->clock_converter_.CyclesPerLine - this->read_pointer_.column, + read_cycles_pool + ); int read_cycles_performed = 0; uint32_t next_cram_value = 0; while(read_cycles_performed < target_read_cycles) { + int read_cycles = target_read_cycles - read_cycles_performed; + if(!read_cycles) continue; + const uint32_t cram_value = next_cram_value; next_cram_value = 0; - int read_cycles = target_read_cycles - read_cycles_performed; - if(!this->upcoming_cram_dots_.empty() && this->upcoming_cram_dots_.front().location.row == this->read_pointer_.row) { - int time_until_dot = this->upcoming_cram_dots_.front().location.column - this->read_pointer_.column; + if constexpr (is_sega_vdp(personality)) { + if(!this->upcoming_cram_dots_.empty() && this->upcoming_cram_dots_.front().location.row == this->read_pointer_.row) { + int time_until_dot = this->upcoming_cram_dots_.front().location.column - this->read_pointer_.column; - if(time_until_dot < read_cycles) { - read_cycles = time_until_dot; - next_cram_value = this->upcoming_cram_dots_.front().value; - this->upcoming_cram_dots_.erase(this->upcoming_cram_dots_.begin()); + if(time_until_dot < read_cycles) { + read_cycles = time_until_dot; + next_cram_value = this->upcoming_cram_dots_.front().value; + this->upcoming_cram_dots_.erase(this->upcoming_cram_dots_.begin()); + } } } - if(!read_cycles) continue; read_cycles_performed += read_cycles; const int end_column = this->read_pointer_.column + read_cycles; @@ -418,6 +425,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { #define border(left, right) intersect(left, right, this->output_border(end - start, cram_value)) + // TODO: CRT clock might need to change? if(line_buffer.line_mode == LineMode::Refresh || this->read_pointer_.row > this->mode_timing_.pixel_lines) { if(this->read_pointer_.row >= this->mode_timing_.first_vsync_line && this->read_pointer_.row < this->mode_timing_.first_vsync_line+4) { // Vertical sync. @@ -507,7 +515,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) { } read_cycles_pool -= target_read_cycles; - if(this->read_pointer_.column == 342) { + if(this->read_pointer_.column == this->clock_converter_.CyclesPerLine) { this->read_pointer_.column = 0; this->read_pointer_.row = (this->read_pointer_.row + 1) % this->mode_timing_.total_lines; } @@ -750,11 +758,6 @@ uint8_t TMS9918<personality>::read(int address) { return result; } -template <Personality personality> -HalfCycles Base<personality>::half_cycles_before_internal_cycles(int internal_cycles) const { - return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3); -} - template <Personality personality> HalfCycles TMS9918<personality>::get_next_sequence_point() const { if(!this->generate_interrupts_ && !this->enable_line_interrupts_) return HalfCycles::max(); @@ -769,7 +772,7 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const { ) % frame_length; if(!time_until_frame_interrupt) time_until_frame_interrupt = frame_length; - if(!this->enable_line_interrupts_) return this->half_cycles_before_internal_cycles(time_until_frame_interrupt); + if(!this->enable_line_interrupts_) return this->clock_converter_.half_cycles_before_internal_cycles(time_until_frame_interrupt); // Calculate when the next line interrupt will occur. int next_line_interrupt_row = -1; @@ -797,17 +800,17 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const { // the frame end interrupt or no interrupt pending as appropriate. if(next_line_interrupt_row == -1) { return this->generate_interrupts_ ? - this->half_cycles_before_internal_cycles(time_until_frame_interrupt) : + this->clock_converter_.half_cycles_before_internal_cycles(time_until_frame_interrupt) : HalfCycles::max(); } // Figure out the number of internal cycles until the next line interrupt, which is the amount // of time to the next tick over and then next_line_interrupt_row - row_ lines further. const int local_cycles_until_line_interrupt = cycles_to_next_interrupt_threshold + (next_line_interrupt_row - line_of_next_interrupt_threshold) * 342; - if(!this->generate_interrupts_) return this->half_cycles_before_internal_cycles(local_cycles_until_line_interrupt); + if(!this->generate_interrupts_) return this->clock_converter_.half_cycles_before_internal_cycles(local_cycles_until_line_interrupt); // Return whichever interrupt is closer. - return this->half_cycles_before_internal_cycles(std::min(local_cycles_until_line_interrupt, time_until_frame_interrupt)); + return this->clock_converter_.half_cycles_before_internal_cycles(std::min(local_cycles_until_line_interrupt, time_until_frame_interrupt)); } template <Personality personality> @@ -825,7 +828,7 @@ HalfCycles TMS9918<personality>::get_time_until_line(int line) { line += this->mode_timing_.total_lines; } - return this->half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*342); + return this->clock_converter_.half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*342); } template <Personality personality> diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 396920ee9..93350cfcd 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -11,6 +11,7 @@ #include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" +#include "ClockConverter.hpp" #include <array> #include <cassert> @@ -199,11 +200,7 @@ template <Personality personality> struct Base { uint8_t text_colour_ = 0; uint8_t background_colour_ = 0; - // This implementation of this chip officially accepts a 3.58Mhz clock, but runs - // internally at 5.37Mhz. The following two help to maintain a lossless conversion - // from the one to the other. - int cycles_error_ = 0; - HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const; + ClockConverter<personality> clock_converter_; // Internal mechanisms for position tracking. int latched_column_ = 0; diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp new file mode 100644 index 000000000..da4b15c16 --- /dev/null +++ b/Components/9918/Implementation/ClockConverter.hpp @@ -0,0 +1,64 @@ +// +// ClockConverter.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/01/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef ClockConverter_hpp +#define ClockConverter_hpp + +#include "../9918.hpp" + +namespace TI { +namespace TMS { + +template <Personality personality> class ClockConverter { + public: + /*! + Converts a number of **half-cycles** to an internal number + of **cycles**. + */ + int to_internal(int source) { + // Default behaviour is top apply a multiplication by 3/4. + const int result = source * 3 + cycles_error_; + cycles_error_ = result & 3; + return result >> 2; + } + + /*! + Provides the number of external cycles that will need to pass in order to advance + _at least_ @c internal_cycles into the future. + */ + HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const { + return HalfCycles( + ((internal_cycles << 2) + (2 - cycles_error_)) / 3 + ); + } + + /* + Converts a position in internal cycles to its corresponding position + on the access-window clock. + */ + static constexpr int to_access_clock(int source) { + return source >> 1; + } + + /// The number of internal cycles in a single line. + constexpr static int CyclesPerLine = 342; + + /// Indicates the number of access-window cycles in a single line. + constexpr static int AccessWindowCyclesPerLine = 171; + + private: + // This implementation of this chip officially accepts a 3.58Mhz clock, but runs + // internally at 5.37Mhz. The following two help to maintain a lossless conversion + // from the one to the other. + int cycles_error_ = 0; +}; + +} +} + +#endif /* ClockConverter_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 595676063..ef9b2324a 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1317,6 +1317,7 @@ 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; }; 4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = "<group>"; }; 4B43983829620FB1006B0BFC /* 9918.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 9918.cpp; sourceTree = "<group>"; }; + 4B43983C29621024006B0BFC /* ClockConverter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockConverter.hpp; sourceTree = "<group>"; }; 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; }; 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; @@ -4734,6 +4735,7 @@ children = ( 4B43983829620FB1006B0BFC /* 9918.cpp */, 4BD388411FE34E010042B588 /* 9918Base.hpp */, + 4B43983C29621024006B0BFC /* ClockConverter.hpp */, ); path = Implementation; sourceTree = "<group>";