openMSX
Yamanooto.cc
Go to the documentation of this file.
1#include "Yamanooto.hh"
2
4#include "MSXCPUInterface.hh"
5
6#include "narrow.hh"
7#include "outer.hh"
8#include "ranges.hh"
9#include "unreachable.hh"
10
11#include <cassert>
12
13// TODO:
14// * HOME/DEL boot keys. How are these handled?
15// * SCC stereo mode.
16
17namespace openmsx {
18
19static constexpr word ENAR = 0x7FFF;
20static constexpr byte REGEN = 0x01;
21static constexpr byte WREN = 0x10;
22static constexpr word OFFR = 0x7FFE;
23static constexpr word CFGR = 0x7FFD;
24static constexpr byte MDIS = 0x01;
25static constexpr byte ECHO = 0x02;
26static constexpr byte ROMDIS = 0x04;
27static constexpr byte K4 = 0x08;
28static constexpr byte SUBOFF = 0x30;
29// undocumented stuff
30static constexpr byte FPGA_EN = 0x40; // write: 1 -> enable communication with FPGA commands ???
31static constexpr byte FPGA_WAIT = 0x80; // read: ready signal ??? (1 = ready)
32static constexpr word FPGA_REG = 0x7FFC; // bi-direction 8-bit communication channel
33
35 : MSXRom(config, std::move(rom_))
36 , romBlockDebug(*this)
37 , flash(rom, AmdFlashChip::S29GL064N90TFI04, {}, config)
38 , scc(getName() + " SCC", config, getCurrentTime(), SCC::Mode::Compatible)
39 , psg(getName() + " PSG", DummyAY8910Periphery::instance(), config, getCurrentTime())
40{
41 // Tests show that the PSG has higher volume than the scc. See
42 // https://www.msx.org/forum/msx-talk/openmsx/openmsx-cartridge-type?page=2#comment-470014
43 // https://msx.pics/images/2024/12/23/yamanooto_volume_comparison.png
44 psg.setSoftwareVolume(3.0f, getCurrentTime());
45
46 scc.setBalance(0, 0.00f);
47 scc.setBalance(1, -0.67f);
48 scc.setBalance(2, 0.67f);
49 scc.setBalance(3, -0.67f);
50 scc.setBalance(4, 0.67f);
51 scc.postSetBalance();
52
53 getCPUInterface().register_IO_Out_range(0x10, 2, this);
54 powerUp(getCurrentTime());
55}
56
58{
59 writeConfigReg(0); // unregister A0-A2 ports
61}
62
63void Yamanooto::powerUp(EmuTime::param time)
64{
65 scc.powerUp(time);
66 reset(time);
67}
68
69void Yamanooto::reset(EmuTime::param time)
70{
71 // TODO is offsetReg changed by reset?
72 // TODO are all bits in configReg set to zero? (also bit 0,1)
73 enableReg = 0;
74 offsetReg = 0;
75 writeConfigReg(0);
76 fpgaFsm = 0;
77
78 ranges::iota(bankRegs, uint16_t(0));
79 sccMode = 0;
80 scc.reset(time);
81
82 psgLatch = 0;
83 psg.reset(time);
84
85 flash.reset();
86
87 invalidateDeviceRCache(); // flush all to be sure
88}
89
90void Yamanooto::writeConfigReg(byte value)
91{
92 if ((value ^ configReg) & ECHO) {
93 if (value & ECHO) {
95 } else {
97 }
98 }
99 configReg = value;
100}
101
102bool Yamanooto::isSCCAccess(word address) const
103{
104 if (configReg & K4) return false; // Konami4 doesn't have SCC
105
106 if (sccMode & 0x20) {
107 // SCC+ range: 0xB800..0xBFFF, excluding 0xBFFE-0xBFFF
108 return (bankRegs[3] & 0x80) && (0xB800 <= address) && (address < 0xBFFE);
109 } else {
110 // SCC range: 0x9800..0x9FFF
111 return ((bankRegs[2] & 0x3F) == 0x3F) && (0x9800 <= address) && (address < 0xA000);
112 }
113}
114
115unsigned Yamanooto::getFlashAddr(unsigned addr) const
116{
117 unsigned page8kB = (addr >> 13) - 2;
118 assert(page8kB < 4); // must be inside [0x4000, 0xBFFF]
119 auto bank = bankRegs[page8kB] & 0x3ff; // max 8MB
120 return (bank << 13) | (addr & 0x1FFF);
121}
122
123[[nodiscard]] static word mirror(word address)
124{
125 if (address < 0x4000 || 0xC000 <= address) {
126 // mirror 0x4000 <-> 0xc000 / 0x8000 <-> 0x0000
127 address ^= 0x8000; // real hw probably just ignores upper address bit
128 }
129 assert((0x4000 <= address) && (address < 0xC000));
130 return address;
131}
132
133static constexpr std::array<byte, 4 + 1> FPGA_ID = {
134 0xFF, // idle
135 0x1F, 0x23, 0x00, 0x00, // TODO check last 2
136};
137byte Yamanooto::peekMem(word address, EmuTime::param time) const
138{
139 address = mirror(address);
140
141 // 0x7ffc-0x7fff
142 if (FPGA_REG <= address && address <= ENAR && (enableReg & REGEN)) {
143 switch (address) {
144 case FPGA_REG:
145 if (!(configReg & FPGA_EN)) return 0xFF; // TODO check this
146 assert(fpgaFsm < 5);
147 return FPGA_ID[fpgaFsm];
148 case CFGR: return configReg | FPGA_WAIT; // not-busy
149 case OFFR: return offsetReg;
150 case ENAR: return enableReg;
151 default: UNREACHABLE;
152 }
153 }
154
155 // 0x9800-0x9FFF or 0xB800-0xBFFD
156 if (isSCCAccess(address)) {
157 return scc.peekMem(narrow_cast<uint8_t>(address & 0xFF), time);
158 }
159 return ((configReg & ROMDIS) == 0) ? flash.peek(getFlashAddr(address))
160 : 0xFF; // access to flash ROM disabled
161}
162
163byte Yamanooto::readMem(word address, EmuTime::param time)
164{
165 // 0x7ffc-0x7fff (NOT mirrored)
166 if (FPGA_REG <= address && address <= ENAR && (enableReg & REGEN)) {
167 return peekMem(address, time);
168 }
169 address = mirror(address);
170
171 // 0x9800-0x9FFF or 0xB800-0xBFFD
172 if (isSCCAccess(address)) {
173 return scc.readMem(narrow_cast<uint8_t>(address & 0xFF), time);
174 }
175 return ((configReg & ROMDIS) == 0) ? flash.read(getFlashAddr(address))
176 : 0xFF; // access to flash ROM disabled
177}
178
179const byte* Yamanooto::getReadCacheLine(word address) const
180{
181 if (((address & CacheLine::HIGH) == (ENAR & CacheLine::HIGH)) && (enableReg & REGEN)) {
182 return nullptr; // Yamanooto registers, non-cacheable
183 }
184 address = mirror(address);
185 if (isSCCAccess(address)) return nullptr;
186 return ((configReg & ROMDIS) == 0) ? flash.getReadCacheLine(getFlashAddr(address))
187 : unmappedRead.data(); // access to flash ROM disabled
188}
189
190void Yamanooto::writeMem(word address, byte value, EmuTime::param time)
191{
192 // 0x7ffc-0x7fff (NOT mirrored)
193 if (FPGA_REG <= address && address <= ENAR) {
194 if (address == ENAR) {
195 enableReg = value;
196 } else if (enableReg & REGEN) {
197 switch (address) {
198 case FPGA_REG:
199 if (!(configReg & FPGA_EN)) break;
200 switch (fpgaFsm) {
201 case 0:
202 if (value == 0x9f) { // read ID command ??
203 ++fpgaFsm;
204 }
205 break;
206 case 1: case 2: case 3:
207 ++fpgaFsm;
208 break;
209 case 4:
210 fpgaFsm = 0;
211 break;
212 default:
214 }
215 break;
216 case CFGR:
217 writeConfigReg(value);
218 break;
219 case OFFR:
220 offsetReg = value; // does NOT immediately switch bankRegs
221 break;
222 default:
224 }
225 }
227 }
228 address = mirror(address);
229 unsigned page8kB = (address >> 13) - 2;
230
231 if (enableReg & WREN) {
232 // write to flash rom
233 if (!(configReg & ROMDIS)) { // disabled?
234 flash.write(getFlashAddr(address), value);
235 invalidateDeviceRCache(); // needed ?
236 }
237 } else {
238 auto invalidateCache = [&](unsigned start, unsigned size) {
239 invalidateDeviceRCache(start, size);
240 invalidateDeviceRCache(start ^ 0x8000, size); // mirror
241 };
242 auto offset = (offsetReg << 2) | ((configReg & SUBOFF) >> 4);
243 if (configReg & K4) {
244 // Konami-4
245 if (((configReg & MDIS) == 0) && (0x6000 <= address)) {
246 // bank 0 is NOT switchable, but it keeps the
247 // value it had before activating K4 mode
248 bankRegs[page8kB] = (value + offset) & 0x3FF;
249 invalidateCache(0x4000 + 0x2000 * page8kB, 0x2000);
250 }
251 } else {
252 // 0x9800-0x9FFF or 0xB800-0xBFFD
253 if (isSCCAccess(address)) {
254 scc.writeMem(narrow_cast<uint8_t>(address & 0xFF), value, time);
255 }
256
257 // Konami-SCC
258 if (((address & 0x1800) == 0x1000) && ((configReg & MDIS) == 0)) {
259 // [0x5000,0x57FF] [0x7000,0x77FF]
260 // [0x9000,0x97FF] [0xB000,0xB7FF]
261 bankRegs[page8kB] = (value + offset) & 0x3FF;
262 invalidateCache(0x4000 + 0x2000 * page8kB, 0x2000);
263 }
264
265 // SCC mode register
266 if ((address & 0xFFFE) == 0xBFFE) {
267 sccMode = value;
268 scc.setMode((value & 0x20) ? SCC::Mode::Plus
270 invalidateCache(0x9800, 0x800);
271 invalidateCache(0xB800, 0x800);
272 }
273 }
274 }
275}
276
278{
279 return nullptr;
280}
281
282byte Yamanooto::peekIO(word /*port*/, EmuTime::param /*time*/) const
283{
284 return 0xff;
285}
286
287byte Yamanooto::readIO(word /*port*/, EmuTime::param /*time*/)
288{
289 // PSG is not readable
290 return 0xff; // should never be called
291}
292
293void Yamanooto::writeIO(word port, byte value, EmuTime::param time)
294{
295 if (port & 1) { // 0x11 or 0xA1
296 psg.writeRegister(psgLatch, value, time);
297 } else { // 0x10 or 0xA0
298 psgLatch = value & 0x0F;
299 }
300}
301
302template<typename Archive>
303void Yamanooto::serialize(Archive& ar, unsigned /*version*/)
304{
305 ar.serialize(
306 "flash", flash,
307 "scc", scc,
308 "psg", psg,
309 "bankRegs", bankRegs,
310 "enableReg", enableReg,
311 "offsetReg", offsetReg,
312 "configReg", configReg,
313 "sccMode", sccMode,
314 "sccMode", sccMode,
315 "psgLatch", psgLatch,
316 "fpgaFsm", fpgaFsm
317 );
318}
321
322
323unsigned Yamanooto::Blocks::readExt(unsigned address)
324{
325 const auto& dev = OUTER(Yamanooto, romBlockDebug);
326 address = mirror(narrow<word>(address));
327 unsigned page8kB = (address >> 13) - 2;
328 return dev.bankRegs[page8kB];
329}
330
331} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
void reset(EmuTime::param time)
Definition AY8910.cc:521
void writeRegister(unsigned reg, uint8_t value, EmuTime::param time)
Definition AY8910.cc:578
void write(size_t address, uint8_t value)
Definition AmdFlash.cc:456
const uint8_t * getReadCacheLine(size_t address) const
Definition AmdFlash.cc:444
uint8_t read(size_t address)
Definition AmdFlash.cc:432
uint8_t peek(size_t address) const
Definition AmdFlash.cc:186
static DummyAY8910Periphery & instance()
void unregister_IO_Out_range(byte port, unsigned num, MSXDevice *device)
void register_IO_Out_range(byte port, unsigned num, MSXDevice *device)
void invalidateDeviceRCache()
Definition MSXDevice.hh:215
static std::array< byte, 0x10000 > unmappedRead
Definition MSXDevice.hh:306
MSXCPUInterface & getCPUInterface() const
Definition MSXDevice.cc:133
void setMode(Mode newMode)
Definition SCC.cc:183
void powerUp(EmuTime::param time)
Definition SCC.cc:141
uint8_t readMem(uint8_t address, EmuTime::param time)
Definition SCC.cc:193
void reset(EmuTime::param time)
Definition SCC.cc:173
uint8_t peekMem(uint8_t address, EmuTime::param time) const
Definition SCC.cc:206
void writeMem(uint8_t address, uint8_t value, EmuTime::param time)
Definition SCC.cc:285
const byte * getReadCacheLine(word address) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
Definition Yamanooto.cc:179
byte readMem(word address, EmuTime::param time) override
Read a byte from a location at a certain time from this device.
Definition Yamanooto.cc:163
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.
Definition Yamanooto.cc:190
void reset(EmuTime::param time) override
This method is called on reset.
Definition Yamanooto.cc:69
byte peekMem(word address, EmuTime::param time) const override
Read a byte from a given memory location.
Definition Yamanooto.cc:137
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Definition Yamanooto.cc:282
void serialize(Archive &ar, unsigned version)
Definition Yamanooto.cc:303
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
Definition Yamanooto.cc:63
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.
Definition Yamanooto.cc:293
byte * getWriteCacheLine(word address) override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
Definition Yamanooto.cc:277
~Yamanooto() override
Definition Yamanooto.cc:57
Yamanooto(const DeviceConfig &config, Rom &&rom)
Definition Yamanooto.cc:34
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
Definition Yamanooto.cc:287
constexpr unsigned HIGH
Definition CacheLine.hh:10
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
constexpr void iota(ForwardIt first, ForwardIt last, T value)
Definition ranges.hh:322
STL namespace.
#define OUTER(type, member)
Definition outer.hh:42
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define UNREACHABLE