Division Clock

Games & Classics

A sweeping analog clock and a rolling odometer on a Super Nintendo. To split the frame counter into hours:minutes:seconds and the odometer into base-10 digits, it divides by compile-time constants (/60, /10, /12) — the textbook case a compiler turns into a magic-reciprocal multiply. The interesting part is what happens when it doesn't (see the notes below). Runs automatically; no joypad needed. Written in C and compiled with the llvm-mos-based 65816 toolchain (+mos-a16, 16-bit accumulator mode).

loading core…

Self-running demo — the clock sweeps and the odometer rolls on their own.

Click the screen to focus, then watch. Tab away and it pauses.

What it is

Dividing by a constant is one of the oldest compiler optimisations: x / 10 becomes a multiply by a carefully chosen "magic" number followed by a shift, because multiplying is far cheaper than dividing — on a CPU with a fast multiplier.

sec = (tick / 60) % 60;      // divide by a COMPILE-TIME constant
min = (tick / 3600) % 60;    //   → magic reciprocal on most CPUs...
for (i=0;i<6;i++) {          //   → but on this soft-multiply target,
    d[i] = v % 10; v /= 10;  //     it stays a __udiv libcall (see below)
}

This demo was built to show that transform. Instead it measured the opposite: on the 65816, where even multiplication is a software routine, the magic-reciprocal form would need the high half of a wide multiply — itself a library call — so the compiler correctly keeps the division routine. The clock and odometer are exact all the same; the point is the honest result, not the assumption.

Compiler stress-test

ItemWhat it exercises
constant divideSplitting a counter into h:m:s and base-10 digits divides by compile-time constants (/60, /10, /12). On CPUs with a fast multiply, the compiler turns these into a "magic reciprocal" — a multiply-high by 0xCCCC… plus a shift — avoiding the divide entirely.
the findingllvm-mos does NOT do that here — even uint16 x/10 lowers to a __udivhi3 call. Measured, not assumed: the strength reduction needs the high half of a wide multiply (MULHU), which is itself a libcall on this 8-bit soft-multiply target — so it cannot beat the divide.
correct, not a bugThe LLVM cost model gates the magic-reciprocal transform on a cheap MULHU. With none available it correctly keeps the division libcall — swapping one libcall for an equally-expensive one would be no win. The differential proves the retained division is bit-exact.
vs #27Distinct from a runtime-divisor modulo (#27): here the divisor is a compile-time constant, the exact case that "should" optimise away — and the interesting result is that, on this target, it does not.

Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg (and MAME where the SPC700 IPL is present). Hit Verify fidelity to reproduce the build gate's WRAM assert (gate CRC 0xF72E — a rotate-XOR fold of the clock fields + hand angles + odometer digits) live in this tab. No far pointers — the same source passes every way: host == default == +mos-a16 == +mos-xy16 == bsnes-jg, -verify clean.