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