OXYGENHUB
◆ main openmsx / src / memory / MSXMegaRam.cc
4.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/*
2
 * Adriano Camargo Rodrigues da Cunha wrote:
3
 *
4
 *  Any address inside a 8k page can change the page.  In other
5
 *  words:
6
 *
7
 *  for 4000h-5FFFh, mapping addresses are 4000h-5FFFh
8
 *  for 6000h-7FFFh, mapping addresses are 6000h-7FFFh
9
 *  for 8000h-9FFFh, mapping addresses are 8000h-9FFFh
10
 *  for A000h-BFFFh, mapping addresses are A000h-BFFFh
11
 *
12
 *  If you do an IN A,(8Eh) (the value of A register is unknown and
13
 *  never used) you can write on MegaRAM pages, but you can't map
14
 *  pages. If you do an OUT (8Eh),A (the value of A register doesn't
15
 *  matter) you can't write to MegaRAM pages, but you can map them.
16
 *
17
 *  Another thing: the MegaRAMs of Ademir Carchano have a mirror
18
 *  effect: if you map the page 0 of MegaRAM slot, you'll be
19
 *  accessing the same area of 8000h-BFFFh of this slot; if you map
20
 *  the page 3 of MegaRAM slot, you'll be accessing the same area of
21
 *  4000h-7FFFh of this slot. I don't know any software that makes
22
 *  use of this feature, except UZIX for MSX1.
23
 */
24
25
#include "MSXMegaRam.hh"
26
#include "DeviceConfig.hh"
27
#include "MSXException.hh"
28
#include "Rom.hh"
29
#include "narrow.hh"
30
#include "serialize.hh"
31
#include "xrange.hh"
32
#include <bit>
33
#include <memory>
34
35
namespace openmsx {
36
37
[[nodiscard]] static unsigned getNumBlocks(const DeviceConfig& config)
38
{
39
	int size = config.getChildDataAsInt("size", 0); // size in kB
40
	if (const auto* err = [&] -> const char* {
41
		if (size < 0)         return "Cannot be negative.";
42
		if (size > (8 * 256)) return "Cannot be larger than 2048.";
43
		if ((size % 8) != 0)  return "Must be a multiple of 8.";
44
		return nullptr;
45
	}()) {
46
		throw MSXException("Invalid MegaRam size: ", size, ". ", err);
47
	}
48
	return size / 8;
49
}
50
51
MSXMegaRam::MSXMegaRam(DeviceConfig& config)
52
	: MSXDevice(config)
53
	, numBlocks(getNumBlocks(config)) // number of 8kB blocks
54
	, ram(config, getName() + " RAM", "Mega-RAM", numBlocks * 0x2000)
55
	, rom(config.findChild("rom")
56
	      ? std::make_unique<Rom>(getName() + " ROM", "Mega-RAM DiskROM", config)
57
	      : nullptr)
58
	, romBlockDebug(*this, bank, 0x0000, 0x10000, 13, 0, 3)
59
	, maskBlocks(narrow<byte>(std::bit_ceil(numBlocks) - 1))
60
{
61
	powerUp(EmuTime::dummy());
62
}
63
64
MSXMegaRam::~MSXMegaRam() = default;
65
66
void MSXMegaRam::powerUp(EmuTime time)
67
{
68
	for (auto i : xrange(byte(4))) {
69
		setBank(i, 0);
70
	}
71
	writeMode = false;
72
	ram.clear();
73
	reset(time);
74
}
75
76
void MSXMegaRam::reset(EmuTime /*time*/)
77
{
78
	// selected banks nor writeMode does change after reset
79
	romMode = rom != nullptr; // select rom mode if there is a rom
80
}
81
82
byte MSXMegaRam::readMem(uint16_t address, EmuTime /*time*/)
83
{
84
	return *MSXMegaRam::getReadCacheLine(address);
85
}
86
87
const byte* MSXMegaRam::getReadCacheLine(uint16_t address) const
88
{
89
	if (romMode) {
90
		if (address >= 0x4000 && address <= 0xBFFF) {
91
			return &(*rom)[address - 0x4000];
92
		}
93
		return unmappedRead.data();
94
	}
95
	unsigned block = bank[(address & 0x7FFF) / 0x2000];
96
	return (block < numBlocks)
97
	     ? &ram[(block * 0x2000) + (address & 0x1FFF)]
98
	     : unmappedRead.data();
99
}
100
101
void MSXMegaRam::writeMem(uint16_t address, byte value, EmuTime /*time*/)
102
{
103
	if (byte* tmp = getWriteCacheLine(address)) {
104
		*tmp = value;
105
	} else {
106
		assert(!romMode && !writeMode);
107
		setBank(narrow<byte>((address & 0x7FFF) / 0x2000), value);
108
	}
109
}
110
111
byte* MSXMegaRam::getWriteCacheLine(uint16_t address)
112
{
113
	if (romMode) return unmappedWrite.data();
114
	if (writeMode) {
115
		unsigned block = bank[(address & 0x7FFF) / 0x2000];
116
		return (block < numBlocks)
117
		     ? &ram[(block * 0x2000) + (address & 0x1FFF)]
118
		     : unmappedWrite.data();
119
	} else {
120
		return nullptr;
121
	}
122
}
123
124
byte MSXMegaRam::readIO(uint16_t port, EmuTime /*time*/)
125
{
126
	switch (port & 1) {
127
		case 0:
128
			// enable writing
129
			writeMode = true;
130
			romMode = false;
131
			break;
132
		case 1:
133
			if (rom) romMode = true;
134
			break;
135
	}
136
	invalidateDeviceRWCache();
137
	return 0xFF; // return value doesn't matter
138
}
139
140
byte MSXMegaRam::peekIO(uint16_t /*port*/, EmuTime /*time*/) const
141
{
142
	return 0xFF;
143
}
144
145
void MSXMegaRam::writeIO(uint16_t port, byte /*value*/, EmuTime /*time*/)
146
{
147
	switch (port & 1) {
148
		case 0:
149
			// enable switching
150
			writeMode = false;
151
			romMode = false;
152
			break;
153
		case 1:
154
			if (rom) romMode = true;
155
			break;
156
	}
157
	invalidateDeviceRWCache();
158
}
159
160
void MSXMegaRam::setBank(byte page, byte block)
161
{
162
	bank[page] = block & maskBlocks;
163
	uint16_t adr = page * 0x2000;
164
	invalidateDeviceRWCache(adr + 0x0000, 0x2000);
165
	invalidateDeviceRWCache(adr + 0x8000, 0x2000);
166
}
167
168
template<typename Archive>
169
void MSXMegaRam::serialize(Archive& ar, unsigned /*version*/)
170
{
171
	ar.template serializeBase<MSXDevice>(*this);
172
	ar.serialize("ram",       ram,
173
	             "bank",      bank,
174
	             "writeMode", writeMode,
175
	             "romMode",   romMode);
176
}
177
INSTANTIATE_SERIALIZE_METHODS(MSXMegaRam);
178
REGISTER_MSXDEVICE(MSXMegaRam, "MegaRAM");
179
180
} // namespace openmsx