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