PlayStation CD-ROM Controller: Commands, Responses, Sector Data, and DMA
Trace PlayStation CD-ROM commands, parameter and response queues, sector delivery, interrupt acknowledgements, DMA, and emulator timing boundaries.
The original PlayStation’s CD-ROM path is not a synchronous function that returns a file. Software programs a controller through a small register window, sends a command and optional parameters, then services status, response, interrupt, and data events over time. The drive mechanism, controller, system DMA, CPU, and audio path all have state that can advance independently. An emulator that shortcuts this into “read sector N now” can boot games yet fail on polling loops, command overlap, streaming audio, or error handling.
This article focuses on the controller interface and its software-visible queues. It does not attempt to specify optical media geometry or the console’s separate MDEC decompressor. Those paths meet at buffers and DMA, but they have distinct protocols and should be debugged independently.
Treat the register window as an indexed protocol
The controller occupies a compact I/O range around 0x1F801800. The low register selection is not a simple set of four independent addresses: index bits select different views, and read and write access can expose different registers at the same offset. The index register itself, request/interrupt status, interrupt mask, command/parameter port, response/data port, and control register each have direction-sensitive semantics. A trace must therefore record the selected index and access direction as well as the nominal address.
Software first selects the register view, checks controller status, writes command parameters through the parameter FIFO, and writes a command byte. The controller can later report an interrupt cause and place one or more response bytes in the response FIFO. Sector payload is a separate stream. Treating all bytes read from the data-side port as interchangeable is a common source of emulator errors: command responses are short protocol records, while sector bytes have their own readiness and consumption contract.
Command execution is asynchronous. A CPU write starts work; it does not prove the drive has reached the requested position or that data is ready. Busy/status bits, response availability, and interrupt causes expose progress. A game may poll, wait for an interrupt, or use a mixture depending on its driver. The model needs deterministic command latency and ordering, even if it does not reproduce the mechanical seek time of a physical drive exactly.
Separate command, response, and payload state
Use distinct queues or equivalent state for command parameters, controller responses, and sector data. Each queue has its own capacity and rules for when entries become visible. A response FIFO can contain the result of a command while a data transfer has not begun; conversely, a sector transfer can make payload available while software still needs to acknowledge a controller event. A single generic byte buffer erases those states and encourages accidental reads from the wrong queue.
The interrupt request register reports latched causes, while the interrupt enable register controls which causes reach the CPU. Acknowledge semantics matter: software commonly writes a mask to clear selected pending causes, and changing the selected register bank changes what that write means. The controller’s request line should be recomputed from pending causes and enable bits. Do not clear every pending event just because one interrupt was delivered, and do not reassert a cleared level until a new event occurs.
The data request bit is another handshake, not a substitute for DMA completion. CPU reads and DMA channel 3 can both consume controller data according to the controller’s mode and request state. The DMA engine has its own transfer count, address, chopping/trigger behavior, and completion interrupt. The controller must not silently discard unread bytes when a DMA count reaches zero, and DMA must not keep pulling bytes after the controller’s current transfer is exhausted.
Model sector mode and command transitions explicitly
Commands such as status queries, location changes, reads, pauses, and mode changes have different parameter and response behavior. A useful implementation records the current command, expected parameter count, drive/controller state, pending response bytes, interrupt cause, current sector address, selected sector format, and whether payload is available. The model should define exactly when each field changes and which CPU-visible event observes the transition.
Location parameters use the controller’s documented address representation rather than an emulator’s internal LBA by accident. Sector addressing, BCD conversion, the lead-in offset, and invalid values should be isolated in tested conversion functions. Sector mode changes affect which bytes are delivered and whether additional interpretation such as XA audio is requested; they do not rewrite the source disc image. Keep raw sector acquisition, controller formatting, and application-level decoding as separate stages.
Read commands are streams. After command acceptance, sector events occur repeatedly until software pauses, stops, changes mode, or the device reports an error. Model the interval between events in emulated time, not as one immediate loop that fills a buffer. The host filesystem or disc-image reader can fetch data ahead of time, but that optimization must not change the cycle-visible queue cadence. Use a deterministic scheduler event for each transition and cancel or invalidate stale events when a command is superseded.
A trace format that distinguishes the layers
For debugging, log one record per register transaction and one per scheduled device event. Include the emulated cycle, selected index, read/write direction, byte value, status before and after, command state, pending response depth, sector-data depth, interrupt request/mask, DMA channel state, and current sector. Avoid logging only decoded labels: raw bytes let another implementation verify the decoder.
The following is a complete conceptual event record, not a PlayStation register write sequence:
{
"cycle": 120000,
"event": "cdrom_response_enqueued",
"command": "GetStat",
"response_bytes": [2],
"irq_cause": "INT3",
"response_depth": 1,
"sector_bytes_ready": 0
}
Keep event naming consistent across CPU polling, interrupt delivery, DMA, and controller scheduling. If a game hangs, compare the last successful command and the next expected transition. A useful test fixture records a command’s register accesses and expected visible state after every scheduler boundary. It should test parameter underflow/overflow, command overlap, disabled interrupts, selective acknowledgement, reading an empty response FIFO, consuming exactly the DMA count, and a read interrupted by pause or stop.
Validate behavior without overfitting one title
Start with controller diagnostics or documented register-level tests, then compare traces against a known implementation or hardware capture. Test both polling and interrupt-driven clients. Cover no-disc and invalid-sector responses, repeated status queries, audio/data mode changes, DMA termination at short and long counts, and save-state restoration while a command is in flight. Save states must include pending queue contents and scheduled-event deadlines, not only the current sector number.
When a game shows a black screen, first establish whether it issued the expected command, received the expected response, and observed payload readiness. If those milestones pass but decompressed frames fail, inspect the separate MDEC path. If data sectors are correct but music is wrong, separate CD-DA/XA delivery from controller queue behavior. Layered evidence prevents a disc-image format or video decoder workaround from concealing a controller timing defect.
The acceptance criterion is not “the game boots.” It is that each documented command produces the right responses and interrupts, sector bytes are delivered through the correct path, DMA consumes exactly what it should, and every state transition is reproducible from the trace. That makes the CD-ROM controller understandable as a timed device rather than a file-loading convenience.
One subtle boundary is data-sector interpretation. A controller’s selected mode and the disc’s stored sector form determine which portion is exposed to software; XA audio and CD-DA playback add consumers with their own buffering and clocks. Do not let the image reader strip headers or subcode before the controller model has had the information required by its mode. Keep a raw-sector fixture alongside the user-data view, and assert the byte offset and length delivered for each command mode. When a transfer reaches end-of-sector, record whether the controller signals another request, queues a response, or waits for the next scheduled sector. This prevents a convenient host API such as read_file(offset, length) from accidentally defining console behavior. It also gives test cases a stable way to distinguish a controller data-path bug from a mastering, dump, or file-format error.
Related:
- How to Preserve Optical Games with Data, Audio Tracks, and Subchannel Evidence
- PlayStation MDEC: Command FIFOs, Macroblocks, and DMA Output
Sources: