3 ms·
Adding 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
by zeta0134 7y ago
Adding 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.)