24static constexpr auto sectorInfo = [] {
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});
36 , flash(
getName() +
" flash", sectorInfo, 0x207e,
37 AmdFlash::Addressing::BITS_12, config)
38 , ram(config,
getName() +
" ram",
"ram", 2048 * 1024)
41 , scc(
getName() +
" scc", config, getCurrentTime(),
SCC::SCC_Compatible)
44 , ym2413(
getName() +
" ym2413", config)
53 configRegs[0x35] = 0xf0;
69 writeSndLVL(0x1b, time);
70 writePSGCtrl(0x1b, time);
82 configRegs[0x00] = 0x30;
83 for (
int i : {0x01, 0x02, 0x03}) configRegs[i] = 0;
84 configRegs[0x05] = shadowConfigRegs[0x05] = 0;
86 configRegs[0x06] = shadowConfigRegs[0x06] = 0xF8;
87 configRegs[0x07] = shadowConfigRegs[0x07] = 0x50;
88 configRegs[0x08] = shadowConfigRegs[0x08] = 0x00;
89 configRegs[0x09] = shadowConfigRegs[0x09] = 0x85;
90 configRegs[0x0a] = shadowConfigRegs[0x0a] = 0x03;
91 configRegs[0x0b] = shadowConfigRegs[0x0b] = 0x40;
93 for (
int i : {0x0f, 0x15, 0x1b}) {
94 configRegs[i] = shadowConfigRegs[i] = 0;
97 configRegs[0x1e] = shadowConfigRegs[0x1e] = 0xff;
98 configRegs[0x20] = 0x02;
99 configRegs[0x28] = 0b11'10'01'00;
103 writeCfgEEPR(0, time);
116 ideSelectedDevice = 0;
117 ideSoftReset =
false;
118 ideDevices[0]->reset(time);
119 ideDevices[1]->reset(time);
137 if (!delayedConfig())
return;
139 if ((!delayedConfig4000() && (address == 0x0000) && (
getCPU().isM1Cycle(address))) ||
140 ( delayedConfig4000() && (address <= 0x4000) && (address < 0x4010))) {
142 for (
auto i :
xrange(0x05, 0x1f)) {
143 configRegs[i] = shadowConfigRegs[i];
148Carnivore2::SubDevice Carnivore2::getSubDevice(
word address)
const
152 if (slotExpanded()) {
153 auto page = narrow<byte>(address >> 14);
154 byte selectedSubSlot = (subSlotReg >> (2 * page)) & 0x03;
155 if (subSlotEnabled(selectedSubSlot)) {
156 subSlot = selectedSubSlot;
159 for (
auto i :
xrange(
byte(4))) {
160 if (subSlotEnabled(i)) {
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;
176 return SubDevice::Nothing;
182 if (slotExpanded() && (address == 0xffff)) {
183 return subSlotReg ^ 0xff;
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;
196 if (slotExpanded() && (address == 0xffff)) {
197 return subSlotReg ^ 0xff;
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;
210 if (slotExpanded() && (address == 0xffff)) {
215 switch (getSubDevice(address)) {
216 case SubDevice::MultiMapper:
217 writeMultiMapperSlot(address, value, time);
220 writeIDESlot(address, value, time);
222 case SubDevice::MemoryMapper:
223 writeMemoryMapperSlot(address, value);
225 case SubDevice::FmPac:
226 writeFmPacSlot(address, value, time);
234unsigned Carnivore2::getDirectFlashAddr()
const
236 return (configRegs[0x01] << 0) |
237 (configRegs[0x02] << 8) |
238 (configRegs[0x03] << 16);
241byte Carnivore2::peekConfigRegister(
word address, EmuTime::param time)
const
244 if ((0x05 <= address) && (address <= 0x1e)) {
247 return shadowConfigRegs[address];
250 case 0x04:
return flash.
peek(getDirectFlashAddr());
251 case 0x1f:
return configRegs[0x00];
252 case 0x23:
return byte(configRegs[address] |
254 case 0x2C:
return '2';
255 case 0x2D:
return '5';
256 case 0x2E:
return '0';
257 default:
return configRegs[address];
262byte Carnivore2::readConfigRegister(
word address, EmuTime::param time)
265 if (address == 0x04) {
266 return flash.
read(getDirectFlashAddr());
268 return peekConfigRegister(address, time);
272static constexpr float volumeLevel(
byte volume)
274 constexpr std::array<byte, 8> tab = {5, 6, 7, 8, 10, 12, 14, 16};
275 return narrow<float>(tab[volume & 7]) / 16.0f;
278void Carnivore2::writeSndLVL(
byte value, EmuTime::param time)
280 configRegs[0x22] = value;
285void Carnivore2::writeCfgEEPR(
byte value, EmuTime::param time)
287 configRegs[0x23] = value & 0x0e;
293void Carnivore2::writePSGCtrl(
byte value, EmuTime::param time)
296 if ((value ^ configRegs[0x24]) & 0x80) {
297 byte ioBase = (configRegs[0x30] & 0x01) ? 0x10 : 0xa0;
306 configRegs[0x24] = value;
310void Carnivore2::writePSGAlt(
byte value)
312 if ((value ^ configRegs[0x30]) & 0x01) {
313 if (configRegs[0x24] & 0x80) {
314 byte ioBaseOld = (configRegs[0x30] & 0x01) ? 0x10 : 0xa0;
315 byte ioBaseNew = (value & 0x01) ? 0x10 : 0xa0;
322 configRegs[0x30] = value;
325void Carnivore2::writePFXN(
byte value)
327 byte oldPort = idControlPort();
328 configRegs[0x35] = 0xf0 | (value & 0b11);
329 if (
auto newPort = idControlPort(); newPort != oldPort) {
338[[nodiscard]]
static bool bitPairsUnique(uint8_t x)
341 for (
int i = 0; i < 4; ++i) {
342 seen |= 1 << (x & 3);
345 return seen == 0b1111;
348void Carnivore2::writeConfigRegister(
word address,
byte value, EmuTime::param time)
351 if ((0x05 <= address) && (address <= 0x1e)) {
353 if (address == 0x05) value &= 0x7f;
354 if ((address == 0x1e) && ((value & 0x8f) == 0x0f))
return;
356 shadowConfigRegs[address] = value;
357 if (!delayedConfig()) configRegs[address] = value;
360 case 0x03: configRegs[address] = value & 0x7f;
break;
361 case 0x04: flash.
write(getDirectFlashAddr(), value);
break;
362 case 0x1f: configRegs[0x00] = value;
break;
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;
370 if (!bitPairsUnique(value)) {
372 "Illegal value of ", value,
373 "written to SLM_cfg register");
376 default: configRegs[address] = value;
break;
381bool Carnivore2::isConfigReg(
word address)
const
383 if (configRegs[0x00] & 0x80)
return false;
384 unsigned base = ((configRegs[0x00] & 0x60) << 9) | 0xF80;
385 return (base <= address) && (address < (base + 0x40));
388std::pair<unsigned, byte> Carnivore2::decodeMultiMapper(
word address)
const
391 for (
auto i :
xrange(4)) {
392 auto base = subspan<6>(configRegs, (i * 6) + 6);
394 if (mult & 8)
continue;
396 byte sizeCode = mult & 7;
397 if (sizeCode < 3)
continue;
400 bool mirroringDisabled = mult & 0x40;
401 static constexpr std::array checkMasks = {
402 std::array<byte, 8>{0x00, 0x00, 0x00, 0x30, 0x60, 0xc0, 0x80, 0x00},
403 std::array<byte, 8>{0x00, 0x00, 0x00, 0xf0, 0xe0, 0xc0, 0x80, 0x00},
405 byte checkMask = checkMasks[mirroringDisabled][sizeCode];
406 if (((address >> 8) & checkMask) != (base[5] & checkMask))
continue;
409 byte bank = base[2] & base[4];
410 unsigned size = 512 << sizeCode;
411 unsigned addr = (bank *
size) | (address & (
size - 1));
412 addr += configRegs[0x05] * 0x10000;
416 return {unsigned(-1),
byte(-1)};
419bool Carnivore2::sccAccess(
word address)
const
421 if (!sccEnabled())
return false;
422 if (sccMode & 0x20) {
424 return (0xb800 <= address) && (address < 0xc000) &&
425 ((sccBank[3] & 0x80) == 0x80);
428 return (0x9800 <= address) && (address < 0xa000) &&
429 ((sccBank[2] & 0x3f) == 0x3f);
433byte Carnivore2::readMultiMapperSlot(
word address, EmuTime::param time)
435 if (isConfigReg(address)) {
436 return readConfigRegister(address, time);
438 if (sccAccess(address)) {
439 return scc.
readMem(narrow_cast<uint8_t>(address & 0xff), time);
442 auto [addr, mult] = decodeMultiMapper(address);
443 if (addr ==
unsigned(-1))
return 0xff;
446 return ram[addr & 0x1fffff];
448 return flash.
read(addr);
452byte Carnivore2::peekMultiMapperSlot(
word address, EmuTime::param time)
const
454 if (isConfigReg(address)) {
455 return peekConfigRegister(address, time);
458 auto [addr, mult] = decodeMultiMapper(address);
459 if (addr ==
unsigned(-1))
return 0xff;
462 return ram[addr & 0x1fffff];
464 return flash.
peek(addr);
468void Carnivore2::writeMultiMapperSlot(
word address,
byte value, EmuTime::param time)
470 if (isConfigReg(address)) {
472 return writeConfigRegister(address, value, time);
476 for (
auto i :
xrange(4)) {
477 auto base = subspan<6>(configRegs, (i * 6) + 6);
482 if (((address >> 8) & mask) == (addr & mask)) {
484 configRegs[(i * 6) + 6 + 2] = value;
485 shadowConfigRegs[(i * 6) + 6 + 2] = value;
490 auto [addr, mult] = decodeMultiMapper(address);
491 if ((addr !=
unsigned(-1)) && (mult & 0x10)) {
493 ram[addr & 0x1fffff] = value;
495 flash.
write(addr, value);
499 if (sccEnabled() && ((address | 1) == 0xbfff)) {
504 if (((sccMode & 0x10) == 0x00) &&
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);
513byte Carnivore2::readIDESlot(
word address, EmuTime::param time)
516 if (ideRegsEnabled() && ((address & 0xfe00) == 0x7c00)) {
518 switch (address & 1) {
520 auto tmp = ideReadData(time);
521 ideRead = narrow_cast<byte>(tmp >> 8);
522 return narrow_cast<byte>(tmp & 0xff);
528 if (ideRegsEnabled() && ((address & 0xff00) == 0x7e00)) {
530 return ideReadReg(address & 0xf, time);
532 if ((0x4000 <= address) && (address < 0x8000)) {
534 unsigned addr = (address & 0x3fff) + (ideBank() * 0x4000) + 0x10000;
535 if (readBIOSfromRAM()) {
538 return flash.
read(addr);
544byte Carnivore2::peekIDESlot(
word address, EmuTime::param )
const
546 if (ideRegsEnabled() && ((address & 0xfe00) == 0x7c00)) {
550 if (ideRegsEnabled() && ((address & 0xff00) == 0x7e00)) {
554 if ((0x4000 <= address) && (address < 0x8000)) {
556 unsigned addr = (address & 0x3fff) + (ideBank() * 0x4000) + 0x10000;
557 if (readBIOSfromRAM()) {
560 return flash.
peek(addr);
566void Carnivore2::writeIDESlot(
word address,
byte value, EmuTime::param time)
569 if (address == 0x4104) {
570 ideControlReg = value;
572 }
else if (ideRegsEnabled() && ((address & 0xfe00) == 0x7c00)) {
574 switch (address & 1) {
579 auto tmp =
word((value << 8) | ideWrite);
580 ideWriteData(tmp, time);
585 }
else if (ideRegsEnabled() && ((address & 0xff00) == 0x7e00)) {
587 ideWriteReg(address & 0xf, value, time);
591word Carnivore2::ideReadData(EmuTime::param time)
593 return ideDevices[ideSelectedDevice]->readData(time);
596void Carnivore2::ideWriteData(
word value, EmuTime::param time)
598 ideDevices[ideSelectedDevice]->writeData(value, time);
601byte Carnivore2::ideReadReg(
byte reg, EmuTime::param time)
603 if (reg == 14) reg = 7;
613 return narrow_cast<byte>(ideReadData(time) & 0xff);
615 auto result = ideDevices[ideSelectedDevice]->readReg(reg, time);
617 result = (result & 0xef) | (ideSelectedDevice ? 0x10 : 0x00);
624void Carnivore2::ideWriteReg(
byte reg,
byte value, EmuTime::param time)
627 if ((reg == 14) && !(value & 0x04)) {
629 ideSoftReset =
false;
634 ideWriteData(narrow_cast<word>((value << 8) | value), time);
636 if ((reg == 14) && (value & 0x04)) {
639 ideDevices[0]->reset(time);
640 ideDevices[1]->reset(time);
643 ideSelectedDevice = (value & 0x10) ? 1 : 0;
645 ideDevices[ideSelectedDevice]->writeReg(reg, value, time);
651bool Carnivore2::isMemMapControl(
word address)
const
653 return (port3C & 0x80) &&
654 (( (port3C & 0x08) && ((address & 0xc000) == 0x4000)) ||
655 (!(port3C & 0x08) && ((address & 0xc000) == 0x8000)));
658unsigned Carnivore2::getMemoryMapperAddress(
word address)
const
660 return (address & 0x3fff) +
661 0x4000 * memMapRegs[address >> 14] +
665bool Carnivore2::isMemoryMapperWriteProtected(
word address)
const
667 auto page = address >> 14;
668 return (port3C & (1 << page)) != 0;
671byte Carnivore2::peekMemoryMapperSlot(
word address)
const
673 if (isMemMapControl(address)) {
674 switch (address & 0xff) {
677 case 0xfc:
case 0xfd:
case 0xfe:
case 0xff:
678 return memMapRegs[address & 0x03];
681 return ram[getMemoryMapperAddress(address)];
684byte Carnivore2::readMemoryMapperSlot(
word address)
686 return peekMemoryMapperSlot(address);
689void Carnivore2::writeMemoryMapperSlot(
word address,
byte value)
691 if (isMemMapControl(address)) {
692 switch (address & 0xff) {
694 value |= (value & 0x02) << 6;
697 case 0xfc:
case 0xfd:
case 0xfe:
case 0xff:
698 memMapRegs[address & 0x03] = value & 0x3f;
702 if (!isMemoryMapperWriteProtected(address)) {
703 ram[getMemoryMapperAddress(address)] = value;
707byte Carnivore2::readFmPacSlot(
word address)
709 if (address == 0x7ff6) {
711 }
else if (address == 0x7ff7) {
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) {
719 }
else if (address == 0x5fff) {
725 unsigned addr = (address & 0x3fff) + (0x4000 * fmPacBank) + 0x30000;
726 if (readBIOSfromRAM()) {
729 return flash.
read(addr);
736byte Carnivore2::peekFmPacSlot(
word address)
const
738 if (address == 0x7ff6) {
740 }
else if (address == 0x7ff7) {
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) {
748 }
else if (address == 0x5fff) {
754 unsigned addr = (address & 0x3fff) + (0x4000 * fmPacBank) + 0x30000;
755 if (readBIOSfromRAM()) {
758 return flash.
peek(addr);
765void Carnivore2::writeFmPacSlot(
word address,
byte value, EmuTime::param time)
767 if ((0x4000 <= address) && (address < 0x5ffe)) {
768 if (fmPacSramEnabled()) {
769 ram[(address & 0x1fff) | 0xfe000] = value;
771 }
else if (address == 0x5ffe) {
773 }
else if (address == 0x5fff) {
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;
786 return peekIO(port, time);
792 if (memMapReadEnabled() && ((port & 0xfc) == 0xfc)) {
795 }
else if ((port & 0xff) == idControlPort() && PF0_RV != 0) {
798 }
else if (PF0_RV == 2) {
808 if (((port & 0xff) == 0xa0) || ((port & 0xff) == 0x10)) {
809 psgLatch = value & 0x0f;
810 }
else if ((port & 0xff) == 0xa1 || (port & 0xff) == 0x11) {
812 }
else if (((port & 0xfe) == 0x7c) &&
813 (fmPacPortEnabled1() || fmPacPortEnabled2())) {
816 }
else if (((port & 0xff) == 0x3c) && writePort3cEnabled()) {
818 port3C = (port3C & 0x7F) | (value & 0x80);
820 }
else if ((port & 0xfc) == 0xfc) {
822 memMapRegs[port & 0x03] = value & 0x3f;
824 }
else if ((port & 0xff) == idControlPort()) {
827 }
else if (value ==
'S') {
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;
838 }
else if (value ==
'M') {
839 shadowConfigRegs[0x1e] |= 1;
849 return memMapRegs[page];
855template<
typename Archive>
858 ar.template serializeBase<MSXDevice>(*
this);
859 ar.serialize(
"flash", flash,
862 "configRegs", configRegs,
863 "shadowConfigRegs", shadowConfigRegs,
864 "subSlotReg", subSlotReg,
870 if (ar.versionAtLeast(version, 3)) {
871 ar.serialize(
"psg", psg,
872 "psgLatch", psgLatch,
879 ar.serializePolymorphic(
"master", *ideDevices[0]);
880 ar.serializePolymorphic(
"slave", *ideDevices[1]);
881 ar.serialize(
"ideSoftReset", ideSoftReset,
882 "ideSelectedDevice", ideSelectedDevice,
883 "ideControlReg", ideControlReg,
885 "ideWrite", ideWrite,
887 "memMapRegs", memMapRegs,
890 "fmPacEnable", fmPacEnable,
891 "fmPacBank", fmPacBank,
892 "fmPac5ffe", fmPac5ffe,
893 "fmPac5fff", fmPac5fff);
895 if constexpr (Archive::IS_LOADER) {
897 writeSndLVL (configRegs[0x22], time);
898 writeCfgEEPR(configRegs[0x23], time);
900 auto backup24 = configRegs[0x24];
901 configRegs[0x24] = 0;
902 writePSGCtrl(backup24, time);
903 auto backup35 = configRegs[0x35];
904 configRegs[0x35] = 0xf0;
void reset(EmuTime::param time)
void writeRegister(unsigned reg, uint8_t value, EmuTime::param time)
void write(size_t address, uint8_t value)
uint8_t peek(size_t address) const
uint8_t read(size_t address) const
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.
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.
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)
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)
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.
CliComm & getCliComm() const
void invalidateDeviceRWCache()
Calls MSXCPUInterface::invalidateXXCache() for the specific (part of) the slot that this device is lo...
byte getPrimarySlot() const
EmuTime::param getCurrentTime() const
MSXCPUInterface & getCPUInterface() const
void setChipMode(ChipMode newMode)
void powerUp(EmuTime::param time)
uint8_t readMem(uint8_t address, EmuTime::param time)
void reset(EmuTime::param time)
void writeMem(uint8_t address, uint8_t value, EmuTime::param time)
void setSoftwareVolume(float volume, EmuTime::param time)
Change the 'software volume' of this sound device.
void writePort(bool port, byte value, EmuTime::param time)
void reset(EmuTime::param time)
std::unique_ptr< IDEDevice > create(const DeviceConfig &config)
std::string getName(KeyCode keyCode)
Translate key code to key name.
This file implemented 3 utility functions:
uint8_t byte
8 bit unsigned integer
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
AmdFlash::SectorInfo Info
uint16_t word
16 bit unsigned integer
constexpr void fill(ForwardRange &&range, const T &value)
constexpr void iota(ForwardIt first, ForwardIt last, T value)
size_t size(std::string_view utf8)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
constexpr auto xrange(T e)