Sega Saturn CD Block: Sector Filters, Partitions, and Host Transfers
Follow Saturn CD sectors through FAD ranges, masked subheader filters, selector connections, partition buffers, DMA, and transfer completion.
The Saturn CD Block is not just a drive that returns a byte stream when software asks for a file. Its command interface configures filter entries, links filter outcomes to destinations, routes matching sectors into partition buffers, reports selector and transfer events, and exposes buffered data through a host data-transfer path. The disc address, sector subheader, filter graph, destination partition, and transfer cursor are distinct pieces of state.
That architecture explains a common debugging trap: a seek can succeed and sectors can arrive while the application still receives no useful payload. A filter may reject the sector, route it to a different partition, or match it but leave the software reading the wrong data length. A correct emulator therefore needs more than ISO file extraction. It must preserve the CD Block’s stateful routing contract and its visible command, status, and data-transfer boundaries.
FAD identifies the physical sector timeline
Saturn CD commands commonly use FAD, or Frame Address, rather than the zero-based LBA terminology used by many host APIs. MAME’s CD Block implementation documents the relationship as FAD = LBA + 150, because the first data sector begins at MSF 00:02:00 rather than absolute frame zero. Keep the conversion at the disc-interface boundary. Mixing FAD and LBA internally by a constant offset creates errors that look like wrong track selection or a filter that never matches.
A read operation is configured with an address range and a sector count. Filter-range commands associate a start FAD and range length with a filter slot. Other selector commands set or retrieve subheader conditions, filter mode, and graph connections. Mednafen’s command decoder gives the argument layout for these operations, including commands to set filter range, set subheader conditions, set mode, connect filters, and get sector data. Treat the command words as a hardware interface: validate their field widths and reject invalid filter indices rather than silently indexing an array.
There are two useful layers to the FAD model. The drive side moves through a continuous disc timeline and raises sector-arrival events. The selector side compares each sector against active rules. A filter with no matching FAD range should not become a match just because its channel or submode fields happen to equal the sector’s subheader. Test range start, final included sector, and first excluded sector explicitly, including a range that crosses an encoded track boundary.
Subheader masks select data streams
Mode 2 sectors carry subheader fields that software can use to distinguish file, channel, submode, and coding information. Saturn’s CD Block filter commands let software provide a channel value and masked submode and coding-information comparisons, as well as a file identifier. This allows one disc region to multiplex a movie, audio stream, and other data without forcing the host to scan every payload after transfer.
The usual masked equality test can be expressed as ((actual ^ expected) & mask) == 0, but do not apply it indiscriminately to every field. First parse the sector layout and mode; then interpret the subheader bytes and filter state according to the programmed command. A test fixture should enumerate each mask bit independently, proving that unmasked differences do not reject a sector and masked differences do. It should also test the controller’s special modes and filter reset semantics against a verified command trace, because a generic boolean predicate does not define every selector mode.
Filters can form a connection graph. A true outcome and a false outcome can lead to further filter nodes or a destination, so routing is not necessarily a flat “all conditions ANDed” list. A typical data path configures filter state, connects its outcome to a partition, starts reading, and then lets each sector pass through the selector. Unmatched sectors can be discarded or sent elsewhere depending on the connection graph. Log the exact node, condition result, and next destination for each sector; a final partition count alone cannot show where routing diverged.
The command API has readback commands for filter range, subheader conditions, mode, and connections. Use them as part of the workflow, rather than assuming a write succeeded. Invalid slot indices should produce the controller’s rejection response, while valid selector changes complete through the selector event path. Mednafen’s reference implementation names the ESEL interrupt condition for selector processing; an emulator should preserve the difference between command acceptance and the later event that software polls or enables as an interrupt.
Partitions are bounded buffers, not filenames
A partition is a buffer destination and accounting domain. It is not the same thing as an ISO directory or a host file. The CD Block firmware’s internal selector and buffer-manager analysis describes a finite pool of sector buffers, linked chains for each partition, and a router that chooses a destination after the newly arrived header is available. The same analysis shows that transfer code walks a partition chain and advances a per-sector cursor. These details come from reverse engineering of a specific firmware image; use them as implementation evidence, and keep any revision-specific observations labeled as such.
This separation matters under load. When software does not drain a partition quickly enough, the buffer pool can fill. A correct model must represent free-buffer count, ownership, and the controller’s full or wait state instead of dropping old sector payloads in a host queue. When data is consumed or deleted, the sector buffer can be returned to the free pool. The exact pause/resume policy should be checked against the target firmware and command behavior, not guessed from a modern asynchronous file API.
Partition selection and transfer format are separate too. The host can request a payload representation that omits sync bytes or includes header and subheader material; transfer-length commands and sector-size settings determine what data bytes are exposed. The reverse-engineered firmware notes identify several observed transfer sizes (including data-only and header-containing forms). An emulator should implement the documented modes it supports and reject or flag unsupported combinations rather than pretending every “sector” is always 2048 bytes.
Host data movement has its own lifecycle
The CPU or DMA engine reads a data-transfer register after the controller has prepared a transfer. This is a producer-consumer boundary: the CD Block can have sectors routed into a partition while the host transfer cursor has not moved. A transfer request identifies a source partition, position, and count; reads advance through sector data and may cross a sector boundary. An end-transfer command closes the active transaction and returns ownership to the controller’s next operation.
Mednafen and MAME both model commands and controller status separately from payload bytes. Mednafen names status and interrupt conditions such as command acceptance, transfer-ready, sector completion, and buffer-full. MAME’s implementation likewise distinguishes a status response from data-transfer state. Do not reduce those to one ready boolean. Software can wait for a specific event, acknowledge it, begin reading, and explicitly end transfer. If an emulator emits correct payload but the HIRQ or status ordering is wrong, games may deadlock before consuming it.
SCU DMA is another consumer path, not the CD selector itself. When DMA is used, the source is still a data-transfer interface and the SCU’s transfer count, source address, and completion interrupt have their own semantics. Keep CD Block buffer availability, transfer-port readiness, and SCU DMA completion separately timestamped. This is especially important when software programs a transfer count that ends in the middle of a sector or issues end-transfer near a DMA completion event.
A bounded FAD and subheader fixture
This fixture validates only two small pure functions: conversion from nonnegative LBA to FAD and masked byte equality. It does not model disc lead-in, CD sector parsing, filter modes, graph connections, buffer queues, controller status, or DMA. The interface layer should test these helpers against command-level traces before wiring them into the asynchronous device.
FAD_LEAD_IN_FRAMES = 150
def lba_to_fad(lba):
if lba < 0:
raise ValueError("LBA must not be negative")
return lba + FAD_LEAD_IN_FRAMES
def masked_byte_match(actual, expected, mask):
for value in (actual, expected, mask):
if not 0 <= value <= 0xFF:
raise ValueError("subheader fields are bytes")
return ((actual ^ expected) & mask) == 0
assert lba_to_fad(0) == 150
assert masked_byte_match(0b10110110, 0b10100100, 0b11100100)
assert not masked_byte_match(0b10110110, 0b00100100, 0b11100100)
The fixture intentionally leaves sector mode and byte offsets to the parser. For a real test, capture one Mode 2 sector, decode its file/channel/submode/coding fields, run the configured filter graph, and compare the selected partition and transfer payload to a reference emulator or hardware trace. Record disc image hash, region, command words, FAD, subheader, filter state, connection path, partition count, transfer cursor, and HIRQ transitions.
A forensic validation matrix
Begin with one filter and a short known sector range. Program the range, subheader masks, mode, and destination connection; read every register back. Feed one matching sector, one sector with a difference in an unmasked bit, one with a difference in a masked bit, and sectors just before and after the FAD interval. Check not only which payload appears but also which status bit changes and whether the data-ready event occurs.
Then test graph behavior. Create a true and a false branch that route to distinct partitions, and include a chain with more than one filter. Verify short-circuit behavior only if the device contract shows it. Test a rejected destination, a reset filter, a disconnected branch, and a full partition. At every step preserve the command result and selector-completion event independently.
Finally test the drain path. Request a sector with each supported transfer length, stop partway through a sector, continue across the next sector, issue end transfer, and test both CPU reads and configured DMA. Fill the partition until backpressure appears, then free data and verify resume. Save state with a partially read sector and with one sector in flight. A restore must reproduce the same next byte, buffer ownership, interrupt status, and command availability.
Acceptance criteria
A dependable Saturn CD Block model keeps FAD conversion, sector parsing, filter state, graph routing, partition ownership, transfer-length selection, host reads, SCU DMA, and interrupt/status state independently testable. It logs enough state to explain why a sector was routed, omitted, or stalled. It uses command traces and source implementations to verify observable behavior, and labels reverse-engineered firmware internals separately from public command-interface guarantees.
Related:
- Sega Saturn SCU DMA: Transfer Levels, Indirect Tables, and Bus Boundaries
- PlayStation 2 SIF: EE-to-IOP DMA, RPC Packets, and Cache Ownership
Sources: