1/*****************************************************************************\
2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3 This file is licensed under the Snes9x License.
4 For further information, consult the LICENSE file in the root directory.
5\*****************************************************************************/
6
7#include "snes9x.h"
8#include "memmap.h"
9#include "cpuops.h"
10#include "dma.h"
11#include "apu/apu.h"
12#include "fxemu.h"
13#include "snapshot.h"
14#include "movie.h"
15#ifdef DEBUGGER
16#include "debug.h"
17#include "missing.h"
18#endif
19
20static inline void S9xReschedule (void);
21
22void S9xMainLoop (void)
23{
24 #define CHECK_FOR_IRQ_CHANGE() \
25 if (Timings.IRQFlagChanging) \
26 { \
27 if (Timings.IRQFlagChanging & IRQ_TRIGGER_NMI) \
28 { \
29 CPU.NMIPending = TRUE; \
30 Timings.NMITriggerPos = CPU.Cycles + 6; \
31 } \
32 if (Timings.IRQFlagChanging & IRQ_CLEAR_FLAG) \
33 ClearIRQ(); \
34 else if (Timings.IRQFlagChanging & IRQ_SET_FLAG) \
35 SetIRQ(); \
36 Timings.IRQFlagChanging = IRQ_NONE; \
37 }
38
39 if (CPU.Flags & SCAN_KEYS_FLAG)
40 {
41 CPU.Flags &= ~SCAN_KEYS_FLAG;
42 S9xMovieUpdate();
43 }
44
45 for (;;)
46 {
47 if (CPU.NMIPending)
48 {
49 #ifdef DEBUGGER
50 if (Settings.TraceHCEvent)
51 S9xTraceFormattedMessage ("Comparing %d to %d\n", Timings.NMITriggerPos, CPU.Cycles);
52 #endif
53 if (Timings.NMITriggerPos <= CPU.Cycles)
54 {
55 CPU.NMIPending = FALSE;
56 Timings.NMITriggerPos = 0xffff;
57 if (CPU.WaitingForInterrupt)
58 {
59 CPU.WaitingForInterrupt = FALSE;
60 Registers.PCw++;
61 CPU.Cycles += TWO_CYCLES + ONE_DOT_CYCLE / 2;
62 while (CPU.Cycles >= CPU.NextEvent)
63 S9xDoHEventProcessing();
64 }
65
66 CHECK_FOR_IRQ_CHANGE();
67 S9xOpcode_NMI();
68 }
69 }
70
71 if (CPU.Cycles >= Timings.NextIRQTimer)
72 {
73 #ifdef DEBUGGER
74 S9xTraceMessage ("Timer triggered\n");
75 #endif
76
77 S9xUpdateIRQPositions(false);
78 CPU.IRQLine = TRUE;
79 }
80
81 if (CPU.IRQLine || CPU.IRQExternal)
82 {
83 if (CPU.WaitingForInterrupt)
84 {
85 CPU.WaitingForInterrupt = FALSE;
86 Registers.PCw++;
87 CPU.Cycles += TWO_CYCLES + ONE_DOT_CYCLE / 2;
88 while (CPU.Cycles >= CPU.NextEvent)
89 S9xDoHEventProcessing();
90 }
91
92 if (!CheckFlag(IRQ))
93 {
94 /* The flag pushed onto the stack is the new value */
95 CHECK_FOR_IRQ_CHANGE();
96 S9xOpcode_IRQ();
97 }
98 }
99
100 /* Change IRQ flag for instructions that set it only on last cycle */
101 CHECK_FOR_IRQ_CHANGE();
102
103 #ifdef DEBUGGER
104 if ((CPU.Flags & BREAK_FLAG) && !(CPU.Flags & SINGLE_STEP_FLAG))
105 {
106 for (int Break = 0; Break != 6; Break++)
107 {
108 if (S9xBreakpoint[Break].Enabled &&
109 S9xBreakpoint[Break].Bank == Registers.PB &&
110 S9xBreakpoint[Break].Address == Registers.PCw)
111 {
112 if (S9xBreakpoint[Break].Enabled == 2)
113 S9xBreakpoint[Break].Enabled = TRUE;
114 else
115 CPU.Flags |= DEBUG_MODE_FLAG;
116 }
117 }
118 }
119
120 if (CPU.Flags & DEBUG_MODE_FLAG)
121 break;
122
123 if (CPU.Flags & TRACE_FLAG)
124 S9xTrace();
125
126 if (CPU.Flags & SINGLE_STEP_FLAG)
127 {
128 CPU.Flags &= ~SINGLE_STEP_FLAG;
129 CPU.Flags |= DEBUG_MODE_FLAG;
130 }
131 #endif
132
133 if (CPU.Flags & SCAN_KEYS_FLAG)
134 {
135 #ifdef DEBUGGER
136 if (!(CPU.Flags & FRAME_ADVANCE_FLAG))
137 #endif
138 {
139 S9xSyncSpeed();
140 }
141
142 break;
143 }
144
145 uint8 Op;
146 struct SOpcodes *Opcodes;
147
148 if (CPU.PCBase)
149 {
150 Op = CPU.PCBase[Registers.PCw];
151 CPU.Cycles += CPU.MemSpeed;
152 Opcodes = ICPU.S9xOpcodes;
153 }
154 else
155 {
156 Op = S9xGetByte(Registers.PBPC);
157 OpenBus = Op;
158 Opcodes = S9xOpcodesSlow;
159 }
160
161 if ((Registers.PCw & MEMMAP_MASK) + ICPU.S9xOpLengths[Op] >= MEMMAP_BLOCK_SIZE)
162 {
163 uint8 *oldPCBase = CPU.PCBase;
164
165 CPU.PCBase = S9xGetBasePointer(ICPU.ShiftedPB + ((uint16) (Registers.PCw + 4)));
166 if (oldPCBase != CPU.PCBase || (Registers.PCw & ~MEMMAP_MASK) == (0xffff & ~MEMMAP_MASK))
167 Opcodes = S9xOpcodesSlow;
168 }
169
170 Registers.PCw++;
171 (*Opcodes[Op].S9xOpcode)();
172
173 if (Settings.SA1)
174 S9xSA1MainLoop();
175 }
176
177 S9xPackStatus();
178}
179
180static inline void S9xReschedule (void)
181{
182 switch (CPU.WhichEvent)
183 {
184 case HC_HBLANK_START_EVENT:
185 CPU.WhichEvent = HC_HDMA_START_EVENT;
186 CPU.NextEvent = Timings.HDMAStart;
187 break;
188
189 case HC_HDMA_START_EVENT:
190 CPU.WhichEvent = HC_HCOUNTER_MAX_EVENT;
191 CPU.NextEvent = Timings.H_Max;
192 break;
193
194 case HC_HCOUNTER_MAX_EVENT:
195 CPU.WhichEvent = HC_HDMA_INIT_EVENT;
196 CPU.NextEvent = Timings.HDMAInit;
197 break;
198
199 case HC_HDMA_INIT_EVENT:
200 CPU.WhichEvent = HC_RENDER_EVENT;
201 CPU.NextEvent = Timings.RenderPos;
202 break;
203
204 case HC_RENDER_EVENT:
205 CPU.WhichEvent = HC_WRAM_REFRESH_EVENT;
206 CPU.NextEvent = Timings.WRAMRefreshPos;
207 break;
208
209 case HC_WRAM_REFRESH_EVENT:
210 CPU.WhichEvent = HC_HBLANK_START_EVENT;
211 CPU.NextEvent = Timings.HBlankStart;
212 break;
213 }
214}
215
216void S9xDoHEventProcessing (void)
217{
218#ifdef DEBUGGER
219 static char eventname[7][32] =
220 {
221 "",
222 "HC_HBLANK_START_EVENT",
223 "HC_HDMA_START_EVENT ",
224 "HC_HCOUNTER_MAX_EVENT",
225 "HC_HDMA_INIT_EVENT ",
226 "HC_RENDER_EVENT ",
227 "HC_WRAM_REFRESH_EVENT"
228 };
229#endif
230
231#ifdef DEBUGGER
232 if (Settings.TraceHCEvent)
233 S9xTraceFormattedMessage("--- HC event processing (%s) expected HC:%04d executed HC:%04d VC:%04d",
234 eventname[CPU.WhichEvent], CPU.NextEvent, CPU.Cycles, CPU.V_Counter);
235#endif
236
237 switch (CPU.WhichEvent)
238 {
239 case HC_HBLANK_START_EVENT:
240 S9xReschedule();
241 break;
242
243 case HC_HDMA_START_EVENT:
244 S9xReschedule();
245
246 if (PPU.HDMA && CPU.V_Counter <= PPU.ScreenHeight)
247 {
248 #ifdef DEBUGGER
249 S9xTraceFormattedMessage("*** HDMA Transfer HC:%04d, Channel:%02x", CPU.Cycles, PPU.HDMA);
250 #endif
251 PPU.HDMA = S9xDoHDMA(PPU.HDMA);
252 }
253
254 break;
255
256 case HC_HCOUNTER_MAX_EVENT:
257 if (Settings.SuperFX)
258 {
259 if (!SuperFX.oneLineDone)
260 S9xSuperFXExec();
261 SuperFX.oneLineDone = FALSE;
262 }
263
264 S9xAPUEndScanline();
265 CPU.Cycles -= Timings.H_Max;
266 if (Timings.NMITriggerPos != 0xffff)
267 Timings.NMITriggerPos -= Timings.H_Max;
268 if (Timings.NextIRQTimer != 0x0fffffff)
269 Timings.NextIRQTimer -= Timings.H_Max;
270 S9xAPUSetReferenceTime(CPU.Cycles);
271
272 if (Settings.SA1)
273 SA1.Cycles -= Timings.H_Max * 3;
274
275 CPU.V_Counter++;
276 if (CPU.V_Counter >= Timings.V_Max) // V ranges from 0 to Timings.V_Max - 1
277 {
278 CPU.V_Counter = 0;
279 Timings.InterlaceField ^= 1;
280
281 // From byuu:
282 // [NTSC]
283 // interlace mode has 525 scanlines: 263 on the even frame, and 262 on the odd.
284 // non-interlace mode has 524 scanlines: 262 scanlines on both even and odd frames.
285 // [PAL] <PAL info is unverified on hardware>
286 // interlace mode has 625 scanlines: 313 on the even frame, and 312 on the odd.
287 // non-interlace mode has 624 scanlines: 312 scanlines on both even and odd frames.
288 if (IPPU.Interlace && !Timings.InterlaceField)
289 Timings.V_Max = Timings.V_Max_Master + 1; // 263 (NTSC), 313?(PAL)
290 else
291 Timings.V_Max = Timings.V_Max_Master; // 262 (NTSC), 312?(PAL)
292
293 Memory.FillRAM[0x213F] ^= 0x80;
294 PPU.RangeTimeOver = 0;
295
296 // FIXME: reading $4210 will wait 2 cycles, then perform reading, then wait 4 more cycles.
297 Memory.FillRAM[0x4210] = Model->_5A22;
298
299 ICPU.Frame++;
300 PPU.HVBeamCounterLatched = 0;
301 }
302
303 // From byuu:
304 // In non-interlace mode, there are 341 dots per scanline, and 262 scanlines per frame.
305 // On odd frames, scanline 240 is one dot short.
306 // In interlace mode, there are always 341 dots per scanline. Even frames have 263 scanlines,
307 // and odd frames have 262 scanlines.
308 // Interlace mode scanline 240 on odd frames is not missing a dot.
309 if (CPU.V_Counter == 240 && !IPPU.Interlace && Timings.InterlaceField) // V=240
310 Timings.H_Max = Timings.H_Max_Master - ONE_DOT_CYCLE; // HC=1360
311 else
312 Timings.H_Max = Timings.H_Max_Master; // HC=1364
313
314 if (Model->_5A22 == 2)
315 {
316 if (CPU.V_Counter != 240 || IPPU.Interlace || !Timings.InterlaceField) // V=240
317 {
318 if (Timings.WRAMRefreshPos == SNES_WRAM_REFRESH_HC_v2 - ONE_DOT_CYCLE) // HC=534
319 Timings.WRAMRefreshPos = SNES_WRAM_REFRESH_HC_v2; // HC=538
320 else
321 Timings.WRAMRefreshPos = SNES_WRAM_REFRESH_HC_v2 - ONE_DOT_CYCLE; // HC=534
322 }
323 }
324 else
325 Timings.WRAMRefreshPos = SNES_WRAM_REFRESH_HC_v1;
326
327 if (CPU.V_Counter == PPU.ScreenHeight + FIRST_VISIBLE_LINE) // VBlank starts from V=225(240).
328 {
329 S9xEndScreenRefresh();
330
331 CPU.Flags |= SCAN_KEYS_FLAG;
332
333 PPU.HDMA = 0;
334 // Bits 7 and 6 of $4212 are computed when read in S9xGetPPU.
335 #ifdef DEBUGGER
336 missing.dma_this_frame = 0;
337 #endif
338 IPPU.MaxBrightness = PPU.Brightness;
339 PPU.ForcedBlanking = (Memory.FillRAM[0x2100] >> 7) & 1;
340
341 if (!PPU.ForcedBlanking)
342 {
343 PPU.OAMAddr = PPU.SavedOAMAddr;
344
345 uint8 tmp = 0;
346
347 if (PPU.OAMPriorityRotation)
348 tmp = (PPU.OAMAddr & 0xFE) >> 1;
349 if ((PPU.OAMFlip & 1) || PPU.FirstSprite != tmp)
350 {
351 PPU.FirstSprite = tmp;
352 IPPU.OBJChanged = TRUE;
353 }
354
355 PPU.OAMFlip = 0;
356 }
357
358 // FIXME: writing to $4210 will wait 6 cycles.
359 Memory.FillRAM[0x4210] = 0x80 | Model->_5A22;
360 if (Memory.FillRAM[0x4200] & 0x80)
361 {
362#ifdef DEBUGGER
363 if (Settings.TraceHCEvent)
364 S9xTraceFormattedMessage ("NMI Scheduled for next scanline.");
365#endif
366 // FIXME: triggered at HC=6, checked just before the final CPU cycle,
367 // then, when to call S9xOpcode_NMI()?
368 CPU.NMIPending = TRUE;
369 Timings.NMITriggerPos = 6 + 6;
370 }
371
372 }
373
374 if (CPU.V_Counter == PPU.ScreenHeight + 3) // FIXME: not true
375 {
376 if (Memory.FillRAM[0x4200] & 1)
377 S9xDoAutoJoypad();
378 }
379
380 if (CPU.V_Counter == FIRST_VISIBLE_LINE) // V=1
381 S9xStartScreenRefresh();
382
383 S9xReschedule();
384
385 break;
386
387 case HC_HDMA_INIT_EVENT:
388 S9xReschedule();
389
390 if (CPU.V_Counter == 0)
391 {
392 #ifdef DEBUGGER
393 S9xTraceFormattedMessage("*** HDMA Init HC:%04d, Channel:%02x", CPU.Cycles, PPU.HDMA);
394 #endif
395 S9xStartHDMA();
396 }
397
398 break;
399
400 case HC_RENDER_EVENT:
401 if (CPU.V_Counter >= FIRST_VISIBLE_LINE && CPU.V_Counter <= PPU.ScreenHeight)
402 RenderLine((uint8) (CPU.V_Counter - FIRST_VISIBLE_LINE));
403
404 S9xReschedule();
405
406 break;
407
408 case HC_WRAM_REFRESH_EVENT:
409 #ifdef DEBUGGER
410 S9xTraceFormattedMessage("*** WRAM Refresh HC:%04d", CPU.Cycles);
411 #endif
412
413 CPU.Cycles += SNES_WRAM_REFRESH_CYCLES;
414
415 S9xReschedule();
416
417 break;
418 }
419
420#ifdef DEBUGGER
421 if (Settings.TraceHCEvent)
422 S9xTraceFormattedMessage("--- HC event rescheduled (%s) expected HC:%04d current HC:%04d",
423 eventname[CPU.WhichEvent], CPU.NextEvent, CPU.Cycles);
424#endif
425}
426