From 4425150cb6f52d15f513690a75645639358a2030 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 18 Jul 2026 22:06:35 +0200 Subject: kernel: CGB double-speed mode - 2x everything (1.99x measured) KEY1 prepare + STOP at KernelInit (skipped on warm reboot if already fast). The PPU/LCD keep their normal rate; the timer and DIV double with the CPU, compensated at the two consumers: - TimerISR now fires at 128 Hz; a toggle byte keeps wTicks at 64 Hz (uptime semantics unchanged) - sys_sleep halves the DIV-delta accumulator's high byte (256 DIV ticks = 1/128 s in double speed) so gsleep/msleep stay real-time Measured: count 60 (scroll-heavy) 3.77s -> 1.89s wall; wTicks 64 Hz over 4s; ping's 800ms msleep pacing unchanged (2.77s for 4 echoes). sl0pboy already emulated KEY1/STOP + per-speed timer/PPU rates. --- src/boot.asm | 14 ++++++++++++++ src/kdata.asm | 2 ++ src/sched.asm | 12 +++++++++--- src/syscall.asm | 9 ++++++--- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/boot.asm b/src/boot.asm index 88eb240..f649008 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -44,6 +44,20 @@ KernelInit: push af ; boot ROM hands the console model in A/B - push bc ; park it on the (uncleared) stack for dmesg + ; CGB double-speed mode: 8.4 MHz for the whole kernel + userland. The + ; PPU/LCD keep their normal rate; the timer and DIV double with the CPU, + ; which TimerISR (128 Hz -> 64 Hz toggle) and sys_sleep (DIV halving) + ; compensate for. P1 select lines off first, per the hardware procedure. + ldh a, [$FF4D] ; KEY1 + bit 7, a ; already fast? (warm reboot) + jr nz, .fast + ld a, $30 + ldh [$FF00], a ; P1: no select lines (STOP quirk guard) + ld a, 1 + ldh [$FF4D], a ; KEY1.0 = prepare speed switch + stop ; switch: continues in double speed +.fast + ; enable cart RAM (MBC5) ld a, $0A ld [MBC5_RAM_ENABLE], a diff --git a/src/kdata.asm b/src/kdata.asm index b0cd71b..13dc6dd 100644 --- a/src/kdata.asm +++ b/src/kdata.asm @@ -66,3 +66,5 @@ wBootA:: DS 1 ; A at entry: console model from the boot ROM wBootB:: DS 1 ; B at entry: bit0 set = AGB (GBA in CGB mode) wBootFsFresh::DS 1 ; fs_init formatted a blank disk this boot wTicks:: DS 4 ; monotonic 64 Hz system tick (TimerISR), LE +wTickTog:: DS 1 ; TimerISR fires at 128 Hz in double-speed mode; + ; this toggle keeps wTicks counting at 64 Hz diff --git a/src/sched.asm b/src/sched.asm index 0369ff4..b4b5a30 100644 --- a/src/sched.asm +++ b/src/sched.asm @@ -67,9 +67,10 @@ FindNextReady:: ret ; ----------------------------------------------------------------------------- -; TimerISR - the system tick. TAC runs the timer at 16384 Hz with TMA=0, so -; TIMA overflows (and this fires) at 64 Hz: wTicks is a monotonic 32-bit -; 1/64-second counter (SYS_UPTIME reads it; wraps after ~2.1 years). +; TimerISR - the system tick. TAC runs the timer at 16384 Hz with TMA=0 - +; but the CPU is in DOUBLE-SPEED mode, so the input clock is really 32768 Hz +; and this fires at 128 Hz. A toggle halves that back down: wTicks stays a +; monotonic 32-bit 1/64-second counter (SYS_UPTIME reads it; wraps ~2.1 yr). ; Scheduling stays cooperative - this ISR is deliberately transparent (all ; registers preserved, fixed-WRAM only), so it can interrupt anything, ; including kernel code mid-syscall. Preemption would hook in here later. @@ -77,6 +78,11 @@ FindNextReady:: TimerISR:: push af push hl + ld hl, wTickTog + ld a, [hl] + xor 1 + ld [hl], a + jr nz, .done ; first IRQ of the 128 Hz pair: count nothing ld hl, wTicks inc [hl] jr nz, .done ; no carry out of byte 0 diff --git a/src/syscall.asm b/src/syscall.asm index e3f0b1b..4bcb601 100644 --- a/src/syscall.asm +++ b/src/syscall.asm @@ -277,8 +277,10 @@ ENDR ; ----------------------------------------------------------------------------- ; sys_sleep(B = 1/64-second units) - cooperative delay driven by the DIV timer. -; DIV (FF04) free-runs at 16384 Hz, so 256 ticks = 1/64 s. We accumulate DIV -; deltas across SchedYields (other procs run) until enough units have elapsed. +; DIV free-runs at 16384 Hz - 32768 Hz in double-speed mode, where 256 ticks +; = 1/128 s - so the accumulated high byte is halved before comparing against +; the target. We accumulate DIV deltas across SchedYields (other procs run) +; until enough 1/64-second units have elapsed. ; ----------------------------------------------------------------------------- sys_sleep: ld a, b @@ -305,7 +307,8 @@ sys_sleep: ld [wSleepAcc], a ld a, [wSleepAcc+1] adc 0 - ld [wSleepAcc+1], a ; acc high byte = elapsed 1/64-second units + ld [wSleepAcc+1], a ; acc high byte = elapsed 1/128-second units + srl a ; /2: double-speed DIV -> 1/64-second units ld b, a ld a, [wSleepTarget] cp b -- cgit v1.3.1-sl0p