openMSX
MSXMoonSound.cc
Go to the documentation of this file.
1 // ATM this class does several things:
2 // - It connects the YMF278b chip to specific I/O ports in the MSX machine
3 // - It glues the YMF262 (FM-part) and YMF278 (Wave-part) classes together in a
4 // full model of a YMF278b chip. IOW part of the logic of the YM278b is
5 // modeled here instead of in a chip-specific class.
6 // TODO it would be nice to move the functionality of the 2nd point to a
7 // different class, but until there's a 2nd user of this chip, this is
8 // low priority.
9 
10 #include "MSXMoonSound.hh"
11 #include "Clock.hh"
12 #include "serialize.hh"
13 #include "unreachable.hh"
14 
15 namespace openmsx {
16 
17 // The master clock, running at 33.8MHz.
19 
20 // Required delay between register select and register read/write.
22 // Required delay after register write.
24 // Datasheet doesn't mention any delay for reads from the FM registers. In fact
25 // it says reads from FM registers are not possible while tests on a real
26 // YMF278 show they do work (value of the NEW2 bit doesn't matter).
27 
28 // Required delay between register select and register read/write.
30 // Required delay after register write.
32 // Datasheet doesn't mention any delay for register reads (except for reads
33 // from register 6, see below). I also couldn't measure any delay on a real
34 // YMF278.
35 
36 // Required delay after memory read.
38 // Required delay after memory write (instead of register write delay).
40 
41 // Required delay after instrument load.
42 // We pick 10000 cycles, this is approximately 300us (the number given in the
43 // datasheet). The exact number of cycles is unknown. But I did some (very
44 // rough) tests on real HW, and this number is not too bad (slightly too high
45 // but within 2%-4% of real value, needs more detailed tests).
46 constexpr auto LOAD_DELAY = MasterClock::duration(10000);
47 
48 
50  : MSXDevice(config)
51  , ymf262(getName() + " FM", config, true)
52  , ymf278(getName() + " wave",
53  config.getChildDataAsInt("sampleram", 512), // size in kb
54  config)
55  , ymf278LoadTime(getCurrentTime())
56  , ymf278BusyTime(getCurrentTime())
57 {
59 }
60 
61 void MSXMoonSound::powerUp(EmuTime::param time)
62 {
63  ymf278.clearRam();
64  reset(time);
65 }
66 
67 void MSXMoonSound::reset(EmuTime::param time)
68 {
69  ymf262.reset(time);
70  ymf278.reset(time);
71 
72  opl4latch = 0; // TODO check
73  opl3latch = 0; // TODO check
74 
75  ymf278BusyTime = time;
76  ymf278LoadTime = time;
77 }
78 
79 byte MSXMoonSound::readIO(word port, EmuTime::param time)
80 {
81  if ((port & 0xFF) < 0xC0) {
82  // WAVE part 0x7E-0x7F
83  switch (port & 0x01) {
84  case 0: // read latch, not supported
85  return 255;
86  case 1: // read wave register
87  // Verified on real YMF278:
88  // Even if NEW2=0 reads happen normally. Also reads
89  // from sample memory (and thus the internal memory
90  // pointer gets increased).
91  if ((3 <= opl4latch) && (opl4latch <= 6)) {
92  // This time is so small that on a MSX you can
93  // never see BUSY=1. So I also couldn't test
94  // whether this timing applies to registers 3-6
95  // (like for write) or only to register 6. I
96  // also couldn't test how the other registers
97  // behave.
98  // TODO Should we comment out this code? It
99  // doesn't have any measurable effect on MSX.
100  ymf278BusyTime = time + MEM_READ_DELAY;
101  }
102  return ymf278.readReg(opl4latch);
103  default: // unreachable, avoid warning
104  UNREACHABLE; return 255;
105  }
106  } else {
107  // FM part 0xC4-0xC7
108  switch (port & 0x03) {
109  case 0: // read status
110  case 2:
111  return ymf262.readStatus() | readYMF278Status(time);
112  case 1:
113  case 3: // read fm register
114  return ymf262.readReg(opl3latch);
115  default: // unreachable, avoid warning
116  UNREACHABLE; return 255;
117  }
118  }
119 }
120 
121 byte MSXMoonSound::peekIO(word port, EmuTime::param time) const
122 {
123  if ((port & 0xFF) < 0xC0) {
124  // WAVE part 0x7E-0x7F
125  switch (port & 0x01) {
126  case 0: // read latch, not supported
127  return 255;
128  case 1: // read wave register
129  return ymf278.peekReg(opl4latch);
130  default: // unreachable, avoid warning
131  UNREACHABLE; return 255;
132  }
133  } else {
134  // FM part 0xC4-0xC7
135  switch (port & 0x03) {
136  case 0: // read status
137  case 2:
138  return ymf262.peekStatus() | readYMF278Status(time);
139  case 1:
140  case 3: // read fm register
141  return ymf262.peekReg(opl3latch);
142  default: // unreachable, avoid warning
143  UNREACHABLE; return 255;
144  }
145  }
146 }
147 
148 void MSXMoonSound::writeIO(word port, byte value, EmuTime::param time)
149 {
150  if ((port & 0xFF) < 0xC0) {
151  // WAVE part 0x7E-0x7F
152  if (getNew2()) {
153  switch (port & 0x01) {
154  case 0: // select register
155  ymf278BusyTime = time + WAVE_REG_SELECT_DELAY;
156  opl4latch = value;
157  break;
158  case 1:
159  if ((0x08 <= opl4latch) && (opl4latch <= 0x1F)) {
160  ymf278LoadTime = time + LOAD_DELAY;
161  }
162  if ((3 <= opl4latch) && (opl4latch <= 6)) {
163  // Note: this time is so small that on
164  // MSX you never see BUSY=1 for these
165  // registers. Confirmed on real HW that
166  // also registers 3-5 are faster.
167  ymf278BusyTime = time + MEM_WRITE_DELAY;
168  } else {
169  // For the other registers it is
170  // possible to see BUSY=1, but only
171  // very briefly and only on R800.
172  ymf278BusyTime = time + WAVE_REG_WRITE_DELAY;
173  }
174  if (opl4latch == 0xf8) {
175  ymf262.setMixLevel(value, time);
176  } else if (opl4latch == 0xf9) {
177  ymf278.setMixLevel(value, time);
178  }
179  ymf278.writeReg(opl4latch, value, time);
180  break;
181  default:
182  UNREACHABLE;
183  }
184  } else {
185  // Verified on real YMF278:
186  // Writes are ignored when NEW2=0 (both register select
187  // and register write).
188  }
189  } else {
190  // FM part 0xC4-0xC7
191  switch (port & 0x03) {
192  case 0: // select register bank 0
193  opl3latch = value;
194  ymf278BusyTime = time + FM_REG_SELECT_DELAY;
195  break;
196  case 2: // select register bank 1
197  opl3latch = value | 0x100;
198  ymf278BusyTime = time + FM_REG_SELECT_DELAY;
199  break;
200  case 1:
201  case 3: // write fm register
202  ymf278BusyTime = time + FM_REG_WRITE_DELAY;
203  ymf262.writeReg(opl3latch, value, time);
204  break;
205  default:
206  UNREACHABLE;
207  }
208  }
209 }
210 
211 bool MSXMoonSound::getNew2() const
212 {
213  return (ymf262.peekReg(0x105) & 0x02) != 0;
214 }
215 
216 byte MSXMoonSound::readYMF278Status(EmuTime::param time) const
217 {
218  byte result = 0;
219  if (time < ymf278BusyTime) result |= 0x01;
220  if (time < ymf278LoadTime) result |= 0x02;
221  return result;
222 }
223 
224 // version 1: initial version
225 // version 2: added alreadyReadID
226 // version 3: moved loadTime and busyTime from YMF278 to here
227 // removed alreadyReadID
228 template<typename Archive>
229 void MSXMoonSound::serialize(Archive& ar, unsigned version)
230 {
231  ar.template serializeBase<MSXDevice>(*this);
232  ar.serialize("ymf262", ymf262,
233  "ymf278", ymf278,
234  "opl3latch", opl3latch,
235  "opl4latch", opl4latch);
236  if (ar.versionAtLeast(version, 3)) {
237  ar.serialize("loadTime", ymf278LoadTime,
238  "busyTime", ymf278BusyTime);
239  } else {
240  assert(Archive::IS_LOADER);
241  // For 100% backwards compatibility we should restore these two
242  // from the (old) YMF278 class. Though that's a lot of extra
243  // work for very little gain.
244  ymf278LoadTime = getCurrentTime();
245  ymf278BusyTime = getCurrentTime();
246  }
247 }
250 
251 } // namespace openmsx
Represents a clock with a fixed frequency.
Definition: Clock.hh:19
static constexpr EmuDuration duration(unsigned ticks)
Calculates the duration of the given number of ticks at this clock's frequency.
Definition: Clock.hh:35
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition: MSXDevice.hh:33
EmuTime::param getCurrentTime() const
Definition: MSXDevice.cc:131
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
Definition: MSXMoonSound.cc:61
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
MSXMoonSound(const DeviceConfig &config)
Definition: MSXMoonSound.cc:49
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.
void serialize(Archive &ar, unsigned version)
void reset(EmuTime::param time) override
This method is called on reset.
Definition: MSXMoonSound.cc:67
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
Definition: MSXMoonSound.cc:79
void reset(EmuTime::param time)
Definition: YMF262.cc:1410
void setMixLevel(uint8_t x, EmuTime::param time)
Definition: YMF262.cc:1521
byte readStatus()
Definition: YMF262.cc:1493
byte readReg(unsigned reg)
Definition: YMF262.cc:1040
void writeReg(unsigned r, byte v, EmuTime::param time)
Definition: YMF262.cc:1051
byte peekReg(unsigned reg) const
Definition: YMF262.cc:1046
byte peekStatus() const
Definition: YMF262.cc:1501
void writeReg(byte reg, byte data, EmuTime::param time)
Definition: YMF278.cc:570
void setMixLevel(uint8_t x, EmuTime::param time)
Definition: YMF278.cc:471
void reset(EmuTime::param time)
Definition: YMF278.cc:824
byte readReg(byte reg)
Definition: YMF278.cc:742
void clearRam()
Definition: YMF278.cc:819
byte peekReg(byte reg) const
Definition: YMF278.cc:757
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:742
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr auto MEM_WRITE_DELAY
Definition: MSXMoonSound.cc:39
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
constexpr auto LOAD_DELAY
Definition: MSXMoonSound.cc:46
constexpr auto WAVE_REG_SELECT_DELAY
Definition: MSXMoonSound.cc:29
constexpr auto FM_REG_WRITE_DELAY
Definition: MSXMoonSound.cc:23
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
constexpr auto FM_REG_SELECT_DELAY
Definition: MSXMoonSound.cc:21
constexpr auto WAVE_REG_WRITE_DELAY
Definition: MSXMoonSound.cc:31
constexpr auto MEM_READ_DELAY
Definition: MSXMoonSound.cc:37
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983
#define UNREACHABLE
Definition: unreachable.hh:38