diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp index 628f1566a..461e16690 100644 --- a/Components/9918/Implementation/9918.cpp +++ b/Components/9918/Implementation/9918.cpp @@ -274,24 +274,21 @@ void TMS9918::run_for(const HalfCycles cycles) { // ------------------------ // Perform memory accesses. // ------------------------ -#define fetch(function) \ - if(final_window != this->clock_converter_.AccessWindowCyclesPerLine) { \ - function(first_window, final_window);\ - } else {\ - function(first_window, final_window);\ +#define fetch(function, converter) \ + const int first_window = this->clock_converter_.converter(this->write_pointer_.column); \ + const int final_window = this->clock_converter_.converter(end_column); \ + if(first_window != final_window) break; \ + if(final_window != TMSAccessWindowsPerLine) { \ + function(first_window, final_window); \ + } else { \ + function(first_window, final_window); \ } - // 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; - case LineMode::Character: fetch(this->template fetch_tms_character); break; - case LineMode::SMS: fetch(this->template fetch_sms); break; - case LineMode::Refresh: fetch(this->template fetch_tms_refresh); break; - } + switch(line_buffer.line_mode) { + case LineMode::Text: { fetch(this->template fetch_tms_text, to_tms_access_clock); } break; + case LineMode::Character: { fetch(this->template fetch_tms_character, to_tms_access_clock); } break; + case LineMode::SMS: { fetch(this->template fetch_sms, to_tms_access_clock); } break; + case LineMode::Refresh: { fetch(this->template fetch_tms_refresh, to_tms_access_clock); } break; } #undef fetch diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp index d11aed510..c89f0bc14 100644 --- a/Components/9918/Implementation/ClockConverter.hpp +++ b/Components/9918/Implementation/ClockConverter.hpp @@ -14,6 +14,17 @@ namespace TI { namespace TMS { +template constexpr int cycles_per_line() { + switch(personality) { + default: return 342; + case Personality::V9938: + case Personality::V9958: return 1368; + case Personality::MDVDP: return 3420; + } +} + +constexpr int TMSAccessWindowsPerLine = 171; + /*! This implementation of the TMS, etc mediates between three clocks: @@ -47,12 +58,36 @@ template class ClockConverter { Given that another @c source external **half-cycles** has occurred, indicates how many complete internal **cycles** have additionally elapsed since the last call to @c to_internal. + + E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock + runs at 342 ticks per line, so the proper conversion is to multiply by 3/4. */ 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; + switch(personality) { + // Default behaviour is to apply a multiplication by 3/4; + // this is correct for the TMS and Sega VDPs other than the Mega Drive. + default: { + const int result = source * 3 + cycles_error_; + cycles_error_ = result & 3; + return result >> 2; + } + + // The two Yamaha chips have an internal clock that is four times + // as fast as the TMS, therefore a stateless translation is possible. + case Personality::V9938: + case Personality::V9958: + return source * 3; + + // The Mega Drive runs at 3420 master clocks per line, which is then + // divided by 4 or 5 depending on other state. That's 7 times the + // rate provided to the CPU; given that the input is in half-cycles + // the proper multiplier is therefore 3.5. + case Personality::MDVDP: { + const int result = source * 7 + cycles_error_; + cycles_error_ = result & 1; + return result >> 1; + } + } } /*! @@ -61,24 +96,45 @@ template class ClockConverter { is discarded. */ HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const { - return HalfCycles( - ((internal_cycles << 2) + (2 - cycles_error_)) / 3 - ); + // Logic here correlates with multipliers as per @c to_internal. + switch(personality) { + default: + return HalfCycles( + ((internal_cycles << 2) + (2 - cycles_error_)) / 3 + ); + + case Personality::V9938: + case Personality::V9958: + return HalfCycles(internal_cycles / 3); + + case Personality::MDVDP: + return HalfCycles( + ((internal_cycles << 1) + (1 - cycles_error_)) / 7 + ); + } } /*! Converts a position in internal cycles to its corresponding position - on the memory-access clock. + on the TMS memory-access clock, i.e. scales down to 171 clocks + per line */ - static constexpr int to_access_clock(int source) { - return source >> 1; + static constexpr int to_tms_access_clock(int source) { + switch(personality) { + default: + return source >> 1; + + case Personality::V9938: + case Personality::V9958: + return source >> 3; + + case Personality::MDVDP: + return source / 20; + } } /// 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; + constexpr static int CyclesPerLine = cycles_per_line(); private: // Holds current residue in conversion from the external to