Mega Drive VDP DMA and FIFO: Bus Ownership, Queues, and Safe Transfers
A hardware-grounded guide to Mega Drive VDP DMA modes, 68000 bus stalls, FIFO status, display-time limits, and emulator validation.
The Mega Drive’s Video Display Processor is not a passive framebuffer. The 68000 communicates with it through data and control ports while the VDP also fetches display data and can move blocks of memory on its own. A program that treats each VDP write as an immediate, unlimited store can work in a blank screen test and fail during active display, a DMA transfer, or a reset sequence.
This article focuses on the original Mega Drive/Genesis VDP behavior documented in Sega’s development material. Sega’s software manual and technical overview survive as archived copies, not currently hosted official product documentation. Emulator source is useful implementation evidence, but it is not a substitute for the hardware manual or a measured console. In particular, MAME’s current 315-5313 source explicitly marks video and DMA timing as needing real-hardware verification; its timing should not be presented as a hardware oracle.
Three DMA modes move different things
The VDP documentation describes three DMA modes selected through its control and register interface:
- Memory to VDP RAM: data is read from 68000 address space and written to a VDP target. Sega’s manual lists VRAM, CRAM, and VSRAM destinations. The source is selected from CPU memory, and the 68000 is stopped while this bus-backed transfer runs. The manual notes that the Z80 can continue if it does not need to access the 68000 memory space.
- VRAM fill: the CPU supplies a write that starts a repeated fill operation. The VDP writes the requested byte pattern across a VRAM range. The operation’s start handshake and target behavior are not interchangeable with a normal memory-to-VRAM copy.
- VRAM copy: the VDP reads from its own VRAM and writes the data to another VRAM range. This does not fetch the data stream from 68000 memory.
The Sega manual names fill and copy as VRAM operations; do not assume they are general-purpose ways to fill CRAM or VSRAM. When implementing or writing homebrew, consult the target manual’s mode-specific details for address units, source alignment, transfer length, increment behavior, and the exact command sequence. Emulator implementations can add valuable cross-checks, but a source code path is an implementation choice, not proof of every board revision’s cycle behavior.
Bus ownership explains why DMA has a visible CPU cost. During a memory-to-VRAM/CRAM/VSRAM DMA, the 68000’s source bus is part of the transfer, so CPU execution pauses. Internal fill/copy modes use the VDP’s own memory path and are not the same 68000 source-bus transaction. Software should not collapse all three into a single “fast copy” abstraction if it depends on CPU availability, Z80 access, or transfer completion.
The FIFO decouples CPU writes from VDP consumption
The VDP also has a write FIFO between CPU port accesses and display-memory operations. Sega’s documentation identifies empty and full status bits in the status word; the archived technical overview describes a four-word FIFO and explains that a tight write loop during active display can fill it, after which the CPU waits for space. This queue reduces immediate stalls when the VDP can accept writes, but it does not make the queue infinite or eliminate display-time contention.
Read the status register from the VDP control port when software needs to know whether a transfer is busy or the write queue is full. Treat these as distinct conditions: a FIFO full status means the producer cannot enqueue another write at that instant; DMA busy means a DMA operation is active. Code that waits only for one condition may still issue an illegal or ineffective access under another. The exact bit numbering is defined by the manual and differs in presentation across diagrams, so keep masks named and derived from a verified register definition rather than copying a bit index from an emulator forum post.
CPU access timing depends on display state. During active scan, VRAM/CRAM/VSRAM access opportunities are constrained by the video fetch schedule; blanking provides substantially more opportunity. Sega’s documentation gives historical throughput examples for specified display modes, but those figures depend on mode and timing assumptions. They should be treated as constraints from that manual, not universal byte-per-frame promises for every console, clone, revision, or emulator.
A robust transfer sequence is stateful
The control port first receives command words that specify a VDP operation and target address. The data port then carries ordinary writes or the initial data for a fill operation. DMA requires its enable/configuration registers and the address/length/source fields to be prepared before the command starts. The transfer length and source registers are part of VDP state and may be updated as work progresses. If software issues a second command at the wrong time, it can overwrite a control sequence or collide with an active transfer.
For production homebrew, wrap the port rules in small functions with explicit preconditions:
wait_until_vdp_write_fifo_has_space()
configure_dma_mode_and_source()
configure_target_address_and_length()
issue_dma_command()
while (status.DMA_BUSY):
poll_status_or_do_work_that_does_not_touch_the_VDP
This is deliberately pseudocode: the precise register writes, command encodings, widths, and source constraints belong in a platform-specific implementation sourced from Sega’s manual and tested on the selected hardware. A generic loop should not casually read status during a DMA mode where the bus is unavailable. Nor should it call a routine that writes VDP registers from an interrupt handler while the main thread assumes the control port is idle.
Interrupts complicate port ownership. A VBlank handler may be the normal place to queue new tile or palette data, but if it can interrupt a foreground routine between the two words of a VDP command, the two routines need a serialization policy. Common strategies are to build a software queue and let one owner drain it, or to mask the relevant interrupt only around the short non-reentrant control sequence. Disabling interrupts around an entire long DMA defeats responsiveness and should be justified separately.
Reset and busy-state handling are not optional details
Sega’s development bulletin documents a specific reset hazard: resetting the 68000 does not reset the VDP. If the CPU restarts while a DMA is still running, immediate VDP accesses can be ineffective. Sega’s correction instructs software to check the DMA-busy status after its initialization path before accessing the VDP. This is a concrete reason to distinguish CPU reset from a full machine power cycle in an emulator and in hardware diagnostics.
An emulator that models CPU reset by clearing all VDP state can hide this behavior. A more faithful test should start a memory DMA, trigger a CPU reset before completion, and verify that VDP transfer state and the CPU’s post-reset polling sequence are coherent. The exact reset source and console revision should be recorded. A front-panel reset, software jump to a reset vector, watchdog, and emulator hard reset may not have identical semantics.
Emulator modeling: separate the operations and state
Represent the VDP command latch, destination memory type/address, auto-increment, DMA mode, source address, remaining length, FIFO entries, status flags, and timing position as distinct state. A memory-to-VDP DMA should model its 68000 bus impact separately from fill/copy. FIFO enqueue and dequeue events should be tied to VDP timing, not immediately committed simply because the host code executed a write function. If the emulator does not yet model exact FIFO timing, document the approximation and avoid claiming bus-cycle accuracy.
MAME’s device source is instructive precisely because its comments identify DMA and video timing as unverified. Genesis Plus GX contains explicit DMA type handling, mode-specific work, and timing tables; it is a valuable comparative implementation. Compare these sources to locate hypotheses and regressions, then validate externally. Two emulators agreeing can still reflect shared assumptions or copied data.
Keep rendering and port behavior related but separable. A missed FIFO stall may change CPU-visible timing and therefore the register write position relative to the beam, even if the final VRAM contents look correct. Conversely, an image mismatch may come from tile data or a wrong VDP command rather than DMA timing. Tests should report both memory outcomes and event traces.
Acceptance tests for a transfer engine
Create small programs with known source bytes, target address, length, and auto-increment. Verify each DMA mode against expected VRAM, CRAM, or VSRAM contents where the manual permits that destination. Test source regions documented for memory-to-VDP DMA, odd/even or alignment boundaries where relevant, and transfer lengths around zero and register-wrap boundaries only after checking the manual’s counter semantics. Avoid using a single copied benchmark as a universal throughput test.
For the FIFO, submit short writes while the display is active, deliberately fill the queue, observe full/empty status, and confirm the CPU eventually resumes when a slot becomes available. Repeat during blanking and with display width modes the emulator claims to support. Capture CPU wait cycles, VDP dot position, FIFO depth, and status-read values in a trace. Then test a DMA in progress, status polling, attempted VDP port access, and CPU reset during transfer.
On real hardware, use a diagnostic ROM that writes a checksum or visual marker after completion and reports timing through a reproducible method. Identify the console model, region, display mode, cartridge image hash, and test ROM checksum. Emulator acceptance should include the same inputs and a bounded tolerance defined in advance. A result such as “the image looks right” is inadequate for queue-full behavior or a CPU stall that occurs only during active display.
The architectural takeaway is that the VDP is a bus participant and scheduler, not a memory array. DMA mode determines who owns the source, FIFO state determines when queued writes can progress, and video timing determines access opportunity. Correct emulation and reliable homebrew both begin by keeping those states separate.
Related:
- Cycle-Accurate Emulation and Why It’s So Hard to Get Right
- Retro Sound Chips: PSG, FM, Wavetable, and Sample Playback Architectures
Sources: