Skip to content
RetrogamingDeep Dive Published Updated 9 min readViews unavailable

MAME Timers and Scanlines: Synchronizing Arcade Events in Emulated Time

Model MAME arcade timers and scanline interrupts against emulated time, scheduler quanta, display position, save states, and repeatable diagnostic evidence.

Arcade software often depends on more than the final pixels in a frame. A raster interrupt may split a display into independently updated regions; a timer may release a sound CPU, trigger a watchdog, or change a control signal at a specific emulated time. If these events are driven by host wall-clock callbacks or by an assumed fixed frame loop, the machine can look convincing in one scene and still fail under fast-forward, save-state loading, or a different host refresh rate.

MAME’s scheduler and timer APIs express events in emulated time. A timer’s callback is dispatched as the emulated machine advances through scheduler time, not as a promise that a host operating-system thread will wake at a physical instant. This gives the driver a consistent time domain and lets MAME coordinate CPUs and devices. It does not make an inaccurate board model accurate: event periods, scanline positions, and interrupt side effects still need evidence.

Separate emulated time from host time

attotime is MAME’s emulated-time representation used by scheduler APIs. It lets devices schedule relative or absolute events with fine resolution independent of the host’s timer granularity. The scheduler coordinates these events with CPU execution and other devices. The host ultimately determines how quickly the emulated timeline advances in real time, but code modeling a board should generally schedule hardware behavior against emulated time rather than sleep or query the host clock.

An allocated emu_timer has callback and expiration state managed by the scheduler. Drivers can allocate a persistent timer and adjust its start, period, and parameter as conditions change. The scheduler header marks anonymous timer_set use as deprecated and recommends allocated timers instead. Choose timer ownership and lifetime deliberately: a periodic device event is usually clearer as a retained, named timer or a device-level scanline timer than as scattered anonymous callbacks.

Never interpret an emulator timer’s fine time resolution as a claim of cycle-perfect hardware accuracy. Resolution is not fidelity. Fidelity depends on the modeled oscillator, divider, bus latency, interrupt edge, and how the device interacts with CPU execution. If a board schematic or logic-analyzer trace is unavailable, state what behavior is known and what is an approximation.

Use the screen device for raster position

For events tied to the raster, MAME provides screen-aware timing facilities. The screen device knows its configured timing and can calculate when a beam position is reached. A driver can configure a scanline timer and receive the current scanline as the screen advances. One established source pattern looks like:

TIMER_DEVICE_CALLBACK_MEMBER(board_state::scanline)
{
    int const scanline = param;

    if (scanline == 240)
        m_maincpu->set_input_line(0, HOLD_LINE);
}

// In the machine configuration, after the screen is configured:
TIMER(config, "scanline_timer").configure_scanline(
    FUNC(board_state::scanline), "screen", 0, 1);

This is an API pattern, not a complete driver. The CPU line, screen tag, callback declaration, and scanline must match the board. The callback’s parameter is the scanline index supplied by the timer configuration. A step of one requests callbacks for every scanline; a coarser step or a different starting line changes the event schedule. Verify the exact helper signature against the MAME revision being built and a current driver using the same API.

The example’s HOLD_LINE choice is not universal. Some hardware raises a level until software acknowledges it, some pulses or latches an edge, and some has multiple interrupt sources sharing a CPU line. Model the interrupt’s lifetime and clear behavior based on the device and software interaction, not just on where a commercial driver happens to assert a line.

Partial screen updates also matter. If a register changes the visible state partway through a frame, update the already-rendered portion before changing the state used for later scanlines. Otherwise, a renderer may apply the new register value to the whole frame and erase a raster effect, even if the interrupt fires on the expected line. The order of partial update, register mutation, and interrupt delivery should reflect the board’s behavior.

Schedule by event semantics

Use a scanline timer when the event is naturally tied to a raster line. Use a general emulation timer for a delay or periodic behavior expressed in time units, such as a watchdog or a peripheral interval not derived from the screen. Use CPU cycle scheduling when the device relationship is specifically measured in CPU cycles and the MAME interface provides the appropriate mechanism. Do not convert every event into a scanline count just because the display has a convenient frame rate.

A timer can be one-shot or periodic. A one-shot event is useful when a write schedules a delayed transition; the handler should cancel or re-arm it when a new control write supersedes the old event. A periodic event should have an explicit period and reset behavior. Preserve the event’s state across save and load so restoring a machine does not duplicate, lose, or shift an event. MAME’s scheduler manages registered allocated timers with save-state support, but driver-owned state and callback logic still need to be coherent after restoration.

Avoid placing important hardware behavior in a UI or rendering callback. Presentation can be skipped, throttled, or configured differently from emulated machine execution. A video callback may be suitable for drawing; it is not automatically the correct clock for a hardware timer. The emulated device model should own the event and expose the state needed by rendering.

Scheduler quanta and multi-CPU coordination

MAME’s scheduler can limit the maximum interval CPUs run before yielding, often called a maximum quantum. A machine configuration may request a smaller maximum quantum to improve synchronization between devices that interact frequently. This affects interleave and scheduling granularity; it is not a magic repair for an incorrect interrupt time, nor does a lower value imply that the original board had that exact scheduling interval.

Use a quantum adjustment only with a concrete synchronization reason. For example, two CPUs may exchange commands through shared memory and depend on observing writes promptly. Measure whether a scheduling change resolves a specific race without introducing unnecessary host CPU cost. Document the interaction and expected timing. Do not copy a numeric quantum from an unrelated driver: the appropriate value depends on the machine’s modeled devices and MAME scheduler behavior.

For deterministic diagnosis, keep scheduler changes, frame skip, speed throttling, and host refresh behavior fixed between comparisons. Fast-forward should advance emulated time faster without changing the modeled ordering of events. If a bug appears only under fast-forward, investigate assumptions tied to wall time or presentation rather than compensating with a different hardware period.

Trace and verify a timing issue

Begin with one title and a reproducible state, such as a known attract-mode transition or a stable scene with a raster split. Record the MAME revision, system name, selected machine configuration, screen refresh and visible area settings, and relevant driver source. Set debugger breakpoints or logging at the timer callback, the interrupt assertion, the CPU acknowledge or clear path, and the register write that changes display state. Emit emulated time and scanline values, not just host timestamps.

Compare the expected sequence with the observed sequence:

Event Evidence to capture
Timer setup Start point, period or scanline step, owner, callback
Trigger Emulated time, current scanline, device state
Interrupt CPU line, assertion type, acknowledge or clear path
Display change Register write, partial update boundary, rendered region
Restore Timer state and next event after save-state load

If the callback fires on the wrong line, verify screen timing and timer parameters before changing the CPU interrupt code. If it fires correctly but software misses it, inspect interrupt masks, vector configuration, line hold or pulse semantics, and acknowledge order. If the interrupt is correct but the screen split is wrong, inspect partial update ordering and rendering state. This separation prevents unrelated fixes from being bundled together.

Test reset, normal gameplay, pause, fast-forward, and save-state restoration. For a state-load test, capture a state just before the event and one just after it. Load each repeatedly and confirm that the next callback and interrupt sequence is stable. If the callback repeats after restoration, identify which state is serialized and whether the timer is being re-armed twice. If it disappears, verify the timer’s saved expiration and the driver’s post-load reconstruction logic.

Use compact diagnostic logging. Printing on every scanline for a long session can flood logs and perturb debugging. Count callbacks, sample only selected lines, or record a bounded event window. Compare event order and emulated timestamp, then remove temporary instrumentation. Screenshots alone show outcome but not the event sequence that produced it.

Common modeling mistakes

An event scheduled from host time drifts when speed throttling or host load changes. A fixed delay approximated from a nominal refresh can be wrong when the screen timing is configured differently. A scanline callback that asserts the wrong line or every frame can create plausible but phase-shifted behavior. An interrupt line held forever may mask future edges; an edge that is immediately cleared may be missed by software. A partial update after changing a display register can retroactively affect pixels that should already be drawn. Finally, an unnecessarily small scheduler quantum can reduce performance without improving correctness.

The cure is not to add more timers. First establish the hardware event and the time domain; then choose a MAME API that represents it directly, serialize or reconstruct the relevant state, and test the event path under repeatable conditions. When evidence is incomplete, document the timing as an approximation and resist claiming cycle accuracy.

MAME’s timer facilities coordinate emulated hardware, while the screen device provides a raster-aware clock for display events. Correct use keeps device behavior independent of host scheduling and makes timing bugs testable. It does not replace board research, and it does not guarantee that every device model is exact. The driver should make its assumptions observable enough that future investigators can validate them against schematics, chips, traces, and software behavior.

Related:

Sources:

Comments