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