-
Notifications
You must be signed in to change notification settings - Fork 0
Lockstep Scheduler
The scheduler is the heart of the cycle-exact emulator: it advances the TIA, the 6507 CPU, the RIOT timer, and any cart coprocessor in tight lockstep at TIA color-clock resolution. It lives in rusty2600-core (the System type) and is the single owner of the run loop — see Architecture-Overview fact 1 and Architecture-Decision-Records ADR 0001.
The master clock is the NTSC color subcarrier, 3.579545 MHz (PAL 3.546894 MHz). Everything else is a fixed integer divisor of it.
| Unit | Advances every N color clocks | Rate (NTSC) | Notes |
|---|---|---|---|
| TIA color clock (master) | 1 | 3.579545 MHz | one pixel emitted per visible color clock |
| 6507 CPU | 3 | ~1.193182 MHz | offset by the seeded power-on phase (0/1/2) |
| RIOT interval timer | 3 (one CPU cycle) | ~1.193182 MHz | further prescaled by 1 / 8 / 64 / 1024 |
| Cart coprocessor (DPC etc.) | 3 (Board::tick) |
~1.193182 MHz | default no-op; DPC oscillator may free-run |
| TIA audio | 114 | ~31.4 kHz | color/114; CPU/114 for the four CPU-clocked AUDC distortion modes |
PAL keeps the same /3 and /114 integer divisors; only the absolute master frequency and the frame line budget differ.
Resolution is integer color clocks. A scanline is 228 color clocks = 68 HBLANK + 160 visible = exactly 76 CPU cycles. Because a 2600 program already races the beam at color-clock granularity, integer resolution is the natural — and sufficient — timebase: there is no sub-color-clock event the program can observe. This is why Architecture-Decision-Records ADR 0002's fractional-timebase refactor is marked "likely unneeded" for this console, in contrast to systems where sub-cycle bus-phase timing matters.
tick_one_color_clock():
tia.tick_color_clock() # emit one dot, advance the beam
if color_clocks % 3 == phase: # CPU phi0 fires every 3rd clock
if tia.rdy_stall():
# RDY held: CPU frozen this cycle (the WSYNC beam-stall)
else:
cpu.tick(bus)
riot.tick() # RIOT timer advances on the CPU cycle
board.tick() # cart coprocessor hook, if any
color_clocks += 1
phase is the per-power-on CPU/color-clock alignment (0, 1, or 2), seeded from a deterministic PRNG — never the OS RNG. Reset preserves it; a cold power-cycle re-rolls it from the seed, per the determinism contract (Architecture-Decision-Records ADR 0004).
Writing WSYNC clears the RDY latch and halts the CPU until the next HBLANK starts. The TIA owns the signal; the scheduler reads it and simply skips the CPU step while it's asserted — the color clock keeps running, only the CPU is frozen. The TIA releases RDY when its color-clock counter wraps at the end of HBLANK.
A real edge case this project found and fixed: because the CPU access ticks its own cycle before the register write is applied, a STA WSYNC whose final cycle lands exactly on the 228→0 wrap arrives with the beam already at the start of the very scanline it was waiting for. Arming the stall in that case would over-wait a full extra scanline — a phantom line. Rusty2600 correctly skips arming rdy_stall when the strobe coincides with the boundary itself, matching Gopher2600/Stella's own "RDY released at the leading edge of the next HBLANK" behavior. This was Frogger's positioning-kernel jitter bug, found and fixed by direct comparison against Gopher2600's own byte-for-byte instruction trace.
The Bus owns the TIA, the RIOT, the cart (boxed Board), and the open-bus latch — there is no wram field (the 2600's only RAM is in the RIOT; see RIOT). The CPU borrows &mut Bus through a narrow CpuBus adapter. The video and audio paths each see a smaller trait: VideoBus for cart-mediated reads, and AudioBus for drawing the TIA's mixed sample.
The 6507 has no IRQ or NMI pins — see CPU-6507. There is no interrupt path in the scheduler at all; the only "timing interrupt" a 2600 program gets is polling the RIOT timer (INTIM) or WSYNC. The BRK software interrupt and the RESET vector still exist and work normally.
Architecture-Overview · CPU-6507 · TIA · RIOT · Architecture-Decision-Records
Rusty2600 is a cycle-accurate Atari 2600 (VCS) emulator written in pure Rust. Licensed under MIT OR Apache-2.0. | GitHub Repository | Web Demo