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 | |
20 | static inline void S9xReschedule (void); |
21 | |
22 | void 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 | |
180 | static 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 | |
216 | void 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 | |