Time for timings

OK, I've been reading up on some docs and have come up with a little bit of work I can do tonight before I go to sleep. I will save the PPU for tomorrow as it is a lot of code to delete and redo..

Anyway, the NES clock divider works as follows:
CPU = Video / 12 (NTSC)
CPU = Video / 15 (PAL)

For NTSC, the video is clocked at 21.47727MHz, yielding a CPU frequency of 1.7897725Mhz (which is in line with what the docs are saying). For PAL the video is clocked at 26.601712MHz, yielding a CPU frequency of 1.7734475. So it looks like in my CNESEngine object, I need to have videoClock and cpuDivisor variables in order to allow my main code to be region agnostic. Simple enough.

Back to what I'm going to do now -- Currently I have a timeless tight loop that runs a specific number of cycles before drawing the screen and generating the NMI for VBlank. As I said before, I'm tearing out the PPU logic; so Basically the whole drawing logic is going to be completely removed, and I will also remove the "dumb" processor loop. Instead, I'm going to maintain a timer, and run cycles based off of the time passed.

Now on to the math and logic (the best part!). If you didn't know, a Hertz (Hz) is defined as the number of cycles (the frequency) in a second. With this, I will obviously need a variable to store the value of SDL_GetTicks() at power-on time (GetTicks returns the number of milliseconds since the init of libSDL). I'll call this variable "lastCpuTick". Since a millisecond is 1/1000th of a second, a millisecond is thus 1000hz. When the code reaches the point where it runs the processor (executes opcodes), the code needs to store the current GetTicks value in a temporary variable (lets call it newCpuTick). Then, it needs to compute something like so:

cpuTickDiff = newCpuTick - lastCpuTick

Because someone MAY be running a retardedly fast computer, I cannot assume that cpuTickDiff will be non-zero. After calculating cpuTickDiff, if the result is 0, then I will skip processing CPU cycles this time around, and will leave lastCpuTick alone. Next time around it should be non-zero.

At this point I need to calculate the number of CPU cycles that must be ran. Again, if the value is 0, we will skip running the CPU, and will not update lastCpuTick. Here is the formula I have come up with:
cpuCyclesPerSecond = (videoClock / cpuDivisor)
cpuCyclesToRender = (cpuTickDiff / 1000) * cpuCyclesPerSecond

What I did here was first calculate the number of CPU cycles per second. Obviously the first formula does not change, and thus I will calculate this once when the region is set, and used the stored value. The cpuCyclesPerSecond formula takes the videoClock in Hz (not Mhz), and divides it by the CPU divider to get the CPU clock in Hz. After doing this, it is trivial to calculate the cycles to render. Since cpuCyclesPerSecond is literally just that -- cycles per second -- I simply need to calculate how much of a second (or perhaps even how MANY seconds) has passed, and simply multiply that with the cycles per second. For speed reasons I may simply truncate the decimal points instead of rounding.

If I get a result of at least 1 CPU cycle to render, I will store the value of cpuTickDiff back into lastCpuTick so that we don't loose any milliseconds used in our calculations.

That's all for now :)

3 comments:

Ben Ryves said...

The easiest solution is to simply tie your render loop to vblank (which for most people these days is the convenient NTSC-compatible 60Hz in any case).

Tim Sarbin said...

The problem is, I have to have my PPU render scanlines with very specific timings. This is because some games modify the scroll registers after a specific point in time in order to have "score" areas at the top or bottom of the screen (or split screen views). I just want to make it as accurate as possible, I'm not really worried about the increase in difficulty.

Ben Ryves said...

Per-scanline accuracy is still possible. I have a method in my video processor emulator called RasteriseLine() that returns true if it just rasterised the last line of the display. The hardware I'm working with executes 228 CPU clocks for every scanline, so I simply end up with the following:

public void RunFrame() {
do {
this.FetchExecute(228);
} while (!Video.RasteriseLine());
}

Post a Comment