Expand description
The canonical master-clock scheduler — the heart of the emulator.
§Timebase model (ADR 0006)
The tick unit is 187.5 MHz (MASTER_HZ) — the LCM of the VR4300’s
93.75 MHz PClock and the RCP’s 62.5 MHz MClock. That makes every emulated
clock domain an integer divisor of one counter:
| Component | Rate | Divider |
|---|---|---|
VR4300 PClock | 93.75 MHz | 2 |
RCP (MClock / SClock) | 62.5 MHz | 3 |
COP0 Count (half PClock) | 46.875 MHz | 4 |
| Serial Interface | 15.625 MHz | 12 |
| Cartridge / PIF | 1.953125 MHz | 96 |
No accumulator, no remainder, no drift — drift is unrepresentable rather
than merely avoided. Note the hardware derives the CPU from MClock (a 3/2
PLL at DivMode = 0b01), not the reverse; ADR 0001 had the CPU as master,
which inverted that and is why the RCP needed a fractional remainder.
Only the VI (VCLK, ~48.68 MHz, off a different crystal) and the AI genuinely are not rational multiples of this tick; those keep a fractional accumulator.
§The one rule
master_ticks is the only counter in the core that is ever incremented.
Every other cycle position is derived — see System::cpu_cycles and
System::rcp_cycles, which are accessors rather than fields. A second
incremented counter agrees with the first only because every call site
remembers to step it: an invariant held by construction rather than by
derivation, correct until one path forgets. The residue_invariants_never_move
test exists to catch exactly that.
A retired-work tally is a different thing and is legitimate
(Cpu::retired); it counts work done and nothing schedules against it.
§Lockstep, edge to edge
This is LOCKSTEP, not catch-up: an RCP event (a DP-done IRQ, an SP halt) is
visible to the very next CPU step. But nothing iterates 187.5M times a second —
the CPU lands on every 2nd tick and the RCP on every 3rd, so the pattern repeats
every 6 ticks and System::step_to_next_edge jumps straight to the next tick
where something is due. The unit is a time base, not a loop counter.
There are never OS threads in the core; one timeline is the whole reason the determinism contract holds (ADR 0004). Rollback / run-ahead live in the frontend.
See docs/scheduler.md and docs/adr/0006-one-canonical-master-clock.md.
Structs§
- System
- Owns the run loop and ties the CPU to the Bus on one timeline.
Constants§
- COUNT_
DIVIDER - Master ticks per COP0
Countincrement —Countruns at halfPClock(46.875 MHz), per VR4300 User’s Manual §6.3.3 Figure 6-3. Forgetting the halving is a documented source of 2x timing bugs. - CPU_
DIVIDER - Master ticks per VR4300
PClock. - CPU_HZ
- The VR4300 pipeline clock (
PClock), 93.75 MHz.MASTER_HZ / CPU_DIVIDER. - EDGE_
PERIOD - Ticks after which the whole edge schedule repeats:
lcm(CPU_DIVIDER, RCP_DIVIDER). - MASTER_
HZ - The canonical master tick rate: 187.5 MHz, the LCM of the CPU and RCP clocks.
- PHASE_
PERIOD - The CPU:RCP interleaving period:
lcm(CPU_DIVIDER, RCP_DIVIDER)master ticks. - PIF_
DIVIDER - Master ticks per cartridge / PIF cycle (1.953125 MHz).
- RCP_
DIVIDER - Master ticks per RCP (
MClock) cycle. - RCP_HZ
- The RCP clock (
MClock, and the CPU’sSClockbus rate), 62.5 MHz. - SI_
DIVIDER - Master ticks per Serial Interface cycle (15.625 MHz).