Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust position of mode latching #1146

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 22 additions & 28 deletions Components/9918/Implementation/9918.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,29 @@ Base<personality>::Base() :
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
// into whether there's a more natural form. It feels unlikely given the diversity of chips modelled.

if constexpr (is_sega_vdp(personality)) {
// Cf. https://www.smspower.org/forums/8161-SMSDisplayTiming

// "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<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.
// 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 = 192 + (LineLayout<personality>::EndOfLeftBorder + 304) / LineLayout<personality>::CyclesPerLine;
}

if constexpr (is_yamaha_vdp(personality)) {
// TODO: this is used for interrupt _prediction_ but won't handle text modes correctly, and indeed
// can't be just a single value where the programmer has changed into or out of text modes during the
// middle of a line, since screen mode is latched (so it'll be one value for that line, another from then onwards).a
mode_timing_.line_interrupt_position = LineLayout<personality>::EndOfPixels;
}

// Establish that output is delayed after reading by `output_lag` cycles,
// i.e. the fetch pointer is currently _ahead_ of the output pointer.
output_pointer_.row = output_pointer_.column = 0;

fetch_pointer_ = output_pointer_;
fetch_pointer_.column += output_lag;

// The fetch pointer is interpreted such that its zero is at the mode-latch cycle.
// Conversely the output pointer has zero be at start of sync. So the following
// is a mere change-of-origin.
//
// Logically, any mode latch time greater than 0 — i.e. beyond the start of sync — will
// cause fetch_pointer_ to **regress**. It will be set to the value it was at the start
// of sync, ready to overflow to 0 upon mode latch. When it overflows, the fetch row
// will be incremented.
//
// Therefore the nominal output row at instantiation needs to be one greater than the
// fetch row, as that's the first row that'll actually be fetched.
fetch_pointer_.column = to_internal<personality, Origin::ModeLatch>(output_pointer_.column);
if(LineLayout<personality>::ModeLatchCycle) {
++output_pointer_.row;
}

fetch_line_buffer_ = line_buffers_.begin();
draw_line_buffer_ = line_buffers_.begin();
fetch_sprite_buffer_ = sprite_buffers_.begin();
Expand Down Expand Up @@ -224,7 +216,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
// 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;
constexpr auto latch_time = to_internal<personality, Origin::ModeLatch>(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_;
Expand Down Expand Up @@ -283,13 +275,15 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
// -------------------------------
// Check for interrupt conditions.
// -------------------------------
if constexpr (is_sega_vdp(personality)) {
if constexpr (LineLayout<personality>::HasFixedLineInterrupt) {
// The Sega VDP offers a decrementing counter for triggering line interrupts;
// it is reloaded either when it overflows or upon every non-pixel line after the first.
// It is otherwise decremented.

constexpr int FixedLineInterrupt = to_internal<personality, Origin::ModeLatch>(LineLayout<personality>::FixedLineInterrupt);
if(
this->fetch_pointer_.column < this->mode_timing_.line_interrupt_position &&
end_column >= this->mode_timing_.line_interrupt_position
this->fetch_pointer_.column < FixedLineInterrupt &&
end_column >= FixedLineInterrupt
) {
if(this->fetch_pointer_.row >= 0 && this->fetch_pointer_.row <= this->mode_timing_.pixel_lines) {
if(!this->line_interrupt_counter_) {
Expand Down
10 changes: 0 additions & 10 deletions Components/9918/Implementation/9918Base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,6 @@ template <Personality personality> struct Base: public Storage<personality> {
// then the appropriate status information will be set.
int maximum_visible_sprites = 4;

// Set the position, in cycles, of the two interrupts,
// within a line.
//
// TODO: redetermine where this number came from.
struct {
int column = 313;
int row = 192;
} end_of_frame_interrupt_position;
int line_interrupt_position = -1;

// Enables or disabled the recognition of the sprite
// list terminator, and sets the terminator value.
bool allow_sprite_terminator = true;
Expand Down
71 changes: 45 additions & 26 deletions Components/9918/Implementation/ClockConverter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ enum class Clock {
TMSMemoryWindow,
/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
CRT,
};

enum class Origin {
///
ModeLatch,

/// 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,
StartOfSync,
};

template <Personality personality, Clock clk> constexpr int clock_rate() {
Expand All @@ -41,7 +47,6 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
case Clock::TMSMemoryWindow: return 171;
case Clock::CRT: return 1368;
case Clock::Internal:
case Clock::FromStartOfSync:
if constexpr (is_classic_vdp(personality)) {
return 342;
} else if constexpr (is_yamaha_vdp(personality)) {
Expand All @@ -52,36 +57,50 @@ template <Personality personality, Clock clk> 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 <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>();
}
/// Scales @c length from @c clock to the internal clock rate.
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
}

if constexpr (!sizeof...(tail)) {
return length;
} else {
return to_internal<personality, tail...>(length);
/// Moves @c position that is relative to @c Origin::StartOfSync so that it is relative to @c origin ;
/// i.e. can be thought of as "to [internal with origin as specified]".
template <Personality personality, Origin origin> constexpr int to_internal(int position) {
if constexpr (origin == Origin::ModeLatch) {
return (
position + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::ModeLatchCycle
) % LineLayout<personality>::CyclesPerLine;
}
return position;
}

/// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder.
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>();
}
/// Converts @c position from one that is measured at the rate implied by @c clock and relative to @c Origin::StartOfSync
/// to one that is at the internal clock rate and relative to @c origin.
template <Personality personality, Origin origin, Clock clock> constexpr int to_internal(int position) {
position = to_internal<personality, clock>(position);
return to_internal<personality, origin>(position);
}

/// Scales @c length from the internal clock rate to @c clock.
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
}

if constexpr (!sizeof...(tail)) {
return length;
} else {
return to_internal<personality, tail...>(length);
/// Moves @c position that is relative to @c origin so that it is relative to @c Origin::StartOfSync ;
/// i.e. can be thought of as "from [internal with origin as specified]".
template <Personality personality, Origin origin> constexpr int from_internal(int length) {
if constexpr (origin == Origin::ModeLatch) {
return (
length + LineLayout<personality>::ModeLatchCycle
) % LineLayout<personality>::CyclesPerLine;
}
return length;
}

/// Converts @c position from one that is measured at the internal clock rate and relative to @c origin
/// to one that is at the rate implied by @c clock and relative to @c Origin::StartOfSync
template <Personality personality, Origin origin, Clock clock> constexpr int from_internal(int position) {
position = from_internal<personality, origin>(position);
return from_internal<personality, clock>(position);
}

/*!
Expand Down
26 changes: 13 additions & 13 deletions Components/9918/Implementation/Fetch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ template<bool use_end, typename SequencerT> void Base<personality>::dispatch(Seq
#define index(n) \
if(use_end && end == n) return; \
[[fallthrough]]; \
case n: fetcher.template fetch<from_internal<personality, Clock::FromStartOfSync>(n)>();
case n: fetcher.template fetch<from_internal<personality, Origin::StartOfSync>(n)>();

switch(start) {
default: assert(false);
Expand Down Expand Up @@ -373,7 +373,7 @@ struct RefreshSequencer {

template <int cycle> void fetch() {
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}
}

Expand All @@ -387,7 +387,7 @@ struct TextSequencer {
template <int cycle> 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<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
return;
} else {
// For the 120 slots in between follow a three-step pattern of:
Expand All @@ -398,7 +398,7 @@ struct TextSequencer {
fetcher.fetch_name(column);
break;
case 1: // (2) external slot.
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
break;
case 2: // (3) fetch tile pattern.
fetcher.fetch_pattern(column);
Expand All @@ -419,7 +419,7 @@ struct CharacterSequencer {

template <int cycle> void fetch() {
if(cycle < 5) {
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}

if(cycle == 5) {
Expand All @@ -430,7 +430,7 @@ struct CharacterSequencer {
}

if(cycle > 14 && cycle < 19) {
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}

// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
Expand All @@ -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<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
} else {
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
sprite_fetcher.fetch_y(sprite);
Expand All @@ -463,7 +463,7 @@ struct CharacterSequencer {
}

if(cycle >= 155 && cycle < 157) {
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}

if(cycle == 157) {
Expand Down Expand Up @@ -511,7 +511,7 @@ struct SMSSequencer {
// window 0 to HSYNC low.
template <int cycle> void fetch() {
if(cycle < 3) {
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}

if(cycle == 3) {
Expand All @@ -522,7 +522,7 @@ struct SMSSequencer {
}

if(cycle == 15 || cycle == 16) {
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}

if(cycle == 17) {
Expand All @@ -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<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
} else {
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
fetcher.posit_sprite(sprite);
Expand All @@ -555,7 +555,7 @@ struct SMSSequencer {
}

if(cycle >= 153 && cycle < 157) {
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}

if(cycle == 157) {
Expand All @@ -566,7 +566,7 @@ struct SMSSequencer {
}

if(cycle >= 169) {
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
}
}

Expand Down
32 changes: 31 additions & 1 deletion Components/9918/Implementation/LineLayout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ template <Personality personality, typename Enable = void> struct LineLayout;
// * 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.
//
// ModeLaytchCycle is the cycle at which the video mode, blank disable/enable and
// sprite enable/disable are latched for the line.

template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality) && !is_sega_vdp(personality)>> {
constexpr static int StartOfSync = 0;
constexpr static int EndOfSync = 26;
constexpr static int StartOfColourBurst = 29;
Expand All @@ -48,11 +51,34 @@ template <Personality personality> struct LineLayout<personality, std::enable_if
// and falls into the collection gap between the final sprite
// graphics and the initial tiles or pixels.

constexpr static bool HasDynamicLineInterrupt = false;
constexpr static bool HasFixedLineInterrupt = false;
constexpr static int EndOfFrameInterrupt = 313;

/// 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_sega_vdp(personality)>> :
public LineLayout<Personality::TMS9918A> {

// Cf. https://www.smspower.org/forums/8161-SMSDisplayTiming

// "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.
constexpr static bool HasFixedLineInterrupt = false;
constexpr static int FixedLineInterrupt = (EndOfLeftBorder + 304) % 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.
constexpr static int EndOfFrameInterrupt = 313;
};

template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
constexpr static int StartOfSync = 0;
constexpr static int EndOfSync = 100;
Expand All @@ -70,6 +96,10 @@ template <Personality personality> struct LineLayout<personality, std::enable_if

constexpr static int ModeLatchCycle = 144;

constexpr static bool HasDynamicLineInterrupt = true;
constexpr static bool HasFixedLineInterrupt = false;
constexpr static int EndOfFrameInterrupt = 313;

/// 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;
Expand Down
3 changes: 2 additions & 1 deletion Components/9918/Implementation/Storage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ struct YamahaFetcher {
size_t index = 0;
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<Personality::V9938, Clock::FromStartOfSync>(c));
const int mapped_location = from_internal<Personality::V9938, Origin::StartOfSync>(c);
const auto event = GeneratorT::event(mapped_location);
if(!event) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.3">
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
Expand Down