Timing the X68000 for responsive game code

The Sharp X68000 gives game developers unusually direct access to the machine’s video, sound, and peripheral hardware. That freedom is a major reason the platform remains attractive for homebrew development, but it also means that timing cannot be treated as an invisible operating-system service. A game that depends on exact raster position, smooth animation, or regular audio updates must understand where interrupts come from and how the 68000 responds to them.

The central idea is simple: an interrupt is a request for the CPU to pause its current work and run a handler. The difficult part is predicting when that request becomes visible to software. Device priority, instruction length, interrupt masking, bus activity, and the work performed by the handler all affect the result. On an X68000, good timing comes from measuring these factors rather than assuming that a nominal video frequency automatically gives a perfect clock.

This makes interrupt timing relevant to every major game subsystem. Vertical synchronization controls frame pacing, horizontal timing can support raster effects, sound updates need regular service, and input polling must happen often enough to avoid a sluggish feel. The best designs give each job a clear timing budget and keep interrupt handlers short, deterministic, and easy to inspect.

How the 68000 handles an interrupt request

The Motorola 68000 family uses seven interrupt priority levels. A device presents an interrupt request at one of those levels, and the CPU accepts it when the requested level is higher than the current interrupt mask in the status register. Level seven is non-maskable in ordinary software terms, while lower levels can be blocked deliberately during critical sections.

When the processor accepts an interrupt, it finishes the current instruction before entering the exception sequence. It then saves the relevant execution state, obtains a vector, and begins executing the interrupt service routine. This instruction-boundary behavior matters because the delay is not always the same. A short register instruction may finish quickly, while a memory operation or a more complex instruction can keep the CPU occupied for longer.

The handler should save only what it uses, acknowledge or clear the source when required, perform the urgent work, and return with the appropriate exception-return instruction. On the X68000, the exact vector and device behavior depend on the hardware source and the software environment. Developers should verify vector assignments and interrupt registers against reliable hardware documentation instead of relying on assumptions from another 68000 system.

An interrupt does not create extra CPU time. Its handler consumes cycles from the same processor budget as game logic, rendering preparation, decompression, and collision detection. If a handler runs too long, lower-priority requests may wait, and a supposedly stable frame loop can begin to drift.

Video interrupts and frame pacing

For most games, the vertical display interval is the most useful periodic timing source. A vertical interrupt can mark the boundary between frames, giving the main loop a consistent opportunity to update game state, prepare drawing commands, and exchange buffers or display lists. The exact implementation varies with the X68000 model, video mode, and software stack, so the event should be treated as a measured hardware signal rather than a universal fixed-rate promise.

A robust loop separates “the display event happened” from “the game has finished its work.” The interrupt handler can set a small flag or increment a counter, then return. The foreground code notices that state and performs the substantial update outside the handler. This reduces register-saving overhead and prevents lengthy game logic from blocking other devices.

A counter is often safer than a Boolean flag. If the main loop is briefly delayed, a Boolean can hide the fact that two display events occurred. A counter preserves that information, allowing the program to catch up, skip a nonessential step, or record an overload. Developers should still place a limit on catch-up work; processing an unlimited backlog can produce a permanent spiral of late frames.

Frame pacing also depends on when drawing reaches video memory. If the program updates visible data while the display hardware is reading it, sprites or tiles may tear or appear partially updated. Synchronizing a buffer swap or critical graphics update with the vertical interval is usually cleaner than trying to make every drawing operation interrupt-driven.

The physical case can even become part of a timing experiment. A project such as backlit logo design illustrates the broader value of understanding the machine as a complete hardware system: visual modifications, display behavior, power delivery, and software timing all benefit from careful observation rather than guesswork.

Horizontal timing and raster effects

Horizontal blanking and scanline-related events open the door to effects that would be difficult to schedule from a normal frame loop. Palette changes, scrolling adjustments, split-screen boundaries, and status-bar transitions can be synchronized to a particular region of the display. These techniques are attractive because they make one frame behave as though it contains several independently controlled areas.

The cost is precision. A handler that executes too late may affect the next scanline or miss the intended visual boundary. Interrupt latency includes the time needed for the CPU to finish its current instruction, any higher-priority interrupt already in progress, the exception entry sequence, and the handler’s own register setup. The code must also account for bus contention and the time required to write the target hardware register.

Raster effects should therefore use the smallest possible handler. Precompute colors, scroll values, and addresses during ordinary game processing, then let the scanline routine perform only the final writes. Avoid division, long loops, memory searches, and conditional logic that can vary greatly from one invocation to the next.

A useful development method is to begin with a broad timing margin. Make the effect happen well inside the safe region, verify it on real hardware, and gradually move it toward the desired boundary. An emulator can help with rapid iteration, but it should not be the final authority for cycle-sensitive behavior. Different models, expansion hardware, or emulation cores may expose different latency and bus behavior.

Priority, masking, and interrupt nesting

A game may need several timing sources at once: a display interrupt, sound service, controller or peripheral input, and perhaps a raster event. Their priority relationship determines which service runs first when requests overlap. A high-priority audio event can interrupt a lower-priority video routine, while a masked section can delay both until the CPU restores a permissive interrupt level.

Critical sections should be short and specific. Raising the interrupt mask around a multi-step update can prevent inconsistent data from being observed, but leaving interrupts disabled during decompression or drawing can create visible timing faults. If shared state consists of a single flag or naturally aligned value, an atomic write may be enough; larger structures may need a carefully designed handoff instead of broad interrupt masking.

Nested interrupts deserve explicit planning. If a high-priority handler can interrupt another handler, every routine must preserve the registers and state it changes. Stack usage also becomes important. A deep chain of nested exceptions can consume more stack than expected, especially if a handler calls library code or uses a compiler-generated frame.

One practical approach is to assign each interrupt source a service contract. The contract should state its priority, maximum execution time, registers used, acknowledgment procedure, and whether nesting is allowed. This turns a vague timing problem into something that can be reviewed and tested.

Timing source Typical purpose Best handler behavior Common failure
Vertical display event Frame pacing and buffer coordination Set a flag or advance a counter Long handler causes missed frames
Horizontal or raster event Split-screen and scanline effects Perform precomputed register writes Visible effect appears one line late
Sound service request Feed samples or update audio state Execute at a stable, bounded interval Audio pitch or buffer timing becomes uneven
Input or peripheral event Low-latency device response Capture state, defer game logic Repeated or lost input events
Timer-driven event Regular software scheduling Run a small tick service Tick drift accumulates over time

Audio timing and cooperative game loops

Sound code often reveals timing problems before graphics do. A game may look acceptable while producing clicks, unstable pitch, or uneven music because its audio updates arrive late. The correct solution depends on the sound hardware and driver model: some systems expect periodic register updates, while others use buffering and require the CPU to keep data available before playback reaches an empty region.

Audio service routines should avoid unpredictable work. Music sequencing, instrument selection, and effect prioritization can be prepared in the main loop, leaving the interrupt-side operation to copy compact state or refill a known buffer. If a sound board uses its own processor or handshake mechanism, the X68000-side code must also respect that device’s readiness and acknowledgment behavior.

The relationship between audio interrupts and video interrupts should be measured. Giving every audio request the highest practical priority may protect sound continuity, but it can also interrupt raster effects or increase frame variance. Conversely, allowing a large graphics routine to delay audio service can produce audible artifacts. A bounded audio workload and a clearly sized buffer provide more protection than simply raising priority.

Hardware emulation projects are especially useful for thinking about this boundary. Work on FPGA sound emulation demonstrates why signal timing, bus interaction, and device behavior must be considered together when reproducing an X68000 subsystem. For game development, the lesson is to document whether a timing value describes a CPU event, a bus transaction, a sound-device event, or an audible output deadline.

Measuring latency on real hardware

Timing should be measured at several points, not inferred from the apparent frame rate. A simple diagnostic can toggle an output bit at the start and end of an interrupt handler, or write a recognizable pattern to a spare hardware register if the development setup supports it safely. An oscilloscope or logic analyzer can then show handler width, spacing, jitter, and overlap with other signals.

Software instrumentation is valuable too. Record the longest handler duration, the number of pending display events, the frequency of missed audio deadlines, and the maximum time spent with interrupts masked. Store these counters in a debug build and display them during a pause screen or diagnostic mode. Measurements taken only during ordinary gameplay may miss worst-case events such as a large enemy spawn or a simultaneous sound effect burst.

Test across the conditions that can change timing. Run with music enabled and disabled, use the busiest screen in the game, exercise every controller input, and test on each target model available. If the game supports an accelerator, memory expansion, or a modified power supply, repeat the measurements there as well. A stable result on a fast development configuration does not guarantee stable behavior on original hardware.

Emulators remain indispensable for debugging and regression testing, especially when they provide trace logs or debugger watchpoints. Still, cycle-sensitive effects should be validated against physical machines. The goal is not to reject emulation, but to understand which properties are architectural and which are artifacts of a particular emulator implementation.

Designing a dependable interrupt architecture

A clean X68000 game architecture usually gives interrupts narrow responsibilities. The video event advances frame coordination, the audio event maintains a bounded service deadline, and peripheral events capture raw state. The main loop then translates those signals into movement, simulation, rendering preparation, and resource management.

Use volatile access or equivalent compiler controls for variables shared between interrupt handlers and foreground code. Keep shared values small where possible, and define what each value means when it changes more than once per frame. A sequence number can be more informative than a flag, while a double-buffered state block can avoid partially updated configuration data.

Practical recommendations include:

  • Keep interrupt handlers short, bounded, and free from blocking calls.
  • Precompute raster, audio, and animation data before the deadline arrives.
  • Measure maximum latency and handler duration on original hardware.
  • Treat interrupt masking as a narrowly scoped operation, never as a general timing fix.
  • Add debug counters for missed events, pending work, and stack usage.

When a timing bug appears, reduce the program to one interrupt source and one visible effect. Confirm the vector, priority, acknowledgment, and handler width before adding the next subsystem. This incremental method is faster than debugging a complete game in which video, sound, input, and resource loading all compete for the same processor.

A well-designed interrupt system makes the rest of the game easier to reason about. Frame updates become repeatable, raster effects become deliberate, and audio service stops depending on luck. Documenting those measurements alongside source code also helps preserve the practical knowledge needed by future X68000 developers.

Build a small timing test program before integrating a full game: display a frame counter, exercise the intended interrupt sources, toggle diagnostic signals, and record the worst-case results. Then use those measurements to set handler budgets and frame workload limits. Sharing reproducible timing notes, hardware observations, and tested code through the X68000 community keeps the platform’s development knowledge active rather than leaving it hidden in one project.

Nereid-X Expansion Board

A personally-produced LAN+USB+Memory expansion board for Sharp X68000 series computers. Multiple production runs were offered, including a final batch and a later revival reproduction run.

Power Supply Repair

X68 power supply repair and modification services were offered by the site owner, with documentation shared through diary entries spanning 2001–2006.

Server & Networking

Notes on FreeBSD administration, ISP changes, server migration, and networking topics. The site itself ran on FreeBSD with the hns diary system and Namazu search integration.

A two-ink risograph print in muted slate-blue and charcoal on off-white paper, showing a stylized desktop computer monitor beside a circuit board with soft geometric trace lines, conveying a calm retro-computing workshop atmosphere. A two-ink risograph print in deep purple and dark grey on cream stock, depicting a compact expansion card with connector ports and subtle Japanese technical annotations, evoking a hobbyist electronics bench. A two-ink risograph print in teal and charcoal on warm white paper, showing a server rack silhouette with soft network-line motifs and a small weather icon, suggesting a personal server room corner.

Get in touch

X68K.NET connects Sharp X68000 enthusiasts through community links and shared projects. Reach out with questions about the Nereid project or X68 resources.