-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathmemoryManager.go
378 lines (325 loc) · 12 KB
/
memoryManager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
package izapple2
import "fmt"
// See https://fabiensanglard.net/fd_proxy/prince_of_persia/Inside%20the%20Apple%20IIe.pdf
// See https://i.stack.imgur.com/yn21s.gif
type memoryManager struct {
apple2 *Apple2
// Main RAM area: 0x0000 to 0xbfff
physicalMainRAM memoryRangeHandler // 0x0000 to 0xbfff, Up to 48 Kb
// Slots area: 0xc000 to 0xcfff
cardsROM [8]memoryHandler // 0xcs00 to 0xcSff. 256 bytes for each card
cardsROMExtra [8]memoryHandler // 0xc800 to 0xcfff. 2048 bytes for each card
// Upper area ROM: 0xc000 to 0xffff (or 0xd000 to 0xffff on the II+)
physicalROM memoryHandler // 0xc000 (or 0xd000) to 0xffff, 16 (or 12) Kb. Up to four banks
// Language card upper area RAM: 0xd000 to 0xffff. One bank for regular LC cards, up to 8 with Saturn
physicalLangRAM []memoryHandler // 0xd000 to 0xffff, 12KB. Up to 8 banks.
physicalLangAltRAM []memoryHandler // 0xd000 to 0xdfff, 4KB. Up to 8 banks.
// Extended RAM: 0x0000 to 0xffff (with 4Kb moved from 0xc000 to 0xd000 alt). One bank for extended Apple 2e card, up to 256 with RamWorks
physicalExtRAM []memoryRangeHandler // 0x0000 to 0xffff. 60Kb, 0xc000 to 0xcfff not used. Up to 256 banks
physicalExtAltRAM []memoryHandler // 0xd000 to 0xdfff, 4Kb. Up to 256 banks.
// Configuration switches, Language cards
lcSelectedBlock uint8 // Language card block selected. Usually, always 0. But Saturn has 8
lcActiveRead bool // Upper RAM active for read
lcActiveWrite bool // Upper RAM active for write
lcAltBank bool // Alternate
// Configuration switches, Apple //e
altZeroPage bool // Use extra RAM from 0x0000 to 0x01ff. And additional language card block
altMainRAMActiveRead bool // Use extra RAM from 0x0200 to 0xbfff for read
altMainRAMActiveWrite bool // Use extra RAM from 0x0200 to 0xbfff for write
store80Active bool // Special pagination for text and graphics areas
slotC3ROMActive bool // Apple2e slot 3 ROM shadow
intCxROMActive bool // Apple2e slots internal ROM shadow
intC8ROMActive bool // C8Rom associated to the internal slot 3. Softswitch not directly accessible. See UtA2e 5-28
activeSlot uint8 // Active slot owner of 0xc800 to 0xcfff
extendedRAMBlock uint8 // Block used for entended memory for RAMWorks cards
mainROMinhibited memoryHandler // Alternative ROM from 0xd000 to 0xffff provided by a card with the INH signal.
// Resolution cache
lastAddressPage uint16 // The first byte is the page. The second is zero when the cached is valid.
lastAddressHandler memoryHandler
}
const (
ioC8Off uint16 = 0xcfff
addressLimitZero uint16 = 0x01ff
addressStartText uint16 = 0x0400
addressLimitText uint16 = 0x07ff
addressStartHgr uint16 = 0x2000
addressLimitHgr uint16 = 0x3fff
addressLimitMainRAM uint16 = 0xbfff
addressLimitIO uint16 = 0xc0ff
addressLimitSlots uint16 = 0xc7ff
addressLimitSlotsExtra uint16 = 0xcfff
addressLimitDArea uint16 = 0xdfff
invalidAddressPage uint16 = 0x0001
)
type memoryHandler interface {
peek(uint16) uint8
poke(uint16, uint8)
}
type memoryRangeHandler interface {
memoryHandler
subRange(a, b uint16) []uint8
}
func newMemoryManager(a *Apple2) *memoryManager {
var mmu memoryManager
mmu.apple2 = a
mmu.slotC3ROMActive = true // For II+, this is the default behaviour
return &mmu
}
func (mmu *memoryManager) accessCArea(address uint16) memoryHandler {
slot := uint8((address >> 8) & 0x0f)
// Internal IIe slot 3
if (address <= addressLimitSlots) && !mmu.slotC3ROMActive && (slot == 3) {
mmu.intC8ROMActive = true
return mmu.physicalROM
}
// Internal IIe CxROM
if mmu.intCxROMActive {
return mmu.physicalROM
}
// First slot area
if slot <= 7 {
mmu.activeSlot = slot
mmu.intC8ROMActive = false
return mmu.cardsROM[slot]
}
// Extra slot area reset
if address == ioC8Off {
// Reset extra slot area owner
// There is not really an activeSlot in c8xx, any card could be active
// we should check all of them and maybe have conflicts. As I don't do that I won't disable and
// just track teh last active card.
// This code is disabled because cards could have different logic for disabling. Most cards disable
// on access to 0xCFFF, but the ProDOS ROM card 3 disables only on writes and not on reads.
// mmu.activeSlot = 0
mmu.intC8ROMActive = false
}
// Extra slot area
if mmu.intC8ROMActive {
return mmu.physicalROM
}
return mmu.cardsROMExtra[mmu.activeSlot]
}
func (mmu *memoryManager) accessUpperRAMArea(address uint16) memoryHandler {
if mmu.altZeroPage && mmu.hasExtendedRAM() {
// Use extended RAM
block := mmu.extendedRAMBlock
if mmu.lcAltBank && address <= addressLimitDArea {
return mmu.physicalExtAltRAM[block]
}
return mmu.physicalExtRAM[mmu.extendedRAMBlock]
}
// Use language card
block := mmu.lcSelectedBlock
if mmu.lcAltBank && address <= addressLimitDArea {
return mmu.physicalLangAltRAM[block]
}
return mmu.physicalLangRAM[block]
}
func (mmu *memoryManager) getPhysicalMainRAM(ext bool) memoryHandler {
if ext && mmu.hasExtendedRAM() {
return mmu.physicalExtRAM[mmu.extendedRAMBlock]
}
return mmu.physicalMainRAM
}
func (mmu *memoryManager) getVideoRAM(ext bool) memoryRangeHandler {
if ext && mmu.hasExtendedRAM() {
// The video memory uses the first extended RAM block, even with RAMWorks
return mmu.physicalExtRAM[0]
}
return mmu.physicalMainRAM
}
func (mmu *memoryManager) inhibitROM(replacement memoryHandler) {
// If a card INH the ROM, it replaces the ROM and the LC RAM
mmu.mainROMinhibited = replacement
mmu.lastAddressPage = invalidAddressPage // Invalidate cache
}
func (mmu *memoryManager) accessRead(address uint16) memoryHandler {
if address <= addressLimitZero {
return mmu.getPhysicalMainRAM(mmu.altZeroPage)
}
if mmu.store80Active && address <= addressLimitHgr {
altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage) // TODO: move flag to mmu property like the store80
if address >= addressStartText && address <= addressLimitText {
return mmu.getPhysicalMainRAM(altPage)
}
hires := mmu.apple2.io.isSoftSwitchActive(ioFlagHiRes)
if hires && address >= addressStartHgr && address <= addressLimitHgr {
return mmu.getPhysicalMainRAM(altPage)
}
}
if address <= addressLimitMainRAM {
return mmu.getPhysicalMainRAM(mmu.altMainRAMActiveRead)
}
if address <= addressLimitIO {
mmu.lastAddressPage = invalidAddressPage
return mmu.apple2.io
}
if address <= addressLimitSlotsExtra {
return mmu.accessCArea(address)
}
if mmu.mainROMinhibited != nil {
return mmu.mainROMinhibited
}
if mmu.lcActiveRead {
return mmu.accessUpperRAMArea(address)
}
return mmu.physicalROM
}
func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
if address <= addressLimitZero {
return mmu.getPhysicalMainRAM(mmu.altZeroPage)
}
if address <= addressLimitHgr && mmu.store80Active {
altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage)
if address >= addressStartText && address <= addressLimitText {
return mmu.getPhysicalMainRAM(altPage)
}
hires := mmu.apple2.io.isSoftSwitchActive(ioFlagHiRes)
if hires && address >= addressStartHgr && address <= addressLimitHgr {
return mmu.getPhysicalMainRAM(altPage)
}
}
if address <= addressLimitMainRAM {
return mmu.getPhysicalMainRAM(mmu.altMainRAMActiveWrite)
}
if address <= addressLimitIO {
mmu.lastAddressPage = invalidAddressPage
return mmu.apple2.io
}
if address <= addressLimitSlotsExtra {
return mmu.accessCArea(address)
}
if mmu.mainROMinhibited != nil {
return mmu.mainROMinhibited
}
if mmu.lcActiveWrite {
return mmu.accessUpperRAMArea(address)
}
return mmu.physicalROM
}
func (mmu *memoryManager) peekWord(address uint16) uint16 {
return uint16(mmu.Peek(address)) +
uint16(mmu.Peek(address+1))<<8
}
// Peek returns the data on the given address
func (mmu *memoryManager) Peek(address uint16) uint8 {
mh := mmu.accessRead(address)
if mh == nil {
return uint8(address) // Or some random number
}
value := mh.peek(address)
// if address >= 0xc400 && address < 0xc500 {
// fmt.Printf("[MMU] Peek at %04x: %02x\n", address, value)
// }
return value
}
// Peek returns the data on the given address optimized for more local requests
func (mmu *memoryManager) PeekCode(address uint16) uint8 {
page := address & 0xff00
var mh memoryHandler
if page == mmu.lastAddressPage {
mh = mmu.lastAddressHandler
} else {
mh = mmu.accessRead(address)
if address&0xf000 != 0xc000 {
// Do not cache 0xC area as it may reconfigure the MMU
mmu.lastAddressPage = page
mmu.lastAddressHandler = mh
}
}
if mh == nil {
return 0xf4 // Or some random number
}
value := mh.peek(address)
// if address >= 0xc400 && address < 0xc500 {
// fmt.Printf("[MMU] PeekCode at %04x: %02x\n", address, value)
// }
return value
}
func (mmu *memoryManager) pokeRange(address uint16, data []uint8) {
for i := 0; i < len(data); i++ {
mmu.Poke(address+uint16(i), data[i])
}
}
// Poke sets the data at the given address
func (mmu *memoryManager) Poke(address uint16, value uint8) {
mh := mmu.accessWrite(address)
if mh != nil {
mh.poke(address, value)
}
// if address >= 0x0036 && address <= 0x0039 {
// fmt.Printf("[MMU] Poke at %04x: %02x\n", address, value)
// }
}
// Memory initialization
func (mmu *memoryManager) setCardROM(slot int, mh memoryHandler) {
mmu.cardsROM[slot] = mh
}
func (mmu *memoryManager) setCardROMExtra(slot int, mh memoryHandler) {
mmu.cardsROMExtra[slot] = mh
}
func (mmu *memoryManager) initLanguageRAM(groups uint8) {
// Apple II+ language card or Saturn (up to 8 groups)
mmu.physicalLangRAM = make([]memoryHandler, groups)
mmu.physicalLangAltRAM = make([]memoryHandler, groups)
for i := uint8(0); i < groups; i++ {
mmu.physicalLangRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x3000), fmt.Sprintf("LC RAM block %v", i))
mmu.physicalLangAltRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x1000), fmt.Sprintf("LC RAM Alt block %v", i))
}
}
func (mmu *memoryManager) initMainRAM() {
// Apple II+ main RAM
mmu.physicalMainRAM = newMemoryRange(0, make([]uint8, 0xc000), "Main RAM")
}
func (mmu *memoryManager) initCustomRAM(customRam memoryRangeHandler) {
mmu.physicalMainRAM = customRam
}
func (mmu *memoryManager) initExtendedRAM(groups int) {
// Apple IIe 80 col card with 64Kb style RAM or RAMWorks (up to 256 banks)
mmu.physicalExtRAM = make([]memoryRangeHandler, groups)
mmu.physicalExtAltRAM = make([]memoryHandler, groups)
for i := 0; i < groups; i++ {
mmu.physicalExtRAM[i] = newMemoryRange(0, make([]uint8, 0x10000), fmt.Sprintf("Extra RAM block %v", i))
mmu.physicalExtAltRAM[i] = newMemoryRange(0xd000, make([]uint8, 0x1000), fmt.Sprintf("Extra RAM Alt block %v", i))
}
}
// Memory configuration
func (mmu *memoryManager) setLanguageRAM(readActive bool, writeActive bool, altBank bool) {
mmu.lcActiveRead = readActive
mmu.lcActiveWrite = writeActive
mmu.lcAltBank = altBank
}
func (mmu *memoryManager) setLanguageRAMActiveBlock(block uint8) {
block %= uint8(len(mmu.physicalLangRAM))
mmu.lcSelectedBlock = block
}
func (mmu *memoryManager) setExtendedRAMActiveBlock(block uint8) {
if int(block) >= len(mmu.physicalExtRAM) {
// How does the real hardware reacts?
block = 0
}
mmu.extendedRAMBlock = block
}
func (mmu *memoryManager) hasExtendedRAM() bool {
return len(mmu.physicalExtRAM) > 0
}
func (mmu *memoryManager) reset() {
if mmu.apple2.isApple2e {
// MMU UtA2e 4-14, 5-22
mmu.altZeroPage = false
mmu.altMainRAMActiveRead = false
mmu.altMainRAMActiveWrite = false
mmu.store80Active = false
mmu.slotC3ROMActive = false
mmu.intCxROMActive = false
mmu.intC8ROMActive = false
// IOU UtaA2e 7-3
// "All softswitches except KEYSTROKE, TEXT and MIXED are reset
// when the RESET line drops low"
mmu.apple2.io.softSwitchesData[ioFlagSecondPage] = ssOff
mmu.apple2.io.softSwitchesData[ioFlagHiRes] = ssOff
mmu.apple2.io.softSwitchesData[ioFlag80Col] = ssOff
mmu.apple2.io.softSwitchesData[ioDataNewVideo] = ssOff
// ioFlagText ?
}
}