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