OXYGENHUB
◆ main openmsx / src / video / VDPAccessSlots.cc
15.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "VDPAccessSlots.hh"
2
3
#include "one_of.hh"
4
5
#include <algorithm>
6
#include <array>
7
#include <bitset>
8
#include <cassert>
9
#include <utility>
10
11
namespace openmsx::VDPAccessSlots {
12
13
// These tables must contain at least one value that is bigger or equal
14
// to 1368+132. So we extend the data with some cyclic duplicates.
15
16
// Screen rendering disabled (or vertical border).
17
// This is correct (measured on real V9938) for bitmap and character mode.
18
// TODO also correct for text mode? See 'vdp-timing-2.html for more details.
19
static constexpr std::array<int16_t, 154 + 17> slotsScreenOff = {
20
	   0,    8,   16,   24,   32,   40,   48,   56,   64,   72,
21
	  80,   88,   96,  104,  112,  120,  164,  172,  180,  188,
22
	 196,  204,  212,  220,  228,  236,  244,  252,  260,  268,
23
	 276,  292,  300,  308,  316,  324,  332,  340,  348,  356,
24
	 364,  372,  380,  388,  396,  404,  420,  428,  436,  444,
25
	 452,  460,  468,  476,  484,  492,  500,  508,  516,  524,
26
	 532,  548,  556,  564,  572,  580,  588,  596,  604,  612,
27
	 620,  628,  636,  644,  652,  660,  676,  684,  692,  700,
28
	 708,  716,  724,  732,  740,  748,  756,  764,  772,  780,
29
	 788,  804,  812,  820,  828,  836,  844,  852,  860,  868,
30
	 876,  884,  892,  900,  908,  916,  932,  940,  948,  956,
31
	 964,  972,  980,  988,  996, 1004, 1012, 1020, 1028, 1036,
32
	1044, 1060, 1068, 1076, 1084, 1092, 1100, 1108, 1116, 1124,
33
	1132, 1140, 1148, 1156, 1164, 1172, 1188, 1196, 1204, 1212,
34
	1220, 1228, 1268, 1276, 1284, 1292, 1300, 1308, 1316, 1324,
35
	1334, 1344, 1352, 1360,
36
	1368+  0, 1368+  8, 1368+16, 1368+ 24, 1368+ 32,
37
	1368+ 40, 1368+ 48, 1368+56, 1368+ 64, 1368+ 72,
38
	1368+ 80, 1368+ 88, 1368+96, 1368+104, 1368+112,
39
	1368+120, 1368+164
40
};
41
42
// Bitmap mode, sprites disabled.
43
static constexpr std::array<int16_t, 88 + 16> slotsSpritesOff = {
44
	   6,   14,   22,   30,   38,   46,   54,   62,   70,   78,
45
	  86,   94,  102,  110,  118,  162,  170,  182,  188,  214,
46
	 220,  246,  252,  278,  310,  316,  342,  348,  374,  380,
47
	 406,  438,  444,  470,  476,  502,  508,  534,  566,  572,
48
	 598,  604,  630,  636,  662,  694,  700,  726,  732,  758,
49
	 764,  790,  822,  828,  854,  860,  886,  892,  918,  950,
50
	 956,  982,  988, 1014, 1020, 1046, 1078, 1084, 1110, 1116,
51
	1142, 1148, 1174, 1206, 1212, 1266, 1274, 1282, 1290, 1298,
52
	1306, 1314, 1322, 1332, 1342, 1350, 1358, 1366,
53
	1368+  6, 1368+14, 1368+ 22, 1368+ 30, 1368+ 38,
54
	1368+ 46, 1368+54, 1368+ 62, 1368+ 70, 1368+ 78,
55
	1368+ 86, 1368+94, 1368+102, 1368+110, 1368+118,
56
	1368+162,
57
};
58
59
// Bitmap mode, sprites enabled.
60
static constexpr std::array<int16_t, 31 + 3> slotsSpritesOn = {
61
	  28,   92,  162,  170,  188,  220,  252,  316,  348,  380,
62
	 444,  476,  508,  572,  604,  636,  700,  732,  764,  828,
63
	 860,  892,  956,  988, 1020, 1084, 1116, 1148, 1212, 1264,
64
	1330,
65
	1368+28, 1368+92, 1368+162,
66
};
67
68
// Character mode, sprites enabled.
69
static constexpr std::array<int16_t, 31 + 3> slotsChar = {
70
	  32,   96,  166,  174,  188,  220,  252,  316,  348,  380,
71
	 444,  476,  508,  572,  604,  636,  700,  732,  764,  828,
72
	 860,  892,  956,  988, 1020, 1084, 1116, 1148, 1212, 1268,
73
	1334,
74
	1368+32, 1368+96, 1368+166,
75
};
76
77
// Text mode.
78
static constexpr std::array<int16_t, 47 + 10> slotsText = {
79
	   2,   10,   18,   26,   34,   42,   50,   58,   66,  166,
80
	 174,  182,  190,  198,  206,  214,  222,  312,  408,  504,
81
	 600,  696,  792,  888,  984, 1080, 1176, 1206, 1214, 1222,
82
	1230, 1238, 1246, 1254, 1262, 1270, 1278, 1286, 1294, 1302,
83
	1310, 1318, 1326, 1336, 1346, 1354, 1362,
84
	1368+ 2, 1368+10, 1368+18, 1368+26, 1368+ 34,
85
	1368+42, 1368+50, 1368+58, 1368+66, 1368+166,
86
};
87
88
89
// TMS9918 (MSX1) cycle numbers translated to V99x8 cycles (multiplied by 4).
90
// MSX1 screen off.
91
static constexpr std::array<int16_t, 107 + 18> slotsMsx1ScreenOff = {
92
	   4,   12,   20,   28,   36,   44,   52,   60,   68,   76,
93
	  84,   92,  100,  108,  116,  124,  132,  140,  148,  156,
94
	 164,  172,  180,  188,  196,  204,  220,  236,  252,  268,
95
	 284,  300,  316,  332,  348,  364,  380,  396,  412,  428,
96
	 444,  460,  476,  492,  508,  524,  540,  556,  572,  588,
97
	 604,  620,  636,  652,  668,  684,  700,  716,  732,  748,
98
	 764,  780,  796,  812,  828,  844,  860,  876,  892,  908,
99
	 924,  940,  956,  972,  988, 1004, 1020, 1036, 1052, 1068,
100
	1084, 1100, 1116, 1132, 1148, 1164, 1180, 1196, 1212, 1228,
101
	1236, 1244, 1252, 1260, 1268, 1276, 1284, 1292, 1300, 1308,
102
	1316, 1324, 1332, 1340, 1348, 1356, 1364,
103
	1368+  4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+ 36,
104
	1368+ 44, 1368+ 52, 1368+ 60, 1368+ 68, 1368+ 76,
105
	1368+ 84, 1368+ 92, 1368+100, 1368+108, 1368+116,
106
	1368+124, 1368+132, 1368+140,
107
};
108
109
// MSX1 graphic mode 1 and 2 (aka screen 1 and 2).
110
static constexpr std::array<int16_t, 19 + 8> slotsMsx1Gfx12 = {
111
	   4,   12,   20,   28,  116,  124,  132,  140,  220,  348,
112
	 476,  604,  732,  860,  988, 1116, 1236, 1244, 1364,
113
	1368+  4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+116,
114
	1368+124, 1368+132, 1368+140,
115
};
116
117
// MSX1 graphic mode 3 (aka screen 3).
118
static constexpr std::array<int16_t, 51 + 8> slotsMsx1Gfx3 = {
119
	   4,   12,   20,   28,  116,  124,  132,  140,  220,  228,
120
	 260,  292,  324,  348,  356,  388,  420,  452,  476,  484,
121
	 516,  548,  580,  604,  612,  644,  676,  708,  732,  740,
122
	 772,  804,  836,  860,  868,  900,  932,  964,  988,  996,
123
	1028, 1060, 1092, 1116, 1124, 1156, 1188, 1220, 1236, 1244,
124
	1364,
125
	1368+  4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+116,
126
	1368+124, 1368+132, 1368+140,
127
};
128
129
130
// MSX1 text mode 1 (aka screen 0 width 40).
131
static constexpr std::array<int16_t, 91 + 18> slotsMsx1Text = {
132
	   4,   12,   20,   28,   36,   44,   52,   60,   68,   76,
133
	  84,   92,  100,  108,  116,  124,  132,  140,  148,  156,
134
	 164,  172,  180,  188,  196,  204,  212,  220,  228,  244,
135
	 268,  292,  316,  340,  364,  388,  412,  436,  460,  484,
136
	 508,  532,  556,  580,  604,  628,  652,  676,  700,  724,
137
	 748,  772,  796,  820,  844,  868,  892,  916,  940,  964,
138
	 988, 1012, 1036, 1060, 1084, 1108, 1132, 1156, 1180, 1196,
139
	1204, 1212, 1220, 1228, 1236, 1244, 1252, 1260, 1268, 1276,
140
	1284, 1292, 1300, 1308, 1316, 1324, 1332, 1340, 1348, 1356,
141
	1364,
142
	1368+  4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+ 36,
143
	1368+ 44, 1368+ 52, 1368+ 60, 1368+ 68, 1368+ 76,
144
	1368+ 84, 1368+ 92, 1368+100, 1368+108, 1368+116,
145
	1368+124, 1368+132, 1368+140,
146
};
147
148
// Helper functions to transform the above tables into a format that is easier
149
// (=faster) to work with.
150
151
/** Upper bound on the number of entries in one of the tables above. */
152
static constexpr size_t MAX_SLOTS = 192;
153
154
/** Extra timing properties of a slot table, based on the investigation
155
  * documented here:
156
  *     https://github.com/sndpl/openMSX/pull/5
157
  *
158
  * 'pad': the VDP pads its line out to 1368 cycles by making a few of its memory
159
  * cycles in the horizontal blanking region longer than the rest. The command
160
  * engine's delay counter does not see those extra cycles, so a delay that spans
161
  * them takes that many more real cycles to be satisfied. Each entry is the
162
  * cycle at which one padded memory cycle has completed, and how many cycles it
163
  * adds; an interval (from, to] loses that many cycles per entry it contains.
164
  * An entry with 'at == 0' is unused.
165
  *
166
  * Measured from /RAS. The total is 4 cycles in each of the three bitmap modes,
167
  * but it is not distributed the same way: with the display off and with sprites
168
  * off two memory cycles are 10 cycles long where the rest of that part of the
169
  * line runs at 8 (and the /CAS of those two follows their /RAS by 2 cycles
170
  * instead of 1); with sprites on the 13/6/10 pattern that part of the line has
171
  * becomes 15/7/11, so three cycles are padded, by 2, 1 and 1.
172
  *
173
  * The underlying reason for this mechanism are the S1/S0 bits in R#9. With these
174
  * the VDP switches between 1368 and 1365 cycles per line (this is not emulated).
175
  * And then in 1368-mode, parts of the VDP 'stall' including the command engine
176
  * part. Measured directly: with S1,S0 != 0,0 the line is 1365 cycles and the
177
  * same memory cycles carry 3 cycles less padding (they do not lose all of it).
178
  *
179
  * 'extra': added to every command engine step. Sprite rendering costs an extra
180
  * cycle, as if the arbitration decision for a slot were taken a cycle earlier
181
  * while the sprite fetch logic is active. (In earlier versions we emulated this
182
  * with 'sprOn' specific values timing values in LMMM and HMMM). The
183
  * measurements admit 1 or 2 here and cannot separate them.
184
  *
185
  * 'lateLead' / 'plainSlots': CPU slots come in two classes. A 'late' slot
186
  * wants 'lateLead' cycles more lookahead than the 16 of a 'plain' one, and it
187
  * releases the CPU's single request buffer one cycle before its access instead
188
  * of one cycle after. The V9938 die shows why: a CPU slot is one tick of a
189
  * two-tick-wide waveform, and which of the two it is decides when the grant is
190
  * sampled. In the sprites-enabled and the G1/G2/G3 tables every CPU slot is
191
  * late except the two in 'plainSlots'; in the other tables 'lateLead' is 0 and
192
  * there are no late slots at all.
193
  *
194
  * These apply to the V99x8 bitmap tables, and the slot classes also to the
195
  * character table; the text and MSX1 tables have never been measured this way
196
  * and are left alone. */
197
struct Timing {
198
	struct Pad {
199
		int16_t at = 0;
200
		int16_t cycles = 0;
201
	};
202
	std::array<Pad, 3> pad = {};
203
	int extra = 0;
204
	int16_t lateLead = 0;
205
	std::array<int16_t, 2> plainSlots = {-1, -1};
206
};
207
208
struct AccessTable
209
{
210
	std::array<uint8_t, NUM_DELTAS * TICKS> values = {};
211
	Timing timing;
212
};
213
214
/** Is this one of the CPU slots that wants the longer lookahead? */
215
[[nodiscard]] static constexpr bool isLate(const Timing& timing, int tick)
216
{
217
	return (timing.lateLead != 0) && !contains(timing.plainSlots, tick);
218
}
219
220
struct CycleTable : AccessTable
221
{
222
	constexpr CycleTable(bool msx1, std::span<const int16_t> slots,
223
	                     Timing timing_ = {})
224
	{
225
		timing = timing_;
226
		assert(std::ranges::is_sorted(slots));
227
228
		// !!! Keep this in sync with the 'Delta' enum !!!
229
		constexpr std::array<int, NUM_DELTAS> delta = {
230
			0, 1, 16, 28, 16, 24, 32, 36, 46, 60, 72, 84, 88, 36+68, 84+36,
231
			60+68, 72+58, 63, 64, 76, 100, 112
232
		};
233
234
		// Memory-cycle time of every cycle in the line: real time minus the
235
		// padding that has completed up to and including it. The distance
236
		// from cycle 'i' to slot 's' as the command engine counts it is then
237
		// mem[s] - mem[i]. Two lines, because the slot tables run past 1368.
238
		std::array<int16_t, 2 * TICKS> mem = {};
239
		for (int t = 1; t < 2 * TICKS; ++t) {
240
			int m = mem[t - 1] + 1;
241
			for (const auto& p : timing.pad) {
242
				if (p.at && ((t == p.at) || (t == p.at + TICKS))) m -= p.cycles;
243
			}
244
			mem[t] = narrow_cast<int16_t>(m);
245
		}
246
247
		// Which slots follow another slot after only 6 cycles? Only the
248
		// sprites-off table has such slots (25 of its 88 slots). And which
249
		// want the longer CPU lookahead? Both indexed by slot, so that the
250
		// search below does not have to ask per candidate.
251
		std::bitset<TICKS> tight;
252
		static_vector<int16_t, 25> tightSlots;
253
		std::array<uint8_t, MAX_SLOTS> cpuLead = {};
254
		for (auto i : xrange(slots.size())) {
255
			auto s = slots[i];
256
			if ((i > 0) && (s >= 6) && ((slots[i - 1] + 6) == s) && (s < TICKS)) {
257
				tight[s] = true; // assume no tight slots wrap around 1368
258
				tightSlots.push_back(s);
259
			}
260
			cpuLead[i] = isLate(timing, s % TICKS)
261
			           ? narrow_cast<uint8_t>(timing.lateLead) : 0;
262
		}
263
		assert(tightSlots.size() == one_of(0u, 25u));
264
265
		size_t out = 0;
266
		for (auto idx : xrange(NUM_DELTAS)) {
267
			bool cmd = (FIRST_CMD_DELTA <= idx) && (idx < LAST_CMD_DELTA);
268
			bool cpu = (FIRST_CPU_DELTA <= idx) && (idx < LAST_CPU_DELTA);
269
			bool skipTight = cpu && (idx != CPU_ANY_DELTA);
270
			int step = delta[idx] + (cmd ? timing.extra : 0);
271
			int p = 0;
272
			for (auto i : xrange(TICKS)) {
273
				// 'mem' is not monotonic in 'i' at the stretched slots, so
274
				// the search can occasionally have to step back one slot.
275
				auto ok = [&](int q) {
276
					if (cpu) {
277
						// The CPU never gets one of the tight slots: the
278
						// VDP spends it on a dummy read and does the CPU's
279
						// access in the next slot. And a 'late' slot hands
280
						// out its grant later than a plain one.
281
						if (skipTight && tight[slots[q] % TICKS]) return false;
282
						return (mem[slots[q]] - mem[i]) >= (step + cpuLead[q]);
283
					}
284
					return cmd ? ((mem[slots[q]] - mem[i]) >= step)
285
					           : ((slots[q] - i) >= step);
286
				};
287
				while (!ok(p)) ++p;
288
				while ((p > 0) && ok(p - 1)) --p;
289
				unsigned t = slots[p] - i;
290
				if (msx1) {
291
					if (step <= 40) assert(t < 256);
292
				} else {
293
					assert(t < 256);
294
				}
295
				values[out++] = narrow_cast<uint8_t>(t);
296
			}
297
		}
298
		// A memory cycle that starts only 6 cycles after the previous
299
		// one finishes one cycle late as far as the command engine is
300
		// concerned, so a step that starts there needs one extra cycle,
301
		// which is the same as starting one cycle later. All the tight
302
		// slots are in the display area, far away from the stretched
303
		// memory cycles, so the shift is exact.
304
		for (auto idx : xrange(FIRST_CMD_DELTA, LAST_CMD_DELTA)) {
305
			for (auto ts : tightSlots) {
306
				size_t b = (size_t(idx) * TICKS) + ts;
307
				values[b] = narrow_cast<uint8_t>(values[b + 1] + 1);
308
			}
309
		}
310
	}
311
};
312
313
static constexpr CycleTable tabSpritesOn    {false, slotsSpritesOn,  Timing{.pad = {{{1330, 2}, {1337, 1}, {1348, 1}}}, .extra = 1, .lateLead = 2, .plainSlots = {162, 170}}};
314
static constexpr CycleTable tabSpritesOff   {false, slotsSpritesOff, Timing{.pad = {{{1332, 2}, {1342, 2}}}, .extra = 0}};
315
static constexpr CycleTable tabChar         {false, slotsChar,        Timing{.lateLead = 2, .plainSlots = {166, 174}}};
316
static constexpr CycleTable tabText         {false, slotsText};
317
static constexpr CycleTable tabScreenOff    {false, slotsScreenOff,  Timing{.pad = {{{1334, 2}, {1344, 2}}}, .extra = 0}};
318
static constexpr CycleTable tabMsx1Gfx12    {true,  slotsMsx1Gfx12};
319
static constexpr CycleTable tabMsx1Gfx3     {true,  slotsMsx1Gfx3};
320
static constexpr CycleTable tabMsx1Text     {true,  slotsMsx1Text};
321
static constexpr CycleTable tabMsx1ScreenOff{true,  slotsMsx1ScreenOff};
322
static constexpr AccessTable tabBroken{};
323
324
325
[[nodiscard]] static inline const AccessTable& getTable(const VDP& vdp)
326
{
327
	if (vdp.getBrokenCmdTiming()) return tabBroken;
328
	// Note: the access grid switches over one line before the display area,
329
	// hence isSlotTableDisplayEnabled() rather than isDisplayEnabled().
330
	bool enabled = vdp.isSlotTableDisplayEnabled();
331
	bool sprites = vdp.spritesEnabledRegister();
332
	auto mode    = vdp.getDisplayMode();
333
	bool bitmap  = mode.isBitmapMode();
334
	bool text    = mode.isTextMode();
335
	bool gfx3    = mode.getBase() == DisplayMode::GRAPHIC3;
336
337
	if (vdp.isMSX1VDP()) {
338
		if (!enabled) return tabMsx1ScreenOff;
339
		return text ? tabMsx1Text
340
		            : (gfx3 ? tabMsx1Gfx3
341
		                    : tabMsx1Gfx12);
342
		// TODO undocumented modes
343
	} else {
344
		if (bitmap) {
345
			return !enabled ? tabScreenOff
346
			      : sprites ? tabSpritesOn
347
			                : tabSpritesOff;
348
		} else {
349
			// 'enabled' or 'sprites' doesn't matter in V99x8 non-bitmap mode
350
			// See: https://github.com/openMSX/openMSX/issues/1754
351
			return text ? tabText
352
			            : tabChar;
353
		}
354
	}
355
}
356
357
EmuTime getAccessSlot(
358
	EmuTime frame_, EmuTime time, Delta delta,
359
	const VDP& vdp)
360
{
361
	VDP::VDPClock frame(frame_);
362
	unsigned ticks = frame.getTicksTill(time) % TICKS;
363
	const auto& tab = getTable(vdp);
364
	return time + VDP::VDPClock::duration(tab.values[std::to_underlying(delta) + ticks]);
365
}
366
367
Calculator getCalculator(
368
	EmuTime frame, EmuTime time, EmuTime limit,
369
	const VDP& vdp)
370
{
371
	return {frame, time, limit, getTable(vdp).values};
372
}
373
374
int paddingCycles(int tick, int n, const VDP& vdp)
375
{
376
	assert(0 <= tick); assert(tick < TICKS);
377
	assert(0 < n); assert(n <= MAX_PADDING_SPAN);
378
379
	int result = 0;
380
	for (const auto& p : getTable(vdp).timing.pad) {
381
		if (!p.at) continue;
382
		int d = p.at - tick;            // cycles till the next time this
383
		if (d <= 0) d += TICKS;         //   padded memory cycle completes
384
		if (d <= n) result += p.cycles;
385
	}
386
	return result;
387
}
388
389
bool isLateCpuSlot(int slotTick, const VDP& vdp)
390
{
391
	return isLate(getTable(vdp).timing, slotTick);
392
}
393
394
} // namespace openmsx::VDPAccessSlots