PlayStation 2 IPU: Bitstream FIFO, Macroblock Decode, and DMA
Trace PlayStation 2 IPU command words, bitstream buffering, MPEG macroblock work, and DMA completion without confusing decoded pixels with GS output.
The PlayStation 2 Image Processing Unit (IPU) is a dedicated compressed-video processing block, not a general-purpose GPU and not the Graphics Synthesizer. Its job is to consume commands and compressed image data, perform decoding or color conversion, and move results through the system’s FIFO and DMA paths. A movie that shows corrupted macroblocks may therefore fail before the GS ever receives a valid image.
The IPU’s central debugging challenge is ownership. The processor can write command words, compressed data can arrive through a FIFO or DMA, the IPU consumes a variable number of bits, and output may leave through another transfer path. MPEG input is not naturally aligned to the CPU’s word-sized writes, so the boundary between bytes transferred and bits consumed by the decoder must be preserved.
Commands and bitstream state
The IPU command interface selects operations such as variable-length decoding, intra-picture decoding, block decoding, and color-space conversion. These operations do not all consume the same number or format of bits. A command may read a header, decode coefficients, apply inverse quantization and an inverse transform, or convert image components to the representation expected by downstream software.
Treat the command register as an instruction boundary and maintain explicit command state: opcode, command parameters, input-bit position, active decoding stage, output progress, and completion condition. A command that is still waiting for input is not the same as a completed command that has produced no visible pixels. Expose the documented busy and status conditions independently from the host video output.
Because variable-length codes cross byte boundaries, a bit reader should retain a reservoir and a precise bit offset. A DMA boundary can split a codeword; the decoder must continue seamlessly when the next input chunk arrives. Conversely, a decoder must not read beyond the IPU’s available input merely because the host buffer contains additional bytes not yet committed to the device.
MPEG structures create nested boundaries: sequence and picture headers, slices, macroblock addresses, coded block patterns, motion data, and coefficient runs. A parser that searches for start-code patterns without respecting the IPU’s current command and bit position can desynchronize on byte values that happen to resemble markers inside payload. Use the decoder’s bitstream rules and stop only when the command’s documented termination condition is met.
FIFO and DMA are separate queues
The IPU has a FIFO interface used by the CPU-visible transfer path, and the system also provides IPU DMA channels. The FIFO reports whether data is available to the decoder and whether output can be accepted. DMA moves blocks between main memory and the IPU path. Those operations are related, but they are not a single infinitely deep queue.
PCSX2 organizes the IPU implementation into separate source files for the IPU core, FIFO, and DMA, a useful architecture for understanding the device boundaries. An emulator should likewise distinguish input FIFO occupancy, decoder consumption, output FIFO occupancy, DMA request state, and DMA completion. Otherwise, a FIFO starvation bug can be mislabeled as a decoder error, or an output backpressure issue can be hidden by immediate host copies.
When software submits a compressed stream, record both the transfer size and the number of bits consumed by the active decoder command. Padding bits may be present in a final word, and command termination can happen before the entire staged host buffer is exhausted. Do not discard or expose the tail until the hardware-visible FIFO state says it is safe.
The same rule applies to output. A block decoder may produce a fixed-size macroblock result, while transfer descriptors and destination alignment control when software can observe it in memory. A correct decode with an incorrect output DMA count still produces a broken movie. Keep decoded component blocks separate from the final packed output and validate both.
Macroblocks and color conversion
An MPEG macroblock is not merely a rectangle of independently encoded pixels. It is a structured set of luma and chroma blocks, with prediction and coefficient data selected by the stream. Intra-coded and motion-compensated paths have different dependencies. The IPU’s commands expose stages that may let software or hardware progress through portions of this pipeline.
Do not infer the console’s exact chroma layout, quantization behavior, or rounding solely from a general MPEG specification. The codec describes a bitstream; the console IPU determines its supported command set, data packing, clipping, and register-level behavior. For compatibility, compare against the IPU manual or an established implementation, and label any behavior derived only from generic MPEG semantics.
Color-space conversion (CSC) is another explicit stage. YCbCr components must be converted with the expected matrix, range, and rounding convention. If the coefficients are off, the picture may decode cleanly but appear too dark, washed out, or tinted. Validate a synthetic color pattern with known component values separately from a natural video frame where compression noise obscures small arithmetic errors.
The decoded pixels then travel to ordinary memory or a graphics consumer according to software’s chosen path. They are not magically a GS framebuffer. A title may use the GS to composite the IPU output with subtitles, overlays, or other graphics. The diagnostic trace should mark the IPU’s output completion separately from GS transfer and display completion.
A bounded bit-reader test
This small Python reader tests one important invariant: a decoder cannot silently consume bits beyond the input explicitly delivered to it.
class BitReader:
def __init__(self, data):
self.data = data
self.bit_offset = 0
def read(self, count):
if count < 0 or self.bit_offset + count > len(self.data) * 8:
raise EOFError("IPU input is not yet available")
value = 0
for _ in range(count):
byte = self.data[self.bit_offset // 8]
shift = 7 - (self.bit_offset % 8)
value = (value << 1) | ((byte >> shift) & 1)
self.bit_offset += 1
return value
An implementation should preserve partial reads across DMA deliveries rather than replacing the reader when a new chunk arrives. Production code also needs the IPU’s endianness, command-specific bit order, status transitions, and input-reservoir semantics. Use the helper for unit tests of bounds and offset continuity only.
Reproducible decode and transfer tests
Start with command-only tests that verify command acceptance, busy status, and completion. Add minimal bitstreams for each supported command, including a header split across two DMA deliveries and a variable-length code split at a byte boundary. Exercise empty input, malformed input, exact FIFO capacity, input starvation, and a command that finishes with padding remaining in the final word.
For output tests, compare intermediate decoded blocks before conversion, then compare CSC output, then test the output FIFO and DMA path. Record source address, transfer count, FIFO occupancy, command opcode, input bit offset, macroblock coordinates, output byte count, and completion interrupt. That allows an engineer to identify whether divergence began in transport, syntax decoding, inverse processing, conversion, or writeback.
Save-state coverage is essential for long videos. Serialize command parameters, bit reservoir and offset, decoder stage, reference-picture state if maintained by the modeled hardware, FIFO contents, DMA progress, and pending interrupts. Restoring only the memory buffer loses the active context needed to interpret its next bits.
Acceptance criteria
A reliable IPU model accounts for variable-rate bit consumption, command-level progress, bounded input and output queues, DMA timing, macroblock state, conversion arithmetic, and separate GS composition. It does not claim that a successful generic MPEG decode proves register compatibility.
The most effective mental model is a command-driven codec with observable queues and bus transfers. Keep the bitstream decoder, IPU FIFOs, DMA controller, output conversion, and GS consumer separately testable. When a movie breaks, that decomposition turns a vague video-is-corrupt report into a measurable first divergence.
Aligning command boundaries with DMA evidence
An IPU input transfer can be large while a decoder command consumes only part of its staged data. Keep the DMA channel’s byte count separate from the bit reader’s consumed-bit position and the command’s logical completion. Those counters answer different questions: how many bytes the system bus delivered, how much compressed syntax the decoder consumed, and whether the command has reached its terminal state. Resetting all three together at a packet boundary is only correct when the device contract says so.
For an MPEG stream, retain the exact remaining bits after a command completes or stalls. A later command may continue from the same stream position, while software may also submit a flush or termination operation. A test should therefore snapshot the FIFO, command status, and bit reservoir before and after each command; testing only the final decoded frame cannot reveal an accidental byte skip between commands.
DMA arbitration also affects the moment the CPU observes completion. An emulator can use a fast block-copy implementation internally, but it must schedule the visible completion and any interrupt at the modeled device boundary. Add a trace field for bus transfer completion cycle and another for IPU command completion cycle, then test cases where one occurs before the other. This avoids making an assumption that all data arrival and decode work are simultaneous.
Related:
- PlayStation MDEC: Command FIFOs, Macroblocks, and DMA Output
- PlayStation 2 VIF and GIF: DMA Paths, Vector Unpacking, and Graphics Handoffs
Sources: