The Game Boy's CPU, PPU, APU, and MBC run in parallel (that is, at the same time), and games are designed with this in mind. Many of them modify the PPU registers and CHR bank numbers multiple times to produce scroll splits, curved roads, and other raster effects, though not nearly to the same extent as the Atari 2600 games featured in Racing the Beam by Nick Montfort and Ian Bogost (ISBN 9780262012577). But most emulators are programmed for a Von Neumann architecture that does only one thing at a time. So in some sense, an emulator must switch among emulating the CPU, PPU, APU, and MBC one at a time. This switching must be fairly fine-grained: if an emulator runs the CPU for a whole frame and then runs the PPU for a whole frame, for example, the raster effects won't be visible.
The design philosophy of some emulators takes clarity and accuracy over speed; they emulate each component for one CPU cycle before switching to the next. Most other emulators optimized for run-time efficiency do some level of catch-up, involving running the emulated CPU for several dozen cycles and then running the PPU and APU until they are synchronized. Keeping one component in the host CPU for a longer time speeds things up because the relevant data stays in the host CPU's fast registers and cache, not (slower) main memory, as long as the end result is as if the emulator ran all components cycle-by-cycle.
The basic technique looks like this:
- Find the next time that one component could affect another, such as the CPU writing to a PPU register or the PPU asserting a STAT interrupt to the CPU.
- Run the least predictable component up to that time. In the Game Boy's case, the CPU is least predictable, so you usually want to run that first.
- Run the other components up to that time.
At the end of each frame (for example, the start of scanline 0 or scanline 144), the emulator catches up everything and hands off the completed video surface and audio stream to the operating system.
One basic technique involves predicting when each component will do something "important", like asserting an interrupt or changing a status register, and then running one component ahead until that time.
Some examples are STAT and NR52 (APU length counter status) values and vblank, STAT, timer, and serial interrupts.
An emulator might make a rough prediction that slightly underestimates the time until that component sees the change, run that component for that amount of time, and then fall back to I/O catch-up or cycle-by-cycle emulation until the "important" event has happened.
Another technique involves remembering at what time (that is, what cycle) the CPU has written to each register, and then having the other component process the write as if it had occurred at that cycle.
But if a timestamp changes a prediction, you'll want to catch-up the other components instead of timestamping the write:
- Writes to LCDC, SCX, WX, or WY might change the predicted duration of mode 3.
- Writes to STAT or other registers controlling interrupt sources might change the mapper IRQ prediction.
- Writes to APU registers might change the length counter prediction.
A scanline-based emulator is an emulator that uses a crude form of prediction and timestamping: something "important" might happen on each scanline, and timestamps are rounded to a scanline boundary. They run the CPU for one scanline's worth of cycles and then run the PPU and mapper for one scanline (456 dots), and after all scanlines are finished, run the APU for one frame. This isn't perfect but can run "well-behaved" games efficiently on emulators designed for old PCs or handheld devices. Few games write to the same APU register multiple times in a frame except possibly to PCM through the wave channel or the primary volume.
Image processing performed in the system software of a PC or modern game console can insert one or possibly more frames of delay. So can the LCD television monitors connected to its video output. To maintain the illusion that a game is responsive to control by the player, some emulators run one or more frames ahead. Then whenever a button changes, the emulator rewinds a few frames with the updated input.
- Catch-up on NESdev Wiki