openMSX
MSXSCCPlusCart.cc
Go to the documentation of this file.
1// Note: this device is actually called SCC-I. But this would take a lot of
2// renaming, which isn't worth it right now. TODO rename this :)
3
4#include "MSXSCCPlusCart.hh"
5#include "File.hh"
6#include "FileContext.hh"
7#include "FileException.hh"
8#include "XMLElement.hh"
9#include "CacheLine.hh"
10#include "enumerate.hh"
11#include "narrow.hh"
12#include "ranges.hh"
13#include "serialize.hh"
14#include <bit>
15
16namespace openmsx {
17
18static auto getMapperConfig(const DeviceConfig& config)
19{
20 MSXSCCPlusCart::MapperConfig result;
21
22 std::string_view subtype = config.getChildData("subtype", "expanded");
23 if (subtype == "Snatcher") {
24 // blocks 0-7 in use, 8-15 unmapped, others are mirrored
25 result.numBlocks = 8; // 64kB
26 result.registerMask = 0b0000'1111; // ignore upper 4 bits
27 result.registerOffset = 0; // start at block 0
28 } else if (subtype == "SD-Snatcher") {
29 // blocks 0-7 unmapped, 8-15 in use, others are mirrored
30 result.numBlocks = 8; // 64kB
31 result.registerMask = 0b0000'1111; // ignore upper 4 bits
32 result.registerOffset = 8; // start at block 8
33 } else if (subtype == "mirrored") {
34 // blocks 0-7 in use, others are mirrored
35 result.numBlocks = 8; // 64kB
36 result.registerMask = 0b0000'0111; // ignore upper 5 bits
37 result.registerOffset = 0; // start at block 0
38 } else if (subtype == "Popolon") {
39 // this subtype supports configurable size (128, 256, 512, 1024, 2048)
40 unsigned ramSize = config.getChildDataAsInt("size", 2048);
41 if (!std::has_single_bit(ramSize)) {
42 throw MSXException(
43 "Popolon type Sound Cartridge must have a power-of-2 RAM size: ",
44 ramSize);
45 }
46 if (ramSize < 128 || ramSize > 2048) {
47 throw MSXException(
48 "Popolon type Sound Cartridge must have a size between 128kB and 2048kB: ",
49 ramSize);
50 }
51 result.numBlocks = ramSize / 8;
52 result.registerMask = narrow<byte>(result.numBlocks - 1); // this assumes mirroring, correct?
53 result.registerOffset = 0; // start at block 0
54 } else {
55 // subtype "expanded", and all others
56 // blocks 0-15 in use, others are mirrored
57 result.numBlocks = 16; // 128kB
58 result.registerMask = 0b0000'1111; // ignore upper 4 bits
59 result.registerOffset = 0; // start at block 0
60 }
61 return result;
62}
63
64
66 : MSXDevice(config)
67 , mapperConfig(getMapperConfig(config))
68 , ram(config, getName() + " RAM", "SCC+ RAM", size_t(mapperConfig.numBlocks) * 0x2000)
69 , scc(getName(), config, getCurrentTime(), SCC::SCC_Compatible)
70 , romBlockDebug(*this, mapper, 0x4000, 0x8000, 13)
71{
72 if (const auto* fileElem = config.findChild("filename")) {
73 // read the rom file
74 const auto& filename = fileElem->getData();
75 try {
76 File file(config.getFileContext().resolve(filename));
77 auto size = std::min(file.getSize(), ram.size());
78 file.read(subspan(ram, 0, size));
79 } catch (FileException&) {
80 throw MSXException("Error reading file: ", filename);
81 }
82 }
83 // make valgrind happy
84 ranges::fill(isRamSegment, true);
85 ranges::fill(mapper, 0);
86
88}
89
90void MSXSCCPlusCart::powerUp(EmuTime::param time)
91{
92 scc.powerUp(time);
93 ram.clear();
94 reset(time);
95}
96
97void MSXSCCPlusCart::reset(EmuTime::param time)
98{
99 setModeRegister(0);
100 setMapper(0, 0);
101 setMapper(1, 1);
102 setMapper(2, 2);
103 setMapper(3, 3);
104 scc.reset(time);
105}
106
107
108byte MSXSCCPlusCart::readMem(word addr, EmuTime::param time)
109{
110 if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) ||
111 ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) {
112 return scc.readMem(narrow_cast<uint8_t>(addr & 0xFF), time);
113 } else {
114 return MSXSCCPlusCart::peekMem(addr, time);
115 }
116}
117
118byte MSXSCCPlusCart::peekMem(word addr, EmuTime::param time) const
119{
120 // modeRegister can not be read!
121 if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) ||
122 ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) {
123 // SCC visible in 0x9800 - 0x9FFF
124 // SCC+ visible in 0xB800 - 0xBFFF
125 return scc.peekMem(narrow_cast<uint8_t>(addr & 0xFF), time);
126 } else if ((0x4000 <= addr) && (addr < 0xC000)) {
127 // SCC(+) enabled/disabled but not requested so memory stuff
128 return internalMemoryBank[(addr >> 13) - 2][addr & 0x1FFF];
129 } else {
130 // outside memory range
131 return 0xFF;
132 }
133}
134
136{
137 if (((enable == EN_SCC) && (0x9800 <= start) && (start < 0xA000)) ||
138 ((enable == EN_SCCPLUS) && (0xB800 <= start) && (start < 0xC000))) {
139 // SCC visible in 0x9800 - 0x9FFF
140 // SCC+ visible in 0xB800 - 0xBFFF
141 return nullptr;
142 } else if ((0x4000 <= start) && (start < 0xC000)) {
143 // SCC(+) enabled/disabled but not requested so memory stuff
144 return &internalMemoryBank[(start >> 13) - 2][start & 0x1FFF];
145 } else {
146 // outside memory range
147 return unmappedRead.data();
148 }
149}
150
151
152void MSXSCCPlusCart::writeMem(word address, byte value, EmuTime::param time)
153{
154 if ((address < 0x4000) || (0xC000 <= address)) {
155 // outside memory range
156 return;
157 }
158
159 // Mode register is mapped upon 0xBFFE and 0xBFFF
160 if ((address | 0x0001) == 0xBFFF) {
161 setModeRegister(value);
162 return;
163 }
164
165 // Write to RAM
166 int region = (address >> 13) - 2;
167 if (isRamSegment[region]) {
168 // According to Sean Young
169 // when the regions are in RAM mode you can read from
170 // the SCC(+) but not write to them
171 // => we assume a write to the memory but maybe
172 // they are just discarded
173 // TODO check this out => ask Sean...
174 if (isMapped[region]) {
175 internalMemoryBank[region][address & 0x1FFF] = value;
176 }
177 return;
178 }
179
180 /* Write to bank-switching registers
181 * The address to change banks:
182 * bank 1: 0x5000 - 0x57FF (0x5000 used)
183 * bank 2: 0x7000 - 0x77FF (0x7000 used)
184 * bank 3: 0x9000 - 0x97FF (0x9000 used)
185 * bank 4: 0xB000 - 0xB7FF (0xB000 used)
186 */
187 if ((address & 0x1800) == 0x1000) {
188 setMapper(region, value);
189 return;
190 }
191
192 // call writeMemInterface of SCC if needed
193 switch (enable) {
194 case EN_NONE:
195 // do nothing
196 break;
197 case EN_SCC:
198 if ((0x9800 <= address) && (address < 0xA000)) {
199 scc.writeMem(narrow_cast<uint8_t>(address & 0xFF), value, time);
200 }
201 break;
202 case EN_SCCPLUS:
203 if ((0xB800 <= address) && (address < 0xC000)) {
204 scc.writeMem(narrow_cast<uint8_t>(address & 0xFF), value, time);
205 }
206 break;
207 }
208}
209
211{
212 if ((0x4000 <= start) && (start < 0xC000)) {
213 if (start == (0xBFFF & CacheLine::HIGH)) {
214 return nullptr;
215 }
216 int region = (start >> 13) - 2;
217 if (isRamSegment[region] && isMapped[region]) {
218 return &internalMemoryBank[region][start & 0x1FFF];
219 }
220 return nullptr;
221 }
222 return unmappedWrite.data();
223}
224
225
226void MSXSCCPlusCart::setMapper(int region, byte value)
227{
228 mapper[region] = value;
229
230 value &= mapperConfig.registerMask;
231 value -= mapperConfig.registerOffset;
232 byte* block = [&] {
233 if (value < mapperConfig.numBlocks) {
234 isMapped[region] = true;
235 return &ram[0x2000 * value];
236 } else {
237 isMapped[region] = false;
238 return unmappedRead.data();
239 }
240 }();
241
242 checkEnable(); // invalidateDeviceRWCache() done below
243 internalMemoryBank[region] = block;
244 invalidateDeviceRWCache(0x4000 + region * 0x2000, 0x2000);
245}
246
247void MSXSCCPlusCart::setModeRegister(byte value)
248{
249 modeRegister = value;
250 checkEnable(); // invalidateDeviceRWCache() done below
251
252 if (modeRegister & 0x20) {
254 } else {
256 }
257
258 if (modeRegister & 0x10) {
259 isRamSegment[0] = true;
260 isRamSegment[1] = true;
261 isRamSegment[2] = true;
262 isRamSegment[3] = true;
263 } else {
264 isRamSegment[0] = (modeRegister & 0x01) == 0x01;
265 isRamSegment[1] = (modeRegister & 0x02) == 0x02;
266 isRamSegment[2] = (modeRegister & 0x24) == 0x24; // extra requirement: SCC+ mode
267 isRamSegment[3] = false;
268 }
269 invalidateDeviceRWCache(0x4000, 0x8000);
270}
271
272void MSXSCCPlusCart::checkEnable()
273{
274 if ((modeRegister & 0x20) && (mapper[3] & 0x80)) {
275 enable = EN_SCCPLUS;
276 } else if ((!(modeRegister & 0x20)) && ((mapper[2] & 0x3F) == 0x3F)) {
277 enable = EN_SCC;
278 } else {
279 enable = EN_NONE;
280 }
281}
282
283
284template<typename Archive>
285void MSXSCCPlusCart::serialize(Archive& ar, unsigned /*version*/)
286{
287 //ar.serialize_blob("ram", std::span{ram}); // TODO error with clang-15/libc++
288 ar.serialize_blob("ram", std::span{ram.begin(), ram.end()});
289 ar.serialize("scc", scc,
290 "mapper", mapper,
291 "mode", modeRegister);
292
293 if constexpr (Archive::IS_LOADER) {
294 // recalculate: isMapped[4], internalMemoryBank[4]
295 for (auto [i, m] : enumerate(mapper)) {
296 setMapper(int(i), m);
297 }
298 // recalculate: enable, isRamSegment[4]
299 setModeRegister(modeRegister);
300 }
301}
304
305} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:354
const FileContext & getFileContext() const
const XMLElement * findChild(std::string_view name) const
std::string resolve(std::string_view filename) const
void read(std::span< uint8_t > buffer)
Read from file.
Definition File.cc:92
size_t getSize()
Returns the size of this file.
Definition File.cc:112
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition MSXDevice.hh:36
static std::array< byte, 0x10000 > unmappedRead
Definition MSXDevice.hh:304
static std::array< byte, 0x10000 > unmappedWrite
Definition MSXDevice.hh:305
void invalidateDeviceRWCache()
Calls MSXCPUInterface::invalidateXXCache() for the specific (part of) the slot that this device is lo...
Definition MSXDevice.hh:212
EmuTime::param getCurrentTime() const
Definition MSXDevice.cc:125
const byte * getReadCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
MSXSCCPlusCart(const DeviceConfig &config)
void reset(EmuTime::param time) override
This method is called on reset.
void serialize(Archive &ar, unsigned version)
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.
byte peekMem(word address, EmuTime::param time) const override
Read a byte from a given memory location.
byte readMem(word address, EmuTime::param time) override
Read a byte from a location at a certain time from this device.
byte * getWriteCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
auto size() const
Definition Ram.hh:44
auto end()
Definition Ram.hh:49
auto begin()
Definition Ram.hh:47
void clear(byte c=0xff)
Definition Ram.cc:35
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
uint8_t peekMem(uint8_t address, EmuTime::param time) const
Definition SCC.cc:206
void writeMem(uint8_t address, uint8_t value, EmuTime::param time)
Definition SCC.cc:285
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition enumerate.hh:28
constexpr unsigned HIGH
Definition CacheLine.hh:10
This file implemented 3 utility functions:
Definition Autofire.cc:9
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:471
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)