diff --git a/benchmark/bench1.cpp b/benchmark/bench1.cpp
index d170de13cc..f9d45a5541 100644
--- a/benchmark/bench1.cpp
+++ b/benchmark/bench1.cpp
@@ -69,9 +69,16 @@ int main(int argc, char** argv) {
return -1;
}
- constexpr uint64_t tbr_freq = 16705000;
-
- ppc_cpu_init(grackle_obj, PPC_VER::MPC750, false, tbr_freq);
+ constexpr uint64_t bus_freq = 66'820'000ULL;
+ constexpr uint64_t tbr_freq = bus_freq / 4;
+ constexpr uint64_t core_freq = bus_freq * 7 / 2; // 233.87 MHz (CPU PLL ratio of 3.5)
+
+ ppc_cpu_init(grackle_obj, {
+ .version = PPC_VER::MPC750,
+ .timebase_freq_hz = tbr_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
/* load executable code into RAM at address 0 */
for (i = 0; i < sizeof(cs_code) / sizeof(cs_code[0]); i++) {
diff --git a/core/hostevents_sdl.cpp b/core/hostevents_sdl.cpp
index 9110929b04..d06d5ce438 100644
--- a/core/hostevents_sdl.cpp
+++ b/core/hostevents_sdl.cpp
@@ -124,23 +124,23 @@ void EventManager::poll_events() {
}
return;
}
- // Control-Alt-Shift-+: speed up icnt_factor
+ // Control-Alt-Shift-+: increase instruction period
if (event.key.keysym.sym == SDLK_EQUALS &&
(event.key.keysym.mod & KMOD_ALL) == (KMOD_LCTRL | KMOD_LALT | KMOD_LSHIFT)
) {
if (event.type == SDL_KEYUP) {
- int icnt_counter_val = increment_icnt_factor();
- LOG_F(INFO, "Incremented icnt_factor: %d", icnt_counter_val);
+ uint64_t instruction_period = increment_instruction_period();
+ LOG_F(INFO, "Incremented instruction period: %d", (int)instruction_period);
}
return;
}
- // Control-Alt-Shift--: slow down icnt_factor
+ // Control-Alt-Shift--: decrease instruction period
if (event.key.keysym.sym == SDLK_MINUS &&
(event.key.keysym.mod & KMOD_ALL) == (KMOD_LCTRL | KMOD_LALT | KMOD_LSHIFT)
) {
if (event.type == SDL_KEYUP) {
- int icnt_counter_val = decrement_icnt_factor();
- LOG_F(INFO, "Decremented icnt_factor: %d", icnt_counter_val);
+ uint64_t instruction_period = decrement_instruction_period();
+ LOG_F(INFO, "Decremented instruction period: %d", (int)instruction_period);
}
return;
}
diff --git a/cpu/ppc/ppcemu.h b/cpu/ppc/ppcemu.h
index af75394e51..b7b90e70c5 100644
--- a/cpu/ppc/ppcemu.h
+++ b/cpu/ppc/ppcemu.h
@@ -470,8 +470,21 @@ typedef enum {
// Placeholder value for cases where we don't have a currently-executing instruction.
constexpr uint32_t NO_OPCODE = 0;
+struct PPC_CPU_Config {
+ uint32_t version;
+ uint64_t timebase_freq_hz;
+ uint64_t bus_freq_hz;
+ uint64_t core_freq_hz;
+};
+
+enum class PPC_CPU_TimingMode {
+ Fixed,
+ PerMachine,
+};
+
// Function prototypes
-extern void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version, bool include_601, uint64_t tb_freq);
+extern void set_cpu_timing_mode(PPC_CPU_TimingMode mode);
+extern void ppc_cpu_init(MemCtrlBase* mem_ctrl, const PPC_CPU_Config& config);
extern void ppc_mmu_init();
void ppc_illegalop(uint32_t opcode);
@@ -720,10 +733,10 @@ extern void ppc_change_endian(bool newLE);
uint64_t get_reg(std::string reg_name); /* get content of the register reg_name */
void set_reg(std::string reg_name, uint64_t val); /* set reg_name to val */
-/* icnt_factor control */
-extern int increment_icnt_factor();
-extern int decrement_icnt_factor();
-extern int get_icnt_factor();
+/* instruction period control */
+extern uint64_t increment_instruction_period();
+extern uint64_t decrement_instruction_period();
+extern uint64_t get_instruction_period();
/* toggle_g_realtime */
extern bool toggle_g_realtime();
diff --git a/cpu/ppc/ppcexec.cpp b/cpu/ppc/ppcexec.cpp
index e089312d80..5bb70e271f 100644
--- a/cpu/ppc/ppcexec.cpp
+++ b/cpu/ppc/ppcexec.cpp
@@ -26,6 +26,7 @@ along with this program. If not, see .
#include "ppcdisasm.h"
#include
+#include
#include
#include
#include
@@ -132,8 +133,26 @@ bool dec_exception_pending = false;
/* variables related to virtual time */
bool g_realtime = false;
uint64_t g_nanoseconds_base;
-uint64_t g_icycles;
-int icnt_factor;
+// Fixed-point nanoseconds, with four fractional bits. This provides enough
+// precision for the supported CPU clocks and over 36 years before overflow.
+using FixedNanoseconds = uint64_t;
+static constexpr int VIRT_TIME_FRAC_BITS = 4;
+static constexpr FixedNanoseconds FIXED_INSTRUCTION_PERIOD =
+ 16ULL << VIRT_TIME_FRAC_BITS; // 16ns/instruction (62.5 MHz)
+static FixedNanoseconds g_virt_time;
+static FixedNanoseconds g_instruction_period;
+static PPC_CPU_TimingMode g_cpu_timing_mode = PPC_CPU_TimingMode::Fixed;
+
+static constexpr FixedNanoseconds instruction_period_for_frequency(uint64_t core_freq_hz)
+{
+ return (((uint64_t)NS_PER_SEC << VIRT_TIME_FRAC_BITS) + core_freq_hz / 2) /
+ core_freq_hz;
+}
+
+void set_cpu_timing_mode(PPC_CPU_TimingMode mode)
+{
+ g_cpu_timing_mode = mode;
+}
/* global variables related to the timebase facility */
uint64_t tbr_wr_timestamp; // stores vCPU virtual time of the last TBR write
@@ -356,7 +375,7 @@ uint64_t get_virt_time_ns()
if (g_realtime) {
return cpu_now_ns() - g_nanoseconds_base;
} else {
- return g_icycles << icnt_factor;
+ return g_virt_time >> VIRT_TIME_FRAC_BITS;
}
}
@@ -365,7 +384,7 @@ void set_virt_time_ns(uint64_t time_now)
if (g_realtime) {
g_nanoseconds_base = cpu_now_ns() - time_now - 5000;
} else {
- g_icycles = time_now >> icnt_factor;
+ g_virt_time = time_now << VIRT_TIME_FRAC_BITS;
}
uint64_t time_new = get_virt_time_ns();
if (g_realtime && time_new > time_now) {
@@ -375,16 +394,16 @@ void set_virt_time_ns(uint64_t time_now)
LOG_F(INFO, "time before: %lld after: %lld change: %lld", time_now, time_new, time_new - time_now);
}
-static uint64_t process_events()
+static FixedNanoseconds process_events()
{
exec_timer = false;
uint64_t slice_ns = TimerManager::get_instance()->process_timers();
if (slice_ns == 0) {
- // execute 25.000 cycles
+ // execute 25,000 instructions
// if there are no pending timers
- return g_icycles + 25000;
+ return g_virt_time + 25'000 * g_instruction_period;
}
- return g_icycles + (slice_ns >> icnt_factor) + 1;
+ return g_virt_time + (slice_ns << VIRT_TIME_FRAC_BITS);
}
static void force_cycle_counter_reload()
@@ -393,29 +412,29 @@ static void force_cycle_counter_reload()
exec_timer = true;
}
-int increment_icnt_factor()
+uint64_t increment_instruction_period()
{
uint64_t time_now = get_virt_time_ns();
- icnt_factor += 1;
+ g_instruction_period += 1;
set_virt_time_ns(time_now);
force_cycle_counter_reload();
- return icnt_factor;
+ return g_instruction_period;
}
-int decrement_icnt_factor()
+uint64_t decrement_instruction_period()
{
- if (icnt_factor > 0) {
+ if (g_instruction_period > 0) {
uint64_t time_now = get_virt_time_ns();
- icnt_factor -= 1;
+ g_instruction_period -= 1;
set_virt_time_ns(time_now);
force_cycle_counter_reload();
}
- return icnt_factor;
+ return g_instruction_period;
}
-int get_icnt_factor()
+uint64_t get_instruction_period()
{
- return icnt_factor;
+ return g_instruction_period;
}
bool toggle_g_realtime()
@@ -437,8 +456,11 @@ typedef enum {
template
static void ppc_exec_inner(uint32_t start_addr, uint32_t size)
{
- uint64_t max_cycles = 0;
- uint32_t page_start, eb_start, eb_end = 0;
+ FixedNanoseconds event_deadline = 0;
+ // Keep these hot-loop values in registers.
+ FixedNanoseconds virt_time = g_virt_time;
+ FixedNanoseconds instruction_period = g_instruction_period;
+ uint32_t eb_start, eb_end = 0;
uint32_t opcode;
PPCOpcode* opcode_grabber = ppc_opcode_grabber;
uint8_t* pc_real;
@@ -451,30 +473,37 @@ static void ppc_exec_inner(uint32_t start_addr, uint32_t size)
if (ppc_state.pc >= eb_end) {
// define boundaries of the next execution block
// max execution block length = one memory page
- eb_start = ppc_state.pc;
- page_start = eb_start & PPC_PAGE_MASK;
- eb_end = page_start + PPC_PAGE_SIZE - 1;
+ eb_start = ppc_state.pc;
+ eb_end = (eb_start & PPC_PAGE_MASK) + PPC_PAGE_SIZE - 1;
exec_flags = 0;
pc_real = mmu_translate_imem(eb_start ATPCP); // &pcp
}
opcode = ppc_read_instruction(pc_real);
ppc_main_opcode(opcode_grabber, opcode);
- if (g_icycles++ >= max_cycles || exec_timer) [[unlikely]]
- max_cycles = process_events();
+ virt_time += instruction_period;
+ g_virt_time = virt_time;
+ if (virt_time >= event_deadline || exec_timer) [[unlikely]] {
+ event_deadline = process_events();
+ virt_time = g_virt_time;
+ instruction_period = g_instruction_period;
+ }
if (exec_flags) {
if ((exec_flags & EXEF_SLEEP) && !(exec_flags & EXEF_EXCEPTION)) [[unlikely]] {
while (power_on && (exec_flags & EXEF_SLEEP)) {
- max_cycles = process_events();
+ event_deadline = process_events();
+ virt_time = g_virt_time;
+ instruction_period = g_instruction_period;
if (!(exec_flags & EXEF_SLEEP)) {
break;
}
- if (max_cycles > g_icycles) {
- g_icycles = max_cycles;
+ if (event_deadline > virt_time) {
+ virt_time = event_deadline;
} else {
- g_icycles++;
+ virt_time += instruction_period;
}
+ g_virt_time = virt_time;
}
}
if (exec_flags & EXEF_OPC_DECODER) [[unlikely]] {
@@ -482,14 +511,14 @@ static void ppc_exec_inner(uint32_t start_addr, uint32_t size)
}
// define next execution block
eb_start = ppc_next_instruction_address;
- if (!(exec_flags & EXEF_RFI) && (eb_start & PPC_PAGE_MASK) == page_start) {
+ if (!(exec_flags & EXEF_RFI) &&
+ (eb_start & PPC_PAGE_MASK) == (eb_end & PPC_PAGE_MASK)) {
if (endian == big_end)
INCPC((int)eb_start - (int)ppc_state.pc);
else
pc_real = mmu_translate_imem(eb_start ATPCP); // &pcp
} else {
- page_start = eb_start & PPC_PAGE_MASK;
- eb_end = page_start + PPC_PAGE_SIZE - 1;
+ eb_end = (eb_start & PPC_PAGE_MASK) + PPC_PAGE_SIZE - 1;
pc_real = mmu_translate_imem(eb_start ATPCP); // &pcp
}
ppc_state.pc = eb_start;
@@ -552,7 +581,7 @@ void ppc_exec_single()
uint8_t* pc_real = mmu_translate_imem(ppc_state.pc ATPCP); // &pcp
uint32_t opcode = ppc_read_instruction(pc_real);
ppc_main_opcode(ppc_opcode_grabber, opcode);
- g_icycles++;
+ g_virt_time += g_instruction_period;
process_events();
if (exec_flags) {
@@ -993,21 +1022,59 @@ void initialize_ppc_opcode_table() {
}
}
-void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version, bool do_include_601, uint64_t tb_freq)
+static uint32_t get_cpu_pll_value(const PPC_CPU_Config& config)
+{
+ switch (config.version) {
+ case PPC_VER::MPC603EV:
+ if (config.core_freq_hz * 2 == config.bus_freq_hz * 9)
+ return 7; // 4.5:1 ratio
+ if (config.core_freq_hz == config.bus_freq_hz * 5)
+ return 11; // 5:1 ratio
+ if (config.core_freq_hz * 2 == config.bus_freq_hz * 11)
+ return 9; // 5.5:1 ratio
+ break;
+ case PPC_VER::MPC750:
+ if (config.core_freq_hz * 2 == config.bus_freq_hz * 7)
+ return 14; // 3.5:1 ratio
+ if (config.core_freq_hz == config.bus_freq_hz * 6)
+ return 13; // 6:1 ratio
+ if (config.core_freq_hz == config.bus_freq_hz * 8)
+ return 12; // 8:1 ratio
+ break;
+ default:
+ break;
+ }
+
+ ABORT_F("Unsupported CPU/bus frequency combination: %" PRIu64 "/%" PRIu64 " Hz",
+ config.core_freq_hz, config.bus_freq_hz);
+}
+
+void ppc_cpu_init(MemCtrlBase* mem_ctrl, const PPC_CPU_Config& config)
{
+ if (!config.timebase_freq_hz || !config.bus_freq_hz || !config.core_freq_hz)
+ ABORT_F("CPU clock frequencies must be non-zero");
+
mem_ctrl_instance = mem_ctrl;
int_pin = false;
std::memset(&ppc_state, 0, sizeof(ppc_state));
set_host_rounding_mode(0);
- ppc_state.spr[SPR::PVR] = cpu_version;
- is_601 = (cpu_version >> 16) == 1;
- include_601 = !is_601 & do_include_601;
+ ppc_state.spr[SPR::PVR] = config.version;
+ switch (config.version) {
+ case PPC_VER::MPC603EV:
+ case PPC_VER::MPC750:
+ ppc_state.spr[SPR::HID1] = get_cpu_pll_value(config) << 28;
+ break;
+ default:
+ break;
+ }
+ is_601 = (config.version >> 16) == 1;
+ include_601 = is_601;
ppc_pow_mode = PPCPowMode::None;
ppc_pow_hid0_mask = 0;
- switch (cpu_version) {
+ switch (config.version) {
case PPC_VER::MPC603:
case PPC_VER::MPC603E:
case PPC_VER::MPC603EV:
@@ -1037,25 +1104,15 @@ void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version, bool do_include_6
mach_timebase_info(&timebase_info);
#endif
g_nanoseconds_base = cpu_now_ns();
- g_icycles = 0;
-
-// // // PDM cpu clock calculated at 0x403036CC in r3
-// icnt_factor = 11; // 1 instruction = 2048 ns = 0.488 MHz // 00068034 = 0.426036 MHz = 2347.219 ns // floppy doesn't work
-// icnt_factor = 10; // 1 instruction = 1024 ns = 0.977 MHz // 000D204C = 0.860236 MHz = 1162.471 ns // [0..10] MHz = invalid clock for PDM gestalt calculation
-// icnt_factor = 9; // 1 instruction = 512 ns = 1.953 MHz // 001A6081 = 1.728641 MHz = 578.489 ns // [0..10] MHz = invalid clock for PDM gestalt calculation
-// icnt_factor = 8; // 1 instruction = 256 ns = 3.906 MHz // 0034E477 = 3.466359 MHz = 288.487 ns // [0..10] MHz = invalid clock for PDM gestalt calculation
-// icnt_factor = 7; // 1 instruction = 128 ns = 7.813 MHz // 0069E54C = 6.939980 MHz = 144.092 ns // [0..10] MHz = invalid clock for PDM gestalt calculation
-// icnt_factor = 6; // 1 instruction = 64 ns = 15.625 MHz // 00D3E6F5 = 13.887221 MHz = 72.008 ns // (10..60] = 50, (60..73] = 66, (73..100] = 80 MHz
-// icnt_factor = 5; // 1 instruction = 32 ns = 31.250 MHz // 01A7B672 = 27.768434 MHz = 36.012 ns //
- icnt_factor = 4; // 1 instruction = 16 ns = 62.500 MHz // 034F0F0F = 55.512847 MHz = 18.013 ns // 6100/60 in Apple System Profiler
-// icnt_factor = 3; // 1 instruction = 8 ns = 125.000 MHz // 069E1E1E = 111.025694 MHz = 9.006 ns // (100...) MHz = invalid clock for PDM gestalt calculation
-// icnt_factor = 2; // 1 instruction = 4 ns = 250.000 MHz // 0D3C3C3C = 222.051388 MHz = 4.503 ns // (100...) MHz = invalid clock for PDM gestalt calculation
-// icnt_factor = 1; // 1 instruction = 2 ns = 500.000 MHz // 1A611A7B = 442.571387 MHz = 2.259 ns // (100...) MHz = invalid clock for PDM gestalt calculation
-// icnt_factor = 0; // 1 instruction = 1 ns = 1500.000 MHz // 3465B2D9 = 879.080153 MHz = 1.137 ns // (100...) MHz = invalid clock for PDM gestalt calculation
+ g_virt_time = 0;
+ g_instruction_period = g_cpu_timing_mode == PPC_CPU_TimingMode::PerMachine
+ ? instruction_period_for_frequency(config.core_freq_hz)
+ : FIXED_INSTRUCTION_PERIOD;
tbr_wr_timestamp = 0;
rtc_timestamp = 0;
tbr_wr_value = 0;
+ uint64_t tb_freq = config.timebase_freq_hz;
if (is_601)
tb_freq <<= 7;
tbr_freq_shift = 0;
diff --git a/machines/machinealchemy.cpp b/machines/machinealchemy.cpp
index d7c0b6d84d..36ad0844bc 100644
--- a/machines/machinealchemy.cpp
+++ b/machines/machinealchemy.cpp
@@ -99,16 +99,19 @@ int MachineAlchemy::initialize(const std::string &id) {
psx_obj->map_phys_ram();
// configure CPU clocks
- uint64_t bus_freq = 40000000ULL;
+ uint64_t bus_freq = 40'000'000ULL;
uint64_t timebase_freq = bus_freq / 4;
+ uint64_t core_freq = 180'000'000ULL;
psx_obj->set_bus_speed(PSX_BUS_SPEED_40);
- // init virtual CPU and request MPC603ev
- ppc_cpu_init(psx_obj, PPC_VER::MPC603E, false, timebase_freq);
-
- // CPU frequency is hardcoded to 225 MHz for now
- //ppc_state.spr[SPR::HID1] = get_cpu_pll_value(225000000) << 28;
+ // init virtual CPU and request MPC603e
+ ppc_cpu_init(psx_obj, {
+ .version = PPC_VER::MPC603E,
+ .timebase_freq_hz = timebase_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
return 0;
}
diff --git a/machines/machinebondi.cpp b/machines/machinebondi.cpp
index bac4f5c1f0..65117d70d4 100644
--- a/machines/machinebondi.cpp
+++ b/machines/machinebondi.cpp
@@ -88,14 +88,17 @@ int MachineBondi::initialize(const std::string &id) {
setup_ram_slot("RAM_DIMM_2", 0x51, bank_2_size);
// configure CPU clocks
- uint64_t bus_freq = 66820000ULL;
+ uint64_t bus_freq = 66'820'000ULL;
uint64_t timebase_freq = bus_freq / 4;
+ uint64_t core_freq = bus_freq * 7 / 2; // 233.87 MHz (CPU PLL ratio of 3.5)
// initialize virtual CPU and request MPC750 CPU aka G3
- ppc_cpu_init(grackle_obj, PPC_VER::MPC750, false, timebase_freq);
-
- // set CPU PLL ratio to 3.5
- ppc_state.spr[SPR::HID1] = 0xE << 28;
+ ppc_cpu_init(grackle_obj, {
+ .version = PPC_VER::MPC750,
+ .timebase_freq_hz = timebase_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
return 0;
}
diff --git a/machines/machinecatalyst.cpp b/machines/machinecatalyst.cpp
index 67fbc09933..86786cee15 100644
--- a/machines/machinecatalyst.cpp
+++ b/machines/machinecatalyst.cpp
@@ -96,19 +96,30 @@ int MachineCatalyst::initialize(const std::string &id) {
std::string cpu = GET_STR_PROP("cpu");
if (cpu == "601") {
+ uint64_t bus_freq = 37'500'000ULL;
+ uint64_t core_freq = 75'000'000ULL;
+
// init virtual CPU and request MPC601
- ppc_cpu_init(platinum_obj, PPC_VER::MPC601, true, 7833600ULL);
+ ppc_cpu_init(platinum_obj, {
+ .version = PPC_VER::MPC601,
+ .timebase_freq_hz = 7'833'600ULL,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
}
else if (cpu == "750") {
// configure CPU clocks
- uint64_t bus_freq = 50000000ULL;
+ uint64_t bus_freq = 50'000'000ULL;
uint64_t timebase_freq = bus_freq / 4;
+ uint64_t core_freq = bus_freq * 8; // 400 MHz (matches G3 upgrade cards; CPU PLL ratio to 8)
// initialize virtual CPU and request MPC750 CPU aka G3
- ppc_cpu_init(platinum_obj, PPC_VER::MPC750, false, timebase_freq);
-
- // set CPU PLL ratio to 3.5
- ppc_state.spr[SPR::HID1] = 0xE << 28;
+ ppc_cpu_init(platinum_obj, {
+ .version = PPC_VER::MPC750,
+ .timebase_freq_hz = timebase_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
}
return 0;
diff --git a/machines/machinefactory.cpp b/machines/machinefactory.cpp
index b3dbfb7f98..acc92ea287 100644
--- a/machines/machinefactory.cpp
+++ b/machines/machinefactory.cpp
@@ -95,6 +95,7 @@ static const map PropHelp = {
{"serial_backend", "specifies the backend for the serial port"},
{"emmo", "enables/disables factory HW tests during startup"},
{"cpu", "specifies CPU"},
+ {"cpu_freq", "specifies CPU frequency in MHz"},
{"adb_devices", "specifies which ADB device(s) to attach"},
{"pds", "specify device for the processsor direct slot"},
};
diff --git a/machines/machinegazelle.cpp b/machines/machinegazelle.cpp
index 894a9349a2..c7e19be27c 100644
--- a/machines/machinegazelle.cpp
+++ b/machines/machinegazelle.cpp
@@ -46,20 +46,6 @@ static std::vector psx_irq_map = {
{"pci_F1", DEV_FUN(0x12,0), IntSrc::PCI_F},
};
-// TODO: move it to /cpu/ppc
-int get_cpu_pll_value(const uint64_t cpu_freq_hz) {
- switch (cpu_freq_hz / 1000000) {
- case 225:
- return 7; // 4.5:1 ratio
- case 250:
- return 11; // 5:1 ratio
- case 275:
- return 9; // 5.5:1 ratio
- default:
- ABORT_F("Unsupported CPU frequency %llu Hz", cpu_freq_hz);
- }
-}
-
class MachineGazelle : public Machine {
public:
static std::unique_ptr create5500() {
@@ -126,14 +112,17 @@ int MachineGazelle::initialize(const std::string &id) {
psx_obj->set_bus_speed(PSX_BUS_SPEED_50);
// configure CPU clocks
- uint64_t bus_freq = 50000000ULL;
+ uint64_t bus_freq = 50'000'000ULL;
uint64_t timebase_freq = bus_freq / 4;
+ uint64_t core_freq = GET_INT_PROP("cpu_freq") * 1'000'000ULL; // 225, 250, or 275 MHz
// init virtual CPU and request MPC603ev
- ppc_cpu_init(psx_obj, PPC_VER::MPC603EV, false, timebase_freq);
-
- // CPU frequency is hardcoded to 225 MHz for now
- ppc_state.spr[SPR::HID1] = get_cpu_pll_value(225000000) << 28;
+ ppc_cpu_init(psx_obj, {
+ .version = PPC_VER::MPC603EV,
+ .timebase_freq_hz = timebase_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
return 0;
}
@@ -147,6 +136,8 @@ static const PropMap pm6500_settings = {
new IntProperty( 0, std::vector({0, 4, 8, 16, 32, 64}))},
{"emmo",
new BinProperty(0)},
+ {"cpu_freq",
+ new IntProperty(225, std::vector({225, 250, 275}))},
{"hdd_config",
new StrProperty("Ide0:0")},
{"pci_F1",
diff --git a/machines/machinegossamer.cpp b/machines/machinegossamer.cpp
index 143362de86..4f746551dd 100644
--- a/machines/machinegossamer.cpp
+++ b/machines/machinegossamer.cpp
@@ -188,14 +188,17 @@ int MachineGossamer::initialize(const std::string &id) {
i2c_bus->register_device(0x53, perch_id);
// configure CPU clocks
- uint64_t bus_freq = 66820000ULL;
+ uint64_t bus_freq = 66'820'000ULL;
uint64_t timebase_freq = bus_freq / 4;
+ uint64_t core_freq = bus_freq * 7 / 2; // 233.87 MHz (CPU PLL ratio of 3.5)
// initialize virtual CPU and request MPC750 CPU aka G3
- ppc_cpu_init(grackle_obj, PPC_VER::MPC750, false, timebase_freq);
-
- // set CPU PLL ratio to 3.5
- ppc_state.spr[SPR::HID1] = 0xE << 28;
+ ppc_cpu_init(grackle_obj, {
+ .version = PPC_VER::MPC750,
+ .timebase_freq_hz = timebase_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
return 0;
}
diff --git a/machines/machinepdm.cpp b/machines/machinepdm.cpp
index 443a7752d0..2f94d48881 100644
--- a/machines/machinepdm.cpp
+++ b/machines/machinepdm.cpp
@@ -87,16 +87,24 @@ int MachinePdm::initialize(const std::string &id) {
LOG_F(INFO, "Building machine PDM...");
uint16_t machine_id;
+ uint64_t bus_freq;
+ uint64_t core_freq;
// get raw pointer to HMC object
HMC* hmc_obj = dynamic_cast(gMachineObj->get_comp_by_name("HMC"));
if (id == "pm6100") {
machine_id = 0x3010;
+ bus_freq = 30'000'000ULL;
+ core_freq = 60'000'000ULL;
} else if (id == "pm7100") {
machine_id = 0x3012;
+ bus_freq = 33'000'000ULL;
+ core_freq = 66'000'000ULL;
} else if (id == "pm8100") {
machine_id = 0x3013;
+ bus_freq = 40'000'000ULL;
+ core_freq = 80'000'000ULL;
} else {
LOG_F(ERROR, "Unknown machine ID: %s!", id.c_str());
return -1;
@@ -136,7 +144,12 @@ int MachinePdm::initialize(const std::string &id) {
setup_pds();
// Init virtual CPU and request MPC601
- ppc_cpu_init(hmc_obj, PPC_VER::MPC601, true, 7833600ULL);
+ ppc_cpu_init(hmc_obj, {
+ .version = PPC_VER::MPC601,
+ .timebase_freq_hz = 7'833'600ULL,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
return 0;
}
diff --git a/machines/machinepippin.cpp b/machines/machinepippin.cpp
index 8ced27d21b..839b959b8e 100644
--- a/machines/machinepippin.cpp
+++ b/machines/machinepippin.cpp
@@ -74,7 +74,12 @@ int MachinePippin::initialize(const std::string &id) {
aspen_obj->insert_ram_dimm(3, GET_INT_PROP("rambank4_size")); // RAM expansion slot
// init virtual CPU
- ppc_cpu_init(aspen_obj, PPC_VER::MPC603, false, 16500000ULL);
+ ppc_cpu_init(aspen_obj, {
+ .version = PPC_VER::MPC603,
+ .timebase_freq_hz = 16'500'000ULL,
+ .bus_freq_hz = 33'000'000ULL,
+ .core_freq_hz = 66'000'000ULL,
+ });
return 0;
}
diff --git a/machines/machinetnt.cpp b/machines/machinetnt.cpp
index 349ce232e5..f23adc020d 100644
--- a/machines/machinetnt.cpp
+++ b/machines/machinetnt.cpp
@@ -136,21 +136,39 @@ int MachineTnt::initialize(const std::string &id) {
// init virtual CPU
std::string cpu = GET_STR_PROP("cpu");
if (cpu == "604e")
- ppc_cpu_init(memctrl_obj, PPC_VER::MPC604E, false, 12500000ULL);
+ ppc_cpu_init(memctrl_obj, {
+ .version = PPC_VER::MPC604E,
+ .timebase_freq_hz = 12'500'000ULL,
+ .bus_freq_hz = 50'000'000ULL,
+ .core_freq_hz = 200'000'000ULL, // 200 MHz
+ });
else if (cpu == "604")
- ppc_cpu_init(memctrl_obj, PPC_VER::MPC604, false, 12500000ULL);
+ ppc_cpu_init(memctrl_obj, {
+ .version = PPC_VER::MPC604,
+ .timebase_freq_hz = 12'500'000ULL,
+ .bus_freq_hz = 50'000'000ULL,
+ .core_freq_hz = 150'000'000ULL, // 150 MHz
+ });
else if (cpu == "601")
- ppc_cpu_init(memctrl_obj, PPC_VER::MPC601, true, 7833600ULL);
+ ppc_cpu_init(memctrl_obj, {
+ .version = PPC_VER::MPC601,
+ .timebase_freq_hz = 7'833'600ULL,
+ .bus_freq_hz = 50'000'000ULL,
+ .core_freq_hz = 100'000'000ULL, // 100 MHz
+ });
else if (cpu == "750") {
// configure CPU clocks
- uint64_t bus_freq = 50000000ULL;
+ uint64_t bus_freq = 50'000'000ULL;
uint64_t timebase_freq = bus_freq / 4;
+ uint64_t core_freq = bus_freq * 6; // 300 MHz (matches G3 upgrade cards; CPU PLL ratio of 6)
// initialize virtual CPU and request MPC750 CPU aka G3
- ppc_cpu_init(memctrl_obj, PPC_VER::MPC750, false, timebase_freq);
-
- // set CPU PLL ratio to 3.5
- ppc_state.spr[SPR::HID1] = 0xE << 28;
+ ppc_cpu_init(memctrl_obj, {
+ .version = PPC_VER::MPC750,
+ .timebase_freq_hz = timebase_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
}
return 0;
diff --git a/machines/machineyosemite.cpp b/machines/machineyosemite.cpp
index 34a619d517..e88305bf80 100644
--- a/machines/machineyosemite.cpp
+++ b/machines/machineyosemite.cpp
@@ -100,14 +100,17 @@ int MachineYosemite::initialize(const std::string &id) {
setup_ram_slot("RAM_DIMM_4", 0x53, GET_INT_PROP("rambank4_size"));
// configure CPU clocks
- uint64_t bus_freq = 66820000ULL;
+ uint64_t bus_freq = 100'000'000ULL;
uint64_t timebase_freq = bus_freq / 4;
+ uint64_t core_freq = bus_freq * 7 / 2; // 350 MHz (CPU PLL ratio of 3.5)
// initialize virtual CPU and request MPC750 CPU aka G3
- ppc_cpu_init(grackle_obj, PPC_VER::MPC750, false, timebase_freq);
-
- // set CPU PLL ratio to 3.5
- ppc_state.spr[SPR::HID1] = 0xE << 28;
+ ppc_cpu_init(grackle_obj, {
+ .version = PPC_VER::MPC750,
+ .timebase_freq_hz = timebase_freq,
+ .bus_freq_hz = bus_freq,
+ .core_freq_hz = core_freq,
+ });
return 0;
}
diff --git a/main.cpp b/main.cpp
index e3b38a6492..08dc7e046f 100644
--- a/main.cpp
+++ b/main.cpp
@@ -115,6 +115,7 @@ int main(int argc, char** argv) {
bool deterministic_interactive = false;
string deterministic_mode = "strict";
string keyboard_string = "Eng_USA";
+ string cpu_timing_mode = "fixed";
const std::map kbd_map{
{"Eng_USA", 0}, {"Eng_GBR", 1}, {"Fra_FRA", 10}, {"Deu_DEU", 20},
@@ -142,6 +143,11 @@ int main(int argc, char** argv) {
"Select deterministic features (strict or interactive)")
->needs(deterministic_opt)
->check(CLI::IsMember({"strict", "interactive"}));
+ emu->add_option("--cpu-timing", cpu_timing_mode,
+ "Select CPU instruction timing (fixed: 16 ns/instruction; "
+ "per-machine: derive from core frequency)")
+ ->check(CLI::IsMember({"fixed", "per-machine"}))
+ ->capture_default_str();
bool log_to_stderr = false;
loguru::Verbosity log_verbosity = loguru::Verbosity_INFO;
@@ -207,6 +213,10 @@ int main(int argc, char** argv) {
execution_mode = debugger;
}
+ set_cpu_timing_mode(cpu_timing_mode == "per-machine"
+ ? PPC_CPU_TimingMode::PerMachine
+ : PPC_CPU_TimingMode::Fixed);
+
/* initialize logging */
loguru::g_preamble_date = false;
loguru::g_preamble_time = false;
@@ -272,6 +282,7 @@ int main(int argc, char** argv) {
cout << "BootROM path: " << bootrom_path << endl;
cout << "Execution mode: " << execution_mode << endl;
+ cout << "CPU timing: " << cpu_timing_mode << endl;
if (is_deterministic) {
cout << "Using deterministic execution mode; disk, NVRAM, and PRAM changes will not be saved." << endl;
if (deterministic_interactive) {
diff --git a/zdocs/developers/cpu/powerpc/powerpcemu.md b/zdocs/developers/cpu/powerpc/powerpcemu.md
new file mode 100644
index 0000000000..aad1e88a1d
--- /dev/null
+++ b/zdocs/developers/cpu/powerpc/powerpcemu.md
@@ -0,0 +1,11 @@
+# PowerPC Emulation
+
+Implementation notes for the PowerPC CPU emulator in DingusPPC.
+
+## Time and CPU Timing
+
+DingusPPC normally advances virtual time as it interprets instructions rather than directly from the host's wall clock. Each machine supplies its bus, core, and timebase frequencies. The bus/core ratio determines supported HID1 PLL values, while the timebase frequency controls the guest TBR and decrementer independently of instruction timing.
+
+The `--cpu-timing` option controls how much virtual time each interpreted instruction consumes. The default `fixed` mode uses a fixed 16 ns per instruction for every machine. `per-machine` mode derives the period from the configured core frequency. Both modes use 60.4 fixed-point nanoseconds internally, and neither changes the machine's configured clocks or guest-visible HID1 value.
+
+The interpreter currently assigns the same period to every instruction; it does not model pipelines, caches, or instruction-specific cycle counts. Therefore, `per-machine` is not cycle-accurate, and a host that cannot execute instructions at the selected rate will run guest time more slowly than wall time. Enabling the separate `g_realtime` flag follows the host clock instead, which can expose guest timeouts when the interpreter cannot keep up.