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
15namespace openmsx {
16
17// The master clock, running at 33.8MHz.
19
20// Required delay between register select and register read/write.
21static constexpr auto FM_REG_SELECT_DELAY = MasterClock::duration(56);
22// Required delay after register write.
23static constexpr auto FM_REG_WRITE_DELAY = MasterClock::duration(56);
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.
29static constexpr auto WAVE_REG_SELECT_DELAY = MasterClock::duration(88);
30// Required delay after register write.
31static constexpr auto WAVE_REG_WRITE_DELAY = MasterClock::duration(88);
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.
37static constexpr auto MEM_READ_DELAY = MasterClock::duration(38);
38// Required delay after memory write (instead of register write delay).
39static constexpr auto MEM_WRITE_DELAY = MasterClock::duration(28);
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).
46static 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
61void MSXMoonSound::powerUp(EmuTime::param time)
62{
63 ymf278.clearRam();
64 reset(time);
65}
66
67void 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
79byte 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:
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:
117 }
118 }
119}
120
121byte 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:
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:
144 }
145 }
146}
147
148void 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:
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:
207 }
208 }
209}
210
211bool MSXMoonSound::getNew2() const
212{
213 return (ymf262.peekReg(0x105) & 0x02) != 0;
214}
215
216byte 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
228template<typename Archive>
229void 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
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:354
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:36
EmuTime::param getCurrentTime() const
Definition MSXDevice.cc:125
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
MSXMoonSound(const DeviceConfig &config)
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.
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
uint8_t readReg(unsigned reg) const
Definition YMF262.cc:1023
void reset(EmuTime::param time)
Definition YMF262.cc:1396
uint8_t peekStatus() const
Definition YMF262.cc:1480
void setMixLevel(uint8_t x, EmuTime::param time)
Definition YMF262.cc:1500
uint8_t readStatus()
Definition YMF262.cc:1472
void writeReg(unsigned r, uint8_t v, EmuTime::param time)
Definition YMF262.cc:1034
uint8_t peekReg(unsigned reg) const
Definition YMF262.cc:1029
uint8_t readReg(uint8_t reg)
Definition YMF278.cc:757
void writeReg(uint8_t reg, uint8_t data, EmuTime::param time)
Definition YMF278.cc:582
void setMixLevel(uint8_t x, EmuTime::param time)
Definition YMF278.cc:492
void reset(EmuTime::param time)
Definition YMF278.cc:844
void clearRam()
Definition YMF278.cc:839
uint8_t peekReg(uint8_t reg) const
Definition YMF278.cc:772
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define UNREACHABLE