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