OXYGENHUB
◆ main openmsx / src / cpu / MSXCPU.cc
16.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "MSXCPU.hh"
2
3
#include "CPUCore.hh"
4
#include "MSXCPUInterface.hh"
5
#include "R800.hh"
6
#include "Z80.hh"
7
8
#include "Debugger.hh"
9
#include "IntegerSetting.hh"
10
#include "MSXMotherBoard.hh"
11
#include "Scheduler.hh"
12
#include "TclObject.hh"
13
#include "serialize.hh"
14
15
#include "outer.hh"
16
#include "ranges.hh"
17
#include "unreachable.hh"
18
#include "xrange.hh"
19
20
#include <algorithm>
21
#include <cassert>
22
#include <memory>
23
24
namespace openmsx {
25
26
MSXCPU::MSXCPU(MSXMotherBoard& motherboard_)
27
	: motherboard(motherboard_)
28
	, diHaltCallback(
29
		motherboard.getCommandController(), "di_halt_callback",
30
		"Tcl proc called when the CPU executed a DI/HALT sequence",
31
		"default_di_halt_callback",
32
		Setting::Save::YES) // user must be able to override
33
	, z80(std::make_unique<CPUCore<Z80TYPE>>(
34
		motherboard, "z80", diHaltCallback, EmuTime::zero()))
35
	, r800(motherboard.isTurboR()
36
		? std::make_unique<CPUCore<R800TYPE>>(
37
			motherboard, "r800", diHaltCallback, EmuTime::zero())
38
		: nullptr)
39
	, timeInfo(motherboard.getMachineInfoCommand())
40
	, z80FreqInfo(motherboard.getMachineInfoCommand(), "z80_freq", *z80)
41
	, r800FreqInfo(r800
42
		? std::make_unique<CPUFreqInfoTopic>(
43
			motherboard.getMachineInfoCommand(), "r800_freq", *r800)
44
		: nullptr)
45
	, debuggable(motherboard_)
46
{
47
	motherboard.getDebugger().setCPU(this);
48
	motherboard.getScheduler().setCPU(this);
49
50
	z80->freqLocked.attach(*this);
51
	z80->freqValue.attach(*this);
52
	if (r800) {
53
		r800->freqLocked.attach(*this);
54
		r800->freqValue.attach(*this);
55
	}
56
	invalidateMemCacheSlot();
57
}
58
59
MSXCPU::~MSXCPU()
60
{
61
	z80->freqLocked.detach(*this);
62
	z80->freqValue.detach(*this);
63
	if (r800) {
64
		r800->freqLocked.detach(*this);
65
		r800->freqValue.detach(*this);
66
	}
67
	motherboard.getScheduler().setCPU(nullptr);
68
	motherboard.getDebugger() .setCPU(nullptr);
69
}
70
71
void MSXCPU::setInterface(MSXCPUInterface* interface_)
72
{
73
	interface = interface_;
74
	          z80 ->setInterface(interface);
75
	if (r800) r800->setInterface(interface);
76
}
77
78
void MSXCPU::doReset(EmuTime time)
79
{
80
	          z80 ->doReset(time);
81
	if (r800) r800->doReset(time);
82
83
	invalidateAllSlotsRWCache(0x0000, 0x10000);
84
}
85
86
void MSXCPU::setActiveCPU(Type cpu)
87
{
88
	if (cpu == Type::R800) assert(r800);
89
90
	bool tmp = cpu == Type::Z80;
91
	if (tmp != z80Active) {
92
		exitCPULoopSync();
93
		newZ80Active = tmp;
94
	}
95
}
96
97
void MSXCPU::setDRAMmode(bool dram)
98
{
99
	assert(r800);
100
	r800->setDRAMmode(dram);
101
}
102
103
void MSXCPU::execute(bool fastForward)
104
{
105
	if (z80Active != newZ80Active) {
106
		EmuTime time = getCurrentTime();
107
		z80Active = newZ80Active;
108
		z80Active ? z80 ->warp(time)
109
		          : r800->warp(time);
110
		// copy Z80/R800 cache lines
111
		auto zCache = z80 ->getCacheLines();
112
		auto rCache = r800->getCacheLines();
113
		auto from = z80Active ? rCache : zCache;
114
		auto to   = z80Active ? zCache : rCache;
115
		copy_to_range(from.read,  to.read);
116
		copy_to_range(from.write, to.write);
117
	}
118
	z80Active ? z80 ->execute(fastForward)
119
	          : r800->execute(fastForward);
120
}
121
122
void MSXCPU::exitCPULoopSync()
123
{
124
	z80Active ? z80 ->exitCPULoopSync()
125
	          : r800->exitCPULoopSync();
126
}
127
void MSXCPU::exitCPULoopAsync()
128
{
129
	z80Active ? z80 ->exitCPULoopAsync()
130
	          : r800->exitCPULoopAsync();
131
}
132
133
EmuTime MSXCPU::getCurrentTime() const
134
{
135
	return z80Active ? z80 ->getCurrentTime()
136
	                 : r800->getCurrentTime();
137
}
138
139
void MSXCPU::setNextSyncPoint(EmuTime time)
140
{
141
	z80Active ? z80 ->setNextSyncPoint(time)
142
	          : r800->setNextSyncPoint(time);
143
}
144
145
void MSXCPU::invalidateMemCacheSlot()
146
{
147
	if (interface) {
148
		for (auto i : xrange(4)) {
149
			slots[i] = 4 * interface->getPrimarySlot(i)
150
			             + interface->getSecondarySlot(i);
151
		}
152
	} else {
153
		std::ranges::fill(slots, 0);
154
	}
155
156
	// nullptr: means not a valid entry and not yet attempted to fill this entry
157
	for (auto i : xrange(16)) {
158
		std::ranges::fill(slotReadLines[i], nullptr);
159
		std::ranges::fill(slotWriteLines[i], nullptr);
160
	}
161
}
162
163
void MSXCPU::updateVisiblePage(uint8_t page, uint8_t primarySlot, uint8_t secondarySlot)
164
{
165
	assert(primarySlot < 4);
166
	assert(secondarySlot < 4);
167
168
	auto from = slots[page];
169
	auto to = narrow<uint8_t>(4 * primarySlot + secondarySlot);
170
	slots[page] = to;
171
172
	auto [cpuReadLines, cpuWriteLines] = z80Active ? z80->getCacheLines() : r800->getCacheLines();
173
174
	unsigned first = page * (0x4000 / CacheLine::SIZE);
175
	unsigned num = 0x4000 / CacheLine::SIZE;
176
	std::copy_n(&cpuReadLines      [first], num, &slotReadLines [from][first]);
177
	std::copy_n(&slotReadLines [to][first], num, &cpuReadLines        [first]);
178
	std::copy_n(&cpuWriteLines     [first], num, &slotWriteLines[from][first]);
179
	std::copy_n(&slotWriteLines[to][first], num, &cpuWriteLines       [first]);
180
181
	if (r800) r800->updateVisiblePage(page, primarySlot, secondarySlot);
182
}
183
184
void MSXCPU::invalidateAllSlotsRWCache(uint16_t start, unsigned size)
185
{
186
	if (interface) interface->tick(CacheLineCounters::InvalidateAllSlots);
187
	auto [cpuReadLines, cpuWriteLines] = z80Active ? z80->getCacheLines() : r800->getCacheLines();
188
189
	unsigned first = start / CacheLine::SIZE;
190
	unsigned num = (size + CacheLine::SIZE - 1) / CacheLine::SIZE;
191
	std::ranges::fill(subspan(cpuReadLines,  first, num), nullptr); // nullptr: means not a valid entry and not
192
	std::ranges::fill(subspan(cpuWriteLines, first, num), nullptr); //   yet attempted to fill this entry
193
194
	for (auto i : xrange(16)) {
195
		std::ranges::fill(subspan(slotReadLines [i], first, num), nullptr);
196
		std::ranges::fill(subspan(slotWriteLines[i], first, num), nullptr);
197
	}
198
}
199
200
template<bool READ, bool WRITE, bool SUB_START>
201
void MSXCPU::setRWCache(unsigned start, unsigned size, const uint8_t* rData, uint8_t* wData, int ps, int ss,
202
                        std::span<const uint8_t, 256> disallowRead,
203
                        std::span<const uint8_t, 256> disallowWrite)
204
{
205
	if constexpr (!SUB_START) {
206
		assert(rData == nullptr);
207
		assert(wData == nullptr);
208
	}
209
210
	// aligned on cache lines
211
	assert((start & CacheLine::LOW) == 0);
212
	assert((size  & CacheLine::LOW) == 0);
213
214
	unsigned slot = 4 * ps + ss;
215
	unsigned page = start >> 14;
216
	assert(((start + size - 1) >> 14) == page); // all in same page
217
	if constexpr (SUB_START && READ)  rData -= start;
218
	if constexpr (SUB_START && WRITE) wData -= start;
219
220
	// select between 'active' or 'shadow' cache lines
221
	auto [readLines, writeLines] = [&] {
222
		if (slot == slots[page]) {
223
			return z80Active ? z80->getCacheLines() : r800->getCacheLines();
224
		} else {
225
			return CacheLines{.read  = slotReadLines [slot],
226
			                  .write = slotWriteLines[slot]};
227
		}
228
	}();
229
230
	unsigned first = start / CacheLine::SIZE;
231
	unsigned num = size / CacheLine::SIZE;
232
233
	static auto* const NON_CACHEABLE = std::bit_cast<uint8_t*>(uintptr_t(1));
234
	for (auto i : xrange(num)) {
235
		if constexpr (READ)  readLines [first + i] = disallowRead [first + i] ? NON_CACHEABLE : rData;
236
		if constexpr (WRITE) writeLines[first + i] = disallowWrite[first + i] ? NON_CACHEABLE : wData;
237
	}
238
}
239
240
static constexpr void extendForAlignment(unsigned& start, unsigned& size)
241
{
242
	constexpr unsigned MASK = ~CacheLine::LOW; // not CacheLine::HIGH because 0x0000ff00 != 0xffffff00
243
244
	auto end = start + size;
245
	start &= MASK;                            // round down to cacheline
246
	end = (end + CacheLine::SIZE - 1) & MASK; // round up to cacheline
247
	size = end - start;
248
}
249
250
void MSXCPU::invalidateRWCache(unsigned start, unsigned size, int ps, int ss,
251
                               std::span<const uint8_t, 256> disallowRead,
252
                               std::span<const uint8_t, 256> disallowWrite)
253
{
254
	// unaligned [start, start+size) is OK for invalidate, then simply invalidate a little more.
255
	extendForAlignment(start, size);
256
	setRWCache<true, true, false>(start, size, nullptr, nullptr, ps, ss, disallowRead, disallowWrite);
257
}
258
void MSXCPU::invalidateRCache(unsigned start, unsigned size, int ps, int ss,
259
                              std::span<const uint8_t, 256> disallowRead,
260
                              std::span<const uint8_t, 256> disallowWrite)
261
{
262
	extendForAlignment(start, size);
263
	setRWCache<true, false, false>(start, size, nullptr, nullptr, ps, ss, disallowRead, disallowWrite);
264
}
265
void MSXCPU::invalidateWCache(unsigned start, unsigned size, int ps, int ss,
266
                              std::span<const uint8_t, 256> disallowRead,
267
                              std::span<const uint8_t, 256> disallowWrite)
268
{
269
	extendForAlignment(start, size);
270
	setRWCache<false, true, false>(start, size, nullptr, nullptr, ps, ss, disallowRead, disallowWrite);
271
}
272
273
void MSXCPU::fillRWCache(unsigned start, unsigned size, const uint8_t* rData, uint8_t* wData, int ps, int ss,
274
                         std::span<const uint8_t, 256> disallowRead,
275
                         std::span<const uint8_t, 256> disallowWrite)
276
{
277
	setRWCache<true, true, true>(start, size, rData, wData, ps, ss, disallowRead, disallowWrite);
278
}
279
void MSXCPU::fillRCache(unsigned start, unsigned size, const uint8_t* rData, int ps, int ss,
280
                        std::span<const uint8_t, 256> disallowRead,
281
                        std::span<const uint8_t, 256> disallowWrite)
282
{
283
	setRWCache<true, false, true>(start, size, rData, nullptr, ps, ss, disallowRead, disallowWrite);
284
}
285
void MSXCPU::fillWCache(unsigned start, unsigned size, uint8_t* wData, int ps, int ss,
286
                        std::span<const uint8_t, 256> disallowRead,
287
                        std::span<const uint8_t, 256> disallowWrite)
288
{
289
	setRWCache<false, true, true>(start, size, nullptr, wData, ps, ss, disallowRead, disallowWrite);
290
}
291
292
void MSXCPU::raiseIRQ()
293
{
294
	          z80 ->raiseIRQ();
295
	if (r800) r800->raiseIRQ();
296
}
297
void MSXCPU::lowerIRQ()
298
{
299
	          z80 ->lowerIRQ();
300
	if (r800) r800->lowerIRQ();
301
}
302
void MSXCPU::raiseNMI()
303
{
304
	          z80 ->raiseNMI();
305
	if (r800) r800->raiseNMI();
306
}
307
void MSXCPU::lowerNMI()
308
{
309
	          z80 ->lowerNMI();
310
	if (r800) r800->lowerNMI();
311
}
312
313
bool MSXCPU::isM1Cycle(unsigned address) const
314
{
315
	return z80Active ? z80 ->isM1Cycle(address)
316
	                 : r800->isM1Cycle(address);
317
}
318
319
void MSXCPU::setZ80Freq(unsigned freq)
320
{
321
	z80->setFreq(freq);
322
}
323
324
void MSXCPU::wait(EmuTime time)
325
{
326
	z80Active ? z80 ->wait(time)
327
	          : r800->wait(time);
328
}
329
330
EmuTime MSXCPU::waitCyclesZ80(EmuTime time, unsigned cycles)
331
{
332
	return z80Active ? z80 ->waitCycles(time, cycles)
333
	                 : time;
334
}
335
336
EmuTime MSXCPU::waitCyclesR800(EmuTime time, unsigned cycles)
337
{
338
	return z80Active ? time
339
	                 : r800->waitCycles(time, cycles);
340
}
341
342
CPURegs& MSXCPU::getRegisters()
343
{
344
	if (z80Active) {
345
		return *z80;
346
	} else {
347
		return *r800;
348
	}
349
}
350
351
void MSXCPU::update(const Setting& setting) noexcept
352
{
353
	          z80 ->update(setting);
354
	if (r800) r800->update(setting);
355
	exitCPULoopSync();
356
}
357
358
void MSXCPU::setPaused(bool paused)
359
{
360
	if (z80Active) {
361
		z80 ->setExtHALT(paused);
362
		z80 ->exitCPULoopSync();
363
	} else {
364
		r800->setExtHALT(paused);
365
		r800->exitCPULoopSync();
366
	}
367
}
368
369
370
// class TimeInfoTopic
371
372
MSXCPU::TimeInfoTopic::TimeInfoTopic(InfoCommand& machineInfoCommand)
373
	: InfoTopic(machineInfoCommand, "time")
374
{
375
}
376
377
void MSXCPU::TimeInfoTopic::execute(
378
	std::span<const TclObject> /*tokens*/, TclObject& result) const
379
{
380
	const auto& cpu = OUTER(MSXCPU, timeInfo);
381
	result = cpu.getCurrentTime().toDouble();
382
}
383
384
std::string MSXCPU::TimeInfoTopic::help(std::span<const TclObject> /*tokens*/) const
385
{
386
	return "Prints the time in seconds that the MSX is powered on\n";
387
}
388
389
390
// class CPUFreqInfoTopic
391
392
MSXCPU::CPUFreqInfoTopic::CPUFreqInfoTopic(
393
		InfoCommand& machineInfoCommand,
394
		const std::string& name_, CPUClock& clock_)
395
	: InfoTopic(machineInfoCommand, name_)
396
	, clock(clock_)
397
{
398
}
399
400
void MSXCPU::CPUFreqInfoTopic::execute(
401
	std::span<const TclObject> /*tokens*/, TclObject& result) const
402
{
403
	result = clock.getFreq();
404
}
405
406
std::string MSXCPU::CPUFreqInfoTopic::help(std::span<const TclObject> /*tokens*/) const
407
{
408
	return "Returns the actual frequency of this CPU.\n"
409
	       "This frequency can vary because:\n"
410
	       " - the user has overridden the freq via the '{z80,r800}_freq' setting\n"
411
	       " - (only on some MSX machines) the MSX software can switch the Z80 between 2 frequencies\n"
412
	       "See also the '{z80,r800}_freq_locked' setting.\n";
413
}
414
415
416
// class Debuggable
417
418
static constexpr static_string_view CPU_REGS_DESC =
419
	"Registers of the active CPU (Z80 or R800).\n"
420
	"Each byte in this debuggable represents one 8 bit register:\n"
421
	"  0 ->  A      1 ->  F      2 -> B       3 -> C\n"
422
	"  4 ->  D      5 ->  E      6 -> H       7 -> L\n"
423
	"  8 ->  A'     9 ->  F'    10 -> B'     11 -> C'\n"
424
	" 12 ->  D'    13 ->  E'    14 -> H'     15 -> L'\n"
425
	" 16 -> IXH    17 -> IXL    18 -> IYH    19 -> IYL\n"
426
	" 20 -> PCH    21 -> PCL    22 -> SPH    23 -> SPL\n"
427
	" 24 ->  I     25 ->  R     26 -> IM     27 -> IFF1/2\n"
428
	"The last position (27) contains the IFF1 and IFF2 flags in respectively\n"
429
	"bit 0 and 1. Bit 2 contains 'IFF1 AND last-instruction-was-not-EI', so\n"
430
	"this effectively indicates that the CPU could accept an interrupt at\n"
431
	"the start of the current instruction.\n";
432
433
MSXCPU::Debuggable::Debuggable(MSXMotherBoard& motherboard_)
434
	: SimpleDebuggable(motherboard_, "CPU regs", CPU_REGS_DESC, 28)
435
{
436
}
437
438
uint8_t MSXCPU::Debuggable::read(unsigned address)
439
{
440
	auto& cpu = OUTER(MSXCPU, debuggable);
441
	const CPURegs& regs = cpu.getRegisters();
442
	switch (address) {
443
	case  0: return regs.getA();
444
	case  1: return regs.getF();
445
	case  2: return regs.getB();
446
	case  3: return regs.getC();
447
	case  4: return regs.getD();
448
	case  5: return regs.getE();
449
	case  6: return regs.getH();
450
	case  7: return regs.getL();
451
	case  8: return regs.getA2();
452
	case  9: return regs.getF2();
453
	case 10: return regs.getB2();
454
	case 11: return regs.getC2();
455
	case 12: return regs.getD2();
456
	case 13: return regs.getE2();
457
	case 14: return regs.getH2();
458
	case 15: return regs.getL2();
459
	case 16: return regs.getIXh();
460
	case 17: return regs.getIXl();
461
	case 18: return regs.getIYh();
462
	case 19: return regs.getIYl();
463
	case 20: return regs.getPCh();
464
	case 21: return regs.getPCl();
465
	case 22: return regs.getSPh();
466
	case 23: return regs.getSPl();
467
	case 24: return regs.getI();
468
	case 25: return regs.getR();
469
	case 26: return regs.getIM();
470
	case 27: return uint8_t(1 *  regs.getIFF1() +
471
	                        2 *  regs.getIFF2() +
472
	                        4 * (regs.getIFF1() && !regs.prevWasEI()));
473
	default: UNREACHABLE;
474
	}
475
}
476
477
void MSXCPU::Debuggable::write(unsigned address, uint8_t value)
478
{
479
	auto& cpu = OUTER(MSXCPU, debuggable);
480
	CPURegs& regs = cpu.getRegisters();
481
	switch (address) {
482
	case  0: regs.setA(value); break;
483
	case  1: regs.setF(value); break;
484
	case  2: regs.setB(value); break;
485
	case  3: regs.setC(value); break;
486
	case  4: regs.setD(value); break;
487
	case  5: regs.setE(value); break;
488
	case  6: regs.setH(value); break;
489
	case  7: regs.setL(value); break;
490
	case  8: regs.setA2(value); break;
491
	case  9: regs.setF2(value); break;
492
	case 10: regs.setB2(value); break;
493
	case 11: regs.setC2(value); break;
494
	case 12: regs.setD2(value); break;
495
	case 13: regs.setE2(value); break;
496
	case 14: regs.setH2(value); break;
497
	case 15: regs.setL2(value); break;
498
	case 16: regs.setIXh(value); break;
499
	case 17: regs.setIXl(value); break;
500
	case 18: regs.setIYh(value); break;
501
	case 19: regs.setIYl(value); break;
502
	case 20: regs.setPCh(value); break;
503
	case 21: regs.setPCl(value); break;
504
	case 22: regs.setSPh(value); break;
505
	case 23: regs.setSPl(value); break;
506
	case 24: regs.setI(value); break;
507
	case 25: regs.setR(value); break;
508
	case 26:
509
		if (value < 3) regs.setIM(value);
510
		break;
511
	case 27:
512
		regs.setIFF1((value & 0x01) != 0);
513
		regs.setIFF2((value & 0x02) != 0);
514
		// can't change afterEI
515
		break;
516
	default:
517
		UNREACHABLE;
518
	}
519
}
520
521
// version 1: initial version
522
// version 2: activeCPU,newCPU -> z80Active,newZ80Active
523
// version 3: removed resetTime
524
template<typename Archive>
525
void MSXCPU::serialize(Archive& ar, unsigned version)
526
{
527
	if (ar.versionAtLeast(version, 2)) {
528
		          ar.serialize("z80",  *z80);
529
		if (r800) ar.serialize("r800", *r800);
530
		ar.serialize("z80Active",    z80Active,
531
		             "newZ80Active", newZ80Active);
532
	} else {
533
		// backwards-compatibility
534
		assert(Archive::IS_LOADER);
535
536
		          ar.serializeWithID("z80",  *z80);
537
		if (r800) ar.serializeWithID("r800", *r800);
538
		CPUBase* activeCPU = nullptr;
539
		CPUBase* newCPU = nullptr;
540
		ar.serializePointerID("activeCPU", activeCPU);
541
		ar.serializePointerID("newCPU",    newCPU);
542
		z80Active = activeCPU == z80.get();
543
		if (newCPU) {
544
			newZ80Active = newCPU == z80.get();
545
		} else {
546
			newZ80Active = z80Active;
547
		}
548
	}
549
550
	if constexpr (Archive::IS_LOADER) {
551
		invalidateMemCacheSlot();
552
		invalidateAllSlotsRWCache(0x0000, 0x10000);
553
	}
554
}
555
INSTANTIATE_SERIALIZE_METHODS(MSXCPU);
556
557
} // namespace openmsx