/home

Query your Pentium CPU frequency using the RTC

Mon, 04 Mar 2024

The simple methods described here work with CPU having the CPUID (80486) and RDTSC (Pentium) instructions but are not meant to support CPU beyond the Intel P54C family because of the power management factor that could disrupt the measures.

However, it is still possible to disable the power management in those cases.
Help yourself to find out ;)

The code below is tailored for WATCOM C.

Pre-conditions: you need a Pentium

The processor monotonically increments the time-stamp counter MSR (Model Specific Register) every clock cycle and resets it to 0 whenever the processor is reset.

The code here after gives access to the rdtsc instruction from the C code through inline assembly so that we can read the CPU time stamp counter:

/* Fills the Time Stamp Counter. */
void x86_rdtsc(u64 __far *tsc);
#pragma aux x86_rdtsc = \
    "rdtsc" \
    "mov DWORD PTR [edi+4], edx" \
    "mov DWORD PTR [edi], eax" \
    parm [edi] \
    modify exact [eax ebx ecx edx edi];

The simple way

By simply waiting for 1 second, it's easy to retrieve the CPU frequency in Hz and then compute it in Mhz. The main drawback to this method is the 1 second delay you need to wait to query the CPU frequency.

u64 tsc0, tsc1;
x86_rdtsc(&tsc0);
sleep(1);
x86_rdtsc(&tsc1);
printf("clock frequency = %llu Hz\n", tsc1 - tsc0);
printf("clock frequency = %lu MHz\n", (tsc1 - tsc0)/1000000);

Slightly optimized

Derived from FREQUENC.ASM found in Intel Processor Identification and the CPUID Instruction.

I mainly removed the power-management registers (remember the introduction?) and rely on the 8254 PIT Channel 0 (System Time Counter / RTC) to code an optimized delay.

The 8254 Channel 0 System Time Counter is found at the BIOS data area address [0040:006C]. It is updated 18.2 times per seconds (e.g., every ~54.95ms), which means that 5 intervals is about 275ms.

First we need to read the bios tick count at address [0040:006C] ([046C] in protected mode)

u32 x86_read_bios_rtc();
#pragma aux x86_read_bios_rtc = \
    "mov    edi, 046Ch" \
    "mov    eax, [edi]" \
    value [eax] \
    modify exact [eax edi];

In the code to query the frequency, we make sure we're counting from the very start of a new tick (first while loop).

u32 ticks = x86_read_bios_rtc();    /* read the RTC counter */
while(x86_read_bios_rtc() == ticks) /* in case we're in the middle of a tick */
    ;

x86_rdtsc(&tsc0);                   /* read and save the initial TSC */

ticks += (5 + 1);                   /* now wait for the delay */
while(x86_read_bios_rtc() != ticks)
    ;
x86_rdtsc(&tsc1);                   /* read and save the new TSC */

cpu_performance_frequency = (tsc1 - tsc0) * (18.2f / 5);

Thanks to this method, the query latency is reduced from ~1000ms to ~275ms (+/- 54ms because of the first while loop to wait for the end of a started RTC tick)

Fine-grained approach

The previous method relies on the 8254 (PIT - Programmable Interval Time) Channel 0 default configuration which is updated every ~55ms, the complex alternative method would rely on modifying the IRQ0 update rate to 1ms so that we get a more precise time screening.

If you're coding a game-loop or demo-loop, you probably need a correct timestep so that your animations are consistent whatever the framerate (because using RDTSC for this purpose comes with a lot of CPU cycles penalty...).

Using such higher precision time counter can also make life easier to benchmark code, but this is another story.

Real-world tests

Hereafter the numbers I get under MS-DOS 7.1 with my machines:

Machine Version Computed frequency
Compaq P200 sleep() 208718873 Hz / 209 Mhz
Compaq P200 RTC Counter 199927588 Hz / 200 Mhz
Siemens P200 MMX sleep() aaaaaaaaa Hz / zzz Mhz
Siemens P200 MMX RTC Counter bbbbbbbbb Hz / yyy Mhz

Note: the code works under 86box and dosbox-x.

Code

cpu_freq.c

References

[1] Beyond MS-DOS and Pentium P54C: SysToolsLib

[2] OSDev.org Detecting_CPU_Speed

[3] Intel Processor Identification and the CPUID Instruction (mirror)

[4] PC BIOS Data Areas to find RTC 40:6C daily timer counter

[5] 8254 PIT (Programmable Interval Timer)