]> Shamusworld >> Repos - apple2/blob - src/mmu.cpp
77578d42271c63c2803da523275ebfeaf9ef6f6b
[apple2] / src / mmu.cpp
1 //
2 // mmu.cpp: Memory management
3 //
4 // by James Hammons
5 // (C) 2013-2018 Underground Software
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  -----------------------------------------------------------
11 // JLH  09/27/2013  Created this file
12 //
13
14 #include "mmu.h"
15 #include "apple2.h"
16 #include "firmware/firmware.h"
17 #include "log.h"
18 #include "mockingboard.h"
19 #include "sound.h"
20 #include "video.h"
21
22 // Debug defines
23 //#define LC_DEBUG
24
25 // Address Map enumeration
26 enum { AM_RAM, AM_ROM, AM_BANKED, AM_READ, AM_WRITE, AM_READ_WRITE, AM_END_OF_LIST };
27
28 // Internal vars
29 uint8_t ** addrPtrRead[0x10000];
30 uint8_t ** addrPtrWrite[0x10000];
31 uint16_t addrOffset[0x10000];
32
33 READFUNC(funcMapRead[0x10000]);
34 WRITEFUNC(funcMapWrite[0x10000]);
35
36 READFUNC(slotHandlerR[8]);
37 WRITEFUNC(slotHandlerW[8]);
38
39 READFUNC(slotHandler2KR[8]);
40 WRITEFUNC(slotHandler2KW[8]);
41
42 uint8_t enabledSlot;
43
44 struct AddressMap
45 {
46         uint16_t start;
47         uint16_t end;
48         int type;
49         uint8_t ** memory;
50         uint8_t ** altMemory;
51         READFUNC(read);
52         WRITEFUNC(write);
53 };
54
55 #define ADDRESS_MAP_END         { 0x0000, 0x0000, AM_END_OF_LIST, 0, 0, 0, 0 }
56
57 // Dunno if I like this approach or not...
58 //ADDRESS_MAP_START()
59 //      AM_RANGE(0x0000, 0xBFFF) AM_RAM AM_BASE(ram) AM_SHARE(1)
60 //      AM_RANGE(0xC000, 0xC001) AM_READWRITE(readFunc, writeFunc)
61 //ADDRESS_MAP_END
62
63 // Would need a pointer for 80STORE as well...
64
65 uint8_t * pageZeroMemory  = &ram[0x0000];       // $0000 - $01FF
66 uint8_t * mainMemoryR     = &ram[0x0200];       // $0200 - $BFFF (read)
67 uint8_t * mainMemoryW     = &ram[0x0200];       // $0200 - $BFFF (write)
68
69 uint8_t * mainMemoryTextR = &ram[0x0400];       // $0400 - $07FF (read)
70 uint8_t * mainMemoryTextW = &ram[0x0400];       // $0400 - $07FF (write)
71 uint8_t * mainMemoryHGRR  = &ram[0x2000];       // $2000 - $3FFF (read)
72 uint8_t * mainMemoryHGRW  = &ram[0x2000];       // $2000 - $3FFF (write)
73
74 uint8_t * slotMemory      = &rom[0xC100];       // $C100 - $C7FF
75 uint8_t * peripheralMemory= &rom[0xC800];       // $C800 - $CFFF
76 uint8_t * slot3Memory     = &rom[0xC300];       // $C300 - $C3FF
77 uint8_t * slot4Memory     = &rom[0xC400];       // $C400 - $C4FF
78 uint8_t * slot6Memory     = &diskROM[0];        // $C600 - $C6FF
79 uint8_t * lcBankMemoryR   = &ram[0xD000];       // $D000 - $DFFF (read)
80 uint8_t * lcBankMemoryW   = &ram[0xD000];       // $D000 - $DFFF (write)
81 uint8_t * upperMemoryR    = &ram[0xE000];       // $E000 - $FFFF (read)
82 uint8_t * upperMemoryW    = &ram[0xE000];       // $E000 - $FFFF (write)
83
84 // Function prototypes
85 uint8_t ReadNOP(uint16_t);
86 void WriteNOP(uint16_t, uint8_t);
87 uint8_t ReadMemory(uint16_t);
88 void WriteMemory(uint16_t, uint8_t);
89 uint8_t SlotR(uint16_t address);
90 void SlotW(uint16_t address, uint8_t byte);
91 uint8_t Slot2KR(uint16_t address);
92 void Slot2KW(uint16_t address, uint8_t byte);
93 uint8_t ReadKeyboard(uint16_t);
94 void Switch80STORE(uint16_t, uint8_t);
95 void SwitchRAMRD(uint16_t, uint8_t);
96 void SwitchRAMWRT(uint16_t, uint8_t);
97 void SwitchSLOTCXROM(uint16_t, uint8_t);
98 void SwitchALTZP(uint16_t, uint8_t);
99 void SwitchSLOTC3ROM(uint16_t, uint8_t);
100 uint8_t SwitchINTC8ROMR(uint16_t);
101 void SwitchINTC8ROMW(uint16_t, uint8_t);
102 void Switch80COL(uint16_t, uint8_t);
103 void SwitchALTCHARSET(uint16_t, uint8_t);
104 uint8_t ReadKeyStrobe(uint16_t);
105 uint8_t ReadBANK2(uint16_t);
106 uint8_t ReadLCRAM(uint16_t);
107 uint8_t ReadRAMRD(uint16_t);
108 uint8_t ReadRAMWRT(uint16_t);
109 uint8_t ReadSLOTCXROM(uint16_t);
110 uint8_t ReadALTZP(uint16_t);
111 uint8_t ReadSLOTC3ROM(uint16_t);
112 uint8_t Read80STORE(uint16_t);
113 uint8_t ReadVBL(uint16_t);
114 uint8_t ReadTEXT(uint16_t);
115 uint8_t ReadMIXED(uint16_t);
116 uint8_t ReadPAGE2(uint16_t);
117 uint8_t ReadHIRES(uint16_t);
118 uint8_t ReadALTCHARSET(uint16_t);
119 uint8_t Read80COL(uint16_t);
120 void WriteKeyStrobe(uint16_t, uint8_t);
121 uint8_t ReadSpeaker(uint16_t);
122 void WriteSpeaker(uint16_t, uint8_t);
123 uint8_t SwitchLCR(uint16_t);
124 void SwitchLCW(uint16_t, uint8_t);
125 void SwitchLC(void);
126 uint8_t SwitchTEXTR(uint16_t);
127 void SwitchTEXTW(uint16_t, uint8_t);
128 uint8_t SwitchMIXEDR(uint16_t);
129 void SwitchMIXEDW(uint16_t, uint8_t);
130 uint8_t SwitchPAGE2R(uint16_t);
131 void SwitchPAGE2W(uint16_t, uint8_t);
132 uint8_t SwitchHIRESR(uint16_t);
133 void SwitchHIRESW(uint16_t, uint8_t);
134 uint8_t SwitchDHIRESR(uint16_t);
135 void SwitchDHIRESW(uint16_t, uint8_t);
136 void SwitchIOUDIS(uint16_t, uint8_t);
137 uint8_t ReadCassetteIn(uint16_t);
138 uint8_t ReadButton0(uint16_t);
139 uint8_t ReadButton1(uint16_t);
140 uint8_t ReadPaddle0(uint16_t);
141 uint8_t ReadIOUDIS(uint16_t);
142 uint8_t ReadDHIRES(uint16_t);
143
144 // The main Apple //e memory map
145 AddressMap memoryMap[] = {
146         { 0x0000, 0x01FF, AM_RAM, &pageZeroMemory, 0, 0, 0 },
147         { 0x0200, 0xBFFF, AM_BANKED, &mainMemoryR, &mainMemoryW, 0, 0 },
148
149         // These will overlay over the previously written memory accessors
150         { 0x0400, 0x07FF, AM_BANKED, &mainMemoryTextR, &mainMemoryTextW, 0, 0 },
151         { 0x2000, 0x3FFF, AM_BANKED, &mainMemoryHGRR, &mainMemoryHGRW, 0, 0 },
152
153         { 0xC000, 0xC001, AM_READ_WRITE, 0, 0, ReadKeyboard, Switch80STORE },
154         { 0xC002, 0xC003, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchRAMRD },
155         { 0xC004, 0xC005, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchRAMWRT },
156         { 0xC006, 0xC007, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchSLOTCXROM },
157         { 0xC008, 0xC009, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchALTZP },
158         { 0xC00A, 0xC00B, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchSLOTC3ROM },
159         { 0xC00C, 0xC00D, AM_READ_WRITE, 0, 0, ReadKeyboard, Switch80COL },
160         { 0xC00E, 0xC00F, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchALTCHARSET },
161         { 0xC010, 0xC010, AM_READ_WRITE, 0, 0, ReadKeyStrobe, WriteKeyStrobe },
162         { 0xC011, 0xC011, AM_READ_WRITE, 0, 0, ReadBANK2, WriteKeyStrobe },
163         { 0xC012, 0xC012, AM_READ_WRITE, 0, 0, ReadLCRAM, WriteKeyStrobe },
164         { 0xC013, 0xC013, AM_READ_WRITE, 0, 0, ReadRAMRD, WriteKeyStrobe },
165         { 0xC014, 0xC014, AM_READ_WRITE, 0, 0, ReadRAMWRT, WriteKeyStrobe },
166         { 0xC015, 0xC015, AM_READ_WRITE, 0, 0, ReadSLOTCXROM, WriteKeyStrobe },
167         { 0xC016, 0xC016, AM_READ_WRITE, 0, 0, ReadALTZP, WriteKeyStrobe },
168         { 0xC017, 0xC017, AM_READ_WRITE, 0, 0, ReadSLOTC3ROM, WriteKeyStrobe },
169         { 0xC018, 0xC018, AM_READ_WRITE, 0, 0, Read80STORE, WriteKeyStrobe },
170         { 0xC019, 0xC019, AM_READ_WRITE, 0, 0, ReadVBL, WriteKeyStrobe },
171         { 0xC01A, 0xC01A, AM_READ_WRITE, 0, 0, ReadTEXT, WriteKeyStrobe },
172         { 0xC01B, 0xC01B, AM_READ_WRITE, 0, 0, ReadMIXED, WriteKeyStrobe },
173         { 0xC01C, 0xC01C, AM_READ_WRITE, 0, 0, ReadPAGE2, WriteKeyStrobe },
174         { 0xC01D, 0xC01D, AM_READ_WRITE, 0, 0, ReadHIRES, WriteKeyStrobe },
175         { 0xC01E, 0xC01E, AM_READ_WRITE, 0, 0, ReadALTCHARSET, WriteKeyStrobe },
176         { 0xC01F, 0xC01F, AM_READ_WRITE, 0, 0, Read80COL, WriteKeyStrobe },
177         // $C020 is "Cassette Out (RO)"
178         { 0xC020, 0xC02F, AM_READ, 0, 0, ReadFloatingBus, 0 },
179         // May have to put a "floating bus" read there... :-/
180         // Apparently, video RAM is put on 'non-responding address'. So will
181         // need to time those out.
182         // So... $C020-$C08F, when read, return video data.
183         // $C090-$C7FF do also, as long as the slot the range refers to is empty
184         // and last and least is $CFFF, which is the Expansion ROM disable.
185         { 0xC030, 0xC03F, AM_READ_WRITE, 0, 0, ReadSpeaker, WriteSpeaker },
186         { 0xC050, 0xC051, AM_READ_WRITE, 0, 0, SwitchTEXTR, SwitchTEXTW },
187         { 0xC052, 0xC053, AM_READ_WRITE, 0, 0, SwitchMIXEDR, SwitchMIXEDW },
188         { 0xC054, 0xC055, AM_READ_WRITE, 0, 0, SwitchPAGE2R, SwitchPAGE2W },
189         { 0xC056, 0xC057, AM_READ_WRITE, 0, 0, SwitchHIRESR, SwitchHIRESW },
190         { 0xC05E, 0xC05F, AM_READ_WRITE, 0, 0, SwitchDHIRESR, SwitchDHIRESW },
191         // $C060 is Cassette IN.  No idea what it reads with N/C
192         { 0xC060, 0xC060, AM_READ, 0, 0, ReadCassetteIn, 0 },
193         { 0xC061, 0xC061, AM_READ, 0, 0, ReadButton0, 0 },
194         { 0xC062, 0xC062, AM_READ, 0, 0, ReadButton1, 0 },
195         { 0xC064, 0xC067, AM_READ, 0, 0, ReadPaddle0, 0 },
196         { 0xC07E, 0xC07E, AM_READ_WRITE, 0, 0, ReadIOUDIS, SwitchIOUDIS },
197         { 0xC07F, 0xC07F, AM_READ_WRITE, 0, 0, ReadDHIRES, SwitchIOUDIS },
198         { 0xC080, 0xC08F, AM_READ_WRITE, 0, 0, SwitchLCR, SwitchLCW },
199
200         { 0xC100, 0xC7FF, AM_READ_WRITE, 0, 0, SlotR, SlotW },
201         { 0xC800, 0xCFFE, AM_READ_WRITE, 0, 0, Slot2KR, Slot2KW },
202         { 0xCFFF, 0xCFFF, AM_READ_WRITE, 0, 0, SwitchINTC8ROMR, SwitchINTC8ROMW },
203
204         { 0xD000, 0xDFFF, AM_BANKED, &lcBankMemoryR, &lcBankMemoryW, 0, 0 },
205         { 0xE000, 0xFFFF, AM_BANKED, &upperMemoryR, &upperMemoryW, 0, 0 },
206         ADDRESS_MAP_END
207 };
208 /*
209 Some stuff that may be useful:
210
211 N.B.: Page 5-22 of UTA2E has INTC8ROM ON/OFF backwards
212 INTC8ROM is turned OFF by R/W access to $CFFF
213 INTC8ROM is turned ON by $C3xx access and SLOTC3ROM' (off)
214 WRONG: (INTC8ROM on puts card's slot ROM/RAM(?) access in $C800-$CFFF)
215
216 OK, so it's slightly more complex than that.  Basically, when there is an access to $CFFF, all peripheral cards must *stop* responding to  I/O STROBE'.  Only when a card gets an I/O SELECT' signal, can it respond to I/O STROBE'.
217
218 INTC8ROM inhibits I/O STROBE' and activates the MB ROM in $C800-$CFFF
219 INTC8ROM is 1 by access to $C3xx when SLOTC3ROM is 0
220 INTC8ROM is 0 by access to $CFFF
221
222 ICX = INTCXROM (aka SLOTCXROM), SC3 = SLOTC3ROM
223
224              ICX=0,SC3=0  ICX=0,SC3=1  ICX=1,SC3=0  ICX=1,SC3=1
225 $C100-$C2FF   slot         slot         internal     internal
226 $C300-$C3FF   internal     slot         internal     internal
227 $C400-$CFFF   slot         slot         internal     internal
228
229 Read from $C800-$CFFF causes I/O STROBE to go low (and INTCXROM and INTC8ROM are not set)
230
231 */
232
233 void SetupAddressMap(void)
234 {
235         for(uint32_t i=0; i<0x10000; i++)
236         {
237                 funcMapRead[i] = ReadNOP;
238                 funcMapWrite[i] = WriteNOP;
239                 addrPtrRead[i] = 0;
240                 addrPtrWrite[i] = 0;
241                 addrOffset[i] = 0;
242         }
243
244         for(uint32_t i=0; i<8; i++)
245         {
246                 slotHandlerR[i] = ReadNOP;
247                 slotHandlerW[i] = WriteNOP;
248                 slotHandler2KR[i] = ReadNOP;
249                 slotHandler2KW[i] = WriteNOP;
250         }
251
252         uint32_t i=0;
253
254         while (memoryMap[i].type != AM_END_OF_LIST)
255         {
256                 switch (memoryMap[i].type)
257                 {
258                 case AM_RAM:
259                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
260                         {
261                                 funcMapRead[j] = ReadMemory;
262                                 funcMapWrite[j] = WriteMemory;
263                                 addrPtrRead[j] = memoryMap[i].memory;
264                                 addrPtrWrite[j] = memoryMap[i].memory;
265                                 addrOffset[j] = j - memoryMap[i].start;
266 //WriteLog("SetupAddressMap: j=$%04X, addrOffset[j]=$%04X\n", j, addrOffset[j]);
267                         }
268
269                         break;
270                 case AM_ROM:
271                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
272                         {
273                                 funcMapRead[j] = ReadMemory;
274                                 addrPtrRead[j] = memoryMap[i].memory;
275                                 addrOffset[j] = j - memoryMap[i].start;
276                         }
277
278                         break;
279                 case AM_BANKED:
280                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
281                         {
282                                 funcMapRead[j] = ReadMemory;
283                                 funcMapWrite[j] = WriteMemory;
284                                 addrPtrRead[j] = memoryMap[i].memory;
285                                 addrPtrWrite[j] = memoryMap[i].altMemory;
286                                 addrOffset[j] = j - memoryMap[i].start;
287                         }
288
289                         break;
290                 case AM_READ:
291                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
292                                 funcMapRead[j] = memoryMap[i].read;
293
294                         break;
295                 case AM_WRITE:
296                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
297                                 funcMapWrite[j] = memoryMap[i].write;
298
299                         break;
300                 case AM_READ_WRITE:
301                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
302                         {
303                                 funcMapRead[j] = memoryMap[i].read;
304                                 funcMapWrite[j] = memoryMap[i].write;
305                         }
306
307                         break;
308                 }
309
310                 i++;
311         };
312
313         // This should correctly set up the LC pointers, but it doesn't
314         // for some reason... :-/
315         // It's because we were storing pointers directly, instead of pointers
316         // to the pointer... It's complicated. :-)
317         SwitchLC();
318 }
319
320 //
321 // Reset the MMU state after a power down event
322 //
323 void ResetMMUPointers(void)
324 {
325         if (store80Mode)
326         {
327                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
328                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
329                 mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
330                 mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
331         }
332         else
333         {
334 // Shouldn't mainMemoryTextR depend on ramrd???  (I think it should...)
335                 mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
336                 mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
337                 mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
338                 mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
339         }
340
341         mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]);
342         mainMemoryW = (ramwrt ?  &ram2[0x0200] : &ram[0x0200]);
343 //      mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
344 //      mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
345
346 //      slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]);
347 //      slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
348         pageZeroMemory = (altzp ? &ram2[0x0000] : &ram[0x0000]);
349         SwitchLC();
350 #if 1
351 WriteLog("RAMWRT = %s\n", (ramwrt ? "ON" : "off"));
352 WriteLog("RAMRD = %s\n", (ramrd ? "ON" : "off"));
353 WriteLog("SLOTCXROM = %s\n", (intCXROM ? "ON" : "off"));
354 WriteLog("SLOTC3ROM = %s\n", (slotC3ROM ? "ON" : "off"));
355 WriteLog("ALTZP = %s\n", (altzp ? "ON" : "off"));
356 #endif
357 }
358
359 //
360 // Set up slot access
361 //
362 void InstallSlotHandler(uint8_t slot, SlotData * slotData)
363 {
364         // Sanity check
365         if (slot > 7)
366         {
367                 WriteLog("InstallSlotHandler: Caller attempted to put device into slot #%u...\n", slot);
368                 return;
369         }
370
371         // Set up I/O read & write functions
372         for(uint32_t i=0; i<16; i++)
373         {
374                 if (slotData->ioR)
375                         funcMapRead[0xC080 + (slot * 16) + i] = slotData->ioR;
376
377                 if (slotData->ioW)
378                         funcMapWrite[0xC080 + (slot * 16) + i] = slotData->ioW;
379         }
380
381         // Set up memory access read/write functions
382         if (slotData->pageR)
383                 slotHandlerR[slot] = slotData->pageR;
384
385         if (slotData->pageW)
386                 slotHandlerW[slot] = slotData->pageW;
387
388         if (slotData->extraR)
389                 slotHandler2KR[slot] = slotData->extraR;
390
391         if (slotData->extraW)
392                 slotHandler2KW[slot] = slotData->extraW;
393 /*
394 Was thinking about how to make these things more self-contained, so that the management overhead would be less.  IOW, you should be able to make an object (struct) that holds everything needed to interface the MMU with itself--InstallSlotHandler *almost* does this, but not quite.  A consequence of this approach is that we would have to add generic slot I/O handlers into the mix, but that shouldn't be too horrible.  So it could be something like so:
395
396 struct Card
397 {
398         void * object;
399         uint16_t type;  // Probably an enum so we can figure out what 'object' is
400         READFUNC(slotIOR);
401         WRITEFUNC(slotIOW);
402         READFUNC(slotPageR);
403         WRITEFUNC(slotPageW);
404         READFUNC(slot2KR);
405         WRITEFUNC(slot2KW);
406 }
407
408 So instead of a bunch of crappy shit that sucks in here, we would have a simple thing like:
409
410 Card * slots[8];
411
412 to encapsulate slots.  This also makes it easier to move them around and makes things less error prone.
413
414 So maybe...
415
416
417 */
418 }
419
420 //
421 // Built-in functions
422 //
423 uint8_t ReadNOP(uint16_t)
424 {
425         // This is for unconnected reads, and some software looks at addresses like
426         // these.  In particular, Mr. Robot and His Robot Factory failed in that it
427         // was looking at the first byte of each slots 256 byte driver space and
428         // failing if it saw a zero there.  Now I have no idea what happens in the
429         // real hardware, but I suspect it would return something that looks like
430         // ReadFloatingBus().
431         return 0xFF;
432 }
433
434 void WriteNOP(uint16_t, uint8_t)
435 {
436 }
437
438 uint8_t ReadMemory(uint16_t address)
439 {
440 //WriteLog("ReadMemory: addr=$%04X, addrPtrRead[addr]=$%X, addrOffset[addr]=$%X, val=$%02X\n", address, addrPtrRead[address], addrOffset[address], addrPtrRead[address][addrOffset[address]]);
441         // We are guaranteed a valid address here by the setup function, so there's
442         // no need to do any checking here.
443         return (*addrPtrRead[address])[addrOffset[address]];
444 }
445
446 void WriteMemory(uint16_t address, uint8_t byte)
447 {
448         // We can write protect memory this way, but it adds a branch to the mix.
449         // :-/ (this can be avoided by setting up another bank of memory which we
450         //  ignore... hmm...)
451         if ((*addrPtrWrite[address]) == 0)
452                 return;
453
454         (*addrPtrWrite[address])[addrOffset[address]] = byte;
455 }
456
457 //
458 // The main memory access functions used by V65C02
459 //
460 uint8_t AppleReadMem(uint16_t address)
461 {
462 #if 0
463 if (address == 0xD4 || address == 0xAC20)
464         WriteLog("Reading $%X...\n", address);
465 #endif
466 #if 0
467         uint8_t memRead = (*(funcMapRead[address]))(address);
468 static uint16_t lastAddr = 0;
469 static uint32_t lastCount = 0;
470 if ((address > 0xC000 && address < 0xC100) || address == 0xC601)
471 {
472         if (lastAddr == address)
473                 lastCount++;
474         else
475         {
476                 if (lastCount > 1)
477                         WriteLog("%d times...\n", lastCount);
478
479                 WriteLog("Reading $%02X from $%X ($%02X, $%02X)\n", memRead, address, diskROM[1], rom[0xC601]);
480                 lastCount = 1;
481                 lastAddr = address;
482         }
483 }
484         return memRead;
485 #else
486         return (*(funcMapRead[address]))(address);
487 #endif
488 }
489
490 void AppleWriteMem(uint16_t address, uint8_t byte)
491 {
492 #if 0
493 static uint16_t lastAddr = 0;
494 static uint32_t lastCount = 0;
495 if ((address > 0xC000 && address < 0xC100) || address == 0xC601)
496 {
497         if (lastAddr == address)
498                 lastCount++;
499         else
500         {
501                 if (lastCount > 1)
502                         WriteLog("%d times...\n", lastCount);
503
504                 WriteLog("Writing to $%X\n", address);
505                 lastCount = 1;
506                 lastAddr = address;
507         }
508 }
509 #endif
510 #if 0
511 if (address == 0xD4 || address == 0xAC20)
512         WriteLog("Writing $%02X @ $%X...\n", byte, address);
513 #endif
514 #if 0
515 //if (address >= 0x0827 && address <= 0x082A)
516 if (address == 0x000D)
517         WriteLog("Writing $%02X @ $%X (PC=$%04X)...\n", byte, address, mainCPU.pc);
518 #endif
519         (*(funcMapWrite[address]))(address, byte);
520 }
521
522 //
523 // Generic slot handlers.  These are set up here so that we can catch INTCXROM,
524 // INTC8ROM & SLOTC3ROM here instead of having to catch them in each slot handler.
525 //
526 uint8_t SlotR(uint16_t address)
527 {
528 //WriteLog("SlotR: address=$%04X, intCXROM=%d, slotC3ROM=%d, intC8ROM=%d\n", address, intCXROM, slotC3ROM, intC8ROM);
529         if (intCXROM)
530                 return rom[address];
531
532         uint8_t slot = (address & 0xF00) >> 8;
533         enabledSlot = slot;
534
535         if ((slotC3ROM == 0) && (slot == 3))
536         {
537                 intC8ROM = 1;
538                 return rom[address];
539         }
540
541         return (*(slotHandlerR[slot]))(address & 0xFF);
542 }
543
544 void SlotW(uint16_t address, uint8_t byte)
545 {
546         if (intCXROM)
547                 return;
548
549         uint8_t slot = (address & 0xF00) >> 8;
550         enabledSlot = slot;
551
552         if ((slotC3ROM == 0) && (slot == 3))
553         {
554                 intC8ROM = 1;
555                 return;
556         }
557
558         (*(slotHandlerW[slot]))(address & 0xFF, byte);
559 }
560
561 //
562 // Slot handling for 2K address space at $C800-$CFFF
563 //
564 uint8_t Slot2KR(uint16_t address)
565 {
566         if (intCXROM || intC8ROM)
567                 return rom[address];
568
569         return (*(slotHandler2KR[enabledSlot]))(address & 0x7FF);
570 }
571
572 void Slot2KW(uint16_t address, uint8_t byte)
573 {
574         if (intCXROM || intC8ROM)
575                 return;
576
577         (*(slotHandler2KW[enabledSlot]))(address & 0x7FF, byte);
578 }
579
580 //
581 // Actual emulated I/O functions follow
582 //
583 uint8_t ReadKeyboard(uint16_t /*addr*/)
584 {
585         return lastKeyPressed | ((uint8_t)keyDown << 7);
586 }
587
588 void Switch80STORE(uint16_t address, uint8_t)
589 {
590         store80Mode = (bool)(address & 0x01);
591 WriteLog("Setting 80STORE to %s...\n", (store80Mode ? "ON" : "off"));
592
593         // It seems this affects more than just the text RAM, it also seems to affect the graphics RAM as well...
594         if (store80Mode)
595         {
596                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
597                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
598                 mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
599                 mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
600         }
601         else
602         {
603                 mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
604                 mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
605                 mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
606                 mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
607         }
608 }
609
610 void SwitchRAMRD(uint16_t address, uint8_t)
611 {
612         ramrd = (bool)(address & 0x01);
613         mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]);
614 //      mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
615
616         if (store80Mode)
617                 return;
618
619         mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
620         mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
621 }
622
623 void SwitchRAMWRT(uint16_t address, uint8_t)
624 {
625         ramwrt = (bool)(address & 0x01);
626         mainMemoryW = (ramwrt ?  &ram2[0x0200] : &ram[0x0200]);
627 //      mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
628
629         if (store80Mode)
630                 return;
631
632         mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
633         mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
634 }
635
636 //
637 // Since any slots that aren't populated are set to read from the ROM anyway,
638 // we only concern ourselves with switching populated slots here.  (Note that
639 // the MB slot is a split ROM / I/O device, and it's taken care of in the
640 // MB handler.)
641 //
642 // N.B.: SLOTCXROM is also INTCXROM
643 //
644 void SwitchSLOTCXROM(uint16_t address, uint8_t)
645 {
646 WriteLog("Setting SLOTCXROM to %s...\n", (address & 0x01 ? "ON" : "off"));
647         intCXROM = (bool)(address & 0x01);
648
649         // INTC8ROM trumps all (only in the $C800--$CFFF range... which we don't account for yet...  :-/)
650 //      if (intC8ROM)
651 //              return;
652 #if 0
653 #if 1
654         if (intCXROM)
655         {
656                 slot3Memory = &rom[0xC300];
657                 slot6Memory = &rom[0xC600];
658         }
659         else
660         {
661                 slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
662                 slot6Memory = &diskROM[0];
663         }
664 #else
665 //      slot3Memory = (intCXROM ? &rom[0xC300] : &rom[0]);
666         slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]);
667 #endif
668 #endif
669 }
670
671 void SwitchALTZP(uint16_t address, uint8_t)
672 {
673         altzp = (bool)(address & 0x01);
674         pageZeroMemory = (altzp ? &ram2[0x0000] : &ram[0x0000]);
675         SwitchLC();
676 }
677
678 //extern bool dumpDis;
679 //
680 // The interpretation of this name is that if it's set then we access the ROM
681 // for the card actually sitting in SLOT 3 (if any)
682 //
683 void SwitchSLOTC3ROM(uint16_t address, uint8_t)
684 {
685 //dumpDis = true;
686 //WriteLog("Setting SLOTC3ROM to %s...\n", (address & 0x01 ? "ON" : "off"));
687         slotC3ROM = (bool)(address & 0x01);
688 #if 1
689         if (intCXROM)
690                 slot3Memory = &rom[0xC300];
691         else
692                 slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
693 #else
694 //      slotC3ROM = false;
695 // Seems the h/w forces this with an 80 column card in slot 3...
696         slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
697 //      slot3Memory = &rom[0xC300];
698 #endif
699 }
700
701 /*
702 We need to see where this is being switched from; if we know that, we can switch in the appropriate ROM to $C800-$CFFF.  N.B.: Will probably need a custom handler routine, as some cards (like the Apple Hi-Speed SCSI card) split the 2K range into a 1K RAM space and a 1K bank switch ROM space.
703 */
704 //
705 // This is a problem with split ROM / I/O regions.  Because we can't do that
706 // cleanly, we have to have a read handler for this.
707 //
708 // N.B.: We could add AM_IOREAD_WRITE and AM_READ_IOWRITE to the memory handlers
709 //       to take care of split ROM / I/O regions...
710 //
711 uint8_t SwitchINTC8ROMR(uint16_t)
712 {
713 WriteLog("Hitting INTC8ROM (read)...\n");
714         intC8ROM = false;
715         return rom[0xCFFF];
716 }
717
718 //
719 // This resets the INTC8ROM switch (RW)
720 //
721 void SwitchINTC8ROMW(uint16_t, uint8_t)
722 {
723 WriteLog("Hitting INTC8ROM (write)...\n");
724         intC8ROM = false;
725 }
726
727 void Switch80COL(uint16_t address, uint8_t)
728 {
729         col80Mode = (bool)(address & 0x01);
730 }
731
732 void SwitchALTCHARSET(uint16_t address, uint8_t)
733 {
734         alternateCharset = (bool)(address & 0x01);
735 WriteLog("Setting ALTCHARSET to %s...\n", (alternateCharset ? "ON" : "off"));
736 }
737
738 uint8_t ReadKeyStrobe(uint16_t)
739 {
740         // No character data is read from here, just the 'any key was pressed'
741         // signal...
742         uint8_t byte = (uint8_t)keyDown << 7;
743         keyDown = false;
744         return byte;
745 }
746
747 uint8_t ReadBANK2(uint16_t)
748 {
749         return (lcState < 0x04 ? 0x80 : 0x00);
750 }
751
752 uint8_t ReadLCRAM(uint16_t)
753 {
754         // If bits 0 & 1 are set, but not at the same time, then it's ROM
755         uint8_t lcROM = (lcState & 0x1) ^ ((lcState & 0x02) >> 1);
756         return (lcROM ? 0x00 : 0x80);
757 }
758
759 uint8_t ReadRAMRD(uint16_t)
760 {
761         return (uint8_t)ramrd << 7;
762 }
763
764 uint8_t ReadRAMWRT(uint16_t)
765 {
766         return (uint8_t)ramwrt << 7;
767 }
768
769 uint8_t ReadSLOTCXROM(uint16_t)
770 {
771         return (uint8_t)intCXROM << 7;
772 }
773
774 uint8_t ReadALTZP(uint16_t)
775 {
776         return (uint8_t)altzp << 7;
777 }
778
779 uint8_t ReadSLOTC3ROM(uint16_t)
780 {
781         return (uint8_t)slotC3ROM << 7;
782 }
783
784 uint8_t Read80STORE(uint16_t)
785 {
786         return (uint8_t)store80Mode << 7;
787 }
788
789 uint8_t ReadVBL(uint16_t)
790 {
791         return (uint8_t)vbl << 7;
792 }
793
794 uint8_t ReadTEXT(uint16_t)
795 {
796         return (uint8_t)textMode << 7;
797 }
798
799 uint8_t ReadMIXED(uint16_t)
800 {
801         return (uint8_t)mixedMode << 7;
802 }
803
804 uint8_t ReadPAGE2(uint16_t)
805 {
806         return (uint8_t)displayPage2 << 7;
807 }
808
809 uint8_t ReadHIRES(uint16_t)
810 {
811         return (uint8_t)hiRes << 7;
812 }
813
814 uint8_t ReadALTCHARSET(uint16_t)
815 {
816         return (uint8_t)alternateCharset << 7;
817 }
818
819 uint8_t Read80COL(uint16_t)
820 {
821         return (uint8_t)col80Mode << 7;
822 }
823
824 void WriteKeyStrobe(uint16_t, uint8_t)
825 {
826         keyDown = false;
827 }
828
829 uint8_t ReadSpeaker(uint16_t)
830 {
831         ToggleSpeaker();
832         return 0;
833 }
834
835 void WriteSpeaker(uint16_t, uint8_t)
836 {
837         ToggleSpeaker();
838 }
839
840 uint8_t SwitchLCR(uint16_t address)
841 {
842         lcState = address & 0x0B;
843         SwitchLC();
844         return 0;
845 }
846
847 void SwitchLCW(uint16_t address, uint8_t)
848 {
849         lcState = address & 0x0B;
850         SwitchLC();
851 }
852
853 void SwitchLC(void)
854 {
855         switch (lcState)
856         {
857         case 0x00:
858 #ifdef LC_DEBUG
859 WriteLog("SwitchLC: Read RAM bank 2, no write\n");
860 #endif
861                 // [R ] Read RAM bank 2; no write
862                 lcBankMemoryR = (altzp ? &ram2[0xD000] : &ram[0xD000]);
863                 lcBankMemoryW = 0;
864                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
865                 upperMemoryW = 0;
866                 break;
867         case 0x01:
868 #ifdef LC_DEBUG
869 WriteLog("SwitchLC: Read ROM, write bank 2\n");
870 #endif
871                 // [RR] Read ROM; write RAM bank 2
872                 lcBankMemoryR = &rom[0xD000];
873                 lcBankMemoryW = (altzp ? &ram2[0xD000] : &ram[0xD000]);
874                 upperMemoryR = &rom[0xE000];
875                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
876                 break;
877         case 0x02:
878 #ifdef LC_DEBUG
879 WriteLog("SwitchLC: Read ROM, no write\n");
880 #endif
881                 // [R ] Read ROM; no write
882                 lcBankMemoryR = &rom[0xD000];
883                 lcBankMemoryW = 0;
884                 upperMemoryR = &rom[0xE000];
885                 upperMemoryW = 0;
886                 break;
887         case 0x03:
888 #ifdef LC_DEBUG
889 WriteLog("SwitchLC: Read/write bank 2\n");
890 #endif
891                 // [RR] Read RAM bank 2; write RAM bank 2
892                 lcBankMemoryR = (altzp ? &ram2[0xD000] : &ram[0xD000]);
893                 lcBankMemoryW = (altzp ? &ram2[0xD000] : &ram[0xD000]);
894                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
895                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
896                 break;
897         case 0x08:
898                 // [R ] Read RAM bank 1; no write
899                 lcBankMemoryR = (altzp ? &ram2[0xC000] : &ram[0xC000]);
900                 lcBankMemoryW = 0;
901                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
902                 upperMemoryW = 0;
903                 break;
904         case 0x09:
905                 // [RR] Read ROM; write RAM bank 1
906                 lcBankMemoryR = &rom[0xD000];
907                 lcBankMemoryW = (altzp ? &ram2[0xC000] : &ram[0xC000]);
908                 upperMemoryR = &rom[0xE000];
909                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
910                 break;
911         case 0x0A:
912                 // [R ] Read ROM; no write
913                 lcBankMemoryR = &rom[0xD000];
914                 lcBankMemoryW = 0;
915                 upperMemoryR = &rom[0xE000];
916                 upperMemoryW = 0;
917                 break;
918         case 0x0B:
919                 // [RR] Read RAM bank 1; write RAM bank 1
920                 lcBankMemoryR = (altzp ? &ram2[0xC000] : &ram[0xC000]);
921                 lcBankMemoryW = (altzp ? &ram2[0xC000] : &ram[0xC000]);
922                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
923                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
924                 break;
925         }
926 }
927
928 uint8_t SwitchTEXTR(uint16_t address)
929 {
930 WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off"));
931         textMode = (bool)(address & 0x01);
932         return 0;
933 }
934
935 void SwitchTEXTW(uint16_t address, uint8_t)
936 {
937 WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off"));
938         textMode = (bool)(address & 0x01);
939 }
940
941 uint8_t SwitchMIXEDR(uint16_t address)
942 {
943 WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off"));
944         mixedMode = (bool)(address & 0x01);
945         return 0;
946 }
947
948 void SwitchMIXEDW(uint16_t address, uint8_t)
949 {
950 WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off"));
951         mixedMode = (bool)(address & 0x01);
952 }
953
954 uint8_t SwitchPAGE2R(uint16_t address)
955 {
956 WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
957         displayPage2 = (bool)(address & 0x01);
958
959         if (store80Mode)
960         {
961                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
962                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
963                 mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
964                 mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
965         }
966
967         return 0;
968 }
969
970 void SwitchPAGE2W(uint16_t address, uint8_t)
971 {
972 WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
973         displayPage2 = (bool)(address & 0x01);
974
975         if (store80Mode)
976         {
977                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
978                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
979                 mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
980                 mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
981         }
982 }
983
984 uint8_t SwitchHIRESR(uint16_t address)
985 {
986 WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off"));
987         hiRes = (bool)(address & 0x01);
988         return 0;
989 }
990
991 void SwitchHIRESW(uint16_t address, uint8_t)
992 {
993 WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off"));
994         hiRes = (bool)(address & 0x01);
995 }
996
997 uint8_t SwitchDHIRESR(uint16_t address)
998 {
999 WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off"));
1000         // Hmm, this breaks convention too, like SLOTCXROM
1001         if (ioudis)
1002                 dhires = !((bool)(address & 0x01));
1003
1004         return 0;
1005 }
1006
1007 void SwitchDHIRESW(uint16_t address, uint8_t)
1008 {
1009 WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off"));
1010         if (ioudis)
1011                 dhires = !((bool)(address & 0x01));
1012 }
1013
1014 void SwitchIOUDIS(uint16_t address, uint8_t)
1015 {
1016         ioudis = !((bool)(address & 0x01));
1017 }
1018
1019 uint8_t ReadCassetteIn(uint16_t)
1020 {
1021         // No idea what it's supposed to return if there's no cassette attached, so let's try this (Serpentine crashes if $FF is returned...)
1022         // Serpentine crashes even earlier if $00 is returned...  Now what???
1023         // This seems to work for Serpentine, not sure what's supposed to be returned here...  Maybe it's an RNG when N/C?
1024         return 0x5A;
1025 }
1026
1027 uint8_t ReadButton0(uint16_t)
1028 {
1029         return (uint8_t)openAppleDown << 7;
1030 }
1031
1032 uint8_t ReadButton1(uint16_t)
1033 {
1034         return (uint8_t)closedAppleDown << 7;
1035 }
1036
1037 // The way the paddles work is that a strobe is written (or read) to $C070,
1038 // then software counts down the time that it takes for the paddle outputs
1039 // to have bit 7 return to 0. If there are no paddles connected, bit 7
1040 // stays at 1.
1041 // NB: This is really paddles 0-3, not just 0 :-P
1042 uint8_t ReadPaddle0(uint16_t)
1043 {
1044         return 0xFF;
1045 }
1046
1047 uint8_t ReadIOUDIS(uint16_t)
1048 {
1049         return (uint8_t)ioudis << 7;
1050 }
1051
1052 uint8_t ReadDHIRES(uint16_t)
1053 {
1054         return (uint8_t)dhires << 7;
1055 }
1056
1057 // Whenever a read is done to a MMIO location that is unconnected to anything,
1058 // it actually sees the RAM access done by the video generation hardware. Some
1059 // programs exploit this, so we emulate it here.
1060
1061 // N.B.: frameCycles will be off by the true amount because this only
1062 //       increments by the amount of a speaker cycle, not the cycle count when
1063 //       the access happens... !!! FIX !!!
1064 uint8_t ReadFloatingBus(uint16_t)
1065 {
1066         // Get the currently elapsed cycle count for this frame
1067         uint32_t frameCycles = mainCPU.clock - frameCycleStart;
1068
1069         // Make counters out of the cycle count. There are 65 cycles per line.
1070         uint32_t numLines = frameCycles / 65;
1071         uint32_t numHTicks = frameCycles - (numLines * 65);
1072
1073         // Convert these to H/V counters
1074         uint32_t hcount = numHTicks - 1;
1075
1076         // HC sees zero twice:
1077         if (hcount == 0xFFFFFFFF)
1078                 hcount = 0;
1079
1080         uint32_t vcount = numLines + 0xFA;
1081
1082         // Now do the address calculations
1083         uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
1084                 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
1085         uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
1086
1087         // Add in particulars for the gfx mode we're in...
1088         if (textMode || (!textMode && !hiRes))
1089                 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
1090                         | (!store80Mode && displayPage2 ? 0x800 : 0);
1091         else
1092                 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
1093                         | (!store80Mode && displayPage2 ? 0x4000 : 0)
1094                         | ((vcount & 0x07) << 10);
1095
1096         // The address so read is *always* in main RAM, never alt RAM
1097         return ram[address];
1098 }