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 "ranges.hh"
12#include "serialize.hh"
13#include <bit>
14
15namespace openmsx {
16
17static 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 (!std::has_single_bit(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", size_t(mapperConfig.numBlocks) * 0x2000)
68 , scc(getName(), config, getCurrentTime(), SCC::SCC_Compatible)
69 , romBlockDebug(*this, mapper, 0x4000, 0x8000, 13)
70{
71 if (const auto* fileElem = config.findChild("filename")) {
72 // read the rom file
73 const auto& filename = fileElem->getData();
74 try {
75 File file(config.getFileContext().resolve(filename));
76 auto size = std::min(file.getSize(), ram.size());
77 file.read(subspan(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
89void MSXSCCPlusCart::powerUp(EmuTime::param time)
90{
91 scc.powerUp(time);
92 ram.clear();
93 reset(time);
94}
95
96void MSXSCCPlusCart::reset(EmuTime::param time)
97{
98 setModeRegister(0);
99 setMapper(0, 0);
100 setMapper(1, 1);
101 setMapper(2, 2);
102 setMapper(3, 3);
103 scc.reset(time);
104}
105
106
107byte MSXSCCPlusCart::readMem(word addr, EmuTime::param time)
108{
109 if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) ||
110 ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) {
111 return scc.readMem(addr & 0xFF, time);
112 } else {
113 return MSXSCCPlusCart::peekMem(addr, time);
114 }
115}
116
117byte MSXSCCPlusCart::peekMem(word addr, EmuTime::param time) const
118{
119 // modeRegister can not be read!
120 if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) ||
121 ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) {
122 // SCC visible in 0x9800 - 0x9FFF
123 // SCC+ visible in 0xB800 - 0xBFFF
124 return scc.peekMem(addr & 0xFF, time);
125 } else if ((0x4000 <= addr) && (addr < 0xC000)) {
126 // SCC(+) enabled/disabled but not requested so memory stuff
127 return internalMemoryBank[(addr >> 13) - 2][addr & 0x1FFF];
128 } else {
129 // outside memory range
130 return 0xFF;
131 }
132}
133
135{
136 if (((enable == EN_SCC) && (0x9800 <= start) && (start < 0xA000)) ||
137 ((enable == EN_SCCPLUS) && (0xB800 <= start) && (start < 0xC000))) {
138 // SCC visible in 0x9800 - 0x9FFF
139 // SCC+ visible in 0xB800 - 0xBFFF
140 return nullptr;
141 } else if ((0x4000 <= start) && (start < 0xC000)) {
142 // SCC(+) enabled/disabled but not requested so memory stuff
143 return &internalMemoryBank[(start >> 13) - 2][start & 0x1FFF];
144 } else {
145 // outside memory range
146 return unmappedRead.data();
147 }
148}
149
150
151void MSXSCCPlusCart::writeMem(word address, byte value, EmuTime::param time)
152{
153 if ((address < 0x4000) || (0xC000 <= address)) {
154 // outside memory range
155 return;
156 }
157
158 // Mode register is mapped upon 0xBFFE and 0xBFFF
159 if ((address | 0x0001) == 0xBFFF) {
160 setModeRegister(value);
161 return;
162 }
163
164 // Write to RAM
165 int region = (address >> 13) - 2;
166 if (isRamSegment[region]) {
167 // According to Sean Young
168 // when the regions are in RAM mode you can read from
169 // the SCC(+) but not write to them
170 // => we assume a write to the memory but maybe
171 // they are just discarded
172 // TODO check this out => ask Sean...
173 if (isMapped[region]) {
174 internalMemoryBank[region][address & 0x1FFF] = value;
175 }
176 return;
177 }
178
179 /* Write to bank-switching registers
180 * The address to change banks:
181 * bank 1: 0x5000 - 0x57FF (0x5000 used)
182 * bank 2: 0x7000 - 0x77FF (0x7000 used)
183 * bank 3: 0x9000 - 0x97FF (0x9000 used)
184 * bank 4: 0xB000 - 0xB7FF (0xB000 used)
185 */
186 if ((address & 0x1800) == 0x1000) {
187 setMapper(region, value);
188 return;
189 }
190
191 // call writeMemInterface of SCC if needed
192 switch (enable) {
193 case EN_NONE:
194 // do nothing
195 break;
196 case EN_SCC:
197 if ((0x9800 <= address) && (address < 0xA000)) {
198 scc.writeMem(address & 0xFF, value, time);
199 }
200 break;
201 case EN_SCCPLUS:
202 if ((0xB800 <= address) && (address < 0xC000)) {
203 scc.writeMem(address & 0xFF, value, time);
204 }
205 break;
206 }
207}
208
210{
211 if ((0x4000 <= start) && (start < 0xC000)) {
212 if (start == (0xBFFF & CacheLine::HIGH)) {
213 return nullptr;
214 }
215 int region = (start >> 13) - 2;
216 if (isRamSegment[region] && isMapped[region]) {
217 return &internalMemoryBank[region][start & 0x1FFF];
218 }
219 return nullptr;
220 }
221 return unmappedWrite.data();
222}
223
224
225void MSXSCCPlusCart::setMapper(int region, byte value)
226{
227 mapper[region] = value;
228
229 value &= mapperConfig.registerMask;
230 value -= mapperConfig.registerOffset;
231 byte* block = [&] {
232 if (value < mapperConfig.numBlocks) {
233 isMapped[region] = true;
234 return &ram[0x2000 * value];
235 } else {
236 isMapped[region] = false;
237 return unmappedRead.data();
238 }
239 }();
240
241 checkEnable(); // invalidateDeviceRWCache() done below
242 internalMemoryBank[region] = block;
243 invalidateDeviceRWCache(0x4000 + region * 0x2000, 0x2000);
244}
245
246void MSXSCCPlusCart::setModeRegister(byte value)
247{
248 modeRegister = value;
249 checkEnable(); // invalidateDeviceRWCache() done below
250
251 if (modeRegister & 0x20) {
253 } else {
255 }
256
257 if (modeRegister & 0x10) {
258 isRamSegment[0] = true;
259 isRamSegment[1] = true;
260 isRamSegment[2] = true;
261 isRamSegment[3] = true;
262 } else {
263 isRamSegment[0] = (modeRegister & 0x01) == 0x01;
264 isRamSegment[1] = (modeRegister & 0x02) == 0x02;
265 isRamSegment[2] = (modeRegister & 0x24) == 0x24; // extra requirement: SCC+ mode
266 isRamSegment[3] = false;
267 }
268 invalidateDeviceRWCache(0x4000, 0x8000);
269}
270
271void MSXSCCPlusCart::checkEnable()
272{
273 if ((modeRegister & 0x20) && (mapper[3] & 0x80)) {
274 enable = EN_SCCPLUS;
275 } else if ((!(modeRegister & 0x20)) && ((mapper[2] & 0x3F) == 0x3F)) {
276 enable = EN_SCC;
277 } else {
278 enable = EN_NONE;
279 }
280}
281
282
283template<typename Archive>
284void MSXSCCPlusCart::serialize(Archive& ar, unsigned /*version*/)
285{
286 //ar.serialize_blob("ram", std::span{ram}); // TODO error with clang-15/libc++
287 ar.serialize_blob("ram", std::span{ram.begin(), ram.end()});
288 ar.serialize("scc", scc,
289 "mapper", mapper,
290 "mode", modeRegister);
291
292 if constexpr (Archive::IS_LOADER) {
293 // recalculate: isMapped[4], internalMemoryBank[4]
294 for (auto [i, m] : enumerate(mapper)) {
295 setMapper(int(i), m);
296 }
297 // recalculate: enable, isRamSegment[4]
298 setModeRegister(modeRegister);
299 }
300}
303
304} // namespace openmsx
const FileContext & getFileContext() const
Definition: DeviceConfig.cc:9
const XMLElement * findChild(std::string_view name) const
Definition: DeviceConfig.cc:66
std::string resolve(std::string_view filename) const
Definition: FileContext.cc:79
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:34
static std::array< byte, 0x10000 > unmappedRead
Definition: MSXDevice.hh:302
static std::array< byte, 0x10000 > unmappedWrite
Definition: MSXDevice.hh:303
void invalidateDeviceRWCache()
Calls MSXCPUInterface::invalidateXXCache() for the specific (part of) the slot that this device is lo...
Definition: MSXDevice.hh:210
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 vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:266
constexpr unsigned HIGH
Definition: CacheLine.hh:10
std::string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:730
This file implemented 3 utility functions:
Definition: Autofire.cc:9
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
size_t size(std::string_view utf8)
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition: ranges.hh:446
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1021