openMSX
MusicalMemoryMapper.cc
Go to the documentation of this file.
2#include "enumerate.hh"
3#include "narrow.hh"
4#include "serialize.hh"
5#include "xrange.hh"
6
7namespace openmsx {
8
9static constexpr byte MEM_ACCESS_ENABLED = 1 << 7;
10static constexpr byte SOUND_PORT_ENABLED = 1 << 6;
11static constexpr byte PORT_ACCESS_DISABLED = 1 << 5;
12static constexpr byte UNUSED = 1 << 4;
13static constexpr byte WRITE_PROTECT = 0x0F;
14
16 : MSXMemoryMapperBase(config)
17 , sn76489(config)
18{
19}
20
21void MusicalMemoryMapper::reset(EmuTime::param time)
22{
23 controlReg = 0x00;
24
25 // MMM inits the page registers to 3, 2, 1, 0 instead of zeroes, so we
26 // don't call the superclass implementation.
27 for (auto [page, reg] : enumerate(registers)) {
28 reg = byte(3 - page);
29 }
30
31 // Note: The actual SN76489AN chip does not have a reset pin. I assume
32 // MMM either powers off the chip on reset or suppresses its audio
33 // output. We instead keep the chip in a silent state until it is
34 // activated.
35 sn76489.reset(time);
36}
37
38byte MusicalMemoryMapper::readIO(word port, EmuTime::param time)
39{
40 if (controlReg & PORT_ACCESS_DISABLED) {
41 return 0xFF;
42 } else {
43 return MSXMemoryMapperBase::readIO(port, time);
44 }
45}
46
47byte MusicalMemoryMapper::peekIO(word port, EmuTime::param time) const
48{
49 if (controlReg & PORT_ACCESS_DISABLED) {
50 return 0xFF;
51 } else {
52 return MSXMemoryMapperBase::peekIO(port, time);
53 }
54}
55
56void MusicalMemoryMapper::writeIO(word port, byte value, EmuTime::param time)
57{
58 if ((port & 0xFC) == 0xFC) {
59 // Mapper port.
60 if (!(controlReg & PORT_ACCESS_DISABLED)) {
61 MSXMemoryMapperBase::writeIOImpl(port, value, time);
62 invalidateDeviceRWCache(0x4000 * (port & 0x03), 0x4000);
63 }
64 } else if (port & 1) {
65 // Sound chip.
66 if (controlReg & SOUND_PORT_ENABLED) {
67 sn76489.write(value, time);
68 }
69 } else {
70 // Control port.
71 // Only bit 7 of the control register is accessible through this port.
72 // The documentation explicitly states that the sound chip port is still
73 // accessible when port access is disabled, but this control port must
74 // also remain accessible or the ROM2MMM loader won't work.
75 updateControlReg((value & MEM_ACCESS_ENABLED) |
76 (controlReg & ~MEM_ACCESS_ENABLED));
77 }
78}
79
80bool MusicalMemoryMapper::registerAccessAt(word address) const
81{
82 if (controlReg & MEM_ACCESS_ENABLED) {
83 if (controlReg & PORT_ACCESS_DISABLED) {
84 // Note: I'm assuming the mirroring is still active when the
85 // address range is restricted, but this is unclear in
86 // the documentation and not tested on real hardware.
87 if (controlReg & 0x08) {
88 return 0x4000 <= address && address < 0x8000;
89 } else {
90 return 0x8000 <= address && address < 0xC000;
91 }
92 } else {
93 return 0x4000 <= address && address < 0xC000;
94 }
95 } else {
96 return false;
97 }
98}
99
100int MusicalMemoryMapper::readReg(word address) const
101{
102 if (registerAccessAt(address)) {
103 switch (address & 0xFF) {
104 case 0x3C:
105 return controlReg;
106 case 0xFC:
107 case 0xFD:
108 case 0xFE:
109 case 0xFF:
110 return 0xC0 | getSelectedSegment(address & 0x03);
111 }
112 }
113 return -1;
114}
115
116void MusicalMemoryMapper::updateControlReg(byte value)
117{
118 // Note: Bit 4 is unused, but I don't know its value when read.
119 // For now, I'll force it to 0.
120 value &= ~UNUSED;
121
122 byte change = value ^ controlReg;
123 if (change) {
124 // Pages for which write protect is toggled must be invalidated.
125 byte invalidate = change & WRITE_PROTECT;
126
127 // Invalidate pages for which register access changes.
128 byte regAccessBefore = 0;
129 for (auto page : xrange(4)) {
130 regAccessBefore |= byte(registerAccessAt(narrow_cast<word>(0x4000 * page)) << page);
131 }
132 controlReg = value;
133 byte regAccessAfter = 0;
134 for (auto page : xrange(4)) {
135 regAccessAfter |= byte(registerAccessAt(narrow_cast<word>(0x4000 * page)) << page);
136 }
137 invalidate |= regAccessBefore ^ regAccessAfter;
138
139 for (auto page : xrange(4)) {
140 if ((invalidate >> page) & 1) {
141 invalidateDeviceRWCache(0x4000 * page, 0x4000);
142 }
143 }
144 }
145}
146
147byte MusicalMemoryMapper::peekMem(word address, EmuTime::param time) const
148{
149 int reg = readReg(address);
150 return reg >= 0 ? narrow<byte>(reg) : MSXMemoryMapperBase::peekMem(address, time);
151}
152
153byte MusicalMemoryMapper::readMem(word address, EmuTime::param time)
154{
155 int reg = readReg(address);
156 return reg >= 0 ? narrow<byte>(reg) : MSXMemoryMapperBase::readMem(address, time);
157}
158
159void MusicalMemoryMapper::writeMem(word address, byte value, EmuTime::param time)
160{
161 if (registerAccessAt(address)) {
162 switch (address & 0xFF) {
163 case 0x3C:
164 // When port access is disabled, memory access is automatically
165 // enabled, according to the manual.
166 if (value & PORT_ACCESS_DISABLED) {
167 value |= MEM_ACCESS_ENABLED;
168 }
169 updateControlReg(value);
170 return;
171 case 0xFC:
172 case 0xFD:
173 case 0xFE:
174 case 0xFF:
175 MSXMemoryMapperBase::writeIOImpl(address & 0xFF, value, time);
176 invalidateDeviceRWCache(0x4000 * (address & 0x03), 0x4000);
177 return;
178 }
179 }
180 if (!writeProtected(address)) {
181 MSXMemoryMapperBase::writeMem(address, value, time);
182 }
183}
184
186{
187 if (controlReg & MEM_ACCESS_ENABLED) {
188 if (0x4000 <= start && start < 0xC000) {
189 return nullptr;
190 }
191 }
193}
194
196{
197 if (controlReg & MEM_ACCESS_ENABLED) {
198 if (0x4000 <= start && start < 0xC000) {
199 return nullptr;
200 }
201 }
202 if (writeProtected(start)) {
203 return unmappedWrite.data();
204 } else {
206 }
207}
208
209template<typename Archive>
210void MusicalMemoryMapper::serialize(Archive& ar, unsigned /*version*/)
211{
212 ar.template serializeBase<MSXMemoryMapperBase>(*this);
213 ar.serialize("ctrl", controlReg,
214 "sn76489", sn76489);
215}
218
219} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
static std::array< byte, 0x10000 > unmappedWrite
Definition MSXDevice.hh:307
void invalidateDeviceRWCache()
Calls MSXCPUInterface::invalidateXXCache() for the specific (part of) the slot that this device is lo...
Definition MSXDevice.hh:214
byte readMem(word address, EmuTime::param time) override
Read a byte from a location at a certain time from this device.
void writeIOImpl(word port, byte value, EmuTime::param time)
byte peekMem(word address, EmuTime::param time) const override
Read a byte from a given memory location.
byte * getWriteCacheLine(word start) override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
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 getSelectedSegment(byte page) const override
Returns the currently selected segment for the given page.
const byte * getReadCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Memory mapper which also controls an SN76489AN sound chip.
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
void reset(EmuTime::param time) override
This method is called on reset.
const byte * getReadCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
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.
void serialize(Archive &ar, unsigned version)
void writeIO(word port, byte value, EmuTime::param time) override
Write a byte to a given IO port at a certain time to this device.
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) override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
MusicalMemoryMapper(const DeviceConfig &config)
byte peekMem(word address, EmuTime::param time) const override
Read a byte from a given memory location.
void reset(EmuTime::param time)
Definition SN76489.cc:144
void write(byte value, EmuTime::param time)
Definition SN76489.cc:150
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
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint8_t byte
8 bit unsigned integer
Definition openmsx.hh:26
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
constexpr auto xrange(T e)
Definition xrange.hh:132