SNES DMA: How the Super Nintendo Moves Data Without the CPU
Oct 5th '26 8:12am:
The Super Nintendo copies data from ROM or RAM into video memory at about **2.68 MB/s**. All the programmer has to do is write one byte to register **$420B**. While the copy runs, though, the CPU sits halted, waiting. That undercuts the popular idea that DMA "frees up the processor to do other things." It only makes the copy far faster than any loop of code could manage, and that is why the SNES can animate an entire Mario at 60 frames per second with a 3.58 MHz processor.
## What the Hardware Offers, in Numbers
The list below is the minimum you need to read any SNES documentation without getting lost:
- **Eight channels** (0 to 7), identical to one another. Each has its own block of registers, from **$4300 to $437F**, with 16 addresses per channel.
- **$420B** triggers general DMA on the channels whose bit is set. **$420C** enables HDMA, the line-by-line variant covered below.
- **Fixed speed:** 8 master clock cycles per byte. With the NTSC master clock at 21.477 MHz, that works out to the 2.68 MB/s mentioned above.
- **Source on "bus A"**, with a 24-bit address (bank plus 16 bits): ROM, WRAM, cartridge RAM. **Destination on "bus B"**, the **$2100–$21FF** range, where the PPU (the video unit) registers live.
- **16-bit size.** A value of zero means 65,536 bytes, not "nothing." We'll come back to that.
- **Eight write patterns.** Mode 1, for example, alternates between two consecutive registers, which suits the $2118/$2119 pair of VRAM. Mode 2 writes twice to the same register, useful for OAM and palette.
For comparison, the 65816's own block-copy instruction, MVN, spends 7 CPU cycles per byte. At fast speed, each CPU cycle equals 6 master cycles, so that's around 42 master cycles per byte against DMA's 8. The gap is close to five times in the best case, and a manual LDA/STA loop is much worse. Anyone who has programmed the NES knows the concept from the OAM copy at $4014. The SNES generalized the idea: any source, any PPU register, eight channels.
## The Budget of One Frame
An NTSC frame has 262 scanlines of 1,364 master cycles each, and VBlank, the interval when the screen isn't being drawn, occupies lines 225 to 261 in 224-line mode. That is 37 lines, or **50,468 master cycles**, about 2.35 ms. Dividing by 8 cycles per byte, the theoretical ceiling is a little over **6 KB per frame**. In practice it comes in lower, because every transfer has setup overhead and the code needs time to configure everything.
The number matters because the 64 KB of VRAM only accepts writes during VBlank or with the screen in *forced blank* (bit 7 of $2100). Writing outside that window is ignored, with no warning. If the game uses 239-line mode (overscan), VBlank shrinks to 22 lines and the ceiling drops to about 3.7 KB.
Part of that budget is already spoken for. OAM, the sprite table, is 544 bytes. CGRAM, the palette, is 512. Updating both every frame eats about a sixth of the best case, and plenty of games do that by default. That is why the dominant pattern of the era is to keep a copy of OAM in WRAM, build it calmly during the frame, and ship it whole during VBlank. From there on, whatever bandwidth is left decides how many new tiles the game can swap per frame, and that is the console's real creative limit.
## Setting Up a Transfer, Step by Step
Order matters, and every step has a trap. We'll use copying tiles to VRAM as the example, the most common case.
**1. Make sure VRAM is accessible.** Either you're inside the NMI (the routine that fires at the start of VBlank), or you've turned on *forced blank*. People who test code only in an emulator rarely find out they got this wrong: some more permissive emulators let through writes that real hardware would discard, and the game "works" until the day it runs on an actual console with tiles missing.
**2. Set the destination on the PPU.** Write the VRAM address to $2116/$2117 and the increment mode to **$2115 (VMAIN)**. For word copies via DMA with mode 1, the typical value is $80, which increments the address after writing the high byte to $2119. This register is the champion of beginner bugs: with $00, the address advances after the low byte, and VRAM turns into a soup of shifted tiles. The symptom, graphics interleaved with garbage, looks like a data error, but it's the increment.
**3. Configure the channel.** $43x0 takes the direction and the transfer mode. $43x1 takes the PPU register, as an offset from $2100: for VRAM, $18, which points to $2118. The default direction is bus A to bus B, and that covers almost everything a game does.
**4. Provide source and size.** The source address goes in $43x2 through $43x4 (low, high, bank) and the size in $43x5/$43x6. Here comes the zero trap: a size of 0 doesn't copy nothing, it copies **64 KB**, which can trample all of VRAM and WRAM when the destination is $2180. Anyone who computes the size from a variable that might be empty needs to check first.
**5. Fire.** Writing a byte with the channel's bit set to $420B starts the transfer. The CPU doesn't execute its next instruction until the copy finishes. When it's done, the size register will read zero and the source address will have advanced. If the channel is reused for another transfer, everything has to be reloaded, including what looked unchanged.
There's one more trick worth knowing: **fixed address mode**. With the source frozen on a zero byte in ROM, DMA becomes a filler, and clearing all of VRAM with it takes less than a frame. To clear WRAM, the destination is $2180, but then the source can't be in WRAM itself, because the two will fight over the same bus. The restriction is in the documentation, and almost everyone only learns it after a crash.
## HDMA: The DMA That Runs Itself Every Scanline
So far, DMA has meant "make a copy now." **HDMA** is different: it runs automatically at the start of the HBlank of each visible line, transferring a small amount of data defined in a table. The result is being able to change a PPU register **between one line and the next**, with no CPU intervention.
The table format is simple. Each entry starts with a count byte: the low 7 bits say for how many lines that value applies, from 1 to 128, and bit 7 means "repeat the transfer on every line." Zero ends the table. After the count byte comes the data, either directly or, in indirect mode, through a 16-bit pointer, which allows tables in different banks.
The cost exists and isn't small. Estimates put it at around **18 master cycles of overhead per active line per channel**, plus 8 per byte transferred, and HDMA has priority over general DMA. A game that turns on four HDMA channels spends a noticeable slice of the frame, and that shows up as lost performance in the main code. One usage detail that tends to catch beginners: HDMA initialization happens at the start of the frame, so the bit in $420C has to be set before the first line. Many games rewrite it every NMI.
## How Games Actually Used This
There's a difference between what the hardware allows and what games did with it, and the second list is much shorter and more interesting.
### Moving Graphics: DMA as a Conveyor Belt
*Super Mario World* is the canonical example of **on-demand** use. Mario's graphics aren't all loaded in VRAM. Every frame, the game works out which pose he's in and fires a DMA that brings only that pose's tiles from ROM straight into sprite memory. The same conveyor updates animated scenery tiles, like spinning coins and blinking question blocks. According to community disassemblies, all of this fits in VBlank because the game transfers a few hundred bytes per frame, never near the 6 KB ceiling.
The pattern repeats across many titles. The frame is assembled in RAM, the data that needs to go up is queued, and the NMI empties the queue. *The Legend of Zelda: A Link to the Past* works similarly, decompressing graphics into WRAM and dispatching them by DMA in batches. Anyone reading those routines notices that the bottleneck was never the copy itself, but **deciding what deserves a place in the VBlank window**.
### Effects That Seem Impossible: HDMA as a Paintbrush
If general DMA is the conveyor belt, HDMA is the paintbrush. Three families of effects show up again and again.
**Perspective in Mode 7.** The SNES's Mode 7 applies a transformation (rotation, scale) to the entire plane, but a single matrix would give you just a rotating flat plane. *F-Zero* and *Super Mario Kart* change the matrix parameters **on every line** via HDMA, and that gives the track its perspective: the upper lines are compressed, the lower ones enlarged. It's the only way to get pseudo-3D without drawing polygons.
**Background distortions.** Changing the plane's scroll line by line creates a ripple. *Super Castlevania IV* uses this in swaying backgrounds and in the rotating room with Mode 7, the kind of effect that, seen in 1991, looked like arcade hardware. The heat shimmer in areas like Norfair in *Super Metroid* is one of the examples the ROM-hacking community often cites for the same principle.
**Windows and gradients.** Changing the window registers line by line produces circles, funnels and V-shaped cuts. That's what explains the keyhole-shaped opening in *Super Mario World* transitions and, going by what the documentation describes, the circle of light in dark rooms in *A Link to the Past*. Changing the fixed color used in color blending (register $2132) on every line creates the sky gradients that appear in so many games: from one line to the next, the blue shifts one step, and the eye reads it as atmosphere.
## Where the Hardware Bites
A few traps are worth writing down, because they're the ones that eat whole weekends.
The first is **resetting the OAM address at the start of VBlank**: if the game doesn't reconfigure the address before the sprite DMA, the data lands in the wrong place and sprites show up swapped or with corrupted attributes. The second is the **order between HDMA and general DMA** in the same frame, because HDMA has priority and interrupts a copy in progress, which can blow the window if the budget is tight. The third is the classic of forgetting that DMA is **not parallel**: planning the frame on the assumption that the CPU keeps running during the copy leads to game logic that loses cycles for no apparent reason.
In my assessment, DMA is the most poorly told topic in SNES architecture. Almost every text mentions "2.68 MB/s" as if it were a virtue on its own, and almost none mention what the number costs: the window is short, the CPU stops, and everything that doesn't fit in VBlank needs a plan. The genius of the games from 1991 to 1996 wasn't in discovering DMA, which is in the manual. It was in **choosing what not to transfer**.
One question the documentation doesn't answer well: how many of the effects we now remember as "graphics tricks" were, at bottom, just cycle arithmetic done by someone who knew the frame budget better than the art team did?