2023-01-01 19:20:45 +00:00
|
|
|
//
|
|
|
|
// 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"
|
2023-01-07 03:39:46 +00:00
|
|
|
#include "PersonalityTraits.hpp"
|
2023-01-01 19:20:45 +00:00
|
|
|
|
2023-04-23 16:08:07 +00:00
|
|
|
namespace TI::TMS {
|
2023-01-01 19:20:45 +00:00
|
|
|
|
2023-01-10 03:34:56 +00:00
|
|
|
enum class Clock {
|
|
|
|
Internal,
|
|
|
|
TMSPixel,
|
|
|
|
TMSMemoryWindow,
|
|
|
|
CRT
|
|
|
|
};
|
|
|
|
|
|
|
|
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
|
|
|
static_assert(
|
|
|
|
is_classic_vdp(personality) ||
|
|
|
|
is_yamaha_vdp(personality) ||
|
|
|
|
(personality == Personality::MDVDP)
|
|
|
|
);
|
|
|
|
|
|
|
|
switch(clk) {
|
|
|
|
case Clock::TMSPixel: return 342;
|
|
|
|
case Clock::TMSMemoryWindow: return 171;
|
|
|
|
case Clock::CRT: return 1368;
|
|
|
|
case Clock::Internal:
|
|
|
|
if constexpr (is_classic_vdp(personality)) {
|
|
|
|
return 342;
|
|
|
|
} else if constexpr (is_yamaha_vdp(personality)) {
|
|
|
|
return 1368;
|
|
|
|
} else if constexpr (personality == Personality::MDVDP) {
|
|
|
|
return 3420;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
|
|
|
|
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
|
|
|
|
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
|
|
|
|
}
|
2023-01-07 03:39:46 +00:00
|
|
|
|
2023-01-08 18:58:12 +00:00
|
|
|
/// Provides default timing measurements that duplicate the layout of a TMS9928's line,
|
|
|
|
/// scaled to the clock rate specified.
|
2023-01-10 03:34:56 +00:00
|
|
|
template <Personality personality> struct StandardTiming {
|
2023-01-07 18:10:51 +00:00
|
|
|
/// The total number of internal cycles per line of output.
|
2023-01-10 03:34:56 +00:00
|
|
|
constexpr static int CyclesPerLine = clock_rate<personality, Clock::Internal>();
|
2023-01-07 18:10:51 +00:00
|
|
|
|
|
|
|
/// The number of internal cycles that must elapse between a request to read or write and
|
|
|
|
/// it becoming a candidate for action.
|
2023-01-07 17:48:43 +00:00
|
|
|
constexpr static int VRAMAccessDelay = 6;
|
2023-01-07 03:39:46 +00:00
|
|
|
};
|
|
|
|
|
2023-01-10 03:34:56 +00:00
|
|
|
/// Provides concrete, specific timing for the nominated personality.
|
|
|
|
template <Personality personality> struct Timing: public StandardTiming<personality> {};
|
2023-01-02 19:59:36 +00:00
|
|
|
|
2023-01-02 02:20:30 +00:00
|
|
|
/*!
|
2023-01-10 03:55:46 +00:00
|
|
|
Provides a [potentially-]stateful conversion between the external and internal clocks.
|
|
|
|
Unlike the other clock conversions, this one may be non-integral, requiring that
|
|
|
|
an error term be tracked.
|
2023-01-02 02:20:30 +00:00
|
|
|
*/
|
2023-01-01 19:20:45 +00:00
|
|
|
template <Personality personality> class ClockConverter {
|
|
|
|
public:
|
|
|
|
/*!
|
2023-01-02 03:18:39 +00:00
|
|
|
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.
|
2023-01-02 19:59:36 +00:00
|
|
|
|
|
|
|
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.
|
2023-01-01 19:20:45 +00:00
|
|
|
*/
|
|
|
|
int to_internal(int source) {
|
2023-01-02 19:59:36 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2023-01-01 19:20:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2023-01-10 03:55:46 +00:00
|
|
|
Provides the number of external cycles that need to begin from now in order to
|
|
|
|
get at least @c internal_cycles into the future.
|
2023-01-01 19:20:45 +00:00
|
|
|
*/
|
|
|
|
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
|
2023-01-02 19:59:36 +00:00
|
|
|
// Logic here correlates with multipliers as per @c to_internal.
|
|
|
|
switch(personality) {
|
|
|
|
default:
|
2023-01-10 03:55:46 +00:00
|
|
|
// Relative to the external clock multiplied by 3, it will definitely take this
|
|
|
|
// many cycles to complete a further (internal_cycles - 1) after the current one.
|
|
|
|
internal_cycles = (internal_cycles - 1) << 2;
|
|
|
|
|
|
|
|
// It will also be necessary to complete the current one.
|
|
|
|
internal_cycles += 4 - cycles_error_;
|
|
|
|
|
|
|
|
// Round up to get the first external cycle after
|
|
|
|
// the number of internal_cycles has elapsed.
|
|
|
|
return HalfCycles((internal_cycles + 2) / 3);
|
2023-01-02 19:59:36 +00:00
|
|
|
|
|
|
|
case Personality::V9938:
|
|
|
|
case Personality::V9958:
|
2023-01-09 02:25:22 +00:00
|
|
|
return HalfCycles((internal_cycles + 2) / 3);
|
2023-01-02 19:59:36 +00:00
|
|
|
|
|
|
|
case Personality::MDVDP:
|
2023-01-10 03:55:46 +00:00
|
|
|
internal_cycles = (internal_cycles - 1) << 1;
|
|
|
|
internal_cycles += 2 - cycles_error_;
|
|
|
|
return HalfCycles((internal_cycles + 6) / 7);
|
2023-01-02 19:59:36 +00:00
|
|
|
}
|
2023-01-01 19:20:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2023-01-02 02:20:30 +00:00
|
|
|
// Holds current residue in conversion from the external to
|
|
|
|
// internal clock.
|
2023-01-01 19:20:45 +00:00
|
|
|
int cycles_error_ = 0;
|
|
|
|
};
|
|
|
|
|
2023-04-23 02:58:23 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
template <Personality personality, typename Enable = void> struct LineLayout;
|
|
|
|
|
|
|
|
// Line layout is:
|
|
|
|
//
|
|
|
|
// [0, EndOfSync] sync
|
|
|
|
// (EndOfSync, StartOfColourBurst] blank
|
|
|
|
// (StartOfColourBurst, EndOfColourBurst] colour burst
|
|
|
|
// (EndOfColourBurst, EndOfLeftErase] blank
|
|
|
|
// (EndOfLeftErase, EndOfLeftBorder] border colour
|
|
|
|
// (EndOfLeftBorder, EndOfPixels] pixel content
|
|
|
|
// (EndOfPixels, EndOfRightBorder] border colour
|
|
|
|
// [EndOfRightBorder, <end of line>] blank
|
|
|
|
//
|
|
|
|
// ... with minor caveats:
|
2023-04-24 02:38:42 +00:00
|
|
|
// * horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
|
|
|
|
// * the Sega VDPs may programatically extend the left border; and
|
|
|
|
// * text mode on all VDPs adjusts border width.
|
2023-04-23 02:58:23 +00:00
|
|
|
|
2023-04-23 16:01:33 +00:00
|
|
|
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
|
2023-04-23 02:58:23 +00:00
|
|
|
constexpr static int EndOfSync = 26;
|
2023-04-23 03:11:01 +00:00
|
|
|
constexpr static int StartOfColourBurst = 29;
|
|
|
|
constexpr static int EndOfColourBurst = 43;
|
2023-04-23 02:58:23 +00:00
|
|
|
constexpr static int EndOfLeftErase = 50;
|
|
|
|
constexpr static int EndOfLeftBorder = 63;
|
|
|
|
constexpr static int EndOfPixels = 319;
|
|
|
|
constexpr static int EndOfRightBorder = 334;
|
2023-04-24 02:18:36 +00:00
|
|
|
|
|
|
|
constexpr static int TextModeEndOfLeftBorder = 69;
|
|
|
|
constexpr static int TextModeEndOfPixels = 309;
|
2023-04-23 02:58:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
|
|
|
constexpr static int EndOfSync = 100;
|
2023-04-23 03:11:01 +00:00
|
|
|
constexpr static int StartOfColourBurst = 113;
|
|
|
|
constexpr static int EndOfColourBurst = 167;
|
2023-04-23 02:58:23 +00:00
|
|
|
constexpr static int EndOfLeftErase = 202;
|
|
|
|
constexpr static int EndOfLeftBorder = 258;
|
|
|
|
constexpr static int EndOfPixels = 1282;
|
|
|
|
constexpr static int EndOfRightBorder = 1341;
|
2023-04-24 02:18:36 +00:00
|
|
|
|
|
|
|
constexpr static int TextModeEndOfLeftBorder = 294;
|
|
|
|
constexpr static int TextModeEndOfPixels = 1254;
|
2023-04-23 02:58:23 +00:00
|
|
|
};
|
|
|
|
|
2023-01-01 19:20:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* ClockConverter_hpp */
|