mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 23:52:26 +00:00
Merge pull request #1141 from TomHarte/ConvertFromGrauw
Clean up further internal magic constants.
This commit is contained in:
commit
dd3fc43bd3
@ -35,12 +35,16 @@ Base<personality>::Base() :
|
|||||||
|
|
||||||
// "For a line interrupt, /INT is pulled low 608 mclks into the appropriate scanline relative to pixel 0.
|
// "For a line interrupt, /INT is pulled low 608 mclks into the appropriate scanline relative to pixel 0.
|
||||||
// This is 3 mclks before the rising edge of /HSYNC which starts the next scanline."
|
// This is 3 mclks before the rising edge of /HSYNC which starts the next scanline."
|
||||||
|
//
|
||||||
|
// i.e. it's 304 internal clocks after the end of the left border.
|
||||||
mode_timing_.line_interrupt_position = (LineLayout<personality>::EndOfLeftBorder + 304) % LineLayout<personality>::CyclesPerLine;
|
mode_timing_.line_interrupt_position = (LineLayout<personality>::EndOfLeftBorder + 304) % LineLayout<personality>::CyclesPerLine;
|
||||||
|
|
||||||
// For a frame interrupt, /INT is pulled low 607 mclks into scanline 192 (of scanlines 0 through 261) relative to pixel 0.
|
// For a frame interrupt, /INT is pulled low 607 mclks into scanline 192 (of scanlines 0 through 261) relative to pixel 0.
|
||||||
// This is 4 mclks before the rising edge of /HSYNC which starts the next scanline.
|
// This is 4 mclks before the rising edge of /HSYNC which starts the next scanline.
|
||||||
|
//
|
||||||
|
// i.e. it's 1/2 cycle before the line interrupt position, which I have rounded. Ugh.
|
||||||
mode_timing_.end_of_frame_interrupt_position.column = mode_timing_.line_interrupt_position - 1;
|
mode_timing_.end_of_frame_interrupt_position.column = mode_timing_.line_interrupt_position - 1;
|
||||||
mode_timing_.end_of_frame_interrupt_position.row = 193;
|
mode_timing_.end_of_frame_interrupt_position.row = 192 + (LineLayout<personality>::EndOfLeftBorder + 304) / LineLayout<personality>::CyclesPerLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (is_yamaha_vdp(personality)) {
|
if constexpr (is_yamaha_vdp(personality)) {
|
||||||
@ -81,6 +85,7 @@ TMS9918<personality>::TMS9918() {
|
|||||||
|
|
||||||
template <Personality personality>
|
template <Personality personality>
|
||||||
void TMS9918<personality>::set_tv_standard(TVStandard standard) {
|
void TMS9918<personality>::set_tv_standard(TVStandard standard) {
|
||||||
|
// TODO: the Yamaha is programmable on this at runtime.
|
||||||
this->tv_standard_ = standard;
|
this->tv_standard_ = standard;
|
||||||
switch(standard) {
|
switch(standard) {
|
||||||
case TVStandard::PAL:
|
case TVStandard::PAL:
|
||||||
@ -215,8 +220,13 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
|||||||
// Latch scrolling position, if necessary.
|
// Latch scrolling position, if necessary.
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
if constexpr (is_sega_vdp(personality)) {
|
if constexpr (is_sega_vdp(personality)) {
|
||||||
if(this->fetch_pointer_.column < 61 && end_column >= 61) {
|
if(!this->fetch_pointer_.row) {
|
||||||
if(!this->fetch_pointer_.row) {
|
// TODO: where did this magic constant come from? https://www.smspower.org/forums/17970-RoadRashHow#111000 mentioned in passing
|
||||||
|
// that "the vertical scroll register is latched at the start of the active display" and this is two clocks before that, so it's
|
||||||
|
// not uncompelling. I can just no longer find my source.
|
||||||
|
constexpr auto latch_time = LineLayout<personality>::EndOfLeftBorder - 2;
|
||||||
|
static_assert(latch_time > 0);
|
||||||
|
if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
|
||||||
Storage<personality>::latched_vertical_scroll_ = Storage<personality>::vertical_scroll_;
|
Storage<personality>::latched_vertical_scroll_ = Storage<personality>::vertical_scroll_;
|
||||||
|
|
||||||
if(Storage<personality>::mode4_enable_) {
|
if(Storage<personality>::mode4_enable_) {
|
||||||
@ -235,7 +245,6 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------
|
// ------------------------
|
||||||
// Perform memory accesses.
|
// Perform memory accesses.
|
||||||
// ------------------------
|
// ------------------------
|
||||||
|
@ -11,14 +11,22 @@
|
|||||||
|
|
||||||
#include "../9918.hpp"
|
#include "../9918.hpp"
|
||||||
#include "PersonalityTraits.hpp"
|
#include "PersonalityTraits.hpp"
|
||||||
|
#include "LineLayout.hpp"
|
||||||
|
|
||||||
namespace TI::TMS {
|
namespace TI::TMS {
|
||||||
|
|
||||||
enum class Clock {
|
enum class Clock {
|
||||||
|
/// Whatever rate this VDP runs at, with location 0 being "the start" of the line per internal preference.
|
||||||
Internal,
|
Internal,
|
||||||
|
/// A 342-cycle/line clock with the same start position as ::Internal.
|
||||||
TMSPixel,
|
TMSPixel,
|
||||||
|
/// A 171-cycle/line clock that begins at the memory window which starts straight after ::Internal = 0.
|
||||||
TMSMemoryWindow,
|
TMSMemoryWindow,
|
||||||
CRT
|
/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
|
||||||
|
CRT,
|
||||||
|
/// Provides the same clock rate as ::Internal but is relocated so that 0 is the start of horizontal sync — very not coincidentally,
|
||||||
|
/// where Grauw puts 0 on his detailed TMS and Yamaha timing diagrams.
|
||||||
|
FromStartOfSync,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
||||||
@ -33,6 +41,7 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
|
|||||||
case Clock::TMSMemoryWindow: return 171;
|
case Clock::TMSMemoryWindow: return 171;
|
||||||
case Clock::CRT: return 1368;
|
case Clock::CRT: return 1368;
|
||||||
case Clock::Internal:
|
case Clock::Internal:
|
||||||
|
case Clock::FromStartOfSync:
|
||||||
if constexpr (is_classic_vdp(personality)) {
|
if constexpr (is_classic_vdp(personality)) {
|
||||||
return 342;
|
return 342;
|
||||||
} else if constexpr (is_yamaha_vdp(personality)) {
|
} else if constexpr (is_yamaha_vdp(personality)) {
|
||||||
@ -43,17 +52,41 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
|
/// Statelessly converts @c length to the internal clock for @c personality; applies conversions per the list of clocks in left-to-right order.
|
||||||
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
|
template <Personality personality, Clock head, Clock... tail> constexpr int to_internal(int length) {
|
||||||
|
if constexpr (head == Clock::FromStartOfSync) {
|
||||||
|
length = (length + LineLayout<personality>::StartOfSync) % LineLayout<personality>::CyclesPerLine;
|
||||||
|
} else {
|
||||||
|
length = length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, head>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (!sizeof...(tail)) {
|
||||||
|
return length;
|
||||||
|
} else {
|
||||||
|
return to_internal<personality, tail...>(length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
|
/// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder.
|
||||||
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
|
template <Personality personality, Clock head, Clock... tail> constexpr int from_internal(int length) {
|
||||||
|
if constexpr (head == Clock::FromStartOfSync) {
|
||||||
|
length =
|
||||||
|
(length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::StartOfSync)
|
||||||
|
% LineLayout<personality>::CyclesPerLine;
|
||||||
|
} else {
|
||||||
|
length = length * clock_rate<personality, head>() / clock_rate<personality, Clock::Internal>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (!sizeof...(tail)) {
|
||||||
|
return length;
|
||||||
|
} else {
|
||||||
|
return to_internal<personality, tail...>(length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Provides a [potentially-]stateful conversion between the external and internal clocks.
|
Provides a [potentially-]stateful conversion between the external and internal clocks.
|
||||||
Unlike the other clock conversions, this one may be non-integral, requiring that
|
Unlike the other clock conversions, this may be non-integral, requiring that
|
||||||
an error term be tracked.
|
an error term be tracked.
|
||||||
*/
|
*/
|
||||||
template <Personality personality> class ClockConverter {
|
template <Personality personality> class ClockConverter {
|
||||||
@ -130,72 +163,6 @@ template <Personality personality> class ClockConverter {
|
|||||||
int cycles_error_ = 0;
|
int cycles_error_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
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:
|
|
||||||
// * 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.
|
|
||||||
|
|
||||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
|
|
||||||
constexpr static int EndOfSync = 26;
|
|
||||||
constexpr static int StartOfColourBurst = 29;
|
|
||||||
constexpr static int EndOfColourBurst = 43;
|
|
||||||
constexpr static int EndOfLeftErase = 50;
|
|
||||||
constexpr static int EndOfLeftBorder = 63;
|
|
||||||
constexpr static int EndOfPixels = 319;
|
|
||||||
constexpr static int EndOfRightBorder = 334;
|
|
||||||
|
|
||||||
constexpr static int CyclesPerLine = 342;
|
|
||||||
|
|
||||||
constexpr static int TextModeEndOfLeftBorder = 69;
|
|
||||||
constexpr static int TextModeEndOfPixels = 309;
|
|
||||||
|
|
||||||
constexpr static int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
|
|
||||||
// and falls into the collection gap between the final sprite
|
|
||||||
// graphics and the initial tiles or pixels.
|
|
||||||
|
|
||||||
/// The number of internal cycles that must elapse between a request to read or write and
|
|
||||||
/// it becoming a candidate for action.
|
|
||||||
constexpr static int VRAMAccessDelay = 6;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
|
||||||
constexpr static int EndOfSync = 100;
|
|
||||||
constexpr static int StartOfColourBurst = 113;
|
|
||||||
constexpr static int EndOfColourBurst = 167;
|
|
||||||
constexpr static int EndOfLeftErase = 202;
|
|
||||||
constexpr static int EndOfLeftBorder = 258;
|
|
||||||
constexpr static int EndOfPixels = 1282;
|
|
||||||
constexpr static int EndOfRightBorder = 1341;
|
|
||||||
|
|
||||||
constexpr static int CyclesPerLine = 1368;
|
|
||||||
|
|
||||||
constexpr static int TextModeEndOfLeftBorder = 294;
|
|
||||||
constexpr static int TextModeEndOfPixels = 1254;
|
|
||||||
|
|
||||||
constexpr static int ModeLatchCycle = 144;
|
|
||||||
|
|
||||||
/// The number of internal cycles that must elapse between a request to read or write and
|
|
||||||
/// it becoming a candidate for action.
|
|
||||||
constexpr static int VRAMAccessDelay = 16;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* ClockConverter_hpp */
|
#endif /* ClockConverter_hpp */
|
||||||
|
@ -16,38 +16,19 @@ namespace TI::TMS {
|
|||||||
|
|
||||||
1) input is a start position and an end position; they should perform the proper
|
1) input is a start position and an end position; they should perform the proper
|
||||||
operations for the period: start <= time < end.
|
operations for the period: start <= time < end.
|
||||||
2) times are measured relative to a 172-cycles-per-line clock (so: they directly
|
2) times are measured relative to the an appropriate clock — they directly
|
||||||
count access windows on the TMS and Master System).
|
count access windows on the TMS and Master System, and cycles on a Yamaha.
|
||||||
3) within each sequencer, time 0 is the access window that straddles the beginning of
|
3) within each sequencer, cycle are numbered as per Grauw's timing diagrams. The difference
|
||||||
horizontal sync. Which, conveniently, is the place to which Grauw's timing diagrams
|
between those and internal timing, if there is one, is handled by the dispatcher.
|
||||||
are aligned.
|
|
||||||
4) all of these functions are templated with a `use_end` parameter. That will be true if
|
4) all of these functions are templated with a `use_end` parameter. That will be true if
|
||||||
end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks,
|
end is < [cycles per line], false otherwise. So functions can use it to eliminate
|
||||||
for the more usual path of execution.
|
should-exit-now checks (which is likely to be the more usual path of execution).
|
||||||
|
|
||||||
[Historically:
|
|
||||||
position 0 was the beginning of the access window immediately after the last pattern/data
|
|
||||||
block fetch that would contribute to this line, in a normal 32-column mode. So:
|
|
||||||
|
|
||||||
* it's cycle 309 on Mattias' TMS diagram;
|
|
||||||
* it's cycle 1238 on his V9938 diagram;
|
|
||||||
* it's after the last background render block in Mask of Destiny's Master System timing diagram.
|
|
||||||
|
|
||||||
That division point was selected, albeit arbitrarily, because it puts all the tile
|
|
||||||
fetches for a single line into the same [0, 171] period.
|
|
||||||
|
|
||||||
I'm moving away from this per the desire not to have V9938 output straddle two lines if horizontally-adjusted,
|
|
||||||
amongst other concerns.]
|
|
||||||
|
|
||||||
Provided for the benefit of the methods below:
|
Provided for the benefit of the methods below:
|
||||||
|
|
||||||
* the function external_slot(), which will perform any pending VRAM read/write.
|
* the function external_slot(), which will perform any pending VRAM read/write.
|
||||||
* the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
|
|
||||||
switch(start)-based implementation.
|
|
||||||
|
|
||||||
All functions should just spool data to intermediary storage. This is because for most VDPs there is
|
All functions should just spool data to intermediary storage. Fetching and drawing are decoupled.
|
||||||
a decoupling between fetch pattern and output pattern, and it's neater to keep the same division
|
|
||||||
for the exceptions.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// MARK: - Address mask helpers.
|
// MARK: - Address mask helpers.
|
||||||
@ -73,7 +54,7 @@ template<bool use_end, typename SequencerT> void Base<personality>::dispatch(Seq
|
|||||||
#define index(n) \
|
#define index(n) \
|
||||||
if(use_end && end == n) return; \
|
if(use_end && end == n) return; \
|
||||||
[[fallthrough]]; \
|
[[fallthrough]]; \
|
||||||
case n: fetcher.template fetch<n>();
|
case n: fetcher.template fetch<from_internal<personality, Clock::FromStartOfSync>(n)>();
|
||||||
|
|
||||||
switch(start) {
|
switch(start) {
|
||||||
default: assert(false);
|
default: assert(false);
|
||||||
@ -392,7 +373,7 @@ struct RefreshSequencer {
|
|||||||
|
|
||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
|
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
|
||||||
base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,16 +387,22 @@ struct TextSequencer {
|
|||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
// The first 30 and the final 4 slots are external.
|
// The first 30 and the final 4 slots are external.
|
||||||
if constexpr (cycle < 30 || cycle >= 150) {
|
if constexpr (cycle < 30 || cycle >= 150) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// For the 120 slots in between follow a three-step pattern of:
|
// For the 120 slots in between follow a three-step pattern of:
|
||||||
constexpr int offset = cycle - 30;
|
constexpr int offset = cycle - 30;
|
||||||
constexpr auto column = AddressT(offset / 3);
|
constexpr auto column = AddressT(offset / 3);
|
||||||
switch(offset % 3) {
|
switch(offset % 3) {
|
||||||
case 0: fetcher.fetch_name(column); break; // (1) fetch tile name.
|
case 0: // (1) fetch tile name.
|
||||||
case 1: fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle)); break; // (2) external slot.
|
fetcher.fetch_name(column);
|
||||||
case 2: fetcher.fetch_pattern(column); break; // (3) fetch tile pattern.
|
break;
|
||||||
|
case 1: // (2) external slot.
|
||||||
|
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
|
break;
|
||||||
|
case 2: // (3) fetch tile pattern.
|
||||||
|
fetcher.fetch_pattern(column);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,7 +419,7 @@ struct CharacterSequencer {
|
|||||||
|
|
||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
if(cycle < 5) {
|
if(cycle < 5) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 5) {
|
if(cycle == 5) {
|
||||||
@ -443,7 +430,7 @@ struct CharacterSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle > 14 && cycle < 19) {
|
if(cycle > 14 && cycle < 19) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
|
// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
|
||||||
@ -461,7 +448,7 @@ struct CharacterSequencer {
|
|||||||
case 0: character_fetcher.fetch_name(block); break;
|
case 0: character_fetcher.fetch_name(block); break;
|
||||||
case 1:
|
case 1:
|
||||||
if(!(block & 3)) {
|
if(!(block & 3)) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
} else {
|
} else {
|
||||||
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
||||||
sprite_fetcher.fetch_y(sprite);
|
sprite_fetcher.fetch_y(sprite);
|
||||||
@ -476,7 +463,7 @@ struct CharacterSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle >= 155 && cycle < 157) {
|
if(cycle >= 155 && cycle < 157) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 157) {
|
if(cycle == 157) {
|
||||||
@ -524,7 +511,7 @@ struct SMSSequencer {
|
|||||||
// window 0 to HSYNC low.
|
// window 0 to HSYNC low.
|
||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
if(cycle < 3) {
|
if(cycle < 3) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 3) {
|
if(cycle == 3) {
|
||||||
@ -535,7 +522,7 @@ struct SMSSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 15 || cycle == 16) {
|
if(cycle == 15 || cycle == 16) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 17) {
|
if(cycle == 17) {
|
||||||
@ -556,7 +543,7 @@ struct SMSSequencer {
|
|||||||
case 0: fetcher.fetch_tile_name(block); break;
|
case 0: fetcher.fetch_tile_name(block); break;
|
||||||
case 1:
|
case 1:
|
||||||
if(!(block & 3)) {
|
if(!(block & 3)) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
} else {
|
} else {
|
||||||
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
||||||
fetcher.posit_sprite(sprite);
|
fetcher.posit_sprite(sprite);
|
||||||
@ -568,7 +555,7 @@ struct SMSSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle >= 153 && cycle < 157) {
|
if(cycle >= 153 && cycle < 157) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 157) {
|
if(cycle == 157) {
|
||||||
@ -579,7 +566,7 @@ struct SMSSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle >= 169) {
|
if(cycle >= 169) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
80
Components/9918/Implementation/LineLayout.hpp
Normal file
80
Components/9918/Implementation/LineLayout.hpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
//
|
||||||
|
// LineLayout.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 18/05/2023.
|
||||||
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef LineLayout_h
|
||||||
|
#define LineLayout_h
|
||||||
|
|
||||||
|
namespace TI::TMS {
|
||||||
|
|
||||||
|
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:
|
||||||
|
// * 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.
|
||||||
|
|
||||||
|
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
|
||||||
|
constexpr static int StartOfSync = 0;
|
||||||
|
constexpr static int EndOfSync = 26;
|
||||||
|
constexpr static int StartOfColourBurst = 29;
|
||||||
|
constexpr static int EndOfColourBurst = 43;
|
||||||
|
constexpr static int EndOfLeftErase = 50;
|
||||||
|
constexpr static int EndOfLeftBorder = 63;
|
||||||
|
constexpr static int EndOfPixels = 319;
|
||||||
|
constexpr static int EndOfRightBorder = 334;
|
||||||
|
|
||||||
|
constexpr static int CyclesPerLine = 342;
|
||||||
|
|
||||||
|
constexpr static int TextModeEndOfLeftBorder = 69;
|
||||||
|
constexpr static int TextModeEndOfPixels = 309;
|
||||||
|
|
||||||
|
constexpr static int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
|
||||||
|
// and falls into the collection gap between the final sprite
|
||||||
|
// graphics and the initial tiles or pixels.
|
||||||
|
|
||||||
|
/// The number of internal cycles that must elapse between a request to read or write and
|
||||||
|
/// it becoming a candidate for action.
|
||||||
|
constexpr static int VRAMAccessDelay = 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||||
|
constexpr static int StartOfSync = 0;
|
||||||
|
constexpr static int EndOfSync = 100;
|
||||||
|
constexpr static int StartOfColourBurst = 113;
|
||||||
|
constexpr static int EndOfColourBurst = 167;
|
||||||
|
constexpr static int EndOfLeftErase = 202;
|
||||||
|
constexpr static int EndOfLeftBorder = 258;
|
||||||
|
constexpr static int EndOfPixels = 1282;
|
||||||
|
constexpr static int EndOfRightBorder = 1341;
|
||||||
|
|
||||||
|
constexpr static int CyclesPerLine = 1368;
|
||||||
|
|
||||||
|
constexpr static int TextModeEndOfLeftBorder = 294;
|
||||||
|
constexpr static int TextModeEndOfPixels = 1254;
|
||||||
|
|
||||||
|
constexpr static int ModeLatchCycle = 144;
|
||||||
|
|
||||||
|
/// The number of internal cycles that must elapse between a request to read or write and
|
||||||
|
/// it becoming a candidate for action.
|
||||||
|
constexpr static int VRAMAccessDelay = 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LineLayout_h */
|
@ -86,7 +86,8 @@ struct YamahaFetcher {
|
|||||||
std::array<Event, size> result{};
|
std::array<Event, size> result{};
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
for(int c = 0; c < 1368; c++) {
|
for(int c = 0; c < 1368; c++) {
|
||||||
const auto event = GeneratorT::event(c);
|
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
|
||||||
|
const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
|
||||||
if(!event) {
|
if(!event) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1100,6 +1100,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
|
||||||
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
||||||
42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
|
42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
|
||||||
42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
|
42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
|
||||||
@ -4738,6 +4739,7 @@
|
|||||||
4B43983F2967459B006B0BFC /* Draw.hpp */,
|
4B43983F2967459B006B0BFC /* Draw.hpp */,
|
||||||
4B43983E29628538006B0BFC /* Fetch.hpp */,
|
4B43983E29628538006B0BFC /* Fetch.hpp */,
|
||||||
4B2A3B5B29995FF6007CE366 /* LineBuffer.hpp */,
|
4B2A3B5B29995FF6007CE366 /* LineBuffer.hpp */,
|
||||||
|
428168372A16C25C008ECD27 /* LineLayout.hpp */,
|
||||||
4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */,
|
4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */,
|
||||||
4B2A3B5A29993DFA007CE366 /* Storage.hpp */,
|
4B2A3B5A29993DFA007CE366 /* Storage.hpp */,
|
||||||
4BF0BC732982E54700CCA2B5 /* YamahaCommands.hpp */,
|
4BF0BC732982E54700CCA2B5 /* YamahaCommands.hpp */,
|
||||||
|
Loading…
Reference in New Issue
Block a user