1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "System.hxx"
19#include "AudioSettings.hxx"
20#include "CartDPC.hxx"
21
22// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
23CartridgeDPC::CartridgeDPC(const ByteBuffer& image, size_t size,
24 const string& md5, const Settings& settings)
25 : Cartridge(settings, md5),
26 mySize(size),
27 myAudioCycles(0),
28 myFractionalClocks(0.0),
29 myBankOffset(0)
30{
31 // Make a copy of the entire image
32 std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin());
33 createCodeAccessBase(8_KB);
34
35 // Pointer to the program ROM (8K @ 0 byte offset)
36 myProgramImage = myImage.data();
37
38 // Pointer to the display ROM (2K @ 8K offset)
39 myDisplayImage = myProgramImage + 8_KB;
40
41 // Initialize the DPC data fetcher registers
42 myTops.fill(0);
43 myBottoms.fill(0);
44 myFlags.fill(0);
45 myCounters.fill(0);
46
47 // None of the data fetchers are in music mode
48 myMusicMode.fill(false);
49
50 // Initialize the DPC's random number generator register (must be non-zero)
51 myRandomNumber = 1;
52}
53
54// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
55void CartridgeDPC::reset()
56{
57 myAudioCycles = 0;
58 myFractionalClocks = 0.0;
59
60 // Upon reset we switch to the startup bank
61 initializeStartBank(1);
62 bank(startBank());
63
64 myDpcPitch = mySettings.getInt(AudioSettings::SETTING_DPC_PITCH);
65}
66
67// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
68void CartridgeDPC::install(System& system)
69{
70 mySystem = &system;
71
72 // Set the page accessing method for the DPC reading & writing pages
73 System::PageAccess access(this, System::PageAccessType::READWRITE);
74 for(uInt16 addr = 0x1000; addr < 0x1080; addr += System::PAGE_SIZE)
75 mySystem->setPageAccess(addr, access);
76
77 // Install pages for the startup bank
78 bank(startBank());
79}
80
81// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
82inline void CartridgeDPC::clockRandomNumberGenerator()
83{
84 // Table for computing the input bit of the random number generator's
85 // shift register (it's the NOT of the EOR of four bits)
86 static constexpr uInt8 f[16] = {
87 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
88 };
89
90 // Using bits 7, 5, 4, & 3 of the shift register compute the input
91 // bit for the shift register
92 uInt8 bit = f[((myRandomNumber >> 3) & 0x07) |
93 ((myRandomNumber & 0x80) ? 0x08 : 0x00)];
94
95 // Update the shift register
96 myRandomNumber = (myRandomNumber << 1) | bit;
97}
98
99// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
100inline void CartridgeDPC::updateMusicModeDataFetchers()
101{
102 // Calculate the number of cycles since the last update
103 uInt32 cycles = uInt32(mySystem->cycles() - myAudioCycles);
104 myAudioCycles = mySystem->cycles();
105
106 // Calculate the number of DPC OSC clocks since the last update
107 double clocks = ((myDpcPitch * cycles) / 1193191.66666667) + myFractionalClocks;
108 uInt32 wholeClocks = uInt32(clocks);
109 myFractionalClocks = clocks - double(wholeClocks);
110
111 if(wholeClocks <= 0)
112 return;
113
114 // Let's update counters and flags of the music mode data fetchers
115 for(int x = 5; x <= 7; ++x)
116 {
117 // Update only if the data fetcher is in music mode
118 if(myMusicMode[x - 5])
119 {
120 Int32 top = myTops[x] + 1;
121 Int32 newLow = Int32(myCounters[x] & 0x00ff);
122
123 if(myTops[x] != 0)
124 {
125 newLow -= (wholeClocks % top);
126 if(newLow < 0)
127 newLow += top;
128 }
129 else
130 newLow = 0;
131
132 // Update flag register for this data fetcher
133 if(newLow <= myBottoms[x])
134 myFlags[x] = 0x00;
135 else if(newLow <= myTops[x])
136 myFlags[x] = 0xff;
137
138 myCounters[x] = (myCounters[x] & 0x0700) | uInt16(newLow);
139 }
140 }
141}
142
143// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
144uInt8 CartridgeDPC::peek(uInt16 address)
145{
146 address &= 0x0FFF;
147
148 // In debugger/bank-locked mode, we ignore all hotspots and in general
149 // anything that can change the internal state of the cart
150 if(bankLocked())
151 return myProgramImage[myBankOffset + address];
152
153 // Clock the random number generator. This should be done for every
154 // cartridge access, however, we're only doing it for the DPC and
155 // hot-spot accesses to save time.
156 clockRandomNumberGenerator();
157
158 if(address < 0x0040)
159 {
160 uInt8 result = 0;
161
162 // Get the index of the data fetcher that's being accessed
163 uInt32 index = address & 0x07;
164 uInt32 function = (address >> 3) & 0x07;
165
166 // Update flag register for selected data fetcher
167 if((myCounters[index] & 0x00ff) == myTops[index])
168 {
169 myFlags[index] = 0xff;
170 }
171 else if((myCounters[index] & 0x00ff) == myBottoms[index])
172 {
173 myFlags[index] = 0x00;
174 }
175
176 switch(function)
177 {
178 case 0x00:
179 {
180 // Is this a random number read
181 if(index < 4)
182 {
183 result = myRandomNumber;
184 }
185 // No, it's a music read
186 else
187 {
188 static constexpr uInt8 musicAmplitudes[8] = {
189 0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f
190 };
191
192 // Update the music data fetchers (counter & flag)
193 updateMusicModeDataFetchers();
194
195 uInt8 i = 0;
196 if(myMusicMode[0] && myFlags[5])
197 {
198 i |= 0x01;
199 }
200 if(myMusicMode[1] && myFlags[6])
201 {
202 i |= 0x02;
203 }
204 if(myMusicMode[2] && myFlags[7])
205 {
206 i |= 0x04;
207 }
208
209 result = musicAmplitudes[i];
210 }
211 break;
212 }
213
214 // DFx display data read
215 case 0x01:
216 {
217 result = myDisplayImage[2047 - myCounters[index]];
218 break;
219 }
220
221 // DFx display data read AND'd w/flag
222 case 0x02:
223 {
224 result = myDisplayImage[2047 - myCounters[index]] & myFlags[index];
225 break;
226 }
227
228 // DFx flag
229 case 0x07:
230 {
231 result = myFlags[index];
232 break;
233 }
234
235 default:
236 {
237 result = 0;
238 }
239 }
240
241 // Clock the selected data fetcher's counter if needed
242 if(index < 5 || !myMusicMode[index - 5])
243 {
244 myCounters[index] = (myCounters[index] - 1) & 0x07ff;
245 }
246
247 return result;
248 }
249 else
250 {
251 // Switch banks if necessary
252 switch(address)
253 {
254 case 0x0FF8:
255 // Set the current bank to the lower 4k bank
256 bank(0);
257 break;
258
259 case 0x0FF9:
260 // Set the current bank to the upper 4k bank
261 bank(1);
262 break;
263
264 default:
265 break;
266 }
267 return myProgramImage[myBankOffset + address];
268 }
269}
270
271// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
272bool CartridgeDPC::poke(uInt16 address, uInt8 value)
273{
274 address &= 0x0FFF;
275
276 // Clock the random number generator. This should be done for every
277 // cartridge access, however, we're only doing it for the DPC and
278 // hot-spot accesses to save time.
279 clockRandomNumberGenerator();
280
281 if((address >= 0x0040) && (address < 0x0080))
282 {
283 // Get the index of the data fetcher that's being accessed
284 uInt32 index = address & 0x07;
285 uInt32 function = (address >> 3) & 0x07;
286
287 switch(function)
288 {
289 // DFx top count
290 case 0x00:
291 {
292 myTops[index] = value;
293 myFlags[index] = 0x00;
294 break;
295 }
296
297 // DFx bottom count
298 case 0x01:
299 {
300 myBottoms[index] = value;
301 break;
302 }
303
304 // DFx counter low
305 case 0x02:
306 {
307 if((index >= 5) && myMusicMode[index - 5])
308 {
309 // Data fetcher is in music mode so its low counter value
310 // should be loaded from the top register not the poked value
311 myCounters[index] = (myCounters[index] & 0x0700) |
312 uInt16(myTops[index]);
313 }
314 else
315 {
316 // Data fetcher is either not a music mode data fetcher or it
317 // isn't in music mode so it's low counter value should be loaded
318 // with the poked value
319 myCounters[index] = (myCounters[index] & 0x0700) | uInt16(value);
320 }
321 break;
322 }
323
324 // DFx counter high
325 case 0x03:
326 {
327 myCounters[index] = ((uInt16(value) & 0x07) << 8) |
328 (myCounters[index] & 0x00ff);
329
330 // Execute special code for music mode data fetchers
331 if(index >= 5)
332 {
333 myMusicMode[index - 5] = (value & 0x10);
334
335 // NOTE: We are not handling the clock source input for
336 // the music mode data fetchers. We're going to assume
337 // they always use the OSC input.
338 }
339 break;
340 }
341
342 // Random Number Generator Reset
343 case 0x06:
344 {
345 myRandomNumber = 1;
346 break;
347 }
348
349 default:
350 {
351 break;
352 }
353 }
354 }
355 else
356 {
357 // Switch banks if necessary
358 switch(address)
359 {
360 case 0x0FF8:
361 // Set the current bank to the lower 4k bank
362 bank(0);
363 break;
364
365 case 0x0FF9:
366 // Set the current bank to the upper 4k bank
367 bank(1);
368 break;
369
370 default:
371 break;
372 }
373 }
374 return false;
375}
376
377// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
378bool CartridgeDPC::bank(uInt16 bank)
379{
380 if(bankLocked()) return false;
381
382 // Remember what bank we're in
383 myBankOffset = bank << 12;
384
385 System::PageAccess access(this, System::PageAccessType::READ);
386
387 // Set the page accessing methods for the hot spots
388 for(uInt16 addr = (0x1FF8 & ~System::PAGE_MASK); addr < 0x2000;
389 addr += System::PAGE_SIZE)
390 {
391 access.codeAccessBase = &myCodeAccessBase[myBankOffset + (addr & 0x0FFF)];
392 mySystem->setPageAccess(addr, access);
393 }
394
395 // Setup the page access methods for the current bank
396 for(uInt16 addr = 0x1080; addr < (0x1FF8U & ~System::PAGE_MASK);
397 addr += System::PAGE_SIZE)
398 {
399 access.directPeekBase = &myProgramImage[myBankOffset + (addr & 0x0FFF)];
400 access.codeAccessBase = &myCodeAccessBase[myBankOffset + (addr & 0x0FFF)];
401 mySystem->setPageAccess(addr, access);
402 }
403 return myBankChanged = true;
404}
405
406// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
407uInt16 CartridgeDPC::getBank(uInt16) const
408{
409 return myBankOffset >> 12;
410}
411
412// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
413uInt16 CartridgeDPC::bankCount() const
414{
415 return 2;
416}
417
418// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
419bool CartridgeDPC::patch(uInt16 address, uInt8 value)
420{
421 address &= 0x0FFF;
422
423 // For now, we ignore attempts to patch the DPC address space
424 if(address >= 0x0080)
425 {
426 myProgramImage[myBankOffset + (address & 0x0FFF)] = value;
427 return myBankChanged = true;
428 }
429 else
430 return false;
431}
432
433// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
434const uInt8* CartridgeDPC::getImage(size_t& size) const
435{
436 size = mySize;
437 return myImage.data();
438}
439
440// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
441bool CartridgeDPC::save(Serializer& out) const
442{
443 try
444 {
445 // Indicates which bank is currently active
446 out.putShort(myBankOffset);
447
448 // The top registers for the data fetchers
449 out.putByteArray(myTops.data(), myTops.size());
450
451 // The bottom registers for the data fetchers
452 out.putByteArray(myBottoms.data(), myBottoms.size());
453
454 // The counter registers for the data fetchers
455 out.putShortArray(myCounters.data(), myCounters.size());
456
457 // The flag registers for the data fetchers
458 out.putByteArray(myFlags.data(), myFlags.size());
459
460 // The music mode flags for the data fetchers
461 for(const auto& mode: myMusicMode)
462 out.putBool(mode);
463
464 // The random number generator register
465 out.putByte(myRandomNumber);
466
467 out.putLong(myAudioCycles);
468 out.putDouble(myFractionalClocks);
469 }
470 catch(...)
471 {
472 cerr << "ERROR: CartridgeDPC::save" << endl;
473 return false;
474 }
475
476 return true;
477}
478
479// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
480bool CartridgeDPC::load(Serializer& in)
481{
482 try
483 {
484 // Indicates which bank is currently active
485 myBankOffset = in.getShort();
486
487 // The top registers for the data fetchers
488 in.getByteArray(myTops.data(), myTops.size());
489
490 // The bottom registers for the data fetchers
491 in.getByteArray(myBottoms.data(), myBottoms.size());
492
493 // The counter registers for the data fetchers
494 in.getShortArray(myCounters.data(), myCounters.size());
495
496 // The flag registers for the data fetchers
497 in.getByteArray(myFlags.data(), myFlags.size());
498
499 // The music mode flags for the data fetchers
500 for(auto& mode: myMusicMode)
501 mode = in.getBool();
502
503 // The random number generator register
504 myRandomNumber = in.getByte();
505
506 // Get system cycles and fractional clocks
507 myAudioCycles = in.getLong();
508 myFractionalClocks = in.getDouble();
509 }
510 catch(...)
511 {
512 cerr << "ERROR: CartridgeDPC::load" << endl;
513 return false;
514 }
515
516 // Now, go to the current bank
517 bank(myBankOffset >> 12);
518
519 return true;
520}
521