From 4cb7abe13d49f749b5791add1d91c8e54567622e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 May 2023 16:28:05 -0400 Subject: [PATCH 1/7] Update old comment. --- Components/9918/Implementation/Fetch.hpp | 33 +++++------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp index efb972617..41ba56cec 100644 --- a/Components/9918/Implementation/Fetch.hpp +++ b/Components/9918/Implementation/Fetch.hpp @@ -16,38 +16,19 @@ namespace TI::TMS { 1) input is a start position and an end position; they should perform the proper operations for the period: start <= time < end. - 2) times are measured relative to a 172-cycles-per-line clock (so: they directly - count access windows on the TMS and Master System). - 3) within each sequencer, time 0 is the access window that straddles the beginning of - horizontal sync. Which, conveniently, is the place to which Grauw's timing diagrams - are aligned. + 2) times are measured relative to the an appropriate clock — they directly + count access windows on the TMS and Master System, and cycles on a Yamaha. + 3) within each sequencer, cycle are numbered as per Grauw's timing diagrams. The difference + between those and internal timing, if there is one, is handled by the dispatcher. 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, - for 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.] + end is < [cycles per line], false otherwise. So functions can use it to eliminate + should-exit-now checks (which is likely to be the more usual path of execution). Provided for the benefit of the methods below: * 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 - a decoupling between fetch pattern and output pattern, and it's neater to keep the same division - for the exceptions. + All functions should just spool data to intermediary storage. Fetching and drawing are decoupled. */ // MARK: - Address mask helpers. From c76048bff90b13e639271989ccd8a18eb38697c3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 May 2023 16:37:48 -0400 Subject: [PATCH 2/7] Formalise the idea of Grauw as a separate clock. --- .../9918/Implementation/ClockConverter.hpp | 86 ++++--------------- Components/9918/Implementation/LineLayout.hpp | 82 ++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + 3 files changed, 102 insertions(+), 68 deletions(-) create mode 100644 Components/9918/Implementation/LineLayout.hpp diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp index d76746aa7..c4e692ce6 100644 --- a/Components/9918/Implementation/ClockConverter.hpp +++ b/Components/9918/Implementation/ClockConverter.hpp @@ -11,14 +11,21 @@ #include "../9918.hpp" #include "PersonalityTraits.hpp" +#include "LineLayout.hpp" namespace TI::TMS { enum class Clock { + /// Whatever rate this VDP runs at, with location 0 being "the start" of the line per internal preference. Internal, + /// A 342-cycle/line clock with the same start position as ::Internal. TMSPixel, + /// A 171-cycle/line clock that begins at the memory window which starts straight after ::Internal = 0. 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 where Grauw put 0 (i.e. at the start of horizontal sync). + Grauw, }; template constexpr int clock_rate() { @@ -33,6 +40,7 @@ template constexpr int clock_rate() { case Clock::TMSMemoryWindow: return 171; case Clock::CRT: return 1368; case Clock::Internal: + case Clock::Grauw: if constexpr (is_classic_vdp(personality)) { return 342; } else if constexpr (is_yamaha_vdp(personality)) { @@ -43,17 +51,25 @@ template constexpr int clock_rate() { } } +/// Statelessly converts @c length in @c clock to the internal clock used by VDPs of @c personality throwing away any remainder. template constexpr int to_internal(int length) { + if constexpr (clock == Clock::Grauw) { + return (length + LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; + } return length * clock_rate() / clock_rate(); } +/// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder. template constexpr int from_internal(int length) { + if constexpr (clock == Clock::Grauw) { + return (length + LineLayout::CyclesPerLine - LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; + } return length * clock_rate() / clock_rate(); } /*! 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. */ template class ClockConverter { @@ -130,72 +146,6 @@ template class ClockConverter { int cycles_error_ = 0; }; - -// -// -// -template 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, ] 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 struct LineLayout> { - 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 struct LineLayout> { - 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 */ diff --git a/Components/9918/Implementation/LineLayout.hpp b/Components/9918/Implementation/LineLayout.hpp new file mode 100644 index 000000000..c8509b505 --- /dev/null +++ b/Components/9918/Implementation/LineLayout.hpp @@ -0,0 +1,82 @@ +// +// 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 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, ] 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 struct LineLayout> { + 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. + + constexpr static int LocationOfGrauwZero = 0; + + /// 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 struct LineLayout> { + 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; + + constexpr static int LocationOfGrauwZero = 0; + + /// 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 */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 3e16aa8a5..75e1fd830 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1100,6 +1100,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = ""; }; 42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = ""; }; 42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = ""; }; @@ -4738,6 +4739,7 @@ 4B43983F2967459B006B0BFC /* Draw.hpp */, 4B43983E29628538006B0BFC /* Fetch.hpp */, 4B2A3B5B29995FF6007CE366 /* LineBuffer.hpp */, + 428168372A16C25C008ECD27 /* LineLayout.hpp */, 4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */, 4B2A3B5A29993DFA007CE366 /* Storage.hpp */, 4BF0BC732982E54700CCA2B5 /* YamahaCommands.hpp */, From ce8bd011d702dbce6bbcf111cfe02d84ba317630 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 May 2023 16:50:46 -0400 Subject: [PATCH 3/7] Add commentary, and TODOs. --- Components/9918/Implementation/9918.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp index 34af6a73b..7011e3681 100644 --- a/Components/9918/Implementation/9918.cpp +++ b/Components/9918/Implementation/9918.cpp @@ -35,12 +35,16 @@ Base::Base() : // "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." + // + // i.e. it's 304 internal clocks after the end of the left border. mode_timing_.line_interrupt_position = (LineLayout::EndOfLeftBorder + 304) % LineLayout::CyclesPerLine; // 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. + // + // 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.row = 193; + mode_timing_.end_of_frame_interrupt_position.row = 192 + (LineLayout::EndOfLeftBorder + 304) / LineLayout::CyclesPerLine; } if constexpr (is_yamaha_vdp(personality)) { @@ -81,6 +85,7 @@ TMS9918::TMS9918() { template void TMS9918::set_tv_standard(TVStandard standard) { + // TODO: the Yamaha is programmable on this at runtime. this->tv_standard_ = standard; switch(standard) { case TVStandard::PAL: @@ -214,8 +219,10 @@ void TMS9918::run_for(const HalfCycles cycles) { // --------------------------------------- // Latch scrolling position, if necessary. // --------------------------------------- + // TODO: shouldn't this happen one per frame? if constexpr (is_sega_vdp(personality)) { - if(this->fetch_pointer_.column < 61 && end_column >= 61) { + constexpr auto latch_time = to_internal(61); // TODO: where did this magic constant come from? Is it the same for the Game Gear, etc? + if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) { if(!this->fetch_pointer_.row) { Storage::latched_vertical_scroll_ = Storage::vertical_scroll_; From dc425a03d3fa1c0ba4ab36c0ae27b63df1f735e5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 May 2023 16:55:17 -0400 Subject: [PATCH 4/7] Partially resolve. --- Components/9918/Implementation/9918.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp index 7011e3681..b8a589e35 100644 --- a/Components/9918/Implementation/9918.cpp +++ b/Components/9918/Implementation/9918.cpp @@ -219,11 +219,14 @@ void TMS9918::run_for(const HalfCycles cycles) { // --------------------------------------- // Latch scrolling position, if necessary. // --------------------------------------- - // TODO: shouldn't this happen one per frame? if constexpr (is_sega_vdp(personality)) { - constexpr auto latch_time = to_internal(61); // TODO: where did this magic constant come from? Is it the same for the Game Gear, etc? - if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) { - 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::EndOfLeftBorder - 2; + static_assert(latch_time > 0); + if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) { Storage::latched_vertical_scroll_ = Storage::vertical_scroll_; if(Storage::mode4_enable_) { @@ -242,7 +245,6 @@ void TMS9918::run_for(const HalfCycles cycles) { } - // ------------------------ // Perform memory accesses. // ------------------------ From d117a44069b603cbc57b6406081b0153e0f86fc6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 May 2023 11:46:49 -0400 Subject: [PATCH 5/7] Allow for potential Grauw offset in TMS and SMS. --- .../9918/Implementation/ClockConverter.hpp | 32 ++++++++++++----- Components/9918/Implementation/Fetch.hpp | 36 +++++++++++-------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp index c4e692ce6..9d73ccefc 100644 --- a/Components/9918/Implementation/ClockConverter.hpp +++ b/Components/9918/Implementation/ClockConverter.hpp @@ -51,20 +51,34 @@ template constexpr int clock_rate() { } } -/// Statelessly converts @c length in @c clock to the internal clock used by VDPs of @c personality throwing away any remainder. -template constexpr int to_internal(int length) { - if constexpr (clock == Clock::Grauw) { - return (length + LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; +/// Statelessly converts @c length to the internal clock for @c personality; applies conversions per the list of clocks in left-to-right order. +template constexpr int to_internal(int length) { + if constexpr (head == Clock::Grauw) { + length = (length + LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; + } else { + length = length * clock_rate() / clock_rate(); + } + + if constexpr (!sizeof...(tail)) { + return length; + } else { + return to_internal(length); } - return length * clock_rate() / clock_rate(); } /// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder. -template constexpr int from_internal(int length) { - if constexpr (clock == Clock::Grauw) { - return (length + LineLayout::CyclesPerLine - LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; +template constexpr int from_internal(int length) { + if constexpr (head == Clock::Grauw) { + length = (length + LineLayout::CyclesPerLine - LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; + } else { + length = length * clock_rate() / clock_rate(); + } + + if constexpr (!sizeof...(tail)) { + return length; + } else { + return to_internal(length); } - return length * clock_rate() / clock_rate(); } /*! diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp index 41ba56cec..98d2b9320 100644 --- a/Components/9918/Implementation/Fetch.hpp +++ b/Components/9918/Implementation/Fetch.hpp @@ -54,7 +54,7 @@ template void Base::dispatch(Seq #define index(n) \ if(use_end && end == n) return; \ [[fallthrough]]; \ - case n: fetcher.template fetch(); + case n: fetcher.template fetch(n)>(); switch(start) { default: assert(false); @@ -373,7 +373,7 @@ struct RefreshSequencer { template void fetch() { if(cycle < 26 || (cycle & 1) || cycle >= 154) { - base->do_external_slot(to_internal(cycle)); + base->do_external_slot(to_internal(cycle)); } } @@ -387,16 +387,22 @@ struct TextSequencer { template void fetch() { // The first 30 and the final 4 slots are external. if constexpr (cycle < 30 || cycle >= 150) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); return; } else { // For the 120 slots in between follow a three-step pattern of: constexpr int offset = cycle - 30; constexpr auto column = AddressT(offset / 3); switch(offset % 3) { - case 0: fetcher.fetch_name(column); break; // (1) fetch tile name. - case 1: fetcher.base->do_external_slot(to_internal(cycle)); break; // (2) external slot. - case 2: fetcher.fetch_pattern(column); break; // (3) fetch tile pattern. + case 0: // (1) fetch tile name. + fetcher.fetch_name(column); + break; + case 1: // (2) external slot. + fetcher.base->do_external_slot(to_internal(cycle)); + break; + case 2: // (3) fetch tile pattern. + fetcher.fetch_pattern(column); + break; } } } @@ -413,7 +419,7 @@ struct CharacterSequencer { template void fetch() { if(cycle < 5) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 5) { @@ -424,7 +430,7 @@ struct CharacterSequencer { } if(cycle > 14 && cycle < 19) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } // Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line. @@ -442,7 +448,7 @@ struct CharacterSequencer { case 0: character_fetcher.fetch_name(block); break; case 1: if(!(block & 3)) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } else { constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1); sprite_fetcher.fetch_y(sprite); @@ -457,7 +463,7 @@ struct CharacterSequencer { } if(cycle >= 155 && cycle < 157) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 157) { @@ -505,7 +511,7 @@ struct SMSSequencer { // window 0 to HSYNC low. template void fetch() { if(cycle < 3) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 3) { @@ -516,7 +522,7 @@ struct SMSSequencer { } if(cycle == 15 || cycle == 16) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 17) { @@ -537,7 +543,7 @@ struct SMSSequencer { case 0: fetcher.fetch_tile_name(block); break; case 1: if(!(block & 3)) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } else { constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1; fetcher.posit_sprite(sprite); @@ -549,7 +555,7 @@ struct SMSSequencer { } if(cycle >= 153 && cycle < 157) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 157) { @@ -560,7 +566,7 @@ struct SMSSequencer { } if(cycle >= 169) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } } From c75efb7dac72ff1d105f9460ef3e822d372dcdf3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 May 2023 13:43:28 -0400 Subject: [PATCH 6/7] Also allow for a potential Grauw conversion in Yamaha land. --- Components/9918/Implementation/Storage.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Components/9918/Implementation/Storage.hpp b/Components/9918/Implementation/Storage.hpp index f0a0611b3..70a4bcd92 100644 --- a/Components/9918/Implementation/Storage.hpp +++ b/Components/9918/Implementation/Storage.hpp @@ -86,7 +86,8 @@ struct YamahaFetcher { std::array result{}; size_t index = 0; 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(c)); if(!event) { continue; } From 40d5bd4e58a02e5157bf727f5d30cd98c1792a88 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 May 2023 14:22:22 -0400 Subject: [PATCH 7/7] Switch to purposive name. --- .../9918/Implementation/ClockConverter.hpp | 17 +++++++----- Components/9918/Implementation/Fetch.hpp | 26 +++++++++---------- Components/9918/Implementation/LineLayout.hpp | 6 ++--- Components/9918/Implementation/Storage.hpp | 4 +-- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp index 9d73ccefc..2aa9d840f 100644 --- a/Components/9918/Implementation/ClockConverter.hpp +++ b/Components/9918/Implementation/ClockConverter.hpp @@ -24,8 +24,9 @@ enum class Clock { TMSMemoryWindow, /// 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 where Grauw put 0 (i.e. at the start of horizontal sync). - Grauw, + /// 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 constexpr int clock_rate() { @@ -40,7 +41,7 @@ template constexpr int clock_rate() { case Clock::TMSMemoryWindow: return 171; case Clock::CRT: return 1368; case Clock::Internal: - case Clock::Grauw: + case Clock::FromStartOfSync: if constexpr (is_classic_vdp(personality)) { return 342; } else if constexpr (is_yamaha_vdp(personality)) { @@ -53,8 +54,8 @@ template constexpr int clock_rate() { /// Statelessly converts @c length to the internal clock for @c personality; applies conversions per the list of clocks in left-to-right order. template constexpr int to_internal(int length) { - if constexpr (head == Clock::Grauw) { - length = (length + LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; + if constexpr (head == Clock::FromStartOfSync) { + length = (length + LineLayout::StartOfSync) % LineLayout::CyclesPerLine; } else { length = length * clock_rate() / clock_rate(); } @@ -68,8 +69,10 @@ template constexpr int to_i /// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder. template constexpr int from_internal(int length) { - if constexpr (head == Clock::Grauw) { - length = (length + LineLayout::CyclesPerLine - LineLayout::LocationOfGrauwZero) % LineLayout::CyclesPerLine; + if constexpr (head == Clock::FromStartOfSync) { + length = + (length + LineLayout::CyclesPerLine - LineLayout::StartOfSync) + % LineLayout::CyclesPerLine; } else { length = length * clock_rate() / clock_rate(); } diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp index 98d2b9320..045770536 100644 --- a/Components/9918/Implementation/Fetch.hpp +++ b/Components/9918/Implementation/Fetch.hpp @@ -54,7 +54,7 @@ template void Base::dispatch(Seq #define index(n) \ if(use_end && end == n) return; \ [[fallthrough]]; \ - case n: fetcher.template fetch(n)>(); + case n: fetcher.template fetch(n)>(); switch(start) { default: assert(false); @@ -373,7 +373,7 @@ struct RefreshSequencer { template void fetch() { if(cycle < 26 || (cycle & 1) || cycle >= 154) { - base->do_external_slot(to_internal(cycle)); + base->do_external_slot(to_internal(cycle)); } } @@ -387,7 +387,7 @@ struct TextSequencer { template void fetch() { // The first 30 and the final 4 slots are external. if constexpr (cycle < 30 || cycle >= 150) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); return; } else { // For the 120 slots in between follow a three-step pattern of: @@ -398,7 +398,7 @@ struct TextSequencer { fetcher.fetch_name(column); break; case 1: // (2) external slot. - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); break; case 2: // (3) fetch tile pattern. fetcher.fetch_pattern(column); @@ -419,7 +419,7 @@ struct CharacterSequencer { template void fetch() { if(cycle < 5) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 5) { @@ -430,7 +430,7 @@ struct CharacterSequencer { } if(cycle > 14 && cycle < 19) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } // Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line. @@ -448,7 +448,7 @@ struct CharacterSequencer { case 0: character_fetcher.fetch_name(block); break; case 1: if(!(block & 3)) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } else { constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1); sprite_fetcher.fetch_y(sprite); @@ -463,7 +463,7 @@ struct CharacterSequencer { } if(cycle >= 155 && cycle < 157) { - character_fetcher.base->do_external_slot(to_internal(cycle)); + character_fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 157) { @@ -511,7 +511,7 @@ struct SMSSequencer { // window 0 to HSYNC low. template void fetch() { if(cycle < 3) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 3) { @@ -522,7 +522,7 @@ struct SMSSequencer { } if(cycle == 15 || cycle == 16) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 17) { @@ -543,7 +543,7 @@ struct SMSSequencer { case 0: fetcher.fetch_tile_name(block); break; case 1: if(!(block & 3)) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } else { constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1; fetcher.posit_sprite(sprite); @@ -555,7 +555,7 @@ struct SMSSequencer { } if(cycle >= 153 && cycle < 157) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 157) { @@ -566,7 +566,7 @@ struct SMSSequencer { } if(cycle >= 169) { - fetcher.base->do_external_slot(to_internal(cycle)); + fetcher.base->do_external_slot(to_internal(cycle)); } } diff --git a/Components/9918/Implementation/LineLayout.hpp b/Components/9918/Implementation/LineLayout.hpp index c8509b505..460ee9d70 100644 --- a/Components/9918/Implementation/LineLayout.hpp +++ b/Components/9918/Implementation/LineLayout.hpp @@ -30,6 +30,7 @@ template struct LineLayout; // * text mode on all VDPs adjusts border width. template struct LineLayout> { + constexpr static int StartOfSync = 0; constexpr static int EndOfSync = 26; constexpr static int StartOfColourBurst = 29; constexpr static int EndOfColourBurst = 43; @@ -47,14 +48,13 @@ template struct LineLayout struct LineLayout> { + constexpr static int StartOfSync = 0; constexpr static int EndOfSync = 100; constexpr static int StartOfColourBurst = 113; constexpr static int EndOfColourBurst = 167; @@ -70,8 +70,6 @@ template struct LineLayout events() { std::array result{}; size_t index = 0; - for(int c = 0; c < 1368; c++) { + for(int c = 0; c < 1368; c++) { // Specific personality doesn't matter here; both Yamahas use the same internal timing. - const auto event = GeneratorT::event(from_internal(c)); + const auto event = GeneratorT::event(from_internal(c)); if(!event) { continue; }