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 <cassert>
19
20#include "Console.hxx"
21#include "Settings.hxx"
22#include "Switches.hxx"
23#include "System.hxx"
24#ifdef DEBUGGER_SUPPORT
25 #include "CartDebug.hxx"
26#endif
27
28#include "M6532.hxx"
29
30// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
31M6532::M6532(const ConsoleIO& console, const Settings& settings)
32 : myConsole(console),
33 mySettings(settings),
34 myTimer(0), mySubTimer(0), myDivider(1),
35 myTimerWrapped(false), myWrappedThisCycle(false),
36 mySetTimerCycle(0), myLastCycle(0),
37 myDDRA(0), myDDRB(0), myOutA(0), myOutB(0),
38 myInterruptFlag(false),
39 myEdgeDetectPositive(false)
40{
41}
42
43// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
44void M6532::reset()
45{
46 static constexpr uInt8 RAM_7800[128] = {
47 0xA9, 0x00, 0xAA, 0x85, 0x01, 0x95, 0x03, 0xE8, 0xE0, 0x2A, 0xD0, 0xF9, 0x85, 0x02, 0xA9, 0x04,
48 0xEA, 0x30, 0x23, 0xA2, 0x04, 0xCA, 0x10, 0xFD, 0x9A, 0x8D, 0x10, 0x01, 0x20, 0xCB, 0x04, 0x20,
49 0xCB, 0x04, 0x85, 0x11, 0x85, 0x1B, 0x85, 0x1C, 0x85, 0x0F, 0xEA, 0x85, 0x02, 0xA9, 0x00, 0xEA,
50 0x30, 0x04, 0x24, 0x03, 0x30, 0x09, 0xA9, 0x02, 0x85, 0x09, 0x8D, 0x12, 0xF1, 0xD0, 0x1E, 0x24,
51 0x02, 0x30, 0x0C, 0xA9, 0x02, 0x85, 0x06, 0x8D, 0x18, 0xF1, 0x8D, 0x60, 0xF4, 0xD0, 0x0E, 0x85,
52 0x2C, 0xA9, 0x08, 0x85, 0x1B, 0x20, 0xCB, 0x04, 0xEA, 0x24, 0x02, 0x30, 0xD9, 0xA9, 0xFD, 0x85,
53 0x08, 0x6C, 0xFC, 0xFF, 0xEA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
54 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
55 };
56
57 // Initialize the 128 bytes of memory
58 bool devSettings = mySettings.getBool("dev.settings");
59 if(mySettings.getString(devSettings ? "dev.console" : "plr.console") == "7800")
60 std::copy_n(RAM_7800, 128, myRAM.begin());
61 else if(mySettings.getBool(devSettings ? "dev.ramrandom" : "plr.ramrandom"))
62 for(uInt32 t = 0; t < 128; ++t) myRAM[t] = mySystem->randGenerator().next();
63 else
64 myRAM.fill(0);
65
66 myTimer = mySystem->randGenerator().next() & 0xff;
67 myDivider = 1024;
68 mySubTimer = 0;
69 myTimerWrapped = false;
70 myWrappedThisCycle = false;
71
72 mySetTimerCycle = myLastCycle = 0;
73
74 // Zero the I/O registers
75 myDDRA = myDDRB = myOutA = myOutB = 0x00;
76
77 // Zero the timer registers
78 myOutTimer.fill(0x00);
79
80 // Zero the interrupt flag register and mark D7 as invalid
81 myInterruptFlag = 0x00;
82
83 // Edge-detect set to negative (high to low)
84 myEdgeDetectPositive = false;
85
86 // Let the controllers know about the reset
87 myConsole.leftController().reset();
88 myConsole.rightController().reset();
89
90#ifdef DEBUGGER_SUPPORT
91 createAccessBases();
92#endif // DEBUGGER_SUPPORT
93}
94
95// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
96void M6532::update()
97{
98 Controller& lport = myConsole.leftController();
99 Controller& rport = myConsole.rightController();
100
101 // Get current PA7 state
102 bool prevPA7 = lport.getPin(Controller::DigitalPin::Four);
103
104 // Update entire port state
105 lport.update();
106 rport.update();
107 myConsole.switches().update();
108
109 // Get new PA7 state
110 bool currPA7 = lport.getPin(Controller::DigitalPin::Four);
111
112 // PA7 Flag is set on active transition in appropriate direction
113 if((!myEdgeDetectPositive && prevPA7 && !currPA7) ||
114 (myEdgeDetectPositive && !prevPA7 && currPA7))
115 myInterruptFlag |= PA7Bit;
116}
117
118// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119void M6532::updateEmulation()
120{
121 uInt32 cycles = uInt32(mySystem->cycles() - myLastCycle);
122 uInt32 subTimer = mySubTimer;
123
124 // Guard against further state changes if the debugger alread forwarded emulation
125 // state (in particular myWrappedThisCycle)
126 if (cycles == 0) return;
127
128 myWrappedThisCycle = false;
129 mySubTimer = (cycles + mySubTimer) % myDivider;
130
131 if(!myTimerWrapped)
132 {
133 uInt32 timerTicks = (cycles + subTimer) / myDivider;
134
135 if(timerTicks > myTimer)
136 {
137 cycles -= ((myTimer + 1) * myDivider - subTimer);
138 myWrappedThisCycle = cycles == 0;
139 myTimer = 0xFF;
140 myTimerWrapped = true;
141 myInterruptFlag |= TimerBit;
142 }
143 else
144 {
145 myTimer -= timerTicks;
146 cycles = 0;
147 }
148 }
149
150 if(myTimerWrapped)
151 myTimer = (myTimer - cycles) & 0xFF;
152
153 myLastCycle = mySystem->cycles();
154}
155
156// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
157void M6532::install(System& system)
158{
159 installDelegate(system, *this);
160}
161
162// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
163void M6532::installDelegate(System& system, Device& device)
164{
165 // Remember which system I'm installed in
166 mySystem = &system;
167
168 // All accesses are to the given device
169 System::PageAccess access(&device, System::PageAccessType::READWRITE);
170
171 // Map all peek/poke to mirrors of RIOT address space to this class
172 // That is, all mirrors of ZP RAM ($80 - $FF) and IO ($280 - $29F) in the
173 // lower 4K of the 2600 address space are mapped here
174 // The two types of addresses are differentiated in peek/poke as follows:
175 // (addr & 0x0200) == 0x0200 is IO (A9 is 1)
176 // (addr & 0x0300) == 0x0100 is Stack (A8 is 1, A9 is 0)
177 // (addr & 0x0300) == 0x0000 is ZP RAM (A8 is 0, A9 is 0)
178 for (uInt16 addr = 0; addr < 0x1000; addr += System::PAGE_SIZE)
179 if ((addr & 0x0080) == 0x0080) {
180 mySystem->setPageAccess(addr, access);
181 }
182}
183
184// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
185uInt8 M6532::peek(uInt16 addr)
186{
187 updateEmulation();
188
189 // A9 distinguishes I/O registers from ZP RAM
190 // A9 = 1 is read from I/O
191 // A9 = 0 is read from RAM
192 if((addr & 0x0200) == 0x0000)
193 return myRAM[addr & 0x007f];
194
195 switch(addr & 0x07)
196 {
197 case 0x00: // SWCHA - Port A I/O Register (Joystick)
198 {
199 uInt8 value = (myConsole.leftController().read() << 4) |
200 myConsole.rightController().read();
201
202 // Each pin is high (1) by default and will only go low (0) if either
203 // (a) External device drives the pin low
204 // (b) Corresponding bit in SWACNT = 1 and SWCHA = 0
205 // Thanks to A. Herbert for this info
206 return (myOutA | ~myDDRA) & value;
207 }
208
209 case 0x01: // SWACNT - Port A Data Direction Register
210 {
211 return myDDRA;
212 }
213
214 case 0x02: // SWCHB - Port B I/O Register (Console switches)
215 {
216 return (myOutB | ~myDDRB) & (myConsole.switches().read() | myDDRB);
217 }
218
219 case 0x03: // SWBCNT - Port B Data Direction Register
220 {
221 return myDDRB;
222 }
223
224 case 0x04: // INTIM - Timer Output
225 case 0x06:
226 {
227 // Timer Flag is always cleared when accessing INTIM
228 if (!myWrappedThisCycle) myInterruptFlag &= ~TimerBit;
229 myTimerWrapped = false;
230 return myTimer;
231 }
232
233 case 0x05: // TIMINT/INSTAT - Interrupt Flag
234 case 0x07:
235 {
236 // PA7 Flag is always cleared after accessing TIMINT
237 uInt8 result = myInterruptFlag;
238 myInterruptFlag &= ~PA7Bit;
239 return result;
240 }
241
242 default:
243 {
244#ifdef DEBUG_ACCESSES
245 cerr << "BAD M6532 Peek: " << hex << addr << endl;
246#endif
247 return 0;
248 }
249 }
250}
251
252// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
253bool M6532::poke(uInt16 addr, uInt8 value)
254{
255 updateEmulation();
256
257 // A9 distinguishes I/O registers from ZP RAM
258 // A9 = 1 is write to I/O
259 // A9 = 0 is write to RAM
260 if((addr & 0x0200) == 0x0000)
261 {
262 myRAM[addr & 0x007f] = value;
263 return true;
264 }
265
266 // A2 distinguishes I/O registers from the timer
267 // A2 = 1 is write to timer
268 // A2 = 0 is write to I/O
269 if((addr & 0x04) != 0)
270 {
271 // A4 = 1 is write to TIMxT (x = 1, 8, 64, 1024)
272 // A4 = 0 is write to edge detect control
273 if((addr & 0x10) != 0)
274 setTimerRegister(value, addr & 0x03); // A1A0 determines interval
275 else
276 myEdgeDetectPositive = addr & 0x01; // A0 determines direction
277 }
278 else
279 {
280 switch(addr & 0x03)
281 {
282 case 0: // SWCHA - Port A I/O Register (Joystick)
283 {
284 myOutA = value;
285 setPinState(true);
286 break;
287 }
288
289 case 1: // SWACNT - Port A Data Direction Register
290 {
291 myDDRA = value;
292 setPinState(false);
293 break;
294 }
295
296 case 2: // SWCHB - Port B I/O Register (Console switches)
297 {
298 myOutB = value;
299 break;
300 }
301
302 case 3: // SWBCNT - Port B Data Direction Register
303 {
304 myDDRB = value;
305 break;
306 }
307 }
308 }
309 return true;
310}
311
312// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
313void M6532::setTimerRegister(uInt8 value, uInt8 interval)
314{
315 static constexpr uInt32 divider[] = { 1, 8, 64, 1024 };
316
317 myDivider = divider[interval];
318 myOutTimer[interval] = value;
319
320 myTimer = value;
321 mySubTimer = myDivider - 1;
322 myTimerWrapped = false;
323
324 // Interrupt timer flag is cleared (and invalid) when writing to the timer
325 myInterruptFlag &= ~TimerBit;
326
327 mySetTimerCycle = mySystem->cycles();
328}
329
330// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
331void M6532::setPinState(bool swcha)
332{
333 /*
334 When a bit in the DDR is set as input, +5V is placed on its output
335 pin. When it's set as output, either +5V or 0V (depending on the
336 contents of SWCHA) will be placed on the output pin.
337 The standard macros for the AtariVox and SaveKey use this fact to
338 send data to the port. This is represented by the following algorithm:
339
340 if(DDR bit is input) set output as 1
341 else if(DDR bit is output) set output as bit in ORA
342 */
343 Controller& lport = myConsole.leftController();
344 Controller& rport = myConsole.rightController();
345
346 uInt8 ioport = myOutA | ~myDDRA;
347
348 lport.write(Controller::DigitalPin::One, ioport & 0b00010000);
349 lport.write(Controller::DigitalPin::Two, ioport & 0b00100000);
350 lport.write(Controller::DigitalPin::Three, ioport & 0b01000000);
351 lport.write(Controller::DigitalPin::Four, ioport & 0b10000000);
352 rport.write(Controller::DigitalPin::One, ioport & 0b00000001);
353 rport.write(Controller::DigitalPin::Two, ioport & 0b00000010);
354 rport.write(Controller::DigitalPin::Three, ioport & 0b00000100);
355 rport.write(Controller::DigitalPin::Four, ioport & 0b00001000);
356
357 if(swcha)
358 {
359 lport.controlWrite(ioport);
360 rport.controlWrite(ioport);
361 }
362}
363
364// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
365bool M6532::save(Serializer& out) const
366{
367 try
368 {
369 out.putByteArray(myRAM.data(), myRAM.size());
370
371 out.putInt(myTimer);
372 out.putInt(mySubTimer);
373 out.putInt(myDivider);
374 out.putBool(myTimerWrapped);
375 out.putBool(myWrappedThisCycle);
376 out.putLong(myLastCycle);
377 out.putLong(mySetTimerCycle);
378
379 out.putByte(myDDRA);
380 out.putByte(myDDRB);
381 out.putByte(myOutA);
382 out.putByte(myOutB);
383
384 out.putByte(myInterruptFlag);
385 out.putBool(myEdgeDetectPositive);
386 out.putByteArray(myOutTimer.data(), myOutTimer.size());
387 }
388 catch(...)
389 {
390 cerr << "ERROR: M6532::save" << endl;
391 return false;
392 }
393
394 return true;
395}
396
397// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
398bool M6532::load(Serializer& in)
399{
400 try
401 {
402 in.getByteArray(myRAM.data(), myRAM.size());
403
404 myTimer = in.getInt();
405 mySubTimer = in.getInt();
406 myDivider = in.getInt();
407 myTimerWrapped = in.getBool();
408 myWrappedThisCycle = in.getBool();
409 myLastCycle = in.getLong();
410 mySetTimerCycle = in.getLong();
411
412 myDDRA = in.getByte();
413 myDDRB = in.getByte();
414 myOutA = in.getByte();
415 myOutB = in.getByte();
416
417 myInterruptFlag = in.getByte();
418 myEdgeDetectPositive = in.getBool();
419 in.getByteArray(myOutTimer.data(), myOutTimer.size());
420 }
421 catch(...)
422 {
423 cerr << "ERROR: M6532::load" << endl;
424 return false;
425 }
426
427 return true;
428}
429
430// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
431uInt8 M6532::intim()
432{
433 updateEmulation();
434
435 return myTimer;
436}
437
438// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
439uInt8 M6532::timint()
440{
441 updateEmulation();
442
443 return myInterruptFlag;
444}
445
446// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
447Int32 M6532::intimClocks()
448{
449 updateEmulation();
450
451 // This method is similar to intim(), except instead of giving the actual
452 // INTIM value, it will give the current number of clocks between one
453 // INTIM value and the next
454
455 return myTimerWrapped ? 1 : (myDivider - mySubTimer);
456}
457
458// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
459uInt32 M6532::timerClocks() const
460{
461 return uInt32(mySystem->cycles() - mySetTimerCycle);
462}
463
464#ifdef DEBUGGER_SUPPORT
465// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
466void M6532::createAccessBases()
467{
468 myRAMAccessBase.fill(CartDebug::NONE);
469 myStackAccessBase.fill(CartDebug::NONE);
470 myIOAccessBase.fill(CartDebug::NONE);
471 myZPAccessDelay.fill(ZP_DELAY);
472}
473
474// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
475uInt8 M6532::getAccessFlags(uInt16 address) const
476{
477 if (address & IO_BIT)
478 return myIOAccessBase[address & IO_MASK];
479 else if (address & STACK_BIT)
480 return myStackAccessBase[address & STACK_MASK];
481 else
482 return myRAMAccessBase[address & RAM_MASK];
483}
484
485// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
486void M6532::setAccessFlags(uInt16 address, uInt8 flags)
487{
488 // ignore none flag
489 if (flags != CartDebug::NONE) {
490 if (address & IO_BIT)
491 myIOAccessBase[address & IO_MASK] |= flags;
492 else {
493 // the first access, either by direct RAM or stack access is assumed as initialization
494 if (myZPAccessDelay[address & RAM_MASK])
495 myZPAccessDelay[address & RAM_MASK]--;
496 else if (address & STACK_BIT)
497 myStackAccessBase[address & STACK_MASK] |= flags;
498 else
499 myRAMAccessBase[address & RAM_MASK] |= flags;
500 }
501 }
502}
503#endif // DEBUGGER_SUPPORT
504