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