How to Implement Overclocking#
This guide explains how overclocking is implemented in vAmiga. By following a similar approach, you can integrate overclocking into your own application.
Running the CPU with Native Speed#
In an actual Amiga, hardware components run at different speeds because they are driven by distinct clocks. All clock signals originate from a single master clock running at 28.375160 MHz on PAL machines.
From this master clock, the CPU clock is derived by dividing its frequency by four, resulting in a CPU speed of 7.093790 MHz. Additionally, two other clocks play important roles:
E-clock: Generated by the CPU by dividing its clock frequency by 10. It drives the two CIAs but is irrelevant for overclocking.
Color clock: Derived from the master clock by dividing its frequency by 8, resulting in a bus speed of 3.546895 MHz. This clock is crucial because it dictates the memory bus speed.
By default, two CPU cycles fit within one bus (DMA) cycle, making synchronization straightforward. The next sections will explain how vAmiga adapts this timing for overclocking.
In a real Amiga, the speed of different hardware components varies because they are driven by different clocks. All clock signals are derived from a single master clock, which runs at 28.375160 MHz on PAL machines. From the master clock, the CPU clock is derived by dividing the frequency of the master clock by four. As a result, the CPU runs at 7.093790 MHz on PAL machines. Two more clocks come into play: the E clock and the color clock. The E-clock is generated by the CPU itself by dividing its own clock frequency by 10. This signal drives the two interface adapters (CIAs) and plays no role here. The color clock, however, is of great importance because it determines the pace of the memory bus. The color clock signal is derived by dividing the frequency of the master clock by 8. This results in a bus speed of 3.546895 MHz on PAL machines. In summary: In the default configuration, two CPU cycles are executed within one bus cycle, which makes synchronizing the CPU with the memory bus quite simple in the default configuration. In the following, we use the term DMA cycle as a synonym for bus cycle.
Synchronization in vAmiga#
In vAmiga, timing is managed by the sync
function in the Moira class. The way sync
is called depends on the emulation mode:
Precise cycle mode
sync
is executed before each memory access, meaning multiple calls per instruction.Simple cycle mode
sync
is executed only once per instruction, at the end of the instruction handler. The function receives the number of elapsed CPU cycles as an argument. Since 2 CPU cycles correspond to 1 DMA cycle, if sync(4) is called, vAmiga must emulate 2 DMA cycles before performing the memory access. With this timing model in place, overclocking can be implemented by adjusting how CPU cycles are processed relative to DMA cycles.
Let’s start by examining how the standard case is implemented in vAmiga:
void
Moira::sync(int cycles)
{
CPU *cpu = (CPU *)this;
if (!cpu->config.overclocking) {
// Advance the CPU clock
clock += cycles;
// Emulate Agnus up to the same cycle
agnus.execute(CPU_AS_DMA_CYCLES(cycles));
} else {
// The overclocking case (see below)
...
}
}
The implementation of overclocking in vAmiga is straightforward. When overclocking is disabled, the internal clock counter (clock
) is incremented by the number of elapsed CPU cycles (cycles
). Then, Agnus is asked to emulate the surrounding logic for the corresponding number of DMA cycles. As you may have guessed, converting CPU cycles to DMA cycles is simply a division by two:
// Converts CPU cycles to DMA cycles
#define CPU_AS_DMA_CYCLES(cycles) (cycles >> 1)
Overclocking the CPU#
To emulate an overclocked CPU in vAmiga, simply set config.overclocking
to a non-zero value. This value acts as an overclocking factor.
config.overclocking
= 1 The CPU runs at native speed (7.093790 MHz in PAL mode). However, vAmiga still executes the overclocking logic, as seen in the else branch of the code snippet below.config.overclocking
= 2 The CPU runs at 14.187580 MHz (twice the native speed).Higher values further increase the CPU frequency accordingly.
Now, let’s take a closer look at how the overclocking logic is handled in vAmiga.
void
Moira::sync(int cycles)
{
CPU *cpu = (CPU *)this;
if (!cpu->config.overclocking) {
// The standard case (see above)
...
} else {
// Compute the number of mico-cycles executed in one DMA cycle
auto microCyclesPerCycle = 2 * cpu->config.overclocking;
cpu->debt += cycles;
while (cpu->debt >= microCyclesPerCycle) {
// Advance the CPU clock by one DMA cycle
clock += 2;
// Emulate Agnus for one DMA cycle
agnus.execute();
cpu->debt -= microCyclesPerCycle;
}
}
}
In the else branch, vAmiga determines how many CPU cycles occur within a single DMA cycle. Since two native CPU cycles correspond to one DMA cycle, this value equals the overclocking factor times 2.
Next, the function updates debt
, a sub-cycle counter that tracks the number of CPU cycles that have elapsed but have not yet been emulated on the Agnus side. The elapsed cycles, passed as a function argument, are simply added to debt
.
To keep Agnus synchronized, the function then enters a while loop, processing Agnus for each full DMA cycle that has elapsed. Any remaining fractional cycles stay in debt
, ensuring accurate timing across multiple sync calls.