diff --git a/build/msvc/openmsx.vcxproj b/build/msvc/openmsx.vcxproj
index 4520a5e354..d9ba7194bc 100644
--- a/build/msvc/openmsx.vcxproj
+++ b/build/msvc/openmsx.vcxproj
@@ -555,6 +555,7 @@
+
@@ -1134,6 +1135,7 @@
+
diff --git a/build/msvc/openmsx.vcxproj.filters b/build/msvc/openmsx.vcxproj.filters
index 3aa4c95869..43ec0792b3 100644
--- a/build/msvc/openmsx.vcxproj.filters
+++ b/build/msvc/openmsx.vcxproj.filters
@@ -765,6 +765,9 @@
memory
+
+ memory
+
memory
@@ -2257,6 +2260,9 @@
memory
+
+ memory
+
memory
diff --git a/share/extensions/ASCII16-X_Mapper_XL.xml b/share/extensions/ASCII16-X_Mapper_XL.xml
new file mode 100644
index 0000000000..6392918e68
--- /dev/null
+++ b/share/extensions/ASCII16-X_Mapper_XL.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ ASCII16-X Mapper XL
+ Grauw
+ ASCII16-X
+ 2024
+ ASCII16-X Mapper Extra Large (XL) 8 MB
+ Flash cartridge
+
+
+
+
+
+
+
+ 8192
+
+ ASCII16-X_Mapper_XL.sram
+
+
+
+
+
diff --git a/src/DeviceFactory.cc b/src/DeviceFactory.cc
index dd02d7b832..d97ea033cd 100644
--- a/src/DeviceFactory.cc
+++ b/src/DeviceFactory.cc
@@ -78,6 +78,7 @@
#include "DebugDevice.hh"
#include "V9990.hh"
#include "Video9000.hh"
+#include "RomAscii16X.hh"
#include "ADVram.hh"
#include "NowindInterface.hh"
#include "MSXMirrorDevice.hh"
@@ -307,6 +308,8 @@ std::unique_ptr DeviceFactory::create(const DeviceConfig& conf)
result = make_unique(conf);
} else if (type == "Carnivore2") {
result = make_unique(conf);
+ } else if (type == "ASCII16-X") {
+ result = make_unique(conf);
} else if (type == "YamahaSKW01") {
result = make_unique(conf);
} else if (type == one_of("T7775", "T7937", "T9763", "T9769")) {
diff --git a/src/memory/AmdFlash.hh b/src/memory/AmdFlash.hh
index 429ad5a9c5..4522fd076c 100644
--- a/src/memory/AmdFlash.hh
+++ b/src/memory/AmdFlash.hh
@@ -258,7 +258,7 @@ private:
[[nodiscard]] bool isSectorWritable(size_t sector) const;
public:
- static constexpr unsigned MAX_CMD_SIZE = 5 + 32; // longest command is BufferProgram
+ static constexpr unsigned MAX_CMD_SIZE = 5 + 256; // longest command is BufferProgram
private:
MSXMotherBoard& motherBoard;
@@ -314,6 +314,19 @@ namespace AmdFlashChip
.primaryAlgorithm{{1, 3}, 0, 0, 2, 4, 1, 4, 0, 0, 1, {0xB5, 0xC5}, 0x02, 1},
},
}};
+
+ // Infineon / Cypress / Spansion S29GL064S70TFI040
+ static constexpr ValidatedChip S29GL064S70TFI040 = {{
+ .autoSelect{.manufacturer = AMD, .device{0x10, 0x00}, .extraCode = 0xFF0A, .readMask = 0x0F},
+ .geometry{DeviceInterface::x8x16, {{8, 0x2000}, {127, 0x10000}}},
+ .program{.bufferCommand = true, .pageSize = 256},
+ .cfi{
+ .command = true, .withAutoSelect = true, .exitCommand = true, .commandMask = 0xFF, .readMask = 0x7F,
+ .systemInterface{{0x27, 0x36, 0x00, 0x00}, {256, 256, 512, 65536}, {8, 8, 2, 1}},
+ .primaryAlgorithm{{1, 3}, 0, 8, 2, 1, 0, 8, 0, 0, 2, {0xB5, 0xC5}, 0x02, 1},
+ },
+ .misc {.statusCommand = true, .continuityCommand = true},
+ }};
}
} // namespace openmsx
diff --git a/src/memory/RomAscii16X.cc b/src/memory/RomAscii16X.cc
new file mode 100644
index 0000000000..865154ca6f
--- /dev/null
+++ b/src/memory/RomAscii16X.cc
@@ -0,0 +1,120 @@
+// ASCII-X 16kB cartridges
+//
+// Banks:
+// first 16kB: 0x4000 - 0x7FFF and 0xC000 - 0xFFFF
+// second 16kB: 0x8000 - 0xBFFF and 0x0000 - 0x3FFF
+//
+// Address to change banks:
+// first 16kB: 0x6000 - 0x6FFF, 0xA000 - 0xAFFF, 0xE000 - 0xEFFF, 0x2000 - 0x2FFF
+// second 16kB: 0x7000 - 0x7FFF, 0xB000 - 0xBFFF, 0xF000 - 0xFFFF, 0x3000 - 0x3FFF
+//
+// 12-bit bank number is composed by address bits 8-11 and the data bits.
+//
+// Backed by FlashROM.
+
+#include "RomAscii16X.hh"
+#include "narrow.hh"
+#include "outer.hh"
+#include "serialize.hh"
+
+namespace openmsx {
+
+RomAscii16X::RomAscii16X(const DeviceConfig& config)
+ : RomAscii16X(config, Rom(std::string(config.getAttributeValue("id")), "ASCII16-X", config))
+{
+}
+
+RomAscii16X::RomAscii16X(const DeviceConfig& config, Rom&& rom_)
+ : MSXRom(config, std::move(rom_))
+ , debuggable(getMotherBoard(), getName())
+ , flash(rom, AmdFlashChip::S29GL064S70TFI040, {}, config)
+{
+}
+
+void RomAscii16X::reset(EmuTime::param /* time */)
+{
+ ranges::iota(bankRegs, word(0));
+
+ flash.reset();
+
+ invalidateDeviceRCache(); // flush all to be sure
+}
+
+unsigned RomAscii16X::getFlashAddr(word addr) const
+{
+ word bank = bankRegs[((addr >> 14) & 1) ^ 1];
+ return (bank << 14) | (addr & 0x3FFF);
+}
+
+byte RomAscii16X::readMem(word addr, EmuTime::param /* time */)
+{
+ return flash.read(getFlashAddr(addr));
+}
+
+byte RomAscii16X::peekMem(word addr, EmuTime::param /* time */) const
+{
+ return flash.peek(getFlashAddr(addr));
+}
+
+const byte* RomAscii16X::getReadCacheLine(word addr) const
+{
+ return flash.getReadCacheLine(getFlashAddr(addr));
+}
+
+void RomAscii16X::writeMem(word addr, byte value, EmuTime::param /* time */)
+{
+ flash.write(getFlashAddr(addr), value);
+
+ if ((addr & 0x3FFF) >= 0x2000) {
+ const word index = (addr >> 12) & 1;
+ bankRegs[index] = (addr & 0x0F00) | value;
+ invalidateDeviceRCache(0x4000 ^ (index << 14), 0x4000);
+ invalidateDeviceRCache(0xC000 ^ (index << 14), 0x4000);
+ }
+}
+
+byte* RomAscii16X::getWriteCacheLine(word /* addr */)
+{
+ return nullptr; // not cacheable
+}
+
+RomAscii16X::Debuggable::Debuggable(MSXMotherBoard& motherBoard_,
+ const std::string& name_)
+ : SimpleDebuggable(motherBoard_, name_ + " regs", "ASCII16-X bank registers", 4)
+{
+}
+
+byte RomAscii16X::Debuggable::read(unsigned address)
+{
+ auto& outer = OUTER(RomAscii16X, debuggable);
+ word bank = outer.bankRegs[(address >> 1) & 1];
+ return narrow(address & 1 ? bank >> 8 : bank & 0xFF);
+}
+
+void RomAscii16X::Debuggable::write(unsigned address, byte value,
+ EmuTime::param /* time */)
+{
+ auto& outer = OUTER(RomAscii16X, debuggable);
+ word& bank = outer.bankRegs[(address >> 1) & 1];
+ if (address & 1) {
+ bank = (bank & 0x00FF) | narrow((value << 8) & 0x0F00);
+ } else {
+ bank = (bank & 0x0F00) | value;
+ }
+
+ outer.invalidateDeviceRCache();
+}
+
+template
+void RomAscii16X::serialize(Archive& ar, unsigned /*version*/)
+{
+ // skip MSXRom base class
+ ar.template serializeBase(*this);
+
+ ar.serialize("flash", flash,
+ "bankRegs", bankRegs);
+}
+INSTANTIATE_SERIALIZE_METHODS(RomAscii16X);
+REGISTER_MSXDEVICE(RomAscii16X, "RomAscii16X");
+
+} // namespace openmsx
diff --git a/src/memory/RomAscii16X.hh b/src/memory/RomAscii16X.hh
new file mode 100644
index 0000000000..84be941840
--- /dev/null
+++ b/src/memory/RomAscii16X.hh
@@ -0,0 +1,43 @@
+#ifndef ROMASCII16X_HH
+#define ROMASCII16X_HH
+
+#include "MSXRom.hh"
+#include "AmdFlash.hh"
+#include "SimpleDebuggable.hh"
+#include
+
+namespace openmsx {
+
+class RomAscii16X final : public MSXRom
+{
+public:
+ explicit RomAscii16X(const DeviceConfig& config);
+ RomAscii16X(const DeviceConfig& config, Rom&& rom);
+
+ void reset(EmuTime::param time) override;
+ [[nodiscard]] byte peekMem(word address, EmuTime::param time) const override;
+ [[nodiscard]] byte readMem(word address, EmuTime::param time) override;
+ [[nodiscard]] const byte* getReadCacheLine(word address) const override;
+ void writeMem(word address, byte value, EmuTime::param time) override;
+ [[nodiscard]] byte* getWriteCacheLine(word address) override;
+
+ template
+ void serialize(Archive& ar, unsigned version);
+
+private:
+ [[nodiscard]] unsigned getFlashAddr(word addr) const;
+
+ struct Debuggable final : SimpleDebuggable {
+ Debuggable(MSXMotherBoard& motherBoard, const std::string& name);
+ [[nodiscard]] byte read(unsigned address) override;
+ void write(unsigned address, byte value, EmuTime::param time) override;
+ } debuggable;
+
+ AmdFlash flash;
+
+ std::array bankRegs = {0, 0};
+};
+
+} // namespace openmsx
+
+#endif
diff --git a/src/memory/RomFactory.cc b/src/memory/RomFactory.cc
index 0e750927a2..c518809cde 100644
--- a/src/memory/RomFactory.cc
+++ b/src/memory/RomFactory.cc
@@ -13,6 +13,7 @@
#include "RomAscii8kB.hh"
#include "RomAscii8_8.hh"
#include "RomAscii16kB.hh"
+#include "RomAscii16X.hh"
#include "RomMSXWrite.hh"
#include "RomPadial8kB.hh"
#include "RomPadial16kB.hh"
@@ -88,6 +89,7 @@ using enum RomType;
if (const size_t signatureOffset = 16, signatureSize = 8; size >= (signatureOffset + signatureSize)) {
auto signature = std::string_view(std::bit_cast(data.data()) + signatureOffset, signatureSize);
+ if (signature == std::string_view("ASCII16X")) return ASCII16X;
if (signature == std::string_view("ROM_NEO8")) return NEO8;
if (signature == std::string_view("ROM_NE16")) return NEO16;
}
@@ -267,6 +269,9 @@ std::unique_ptr create(const DeviceConfig& config)
case ASCII16:
result = make_unique(config, std::move(rom));
break;
+ case ASCII16X:
+ result = make_unique(config, std::move(rom));
+ break;
case MSXWRITE:
result = make_unique(config, std::move(rom));
break;
diff --git a/src/memory/RomInfo.cc b/src/memory/RomInfo.cc
index 799da34735..50bc0b6520 100644
--- a/src/memory/RomInfo.cc
+++ b/src/memory/RomInfo.cc
@@ -26,6 +26,7 @@ static constexpr auto romTypeInfoArray = [] {
r[KBDMASTER] = {0x4000, "KeyboardMaster", "Konami Keyboard Master with VLM5030"}; // officially plain 16K
r[ASCII8] = {0x2000, "ASCII8", "ASCII 8kB"};
r[ASCII16] = {0x4000, "ASCII16", "ASCII 16kB"};
+ r[ASCII16X] = {0x4000, "ASCII16-X", "ASCII-X 16kB"};
r[R_TYPE] = {0x4000, "R-Type", "R-Type"};
r[CROSS_BLAIM] = {0x4000, "CrossBlaim", "Cross Blaim"};
r[HARRY_FOX] = {0x4000, "HarryFox", "Harry Fox"};
diff --git a/src/memory/RomTypes.hh b/src/memory/RomTypes.hh
index e12ceff66f..89b66a0f3b 100644
--- a/src/memory/RomTypes.hh
+++ b/src/memory/RomTypes.hh
@@ -14,6 +14,7 @@ enum class RomType {
ASCII16,
ASCII16_2,
ASCII16_8,
+ ASCII16X,
COLECOMEGACART,
CROSS_BLAIM,
DOOLY,
diff --git a/src/meson.build b/src/meson.build
index 69ebd305fa..103cd8030c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -267,6 +267,7 @@ sources = files(
'memory/RomArc.cc',
'memory/RomAscii16_2.cc',
'memory/RomAscii16kB.cc',
+ 'memory/RomAscii16X.cc',
'memory/RomAscii8_8.cc',
'memory/RomAscii8kB.cc',
'memory/RomBlocks.cc',