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.