Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Cherry/Core/include/OCMMapper.h
2 views
1
/*
2
* Gearcoleco - ColecoVision Emulator
3
* Copyright (C) 2021 Ignacio Sanchez
4
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 3 of the License, or
8
* any later version.
9
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
14
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see http://www.gnu.org/licenses/
17
*
18
*/
19
20
#ifndef OCMMAPPER_H
21
#define OCMMAPPER_H
22
23
#include "Mapper.h"
24
#include "Cartridge.h"
25
26
#define OCM_CYCLES_PER_FRAME 59740
27
28
class OCMMapper : public Mapper
29
{
30
public:
31
OCMMapper(Cartridge* pCartridge, CVMemory* pMemory);
32
virtual ~OCMMapper();
33
34
virtual void Reset();
35
virtual u8 Read(u16 address);
36
virtual void Write(u16 address, u8 value);
37
virtual void SaveState(std::ostream& stream);
38
virtual void LoadState(std::istream& stream);
39
virtual u8 GetBankReg(int index) { return (index >= 0 && index < 4) ? m_BankReg[index] : 0; }
40
41
private:
42
// EEPROM state machine
43
enum
44
{
45
EEP_NONE = 0,
46
EEP_INIT = 1,
47
EEP_STATUS = 2,
48
EEP_WRITE = 3
49
};
50
51
inline bool EepromReadWindowActive() const
52
{
53
if (m_EepromReadExpireCycles == 0)
54
return false;
55
u64 now = m_pMemory->GetTotalCycles();
56
if (now < m_EepromReadExpireCycles)
57
return true;
58
return false;
59
}
60
61
inline void ArmEepromReadWindow(u8 value)
62
{
63
if ((value & 0x0F) == 0x0F)
64
{
65
u64 now = m_pMemory->GetTotalCycles();
66
m_EepromReadExpireCycles = now + (OCM_CYCLES_PER_FRAME * 3ULL);
67
}
68
else
69
m_EepromReadExpireCycles = 0;
70
}
71
72
private:
73
CVMemory* m_pMemory;
74
u8 m_BankReg[4];
75
u8 m_EepromCmdPos;
76
u8 m_EepromState;
77
u64 m_EepromReadExpireCycles;
78
};
79
80
inline OCMMapper::OCMMapper(Cartridge* pCartridge, CVMemory* pMemory)
81
: Mapper(pCartridge)
82
{
83
m_pMemory = pMemory;
84
Reset();
85
}
86
87
inline OCMMapper::~OCMMapper()
88
{
89
}
90
91
inline void OCMMapper::Reset()
92
{
93
m_BankReg[0] = 3;
94
m_BankReg[1] = 2;
95
m_BankReg[2] = 1;
96
m_BankReg[3] = 0;
97
m_EepromCmdPos = 0;
98
m_EepromState = EEP_NONE;
99
m_EepromReadExpireCycles = 0;
100
}
101
102
inline u8 OCMMapper::Read(u16 address)
103
{
104
u8* pRom = m_pCartridge->GetROM();
105
106
if (address < 0xA000)
107
{
108
// 8000-9FFF: Bank 3
109
int bankOffset = (address - 0x8000) + (m_BankReg[3] * 0x2000);
110
return pRom[bankOffset];
111
}
112
else if (address < 0xC000)
113
{
114
// A000-BFFF: Bank 0
115
int bankOffset = (address - 0xA000) + (m_BankReg[0] * 0x2000);
116
return pRom[bankOffset];
117
}
118
else if (address < 0xE000)
119
{
120
// C000-DFFF: Bank 1
121
int bankOffset = (address - 0xC000) + (m_BankReg[1] * 0x2000);
122
return pRom[bankOffset];
123
}
124
else
125
{
126
// E000-FFFF: Bank 2 (ROM or EEPROM)
127
// If STATUS mode, reading E000 returns 0xFF (status OK)
128
if (((address & 0x0FFF) == 0x0000) && (m_EepromState == EEP_STATUS))
129
{
130
return 0xFF;
131
}
132
133
// EEPROM read window: bank2==0x0F, active timer, E000-E0FF -> EEPROM
134
if (m_BankReg[2] == 0x0F && EepromReadWindowActive() && ((address & 0x0FFF) < 0x0100))
135
{
136
u8* pEEPROM = m_pCartridge->GetEEPROM();
137
if (IsValidPointer(pEEPROM))
138
{
139
return pEEPROM[address & 0x03FF];
140
}
141
return 0xFF;
142
}
143
144
// Otherwise, ROM from bank 2
145
int bankOffset = (address - 0xE000) + (m_BankReg[2] * 0x2000);
146
return pRom[bankOffset];
147
}
148
}
149
150
inline void OCMMapper::Write(u16 address, u8 value)
151
{
152
// OCM EEPROM & banking are only active on E000-FFFF range
153
if (address < 0xE000)
154
{
155
return;
156
}
157
158
// EEPROM command/byte write on E000..FFFB
159
if (address <= 0xFFFB)
160
{
161
// Handshake: AA -> 55 -> CMD
162
if (value == 0xAA && m_EepromCmdPos == 0)
163
{
164
m_EepromCmdPos = 1;
165
return;
166
}
167
else if (value == 0x55 && m_EepromCmdPos == 1)
168
{
169
m_EepromCmdPos = 2;
170
return;
171
}
172
else if (m_EepromCmdPos == 2)
173
{
174
// Interpret command byte
175
if (value == 0x80)
176
{
177
m_EepromState = EEP_INIT;
178
}
179
else if (value == 0x30)
180
{
181
if (m_EepromState == EEP_INIT)
182
{
183
m_EepromState = EEP_STATUS;
184
}
185
}
186
else if (value == 0xA0)
187
{
188
m_EepromState = EEP_WRITE;
189
}
190
else
191
{
192
Debug("--> OCM EEP Unknown command: %02X @ %04X", value, address);
193
}
194
195
m_EepromCmdPos = 0;
196
return;
197
}
198
else if (m_EepromState == EEP_WRITE)
199
{
200
// Next write persists a single byte to 1KB EEPROM space
201
u8* pEEPROM = m_pCartridge->GetEEPROM();
202
if (IsValidPointer(pEEPROM))
203
{
204
pEEPROM[address & 0x03FF] = value;
205
}
206
m_EepromState = EEP_NONE;
207
return;
208
}
209
210
// Any other writes in this range that don't match the handshake are ignored
211
return;
212
}
213
214
// Bank registers and EEPROM read-window trigger
215
// FFFC -> bank 0, FFFD -> bank 1, FFFE -> bank 2 (also arms read-window), FFFF -> bank 3
216
if (address >= 0xFFFC)
217
{
218
if (address == 0xFFFE)
219
{
220
// Arm or clear the 3-frame EEPROM read window
221
ArmEepromReadWindow(value);
222
}
223
224
m_BankReg[address & 0x0003] = (u8)(value & 0x0F);
225
226
// Debug("--> OCM Bank[%d] = %d (addr=%04X, val=%02X)", (address & 3), m_BankReg[address & 3], address, value);
227
return;
228
}
229
}
230
231
inline void OCMMapper::SaveState(std::ostream& stream)
232
{
233
234
stream.write(reinterpret_cast<const char*>(m_BankReg), sizeof(m_BankReg));
235
stream.write(reinterpret_cast<const char*>(&m_EepromCmdPos), sizeof(m_EepromCmdPos));
236
stream.write(reinterpret_cast<const char*>(&m_EepromState), sizeof(m_EepromState));
237
238
// Persist “frames remaining” instead of raw cycle deadline
239
u64 now = m_pMemory->GetTotalCycles();
240
u8 framesLeft = 0;
241
if (m_EepromReadExpireCycles > now)
242
{
243
u64 delta = m_EepromReadExpireCycles - now;
244
// Ceil to frames so the window isn't lost by rounding
245
framesLeft = (u8)((delta + (OCM_CYCLES_PER_FRAME - 1)) / OCM_CYCLES_PER_FRAME);
246
if (framesLeft > 3)
247
framesLeft = 3;
248
}
249
stream.write(reinterpret_cast<const char*>(&framesLeft), sizeof(framesLeft));
250
251
u8* pEEPROM = m_pCartridge->GetEEPROM();
252
stream.write(reinterpret_cast<const char*>(pEEPROM), 0x400);
253
}
254
255
inline void OCMMapper::LoadState(std::istream& stream)
256
{
257
stream.read(reinterpret_cast<char*>(m_BankReg), sizeof(m_BankReg));
258
stream.read(reinterpret_cast<char*>(&m_EepromCmdPos), sizeof(m_EepromCmdPos));
259
stream.read(reinterpret_cast<char*>(&m_EepromState), sizeof(m_EepromState));
260
261
u8 framesLeft = 0;
262
stream.read(reinterpret_cast<char*>(&framesLeft), sizeof(framesLeft));
263
if (framesLeft > 0)
264
{
265
u64 now = m_pMemory->GetTotalCycles();
266
m_EepromReadExpireCycles = now + (u64)framesLeft * OCM_CYCLES_PER_FRAME;
267
}
268
else
269
{
270
m_EepromReadExpireCycles = 0;
271
}
272
273
u8* pEEPROM = m_pCartridge->GetEEPROM();
274
stream.read(reinterpret_cast<char*>(pEEPROM), 0x400);
275
}
276
277
#endif /* OCMMAPPER_H */
278
279