Sega Master System Line Interrupts: VDP Counter Reloads and Raster Timing
Implement Master System horizontal interrupts by separating the VDP line counter, active-display reload rules, frame IRQ, status acknowledgement, and model timing.
The Sega Master System VDP can interrupt the Z80 for two different video events: a frame interrupt and a horizontal line interrupt. Games use the horizontal interrupt for raster effects, split-screen scrolling, status bars, and timed work during active display. Treating it as a fixed timer that fires every N host frames is inaccurate. Its counter is reloaded and decremented as the VDP traverses logical scanlines, and the frame and line interrupt enables are separate.
The original VDP documentation by Charles MacDonald describes register and status behavior, while current MAME source shows a concrete device model with distinct pending line and frame flags. These are technical references rather than a claim that every VDP revision has identical sub-scanline edge placement. The source explicitly calls out areas where timing remains under investigation. An emulator should preserve that evidence boundary, especially when extending from Master System to Game Gear or Genesis compatibility modes.
Two interrupt sources, separate enables
The VDP’s line counter uses register 10 as its reload value. Register 0 bit 4 enables the horizontal interrupt output. The vertical/frame interrupt uses a different status condition and enable in register 1. Software can enable one source while leaving the other disabled; combining them into one generic VDP IRQ flag loses that distinction.
The status port communicates pending video conditions and participates in acknowledgement. Reading it is not merely a passive debug operation: it clears interrupt state in documented behavior and in MAME’s implementation. A game that polls status in its interrupt handler can therefore alter whether a later IRQ remains asserted. Avoid updating status lazily only when the CPU reads it; the VDP event and external IRQ state must evolve on video time.
The Z80’s maskable interrupt control remains a separate layer. A pending VDP condition can exist while the CPU has interrupts disabled, and enabling a VDP source does not itself make the Z80 accept the IRQ. Model the VDP source, VDP output line, and CPU interrupt-enable state independently.
Understand counter phase and reload boundaries
Register 10 is not a host millisecond period. During the relevant active-display interval, the VDP compares its internal line counter with zero. A nonzero count is decremented; when it is zero, the VDP schedules a horizontal interrupt event and reloads the programmed register value. The timing of that event within the line depends on VDP timing. Current MAME schedules a pending horizontal event at a device-specific horizontal position instead of asserting the interrupt at an arbitrary host callback.
The counter is initialized or reloaded outside active display according to the VDP’s vertical-region handling. Current MAME source reloads it during blanking and border regions and explicitly handles display-height-dependent boundaries. Therefore a one-line offset in an emulator often comes from starting the counter at the wrong phase, not from using the wrong arithmetic. A model that decrements continuously through vertical blanking can drift from a model that reloads at the documented boundaries.
The reload value zero deserves its own test. Depending on the exact boundary logic, zero can represent an event on each eligible line rather than a long interval. Avoid treating the counter as an unsigned countdown whose zero silently becomes 255 unless that behavior is backed by the target VDP evidence. Characterize it with hardware tests or trusted emulator test ROMs for the specific model.
Frame interrupts are not line-counter interrupts
Frame interrupt timing is associated with the VDP’s transition through the vertical display region. The documented line depends on selected display height. A frame IRQ can occur even when horizontal interrupts are disabled, and vice versa. Each condition needs its own pending flag and enable logic, while both may feed the same external interrupt line.
For a clean design, maintain at least: the line counter, programmed reload value, active line and horizontal phase, horizontal pending/occurred state, frame pending state, the two enable bits, and the resulting IRQ line level. Reading status should clear only the documented pending conditions. Writes to mode or display-height registers should update timing through explicit transition rules rather than retroactively changing a line already in progress.
When an IRQ handler reads status and writes register 10, define which scanline observes the new reload value. The bus write has a point in the emulated timeline. Applying it at the beginning of a frame or once per frontend call can cause a line effect to move by one row when the same software works on hardware.
Coordinate CPU, VDP, and display time
The Z80 clock, VDP pixel/line counters, interrupt assertion, and frontend frame callback use different units. Drive the VDP from emulated clock progress and convert between CPU cycles and pixels using the selected system clock and video mode. Do not use wall-clock sleeps, host monitor refresh, or frontend run-call count as the interrupt source.
The V counter’s visible value is a separate observation from the internal line-interrupt counter. Software can read a V counter while the line counter is reloading or awaiting a scheduled horizontal event. Keep these state variables separate so that a debugger read does not accidentally consume the line interrupt.
Regions and display modes affect the frame geometry. A fixed number of scanlines per frame may match one NTSC mode but fail for PAL timing or another selected display height. MAME documents different frame regions for 192-, 224-, and 240-line configurations and distinguishes NTSC and PAL. Treat that table as a reference to its modeled timing, and verify the actual target system and software mode rather than assuming a universal 262-line frame.
Instrument the event chain
For diagnosis, log every scanline transition with the emulated frame number, VDP line, horizontal position of the event, line-counter value before and after, register 10, active mode, both enable bits, pending flags, and external IRQ state. At the CPU boundary, log status-port reads, register writes, interrupt acceptance, and interrupt return. This makes it possible to identify whether an error is a counter reload, an IRQ edge/level problem, or a Z80 interrupt-mask issue.
Use a targeted regression sequence:
- Program a small nonzero line count and observe multiple interrupts across a frame.
- Repeat with zero and the maximum byte value.
- Change the reload register inside the handler and check the first affected scanline.
- Enable frame IRQ only, line IRQ only, and both together.
- Read the status port before and after each source becomes pending.
- Disable the CPU’s interrupt flag while the VDP event occurs, then enable it.
- Repeat for each display height and regional timing supported by the emulator.
Automated tests should assert both the line at which the event occurs and the ordering of the status read, IRQ assertion, and CPU interrupt entry. A screenshot-only test can miss an IRQ that is one CPU cycle early but still produces the same static frame.
Common implementation failures
A common shortcut decrements the register value once per rendered frame. That cannot produce multiple line events during one frame, and it cannot model counter reload at display boundaries. Another shortcut asserts the IRQ at the start of a scanline. Games that update scroll or palette state inside the line can expose a horizontal phase error even if the vertical row is correct.
Status acknowledgement is another trap. If status is cleared only in the VBlank handler, a line interrupt can remain pending and retrigger unexpectedly. If every status read clears every state bit, the frame source may be acknowledged when software intended to inspect only another condition. Follow the documented register behavior and tests for the modeled VDP revision.
Finally, do not import assumptions from the Texas Instruments TMS9918 or Mega Drive VDP just because the Sega VDP family shares ancestry. The Master System added different display modes and interrupt handling. The current MAME device source identifies compatibility modes and VDP variants explicitly.
Acceptance criteria
A robust implementation can explain the counter’s active-display phase, reload boundaries, zero-value behavior, and horizontal event position for each supported model. It keeps horizontal and frame conditions distinct through status acknowledgement and CPU masking, records their timing in emulated units, and has regression tests for register writes near an interrupt.
The line counter is a small state machine tied to raster progress, not a timer guessed from average frame rate. Model the exact source behavior where documented, use upstream implementation evidence for additional edge handling, and label any sub-line timing that remains unverified.
Related:
- Game Boy STAT and LYC Interrupts: Building Reliable Scanline Effects
- NES PPU VBlank and NMI Timing: Model the Event, Not Just the Frame
Sources: