3

I'm working on an implementation for the DMG-01 (A.K.A gameboy 1989) on my github. I've already implemented both the APU and the PPU, with (almost) perfect timing on my pc (and the pc of my friends). However, when I run the emulator on one of my friend's pc, it runs twice as fast as mine or the rest of my friends.

The code for syncronizing the clock (between the gameboy and the pc it's running on) is as follows:

Clock.h Header File:

class Clock
{
// ...
public:
    void SyncClock();

private:
    /* API::LR35902_HZ_CLOCK is 4'194'304 */
    using lr35902_clock_period = std::chrono::duration<int64_t, std::ratio<1, API::LR35902_HZ_CLOCK>>;
    static constexpr lr35902_clock_period one_clock_period{1};
    using clock = std::chrono::high_resolution_clock;

private:
    decltype(clock::now()) _last_tick{std::chrono::time_point_cast<clock::duration>(clock::now() + one_clock_period)};
};

Clock.cpp file

void Clock::SyncClock()
{
    // Sleep until one tick has passed.
    std::this_thread::sleep_until(this->_last_tick);

    // Use time_point_cast to convert (via truncation towards zero) back to
    // the "native" duration of high_resolution_clock
    this->_last_tick = std::chrono::time_point_cast<clock::duration>(this->_last_tick + one_clock_period);
}

Which gets called in main.cpp like this:

int main()
{
    // ...
    while (true)
    {
        // processor.Clock() returns the number of clocks it took for the processor to run the
        // current instruction. We need to sleep this thread for each clock passed.
        for (std::size_t current_clock = processor.Clock(); current_clock > 0; --current_clock)
        {
            clock.SyncClock();
        }
    }
    // ...
}

Is there a reason why chrono in this case would be affected in a different way in other computers? Time is absolute, I would understand why in one pc, running the emulator would be slower, but why faster? I checked out the type of my clock (high_resolution_clock) but I don't see why this would be the case. Thanks!

Noam Rodrik
  • 552
  • 2
  • 16
  • If you check the `period` type of [`std::high_resolution_clock`](https://en.cppreference.com/w/cpp/chrono/high_resolution_clock), does it differ between the two systems? – Some programmer dude Oct 14 '20 at 11:52
  • @Someprogrammerdude My friend isn't much of a programmer so i'll have to install visual studio on his pc (if hes cool with but), but either way I don't see a reason why std::ratio would change between two systems (neither does the standard define such a difference) – Noam Rodrik Oct 14 '20 at 11:57
  • One of you runs hyperthreading and the other doesnt? – Surt Oct 14 '20 at 12:09
  • You don't have to install Visual Studio, just add logging of the [`std::ratio`](https://en.cppreference.com/w/cpp/numeric/ratio/ratio) `num` and `den` members in your program. – Some programmer dude Oct 14 '20 at 12:16
  • @Someprogrammerdude Good idea, i'll try it and update it here. – Noam Rodrik Oct 14 '20 at 12:18
  • @Surt How does hyperthreading affect "std::this_thread::sleep_until"? – Noam Rodrik Oct 14 '20 at 12:18
  • @NoamRodrik it shouldn't but scheduling might be different. – Surt Oct 14 '20 at 12:55
  • How quickly a program resumes after a sleep_until() is dependent on system configuration. There are programs that mess with that, browsers do for example, Chrome is especially notorious. To get consistent timing you have to get ahead of them, being at least as unreasonable as such a program. Call [timeBeginPeriod(1)](https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod). Get a diagnostic by running powercfg.exe /energy from an elevated command prompt. – Hans Passant Oct 14 '20 at 13:12

1 Answers1

5

I think you may be running into overflow under the hood of <chrono>.

The expression:

clock::now() + one_clock_period

is problematic. clock is high_resolution_clock, and it is common for this to have nanoseconds resolution. one_clock_period has units of 1/4'194'304. The resultant expression will be a time_point with a period of 1/8'192'000'000'000.

Using signed 64 bit integral types, the max() on such a precision is slightly over 13 days. So if clock::now() returns a .time_since_epoch() greater than 13 days, _last_tick is going to overflow, and may some times be negative (depending on how much clock::now() is beyond 13 days).

To correct try casting one_clock_period to the precision of clock immediately:

static constexpr clock::duration one_clock_period{
    std::chrono::duration_cast<clock::duration>(lr35902_clock_period{1})};
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577