1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-29 12:50:28 +00:00

Start attempting to use table-based Yamaha fetch.

This commit is contained in:
Thomas Harte 2023-01-22 22:00:28 -05:00
parent c6dd7d4726
commit 91047e5b3a
3 changed files with 221 additions and 174 deletions

View File

@ -200,52 +200,28 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
// ------------------------ // ------------------------
// Perform memory accesses. // Perform memory accesses.
// ------------------------ // ------------------------
#define fetch(function_true, function_false, clock) { \ #define fetch(function, clock) { \
const int first_window = from_internal<personality, clock>(this->fetch_pointer_.column);\ const int first_window = from_internal<personality, clock>(this->fetch_pointer_.column);\
const int final_window = from_internal<personality, clock>(end_column); \ const int final_window = from_internal<personality, clock>(end_column); \
if(first_window == final_window) break; \ if(first_window == final_window) break; \
if(final_window != clock_rate<personality, clock>()) { \ if(final_window != clock_rate<personality, clock>()) { \
function_true(first_window, final_window); \ function<true>(first_window, final_window); \
} else { \ } else { \
function_false(first_window, final_window); \ function<false>(first_window, final_window); \
} \ } \
} }
#define fetch_simple(function, clock) fetch(function<true>, function<false>, clock)
switch(line_buffer.line_mode) { switch(line_buffer.line_mode) {
case LineMode::Text: fetch_simple(this->template fetch_tms_text, Clock::TMSMemoryWindow); break; case LineMode::Text: fetch(this->template fetch_tms_text, Clock::TMSMemoryWindow); break;
case LineMode::Character: fetch_simple(this->template fetch_tms_character, Clock::TMSMemoryWindow); break; case LineMode::Character: fetch(this->template fetch_tms_character, Clock::TMSMemoryWindow); break;
case LineMode::SMS: fetch_simple(this->template fetch_sms, Clock::TMSMemoryWindow); break; case LineMode::SMS: fetch(this->template fetch_sms, Clock::TMSMemoryWindow); break;
case LineMode::Refresh: fetch_simple(this->template fetch_tms_refresh, Clock::TMSMemoryWindow); break; case LineMode::Refresh: fetch(this->template fetch_tms_refresh, Clock::TMSMemoryWindow); break;
case LineMode::Yamaha: case LineMode::Yamaha: fetch(this->template fetch_yamaha, Clock::Internal); break;
#define true_func this->template fetch_yamaha<true, true>
#define false_func this->template fetch_yamaha<false, true>
fetch(
true_func,
false_func,
Clock::Internal);
#undef true_func
#undef false_func
break;
#define true_func this->template fetch_yamaha<true, false>
#define false_func this->template fetch_yamaha<false, false>
case LineMode::YamahaNoSprites:
fetch(
true_func,
false_func,
Clock::Internal);
#undef true_func
#undef false_func
break;
} }
#undef fetch_simple
#undef fetch #undef fetch
// TODO: the above is too macro-heavy. Simplify.
// ------------------------------- // -------------------------------
// Check for interrupt conditions. // Check for interrupt conditions.
@ -316,8 +292,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
case ScreenMode::YamahaGraphics5: case ScreenMode::YamahaGraphics5:
case ScreenMode::YamahaGraphics6: case ScreenMode::YamahaGraphics6:
case ScreenMode::YamahaGraphics7: case ScreenMode::YamahaGraphics7:
// TODO: sprites? next_line_buffer.line_mode = LineMode::Yamaha;
next_line_buffer.line_mode = LineMode::YamahaNoSprites;
break; break;
default: default:
// This covers both MultiColour and Graphics modes. // This covers both MultiColour and Graphics modes.
@ -329,6 +304,9 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
(this->screen_mode_ == ScreenMode::Blank) || (this->screen_mode_ == ScreenMode::Blank) ||
this->is_vertical_blank()) this->is_vertical_blank())
next_line_buffer.line_mode = LineMode::Refresh; next_line_buffer.line_mode = LineMode::Refresh;
// TODO: an actual sprites-enabled flag.
Storage<personality>::begin_line(this->screen_mode_, next_line_buffer.line_mode == LineMode::Refresh, false);
} }
} }

View File

@ -59,7 +59,6 @@ enum class LineMode {
Refresh, Refresh,
SMS, SMS,
Yamaha, Yamaha,
YamahaNoSprites,
}; };
enum class MemoryAccess { enum class MemoryAccess {
@ -155,6 +154,7 @@ template <Personality personality, typename Enable = void> struct Storage {
}; };
template <> struct Storage<Personality::TMS9918A> { template <> struct Storage<Personality::TMS9918A> {
void begin_line(ScreenMode, bool, bool) {}
}; };
// Yamaha-specific storage. // Yamaha-specific storage.
@ -169,6 +169,183 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
uint8_t palette_entry_ = 0; uint8_t palette_entry_ = 0;
uint8_t mode_ = 0; uint8_t mode_ = 0;
/// Describes an _observable_ memory access event. i.e. anything that it is safe
/// (and convenient) to treat as atomic in between external slots.
struct Event {
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
/// external data must be ready by in order to take part in those slots.
int offset = 1368;
enum class Type {
External,
DataBlock,
} type = Type::External;
constexpr Event(int offset, Type type) noexcept :
offset(grauw_to_internal(offset)),
type(type) {}
constexpr Event(int offset) noexcept :
offset(grauw_to_internal(offset)) {}
constexpr Event() noexcept {}
};
const Event *next_event_ = nullptr;
void begin_line([[maybe_unused]] ScreenMode mode, bool is_refresh, [[maybe_unused]] bool sprites_enabled) {
// TODO: remove this check. It's temporary, while the Yamaha is still using the TMS fetchers.
if(mode < ScreenMode::YamahaText80) {
return;
}
assert(next_event_ == nullptr || next_event_->offset == 1368);
if(is_refresh) {
next_event_ = refresh_events;
return;
}
// TODO: obey sprites_enabled flag, at least.
next_event_ = no_sprites_events;
}
Storage() noexcept {
// Perform sanity checks on the event lists.
#ifndef NDEBUG
const Event *lists[] = { no_sprites_events, refresh_events, nullptr };
const Event **list = lists;
while(*list) {
const Event *cursor = *list;
++list;
while(cursor[1].offset != 1368) {
assert(cursor[1].offset > cursor[0].offset);
++cursor;
}
}
#endif
}
private:
// This emulator treats position 0 as being immediately after the standard pixel area.
// i.e. offset 1282 on Grauw's http://map.grauw.nl/articles/vdp-vram-timing/vdp-timing.png
constexpr static int grauw_to_internal(int offset) {
return (offset + 1368 - 1282) % 1368;
}
static constexpr Event refresh_events[] = {
Event(1284), Event(1292), Event(1300), Event(1308), Event(1316), Event(1324),
Event(1334), Event(1344), Event(1352), Event(1360), Event(0), Event(8),
Event(16), Event(24), Event(32), Event(40), Event(48), Event(56),
Event(64), Event(72), Event(80), Event(88), Event(96), Event(104),
Event(112), Event(120),
Event(164), Event(172), Event(180), Event(188), Event(196), Event(204),
Event(212), Event(220), Event(228), Event(236), Event(244), Event(252),
Event(260), Event(268), Event(276), /* Refresh. */ Event(292), Event(300),
Event(308), Event(316), Event(324), Event(332), Event(340), Event(348),
Event(356), Event(364), Event(372), Event(380), Event(388), Event(396),
Event(404), /* Refresh. */ Event(420), Event(428), Event(436), Event(444),
Event(452), Event(460), Event(468), Event(476), Event(484), Event(492),
Event(500), Event(508), Event(516), Event(524), Event(532), /* Refresh. */
Event(548), Event(556), Event(564), Event(570), Event(580), Event(588),
Event(596), Event(604), Event(612), Event(620), Event(628), Event(636),
Event(644), Event(652), Event(660), /* Refresh. */ Event(676), Event(684),
Event(692), Event(700), Event(708), Event(716), Event(724), Event(732),
Event(740), Event(748), Event(756), Event(764), Event(772), Event(780),
Event(788), /* Refresh. */ Event(804), Event(812), Event(820), Event(828),
Event(836), Event(844), Event(852), Event(860), Event(868), Event(876),
Event(884), Event(892), Event(900), Event(908), Event(916), /* Refresh. */
Event(932), Event(940), Event(948), Event(956), Event(964), Event(972),
Event(980), Event(988), Event(996), Event(1004), Event(1012), Event(1020),
Event(1028), Event(1036), Event(1044), /* Refresh. */ Event(1060), Event(1068),
Event(1076), Event(1084), Event(1092), Event(1100), Event(1108), Event(1116),
Event(1124), Event(1132), Event(1140), Event(1148), Event(1156), Event(1164),
Event(1172), /* Refresh. */ Event(1188), Event(1196), Event(1204), Event(1212),
Event(1220), Event(1228),
Event(1268), Event(1276),
Event()
};
static constexpr Event no_sprites_events[] = {
Event(1282, Event::Type::External),
Event(1290, Event::Type::External),
Event(1298, Event::Type::External),
Event(1306, Event::Type::External),
Event(1314, Event::Type::External),
Event(1322, Event::Type::External),
Event(1332, Event::Type::External),
Event(1342, Event::Type::External),
Event(1350, Event::Type::External),
Event(1358, Event::Type::External),
Event(1366, Event::Type::External),
Event(6, Event::Type::External),
Event(14, Event::Type::External),
Event(22, Event::Type::External),
Event(30, Event::Type::External),
Event(38, Event::Type::External),
Event(46, Event::Type::External),
Event(54, Event::Type::External),
Event(62, Event::Type::External),
Event(70, Event::Type::External),
Event(78, Event::Type::External),
Event(86, Event::Type::External),
Event(94, Event::Type::External),
Event(102, Event::Type::External),
Event(110, Event::Type::External),
Event(118, Event::Type::External),
Event(162, Event::Type::External),
Event(170, Event::Type::External),
Event(182, Event::Type::External),
Event(188, Event::Type::External),
// Omitted: dummy data block. Is not observable.
Event(214, Event::Type::External),
Event(220, Event::Type::External),
Event(226, Event::Type::DataBlock), Event(246, Event::Type::External), Event(252, Event::Type::External),
Event(258, Event::Type::DataBlock), Event(278, Event::Type::External), // Omitted: refresh.
Event(290, Event::Type::DataBlock), Event(310, Event::Type::External), Event(316, Event::Type::External),
Event(322, Event::Type::DataBlock), Event(342, Event::Type::External), Event(348, Event::Type::External),
Event(354, Event::Type::DataBlock), Event(374, Event::Type::External), Event(380, Event::Type::External),
Event(386, Event::Type::DataBlock), Event(406, Event::Type::External), // Omitted: refresh.
Event(418, Event::Type::DataBlock), Event(438, Event::Type::External), Event(444, Event::Type::External),
Event(450, Event::Type::DataBlock), Event(470, Event::Type::External), Event(476, Event::Type::External),
Event(482, Event::Type::DataBlock), Event(502, Event::Type::External), Event(508, Event::Type::External),
Event(514, Event::Type::DataBlock), Event(534, Event::Type::External), // Omitted: refresh.
Event(546, Event::Type::DataBlock), Event(566, Event::Type::External), Event(572, Event::Type::External),
Event(578, Event::Type::DataBlock), Event(598, Event::Type::External), Event(604, Event::Type::External),
Event(610, Event::Type::DataBlock), Event(630, Event::Type::External), Event(636, Event::Type::External),
Event(642, Event::Type::DataBlock), Event(662, Event::Type::External), // Omitted: refresh.
Event(674, Event::Type::DataBlock), Event(694, Event::Type::External), Event(700, Event::Type::External),
Event(706, Event::Type::DataBlock), Event(726, Event::Type::External), Event(732, Event::Type::External),
Event(738, Event::Type::DataBlock), Event(758, Event::Type::External), Event(764, Event::Type::External),
Event(770, Event::Type::DataBlock), Event(790, Event::Type::External), // Omitted: refresh.
Event(802, Event::Type::DataBlock), Event(822, Event::Type::External), Event(828, Event::Type::External),
Event(834, Event::Type::DataBlock), Event(854, Event::Type::External), Event(860, Event::Type::External),
Event(866, Event::Type::DataBlock), Event(886, Event::Type::External), Event(892, Event::Type::External),
Event(898, Event::Type::DataBlock), Event(918, Event::Type::External), // Omitted: refresh.
Event(930, Event::Type::DataBlock), Event(950, Event::Type::External), Event(956, Event::Type::External),
Event(962, Event::Type::DataBlock), Event(982, Event::Type::External), Event(988, Event::Type::External),
Event(994, Event::Type::DataBlock), Event(1014, Event::Type::External), Event(1020, Event::Type::External),
Event(1026, Event::Type::DataBlock), Event(1046, Event::Type::External), // Omitted: refresh.
Event(1058, Event::Type::DataBlock), Event(1078, Event::Type::External), Event(1084, Event::Type::External),
Event(1090, Event::Type::DataBlock), Event(1110, Event::Type::External), Event(1116, Event::Type::External),
Event(1122, Event::Type::DataBlock), Event(1142, Event::Type::External), Event(1148, Event::Type::External),
Event(1154, Event::Type::DataBlock), Event(1174, Event::Type::External), // Omitted: refresh.
Event(1186, Event::Type::DataBlock), Event(1206, Event::Type::External), Event(1212, Event::Type::External),
Event(1218, Event::Type::DataBlock),
Event(1266, Event::Type::External),
Event(1274, Event::Type::External),
Event()
};
}; };
// Master System-specific storage. // Master System-specific storage.
@ -207,6 +384,8 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
size_t pattern_name_address_; size_t pattern_name_address_;
size_t sprite_attribute_table_address_; size_t sprite_attribute_table_address_;
size_t sprite_generator_table_address_; size_t sprite_generator_table_address_;
void begin_line(ScreenMode, bool, bool) {}
}; };
template <Personality personality> struct Base: public Storage<personality> { template <Personality personality> struct Base: public Storage<personality> {
@ -475,8 +654,7 @@ template <Personality personality> struct Base: public Storage<personality> {
template<bool use_end> void fetch_tms_text(int start, int end); template<bool use_end> void fetch_tms_text(int start, int end);
template<bool use_end> void fetch_tms_character(int start, int end); template<bool use_end> void fetch_tms_character(int start, int end);
template<bool use_end> void fetch_yamaha_refresh(int start, int end); template<bool use_end> void fetch_yamaha(int start, int end);
template<bool use_end, bool fetch_sprites> void fetch_yamaha(int start, int end);
template<bool use_end> void fetch_sms(int start, int end); template<bool use_end> void fetch_sms(int start, int end);

View File

@ -470,148 +470,39 @@ template<bool use_end> void Base<personality>::fetch_sms(int start, int end) {
// MARK: - Yamaha // MARK: - Yamaha
// TODO.
namespace {
// This emulator treats position 0 as being immediately after the standard pixel area.
// i.e. offset 1282 on Grauw's http://map.grauw.nl/articles/vdp-vram-timing/vdp-timing.png
constexpr int grauw_to_internal(int offset) {
return (offset + 1368 - 1282) % 1368;
}
}
template <Personality personality> template <Personality personality>
template<bool use_end> void Base<personality>::fetch_yamaha_refresh(int start, int end) { template<bool use_end> void Base<personality>::fetch_yamaha([[maybe_unused]] int start, int end) {
(void)start; if constexpr (is_yamaha_vdp(personality)) {
(void)end; /*
} Per http://map.grauw.nl/articles/vdp-vram-timing/vdp-timing.html :
template <Personality personality> The major change compared to the previous mode is that now the VDP needs to fetch extra data
template<bool use_end, bool fetch_sprites> void Base<personality>::fetch_yamaha(int start, int end) { for the bitmap rendering. These fetches happen in 32 blocks of 4 bytes (screen 5/6) or
/* 8 bytes (screen 7/8). The fetches within one block happen in burst mode. This means that
Per http://map.grauw.nl/articles/vdp-vram-timing/vdp-timing.html : one block takes 18 cycles (screen 5/6) or 20 cycles (screen 7/8). Though later we'll see
that the two spare cycles for screen 5/6 are not used for anything else, so for simplicity
we can say that in all bitmap modes a bitmap-fetch-block takes 20 cycles. This is even
clearer if you look at the RAS signal: this signal follows the exact same pattern in all
(bitmap) screen modes, so in screen 5/6 it remains active for two cycles longer than
strictly necessary.
The major change compared to the previous mode is that now the VDP needs to fetch extra data Actually before these 32 blocks there's one extra dummy block. This block has the same timing
for the bitmap rendering. These fetches happen in 32 blocks of 4 bytes (screen 5/6) or as the other blocks, but it always reads address 0x1FFFF. From an emulator point of view,
8 bytes (screen 7/8). The fetches within one block happen in burst mode. This means that these dummy reads don't matter, it only matters that at those moments no other VRAM accesses
one block takes 18 cycles (screen 5/6) or 20 cycles (screen 7/8). Though later we'll see can occur.
that the two spare cycles for screen 5/6 are not used for anything else, so for simplicity */
we can say that in all bitmap modes a bitmap-fetch-block takes 20 cycles. This is even while(Storage<personality>::next_event_->offset < end) {
clearer if you look at the RAS signal: this signal follows the exact same pattern in all switch(Storage<personality>::next_event_->type) {
(bitmap) screen modes, so in screen 5/6 it remains active for two cycles longer than case Storage<personality>::Event::Type::External:
strictly necessary. do_external_slot(Storage<personality>::next_event_->offset);
break;
Actually before these 32 blocks there's one extra dummy block. This block has the same timing default: break;
as the other blocks, but it always reads address 0x1FFFF. From an emulator point of view, }
these dummy reads don't matter, it only matters that at those moments no other VRAM accesses
can occur.
*/
// This emulator treats position 0 as being immediately after the standard pixel area. ++Storage<personality>::next_event_;
// i.e. offset 1282 on Grauw's http://map.grauw.nl/articles/vdp-vram-timing/vdp-timing.png }
// }
// That being the case...
//
// Data blocks are located at:
//
// 194
/// Describes an _observable_ memory access event. i.e. anything that it is safe
/// (and convenient) to treat as atomic in between external slots.
struct Event {
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
/// external data must be ready by in order to take part in those slots.
int offset;
enum class Type {
External,
DataBlock,
} type;
constexpr Event(int offset, Type type) noexcept :
offset(grauw_to_internal(offset)),
type(type) {}
};
constexpr Event no_sprites_events[] = {
Event(1282, Event::Type::External),
Event(1290, Event::Type::External),
Event(1298, Event::Type::External),
Event(1306, Event::Type::External),
Event(1314, Event::Type::External),
Event(1322, Event::Type::External),
Event(1332, Event::Type::External),
Event(1342, Event::Type::External),
Event(1350, Event::Type::External),
Event(1358, Event::Type::External),
Event(1366, Event::Type::External),
Event(6, Event::Type::External),
Event(14, Event::Type::External),
Event(22, Event::Type::External),
Event(30, Event::Type::External),
Event(38, Event::Type::External),
Event(46, Event::Type::External),
Event(54, Event::Type::External),
Event(62, Event::Type::External),
Event(70, Event::Type::External),
Event(78, Event::Type::External),
Event(86, Event::Type::External),
Event(94, Event::Type::External),
Event(102, Event::Type::External),
Event(110, Event::Type::External),
Event(118, Event::Type::External),
Event(162, Event::Type::External),
Event(170, Event::Type::External),
Event(182, Event::Type::External),
Event(188, Event::Type::External),
// Omitted: dummy data block. Is not observable.
Event(214, Event::Type::External),
Event(220, Event::Type::External),
Event(226, Event::Type::DataBlock), Event(246, Event::Type::External), Event(252, Event::Type::External),
Event(258, Event::Type::DataBlock), Event(278, Event::Type::External), // Omitted: refresh.
Event(290, Event::Type::DataBlock), Event(310, Event::Type::External), Event(316, Event::Type::External),
Event(322, Event::Type::DataBlock), Event(342, Event::Type::External), Event(348, Event::Type::External),
Event(354, Event::Type::DataBlock), Event(374, Event::Type::External), Event(380, Event::Type::External),
Event(386, Event::Type::DataBlock), Event(406, Event::Type::External), // Omitted: refresh.
Event(418, Event::Type::DataBlock), Event(438, Event::Type::External), Event(444, Event::Type::External),
Event(450, Event::Type::DataBlock), Event(470, Event::Type::External), Event(476, Event::Type::External),
Event(482, Event::Type::DataBlock), Event(502, Event::Type::External), Event(508, Event::Type::External),
Event(514, Event::Type::DataBlock), Event(534, Event::Type::External), // Omitted: refresh.
Event(546, Event::Type::DataBlock), Event(566, Event::Type::External), Event(572, Event::Type::External),
Event(578, Event::Type::DataBlock), Event(598, Event::Type::External), Event(604, Event::Type::External),
Event(610, Event::Type::DataBlock), Event(630, Event::Type::External), Event(636, Event::Type::External),
Event(642, Event::Type::DataBlock), Event(662, Event::Type::External), // Omitted: refresh.
Event(674, Event::Type::DataBlock), Event(694, Event::Type::External), Event(700, Event::Type::External),
Event(706, Event::Type::DataBlock), Event(726, Event::Type::External), Event(732, Event::Type::External),
Event(738, Event::Type::DataBlock), Event(758, Event::Type::External), Event(764, Event::Type::External),
Event(770, Event::Type::DataBlock), Event(790, Event::Type::External), // Omitted: refresh.
Event(802, Event::Type::DataBlock), Event(822, Event::Type::External), Event(828, Event::Type::External),
Event(834, Event::Type::DataBlock), Event(854, Event::Type::External), Event(860, Event::Type::External),
Event(866, Event::Type::DataBlock), Event(886, Event::Type::External), Event(892, Event::Type::External),
Event(898, Event::Type::DataBlock), Event(918, Event::Type::External), // Omitted: refresh.
Event(930, Event::Type::DataBlock), Event(950, Event::Type::External), Event(956, Event::Type::External),
Event(962, Event::Type::DataBlock), Event(982, Event::Type::External), Event(988, Event::Type::External),
Event(994, Event::Type::DataBlock), Event(1014, Event::Type::External), Event(1020, Event::Type::External),
Event(1026, Event::Type::DataBlock), Event(1046, Event::Type::External), // Omitted: refresh.
Event(1058, Event::Type::DataBlock), Event(1078, Event::Type::External), Event(1084, Event::Type::External),
Event(1090, Event::Type::DataBlock), Event(1110, Event::Type::External), Event(1116, Event::Type::External),
Event(1122, Event::Type::DataBlock), Event(1142, Event::Type::External), Event(1148, Event::Type::External),
Event(1154, Event::Type::DataBlock), Event(1174, Event::Type::External), // Omitted: refresh.
Event(1186, Event::Type::DataBlock), Event(1206, Event::Type::External), Event(1212, Event::Type::External),
Event(1218, Event::Type::DataBlock),
Event(1266, Event::Type::External),
Event(1274, Event::Type::External),
};
(void)start;
(void)end;
} }
// MARK: - Mega Drive // MARK: - Mega Drive