OXYGENHUB
◆ main openmsx / src / memory / Carnivore2.cc
24.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "Carnivore2.hh"
2
3
#include "DummyAY8910Periphery.hh"
4
#include "IDEDevice.hh"
5
#include "IDEDeviceFactory.hh"
6
#include "MSXCPU.hh"
7
#include "MSXCliComm.hh"
8
9
#include "narrow.hh"
10
#include "one_of.hh"
11
#include "ranges.hh"
12
#include "xrange.hh"
13
14
#include <array>
15
16
// TODO (besides what's in the code below):
17
// - FM-PAC mono/stereo setting (bit 7)
18
// - the PPI volume setting
19
// - slave slot support
20
21
// Note that the somewhat complex (and illogical?) behaviour of the
22
// user-defined ID and control port I/O is mimicking what the actual VHDL code
23
// is doing (in the released 2.50 firmware code).
24
25
namespace openmsx {
26
27
Carnivore2::Carnivore2(DeviceConfig& config)
28
	: MSXDevice(config)
29
	, MSXMapperIOClient(getMotherBoard())
30
	, flash(getName() + " flash", AmdFlashChip::M29W640GB, {}, config)
31
	, ram(config, getName() + " ram", "ram", 2048 * 1024)
32
	, eeprom(getName() + " eeprom",
33
	         DeviceConfig(config, config.getChild("eeprom")))
34
	, scc(getName() + " scc", config, getCurrentTime(), SCC::Mode::Compatible)
35
	, psg(getName() + " PSG", DummyAY8910Periphery::instance(), config,
36
              getCurrentTime())
37
	, ym2413(getName() + " ym2413", config)
38
{
39
	// adjust PSG volume, see details in https://github.com/openMSX/openMSX/issues/1934
40
	// note: this is a theoretical value. The actual relative volume should be measured!
41
	psg.setSoftwareVolume(21000.0f/9000.0f, getCurrentTime());
42
43
	ideDevices[0] = IDEDeviceFactory::create(
44
		DeviceConfig(config, config.findChild("master")));
45
	ideDevices[1] = IDEDeviceFactory::create(
46
		DeviceConfig(config, config.findChild("slave")));
47
48
	configRegs[0x24] = 0; // to avoid UMR in powerUp -> reset -> writePSGAlt
49
	configRegs[0x30] = 0; // to avoid UMR in powerUp -> writePSGCtrl
50
	configRegs[0x35] = 0xf0; // to avoid UMR in powerUp -> writePFXN
51
	getCPUInterface().register_IO_InOut(idControlPort(), this);
52
53
	powerUp(getCurrentTime());
54
}
55
56
Carnivore2::~Carnivore2()
57
{
58
	// unregister PSG I/O ports, by disabling PSG
59
	writePSGCtrl(0, getCurrentTime());
60
	// unregister user-defined ID and control port
61
	getCPUInterface().unregister_IO_InOut(idControlPort(), this);
62
}
63
64
void Carnivore2::powerUp(EmuTime time)
65
{
66
	writeSndLVL(0x1b, time);
67
	writePSGCtrl(0x1b, time);
68
	writePFXN(0xf0);
69
	scc.powerUp(time);
70
	reset(time);
71
}
72
73
void Carnivore2::reset(EmuTime time)
74
{
75
	subSlotReg = 0;
76
	port3C = 0;
77
78
	// config regs
79
	configRegs[0x00] = 0x30; // CardMDR
80
	for (int i : {0x01, 0x02, 0x03}) configRegs[i] = 0; // AddrM<i>
81
	configRegs[0x05] = shadowConfigRegs[0x05] = 0; // AddrFR
82
83
	configRegs[0x06] = shadowConfigRegs[0x06] = 0xF8; // R1Mask
84
	configRegs[0x07] = shadowConfigRegs[0x07] = 0x50; // R1Addr
85
	configRegs[0x08] = shadowConfigRegs[0x08] = 0x00; // R1Reg
86
	configRegs[0x09] = shadowConfigRegs[0x09] = 0x85; // R1Mult
87
	configRegs[0x0a] = shadowConfigRegs[0x0a] = 0x03; // B1MaskR
88
	configRegs[0x0b] = shadowConfigRegs[0x0b] = 0x40; // B1AdrD
89
90
	for (int i : {0x0f, 0x15, 0x1b}) {
91
		configRegs[i] = shadowConfigRegs[i] = 0; // R<i>Mult
92
	}
93
94
	configRegs[0x1e] = shadowConfigRegs[0x1e] = 0xff;
95
	configRegs[0x20] = 0x02;
96
	configRegs[0x28] = 0b11'10'01'00; // SLM_cfg
97
98
	writePSGAlt(0);
99
100
	writeCfgEEPR(0, time);
101
102
	// multi-mapper
103
	scc.reset(time);
104
	sccMode = 0;
105
	ranges::iota(sccBank, byte(0));
106
107
	// PSG
108
	psgLatch = 0;
109
	psg.reset(time);
110
111
	// ide
112
	ideControlReg = 0;
113
	ideSelectedDevice = 0;
114
	ideSoftReset = false;
115
	ideDevices[0]->reset(time);
116
	ideDevices[1]->reset(time);
117
118
	// memory mapper
119
	ranges::iota(memMapRegs, byte(0)); // Note: different from how BIOS initializes these registers
120
121
	// fm-pac
122
	ym2413.reset(time);
123
	fmPacEnable = 0x10;
124
	fmPacBank = 0;
125
	fmPac5ffe = 0;
126
	fmPac5fff = 0;
127
128
	// User-defined ID and control port I/O
129
	PF0_RV = 0;
130
}
131
132
void Carnivore2::globalRead(uint16_t address, EmuTime /*time*/)
133
{
134
	if (!delayedConfig()) return;
135
136
	if ((!delayedConfig4000() && (address == 0x0000) && (getCPU().isM1Cycle(address))) ||
137
	    ( delayedConfig4000() && (address <= 0x4000) && (address < 0x4010))) {
138
		// activate delayed configuration
139
		for (auto i : xrange(0x05, 0x1f)) {
140
			configRegs[i] = shadowConfigRegs[i];
141
		}
142
	}
143
}
144
145
Carnivore2::SubDevice Carnivore2::getSubDevice(uint16_t address) const
146
{
147
	byte subSlot = 0xff;
148
149
	if (slotExpanded()) {
150
		auto page = narrow<byte>(address >> 14);
151
		byte selectedSubSlot = (subSlotReg >> (2 * page)) & 0x03;
152
		if (subSlotEnabled(selectedSubSlot)) {
153
			subSlot = selectedSubSlot;
154
		}
155
	} else {
156
		for (auto i : xrange(byte(4))) {
157
			if (subSlotEnabled(i)) {
158
				subSlot = i;
159
				break;
160
			}
161
		}
162
	}
163
164
	using enum SubDevice;
165
	if        (subSlot == (configRegs[0x28] & 0b00'00'00'11) >> 0) {
166
		return MultiMapper;
167
	} else if (subSlot == (configRegs[0x28] & 0b00'00'11'00) >> 2) {
168
		return IDE;
169
	} else if (subSlot == (configRegs[0x28] & 0b00'11'00'00) >> 4) {
170
		return MemoryMapper;
171
	} else if (subSlot == (configRegs[0x28] & 0b11'00'00'00) >> 6) {
172
		return FmPac;
173
	} else {
174
		return Nothing;
175
	}
176
}
177
178
byte Carnivore2::readMem(uint16_t address, EmuTime time)
179
{
180
	if (slotExpanded() && (address == 0xffff)) {
181
		return subSlotReg ^ 0xff;
182
	}
183
	switch (getSubDevice(address)) {
184
		using enum SubDevice;
185
		case MultiMapper:  return readMultiMapperSlot(address, time);
186
		case IDE:          return readIDESlot(address, time);
187
		case MemoryMapper: return readMemoryMapperSlot(address);
188
		case FmPac:        return readFmPacSlot(address, time);
189
		default:           return 0xff;
190
	}
191
}
192
193
byte Carnivore2::peekMem(uint16_t address, EmuTime time) const
194
{
195
	if (slotExpanded() && (address == 0xffff)) {
196
		return subSlotReg ^ 0xff;
197
	}
198
	switch (getSubDevice(address)) {
199
		using enum SubDevice;
200
		case MultiMapper:  return peekMultiMapperSlot(address, time);
201
		case IDE:          return peekIDESlot(address, time);
202
		case MemoryMapper: return peekMemoryMapperSlot(address);
203
		case FmPac:        return peekFmPacSlot(address, time);
204
		default:           return 0xff;
205
	}
206
}
207
208
void Carnivore2::writeMem(uint16_t address, byte value, EmuTime time)
209
{
210
	if (slotExpanded() && (address == 0xffff)) {
211
		subSlotReg = value;
212
		// this does not block the writes below
213
	}
214
215
	switch (getSubDevice(address)) {
216
		using enum SubDevice;
217
		case MultiMapper:
218
			writeMultiMapperSlot(address, value, time);
219
			break;
220
		case IDE:
221
			writeIDESlot(address, value, time);
222
			break;
223
		case MemoryMapper:
224
			writeMemoryMapperSlot(address, value);
225
			break;
226
		case FmPac:
227
			writeFmPacSlot(address, value, time);
228
			break;
229
		default:
230
			// unmapped, do nothing
231
			break;
232
	}
233
}
234
235
unsigned Carnivore2::getDirectFlashAddr() const
236
{
237
	return (configRegs[0x01] <<  0) |
238
	       (configRegs[0x02] <<  8) |
239
	       (configRegs[0x03] << 16);  // already masked to 7 bits
240
}
241
242
byte Carnivore2::peekConfigRegister(uint16_t address, EmuTime time) const
243
{
244
	address &= 0x3f;
245
	if ((0x05 <= address) && (address <= 0x1e)) {
246
		// only these registers have a shadow counterpart,
247
		// reads happen from the shadowed version
248
		return shadowConfigRegs[address];
249
	} else {
250
		switch (address) {
251
			case 0x04: return flash.peek(getDirectFlashAddr(), time);
252
			case 0x1f: return configRegs[0x00]; // mirror 'CardMDR' register
253
			case 0x23: return byte(configRegs[address] |
254
					       byte(eeprom.read_DO(time)));
255
			case 0x2C: return '2';
256
			case 0x2D: return '5';
257
			case 0x2E: return '0';
258
			default:   return configRegs[address];
259
		}
260
	}
261
}
262
263
byte Carnivore2::readConfigRegister(uint16_t address, EmuTime time)
264
{
265
	address &= 0x3f;
266
	if (address == 0x04) {
267
		return flash.read(getDirectFlashAddr(), time);
268
	} else {
269
		return peekConfigRegister(address, time);
270
	}
271
}
272
273
static constexpr float volumeLevel(byte volume)
274
{
275
	constexpr std::array<byte, 8> tab = {5, 6, 7, 8, 10, 12, 14, 16};
276
	return narrow<float>(tab[volume & 7]) * (1.0f / 16.0f);
277
}
278
279
void Carnivore2::writeSndLVL(byte value, EmuTime time)
280
{
281
	configRegs[0x22] = value;
282
	ym2413.setSoftwareVolume(volumeLevel(value >> 3), time);
283
	scc   .setSoftwareVolume(volumeLevel(value >> 0), time);
284
}
285
286
void Carnivore2::writeCfgEEPR(byte value, EmuTime time)
287
{
288
	configRegs[0x23] = value & 0x0e;
289
	eeprom.write_DI (value & 2, time);
290
	eeprom.write_CLK(value & 4, time);
291
	eeprom.write_CS (value & 8, time);
292
}
293
294
void Carnivore2::writePSGCtrl(byte value, EmuTime time)
295
{
296
	// TODO: PPI clicker
297
	if ((value ^ configRegs[0x24]) & 0x80) {   // enable changed
298
		byte ioBase = (configRegs[0x30] & 0x01) ?  0x10 : 0xa0;
299
		if (value & 0x80) {
300
			getCPUInterface().  register_IO_Out_range(ioBase, 2, this);
301
		} else {
302
			getCPUInterface().unregister_IO_Out_range(ioBase, 2, this);
303
		}
304
	}
305
	configRegs[0x24] = value;
306
	psg.setSoftwareVolume(volumeLevel((value >> 3) & 7), time);
307
}
308
309
void Carnivore2::writePSGAlt(byte value)
310
{
311
	if ((value ^ configRegs[0x30]) & 0x01) { // ports changed
312
		if (configRegs[0x24] & 0x80) {
313
			byte ioBaseOld = (configRegs[0x30] & 0x01) ? 0x10 : 0xa0;
314
			byte ioBaseNew = (value            & 0x01) ? 0x10 : 0xa0;
315
			getCPUInterface().unregister_IO_Out_range(ioBaseOld, 2, this);
316
			getCPUInterface().  register_IO_Out_range(ioBaseNew, 2, this);
317
		}
318
	}
319
	configRegs[0x30] = value;
320
}
321
322
void Carnivore2::writePFXN(byte value)
323
{
324
	byte oldPort = idControlPort();
325
	configRegs[0x35] = 0xf0 | (value & 0b11);
326
	if (auto newPort = idControlPort(); newPort != oldPort) {
327
		getCPUInterface().unregister_IO_InOut(oldPort, this);
328
		getCPUInterface().  register_IO_InOut(newPort, this);
329
	}
330
}
331
332
// check whether each of the 4 bit pairs are unique in the given byte x
333
[[nodiscard]] static bool bitPairsUnique(uint8_t x)
334
{
335
	uint8_t seen = 0;
336
	for (int i = 0; i < 4; ++i) {
337
		seen |= 1 << (x & 3);
338
		x >>= 2;
339
	}
340
	return seen == 0b1111;
341
}
342
343
void Carnivore2::writeConfigRegister(uint16_t address, byte value, EmuTime time)
344
{
345
	address &= 0x3f;
346
	if ((0x05 <= address) && (address <= 0x1e)) {
347
		// shadow registers
348
		if (address == 0x05) value &= 0x7f;
349
		if ((address == 0x1e) && ((value & 0x8f) == 0x0f)) return; // ignore write
350
351
		shadowConfigRegs[address] = value;
352
		if (!delayedConfig()) configRegs[address] = value;
353
	} else {
354
		switch (address) {
355
			case 0x03: configRegs[address] = value & 0x7f; break;
356
			case 0x04: flash.write(getDirectFlashAddr(), value, time); break;
357
			case 0x1f: configRegs[0x00] = value; break; // mirror 'CardMDR' register
358
			case 0x20: configRegs[address] = value & 0x07; break;
359
			case 0x22: writeSndLVL(value, time); break;
360
			case 0x23: writeCfgEEPR(value, time); break;
361
			case 0x24: writePSGCtrl(value, time); break;
362
			case 0x30: writePSGAlt(value); break;
363
			case 0x35: writePFXN(value); break;
364
			case 0x28:
365
				if (!bitPairsUnique(value)) {
366
					   getCliComm().printWarning(
367
						"Illegal value of ", value,
368
						"written to SLM_cfg register");
369
				}
370
				[[fallthrough]];
371
			default:   configRegs[address] = value; break;
372
		}
373
	}
374
}
375
376
bool Carnivore2::isConfigReg(uint16_t address) const
377
{
378
	if (configRegs[0x00] & 0x80) return false; // config regs disabled
379
	unsigned base = ((configRegs[0x00] & 0x60) << 9) | 0xF80;
380
	return (base <= address) && (address < (base + 0x40));
381
}
382
383
std::pair<unsigned, byte> Carnivore2::decodeMultiMapper(uint16_t address) const
384
{
385
	// check up to 4 possible banks
386
	for (auto i : xrange(4)) {
387
		auto base = subspan<6>(configRegs, (i * 6) + 6); // points to R<i>Mask
388
		byte mult = base[3];
389
		if (mult & 8) continue; // bank disabled
390
391
		byte sizeCode = mult & 7;
392
		if (sizeCode < 3) continue; // invalid size
393
394
		// check address
395
		bool mirroringDisabled = mult & 0x40;
396
		static constexpr std::array checkMasks = {
397
			std::array<byte, 8>{0x00, 0x00, 0x00, 0x30, 0x60, 0xc0, 0x80, 0x00}, // mirroring enabled
398
			std::array<byte, 8>{0x00, 0x00, 0x00, 0xf0, 0xe0, 0xc0, 0x80, 0x00}, // mirroring disabled
399
		};
400
		byte checkMask = checkMasks[mirroringDisabled][sizeCode];
401
		if (((address >> 8) & checkMask) != (base[5] & checkMask)) continue;
402
403
		// found bank
404
		byte bank = base[2] & base[4];
405
		unsigned size = 512 << sizeCode; // 7->64k, 6->32k, ..., 3->4k
406
		unsigned addr = (bank * size) | (address & (size - 1));
407
		addr += configRegs[0x05] * 0x10000; // 64kB block offset
408
		addr &= 0x7fffff; // 8MB
409
		return {addr, mult};
410
	}
411
	return {unsigned(-1), byte(-1)};
412
}
413
414
bool Carnivore2::sccAccess(uint16_t address) const
415
{
416
	if (!sccEnabled()) return false;
417
	if (sccMode & 0x20) {
418
		// check scc plus
419
		return (0xb800 <= address) && (address < 0xc000) &&
420
		       ((sccBank[3] & 0x80) == 0x80);
421
	} else {
422
		// check scc compatible
423
		return (0x9800 <= address) && (address < 0xa000) &&
424
	               ((sccBank[2] & 0x3f) == 0x3f);
425
	}
426
}
427
428
byte Carnivore2::readMultiMapperSlot(uint16_t address, EmuTime time)
429
{
430
	if (isConfigReg(address)) {
431
		return readConfigRegister(address, time);
432
	}
433
	if (sccAccess(address)) {
434
		return scc.readMem(narrow_cast<uint8_t>(address & 0xff), time);
435
	}
436
437
	auto [addr, mult] = decodeMultiMapper(address);
438
	if (addr == unsigned(-1)) return 0xff; // unmapped
439
440
	if (mult & 0x20) {
441
		return ram[addr & 0x1fffff]; // 2MB
442
	} else {
443
		return flash.read(addr, time);
444
	}
445
}
446
447
byte Carnivore2::peekMultiMapperSlot(uint16_t address, EmuTime time) const
448
{
449
	if (isConfigReg(address)) {
450
		return peekConfigRegister(address, time);
451
	}
452
453
	auto [addr, mult] = decodeMultiMapper(address);
454
	if (addr == unsigned(-1)) return 0xff; // unmapped
455
456
	if (mult & 0x20) {
457
		return ram[addr & 0x1fffff]; // 2MB
458
	} else {
459
		return flash.peek(addr, time);
460
	}
461
}
462
463
void Carnivore2::writeMultiMapperSlot(uint16_t address, byte value, EmuTime time)
464
{
465
	if (isConfigReg(address)) {
466
		// this blocks writes to switch-region and bank-region
467
		writeConfigRegister(address, value, time);
468
		return;
469
	}
470
471
	// check (all) 4 bank switch regions
472
	for (auto i : xrange(4)) {
473
		auto base = subspan<6>(configRegs, (i * 6) + 6); // points to R<i>Mask
474
		byte mask = base[0];
475
		byte addr = base[1];
476
		byte mult = base[3];
477
		if (mult & 0x80) { // enable bit in R<i>Mult
478
			if (((address >> 8) & mask) == (addr & mask)) {
479
				// update actual+shadow reg
480
				      configRegs[(i * 6) + 6 + 2] = value;
481
				shadowConfigRegs[(i * 6) + 6 + 2] = value;
482
			}
483
		}
484
	}
485
486
	auto [addr, mult] = decodeMultiMapper(address);
487
	if ((addr != unsigned(-1)) && (mult & 0x10)) { // write enable
488
		if (mult & 0x20) {
489
			ram[addr & 0x1fffff] = value; // 2MB
490
		} else {
491
			flash.write(addr, value, time);
492
		}
493
	}
494
495
	if (sccEnabled() && ((address | 1) == 0xbfff)) {
496
		// write scc mode register (write-only)
497
		sccMode = value;
498
		scc.setMode((sccMode & 0x20) ? SCC::Mode::Plus : SCC::Mode::Compatible);
499
	}
500
	if (((sccMode & 0x10) == 0x00) && // note: no check for sccEnabled()
501
	    ((address & 0x1800) == 0x1000)) {
502
		auto region = narrow<byte>((address >> 13) - 2);
503
		sccBank[region] = value;
504
	} else if (sccAccess(address)) {
505
		scc.writeMem(narrow_cast<uint8_t>(address & 0xff), value, time);
506
	}
507
}
508
509
byte Carnivore2::readIDESlot(uint16_t address, EmuTime time)
510
{
511
	// TODO mirroring is different from SunriseIDE
512
	if (ideRegsEnabled() && ((address & 0xfe00) == 0x7c00)) {
513
		// 0x7c00-0x7dff   IDE data register
514
		switch (address & 1) {
515
			case 0: { // data low
516
				auto tmp = ideReadData(time);
517
				ideRead = narrow_cast<byte>(tmp >> 8);
518
				return narrow_cast<byte>(tmp & 0xff);
519
			}
520
			case 1: // data high
521
				return ideRead;
522
		}
523
	}
524
	if (ideRegsEnabled() && ((address & 0xff00) == 0x7e00)) {
525
		// 0x7e00-0x7eff   IDE registers
526
		return ideReadReg(address & 0xf, time);
527
	}
528
	if ((0x4000 <= address) && (address < 0x8000)) {
529
		// read IDE flash rom
530
		unsigned addr = (address & 0x3fff) + (ideBank() * 0x4000) + 0x10000;
531
		if (readBIOSfromRAM()) {
532
			return ram[addr];
533
		} else {
534
			return flash.read(addr, time);
535
		}
536
	}
537
	return 0xff;
538
}
539
540
byte Carnivore2::peekIDESlot(uint16_t address, EmuTime time) const
541
{
542
	if (ideRegsEnabled() && ((address & 0xfe00) == 0x7c00)) {
543
		// 0x7c00-0x7dff   IDE data register
544
		return 0xff; // TODO not yet implemented
545
	}
546
	if (ideRegsEnabled() && ((address & 0xff00) == 0x7e00)) {
547
		// 0x7e00-0x7eff   IDE registers
548
		return 0xff; // TODO not yet implemented
549
	}
550
	if ((0x4000 <= address) && (address < 0x8000)) {
551
		// read IDE flash rom
552
		unsigned addr = (address & 0x3fff) + (ideBank() * 0x4000) + 0x10000;
553
		if (readBIOSfromRAM()) {
554
			return ram[addr];
555
		} else {
556
			return flash.peek(addr, time);
557
		}
558
	}
559
	return 0xff;
560
}
561
562
void Carnivore2::writeIDESlot(uint16_t address, byte value, EmuTime time)
563
{
564
	// TODO mirroring is different from SunriseIDE
565
	if (address == 0x4104) {
566
		ideControlReg = value;
567
568
	} else if (ideRegsEnabled() && ((address & 0xfe00) == 0x7c00)) {
569
		// 0x7c00-0x7dff   IDE data register
570
		switch (address & 1) {
571
			case 0: // data low
572
				ideWrite = value;
573
				break;
574
			case 1: { // data high
575
				auto tmp = uint16_t((value << 8) | ideWrite);
576
				ideWriteData(tmp, time);
577
				break;
578
			}
579
		}
580
581
	} else if (ideRegsEnabled() && ((address & 0xff00) == 0x7e00)) {
582
		// 0x7e00-0x7eff   IDE registers
583
		ideWriteReg(address & 0xf, value, time);
584
	}
585
}
586
587
uint16_t Carnivore2::ideReadData(EmuTime time)
588
{
589
	return ideDevices[ideSelectedDevice]->readData(time);
590
}
591
592
void Carnivore2::ideWriteData(uint16_t value, EmuTime time)
593
{
594
	ideDevices[ideSelectedDevice]->writeData(value, time);
595
}
596
597
byte Carnivore2::ideReadReg(byte reg, EmuTime time)
598
{
599
	if (reg == 14) reg = 7; // alternate status register
600
601
	if (ideSoftReset) {
602
		if (reg == 7) { // read status
603
			return 0xff; // busy
604
		} else { // all others
605
			return 0x7f; // don't care
606
		}
607
	} else {
608
		if (reg == 0) {
609
			return narrow_cast<byte>(ideReadData(time) & 0xff);
610
		} else {
611
			auto result = ideDevices[ideSelectedDevice]->readReg(reg, time);
612
			if (reg == 6) {
613
				result = (result & 0xef) | (ideSelectedDevice ? 0x10 : 0x00);
614
			}
615
			return result;
616
		}
617
	}
618
}
619
620
void Carnivore2::ideWriteReg(byte reg, byte value, EmuTime time)
621
{
622
	if (ideSoftReset) {
623
		if ((reg == 14) && !(value & 0x04)) {
624
			// clear SRST
625
			ideSoftReset = false;
626
		}
627
		// ignore all other writes
628
	} else {
629
		if (reg == 0) {
630
			ideWriteData(narrow_cast<uint16_t>((value << 8) | value), time);
631
		} else {
632
			if ((reg == 14) && (value & 0x04)) {
633
				// set SRST
634
				ideSoftReset = true;
635
				ideDevices[0]->reset(time);
636
				ideDevices[1]->reset(time);
637
			} else {
638
				if (reg == 6) {
639
					ideSelectedDevice = (value & 0x10) ? 1 : 0;
640
				}
641
				ideDevices[ideSelectedDevice]->writeReg(reg, value, time);
642
			}
643
		}
644
	}
645
}
646
647
bool Carnivore2::isMemMapControl(uint16_t address) const
648
{
649
	return (port3C & 0x80) &&
650
	       (( (port3C & 0x08) && ((address & 0xc000) == 0x4000)) ||
651
	        (!(port3C & 0x08) && ((address & 0xc000) == 0x8000)));
652
}
653
654
unsigned Carnivore2::getMemoryMapperAddress(uint16_t address) const
655
{
656
	return (address & 0x3fff) +
657
	       0x4000 * memMapRegs[address >> 14] +
658
	       0x100000; // 2nd half of 2MB
659
}
660
661
bool Carnivore2::isMemoryMapperWriteProtected(uint16_t address) const
662
{
663
	auto page = address >> 14;
664
	return (port3C & (1 << page)) != 0;
665
}
666
667
byte Carnivore2::peekMemoryMapperSlot(uint16_t address) const
668
{
669
	if (isMemMapControl(address)) {
670
		switch (address & 0xff) {
671
		case 0x3c:
672
			return port3C;
673
		case 0xfc: case 0xfd: case 0xfe: case 0xff:
674
			return memMapRegs[address & 0x03];
675
		}
676
	}
677
	return ram[getMemoryMapperAddress(address)];
678
}
679
680
byte Carnivore2::readMemoryMapperSlot(uint16_t address) const
681
{
682
	return peekMemoryMapperSlot(address);
683
}
684
685
void Carnivore2::writeMemoryMapperSlot(uint16_t address, byte value)
686
{
687
	if (isMemMapControl(address)) {
688
		switch (address & 0xff) {
689
		case 0x3c:
690
			value |= (value & 0x02) << 6; // TODO should be '(.. 0x20) << 2' ???
691
			port3C = value;
692
			return;
693
		case 0xfc: case 0xfd: case 0xfe: case 0xff:
694
			memMapRegs[address & 0x03] = value & 0x3f;
695
			return;
696
		}
697
	}
698
	if (!isMemoryMapperWriteProtected(address)) {
699
		ram[getMemoryMapperAddress(address)] = value;
700
	}
701
}
702
703
byte Carnivore2::readFmPacSlot(uint16_t address, EmuTime time)
704
{
705
	if (address == 0x7ff6) {
706
		return fmPacEnable; // enable
707
	} else if (address == 0x7ff7) {
708
		return fmPacBank; // bank
709
	} else if ((0x4000 <= address) && (address < 0x8000)) {
710
		if (fmPacSramEnabled()) {
711
			if (address < 0x5ffe) {
712
				return ram[(address & 0x1fff) | 0xfe000];
713
			} else if (address == 0x5ffe) {
714
				return fmPac5ffe; // always 0x4d
715
			} else if (address == 0x5fff) {
716
				return fmPac5fff; // always 0x69
717
			} else {
718
				return 0xff;
719
			}
720
		} else {
721
			unsigned addr = (address & 0x3fff) + (0x4000 * fmPacBank) + 0x30000;
722
			if (readBIOSfromRAM()) {
723
				return ram[addr];
724
			} else {
725
				return flash.read(addr, time);
726
			}
727
		}
728
	}
729
	return 0xff;
730
}
731
732
byte Carnivore2::peekFmPacSlot(uint16_t address, EmuTime time) const
733
{
734
	if (address == 0x7ff6) {
735
		return fmPacEnable; // enable
736
	} else if (address == 0x7ff7) {
737
		return fmPacBank; // bank
738
	} else if ((0x4000 <= address) && (address < 0x8000)) {
739
		if (fmPacSramEnabled()) {
740
			if (address < 0x5ffe) {
741
				return ram[(address & 0x1fff) | 0xfe000];
742
			} else if (address == 0x5ffe) {
743
				return fmPac5ffe; // always 0x4d
744
			} else if (address == 0x5fff) {
745
				return fmPac5fff; // always 0x69
746
			} else {
747
				return 0xff;
748
			}
749
		} else {
750
			unsigned addr = (address & 0x3fff) + (0x4000 * fmPacBank) + 0x30000;
751
			if (readBIOSfromRAM()) {
752
				return ram[addr];
753
			} else {
754
				return flash.peek(addr, time);
755
			}
756
		}
757
	}
758
	return 0xff;
759
}
760
761
void Carnivore2::writeFmPacSlot(uint16_t address, byte value, EmuTime time)
762
{
763
	if ((0x4000 <= address) && (address < 0x5ffe)) {
764
		if (fmPacSramEnabled()) {
765
			ram[(address & 0x1fff) | 0xfe000] = value;
766
		}
767
	} else if (address == 0x5ffe) {
768
		fmPac5ffe = value;
769
	} else if (address == 0x5fff) {
770
		fmPac5fff = value;
771
	} else if (address == one_of(0x7ff4, 0x7ff5)) {
772
		ym2413.writePort(address & 1, value, time);
773
	} else if (address == 0x7ff6) {
774
		fmPacEnable = value & 0x11;
775
	} else if (address == 0x7ff7) {
776
		fmPacBank = value & 0x03;
777
	}
778
}
779
780
byte Carnivore2::readIO(uint16_t port, EmuTime time)
781
{
782
	return peekIO(port, time);
783
}
784
785
byte Carnivore2::peekIO(uint16_t port, EmuTime /*time*/) const
786
{
787
	// reading ports 0x3c, 0x7c, 0x7d has no effect
788
	if (memMapReadEnabled() && ((port & 0xfc) == 0xfc)) {
789
		// memory mapper registers
790
		return getSelectedSegment(port & 3);
791
	} else if ((port & 0xff) == idControlPort() && PF0_RV != 0) {
792
		if (PF0_RV == 1) {
793
			return '2';
794
		} else if (PF0_RV == 2) {
795
			return 0x30 | getPrimarySlot();
796
		}
797
	}
798
	return 0xff;
799
}
800
801
void Carnivore2::writeIO(uint16_t port, byte value, EmuTime time)
802
{
803
	// note: we only get writes to either PSG ports if they're enabled/configured
804
	if (((port & 0xff) == 0xa0) || ((port & 0xff) == 0x10)) {
805
		psgLatch = value & 0x0f;
806
	} else if ((port & 0xff) == 0xa1 || (port & 0xff) == 0x11) {
807
		psg.writeRegister(psgLatch, value, time);
808
	} else if (((port & 0xfe) == 0x7c) &&
809
	    (fmPacPortEnabled1() || fmPacPortEnabled2())) {
810
		// fm-pac registers
811
		ym2413.writePort(port & 1, value, time);
812
	} else if (((port & 0xff) == 0x3c) && writePort3cEnabled()) {
813
		// only change bit 7
814
		port3C = (port3C & 0x7F) | (value & 0x80);
815
816
	} else if ((port & 0xfc) == 0xfc) {
817
		// memory mapper registers
818
		memMapRegs[port & 0x03] = value & 0x3f;
819
		invalidateDeviceRWCache(0x4000 * (port & 0x03), 0x4000);
820
	} else if ((port & 0xff) == idControlPort()) {
821
		if (value == 'C') {
822
			PF0_RV = 1;
823
		} else if (value == 'S') {
824
			PF0_RV = 2;
825
		} else if (value == 'H') {
826
			configRegs[0x00] |=  1;
827
		} else if (value == 'R') {
828
			configRegs[0x00] &= ~1;
829
		} else if ('0' <= value && value <= '3') {
830
			configRegs[0x00] &= ~(0b11 << 5);
831
			configRegs[0x00] |= byte((value - '0') << 5);
832
		} else if (value == 'A') {
833
			shadowConfigRegs[0x1e] &= ~1; // Mconf
834
		} else if (value == 'M') {
835
			shadowConfigRegs[0x1e] |=  1; // Mconf
836
		} else {
837
			// yes, this is the only case where we will reset this
838
			PF0_RV = 0;
839
		}
840
	}
841
}
842
843
byte Carnivore2::getSelectedSegment(byte page) const
844
{
845
	return memMapRegs[page];
846
}
847
848
// version 1: initial version
849
// version 2: removed fmPacRegSelect
850
// version 3: added PSG and user-defined ID and control port I/O
851
template<typename Archive>
852
void Carnivore2::serialize(Archive& ar, unsigned version)
853
{
854
	ar.template serializeBase<MSXDevice>(*this);
855
	ar.serialize("flash",            flash,
856
	             "ram",              ram,
857
	             "eeprom",           eeprom,
858
	             "configRegs",       configRegs,
859
	             "shadowConfigRegs", shadowConfigRegs,
860
	             "subSlotReg",       subSlotReg,
861
	             "port3C",           port3C,
862
863
	             "scc",              scc,
864
	             "sccMode",          sccMode,
865
	             "sccBank",          sccBank);
866
	if (ar.versionAtLeast(version, 3)) {
867
		ar.serialize("psg",      psg,
868
		             "psgLatch", psgLatch,
869
		             "PF0_RV",   PF0_RV);
870
	} else {
871
		psgLatch = 0;
872
		PF0_RV = 0;
873
	}
874
875
	ar.serializePolymorphic("master", *ideDevices[0]);
876
	ar.serializePolymorphic("slave",  *ideDevices[1]);
877
	ar.serialize("ideSoftReset",      ideSoftReset,
878
	             "ideSelectedDevice", ideSelectedDevice,
879
	             "ideControlReg",     ideControlReg,
880
	             "ideRead",           ideRead,
881
	             "ideWrite",          ideWrite,
882
883
	             "memMapRegs",        memMapRegs,
884
885
	             "ym2413",            ym2413,
886
	             "fmPacEnable",       fmPacEnable,
887
	             "fmPacBank",         fmPacBank,
888
	             "fmPac5ffe",         fmPac5ffe,
889
	             "fmPac5fff",         fmPac5fff);
890
891
	if constexpr (Archive::IS_LOADER) {
892
		auto time = getCurrentTime();
893
		writeSndLVL (configRegs[0x22], time);
894
		writeCfgEEPR(configRegs[0x23], time);
895
		// make sure changes are seen by setting the register to 0 first
896
		auto backup24 = configRegs[0x24];
897
		configRegs[0x24] = 0;
898
		writePSGCtrl(backup24, time);
899
		auto backup35 = configRegs[0x35];
900
		configRegs[0x35] = 0xf0;
901
		writePFXN(backup35);
902
	}
903
}
904
INSTANTIATE_SERIALIZE_METHODS(Carnivore2);
905
REGISTER_MSXDEVICE(Carnivore2, "Carnivore2");
906
907
} // namespace openmsx