cpu.cpp
  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 "memory.h"
  9  #include "dma.h"
 10  #include "apu.h"
 11  //#include "snapshot.h"
 12  #include "cpuops.h"
 13  #ifdef DEBUGGER
 14  #include "debug.h"
 15  #endif
 16  
 17  struct SCPUState		CPU;
 18  struct SICPU			ICPU;
 19  struct SRegisters		Registers;
 20  
 21  
 22  IRAM_ATTR void S9xMainLoop (void)
 23  {
 24  	uint32 loops = 0;
 25  	// uint32 slow = 0;
 26  	uint32 Op = 0;
 27  
 28  	CPU.Flags &= ~SCAN_KEYS_FLAG;
 29  
 30  	#define CHECK_FOR_IRQ_CHANGE() \
 31  	if (CPU.IRQFlagChanging) \
 32  	{ \
 33  		if (CPU.IRQFlagChanging & IRQ_TRIGGER_NMI) \
 34  		{ \
 35  			CPU.NMIPending = TRUE; \
 36  			CPU.NMITriggerPos = CPU.Cycles + 6; \
 37  		} \
 38  		if (CPU.IRQFlagChanging & IRQ_CLEAR_FLAG) \
 39  			ClearIRQ(); \
 40  		else if (CPU.IRQFlagChanging & IRQ_SET_FLAG) \
 41  			SetIRQ(); \
 42  		CPU.IRQFlagChanging = IRQ_NONE; \
 43  	}
 44  
 45  	for (;;)
 46  	{
 47  		#if (RETRO_LESS_ACCURATE_CPU || RETRO_LESS_ACCURATE_MEM)
 48  			// Only check for interrupts every 15 loops. More than that breaks too many games
 49  			// This is temporary until we improve speed elsewhere...
 50  			if (loops--)
 51  				goto run_opcode;
 52  			else
 53  				loops = 20;
 54  
 55  			if (CPU.Cycles >= CPU.NextEvent)
 56  				S9xDoHEventProcessing();
 57  		#endif
 58  
 59  		if (CPU.NMIPending)
 60  		{
 61  			#ifdef DEBUGGER
 62  			if (Settings.TraceHCEvent)
 63  			    S9xTraceFormattedMessage ("Comparing %d to %d\n", CPU.NMITriggerPos, CPU.Cycles);
 64  			#endif
 65  			if (CPU.NMITriggerPos <= CPU.Cycles)
 66  			{
 67  				CPU.NMIPending = FALSE;
 68  				CPU.NMITriggerPos = 0xffff;
 69  				if (CPU.WaitingForInterrupt)
 70  				{
 71  					CPU.WaitingForInterrupt = FALSE;
 72  					Registers.PCw++;
 73  					CPU.Cycles += TWO_CYCLES + ONE_DOT_CYCLE / 2;
 74  					if (CPU.Cycles >= CPU.NextEvent)
 75  						S9xDoHEventProcessing();
 76  				}
 77  
 78  				CHECK_FOR_IRQ_CHANGE();
 79  				S9xOpcode_NMI();
 80  			}
 81  		}
 82  
 83  		if (CPU.Cycles >= CPU.NextIRQTimer)
 84  		{
 85  			#ifdef DEBUGGER
 86  			S9xTraceMessage ("Timer triggered\n");
 87  			#endif
 88  
 89  			S9xUpdateIRQPositions(false);
 90  			CPU.IRQLine = TRUE;
 91  		}
 92  
 93  		if (CPU.IRQLine)
 94  		{
 95  			if (CPU.WaitingForInterrupt)
 96  			{
 97  				CPU.WaitingForInterrupt = FALSE;
 98  				Registers.PCw++;
 99  				CPU.Cycles += TWO_CYCLES + ONE_DOT_CYCLE / 2;
100  				if (CPU.Cycles >= CPU.NextEvent)
101  					S9xDoHEventProcessing();
102  			}
103  
104  			if (!CheckFlag(IRQ))
105  			{
106  				/* The flag pushed onto the stack is the new value */
107  				CHECK_FOR_IRQ_CHANGE();
108  				S9xOpcode_IRQ();
109  			}
110  		}
111  
112  		/* Change IRQ flag for instructions that set it only on last cycle */
113  		CHECK_FOR_IRQ_CHANGE();
114  
115  	#ifdef DEBUGGER
116  		if ((CPU.Flags & BREAK_FLAG) && !(CPU.Flags & SINGLE_STEP_FLAG))
117  		{
118  			for (int Break = 0; Break != 6; Break++)
119  			{
120  				if (S9xBreakpoint[Break].Enabled &&
121  					S9xBreakpoint[Break].Bank == Registers.PB &&
122  					S9xBreakpoint[Break].Address == Registers.PCw)
123  				{
124  					if (S9xBreakpoint[Break].Enabled == 2)
125  						S9xBreakpoint[Break].Enabled = TRUE;
126  					else
127  						CPU.Flags |= DEBUG_MODE_FLAG;
128  				}
129  			}
130  		}
131  
132  		if (CPU.Flags & DEBUG_MODE_FLAG)
133  			break;
134  
135  		if (CPU.Flags & TRACE_FLAG)
136  			S9xTrace();
137  
138  		if (CPU.Flags & SINGLE_STEP_FLAG)
139  		{
140  			CPU.Flags &= ~SINGLE_STEP_FLAG;
141  			CPU.Flags |= DEBUG_MODE_FLAG;
142  		}
143  	#endif
144  
145  		if (CPU.Flags & SCAN_KEYS_FLAG)
146  		{
147  			#ifdef DEBUGGER
148  			if (!(CPU.Flags & FRAME_ADVANCE_FLAG))
149  			#endif
150  			{
151  				// S9xSyncSpeed();
152  			}
153  
154  			break;
155  		}
156  
157  	run_opcode:
158  
159  		// If we're crossing a page or PCBase isn't set then we must use the slow route!
160  		if (CPU.PCBase == NULL || (Registers.PCw & MEMMAP_MASK) + 4 >= MEMMAP_BLOCK_SIZE)
161  		{
162  			Op = S9xGetByte(Registers.PBPC);
163  			OpenBus = Op;
164  
165  			Registers.PCw++;
166  			(S9xOpcodesSlow[Op])();
167  			// slow++;
168  		}
169  		else
170  		{
171  			Op = CPU.PCBase[Registers.PCw];
172  			CPU.Cycles += CPU.MemSpeed;
173  
174  			Registers.PCw++;
175  			(ICPU.S9xOpcodes[Op])();
176  		}
177  	}
178  
179  	// printf("fast: %d /  slow: %d\n", loops - slow, slow);
180  }
181  
182  IRAM_ATTR void S9xDoHEventProcessing (void)
183  {
184  #ifdef DEBUGGER
185  	const char	eventname[7][32] =
186  	{
187  		"",
188  		"HC_HBLANK_START_EVENT",
189  		"HC_HDMA_START_EVENT  ",
190  		"HC_HCOUNTER_MAX_EVENT",
191  		"HC_HDMA_INIT_EVENT   ",
192  		"HC_RENDER_EVENT      ",
193  		"HC_WRAM_REFRESH_EVENT"
194  	};
195  #endif
196  
197  	do
198  	{
199  	#ifdef DEBUGGER
200  		if (Settings.TraceHCEvent)
201  			S9xTraceFormattedMessage("--- HC event processing  (%s)  expected HC:%04d  executed HC:%04d VC:%04d",
202  				eventname[CPU.WhichEvent], CPU.NextEvent, CPU.Cycles, CPU.V_Counter);
203  	#endif
204  
205  		switch (CPU.WhichEvent)
206  		{
207  			case HC_HBLANK_START_EVENT:
208  				CPU.WhichEvent = HC_HDMA_START_EVENT;
209  				CPU.NextEvent  = SNES_HDMA_START_HC;
210  				break;
211  
212  			case HC_HDMA_START_EVENT:
213  				CPU.WhichEvent = HC_HCOUNTER_MAX_EVENT;
214  				CPU.NextEvent  = SNES_CYCLES_PER_SCANLINE;
215  
216  				if (PPU.HDMA && CPU.V_Counter <= PPU.ScreenHeight)
217  				{
218  				#ifdef DEBUGGER
219  					S9xTraceFormattedMessage("*** HDMA Transfer HC:%04d, Channel:%02x", CPU.Cycles, PPU.HDMA);
220  				#endif
221  					PPU.HDMA = S9xDoHDMA(PPU.HDMA);
222  				}
223  
224  				break;
225  
226  			case HC_HCOUNTER_MAX_EVENT:
227  				S9xAPUEndScanline();
228  				CPU.Cycles -= SNES_CYCLES_PER_SCANLINE;
229  				if (CPU.NMITriggerPos != 0xffff)
230  					CPU.NMITriggerPos -= SNES_CYCLES_PER_SCANLINE;
231  				if (CPU.NextIRQTimer != 0x0fffffff)
232  					CPU.NextIRQTimer -= SNES_CYCLES_PER_SCANLINE;
233  				S9xAPUSetReferenceTime(CPU.Cycles);
234  
235  				CPU.V_Counter++;
236  				if (CPU.V_Counter >= SNES_MAX_VCOUNTER)	// V ranges from 0 to MAX_VCOUNTER - 1
237  				{
238  					CPU.V_Counter = 0;
239  
240  					Memory.PPU_IO[0x13F] ^= 0x80;
241  					PPU.RangeTimeOver = 0;
242  
243  					// FIXME: reading $4210 will wait 2 cycles, then perform reading, then wait 4 more cycles.
244  					Memory.CPU_IO[0x210] = 2;
245  
246  					ICPU.Frame++;
247  					PPU.HVBeamCounterLatched = 0;
248  				}
249  
250  				if (CPU.V_Counter == PPU.ScreenHeight + FIRST_VISIBLE_LINE)	// VBlank starts from V=225(240).
251  				{
252  					S9xEndScreenRefresh();
253  
254  					CPU.Flags |= SCAN_KEYS_FLAG;
255  
256  					PPU.HDMA = 0;
257  					// Bits 7 and 6 of $4212 are computed when read in S9xGetPPU.
258  					IPPU.MaxBrightness = PPU.Brightness;
259  					PPU.ForcedBlanking = (Memory.PPU_IO[0x100] >> 7) & 1;
260  
261  					if (!PPU.ForcedBlanking)
262  					{
263  						PPU.OAMAddr = PPU.SavedOAMAddr;
264  
265  						uint8	tmp = 0;
266  
267  						if (PPU.OAMPriorityRotation)
268  							tmp = (PPU.OAMAddr & 0xFE) >> 1;
269  						if ((PPU.OAMFlip & 1) || PPU.FirstSprite != tmp)
270  						{
271  							PPU.FirstSprite = tmp;
272  							IPPU.OBJChanged = TRUE;
273  						}
274  
275  						PPU.OAMFlip = 0;
276  					}
277  
278  					// FIXME: writing to $4210 will wait 6 cycles.
279  					Memory.CPU_IO[0x210] = 0x80 | 2;
280  					if (Memory.CPU_IO[0x200] & 0x80)
281  					{
282  					#ifdef DEBUGGER
283  						if (Settings.TraceHCEvent)
284  							S9xTraceFormattedMessage ("NMI Scheduled for next scanline.");
285  					#endif
286  						// FIXME: triggered at HC=6, checked just before the final CPU cycle,
287  						// then, when to call S9xOpcode_NMI()?
288  						CPU.NMIPending = TRUE;
289  						CPU.NMITriggerPos = 6 + 6;
290  					}
291  
292  				}
293  
294  				if (CPU.V_Counter == PPU.ScreenHeight + 3)	// FIXME: not true
295  				{
296  					if (Memory.CPU_IO[0x200] & 1)
297  						S9xDoAutoJoypad();
298  				}
299  
300  				if (CPU.V_Counter == FIRST_VISIBLE_LINE)	// V=1
301  					S9xStartScreenRefresh();
302  
303  				CPU.WhichEvent = HC_HDMA_INIT_EVENT;
304  				CPU.NextEvent  = SNES_HDMA_INIT_HC;
305  				break;
306  
307  			case HC_HDMA_INIT_EVENT:
308  				CPU.WhichEvent = HC_RENDER_EVENT;
309  				CPU.NextEvent  = SNES_RENDER_START_HC;
310  
311  				if (CPU.V_Counter == 0)
312  				{
313  				#ifdef DEBUGGER
314  					S9xTraceFormattedMessage("*** HDMA Init     HC:%04d, Channel:%02x", CPU.Cycles, PPU.HDMA);
315  				#endif
316  					S9xStartHDMA();
317  				}
318  				break;
319  
320  			case HC_RENDER_EVENT:
321  				if (CPU.V_Counter >= FIRST_VISIBLE_LINE && CPU.V_Counter <= PPU.ScreenHeight)
322  					S9xRenderLine((uint8) (CPU.V_Counter - FIRST_VISIBLE_LINE));
323  
324  				CPU.WhichEvent = HC_WRAM_REFRESH_EVENT;
325  				CPU.NextEvent  = SNES_WRAM_REFRESH_HC;
326  				break;
327  
328  			case HC_WRAM_REFRESH_EVENT:
329  			#ifdef DEBUGGER
330  				S9xTraceFormattedMessage("*** WRAM Refresh  HC:%04d", CPU.Cycles);
331  			#endif
332  
333  				CPU.Cycles += SNES_WRAM_REFRESH_CYCLES;
334  				CPU.WhichEvent = HC_HBLANK_START_EVENT;
335  				CPU.NextEvent  = SNES_HBLANK_START_HC;
336  				break;
337  		}
338  
339  	#ifdef DEBUGGER
340  		if (Settings.TraceHCEvent)
341  			S9xTraceFormattedMessage("--- HC event rescheduled (%s)  expected HC:%04d  current  HC:%04d",
342  				eventname[CPU.WhichEvent], CPU.NextEvent, CPU.Cycles);
343  	#endif
344  	} while (CPU.Cycles >= CPU.NextEvent);
345  }
346  
347  static void S9xSoftResetCPU (void)
348  {
349  	CPU.Cycles = 182; // Or 188. This is the cycle count just after the jump to the Reset Vector.
350  	CPU.V_Counter = 0;
351  	CPU.Flags = CPU.Flags & (DEBUG_MODE_FLAG | TRACE_FLAG);
352  	CPU.PCBase = NULL;
353  	CPU.NMIPending = FALSE;
354  	CPU.IRQLine = FALSE;
355  	CPU.MemSpeed = SLOW_ONE_CYCLE;
356  	CPU.MemSpeedx2 = SLOW_ONE_CYCLE * 2;
357  	CPU.FastROMSpeed = SLOW_ONE_CYCLE;
358  	CPU.InDMA = FALSE;
359  	CPU.InHDMA = FALSE;
360  	CPU.InDMAorHDMA = FALSE;
361  	CPU.InWRAMDMAorHDMA = FALSE;
362  	CPU.HDMARanInDMA = 0;
363  	CPU.CurrentDMAorHDMAChannel = -1;
364  	CPU.WhichEvent = HC_RENDER_EVENT;
365  	CPU.NextEvent  = SNES_RENDER_START_HC;
366  	CPU.WaitingForInterrupt = FALSE;
367  	CPU.AutoSaveTimer = 0;
368  	CPU.SRAMModified = FALSE;
369  
370  	Registers.PBPC = 0;
371  	Registers.PB = 0;
372  	Registers.PCw = S9xGetWord(0xfffc);
373  	OpenBus = Registers.PCh;
374  	Registers.D.W = 0;
375  	Registers.DB = 0;
376  	Registers.SH = 1;
377  	Registers.SL -= 3;
378  	Registers.XH = 0;
379  	Registers.YH = 0;
380  
381  	ICPU.ShiftedPB = 0;
382  	ICPU.ShiftedDB = 0;
383  	SetFlags(MemoryFlag | IndexFlag | IRQ | Emulation);
384  	ClearFlags(Decimal);
385  
386  	CPU.NMITriggerPos = 0xffff;
387  	CPU.NextIRQTimer = 0x0fffffff;
388  	CPU.IRQFlagChanging = IRQ_NONE;
389  
390  	S9xSetPCBase(Registers.PBPC);
391  	S9xFixCycles();
392  }
393  
394  static void S9xResetCPU (void)
395  {
396  	S9xSoftResetCPU();
397  	Registers.SL = 0xff;
398  	Registers.P.W = 0;
399  	Registers.A.W = 0;
400  	Registers.X.W = 0;
401  	Registers.Y.W = 0;
402  	SetFlags(MemoryFlag | IndexFlag | IRQ | Emulation);
403  	ClearFlags(Decimal);
404  }
405  
406  void S9xReset (void)
407  {
408  	memset(Memory.RAM, 0x55, 0x20000);
409  	memset(Memory.VRAM, 0x00, 0x10000);
410  	memset(Memory.CPU_IO, 0, 0x400);
411  
412  	S9xResetCPU();
413  	S9xResetPPU();
414  	S9xResetDMA();
415  	S9xResetAPU();
416  
417  	if (Settings.DSP)
418  		S9xResetDSP();
419  }
420  
421  void S9xSoftReset (void)
422  {
423  	memset(Memory.CPU_IO, 0, 0x400);
424  
425  	S9xSoftResetCPU();
426  	S9xSoftResetPPU();
427  	S9xResetDMA();
428  	S9xSoftResetAPU();
429  
430  	if (Settings.DSP)
431  		S9xResetDSP();
432  }