openMSX
MusicalMemoryMapper.cc
Go to the documentation of this file.
1 #include "MusicalMemoryMapper.hh"
2 #include "enumerate.hh"
3 #include "serialize.hh"
4 #include "xrange.hh"
5 
6 namespace openmsx {
7 
8 constexpr byte MEM_ACCESS_ENABLED = 1 << 7;
9 constexpr byte SOUND_PORT_ENABLED = 1 << 6;
10 constexpr byte PORT_ACCESS_DISABLED = 1 << 5;
11 constexpr byte UNUSED = 1 << 4;
12 constexpr byte WRITE_PROTECT = 0x0F;
13 
15  : MSXMemoryMapperBase(config)
16  , sn76489(config)
17  , controlReg(0x00)
18 {
19 }
20 
21 void 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 
38 byte 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 
47 byte 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 
56 void 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 
80 bool 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 
100 int 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 
116 void 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 |= registerAccessAt(0x4000 * page) << page;
131  }
132  controlReg = value;
133  byte regAccessAfter = 0;
134  for (auto page : xrange(4)) {
135  regAccessAfter |= registerAccessAt(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 
147 byte MusicalMemoryMapper::peekMem(word address, EmuTime::param time) const
148 {
149  int reg = readReg(address);
150  return reg >= 0 ? reg : MSXMemoryMapperBase::peekMem(address, time);
151 }
152 
153 byte MusicalMemoryMapper::readMem(word address, EmuTime::param time)
154 {
155  int reg = readReg(address);
156  return reg >= 0 ? reg : MSXMemoryMapperBase::readMem(address, time);
157 }
158 
159 void 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;
204  } else {
206  }
207 }
208 
209 template<typename Archive>
210 void MusicalMemoryMapper::serialize(Archive& ar, unsigned /*version*/)
211 {
212  ar.template serializeBase<MSXMemoryMapperBase>(*this);
213  ar.serialize("ctrl", controlReg,
214  "sn76489", sn76489);
215 }
217 REGISTER_MSXDEVICE(MusicalMemoryMapper, "MusicalMemoryMapper");
218 
219 } // namespace openmsx
void invalidateDeviceRWCache()
Calls MSXCPUInterface::invalidateXXCache() for the specific (part of) the slot that this device is lo...
Definition: MSXDevice.hh:209
static byte unmappedWrite[0x10000]
Definition: MSXDevice.hh:302
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.
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 * getWriteCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
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 * getWriteCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
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.
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:140
void write(byte value, EmuTime::param time)
Definition: SN76489.cc:146
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:9
constexpr byte PORT_ACCESS_DISABLED
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
constexpr byte MEM_ACCESS_ENABLED
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
constexpr byte UNUSED
constexpr byte SOUND_PORT_ENABLED
constexpr byte WRITE_PROTECT
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:998
constexpr auto xrange(T e)
Definition: xrange.hh:155