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