PlayStation DMA Controller: DPCR Priority, Chopping, and DICR Flags
Model PS1 DMA as seven timed channel engines with DPCR priority, synchronization modes, chopping windows, linked lists, and latched DICR flags.
The original PlayStation has seven DMA channels, but treating them as seven copies of memcpy misses most of the device’s contract. Each channel has a source or destination address, a block description, a transfer direction, a synchronization mode, and enable state. The global DMA control register, DPCR, supplies per-channel priority and master-enable controls; the interrupt register, DICR, combines per-channel enables, completion flags, and a master interrupt condition. GPU linked-list transfers and ordering-table clears have additional behavior that is not equivalent to a normal fixed-size copy.
DMA affects CPU progress, device state, interrupt timing, and what software observes in channel registers after a transfer. It is a scheduling problem shared among RAM, peripherals, and the bus arbiter. A fast emulator can implement whole-block copies, but it still needs to preserve the specified order and externally visible boundaries. A debugger should be able to answer which channel owned the bus, why it was eligible, how many words it moved, and which flag or interrupt changed when it stopped.
Seven channels have different endpoints
The channel map connects two MDEC directions, GPU, CD-ROM, SPU, the expansion port, and the ordering-table clear operation. Some endpoints are memory-to-device, some device-to-memory, and some have special endpoint-side control registers. Configure both ends: enabling a CPU-side DMA channel does not replace the GPU, SPU, or other peripheral setup that defines its own transfer path.
The base-address register stores a 24-bit address field. Alignment and update behavior depend on mode and transfer phase; do not assume every write is rounded immediately in the register file. The block-control word is interpreted differently for manual and request-based modes. Preserve the raw programmed value for diagnostics, then derive the current block length and remaining work in explicit units. A zero field can have mode-specific meaning and must not be normalized into “zero bytes” or “maximum bytes” globally.
The RAM region itself is finite. Address increment or decrement can wrap the 24-bit counter, but a wrapped bus address does not imply that the console has valid RAM at every address. The access path, memory map, and endpoint determine whether the transfer reads data, ignores it, raises an error, or produces a hardware-specific side effect. Keep address arithmetic separate from memory decoding.
DPCR arbitrates readiness and priority
DPCR contains a small control field for each DMA channel. The field selects relative priority and a master enable. A channel may be enabled locally but unable to run because its DPCR master bit is clear. Several channels may be eligible at once, so the arbiter chooses according to priority and the machine’s active transfer rules. A model that processes channels in array order instead of arbitration order can produce the wrong interleaving even if every independent transfer completes.
Priority does not mean a high-priority transfer should be treated as infinite and uninterruptible. Transfers consume bus time, and the controller has defined points at which another request, a chop window, or a peripheral condition can affect progress. Model ownership as a scheduled grant. Keep a pending request separate from the currently granted bus phase, and record when the phase returns to CPU or another device.
For tests, arrange two simultaneous request-mode transfers to different endpoints, swap their DPCR priorities, and observe the first grant and resulting side effects. Then clear a master-enable bit while one channel’s local enable remains set and verify the documented disabled behavior. The test should compare a trace of grants and completion state, not just final RAM bytes.
Synchronization modes define when a channel runs
The synchronization-mode field selects manual block transfer, request-driven block transfer, or GPU linked-list operation. Manual mode begins under software control. Request mode waits for the peripheral’s DMA request and uses the programmed block structure. Linked-list mode walks packet nodes from RAM, with each node header carrying a word count and the next-node address; the GPU is the usual consumer of this command stream.
Do not use one loop for every mode. A manual transfer has a software start boundary and fixed amount of work. A request-driven transfer must pause when the endpoint is not requesting service and can expose block counters as progress advances. Linked-list mode must read the node header, send the node’s payload in order, detect the documented terminal marker, and avoid interpreting payload as a header. Invalid pointers, address wrap, and channel aborts must terminate in a deterministic and hardware-supported way rather than walking arbitrary host memory.
Peripheral readiness is not interchangeable with DMA channel activity. The CD-ROM controller may supply a stream when sectors arrive; SPU has separate transfer ports and control; GPU GP0 accepts command words under its own FIFO conditions. Maintain the endpoint’s request signal and the DMA channel’s armed state independently. Otherwise the emulator cannot explain whether a missed transfer came from a peripheral that never requested service or a DMA channel that was masked.
Chopping splits work into CPU and DMA windows
Chopping allows a channel transfer to be divided into smaller DMA windows separated by CPU execution windows. DPCR priority and chopping are distinct: priority resolves competition, while chopping limits uninterrupted occupation. The channel’s chop-size and CPU-window fields select the window sizes. A channel may update its address as chunks progress, so software-visible MADR state can differ from the start address during a chopped transfer.
Implement chopping using explicit remaining words, words remaining in the current DMA slice, and CPU cycles remaining before the next slice. Do not approximate it by yielding after an arbitrary host-time interval. The emulated CPU must regain progress at the documented boundary, and the DMA channel must resume with its current pointer and block counters. Reset, disable, and save-state restore all need tests while the channel is between windows.
Chopping is particularly useful to software that wants peripheral throughput without starving the processor. It is also a strong test of the bus model because a large transfer and the same transfer chopped into small pieces can end with the same data but different CPU-visible progress. Compare instruction milestones and interrupt timing across both configurations.
DICR is a state machine, not a completion boolean
DICR includes force, master-enable, per-channel interrupt-enable, a master flag, and per-channel completion flags. A channel finishing does not necessarily assert the CPU interrupt: its enable and the global/master conditions matter. Completion flags are state that software may acknowledge through defined writes. Preserve the distinction between a channel reaching its endpoint, a channel’s completion flag becoming set, the master condition becoming true, and the interrupt controller seeing the DMA line.
Because the register contains control bits and latched status bits, implement reads and writes with separate masks. Do not store the last written DICR word and return it unchanged. On a write, update writable enables, apply the documented acknowledgement behavior to completion flags, recompute the master condition, and then update the CPU interrupt line. On a read, combine current control state with current latched status. Test multiple channels completing before the guest acknowledges either flag.
Keep the DMA controller’s completion flags separate from the CPU interrupt controller’s pending IRQ3 bit. Acknowledging a DICR completion flag does not by itself acknowledge IRQ3 in I_STAT; software and emulators must preserve the two register-level states and clear each through its own documented write semantics.
The diagnostic event should include the channel, transfer mode, completion cycle, DICR before and after, and interrupt-controller state. This makes it possible to find a bug where two independent channels race to update an aggregate flag, or an implementation forgets to clear the CPU line after software acknowledges both layers.
A transfer ledger for tests
This small representation helps a test assert that a request is serviced only after it is eligible and that its completion belongs to the correct channel. It is not a register emulator or an encoding of the hardware’s exact bitfields.
from dataclasses import dataclass
@dataclass(frozen=True)
class Grant:
channel: int
start_cycle: int
words: int
mode: str
def validate_grant(grant):
if not 0 <= grant.channel <= 6:
raise ValueError("PlayStation DMA channel must be 0..6")
if grant.start_cycle < 0 or grant.words <= 0:
raise ValueError("grant must have a nonnegative cycle and positive length")
if grant.mode not in {"manual", "request", "linked-list"}:
raise ValueError("unknown DMA synchronization mode")
validate_grant(Grant(channel=2, start_cycle=120, words=16, mode="request"))
Extend the test record with DPCR priority, endpoint readiness, chop counters, source/destination, and DICR state. Each recorded bus grant should be reproducible from the guest’s register writes and the peripheral’s request history.
Failure isolation and acceptance criteria
Use one focused fixture for each channel and mode before trying a full game. Verify the address fields, direction, request timing, block-size interpretation, pointer update, interrupt masks, and endpoint-side effects independently. Then exercise competing channels, a chopped request, a linked-list end marker, an abort, and a save state taken mid-transfer. Include invalid addresses only where the hardware behavior is documented or established by a reproducible test; do not guess that all bad transfers are harmless.
A dependable model has a single source of truth for channel state and bus grants. It keeps DPCR arbitration, DICR latches, CPU interrupt propagation, and peripheral request lines separate but connected. It can explain every completion flag and every incremented address from a timestamped transfer trace. With those conditions in place, DMA bugs become identifiable scheduling or endpoint defects rather than unexplained corruption.
Related:
- PlayStation CD-ROM Controller: Commands, Responses, Sector Data, and DMA
- GameCube GX Command Processor: Gather Pipe, FIFO Watermarks, and Draw State
Sources: