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 }