-
-
Notifications
You must be signed in to change notification settings - Fork 138
Fix timer accuracy #2001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix timer accuracy #2001
Changes from 5 commits
afeb20a
3d0297e
515affa
4c860eb
76df40a
1a96364
68e9cc0
7c04db5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| /*************************************************************************** | ||
|
Check warning on line 1 in src/core/psxcounters.cc
|
||
| * Copyright (C) 2010 by Blade_Arma * | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
|
|
@@ -43,7 +43,11 @@ | |
| m_rcnts[index].cycleStart = PCSX::g_emulator->m_cpu->m_regs.cycle; | ||
| m_rcnts[index].cycleStart -= value * m_rcnts[index].rate; | ||
|
|
||
| // TODO: <=. | ||
| // Hardware tested (SCPH-5501): counter reaches target value briefly before | ||
| // resetting. Fast reads can observe the target value; slow reads (printf) cannot. | ||
| // The comparison boundary needs more precise measurement to determine if | ||
| // reset happens on the same cycle as reaching target or the next cycle. | ||
| // TODO: determine exact reset timing with cycle-accurate measurement. | ||
| if (value < m_rcnts[index].target) { | ||
| m_rcnts[index].cycle = m_rcnts[index].target * m_rcnts[index].rate; | ||
| m_rcnts[index].counterState = CountToTarget; | ||
|
|
@@ -132,7 +136,15 @@ | |
| m_rcnts[index].mode |= RcOverflow; | ||
| } | ||
|
|
||
| m_rcnts[index].mode |= RcIrqRequest; | ||
| // IRQ request flag (bit 10) behavior depends on bit 7 (toggle mode): | ||
| // Pulse mode (bit7=0): bit 10 resets to 1 after IRQ (short pulse low) | ||
| // Toggle mode (bit7=1): bit 10 toggles (XOR) on each IRQ | ||
| // Hardware verified: toggle mode produces different bit10 state than pulse mode. | ||
| if (m_rcnts[index].mode & RcIrqToggle) { | ||
| m_rcnts[index].mode ^= RcIrqRequest; | ||
| } else { | ||
| m_rcnts[index].mode |= RcIrqRequest; | ||
| } | ||
|
Check warning on line 147 in src/core/psxcounters.cc
|
||
|
|
||
| set(); | ||
| } | ||
|
|
@@ -179,24 +191,60 @@ | |
| m_hSyncCount++; | ||
| m_spuSyncCountdown--; | ||
|
|
||
| // Counter 0 gate: triggered by Hblank | ||
| if (isGateEnabled(0, m_rcnts[0].mode)) { | ||
| switch (gateSyncMode(m_rcnts[0].mode)) { | ||
| case 1: // Reset at Hblank | ||
| case 2: // Reset at Hblank + pause outside | ||
| writeCounterInternal(0, 0); | ||
| break; | ||
| case 3: // Pause until first Hblank, then free run | ||
| if (!m_rcnts[0].gateStarted) { | ||
| m_rcnts[0].gateStarted = 1; | ||
| recalculateRate(0); | ||
| writeCounterInternal(0, 0); | ||
| set(); | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // Update spu. | ||
| if (m_spuSyncCountdown <= 0) { | ||
| // Scanlines until next sync | ||
| const auto scanlines = SpuUpdInterval[PCSX::g_emulator->settings.get<PCSX::Emulator::SettingVideo>()]; | ||
| m_spuSyncCountdown = scanlines; | ||
|
|
||
| PCSX::g_emulator->m_spu->async(scanlines * m_rcnts[3].target); | ||
| } | ||
|
|
||
| // SIO1 callback on hsync to process data | ||
| if (m_pollSIO1) { | ||
| PCSX::g_emulator->m_sio1->poll(); | ||
| } | ||
|
|
||
| // Trigger VBlank IRQ when VBlank starts | ||
| if (m_hSyncCount == VBlankStart[PCSX::g_emulator->settings.get<PCSX::Emulator::SettingVideo>()]) { | ||
| setIrq(0x01); | ||
| PCSX::g_emulator->vsync(); | ||
|
|
||
| // Counter 1 gate: triggered by VBlank start | ||
| if (isGateEnabled(1, m_rcnts[1].mode)) { | ||
| switch (gateSyncMode(m_rcnts[1].mode)) { | ||
| case 1: // Reset at VBlank | ||
| case 2: // Reset at VBlank + pause outside | ||
| writeCounterInternal(1, 0); | ||
| break; | ||
| case 3: // Pause until first VBlank, then free run | ||
| if (!m_rcnts[1].gateStarted) { | ||
| m_rcnts[1].gateStarted = 1; | ||
| recalculateRate(1); | ||
| writeCounterInternal(1, 0); | ||
| set(); | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
Check warning on line 247 in src/core/psxcounters.cc
|
||
|
Comment on lines
+237
to
+253
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gate mode 0 (pause during VBlank) also missing for counter 1. Similar to counter 0, gate mode 0 for counter 1 (pause during VBlank) is not implemented. The switch only handles modes 1, 2, and 3. 🧰 Tools🪛 GitHub Check: CodeScene Code Health Review (main)[warning] 194-247: ❌ Getting worse: Complex Method [warning] 194-247: ❌ New issue: Bumpy Road Ahead [warning] 194-247: ❌ New issue: Deep, Nested Complexity 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| if (m_hSyncCount >= m_HSyncTotal[PCSX::g_emulator->settings.get<PCSX::Emulator::SettingVideo>()]) { | ||
|
|
@@ -213,43 +261,73 @@ | |
| set(); | ||
| } | ||
|
|
||
| void PCSX::Counters::writeMode(uint32_t index, uint32_t value) { | ||
| verboseLog(1, "[RCNT %i] writeMode: %x\n", index, value); | ||
|
|
||
| update(); | ||
| m_rcnts[index].mode = value; | ||
| m_rcnts[index].irqState = false; | ||
|
|
||
| void PCSX::Counters::recalculateRate(uint32_t index) { | ||
| uint16_t value = m_rcnts[index].mode; | ||
| switch (index) { | ||
| case 0: | ||
| if (value & Rc0PixelClock) { | ||
| m_rcnts[index].rate = 5; | ||
| // Dotclock rate depends on GPU horizontal resolution and video standard. | ||
| static constexpr uint32_t dotclockDividers[] = {10, 8, 5, 4, 7, 7}; | ||
| auto hres = PCSX::g_emulator->m_gpu->m_display.info.hres; | ||
| auto videoMode = PCSX::g_emulator->m_gpu->m_display.info.mode; | ||
| uint32_t divider = dotclockDividers[static_cast<int>(hres)]; | ||
| uint32_t videoCyclesPerScanline = (videoMode == GPU::CtrlDisplayMode::VM_PAL) ? 3406 : 3413; | ||
| uint32_t dotsPerScanline = videoCyclesPerScanline / divider; | ||
| uint32_t cpuCyclesPerScanline = (PCSX::g_emulator->m_psxClockSpeed / | ||
| (FrameRate[PCSX::g_emulator->settings.get<PCSX::Emulator::SettingVideo>()] * | ||
| m_HSyncTotal[PCSX::g_emulator->settings.get<PCSX::Emulator::SettingVideo>()])); | ||
| m_rcnts[index].rate = std::max<uint32_t>(cpuCyclesPerScanline / dotsPerScanline, 1); | ||
| } else { | ||
| m_rcnts[index].rate = 1; | ||
| } | ||
| break; | ||
| case 1: | ||
| if (value & Rc1HSyncClock) { | ||
| m_rcnts[index].rate = (PCSX::g_emulator->m_psxClockSpeed / | ||
| (FrameRate[PCSX::g_emulator->settings.get<PCSX::Emulator::SettingVideo>()] * | ||
| m_HSyncTotal[PCSX::g_emulator->settings.get<PCSX::Emulator::SettingVideo>()])); | ||
| } else { | ||
| m_rcnts[index].rate = 1; | ||
| } | ||
| break; | ||
| case 2: | ||
| if (value & Rc2OneEighthClock) { | ||
| m_rcnts[index].rate = 8; | ||
| } else { | ||
| m_rcnts[index].rate = 1; | ||
| } | ||
|
|
||
| // TODO: wcount must work. | ||
| // Timer 2 sync modes: 0,3 = stop counter; 1,2 = free run | ||
| // Hardware verified (SCPH-5501): modes 0,3 produce delta=0, modes 1,2 run normally. | ||
| if (value & Rc2Disable) { | ||
| m_rcnts[index].rate = 0xffffffff; | ||
| uint8_t syncMode = gateSyncMode(value); | ||
| if (syncMode == 0 || syncMode == 3) { | ||
| m_rcnts[index].rate = 0xffffffff; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
Check warning on line 309 in src/core/psxcounters.cc
|
||
|
|
||
| void PCSX::Counters::writeMode(uint32_t index, uint32_t value) { | ||
| verboseLog(1, "[RCNT %i] writeMode: %x\n", index, value); | ||
|
|
||
| update(); | ||
| m_rcnts[index].mode = value; | ||
| m_rcnts[index].irqState = false; | ||
|
|
||
| recalculateRate(index); | ||
|
|
||
| // Initialize gate state | ||
| m_rcnts[index].gateStarted = 0; | ||
|
|
||
| if (isGateEnabled(index, value)) { | ||
| uint8_t syncMode = gateSyncMode(value); | ||
| if (syncMode == 2 || syncMode == 3) { | ||
| // Mode 2: pause outside blank. Mode 3: pause until first blank. | ||
| // Both start paused. | ||
| m_rcnts[index].rate = 0xffffffff; | ||
| } | ||
| } | ||
|
|
||
| writeCounterInternal(index, 0); | ||
| set(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| USE_FUNCTION_SECTIONS = false | ||
| TYPE = ps-exe | ||
|
|
||
| SRCS = \ | ||
| ../uC-sdk-glue/BoardConsole.c \ | ||
| ../uC-sdk-glue/BoardInit.c \ | ||
| ../uC-sdk-glue/init.c \ | ||
| \ | ||
| ../../../../third_party/uC-sdk/libc/src/cxx-glue.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/errno.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/initfini.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/malloc.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/qsort.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/rand.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/reent.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/stdio.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/string.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/strto.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/unistd.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/xprintf.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/xscanf.c \ | ||
| ../../../../third_party/uC-sdk/libc/src/yscanf.c \ | ||
| ../../../../third_party/uC-sdk/os/src/devfs.c \ | ||
| ../../../../third_party/uC-sdk/os/src/filesystem.c \ | ||
| ../../../../third_party/uC-sdk/os/src/fio.c \ | ||
| ../../../../third_party/uC-sdk/os/src/hash-djb2.c \ | ||
| ../../../../third_party/uC-sdk/os/src/init.c \ | ||
| ../../../../third_party/uC-sdk/os/src/osdebug.c \ | ||
| ../../../../third_party/uC-sdk/os/src/romfs.c \ | ||
| ../../../../third_party/uC-sdk/os/src/sbrk.c \ | ||
| ../../common/syscalls/printf.s \ | ||
| ../../common/crt0/uC-sdk-crt0.s \ | ||
|
|
||
| CPPFLAGS = -DNOFLOATINGPOINT | ||
| CPPFLAGS += -I. | ||
| CPPFLAGS += -I../../../../third_party/uC-sdk/libc/include | ||
| CPPFLAGS += -I../../../../third_party/uC-sdk/os/include | ||
| CPPFLAGS += -I../../../../third_party/libcester/include | ||
| CPPFLAGS += -I../../openbios/uC-sdk-glue | ||
|
|
||
| ifeq ($(PCSX_TESTS),true) | ||
| CPPFLAGS += -DPCSX_TESTS=1 | ||
| endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| TARGET = timers | ||
|
|
||
| include ../common.mk | ||
|
|
||
| SRCS += \ | ||
| timers.c \ | ||
|
|
||
| include ../../common.mk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 631
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 1577
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 600
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 2308
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 49
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 1684
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 1187
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 198
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 408
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 575
🏁 Script executed:
Repository: grumpycoders/pcsx-redux
Length of output: 3007
Add missing gate mode 0 handling for Counter 0 (pause during HBlank).
The switch statement at lines 196-209 handles gate modes 1, 2, and 3, but mode 0 is missing. When
gateSyncMode()returns 0, the switch falls through without any action, causing the counter to run at full speed instead of pausing during HBlank. The testtimerC0GateMode0PauseDuringHblankexpects mode 0 to produce a lower counter value than free-running, confirming the pause behavior should be implemented. This is inconsistent with Timer 2, which explicitly pauses on mode 0 (line 303-305).🧰 Tools
🪛 Cppcheck (2.20.0)
[error] 210-210: failed to expand 'TYPESTRING', Invalid ## usage when expanding 'TYPESTRING1024'
(syntaxError)
[error] 197-197: failed to expand 'TYPESTRING', Invalid ## usage when expanding 'TYPESTRING256'
(syntaxError)
[error] 206-206: failed to expand 'TYPESTRING', Invalid ## usage when expanding 'TYPESTRING512'
(syntaxError)
🪛 GitHub Check: CodeScene Code Health Review (main)
[warning] 194-247: ❌ Getting worse: Complex Method
PCSX::Counters::update increases in cyclomatic complexity from 11 to 21, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
[warning] 194-247: ❌ New issue: Bumpy Road Ahead
PCSX::Counters::update has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.
[warning] 194-247: ❌ New issue: Deep, Nested Complexity
PCSX::Counters::update has a nested complexity depth of 5, threshold = 4. This function contains deeply nested logic such as if statements and/or loops. The deeper the nesting, the lower the code health.
🤖 Prompt for AI Agents