3 ms·
Most (game console) emulators don't try to limit the speed of actual CPU emulation directly, since that makes the code more complex, and makes testing slower, e
by thristian 7y ago
Most (game console) emulators don't try to limit the speed of actual CPU emulation directly, since that makes the code more complex, and makes testing slower, etc. Instead, the only time the emulated system speed is when it has to talk to humans - render a frame of video, generate audio samples, etc. So if you have a 60Hz monitor (as most people do), and your emulated CPU takes 180,000 cycles (or whatever) to render a video frame, then your emulator just runs at top speed until a video frame is available, waits for the "vertical synchronisation" signal from your graphics API, draws the frame with the graphics API, and repeats.
Because of the delay introduced by vertical sync, your emulated CPU's average speed will be correct, even though the instantaneous speed is either "way too fast" or "zero".
- zeta0134 7y agoAdding on to this, in the context of a typical emulator, it's important that the other hardware be emulated mostly in lockstep with the CPU. Each time the 6502 reads or writes memory, the hardware registers it is talking to should be "caught up" so that things like video and audio signal timings work correctly. There are a bunch of different approaches to this depending on how accurate you want things to be, but the most straightforward is to just clock the components directly, one after the other. I have a function in RusticNES that clocks the CPU + APU, another that clocks the PPU, and since the PPU runs 3x as fast, a global clock function that looks like: pub fn cycle(&mut self) { cycle_cpu::run_one_clock(self); self.master_clock = self.master_clock + 12; // Three PPU clocks per every 1 CPU clock self.ppu.clock(&mut *self.mapper); self.ppu.clock(&mut *self.mapper); self.ppu.clock(&mut *self.mapper); self.apu.clock_apu(&mut *self.mapper); } This way, all of the emulated chips remain mostly synchronized with each other, but I don't bother to synchronize with the host until either the PPU is ready to draw a frame, or the APU has filled its audio buffer. This approach uses the CPU cycle as the boundary, but there are other approaches to consider. You could run an entire opcode and then "catch up" the rest of the hardware (maybe easier to manage CPU state), or run entire scanlines at once. (Maybe easier to manage PPU state at first? The NES's PPU is well understood, but very tricky to emulate correctly.)